diff --git a/.github/actions/copy-cloud-docs-for-tfe/README.md b/.github/actions/copy-cloud-docs-for-tfe/README.md new file mode 100644 index 0000000000..0f7e343d86 --- /dev/null +++ b/.github/actions/copy-cloud-docs-for-tfe/README.md @@ -0,0 +1,59 @@ +# `copy-cloud-docs-for-tfe` + +This composite action is consumed by the `copy-docs.yml` workflow, which is triggered +at the time of the Terraform Enterprise team's **APP_DEADLINE** event. + +Roughly, it behaves as depicted in this diagram: + +```mermaid +graph LR + A[terraform-docs-common] -->|copy `/cloud-docs/*` contents
alongside `/enterprise/*` contents| B[ptfe-releases] +``` + +## Overview + +This action looks for a few things in authored `mdx`, to determine +if sections or entire pages should be ignored from this copy process. + +### Frontmatter + +Adding a `tfc_only: true` line in markdown frontmatter signals to +the action that the associated `.mdx` file should not be handled in the copy process. + +#### Example + +```markdown +--- +page_title: Assessments - API Docs - Terraform Cloud +tfc_only: true +description: >- + Assessment results contain information about continuous validation in + Terraform Cloud, like drift detection. +--- +``` + +### HTML Comments + +Specially formatted HTML comments can be used in matching pairs +to omit multiple **lines** of text from the copy process. + +> **Warning**: This only works with MDX v1. + +#### Example + +```markdown +Some content available in both TFC & TFE... + + +## Some section + +This will only be visible in TFC + + + +More content available in both TFC & TFE... +``` + +> **Note**: More details are available in this [TFC/TFE Content exclusion][rfc] RFC. + +[rfc]: https://docs.google.com/document/d/1DPJU6_7AdGIJVlwJUWBlRqREmYon2IgYf_DrtKjhkcE/edit diff --git a/.github/actions/copy-cloud-docs-for-tfe/__tests__/__fixtures__/source-template/website/data/cloud-docs-nav-data.json b/.github/actions/copy-cloud-docs-for-tfe/__tests__/__fixtures__/source-template/website/data/cloud-docs-nav-data.json new file mode 100644 index 0000000000..0967ef424b --- /dev/null +++ b/.github/actions/copy-cloud-docs-for-tfe/__tests__/__fixtures__/source-template/website/data/cloud-docs-nav-data.json @@ -0,0 +1 @@ +{} diff --git a/.github/actions/copy-cloud-docs-for-tfe/__tests__/__fixtures__/source-template/website/docs/cloud-docs/api-docs/index.mdx b/.github/actions/copy-cloud-docs-for-tfe/__tests__/__fixtures__/source-template/website/docs/cloud-docs/api-docs/index.mdx new file mode 100644 index 0000000000..e626e3f496 --- /dev/null +++ b/.github/actions/copy-cloud-docs-for-tfe/__tests__/__fixtures__/source-template/website/docs/cloud-docs/api-docs/index.mdx @@ -0,0 +1,13 @@ +--- +page_title: API Docs - Terraform Cloud +description: >- + Use the API to manage runs, workspaces, policies, and more. This introduction + includes authentication, features, and formatting. +--- + +[link to transform]: /terraform/cloud-docs/api-docs +[agents link do not transform]: /cloud-docs/agents +[agents link2 do not transform]: /cloud-docs/agents/nested-path +[agents link3 do not transform]: /terraform/cloud-docs/agents +[json api document]: /cloud-docs/api-docs#json-api-documents +[json api error object]: https://jsonapi.org/format/#error-objects diff --git a/.github/actions/copy-cloud-docs-for-tfe/__tests__/__fixtures__/source-template/website/docs/cloud-docs/api-docs/tfc-only-doc.mdx b/.github/actions/copy-cloud-docs-for-tfe/__tests__/__fixtures__/source-template/website/docs/cloud-docs/api-docs/tfc-only-doc.mdx new file mode 100644 index 0000000000..aadfa2f002 --- /dev/null +++ b/.github/actions/copy-cloud-docs-for-tfe/__tests__/__fixtures__/source-template/website/docs/cloud-docs/api-docs/tfc-only-doc.mdx @@ -0,0 +1,5 @@ +--- +page_title: This is a TFC-only file +description: +tfc_only: true +--- diff --git a/.github/actions/copy-cloud-docs-for-tfe/__tests__/__fixtures__/source-template/website/docs/cloud-docs/index.mdx b/.github/actions/copy-cloud-docs-for-tfe/__tests__/__fixtures__/source-template/website/docs/cloud-docs/index.mdx new file mode 100644 index 0000000000..2647cc99bf --- /dev/null +++ b/.github/actions/copy-cloud-docs-for-tfe/__tests__/__fixtures__/source-template/website/docs/cloud-docs/index.mdx @@ -0,0 +1,16 @@ +--- +page_title: Test Docs +description: This is a test document +--- + +The associated file for this image should be copied + +![An Image](/img/docs/image.png) + +This link definition should be updated to /enterprise + +[json api document]: /cloud-docs/api-docs#json-api-documents + +[transform me](/cloud-docs/api-docs/users) + +Foobar diff --git a/.github/actions/copy-cloud-docs-for-tfe/__tests__/__fixtures__/source-template/website/docs/cloud-docs/registry/index.mdx b/.github/actions/copy-cloud-docs-for-tfe/__tests__/__fixtures__/source-template/website/docs/cloud-docs/registry/index.mdx new file mode 100644 index 0000000000..c60fe7f7af --- /dev/null +++ b/.github/actions/copy-cloud-docs-for-tfe/__tests__/__fixtures__/source-template/website/docs/cloud-docs/registry/index.mdx @@ -0,0 +1,13 @@ +--- +page_title: Private Registry - Terraform Cloud +description: >- + Use the Terraform Cloud private registry to share Terraform providers and + modules across your organization. +--- + +# Private Registry + +-> **Note:** I am an info alert +=> **Note:** I am a success alert +~> **Note:** I am a warning alert +!> **Note:** I am a warning alert diff --git a/.github/actions/copy-cloud-docs-for-tfe/__tests__/__fixtures__/source-template/website/docs/cloud-docs/tfc_only.mdx b/.github/actions/copy-cloud-docs-for-tfe/__tests__/__fixtures__/source-template/website/docs/cloud-docs/tfc_only.mdx new file mode 100644 index 0000000000..7b2fce85e5 --- /dev/null +++ b/.github/actions/copy-cloud-docs-for-tfe/__tests__/__fixtures__/source-template/website/docs/cloud-docs/tfc_only.mdx @@ -0,0 +1,12 @@ +--- +tfc_only: true +--- + +This simulates a page that + +- was previously copied over to TFE + (It will have an entry in `last-cloud-docs-sync.txt`) +- had `tfc_only: true` added **afterwards** + +The expected behavior for this file is that +it will get pruned on the next copy-docs process. diff --git a/.github/actions/copy-cloud-docs-for-tfe/__tests__/__fixtures__/source-template/website/img/docs/image.png b/.github/actions/copy-cloud-docs-for-tfe/__tests__/__fixtures__/source-template/website/img/docs/image.png new file mode 100644 index 0000000000..6c78a11e42 Binary files /dev/null and b/.github/actions/copy-cloud-docs-for-tfe/__tests__/__fixtures__/source-template/website/img/docs/image.png differ diff --git a/.github/actions/copy-cloud-docs-for-tfe/__tests__/__fixtures__/target-template/website/data/enterprise-nav-data.json b/.github/actions/copy-cloud-docs-for-tfe/__tests__/__fixtures__/target-template/website/data/enterprise-nav-data.json new file mode 100644 index 0000000000..0967ef424b --- /dev/null +++ b/.github/actions/copy-cloud-docs-for-tfe/__tests__/__fixtures__/target-template/website/data/enterprise-nav-data.json @@ -0,0 +1 @@ +{} diff --git a/.github/actions/copy-cloud-docs-for-tfe/__tests__/__fixtures__/target-template/website/docs/enterprise/do-not-remove.mdx b/.github/actions/copy-cloud-docs-for-tfe/__tests__/__fixtures__/target-template/website/docs/enterprise/do-not-remove.mdx new file mode 100644 index 0000000000..2862cdede1 --- /dev/null +++ b/.github/actions/copy-cloud-docs-for-tfe/__tests__/__fixtures__/target-template/website/docs/enterprise/do-not-remove.mdx @@ -0,0 +1,7 @@ +--- +page_title: Test Docs +description: This is a test document +--- + +This is previously existing doc in ptfe-releases. +Ensure this does not get removed diff --git a/.github/actions/copy-cloud-docs-for-tfe/__tests__/__fixtures__/target-template/website/docs/enterprise/index.mdx b/.github/actions/copy-cloud-docs-for-tfe/__tests__/__fixtures__/target-template/website/docs/enterprise/index.mdx new file mode 100644 index 0000000000..7933429452 --- /dev/null +++ b/.github/actions/copy-cloud-docs-for-tfe/__tests__/__fixtures__/target-template/website/docs/enterprise/index.mdx @@ -0,0 +1,10 @@ +--- +page_title: Index +description: Index +--- + +This filename is shared between `cloud-docs/index.mdx` and `enterprise/index.mdx`. + +This is a special case where we want to ignore the `cloud-docs` file. + +See `IGNORE_LIST` for more details. diff --git a/.github/actions/copy-cloud-docs-for-tfe/__tests__/__fixtures__/target-template/website/docs/enterprise/stale-cloud-docs/index.mdx b/.github/actions/copy-cloud-docs-for-tfe/__tests__/__fixtures__/target-template/website/docs/enterprise/stale-cloud-docs/index.mdx new file mode 100644 index 0000000000..3121149318 --- /dev/null +++ b/.github/actions/copy-cloud-docs-for-tfe/__tests__/__fixtures__/target-template/website/docs/enterprise/stale-cloud-docs/index.mdx @@ -0,0 +1,8 @@ +--- +page_title: Stale Page +description: This is a stale cloud-docs document +--- + +This represents a stale page, once copied from +terraform-docs-common, and now intended to be +pruned. diff --git a/.github/actions/copy-cloud-docs-for-tfe/__tests__/__fixtures__/target-template/website/docs/enterprise/tfc_only.mdx b/.github/actions/copy-cloud-docs-for-tfe/__tests__/__fixtures__/target-template/website/docs/enterprise/tfc_only.mdx new file mode 100644 index 0000000000..e69de29bb2 diff --git a/.github/actions/copy-cloud-docs-for-tfe/__tests__/__fixtures__/target-template/website/img/docs/.gitkeep b/.github/actions/copy-cloud-docs-for-tfe/__tests__/__fixtures__/target-template/website/img/docs/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/.github/actions/copy-cloud-docs-for-tfe/__tests__/__fixtures__/target-template/website/img/docs/_favicon.ico b/.github/actions/copy-cloud-docs-for-tfe/__tests__/__fixtures__/target-template/website/img/docs/_favicon.ico new file mode 100644 index 0000000000..eced811f5c Binary files /dev/null and b/.github/actions/copy-cloud-docs-for-tfe/__tests__/__fixtures__/target-template/website/img/docs/_favicon.ico differ diff --git a/.github/actions/copy-cloud-docs-for-tfe/__tests__/__fixtures__/target-template/website/last-cloud-docs-sync.txt b/.github/actions/copy-cloud-docs-for-tfe/__tests__/__fixtures__/target-template/website/last-cloud-docs-sync.txt new file mode 100644 index 0000000000..49dbf4422b --- /dev/null +++ b/.github/actions/copy-cloud-docs-for-tfe/__tests__/__fixtures__/target-template/website/last-cloud-docs-sync.txt @@ -0,0 +1,3 @@ +website/docs/enterprise/stale-cloud-docs/index.mdx +website/img/docs/_favicon.ico +website/docs/enterprise/tfc_only.mdx \ No newline at end of file diff --git a/.github/actions/copy-cloud-docs-for-tfe/__tests__/main.test.ts b/.github/actions/copy-cloud-docs-for-tfe/__tests__/main.test.ts new file mode 100644 index 0000000000..bdfdc8245e --- /dev/null +++ b/.github/actions/copy-cloud-docs-for-tfe/__tests__/main.test.ts @@ -0,0 +1,309 @@ +import * as path from 'path' +import * as fs from 'fs' +import fse from 'fs-extra' +import tree from 'tree-node-cli' + +import { main } from '../main' + +describe('copy-cloud-docs-for-tfe', () => { + const fixtureDir = './__fixtures__' + + const sourceTemplatePath = path.join(__dirname, fixtureDir, 'source-template') + const targetTemplatePath = path.join(__dirname, fixtureDir, 'target-template') + + const sourcePath = path.join(__dirname, fixtureDir, 'terraform-docs-common') + const targetPath = path.join(__dirname, fixtureDir, 'ptfe-releases') + + beforeEach(() => { + // simulate cloning of our two repos + fse.copySync(sourceTemplatePath, sourcePath) + fse.copySync(targetTemplatePath, targetPath) + }) + + afterEach(() => { + // clean up cloned files + fs.rmSync(path.join(sourcePath), { recursive: true }) + fs.rmSync(path.join(targetPath), { recursive: true }) + }) + + it('copies /cloud-docs to /enterprise', async () => { + // static file paths + const pathToTargetImgDir = path.join(targetPath, 'website/img/docs') + const pathToTargetDocsDir = path.join(targetPath, 'website/docs/enterprise') + const pathToTargetLogFile = path.join( + targetPath, + 'website/last-cloud-docs-sync.txt' + ) + + // before + expect(tree(pathToTargetImgDir)).toMatchInlineSnapshot(` + "docs + └── _favicon.ico" + `) + + expect(tree(pathToTargetDocsDir)).toMatchInlineSnapshot(` + "enterprise + ├── do-not-remove.mdx + ├── index.mdx + ├── stale-cloud-docs + │ └── index.mdx + └── tfc_only.mdx" + `) + + expect(fs.readFileSync(pathToTargetLogFile, 'utf-8')) + .toMatchInlineSnapshot(` + "website/docs/enterprise/stale-cloud-docs/index.mdx + website/img/docs/_favicon.ico + website/docs/enterprise/tfc_only.mdx" + `) + + // run our action + await main(sourcePath, targetPath) + + // after + expect(tree(pathToTargetImgDir)).toMatchInlineSnapshot(`"docs"`) + expect(tree(pathToTargetDocsDir)).toMatchInlineSnapshot(` + "enterprise + ├── api-docs + │ └── index.mdx + ├── do-not-remove.mdx + ├── index.mdx + └── registry + └── index.mdx" + `) + expect(fs.readFileSync(pathToTargetLogFile, 'utf-8')) + .toMatchInlineSnapshot(` + "website/docs/enterprise/api-docs/index.mdx + website/docs/enterprise/registry/index.mdx + " + `) + }) + + it('removes previously copied files that have since received `tfc_only: true`', async () => { + // static file paths + const pathToTfcOnlyFile = path.join( + targetPath, + 'website/docs/enterprise/tfc_only.mdx' + ) + const pathToTargetLogFile = path.join( + targetPath, + 'website/last-cloud-docs-sync.txt' + ) + // before + expect(fs.existsSync(pathToTfcOnlyFile)).toBe(true) + expect(fs.readFileSync(pathToTargetLogFile, 'utf-8')) + .toMatchInlineSnapshot(` + "website/docs/enterprise/stale-cloud-docs/index.mdx + website/img/docs/_favicon.ico + website/docs/enterprise/tfc_only.mdx" + `) + + // run our action + await main(sourcePath, targetPath) + + // after + expect(fs.existsSync(pathToTfcOnlyFile)).toBe(false) + expect(fs.readFileSync(pathToTargetLogFile, 'utf-8')) + .toMatchInlineSnapshot(` + "website/docs/enterprise/api-docs/index.mdx + website/docs/enterprise/registry/index.mdx + " + `) + }) + + it('does not remove previously existing docs', async () => { + // static file paths + const pathToFile = path.join( + targetPath, + 'website/docs/enterprise/index.mdx' + ) + + // before + + // run our action + await main(sourcePath, targetPath) + + // after + expect(fs.existsSync(pathToFile)).toBe(true) + }) + + it('prunes stale cloud-docs', async () => { + // static file paths + const pathToStaleFile = path.join( + targetPath, + 'website/docs/enterprise/stale-cloud-docs/index.mdx' + ) + const pathToTargetDocsDir = path.join(targetPath, 'website/docs/enterprise') + // before + expect(tree(pathToTargetDocsDir)).toMatchInlineSnapshot(` + "enterprise + ├── do-not-remove.mdx + ├── index.mdx + ├── stale-cloud-docs + │ └── index.mdx + └── tfc_only.mdx" + `) + expect(fs.existsSync(pathToStaleFile)).toBe(true) + + // run our action + await main(sourcePath, targetPath) + + // after + expect(tree(pathToTargetDocsDir)).toMatchInlineSnapshot(` + "enterprise + ├── api-docs + │ └── index.mdx + ├── do-not-remove.mdx + ├── index.mdx + └── registry + └── index.mdx" + `) + expect(fs.existsSync(pathToStaleFile)).toBe(false) + }) + + it('updates `last-cloud-docs-sync.txt`', async () => { + const cloudDocsFilesPath = path.join( + targetPath, + 'website/last-cloud-docs-sync.txt' + ) + + // Before + expect(fs.readFileSync(cloudDocsFilesPath).toString()) + .toMatchInlineSnapshot(` + "website/docs/enterprise/stale-cloud-docs/index.mdx + website/img/docs/_favicon.ico + website/docs/enterprise/tfc_only.mdx" + `) + + await main(sourcePath, targetPath) + + // After + expect(fs.readFileSync(cloudDocsFilesPath).toString()) + .toMatchInlineSnapshot(` + "website/docs/enterprise/api-docs/index.mdx + website/docs/enterprise/registry/index.mdx + " + `) + }) + + // note: This is an integration test w/ a local remark plugin + it('transforms /cloud-docs links to /enterprise', async () => { + await main(sourcePath, targetPath) + + expect( + String( + fs.readFileSync( + path.join(targetPath, 'website/docs/enterprise/api-docs/index.mdx') + ) + ) + ).toMatchInlineSnapshot(` + "--- + page_title: API Docs - Terraform Enterprise + description: >- + Use the API to manage runs, workspaces, policies, and more. This introduction + includes authentication, features, and formatting. + source: terraform-docs-common + --- + + [link to transform]: /terraform/enterprise/api-docs + + [agents link do not transform]: /cloud-docs/agents + + [agents link2 do not transform]: /cloud-docs/agents/nested-path + + [agents link3 do not transform]: /terraform/cloud-docs/agents + + [json api document]: /enterprise/api-docs#json-api-documents + + [json api error object]: https://jsonapi.org/format/#error-objects + " + `) + }) + + // note: This is dependent on the hardcoded `IGNORE_LIST` in `copy-cloud-docs-for-tfe` + it('should ignore files in the IGNORE_LIST', async () => { + const beforeFileContents = fs.readFileSync( + path.join(targetPath, 'website/docs/enterprise/index.mdx'), + 'utf-8' + ) + + await main(sourcePath, targetPath) + + const afterFileContents = fs.readFileSync( + path.join(targetPath, 'website/docs/enterprise/index.mdx'), + 'utf-8' + ) + + // assert that cloud-docs/index.mdx doesn't overwrite enterprise/index.mdx + expect(beforeFileContents).toEqual(afterFileContents) + + // snapshot for observability + expect(afterFileContents).toMatchInlineSnapshot(` + "--- + page_title: Index + description: Index + --- + + This filename is shared between \`cloud-docs/index.mdx\` and \`enterprise/index.mdx\`. + + This is a special case where we want to ignore the \`cloud-docs\` file. + + See \`IGNORE_LIST\` for more details. + " + `) + }) + + it('should transform "Cloud" to "Enterprise" in frontmatter', async () => { + await main(sourcePath, targetPath) + + const afterFileContents = fs.readFileSync( + path.join(targetPath, 'website/docs/enterprise/api-docs/index.mdx'), + 'utf-8' + ) + + // Assert that the new file contains `Terraform Enterprise` in the frontmatter + expect(afterFileContents).toEqual( + expect.stringContaining(`--- +page_title: API Docs - Terraform Enterprise`) + ) + }) + + it('copies over alerts correctly', async () => { + await main(sourcePath, targetPath) + + const contentsWithAlert = fs + .readFileSync( + path.join(targetPath, 'website/docs/enterprise/registry/index.mdx') + ) + .toString() + expect(contentsWithAlert).toMatchInlineSnapshot(` + "--- + page_title: Private Registry - Terraform Enterprise + description: >- + Use the Terraform Enterprise private registry to share Terraform providers and + modules across your organization. + source: terraform-docs-common + --- + + # Private Registry + + -> **Note:** I am an info alert + => **Note:** I am a success alert + ~> **Note:** I am a warning alert + !> **Note:** I am a warning alert + " + `) + }) + + it('should not copy over cloud-docs-nav-data.json', async () => { + // run our action + await main(sourcePath, targetPath) + + const enterpriseDataDir = path.join(targetPath, 'website/data') + // after + expect(tree(enterpriseDataDir)).toMatchInlineSnapshot(` + "data + └── enterprise-nav-data.json" + `) + }) +}) diff --git a/.github/actions/copy-cloud-docs-for-tfe/action.yml b/.github/actions/copy-cloud-docs-for-tfe/action.yml new file mode 100644 index 0000000000..0342ae9e54 --- /dev/null +++ b/.github/actions/copy-cloud-docs-for-tfe/action.yml @@ -0,0 +1,9 @@ +name: Copy cloud-docs for TFE +description: Copy docs/nav-data/assets from source dir to target dir +runs: + using: 'node20' + main: 'out/index.js' +inputs: + new_TFE_version: + required: true + description: 'The new TFE version to create and copy docs into.' diff --git a/.github/actions/copy-cloud-docs-for-tfe/index.ts b/.github/actions/copy-cloud-docs-for-tfe/index.ts new file mode 100644 index 0000000000..3ce65be989 --- /dev/null +++ b/.github/actions/copy-cloud-docs-for-tfe/index.ts @@ -0,0 +1,12 @@ +import * as core from '@actions/core' + +import { main } from './main' + +async function action() { + const newTFEVersion = core.getInput('new_TFE_version') + // const newTFEVersion = 'v000011-1' + + await main(newTFEVersion) +} + +action() diff --git a/.github/actions/copy-cloud-docs-for-tfe/main.ts b/.github/actions/copy-cloud-docs-for-tfe/main.ts new file mode 100644 index 0000000000..2f8714d149 --- /dev/null +++ b/.github/actions/copy-cloud-docs-for-tfe/main.ts @@ -0,0 +1,256 @@ +// stdlib +import * as fs from 'fs' +import * as path from 'path' + +// fs traversal +import walk from 'klaw-sync' + +// initial processing +import matter from 'gray-matter' + +// mdx processing +import remark from 'remark' + +import remarkMdx from 'remark-mdx' + +// plugins +import { remarkGetImages } from './remark-get-images-plugin' +import { remarkTransformCloudDocsLinks } from './remark-transfrom-cloud-docs-links' + +const imageSrcSet = new Set() + +// List of MDX files to exclude from being copied +const IGNORE_LIST = ['cloud-docs/index.mdx'] + +export const IGNORE_PATTERNS: RegExp[] = [/cloud-docs\/agents/i] +const SUB_PATH_MAPPINGS: { + source: string + target: string +}[] = [ + { + source: 'cloud-docs', + target: 'enterprise', + }, +] + +/** + * This is a helper to be passed to `walk` dry up repeated logic + * for ignore certain files. + */ +const filterFunc = (item: walk.Item) => { + // if the item matches a IGNORE_PATTERNS expression, exclude it + if ( + IGNORE_PATTERNS.some((pattern: RegExp) => { + return pattern.test(item.path) + }) + ) { + return false + } + + // Check files for `tfc_only` frontmatter property; Ignore them if true + if (item.stats.isFile()) { + const fullContent = fs.readFileSync(item.path, 'utf8') + const { data } = matter(fullContent) + if (data.tfc_only == true) { + return false + } + } + + return true +} + +/** + * A helper that accepts a data object and an array of functions that + * receive the object as an arg and transform it. + */ +const transformObject = >( + data: T, + plugins: Array<(data: T) => T>, +): T => { + let result = data + + plugins.forEach((fn: (data: T) => T) => { + result = fn(result) + }) + + return result +} + +/** + * This function will copy 3 things + * - MDX files + * - these can be at varying levels of nesting + * - used images + * - these are expected to all be at the same level + * - nav-data JSON files + * + * This function will also prune the target directory + * of any files that are not in the source directory. + * + * @param newTFEVersion An absolute path to a GitHub repository on disk + */ +export async function main( + // sourceDir: string, + // targetDir: string, + newTFEVersion: string, +): Promise { + const newTFEVersionDir = path.join('./content/ptfe-releases', newTFEVersion) + + // Create a new folder for the new TFE version + // if (fs.existsSync(targetDir)) { + // throw new Error(`Directory already exists: ${targetDir}`) + // } + // fs.mkdirSync(targetDir, { recursive: true }) + + const HCPsourceDir = './testing/content/terraform-docs-common' + // const HCPsourceDir = './content/terraform-docs-common' + + const HCPContentDir = path.join(HCPsourceDir, 'docs') + + const newTFEVersionContentDir = path.join(newTFEVersionDir, 'docs') + const newTFEVersionImageDir = path.join(newTFEVersionDir, 'img/docs') + + // Read version metadata and get the latest version of ptfe-releases + // const versionMetadataPath = path.resolve('app/api/versionMetadata.json') + // const versionMetadata = JSON.parse(fs.readFileSync(versionMetadataPath, 'utf8')) + + // const ptfeReleases = versionMetadata['ptfe-releases'] + // if (!ptfeReleases || ptfeReleases.length === 0) { + // throw new Error('No ptfe-releases found in versionMetadata.json') + // } + + // const latestPtfeRelease = ptfeReleases[0] + + // Actually don't think I need this + + // traverse source docs and accumulate mdx files for a given set of "subPaths" + let items: ReadonlyArray = [] + + for (const { source: subPath } of SUB_PATH_MAPPINGS) { + const src = path.join(HCPContentDir, subPath) + const docItems = walk(src, { + nodir: true, + filter: filterFunc, + }) + items = items.concat(docItems) + } + + // process each mdx file + for (const item of items) { + // ignore some files + if ( + IGNORE_LIST.some((ignore: string) => { + return item.path.endsWith(ignore) + }) + ) { + continue + } + + // extract mdx content; ignore frontmatter + const fullContent = fs.readFileSync(item.path, 'utf8') + + // eslint-disable-next-line prefer-const + let { content, data } = matter(fullContent) + + data = transformObject(data, [ + // inject `source` frontmatter property + function injectSource(d: { [key: string]: any }) { + d.source = path.basename(HCPsourceDir) + return d + }, + // replace cloud instances with enterprise + function replaceCloudWithEnterprise(d: { [key: string]: any }) { + // Some docs do not have all frontmatter properties. Make sure + // we do not assign `undefined` (which is invalid) in YAML + if (d.page_title) { + d.page_title = d.page_title.replace( + 'Terraform Cloud', + 'Terraform Enterprise', + ) + d.page_title = d.page_title.replace( + 'HCP Terraform', + 'Terraform Enterprise', + ) + } + + if (d.description) { + d.description = d.description.replace( + 'Terraform Cloud', + 'Terraform Enterprise', + ) + d.description = d.description.replace( + 'HCP Terraform', + 'Terraform Enterprise', + ) + } + return d + }, + ]) + + const vfile = await remark() + .use(remarkMdx) + // @ts-expect-error remark is being passed in through the pipeline + .use(remarkGetImages, HCPsourceDir, imageSrcSet) + // @ts-expect-error remark is being passed in through the pipeline + .use(remarkTransformCloudDocsLinks) + .process(content) + + // replace \-> with -> + const stringOutput = vfile.toString().replaceAll('\\->', '->') + + // overwrite original file with transformed content + const contents = matter.stringify('\n' + stringOutput, data) + fs.writeFileSync(item.path, contents) + } + + // keep track of the files that were copied in the target repo + const copiedTargetRepoRelativePaths: string[] = [] + + // Copy an entire directory + // --------------------------------------------- + // /{source}/cloud-docs/dir/some-doc.mdx + // ↓ ↓ ↓ ↓ + // /{target}/enterprise/dir/some-docs.mdx + // --------------------------------------------- + for (const { source, target } of SUB_PATH_MAPPINGS) { + const src = path.join(HCPContentDir, source) + const dest = path.join(newTFEVersionContentDir, target) + + const items = walk(src, { + nodir: true, + filter: filterFunc, + }) + + for (const item of items) { + // ignore some files + if ( + IGNORE_LIST.some((ignore: string) => { + return item.path.endsWith(ignore) + }) + ) { + continue + } + + const destAbsolutePath = item.path.replace(src, dest) + fs.mkdirSync(path.dirname(destAbsolutePath), { recursive: true }) + fs.copyFileSync(item.path, destAbsolutePath) + + // accumulate copied files + const relativePath = path.relative(newTFEVersionDir, destAbsolutePath) + copiedTargetRepoRelativePaths.push(relativePath) + } + } + + // Copy images + for (const src of Array.from(imageSrcSet)) { + const basename = path.basename(src) + const target = path.join(newTFEVersionImageDir, basename) + + fs.mkdirSync(newTFEVersionImageDir, { recursive: true }) + fs.copyFileSync(src, target) + + // accumulate copied files + const relativePath = path.relative(newTFEVersionDir, target) + copiedTargetRepoRelativePaths.push(relativePath) + } +} diff --git a/.github/actions/copy-cloud-docs-for-tfe/out/file.js b/.github/actions/copy-cloud-docs-for-tfe/out/file.js new file mode 100644 index 0000000000..ed9d86f047 --- /dev/null +++ b/.github/actions/copy-cloud-docs-for-tfe/out/file.js @@ -0,0 +1,12 @@ +'use strict' + +const pino = require('./pino') +const { once } = require('events') + +module.exports = async function (opts = {}) { + const destOpts = Object.assign({}, opts, { dest: opts.destination || 1, sync: false }) + delete destOpts.destination + const destination = pino.destination(destOpts) + await once(destination, 'ready') + return destination +} diff --git a/.github/actions/copy-cloud-docs-for-tfe/out/index.js b/.github/actions/copy-cloud-docs-for-tfe/out/index.js new file mode 100644 index 0000000000..52ec76cc30 --- /dev/null +++ b/.github/actions/copy-cloud-docs-for-tfe/out/index.js @@ -0,0 +1,114465 @@ +/******/ (() => { // webpackBootstrap +/******/ var __webpack_modules__ = ({ + +/***/ 18541: +/***/ ((module) => { + +function webpackEmptyAsyncContext(req) { + // Here Promise.resolve().then() is used instead of new Promise() to prevent + // uncaught exception popping up in devtools + return Promise.resolve().then(() => { + var e = new Error("Cannot find module '" + req + "'"); + e.code = 'MODULE_NOT_FOUND'; + throw e; + }); +} +webpackEmptyAsyncContext.keys = () => ([]); +webpackEmptyAsyncContext.resolve = webpackEmptyAsyncContext; +webpackEmptyAsyncContext.id = 18541; +module.exports = webpackEmptyAsyncContext; + +/***/ }), + +/***/ 44914: +/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) { + +"use strict"; + +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + var desc = Object.getOwnPropertyDescriptor(m, k); + if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { + desc = { enumerable: true, get: function() { return m[k]; } }; + } + Object.defineProperty(o, k2, desc); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.issue = exports.issueCommand = void 0; +const os = __importStar(__nccwpck_require__(70857)); +const utils_1 = __nccwpck_require__(30302); +/** + * Commands + * + * Command Format: + * ::name key=value,key=value::message + * + * Examples: + * ::warning::This is the message + * ::set-env name=MY_VAR::some value + */ +function issueCommand(command, properties, message) { + const cmd = new Command(command, properties, message); + process.stdout.write(cmd.toString() + os.EOL); +} +exports.issueCommand = issueCommand; +function issue(name, message = '') { + issueCommand(name, {}, message); +} +exports.issue = issue; +const CMD_STRING = '::'; +class Command { + constructor(command, properties, message) { + if (!command) { + command = 'missing.command'; + } + this.command = command; + this.properties = properties; + this.message = message; + } + toString() { + let cmdStr = CMD_STRING + this.command; + if (this.properties && Object.keys(this.properties).length > 0) { + cmdStr += ' '; + let first = true; + for (const key in this.properties) { + if (this.properties.hasOwnProperty(key)) { + const val = this.properties[key]; + if (val) { + if (first) { + first = false; + } + else { + cmdStr += ','; + } + cmdStr += `${key}=${escapeProperty(val)}`; + } + } + } + } + cmdStr += `${CMD_STRING}${escapeData(this.message)}`; + return cmdStr; + } +} +function escapeData(s) { + return (0, utils_1.toCommandValue)(s) + .replace(/%/g, '%25') + .replace(/\r/g, '%0D') + .replace(/\n/g, '%0A'); +} +function escapeProperty(s) { + return (0, utils_1.toCommandValue)(s) + .replace(/%/g, '%25') + .replace(/\r/g, '%0D') + .replace(/\n/g, '%0A') + .replace(/:/g, '%3A') + .replace(/,/g, '%2C'); +} +//# sourceMappingURL=command.js.map + +/***/ }), + +/***/ 37484: +/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) { + +"use strict"; + +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + var desc = Object.getOwnPropertyDescriptor(m, k); + if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { + desc = { enumerable: true, get: function() { return m[k]; } }; + } + Object.defineProperty(o, k2, desc); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.platform = exports.toPlatformPath = exports.toWin32Path = exports.toPosixPath = exports.markdownSummary = exports.summary = exports.getIDToken = exports.getState = exports.saveState = exports.group = exports.endGroup = exports.startGroup = exports.info = exports.notice = exports.warning = exports.error = exports.debug = exports.isDebug = exports.setFailed = exports.setCommandEcho = exports.setOutput = exports.getBooleanInput = exports.getMultilineInput = exports.getInput = exports.addPath = exports.setSecret = exports.exportVariable = exports.ExitCode = void 0; +const command_1 = __nccwpck_require__(44914); +const file_command_1 = __nccwpck_require__(24753); +const utils_1 = __nccwpck_require__(30302); +const os = __importStar(__nccwpck_require__(70857)); +const path = __importStar(__nccwpck_require__(16928)); +const oidc_utils_1 = __nccwpck_require__(35306); +/** + * The code to exit an action + */ +var ExitCode; +(function (ExitCode) { + /** + * A code indicating that the action was successful + */ + ExitCode[ExitCode["Success"] = 0] = "Success"; + /** + * A code indicating that the action was a failure + */ + ExitCode[ExitCode["Failure"] = 1] = "Failure"; +})(ExitCode || (exports.ExitCode = ExitCode = {})); +//----------------------------------------------------------------------- +// Variables +//----------------------------------------------------------------------- +/** + * Sets env variable for this action and future actions in the job + * @param name the name of the variable to set + * @param val the value of the variable. Non-string values will be converted to a string via JSON.stringify + */ +// eslint-disable-next-line @typescript-eslint/no-explicit-any +function exportVariable(name, val) { + const convertedVal = (0, utils_1.toCommandValue)(val); + process.env[name] = convertedVal; + const filePath = process.env['GITHUB_ENV'] || ''; + if (filePath) { + return (0, file_command_1.issueFileCommand)('ENV', (0, file_command_1.prepareKeyValueMessage)(name, val)); + } + (0, command_1.issueCommand)('set-env', { name }, convertedVal); +} +exports.exportVariable = exportVariable; +/** + * Registers a secret which will get masked from logs + * @param secret value of the secret + */ +function setSecret(secret) { + (0, command_1.issueCommand)('add-mask', {}, secret); +} +exports.setSecret = setSecret; +/** + * Prepends inputPath to the PATH (for this action and future actions) + * @param inputPath + */ +function addPath(inputPath) { + const filePath = process.env['GITHUB_PATH'] || ''; + if (filePath) { + (0, file_command_1.issueFileCommand)('PATH', inputPath); + } + else { + (0, command_1.issueCommand)('add-path', {}, inputPath); + } + process.env['PATH'] = `${inputPath}${path.delimiter}${process.env['PATH']}`; +} +exports.addPath = addPath; +/** + * Gets the value of an input. + * Unless trimWhitespace is set to false in InputOptions, the value is also trimmed. + * Returns an empty string if the value is not defined. + * + * @param name name of the input to get + * @param options optional. See InputOptions. + * @returns string + */ +function getInput(name, options) { + const val = process.env[`INPUT_${name.replace(/ /g, '_').toUpperCase()}`] || ''; + if (options && options.required && !val) { + throw new Error(`Input required and not supplied: ${name}`); + } + if (options && options.trimWhitespace === false) { + return val; + } + return val.trim(); +} +exports.getInput = getInput; +/** + * Gets the values of an multiline input. Each value is also trimmed. + * + * @param name name of the input to get + * @param options optional. See InputOptions. + * @returns string[] + * + */ +function getMultilineInput(name, options) { + const inputs = getInput(name, options) + .split('\n') + .filter(x => x !== ''); + if (options && options.trimWhitespace === false) { + return inputs; + } + return inputs.map(input => input.trim()); +} +exports.getMultilineInput = getMultilineInput; +/** + * Gets the input value of the boolean type in the YAML 1.2 "core schema" specification. + * Support boolean input list: `true | True | TRUE | false | False | FALSE` . + * The return value is also in boolean type. + * ref: https://yaml.org/spec/1.2/spec.html#id2804923 + * + * @param name name of the input to get + * @param options optional. See InputOptions. + * @returns boolean + */ +function getBooleanInput(name, options) { + const trueValue = ['true', 'True', 'TRUE']; + const falseValue = ['false', 'False', 'FALSE']; + const val = getInput(name, options); + if (trueValue.includes(val)) + return true; + if (falseValue.includes(val)) + return false; + throw new TypeError(`Input does not meet YAML 1.2 "Core Schema" specification: ${name}\n` + + `Support boolean input list: \`true | True | TRUE | false | False | FALSE\``); +} +exports.getBooleanInput = getBooleanInput; +/** + * Sets the value of an output. + * + * @param name name of the output to set + * @param value value to store. Non-string values will be converted to a string via JSON.stringify + */ +// eslint-disable-next-line @typescript-eslint/no-explicit-any +function setOutput(name, value) { + const filePath = process.env['GITHUB_OUTPUT'] || ''; + if (filePath) { + return (0, file_command_1.issueFileCommand)('OUTPUT', (0, file_command_1.prepareKeyValueMessage)(name, value)); + } + process.stdout.write(os.EOL); + (0, command_1.issueCommand)('set-output', { name }, (0, utils_1.toCommandValue)(value)); +} +exports.setOutput = setOutput; +/** + * Enables or disables the echoing of commands into stdout for the rest of the step. + * Echoing is disabled by default if ACTIONS_STEP_DEBUG is not set. + * + */ +function setCommandEcho(enabled) { + (0, command_1.issue)('echo', enabled ? 'on' : 'off'); +} +exports.setCommandEcho = setCommandEcho; +//----------------------------------------------------------------------- +// Results +//----------------------------------------------------------------------- +/** + * Sets the action status to failed. + * When the action exits it will be with an exit code of 1 + * @param message add error issue message + */ +function setFailed(message) { + process.exitCode = ExitCode.Failure; + error(message); +} +exports.setFailed = setFailed; +//----------------------------------------------------------------------- +// Logging Commands +//----------------------------------------------------------------------- +/** + * Gets whether Actions Step Debug is on or not + */ +function isDebug() { + return process.env['RUNNER_DEBUG'] === '1'; +} +exports.isDebug = isDebug; +/** + * Writes debug message to user log + * @param message debug message + */ +function debug(message) { + (0, command_1.issueCommand)('debug', {}, message); +} +exports.debug = debug; +/** + * Adds an error issue + * @param message error issue message. Errors will be converted to string via toString() + * @param properties optional properties to add to the annotation. + */ +function error(message, properties = {}) { + (0, command_1.issueCommand)('error', (0, utils_1.toCommandProperties)(properties), message instanceof Error ? message.toString() : message); +} +exports.error = error; +/** + * Adds a warning issue + * @param message warning issue message. Errors will be converted to string via toString() + * @param properties optional properties to add to the annotation. + */ +function warning(message, properties = {}) { + (0, command_1.issueCommand)('warning', (0, utils_1.toCommandProperties)(properties), message instanceof Error ? message.toString() : message); +} +exports.warning = warning; +/** + * Adds a notice issue + * @param message notice issue message. Errors will be converted to string via toString() + * @param properties optional properties to add to the annotation. + */ +function notice(message, properties = {}) { + (0, command_1.issueCommand)('notice', (0, utils_1.toCommandProperties)(properties), message instanceof Error ? message.toString() : message); +} +exports.notice = notice; +/** + * Writes info to log with console.log. + * @param message info message + */ +function info(message) { + process.stdout.write(message + os.EOL); +} +exports.info = info; +/** + * Begin an output group. + * + * Output until the next `groupEnd` will be foldable in this group + * + * @param name The name of the output group + */ +function startGroup(name) { + (0, command_1.issue)('group', name); +} +exports.startGroup = startGroup; +/** + * End an output group. + */ +function endGroup() { + (0, command_1.issue)('endgroup'); +} +exports.endGroup = endGroup; +/** + * Wrap an asynchronous function call in a group. + * + * Returns the same type as the function itself. + * + * @param name The name of the group + * @param fn The function to wrap in the group + */ +function group(name, fn) { + return __awaiter(this, void 0, void 0, function* () { + startGroup(name); + let result; + try { + result = yield fn(); + } + finally { + endGroup(); + } + return result; + }); +} +exports.group = group; +//----------------------------------------------------------------------- +// Wrapper action state +//----------------------------------------------------------------------- +/** + * Saves state for current action, the state can only be retrieved by this action's post job execution. + * + * @param name name of the state to store + * @param value value to store. Non-string values will be converted to a string via JSON.stringify + */ +// eslint-disable-next-line @typescript-eslint/no-explicit-any +function saveState(name, value) { + const filePath = process.env['GITHUB_STATE'] || ''; + if (filePath) { + return (0, file_command_1.issueFileCommand)('STATE', (0, file_command_1.prepareKeyValueMessage)(name, value)); + } + (0, command_1.issueCommand)('save-state', { name }, (0, utils_1.toCommandValue)(value)); +} +exports.saveState = saveState; +/** + * Gets the value of an state set by this action's main execution. + * + * @param name name of the state to get + * @returns string + */ +function getState(name) { + return process.env[`STATE_${name}`] || ''; +} +exports.getState = getState; +function getIDToken(aud) { + return __awaiter(this, void 0, void 0, function* () { + return yield oidc_utils_1.OidcClient.getIDToken(aud); + }); +} +exports.getIDToken = getIDToken; +/** + * Summary exports + */ +var summary_1 = __nccwpck_require__(71847); +Object.defineProperty(exports, "summary", ({ enumerable: true, get: function () { return summary_1.summary; } })); +/** + * @deprecated use core.summary + */ +var summary_2 = __nccwpck_require__(71847); +Object.defineProperty(exports, "markdownSummary", ({ enumerable: true, get: function () { return summary_2.markdownSummary; } })); +/** + * Path exports + */ +var path_utils_1 = __nccwpck_require__(31976); +Object.defineProperty(exports, "toPosixPath", ({ enumerable: true, get: function () { return path_utils_1.toPosixPath; } })); +Object.defineProperty(exports, "toWin32Path", ({ enumerable: true, get: function () { return path_utils_1.toWin32Path; } })); +Object.defineProperty(exports, "toPlatformPath", ({ enumerable: true, get: function () { return path_utils_1.toPlatformPath; } })); +/** + * Platform utilities exports + */ +exports.platform = __importStar(__nccwpck_require__(18968)); +//# sourceMappingURL=core.js.map + +/***/ }), + +/***/ 24753: +/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) { + +"use strict"; + +// For internal use, subject to change. +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + var desc = Object.getOwnPropertyDescriptor(m, k); + if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { + desc = { enumerable: true, get: function() { return m[k]; } }; + } + Object.defineProperty(o, k2, desc); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.prepareKeyValueMessage = exports.issueFileCommand = void 0; +// We use any as a valid input type +/* eslint-disable @typescript-eslint/no-explicit-any */ +const crypto = __importStar(__nccwpck_require__(76982)); +const fs = __importStar(__nccwpck_require__(79896)); +const os = __importStar(__nccwpck_require__(70857)); +const utils_1 = __nccwpck_require__(30302); +function issueFileCommand(command, message) { + const filePath = process.env[`GITHUB_${command}`]; + if (!filePath) { + throw new Error(`Unable to find environment variable for file command ${command}`); + } + if (!fs.existsSync(filePath)) { + throw new Error(`Missing file at path: ${filePath}`); + } + fs.appendFileSync(filePath, `${(0, utils_1.toCommandValue)(message)}${os.EOL}`, { + encoding: 'utf8' + }); +} +exports.issueFileCommand = issueFileCommand; +function prepareKeyValueMessage(key, value) { + const delimiter = `ghadelimiter_${crypto.randomUUID()}`; + const convertedValue = (0, utils_1.toCommandValue)(value); + // These should realistically never happen, but just in case someone finds a + // way to exploit uuid generation let's not allow keys or values that contain + // the delimiter. + if (key.includes(delimiter)) { + throw new Error(`Unexpected input: name should not contain the delimiter "${delimiter}"`); + } + if (convertedValue.includes(delimiter)) { + throw new Error(`Unexpected input: value should not contain the delimiter "${delimiter}"`); + } + return `${key}<<${delimiter}${os.EOL}${convertedValue}${os.EOL}${delimiter}`; +} +exports.prepareKeyValueMessage = prepareKeyValueMessage; +//# sourceMappingURL=file-command.js.map + +/***/ }), + +/***/ 35306: +/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) { + +"use strict"; + +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.OidcClient = void 0; +const http_client_1 = __nccwpck_require__(54844); +const auth_1 = __nccwpck_require__(44552); +const core_1 = __nccwpck_require__(37484); +class OidcClient { + static createHttpClient(allowRetry = true, maxRetry = 10) { + const requestOptions = { + allowRetries: allowRetry, + maxRetries: maxRetry + }; + return new http_client_1.HttpClient('actions/oidc-client', [new auth_1.BearerCredentialHandler(OidcClient.getRequestToken())], requestOptions); + } + static getRequestToken() { + const token = process.env['ACTIONS_ID_TOKEN_REQUEST_TOKEN']; + if (!token) { + throw new Error('Unable to get ACTIONS_ID_TOKEN_REQUEST_TOKEN env variable'); + } + return token; + } + static getIDTokenUrl() { + const runtimeUrl = process.env['ACTIONS_ID_TOKEN_REQUEST_URL']; + if (!runtimeUrl) { + throw new Error('Unable to get ACTIONS_ID_TOKEN_REQUEST_URL env variable'); + } + return runtimeUrl; + } + static getCall(id_token_url) { + var _a; + return __awaiter(this, void 0, void 0, function* () { + const httpclient = OidcClient.createHttpClient(); + const res = yield httpclient + .getJson(id_token_url) + .catch(error => { + throw new Error(`Failed to get ID Token. \n + Error Code : ${error.statusCode}\n + Error Message: ${error.message}`); + }); + const id_token = (_a = res.result) === null || _a === void 0 ? void 0 : _a.value; + if (!id_token) { + throw new Error('Response json body do not have ID Token field'); + } + return id_token; + }); + } + static getIDToken(audience) { + return __awaiter(this, void 0, void 0, function* () { + try { + // New ID Token is requested from action service + let id_token_url = OidcClient.getIDTokenUrl(); + if (audience) { + const encodedAudience = encodeURIComponent(audience); + id_token_url = `${id_token_url}&audience=${encodedAudience}`; + } + (0, core_1.debug)(`ID token url is ${id_token_url}`); + const id_token = yield OidcClient.getCall(id_token_url); + (0, core_1.setSecret)(id_token); + return id_token; + } + catch (error) { + throw new Error(`Error message: ${error.message}`); + } + }); + } +} +exports.OidcClient = OidcClient; +//# sourceMappingURL=oidc-utils.js.map + +/***/ }), + +/***/ 31976: +/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) { + +"use strict"; + +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + var desc = Object.getOwnPropertyDescriptor(m, k); + if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { + desc = { enumerable: true, get: function() { return m[k]; } }; + } + Object.defineProperty(o, k2, desc); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.toPlatformPath = exports.toWin32Path = exports.toPosixPath = void 0; +const path = __importStar(__nccwpck_require__(16928)); +/** + * toPosixPath converts the given path to the posix form. On Windows, \\ will be + * replaced with /. + * + * @param pth. Path to transform. + * @return string Posix path. + */ +function toPosixPath(pth) { + return pth.replace(/[\\]/g, '/'); +} +exports.toPosixPath = toPosixPath; +/** + * toWin32Path converts the given path to the win32 form. On Linux, / will be + * replaced with \\. + * + * @param pth. Path to transform. + * @return string Win32 path. + */ +function toWin32Path(pth) { + return pth.replace(/[/]/g, '\\'); +} +exports.toWin32Path = toWin32Path; +/** + * toPlatformPath converts the given path to a platform-specific path. It does + * this by replacing instances of / and \ with the platform-specific path + * separator. + * + * @param pth The path to platformize. + * @return string The platform-specific path. + */ +function toPlatformPath(pth) { + return pth.replace(/[/\\]/g, path.sep); +} +exports.toPlatformPath = toPlatformPath; +//# sourceMappingURL=path-utils.js.map + +/***/ }), + +/***/ 18968: +/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) { + +"use strict"; + +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + var desc = Object.getOwnPropertyDescriptor(m, k); + if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { + desc = { enumerable: true, get: function() { return m[k]; } }; + } + Object.defineProperty(o, k2, desc); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + 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 })); +exports.getDetails = exports.isLinux = exports.isMacOS = exports.isWindows = exports.arch = exports.platform = void 0; +const os_1 = __importDefault(__nccwpck_require__(70857)); +const exec = __importStar(__nccwpck_require__(95236)); +const getWindowsInfo = () => __awaiter(void 0, void 0, void 0, function* () { + const { stdout: version } = yield exec.getExecOutput('powershell -command "(Get-CimInstance -ClassName Win32_OperatingSystem).Version"', undefined, { + silent: true + }); + const { stdout: name } = yield exec.getExecOutput('powershell -command "(Get-CimInstance -ClassName Win32_OperatingSystem).Caption"', undefined, { + silent: true + }); + return { + name: name.trim(), + version: version.trim() + }; +}); +const getMacOsInfo = () => __awaiter(void 0, void 0, void 0, function* () { + var _a, _b, _c, _d; + const { stdout } = yield exec.getExecOutput('sw_vers', undefined, { + silent: true + }); + const version = (_b = (_a = stdout.match(/ProductVersion:\s*(.+)/)) === null || _a === void 0 ? void 0 : _a[1]) !== null && _b !== void 0 ? _b : ''; + const name = (_d = (_c = stdout.match(/ProductName:\s*(.+)/)) === null || _c === void 0 ? void 0 : _c[1]) !== null && _d !== void 0 ? _d : ''; + return { + name, + version + }; +}); +const getLinuxInfo = () => __awaiter(void 0, void 0, void 0, function* () { + const { stdout } = yield exec.getExecOutput('lsb_release', ['-i', '-r', '-s'], { + silent: true + }); + const [name, version] = stdout.trim().split('\n'); + return { + name, + version + }; +}); +exports.platform = os_1.default.platform(); +exports.arch = os_1.default.arch(); +exports.isWindows = exports.platform === 'win32'; +exports.isMacOS = exports.platform === 'darwin'; +exports.isLinux = exports.platform === 'linux'; +function getDetails() { + return __awaiter(this, void 0, void 0, function* () { + return Object.assign(Object.assign({}, (yield (exports.isWindows + ? getWindowsInfo() + : exports.isMacOS + ? getMacOsInfo() + : getLinuxInfo()))), { platform: exports.platform, + arch: exports.arch, + isWindows: exports.isWindows, + isMacOS: exports.isMacOS, + isLinux: exports.isLinux }); + }); +} +exports.getDetails = getDetails; +//# sourceMappingURL=platform.js.map + +/***/ }), + +/***/ 71847: +/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) { + +"use strict"; + +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.summary = exports.markdownSummary = exports.SUMMARY_DOCS_URL = exports.SUMMARY_ENV_VAR = void 0; +const os_1 = __nccwpck_require__(70857); +const fs_1 = __nccwpck_require__(79896); +const { access, appendFile, writeFile } = fs_1.promises; +exports.SUMMARY_ENV_VAR = 'GITHUB_STEP_SUMMARY'; +exports.SUMMARY_DOCS_URL = 'https://docs.github.com/actions/using-workflows/workflow-commands-for-github-actions#adding-a-job-summary'; +class Summary { + constructor() { + this._buffer = ''; + } + /** + * Finds the summary file path from the environment, rejects if env var is not found or file does not exist + * Also checks r/w permissions. + * + * @returns step summary file path + */ + filePath() { + return __awaiter(this, void 0, void 0, function* () { + if (this._filePath) { + return this._filePath; + } + const pathFromEnv = process.env[exports.SUMMARY_ENV_VAR]; + if (!pathFromEnv) { + throw new Error(`Unable to find environment variable for $${exports.SUMMARY_ENV_VAR}. Check if your runtime environment supports job summaries.`); + } + try { + yield access(pathFromEnv, fs_1.constants.R_OK | fs_1.constants.W_OK); + } + catch (_a) { + throw new Error(`Unable to access summary file: '${pathFromEnv}'. Check if the file has correct read/write permissions.`); + } + this._filePath = pathFromEnv; + return this._filePath; + }); + } + /** + * Wraps content in an HTML tag, adding any HTML attributes + * + * @param {string} tag HTML tag to wrap + * @param {string | null} content content within the tag + * @param {[attribute: string]: string} attrs key-value list of HTML attributes to add + * + * @returns {string} content wrapped in HTML element + */ + wrap(tag, content, attrs = {}) { + const htmlAttrs = Object.entries(attrs) + .map(([key, value]) => ` ${key}="${value}"`) + .join(''); + if (!content) { + return `<${tag}${htmlAttrs}>`; + } + return `<${tag}${htmlAttrs}>${content}`; + } + /** + * Writes text in the buffer to the summary buffer file and empties buffer. Will append by default. + * + * @param {SummaryWriteOptions} [options] (optional) options for write operation + * + * @returns {Promise} summary instance + */ + write(options) { + return __awaiter(this, void 0, void 0, function* () { + const overwrite = !!(options === null || options === void 0 ? void 0 : options.overwrite); + const filePath = yield this.filePath(); + const writeFunc = overwrite ? writeFile : appendFile; + yield writeFunc(filePath, this._buffer, { encoding: 'utf8' }); + return this.emptyBuffer(); + }); + } + /** + * Clears the summary buffer and wipes the summary file + * + * @returns {Summary} summary instance + */ + clear() { + return __awaiter(this, void 0, void 0, function* () { + return this.emptyBuffer().write({ overwrite: true }); + }); + } + /** + * Returns the current summary buffer as a string + * + * @returns {string} string of summary buffer + */ + stringify() { + return this._buffer; + } + /** + * If the summary buffer is empty + * + * @returns {boolen} true if the buffer is empty + */ + isEmptyBuffer() { + return this._buffer.length === 0; + } + /** + * Resets the summary buffer without writing to summary file + * + * @returns {Summary} summary instance + */ + emptyBuffer() { + this._buffer = ''; + return this; + } + /** + * Adds raw text to the summary buffer + * + * @param {string} text content to add + * @param {boolean} [addEOL=false] (optional) append an EOL to the raw text (default: false) + * + * @returns {Summary} summary instance + */ + addRaw(text, addEOL = false) { + this._buffer += text; + return addEOL ? this.addEOL() : this; + } + /** + * Adds the operating system-specific end-of-line marker to the buffer + * + * @returns {Summary} summary instance + */ + addEOL() { + return this.addRaw(os_1.EOL); + } + /** + * Adds an HTML codeblock to the summary buffer + * + * @param {string} code content to render within fenced code block + * @param {string} lang (optional) language to syntax highlight code + * + * @returns {Summary} summary instance + */ + addCodeBlock(code, lang) { + const attrs = Object.assign({}, (lang && { lang })); + const element = this.wrap('pre', this.wrap('code', code), attrs); + return this.addRaw(element).addEOL(); + } + /** + * Adds an HTML list to the summary buffer + * + * @param {string[]} items list of items to render + * @param {boolean} [ordered=false] (optional) if the rendered list should be ordered or not (default: false) + * + * @returns {Summary} summary instance + */ + addList(items, ordered = false) { + const tag = ordered ? 'ol' : 'ul'; + const listItems = items.map(item => this.wrap('li', item)).join(''); + const element = this.wrap(tag, listItems); + return this.addRaw(element).addEOL(); + } + /** + * Adds an HTML table to the summary buffer + * + * @param {SummaryTableCell[]} rows table rows + * + * @returns {Summary} summary instance + */ + addTable(rows) { + const tableBody = rows + .map(row => { + const cells = row + .map(cell => { + if (typeof cell === 'string') { + return this.wrap('td', cell); + } + const { header, data, colspan, rowspan } = cell; + const tag = header ? 'th' : 'td'; + const attrs = Object.assign(Object.assign({}, (colspan && { colspan })), (rowspan && { rowspan })); + return this.wrap(tag, data, attrs); + }) + .join(''); + return this.wrap('tr', cells); + }) + .join(''); + const element = this.wrap('table', tableBody); + return this.addRaw(element).addEOL(); + } + /** + * Adds a collapsable HTML details element to the summary buffer + * + * @param {string} label text for the closed state + * @param {string} content collapsable content + * + * @returns {Summary} summary instance + */ + addDetails(label, content) { + const element = this.wrap('details', this.wrap('summary', label) + content); + return this.addRaw(element).addEOL(); + } + /** + * Adds an HTML image tag to the summary buffer + * + * @param {string} src path to the image you to embed + * @param {string} alt text description of the image + * @param {SummaryImageOptions} options (optional) addition image attributes + * + * @returns {Summary} summary instance + */ + addImage(src, alt, options) { + const { width, height } = options || {}; + const attrs = Object.assign(Object.assign({}, (width && { width })), (height && { height })); + const element = this.wrap('img', null, Object.assign({ src, alt }, attrs)); + return this.addRaw(element).addEOL(); + } + /** + * Adds an HTML section heading element + * + * @param {string} text heading text + * @param {number | string} [level=1] (optional) the heading level, default: 1 + * + * @returns {Summary} summary instance + */ + addHeading(text, level) { + const tag = `h${level}`; + const allowedTag = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'].includes(tag) + ? tag + : 'h1'; + const element = this.wrap(allowedTag, text); + return this.addRaw(element).addEOL(); + } + /** + * Adds an HTML thematic break (
) to the summary buffer + * + * @returns {Summary} summary instance + */ + addSeparator() { + const element = this.wrap('hr', null); + return this.addRaw(element).addEOL(); + } + /** + * Adds an HTML line break (
) to the summary buffer + * + * @returns {Summary} summary instance + */ + addBreak() { + const element = this.wrap('br', null); + return this.addRaw(element).addEOL(); + } + /** + * Adds an HTML blockquote to the summary buffer + * + * @param {string} text quote text + * @param {string} cite (optional) citation url + * + * @returns {Summary} summary instance + */ + addQuote(text, cite) { + const attrs = Object.assign({}, (cite && { cite })); + const element = this.wrap('blockquote', text, attrs); + return this.addRaw(element).addEOL(); + } + /** + * Adds an HTML anchor tag to the summary buffer + * + * @param {string} text link text/content + * @param {string} href hyperlink + * + * @returns {Summary} summary instance + */ + addLink(text, href) { + const element = this.wrap('a', text, { href }); + return this.addRaw(element).addEOL(); + } +} +const _summary = new Summary(); +/** + * @deprecated use `core.summary` + */ +exports.markdownSummary = _summary; +exports.summary = _summary; +//# sourceMappingURL=summary.js.map + +/***/ }), + +/***/ 30302: +/***/ ((__unused_webpack_module, exports) => { + +"use strict"; + +// We use any as a valid input type +/* eslint-disable @typescript-eslint/no-explicit-any */ +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.toCommandProperties = exports.toCommandValue = void 0; +/** + * Sanitizes an input into a string so it can be passed into issueCommand safely + * @param input input to sanitize into a string + */ +function toCommandValue(input) { + if (input === null || input === undefined) { + return ''; + } + else if (typeof input === 'string' || input instanceof String) { + return input; + } + return JSON.stringify(input); +} +exports.toCommandValue = toCommandValue; +/** + * + * @param annotationProperties + * @returns The command properties to send with the actual annotation command + * See IssueCommandProperties: https://github.com/actions/runner/blob/main/src/Runner.Worker/ActionCommandManager.cs#L646 + */ +function toCommandProperties(annotationProperties) { + if (!Object.keys(annotationProperties).length) { + return {}; + } + return { + title: annotationProperties.title, + file: annotationProperties.file, + line: annotationProperties.startLine, + endLine: annotationProperties.endLine, + col: annotationProperties.startColumn, + endColumn: annotationProperties.endColumn + }; +} +exports.toCommandProperties = toCommandProperties; +//# sourceMappingURL=utils.js.map + +/***/ }), + +/***/ 95236: +/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) { + +"use strict"; + +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.getExecOutput = exports.exec = void 0; +const string_decoder_1 = __nccwpck_require__(13193); +const tr = __importStar(__nccwpck_require__(6665)); +/** + * Exec a command. + * Output will be streamed to the live console. + * Returns promise with return code + * + * @param commandLine command to execute (can include additional args). Must be correctly escaped. + * @param args optional arguments for tool. Escaping is handled by the lib. + * @param options optional exec options. See ExecOptions + * @returns Promise exit code + */ +function exec(commandLine, args, options) { + return __awaiter(this, void 0, void 0, function* () { + const commandArgs = tr.argStringToArray(commandLine); + if (commandArgs.length === 0) { + throw new Error(`Parameter 'commandLine' cannot be null or empty.`); + } + // Path to tool to execute should be first arg + const toolPath = commandArgs[0]; + args = commandArgs.slice(1).concat(args || []); + const runner = new tr.ToolRunner(toolPath, args, options); + return runner.exec(); + }); +} +exports.exec = exec; +/** + * Exec a command and get the output. + * Output will be streamed to the live console. + * Returns promise with the exit code and collected stdout and stderr + * + * @param commandLine command to execute (can include additional args). Must be correctly escaped. + * @param args optional arguments for tool. Escaping is handled by the lib. + * @param options optional exec options. See ExecOptions + * @returns Promise exit code, stdout, and stderr + */ +function getExecOutput(commandLine, args, options) { + var _a, _b; + return __awaiter(this, void 0, void 0, function* () { + let stdout = ''; + let stderr = ''; + //Using string decoder covers the case where a mult-byte character is split + const stdoutDecoder = new string_decoder_1.StringDecoder('utf8'); + const stderrDecoder = new string_decoder_1.StringDecoder('utf8'); + const originalStdoutListener = (_a = options === null || options === void 0 ? void 0 : options.listeners) === null || _a === void 0 ? void 0 : _a.stdout; + const originalStdErrListener = (_b = options === null || options === void 0 ? void 0 : options.listeners) === null || _b === void 0 ? void 0 : _b.stderr; + const stdErrListener = (data) => { + stderr += stderrDecoder.write(data); + if (originalStdErrListener) { + originalStdErrListener(data); + } + }; + const stdOutListener = (data) => { + stdout += stdoutDecoder.write(data); + if (originalStdoutListener) { + originalStdoutListener(data); + } + }; + const listeners = Object.assign(Object.assign({}, options === null || options === void 0 ? void 0 : options.listeners), { stdout: stdOutListener, stderr: stdErrListener }); + const exitCode = yield exec(commandLine, args, Object.assign(Object.assign({}, options), { listeners })); + //flush any remaining characters + stdout += stdoutDecoder.end(); + stderr += stderrDecoder.end(); + return { + exitCode, + stdout, + stderr + }; + }); +} +exports.getExecOutput = getExecOutput; +//# sourceMappingURL=exec.js.map + +/***/ }), + +/***/ 6665: +/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) { + +"use strict"; + +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.argStringToArray = exports.ToolRunner = void 0; +const os = __importStar(__nccwpck_require__(70857)); +const events = __importStar(__nccwpck_require__(24434)); +const child = __importStar(__nccwpck_require__(35317)); +const path = __importStar(__nccwpck_require__(16928)); +const io = __importStar(__nccwpck_require__(94994)); +const ioUtil = __importStar(__nccwpck_require__(75207)); +const timers_1 = __nccwpck_require__(53557); +/* eslint-disable @typescript-eslint/unbound-method */ +const IS_WINDOWS = process.platform === 'win32'; +/* + * Class for running command line tools. Handles quoting and arg parsing in a platform agnostic way. + */ +class ToolRunner extends events.EventEmitter { + constructor(toolPath, args, options) { + super(); + if (!toolPath) { + throw new Error("Parameter 'toolPath' cannot be null or empty."); + } + this.toolPath = toolPath; + this.args = args || []; + this.options = options || {}; + } + _debug(message) { + if (this.options.listeners && this.options.listeners.debug) { + this.options.listeners.debug(message); + } + } + _getCommandString(options, noPrefix) { + const toolPath = this._getSpawnFileName(); + const args = this._getSpawnArgs(options); + let cmd = noPrefix ? '' : '[command]'; // omit prefix when piped to a second tool + if (IS_WINDOWS) { + // Windows + cmd file + if (this._isCmdFile()) { + cmd += toolPath; + for (const a of args) { + cmd += ` ${a}`; + } + } + // Windows + verbatim + else if (options.windowsVerbatimArguments) { + cmd += `"${toolPath}"`; + for (const a of args) { + cmd += ` ${a}`; + } + } + // Windows (regular) + else { + cmd += this._windowsQuoteCmdArg(toolPath); + for (const a of args) { + cmd += ` ${this._windowsQuoteCmdArg(a)}`; + } + } + } + else { + // OSX/Linux - this can likely be improved with some form of quoting. + // creating processes on Unix is fundamentally different than Windows. + // on Unix, execvp() takes an arg array. + cmd += toolPath; + for (const a of args) { + cmd += ` ${a}`; + } + } + return cmd; + } + _processLineBuffer(data, strBuffer, onLine) { + try { + let s = strBuffer + data.toString(); + let n = s.indexOf(os.EOL); + while (n > -1) { + const line = s.substring(0, n); + onLine(line); + // the rest of the string ... + s = s.substring(n + os.EOL.length); + n = s.indexOf(os.EOL); + } + return s; + } + catch (err) { + // streaming lines to console is best effort. Don't fail a build. + this._debug(`error processing line. Failed with error ${err}`); + return ''; + } + } + _getSpawnFileName() { + if (IS_WINDOWS) { + if (this._isCmdFile()) { + return process.env['COMSPEC'] || 'cmd.exe'; + } + } + return this.toolPath; + } + _getSpawnArgs(options) { + if (IS_WINDOWS) { + if (this._isCmdFile()) { + let argline = `/D /S /C "${this._windowsQuoteCmdArg(this.toolPath)}`; + for (const a of this.args) { + argline += ' '; + argline += options.windowsVerbatimArguments + ? a + : this._windowsQuoteCmdArg(a); + } + argline += '"'; + return [argline]; + } + } + return this.args; + } + _endsWith(str, end) { + return str.endsWith(end); + } + _isCmdFile() { + const upperToolPath = this.toolPath.toUpperCase(); + return (this._endsWith(upperToolPath, '.CMD') || + this._endsWith(upperToolPath, '.BAT')); + } + _windowsQuoteCmdArg(arg) { + // for .exe, apply the normal quoting rules that libuv applies + if (!this._isCmdFile()) { + return this._uvQuoteCmdArg(arg); + } + // otherwise apply quoting rules specific to the cmd.exe command line parser. + // the libuv rules are generic and are not designed specifically for cmd.exe + // command line parser. + // + // for a detailed description of the cmd.exe command line parser, refer to + // http://stackoverflow.com/questions/4094699/how-does-the-windows-command-interpreter-cmd-exe-parse-scripts/7970912#7970912 + // need quotes for empty arg + if (!arg) { + return '""'; + } + // determine whether the arg needs to be quoted + const cmdSpecialChars = [ + ' ', + '\t', + '&', + '(', + ')', + '[', + ']', + '{', + '}', + '^', + '=', + ';', + '!', + "'", + '+', + ',', + '`', + '~', + '|', + '<', + '>', + '"' + ]; + let needsQuotes = false; + for (const char of arg) { + if (cmdSpecialChars.some(x => x === char)) { + needsQuotes = true; + break; + } + } + // short-circuit if quotes not needed + if (!needsQuotes) { + return arg; + } + // the following quoting rules are very similar to the rules that by libuv applies. + // + // 1) wrap the string in quotes + // + // 2) double-up quotes - i.e. " => "" + // + // this is different from the libuv quoting rules. libuv replaces " with \", which unfortunately + // doesn't work well with a cmd.exe command line. + // + // note, replacing " with "" also works well if the arg is passed to a downstream .NET console app. + // for example, the command line: + // foo.exe "myarg:""my val""" + // is parsed by a .NET console app into an arg array: + // [ "myarg:\"my val\"" ] + // which is the same end result when applying libuv quoting rules. although the actual + // command line from libuv quoting rules would look like: + // foo.exe "myarg:\"my val\"" + // + // 3) double-up slashes that precede a quote, + // e.g. hello \world => "hello \world" + // hello\"world => "hello\\""world" + // hello\\"world => "hello\\\\""world" + // hello world\ => "hello world\\" + // + // technically this is not required for a cmd.exe command line, or the batch argument parser. + // the reasons for including this as a .cmd quoting rule are: + // + // a) this is optimized for the scenario where the argument is passed from the .cmd file to an + // external program. many programs (e.g. .NET console apps) rely on the slash-doubling rule. + // + // b) it's what we've been doing previously (by deferring to node default behavior) and we + // haven't heard any complaints about that aspect. + // + // note, a weakness of the quoting rules chosen here, is that % is not escaped. in fact, % cannot be + // escaped when used on the command line directly - even though within a .cmd file % can be escaped + // by using %%. + // + // the saving grace is, on the command line, %var% is left as-is if var is not defined. this contrasts + // the line parsing rules within a .cmd file, where if var is not defined it is replaced with nothing. + // + // one option that was explored was replacing % with ^% - i.e. %var% => ^%var^%. this hack would + // often work, since it is unlikely that var^ would exist, and the ^ character is removed when the + // variable is used. the problem, however, is that ^ is not removed when %* is used to pass the args + // to an external program. + // + // an unexplored potential solution for the % escaping problem, is to create a wrapper .cmd file. + // % can be escaped within a .cmd file. + let reverse = '"'; + let quoteHit = true; + for (let i = arg.length; i > 0; i--) { + // walk the string in reverse + reverse += arg[i - 1]; + if (quoteHit && arg[i - 1] === '\\') { + reverse += '\\'; // double the slash + } + else if (arg[i - 1] === '"') { + quoteHit = true; + reverse += '"'; // double the quote + } + else { + quoteHit = false; + } + } + reverse += '"'; + return reverse + .split('') + .reverse() + .join(''); + } + _uvQuoteCmdArg(arg) { + // Tool runner wraps child_process.spawn() and needs to apply the same quoting as + // Node in certain cases where the undocumented spawn option windowsVerbatimArguments + // is used. + // + // Since this function is a port of quote_cmd_arg from Node 4.x (technically, lib UV, + // see https://github.com/nodejs/node/blob/v4.x/deps/uv/src/win/process.c for details), + // pasting copyright notice from Node within this function: + // + // Copyright Joyent, Inc. and other Node contributors. All rights reserved. + // + // Permission is hereby granted, free of charge, to any person obtaining a copy + // of this software and associated documentation files (the "Software"), to + // deal in the Software without restriction, including without limitation the + // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + // sell copies of the Software, and to permit persons to whom the Software is + // furnished to do so, subject to the following conditions: + // + // The above copyright notice and this permission notice shall be included in + // all copies or substantial portions of the Software. + // + // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + // IN THE SOFTWARE. + if (!arg) { + // Need double quotation for empty argument + return '""'; + } + if (!arg.includes(' ') && !arg.includes('\t') && !arg.includes('"')) { + // No quotation needed + return arg; + } + if (!arg.includes('"') && !arg.includes('\\')) { + // No embedded double quotes or backslashes, so I can just wrap + // quote marks around the whole thing. + return `"${arg}"`; + } + // Expected input/output: + // input : hello"world + // output: "hello\"world" + // input : hello""world + // output: "hello\"\"world" + // input : hello\world + // output: hello\world + // input : hello\\world + // output: hello\\world + // input : hello\"world + // output: "hello\\\"world" + // input : hello\\"world + // output: "hello\\\\\"world" + // input : hello world\ + // output: "hello world\\" - note the comment in libuv actually reads "hello world\" + // but it appears the comment is wrong, it should be "hello world\\" + let reverse = '"'; + let quoteHit = true; + for (let i = arg.length; i > 0; i--) { + // walk the string in reverse + reverse += arg[i - 1]; + if (quoteHit && arg[i - 1] === '\\') { + reverse += '\\'; + } + else if (arg[i - 1] === '"') { + quoteHit = true; + reverse += '\\'; + } + else { + quoteHit = false; + } + } + reverse += '"'; + return reverse + .split('') + .reverse() + .join(''); + } + _cloneExecOptions(options) { + options = options || {}; + const result = { + cwd: options.cwd || process.cwd(), + env: options.env || process.env, + silent: options.silent || false, + windowsVerbatimArguments: options.windowsVerbatimArguments || false, + failOnStdErr: options.failOnStdErr || false, + ignoreReturnCode: options.ignoreReturnCode || false, + delay: options.delay || 10000 + }; + result.outStream = options.outStream || process.stdout; + result.errStream = options.errStream || process.stderr; + return result; + } + _getSpawnOptions(options, toolPath) { + options = options || {}; + const result = {}; + result.cwd = options.cwd; + result.env = options.env; + result['windowsVerbatimArguments'] = + options.windowsVerbatimArguments || this._isCmdFile(); + if (options.windowsVerbatimArguments) { + result.argv0 = `"${toolPath}"`; + } + return result; + } + /** + * Exec a tool. + * Output will be streamed to the live console. + * Returns promise with return code + * + * @param tool path to tool to exec + * @param options optional exec options. See ExecOptions + * @returns number + */ + exec() { + return __awaiter(this, void 0, void 0, function* () { + // root the tool path if it is unrooted and contains relative pathing + if (!ioUtil.isRooted(this.toolPath) && + (this.toolPath.includes('/') || + (IS_WINDOWS && this.toolPath.includes('\\')))) { + // prefer options.cwd if it is specified, however options.cwd may also need to be rooted + this.toolPath = path.resolve(process.cwd(), this.options.cwd || process.cwd(), this.toolPath); + } + // if the tool is only a file name, then resolve it from the PATH + // otherwise verify it exists (add extension on Windows if necessary) + this.toolPath = yield io.which(this.toolPath, true); + return new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () { + this._debug(`exec tool: ${this.toolPath}`); + this._debug('arguments:'); + for (const arg of this.args) { + this._debug(` ${arg}`); + } + const optionsNonNull = this._cloneExecOptions(this.options); + if (!optionsNonNull.silent && optionsNonNull.outStream) { + optionsNonNull.outStream.write(this._getCommandString(optionsNonNull) + os.EOL); + } + const state = new ExecState(optionsNonNull, this.toolPath); + state.on('debug', (message) => { + this._debug(message); + }); + if (this.options.cwd && !(yield ioUtil.exists(this.options.cwd))) { + return reject(new Error(`The cwd: ${this.options.cwd} does not exist!`)); + } + const fileName = this._getSpawnFileName(); + const cp = child.spawn(fileName, this._getSpawnArgs(optionsNonNull), this._getSpawnOptions(this.options, fileName)); + let stdbuffer = ''; + if (cp.stdout) { + cp.stdout.on('data', (data) => { + if (this.options.listeners && this.options.listeners.stdout) { + this.options.listeners.stdout(data); + } + if (!optionsNonNull.silent && optionsNonNull.outStream) { + optionsNonNull.outStream.write(data); + } + stdbuffer = this._processLineBuffer(data, stdbuffer, (line) => { + if (this.options.listeners && this.options.listeners.stdline) { + this.options.listeners.stdline(line); + } + }); + }); + } + let errbuffer = ''; + if (cp.stderr) { + cp.stderr.on('data', (data) => { + state.processStderr = true; + if (this.options.listeners && this.options.listeners.stderr) { + this.options.listeners.stderr(data); + } + if (!optionsNonNull.silent && + optionsNonNull.errStream && + optionsNonNull.outStream) { + const s = optionsNonNull.failOnStdErr + ? optionsNonNull.errStream + : optionsNonNull.outStream; + s.write(data); + } + errbuffer = this._processLineBuffer(data, errbuffer, (line) => { + if (this.options.listeners && this.options.listeners.errline) { + this.options.listeners.errline(line); + } + }); + }); + } + cp.on('error', (err) => { + state.processError = err.message; + state.processExited = true; + state.processClosed = true; + state.CheckComplete(); + }); + cp.on('exit', (code) => { + state.processExitCode = code; + state.processExited = true; + this._debug(`Exit code ${code} received from tool '${this.toolPath}'`); + state.CheckComplete(); + }); + cp.on('close', (code) => { + state.processExitCode = code; + state.processExited = true; + state.processClosed = true; + this._debug(`STDIO streams have closed for tool '${this.toolPath}'`); + state.CheckComplete(); + }); + state.on('done', (error, exitCode) => { + if (stdbuffer.length > 0) { + this.emit('stdline', stdbuffer); + } + if (errbuffer.length > 0) { + this.emit('errline', errbuffer); + } + cp.removeAllListeners(); + if (error) { + reject(error); + } + else { + resolve(exitCode); + } + }); + if (this.options.input) { + if (!cp.stdin) { + throw new Error('child process missing stdin'); + } + cp.stdin.end(this.options.input); + } + })); + }); + } +} +exports.ToolRunner = ToolRunner; +/** + * Convert an arg string to an array of args. Handles escaping + * + * @param argString string of arguments + * @returns string[] array of arguments + */ +function argStringToArray(argString) { + const args = []; + let inQuotes = false; + let escaped = false; + let arg = ''; + function append(c) { + // we only escape double quotes. + if (escaped && c !== '"') { + arg += '\\'; + } + arg += c; + escaped = false; + } + for (let i = 0; i < argString.length; i++) { + const c = argString.charAt(i); + if (c === '"') { + if (!escaped) { + inQuotes = !inQuotes; + } + else { + append(c); + } + continue; + } + if (c === '\\' && escaped) { + append(c); + continue; + } + if (c === '\\' && inQuotes) { + escaped = true; + continue; + } + if (c === ' ' && !inQuotes) { + if (arg.length > 0) { + args.push(arg); + arg = ''; + } + continue; + } + append(c); + } + if (arg.length > 0) { + args.push(arg.trim()); + } + return args; +} +exports.argStringToArray = argStringToArray; +class ExecState extends events.EventEmitter { + constructor(options, toolPath) { + super(); + this.processClosed = false; // tracks whether the process has exited and stdio is closed + this.processError = ''; + this.processExitCode = 0; + this.processExited = false; // tracks whether the process has exited + this.processStderr = false; // tracks whether stderr was written to + this.delay = 10000; // 10 seconds + this.done = false; + this.timeout = null; + if (!toolPath) { + throw new Error('toolPath must not be empty'); + } + this.options = options; + this.toolPath = toolPath; + if (options.delay) { + this.delay = options.delay; + } + } + CheckComplete() { + if (this.done) { + return; + } + if (this.processClosed) { + this._setResult(); + } + else if (this.processExited) { + this.timeout = timers_1.setTimeout(ExecState.HandleTimeout, this.delay, this); + } + } + _debug(message) { + this.emit('debug', message); + } + _setResult() { + // determine whether there is an error + let error; + if (this.processExited) { + if (this.processError) { + error = new Error(`There was an error when attempting to execute the process '${this.toolPath}'. This may indicate the process failed to start. Error: ${this.processError}`); + } + else if (this.processExitCode !== 0 && !this.options.ignoreReturnCode) { + error = new Error(`The process '${this.toolPath}' failed with exit code ${this.processExitCode}`); + } + else if (this.processStderr && this.options.failOnStdErr) { + error = new Error(`The process '${this.toolPath}' failed because one or more lines were written to the STDERR stream`); + } + } + // clear the timeout + if (this.timeout) { + clearTimeout(this.timeout); + this.timeout = null; + } + this.done = true; + this.emit('done', error, this.processExitCode); + } + static HandleTimeout(state) { + if (state.done) { + return; + } + if (!state.processClosed && state.processExited) { + const message = `The STDIO streams did not close within ${state.delay / + 1000} seconds of the exit event from process '${state.toolPath}'. This may indicate a child process inherited the STDIO streams and has not yet exited.`; + state._debug(message); + } + state._setResult(); + } +} +//# sourceMappingURL=toolrunner.js.map + +/***/ }), + +/***/ 44552: +/***/ (function(__unused_webpack_module, exports) { + +"use strict"; + +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.PersonalAccessTokenCredentialHandler = exports.BearerCredentialHandler = exports.BasicCredentialHandler = void 0; +class BasicCredentialHandler { + constructor(username, password) { + this.username = username; + this.password = password; + } + prepareRequest(options) { + if (!options.headers) { + throw Error('The request has no headers'); + } + options.headers['Authorization'] = `Basic ${Buffer.from(`${this.username}:${this.password}`).toString('base64')}`; + } + // This handler cannot handle 401 + canHandleAuthentication() { + return false; + } + handleAuthentication() { + return __awaiter(this, void 0, void 0, function* () { + throw new Error('not implemented'); + }); + } +} +exports.BasicCredentialHandler = BasicCredentialHandler; +class BearerCredentialHandler { + constructor(token) { + this.token = token; + } + // currently implements pre-authorization + // TODO: support preAuth = false where it hooks on 401 + prepareRequest(options) { + if (!options.headers) { + throw Error('The request has no headers'); + } + options.headers['Authorization'] = `Bearer ${this.token}`; + } + // This handler cannot handle 401 + canHandleAuthentication() { + return false; + } + handleAuthentication() { + return __awaiter(this, void 0, void 0, function* () { + throw new Error('not implemented'); + }); + } +} +exports.BearerCredentialHandler = BearerCredentialHandler; +class PersonalAccessTokenCredentialHandler { + constructor(token) { + this.token = token; + } + // currently implements pre-authorization + // TODO: support preAuth = false where it hooks on 401 + prepareRequest(options) { + if (!options.headers) { + throw Error('The request has no headers'); + } + options.headers['Authorization'] = `Basic ${Buffer.from(`PAT:${this.token}`).toString('base64')}`; + } + // This handler cannot handle 401 + canHandleAuthentication() { + return false; + } + handleAuthentication() { + return __awaiter(this, void 0, void 0, function* () { + throw new Error('not implemented'); + }); + } +} +exports.PersonalAccessTokenCredentialHandler = PersonalAccessTokenCredentialHandler; +//# sourceMappingURL=auth.js.map + +/***/ }), + +/***/ 54844: +/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) { + +"use strict"; + +/* eslint-disable @typescript-eslint/no-explicit-any */ +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + var desc = Object.getOwnPropertyDescriptor(m, k); + if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { + desc = { enumerable: true, get: function() { return m[k]; } }; + } + Object.defineProperty(o, k2, desc); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.HttpClient = exports.isHttps = exports.HttpClientResponse = exports.HttpClientError = exports.getProxyUrl = exports.MediaTypes = exports.Headers = exports.HttpCodes = void 0; +const http = __importStar(__nccwpck_require__(58611)); +const https = __importStar(__nccwpck_require__(65692)); +const pm = __importStar(__nccwpck_require__(54988)); +const tunnel = __importStar(__nccwpck_require__(20770)); +const undici_1 = __nccwpck_require__(46752); +var HttpCodes; +(function (HttpCodes) { + HttpCodes[HttpCodes["OK"] = 200] = "OK"; + HttpCodes[HttpCodes["MultipleChoices"] = 300] = "MultipleChoices"; + HttpCodes[HttpCodes["MovedPermanently"] = 301] = "MovedPermanently"; + HttpCodes[HttpCodes["ResourceMoved"] = 302] = "ResourceMoved"; + HttpCodes[HttpCodes["SeeOther"] = 303] = "SeeOther"; + HttpCodes[HttpCodes["NotModified"] = 304] = "NotModified"; + HttpCodes[HttpCodes["UseProxy"] = 305] = "UseProxy"; + HttpCodes[HttpCodes["SwitchProxy"] = 306] = "SwitchProxy"; + HttpCodes[HttpCodes["TemporaryRedirect"] = 307] = "TemporaryRedirect"; + HttpCodes[HttpCodes["PermanentRedirect"] = 308] = "PermanentRedirect"; + HttpCodes[HttpCodes["BadRequest"] = 400] = "BadRequest"; + HttpCodes[HttpCodes["Unauthorized"] = 401] = "Unauthorized"; + HttpCodes[HttpCodes["PaymentRequired"] = 402] = "PaymentRequired"; + HttpCodes[HttpCodes["Forbidden"] = 403] = "Forbidden"; + HttpCodes[HttpCodes["NotFound"] = 404] = "NotFound"; + HttpCodes[HttpCodes["MethodNotAllowed"] = 405] = "MethodNotAllowed"; + HttpCodes[HttpCodes["NotAcceptable"] = 406] = "NotAcceptable"; + HttpCodes[HttpCodes["ProxyAuthenticationRequired"] = 407] = "ProxyAuthenticationRequired"; + HttpCodes[HttpCodes["RequestTimeout"] = 408] = "RequestTimeout"; + HttpCodes[HttpCodes["Conflict"] = 409] = "Conflict"; + HttpCodes[HttpCodes["Gone"] = 410] = "Gone"; + HttpCodes[HttpCodes["TooManyRequests"] = 429] = "TooManyRequests"; + HttpCodes[HttpCodes["InternalServerError"] = 500] = "InternalServerError"; + HttpCodes[HttpCodes["NotImplemented"] = 501] = "NotImplemented"; + HttpCodes[HttpCodes["BadGateway"] = 502] = "BadGateway"; + HttpCodes[HttpCodes["ServiceUnavailable"] = 503] = "ServiceUnavailable"; + HttpCodes[HttpCodes["GatewayTimeout"] = 504] = "GatewayTimeout"; +})(HttpCodes || (exports.HttpCodes = HttpCodes = {})); +var Headers; +(function (Headers) { + Headers["Accept"] = "accept"; + Headers["ContentType"] = "content-type"; +})(Headers || (exports.Headers = Headers = {})); +var MediaTypes; +(function (MediaTypes) { + MediaTypes["ApplicationJson"] = "application/json"; +})(MediaTypes || (exports.MediaTypes = MediaTypes = {})); +/** + * Returns the proxy URL, depending upon the supplied url and proxy environment variables. + * @param serverUrl The server URL where the request will be sent. For example, https://api.github.com + */ +function getProxyUrl(serverUrl) { + const proxyUrl = pm.getProxyUrl(new URL(serverUrl)); + return proxyUrl ? proxyUrl.href : ''; +} +exports.getProxyUrl = getProxyUrl; +const HttpRedirectCodes = [ + HttpCodes.MovedPermanently, + HttpCodes.ResourceMoved, + HttpCodes.SeeOther, + HttpCodes.TemporaryRedirect, + HttpCodes.PermanentRedirect +]; +const HttpResponseRetryCodes = [ + HttpCodes.BadGateway, + HttpCodes.ServiceUnavailable, + HttpCodes.GatewayTimeout +]; +const RetryableHttpVerbs = ['OPTIONS', 'GET', 'DELETE', 'HEAD']; +const ExponentialBackoffCeiling = 10; +const ExponentialBackoffTimeSlice = 5; +class HttpClientError extends Error { + constructor(message, statusCode) { + super(message); + this.name = 'HttpClientError'; + this.statusCode = statusCode; + Object.setPrototypeOf(this, HttpClientError.prototype); + } +} +exports.HttpClientError = HttpClientError; +class HttpClientResponse { + constructor(message) { + this.message = message; + } + readBody() { + return __awaiter(this, void 0, void 0, function* () { + return new Promise((resolve) => __awaiter(this, void 0, void 0, function* () { + let output = Buffer.alloc(0); + this.message.on('data', (chunk) => { + output = Buffer.concat([output, chunk]); + }); + this.message.on('end', () => { + resolve(output.toString()); + }); + })); + }); + } + readBodyBuffer() { + return __awaiter(this, void 0, void 0, function* () { + return new Promise((resolve) => __awaiter(this, void 0, void 0, function* () { + const chunks = []; + this.message.on('data', (chunk) => { + chunks.push(chunk); + }); + this.message.on('end', () => { + resolve(Buffer.concat(chunks)); + }); + })); + }); + } +} +exports.HttpClientResponse = HttpClientResponse; +function isHttps(requestUrl) { + const parsedUrl = new URL(requestUrl); + return parsedUrl.protocol === 'https:'; +} +exports.isHttps = isHttps; +class HttpClient { + constructor(userAgent, handlers, requestOptions) { + this._ignoreSslError = false; + this._allowRedirects = true; + this._allowRedirectDowngrade = false; + this._maxRedirects = 50; + this._allowRetries = false; + this._maxRetries = 1; + this._keepAlive = false; + this._disposed = false; + this.userAgent = userAgent; + this.handlers = handlers || []; + this.requestOptions = requestOptions; + if (requestOptions) { + if (requestOptions.ignoreSslError != null) { + this._ignoreSslError = requestOptions.ignoreSslError; + } + this._socketTimeout = requestOptions.socketTimeout; + if (requestOptions.allowRedirects != null) { + this._allowRedirects = requestOptions.allowRedirects; + } + if (requestOptions.allowRedirectDowngrade != null) { + this._allowRedirectDowngrade = requestOptions.allowRedirectDowngrade; + } + if (requestOptions.maxRedirects != null) { + this._maxRedirects = Math.max(requestOptions.maxRedirects, 0); + } + if (requestOptions.keepAlive != null) { + this._keepAlive = requestOptions.keepAlive; + } + if (requestOptions.allowRetries != null) { + this._allowRetries = requestOptions.allowRetries; + } + if (requestOptions.maxRetries != null) { + this._maxRetries = requestOptions.maxRetries; + } + } + } + options(requestUrl, additionalHeaders) { + return __awaiter(this, void 0, void 0, function* () { + return this.request('OPTIONS', requestUrl, null, additionalHeaders || {}); + }); + } + get(requestUrl, additionalHeaders) { + return __awaiter(this, void 0, void 0, function* () { + return this.request('GET', requestUrl, null, additionalHeaders || {}); + }); + } + del(requestUrl, additionalHeaders) { + return __awaiter(this, void 0, void 0, function* () { + return this.request('DELETE', requestUrl, null, additionalHeaders || {}); + }); + } + post(requestUrl, data, additionalHeaders) { + return __awaiter(this, void 0, void 0, function* () { + return this.request('POST', requestUrl, data, additionalHeaders || {}); + }); + } + patch(requestUrl, data, additionalHeaders) { + return __awaiter(this, void 0, void 0, function* () { + return this.request('PATCH', requestUrl, data, additionalHeaders || {}); + }); + } + put(requestUrl, data, additionalHeaders) { + return __awaiter(this, void 0, void 0, function* () { + return this.request('PUT', requestUrl, data, additionalHeaders || {}); + }); + } + head(requestUrl, additionalHeaders) { + return __awaiter(this, void 0, void 0, function* () { + return this.request('HEAD', requestUrl, null, additionalHeaders || {}); + }); + } + sendStream(verb, requestUrl, stream, additionalHeaders) { + return __awaiter(this, void 0, void 0, function* () { + return this.request(verb, requestUrl, stream, additionalHeaders); + }); + } + /** + * Gets a typed object from an endpoint + * Be aware that not found returns a null. Other errors (4xx, 5xx) reject the promise + */ + getJson(requestUrl, additionalHeaders = {}) { + return __awaiter(this, void 0, void 0, function* () { + additionalHeaders[Headers.Accept] = this._getExistingOrDefaultHeader(additionalHeaders, Headers.Accept, MediaTypes.ApplicationJson); + const res = yield this.get(requestUrl, additionalHeaders); + return this._processResponse(res, this.requestOptions); + }); + } + postJson(requestUrl, obj, additionalHeaders = {}) { + return __awaiter(this, void 0, void 0, function* () { + const data = JSON.stringify(obj, null, 2); + additionalHeaders[Headers.Accept] = this._getExistingOrDefaultHeader(additionalHeaders, Headers.Accept, MediaTypes.ApplicationJson); + additionalHeaders[Headers.ContentType] = this._getExistingOrDefaultHeader(additionalHeaders, Headers.ContentType, MediaTypes.ApplicationJson); + const res = yield this.post(requestUrl, data, additionalHeaders); + return this._processResponse(res, this.requestOptions); + }); + } + putJson(requestUrl, obj, additionalHeaders = {}) { + return __awaiter(this, void 0, void 0, function* () { + const data = JSON.stringify(obj, null, 2); + additionalHeaders[Headers.Accept] = this._getExistingOrDefaultHeader(additionalHeaders, Headers.Accept, MediaTypes.ApplicationJson); + additionalHeaders[Headers.ContentType] = this._getExistingOrDefaultHeader(additionalHeaders, Headers.ContentType, MediaTypes.ApplicationJson); + const res = yield this.put(requestUrl, data, additionalHeaders); + return this._processResponse(res, this.requestOptions); + }); + } + patchJson(requestUrl, obj, additionalHeaders = {}) { + return __awaiter(this, void 0, void 0, function* () { + const data = JSON.stringify(obj, null, 2); + additionalHeaders[Headers.Accept] = this._getExistingOrDefaultHeader(additionalHeaders, Headers.Accept, MediaTypes.ApplicationJson); + additionalHeaders[Headers.ContentType] = this._getExistingOrDefaultHeader(additionalHeaders, Headers.ContentType, MediaTypes.ApplicationJson); + const res = yield this.patch(requestUrl, data, additionalHeaders); + return this._processResponse(res, this.requestOptions); + }); + } + /** + * Makes a raw http request. + * All other methods such as get, post, patch, and request ultimately call this. + * Prefer get, del, post and patch + */ + request(verb, requestUrl, data, headers) { + return __awaiter(this, void 0, void 0, function* () { + if (this._disposed) { + throw new Error('Client has already been disposed.'); + } + const parsedUrl = new URL(requestUrl); + let info = this._prepareRequest(verb, parsedUrl, headers); + // Only perform retries on reads since writes may not be idempotent. + const maxTries = this._allowRetries && RetryableHttpVerbs.includes(verb) + ? this._maxRetries + 1 + : 1; + let numTries = 0; + let response; + do { + response = yield this.requestRaw(info, data); + // Check if it's an authentication challenge + if (response && + response.message && + response.message.statusCode === HttpCodes.Unauthorized) { + let authenticationHandler; + for (const handler of this.handlers) { + if (handler.canHandleAuthentication(response)) { + authenticationHandler = handler; + break; + } + } + if (authenticationHandler) { + return authenticationHandler.handleAuthentication(this, info, data); + } + else { + // We have received an unauthorized response but have no handlers to handle it. + // Let the response return to the caller. + return response; + } + } + let redirectsRemaining = this._maxRedirects; + while (response.message.statusCode && + HttpRedirectCodes.includes(response.message.statusCode) && + this._allowRedirects && + redirectsRemaining > 0) { + const redirectUrl = response.message.headers['location']; + if (!redirectUrl) { + // if there's no location to redirect to, we won't + break; + } + const parsedRedirectUrl = new URL(redirectUrl); + if (parsedUrl.protocol === 'https:' && + parsedUrl.protocol !== parsedRedirectUrl.protocol && + !this._allowRedirectDowngrade) { + throw new Error('Redirect from HTTPS to HTTP protocol. This downgrade is not allowed for security reasons. If you want to allow this behavior, set the allowRedirectDowngrade option to true.'); + } + // we need to finish reading the response before reassigning response + // which will leak the open socket. + yield response.readBody(); + // strip authorization header if redirected to a different hostname + if (parsedRedirectUrl.hostname !== parsedUrl.hostname) { + for (const header in headers) { + // header names are case insensitive + if (header.toLowerCase() === 'authorization') { + delete headers[header]; + } + } + } + // let's make the request with the new redirectUrl + info = this._prepareRequest(verb, parsedRedirectUrl, headers); + response = yield this.requestRaw(info, data); + redirectsRemaining--; + } + if (!response.message.statusCode || + !HttpResponseRetryCodes.includes(response.message.statusCode)) { + // If not a retry code, return immediately instead of retrying + return response; + } + numTries += 1; + if (numTries < maxTries) { + yield response.readBody(); + yield this._performExponentialBackoff(numTries); + } + } while (numTries < maxTries); + return response; + }); + } + /** + * Needs to be called if keepAlive is set to true in request options. + */ + dispose() { + if (this._agent) { + this._agent.destroy(); + } + this._disposed = true; + } + /** + * Raw request. + * @param info + * @param data + */ + requestRaw(info, data) { + return __awaiter(this, void 0, void 0, function* () { + return new Promise((resolve, reject) => { + function callbackForResult(err, res) { + if (err) { + reject(err); + } + else if (!res) { + // If `err` is not passed, then `res` must be passed. + reject(new Error('Unknown error')); + } + else { + resolve(res); + } + } + this.requestRawWithCallback(info, data, callbackForResult); + }); + }); + } + /** + * Raw request with callback. + * @param info + * @param data + * @param onResult + */ + requestRawWithCallback(info, data, onResult) { + if (typeof data === 'string') { + if (!info.options.headers) { + info.options.headers = {}; + } + info.options.headers['Content-Length'] = Buffer.byteLength(data, 'utf8'); + } + let callbackCalled = false; + function handleResult(err, res) { + if (!callbackCalled) { + callbackCalled = true; + onResult(err, res); + } + } + const req = info.httpModule.request(info.options, (msg) => { + const res = new HttpClientResponse(msg); + handleResult(undefined, res); + }); + let socket; + req.on('socket', sock => { + socket = sock; + }); + // If we ever get disconnected, we want the socket to timeout eventually + req.setTimeout(this._socketTimeout || 3 * 60000, () => { + if (socket) { + socket.end(); + } + handleResult(new Error(`Request timeout: ${info.options.path}`)); + }); + req.on('error', function (err) { + // err has statusCode property + // res should have headers + handleResult(err); + }); + if (data && typeof data === 'string') { + req.write(data, 'utf8'); + } + if (data && typeof data !== 'string') { + data.on('close', function () { + req.end(); + }); + data.pipe(req); + } + else { + req.end(); + } + } + /** + * Gets an http agent. This function is useful when you need an http agent that handles + * routing through a proxy server - depending upon the url and proxy environment variables. + * @param serverUrl The server URL where the request will be sent. For example, https://api.github.com + */ + getAgent(serverUrl) { + const parsedUrl = new URL(serverUrl); + return this._getAgent(parsedUrl); + } + getAgentDispatcher(serverUrl) { + const parsedUrl = new URL(serverUrl); + const proxyUrl = pm.getProxyUrl(parsedUrl); + const useProxy = proxyUrl && proxyUrl.hostname; + if (!useProxy) { + return; + } + return this._getProxyAgentDispatcher(parsedUrl, proxyUrl); + } + _prepareRequest(method, requestUrl, headers) { + const info = {}; + info.parsedUrl = requestUrl; + const usingSsl = info.parsedUrl.protocol === 'https:'; + info.httpModule = usingSsl ? https : http; + const defaultPort = usingSsl ? 443 : 80; + info.options = {}; + info.options.host = info.parsedUrl.hostname; + info.options.port = info.parsedUrl.port + ? parseInt(info.parsedUrl.port) + : defaultPort; + info.options.path = + (info.parsedUrl.pathname || '') + (info.parsedUrl.search || ''); + info.options.method = method; + info.options.headers = this._mergeHeaders(headers); + if (this.userAgent != null) { + info.options.headers['user-agent'] = this.userAgent; + } + info.options.agent = this._getAgent(info.parsedUrl); + // gives handlers an opportunity to participate + if (this.handlers) { + for (const handler of this.handlers) { + handler.prepareRequest(info.options); + } + } + return info; + } + _mergeHeaders(headers) { + if (this.requestOptions && this.requestOptions.headers) { + return Object.assign({}, lowercaseKeys(this.requestOptions.headers), lowercaseKeys(headers || {})); + } + return lowercaseKeys(headers || {}); + } + _getExistingOrDefaultHeader(additionalHeaders, header, _default) { + let clientHeader; + if (this.requestOptions && this.requestOptions.headers) { + clientHeader = lowercaseKeys(this.requestOptions.headers)[header]; + } + return additionalHeaders[header] || clientHeader || _default; + } + _getAgent(parsedUrl) { + let agent; + const proxyUrl = pm.getProxyUrl(parsedUrl); + const useProxy = proxyUrl && proxyUrl.hostname; + if (this._keepAlive && useProxy) { + agent = this._proxyAgent; + } + if (!useProxy) { + agent = this._agent; + } + // if agent is already assigned use that agent. + if (agent) { + return agent; + } + const usingSsl = parsedUrl.protocol === 'https:'; + let maxSockets = 100; + if (this.requestOptions) { + maxSockets = this.requestOptions.maxSockets || http.globalAgent.maxSockets; + } + // This is `useProxy` again, but we need to check `proxyURl` directly for TypeScripts's flow analysis. + if (proxyUrl && proxyUrl.hostname) { + const agentOptions = { + maxSockets, + keepAlive: this._keepAlive, + proxy: Object.assign(Object.assign({}, ((proxyUrl.username || proxyUrl.password) && { + proxyAuth: `${proxyUrl.username}:${proxyUrl.password}` + })), { host: proxyUrl.hostname, port: proxyUrl.port }) + }; + let tunnelAgent; + const overHttps = proxyUrl.protocol === 'https:'; + if (usingSsl) { + tunnelAgent = overHttps ? tunnel.httpsOverHttps : tunnel.httpsOverHttp; + } + else { + tunnelAgent = overHttps ? tunnel.httpOverHttps : tunnel.httpOverHttp; + } + agent = tunnelAgent(agentOptions); + this._proxyAgent = agent; + } + // if tunneling agent isn't assigned create a new agent + if (!agent) { + const options = { keepAlive: this._keepAlive, maxSockets }; + agent = usingSsl ? new https.Agent(options) : new http.Agent(options); + this._agent = agent; + } + if (usingSsl && this._ignoreSslError) { + // we don't want to set NODE_TLS_REJECT_UNAUTHORIZED=0 since that will affect request for entire process + // http.RequestOptions doesn't expose a way to modify RequestOptions.agent.options + // we have to cast it to any and change it directly + agent.options = Object.assign(agent.options || {}, { + rejectUnauthorized: false + }); + } + return agent; + } + _getProxyAgentDispatcher(parsedUrl, proxyUrl) { + let proxyAgent; + if (this._keepAlive) { + proxyAgent = this._proxyAgentDispatcher; + } + // if agent is already assigned use that agent. + if (proxyAgent) { + return proxyAgent; + } + const usingSsl = parsedUrl.protocol === 'https:'; + proxyAgent = new undici_1.ProxyAgent(Object.assign({ uri: proxyUrl.href, pipelining: !this._keepAlive ? 0 : 1 }, ((proxyUrl.username || proxyUrl.password) && { + token: `Basic ${Buffer.from(`${proxyUrl.username}:${proxyUrl.password}`).toString('base64')}` + }))); + this._proxyAgentDispatcher = proxyAgent; + if (usingSsl && this._ignoreSslError) { + // we don't want to set NODE_TLS_REJECT_UNAUTHORIZED=0 since that will affect request for entire process + // http.RequestOptions doesn't expose a way to modify RequestOptions.agent.options + // we have to cast it to any and change it directly + proxyAgent.options = Object.assign(proxyAgent.options.requestTls || {}, { + rejectUnauthorized: false + }); + } + return proxyAgent; + } + _performExponentialBackoff(retryNumber) { + return __awaiter(this, void 0, void 0, function* () { + retryNumber = Math.min(ExponentialBackoffCeiling, retryNumber); + const ms = ExponentialBackoffTimeSlice * Math.pow(2, retryNumber); + return new Promise(resolve => setTimeout(() => resolve(), ms)); + }); + } + _processResponse(res, options) { + return __awaiter(this, void 0, void 0, function* () { + return new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () { + const statusCode = res.message.statusCode || 0; + const response = { + statusCode, + result: null, + headers: {} + }; + // not found leads to null obj returned + if (statusCode === HttpCodes.NotFound) { + resolve(response); + } + // get the result from the body + function dateTimeDeserializer(key, value) { + if (typeof value === 'string') { + const a = new Date(value); + if (!isNaN(a.valueOf())) { + return a; + } + } + return value; + } + let obj; + let contents; + try { + contents = yield res.readBody(); + if (contents && contents.length > 0) { + if (options && options.deserializeDates) { + obj = JSON.parse(contents, dateTimeDeserializer); + } + else { + obj = JSON.parse(contents); + } + response.result = obj; + } + response.headers = res.message.headers; + } + catch (err) { + // Invalid resource (contents not json); leaving result obj null + } + // note that 3xx redirects are handled by the http layer. + if (statusCode > 299) { + let msg; + // if exception/error in body, attempt to get better error + if (obj && obj.message) { + msg = obj.message; + } + else if (contents && contents.length > 0) { + // it may be the case that the exception is in the body message as string + msg = contents; + } + else { + msg = `Failed request: (${statusCode})`; + } + const err = new HttpClientError(msg, statusCode); + err.result = response.result; + reject(err); + } + else { + resolve(response); + } + })); + }); + } +} +exports.HttpClient = HttpClient; +const lowercaseKeys = (obj) => Object.keys(obj).reduce((c, k) => ((c[k.toLowerCase()] = obj[k]), c), {}); +//# sourceMappingURL=index.js.map + +/***/ }), + +/***/ 54988: +/***/ ((__unused_webpack_module, exports) => { + +"use strict"; + +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.checkBypass = exports.getProxyUrl = void 0; +function getProxyUrl(reqUrl) { + const usingSsl = reqUrl.protocol === 'https:'; + if (checkBypass(reqUrl)) { + return undefined; + } + const proxyVar = (() => { + if (usingSsl) { + return process.env['https_proxy'] || process.env['HTTPS_PROXY']; + } + else { + return process.env['http_proxy'] || process.env['HTTP_PROXY']; + } + })(); + if (proxyVar) { + try { + return new DecodedURL(proxyVar); + } + catch (_a) { + if (!proxyVar.startsWith('http://') && !proxyVar.startsWith('https://')) + return new DecodedURL(`http://${proxyVar}`); + } + } + else { + return undefined; + } +} +exports.getProxyUrl = getProxyUrl; +function checkBypass(reqUrl) { + if (!reqUrl.hostname) { + return false; + } + const reqHost = reqUrl.hostname; + if (isLoopbackAddress(reqHost)) { + return true; + } + const noProxy = process.env['no_proxy'] || process.env['NO_PROXY'] || ''; + if (!noProxy) { + return false; + } + // Determine the request port + let reqPort; + if (reqUrl.port) { + reqPort = Number(reqUrl.port); + } + else if (reqUrl.protocol === 'http:') { + reqPort = 80; + } + else if (reqUrl.protocol === 'https:') { + reqPort = 443; + } + // Format the request hostname and hostname with port + const upperReqHosts = [reqUrl.hostname.toUpperCase()]; + if (typeof reqPort === 'number') { + upperReqHosts.push(`${upperReqHosts[0]}:${reqPort}`); + } + // Compare request host against noproxy + for (const upperNoProxyItem of noProxy + .split(',') + .map(x => x.trim().toUpperCase()) + .filter(x => x)) { + if (upperNoProxyItem === '*' || + upperReqHosts.some(x => x === upperNoProxyItem || + x.endsWith(`.${upperNoProxyItem}`) || + (upperNoProxyItem.startsWith('.') && + x.endsWith(`${upperNoProxyItem}`)))) { + return true; + } + } + return false; +} +exports.checkBypass = checkBypass; +function isLoopbackAddress(host) { + const hostLower = host.toLowerCase(); + return (hostLower === 'localhost' || + hostLower.startsWith('127.') || + hostLower.startsWith('[::1]') || + hostLower.startsWith('[0:0:0:0:0:0:0:1]')); +} +class DecodedURL extends URL { + constructor(url, base) { + super(url, base); + this._decodedUsername = decodeURIComponent(super.username); + this._decodedPassword = decodeURIComponent(super.password); + } + get username() { + return this._decodedUsername; + } + get password() { + return this._decodedPassword; + } +} +//# sourceMappingURL=proxy.js.map + +/***/ }), + +/***/ 75207: +/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) { + +"use strict"; + +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var _a; +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.getCmdPath = exports.tryGetExecutablePath = exports.isRooted = exports.isDirectory = exports.exists = exports.READONLY = exports.UV_FS_O_EXLOCK = exports.IS_WINDOWS = exports.unlink = exports.symlink = exports.stat = exports.rmdir = exports.rm = exports.rename = exports.readlink = exports.readdir = exports.open = exports.mkdir = exports.lstat = exports.copyFile = exports.chmod = void 0; +const fs = __importStar(__nccwpck_require__(79896)); +const path = __importStar(__nccwpck_require__(16928)); +_a = fs.promises +// export const {open} = 'fs' +, exports.chmod = _a.chmod, exports.copyFile = _a.copyFile, exports.lstat = _a.lstat, exports.mkdir = _a.mkdir, exports.open = _a.open, exports.readdir = _a.readdir, exports.readlink = _a.readlink, exports.rename = _a.rename, exports.rm = _a.rm, exports.rmdir = _a.rmdir, exports.stat = _a.stat, exports.symlink = _a.symlink, exports.unlink = _a.unlink; +// export const {open} = 'fs' +exports.IS_WINDOWS = process.platform === 'win32'; +// See https://github.com/nodejs/node/blob/d0153aee367422d0858105abec186da4dff0a0c5/deps/uv/include/uv/win.h#L691 +exports.UV_FS_O_EXLOCK = 0x10000000; +exports.READONLY = fs.constants.O_RDONLY; +function exists(fsPath) { + return __awaiter(this, void 0, void 0, function* () { + try { + yield exports.stat(fsPath); + } + catch (err) { + if (err.code === 'ENOENT') { + return false; + } + throw err; + } + return true; + }); +} +exports.exists = exists; +function isDirectory(fsPath, useStat = false) { + return __awaiter(this, void 0, void 0, function* () { + const stats = useStat ? yield exports.stat(fsPath) : yield exports.lstat(fsPath); + return stats.isDirectory(); + }); +} +exports.isDirectory = isDirectory; +/** + * On OSX/Linux, true if path starts with '/'. On Windows, true for paths like: + * \, \hello, \\hello\share, C:, and C:\hello (and corresponding alternate separator cases). + */ +function isRooted(p) { + p = normalizeSeparators(p); + if (!p) { + throw new Error('isRooted() parameter "p" cannot be empty'); + } + if (exports.IS_WINDOWS) { + return (p.startsWith('\\') || /^[A-Z]:/i.test(p) // e.g. \ or \hello or \\hello + ); // e.g. C: or C:\hello + } + return p.startsWith('/'); +} +exports.isRooted = isRooted; +/** + * Best effort attempt to determine whether a file exists and is executable. + * @param filePath file path to check + * @param extensions additional file extensions to try + * @return if file exists and is executable, returns the file path. otherwise empty string. + */ +function tryGetExecutablePath(filePath, extensions) { + return __awaiter(this, void 0, void 0, function* () { + let stats = undefined; + try { + // test file exists + stats = yield exports.stat(filePath); + } + catch (err) { + if (err.code !== 'ENOENT') { + // eslint-disable-next-line no-console + console.log(`Unexpected error attempting to determine if executable file exists '${filePath}': ${err}`); + } + } + if (stats && stats.isFile()) { + if (exports.IS_WINDOWS) { + // on Windows, test for valid extension + const upperExt = path.extname(filePath).toUpperCase(); + if (extensions.some(validExt => validExt.toUpperCase() === upperExt)) { + return filePath; + } + } + else { + if (isUnixExecutable(stats)) { + return filePath; + } + } + } + // try each extension + const originalFilePath = filePath; + for (const extension of extensions) { + filePath = originalFilePath + extension; + stats = undefined; + try { + stats = yield exports.stat(filePath); + } + catch (err) { + if (err.code !== 'ENOENT') { + // eslint-disable-next-line no-console + console.log(`Unexpected error attempting to determine if executable file exists '${filePath}': ${err}`); + } + } + if (stats && stats.isFile()) { + if (exports.IS_WINDOWS) { + // preserve the case of the actual file (since an extension was appended) + try { + const directory = path.dirname(filePath); + const upperName = path.basename(filePath).toUpperCase(); + for (const actualName of yield exports.readdir(directory)) { + if (upperName === actualName.toUpperCase()) { + filePath = path.join(directory, actualName); + break; + } + } + } + catch (err) { + // eslint-disable-next-line no-console + console.log(`Unexpected error attempting to determine the actual case of the file '${filePath}': ${err}`); + } + return filePath; + } + else { + if (isUnixExecutable(stats)) { + return filePath; + } + } + } + } + return ''; + }); +} +exports.tryGetExecutablePath = tryGetExecutablePath; +function normalizeSeparators(p) { + p = p || ''; + if (exports.IS_WINDOWS) { + // convert slashes on Windows + p = p.replace(/\//g, '\\'); + // remove redundant slashes + return p.replace(/\\\\+/g, '\\'); + } + // remove redundant slashes + return p.replace(/\/\/+/g, '/'); +} +// on Mac/Linux, test the execute bit +// R W X R W X R W X +// 256 128 64 32 16 8 4 2 1 +function isUnixExecutable(stats) { + return ((stats.mode & 1) > 0 || + ((stats.mode & 8) > 0 && stats.gid === process.getgid()) || + ((stats.mode & 64) > 0 && stats.uid === process.getuid())); +} +// Get the path of cmd.exe in windows +function getCmdPath() { + var _a; + return (_a = process.env['COMSPEC']) !== null && _a !== void 0 ? _a : `cmd.exe`; +} +exports.getCmdPath = getCmdPath; +//# sourceMappingURL=io-util.js.map + +/***/ }), + +/***/ 94994: +/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) { + +"use strict"; + +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.findInPath = exports.which = exports.mkdirP = exports.rmRF = exports.mv = exports.cp = void 0; +const assert_1 = __nccwpck_require__(42613); +const path = __importStar(__nccwpck_require__(16928)); +const ioUtil = __importStar(__nccwpck_require__(75207)); +/** + * Copies a file or folder. + * Based off of shelljs - https://github.com/shelljs/shelljs/blob/9237f66c52e5daa40458f94f9565e18e8132f5a6/src/cp.js + * + * @param source source path + * @param dest destination path + * @param options optional. See CopyOptions. + */ +function cp(source, dest, options = {}) { + return __awaiter(this, void 0, void 0, function* () { + const { force, recursive, copySourceDirectory } = readCopyOptions(options); + const destStat = (yield ioUtil.exists(dest)) ? yield ioUtil.stat(dest) : null; + // Dest is an existing file, but not forcing + if (destStat && destStat.isFile() && !force) { + return; + } + // If dest is an existing directory, should copy inside. + const newDest = destStat && destStat.isDirectory() && copySourceDirectory + ? path.join(dest, path.basename(source)) + : dest; + if (!(yield ioUtil.exists(source))) { + throw new Error(`no such file or directory: ${source}`); + } + const sourceStat = yield ioUtil.stat(source); + if (sourceStat.isDirectory()) { + if (!recursive) { + throw new Error(`Failed to copy. ${source} is a directory, but tried to copy without recursive flag.`); + } + else { + yield cpDirRecursive(source, newDest, 0, force); + } + } + else { + if (path.relative(source, newDest) === '') { + // a file cannot be copied to itself + throw new Error(`'${newDest}' and '${source}' are the same file`); + } + yield copyFile(source, newDest, force); + } + }); +} +exports.cp = cp; +/** + * Moves a path. + * + * @param source source path + * @param dest destination path + * @param options optional. See MoveOptions. + */ +function mv(source, dest, options = {}) { + return __awaiter(this, void 0, void 0, function* () { + if (yield ioUtil.exists(dest)) { + let destExists = true; + if (yield ioUtil.isDirectory(dest)) { + // If dest is directory copy src into dest + dest = path.join(dest, path.basename(source)); + destExists = yield ioUtil.exists(dest); + } + if (destExists) { + if (options.force == null || options.force) { + yield rmRF(dest); + } + else { + throw new Error('Destination already exists'); + } + } + } + yield mkdirP(path.dirname(dest)); + yield ioUtil.rename(source, dest); + }); +} +exports.mv = mv; +/** + * Remove a path recursively with force + * + * @param inputPath path to remove + */ +function rmRF(inputPath) { + return __awaiter(this, void 0, void 0, function* () { + if (ioUtil.IS_WINDOWS) { + // Check for invalid characters + // https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file + if (/[*"<>|]/.test(inputPath)) { + throw new Error('File path must not contain `*`, `"`, `<`, `>` or `|` on Windows'); + } + } + try { + // note if path does not exist, error is silent + yield ioUtil.rm(inputPath, { + force: true, + maxRetries: 3, + recursive: true, + retryDelay: 300 + }); + } + catch (err) { + throw new Error(`File was unable to be removed ${err}`); + } + }); +} +exports.rmRF = rmRF; +/** + * Make a directory. Creates the full path with folders in between + * Will throw if it fails + * + * @param fsPath path to create + * @returns Promise + */ +function mkdirP(fsPath) { + return __awaiter(this, void 0, void 0, function* () { + assert_1.ok(fsPath, 'a path argument must be provided'); + yield ioUtil.mkdir(fsPath, { recursive: true }); + }); +} +exports.mkdirP = mkdirP; +/** + * Returns path of a tool had the tool actually been invoked. Resolves via paths. + * If you check and the tool does not exist, it will throw. + * + * @param tool name of the tool + * @param check whether to check if tool exists + * @returns Promise path to tool + */ +function which(tool, check) { + return __awaiter(this, void 0, void 0, function* () { + if (!tool) { + throw new Error("parameter 'tool' is required"); + } + // recursive when check=true + if (check) { + const result = yield which(tool, false); + if (!result) { + if (ioUtil.IS_WINDOWS) { + throw new Error(`Unable to locate executable file: ${tool}. Please verify either the file path exists or the file can be found within a directory specified by the PATH environment variable. Also verify the file has a valid extension for an executable file.`); + } + else { + throw new Error(`Unable to locate executable file: ${tool}. Please verify either the file path exists or the file can be found within a directory specified by the PATH environment variable. Also check the file mode to verify the file is executable.`); + } + } + return result; + } + const matches = yield findInPath(tool); + if (matches && matches.length > 0) { + return matches[0]; + } + return ''; + }); +} +exports.which = which; +/** + * Returns a list of all occurrences of the given tool on the system path. + * + * @returns Promise the paths of the tool + */ +function findInPath(tool) { + return __awaiter(this, void 0, void 0, function* () { + if (!tool) { + throw new Error("parameter 'tool' is required"); + } + // build the list of extensions to try + const extensions = []; + if (ioUtil.IS_WINDOWS && process.env['PATHEXT']) { + for (const extension of process.env['PATHEXT'].split(path.delimiter)) { + if (extension) { + extensions.push(extension); + } + } + } + // if it's rooted, return it if exists. otherwise return empty. + if (ioUtil.isRooted(tool)) { + const filePath = yield ioUtil.tryGetExecutablePath(tool, extensions); + if (filePath) { + return [filePath]; + } + return []; + } + // if any path separators, return empty + if (tool.includes(path.sep)) { + return []; + } + // build the list of directories + // + // Note, technically "where" checks the current directory on Windows. From a toolkit perspective, + // it feels like we should not do this. Checking the current directory seems like more of a use + // case of a shell, and the which() function exposed by the toolkit should strive for consistency + // across platforms. + const directories = []; + if (process.env.PATH) { + for (const p of process.env.PATH.split(path.delimiter)) { + if (p) { + directories.push(p); + } + } + } + // find all matches + const matches = []; + for (const directory of directories) { + const filePath = yield ioUtil.tryGetExecutablePath(path.join(directory, tool), extensions); + if (filePath) { + matches.push(filePath); + } + } + return matches; + }); +} +exports.findInPath = findInPath; +function readCopyOptions(options) { + const force = options.force == null ? true : options.force; + const recursive = Boolean(options.recursive); + const copySourceDirectory = options.copySourceDirectory == null + ? true + : Boolean(options.copySourceDirectory); + return { force, recursive, copySourceDirectory }; +} +function cpDirRecursive(sourceDir, destDir, currentDepth, force) { + return __awaiter(this, void 0, void 0, function* () { + // Ensure there is not a run away recursive copy + if (currentDepth >= 255) + return; + currentDepth++; + yield mkdirP(destDir); + const files = yield ioUtil.readdir(sourceDir); + for (const fileName of files) { + const srcFile = `${sourceDir}/${fileName}`; + const destFile = `${destDir}/${fileName}`; + const srcFileStat = yield ioUtil.lstat(srcFile); + if (srcFileStat.isDirectory()) { + // Recurse + yield cpDirRecursive(srcFile, destFile, currentDepth, force); + } + else { + yield copyFile(srcFile, destFile, force); + } + } + // Change the mode for the newly created directory + yield ioUtil.chmod(destDir, (yield ioUtil.stat(sourceDir)).mode); + }); +} +// Buffered file copy +function copyFile(srcFile, destFile, force) { + return __awaiter(this, void 0, void 0, function* () { + if ((yield ioUtil.lstat(srcFile)).isSymbolicLink()) { + // unlink/re-link it + try { + yield ioUtil.lstat(destFile); + yield ioUtil.unlink(destFile); + } + catch (e) { + // Try to override file permission + if (e.code === 'EPERM') { + yield ioUtil.chmod(destFile, '0666'); + yield ioUtil.unlink(destFile); + } + // other errors = it doesn't exist, no work to do + } + // Copy over symlink + const symlinkFull = yield ioUtil.readlink(srcFile); + yield ioUtil.symlink(symlinkFull, destFile, ioUtil.IS_WINDOWS ? 'junction' : null); + } + else if (!(yield ioUtil.exists(destFile)) || force) { + yield ioUtil.copyFile(srcFile, destFile); + } + }); +} +//# sourceMappingURL=io.js.map + +/***/ }), + +/***/ 46268: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports.makeWeakCache = makeWeakCache; +exports.makeWeakCacheSync = makeWeakCacheSync; +exports.makeStrongCache = makeStrongCache; +exports.makeStrongCacheSync = makeStrongCacheSync; +exports.assertSimpleType = assertSimpleType; + +function _gensync() { + const data = _interopRequireDefault(__nccwpck_require__(9621)); + + _gensync = function () { + return data; + }; + + return data; +} + +var _async = __nccwpck_require__(87360); + +var _util = __nccwpck_require__(54493); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +const synchronize = gen => { + return (0, _gensync().default)(gen).sync; +}; + +function* genTrue(data) { + return true; +} + +function makeWeakCache(handler) { + return makeCachedFunction(WeakMap, handler); +} + +function makeWeakCacheSync(handler) { + return synchronize(makeWeakCache(handler)); +} + +function makeStrongCache(handler) { + return makeCachedFunction(Map, handler); +} + +function makeStrongCacheSync(handler) { + return synchronize(makeStrongCache(handler)); +} + +function makeCachedFunction(CallCache, handler) { + const callCacheSync = new CallCache(); + const callCacheAsync = new CallCache(); + const futureCache = new CallCache(); + return function* cachedFunction(arg, data) { + const asyncContext = yield* (0, _async.isAsync)(); + const callCache = asyncContext ? callCacheAsync : callCacheSync; + const cached = yield* getCachedValueOrWait(asyncContext, callCache, futureCache, arg, data); + if (cached.valid) return cached.value; + const cache = new CacheConfigurator(data); + const handlerResult = handler(arg, cache); + let finishLock; + let value; + + if ((0, _util.isIterableIterator)(handlerResult)) { + const gen = handlerResult; + value = yield* (0, _async.onFirstPause)(gen, () => { + finishLock = setupAsyncLocks(cache, futureCache, arg); + }); + } else { + value = handlerResult; + } + + updateFunctionCache(callCache, cache, arg, value); + + if (finishLock) { + futureCache.delete(arg); + finishLock.release(value); + } + + return value; + }; +} + +function* getCachedValue(cache, arg, data) { + const cachedValue = cache.get(arg); + + if (cachedValue) { + for (const { + value, + valid + } of cachedValue) { + if (yield* valid(data)) return { + valid: true, + value + }; + } + } + + return { + valid: false, + value: null + }; +} + +function* getCachedValueOrWait(asyncContext, callCache, futureCache, arg, data) { + const cached = yield* getCachedValue(callCache, arg, data); + + if (cached.valid) { + return cached; + } + + if (asyncContext) { + const cached = yield* getCachedValue(futureCache, arg, data); + + if (cached.valid) { + const value = yield* (0, _async.waitFor)(cached.value.promise); + return { + valid: true, + value + }; + } + } + + return { + valid: false, + value: null + }; +} + +function setupAsyncLocks(config, futureCache, arg) { + const finishLock = new Lock(); + updateFunctionCache(futureCache, config, arg, finishLock); + return finishLock; +} + +function updateFunctionCache(cache, config, arg, value) { + if (!config.configured()) config.forever(); + let cachedValue = cache.get(arg); + config.deactivate(); + + switch (config.mode()) { + case "forever": + cachedValue = [{ + value, + valid: genTrue + }]; + cache.set(arg, cachedValue); + break; + + case "invalidate": + cachedValue = [{ + value, + valid: config.validator() + }]; + cache.set(arg, cachedValue); + break; + + case "valid": + if (cachedValue) { + cachedValue.push({ + value, + valid: config.validator() + }); + } else { + cachedValue = [{ + value, + valid: config.validator() + }]; + cache.set(arg, cachedValue); + } + + } +} + +class CacheConfigurator { + constructor(data) { + this._active = true; + this._never = false; + this._forever = false; + this._invalidate = false; + this._configured = false; + this._pairs = []; + this._data = void 0; + this._data = data; + } + + simple() { + return makeSimpleConfigurator(this); + } + + mode() { + if (this._never) return "never"; + if (this._forever) return "forever"; + if (this._invalidate) return "invalidate"; + return "valid"; + } + + forever() { + if (!this._active) { + throw new Error("Cannot change caching after evaluation has completed."); + } + + if (this._never) { + throw new Error("Caching has already been configured with .never()"); + } + + this._forever = true; + this._configured = true; + } + + never() { + if (!this._active) { + throw new Error("Cannot change caching after evaluation has completed."); + } + + if (this._forever) { + throw new Error("Caching has already been configured with .forever()"); + } + + this._never = true; + this._configured = true; + } + + using(handler) { + if (!this._active) { + throw new Error("Cannot change caching after evaluation has completed."); + } + + if (this._never || this._forever) { + throw new Error("Caching has already been configured with .never or .forever()"); + } + + this._configured = true; + const key = handler(this._data); + const fn = (0, _async.maybeAsync)(handler, `You appear to be using an async cache handler, but Babel has been called synchronously`); + + if ((0, _async.isThenable)(key)) { + return key.then(key => { + this._pairs.push([key, fn]); + + return key; + }); + } + + this._pairs.push([key, fn]); + + return key; + } + + invalidate(handler) { + this._invalidate = true; + return this.using(handler); + } + + validator() { + const pairs = this._pairs; + return function* (data) { + for (const [key, fn] of pairs) { + if (key !== (yield* fn(data))) return false; + } + + return true; + }; + } + + deactivate() { + this._active = false; + } + + configured() { + return this._configured; + } + +} + +function makeSimpleConfigurator(cache) { + function cacheFn(val) { + if (typeof val === "boolean") { + if (val) cache.forever();else cache.never(); + return; + } + + return cache.using(() => assertSimpleType(val())); + } + + cacheFn.forever = () => cache.forever(); + + cacheFn.never = () => cache.never(); + + cacheFn.using = cb => cache.using(() => assertSimpleType(cb())); + + cacheFn.invalidate = cb => cache.invalidate(() => assertSimpleType(cb())); + + return cacheFn; +} + +function assertSimpleType(value) { + if ((0, _async.isThenable)(value)) { + throw new Error(`You appear to be using an async cache handler, ` + `which your current version of Babel does not support. ` + `We may add support for this in the future, ` + `but if you're on the most recent version of @babel/core and still ` + `seeing this error, then you'll need to synchronously handle your caching logic.`); + } + + if (value != null && typeof value !== "string" && typeof value !== "boolean" && typeof value !== "number") { + throw new Error("Cache keys must be either string, boolean, number, null, or undefined."); + } + + return value; +} + +class Lock { + constructor() { + this.released = false; + this.promise = void 0; + this._resolve = void 0; + this.promise = new Promise(resolve => { + this._resolve = resolve; + }); + } + + release(value) { + this.released = true; + + this._resolve(value); + } + +} + +/***/ }), + +/***/ 70835: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports.buildPresetChain = buildPresetChain; +exports.buildRootChain = buildRootChain; +exports.buildPresetChainWalker = void 0; + +function _path() { + const data = _interopRequireDefault(__nccwpck_require__(16928)); + + _path = function () { + return data; + }; + + return data; +} + +function _debug() { + const data = _interopRequireDefault(__nccwpck_require__(2830)); + + _debug = function () { + return data; + }; + + return data; +} + +var _options = __nccwpck_require__(44743); + +var _patternToRegex = _interopRequireDefault(__nccwpck_require__(79619)); + +var _printer = __nccwpck_require__(30333); + +var _files = __nccwpck_require__(37217); + +var _caching = __nccwpck_require__(46268); + +var _configDescriptors = __nccwpck_require__(2420); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +const debug = (0, _debug().default)("babel:config:config-chain"); + +function* buildPresetChain(arg, context) { + const chain = yield* buildPresetChainWalker(arg, context); + if (!chain) return null; + return { + plugins: dedupDescriptors(chain.plugins), + presets: dedupDescriptors(chain.presets), + options: chain.options.map(o => normalizeOptions(o)), + files: new Set() + }; +} + +const buildPresetChainWalker = makeChainWalker({ + root: preset => loadPresetDescriptors(preset), + env: (preset, envName) => loadPresetEnvDescriptors(preset)(envName), + overrides: (preset, index) => loadPresetOverridesDescriptors(preset)(index), + overridesEnv: (preset, index, envName) => loadPresetOverridesEnvDescriptors(preset)(index)(envName), + createLogger: () => () => {} +}); +exports.buildPresetChainWalker = buildPresetChainWalker; +const loadPresetDescriptors = (0, _caching.makeWeakCacheSync)(preset => buildRootDescriptors(preset, preset.alias, _configDescriptors.createUncachedDescriptors)); +const loadPresetEnvDescriptors = (0, _caching.makeWeakCacheSync)(preset => (0, _caching.makeStrongCacheSync)(envName => buildEnvDescriptors(preset, preset.alias, _configDescriptors.createUncachedDescriptors, envName))); +const loadPresetOverridesDescriptors = (0, _caching.makeWeakCacheSync)(preset => (0, _caching.makeStrongCacheSync)(index => buildOverrideDescriptors(preset, preset.alias, _configDescriptors.createUncachedDescriptors, index))); +const loadPresetOverridesEnvDescriptors = (0, _caching.makeWeakCacheSync)(preset => (0, _caching.makeStrongCacheSync)(index => (0, _caching.makeStrongCacheSync)(envName => buildOverrideEnvDescriptors(preset, preset.alias, _configDescriptors.createUncachedDescriptors, index, envName)))); + +function* buildRootChain(opts, context) { + let configReport, babelRcReport; + const programmaticLogger = new _printer.ConfigPrinter(); + const programmaticChain = yield* loadProgrammaticChain({ + options: opts, + dirname: context.cwd + }, context, undefined, programmaticLogger); + if (!programmaticChain) return null; + const programmaticReport = programmaticLogger.output(); + let configFile; + + if (typeof opts.configFile === "string") { + configFile = yield* (0, _files.loadConfig)(opts.configFile, context.cwd, context.envName, context.caller); + } else if (opts.configFile !== false) { + configFile = yield* (0, _files.findRootConfig)(context.root, context.envName, context.caller); + } + + let { + babelrc, + babelrcRoots + } = opts; + let babelrcRootsDirectory = context.cwd; + const configFileChain = emptyChain(); + const configFileLogger = new _printer.ConfigPrinter(); + + if (configFile) { + const validatedFile = validateConfigFile(configFile); + const result = yield* loadFileChain(validatedFile, context, undefined, configFileLogger); + if (!result) return null; + configReport = configFileLogger.output(); + + if (babelrc === undefined) { + babelrc = validatedFile.options.babelrc; + } + + if (babelrcRoots === undefined) { + babelrcRootsDirectory = validatedFile.dirname; + babelrcRoots = validatedFile.options.babelrcRoots; + } + + mergeChain(configFileChain, result); + } + + const pkgData = typeof context.filename === "string" ? yield* (0, _files.findPackageData)(context.filename) : null; + let ignoreFile, babelrcFile; + let isIgnored = false; + const fileChain = emptyChain(); + + if ((babelrc === true || babelrc === undefined) && pkgData && babelrcLoadEnabled(context, pkgData, babelrcRoots, babelrcRootsDirectory)) { + ({ + ignore: ignoreFile, + config: babelrcFile + } = yield* (0, _files.findRelativeConfig)(pkgData, context.envName, context.caller)); + + if (ignoreFile) { + fileChain.files.add(ignoreFile.filepath); + } + + if (ignoreFile && shouldIgnore(context, ignoreFile.ignore, null, ignoreFile.dirname)) { + isIgnored = true; + } + + if (babelrcFile && !isIgnored) { + const validatedFile = validateBabelrcFile(babelrcFile); + const babelrcLogger = new _printer.ConfigPrinter(); + const result = yield* loadFileChain(validatedFile, context, undefined, babelrcLogger); + + if (!result) { + isIgnored = true; + } else { + babelRcReport = babelrcLogger.output(); + mergeChain(fileChain, result); + } + } + + if (babelrcFile && isIgnored) { + fileChain.files.add(babelrcFile.filepath); + } + } + + if (context.showConfig) { + console.log(`Babel configs on "${context.filename}" (ascending priority):\n` + [configReport, babelRcReport, programmaticReport].filter(x => !!x).join("\n\n")); + return null; + } + + const chain = mergeChain(mergeChain(mergeChain(emptyChain(), configFileChain), fileChain), programmaticChain); + return { + plugins: isIgnored ? [] : dedupDescriptors(chain.plugins), + presets: isIgnored ? [] : dedupDescriptors(chain.presets), + options: isIgnored ? [] : chain.options.map(o => normalizeOptions(o)), + fileHandling: isIgnored ? "ignored" : "transpile", + ignore: ignoreFile || undefined, + babelrc: babelrcFile || undefined, + config: configFile || undefined, + files: chain.files + }; +} + +function babelrcLoadEnabled(context, pkgData, babelrcRoots, babelrcRootsDirectory) { + if (typeof babelrcRoots === "boolean") return babelrcRoots; + const absoluteRoot = context.root; + + if (babelrcRoots === undefined) { + return pkgData.directories.indexOf(absoluteRoot) !== -1; + } + + let babelrcPatterns = babelrcRoots; + if (!Array.isArray(babelrcPatterns)) babelrcPatterns = [babelrcPatterns]; + babelrcPatterns = babelrcPatterns.map(pat => { + return typeof pat === "string" ? _path().default.resolve(babelrcRootsDirectory, pat) : pat; + }); + + if (babelrcPatterns.length === 1 && babelrcPatterns[0] === absoluteRoot) { + return pkgData.directories.indexOf(absoluteRoot) !== -1; + } + + return babelrcPatterns.some(pat => { + if (typeof pat === "string") { + pat = (0, _patternToRegex.default)(pat, babelrcRootsDirectory); + } + + return pkgData.directories.some(directory => { + return matchPattern(pat, babelrcRootsDirectory, directory, context); + }); + }); +} + +const validateConfigFile = (0, _caching.makeWeakCacheSync)(file => ({ + filepath: file.filepath, + dirname: file.dirname, + options: (0, _options.validate)("configfile", file.options) +})); +const validateBabelrcFile = (0, _caching.makeWeakCacheSync)(file => ({ + filepath: file.filepath, + dirname: file.dirname, + options: (0, _options.validate)("babelrcfile", file.options) +})); +const validateExtendFile = (0, _caching.makeWeakCacheSync)(file => ({ + filepath: file.filepath, + dirname: file.dirname, + options: (0, _options.validate)("extendsfile", file.options) +})); +const loadProgrammaticChain = makeChainWalker({ + root: input => buildRootDescriptors(input, "base", _configDescriptors.createCachedDescriptors), + env: (input, envName) => buildEnvDescriptors(input, "base", _configDescriptors.createCachedDescriptors, envName), + overrides: (input, index) => buildOverrideDescriptors(input, "base", _configDescriptors.createCachedDescriptors, index), + overridesEnv: (input, index, envName) => buildOverrideEnvDescriptors(input, "base", _configDescriptors.createCachedDescriptors, index, envName), + createLogger: (input, context, baseLogger) => buildProgrammaticLogger(input, context, baseLogger) +}); +const loadFileChainWalker = makeChainWalker({ + root: file => loadFileDescriptors(file), + env: (file, envName) => loadFileEnvDescriptors(file)(envName), + overrides: (file, index) => loadFileOverridesDescriptors(file)(index), + overridesEnv: (file, index, envName) => loadFileOverridesEnvDescriptors(file)(index)(envName), + createLogger: (file, context, baseLogger) => buildFileLogger(file.filepath, context, baseLogger) +}); + +function* loadFileChain(input, context, files, baseLogger) { + const chain = yield* loadFileChainWalker(input, context, files, baseLogger); + + if (chain) { + chain.files.add(input.filepath); + } + + return chain; +} + +const loadFileDescriptors = (0, _caching.makeWeakCacheSync)(file => buildRootDescriptors(file, file.filepath, _configDescriptors.createUncachedDescriptors)); +const loadFileEnvDescriptors = (0, _caching.makeWeakCacheSync)(file => (0, _caching.makeStrongCacheSync)(envName => buildEnvDescriptors(file, file.filepath, _configDescriptors.createUncachedDescriptors, envName))); +const loadFileOverridesDescriptors = (0, _caching.makeWeakCacheSync)(file => (0, _caching.makeStrongCacheSync)(index => buildOverrideDescriptors(file, file.filepath, _configDescriptors.createUncachedDescriptors, index))); +const loadFileOverridesEnvDescriptors = (0, _caching.makeWeakCacheSync)(file => (0, _caching.makeStrongCacheSync)(index => (0, _caching.makeStrongCacheSync)(envName => buildOverrideEnvDescriptors(file, file.filepath, _configDescriptors.createUncachedDescriptors, index, envName)))); + +function buildFileLogger(filepath, context, baseLogger) { + if (!baseLogger) { + return () => {}; + } + + return baseLogger.configure(context.showConfig, _printer.ChainFormatter.Config, { + filepath + }); +} + +function buildRootDescriptors({ + dirname, + options +}, alias, descriptors) { + return descriptors(dirname, options, alias); +} + +function buildProgrammaticLogger(_, context, baseLogger) { + var _context$caller; + + if (!baseLogger) { + return () => {}; + } + + return baseLogger.configure(context.showConfig, _printer.ChainFormatter.Programmatic, { + callerName: (_context$caller = context.caller) == null ? void 0 : _context$caller.name + }); +} + +function buildEnvDescriptors({ + dirname, + options +}, alias, descriptors, envName) { + const opts = options.env && options.env[envName]; + return opts ? descriptors(dirname, opts, `${alias}.env["${envName}"]`) : null; +} + +function buildOverrideDescriptors({ + dirname, + options +}, alias, descriptors, index) { + const opts = options.overrides && options.overrides[index]; + if (!opts) throw new Error("Assertion failure - missing override"); + return descriptors(dirname, opts, `${alias}.overrides[${index}]`); +} + +function buildOverrideEnvDescriptors({ + dirname, + options +}, alias, descriptors, index, envName) { + const override = options.overrides && options.overrides[index]; + if (!override) throw new Error("Assertion failure - missing override"); + const opts = override.env && override.env[envName]; + return opts ? descriptors(dirname, opts, `${alias}.overrides[${index}].env["${envName}"]`) : null; +} + +function makeChainWalker({ + root, + env, + overrides, + overridesEnv, + createLogger +}) { + return function* (input, context, files = new Set(), baseLogger) { + const { + dirname + } = input; + const flattenedConfigs = []; + const rootOpts = root(input); + + if (configIsApplicable(rootOpts, dirname, context)) { + flattenedConfigs.push({ + config: rootOpts, + envName: undefined, + index: undefined + }); + const envOpts = env(input, context.envName); + + if (envOpts && configIsApplicable(envOpts, dirname, context)) { + flattenedConfigs.push({ + config: envOpts, + envName: context.envName, + index: undefined + }); + } + + (rootOpts.options.overrides || []).forEach((_, index) => { + const overrideOps = overrides(input, index); + + if (configIsApplicable(overrideOps, dirname, context)) { + flattenedConfigs.push({ + config: overrideOps, + index, + envName: undefined + }); + const overrideEnvOpts = overridesEnv(input, index, context.envName); + + if (overrideEnvOpts && configIsApplicable(overrideEnvOpts, dirname, context)) { + flattenedConfigs.push({ + config: overrideEnvOpts, + index, + envName: context.envName + }); + } + } + }); + } + + if (flattenedConfigs.some(({ + config: { + options: { + ignore, + only + } + } + }) => shouldIgnore(context, ignore, only, dirname))) { + return null; + } + + const chain = emptyChain(); + const logger = createLogger(input, context, baseLogger); + + for (const { + config, + index, + envName + } of flattenedConfigs) { + if (!(yield* mergeExtendsChain(chain, config.options, dirname, context, files, baseLogger))) { + return null; + } + + logger(config, index, envName); + mergeChainOpts(chain, config); + } + + return chain; + }; +} + +function* mergeExtendsChain(chain, opts, dirname, context, files, baseLogger) { + if (opts.extends === undefined) return true; + const file = yield* (0, _files.loadConfig)(opts.extends, dirname, context.envName, context.caller); + + if (files.has(file)) { + throw new Error(`Configuration cycle detected loading ${file.filepath}.\n` + `File already loaded following the config chain:\n` + Array.from(files, file => ` - ${file.filepath}`).join("\n")); + } + + files.add(file); + const fileChain = yield* loadFileChain(validateExtendFile(file), context, files, baseLogger); + files.delete(file); + if (!fileChain) return false; + mergeChain(chain, fileChain); + return true; +} + +function mergeChain(target, source) { + target.options.push(...source.options); + target.plugins.push(...source.plugins); + target.presets.push(...source.presets); + + for (const file of source.files) { + target.files.add(file); + } + + return target; +} + +function mergeChainOpts(target, { + options, + plugins, + presets +}) { + target.options.push(options); + target.plugins.push(...plugins()); + target.presets.push(...presets()); + return target; +} + +function emptyChain() { + return { + options: [], + presets: [], + plugins: [], + files: new Set() + }; +} + +function normalizeOptions(opts) { + const options = Object.assign({}, opts); + delete options.extends; + delete options.env; + delete options.overrides; + delete options.plugins; + delete options.presets; + delete options.passPerPreset; + delete options.ignore; + delete options.only; + delete options.test; + delete options.include; + delete options.exclude; + + if (Object.prototype.hasOwnProperty.call(options, "sourceMap")) { + options.sourceMaps = options.sourceMap; + delete options.sourceMap; + } + + return options; +} + +function dedupDescriptors(items) { + const map = new Map(); + const descriptors = []; + + for (const item of items) { + if (typeof item.value === "function") { + const fnKey = item.value; + let nameMap = map.get(fnKey); + + if (!nameMap) { + nameMap = new Map(); + map.set(fnKey, nameMap); + } + + let desc = nameMap.get(item.name); + + if (!desc) { + desc = { + value: item + }; + descriptors.push(desc); + if (!item.ownPass) nameMap.set(item.name, desc); + } else { + desc.value = item; + } + } else { + descriptors.push({ + value: item + }); + } + } + + return descriptors.reduce((acc, desc) => { + acc.push(desc.value); + return acc; + }, []); +} + +function configIsApplicable({ + options +}, dirname, context) { + return (options.test === undefined || configFieldIsApplicable(context, options.test, dirname)) && (options.include === undefined || configFieldIsApplicable(context, options.include, dirname)) && (options.exclude === undefined || !configFieldIsApplicable(context, options.exclude, dirname)); +} + +function configFieldIsApplicable(context, test, dirname) { + const patterns = Array.isArray(test) ? test : [test]; + return matchesPatterns(context, patterns, dirname); +} + +function shouldIgnore(context, ignore, only, dirname) { + if (ignore && matchesPatterns(context, ignore, dirname)) { + var _context$filename; + + const message = `No config is applied to "${(_context$filename = context.filename) != null ? _context$filename : "(unknown)"}" because it matches one of \`ignore: ${JSON.stringify(ignore)}\` from "${dirname}"`; + debug(message); + + if (context.showConfig) { + console.log(message); + } + + return true; + } + + if (only && !matchesPatterns(context, only, dirname)) { + var _context$filename2; + + const message = `No config is applied to "${(_context$filename2 = context.filename) != null ? _context$filename2 : "(unknown)"}" because it fails to match one of \`only: ${JSON.stringify(only)}\` from "${dirname}"`; + debug(message); + + if (context.showConfig) { + console.log(message); + } + + return true; + } + + return false; +} + +function matchesPatterns(context, patterns, dirname) { + return patterns.some(pattern => matchPattern(pattern, dirname, context.filename, context)); +} + +function matchPattern(pattern, dirname, pathToTest, context) { + if (typeof pattern === "function") { + return !!pattern(pathToTest, { + dirname, + envName: context.envName, + caller: context.caller + }); + } + + if (typeof pathToTest !== "string") { + throw new Error(`Configuration contains string/RegExp pattern, but no filename was passed to Babel`); + } + + if (typeof pattern === "string") { + pattern = (0, _patternToRegex.default)(pattern, dirname); + } + + return pattern.test(pathToTest); +} + +/***/ }), + +/***/ 2420: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports.createCachedDescriptors = createCachedDescriptors; +exports.createUncachedDescriptors = createUncachedDescriptors; +exports.createDescriptor = createDescriptor; + +var _files = __nccwpck_require__(37217); + +var _item = __nccwpck_require__(72478); + +var _caching = __nccwpck_require__(46268); + +function isEqualDescriptor(a, b) { + return a.name === b.name && a.value === b.value && a.options === b.options && a.dirname === b.dirname && a.alias === b.alias && a.ownPass === b.ownPass && (a.file && a.file.request) === (b.file && b.file.request) && (a.file && a.file.resolved) === (b.file && b.file.resolved); +} + +function createCachedDescriptors(dirname, options, alias) { + const { + plugins, + presets, + passPerPreset + } = options; + return { + options, + plugins: plugins ? () => createCachedPluginDescriptors(plugins, dirname)(alias) : () => [], + presets: presets ? () => createCachedPresetDescriptors(presets, dirname)(alias)(!!passPerPreset) : () => [] + }; +} + +function createUncachedDescriptors(dirname, options, alias) { + let plugins; + let presets; + return { + options, + plugins: () => { + if (!plugins) { + plugins = createPluginDescriptors(options.plugins || [], dirname, alias); + } + + return plugins; + }, + presets: () => { + if (!presets) { + presets = createPresetDescriptors(options.presets || [], dirname, alias, !!options.passPerPreset); + } + + return presets; + } + }; +} + +const PRESET_DESCRIPTOR_CACHE = new WeakMap(); +const createCachedPresetDescriptors = (0, _caching.makeWeakCacheSync)((items, cache) => { + const dirname = cache.using(dir => dir); + return (0, _caching.makeStrongCacheSync)(alias => (0, _caching.makeStrongCacheSync)(passPerPreset => createPresetDescriptors(items, dirname, alias, passPerPreset).map(desc => loadCachedDescriptor(PRESET_DESCRIPTOR_CACHE, desc)))); +}); +const PLUGIN_DESCRIPTOR_CACHE = new WeakMap(); +const createCachedPluginDescriptors = (0, _caching.makeWeakCacheSync)((items, cache) => { + const dirname = cache.using(dir => dir); + return (0, _caching.makeStrongCacheSync)(alias => createPluginDescriptors(items, dirname, alias).map(desc => loadCachedDescriptor(PLUGIN_DESCRIPTOR_CACHE, desc))); +}); +const DEFAULT_OPTIONS = {}; + +function loadCachedDescriptor(cache, desc) { + const { + value, + options = DEFAULT_OPTIONS + } = desc; + if (options === false) return desc; + let cacheByOptions = cache.get(value); + + if (!cacheByOptions) { + cacheByOptions = new WeakMap(); + cache.set(value, cacheByOptions); + } + + let possibilities = cacheByOptions.get(options); + + if (!possibilities) { + possibilities = []; + cacheByOptions.set(options, possibilities); + } + + if (possibilities.indexOf(desc) === -1) { + const matches = possibilities.filter(possibility => isEqualDescriptor(possibility, desc)); + + if (matches.length > 0) { + return matches[0]; + } + + possibilities.push(desc); + } + + return desc; +} + +function createPresetDescriptors(items, dirname, alias, passPerPreset) { + return createDescriptors("preset", items, dirname, alias, passPerPreset); +} + +function createPluginDescriptors(items, dirname, alias) { + return createDescriptors("plugin", items, dirname, alias); +} + +function createDescriptors(type, items, dirname, alias, ownPass) { + const descriptors = items.map((item, index) => createDescriptor(item, dirname, { + type, + alias: `${alias}$${index}`, + ownPass: !!ownPass + })); + assertNoDuplicates(descriptors); + return descriptors; +} + +function createDescriptor(pair, dirname, { + type, + alias, + ownPass +}) { + const desc = (0, _item.getItemDescriptor)(pair); + + if (desc) { + return desc; + } + + let name; + let options; + let value = pair; + + if (Array.isArray(value)) { + if (value.length === 3) { + [value, options, name] = value; + } else { + [value, options] = value; + } + } + + let file = undefined; + let filepath = null; + + if (typeof value === "string") { + if (typeof type !== "string") { + throw new Error("To resolve a string-based item, the type of item must be given"); + } + + const resolver = type === "plugin" ? _files.loadPlugin : _files.loadPreset; + const request = value; + ({ + filepath, + value + } = resolver(value, dirname)); + file = { + request, + resolved: filepath + }; + } + + if (!value) { + throw new Error(`Unexpected falsy value: ${String(value)}`); + } + + if (typeof value === "object" && value.__esModule) { + if (value.default) { + value = value.default; + } else { + throw new Error("Must export a default export when using ES6 modules."); + } + } + + if (typeof value !== "object" && typeof value !== "function") { + throw new Error(`Unsupported format: ${typeof value}. Expected an object or a function.`); + } + + if (filepath !== null && typeof value === "object" && value) { + throw new Error(`Plugin/Preset files are not allowed to export objects, only functions. In ${filepath}`); + } + + return { + name, + alias: filepath || alias, + value, + options, + dirname, + ownPass, + file + }; +} + +function assertNoDuplicates(items) { + const map = new Map(); + + for (const item of items) { + if (typeof item.value !== "function") continue; + let nameMap = map.get(item.value); + + if (!nameMap) { + nameMap = new Set(); + map.set(item.value, nameMap); + } + + if (nameMap.has(item.name)) { + const conflicts = items.filter(i => i.value === item.value); + throw new Error([`Duplicate plugin/preset detected.`, `If you'd like to use two separate instances of a plugin,`, `they need separate names, e.g.`, ``, ` plugins: [`, ` ['some-plugin', {}],`, ` ['some-plugin', {}, 'some unique name'],`, ` ]`, ``, `Duplicates detected are:`, `${JSON.stringify(conflicts, null, 2)}`].join("\n")); + } + + nameMap.add(item.name); + } +} + +/***/ }), + +/***/ 86199: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports.findConfigUpwards = findConfigUpwards; +exports.findRelativeConfig = findRelativeConfig; +exports.findRootConfig = findRootConfig; +exports.loadConfig = loadConfig; +exports.resolveShowConfigPath = resolveShowConfigPath; +exports.ROOT_CONFIG_FILENAMES = void 0; + +function _debug() { + const data = _interopRequireDefault(__nccwpck_require__(2830)); + + _debug = function () { + return data; + }; + + return data; +} + +function _path() { + const data = _interopRequireDefault(__nccwpck_require__(16928)); + + _path = function () { + return data; + }; + + return data; +} + +function _json() { + const data = _interopRequireDefault(__nccwpck_require__(84841)); + + _json = function () { + return data; + }; + + return data; +} + +function _gensync() { + const data = _interopRequireDefault(__nccwpck_require__(9621)); + + _gensync = function () { + return data; + }; + + return data; +} + +var _caching = __nccwpck_require__(46268); + +var _configApi = _interopRequireDefault(__nccwpck_require__(33700)); + +var _utils = __nccwpck_require__(748); + +var _moduleTypes = _interopRequireDefault(__nccwpck_require__(67465)); + +var _patternToRegex = _interopRequireDefault(__nccwpck_require__(79619)); + +var fs = _interopRequireWildcard(__nccwpck_require__(10733)); + +var _resolve = _interopRequireDefault(__nccwpck_require__(12620)); + +function _getRequireWildcardCache() { if (typeof WeakMap !== "function") return null; var cache = new WeakMap(); _getRequireWildcardCache = function () { return cache; }; return cache; } + +function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; } + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +const debug = (0, _debug().default)("babel:config:loading:files:configuration"); +const ROOT_CONFIG_FILENAMES = ["babel.config.js", "babel.config.cjs", "babel.config.mjs", "babel.config.json"]; +exports.ROOT_CONFIG_FILENAMES = ROOT_CONFIG_FILENAMES; +const RELATIVE_CONFIG_FILENAMES = [".babelrc", ".babelrc.js", ".babelrc.cjs", ".babelrc.mjs", ".babelrc.json"]; +const BABELIGNORE_FILENAME = ".babelignore"; + +function* findConfigUpwards(rootDir) { + let dirname = rootDir; + + while (true) { + for (const filename of ROOT_CONFIG_FILENAMES) { + if (yield* fs.exists(_path().default.join(dirname, filename))) { + return dirname; + } + } + + const nextDir = _path().default.dirname(dirname); + + if (dirname === nextDir) break; + dirname = nextDir; + } + + return null; +} + +function* findRelativeConfig(packageData, envName, caller) { + let config = null; + let ignore = null; + + const dirname = _path().default.dirname(packageData.filepath); + + for (const loc of packageData.directories) { + if (!config) { + var _packageData$pkg; + + config = yield* loadOneConfig(RELATIVE_CONFIG_FILENAMES, loc, envName, caller, ((_packageData$pkg = packageData.pkg) == null ? void 0 : _packageData$pkg.dirname) === loc ? packageToBabelConfig(packageData.pkg) : null); + } + + if (!ignore) { + const ignoreLoc = _path().default.join(loc, BABELIGNORE_FILENAME); + + ignore = yield* readIgnoreConfig(ignoreLoc); + + if (ignore) { + debug("Found ignore %o from %o.", ignore.filepath, dirname); + } + } + } + + return { + config, + ignore + }; +} + +function findRootConfig(dirname, envName, caller) { + return loadOneConfig(ROOT_CONFIG_FILENAMES, dirname, envName, caller); +} + +function* loadOneConfig(names, dirname, envName, caller, previousConfig = null) { + const configs = yield* _gensync().default.all(names.map(filename => readConfig(_path().default.join(dirname, filename), envName, caller))); + const config = configs.reduce((previousConfig, config) => { + if (config && previousConfig) { + throw new Error(`Multiple configuration files found. Please remove one:\n` + ` - ${_path().default.basename(previousConfig.filepath)}\n` + ` - ${config.filepath}\n` + `from ${dirname}`); + } + + return config || previousConfig; + }, previousConfig); + + if (config) { + debug("Found configuration %o from %o.", config.filepath, dirname); + } + + return config; +} + +function* loadConfig(name, dirname, envName, caller) { + const filepath = yield* (0, _resolve.default)(name, { + basedir: dirname + }); + const conf = yield* readConfig(filepath, envName, caller); + + if (!conf) { + throw new Error(`Config file ${filepath} contains no configuration data`); + } + + debug("Loaded config %o from %o.", name, dirname); + return conf; +} + +function readConfig(filepath, envName, caller) { + const ext = _path().default.extname(filepath); + + return ext === ".js" || ext === ".cjs" || ext === ".mjs" ? readConfigJS(filepath, { + envName, + caller + }) : readConfigJSON5(filepath); +} + +const LOADING_CONFIGS = new Set(); +const readConfigJS = (0, _caching.makeStrongCache)(function* readConfigJS(filepath, cache) { + if (!fs.exists.sync(filepath)) { + cache.forever(); + return null; + } + + if (LOADING_CONFIGS.has(filepath)) { + cache.never(); + debug("Auto-ignoring usage of config %o.", filepath); + return { + filepath, + dirname: _path().default.dirname(filepath), + options: {} + }; + } + + let options; + + try { + LOADING_CONFIGS.add(filepath); + options = yield* (0, _moduleTypes.default)(filepath, "You appear to be using a native ECMAScript module configuration " + "file, which is only supported when running Babel asynchronously."); + } catch (err) { + err.message = `${filepath}: Error while loading config - ${err.message}`; + throw err; + } finally { + LOADING_CONFIGS.delete(filepath); + } + + let assertCache = false; + + if (typeof options === "function") { + yield* []; + options = options((0, _configApi.default)(cache)); + assertCache = true; + } + + if (!options || typeof options !== "object" || Array.isArray(options)) { + throw new Error(`${filepath}: Configuration should be an exported JavaScript object.`); + } + + if (typeof options.then === "function") { + throw new Error(`You appear to be using an async configuration, ` + `which your current version of Babel does not support. ` + `We may add support for this in the future, ` + `but if you're on the most recent version of @babel/core and still ` + `seeing this error, then you'll need to synchronously return your config.`); + } + + if (assertCache && !cache.configured()) throwConfigError(); + return { + filepath, + dirname: _path().default.dirname(filepath), + options + }; +}); +const packageToBabelConfig = (0, _caching.makeWeakCacheSync)(file => { + const babel = file.options["babel"]; + if (typeof babel === "undefined") return null; + + if (typeof babel !== "object" || Array.isArray(babel) || babel === null) { + throw new Error(`${file.filepath}: .babel property must be an object`); + } + + return { + filepath: file.filepath, + dirname: file.dirname, + options: babel + }; +}); +const readConfigJSON5 = (0, _utils.makeStaticFileCache)((filepath, content) => { + let options; + + try { + options = _json().default.parse(content); + } catch (err) { + err.message = `${filepath}: Error while parsing config - ${err.message}`; + throw err; + } + + if (!options) throw new Error(`${filepath}: No config detected`); + + if (typeof options !== "object") { + throw new Error(`${filepath}: Config returned typeof ${typeof options}`); + } + + if (Array.isArray(options)) { + throw new Error(`${filepath}: Expected config object but found array`); + } + + return { + filepath, + dirname: _path().default.dirname(filepath), + options + }; +}); +const readIgnoreConfig = (0, _utils.makeStaticFileCache)((filepath, content) => { + const ignoreDir = _path().default.dirname(filepath); + + const ignorePatterns = content.split("\n").map(line => line.replace(/#(.*?)$/, "").trim()).filter(line => !!line); + + for (const pattern of ignorePatterns) { + if (pattern[0] === "!") { + throw new Error(`Negation of file paths is not supported.`); + } + } + + return { + filepath, + dirname: _path().default.dirname(filepath), + ignore: ignorePatterns.map(pattern => (0, _patternToRegex.default)(pattern, ignoreDir)) + }; +}); + +function* resolveShowConfigPath(dirname) { + const targetPath = process.env.BABEL_SHOW_CONFIG_FOR; + + if (targetPath != null) { + const absolutePath = _path().default.resolve(dirname, targetPath); + + const stats = yield* fs.stat(absolutePath); + + if (!stats.isFile()) { + throw new Error(`${absolutePath}: BABEL_SHOW_CONFIG_FOR must refer to a regular file, directories are not supported.`); + } + + return absolutePath; + } + + return null; +} + +function throwConfigError() { + throw new Error(`\ +Caching was left unconfigured. Babel's plugins, presets, and .babelrc.js files can be configured +for various types of caching, using the first param of their handler functions: + +module.exports = function(api) { + // The API exposes the following: + + // Cache the returned value forever and don't call this function again. + api.cache(true); + + // Don't cache at all. Not recommended because it will be very slow. + api.cache(false); + + // Cached based on the value of some function. If this function returns a value different from + // a previously-encountered value, the plugins will re-evaluate. + var env = api.cache(() => process.env.NODE_ENV); + + // If testing for a specific env, we recommend specifics to avoid instantiating a plugin for + // any possible NODE_ENV value that might come up during plugin execution. + var isProd = api.cache(() => process.env.NODE_ENV === "production"); + + // .cache(fn) will perform a linear search though instances to find the matching plugin based + // based on previous instantiated plugins. If you want to recreate the plugin and discard the + // previous instance whenever something changes, you may use: + var isProd = api.cache.invalidate(() => process.env.NODE_ENV === "production"); + + // Note, we also expose the following more-verbose versions of the above examples: + api.cache.forever(); // api.cache(true) + api.cache.never(); // api.cache(false) + api.cache.using(fn); // api.cache(fn) + + // Return the value that will be cached. + return { }; +};`); +} + +/***/ }), + +/***/ 81028: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; +var __webpack_unused_export__; + + +__webpack_unused_export__ = ({ + value: true +}); +exports.A = import_; + +function import_(filepath) { + return __nccwpck_require__(18541)(filepath); +} + +/***/ }), + +/***/ 37217: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +Object.defineProperty(exports, "findPackageData", ({ + enumerable: true, + get: function () { + return _package.findPackageData; + } +})); +Object.defineProperty(exports, "findConfigUpwards", ({ + enumerable: true, + get: function () { + return _configuration.findConfigUpwards; + } +})); +Object.defineProperty(exports, "findRelativeConfig", ({ + enumerable: true, + get: function () { + return _configuration.findRelativeConfig; + } +})); +Object.defineProperty(exports, "findRootConfig", ({ + enumerable: true, + get: function () { + return _configuration.findRootConfig; + } +})); +Object.defineProperty(exports, "loadConfig", ({ + enumerable: true, + get: function () { + return _configuration.loadConfig; + } +})); +Object.defineProperty(exports, "resolveShowConfigPath", ({ + enumerable: true, + get: function () { + return _configuration.resolveShowConfigPath; + } +})); +Object.defineProperty(exports, "ROOT_CONFIG_FILENAMES", ({ + enumerable: true, + get: function () { + return _configuration.ROOT_CONFIG_FILENAMES; + } +})); +Object.defineProperty(exports, "resolvePlugin", ({ + enumerable: true, + get: function () { + return _plugins.resolvePlugin; + } +})); +Object.defineProperty(exports, "resolvePreset", ({ + enumerable: true, + get: function () { + return _plugins.resolvePreset; + } +})); +Object.defineProperty(exports, "loadPlugin", ({ + enumerable: true, + get: function () { + return _plugins.loadPlugin; + } +})); +Object.defineProperty(exports, "loadPreset", ({ + enumerable: true, + get: function () { + return _plugins.loadPreset; + } +})); + +var _package = __nccwpck_require__(70597); + +var _configuration = __nccwpck_require__(86199); + +var _plugins = __nccwpck_require__(19669); + +({}); + +/***/ }), + +/***/ 67465: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports["default"] = loadCjsOrMjsDefault; + +var _async = __nccwpck_require__(87360); + +function _path() { + const data = _interopRequireDefault(__nccwpck_require__(16928)); + + _path = function () { + return data; + }; + + return data; +} + +function _url() { + const data = __nccwpck_require__(87016); + + _url = function () { + return data; + }; + + return data; +} + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { Promise.resolve(value).then(_next, _throw); } } + +function _asyncToGenerator(fn) { return function () { var self = this, args = arguments; return new Promise(function (resolve, reject) { var gen = fn.apply(self, args); function _next(value) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value); } function _throw(err) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err); } _next(undefined); }); }; } + +let import_; + +try { + import_ = (__nccwpck_require__(81028)/* ["default"] */ .A); +} catch (_unused) {} + +function* loadCjsOrMjsDefault(filepath, asyncError) { + switch (guessJSModuleType(filepath)) { + case "cjs": + return loadCjsDefault(filepath); + + case "unknown": + try { + return loadCjsDefault(filepath); + } catch (e) { + if (e.code !== "ERR_REQUIRE_ESM") throw e; + } + + case "mjs": + if (yield* (0, _async.isAsync)()) { + return yield* (0, _async.waitFor)(loadMjsDefault(filepath)); + } + + throw new Error(asyncError); + } +} + +function guessJSModuleType(filename) { + switch (_path().default.extname(filename)) { + case ".cjs": + return "cjs"; + + case ".mjs": + return "mjs"; + + default: + return "unknown"; + } +} + +function loadCjsDefault(filepath) { + const module = require(filepath); + + return (module == null ? void 0 : module.__esModule) ? module.default || undefined : module; +} + +function loadMjsDefault(_x) { + return _loadMjsDefault.apply(this, arguments); +} + +function _loadMjsDefault() { + _loadMjsDefault = _asyncToGenerator(function* (filepath) { + if (!import_) { + throw new Error("Internal error: Native ECMAScript modules aren't supported" + " by this platform.\n"); + } + + const module = yield import_((0, _url().pathToFileURL)(filepath)); + return module.default; + }); + return _loadMjsDefault.apply(this, arguments); +} + +/***/ }), + +/***/ 70597: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports.findPackageData = findPackageData; + +function _path() { + const data = _interopRequireDefault(__nccwpck_require__(16928)); + + _path = function () { + return data; + }; + + return data; +} + +var _utils = __nccwpck_require__(748); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +const PACKAGE_FILENAME = "package.json"; + +function* findPackageData(filepath) { + let pkg = null; + const directories = []; + let isPackage = true; + + let dirname = _path().default.dirname(filepath); + + while (!pkg && _path().default.basename(dirname) !== "node_modules") { + directories.push(dirname); + pkg = yield* readConfigPackage(_path().default.join(dirname, PACKAGE_FILENAME)); + + const nextLoc = _path().default.dirname(dirname); + + if (dirname === nextLoc) { + isPackage = false; + break; + } + + dirname = nextLoc; + } + + return { + filepath, + directories, + pkg, + isPackage + }; +} + +const readConfigPackage = (0, _utils.makeStaticFileCache)((filepath, content) => { + let options; + + try { + options = JSON.parse(content); + } catch (err) { + err.message = `${filepath}: Error while parsing JSON - ${err.message}`; + throw err; + } + + if (!options) throw new Error(`${filepath}: No config detected`); + + if (typeof options !== "object") { + throw new Error(`${filepath}: Config returned typeof ${typeof options}`); + } + + if (Array.isArray(options)) { + throw new Error(`${filepath}: Expected config object but found array`); + } + + return { + filepath, + dirname: _path().default.dirname(filepath), + options + }; +}); + +/***/ }), + +/***/ 19669: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports.resolvePlugin = resolvePlugin; +exports.resolvePreset = resolvePreset; +exports.loadPlugin = loadPlugin; +exports.loadPreset = loadPreset; + +function _debug() { + const data = _interopRequireDefault(__nccwpck_require__(2830)); + + _debug = function () { + return data; + }; + + return data; +} + +function _resolve() { + const data = _interopRequireDefault(__nccwpck_require__(92312)); + + _resolve = function () { + return data; + }; + + return data; +} + +function _path() { + const data = _interopRequireDefault(__nccwpck_require__(16928)); + + _path = function () { + return data; + }; + + return data; +} + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +const debug = (0, _debug().default)("babel:config:loading:files:plugins"); +const EXACT_RE = /^module:/; +const BABEL_PLUGIN_PREFIX_RE = /^(?!@|module:|[^/]+\/|babel-plugin-)/; +const BABEL_PRESET_PREFIX_RE = /^(?!@|module:|[^/]+\/|babel-preset-)/; +const BABEL_PLUGIN_ORG_RE = /^(@babel\/)(?!plugin-|[^/]+\/)/; +const BABEL_PRESET_ORG_RE = /^(@babel\/)(?!preset-|[^/]+\/)/; +const OTHER_PLUGIN_ORG_RE = /^(@(?!babel\/)[^/]+\/)(?![^/]*babel-plugin(?:-|\/|$)|[^/]+\/)/; +const OTHER_PRESET_ORG_RE = /^(@(?!babel\/)[^/]+\/)(?![^/]*babel-preset(?:-|\/|$)|[^/]+\/)/; +const OTHER_ORG_DEFAULT_RE = /^(@(?!babel$)[^/]+)$/; + +function resolvePlugin(name, dirname) { + return resolveStandardizedName("plugin", name, dirname); +} + +function resolvePreset(name, dirname) { + return resolveStandardizedName("preset", name, dirname); +} + +function loadPlugin(name, dirname) { + const filepath = resolvePlugin(name, dirname); + + if (!filepath) { + throw new Error(`Plugin ${name} not found relative to ${dirname}`); + } + + const value = requireModule("plugin", filepath); + debug("Loaded plugin %o from %o.", name, dirname); + return { + filepath, + value + }; +} + +function loadPreset(name, dirname) { + const filepath = resolvePreset(name, dirname); + + if (!filepath) { + throw new Error(`Preset ${name} not found relative to ${dirname}`); + } + + const value = requireModule("preset", filepath); + debug("Loaded preset %o from %o.", name, dirname); + return { + filepath, + value + }; +} + +function standardizeName(type, name) { + if (_path().default.isAbsolute(name)) return name; + const isPreset = type === "preset"; + return name.replace(isPreset ? BABEL_PRESET_PREFIX_RE : BABEL_PLUGIN_PREFIX_RE, `babel-${type}-`).replace(isPreset ? BABEL_PRESET_ORG_RE : BABEL_PLUGIN_ORG_RE, `$1${type}-`).replace(isPreset ? OTHER_PRESET_ORG_RE : OTHER_PLUGIN_ORG_RE, `$1babel-${type}-`).replace(OTHER_ORG_DEFAULT_RE, `$1/babel-${type}`).replace(EXACT_RE, ""); +} + +function resolveStandardizedName(type, name, dirname = process.cwd()) { + const standardizedName = standardizeName(type, name); + + try { + return _resolve().default.sync(standardizedName, { + basedir: dirname + }); + } catch (e) { + if (e.code !== "MODULE_NOT_FOUND") throw e; + + if (standardizedName !== name) { + let resolvedOriginal = false; + + try { + _resolve().default.sync(name, { + basedir: dirname + }); + + resolvedOriginal = true; + } catch (_unused) {} + + if (resolvedOriginal) { + e.message += `\n- If you want to resolve "${name}", use "module:${name}"`; + } + } + + let resolvedBabel = false; + + try { + _resolve().default.sync(standardizeName(type, "@babel/" + name), { + basedir: dirname + }); + + resolvedBabel = true; + } catch (_unused2) {} + + if (resolvedBabel) { + e.message += `\n- Did you mean "@babel/${name}"?`; + } + + let resolvedOppositeType = false; + const oppositeType = type === "preset" ? "plugin" : "preset"; + + try { + _resolve().default.sync(standardizeName(oppositeType, name), { + basedir: dirname + }); + + resolvedOppositeType = true; + } catch (_unused3) {} + + if (resolvedOppositeType) { + e.message += `\n- Did you accidentally pass a ${oppositeType} as a ${type}?`; + } + + throw e; + } +} + +const LOADING_MODULES = new Set(); + +function requireModule(type, name) { + if (LOADING_MODULES.has(name)) { + throw new Error(`Reentrant ${type} detected trying to load "${name}". This module is not ignored ` + "and is trying to load itself while compiling itself, leading to a dependency cycle. " + 'We recommend adding it to your "ignore" list in your babelrc, or to a .babelignore.'); + } + + try { + LOADING_MODULES.add(name); + return require(name); + } finally { + LOADING_MODULES.delete(name); + } +} + +/***/ }), + +/***/ 748: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports.makeStaticFileCache = makeStaticFileCache; + +var _caching = __nccwpck_require__(46268); + +var fs = _interopRequireWildcard(__nccwpck_require__(10733)); + +function _fs2() { + const data = _interopRequireDefault(__nccwpck_require__(79896)); + + _fs2 = function () { + return data; + }; + + return data; +} + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +function _getRequireWildcardCache() { if (typeof WeakMap !== "function") return null; var cache = new WeakMap(); _getRequireWildcardCache = function () { return cache; }; return cache; } + +function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; } + +function makeStaticFileCache(fn) { + return (0, _caching.makeStrongCache)(function* (filepath, cache) { + const cached = cache.invalidate(() => fileMtime(filepath)); + + if (cached === null) { + return null; + } + + return fn(filepath, yield* fs.readFile(filepath, "utf8")); + }); +} + +function fileMtime(filepath) { + try { + return +_fs2().default.statSync(filepath).mtime; + } catch (e) { + if (e.code !== "ENOENT" && e.code !== "ENOTDIR") throw e; + } + + return null; +} + +/***/ }), + +/***/ 74396: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports["default"] = void 0; + +function _gensync() { + const data = _interopRequireDefault(__nccwpck_require__(9621)); + + _gensync = function () { + return data; + }; + + return data; +} + +var _async = __nccwpck_require__(87360); + +var _util = __nccwpck_require__(54493); + +var context = _interopRequireWildcard(__nccwpck_require__(85414)); + +var _plugin = _interopRequireDefault(__nccwpck_require__(68618)); + +var _item = __nccwpck_require__(72478); + +var _configChain = __nccwpck_require__(70835); + +function _traverse() { + const data = _interopRequireDefault(__nccwpck_require__(50148)); + + _traverse = function () { + return data; + }; + + return data; +} + +var _caching = __nccwpck_require__(46268); + +var _options = __nccwpck_require__(44743); + +var _plugins = __nccwpck_require__(22449); + +var _configApi = _interopRequireDefault(__nccwpck_require__(33700)); + +var _partial = _interopRequireDefault(__nccwpck_require__(71700)); + +function _getRequireWildcardCache() { if (typeof WeakMap !== "function") return null; var cache = new WeakMap(); _getRequireWildcardCache = function () { return cache; }; return cache; } + +function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; } + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +var _default = (0, _gensync().default)(function* loadFullConfig(inputOpts) { + const result = yield* (0, _partial.default)(inputOpts); + + if (!result) { + return null; + } + + const { + options, + context, + fileHandling + } = result; + + if (fileHandling === "ignored") { + return null; + } + + const optionDefaults = {}; + const { + plugins, + presets + } = options; + + if (!plugins || !presets) { + throw new Error("Assertion failure - plugins and presets exist"); + } + + const toDescriptor = item => { + const desc = (0, _item.getItemDescriptor)(item); + + if (!desc) { + throw new Error("Assertion failure - must be config item"); + } + + return desc; + }; + + const presetsDescriptors = presets.map(toDescriptor); + const initialPluginsDescriptors = plugins.map(toDescriptor); + const pluginDescriptorsByPass = [[]]; + const passes = []; + const ignored = yield* enhanceError(context, function* recursePresetDescriptors(rawPresets, pluginDescriptorsPass) { + const presets = []; + + for (let i = 0; i < rawPresets.length; i++) { + const descriptor = rawPresets[i]; + + if (descriptor.options !== false) { + try { + if (descriptor.ownPass) { + presets.push({ + preset: yield* loadPresetDescriptor(descriptor, context), + pass: [] + }); + } else { + presets.unshift({ + preset: yield* loadPresetDescriptor(descriptor, context), + pass: pluginDescriptorsPass + }); + } + } catch (e) { + if (e.code === "BABEL_UNKNOWN_OPTION") { + (0, _options.checkNoUnwrappedItemOptionPairs)(rawPresets, i, "preset", e); + } + + throw e; + } + } + } + + if (presets.length > 0) { + pluginDescriptorsByPass.splice(1, 0, ...presets.map(o => o.pass).filter(p => p !== pluginDescriptorsPass)); + + for (const { + preset, + pass + } of presets) { + if (!preset) return true; + pass.push(...preset.plugins); + const ignored = yield* recursePresetDescriptors(preset.presets, pass); + if (ignored) return true; + preset.options.forEach(opts => { + (0, _util.mergeOptions)(optionDefaults, opts); + }); + } + } + })(presetsDescriptors, pluginDescriptorsByPass[0]); + if (ignored) return null; + const opts = optionDefaults; + (0, _util.mergeOptions)(opts, options); + yield* enhanceError(context, function* loadPluginDescriptors() { + pluginDescriptorsByPass[0].unshift(...initialPluginsDescriptors); + + for (const descs of pluginDescriptorsByPass) { + const pass = []; + passes.push(pass); + + for (let i = 0; i < descs.length; i++) { + const descriptor = descs[i]; + + if (descriptor.options !== false) { + try { + pass.push(yield* loadPluginDescriptor(descriptor, context)); + } catch (e) { + if (e.code === "BABEL_UNKNOWN_PLUGIN_PROPERTY") { + (0, _options.checkNoUnwrappedItemOptionPairs)(descs, i, "plugin", e); + } + + throw e; + } + } + } + } + })(); + opts.plugins = passes[0]; + opts.presets = passes.slice(1).filter(plugins => plugins.length > 0).map(plugins => ({ + plugins + })); + opts.passPerPreset = opts.presets.length > 0; + return { + options: opts, + passes: passes + }; +}); + +exports["default"] = _default; + +function enhanceError(context, fn) { + return function* (arg1, arg2) { + try { + return yield* fn(arg1, arg2); + } catch (e) { + if (!/^\[BABEL\]/.test(e.message)) { + e.message = `[BABEL] ${context.filename || "unknown"}: ${e.message}`; + } + + throw e; + } + }; +} + +const loadDescriptor = (0, _caching.makeWeakCache)(function* ({ + value, + options, + dirname, + alias +}, cache) { + if (options === false) throw new Error("Assertion failure"); + options = options || {}; + let item = value; + + if (typeof value === "function") { + const api = Object.assign({}, context, (0, _configApi.default)(cache)); + + try { + item = value(api, options, dirname); + } catch (e) { + if (alias) { + e.message += ` (While processing: ${JSON.stringify(alias)})`; + } + + throw e; + } + } + + if (!item || typeof item !== "object") { + throw new Error("Plugin/Preset did not return an object."); + } + + if (typeof item.then === "function") { + yield* []; + throw new Error(`You appear to be using an async plugin, ` + `which your current version of Babel does not support. ` + `If you're using a published plugin, ` + `you may need to upgrade your @babel/core version.`); + } + + return { + value: item, + options, + dirname, + alias + }; +}); + +function* loadPluginDescriptor(descriptor, context) { + if (descriptor.value instanceof _plugin.default) { + if (descriptor.options) { + throw new Error("Passed options to an existing Plugin instance will not work."); + } + + return descriptor.value; + } + + return yield* instantiatePlugin(yield* loadDescriptor(descriptor, context), context); +} + +const instantiatePlugin = (0, _caching.makeWeakCache)(function* ({ + value, + options, + dirname, + alias +}, cache) { + const pluginObj = (0, _plugins.validatePluginObject)(value); + const plugin = Object.assign({}, pluginObj); + + if (plugin.visitor) { + plugin.visitor = _traverse().default.explode(Object.assign({}, plugin.visitor)); + } + + if (plugin.inherits) { + const inheritsDescriptor = { + name: undefined, + alias: `${alias}$inherits`, + value: plugin.inherits, + options, + dirname + }; + const inherits = yield* (0, _async.forwardAsync)(loadPluginDescriptor, run => { + return cache.invalidate(data => run(inheritsDescriptor, data)); + }); + plugin.pre = chain(inherits.pre, plugin.pre); + plugin.post = chain(inherits.post, plugin.post); + plugin.manipulateOptions = chain(inherits.manipulateOptions, plugin.manipulateOptions); + plugin.visitor = _traverse().default.visitors.merge([inherits.visitor || {}, plugin.visitor || {}]); + } + + return new _plugin.default(plugin, options, alias); +}); + +const validateIfOptionNeedsFilename = (options, descriptor) => { + if (options.test || options.include || options.exclude) { + const formattedPresetName = descriptor.name ? `"${descriptor.name}"` : "/* your preset */"; + throw new Error([`Preset ${formattedPresetName} requires a filename to be set when babel is called directly,`, `\`\`\``, `babel.transform(code, { filename: 'file.ts', presets: [${formattedPresetName}] });`, `\`\`\``, `See https://babeljs.io/docs/en/options#filename for more information.`].join("\n")); + } +}; + +const validatePreset = (preset, context, descriptor) => { + if (!context.filename) { + const { + options + } = preset; + validateIfOptionNeedsFilename(options, descriptor); + + if (options.overrides) { + options.overrides.forEach(overrideOptions => validateIfOptionNeedsFilename(overrideOptions, descriptor)); + } + } +}; + +function* loadPresetDescriptor(descriptor, context) { + const preset = instantiatePreset(yield* loadDescriptor(descriptor, context)); + validatePreset(preset, context, descriptor); + return yield* (0, _configChain.buildPresetChain)(preset, context); +} + +const instantiatePreset = (0, _caching.makeWeakCacheSync)(({ + value, + dirname, + alias +}) => { + return { + options: (0, _options.validate)("preset", value), + alias, + dirname + }; +}); + +function chain(a, b) { + const fns = [a, b].filter(Boolean); + if (fns.length <= 1) return fns[0]; + return function (...args) { + for (const fn of fns) { + fn.apply(this, args); + } + }; +} + +/***/ }), + +/***/ 33700: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports["default"] = makeAPI; + +function _semver() { + const data = _interopRequireDefault(__nccwpck_require__(39318)); + + _semver = function () { + return data; + }; + + return data; +} + +var _ = __nccwpck_require__(85414); + +var _caching = __nccwpck_require__(46268); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +function makeAPI(cache) { + const env = value => cache.using(data => { + if (typeof value === "undefined") return data.envName; + + if (typeof value === "function") { + return (0, _caching.assertSimpleType)(value(data.envName)); + } + + if (!Array.isArray(value)) value = [value]; + return value.some(entry => { + if (typeof entry !== "string") { + throw new Error("Unexpected non-string value"); + } + + return entry === data.envName; + }); + }); + + const caller = cb => cache.using(data => (0, _caching.assertSimpleType)(cb(data.caller))); + + return { + version: _.version, + cache: cache.simple(), + env, + async: () => false, + caller, + assertVersion + }; +} + +function assertVersion(range) { + if (typeof range === "number") { + if (!Number.isInteger(range)) { + throw new Error("Expected string or integer value."); + } + + range = `^${range}.0.0-0`; + } + + if (typeof range !== "string") { + throw new Error("Expected string or integer value."); + } + + if (_semver().default.satisfies(_.version, range)) return; + const limit = Error.stackTraceLimit; + + if (typeof limit === "number" && limit < 25) { + Error.stackTraceLimit = 25; + } + + const err = new Error(`Requires Babel "${range}", but was loaded with "${_.version}". ` + `If you are sure you have a compatible version of @babel/core, ` + `it is likely that something in your build process is loading the ` + `wrong version. Inspect the stack trace of this error to look for ` + `the first entry that doesn't mention "@babel/core" or "babel-core" ` + `to see what is calling Babel.`); + + if (typeof limit === "number") { + Error.stackTraceLimit = limit; + } + + throw Object.assign(err, { + code: "BABEL_VERSION_UNSUPPORTED", + version: _.version, + range + }); +} + +/***/ }), + +/***/ 3110: +/***/ ((__unused_webpack_module, exports) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports.getEnv = getEnv; + +function getEnv(defaultValue = "development") { + return process.env.BABEL_ENV || process.env.NODE_ENV || defaultValue; +} + +/***/ }), + +/***/ 73677: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +Object.defineProperty(exports, "default", ({ + enumerable: true, + get: function () { + return _full.default; + } +})); +exports.loadOptionsAsync = exports.loadOptionsSync = exports.loadOptions = exports.loadPartialConfigAsync = exports.loadPartialConfigSync = exports.loadPartialConfig = void 0; + +function _gensync() { + const data = _interopRequireDefault(__nccwpck_require__(9621)); + + _gensync = function () { + return data; + }; + + return data; +} + +var _full = _interopRequireDefault(__nccwpck_require__(74396)); + +var _partial = __nccwpck_require__(71700); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +const loadOptionsRunner = (0, _gensync().default)(function* (opts) { + var _config$options; + + const config = yield* (0, _full.default)(opts); + return (_config$options = config == null ? void 0 : config.options) != null ? _config$options : null; +}); + +const maybeErrback = runner => (opts, callback) => { + if (callback === undefined && typeof opts === "function") { + callback = opts; + opts = undefined; + } + + return callback ? runner.errback(opts, callback) : runner.sync(opts); +}; + +const loadPartialConfig = maybeErrback(_partial.loadPartialConfig); +exports.loadPartialConfig = loadPartialConfig; +const loadPartialConfigSync = _partial.loadPartialConfig.sync; +exports.loadPartialConfigSync = loadPartialConfigSync; +const loadPartialConfigAsync = _partial.loadPartialConfig.async; +exports.loadPartialConfigAsync = loadPartialConfigAsync; +const loadOptions = maybeErrback(loadOptionsRunner); +exports.loadOptions = loadOptions; +const loadOptionsSync = loadOptionsRunner.sync; +exports.loadOptionsSync = loadOptionsSync; +const loadOptionsAsync = loadOptionsRunner.async; +exports.loadOptionsAsync = loadOptionsAsync; + +/***/ }), + +/***/ 72478: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports.createItemFromDescriptor = createItemFromDescriptor; +exports.createConfigItem = createConfigItem; +exports.getItemDescriptor = getItemDescriptor; + +function _path() { + const data = _interopRequireDefault(__nccwpck_require__(16928)); + + _path = function () { + return data; + }; + + return data; +} + +var _configDescriptors = __nccwpck_require__(2420); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +function createItemFromDescriptor(desc) { + return new ConfigItem(desc); +} + +function createConfigItem(value, { + dirname = ".", + type +} = {}) { + const descriptor = (0, _configDescriptors.createDescriptor)(value, _path().default.resolve(dirname), { + type, + alias: "programmatic item" + }); + return createItemFromDescriptor(descriptor); +} + +function getItemDescriptor(item) { + if (item == null ? void 0 : item[CONFIG_ITEM_BRAND]) { + return item._descriptor; + } + + return undefined; +} + +const CONFIG_ITEM_BRAND = Symbol.for("@babel/core@7 - ConfigItem"); + +class ConfigItem { + constructor(descriptor) { + this._descriptor = void 0; + this[CONFIG_ITEM_BRAND] = true; + this.value = void 0; + this.options = void 0; + this.dirname = void 0; + this.name = void 0; + this.file = void 0; + this._descriptor = descriptor; + Object.defineProperty(this, "_descriptor", { + enumerable: false + }); + Object.defineProperty(this, CONFIG_ITEM_BRAND, { + enumerable: false + }); + this.value = this._descriptor.value; + this.options = this._descriptor.options; + this.dirname = this._descriptor.dirname; + this.name = this._descriptor.name; + this.file = this._descriptor.file ? { + request: this._descriptor.file.request, + resolved: this._descriptor.file.resolved + } : undefined; + Object.freeze(this); + } + +} + +Object.freeze(ConfigItem.prototype); + +/***/ }), + +/***/ 71700: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports["default"] = loadPrivatePartialConfig; +exports.loadPartialConfig = void 0; + +function _path() { + const data = _interopRequireDefault(__nccwpck_require__(16928)); + + _path = function () { + return data; + }; + + return data; +} + +function _gensync() { + const data = _interopRequireDefault(__nccwpck_require__(9621)); + + _gensync = function () { + return data; + }; + + return data; +} + +var _plugin = _interopRequireDefault(__nccwpck_require__(68618)); + +var _util = __nccwpck_require__(54493); + +var _item = __nccwpck_require__(72478); + +var _configChain = __nccwpck_require__(70835); + +var _environment = __nccwpck_require__(3110); + +var _options = __nccwpck_require__(44743); + +var _files = __nccwpck_require__(37217); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +function _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; } + +function* resolveRootMode(rootDir, rootMode) { + switch (rootMode) { + case "root": + return rootDir; + + case "upward-optional": + { + const upwardRootDir = yield* (0, _files.findConfigUpwards)(rootDir); + return upwardRootDir === null ? rootDir : upwardRootDir; + } + + case "upward": + { + const upwardRootDir = yield* (0, _files.findConfigUpwards)(rootDir); + if (upwardRootDir !== null) return upwardRootDir; + throw Object.assign(new Error(`Babel was run with rootMode:"upward" but a root could not ` + `be found when searching upward from "${rootDir}".\n` + `One of the following config files must be in the directory tree: ` + `"${_files.ROOT_CONFIG_FILENAMES.join(", ")}".`), { + code: "BABEL_ROOT_NOT_FOUND", + dirname: rootDir + }); + } + + default: + throw new Error(`Assertion failure - unknown rootMode value.`); + } +} + +function* loadPrivatePartialConfig(inputOpts) { + if (inputOpts != null && (typeof inputOpts !== "object" || Array.isArray(inputOpts))) { + throw new Error("Babel options must be an object, null, or undefined"); + } + + const args = inputOpts ? (0, _options.validate)("arguments", inputOpts) : {}; + const { + envName = (0, _environment.getEnv)(), + cwd = ".", + root: rootDir = ".", + rootMode = "root", + caller, + cloneInputAst = true + } = args; + + const absoluteCwd = _path().default.resolve(cwd); + + const absoluteRootDir = yield* resolveRootMode(_path().default.resolve(absoluteCwd, rootDir), rootMode); + const filename = typeof args.filename === "string" ? _path().default.resolve(cwd, args.filename) : undefined; + const showConfigPath = yield* (0, _files.resolveShowConfigPath)(absoluteCwd); + const context = { + filename, + cwd: absoluteCwd, + root: absoluteRootDir, + envName, + caller, + showConfig: showConfigPath === filename + }; + const configChain = yield* (0, _configChain.buildRootChain)(args, context); + if (!configChain) return null; + const options = {}; + configChain.options.forEach(opts => { + (0, _util.mergeOptions)(options, opts); + }); + options.cloneInputAst = cloneInputAst; + options.babelrc = false; + options.configFile = false; + options.passPerPreset = false; + options.envName = context.envName; + options.cwd = context.cwd; + options.root = context.root; + options.filename = typeof context.filename === "string" ? context.filename : undefined; + options.plugins = configChain.plugins.map(descriptor => (0, _item.createItemFromDescriptor)(descriptor)); + options.presets = configChain.presets.map(descriptor => (0, _item.createItemFromDescriptor)(descriptor)); + return { + options, + context, + fileHandling: configChain.fileHandling, + ignore: configChain.ignore, + babelrc: configChain.babelrc, + config: configChain.config, + files: configChain.files + }; +} + +const loadPartialConfig = (0, _gensync().default)(function* (opts) { + let showIgnoredFiles = false; + + if (typeof opts === "object" && opts !== null && !Array.isArray(opts)) { + var _opts = opts; + ({ + showIgnoredFiles + } = _opts); + opts = _objectWithoutPropertiesLoose(_opts, ["showIgnoredFiles"]); + _opts; + } + + const result = yield* loadPrivatePartialConfig(opts); + if (!result) return null; + const { + options, + babelrc, + ignore, + config, + fileHandling, + files + } = result; + + if (fileHandling === "ignored" && !showIgnoredFiles) { + return null; + } + + (options.plugins || []).forEach(item => { + if (item.value instanceof _plugin.default) { + throw new Error("Passing cached plugin instances is not supported in " + "babel.loadPartialConfig()"); + } + }); + return new PartialConfig(options, babelrc ? babelrc.filepath : undefined, ignore ? ignore.filepath : undefined, config ? config.filepath : undefined, fileHandling, files); +}); +exports.loadPartialConfig = loadPartialConfig; + +class PartialConfig { + constructor(options, babelrc, ignore, config, fileHandling, files) { + this.options = void 0; + this.babelrc = void 0; + this.babelignore = void 0; + this.config = void 0; + this.fileHandling = void 0; + this.files = void 0; + this.options = options; + this.babelignore = ignore; + this.babelrc = babelrc; + this.config = config; + this.fileHandling = fileHandling; + this.files = files; + Object.freeze(this); + } + + hasFilesystemConfig() { + return this.babelrc !== undefined || this.config !== undefined; + } + +} + +Object.freeze(PartialConfig.prototype); + +/***/ }), + +/***/ 79619: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports["default"] = pathToPattern; + +function _path() { + const data = _interopRequireDefault(__nccwpck_require__(16928)); + + _path = function () { + return data; + }; + + return data; +} + +function _escapeRegExp() { + const data = _interopRequireDefault(__nccwpck_require__(66195)); + + _escapeRegExp = function () { + return data; + }; + + return data; +} + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +const sep = `\\${_path().default.sep}`; +const endSep = `(?:${sep}|$)`; +const substitution = `[^${sep}]+`; +const starPat = `(?:${substitution}${sep})`; +const starPatLast = `(?:${substitution}${endSep})`; +const starStarPat = `${starPat}*?`; +const starStarPatLast = `${starPat}*?${starPatLast}?`; + +function pathToPattern(pattern, dirname) { + const parts = _path().default.resolve(dirname, pattern).split(_path().default.sep); + + return new RegExp(["^", ...parts.map((part, i) => { + const last = i === parts.length - 1; + if (part === "**") return last ? starStarPatLast : starStarPat; + if (part === "*") return last ? starPatLast : starPat; + + if (part.indexOf("*.") === 0) { + return substitution + (0, _escapeRegExp().default)(part.slice(1)) + (last ? endSep : sep); + } + + return (0, _escapeRegExp().default)(part) + (last ? endSep : sep); + })].join("")); +} + +/***/ }), + +/***/ 68618: +/***/ ((__unused_webpack_module, exports) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports["default"] = void 0; + +class Plugin { + constructor(plugin, options, key) { + this.key = void 0; + this.manipulateOptions = void 0; + this.post = void 0; + this.pre = void 0; + this.visitor = void 0; + this.parserOverride = void 0; + this.generatorOverride = void 0; + this.options = void 0; + this.key = plugin.name || key; + this.manipulateOptions = plugin.manipulateOptions; + this.post = plugin.post; + this.pre = plugin.pre; + this.visitor = plugin.visitor || {}; + this.parserOverride = plugin.parserOverride; + this.generatorOverride = plugin.generatorOverride; + this.options = options; + } + +} + +exports["default"] = Plugin; + +/***/ }), + +/***/ 30333: +/***/ ((__unused_webpack_module, exports) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports.ConfigPrinter = exports.ChainFormatter = void 0; +const ChainFormatter = { + Programmatic: 0, + Config: 1 +}; +exports.ChainFormatter = ChainFormatter; +const Formatter = { + title(type, callerName, filepath) { + let title = ""; + + if (type === ChainFormatter.Programmatic) { + title = "programmatic options"; + + if (callerName) { + title += " from " + callerName; + } + } else { + title = "config " + filepath; + } + + return title; + }, + + loc(index, envName) { + let loc = ""; + + if (index != null) { + loc += `.overrides[${index}]`; + } + + if (envName != null) { + loc += `.env["${envName}"]`; + } + + return loc; + }, + + optionsAndDescriptors(opt) { + const content = Object.assign({}, opt.options); + delete content.overrides; + delete content.env; + const pluginDescriptors = [...opt.plugins()]; + + if (pluginDescriptors.length) { + content.plugins = pluginDescriptors.map(d => descriptorToConfig(d)); + } + + const presetDescriptors = [...opt.presets()]; + + if (presetDescriptors.length) { + content.presets = [...presetDescriptors].map(d => descriptorToConfig(d)); + } + + return JSON.stringify(content, undefined, 2); + } + +}; + +function descriptorToConfig(d) { + var _d$file; + + let name = (_d$file = d.file) == null ? void 0 : _d$file.request; + + if (name == null) { + if (typeof d.value === "object") { + name = d.value; + } else if (typeof d.value === "function") { + name = `[Function: ${d.value.toString().substr(0, 50)} ... ]`; + } + } + + if (name == null) { + name = "[Unknown]"; + } + + if (d.options === undefined) { + return name; + } else if (d.name == null) { + return [name, d.options]; + } else { + return [name, d.options, d.name]; + } +} + +class ConfigPrinter { + constructor() { + this._stack = []; + } + + configure(enabled, type, { + callerName, + filepath + }) { + if (!enabled) return () => {}; + return (content, index, envName) => { + this._stack.push({ + type, + callerName, + filepath, + content, + index, + envName + }); + }; + } + + static format(config) { + let title = Formatter.title(config.type, config.callerName, config.filepath); + const loc = Formatter.loc(config.index, config.envName); + if (loc) title += ` ${loc}`; + const content = Formatter.optionsAndDescriptors(config.content); + return `${title}\n${content}`; + } + + output() { + if (this._stack.length === 0) return ""; + return this._stack.map(s => ConfigPrinter.format(s)).join("\n\n"); + } + +} + +exports.ConfigPrinter = ConfigPrinter; + +/***/ }), + +/***/ 54493: +/***/ ((__unused_webpack_module, exports) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports.mergeOptions = mergeOptions; +exports.isIterableIterator = isIterableIterator; + +function mergeOptions(target, source) { + for (const k of Object.keys(source)) { + if (k === "parserOpts" && source.parserOpts) { + const parserOpts = source.parserOpts; + const targetObj = target.parserOpts = target.parserOpts || {}; + mergeDefaultFields(targetObj, parserOpts); + } else if (k === "generatorOpts" && source.generatorOpts) { + const generatorOpts = source.generatorOpts; + const targetObj = target.generatorOpts = target.generatorOpts || {}; + mergeDefaultFields(targetObj, generatorOpts); + } else { + const val = source[k]; + if (val !== undefined) target[k] = val; + } + } +} + +function mergeDefaultFields(target, source) { + for (const k of Object.keys(source)) { + const val = source[k]; + if (val !== undefined) target[k] = val; + } +} + +function isIterableIterator(value) { + return !!value && typeof value.next === "function" && typeof value[Symbol.iterator] === "function"; +} + +/***/ }), + +/***/ 28: +/***/ ((__unused_webpack_module, exports) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports.msg = msg; +exports.access = access; +exports.assertRootMode = assertRootMode; +exports.assertSourceMaps = assertSourceMaps; +exports.assertCompact = assertCompact; +exports.assertSourceType = assertSourceType; +exports.assertCallerMetadata = assertCallerMetadata; +exports.assertInputSourceMap = assertInputSourceMap; +exports.assertString = assertString; +exports.assertFunction = assertFunction; +exports.assertBoolean = assertBoolean; +exports.assertObject = assertObject; +exports.assertArray = assertArray; +exports.assertIgnoreList = assertIgnoreList; +exports.assertConfigApplicableTest = assertConfigApplicableTest; +exports.assertConfigFileSearch = assertConfigFileSearch; +exports.assertBabelrcSearch = assertBabelrcSearch; +exports.assertPluginList = assertPluginList; + +function msg(loc) { + switch (loc.type) { + case "root": + return ``; + + case "env": + return `${msg(loc.parent)}.env["${loc.name}"]`; + + case "overrides": + return `${msg(loc.parent)}.overrides[${loc.index}]`; + + case "option": + return `${msg(loc.parent)}.${loc.name}`; + + case "access": + return `${msg(loc.parent)}[${JSON.stringify(loc.name)}]`; + + default: + throw new Error(`Assertion failure: Unknown type ${loc.type}`); + } +} + +function access(loc, name) { + return { + type: "access", + name, + parent: loc + }; +} + +function assertRootMode(loc, value) { + if (value !== undefined && value !== "root" && value !== "upward" && value !== "upward-optional") { + throw new Error(`${msg(loc)} must be a "root", "upward", "upward-optional" or undefined`); + } + + return value; +} + +function assertSourceMaps(loc, value) { + if (value !== undefined && typeof value !== "boolean" && value !== "inline" && value !== "both") { + throw new Error(`${msg(loc)} must be a boolean, "inline", "both", or undefined`); + } + + return value; +} + +function assertCompact(loc, value) { + if (value !== undefined && typeof value !== "boolean" && value !== "auto") { + throw new Error(`${msg(loc)} must be a boolean, "auto", or undefined`); + } + + return value; +} + +function assertSourceType(loc, value) { + if (value !== undefined && value !== "module" && value !== "script" && value !== "unambiguous") { + throw new Error(`${msg(loc)} must be "module", "script", "unambiguous", or undefined`); + } + + return value; +} + +function assertCallerMetadata(loc, value) { + const obj = assertObject(loc, value); + + if (obj) { + if (typeof obj["name"] !== "string") { + throw new Error(`${msg(loc)} set but does not contain "name" property string`); + } + + for (const prop of Object.keys(obj)) { + const propLoc = access(loc, prop); + const value = obj[prop]; + + if (value != null && typeof value !== "boolean" && typeof value !== "string" && typeof value !== "number") { + throw new Error(`${msg(propLoc)} must be null, undefined, a boolean, a string, or a number.`); + } + } + } + + return value; +} + +function assertInputSourceMap(loc, value) { + if (value !== undefined && typeof value !== "boolean" && (typeof value !== "object" || !value)) { + throw new Error(`${msg(loc)} must be a boolean, object, or undefined`); + } + + return value; +} + +function assertString(loc, value) { + if (value !== undefined && typeof value !== "string") { + throw new Error(`${msg(loc)} must be a string, or undefined`); + } + + return value; +} + +function assertFunction(loc, value) { + if (value !== undefined && typeof value !== "function") { + throw new Error(`${msg(loc)} must be a function, or undefined`); + } + + return value; +} + +function assertBoolean(loc, value) { + if (value !== undefined && typeof value !== "boolean") { + throw new Error(`${msg(loc)} must be a boolean, or undefined`); + } + + return value; +} + +function assertObject(loc, value) { + if (value !== undefined && (typeof value !== "object" || Array.isArray(value) || !value)) { + throw new Error(`${msg(loc)} must be an object, or undefined`); + } + + return value; +} + +function assertArray(loc, value) { + if (value != null && !Array.isArray(value)) { + throw new Error(`${msg(loc)} must be an array, or undefined`); + } + + return value; +} + +function assertIgnoreList(loc, value) { + const arr = assertArray(loc, value); + + if (arr) { + arr.forEach((item, i) => assertIgnoreItem(access(loc, i), item)); + } + + return arr; +} + +function assertIgnoreItem(loc, value) { + if (typeof value !== "string" && typeof value !== "function" && !(value instanceof RegExp)) { + throw new Error(`${msg(loc)} must be an array of string/Function/RegExp values, or undefined`); + } + + return value; +} + +function assertConfigApplicableTest(loc, value) { + if (value === undefined) return value; + + if (Array.isArray(value)) { + value.forEach((item, i) => { + if (!checkValidTest(item)) { + throw new Error(`${msg(access(loc, i))} must be a string/Function/RegExp.`); + } + }); + } else if (!checkValidTest(value)) { + throw new Error(`${msg(loc)} must be a string/Function/RegExp, or an array of those`); + } + + return value; +} + +function checkValidTest(value) { + return typeof value === "string" || typeof value === "function" || value instanceof RegExp; +} + +function assertConfigFileSearch(loc, value) { + if (value !== undefined && typeof value !== "boolean" && typeof value !== "string") { + throw new Error(`${msg(loc)} must be a undefined, a boolean, a string, ` + `got ${JSON.stringify(value)}`); + } + + return value; +} + +function assertBabelrcSearch(loc, value) { + if (value === undefined || typeof value === "boolean") return value; + + if (Array.isArray(value)) { + value.forEach((item, i) => { + if (!checkValidTest(item)) { + throw new Error(`${msg(access(loc, i))} must be a string/Function/RegExp.`); + } + }); + } else if (!checkValidTest(value)) { + throw new Error(`${msg(loc)} must be a undefined, a boolean, a string/Function/RegExp ` + `or an array of those, got ${JSON.stringify(value)}`); + } + + return value; +} + +function assertPluginList(loc, value) { + const arr = assertArray(loc, value); + + if (arr) { + arr.forEach((item, i) => assertPluginItem(access(loc, i), item)); + } + + return arr; +} + +function assertPluginItem(loc, value) { + if (Array.isArray(value)) { + if (value.length === 0) { + throw new Error(`${msg(loc)} must include an object`); + } + + if (value.length > 3) { + throw new Error(`${msg(loc)} may only be a two-tuple or three-tuple`); + } + + assertPluginTarget(access(loc, 0), value[0]); + + if (value.length > 1) { + const opts = value[1]; + + if (opts !== undefined && opts !== false && (typeof opts !== "object" || Array.isArray(opts) || opts === null)) { + throw new Error(`${msg(access(loc, 1))} must be an object, false, or undefined`); + } + } + + if (value.length === 3) { + const name = value[2]; + + if (name !== undefined && typeof name !== "string") { + throw new Error(`${msg(access(loc, 2))} must be a string, or undefined`); + } + } + } else { + assertPluginTarget(loc, value); + } + + return value; +} + +function assertPluginTarget(loc, value) { + if ((typeof value !== "object" || !value) && typeof value !== "string" && typeof value !== "function") { + throw new Error(`${msg(loc)} must be a string, object, function`); + } + + return value; +} + +/***/ }), + +/***/ 44743: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports.validate = validate; +exports.checkNoUnwrappedItemOptionPairs = checkNoUnwrappedItemOptionPairs; + +var _plugin = _interopRequireDefault(__nccwpck_require__(68618)); + +var _removed = _interopRequireDefault(__nccwpck_require__(87269)); + +var _optionAssertions = __nccwpck_require__(28); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +const ROOT_VALIDATORS = { + cwd: _optionAssertions.assertString, + root: _optionAssertions.assertString, + rootMode: _optionAssertions.assertRootMode, + configFile: _optionAssertions.assertConfigFileSearch, + caller: _optionAssertions.assertCallerMetadata, + filename: _optionAssertions.assertString, + filenameRelative: _optionAssertions.assertString, + code: _optionAssertions.assertBoolean, + ast: _optionAssertions.assertBoolean, + cloneInputAst: _optionAssertions.assertBoolean, + envName: _optionAssertions.assertString +}; +const BABELRC_VALIDATORS = { + babelrc: _optionAssertions.assertBoolean, + babelrcRoots: _optionAssertions.assertBabelrcSearch +}; +const NONPRESET_VALIDATORS = { + extends: _optionAssertions.assertString, + ignore: _optionAssertions.assertIgnoreList, + only: _optionAssertions.assertIgnoreList +}; +const COMMON_VALIDATORS = { + inputSourceMap: _optionAssertions.assertInputSourceMap, + presets: _optionAssertions.assertPluginList, + plugins: _optionAssertions.assertPluginList, + passPerPreset: _optionAssertions.assertBoolean, + env: assertEnvSet, + overrides: assertOverridesList, + test: _optionAssertions.assertConfigApplicableTest, + include: _optionAssertions.assertConfigApplicableTest, + exclude: _optionAssertions.assertConfigApplicableTest, + retainLines: _optionAssertions.assertBoolean, + comments: _optionAssertions.assertBoolean, + shouldPrintComment: _optionAssertions.assertFunction, + compact: _optionAssertions.assertCompact, + minified: _optionAssertions.assertBoolean, + auxiliaryCommentBefore: _optionAssertions.assertString, + auxiliaryCommentAfter: _optionAssertions.assertString, + sourceType: _optionAssertions.assertSourceType, + wrapPluginVisitorMethod: _optionAssertions.assertFunction, + highlightCode: _optionAssertions.assertBoolean, + sourceMaps: _optionAssertions.assertSourceMaps, + sourceMap: _optionAssertions.assertSourceMaps, + sourceFileName: _optionAssertions.assertString, + sourceRoot: _optionAssertions.assertString, + getModuleId: _optionAssertions.assertFunction, + moduleRoot: _optionAssertions.assertString, + moduleIds: _optionAssertions.assertBoolean, + moduleId: _optionAssertions.assertString, + parserOpts: _optionAssertions.assertObject, + generatorOpts: _optionAssertions.assertObject +}; + +function getSource(loc) { + return loc.type === "root" ? loc.source : getSource(loc.parent); +} + +function validate(type, opts) { + return validateNested({ + type: "root", + source: type + }, opts); +} + +function validateNested(loc, opts) { + const type = getSource(loc); + assertNoDuplicateSourcemap(opts); + Object.keys(opts).forEach(key => { + const optLoc = { + type: "option", + name: key, + parent: loc + }; + + if (type === "preset" && NONPRESET_VALIDATORS[key]) { + throw new Error(`${(0, _optionAssertions.msg)(optLoc)} is not allowed in preset options`); + } + + if (type !== "arguments" && ROOT_VALIDATORS[key]) { + throw new Error(`${(0, _optionAssertions.msg)(optLoc)} is only allowed in root programmatic options`); + } + + if (type !== "arguments" && type !== "configfile" && BABELRC_VALIDATORS[key]) { + if (type === "babelrcfile" || type === "extendsfile") { + throw new Error(`${(0, _optionAssertions.msg)(optLoc)} is not allowed in .babelrc or "extends"ed files, only in root programmatic options, ` + `or babel.config.js/config file options`); + } + + throw new Error(`${(0, _optionAssertions.msg)(optLoc)} is only allowed in root programmatic options, or babel.config.js/config file options`); + } + + const validator = COMMON_VALIDATORS[key] || NONPRESET_VALIDATORS[key] || BABELRC_VALIDATORS[key] || ROOT_VALIDATORS[key] || throwUnknownError; + validator(optLoc, opts[key]); + }); + return opts; +} + +function throwUnknownError(loc) { + const key = loc.name; + + if (_removed.default[key]) { + const { + message, + version = 5 + } = _removed.default[key]; + throw new Error(`Using removed Babel ${version} option: ${(0, _optionAssertions.msg)(loc)} - ${message}`); + } else { + const unknownOptErr = new Error(`Unknown option: ${(0, _optionAssertions.msg)(loc)}. Check out https://babeljs.io/docs/en/babel-core/#options for more information about options.`); + unknownOptErr.code = "BABEL_UNKNOWN_OPTION"; + throw unknownOptErr; + } +} + +function has(obj, key) { + return Object.prototype.hasOwnProperty.call(obj, key); +} + +function assertNoDuplicateSourcemap(opts) { + if (has(opts, "sourceMap") && has(opts, "sourceMaps")) { + throw new Error(".sourceMap is an alias for .sourceMaps, cannot use both"); + } +} + +function assertEnvSet(loc, value) { + if (loc.parent.type === "env") { + throw new Error(`${(0, _optionAssertions.msg)(loc)} is not allowed inside of another .env block`); + } + + const parent = loc.parent; + const obj = (0, _optionAssertions.assertObject)(loc, value); + + if (obj) { + for (const envName of Object.keys(obj)) { + const env = (0, _optionAssertions.assertObject)((0, _optionAssertions.access)(loc, envName), obj[envName]); + if (!env) continue; + const envLoc = { + type: "env", + name: envName, + parent + }; + validateNested(envLoc, env); + } + } + + return obj; +} + +function assertOverridesList(loc, value) { + if (loc.parent.type === "env") { + throw new Error(`${(0, _optionAssertions.msg)(loc)} is not allowed inside an .env block`); + } + + if (loc.parent.type === "overrides") { + throw new Error(`${(0, _optionAssertions.msg)(loc)} is not allowed inside an .overrides block`); + } + + const parent = loc.parent; + const arr = (0, _optionAssertions.assertArray)(loc, value); + + if (arr) { + for (const [index, item] of arr.entries()) { + const objLoc = (0, _optionAssertions.access)(loc, index); + const env = (0, _optionAssertions.assertObject)(objLoc, item); + if (!env) throw new Error(`${(0, _optionAssertions.msg)(objLoc)} must be an object`); + const overridesLoc = { + type: "overrides", + index, + parent + }; + validateNested(overridesLoc, env); + } + } + + return arr; +} + +function checkNoUnwrappedItemOptionPairs(items, index, type, e) { + if (index === 0) return; + const lastItem = items[index - 1]; + const thisItem = items[index]; + + if (lastItem.file && lastItem.options === undefined && typeof thisItem.value === "object") { + e.message += `\n- Maybe you meant to use\n` + `"${type}": [\n ["${lastItem.file.request}", ${JSON.stringify(thisItem.value, undefined, 2)}]\n]\n` + `To be a valid ${type}, its name and options should be wrapped in a pair of brackets`; + } +} + +/***/ }), + +/***/ 22449: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports.validatePluginObject = validatePluginObject; + +var _optionAssertions = __nccwpck_require__(28); + +const VALIDATORS = { + name: _optionAssertions.assertString, + manipulateOptions: _optionAssertions.assertFunction, + pre: _optionAssertions.assertFunction, + post: _optionAssertions.assertFunction, + inherits: _optionAssertions.assertFunction, + visitor: assertVisitorMap, + parserOverride: _optionAssertions.assertFunction, + generatorOverride: _optionAssertions.assertFunction +}; + +function assertVisitorMap(loc, value) { + const obj = (0, _optionAssertions.assertObject)(loc, value); + + if (obj) { + Object.keys(obj).forEach(prop => assertVisitorHandler(prop, obj[prop])); + + if (obj.enter || obj.exit) { + throw new Error(`${(0, _optionAssertions.msg)(loc)} cannot contain catch-all "enter" or "exit" handlers. Please target individual nodes.`); + } + } + + return obj; +} + +function assertVisitorHandler(key, value) { + if (value && typeof value === "object") { + Object.keys(value).forEach(handler => { + if (handler !== "enter" && handler !== "exit") { + throw new Error(`.visitor["${key}"] may only have .enter and/or .exit handlers.`); + } + }); + } else if (typeof value !== "function") { + throw new Error(`.visitor["${key}"] must be a function`); + } + + return value; +} + +function validatePluginObject(obj) { + const rootPath = { + type: "root", + source: "plugin" + }; + Object.keys(obj).forEach(key => { + const validator = VALIDATORS[key]; + + if (validator) { + const optLoc = { + type: "option", + name: key, + parent: rootPath + }; + validator(optLoc, obj[key]); + } else { + const invalidPluginPropertyError = new Error(`.${key} is not a valid Plugin property`); + invalidPluginPropertyError.code = "BABEL_UNKNOWN_PLUGIN_PROPERTY"; + throw invalidPluginPropertyError; + } + }); + return obj; +} + +/***/ }), + +/***/ 87269: +/***/ ((__unused_webpack_module, exports) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports["default"] = void 0; +var _default = { + auxiliaryComment: { + message: "Use `auxiliaryCommentBefore` or `auxiliaryCommentAfter`" + }, + blacklist: { + message: "Put the specific transforms you want in the `plugins` option" + }, + breakConfig: { + message: "This is not a necessary option in Babel 6" + }, + experimental: { + message: "Put the specific transforms you want in the `plugins` option" + }, + externalHelpers: { + message: "Use the `external-helpers` plugin instead. " + "Check out http://babeljs.io/docs/plugins/external-helpers/" + }, + extra: { + message: "" + }, + jsxPragma: { + message: "use the `pragma` option in the `react-jsx` plugin. " + "Check out http://babeljs.io/docs/plugins/transform-react-jsx/" + }, + loose: { + message: "Specify the `loose` option for the relevant plugin you are using " + "or use a preset that sets the option." + }, + metadataUsedHelpers: { + message: "Not required anymore as this is enabled by default" + }, + modules: { + message: "Use the corresponding module transform plugin in the `plugins` option. " + "Check out http://babeljs.io/docs/plugins/#modules" + }, + nonStandard: { + message: "Use the `react-jsx` and `flow-strip-types` plugins to support JSX and Flow. " + "Also check out the react preset http://babeljs.io/docs/plugins/preset-react/" + }, + optional: { + message: "Put the specific transforms you want in the `plugins` option" + }, + sourceMapName: { + message: "The `sourceMapName` option has been removed because it makes more sense for the " + "tooling that calls Babel to assign `map.file` themselves." + }, + stage: { + message: "Check out the corresponding stage-x presets http://babeljs.io/docs/plugins/#presets" + }, + whitelist: { + message: "Put the specific transforms you want in the `plugins` option" + }, + resolveModuleSource: { + version: 6, + message: "Use `babel-plugin-module-resolver@3`'s 'resolvePath' options" + }, + metadata: { + version: 6, + message: "Generated plugin metadata is always included in the output result" + }, + sourceMapTarget: { + version: 6, + message: "The `sourceMapTarget` option has been removed because it makes more sense for the tooling " + "that calls Babel to assign `map.file` themselves." + } +}; +exports["default"] = _default; + +/***/ }), + +/***/ 87360: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports.maybeAsync = maybeAsync; +exports.forwardAsync = forwardAsync; +exports.isThenable = isThenable; +exports.waitFor = exports.onFirstPause = exports.isAsync = void 0; + +function _gensync() { + const data = _interopRequireDefault(__nccwpck_require__(9621)); + + _gensync = function () { + return data; + }; + + return data; +} + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +const id = x => x; + +const runGenerator = (0, _gensync().default)(function* (item) { + return yield* item; +}); +const isAsync = (0, _gensync().default)({ + sync: () => false, + errback: cb => cb(null, true) +}); +exports.isAsync = isAsync; + +function maybeAsync(fn, message) { + return (0, _gensync().default)({ + sync(...args) { + const result = fn.apply(this, args); + if (isThenable(result)) throw new Error(message); + return result; + }, + + async(...args) { + return Promise.resolve(fn.apply(this, args)); + } + + }); +} + +const withKind = (0, _gensync().default)({ + sync: cb => cb("sync"), + async: cb => cb("async") +}); + +function forwardAsync(action, cb) { + const g = (0, _gensync().default)(action); + return withKind(kind => { + const adapted = g[kind]; + return cb(adapted); + }); +} + +const onFirstPause = (0, _gensync().default)({ + name: "onFirstPause", + arity: 2, + sync: function (item) { + return runGenerator.sync(item); + }, + errback: function (item, firstPause, cb) { + let completed = false; + runGenerator.errback(item, (err, value) => { + completed = true; + cb(err, value); + }); + + if (!completed) { + firstPause(); + } + } +}); +exports.onFirstPause = onFirstPause; +const waitFor = (0, _gensync().default)({ + sync: id, + async: id +}); +exports.waitFor = waitFor; + +function isThenable(val) { + return !!val && (typeof val === "object" || typeof val === "function") && !!val.then && typeof val.then === "function"; +} + +/***/ }), + +/***/ 10733: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports.stat = exports.exists = exports.readFile = void 0; + +function _fs() { + const data = _interopRequireDefault(__nccwpck_require__(79896)); + + _fs = function () { + return data; + }; + + return data; +} + +function _gensync() { + const data = _interopRequireDefault(__nccwpck_require__(9621)); + + _gensync = function () { + return data; + }; + + return data; +} + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +const readFile = (0, _gensync().default)({ + sync: _fs().default.readFileSync, + errback: _fs().default.readFile +}); +exports.readFile = readFile; +const exists = (0, _gensync().default)({ + sync(path) { + try { + _fs().default.accessSync(path); + + return true; + } catch (_unused) { + return false; + } + }, + + errback: (path, cb) => _fs().default.access(path, undefined, err => cb(null, !err)) +}); +exports.exists = exists; +const stat = (0, _gensync().default)({ + sync: _fs().default.statSync, + errback: _fs().default.stat +}); +exports.stat = stat; + +/***/ }), + +/***/ 12620: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports["default"] = void 0; + +function _resolve() { + const data = _interopRequireDefault(__nccwpck_require__(92312)); + + _resolve = function () { + return data; + }; + + return data; +} + +function _gensync() { + const data = _interopRequireDefault(__nccwpck_require__(9621)); + + _gensync = function () { + return data; + }; + + return data; +} + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +var _default = (0, _gensync().default)({ + sync: _resolve().default.sync, + errback: _resolve().default +}); + +exports["default"] = _default; + +/***/ }), + +/***/ 85414: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports.Plugin = Plugin; +Object.defineProperty(exports, "File", ({ + enumerable: true, + get: function () { + return _file.default; + } +})); +Object.defineProperty(exports, "buildExternalHelpers", ({ + enumerable: true, + get: function () { + return _buildExternalHelpers.default; + } +})); +Object.defineProperty(exports, "resolvePlugin", ({ + enumerable: true, + get: function () { + return _files.resolvePlugin; + } +})); +Object.defineProperty(exports, "resolvePreset", ({ + enumerable: true, + get: function () { + return _files.resolvePreset; + } +})); +Object.defineProperty(exports, "version", ({ + enumerable: true, + get: function () { + return _package.version; + } +})); +Object.defineProperty(exports, "getEnv", ({ + enumerable: true, + get: function () { + return _environment.getEnv; + } +})); +Object.defineProperty(exports, "tokTypes", ({ + enumerable: true, + get: function () { + return _parser().tokTypes; + } +})); +Object.defineProperty(exports, "traverse", ({ + enumerable: true, + get: function () { + return _traverse().default; + } +})); +Object.defineProperty(exports, "template", ({ + enumerable: true, + get: function () { + return _template().default; + } +})); +Object.defineProperty(exports, "createConfigItem", ({ + enumerable: true, + get: function () { + return _item.createConfigItem; + } +})); +Object.defineProperty(exports, "loadPartialConfig", ({ + enumerable: true, + get: function () { + return _config.loadPartialConfig; + } +})); +Object.defineProperty(exports, "loadPartialConfigSync", ({ + enumerable: true, + get: function () { + return _config.loadPartialConfigSync; + } +})); +Object.defineProperty(exports, "loadPartialConfigAsync", ({ + enumerable: true, + get: function () { + return _config.loadPartialConfigAsync; + } +})); +Object.defineProperty(exports, "loadOptions", ({ + enumerable: true, + get: function () { + return _config.loadOptions; + } +})); +Object.defineProperty(exports, "loadOptionsSync", ({ + enumerable: true, + get: function () { + return _config.loadOptionsSync; + } +})); +Object.defineProperty(exports, "loadOptionsAsync", ({ + enumerable: true, + get: function () { + return _config.loadOptionsAsync; + } +})); +Object.defineProperty(exports, "transform", ({ + enumerable: true, + get: function () { + return _transform.transform; + } +})); +Object.defineProperty(exports, "transformSync", ({ + enumerable: true, + get: function () { + return _transform.transformSync; + } +})); +Object.defineProperty(exports, "transformAsync", ({ + enumerable: true, + get: function () { + return _transform.transformAsync; + } +})); +Object.defineProperty(exports, "transformFile", ({ + enumerable: true, + get: function () { + return _transformFile.transformFile; + } +})); +Object.defineProperty(exports, "transformFileSync", ({ + enumerable: true, + get: function () { + return _transformFile.transformFileSync; + } +})); +Object.defineProperty(exports, "transformFileAsync", ({ + enumerable: true, + get: function () { + return _transformFile.transformFileAsync; + } +})); +Object.defineProperty(exports, "transformFromAst", ({ + enumerable: true, + get: function () { + return _transformAst.transformFromAst; + } +})); +Object.defineProperty(exports, "transformFromAstSync", ({ + enumerable: true, + get: function () { + return _transformAst.transformFromAstSync; + } +})); +Object.defineProperty(exports, "transformFromAstAsync", ({ + enumerable: true, + get: function () { + return _transformAst.transformFromAstAsync; + } +})); +Object.defineProperty(exports, "parse", ({ + enumerable: true, + get: function () { + return _parse.parse; + } +})); +Object.defineProperty(exports, "parseSync", ({ + enumerable: true, + get: function () { + return _parse.parseSync; + } +})); +Object.defineProperty(exports, "parseAsync", ({ + enumerable: true, + get: function () { + return _parse.parseAsync; + } +})); +exports.types = exports.OptionManager = exports.DEFAULT_EXTENSIONS = void 0; + +var _file = _interopRequireDefault(__nccwpck_require__(12211)); + +var _buildExternalHelpers = _interopRequireDefault(__nccwpck_require__(60316)); + +var _files = __nccwpck_require__(37217); + +var _package = __nccwpck_require__(29602); + +var _environment = __nccwpck_require__(3110); + +function _types() { + const data = _interopRequireWildcard(__nccwpck_require__(16535)); + + _types = function () { + return data; + }; + + return data; +} + +Object.defineProperty(exports, "types", ({ + enumerable: true, + get: function () { + return _types(); + } +})); + +function _parser() { + const data = __nccwpck_require__(5429); + + _parser = function () { + return data; + }; + + return data; +} + +function _traverse() { + const data = _interopRequireDefault(__nccwpck_require__(50148)); + + _traverse = function () { + return data; + }; + + return data; +} + +function _template() { + const data = _interopRequireDefault(__nccwpck_require__(19648)); + + _template = function () { + return data; + }; + + return data; +} + +var _item = __nccwpck_require__(72478); + +var _config = __nccwpck_require__(73677); + +var _transform = __nccwpck_require__(46442); + +var _transformFile = __nccwpck_require__(39037); + +var _transformAst = __nccwpck_require__(98239); + +var _parse = __nccwpck_require__(35973); + +function _getRequireWildcardCache() { if (typeof WeakMap !== "function") return null; var cache = new WeakMap(); _getRequireWildcardCache = function () { return cache; }; return cache; } + +function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; } + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +const DEFAULT_EXTENSIONS = Object.freeze([".js", ".jsx", ".es6", ".es", ".mjs"]); +exports.DEFAULT_EXTENSIONS = DEFAULT_EXTENSIONS; + +class OptionManager { + init(opts) { + return (0, _config.loadOptions)(opts); + } + +} + +exports.OptionManager = OptionManager; + +function Plugin(alias) { + throw new Error(`The (${alias}) Babel 5 plugin is being run with an unsupported Babel version.`); +} + +/***/ }), + +/***/ 35973: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports.parseAsync = exports.parseSync = exports.parse = void 0; + +function _gensync() { + const data = _interopRequireDefault(__nccwpck_require__(9621)); + + _gensync = function () { + return data; + }; + + return data; +} + +var _config = _interopRequireDefault(__nccwpck_require__(73677)); + +var _parser = _interopRequireDefault(__nccwpck_require__(99904)); + +var _normalizeOpts = _interopRequireDefault(__nccwpck_require__(74944)); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +const parseRunner = (0, _gensync().default)(function* parse(code, opts) { + const config = yield* (0, _config.default)(opts); + + if (config === null) { + return null; + } + + return yield* (0, _parser.default)(config.passes, (0, _normalizeOpts.default)(config), code); +}); + +const parse = function parse(code, opts, callback) { + if (typeof opts === "function") { + callback = opts; + opts = undefined; + } + + if (callback === undefined) return parseRunner.sync(code, opts); + parseRunner.errback(code, opts, callback); +}; + +exports.parse = parse; +const parseSync = parseRunner.sync; +exports.parseSync = parseSync; +const parseAsync = parseRunner.async; +exports.parseAsync = parseAsync; + +/***/ }), + +/***/ 99904: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports["default"] = parser; + +function _parser() { + const data = __nccwpck_require__(5429); + + _parser = function () { + return data; + }; + + return data; +} + +function _codeFrame() { + const data = __nccwpck_require__(90147); + + _codeFrame = function () { + return data; + }; + + return data; +} + +var _missingPluginHelper = _interopRequireDefault(__nccwpck_require__(3498)); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +function* parser(pluginPasses, { + parserOpts, + highlightCode = true, + filename = "unknown" +}, code) { + try { + const results = []; + + for (const plugins of pluginPasses) { + for (const plugin of plugins) { + const { + parserOverride + } = plugin; + + if (parserOverride) { + const ast = parserOverride(code, parserOpts, _parser().parse); + if (ast !== undefined) results.push(ast); + } + } + } + + if (results.length === 0) { + return (0, _parser().parse)(code, parserOpts); + } else if (results.length === 1) { + yield* []; + + if (typeof results[0].then === "function") { + throw new Error(`You appear to be using an async parser plugin, ` + `which your current version of Babel does not support. ` + `If you're using a published plugin, you may need to upgrade ` + `your @babel/core version.`); + } + + return results[0]; + } + + throw new Error("More than one plugin attempted to override parsing."); + } catch (err) { + if (err.code === "BABEL_PARSER_SOURCETYPE_MODULE_REQUIRED") { + err.message += "\nConsider renaming the file to '.mjs', or setting sourceType:module " + "or sourceType:unambiguous in your Babel config for this file."; + } + + const { + loc, + missingPlugin + } = err; + + if (loc) { + const codeFrame = (0, _codeFrame().codeFrameColumns)(code, { + start: { + line: loc.line, + column: loc.column + 1 + } + }, { + highlightCode + }); + + if (missingPlugin) { + err.message = `${filename}: ` + (0, _missingPluginHelper.default)(missingPlugin[0], loc, codeFrame); + } else { + err.message = `${filename}: ${err.message}\n\n` + codeFrame; + } + + err.code = "BABEL_PARSE_ERROR"; + } + + throw err; + } +} + +/***/ }), + +/***/ 3498: +/***/ ((__unused_webpack_module, exports) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports["default"] = generateMissingPluginMessage; +const pluginNameMap = { + classProperties: { + syntax: { + name: "@babel/plugin-syntax-class-properties", + url: "https://git.io/vb4yQ" + }, + transform: { + name: "@babel/plugin-proposal-class-properties", + url: "https://git.io/vb4SL" + } + }, + classPrivateProperties: { + syntax: { + name: "@babel/plugin-syntax-class-properties", + url: "https://git.io/vb4yQ" + }, + transform: { + name: "@babel/plugin-proposal-class-properties", + url: "https://git.io/vb4SL" + } + }, + classPrivateMethods: { + syntax: { + name: "@babel/plugin-syntax-class-properties", + url: "https://git.io/vb4yQ" + }, + transform: { + name: "@babel/plugin-proposal-private-methods", + url: "https://git.io/JvpRG" + } + }, + classStaticBlock: { + syntax: { + name: "@babel/plugin-syntax-class-static-block", + url: "https://git.io/JTLB6" + }, + transform: { + name: "@babel/plugin-proposal-class-static-block", + url: "https://git.io/JTLBP" + } + }, + decimal: { + syntax: { + name: "@babel/plugin-syntax-decimal", + url: "https://git.io/JfKOH" + } + }, + decorators: { + syntax: { + name: "@babel/plugin-syntax-decorators", + url: "https://git.io/vb4y9" + }, + transform: { + name: "@babel/plugin-proposal-decorators", + url: "https://git.io/vb4ST" + } + }, + doExpressions: { + syntax: { + name: "@babel/plugin-syntax-do-expressions", + url: "https://git.io/vb4yh" + }, + transform: { + name: "@babel/plugin-proposal-do-expressions", + url: "https://git.io/vb4S3" + } + }, + dynamicImport: { + syntax: { + name: "@babel/plugin-syntax-dynamic-import", + url: "https://git.io/vb4Sv" + } + }, + exportDefaultFrom: { + syntax: { + name: "@babel/plugin-syntax-export-default-from", + url: "https://git.io/vb4SO" + }, + transform: { + name: "@babel/plugin-proposal-export-default-from", + url: "https://git.io/vb4yH" + } + }, + exportNamespaceFrom: { + syntax: { + name: "@babel/plugin-syntax-export-namespace-from", + url: "https://git.io/vb4Sf" + }, + transform: { + name: "@babel/plugin-proposal-export-namespace-from", + url: "https://git.io/vb4SG" + } + }, + flow: { + syntax: { + name: "@babel/plugin-syntax-flow", + url: "https://git.io/vb4yb" + }, + transform: { + name: "@babel/preset-flow", + url: "https://git.io/JfeDn" + } + }, + functionBind: { + syntax: { + name: "@babel/plugin-syntax-function-bind", + url: "https://git.io/vb4y7" + }, + transform: { + name: "@babel/plugin-proposal-function-bind", + url: "https://git.io/vb4St" + } + }, + functionSent: { + syntax: { + name: "@babel/plugin-syntax-function-sent", + url: "https://git.io/vb4yN" + }, + transform: { + name: "@babel/plugin-proposal-function-sent", + url: "https://git.io/vb4SZ" + } + }, + importMeta: { + syntax: { + name: "@babel/plugin-syntax-import-meta", + url: "https://git.io/vbKK6" + } + }, + jsx: { + syntax: { + name: "@babel/plugin-syntax-jsx", + url: "https://git.io/vb4yA" + }, + transform: { + name: "@babel/preset-react", + url: "https://git.io/JfeDR" + } + }, + importAssertions: { + syntax: { + name: "@babel/plugin-syntax-import-assertions", + url: "https://git.io/JUbkv" + } + }, + moduleStringNames: { + syntax: { + name: "@babel/plugin-syntax-module-string-names", + url: "https://git.io/JTL8G" + } + }, + numericSeparator: { + syntax: { + name: "@babel/plugin-syntax-numeric-separator", + url: "https://git.io/vb4Sq" + }, + transform: { + name: "@babel/plugin-proposal-numeric-separator", + url: "https://git.io/vb4yS" + } + }, + optionalChaining: { + syntax: { + name: "@babel/plugin-syntax-optional-chaining", + url: "https://git.io/vb4Sc" + }, + transform: { + name: "@babel/plugin-proposal-optional-chaining", + url: "https://git.io/vb4Sk" + } + }, + pipelineOperator: { + syntax: { + name: "@babel/plugin-syntax-pipeline-operator", + url: "https://git.io/vb4yj" + }, + transform: { + name: "@babel/plugin-proposal-pipeline-operator", + url: "https://git.io/vb4SU" + } + }, + privateIn: { + syntax: { + name: "@babel/plugin-syntax-private-property-in-object", + url: "https://git.io/JfK3q" + }, + transform: { + name: "@babel/plugin-proposal-private-property-in-object", + url: "https://git.io/JfK3O" + } + }, + recordAndTuple: { + syntax: { + name: "@babel/plugin-syntax-record-and-tuple", + url: "https://git.io/JvKp3" + } + }, + throwExpressions: { + syntax: { + name: "@babel/plugin-syntax-throw-expressions", + url: "https://git.io/vb4SJ" + }, + transform: { + name: "@babel/plugin-proposal-throw-expressions", + url: "https://git.io/vb4yF" + } + }, + typescript: { + syntax: { + name: "@babel/plugin-syntax-typescript", + url: "https://git.io/vb4SC" + }, + transform: { + name: "@babel/preset-typescript", + url: "https://git.io/JfeDz" + } + }, + asyncGenerators: { + syntax: { + name: "@babel/plugin-syntax-async-generators", + url: "https://git.io/vb4SY" + }, + transform: { + name: "@babel/plugin-proposal-async-generator-functions", + url: "https://git.io/vb4yp" + } + }, + logicalAssignment: { + syntax: { + name: "@babel/plugin-syntax-logical-assignment-operators", + url: "https://git.io/vAlBp" + }, + transform: { + name: "@babel/plugin-proposal-logical-assignment-operators", + url: "https://git.io/vAlRe" + } + }, + nullishCoalescingOperator: { + syntax: { + name: "@babel/plugin-syntax-nullish-coalescing-operator", + url: "https://git.io/vb4yx" + }, + transform: { + name: "@babel/plugin-proposal-nullish-coalescing-operator", + url: "https://git.io/vb4Se" + } + }, + objectRestSpread: { + syntax: { + name: "@babel/plugin-syntax-object-rest-spread", + url: "https://git.io/vb4y5" + }, + transform: { + name: "@babel/plugin-proposal-object-rest-spread", + url: "https://git.io/vb4Ss" + } + }, + optionalCatchBinding: { + syntax: { + name: "@babel/plugin-syntax-optional-catch-binding", + url: "https://git.io/vb4Sn" + }, + transform: { + name: "@babel/plugin-proposal-optional-catch-binding", + url: "https://git.io/vb4SI" + } + } +}; +pluginNameMap.privateIn.syntax = pluginNameMap.privateIn.transform; + +const getNameURLCombination = ({ + name, + url +}) => `${name} (${url})`; + +function generateMissingPluginMessage(missingPluginName, loc, codeFrame) { + let helpMessage = `Support for the experimental syntax '${missingPluginName}' isn't currently enabled ` + `(${loc.line}:${loc.column + 1}):\n\n` + codeFrame; + const pluginInfo = pluginNameMap[missingPluginName]; + + if (pluginInfo) { + const { + syntax: syntaxPlugin, + transform: transformPlugin + } = pluginInfo; + + if (syntaxPlugin) { + const syntaxPluginInfo = getNameURLCombination(syntaxPlugin); + + if (transformPlugin) { + const transformPluginInfo = getNameURLCombination(transformPlugin); + const sectionType = transformPlugin.name.startsWith("@babel/plugin") ? "plugins" : "presets"; + helpMessage += `\n\nAdd ${transformPluginInfo} to the '${sectionType}' section of your Babel config to enable transformation. +If you want to leave it as-is, add ${syntaxPluginInfo} to the 'plugins' section to enable parsing.`; + } else { + helpMessage += `\n\nAdd ${syntaxPluginInfo} to the 'plugins' section of your Babel config ` + `to enable parsing.`; + } + } + } + + return helpMessage; +} + +/***/ }), + +/***/ 60316: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports["default"] = _default; + +function helpers() { + const data = _interopRequireWildcard(__nccwpck_require__(71475)); + + helpers = function () { + return data; + }; + + return data; +} + +function _generator() { + const data = _interopRequireDefault(__nccwpck_require__(12123)); + + _generator = function () { + return data; + }; + + return data; +} + +function _template() { + const data = _interopRequireDefault(__nccwpck_require__(19648)); + + _template = function () { + return data; + }; + + return data; +} + +function t() { + const data = _interopRequireWildcard(__nccwpck_require__(16535)); + + t = function () { + return data; + }; + + return data; +} + +var _file = _interopRequireDefault(__nccwpck_require__(12211)); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +function _getRequireWildcardCache() { if (typeof WeakMap !== "function") return null; var cache = new WeakMap(); _getRequireWildcardCache = function () { return cache; }; return cache; } + +function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; } + +const buildUmdWrapper = replacements => (0, _template().default)` + (function (root, factory) { + if (typeof define === "function" && define.amd) { + define(AMD_ARGUMENTS, factory); + } else if (typeof exports === "object") { + factory(COMMON_ARGUMENTS); + } else { + factory(BROWSER_ARGUMENTS); + } + })(UMD_ROOT, function (FACTORY_PARAMETERS) { + FACTORY_BODY + }); + `(replacements); + +function buildGlobal(allowlist) { + const namespace = t().identifier("babelHelpers"); + const body = []; + const container = t().functionExpression(null, [t().identifier("global")], t().blockStatement(body)); + const tree = t().program([t().expressionStatement(t().callExpression(container, [t().conditionalExpression(t().binaryExpression("===", t().unaryExpression("typeof", t().identifier("global")), t().stringLiteral("undefined")), t().identifier("self"), t().identifier("global"))]))]); + body.push(t().variableDeclaration("var", [t().variableDeclarator(namespace, t().assignmentExpression("=", t().memberExpression(t().identifier("global"), namespace), t().objectExpression([])))])); + buildHelpers(body, namespace, allowlist); + return tree; +} + +function buildModule(allowlist) { + const body = []; + const refs = buildHelpers(body, null, allowlist); + body.unshift(t().exportNamedDeclaration(null, Object.keys(refs).map(name => { + return t().exportSpecifier(t().cloneNode(refs[name]), t().identifier(name)); + }))); + return t().program(body, [], "module"); +} + +function buildUmd(allowlist) { + const namespace = t().identifier("babelHelpers"); + const body = []; + body.push(t().variableDeclaration("var", [t().variableDeclarator(namespace, t().identifier("global"))])); + buildHelpers(body, namespace, allowlist); + return t().program([buildUmdWrapper({ + FACTORY_PARAMETERS: t().identifier("global"), + BROWSER_ARGUMENTS: t().assignmentExpression("=", t().memberExpression(t().identifier("root"), namespace), t().objectExpression([])), + COMMON_ARGUMENTS: t().identifier("exports"), + AMD_ARGUMENTS: t().arrayExpression([t().stringLiteral("exports")]), + FACTORY_BODY: body, + UMD_ROOT: t().identifier("this") + })]); +} + +function buildVar(allowlist) { + const namespace = t().identifier("babelHelpers"); + const body = []; + body.push(t().variableDeclaration("var", [t().variableDeclarator(namespace, t().objectExpression([]))])); + const tree = t().program(body); + buildHelpers(body, namespace, allowlist); + body.push(t().expressionStatement(namespace)); + return tree; +} + +function buildHelpers(body, namespace, allowlist) { + const getHelperReference = name => { + return namespace ? t().memberExpression(namespace, t().identifier(name)) : t().identifier(`_${name}`); + }; + + const refs = {}; + helpers().list.forEach(function (name) { + if (allowlist && allowlist.indexOf(name) < 0) return; + const ref = refs[name] = getHelperReference(name); + helpers().ensure(name, _file.default); + const { + nodes + } = helpers().get(name, getHelperReference, ref); + body.push(...nodes); + }); + return refs; +} + +function _default(allowlist, outputType = "global") { + let tree; + const build = { + global: buildGlobal, + module: buildModule, + umd: buildUmd, + var: buildVar + }[outputType]; + + if (build) { + tree = build(allowlist); + } else { + throw new Error(`Unsupported output type ${outputType}`); + } + + return (0, _generator().default)(tree).code; +} + +/***/ }), + +/***/ 98239: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports.transformFromAstAsync = exports.transformFromAstSync = exports.transformFromAst = void 0; + +function _gensync() { + const data = _interopRequireDefault(__nccwpck_require__(9621)); + + _gensync = function () { + return data; + }; + + return data; +} + +var _config = _interopRequireDefault(__nccwpck_require__(73677)); + +var _transformation = __nccwpck_require__(16486); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +const transformFromAstRunner = (0, _gensync().default)(function* (ast, code, opts) { + const config = yield* (0, _config.default)(opts); + if (config === null) return null; + if (!ast) throw new Error("No AST given"); + return yield* (0, _transformation.run)(config, code, ast); +}); + +const transformFromAst = function transformFromAst(ast, code, opts, callback) { + if (typeof opts === "function") { + callback = opts; + opts = undefined; + } + + if (callback === undefined) { + return transformFromAstRunner.sync(ast, code, opts); + } + + transformFromAstRunner.errback(ast, code, opts, callback); +}; + +exports.transformFromAst = transformFromAst; +const transformFromAstSync = transformFromAstRunner.sync; +exports.transformFromAstSync = transformFromAstSync; +const transformFromAstAsync = transformFromAstRunner.async; +exports.transformFromAstAsync = transformFromAstAsync; + +/***/ }), + +/***/ 39037: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports.transformFileAsync = exports.transformFileSync = exports.transformFile = void 0; + +function _gensync() { + const data = _interopRequireDefault(__nccwpck_require__(9621)); + + _gensync = function () { + return data; + }; + + return data; +} + +var _config = _interopRequireDefault(__nccwpck_require__(73677)); + +var _transformation = __nccwpck_require__(16486); + +var fs = _interopRequireWildcard(__nccwpck_require__(10733)); + +function _getRequireWildcardCache() { if (typeof WeakMap !== "function") return null; var cache = new WeakMap(); _getRequireWildcardCache = function () { return cache; }; return cache; } + +function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; } + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +({}); +const transformFileRunner = (0, _gensync().default)(function* (filename, opts) { + const options = Object.assign({}, opts, { + filename + }); + const config = yield* (0, _config.default)(options); + if (config === null) return null; + const code = yield* fs.readFile(filename, "utf8"); + return yield* (0, _transformation.run)(config, code); +}); +const transformFile = transformFileRunner.errback; +exports.transformFile = transformFile; +const transformFileSync = transformFileRunner.sync; +exports.transformFileSync = transformFileSync; +const transformFileAsync = transformFileRunner.async; +exports.transformFileAsync = transformFileAsync; + +/***/ }), + +/***/ 46442: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports.transformAsync = exports.transformSync = exports.transform = void 0; + +function _gensync() { + const data = _interopRequireDefault(__nccwpck_require__(9621)); + + _gensync = function () { + return data; + }; + + return data; +} + +var _config = _interopRequireDefault(__nccwpck_require__(73677)); + +var _transformation = __nccwpck_require__(16486); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +const transformRunner = (0, _gensync().default)(function* transform(code, opts) { + const config = yield* (0, _config.default)(opts); + if (config === null) return null; + return yield* (0, _transformation.run)(config, code); +}); + +const transform = function transform(code, opts, callback) { + if (typeof opts === "function") { + callback = opts; + opts = undefined; + } + + if (callback === undefined) return transformRunner.sync(code, opts); + transformRunner.errback(code, opts, callback); +}; + +exports.transform = transform; +const transformSync = transformRunner.sync; +exports.transformSync = transformSync; +const transformAsync = transformRunner.async; +exports.transformAsync = transformAsync; + +/***/ }), + +/***/ 28925: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports["default"] = loadBlockHoistPlugin; + +function _sortBy() { + const data = _interopRequireDefault(__nccwpck_require__(94604)); + + _sortBy = function () { + return data; + }; + + return data; +} + +var _config = _interopRequireDefault(__nccwpck_require__(73677)); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +let LOADED_PLUGIN; + +function loadBlockHoistPlugin() { + if (!LOADED_PLUGIN) { + const config = _config.default.sync({ + babelrc: false, + configFile: false, + plugins: [blockHoistPlugin] + }); + + LOADED_PLUGIN = config ? config.passes[0][0] : undefined; + if (!LOADED_PLUGIN) throw new Error("Assertion failure"); + } + + return LOADED_PLUGIN; +} + +const blockHoistPlugin = { + name: "internal.blockHoist", + visitor: { + Block: { + exit({ + node + }) { + let hasChange = false; + + for (let i = 0; i < node.body.length; i++) { + const bodyNode = node.body[i]; + + if ((bodyNode == null ? void 0 : bodyNode._blockHoist) != null) { + hasChange = true; + break; + } + } + + if (!hasChange) return; + node.body = (0, _sortBy().default)(node.body, function (bodyNode) { + let priority = bodyNode == null ? void 0 : bodyNode._blockHoist; + if (priority == null) priority = 1; + if (priority === true) priority = 2; + return -1 * priority; + }); + } + + } + } +}; + +/***/ }), + +/***/ 12211: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports["default"] = void 0; + +function helpers() { + const data = _interopRequireWildcard(__nccwpck_require__(71475)); + + helpers = function () { + return data; + }; + + return data; +} + +function _traverse() { + const data = _interopRequireWildcard(__nccwpck_require__(50148)); + + _traverse = function () { + return data; + }; + + return data; +} + +function _codeFrame() { + const data = __nccwpck_require__(90147); + + _codeFrame = function () { + return data; + }; + + return data; +} + +function t() { + const data = _interopRequireWildcard(__nccwpck_require__(16535)); + + t = function () { + return data; + }; + + return data; +} + +function _helperModuleTransforms() { + const data = __nccwpck_require__(3665); + + _helperModuleTransforms = function () { + return data; + }; + + return data; +} + +function _semver() { + const data = _interopRequireDefault(__nccwpck_require__(39318)); + + _semver = function () { + return data; + }; + + return data; +} + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +function _getRequireWildcardCache() { if (typeof WeakMap !== "function") return null; var cache = new WeakMap(); _getRequireWildcardCache = function () { return cache; }; return cache; } + +function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; } + +const errorVisitor = { + enter(path, state) { + const loc = path.node.loc; + + if (loc) { + state.loc = loc; + path.stop(); + } + } + +}; + +class File { + constructor(options, { + code, + ast, + inputMap + }) { + this._map = new Map(); + this.opts = void 0; + this.declarations = {}; + this.path = null; + this.ast = {}; + this.scope = void 0; + this.metadata = {}; + this.code = ""; + this.inputMap = null; + this.hub = { + file: this, + getCode: () => this.code, + getScope: () => this.scope, + addHelper: this.addHelper.bind(this), + buildError: this.buildCodeFrameError.bind(this) + }; + this.opts = options; + this.code = code; + this.ast = ast; + this.inputMap = inputMap; + this.path = _traverse().NodePath.get({ + hub: this.hub, + parentPath: null, + parent: this.ast, + container: this.ast, + key: "program" + }).setContext(); + this.scope = this.path.scope; + } + + get shebang() { + const { + interpreter + } = this.path.node; + return interpreter ? interpreter.value : ""; + } + + set shebang(value) { + if (value) { + this.path.get("interpreter").replaceWith(t().interpreterDirective(value)); + } else { + this.path.get("interpreter").remove(); + } + } + + set(key, val) { + if (key === "helpersNamespace") { + throw new Error("Babel 7.0.0-beta.56 has dropped support for the 'helpersNamespace' utility." + "If you are using @babel/plugin-external-helpers you will need to use a newer " + "version than the one you currently have installed. " + "If you have your own implementation, you'll want to explore using 'helperGenerator' " + "alongside 'file.availableHelper()'."); + } + + this._map.set(key, val); + } + + get(key) { + return this._map.get(key); + } + + has(key) { + return this._map.has(key); + } + + getModuleName() { + return (0, _helperModuleTransforms().getModuleName)(this.opts, this.opts); + } + + addImport() { + throw new Error("This API has been removed. If you're looking for this " + "functionality in Babel 7, you should import the " + "'@babel/helper-module-imports' module and use the functions exposed " + " from that module, such as 'addNamed' or 'addDefault'."); + } + + availableHelper(name, versionRange) { + let minVersion; + + try { + minVersion = helpers().minVersion(name); + } catch (err) { + if (err.code !== "BABEL_HELPER_UNKNOWN") throw err; + return false; + } + + if (typeof versionRange !== "string") return true; + if (_semver().default.valid(versionRange)) versionRange = `^${versionRange}`; + return !_semver().default.intersects(`<${minVersion}`, versionRange) && !_semver().default.intersects(`>=8.0.0`, versionRange); + } + + addHelper(name) { + const declar = this.declarations[name]; + if (declar) return t().cloneNode(declar); + const generator = this.get("helperGenerator"); + + if (generator) { + const res = generator(name); + if (res) return res; + } + + helpers().ensure(name, File); + const uid = this.declarations[name] = this.scope.generateUidIdentifier(name); + const dependencies = {}; + + for (const dep of helpers().getDependencies(name)) { + dependencies[dep] = this.addHelper(dep); + } + + const { + nodes, + globals + } = helpers().get(name, dep => dependencies[dep], uid, Object.keys(this.scope.getAllBindings())); + globals.forEach(name => { + if (this.path.scope.hasBinding(name, true)) { + this.path.scope.rename(name); + } + }); + nodes.forEach(node => { + node._compact = true; + }); + this.path.unshiftContainer("body", nodes); + this.path.get("body").forEach(path => { + if (nodes.indexOf(path.node) === -1) return; + if (path.isVariableDeclaration()) this.scope.registerDeclaration(path); + }); + return uid; + } + + addTemplateObject() { + throw new Error("This function has been moved into the template literal transform itself."); + } + + buildCodeFrameError(node, msg, Error = SyntaxError) { + let loc = node && (node.loc || node._loc); + + if (!loc && node) { + const state = { + loc: null + }; + (0, _traverse().default)(node, errorVisitor, this.scope, state); + loc = state.loc; + let txt = "This is an error on an internal node. Probably an internal error."; + if (loc) txt += " Location has been estimated."; + msg += ` (${txt})`; + } + + if (loc) { + const { + highlightCode = true + } = this.opts; + msg += "\n" + (0, _codeFrame().codeFrameColumns)(this.code, { + start: { + line: loc.start.line, + column: loc.start.column + 1 + }, + end: loc.end && loc.start.line === loc.end.line ? { + line: loc.end.line, + column: loc.end.column + 1 + } : undefined + }, { + highlightCode + }); + } + + return new Error(msg); + } + +} + +exports["default"] = File; + +/***/ }), + +/***/ 48358: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports["default"] = generateCode; + +function _convertSourceMap() { + const data = _interopRequireDefault(__nccwpck_require__(25288)); + + _convertSourceMap = function () { + return data; + }; + + return data; +} + +function _generator() { + const data = _interopRequireDefault(__nccwpck_require__(12123)); + + _generator = function () { + return data; + }; + + return data; +} + +var _mergeMap = _interopRequireDefault(__nccwpck_require__(5890)); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +function generateCode(pluginPasses, file) { + const { + opts, + ast, + code, + inputMap + } = file; + const results = []; + + for (const plugins of pluginPasses) { + for (const plugin of plugins) { + const { + generatorOverride + } = plugin; + + if (generatorOverride) { + const result = generatorOverride(ast, opts.generatorOpts, code, _generator().default); + if (result !== undefined) results.push(result); + } + } + } + + let result; + + if (results.length === 0) { + result = (0, _generator().default)(ast, opts.generatorOpts, code); + } else if (results.length === 1) { + result = results[0]; + + if (typeof result.then === "function") { + throw new Error(`You appear to be using an async codegen plugin, ` + `which your current version of Babel does not support. ` + `If you're using a published plugin, ` + `you may need to upgrade your @babel/core version.`); + } + } else { + throw new Error("More than one plugin attempted to override codegen."); + } + + let { + code: outputCode, + map: outputMap + } = result; + + if (outputMap && inputMap) { + outputMap = (0, _mergeMap.default)(inputMap.toObject(), outputMap); + } + + if (opts.sourceMaps === "inline" || opts.sourceMaps === "both") { + outputCode += "\n" + _convertSourceMap().default.fromObject(outputMap).toComment(); + } + + if (opts.sourceMaps === "inline") { + outputMap = null; + } + + return { + outputCode, + outputMap + }; +} + +/***/ }), + +/***/ 5890: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports["default"] = mergeSourceMap; + +function _sourceMap() { + const data = _interopRequireDefault(__nccwpck_require__(62618)); + + _sourceMap = function () { + return data; + }; + + return data; +} + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +function mergeSourceMap(inputMap, map) { + const input = buildMappingData(inputMap); + const output = buildMappingData(map); + const mergedGenerator = new (_sourceMap().default.SourceMapGenerator)(); + + for (const { + source + } of input.sources) { + if (typeof source.content === "string") { + mergedGenerator.setSourceContent(source.path, source.content); + } + } + + if (output.sources.length === 1) { + const defaultSource = output.sources[0]; + const insertedMappings = new Map(); + eachInputGeneratedRange(input, (generated, original, source) => { + eachOverlappingGeneratedOutputRange(defaultSource, generated, item => { + const key = makeMappingKey(item); + if (insertedMappings.has(key)) return; + insertedMappings.set(key, item); + mergedGenerator.addMapping({ + source: source.path, + original: { + line: original.line, + column: original.columnStart + }, + generated: { + line: item.line, + column: item.columnStart + }, + name: original.name + }); + }); + }); + + for (const item of insertedMappings.values()) { + if (item.columnEnd === Infinity) { + continue; + } + + const clearItem = { + line: item.line, + columnStart: item.columnEnd + }; + const key = makeMappingKey(clearItem); + + if (insertedMappings.has(key)) { + continue; + } + + mergedGenerator.addMapping({ + generated: { + line: clearItem.line, + column: clearItem.columnStart + } + }); + } + } + + const result = mergedGenerator.toJSON(); + + if (typeof input.sourceRoot === "string") { + result.sourceRoot = input.sourceRoot; + } + + return result; +} + +function makeMappingKey(item) { + return `${item.line}/${item.columnStart}`; +} + +function eachOverlappingGeneratedOutputRange(outputFile, inputGeneratedRange, callback) { + const overlappingOriginal = filterApplicableOriginalRanges(outputFile, inputGeneratedRange); + + for (const { + generated + } of overlappingOriginal) { + for (const item of generated) { + callback(item); + } + } +} + +function filterApplicableOriginalRanges({ + mappings +}, { + line, + columnStart, + columnEnd +}) { + return filterSortedArray(mappings, ({ + original: outOriginal + }) => { + if (line > outOriginal.line) return -1; + if (line < outOriginal.line) return 1; + if (columnStart >= outOriginal.columnEnd) return -1; + if (columnEnd <= outOriginal.columnStart) return 1; + return 0; + }); +} + +function eachInputGeneratedRange(map, callback) { + for (const { + source, + mappings + } of map.sources) { + for (const { + original, + generated + } of mappings) { + for (const item of generated) { + callback(item, original, source); + } + } + } +} + +function buildMappingData(map) { + const consumer = new (_sourceMap().default.SourceMapConsumer)(Object.assign({}, map, { + sourceRoot: null + })); + const sources = new Map(); + const mappings = new Map(); + let last = null; + consumer.computeColumnSpans(); + consumer.eachMapping(m => { + if (m.originalLine === null) return; + let source = sources.get(m.source); + + if (!source) { + source = { + path: m.source, + content: consumer.sourceContentFor(m.source, true) + }; + sources.set(m.source, source); + } + + let sourceData = mappings.get(source); + + if (!sourceData) { + sourceData = { + source, + mappings: [] + }; + mappings.set(source, sourceData); + } + + const obj = { + line: m.originalLine, + columnStart: m.originalColumn, + columnEnd: Infinity, + name: m.name + }; + + if (last && last.source === source && last.mapping.line === m.originalLine) { + last.mapping.columnEnd = m.originalColumn; + } + + last = { + source, + mapping: obj + }; + sourceData.mappings.push({ + original: obj, + generated: consumer.allGeneratedPositionsFor({ + source: m.source, + line: m.originalLine, + column: m.originalColumn + }).map(item => ({ + line: item.line, + columnStart: item.column, + columnEnd: item.lastColumn + 1 + })) + }); + }, null, _sourceMap().default.SourceMapConsumer.ORIGINAL_ORDER); + return { + file: map.file, + sourceRoot: map.sourceRoot, + sources: Array.from(mappings.values()) + }; +} + +function findInsertionLocation(array, callback) { + let left = 0; + let right = array.length; + + while (left < right) { + const mid = Math.floor((left + right) / 2); + const item = array[mid]; + const result = callback(item); + + if (result === 0) { + left = mid; + break; + } + + if (result >= 0) { + right = mid; + } else { + left = mid + 1; + } + } + + let i = left; + + if (i < array.length) { + while (i >= 0 && callback(array[i]) >= 0) { + i--; + } + + return i + 1; + } + + return i; +} + +function filterSortedArray(array, callback) { + const start = findInsertionLocation(array, callback); + const results = []; + + for (let i = start; i < array.length && callback(array[i]) === 0; i++) { + results.push(array[i]); + } + + return results; +} + +/***/ }), + +/***/ 16486: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports.run = run; + +function _traverse() { + const data = _interopRequireDefault(__nccwpck_require__(50148)); + + _traverse = function () { + return data; + }; + + return data; +} + +var _pluginPass = _interopRequireDefault(__nccwpck_require__(20937)); + +var _blockHoistPlugin = _interopRequireDefault(__nccwpck_require__(28925)); + +var _normalizeOpts = _interopRequireDefault(__nccwpck_require__(74944)); + +var _normalizeFile = _interopRequireDefault(__nccwpck_require__(99168)); + +var _generate = _interopRequireDefault(__nccwpck_require__(48358)); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +function* run(config, code, ast) { + const file = yield* (0, _normalizeFile.default)(config.passes, (0, _normalizeOpts.default)(config), code, ast); + const opts = file.opts; + + try { + yield* transformFile(file, config.passes); + } catch (e) { + var _opts$filename; + + e.message = `${(_opts$filename = opts.filename) != null ? _opts$filename : "unknown"}: ${e.message}`; + + if (!e.code) { + e.code = "BABEL_TRANSFORM_ERROR"; + } + + throw e; + } + + let outputCode, outputMap; + + try { + if (opts.code !== false) { + ({ + outputCode, + outputMap + } = (0, _generate.default)(config.passes, file)); + } + } catch (e) { + var _opts$filename2; + + e.message = `${(_opts$filename2 = opts.filename) != null ? _opts$filename2 : "unknown"}: ${e.message}`; + + if (!e.code) { + e.code = "BABEL_GENERATE_ERROR"; + } + + throw e; + } + + return { + metadata: file.metadata, + options: opts, + ast: opts.ast === true ? file.ast : null, + code: outputCode === undefined ? null : outputCode, + map: outputMap === undefined ? null : outputMap, + sourceType: file.ast.program.sourceType + }; +} + +function* transformFile(file, pluginPasses) { + for (const pluginPairs of pluginPasses) { + const passPairs = []; + const passes = []; + const visitors = []; + + for (const plugin of pluginPairs.concat([(0, _blockHoistPlugin.default)()])) { + const pass = new _pluginPass.default(file, plugin.key, plugin.options); + passPairs.push([plugin, pass]); + passes.push(pass); + visitors.push(plugin.visitor); + } + + for (const [plugin, pass] of passPairs) { + const fn = plugin.pre; + + if (fn) { + const result = fn.call(pass, file); + yield* []; + + if (isThenable(result)) { + throw new Error(`You appear to be using an plugin with an async .pre, ` + `which your current version of Babel does not support. ` + `If you're using a published plugin, you may need to upgrade ` + `your @babel/core version.`); + } + } + } + + const visitor = _traverse().default.visitors.merge(visitors, passes, file.opts.wrapPluginVisitorMethod); + + (0, _traverse().default)(file.ast, visitor, file.scope); + + for (const [plugin, pass] of passPairs) { + const fn = plugin.post; + + if (fn) { + const result = fn.call(pass, file); + yield* []; + + if (isThenable(result)) { + throw new Error(`You appear to be using an plugin with an async .post, ` + `which your current version of Babel does not support. ` + `If you're using a published plugin, you may need to upgrade ` + `your @babel/core version.`); + } + } + } + } +} + +function isThenable(val) { + return !!val && (typeof val === "object" || typeof val === "function") && !!val.then && typeof val.then === "function"; +} + +/***/ }), + +/***/ 99168: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports["default"] = normalizeFile; + +function _fs() { + const data = _interopRequireDefault(__nccwpck_require__(79896)); + + _fs = function () { + return data; + }; + + return data; +} + +function _path() { + const data = _interopRequireDefault(__nccwpck_require__(16928)); + + _path = function () { + return data; + }; + + return data; +} + +function _debug() { + const data = _interopRequireDefault(__nccwpck_require__(2830)); + + _debug = function () { + return data; + }; + + return data; +} + +function _cloneDeep() { + const data = _interopRequireDefault(__nccwpck_require__(80542)); + + _cloneDeep = function () { + return data; + }; + + return data; +} + +function t() { + const data = _interopRequireWildcard(__nccwpck_require__(16535)); + + t = function () { + return data; + }; + + return data; +} + +function _convertSourceMap() { + const data = _interopRequireDefault(__nccwpck_require__(25288)); + + _convertSourceMap = function () { + return data; + }; + + return data; +} + +var _file = _interopRequireDefault(__nccwpck_require__(12211)); + +var _parser = _interopRequireDefault(__nccwpck_require__(99904)); + +function _getRequireWildcardCache() { if (typeof WeakMap !== "function") return null; var cache = new WeakMap(); _getRequireWildcardCache = function () { return cache; }; return cache; } + +function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; } + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +const debug = (0, _debug().default)("babel:transform:file"); +const LARGE_INPUT_SOURCEMAP_THRESHOLD = 1000000; + +function* normalizeFile(pluginPasses, options, code, ast) { + code = `${code || ""}`; + + if (ast) { + if (ast.type === "Program") { + ast = t().file(ast, [], []); + } else if (ast.type !== "File") { + throw new Error("AST root must be a Program or File node"); + } + + const { + cloneInputAst + } = options; + + if (cloneInputAst) { + ast = (0, _cloneDeep().default)(ast); + } + } else { + ast = yield* (0, _parser.default)(pluginPasses, options, code); + } + + let inputMap = null; + + if (options.inputSourceMap !== false) { + if (typeof options.inputSourceMap === "object") { + inputMap = _convertSourceMap().default.fromObject(options.inputSourceMap); + } + + if (!inputMap) { + const lastComment = extractComments(INLINE_SOURCEMAP_REGEX, ast); + + if (lastComment) { + try { + inputMap = _convertSourceMap().default.fromComment(lastComment); + } catch (err) { + debug("discarding unknown inline input sourcemap", err); + } + } + } + + if (!inputMap) { + const lastComment = extractComments(EXTERNAL_SOURCEMAP_REGEX, ast); + + if (typeof options.filename === "string" && lastComment) { + try { + const match = EXTERNAL_SOURCEMAP_REGEX.exec(lastComment); + + const inputMapContent = _fs().default.readFileSync(_path().default.resolve(_path().default.dirname(options.filename), match[1])); + + if (inputMapContent.length > LARGE_INPUT_SOURCEMAP_THRESHOLD) { + debug("skip merging input map > 1 MB"); + } else { + inputMap = _convertSourceMap().default.fromJSON(inputMapContent); + } + } catch (err) { + debug("discarding unknown file input sourcemap", err); + } + } else if (lastComment) { + debug("discarding un-loadable file input sourcemap"); + } + } + } + + return new _file.default(options, { + code, + ast, + inputMap + }); +} + +const INLINE_SOURCEMAP_REGEX = /^[@#]\s+sourceMappingURL=data:(?:application|text)\/json;(?:charset[:=]\S+?;)?base64,(?:.*)$/; +const EXTERNAL_SOURCEMAP_REGEX = /^[@#][ \t]+sourceMappingURL=([^\s'"`]+)[ \t]*$/; + +function extractCommentsFromList(regex, comments, lastComment) { + if (comments) { + comments = comments.filter(({ + value + }) => { + if (regex.test(value)) { + lastComment = value; + return false; + } + + return true; + }); + } + + return [comments, lastComment]; +} + +function extractComments(regex, ast) { + let lastComment = null; + t().traverseFast(ast, node => { + [node.leadingComments, lastComment] = extractCommentsFromList(regex, node.leadingComments, lastComment); + [node.innerComments, lastComment] = extractCommentsFromList(regex, node.innerComments, lastComment); + [node.trailingComments, lastComment] = extractCommentsFromList(regex, node.trailingComments, lastComment); + }); + return lastComment; +} + +/***/ }), + +/***/ 74944: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports["default"] = normalizeOptions; + +function _path() { + const data = _interopRequireDefault(__nccwpck_require__(16928)); + + _path = function () { + return data; + }; + + return data; +} + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +function normalizeOptions(config) { + const { + filename, + cwd, + filenameRelative = typeof filename === "string" ? _path().default.relative(cwd, filename) : "unknown", + sourceType = "module", + inputSourceMap, + sourceMaps = !!inputSourceMap, + moduleRoot, + sourceRoot = moduleRoot, + sourceFileName = _path().default.basename(filenameRelative), + comments = true, + compact = "auto" + } = config.options; + const opts = config.options; + const options = Object.assign({}, opts, { + parserOpts: Object.assign({ + sourceType: _path().default.extname(filenameRelative) === ".mjs" ? "module" : sourceType, + sourceFileName: filename, + plugins: [] + }, opts.parserOpts), + generatorOpts: Object.assign({ + filename, + auxiliaryCommentBefore: opts.auxiliaryCommentBefore, + auxiliaryCommentAfter: opts.auxiliaryCommentAfter, + retainLines: opts.retainLines, + comments, + shouldPrintComment: opts.shouldPrintComment, + compact, + minified: opts.minified, + sourceMaps, + sourceRoot, + sourceFileName + }, opts.generatorOpts) + }); + + for (const plugins of config.passes) { + for (const plugin of plugins) { + if (plugin.manipulateOptions) { + plugin.manipulateOptions(options, options.parserOpts); + } + } + } + + return options; +} + +/***/ }), + +/***/ 20937: +/***/ ((__unused_webpack_module, exports) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports["default"] = void 0; + +class PluginPass { + constructor(file, key, options) { + this._map = new Map(); + this.key = void 0; + this.file = void 0; + this.opts = void 0; + this.cwd = void 0; + this.filename = void 0; + this.key = key; + this.file = file; + this.opts = options || {}; + this.cwd = file.opts.cwd; + this.filename = file.opts.filename; + } + + set(key, val) { + this._map.set(key, val); + } + + get(key) { + return this._map.get(key); + } + + availableHelper(name, versionRange) { + return this.file.availableHelper(name, versionRange); + } + + addHelper(name) { + return this.file.addHelper(name); + } + + addImport() { + return this.file.addImport(); + } + + getModuleName() { + return this.file.getModuleName(); + } + + buildCodeFrameError(node, msg, Error) { + return this.file.buildCodeFrameError(node, msg, Error); + } + +} + +exports["default"] = PluginPass; + +/***/ }), + +/***/ 25683: +/***/ ((__unused_webpack_module, exports) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports.declare = declare; + +function declare(builder) { + return (api, options, dirname) => { + if (!api.assertVersion) { + api = Object.assign(copyApiObject(api), { + assertVersion(range) { + throwVersionError(range, api.version); + } + + }); + } + + return builder(api, options || {}, dirname); + }; +} + +function copyApiObject(api) { + let proto = null; + + if (typeof api.version === "string" && /^7\./.test(api.version)) { + proto = Object.getPrototypeOf(api); + + if (proto && (!has(proto, "version") || !has(proto, "transform") || !has(proto, "template") || !has(proto, "types"))) { + proto = null; + } + } + + return Object.assign({}, proto, api); +} + +function has(obj, key) { + return Object.prototype.hasOwnProperty.call(obj, key); +} + +function throwVersionError(range, version) { + if (typeof range === "number") { + if (!Number.isInteger(range)) { + throw new Error("Expected string or integer value."); + } + + range = `^${range}.0.0-0`; + } + + if (typeof range !== "string") { + throw new Error("Expected string or integer value."); + } + + const limit = Error.stackTraceLimit; + + if (typeof limit === "number" && limit < 25) { + Error.stackTraceLimit = 25; + } + + let err; + + if (version.slice(0, 2) === "7.") { + err = new Error(`Requires Babel "^7.0.0-beta.41", but was loaded with "${version}". ` + `You'll need to update your @babel/core version.`); + } else { + err = new Error(`Requires Babel "${range}", but was loaded with "${version}". ` + `If you are sure you have a compatible version of @babel/core, ` + `it is likely that something in your build process is loading the ` + `wrong version. Inspect the stack trace of this error to look for ` + `the first entry that doesn't mention "@babel/core" or "babel-core" ` + `to see what is calling Babel.`); + } + + if (typeof limit === "number") { + Error.stackTraceLimit = limit; + } + + throw Object.assign(err, { + code: "BABEL_VERSION_UNSUPPORTED", + version, + range + }); +} + +/***/ }), + +/***/ 16186: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports["default"] = void 0; + +var _helperPluginUtils = __nccwpck_require__(25683); + +var _pluginSyntaxObjectRestSpread = _interopRequireDefault(__nccwpck_require__(55165)); + +var _core = __nccwpck_require__(85414); + +var _pluginTransformParameters = __nccwpck_require__(32519); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +const ZERO_REFS = (() => { + const node = _core.types.identifier("a"); + + const property = _core.types.objectProperty(_core.types.identifier("key"), node); + + const pattern = _core.types.objectPattern([property]); + + return _core.types.isReferenced(node, property, pattern) ? 1 : 0; +})(); + +var _default = (0, _helperPluginUtils.declare)((api, opts) => { + api.assertVersion(7); + const { + useBuiltIns = false, + loose = false + } = opts; + + if (typeof loose !== "boolean") { + throw new Error(".loose must be a boolean, or undefined"); + } + + function getExtendsHelper(file) { + return useBuiltIns ? _core.types.memberExpression(_core.types.identifier("Object"), _core.types.identifier("assign")) : file.addHelper("extends"); + } + + function hasRestElement(path) { + let foundRestElement = false; + visitRestElements(path, restElement => { + foundRestElement = true; + restElement.stop(); + }); + return foundRestElement; + } + + function hasObjectPatternRestElement(path) { + let foundRestElement = false; + visitRestElements(path, restElement => { + if (restElement.parentPath.isObjectPattern()) { + foundRestElement = true; + restElement.stop(); + } + }); + return foundRestElement; + } + + function visitRestElements(path, visitor) { + path.traverse({ + Expression(path) { + const parentType = path.parent.type; + + if (parentType === "AssignmentPattern" && path.key === "right" || parentType === "ObjectProperty" && path.parent.computed && path.key === "key") { + path.skip(); + } + }, + + RestElement: visitor + }); + } + + function hasSpread(node) { + for (const prop of node.properties) { + if (_core.types.isSpreadElement(prop)) { + return true; + } + } + + return false; + } + + function extractNormalizedKeys(path) { + const props = path.node.properties; + const keys = []; + let allLiteral = true; + + for (const prop of props) { + if (_core.types.isIdentifier(prop.key) && !prop.computed) { + keys.push(_core.types.stringLiteral(prop.key.name)); + } else if (_core.types.isTemplateLiteral(prop.key)) { + keys.push(_core.types.cloneNode(prop.key)); + } else if (_core.types.isLiteral(prop.key)) { + keys.push(_core.types.stringLiteral(String(prop.key.value))); + } else { + keys.push(_core.types.cloneNode(prop.key)); + allLiteral = false; + } + } + + return { + keys, + allLiteral + }; + } + + function replaceImpureComputedKeys(properties, scope) { + const impureComputedPropertyDeclarators = []; + + for (const propPath of properties) { + const key = propPath.get("key"); + + if (propPath.node.computed && !key.isPure()) { + const name = scope.generateUidBasedOnNode(key.node); + + const declarator = _core.types.variableDeclarator(_core.types.identifier(name), key.node); + + impureComputedPropertyDeclarators.push(declarator); + key.replaceWith(_core.types.identifier(name)); + } + } + + return impureComputedPropertyDeclarators; + } + + function removeUnusedExcludedKeys(path) { + const bindings = path.getOuterBindingIdentifierPaths(); + Object.keys(bindings).forEach(bindingName => { + const bindingParentPath = bindings[bindingName].parentPath; + + if (path.scope.getBinding(bindingName).references > ZERO_REFS || !bindingParentPath.isObjectProperty()) { + return; + } + + bindingParentPath.remove(); + }); + } + + function createObjectSpread(path, file, objRef) { + const props = path.get("properties"); + const last = props[props.length - 1]; + + _core.types.assertRestElement(last.node); + + const restElement = _core.types.cloneNode(last.node); + + last.remove(); + const impureComputedPropertyDeclarators = replaceImpureComputedKeys(path.get("properties"), path.scope); + const { + keys, + allLiteral + } = extractNormalizedKeys(path); + + if (keys.length === 0) { + return [impureComputedPropertyDeclarators, restElement.argument, _core.types.callExpression(getExtendsHelper(file), [_core.types.objectExpression([]), _core.types.cloneNode(objRef)])]; + } + + let keyExpression; + + if (!allLiteral) { + keyExpression = _core.types.callExpression(_core.types.memberExpression(_core.types.arrayExpression(keys), _core.types.identifier("map")), [file.addHelper("toPropertyKey")]); + } else { + keyExpression = _core.types.arrayExpression(keys); + } + + return [impureComputedPropertyDeclarators, restElement.argument, _core.types.callExpression(file.addHelper(`objectWithoutProperties${loose ? "Loose" : ""}`), [_core.types.cloneNode(objRef), keyExpression])]; + } + + function replaceRestElement(parentPath, paramPath, container) { + if (paramPath.isAssignmentPattern()) { + replaceRestElement(parentPath, paramPath.get("left"), container); + return; + } + + if (paramPath.isArrayPattern() && hasRestElement(paramPath)) { + const elements = paramPath.get("elements"); + + for (let i = 0; i < elements.length; i++) { + replaceRestElement(parentPath, elements[i], container); + } + } + + if (paramPath.isObjectPattern() && hasRestElement(paramPath)) { + const uid = parentPath.scope.generateUidIdentifier("ref"); + + const declar = _core.types.variableDeclaration("let", [_core.types.variableDeclarator(paramPath.node, uid)]); + + if (container) { + container.push(declar); + } else { + parentPath.ensureBlock(); + parentPath.get("body").unshiftContainer("body", declar); + } + + paramPath.replaceWith(_core.types.cloneNode(uid)); + } + } + + return { + name: "proposal-object-rest-spread", + inherits: _pluginSyntaxObjectRestSpread.default, + visitor: { + Function(path) { + const params = path.get("params"); + const paramsWithRestElement = new Set(); + const idsInRestParams = new Set(); + + for (let i = 0; i < params.length; ++i) { + const param = params[i]; + + if (hasRestElement(param)) { + paramsWithRestElement.add(i); + + for (const name of Object.keys(param.getBindingIdentifiers())) { + idsInRestParams.add(name); + } + } + } + + let idInRest = false; + + const IdentifierHandler = function (path, functionScope) { + const name = path.node.name; + + if (path.scope.getBinding(name) === functionScope.getBinding(name) && idsInRestParams.has(name)) { + idInRest = true; + path.stop(); + } + }; + + let i; + + for (i = 0; i < params.length && !idInRest; ++i) { + const param = params[i]; + + if (!paramsWithRestElement.has(i)) { + if (param.isReferencedIdentifier() || param.isBindingIdentifier()) { + IdentifierHandler(path, path.scope); + } else { + param.traverse({ + "Scope|TypeAnnotation|TSTypeAnnotation": path => path.skip(), + "ReferencedIdentifier|BindingIdentifier": IdentifierHandler + }, path.scope); + } + } + } + + if (!idInRest) { + for (let i = 0; i < params.length; ++i) { + const param = params[i]; + + if (paramsWithRestElement.has(i)) { + replaceRestElement(param.parentPath, param); + } + } + } else { + const shouldTransformParam = idx => idx >= i - 1 || paramsWithRestElement.has(idx); + + (0, _pluginTransformParameters.convertFunctionParams)(path, loose, shouldTransformParam, replaceRestElement); + } + }, + + VariableDeclarator(path, file) { + if (!path.get("id").isObjectPattern()) { + return; + } + + let insertionPath = path; + const originalPath = path; + visitRestElements(path.get("id"), path => { + if (!path.parentPath.isObjectPattern()) { + return; + } + + if (originalPath.node.id.properties.length > 1 && !_core.types.isIdentifier(originalPath.node.init)) { + const initRef = path.scope.generateUidIdentifierBasedOnNode(originalPath.node.init, "ref"); + originalPath.insertBefore(_core.types.variableDeclarator(initRef, originalPath.node.init)); + originalPath.replaceWith(_core.types.variableDeclarator(originalPath.node.id, _core.types.cloneNode(initRef))); + return; + } + + let ref = originalPath.node.init; + const refPropertyPath = []; + let kind; + path.findParent(path => { + if (path.isObjectProperty()) { + refPropertyPath.unshift(path); + } else if (path.isVariableDeclarator()) { + kind = path.parentPath.node.kind; + return true; + } + }); + const impureObjRefComputedDeclarators = replaceImpureComputedKeys(refPropertyPath, path.scope); + refPropertyPath.forEach(prop => { + const { + node + } = prop; + ref = _core.types.memberExpression(ref, _core.types.cloneNode(node.key), node.computed || _core.types.isLiteral(node.key)); + }); + const objectPatternPath = path.findParent(path => path.isObjectPattern()); + const [impureComputedPropertyDeclarators, argument, callExpression] = createObjectSpread(objectPatternPath, file, ref); + + if (loose) { + removeUnusedExcludedKeys(objectPatternPath); + } + + _core.types.assertIdentifier(argument); + + insertionPath.insertBefore(impureComputedPropertyDeclarators); + insertionPath.insertBefore(impureObjRefComputedDeclarators); + insertionPath.insertAfter(_core.types.variableDeclarator(argument, callExpression)); + insertionPath = insertionPath.getSibling(insertionPath.key + 1); + path.scope.registerBinding(kind, insertionPath); + + if (objectPatternPath.node.properties.length === 0) { + objectPatternPath.findParent(path => path.isObjectProperty() || path.isVariableDeclarator()).remove(); + } + }); + }, + + ExportNamedDeclaration(path) { + const declaration = path.get("declaration"); + if (!declaration.isVariableDeclaration()) return; + const hasRest = declaration.get("declarations").some(path => hasObjectPatternRestElement(path.get("id"))); + if (!hasRest) return; + const specifiers = []; + + for (const name of Object.keys(path.getOuterBindingIdentifiers(path))) { + specifiers.push(_core.types.exportSpecifier(_core.types.identifier(name), _core.types.identifier(name))); + } + + path.replaceWith(declaration.node); + path.insertAfter(_core.types.exportNamedDeclaration(null, specifiers)); + }, + + CatchClause(path) { + const paramPath = path.get("param"); + replaceRestElement(paramPath.parentPath, paramPath); + }, + + AssignmentExpression(path, file) { + const leftPath = path.get("left"); + + if (leftPath.isObjectPattern() && hasRestElement(leftPath)) { + const nodes = []; + const refName = path.scope.generateUidBasedOnNode(path.node.right, "ref"); + nodes.push(_core.types.variableDeclaration("var", [_core.types.variableDeclarator(_core.types.identifier(refName), path.node.right)])); + const [impureComputedPropertyDeclarators, argument, callExpression] = createObjectSpread(leftPath, file, _core.types.identifier(refName)); + + if (impureComputedPropertyDeclarators.length > 0) { + nodes.push(_core.types.variableDeclaration("var", impureComputedPropertyDeclarators)); + } + + const nodeWithoutSpread = _core.types.cloneNode(path.node); + + nodeWithoutSpread.right = _core.types.identifier(refName); + nodes.push(_core.types.expressionStatement(nodeWithoutSpread)); + nodes.push(_core.types.toStatement(_core.types.assignmentExpression("=", argument, callExpression))); + nodes.push(_core.types.expressionStatement(_core.types.identifier(refName))); + path.replaceWithMultiple(nodes); + } + }, + + ForXStatement(path) { + const { + node, + scope + } = path; + const leftPath = path.get("left"); + const left = node.left; + + if (!hasObjectPatternRestElement(leftPath)) { + return; + } + + if (!_core.types.isVariableDeclaration(left)) { + const temp = scope.generateUidIdentifier("ref"); + node.left = _core.types.variableDeclaration("var", [_core.types.variableDeclarator(temp)]); + path.ensureBlock(); + + if (node.body.body.length === 0 && path.isCompletionRecord()) { + node.body.body.unshift(_core.types.expressionStatement(scope.buildUndefinedNode())); + } + + node.body.body.unshift(_core.types.expressionStatement(_core.types.assignmentExpression("=", left, _core.types.cloneNode(temp)))); + } else { + const pattern = left.declarations[0].id; + const key = scope.generateUidIdentifier("ref"); + node.left = _core.types.variableDeclaration(left.kind, [_core.types.variableDeclarator(key, null)]); + path.ensureBlock(); + node.body.body.unshift(_core.types.variableDeclaration(node.left.kind, [_core.types.variableDeclarator(pattern, _core.types.cloneNode(key))])); + } + }, + + ArrayPattern(path) { + const objectPatterns = []; + visitRestElements(path, path => { + if (!path.parentPath.isObjectPattern()) { + return; + } + + const objectPattern = path.parentPath; + const uid = path.scope.generateUidIdentifier("ref"); + objectPatterns.push(_core.types.variableDeclarator(objectPattern.node, uid)); + objectPattern.replaceWith(_core.types.cloneNode(uid)); + path.skip(); + }); + + if (objectPatterns.length > 0) { + const statementPath = path.getStatementParent(); + statementPath.insertAfter(_core.types.variableDeclaration(statementPath.node.kind || "var", objectPatterns)); + } + }, + + ObjectExpression(path, file) { + if (!hasSpread(path.node)) return; + let helper; + + if (loose) { + helper = getExtendsHelper(file); + } else { + try { + helper = file.addHelper("objectSpread2"); + } catch (_unused) { + this.file.declarations["objectSpread2"] = null; + helper = file.addHelper("objectSpread"); + } + } + + let exp = null; + let props = []; + + function make() { + const hadProps = props.length > 0; + + const obj = _core.types.objectExpression(props); + + props = []; + + if (!exp) { + exp = _core.types.callExpression(helper, [obj]); + return; + } + + if (loose) { + if (hadProps) { + exp.arguments.push(obj); + } + + return; + } + + exp = _core.types.callExpression(_core.types.cloneNode(helper), [exp, ...(hadProps ? [_core.types.objectExpression([]), obj] : [])]); + } + + for (const prop of path.node.properties) { + if (_core.types.isSpreadElement(prop)) { + make(); + exp.arguments.push(prop.argument); + } else { + props.push(prop); + } + } + + if (props.length) make(); + path.replaceWith(exp); + } + + } + }; +}); + +exports["default"] = _default; + +/***/ }), + +/***/ 47358: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports["default"] = void 0; + +var _helperPluginUtils = __nccwpck_require__(25683); + +var _default = (0, _helperPluginUtils.declare)(api => { + api.assertVersion(7); + return { + name: "syntax-jsx", + + manipulateOptions(opts, parserOpts) { + if (parserOpts.plugins.some(p => (Array.isArray(p) ? p[0] : p) === "typescript")) { + return; + } + + parserOpts.plugins.push("jsx"); + } + + }; +}); + +exports["default"] = _default; + +/***/ }), + +/***/ 55165: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports["default"] = void 0; + +var _helperPluginUtils = __nccwpck_require__(25683); + +var _default = (0, _helperPluginUtils.declare)(api => { + api.assertVersion(7); + return { + name: "syntax-object-rest-spread", + + manipulateOptions(opts, parserOpts) { + parserOpts.plugins.push("objectRestSpread"); + } + + }; +}); + +exports["default"] = _default; + +/***/ }), + +/***/ 36153: +/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) { + +(function (global, factory) { + true ? factory(exports, __nccwpck_require__(83199), __nccwpck_require__(69551), __nccwpck_require__(99535)) : + 0; +})(this, (function (exports, setArray, sourcemapCodec, traceMapping) { 'use strict'; + + const COLUMN = 0; + const SOURCES_INDEX = 1; + const SOURCE_LINE = 2; + const SOURCE_COLUMN = 3; + const NAMES_INDEX = 4; + + const NO_NAME = -1; + /** + * Provides the state to generate a sourcemap. + */ + class GenMapping { + constructor({ file, sourceRoot } = {}) { + this._names = new setArray.SetArray(); + this._sources = new setArray.SetArray(); + this._sourcesContent = []; + this._mappings = []; + this.file = file; + this.sourceRoot = sourceRoot; + this._ignoreList = new setArray.SetArray(); + } + } + /** + * Typescript doesn't allow friend access to private fields, so this just casts the map into a type + * with public access modifiers. + */ + function cast(map) { + return map; + } + function addSegment(map, genLine, genColumn, source, sourceLine, sourceColumn, name, content) { + return addSegmentInternal(false, map, genLine, genColumn, source, sourceLine, sourceColumn, name, content); + } + function addMapping(map, mapping) { + return addMappingInternal(false, map, mapping); + } + /** + * Same as `addSegment`, but will only add the segment if it generates useful information in the + * resulting map. This only works correctly if segments are added **in order**, meaning you should + * not add a segment with a lower generated line/column than one that came before. + */ + const maybeAddSegment = (map, genLine, genColumn, source, sourceLine, sourceColumn, name, content) => { + return addSegmentInternal(true, map, genLine, genColumn, source, sourceLine, sourceColumn, name, content); + }; + /** + * Same as `addMapping`, but will only add the mapping if it generates useful information in the + * resulting map. This only works correctly if mappings are added **in order**, meaning you should + * not add a mapping with a lower generated line/column than one that came before. + */ + const maybeAddMapping = (map, mapping) => { + return addMappingInternal(true, map, mapping); + }; + /** + * Adds/removes the content of the source file to the source map. + */ + function setSourceContent(map, source, content) { + const { _sources: sources, _sourcesContent: sourcesContent } = cast(map); + const index = setArray.put(sources, source); + sourcesContent[index] = content; + } + function setIgnore(map, source, ignore = true) { + const { _sources: sources, _sourcesContent: sourcesContent, _ignoreList: ignoreList } = cast(map); + const index = setArray.put(sources, source); + if (index === sourcesContent.length) + sourcesContent[index] = null; + if (ignore) + setArray.put(ignoreList, index); + else + setArray.remove(ignoreList, index); + } + /** + * Returns a sourcemap object (with decoded mappings) suitable for passing to a library that expects + * a sourcemap, or to JSON.stringify. + */ + function toDecodedMap(map) { + const { _mappings: mappings, _sources: sources, _sourcesContent: sourcesContent, _names: names, _ignoreList: ignoreList, } = cast(map); + removeEmptyFinalLines(mappings); + return { + version: 3, + file: map.file || undefined, + names: names.array, + sourceRoot: map.sourceRoot || undefined, + sources: sources.array, + sourcesContent, + mappings, + ignoreList: ignoreList.array, + }; + } + /** + * Returns a sourcemap object (with encoded mappings) suitable for passing to a library that expects + * a sourcemap, or to JSON.stringify. + */ + function toEncodedMap(map) { + const decoded = toDecodedMap(map); + return Object.assign(Object.assign({}, decoded), { mappings: sourcemapCodec.encode(decoded.mappings) }); + } + /** + * Constructs a new GenMapping, using the already present mappings of the input. + */ + function fromMap(input) { + const map = new traceMapping.TraceMap(input); + const gen = new GenMapping({ file: map.file, sourceRoot: map.sourceRoot }); + putAll(cast(gen)._names, map.names); + putAll(cast(gen)._sources, map.sources); + cast(gen)._sourcesContent = map.sourcesContent || map.sources.map(() => null); + cast(gen)._mappings = traceMapping.decodedMappings(map); + if (map.ignoreList) + putAll(cast(gen)._ignoreList, map.ignoreList); + return gen; + } + /** + * Returns an array of high-level mapping objects for every recorded segment, which could then be + * passed to the `source-map` library. + */ + function allMappings(map) { + const out = []; + const { _mappings: mappings, _sources: sources, _names: names } = cast(map); + for (let i = 0; i < mappings.length; i++) { + const line = mappings[i]; + for (let j = 0; j < line.length; j++) { + const seg = line[j]; + const generated = { line: i + 1, column: seg[COLUMN] }; + let source = undefined; + let original = undefined; + let name = undefined; + if (seg.length !== 1) { + source = sources.array[seg[SOURCES_INDEX]]; + original = { line: seg[SOURCE_LINE] + 1, column: seg[SOURCE_COLUMN] }; + if (seg.length === 5) + name = names.array[seg[NAMES_INDEX]]; + } + out.push({ generated, source, original, name }); + } + } + return out; + } + // This split declaration is only so that terser can elminiate the static initialization block. + function addSegmentInternal(skipable, map, genLine, genColumn, source, sourceLine, sourceColumn, name, content) { + const { _mappings: mappings, _sources: sources, _sourcesContent: sourcesContent, _names: names, } = cast(map); + const line = getLine(mappings, genLine); + const index = getColumnIndex(line, genColumn); + if (!source) { + if (skipable && skipSourceless(line, index)) + return; + return insert(line, index, [genColumn]); + } + const sourcesIndex = setArray.put(sources, source); + const namesIndex = name ? setArray.put(names, name) : NO_NAME; + if (sourcesIndex === sourcesContent.length) + sourcesContent[sourcesIndex] = content !== null && content !== void 0 ? content : null; + if (skipable && skipSource(line, index, sourcesIndex, sourceLine, sourceColumn, namesIndex)) { + return; + } + return insert(line, index, name + ? [genColumn, sourcesIndex, sourceLine, sourceColumn, namesIndex] + : [genColumn, sourcesIndex, sourceLine, sourceColumn]); + } + function getLine(mappings, index) { + for (let i = mappings.length; i <= index; i++) { + mappings[i] = []; + } + return mappings[index]; + } + function getColumnIndex(line, genColumn) { + let index = line.length; + for (let i = index - 1; i >= 0; index = i--) { + const current = line[i]; + if (genColumn >= current[COLUMN]) + break; + } + return index; + } + function insert(array, index, value) { + for (let i = array.length; i > index; i--) { + array[i] = array[i - 1]; + } + array[index] = value; + } + function removeEmptyFinalLines(mappings) { + const { length } = mappings; + let len = length; + for (let i = len - 1; i >= 0; len = i, i--) { + if (mappings[i].length > 0) + break; + } + if (len < length) + mappings.length = len; + } + function putAll(setarr, array) { + for (let i = 0; i < array.length; i++) + setArray.put(setarr, array[i]); + } + function skipSourceless(line, index) { + // The start of a line is already sourceless, so adding a sourceless segment to the beginning + // doesn't generate any useful information. + if (index === 0) + return true; + const prev = line[index - 1]; + // If the previous segment is also sourceless, then adding another sourceless segment doesn't + // genrate any new information. Else, this segment will end the source/named segment and point to + // a sourceless position, which is useful. + return prev.length === 1; + } + function skipSource(line, index, sourcesIndex, sourceLine, sourceColumn, namesIndex) { + // A source/named segment at the start of a line gives position at that genColumn + if (index === 0) + return false; + const prev = line[index - 1]; + // If the previous segment is sourceless, then we're transitioning to a source. + if (prev.length === 1) + return false; + // If the previous segment maps to the exact same source position, then this segment doesn't + // provide any new position information. + return (sourcesIndex === prev[SOURCES_INDEX] && + sourceLine === prev[SOURCE_LINE] && + sourceColumn === prev[SOURCE_COLUMN] && + namesIndex === (prev.length === 5 ? prev[NAMES_INDEX] : NO_NAME)); + } + function addMappingInternal(skipable, map, mapping) { + const { generated, source, original, name, content } = mapping; + if (!source) { + return addSegmentInternal(skipable, map, generated.line - 1, generated.column, null, null, null, null, null); + } + return addSegmentInternal(skipable, map, generated.line - 1, generated.column, source, original.line - 1, original.column, name, content); + } + + exports.GenMapping = GenMapping; + exports.addMapping = addMapping; + exports.addSegment = addSegment; + exports.allMappings = allMappings; + exports.fromMap = fromMap; + exports.maybeAddMapping = maybeAddMapping; + exports.maybeAddSegment = maybeAddSegment; + exports.setIgnore = setIgnore; + exports.setSourceContent = setSourceContent; + exports.toDecodedMap = toDecodedMap; + exports.toEncodedMap = toEncodedMap; + + Object.defineProperty(exports, '__esModule', { value: true }); + +})); +//# sourceMappingURL=gen-mapping.umd.js.map + + +/***/ }), + +/***/ 59445: +/***/ (function(module) { + +(function (global, factory) { + true ? module.exports = factory() : + 0; +})(this, (function () { 'use strict'; + + // Matches the scheme of a URL, eg "http://" + const schemeRegex = /^[\w+.-]+:\/\//; + /** + * Matches the parts of a URL: + * 1. Scheme, including ":", guaranteed. + * 2. User/password, including "@", optional. + * 3. Host, guaranteed. + * 4. Port, including ":", optional. + * 5. Path, including "/", optional. + * 6. Query, including "?", optional. + * 7. Hash, including "#", optional. + */ + const urlRegex = /^([\w+.-]+:)\/\/([^@/#?]*@)?([^:/#?]*)(:\d+)?(\/[^#?]*)?(\?[^#]*)?(#.*)?/; + /** + * File URLs are weird. They dont' need the regular `//` in the scheme, they may or may not start + * with a leading `/`, they can have a domain (but only if they don't start with a Windows drive). + * + * 1. Host, optional. + * 2. Path, which may include "/", guaranteed. + * 3. Query, including "?", optional. + * 4. Hash, including "#", optional. + */ + const fileRegex = /^file:(?:\/\/((?![a-z]:)[^/#?]*)?)?(\/?[^#?]*)(\?[^#]*)?(#.*)?/i; + function isAbsoluteUrl(input) { + return schemeRegex.test(input); + } + function isSchemeRelativeUrl(input) { + return input.startsWith('//'); + } + function isAbsolutePath(input) { + return input.startsWith('/'); + } + function isFileUrl(input) { + return input.startsWith('file:'); + } + function isRelative(input) { + return /^[.?#]/.test(input); + } + function parseAbsoluteUrl(input) { + const match = urlRegex.exec(input); + return makeUrl(match[1], match[2] || '', match[3], match[4] || '', match[5] || '/', match[6] || '', match[7] || ''); + } + function parseFileUrl(input) { + const match = fileRegex.exec(input); + const path = match[2]; + return makeUrl('file:', '', match[1] || '', '', isAbsolutePath(path) ? path : '/' + path, match[3] || '', match[4] || ''); + } + function makeUrl(scheme, user, host, port, path, query, hash) { + return { + scheme, + user, + host, + port, + path, + query, + hash, + type: 7 /* Absolute */, + }; + } + function parseUrl(input) { + if (isSchemeRelativeUrl(input)) { + const url = parseAbsoluteUrl('http:' + input); + url.scheme = ''; + url.type = 6 /* SchemeRelative */; + return url; + } + if (isAbsolutePath(input)) { + const url = parseAbsoluteUrl('http://foo.com' + input); + url.scheme = ''; + url.host = ''; + url.type = 5 /* AbsolutePath */; + return url; + } + if (isFileUrl(input)) + return parseFileUrl(input); + if (isAbsoluteUrl(input)) + return parseAbsoluteUrl(input); + const url = parseAbsoluteUrl('http://foo.com/' + input); + url.scheme = ''; + url.host = ''; + url.type = input + ? input.startsWith('?') + ? 3 /* Query */ + : input.startsWith('#') + ? 2 /* Hash */ + : 4 /* RelativePath */ + : 1 /* Empty */; + return url; + } + function stripPathFilename(path) { + // If a path ends with a parent directory "..", then it's a relative path with excess parent + // paths. It's not a file, so we can't strip it. + if (path.endsWith('/..')) + return path; + const index = path.lastIndexOf('/'); + return path.slice(0, index + 1); + } + function mergePaths(url, base) { + normalizePath(base, base.type); + // If the path is just a "/", then it was an empty path to begin with (remember, we're a relative + // path). + if (url.path === '/') { + url.path = base.path; + } + else { + // Resolution happens relative to the base path's directory, not the file. + url.path = stripPathFilename(base.path) + url.path; + } + } + /** + * The path can have empty directories "//", unneeded parents "foo/..", or current directory + * "foo/.". We need to normalize to a standard representation. + */ + function normalizePath(url, type) { + const rel = type <= 4 /* RelativePath */; + const pieces = url.path.split('/'); + // We need to preserve the first piece always, so that we output a leading slash. The item at + // pieces[0] is an empty string. + let pointer = 1; + // Positive is the number of real directories we've output, used for popping a parent directory. + // Eg, "foo/bar/.." will have a positive 2, and we can decrement to be left with just "foo". + let positive = 0; + // We need to keep a trailing slash if we encounter an empty directory (eg, splitting "foo/" will + // generate `["foo", ""]` pieces). And, if we pop a parent directory. But once we encounter a + // real directory, we won't need to append, unless the other conditions happen again. + let addTrailingSlash = false; + for (let i = 1; i < pieces.length; i++) { + const piece = pieces[i]; + // An empty directory, could be a trailing slash, or just a double "//" in the path. + if (!piece) { + addTrailingSlash = true; + continue; + } + // If we encounter a real directory, then we don't need to append anymore. + addTrailingSlash = false; + // A current directory, which we can always drop. + if (piece === '.') + continue; + // A parent directory, we need to see if there are any real directories we can pop. Else, we + // have an excess of parents, and we'll need to keep the "..". + if (piece === '..') { + if (positive) { + addTrailingSlash = true; + positive--; + pointer--; + } + else if (rel) { + // If we're in a relativePath, then we need to keep the excess parents. Else, in an absolute + // URL, protocol relative URL, or an absolute path, we don't need to keep excess. + pieces[pointer++] = piece; + } + continue; + } + // We've encountered a real directory. Move it to the next insertion pointer, which accounts for + // any popped or dropped directories. + pieces[pointer++] = piece; + positive++; + } + let path = ''; + for (let i = 1; i < pointer; i++) { + path += '/' + pieces[i]; + } + if (!path || (addTrailingSlash && !path.endsWith('/..'))) { + path += '/'; + } + url.path = path; + } + /** + * Attempts to resolve `input` URL/path relative to `base`. + */ + function resolve(input, base) { + if (!input && !base) + return ''; + const url = parseUrl(input); + let inputType = url.type; + if (base && inputType !== 7 /* Absolute */) { + const baseUrl = parseUrl(base); + const baseType = baseUrl.type; + switch (inputType) { + case 1 /* Empty */: + url.hash = baseUrl.hash; + // fall through + case 2 /* Hash */: + url.query = baseUrl.query; + // fall through + case 3 /* Query */: + case 4 /* RelativePath */: + mergePaths(url, baseUrl); + // fall through + case 5 /* AbsolutePath */: + // The host, user, and port are joined, you can't copy one without the others. + url.user = baseUrl.user; + url.host = baseUrl.host; + url.port = baseUrl.port; + // fall through + case 6 /* SchemeRelative */: + // The input doesn't have a schema at least, so we need to copy at least that over. + url.scheme = baseUrl.scheme; + } + if (baseType > inputType) + inputType = baseType; + } + normalizePath(url, inputType); + const queryHash = url.query + url.hash; + switch (inputType) { + // This is impossible, because of the empty checks at the start of the function. + // case UrlType.Empty: + case 2 /* Hash */: + case 3 /* Query */: + return queryHash; + case 4 /* RelativePath */: { + // The first char is always a "/", and we need it to be relative. + const path = url.path.slice(1); + if (!path) + return queryHash || '.'; + if (isRelative(base || input) && !isRelative(path)) { + // If base started with a leading ".", or there is no base and input started with a ".", + // then we need to ensure that the relative path starts with a ".". We don't know if + // relative starts with a "..", though, so check before prepending. + return './' + path + queryHash; + } + return path + queryHash; + } + case 5 /* AbsolutePath */: + return url.path + queryHash; + default: + return url.scheme + '//' + url.user + url.host + url.port + url.path + queryHash; + } + } + + return resolve; + +})); +//# sourceMappingURL=resolve-uri.umd.js.map + + +/***/ }), + +/***/ 83199: +/***/ (function(__unused_webpack_module, exports) { + +(function (global, factory) { + true ? factory(exports) : + 0; +})(this, (function (exports) { 'use strict'; + + /** + * SetArray acts like a `Set` (allowing only one occurrence of a string `key`), but provides the + * index of the `key` in the backing array. + * + * This is designed to allow synchronizing a second array with the contents of the backing array, + * like how in a sourcemap `sourcesContent[i]` is the source content associated with `source[i]`, + * and there are never duplicates. + */ + class SetArray { + constructor() { + this._indexes = { __proto__: null }; + this.array = []; + } + } + /** + * Typescript doesn't allow friend access to private fields, so this just casts the set into a type + * with public access modifiers. + */ + function cast(set) { + return set; + } + /** + * Gets the index associated with `key` in the backing array, if it is already present. + */ + function get(setarr, key) { + return cast(setarr)._indexes[key]; + } + /** + * Puts `key` into the backing array, if it is not already present. Returns + * the index of the `key` in the backing array. + */ + function put(setarr, key) { + // The key may or may not be present. If it is present, it's a number. + const index = get(setarr, key); + if (index !== undefined) + return index; + const { array, _indexes: indexes } = cast(setarr); + const length = array.push(key); + return (indexes[key] = length - 1); + } + /** + * Pops the last added item out of the SetArray. + */ + function pop(setarr) { + const { array, _indexes: indexes } = cast(setarr); + if (array.length === 0) + return; + const last = array.pop(); + indexes[last] = undefined; + } + /** + * Removes the key, if it exists in the set. + */ + function remove(setarr, key) { + const index = get(setarr, key); + if (index === undefined) + return; + const { array, _indexes: indexes } = cast(setarr); + for (let i = index + 1; i < array.length; i++) { + const k = array[i]; + array[i - 1] = k; + indexes[k]--; + } + indexes[key] = undefined; + array.pop(); + } + + exports.SetArray = SetArray; + exports.get = get; + exports.pop = pop; + exports.put = put; + exports.remove = remove; + + Object.defineProperty(exports, '__esModule', { value: true }); + +})); +//# sourceMappingURL=set-array.umd.js.map + + +/***/ }), + +/***/ 69551: +/***/ (function(__unused_webpack_module, exports) { + +(function (global, factory) { + true ? factory(exports) : + 0; +})(this, (function (exports) { 'use strict'; + + const comma = ','.charCodeAt(0); + const semicolon = ';'.charCodeAt(0); + const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; + const intToChar = new Uint8Array(64); // 64 possible chars. + const charToInt = new Uint8Array(128); // z is 122 in ASCII + for (let i = 0; i < chars.length; i++) { + const c = chars.charCodeAt(i); + intToChar[i] = c; + charToInt[c] = i; + } + function decodeInteger(reader, relative) { + let value = 0; + let shift = 0; + let integer = 0; + do { + const c = reader.next(); + integer = charToInt[c]; + value |= (integer & 31) << shift; + shift += 5; + } while (integer & 32); + const shouldNegate = value & 1; + value >>>= 1; + if (shouldNegate) { + value = -0x80000000 | -value; + } + return relative + value; + } + function encodeInteger(builder, num, relative) { + let delta = num - relative; + delta = delta < 0 ? (-delta << 1) | 1 : delta << 1; + do { + let clamped = delta & 0b011111; + delta >>>= 5; + if (delta > 0) + clamped |= 0b100000; + builder.write(intToChar[clamped]); + } while (delta > 0); + return num; + } + function hasMoreVlq(reader, max) { + if (reader.pos >= max) + return false; + return reader.peek() !== comma; + } + + const bufLength = 1024 * 16; + // Provide a fallback for older environments. + const td = typeof TextDecoder !== 'undefined' + ? /* #__PURE__ */ new TextDecoder() + : typeof Buffer !== 'undefined' + ? { + decode(buf) { + const out = Buffer.from(buf.buffer, buf.byteOffset, buf.byteLength); + return out.toString(); + }, + } + : { + decode(buf) { + let out = ''; + for (let i = 0; i < buf.length; i++) { + out += String.fromCharCode(buf[i]); + } + return out; + }, + }; + class StringWriter { + constructor() { + this.pos = 0; + this.out = ''; + this.buffer = new Uint8Array(bufLength); + } + write(v) { + const { buffer } = this; + buffer[this.pos++] = v; + if (this.pos === bufLength) { + this.out += td.decode(buffer); + this.pos = 0; + } + } + flush() { + const { buffer, out, pos } = this; + return pos > 0 ? out + td.decode(buffer.subarray(0, pos)) : out; + } + } + class StringReader { + constructor(buffer) { + this.pos = 0; + this.buffer = buffer; + } + next() { + return this.buffer.charCodeAt(this.pos++); + } + peek() { + return this.buffer.charCodeAt(this.pos); + } + indexOf(char) { + const { buffer, pos } = this; + const idx = buffer.indexOf(char, pos); + return idx === -1 ? buffer.length : idx; + } + } + + const EMPTY = []; + function decodeOriginalScopes(input) { + const { length } = input; + const reader = new StringReader(input); + const scopes = []; + const stack = []; + let line = 0; + for (; reader.pos < length; reader.pos++) { + line = decodeInteger(reader, line); + const column = decodeInteger(reader, 0); + if (!hasMoreVlq(reader, length)) { + const last = stack.pop(); + last[2] = line; + last[3] = column; + continue; + } + const kind = decodeInteger(reader, 0); + const fields = decodeInteger(reader, 0); + const hasName = fields & 0b0001; + const scope = (hasName ? [line, column, 0, 0, kind, decodeInteger(reader, 0)] : [line, column, 0, 0, kind]); + let vars = EMPTY; + if (hasMoreVlq(reader, length)) { + vars = []; + do { + const varsIndex = decodeInteger(reader, 0); + vars.push(varsIndex); + } while (hasMoreVlq(reader, length)); + } + scope.vars = vars; + scopes.push(scope); + stack.push(scope); + } + return scopes; + } + function encodeOriginalScopes(scopes) { + const writer = new StringWriter(); + for (let i = 0; i < scopes.length;) { + i = _encodeOriginalScopes(scopes, i, writer, [0]); + } + return writer.flush(); + } + function _encodeOriginalScopes(scopes, index, writer, state) { + const scope = scopes[index]; + const { 0: startLine, 1: startColumn, 2: endLine, 3: endColumn, 4: kind, vars } = scope; + if (index > 0) + writer.write(comma); + state[0] = encodeInteger(writer, startLine, state[0]); + encodeInteger(writer, startColumn, 0); + encodeInteger(writer, kind, 0); + const fields = scope.length === 6 ? 0b0001 : 0; + encodeInteger(writer, fields, 0); + if (scope.length === 6) + encodeInteger(writer, scope[5], 0); + for (const v of vars) { + encodeInteger(writer, v, 0); + } + for (index++; index < scopes.length;) { + const next = scopes[index]; + const { 0: l, 1: c } = next; + if (l > endLine || (l === endLine && c >= endColumn)) { + break; + } + index = _encodeOriginalScopes(scopes, index, writer, state); + } + writer.write(comma); + state[0] = encodeInteger(writer, endLine, state[0]); + encodeInteger(writer, endColumn, 0); + return index; + } + function decodeGeneratedRanges(input) { + const { length } = input; + const reader = new StringReader(input); + const ranges = []; + const stack = []; + let genLine = 0; + let definitionSourcesIndex = 0; + let definitionScopeIndex = 0; + let callsiteSourcesIndex = 0; + let callsiteLine = 0; + let callsiteColumn = 0; + let bindingLine = 0; + let bindingColumn = 0; + do { + const semi = reader.indexOf(';'); + let genColumn = 0; + for (; reader.pos < semi; reader.pos++) { + genColumn = decodeInteger(reader, genColumn); + if (!hasMoreVlq(reader, semi)) { + const last = stack.pop(); + last[2] = genLine; + last[3] = genColumn; + continue; + } + const fields = decodeInteger(reader, 0); + const hasDefinition = fields & 0b0001; + const hasCallsite = fields & 0b0010; + const hasScope = fields & 0b0100; + let callsite = null; + let bindings = EMPTY; + let range; + if (hasDefinition) { + const defSourcesIndex = decodeInteger(reader, definitionSourcesIndex); + definitionScopeIndex = decodeInteger(reader, definitionSourcesIndex === defSourcesIndex ? definitionScopeIndex : 0); + definitionSourcesIndex = defSourcesIndex; + range = [genLine, genColumn, 0, 0, defSourcesIndex, definitionScopeIndex]; + } + else { + range = [genLine, genColumn, 0, 0]; + } + range.isScope = !!hasScope; + if (hasCallsite) { + const prevCsi = callsiteSourcesIndex; + const prevLine = callsiteLine; + callsiteSourcesIndex = decodeInteger(reader, callsiteSourcesIndex); + const sameSource = prevCsi === callsiteSourcesIndex; + callsiteLine = decodeInteger(reader, sameSource ? callsiteLine : 0); + callsiteColumn = decodeInteger(reader, sameSource && prevLine === callsiteLine ? callsiteColumn : 0); + callsite = [callsiteSourcesIndex, callsiteLine, callsiteColumn]; + } + range.callsite = callsite; + if (hasMoreVlq(reader, semi)) { + bindings = []; + do { + bindingLine = genLine; + bindingColumn = genColumn; + const expressionsCount = decodeInteger(reader, 0); + let expressionRanges; + if (expressionsCount < -1) { + expressionRanges = [[decodeInteger(reader, 0)]]; + for (let i = -1; i > expressionsCount; i--) { + const prevBl = bindingLine; + bindingLine = decodeInteger(reader, bindingLine); + bindingColumn = decodeInteger(reader, bindingLine === prevBl ? bindingColumn : 0); + const expression = decodeInteger(reader, 0); + expressionRanges.push([expression, bindingLine, bindingColumn]); + } + } + else { + expressionRanges = [[expressionsCount]]; + } + bindings.push(expressionRanges); + } while (hasMoreVlq(reader, semi)); + } + range.bindings = bindings; + ranges.push(range); + stack.push(range); + } + genLine++; + reader.pos = semi + 1; + } while (reader.pos < length); + return ranges; + } + function encodeGeneratedRanges(ranges) { + if (ranges.length === 0) + return ''; + const writer = new StringWriter(); + for (let i = 0; i < ranges.length;) { + i = _encodeGeneratedRanges(ranges, i, writer, [0, 0, 0, 0, 0, 0, 0]); + } + return writer.flush(); + } + function _encodeGeneratedRanges(ranges, index, writer, state) { + const range = ranges[index]; + const { 0: startLine, 1: startColumn, 2: endLine, 3: endColumn, isScope, callsite, bindings, } = range; + if (state[0] < startLine) { + catchupLine(writer, state[0], startLine); + state[0] = startLine; + state[1] = 0; + } + else if (index > 0) { + writer.write(comma); + } + state[1] = encodeInteger(writer, range[1], state[1]); + const fields = (range.length === 6 ? 0b0001 : 0) | (callsite ? 0b0010 : 0) | (isScope ? 0b0100 : 0); + encodeInteger(writer, fields, 0); + if (range.length === 6) { + const { 4: sourcesIndex, 5: scopesIndex } = range; + if (sourcesIndex !== state[2]) { + state[3] = 0; + } + state[2] = encodeInteger(writer, sourcesIndex, state[2]); + state[3] = encodeInteger(writer, scopesIndex, state[3]); + } + if (callsite) { + const { 0: sourcesIndex, 1: callLine, 2: callColumn } = range.callsite; + if (sourcesIndex !== state[4]) { + state[5] = 0; + state[6] = 0; + } + else if (callLine !== state[5]) { + state[6] = 0; + } + state[4] = encodeInteger(writer, sourcesIndex, state[4]); + state[5] = encodeInteger(writer, callLine, state[5]); + state[6] = encodeInteger(writer, callColumn, state[6]); + } + if (bindings) { + for (const binding of bindings) { + if (binding.length > 1) + encodeInteger(writer, -binding.length, 0); + const expression = binding[0][0]; + encodeInteger(writer, expression, 0); + let bindingStartLine = startLine; + let bindingStartColumn = startColumn; + for (let i = 1; i < binding.length; i++) { + const expRange = binding[i]; + bindingStartLine = encodeInteger(writer, expRange[1], bindingStartLine); + bindingStartColumn = encodeInteger(writer, expRange[2], bindingStartColumn); + encodeInteger(writer, expRange[0], 0); + } + } + } + for (index++; index < ranges.length;) { + const next = ranges[index]; + const { 0: l, 1: c } = next; + if (l > endLine || (l === endLine && c >= endColumn)) { + break; + } + index = _encodeGeneratedRanges(ranges, index, writer, state); + } + if (state[0] < endLine) { + catchupLine(writer, state[0], endLine); + state[0] = endLine; + state[1] = 0; + } + else { + writer.write(comma); + } + state[1] = encodeInteger(writer, endColumn, state[1]); + return index; + } + function catchupLine(writer, lastLine, line) { + do { + writer.write(semicolon); + } while (++lastLine < line); + } + + function decode(mappings) { + const { length } = mappings; + const reader = new StringReader(mappings); + const decoded = []; + let genColumn = 0; + let sourcesIndex = 0; + let sourceLine = 0; + let sourceColumn = 0; + let namesIndex = 0; + do { + const semi = reader.indexOf(';'); + const line = []; + let sorted = true; + let lastCol = 0; + genColumn = 0; + while (reader.pos < semi) { + let seg; + genColumn = decodeInteger(reader, genColumn); + if (genColumn < lastCol) + sorted = false; + lastCol = genColumn; + if (hasMoreVlq(reader, semi)) { + sourcesIndex = decodeInteger(reader, sourcesIndex); + sourceLine = decodeInteger(reader, sourceLine); + sourceColumn = decodeInteger(reader, sourceColumn); + if (hasMoreVlq(reader, semi)) { + namesIndex = decodeInteger(reader, namesIndex); + seg = [genColumn, sourcesIndex, sourceLine, sourceColumn, namesIndex]; + } + else { + seg = [genColumn, sourcesIndex, sourceLine, sourceColumn]; + } + } + else { + seg = [genColumn]; + } + line.push(seg); + reader.pos++; + } + if (!sorted) + sort(line); + decoded.push(line); + reader.pos = semi + 1; + } while (reader.pos <= length); + return decoded; + } + function sort(line) { + line.sort(sortComparator); + } + function sortComparator(a, b) { + return a[0] - b[0]; + } + function encode(decoded) { + const writer = new StringWriter(); + let sourcesIndex = 0; + let sourceLine = 0; + let sourceColumn = 0; + let namesIndex = 0; + for (let i = 0; i < decoded.length; i++) { + const line = decoded[i]; + if (i > 0) + writer.write(semicolon); + if (line.length === 0) + continue; + let genColumn = 0; + for (let j = 0; j < line.length; j++) { + const segment = line[j]; + if (j > 0) + writer.write(comma); + genColumn = encodeInteger(writer, segment[0], genColumn); + if (segment.length === 1) + continue; + sourcesIndex = encodeInteger(writer, segment[1], sourcesIndex); + sourceLine = encodeInteger(writer, segment[2], sourceLine); + sourceColumn = encodeInteger(writer, segment[3], sourceColumn); + if (segment.length === 4) + continue; + namesIndex = encodeInteger(writer, segment[4], namesIndex); + } + } + return writer.flush(); + } + + exports.decode = decode; + exports.decodeGeneratedRanges = decodeGeneratedRanges; + exports.decodeOriginalScopes = decodeOriginalScopes; + exports.encode = encode; + exports.encodeGeneratedRanges = encodeGeneratedRanges; + exports.encodeOriginalScopes = encodeOriginalScopes; + + Object.defineProperty(exports, '__esModule', { value: true }); + +})); +//# sourceMappingURL=sourcemap-codec.umd.js.map + + +/***/ }), + +/***/ 99535: +/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) { + +(function (global, factory) { + true ? factory(exports, __nccwpck_require__(69551), __nccwpck_require__(59445)) : + 0; +})(this, (function (exports, sourcemapCodec, resolveUri) { 'use strict'; + + function resolve(input, base) { + // The base is always treated as a directory, if it's not empty. + // https://github.com/mozilla/source-map/blob/8cb3ee57/lib/util.js#L327 + // https://github.com/chromium/chromium/blob/da4adbb3/third_party/blink/renderer/devtools/front_end/sdk/SourceMap.js#L400-L401 + if (base && !base.endsWith('/')) + base += '/'; + return resolveUri(input, base); + } + + /** + * Removes everything after the last "/", but leaves the slash. + */ + function stripFilename(path) { + if (!path) + return ''; + const index = path.lastIndexOf('/'); + return path.slice(0, index + 1); + } + + const COLUMN = 0; + const SOURCES_INDEX = 1; + const SOURCE_LINE = 2; + const SOURCE_COLUMN = 3; + const NAMES_INDEX = 4; + const REV_GENERATED_LINE = 1; + const REV_GENERATED_COLUMN = 2; + + function maybeSort(mappings, owned) { + const unsortedIndex = nextUnsortedSegmentLine(mappings, 0); + if (unsortedIndex === mappings.length) + return mappings; + // If we own the array (meaning we parsed it from JSON), then we're free to directly mutate it. If + // not, we do not want to modify the consumer's input array. + if (!owned) + mappings = mappings.slice(); + for (let i = unsortedIndex; i < mappings.length; i = nextUnsortedSegmentLine(mappings, i + 1)) { + mappings[i] = sortSegments(mappings[i], owned); + } + return mappings; + } + function nextUnsortedSegmentLine(mappings, start) { + for (let i = start; i < mappings.length; i++) { + if (!isSorted(mappings[i])) + return i; + } + return mappings.length; + } + function isSorted(line) { + for (let j = 1; j < line.length; j++) { + if (line[j][COLUMN] < line[j - 1][COLUMN]) { + return false; + } + } + return true; + } + function sortSegments(line, owned) { + if (!owned) + line = line.slice(); + return line.sort(sortComparator); + } + function sortComparator(a, b) { + return a[COLUMN] - b[COLUMN]; + } + + let found = false; + /** + * A binary search implementation that returns the index if a match is found. + * If no match is found, then the left-index (the index associated with the item that comes just + * before the desired index) is returned. To maintain proper sort order, a splice would happen at + * the next index: + * + * ```js + * const array = [1, 3]; + * const needle = 2; + * const index = binarySearch(array, needle, (item, needle) => item - needle); + * + * assert.equal(index, 0); + * array.splice(index + 1, 0, needle); + * assert.deepEqual(array, [1, 2, 3]); + * ``` + */ + function binarySearch(haystack, needle, low, high) { + while (low <= high) { + const mid = low + ((high - low) >> 1); + const cmp = haystack[mid][COLUMN] - needle; + if (cmp === 0) { + found = true; + return mid; + } + if (cmp < 0) { + low = mid + 1; + } + else { + high = mid - 1; + } + } + found = false; + return low - 1; + } + function upperBound(haystack, needle, index) { + for (let i = index + 1; i < haystack.length; index = i++) { + if (haystack[i][COLUMN] !== needle) + break; + } + return index; + } + function lowerBound(haystack, needle, index) { + for (let i = index - 1; i >= 0; index = i--) { + if (haystack[i][COLUMN] !== needle) + break; + } + return index; + } + function memoizedState() { + return { + lastKey: -1, + lastNeedle: -1, + lastIndex: -1, + }; + } + /** + * This overly complicated beast is just to record the last tested line/column and the resulting + * index, allowing us to skip a few tests if mappings are monotonically increasing. + */ + function memoizedBinarySearch(haystack, needle, state, key) { + const { lastKey, lastNeedle, lastIndex } = state; + let low = 0; + let high = haystack.length - 1; + if (key === lastKey) { + if (needle === lastNeedle) { + found = lastIndex !== -1 && haystack[lastIndex][COLUMN] === needle; + return lastIndex; + } + if (needle >= lastNeedle) { + // lastIndex may be -1 if the previous needle was not found. + low = lastIndex === -1 ? 0 : lastIndex; + } + else { + high = lastIndex; + } + } + state.lastKey = key; + state.lastNeedle = needle; + return (state.lastIndex = binarySearch(haystack, needle, low, high)); + } + + // Rebuilds the original source files, with mappings that are ordered by source line/column instead + // of generated line/column. + function buildBySources(decoded, memos) { + const sources = memos.map(buildNullArray); + for (let i = 0; i < decoded.length; i++) { + const line = decoded[i]; + for (let j = 0; j < line.length; j++) { + const seg = line[j]; + if (seg.length === 1) + continue; + const sourceIndex = seg[SOURCES_INDEX]; + const sourceLine = seg[SOURCE_LINE]; + const sourceColumn = seg[SOURCE_COLUMN]; + const originalSource = sources[sourceIndex]; + const originalLine = (originalSource[sourceLine] || (originalSource[sourceLine] = [])); + const memo = memos[sourceIndex]; + // The binary search either found a match, or it found the left-index just before where the + // segment should go. Either way, we want to insert after that. And there may be multiple + // generated segments associated with an original location, so there may need to move several + // indexes before we find where we need to insert. + let index = upperBound(originalLine, sourceColumn, memoizedBinarySearch(originalLine, sourceColumn, memo, sourceLine)); + memo.lastIndex = ++index; + insert(originalLine, index, [sourceColumn, i, seg[COLUMN]]); + } + } + return sources; + } + function insert(array, index, value) { + for (let i = array.length; i > index; i--) { + array[i] = array[i - 1]; + } + array[index] = value; + } + // Null arrays allow us to use ordered index keys without actually allocating contiguous memory like + // a real array. We use a null-prototype object to avoid prototype pollution and deoptimizations. + // Numeric properties on objects are magically sorted in ascending order by the engine regardless of + // the insertion order. So, by setting any numeric keys, even out of order, we'll get ascending + // order when iterating with for-in. + function buildNullArray() { + return { __proto__: null }; + } + + const AnyMap = function (map, mapUrl) { + const parsed = parse(map); + if (!('sections' in parsed)) { + return new TraceMap(parsed, mapUrl); + } + const mappings = []; + const sources = []; + const sourcesContent = []; + const names = []; + const ignoreList = []; + recurse(parsed, mapUrl, mappings, sources, sourcesContent, names, ignoreList, 0, 0, Infinity, Infinity); + const joined = { + version: 3, + file: parsed.file, + names, + sources, + sourcesContent, + mappings, + ignoreList, + }; + return presortedDecodedMap(joined); + }; + function parse(map) { + return typeof map === 'string' ? JSON.parse(map) : map; + } + function recurse(input, mapUrl, mappings, sources, sourcesContent, names, ignoreList, lineOffset, columnOffset, stopLine, stopColumn) { + const { sections } = input; + for (let i = 0; i < sections.length; i++) { + const { map, offset } = sections[i]; + let sl = stopLine; + let sc = stopColumn; + if (i + 1 < sections.length) { + const nextOffset = sections[i + 1].offset; + sl = Math.min(stopLine, lineOffset + nextOffset.line); + if (sl === stopLine) { + sc = Math.min(stopColumn, columnOffset + nextOffset.column); + } + else if (sl < stopLine) { + sc = columnOffset + nextOffset.column; + } + } + addSection(map, mapUrl, mappings, sources, sourcesContent, names, ignoreList, lineOffset + offset.line, columnOffset + offset.column, sl, sc); + } + } + function addSection(input, mapUrl, mappings, sources, sourcesContent, names, ignoreList, lineOffset, columnOffset, stopLine, stopColumn) { + const parsed = parse(input); + if ('sections' in parsed) + return recurse(...arguments); + const map = new TraceMap(parsed, mapUrl); + const sourcesOffset = sources.length; + const namesOffset = names.length; + const decoded = decodedMappings(map); + const { resolvedSources, sourcesContent: contents, ignoreList: ignores } = map; + append(sources, resolvedSources); + append(names, map.names); + if (contents) + append(sourcesContent, contents); + else + for (let i = 0; i < resolvedSources.length; i++) + sourcesContent.push(null); + if (ignores) + for (let i = 0; i < ignores.length; i++) + ignoreList.push(ignores[i] + sourcesOffset); + for (let i = 0; i < decoded.length; i++) { + const lineI = lineOffset + i; + // We can only add so many lines before we step into the range that the next section's map + // controls. When we get to the last line, then we'll start checking the segments to see if + // they've crossed into the column range. But it may not have any columns that overstep, so we + // still need to check that we don't overstep lines, too. + if (lineI > stopLine) + return; + // The out line may already exist in mappings (if we're continuing the line started by a + // previous section). Or, we may have jumped ahead several lines to start this section. + const out = getLine(mappings, lineI); + // On the 0th loop, the section's column offset shifts us forward. On all other lines (since the + // map can be multiple lines), it doesn't. + const cOffset = i === 0 ? columnOffset : 0; + const line = decoded[i]; + for (let j = 0; j < line.length; j++) { + const seg = line[j]; + const column = cOffset + seg[COLUMN]; + // If this segment steps into the column range that the next section's map controls, we need + // to stop early. + if (lineI === stopLine && column >= stopColumn) + return; + if (seg.length === 1) { + out.push([column]); + continue; + } + const sourcesIndex = sourcesOffset + seg[SOURCES_INDEX]; + const sourceLine = seg[SOURCE_LINE]; + const sourceColumn = seg[SOURCE_COLUMN]; + out.push(seg.length === 4 + ? [column, sourcesIndex, sourceLine, sourceColumn] + : [column, sourcesIndex, sourceLine, sourceColumn, namesOffset + seg[NAMES_INDEX]]); + } + } + } + function append(arr, other) { + for (let i = 0; i < other.length; i++) + arr.push(other[i]); + } + function getLine(arr, index) { + for (let i = arr.length; i <= index; i++) + arr[i] = []; + return arr[index]; + } + + const LINE_GTR_ZERO = '`line` must be greater than 0 (lines start at line 1)'; + const COL_GTR_EQ_ZERO = '`column` must be greater than or equal to 0 (columns start at column 0)'; + const LEAST_UPPER_BOUND = -1; + const GREATEST_LOWER_BOUND = 1; + class TraceMap { + constructor(map, mapUrl) { + const isString = typeof map === 'string'; + if (!isString && map._decodedMemo) + return map; + const parsed = (isString ? JSON.parse(map) : map); + const { version, file, names, sourceRoot, sources, sourcesContent } = parsed; + this.version = version; + this.file = file; + this.names = names || []; + this.sourceRoot = sourceRoot; + this.sources = sources; + this.sourcesContent = sourcesContent; + this.ignoreList = parsed.ignoreList || parsed.x_google_ignoreList || undefined; + const from = resolve(sourceRoot || '', stripFilename(mapUrl)); + this.resolvedSources = sources.map((s) => resolve(s || '', from)); + const { mappings } = parsed; + if (typeof mappings === 'string') { + this._encoded = mappings; + this._decoded = undefined; + } + else { + this._encoded = undefined; + this._decoded = maybeSort(mappings, isString); + } + this._decodedMemo = memoizedState(); + this._bySources = undefined; + this._bySourceMemos = undefined; + } + } + /** + * Typescript doesn't allow friend access to private fields, so this just casts the map into a type + * with public access modifiers. + */ + function cast(map) { + return map; + } + /** + * Returns the encoded (VLQ string) form of the SourceMap's mappings field. + */ + function encodedMappings(map) { + var _a; + var _b; + return ((_a = (_b = cast(map))._encoded) !== null && _a !== void 0 ? _a : (_b._encoded = sourcemapCodec.encode(cast(map)._decoded))); + } + /** + * Returns the decoded (array of lines of segments) form of the SourceMap's mappings field. + */ + function decodedMappings(map) { + var _a; + return ((_a = cast(map))._decoded || (_a._decoded = sourcemapCodec.decode(cast(map)._encoded))); + } + /** + * A low-level API to find the segment associated with a generated line/column (think, from a + * stack trace). Line and column here are 0-based, unlike `originalPositionFor`. + */ + function traceSegment(map, line, column) { + const decoded = decodedMappings(map); + // It's common for parent source maps to have pointers to lines that have no + // mapping (like a "//# sourceMappingURL=") at the end of the child file. + if (line >= decoded.length) + return null; + const segments = decoded[line]; + const index = traceSegmentInternal(segments, cast(map)._decodedMemo, line, column, GREATEST_LOWER_BOUND); + return index === -1 ? null : segments[index]; + } + /** + * A higher-level API to find the source/line/column associated with a generated line/column + * (think, from a stack trace). Line is 1-based, but column is 0-based, due to legacy behavior in + * `source-map` library. + */ + function originalPositionFor(map, needle) { + let { line, column, bias } = needle; + line--; + if (line < 0) + throw new Error(LINE_GTR_ZERO); + if (column < 0) + throw new Error(COL_GTR_EQ_ZERO); + const decoded = decodedMappings(map); + // It's common for parent source maps to have pointers to lines that have no + // mapping (like a "//# sourceMappingURL=") at the end of the child file. + if (line >= decoded.length) + return OMapping(null, null, null, null); + const segments = decoded[line]; + const index = traceSegmentInternal(segments, cast(map)._decodedMemo, line, column, bias || GREATEST_LOWER_BOUND); + if (index === -1) + return OMapping(null, null, null, null); + const segment = segments[index]; + if (segment.length === 1) + return OMapping(null, null, null, null); + const { names, resolvedSources } = map; + return OMapping(resolvedSources[segment[SOURCES_INDEX]], segment[SOURCE_LINE] + 1, segment[SOURCE_COLUMN], segment.length === 5 ? names[segment[NAMES_INDEX]] : null); + } + /** + * Finds the generated line/column position of the provided source/line/column source position. + */ + function generatedPositionFor(map, needle) { + const { source, line, column, bias } = needle; + return generatedPosition(map, source, line, column, bias || GREATEST_LOWER_BOUND, false); + } + /** + * Finds all generated line/column positions of the provided source/line/column source position. + */ + function allGeneratedPositionsFor(map, needle) { + const { source, line, column, bias } = needle; + // SourceMapConsumer uses LEAST_UPPER_BOUND for some reason, so we follow suit. + return generatedPosition(map, source, line, column, bias || LEAST_UPPER_BOUND, true); + } + /** + * Iterates each mapping in generated position order. + */ + function eachMapping(map, cb) { + const decoded = decodedMappings(map); + const { names, resolvedSources } = map; + for (let i = 0; i < decoded.length; i++) { + const line = decoded[i]; + for (let j = 0; j < line.length; j++) { + const seg = line[j]; + const generatedLine = i + 1; + const generatedColumn = seg[0]; + let source = null; + let originalLine = null; + let originalColumn = null; + let name = null; + if (seg.length !== 1) { + source = resolvedSources[seg[1]]; + originalLine = seg[2] + 1; + originalColumn = seg[3]; + } + if (seg.length === 5) + name = names[seg[4]]; + cb({ + generatedLine, + generatedColumn, + source, + originalLine, + originalColumn, + name, + }); + } + } + } + function sourceIndex(map, source) { + const { sources, resolvedSources } = map; + let index = sources.indexOf(source); + if (index === -1) + index = resolvedSources.indexOf(source); + return index; + } + /** + * Retrieves the source content for a particular source, if its found. Returns null if not. + */ + function sourceContentFor(map, source) { + const { sourcesContent } = map; + if (sourcesContent == null) + return null; + const index = sourceIndex(map, source); + return index === -1 ? null : sourcesContent[index]; + } + /** + * Determines if the source is marked to ignore by the source map. + */ + function isIgnored(map, source) { + const { ignoreList } = map; + if (ignoreList == null) + return false; + const index = sourceIndex(map, source); + return index === -1 ? false : ignoreList.includes(index); + } + /** + * A helper that skips sorting of the input map's mappings array, which can be expensive for larger + * maps. + */ + function presortedDecodedMap(map, mapUrl) { + const tracer = new TraceMap(clone(map, []), mapUrl); + cast(tracer)._decoded = map.mappings; + return tracer; + } + /** + * Returns a sourcemap object (with decoded mappings) suitable for passing to a library that expects + * a sourcemap, or to JSON.stringify. + */ + function decodedMap(map) { + return clone(map, decodedMappings(map)); + } + /** + * Returns a sourcemap object (with encoded mappings) suitable for passing to a library that expects + * a sourcemap, or to JSON.stringify. + */ + function encodedMap(map) { + return clone(map, encodedMappings(map)); + } + function clone(map, mappings) { + return { + version: map.version, + file: map.file, + names: map.names, + sourceRoot: map.sourceRoot, + sources: map.sources, + sourcesContent: map.sourcesContent, + mappings, + ignoreList: map.ignoreList || map.x_google_ignoreList, + }; + } + function OMapping(source, line, column, name) { + return { source, line, column, name }; + } + function GMapping(line, column) { + return { line, column }; + } + function traceSegmentInternal(segments, memo, line, column, bias) { + let index = memoizedBinarySearch(segments, column, memo, line); + if (found) { + index = (bias === LEAST_UPPER_BOUND ? upperBound : lowerBound)(segments, column, index); + } + else if (bias === LEAST_UPPER_BOUND) + index++; + if (index === -1 || index === segments.length) + return -1; + return index; + } + function sliceGeneratedPositions(segments, memo, line, column, bias) { + let min = traceSegmentInternal(segments, memo, line, column, GREATEST_LOWER_BOUND); + // We ignored the bias when tracing the segment so that we're guarnateed to find the first (in + // insertion order) segment that matched. Even if we did respect the bias when tracing, we would + // still need to call `lowerBound()` to find the first segment, which is slower than just looking + // for the GREATEST_LOWER_BOUND to begin with. The only difference that matters for us is when the + // binary search didn't match, in which case GREATEST_LOWER_BOUND just needs to increment to + // match LEAST_UPPER_BOUND. + if (!found && bias === LEAST_UPPER_BOUND) + min++; + if (min === -1 || min === segments.length) + return []; + // We may have found the segment that started at an earlier column. If this is the case, then we + // need to slice all generated segments that match _that_ column, because all such segments span + // to our desired column. + const matchedColumn = found ? column : segments[min][COLUMN]; + // The binary search is not guaranteed to find the lower bound when a match wasn't found. + if (!found) + min = lowerBound(segments, matchedColumn, min); + const max = upperBound(segments, matchedColumn, min); + const result = []; + for (; min <= max; min++) { + const segment = segments[min]; + result.push(GMapping(segment[REV_GENERATED_LINE] + 1, segment[REV_GENERATED_COLUMN])); + } + return result; + } + function generatedPosition(map, source, line, column, bias, all) { + var _a; + line--; + if (line < 0) + throw new Error(LINE_GTR_ZERO); + if (column < 0) + throw new Error(COL_GTR_EQ_ZERO); + const { sources, resolvedSources } = map; + let sourceIndex = sources.indexOf(source); + if (sourceIndex === -1) + sourceIndex = resolvedSources.indexOf(source); + if (sourceIndex === -1) + return all ? [] : GMapping(null, null); + const generated = ((_a = cast(map))._bySources || (_a._bySources = buildBySources(decodedMappings(map), (cast(map)._bySourceMemos = sources.map(memoizedState))))); + const segments = generated[sourceIndex][line]; + if (segments == null) + return all ? [] : GMapping(null, null); + const memo = cast(map)._bySourceMemos[sourceIndex]; + if (all) + return sliceGeneratedPositions(segments, memo, line, column, bias); + const index = traceSegmentInternal(segments, memo, line, column, bias); + if (index === -1) + return GMapping(null, null); + const segment = segments[index]; + return GMapping(segment[REV_GENERATED_LINE] + 1, segment[REV_GENERATED_COLUMN]); + } + + exports.AnyMap = AnyMap; + exports.GREATEST_LOWER_BOUND = GREATEST_LOWER_BOUND; + exports.LEAST_UPPER_BOUND = LEAST_UPPER_BOUND; + exports.TraceMap = TraceMap; + exports.allGeneratedPositionsFor = allGeneratedPositionsFor; + exports.decodedMap = decodedMap; + exports.decodedMappings = decodedMappings; + exports.eachMapping = eachMapping; + exports.encodedMap = encodedMap; + exports.encodedMappings = encodedMappings; + exports.generatedPositionFor = generatedPositionFor; + exports.isIgnored = isIgnored; + exports.originalPositionFor = originalPositionFor; + exports.presortedDecodedMap = presortedDecodedMap; + exports.sourceContentFor = sourceContentFor; + exports.traceSegment = traceSegment; + +})); +//# sourceMappingURL=trace-mapping.umd.js.map + + +/***/ }), + +/***/ 73654: +/***/ ((module) => { + +const IMPORT_REGEX = /^import\s/ +const EXPORT_REGEX = /^export\s/ +const EXPORT_DEFAULT_REGEX = /^export default\s/ +const STARTS_WITH_CAPITAL_LETTER_REGEX = /^[A-Z]/ +const EMPTY_NEWLINE = '\n\n' +const COMMENT_OPEN = '' + +const isImport = text => IMPORT_REGEX.test(text) +const isExport = text => EXPORT_REGEX.test(text) +const isExportDefault = text => EXPORT_DEFAULT_REGEX.test(text) +const isImportOrExport = text => isImport(text) || isExport(text) + +const isComment = str => + str.startsWith(COMMENT_OPEN) && str.endsWith(COMMENT_CLOSE) + +const getCommentContents = str => + str.slice(COMMENT_OPEN.length, -COMMENT_CLOSE.length) + +const startsWithCapitalLetter = str => + STARTS_WITH_CAPITAL_LETTER_REGEX.test(str) + +const paramCase = string => + string + .replace(/([a-z0-9])([A-Z])/g, '$1-$2') + .replace(/([a-z])([0-9])/g, '$1-$2') + .toLowerCase() + +const toTemplateLiteral = text => { + const escaped = text + .replace(/\\(?!\$)/g, '\\\\') // Escape all "\" to avoid unwanted escaping in text nodes + // and ignore "\$" since it's already escaped and is common + // with prettier https://github.com/mdx-js/mdx/issues/606 + .replace(/`/g, '\\`') // Escape "`"" since + .replace(/(\\\$)/g, '\\$1') // Escape \$ so render it as it is + .replace(/(\\\$)(\{)/g, '\\$1\\$2') // Escape \${} so render it as it is + .replace(/\$\{/g, '\\${') // Escape ${} in text so that it doesn't eval + + return '{`' + escaped + '`}' +} + +module.exports.EMPTY_NEWLINE = EMPTY_NEWLINE +module.exports.isImport = isImport +module.exports.isExport = isExport +module.exports.isExportDefault = isExportDefault +module.exports.isImportOrExport = isImportOrExport +module.exports.startsWithCapitalLetter = startsWithCapitalLetter +module.exports.isComment = isComment +module.exports.getCommentContents = getCommentContents +module.exports.paramCase = paramCase +module.exports.toTemplateLiteral = toTemplateLiteral + + +/***/ }), + +/***/ 83076: +/***/ ((module) => { + +"use strict"; + + +module.exports = ccount + +function ccount(source, character) { + var value = String(source) + var count = 0 + var index + + if (typeof character !== 'string') { + throw new Error('Expected character') + } + + index = value.indexOf(character) + + while (index !== -1) { + count++ + index = value.indexOf(character, index + character.length) + } + + return count +} + + +/***/ }), + +/***/ 37352: +/***/ ((module) => { + +"use strict"; + + +module.exports = collapse + +// `collapse(' \t\nbar \nbaz\t') // ' bar baz '` +function collapse(value) { + return String(value).replace(/\s+/g, ' ') +} + + +/***/ }), + +/***/ 25288: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + +var fs = __nccwpck_require__(79896); +var path = __nccwpck_require__(16928); + +Object.defineProperty(exports, "commentRegex", ({ + get: function getCommentRegex () { + return /^\s*\/(?:\/|\*)[@#]\s+sourceMappingURL=data:(?:application|text)\/json;(?:charset[:=]\S+?;)?base64,(?:.*)$/mg; + } +})); + +Object.defineProperty(exports, "mapFileCommentRegex", ({ + get: function getMapFileCommentRegex () { + // Matches sourceMappingURL in either // or /* comment styles. + return /(?:\/\/[@#][ \t]+sourceMappingURL=([^\s'"`]+?)[ \t]*$)|(?:\/\*[@#][ \t]+sourceMappingURL=([^\*]+?)[ \t]*(?:\*\/){1}[ \t]*$)/mg; + } +})); + +var decodeBase64; +if (typeof Buffer !== 'undefined') { + if (typeof Buffer.from === 'function') { + decodeBase64 = decodeBase64WithBufferFrom; + } else { + decodeBase64 = decodeBase64WithNewBuffer; + } +} else { + decodeBase64 = decodeBase64WithAtob; +} + +function decodeBase64WithBufferFrom(base64) { + return Buffer.from(base64, 'base64').toString(); +} + +function decodeBase64WithNewBuffer(base64) { + if (typeof value === 'number') { + throw new TypeError('The value to decode must not be of type number.'); + } + return new Buffer(base64, 'base64').toString(); +} + +function decodeBase64WithAtob(base64) { + return decodeURIComponent(escape(atob(base64))); +} + +function stripComment(sm) { + return sm.split(',').pop(); +} + +function readFromFileMap(sm, dir) { + // NOTE: this will only work on the server since it attempts to read the map file + + var r = exports.mapFileCommentRegex.exec(sm); + + // for some odd reason //# .. captures in 1 and /* .. */ in 2 + var filename = r[1] || r[2]; + var filepath = path.resolve(dir, filename); + + try { + return fs.readFileSync(filepath, 'utf8'); + } catch (e) { + throw new Error('An error occurred while trying to read the map file at ' + filepath + '\n' + e); + } +} + +function Converter (sm, opts) { + opts = opts || {}; + + if (opts.isFileComment) sm = readFromFileMap(sm, opts.commentFileDir); + if (opts.hasComment) sm = stripComment(sm); + if (opts.isEncoded) sm = decodeBase64(sm); + if (opts.isJSON || opts.isEncoded) sm = JSON.parse(sm); + + this.sourcemap = sm; +} + +Converter.prototype.toJSON = function (space) { + return JSON.stringify(this.sourcemap, null, space); +}; + +if (typeof Buffer !== 'undefined') { + if (typeof Buffer.from === 'function') { + Converter.prototype.toBase64 = encodeBase64WithBufferFrom; + } else { + Converter.prototype.toBase64 = encodeBase64WithNewBuffer; + } +} else { + Converter.prototype.toBase64 = encodeBase64WithBtoa; +} + +function encodeBase64WithBufferFrom() { + var json = this.toJSON(); + return Buffer.from(json, 'utf8').toString('base64'); +} + +function encodeBase64WithNewBuffer() { + var json = this.toJSON(); + if (typeof json === 'number') { + throw new TypeError('The json to encode must not be of type number.'); + } + return new Buffer(json, 'utf8').toString('base64'); +} + +function encodeBase64WithBtoa() { + var json = this.toJSON(); + return btoa(unescape(encodeURIComponent(json))); +} + +Converter.prototype.toComment = function (options) { + var base64 = this.toBase64(); + var data = 'sourceMappingURL=data:application/json;charset=utf-8;base64,' + base64; + return options && options.multiline ? '/*# ' + data + ' */' : '//# ' + data; +}; + +// returns copy instead of original +Converter.prototype.toObject = function () { + return JSON.parse(this.toJSON()); +}; + +Converter.prototype.addProperty = function (key, value) { + if (this.sourcemap.hasOwnProperty(key)) throw new Error('property "' + key + '" already exists on the sourcemap, use set property instead'); + return this.setProperty(key, value); +}; + +Converter.prototype.setProperty = function (key, value) { + this.sourcemap[key] = value; + return this; +}; + +Converter.prototype.getProperty = function (key) { + return this.sourcemap[key]; +}; + +exports.fromObject = function (obj) { + return new Converter(obj); +}; + +exports.fromJSON = function (json) { + return new Converter(json, { isJSON: true }); +}; + +exports.fromBase64 = function (base64) { + return new Converter(base64, { isEncoded: true }); +}; + +exports.fromComment = function (comment) { + comment = comment + .replace(/^\/\*/g, '//') + .replace(/\*\/$/g, ''); + + return new Converter(comment, { isEncoded: true, hasComment: true }); +}; + +exports.fromMapFileComment = function (comment, dir) { + return new Converter(comment, { commentFileDir: dir, isFileComment: true, isJSON: true }); +}; + +// Finds last sourcemap comment in file or returns null if none was found +exports.fromSource = function (content) { + var m = content.match(exports.commentRegex); + return m ? exports.fromComment(m.pop()) : null; +}; + +// Finds last sourcemap comment in file or returns null if none was found +exports.fromMapFileSource = function (content, dir) { + var m = content.match(exports.mapFileCommentRegex); + return m ? exports.fromMapFileComment(m.pop(), dir) : null; +}; + +exports.removeComments = function (src) { + return src.replace(exports.commentRegex, ''); +}; + +exports.removeMapFileComments = function (src) { + return src.replace(exports.mapFileCommentRegex, ''); +}; + +exports.generateMapFileComment = function (file, options) { + var data = 'sourceMappingURL=' + file; + return options && options.multiline ? '/*# ' + data + ' */' : '//# ' + data; +}; + + +/***/ }), + +/***/ 6110: +/***/ ((module, exports, __nccwpck_require__) => { + +/* eslint-env browser */ + +/** + * This is the web browser implementation of `debug()`. + */ + +exports.formatArgs = formatArgs; +exports.save = save; +exports.load = load; +exports.useColors = useColors; +exports.storage = localstorage(); +exports.destroy = (() => { + let warned = false; + + return () => { + if (!warned) { + warned = true; + console.warn('Instance method `debug.destroy()` is deprecated and no longer does anything. It will be removed in the next major version of `debug`.'); + } + }; +})(); + +/** + * Colors. + */ + +exports.colors = [ + '#0000CC', + '#0000FF', + '#0033CC', + '#0033FF', + '#0066CC', + '#0066FF', + '#0099CC', + '#0099FF', + '#00CC00', + '#00CC33', + '#00CC66', + '#00CC99', + '#00CCCC', + '#00CCFF', + '#3300CC', + '#3300FF', + '#3333CC', + '#3333FF', + '#3366CC', + '#3366FF', + '#3399CC', + '#3399FF', + '#33CC00', + '#33CC33', + '#33CC66', + '#33CC99', + '#33CCCC', + '#33CCFF', + '#6600CC', + '#6600FF', + '#6633CC', + '#6633FF', + '#66CC00', + '#66CC33', + '#9900CC', + '#9900FF', + '#9933CC', + '#9933FF', + '#99CC00', + '#99CC33', + '#CC0000', + '#CC0033', + '#CC0066', + '#CC0099', + '#CC00CC', + '#CC00FF', + '#CC3300', + '#CC3333', + '#CC3366', + '#CC3399', + '#CC33CC', + '#CC33FF', + '#CC6600', + '#CC6633', + '#CC9900', + '#CC9933', + '#CCCC00', + '#CCCC33', + '#FF0000', + '#FF0033', + '#FF0066', + '#FF0099', + '#FF00CC', + '#FF00FF', + '#FF3300', + '#FF3333', + '#FF3366', + '#FF3399', + '#FF33CC', + '#FF33FF', + '#FF6600', + '#FF6633', + '#FF9900', + '#FF9933', + '#FFCC00', + '#FFCC33' +]; + +/** + * Currently only WebKit-based Web Inspectors, Firefox >= v31, + * and the Firebug extension (any Firefox version) are known + * to support "%c" CSS customizations. + * + * TODO: add a `localStorage` variable to explicitly enable/disable colors + */ + +// eslint-disable-next-line complexity +function useColors() { + // NB: In an Electron preload script, document will be defined but not fully + // initialized. Since we know we're in Chrome, we'll just detect this case + // explicitly + if (typeof window !== 'undefined' && window.process && (window.process.type === 'renderer' || window.process.__nwjs)) { + return true; + } + + // Internet Explorer and Edge do not support colors. + if (typeof navigator !== 'undefined' && navigator.userAgent && navigator.userAgent.toLowerCase().match(/(edge|trident)\/(\d+)/)) { + return false; + } + + let m; + + // Is webkit? http://stackoverflow.com/a/16459606/376773 + // document is undefined in react-native: https://github.com/facebook/react-native/pull/1632 + // eslint-disable-next-line no-return-assign + return (typeof document !== 'undefined' && document.documentElement && document.documentElement.style && document.documentElement.style.WebkitAppearance) || + // Is firebug? http://stackoverflow.com/a/398120/376773 + (typeof window !== 'undefined' && window.console && (window.console.firebug || (window.console.exception && window.console.table))) || + // Is firefox >= v31? + // https://developer.mozilla.org/en-US/docs/Tools/Web_Console#Styling_messages + (typeof navigator !== 'undefined' && navigator.userAgent && (m = navigator.userAgent.toLowerCase().match(/firefox\/(\d+)/)) && parseInt(m[1], 10) >= 31) || + // Double check webkit in userAgent just in case we are in a worker + (typeof navigator !== 'undefined' && navigator.userAgent && navigator.userAgent.toLowerCase().match(/applewebkit\/(\d+)/)); +} + +/** + * Colorize log arguments if enabled. + * + * @api public + */ + +function formatArgs(args) { + args[0] = (this.useColors ? '%c' : '') + + this.namespace + + (this.useColors ? ' %c' : ' ') + + args[0] + + (this.useColors ? '%c ' : ' ') + + '+' + module.exports.humanize(this.diff); + + if (!this.useColors) { + return; + } + + const c = 'color: ' + this.color; + args.splice(1, 0, c, 'color: inherit'); + + // The final "%c" is somewhat tricky, because there could be other + // arguments passed either before or after the %c, so we need to + // figure out the correct index to insert the CSS into + let index = 0; + let lastC = 0; + args[0].replace(/%[a-zA-Z%]/g, match => { + if (match === '%%') { + return; + } + index++; + if (match === '%c') { + // We only are interested in the *last* %c + // (the user may have provided their own) + lastC = index; + } + }); + + args.splice(lastC, 0, c); +} + +/** + * Invokes `console.debug()` when available. + * No-op when `console.debug` is not a "function". + * If `console.debug` is not available, falls back + * to `console.log`. + * + * @api public + */ +exports.log = console.debug || console.log || (() => {}); + +/** + * Save `namespaces`. + * + * @param {String} namespaces + * @api private + */ +function save(namespaces) { + try { + if (namespaces) { + exports.storage.setItem('debug', namespaces); + } else { + exports.storage.removeItem('debug'); + } + } catch (error) { + // Swallow + // XXX (@Qix-) should we be logging these? + } +} + +/** + * Load `namespaces`. + * + * @return {String} returns the previously persisted debug modes + * @api private + */ +function load() { + let r; + try { + r = exports.storage.getItem('debug') || exports.storage.getItem('DEBUG') ; + } catch (error) { + // Swallow + // XXX (@Qix-) should we be logging these? + } + + // If debug isn't set in LS, and we're in Electron, try to load $DEBUG + if (!r && typeof process !== 'undefined' && 'env' in process) { + r = process.env.DEBUG; + } + + return r; +} + +/** + * Localstorage attempts to return the localstorage. + * + * This is necessary because safari throws + * when a user disables cookies/localstorage + * and you attempt to access it. + * + * @return {LocalStorage} + * @api private + */ + +function localstorage() { + try { + // TVMLKit (Apple TV JS Runtime) does not have a window object, just localStorage in the global context + // The Browser also has localStorage in the global context. + return localStorage; + } catch (error) { + // Swallow + // XXX (@Qix-) should we be logging these? + } +} + +module.exports = __nccwpck_require__(40897)(exports); + +const {formatters} = module.exports; + +/** + * Map %j to `JSON.stringify()`, since no Web Inspectors do that by default. + */ + +formatters.j = function (v) { + try { + return JSON.stringify(v); + } catch (error) { + return '[UnexpectedJSONParseError]: ' + error.message; + } +}; + + +/***/ }), + +/***/ 40897: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + + +/** + * This is the common logic for both the Node.js and web browser + * implementations of `debug()`. + */ + +function setup(env) { + createDebug.debug = createDebug; + createDebug.default = createDebug; + createDebug.coerce = coerce; + createDebug.disable = disable; + createDebug.enable = enable; + createDebug.enabled = enabled; + createDebug.humanize = __nccwpck_require__(70744); + createDebug.destroy = destroy; + + Object.keys(env).forEach(key => { + createDebug[key] = env[key]; + }); + + /** + * The currently active debug mode names, and names to skip. + */ + + createDebug.names = []; + createDebug.skips = []; + + /** + * Map of special "%n" handling functions, for the debug "format" argument. + * + * Valid key names are a single, lower or upper-case letter, i.e. "n" and "N". + */ + createDebug.formatters = {}; + + /** + * Selects a color for a debug namespace + * @param {String} namespace The namespace string for the debug instance to be colored + * @return {Number|String} An ANSI color code for the given namespace + * @api private + */ + function selectColor(namespace) { + let hash = 0; + + for (let i = 0; i < namespace.length; i++) { + hash = ((hash << 5) - hash) + namespace.charCodeAt(i); + hash |= 0; // Convert to 32bit integer + } + + return createDebug.colors[Math.abs(hash) % createDebug.colors.length]; + } + createDebug.selectColor = selectColor; + + /** + * Create a debugger with the given `namespace`. + * + * @param {String} namespace + * @return {Function} + * @api public + */ + function createDebug(namespace) { + let prevTime; + let enableOverride = null; + let namespacesCache; + let enabledCache; + + function debug(...args) { + // Disabled? + if (!debug.enabled) { + return; + } + + const self = debug; + + // Set `diff` timestamp + const curr = Number(new Date()); + const ms = curr - (prevTime || curr); + self.diff = ms; + self.prev = prevTime; + self.curr = curr; + prevTime = curr; + + args[0] = createDebug.coerce(args[0]); + + if (typeof args[0] !== 'string') { + // Anything else let's inspect with %O + args.unshift('%O'); + } + + // Apply any `formatters` transformations + let index = 0; + args[0] = args[0].replace(/%([a-zA-Z%])/g, (match, format) => { + // If we encounter an escaped % then don't increase the array index + if (match === '%%') { + return '%'; + } + index++; + const formatter = createDebug.formatters[format]; + if (typeof formatter === 'function') { + const val = args[index]; + match = formatter.call(self, val); + + // Now we need to remove `args[index]` since it's inlined in the `format` + args.splice(index, 1); + index--; + } + return match; + }); + + // Apply env-specific formatting (colors, etc.) + createDebug.formatArgs.call(self, args); + + const logFn = self.log || createDebug.log; + logFn.apply(self, args); + } + + debug.namespace = namespace; + debug.useColors = createDebug.useColors(); + debug.color = createDebug.selectColor(namespace); + debug.extend = extend; + debug.destroy = createDebug.destroy; // XXX Temporary. Will be removed in the next major release. + + Object.defineProperty(debug, 'enabled', { + enumerable: true, + configurable: false, + get: () => { + if (enableOverride !== null) { + return enableOverride; + } + if (namespacesCache !== createDebug.namespaces) { + namespacesCache = createDebug.namespaces; + enabledCache = createDebug.enabled(namespace); + } + + return enabledCache; + }, + set: v => { + enableOverride = v; + } + }); + + // Env-specific initialization logic for debug instances + if (typeof createDebug.init === 'function') { + createDebug.init(debug); + } + + return debug; + } + + function extend(namespace, delimiter) { + const newDebug = createDebug(this.namespace + (typeof delimiter === 'undefined' ? ':' : delimiter) + namespace); + newDebug.log = this.log; + return newDebug; + } + + /** + * Enables a debug mode by namespaces. This can include modes + * separated by a colon and wildcards. + * + * @param {String} namespaces + * @api public + */ + function enable(namespaces) { + createDebug.save(namespaces); + createDebug.namespaces = namespaces; + + createDebug.names = []; + createDebug.skips = []; + + const split = (typeof namespaces === 'string' ? namespaces : '') + .trim() + .replace(/\s+/g, ',') + .split(',') + .filter(Boolean); + + for (const ns of split) { + if (ns[0] === '-') { + createDebug.skips.push(ns.slice(1)); + } else { + createDebug.names.push(ns); + } + } + } + + /** + * Checks if the given string matches a namespace template, honoring + * asterisks as wildcards. + * + * @param {String} search + * @param {String} template + * @return {Boolean} + */ + function matchesTemplate(search, template) { + let searchIndex = 0; + let templateIndex = 0; + let starIndex = -1; + let matchIndex = 0; + + while (searchIndex < search.length) { + if (templateIndex < template.length && (template[templateIndex] === search[searchIndex] || template[templateIndex] === '*')) { + // Match character or proceed with wildcard + if (template[templateIndex] === '*') { + starIndex = templateIndex; + matchIndex = searchIndex; + templateIndex++; // Skip the '*' + } else { + searchIndex++; + templateIndex++; + } + } else if (starIndex !== -1) { // eslint-disable-line no-negated-condition + // Backtrack to the last '*' and try to match more characters + templateIndex = starIndex + 1; + matchIndex++; + searchIndex = matchIndex; + } else { + return false; // No match + } + } + + // Handle trailing '*' in template + while (templateIndex < template.length && template[templateIndex] === '*') { + templateIndex++; + } + + return templateIndex === template.length; + } + + /** + * Disable debug output. + * + * @return {String} namespaces + * @api public + */ + function disable() { + const namespaces = [ + ...createDebug.names, + ...createDebug.skips.map(namespace => '-' + namespace) + ].join(','); + createDebug.enable(''); + return namespaces; + } + + /** + * Returns true if the given mode name is enabled, false otherwise. + * + * @param {String} name + * @return {Boolean} + * @api public + */ + function enabled(name) { + for (const skip of createDebug.skips) { + if (matchesTemplate(name, skip)) { + return false; + } + } + + for (const ns of createDebug.names) { + if (matchesTemplate(name, ns)) { + return true; + } + } + + return false; + } + + /** + * Coerce `val`. + * + * @param {Mixed} val + * @return {Mixed} + * @api private + */ + function coerce(val) { + if (val instanceof Error) { + return val.stack || val.message; + } + return val; + } + + /** + * XXX DO NOT USE. This is a temporary stub function. + * XXX It WILL be removed in the next major release. + */ + function destroy() { + console.warn('Instance method `debug.destroy()` is deprecated and no longer does anything. It will be removed in the next major version of `debug`.'); + } + + createDebug.enable(createDebug.load()); + + return createDebug; +} + +module.exports = setup; + + +/***/ }), + +/***/ 2830: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +/** + * Detect Electron renderer / nwjs process, which is node, but we should + * treat as a browser. + */ + +if (typeof process === 'undefined' || process.type === 'renderer' || process.browser === true || process.__nwjs) { + module.exports = __nccwpck_require__(6110); +} else { + module.exports = __nccwpck_require__(95108); +} + + +/***/ }), + +/***/ 95108: +/***/ ((module, exports, __nccwpck_require__) => { + +/** + * Module dependencies. + */ + +const tty = __nccwpck_require__(52018); +const util = __nccwpck_require__(39023); + +/** + * This is the Node.js implementation of `debug()`. + */ + +exports.init = init; +exports.log = log; +exports.formatArgs = formatArgs; +exports.save = save; +exports.load = load; +exports.useColors = useColors; +exports.destroy = util.deprecate( + () => {}, + 'Instance method `debug.destroy()` is deprecated and no longer does anything. It will be removed in the next major version of `debug`.' +); + +/** + * Colors. + */ + +exports.colors = [6, 2, 3, 4, 5, 1]; + +try { + // Optional dependency (as in, doesn't need to be installed, NOT like optionalDependencies in package.json) + // eslint-disable-next-line import/no-extraneous-dependencies + const supportsColor = __nccwpck_require__(21450); + + if (supportsColor && (supportsColor.stderr || supportsColor).level >= 2) { + exports.colors = [ + 20, + 21, + 26, + 27, + 32, + 33, + 38, + 39, + 40, + 41, + 42, + 43, + 44, + 45, + 56, + 57, + 62, + 63, + 68, + 69, + 74, + 75, + 76, + 77, + 78, + 79, + 80, + 81, + 92, + 93, + 98, + 99, + 112, + 113, + 128, + 129, + 134, + 135, + 148, + 149, + 160, + 161, + 162, + 163, + 164, + 165, + 166, + 167, + 168, + 169, + 170, + 171, + 172, + 173, + 178, + 179, + 184, + 185, + 196, + 197, + 198, + 199, + 200, + 201, + 202, + 203, + 204, + 205, + 206, + 207, + 208, + 209, + 214, + 215, + 220, + 221 + ]; + } +} catch (error) { + // Swallow - we only care if `supports-color` is available; it doesn't have to be. +} + +/** + * Build up the default `inspectOpts` object from the environment variables. + * + * $ DEBUG_COLORS=no DEBUG_DEPTH=10 DEBUG_SHOW_HIDDEN=enabled node script.js + */ + +exports.inspectOpts = Object.keys(process.env).filter(key => { + return /^debug_/i.test(key); +}).reduce((obj, key) => { + // Camel-case + const prop = key + .substring(6) + .toLowerCase() + .replace(/_([a-z])/g, (_, k) => { + return k.toUpperCase(); + }); + + // Coerce string value into JS value + let val = process.env[key]; + if (/^(yes|on|true|enabled)$/i.test(val)) { + val = true; + } else if (/^(no|off|false|disabled)$/i.test(val)) { + val = false; + } else if (val === 'null') { + val = null; + } else { + val = Number(val); + } + + obj[prop] = val; + return obj; +}, {}); + +/** + * Is stdout a TTY? Colored output is enabled when `true`. + */ + +function useColors() { + return 'colors' in exports.inspectOpts ? + Boolean(exports.inspectOpts.colors) : + tty.isatty(process.stderr.fd); +} + +/** + * Adds ANSI color escape codes if enabled. + * + * @api public + */ + +function formatArgs(args) { + const {namespace: name, useColors} = this; + + if (useColors) { + const c = this.color; + const colorCode = '\u001B[3' + (c < 8 ? c : '8;5;' + c); + const prefix = ` ${colorCode};1m${name} \u001B[0m`; + + args[0] = prefix + args[0].split('\n').join('\n' + prefix); + args.push(colorCode + 'm+' + module.exports.humanize(this.diff) + '\u001B[0m'); + } else { + args[0] = getDate() + name + ' ' + args[0]; + } +} + +function getDate() { + if (exports.inspectOpts.hideDate) { + return ''; + } + return new Date().toISOString() + ' '; +} + +/** + * Invokes `util.formatWithOptions()` with the specified arguments and writes to stderr. + */ + +function log(...args) { + return process.stderr.write(util.formatWithOptions(exports.inspectOpts, ...args) + '\n'); +} + +/** + * Save `namespaces`. + * + * @param {String} namespaces + * @api private + */ +function save(namespaces) { + if (namespaces) { + process.env.DEBUG = namespaces; + } else { + // If you set a process.env field to null or undefined, it gets cast to the + // string 'null' or 'undefined'. Just delete instead. + delete process.env.DEBUG; + } +} + +/** + * Load `namespaces`. + * + * @return {String} returns the previously persisted debug modes + * @api private + */ + +function load() { + return process.env.DEBUG; +} + +/** + * Init logic for `debug` instances. + * + * Create a new `inspectOpts` object in case `useColors` is set + * differently for a particular `debug` instance. + */ + +function init(debug) { + debug.inspectOpts = {}; + + const keys = Object.keys(exports.inspectOpts); + for (let i = 0; i < keys.length; i++) { + debug.inspectOpts[keys[i]] = exports.inspectOpts[keys[i]]; + } +} + +module.exports = __nccwpck_require__(40897)(exports); + +const {formatters} = module.exports; + +/** + * Map %o to `util.inspect()`, all on a single line. + */ + +formatters.o = function (v) { + this.inspectOpts.colors = this.useColors; + return util.inspect(v, this.inspectOpts) + .split('\n') + .map(str => str.trim()) + .join(' '); +}; + +/** + * Map %O to `util.inspect()`, allowing multiple lines if needed. + */ + +formatters.O = function (v) { + this.inspectOpts.colors = this.useColors; + return util.inspect(v, this.inspectOpts); +}; + + +/***/ }), + +/***/ 48905: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +var isObject = __nccwpck_require__(36403); + +module.exports = function extend(o/*, objects*/) { + if (!isObject(o)) { o = {}; } + + var len = arguments.length; + for (var i = 1; i < len; i++) { + var obj = arguments[i]; + + if (isObject(obj)) { + assign(o, obj); + } + } + return o; +}; + +function assign(a, b) { + for (var key in b) { + if (hasOwn(b, key)) { + a[key] = b[key]; + } + } +} + +/** + * Returns true if the given `key` is an own property of `obj`. + */ + +function hasOwn(obj, key) { + return Object.prototype.hasOwnProperty.call(obj, key); +} + + +/***/ }), + +/***/ 23860: +/***/ ((module) => { + +"use strict"; + + +var hasOwn = Object.prototype.hasOwnProperty; +var toStr = Object.prototype.toString; +var defineProperty = Object.defineProperty; +var gOPD = Object.getOwnPropertyDescriptor; + +var isArray = function isArray(arr) { + if (typeof Array.isArray === 'function') { + return Array.isArray(arr); + } + + return toStr.call(arr) === '[object Array]'; +}; + +var isPlainObject = function isPlainObject(obj) { + if (!obj || toStr.call(obj) !== '[object Object]') { + return false; + } + + var hasOwnConstructor = hasOwn.call(obj, 'constructor'); + var hasIsPrototypeOf = obj.constructor && obj.constructor.prototype && hasOwn.call(obj.constructor.prototype, 'isPrototypeOf'); + // Not own constructor property must be Object + if (obj.constructor && !hasOwnConstructor && !hasIsPrototypeOf) { + return false; + } + + // Own properties are enumerated firstly, so to speed up, + // if last one is own, then all properties are own. + var key; + for (key in obj) { /**/ } + + return typeof key === 'undefined' || hasOwn.call(obj, key); +}; + +// If name is '__proto__', and Object.defineProperty is available, define __proto__ as an own property on target +var setProperty = function setProperty(target, options) { + if (defineProperty && options.name === '__proto__') { + defineProperty(target, options.name, { + enumerable: true, + configurable: true, + value: options.newValue, + writable: true + }); + } else { + target[options.name] = options.newValue; + } +}; + +// Return undefined instead of __proto__ if '__proto__' is not an own property +var getProperty = function getProperty(obj, name) { + if (name === '__proto__') { + if (!hasOwn.call(obj, name)) { + return void 0; + } else if (gOPD) { + // In early versions of node, obj['__proto__'] is buggy when obj has + // __proto__ as an own property. Object.getOwnPropertyDescriptor() works. + return gOPD(obj, name).value; + } + } + + return obj[name]; +}; + +module.exports = function extend() { + var options, name, src, copy, copyIsArray, clone; + var target = arguments[0]; + var i = 1; + var length = arguments.length; + var deep = false; + + // Handle a deep copy situation + if (typeof target === 'boolean') { + deep = target; + target = arguments[1] || {}; + // skip the boolean and the target + i = 2; + } + if (target == null || (typeof target !== 'object' && typeof target !== 'function')) { + target = {}; + } + + for (; i < length; ++i) { + options = arguments[i]; + // Only deal with non-null/undefined values + if (options != null) { + // Extend the base object + for (name in options) { + src = getProperty(target, name); + copy = getProperty(options, name); + + // Prevent never-ending loop + if (target !== copy) { + // Recurse if we're merging plain objects or arrays + if (deep && copy && (isPlainObject(copy) || (copyIsArray = isArray(copy)))) { + if (copyIsArray) { + copyIsArray = false; + clone = src && isArray(src) ? src : []; + } else { + clone = src && isPlainObject(src) ? src : {}; + } + + // Never move original objects, clone them + setProperty(target, { name: name, newValue: extend(deep, clone, copy) }); + + // Don't bring in undefined values + } else if (typeof copy !== 'undefined') { + setProperty(target, { name: name, newValue: copy }); + } + } + } + } + } + + // Return the modified object + return target; +}; + + +/***/ }), + +/***/ 99808: +/***/ ((module) => { + +"use strict"; + + +/* eslint no-invalid-this: 1 */ + +var ERROR_MESSAGE = 'Function.prototype.bind called on incompatible '; +var toStr = Object.prototype.toString; +var max = Math.max; +var funcType = '[object Function]'; + +var concatty = function concatty(a, b) { + var arr = []; + + for (var i = 0; i < a.length; i += 1) { + arr[i] = a[i]; + } + for (var j = 0; j < b.length; j += 1) { + arr[j + a.length] = b[j]; + } + + return arr; +}; + +var slicy = function slicy(arrLike, offset) { + var arr = []; + for (var i = offset || 0, j = 0; i < arrLike.length; i += 1, j += 1) { + arr[j] = arrLike[i]; + } + return arr; +}; + +var joiny = function (arr, joiner) { + var str = ''; + for (var i = 0; i < arr.length; i += 1) { + str += arr[i]; + if (i + 1 < arr.length) { + str += joiner; + } + } + return str; +}; + +module.exports = function bind(that) { + var target = this; + if (typeof target !== 'function' || toStr.apply(target) !== funcType) { + throw new TypeError(ERROR_MESSAGE + target); + } + var args = slicy(arguments, 1); + + var bound; + var binder = function () { + if (this instanceof bound) { + var result = target.apply( + this, + concatty(args, arguments) + ); + if (Object(result) === result) { + return result; + } + return this; + } + return target.apply( + that, + concatty(args, arguments) + ); + + }; + + var boundLength = max(0, target.length - args.length); + var boundArgs = []; + for (var i = 0; i < boundLength; i++) { + boundArgs[i] = '$' + i; + } + + bound = Function('binder', 'return function (' + joiny(boundArgs, ',') + '){ return binder.apply(this,arguments); }')(binder); + + if (target.prototype) { + var Empty = function Empty() {}; + Empty.prototype = target.prototype; + bound.prototype = new Empty(); + Empty.prototype = null; + } + + return bound; +}; + + +/***/ }), + +/***/ 37564: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +var implementation = __nccwpck_require__(99808); + +module.exports = Function.prototype.bind || implementation; + + +/***/ }), + +/***/ 9621: +/***/ ((module) => { + +"use strict"; + + +// These use the global symbol registry so that multiple copies of this +// library can work together in case they are not deduped. +const GENSYNC_START = Symbol.for("gensync:v1:start"); +const GENSYNC_SUSPEND = Symbol.for("gensync:v1:suspend"); + +const GENSYNC_EXPECTED_START = "GENSYNC_EXPECTED_START"; +const GENSYNC_EXPECTED_SUSPEND = "GENSYNC_EXPECTED_SUSPEND"; +const GENSYNC_OPTIONS_ERROR = "GENSYNC_OPTIONS_ERROR"; +const GENSYNC_RACE_NONEMPTY = "GENSYNC_RACE_NONEMPTY"; +const GENSYNC_ERRBACK_NO_CALLBACK = "GENSYNC_ERRBACK_NO_CALLBACK"; + +module.exports = Object.assign( + function gensync(optsOrFn) { + let genFn = optsOrFn; + if (typeof optsOrFn !== "function") { + genFn = newGenerator(optsOrFn); + } else { + genFn = wrapGenerator(optsOrFn); + } + + return Object.assign(genFn, makeFunctionAPI(genFn)); + }, + { + all: buildOperation({ + name: "all", + arity: 1, + sync: function(args) { + const items = Array.from(args[0]); + return items.map(item => evaluateSync(item)); + }, + async: function(args, resolve, reject) { + const items = Array.from(args[0]); + + if (items.length === 0) { + Promise.resolve().then(() => resolve([])); + return; + } + + let count = 0; + const results = items.map(() => undefined); + items.forEach((item, i) => { + evaluateAsync( + item, + val => { + results[i] = val; + count += 1; + + if (count === results.length) resolve(results); + }, + reject + ); + }); + }, + }), + race: buildOperation({ + name: "race", + arity: 1, + sync: function(args) { + const items = Array.from(args[0]); + if (items.length === 0) { + throw makeError("Must race at least 1 item", GENSYNC_RACE_NONEMPTY); + } + + return evaluateSync(items[0]); + }, + async: function(args, resolve, reject) { + const items = Array.from(args[0]); + if (items.length === 0) { + throw makeError("Must race at least 1 item", GENSYNC_RACE_NONEMPTY); + } + + for (const item of items) { + evaluateAsync(item, resolve, reject); + } + }, + }), + } +); + +/** + * Given a generator function, return the standard API object that executes + * the generator and calls the callbacks. + */ +function makeFunctionAPI(genFn) { + const fns = { + sync: function(...args) { + return evaluateSync(genFn.apply(this, args)); + }, + async: function(...args) { + return new Promise((resolve, reject) => { + evaluateAsync(genFn.apply(this, args), resolve, reject); + }); + }, + errback: function(...args) { + const cb = args.pop(); + if (typeof cb !== "function") { + throw makeError( + "Asynchronous function called without callback", + GENSYNC_ERRBACK_NO_CALLBACK + ); + } + + let gen; + try { + gen = genFn.apply(this, args); + } catch (err) { + cb(err); + return; + } + + evaluateAsync(gen, val => cb(undefined, val), err => cb(err)); + }, + }; + return fns; +} + +function assertTypeof(type, name, value, allowUndefined) { + if ( + typeof value === type || + (allowUndefined && typeof value === "undefined") + ) { + return; + } + + let msg; + if (allowUndefined) { + msg = `Expected opts.${name} to be either a ${type}, or undefined.`; + } else { + msg = `Expected opts.${name} to be a ${type}.`; + } + + throw makeError(msg, GENSYNC_OPTIONS_ERROR); +} +function makeError(msg, code) { + return Object.assign(new Error(msg), { code }); +} + +/** + * Given an options object, return a new generator that dispatches the + * correct handler based on sync or async execution. + */ +function newGenerator({ name, arity, sync, async, errback }) { + assertTypeof("string", "name", name, true /* allowUndefined */); + assertTypeof("number", "arity", arity, true /* allowUndefined */); + assertTypeof("function", "sync", sync); + assertTypeof("function", "async", async, true /* allowUndefined */); + assertTypeof("function", "errback", errback, true /* allowUndefined */); + if (async && errback) { + throw makeError( + "Expected one of either opts.async or opts.errback, but got _both_.", + GENSYNC_OPTIONS_ERROR + ); + } + + if (typeof name !== "string") { + let fnName; + if (errback && errback.name && errback.name !== "errback") { + fnName = errback.name; + } + if (async && async.name && async.name !== "async") { + fnName = async.name.replace(/Async$/, ""); + } + if (sync && sync.name && sync.name !== "sync") { + fnName = sync.name.replace(/Sync$/, ""); + } + + if (typeof fnName === "string") { + name = fnName; + } + } + + if (typeof arity !== "number") { + arity = sync.length; + } + + return buildOperation({ + name, + arity, + sync: function(args) { + return sync.apply(this, args); + }, + async: function(args, resolve, reject) { + if (async) { + async.apply(this, args).then(resolve, reject); + } else if (errback) { + errback.call(this, ...args, (err, value) => { + if (err == null) resolve(value); + else reject(err); + }); + } else { + resolve(sync.apply(this, args)); + } + }, + }); +} + +function wrapGenerator(genFn) { + return setFunctionMetadata(genFn.name, genFn.length, function(...args) { + return genFn.apply(this, args); + }); +} + +function buildOperation({ name, arity, sync, async }) { + return setFunctionMetadata(name, arity, function*(...args) { + const resume = yield GENSYNC_START; + if (!resume) { + // Break the tail call to avoid a bug in V8 v6.X with --harmony enabled. + const res = sync.call(this, args); + return res; + } + + let result; + try { + async.call( + this, + args, + value => { + if (result) return; + + result = { value }; + resume(); + }, + err => { + if (result) return; + + result = { err }; + resume(); + } + ); + } catch (err) { + result = { err }; + resume(); + } + + // Suspend until the callbacks run. Will resume synchronously if the + // callback was already called. + yield GENSYNC_SUSPEND; + + if (result.hasOwnProperty("err")) { + throw result.err; + } + + return result.value; + }); +} + +function evaluateSync(gen) { + let value; + while (!({ value } = gen.next()).done) { + assertStart(value, gen); + } + return value; +} + +function evaluateAsync(gen, resolve, reject) { + (function step() { + try { + let value; + while (!({ value } = gen.next()).done) { + assertStart(value, gen); + + // If this throws, it is considered to have broken the contract + // established for async handlers. If these handlers are called + // synchronously, it is also considered bad behavior. + let sync = true; + let didSyncResume = false; + const out = gen.next(() => { + if (sync) { + didSyncResume = true; + } else { + step(); + } + }); + sync = false; + + assertSuspend(out, gen); + + if (!didSyncResume) { + // Callback wasn't called synchronously, so break out of the loop + // and let it call 'step' later. + return; + } + } + + return resolve(value); + } catch (err) { + return reject(err); + } + })(); +} + +function assertStart(value, gen) { + if (value === GENSYNC_START) return; + + throwError( + gen, + makeError( + `Got unexpected yielded value in gensync generator: ${JSON.stringify( + value + )}. Did you perhaps mean to use 'yield*' instead of 'yield'?`, + GENSYNC_EXPECTED_START + ) + ); +} +function assertSuspend({ value, done }, gen) { + if (!done && value === GENSYNC_SUSPEND) return; + + throwError( + gen, + makeError( + done + ? "Unexpected generator completion. If you get this, it is probably a gensync bug." + : `Expected GENSYNC_SUSPEND, got ${JSON.stringify( + value + )}. If you get this, it is probably a gensync bug.`, + GENSYNC_EXPECTED_SUSPEND + ) + ); +} + +function throwError(gen, err) { + // Call `.throw` so that users can step in a debugger to easily see which + // 'yield' passed an unexpected value. If the `.throw` call didn't throw + // back to the generator, we explicitly do it to stop the error + // from being swallowed by user code try/catches. + if (gen.throw) gen.throw(err); + throw err; +} + +function isIterable(value) { + return ( + !!value && + (typeof value === "object" || typeof value === "function") && + !value[Symbol.iterator] + ); +} + +function setFunctionMetadata(name, arity, fn) { + if (typeof name === "string") { + // This should always work on the supported Node versions, but for the + // sake of users that are compiling to older versions, we check for + // configurability so we don't throw. + const nameDesc = Object.getOwnPropertyDescriptor(fn, "name"); + if (!nameDesc || nameDesc.configurable) { + Object.defineProperty( + fn, + "name", + Object.assign(nameDesc || {}, { + configurable: true, + value: name, + }) + ); + } + } + + if (typeof arity === "number") { + const lengthDesc = Object.getOwnPropertyDescriptor(fn, "length"); + if (!lengthDesc || lengthDesc.configurable) { + Object.defineProperty( + fn, + "length", + Object.assign(lengthDesc || {}, { + configurable: true, + value: arity, + }) + ); + } + } + + return fn; +} + + +/***/ }), + +/***/ 21384: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + +module.exports = __nccwpck_require__(14652); + + +/***/ }), + +/***/ 73964: +/***/ ((module) => { + +"use strict"; + + +module.exports = clone + +var getPrototypeOf = Object.getPrototypeOf || function (obj) { + return obj.__proto__ +} + +function clone (obj) { + if (obj === null || typeof obj !== 'object') + return obj + + if (obj instanceof Object) + var copy = { __proto__: getPrototypeOf(obj) } + else + var copy = Object.create(null) + + Object.getOwnPropertyNames(obj).forEach(function (key) { + Object.defineProperty(copy, key, Object.getOwnPropertyDescriptor(obj, key)) + }) + + return copy +} + + +/***/ }), + +/***/ 35744: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +var fs = __nccwpck_require__(79896) +var polyfills = __nccwpck_require__(83501) +var legacy = __nccwpck_require__(12270) +var clone = __nccwpck_require__(73964) + +var util = __nccwpck_require__(39023) + +/* istanbul ignore next - node 0.x polyfill */ +var gracefulQueue +var previousSymbol + +/* istanbul ignore else - node 0.x polyfill */ +if (typeof Symbol === 'function' && typeof Symbol.for === 'function') { + gracefulQueue = Symbol.for('graceful-fs.queue') + // This is used in testing by future versions + previousSymbol = Symbol.for('graceful-fs.previous') +} else { + gracefulQueue = '___graceful-fs.queue' + previousSymbol = '___graceful-fs.previous' +} + +function noop () {} + +function publishQueue(context, queue) { + Object.defineProperty(context, gracefulQueue, { + get: function() { + return queue + } + }) +} + +var debug = noop +if (util.debuglog) + debug = util.debuglog('gfs4') +else if (/\bgfs4\b/i.test(process.env.NODE_DEBUG || '')) + debug = function() { + var m = util.format.apply(util, arguments) + m = 'GFS4: ' + m.split(/\n/).join('\nGFS4: ') + console.error(m) + } + +// Once time initialization +if (!fs[gracefulQueue]) { + // This queue can be shared by multiple loaded instances + var queue = global[gracefulQueue] || [] + publishQueue(fs, queue) + + // Patch fs.close/closeSync to shared queue version, because we need + // to retry() whenever a close happens *anywhere* in the program. + // This is essential when multiple graceful-fs instances are + // in play at the same time. + fs.close = (function (fs$close) { + function close (fd, cb) { + return fs$close.call(fs, fd, function (err) { + // This function uses the graceful-fs shared queue + if (!err) { + resetQueue() + } + + if (typeof cb === 'function') + cb.apply(this, arguments) + }) + } + + Object.defineProperty(close, previousSymbol, { + value: fs$close + }) + return close + })(fs.close) + + fs.closeSync = (function (fs$closeSync) { + function closeSync (fd) { + // This function uses the graceful-fs shared queue + fs$closeSync.apply(fs, arguments) + resetQueue() + } + + Object.defineProperty(closeSync, previousSymbol, { + value: fs$closeSync + }) + return closeSync + })(fs.closeSync) + + if (/\bgfs4\b/i.test(process.env.NODE_DEBUG || '')) { + process.on('exit', function() { + debug(fs[gracefulQueue]) + __nccwpck_require__(42613).equal(fs[gracefulQueue].length, 0) + }) + } +} + +if (!global[gracefulQueue]) { + publishQueue(global, fs[gracefulQueue]); +} + +module.exports = patch(clone(fs)) +if (process.env.TEST_GRACEFUL_FS_GLOBAL_PATCH && !fs.__patched) { + module.exports = patch(fs) + fs.__patched = true; +} + +function patch (fs) { + // Everything that references the open() function needs to be in here + polyfills(fs) + fs.gracefulify = patch + + fs.createReadStream = createReadStream + fs.createWriteStream = createWriteStream + var fs$readFile = fs.readFile + fs.readFile = readFile + function readFile (path, options, cb) { + if (typeof options === 'function') + cb = options, options = null + + return go$readFile(path, options, cb) + + function go$readFile (path, options, cb, startTime) { + return fs$readFile(path, options, function (err) { + if (err && (err.code === 'EMFILE' || err.code === 'ENFILE')) + enqueue([go$readFile, [path, options, cb], err, startTime || Date.now(), Date.now()]) + else { + if (typeof cb === 'function') + cb.apply(this, arguments) + } + }) + } + } + + var fs$writeFile = fs.writeFile + fs.writeFile = writeFile + function writeFile (path, data, options, cb) { + if (typeof options === 'function') + cb = options, options = null + + return go$writeFile(path, data, options, cb) + + function go$writeFile (path, data, options, cb, startTime) { + return fs$writeFile(path, data, options, function (err) { + if (err && (err.code === 'EMFILE' || err.code === 'ENFILE')) + enqueue([go$writeFile, [path, data, options, cb], err, startTime || Date.now(), Date.now()]) + else { + if (typeof cb === 'function') + cb.apply(this, arguments) + } + }) + } + } + + var fs$appendFile = fs.appendFile + if (fs$appendFile) + fs.appendFile = appendFile + function appendFile (path, data, options, cb) { + if (typeof options === 'function') + cb = options, options = null + + return go$appendFile(path, data, options, cb) + + function go$appendFile (path, data, options, cb, startTime) { + return fs$appendFile(path, data, options, function (err) { + if (err && (err.code === 'EMFILE' || err.code === 'ENFILE')) + enqueue([go$appendFile, [path, data, options, cb], err, startTime || Date.now(), Date.now()]) + else { + if (typeof cb === 'function') + cb.apply(this, arguments) + } + }) + } + } + + var fs$copyFile = fs.copyFile + if (fs$copyFile) + fs.copyFile = copyFile + function copyFile (src, dest, flags, cb) { + if (typeof flags === 'function') { + cb = flags + flags = 0 + } + return go$copyFile(src, dest, flags, cb) + + function go$copyFile (src, dest, flags, cb, startTime) { + return fs$copyFile(src, dest, flags, function (err) { + if (err && (err.code === 'EMFILE' || err.code === 'ENFILE')) + enqueue([go$copyFile, [src, dest, flags, cb], err, startTime || Date.now(), Date.now()]) + else { + if (typeof cb === 'function') + cb.apply(this, arguments) + } + }) + } + } + + var fs$readdir = fs.readdir + fs.readdir = readdir + var noReaddirOptionVersions = /^v[0-5]\./ + function readdir (path, options, cb) { + if (typeof options === 'function') + cb = options, options = null + + var go$readdir = noReaddirOptionVersions.test(process.version) + ? function go$readdir (path, options, cb, startTime) { + return fs$readdir(path, fs$readdirCallback( + path, options, cb, startTime + )) + } + : function go$readdir (path, options, cb, startTime) { + return fs$readdir(path, options, fs$readdirCallback( + path, options, cb, startTime + )) + } + + return go$readdir(path, options, cb) + + function fs$readdirCallback (path, options, cb, startTime) { + return function (err, files) { + if (err && (err.code === 'EMFILE' || err.code === 'ENFILE')) + enqueue([ + go$readdir, + [path, options, cb], + err, + startTime || Date.now(), + Date.now() + ]) + else { + if (files && files.sort) + files.sort() + + if (typeof cb === 'function') + cb.call(this, err, files) + } + } + } + } + + if (process.version.substr(0, 4) === 'v0.8') { + var legStreams = legacy(fs) + ReadStream = legStreams.ReadStream + WriteStream = legStreams.WriteStream + } + + var fs$ReadStream = fs.ReadStream + if (fs$ReadStream) { + ReadStream.prototype = Object.create(fs$ReadStream.prototype) + ReadStream.prototype.open = ReadStream$open + } + + var fs$WriteStream = fs.WriteStream + if (fs$WriteStream) { + WriteStream.prototype = Object.create(fs$WriteStream.prototype) + WriteStream.prototype.open = WriteStream$open + } + + Object.defineProperty(fs, 'ReadStream', { + get: function () { + return ReadStream + }, + set: function (val) { + ReadStream = val + }, + enumerable: true, + configurable: true + }) + Object.defineProperty(fs, 'WriteStream', { + get: function () { + return WriteStream + }, + set: function (val) { + WriteStream = val + }, + enumerable: true, + configurable: true + }) + + // legacy names + var FileReadStream = ReadStream + Object.defineProperty(fs, 'FileReadStream', { + get: function () { + return FileReadStream + }, + set: function (val) { + FileReadStream = val + }, + enumerable: true, + configurable: true + }) + var FileWriteStream = WriteStream + Object.defineProperty(fs, 'FileWriteStream', { + get: function () { + return FileWriteStream + }, + set: function (val) { + FileWriteStream = val + }, + enumerable: true, + configurable: true + }) + + function ReadStream (path, options) { + if (this instanceof ReadStream) + return fs$ReadStream.apply(this, arguments), this + else + return ReadStream.apply(Object.create(ReadStream.prototype), arguments) + } + + function ReadStream$open () { + var that = this + open(that.path, that.flags, that.mode, function (err, fd) { + if (err) { + if (that.autoClose) + that.destroy() + + that.emit('error', err) + } else { + that.fd = fd + that.emit('open', fd) + that.read() + } + }) + } + + function WriteStream (path, options) { + if (this instanceof WriteStream) + return fs$WriteStream.apply(this, arguments), this + else + return WriteStream.apply(Object.create(WriteStream.prototype), arguments) + } + + function WriteStream$open () { + var that = this + open(that.path, that.flags, that.mode, function (err, fd) { + if (err) { + that.destroy() + that.emit('error', err) + } else { + that.fd = fd + that.emit('open', fd) + } + }) + } + + function createReadStream (path, options) { + return new fs.ReadStream(path, options) + } + + function createWriteStream (path, options) { + return new fs.WriteStream(path, options) + } + + var fs$open = fs.open + fs.open = open + function open (path, flags, mode, cb) { + if (typeof mode === 'function') + cb = mode, mode = null + + return go$open(path, flags, mode, cb) + + function go$open (path, flags, mode, cb, startTime) { + return fs$open(path, flags, mode, function (err, fd) { + if (err && (err.code === 'EMFILE' || err.code === 'ENFILE')) + enqueue([go$open, [path, flags, mode, cb], err, startTime || Date.now(), Date.now()]) + else { + if (typeof cb === 'function') + cb.apply(this, arguments) + } + }) + } + } + + return fs +} + +function enqueue (elem) { + debug('ENQUEUE', elem[0].name, elem[1]) + fs[gracefulQueue].push(elem) + retry() +} + +// keep track of the timeout between retry() calls +var retryTimer + +// reset the startTime and lastTime to now +// this resets the start of the 60 second overall timeout as well as the +// delay between attempts so that we'll retry these jobs sooner +function resetQueue () { + var now = Date.now() + for (var i = 0; i < fs[gracefulQueue].length; ++i) { + // entries that are only a length of 2 are from an older version, don't + // bother modifying those since they'll be retried anyway. + if (fs[gracefulQueue][i].length > 2) { + fs[gracefulQueue][i][3] = now // startTime + fs[gracefulQueue][i][4] = now // lastTime + } + } + // call retry to make sure we're actively processing the queue + retry() +} + +function retry () { + // clear the timer and remove it to help prevent unintended concurrency + clearTimeout(retryTimer) + retryTimer = undefined + + if (fs[gracefulQueue].length === 0) + return + + var elem = fs[gracefulQueue].shift() + var fn = elem[0] + var args = elem[1] + // these items may be unset if they were added by an older graceful-fs + var err = elem[2] + var startTime = elem[3] + var lastTime = elem[4] + + // if we don't have a startTime we have no way of knowing if we've waited + // long enough, so go ahead and retry this item now + if (startTime === undefined) { + debug('RETRY', fn.name, args) + fn.apply(null, args) + } else if (Date.now() - startTime >= 60000) { + // it's been more than 60 seconds total, bail now + debug('TIMEOUT', fn.name, args) + var cb = args.pop() + if (typeof cb === 'function') + cb.call(null, err) + } else { + // the amount of time between the last attempt and right now + var sinceAttempt = Date.now() - lastTime + // the amount of time between when we first tried, and when we last tried + // rounded up to at least 1 + var sinceStart = Math.max(lastTime - startTime, 1) + // backoff. wait longer than the total time we've been retrying, but only + // up to a maximum of 100ms + var desiredDelay = Math.min(sinceStart * 1.2, 100) + // it's been long enough since the last retry, do it again + if (sinceAttempt >= desiredDelay) { + debug('RETRY', fn.name, args) + fn.apply(null, args.concat([startTime])) + } else { + // if we can't do this job yet, push it to the end of the queue + // and let the next iteration check again + fs[gracefulQueue].push(elem) + } + } + + // schedule our next run if one isn't already scheduled + if (retryTimer === undefined) { + retryTimer = setTimeout(retry, 0) + } +} + + +/***/ }), + +/***/ 12270: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +var Stream = (__nccwpck_require__(2203).Stream) + +module.exports = legacy + +function legacy (fs) { + return { + ReadStream: ReadStream, + WriteStream: WriteStream + } + + function ReadStream (path, options) { + if (!(this instanceof ReadStream)) return new ReadStream(path, options); + + Stream.call(this); + + var self = this; + + this.path = path; + this.fd = null; + this.readable = true; + this.paused = false; + + this.flags = 'r'; + this.mode = 438; /*=0666*/ + this.bufferSize = 64 * 1024; + + options = options || {}; + + // Mixin options into this + var keys = Object.keys(options); + for (var index = 0, length = keys.length; index < length; index++) { + var key = keys[index]; + this[key] = options[key]; + } + + if (this.encoding) this.setEncoding(this.encoding); + + if (this.start !== undefined) { + if ('number' !== typeof this.start) { + throw TypeError('start must be a Number'); + } + if (this.end === undefined) { + this.end = Infinity; + } else if ('number' !== typeof this.end) { + throw TypeError('end must be a Number'); + } + + if (this.start > this.end) { + throw new Error('start must be <= end'); + } + + this.pos = this.start; + } + + if (this.fd !== null) { + process.nextTick(function() { + self._read(); + }); + return; + } + + fs.open(this.path, this.flags, this.mode, function (err, fd) { + if (err) { + self.emit('error', err); + self.readable = false; + return; + } + + self.fd = fd; + self.emit('open', fd); + self._read(); + }) + } + + function WriteStream (path, options) { + if (!(this instanceof WriteStream)) return new WriteStream(path, options); + + Stream.call(this); + + this.path = path; + this.fd = null; + this.writable = true; + + this.flags = 'w'; + this.encoding = 'binary'; + this.mode = 438; /*=0666*/ + this.bytesWritten = 0; + + options = options || {}; + + // Mixin options into this + var keys = Object.keys(options); + for (var index = 0, length = keys.length; index < length; index++) { + var key = keys[index]; + this[key] = options[key]; + } + + if (this.start !== undefined) { + if ('number' !== typeof this.start) { + throw TypeError('start must be a Number'); + } + if (this.start < 0) { + throw new Error('start must be >= zero'); + } + + this.pos = this.start; + } + + this.busy = false; + this._queue = []; + + if (this.fd === null) { + this._open = fs.open; + this._queue.push([this._open, this.path, this.flags, this.mode, undefined]); + this.flush(); + } + } +} + + +/***/ }), + +/***/ 83501: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +var constants = __nccwpck_require__(49140) + +var origCwd = process.cwd +var cwd = null + +var platform = process.env.GRACEFUL_FS_PLATFORM || process.platform + +process.cwd = function() { + if (!cwd) + cwd = origCwd.call(process) + return cwd +} +try { + process.cwd() +} catch (er) {} + +// This check is needed until node.js 12 is required +if (typeof process.chdir === 'function') { + var chdir = process.chdir + process.chdir = function (d) { + cwd = null + chdir.call(process, d) + } + if (Object.setPrototypeOf) Object.setPrototypeOf(process.chdir, chdir) +} + +module.exports = patch + +function patch (fs) { + // (re-)implement some things that are known busted or missing. + + // lchmod, broken prior to 0.6.2 + // back-port the fix here. + if (constants.hasOwnProperty('O_SYMLINK') && + process.version.match(/^v0\.6\.[0-2]|^v0\.5\./)) { + patchLchmod(fs) + } + + // lutimes implementation, or no-op + if (!fs.lutimes) { + patchLutimes(fs) + } + + // https://github.com/isaacs/node-graceful-fs/issues/4 + // Chown should not fail on einval or eperm if non-root. + // It should not fail on enosys ever, as this just indicates + // that a fs doesn't support the intended operation. + + fs.chown = chownFix(fs.chown) + fs.fchown = chownFix(fs.fchown) + fs.lchown = chownFix(fs.lchown) + + fs.chmod = chmodFix(fs.chmod) + fs.fchmod = chmodFix(fs.fchmod) + fs.lchmod = chmodFix(fs.lchmod) + + fs.chownSync = chownFixSync(fs.chownSync) + fs.fchownSync = chownFixSync(fs.fchownSync) + fs.lchownSync = chownFixSync(fs.lchownSync) + + fs.chmodSync = chmodFixSync(fs.chmodSync) + fs.fchmodSync = chmodFixSync(fs.fchmodSync) + fs.lchmodSync = chmodFixSync(fs.lchmodSync) + + fs.stat = statFix(fs.stat) + fs.fstat = statFix(fs.fstat) + fs.lstat = statFix(fs.lstat) + + fs.statSync = statFixSync(fs.statSync) + fs.fstatSync = statFixSync(fs.fstatSync) + fs.lstatSync = statFixSync(fs.lstatSync) + + // if lchmod/lchown do not exist, then make them no-ops + if (fs.chmod && !fs.lchmod) { + fs.lchmod = function (path, mode, cb) { + if (cb) process.nextTick(cb) + } + fs.lchmodSync = function () {} + } + if (fs.chown && !fs.lchown) { + fs.lchown = function (path, uid, gid, cb) { + if (cb) process.nextTick(cb) + } + fs.lchownSync = function () {} + } + + // on Windows, A/V software can lock the directory, causing this + // to fail with an EACCES or EPERM if the directory contains newly + // created files. Try again on failure, for up to 60 seconds. + + // Set the timeout this long because some Windows Anti-Virus, such as Parity + // bit9, may lock files for up to a minute, causing npm package install + // failures. Also, take care to yield the scheduler. Windows scheduling gives + // CPU to a busy looping process, which can cause the program causing the lock + // contention to be starved of CPU by node, so the contention doesn't resolve. + if (platform === "win32") { + fs.rename = typeof fs.rename !== 'function' ? fs.rename + : (function (fs$rename) { + function rename (from, to, cb) { + var start = Date.now() + var backoff = 0; + fs$rename(from, to, function CB (er) { + if (er + && (er.code === "EACCES" || er.code === "EPERM" || er.code === "EBUSY") + && Date.now() - start < 60000) { + setTimeout(function() { + fs.stat(to, function (stater, st) { + if (stater && stater.code === "ENOENT") + fs$rename(from, to, CB); + else + cb(er) + }) + }, backoff) + if (backoff < 100) + backoff += 10; + return; + } + if (cb) cb(er) + }) + } + if (Object.setPrototypeOf) Object.setPrototypeOf(rename, fs$rename) + return rename + })(fs.rename) + } + + // if read() returns EAGAIN, then just try it again. + fs.read = typeof fs.read !== 'function' ? fs.read + : (function (fs$read) { + function read (fd, buffer, offset, length, position, callback_) { + var callback + if (callback_ && typeof callback_ === 'function') { + var eagCounter = 0 + callback = function (er, _, __) { + if (er && er.code === 'EAGAIN' && eagCounter < 10) { + eagCounter ++ + return fs$read.call(fs, fd, buffer, offset, length, position, callback) + } + callback_.apply(this, arguments) + } + } + return fs$read.call(fs, fd, buffer, offset, length, position, callback) + } + + // This ensures `util.promisify` works as it does for native `fs.read`. + if (Object.setPrototypeOf) Object.setPrototypeOf(read, fs$read) + return read + })(fs.read) + + fs.readSync = typeof fs.readSync !== 'function' ? fs.readSync + : (function (fs$readSync) { return function (fd, buffer, offset, length, position) { + var eagCounter = 0 + while (true) { + try { + return fs$readSync.call(fs, fd, buffer, offset, length, position) + } catch (er) { + if (er.code === 'EAGAIN' && eagCounter < 10) { + eagCounter ++ + continue + } + throw er + } + } + }})(fs.readSync) + + function patchLchmod (fs) { + fs.lchmod = function (path, mode, callback) { + fs.open( path + , constants.O_WRONLY | constants.O_SYMLINK + , mode + , function (err, fd) { + if (err) { + if (callback) callback(err) + return + } + // prefer to return the chmod error, if one occurs, + // but still try to close, and report closing errors if they occur. + fs.fchmod(fd, mode, function (err) { + fs.close(fd, function(err2) { + if (callback) callback(err || err2) + }) + }) + }) + } + + fs.lchmodSync = function (path, mode) { + var fd = fs.openSync(path, constants.O_WRONLY | constants.O_SYMLINK, mode) + + // prefer to return the chmod error, if one occurs, + // but still try to close, and report closing errors if they occur. + var threw = true + var ret + try { + ret = fs.fchmodSync(fd, mode) + threw = false + } finally { + if (threw) { + try { + fs.closeSync(fd) + } catch (er) {} + } else { + fs.closeSync(fd) + } + } + return ret + } + } + + function patchLutimes (fs) { + if (constants.hasOwnProperty("O_SYMLINK") && fs.futimes) { + fs.lutimes = function (path, at, mt, cb) { + fs.open(path, constants.O_SYMLINK, function (er, fd) { + if (er) { + if (cb) cb(er) + return + } + fs.futimes(fd, at, mt, function (er) { + fs.close(fd, function (er2) { + if (cb) cb(er || er2) + }) + }) + }) + } + + fs.lutimesSync = function (path, at, mt) { + var fd = fs.openSync(path, constants.O_SYMLINK) + var ret + var threw = true + try { + ret = fs.futimesSync(fd, at, mt) + threw = false + } finally { + if (threw) { + try { + fs.closeSync(fd) + } catch (er) {} + } else { + fs.closeSync(fd) + } + } + return ret + } + + } else if (fs.futimes) { + fs.lutimes = function (_a, _b, _c, cb) { if (cb) process.nextTick(cb) } + fs.lutimesSync = function () {} + } + } + + function chmodFix (orig) { + if (!orig) return orig + return function (target, mode, cb) { + return orig.call(fs, target, mode, function (er) { + if (chownErOk(er)) er = null + if (cb) cb.apply(this, arguments) + }) + } + } + + function chmodFixSync (orig) { + if (!orig) return orig + return function (target, mode) { + try { + return orig.call(fs, target, mode) + } catch (er) { + if (!chownErOk(er)) throw er + } + } + } + + + function chownFix (orig) { + if (!orig) return orig + return function (target, uid, gid, cb) { + return orig.call(fs, target, uid, gid, function (er) { + if (chownErOk(er)) er = null + if (cb) cb.apply(this, arguments) + }) + } + } + + function chownFixSync (orig) { + if (!orig) return orig + return function (target, uid, gid) { + try { + return orig.call(fs, target, uid, gid) + } catch (er) { + if (!chownErOk(er)) throw er + } + } + } + + function statFix (orig) { + if (!orig) return orig + // Older versions of Node erroneously returned signed integers for + // uid + gid. + return function (target, options, cb) { + if (typeof options === 'function') { + cb = options + options = null + } + function callback (er, stats) { + if (stats) { + if (stats.uid < 0) stats.uid += 0x100000000 + if (stats.gid < 0) stats.gid += 0x100000000 + } + if (cb) cb.apply(this, arguments) + } + return options ? orig.call(fs, target, options, callback) + : orig.call(fs, target, callback) + } + } + + function statFixSync (orig) { + if (!orig) return orig + // Older versions of Node erroneously returned signed integers for + // uid + gid. + return function (target, options) { + var stats = options ? orig.call(fs, target, options) + : orig.call(fs, target) + if (stats) { + if (stats.uid < 0) stats.uid += 0x100000000 + if (stats.gid < 0) stats.gid += 0x100000000 + } + return stats; + } + } + + // ENOSYS means that the fs doesn't support the op. Just ignore + // that, because it doesn't matter. + // + // if there's no getuid, or if getuid() is something other + // than 0, and the error is EINVAL or EPERM, then just ignore + // it. + // + // This specific case is a silent failure in cp, install, tar, + // and most other unix tools that manage permissions. + // + // When running as root, or if other types of errors are + // encountered, then it's strict. + function chownErOk (er) { + if (!er) + return true + + if (er.code === "ENOSYS") + return true + + var nonroot = !process.getuid || process.getuid() !== 0 + if (nonroot) { + if (er.code === "EINVAL" || er.code === "EPERM") + return true + } + + return false + } +} + + +/***/ }), + +/***/ 19599: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const fs = __nccwpck_require__(79896); +const sections = __nccwpck_require__(89495); +const defaults = __nccwpck_require__(27545); +const stringify = __nccwpck_require__(34710); +const excerpt = __nccwpck_require__(77894); +const engines = __nccwpck_require__(74452); +const toFile = __nccwpck_require__(48073); +const parse = __nccwpck_require__(88120); +const utils = __nccwpck_require__(58698); + +/** + * Takes a string or object with `content` property, extracts + * and parses front-matter from the string, then returns an object + * with `data`, `content` and other [useful properties](#returned-object). + * + * ```js + * const matter = require('gray-matter'); + * console.log(matter('---\ntitle: Home\n---\nOther stuff')); + * //=> { data: { title: 'Home'}, content: 'Other stuff' } + * ``` + * @param {Object|String} `input` String, or object with `content` string + * @param {Object} `options` + * @return {Object} + * @api public + */ + +function matter(input, options) { + if (input === '') { + return { data: {}, content: input, excerpt: '', orig: input }; + } + + let file = toFile(input); + const cached = matter.cache[file.content]; + + if (!options) { + if (cached) { + file = Object.assign({}, cached); + file.orig = cached.orig; + return file; + } + + // only cache if there are no options passed. if we cache when options + // are passed, we would need to also cache options values, which would + // negate any performance benefits of caching + matter.cache[file.content] = file; + } + + return parseMatter(file, options); +} + +/** + * Parse front matter + */ + +function parseMatter(file, options) { + const opts = defaults(options); + const open = opts.delimiters[0]; + const close = '\n' + opts.delimiters[1]; + let str = file.content; + + if (opts.language) { + file.language = opts.language; + } + + // get the length of the opening delimiter + const openLen = open.length; + if (!utils.startsWith(str, open, openLen)) { + excerpt(file, opts); + return file; + } + + // if the next character after the opening delimiter is + // a character from the delimiter, then it's not a front- + // matter delimiter + if (str.charAt(openLen) === open.slice(-1)) { + return file; + } + + // strip the opening delimiter + str = str.slice(openLen); + const len = str.length; + + // use the language defined after first delimiter, if it exists + const language = matter.language(str, opts); + if (language.name) { + file.language = language.name; + str = str.slice(language.raw.length); + } + + // get the index of the closing delimiter + let closeIndex = str.indexOf(close); + if (closeIndex === -1) { + closeIndex = len; + } + + // get the raw front-matter block + file.matter = str.slice(0, closeIndex); + + const block = file.matter.replace(/^\s*#[^\n]+/gm, '').trim(); + if (block === '') { + file.isEmpty = true; + file.empty = file.content; + file.data = {}; + } else { + + // create file.data by parsing the raw file.matter block + file.data = parse(file.language, file.matter, opts); + } + + // update file.content + if (closeIndex === len) { + file.content = ''; + } else { + file.content = str.slice(closeIndex + close.length); + if (file.content[0] === '\r') { + file.content = file.content.slice(1); + } + if (file.content[0] === '\n') { + file.content = file.content.slice(1); + } + } + + excerpt(file, opts); + + if (opts.sections === true || typeof opts.section === 'function') { + sections(file, opts.section); + } + return file; +} + +/** + * Expose engines + */ + +matter.engines = engines; + +/** + * Stringify an object to YAML or the specified language, and + * append it to the given string. By default, only YAML and JSON + * can be stringified. See the [engines](#engines) section to learn + * how to stringify other languages. + * + * ```js + * console.log(matter.stringify('foo bar baz', {title: 'Home'})); + * // results in: + * // --- + * // title: Home + * // --- + * // foo bar baz + * ``` + * @param {String|Object} `file` The content string to append to stringified front-matter, or a file object with `file.content` string. + * @param {Object} `data` Front matter to stringify. + * @param {Object} `options` [Options](#options) to pass to gray-matter and [js-yaml]. + * @return {String} Returns a string created by wrapping stringified yaml with delimiters, and appending that to the given string. + * @api public + */ + +matter.stringify = function(file, data, options) { + if (typeof file === 'string') file = matter(file, options); + return stringify(file, data, options); +}; + +/** + * Synchronously read a file from the file system and parse + * front matter. Returns the same object as the [main function](#matter). + * + * ```js + * const file = matter.read('./content/blog-post.md'); + * ``` + * @param {String} `filepath` file path of the file to read. + * @param {Object} `options` [Options](#options) to pass to gray-matter. + * @return {Object} Returns [an object](#returned-object) with `data` and `content` + * @api public + */ + +matter.read = function(filepath, options) { + const str = fs.readFileSync(filepath, 'utf8'); + const file = matter(str, options); + file.path = filepath; + return file; +}; + +/** + * Returns true if the given `string` has front matter. + * @param {String} `string` + * @param {Object} `options` + * @return {Boolean} True if front matter exists. + * @api public + */ + +matter.test = function(str, options) { + return utils.startsWith(str, defaults(options).delimiters[0]); +}; + +/** + * Detect the language to use, if one is defined after the + * first front-matter delimiter. + * @param {String} `string` + * @param {Object} `options` + * @return {Object} Object with `raw` (actual language string), and `name`, the language with whitespace trimmed + */ + +matter.language = function(str, options) { + const opts = defaults(options); + const open = opts.delimiters[0]; + + if (matter.test(str)) { + str = str.slice(open.length); + } + + const language = str.slice(0, str.search(/\r?\n/)); + return { + raw: language, + name: language ? language.trim() : '' + }; +}; + +/** + * Expose `matter` + */ + +matter.cache = {}; +matter.clearCache = function() { + matter.cache = {}; +}; +module.exports = matter; + + +/***/ }), + +/***/ 27545: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const engines = __nccwpck_require__(74452); +const utils = __nccwpck_require__(58698); + +module.exports = function(options) { + const opts = Object.assign({}, options); + + // ensure that delimiters are an array + opts.delimiters = utils.arrayify(opts.delims || opts.delimiters || '---'); + if (opts.delimiters.length === 1) { + opts.delimiters.push(opts.delimiters[0]); + } + + opts.language = (opts.language || opts.lang || 'yaml').toLowerCase(); + opts.engines = Object.assign({}, engines, opts.parsers, opts.engines); + return opts; +}; + + +/***/ }), + +/***/ 5921: +/***/ ((module) => { + +"use strict"; + + +module.exports = function(name, options) { + let engine = options.engines[name] || options.engines[aliase(name)]; + if (typeof engine === 'undefined') { + throw new Error('gray-matter engine "' + name + '" is not registered'); + } + if (typeof engine === 'function') { + engine = { parse: engine }; + } + return engine; +}; + +function aliase(name) { + switch (name.toLowerCase()) { + case 'js': + case 'javascript': + return 'javascript'; + case 'coffee': + case 'coffeescript': + case 'cson': + return 'coffee'; + case 'yaml': + case 'yml': + return 'yaml'; + default: { + return name; + } + } +} + + +/***/ }), + +/***/ 74452: +/***/ ((module, exports, __nccwpck_require__) => { + +"use strict"; + + +const yaml = __nccwpck_require__(74281); + +/** + * Default engines + */ + +const engines = exports = module.exports; + +/** + * YAML + */ + +engines.yaml = { + parse: yaml.safeLoad.bind(yaml), + stringify: yaml.safeDump.bind(yaml) +}; + +/** + * JSON + */ + +engines.json = { + parse: JSON.parse.bind(JSON), + stringify: function(obj, options) { + const opts = Object.assign({replacer: null, space: 2}, options); + return JSON.stringify(obj, opts.replacer, opts.space); + } +}; + +/** + * JavaScript + */ + +engines.javascript = { + parse: function parse(str, options, wrap) { + /* eslint no-eval: 0 */ + try { + if (wrap !== false) { + str = '(function() {\nreturn ' + str.trim() + ';\n}());'; + } + return eval(str) || {}; + } catch (err) { + if (wrap !== false && /(unexpected|identifier)/i.test(err.message)) { + return parse(str, options, false); + } + throw new SyntaxError(err); + } + }, + stringify: function() { + throw new Error('stringifying JavaScript is not supported'); + } +}; + + +/***/ }), + +/***/ 77894: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const defaults = __nccwpck_require__(27545); + +module.exports = function(file, options) { + const opts = defaults(options); + + if (file.data == null) { + file.data = {}; + } + + if (typeof opts.excerpt === 'function') { + return opts.excerpt(file, opts); + } + + const sep = file.data.excerpt_separator || opts.excerpt_separator; + if (sep == null && (opts.excerpt === false || opts.excerpt == null)) { + return file; + } + + const delimiter = typeof opts.excerpt === 'string' + ? opts.excerpt + : (sep || opts.delimiters[0]); + + // if enabled, get the excerpt defined after front-matter + const idx = file.content.indexOf(delimiter); + if (idx !== -1) { + file.excerpt = file.content.slice(0, idx); + } + + return file; +}; + + +/***/ }), + +/***/ 88120: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const getEngine = __nccwpck_require__(5921); +const defaults = __nccwpck_require__(27545); + +module.exports = function(language, str, options) { + const opts = defaults(options); + const engine = getEngine(language, opts); + if (typeof engine.parse !== 'function') { + throw new TypeError('expected "' + language + '.parse" to be a function'); + } + return engine.parse(str, opts); +}; + + +/***/ }), + +/***/ 34710: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const typeOf = __nccwpck_require__(5140); +const getEngine = __nccwpck_require__(5921); +const defaults = __nccwpck_require__(27545); + +module.exports = function(file, data, options) { + if (data == null && options == null) { + switch (typeOf(file)) { + case 'object': + data = file.data; + options = {}; + break; + case 'string': + return file; + default: { + throw new TypeError('expected file to be a string or object'); + } + } + } + + const str = file.content; + const opts = defaults(options); + if (data == null) { + if (!opts.data) return file; + data = opts.data; + } + + const language = file.language || opts.language; + const engine = getEngine(language, opts); + if (typeof engine.stringify !== 'function') { + throw new TypeError('expected "' + language + '.stringify" to be a function'); + } + + data = Object.assign({}, file.data, data); + const open = opts.delimiters[0]; + const close = opts.delimiters[1]; + const matter = engine.stringify(data, options).trim(); + let buf = ''; + + if (matter !== '{}') { + buf = newline(open) + newline(matter) + newline(close); + } + + if (typeof file.excerpt === 'string' && file.excerpt !== '') { + if (str.indexOf(file.excerpt.trim()) === -1) { + buf += newline(file.excerpt) + newline(close); + } + } + + return buf + newline(str); +}; + +function newline(str) { + return str.slice(-1) !== '\n' ? str + '\n' : str; +} + + +/***/ }), + +/***/ 48073: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const typeOf = __nccwpck_require__(5140); +const stringify = __nccwpck_require__(34710); +const utils = __nccwpck_require__(58698); + +/** + * Normalize the given value to ensure an object is returned + * with the expected properties. + */ + +module.exports = function(file) { + if (typeOf(file) !== 'object') { + file = { content: file }; + } + + if (typeOf(file.data) !== 'object') { + file.data = {}; + } + + // if file was passed as an object, ensure that + // "file.content" is set + if (file.contents && file.content == null) { + file.content = file.contents; + } + + // set non-enumerable properties on the file object + utils.define(file, 'orig', utils.toBuffer(file.content)); + utils.define(file, 'language', file.language || ''); + utils.define(file, 'matter', file.matter || ''); + utils.define(file, 'stringify', function(data, options) { + if (options && options.language) { + file.language = options.language; + } + return stringify(file, data, options); + }); + + // strip BOM and ensure that "file.content" is a string + file.content = utils.toString(file.content); + file.isEmpty = false; + file.excerpt = ''; + return file; +}; + + +/***/ }), + +/***/ 58698: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +const stripBom = __nccwpck_require__(91389); +const typeOf = __nccwpck_require__(5140); + +exports.define = function(obj, key, val) { + Reflect.defineProperty(obj, key, { + enumerable: false, + configurable: true, + writable: true, + value: val + }); +}; + +/** + * Returns true if `val` is a buffer + */ + +exports.isBuffer = function(val) { + return typeOf(val) === 'buffer'; +}; + +/** + * Returns true if `val` is an object + */ + +exports.isObject = function(val) { + return typeOf(val) === 'object'; +}; + +/** + * Cast `input` to a buffer + */ + +exports.toBuffer = function(input) { + return typeof input === 'string' ? Buffer.from(input) : input; +}; + +/** + * Cast `val` to a string. + */ + +exports.toString = function(input) { + if (exports.isBuffer(input)) return stripBom(String(input)); + if (typeof input !== 'string') { + throw new TypeError('expected input to be a string or buffer'); + } + return stripBom(input); +}; + +/** + * Cast `val` to an array. + */ + +exports.arrayify = function(val) { + return val ? (Array.isArray(val) ? val : [val]) : []; +}; + +/** + * Returns true if `str` starts with `substr`. + */ + +exports.startsWith = function(str, substr, len) { + if (typeof len !== 'number') len = substr.length; + return str.slice(0, len) === substr; +}; + + +/***/ }), + +/***/ 54076: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +var call = Function.prototype.call; +var $hasOwn = Object.prototype.hasOwnProperty; +var bind = __nccwpck_require__(37564); + +/** @type {import('.')} */ +module.exports = bind.call(call, $hasOwn); + + +/***/ }), + +/***/ 39598: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +try { + var util = __nccwpck_require__(39023); + /* istanbul ignore next */ + if (typeof util.inherits !== 'function') throw ''; + module.exports = util.inherits; +} catch (e) { + /* istanbul ignore next */ + module.exports = __nccwpck_require__(26589); +} + + +/***/ }), + +/***/ 26589: +/***/ ((module) => { + +if (typeof Object.create === 'function') { + // implementation from standard node.js 'util' module + module.exports = function inherits(ctor, superCtor) { + if (superCtor) { + ctor.super_ = superCtor + ctor.prototype = Object.create(superCtor.prototype, { + constructor: { + value: ctor, + enumerable: false, + writable: true, + configurable: true + } + }) + } + }; +} else { + // old school shim for old browsers + module.exports = function inherits(ctor, superCtor) { + if (superCtor) { + ctor.super_ = superCtor + var TempCtor = function () {} + TempCtor.prototype = superCtor.prototype + ctor.prototype = new TempCtor() + ctor.prototype.constructor = ctor + } + } +} + + +/***/ }), + +/***/ 59201: +/***/ ((module) => { + +"use strict"; + + +module.exports = alphabetical + +// Check if the given character code, or the character code at the first +// character, is alphabetical. +function alphabetical(character) { + var code = typeof character === 'string' ? character.charCodeAt(0) : character + + return ( + (code >= 97 && code <= 122) /* a-z */ || + (code >= 65 && code <= 90) /* A-Z */ + ) +} + + +/***/ }), + +/***/ 99624: +/***/ ((module) => { + +"use strict"; + +module.exports = function (str) { + if (typeof str !== 'string') { + throw new TypeError('Expected a string'); + } + + return !/[^0-9a-z\xDF-\xFF]/.test(str.toLowerCase()); +}; + + +/***/ }), + +/***/ 51685: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +var alphabetical = __nccwpck_require__(59201) +var decimal = __nccwpck_require__(96734) + +module.exports = alphanumerical + +// Check if the given character code, or the character code at the first +// character, is alphanumerical. +function alphanumerical(character) { + return alphabetical(character) || decimal(character) +} + + +/***/ }), + +/***/ 65223: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +var hasOwn = __nccwpck_require__(54076); + +function specifierIncluded(current, specifier) { + var nodeParts = current.split('.'); + var parts = specifier.split(' '); + var op = parts.length > 1 ? parts[0] : '='; + var versionParts = (parts.length > 1 ? parts[1] : parts[0]).split('.'); + + for (var i = 0; i < 3; ++i) { + var cur = parseInt(nodeParts[i] || 0, 10); + var ver = parseInt(versionParts[i] || 0, 10); + if (cur === ver) { + continue; // eslint-disable-line no-restricted-syntax, no-continue + } + if (op === '<') { + return cur < ver; + } + if (op === '>=') { + return cur >= ver; + } + return false; + } + return op === '>='; +} + +function matchesRange(current, range) { + var specifiers = range.split(/ ?&& ?/); + if (specifiers.length === 0) { + return false; + } + for (var i = 0; i < specifiers.length; ++i) { + if (!specifierIncluded(current, specifiers[i])) { + return false; + } + } + return true; +} + +function versionIncluded(nodeVersion, specifierValue) { + if (typeof specifierValue === 'boolean') { + return specifierValue; + } + + var current = typeof nodeVersion === 'undefined' + ? process.versions && process.versions.node + : nodeVersion; + + if (typeof current !== 'string') { + throw new TypeError(typeof nodeVersion === 'undefined' ? 'Unable to determine current node version' : 'If provided, a valid node version is required'); + } + + if (specifierValue && typeof specifierValue === 'object') { + for (var i = 0; i < specifierValue.length; ++i) { + if (matchesRange(current, specifierValue[i])) { + return true; + } + } + return false; + } + return matchesRange(current, specifierValue); +} + +var data = __nccwpck_require__(17324); + +module.exports = function isCore(x, nodeVersion) { + return hasOwn(data, x) && versionIncluded(nodeVersion, data[x]); +}; + + +/***/ }), + +/***/ 96734: +/***/ ((module) => { + +"use strict"; + + +module.exports = decimal + +// Check if the given character code, or the character code at the first +// character, is decimal. +function decimal(character) { + var code = typeof character === 'string' ? character.charCodeAt(0) : character + + return code >= 48 && code <= 57 /* 0-9 */ +} + + +/***/ }), + +/***/ 36403: +/***/ ((module) => { + +"use strict"; +/*! + * is-extendable + * + * Copyright (c) 2015, Jon Schlinkert. + * Licensed under the MIT License. + */ + + + +module.exports = function isExtendable(val) { + return typeof val !== 'undefined' && val !== null + && (typeof val === 'object' || typeof val === 'function'); +}; + + +/***/ }), + +/***/ 84794: +/***/ ((module) => { + +"use strict"; + + +module.exports = hexadecimal + +// Check if the given character code, or the character code at the first +// character, is hexadecimal. +function hexadecimal(character) { + var code = typeof character === 'string' ? character.charCodeAt(0) : character + + return ( + (code >= 97 /* a */ && code <= 102) /* z */ || + (code >= 65 /* A */ && code <= 70) /* Z */ || + (code >= 48 /* A */ && code <= 57) /* Z */ + ) +} + + +/***/ }), + +/***/ 96472: +/***/ ((module) => { + +"use strict"; + + +module.exports = whitespace + +var fromCode = String.fromCharCode +var re = /\s/ + +// Check if the given character code, or the character code at the first +// character, is a whitespace character. +function whitespace(character) { + return re.test( + typeof character === 'number' ? fromCode(character) : character.charAt(0) + ) +} + + +/***/ }), + +/***/ 70009: +/***/ ((module) => { + +"use strict"; + + +module.exports = wordCharacter + +var fromCode = String.fromCharCode +var re = /\w/ + +// Check if the given character code, or the character code at the first +// character, is a word character. +function wordCharacter(character) { + return re.test( + typeof character === 'number' ? fromCode(character) : character.charAt(0) + ) +} + + +/***/ }), + +/***/ 85756: +/***/ ((__unused_webpack_module, exports) => { + +// Copyright 2014, 2015, 2016, 2017, 2018 Simon Lydell +// License: MIT. (See LICENSE.) + +Object.defineProperty(exports, "__esModule", ({ + value: true +})) + +// This regex comes from regex.coffee, and is inserted here by generate-index.js +// (run `npm run build`). +exports["default"] = /((['"])(?:(?!\2|\\).|\\(?:\r\n|[\s\S]))*(\2)?|`(?:[^`\\$]|\\[\s\S]|\$(?!\{)|\$\{(?:[^{}]|\{[^}]*\}?)*\}?)*(`)?)|(\/\/.*)|(\/\*(?:[^*]|\*(?!\/))*(\*\/)?)|(\/(?!\*)(?:\[(?:(?![\]\\]).|\\.)*\]|(?![\/\]\\]).|\\.)+\/(?:(?!\s*(?:\b|[\u0080-\uFFFF$\\'"~({]|[+\-!](?!=)|\.?\d))|[gmiyus]{1,6}\b(?![\u0080-\uFFFF$\\]|\s*(?:[+\-*%&|^<>!=?({]|\/(?![\/*])))))|(0[xX][\da-fA-F]+|0[oO][0-7]+|0[bB][01]+|(?:\d*\.\d+|\d+\.?)(?:[eE][+-]?\d+)?)|((?!\d)(?:(?!\s)[$\w\u0080-\uFFFF]|\\u[\da-fA-F]{4}|\\u\{[\da-fA-F]+\})+)|(--|\+\+|&&|\|\||=>|\.{3}|(?:[+\-\/%&|^]|\*{1,2}|<{1,2}|>{1,3}|!=?|={1,2})=?|[?~.,:;[\](){}])|(\s+)|(^$|[\s\S])/g + +exports.matchToToken = function(match) { + var token = {type: "invalid", value: match[0], closed: undefined} + if (match[ 1]) token.type = "string" , token.closed = !!(match[3] || match[4]) + else if (match[ 5]) token.type = "comment" + else if (match[ 6]) token.type = "comment", token.closed = !!match[7] + else if (match[ 8]) token.type = "regex" + else if (match[ 9]) token.type = "number" + else if (match[10]) token.type = "name" + else if (match[11]) token.type = "punctuator" + else if (match[12]) token.type = "whitespace" + return token +} + + +/***/ }), + +/***/ 74281: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + + +var yaml = __nccwpck_require__(24040); + + +module.exports = yaml; + + +/***/ }), + +/***/ 24040: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + + +var loader = __nccwpck_require__(75868); +var dumper = __nccwpck_require__(45078); + + +function deprecated(name) { + return function () { + throw new Error('Function ' + name + ' is deprecated and cannot be used.'); + }; +} + + +module.exports.Type = __nccwpck_require__(50323); +module.exports.Schema = __nccwpck_require__(45868); +module.exports.FAILSAFE_SCHEMA = __nccwpck_require__(86810); +module.exports.JSON_SCHEMA = __nccwpck_require__(96613); +module.exports.CORE_SCHEMA = __nccwpck_require__(55116); +module.exports.DEFAULT_SAFE_SCHEMA = __nccwpck_require__(76032); +module.exports.DEFAULT_FULL_SCHEMA = __nccwpck_require__(60948); +module.exports.load = loader.load; +module.exports.loadAll = loader.loadAll; +module.exports.safeLoad = loader.safeLoad; +module.exports.safeLoadAll = loader.safeLoadAll; +module.exports.dump = dumper.dump; +module.exports.safeDump = dumper.safeDump; +module.exports.YAMLException = __nccwpck_require__(15622); + +// Deprecated schema names from JS-YAML 2.0.x +module.exports.MINIMAL_SCHEMA = __nccwpck_require__(86810); +module.exports.SAFE_SCHEMA = __nccwpck_require__(76032); +module.exports.DEFAULT_SCHEMA = __nccwpck_require__(60948); + +// Deprecated functions from JS-YAML 1.x.x +module.exports.scan = deprecated('scan'); +module.exports.parse = deprecated('parse'); +module.exports.compose = deprecated('compose'); +module.exports.addConstructor = deprecated('addConstructor'); + + +/***/ }), + +/***/ 24206: +/***/ ((module) => { + +"use strict"; + + + +function isNothing(subject) { + return (typeof subject === 'undefined') || (subject === null); +} + + +function isObject(subject) { + return (typeof subject === 'object') && (subject !== null); +} + + +function toArray(sequence) { + if (Array.isArray(sequence)) return sequence; + else if (isNothing(sequence)) return []; + + return [ sequence ]; +} + + +function extend(target, source) { + var index, length, key, sourceKeys; + + if (source) { + sourceKeys = Object.keys(source); + + for (index = 0, length = sourceKeys.length; index < length; index += 1) { + key = sourceKeys[index]; + target[key] = source[key]; + } + } + + return target; +} + + +function repeat(string, count) { + var result = '', cycle; + + for (cycle = 0; cycle < count; cycle += 1) { + result += string; + } + + return result; +} + + +function isNegativeZero(number) { + return (number === 0) && (Number.NEGATIVE_INFINITY === 1 / number); +} + + +module.exports.isNothing = isNothing; +module.exports.isObject = isObject; +module.exports.toArray = toArray; +module.exports.repeat = repeat; +module.exports.isNegativeZero = isNegativeZero; +module.exports.extend = extend; + + +/***/ }), + +/***/ 45078: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +/*eslint-disable no-use-before-define*/ + +var common = __nccwpck_require__(24206); +var YAMLException = __nccwpck_require__(15622); +var DEFAULT_FULL_SCHEMA = __nccwpck_require__(60948); +var DEFAULT_SAFE_SCHEMA = __nccwpck_require__(76032); + +var _toString = Object.prototype.toString; +var _hasOwnProperty = Object.prototype.hasOwnProperty; + +var CHAR_TAB = 0x09; /* Tab */ +var CHAR_LINE_FEED = 0x0A; /* LF */ +var CHAR_CARRIAGE_RETURN = 0x0D; /* CR */ +var CHAR_SPACE = 0x20; /* Space */ +var CHAR_EXCLAMATION = 0x21; /* ! */ +var CHAR_DOUBLE_QUOTE = 0x22; /* " */ +var CHAR_SHARP = 0x23; /* # */ +var CHAR_PERCENT = 0x25; /* % */ +var CHAR_AMPERSAND = 0x26; /* & */ +var CHAR_SINGLE_QUOTE = 0x27; /* ' */ +var CHAR_ASTERISK = 0x2A; /* * */ +var CHAR_COMMA = 0x2C; /* , */ +var CHAR_MINUS = 0x2D; /* - */ +var CHAR_COLON = 0x3A; /* : */ +var CHAR_EQUALS = 0x3D; /* = */ +var CHAR_GREATER_THAN = 0x3E; /* > */ +var CHAR_QUESTION = 0x3F; /* ? */ +var CHAR_COMMERCIAL_AT = 0x40; /* @ */ +var CHAR_LEFT_SQUARE_BRACKET = 0x5B; /* [ */ +var CHAR_RIGHT_SQUARE_BRACKET = 0x5D; /* ] */ +var CHAR_GRAVE_ACCENT = 0x60; /* ` */ +var CHAR_LEFT_CURLY_BRACKET = 0x7B; /* { */ +var CHAR_VERTICAL_LINE = 0x7C; /* | */ +var CHAR_RIGHT_CURLY_BRACKET = 0x7D; /* } */ + +var ESCAPE_SEQUENCES = {}; + +ESCAPE_SEQUENCES[0x00] = '\\0'; +ESCAPE_SEQUENCES[0x07] = '\\a'; +ESCAPE_SEQUENCES[0x08] = '\\b'; +ESCAPE_SEQUENCES[0x09] = '\\t'; +ESCAPE_SEQUENCES[0x0A] = '\\n'; +ESCAPE_SEQUENCES[0x0B] = '\\v'; +ESCAPE_SEQUENCES[0x0C] = '\\f'; +ESCAPE_SEQUENCES[0x0D] = '\\r'; +ESCAPE_SEQUENCES[0x1B] = '\\e'; +ESCAPE_SEQUENCES[0x22] = '\\"'; +ESCAPE_SEQUENCES[0x5C] = '\\\\'; +ESCAPE_SEQUENCES[0x85] = '\\N'; +ESCAPE_SEQUENCES[0xA0] = '\\_'; +ESCAPE_SEQUENCES[0x2028] = '\\L'; +ESCAPE_SEQUENCES[0x2029] = '\\P'; + +var DEPRECATED_BOOLEANS_SYNTAX = [ + 'y', 'Y', 'yes', 'Yes', 'YES', 'on', 'On', 'ON', + 'n', 'N', 'no', 'No', 'NO', 'off', 'Off', 'OFF' +]; + +function compileStyleMap(schema, map) { + var result, keys, index, length, tag, style, type; + + if (map === null) return {}; + + result = {}; + keys = Object.keys(map); + + for (index = 0, length = keys.length; index < length; index += 1) { + tag = keys[index]; + style = String(map[tag]); + + if (tag.slice(0, 2) === '!!') { + tag = 'tag:yaml.org,2002:' + tag.slice(2); + } + type = schema.compiledTypeMap['fallback'][tag]; + + if (type && _hasOwnProperty.call(type.styleAliases, style)) { + style = type.styleAliases[style]; + } + + result[tag] = style; + } + + return result; +} + +function encodeHex(character) { + var string, handle, length; + + string = character.toString(16).toUpperCase(); + + if (character <= 0xFF) { + handle = 'x'; + length = 2; + } else if (character <= 0xFFFF) { + handle = 'u'; + length = 4; + } else if (character <= 0xFFFFFFFF) { + handle = 'U'; + length = 8; + } else { + throw new YAMLException('code point within a string may not be greater than 0xFFFFFFFF'); + } + + return '\\' + handle + common.repeat('0', length - string.length) + string; +} + +function State(options) { + this.schema = options['schema'] || DEFAULT_FULL_SCHEMA; + this.indent = Math.max(1, (options['indent'] || 2)); + this.noArrayIndent = options['noArrayIndent'] || false; + this.skipInvalid = options['skipInvalid'] || false; + this.flowLevel = (common.isNothing(options['flowLevel']) ? -1 : options['flowLevel']); + this.styleMap = compileStyleMap(this.schema, options['styles'] || null); + this.sortKeys = options['sortKeys'] || false; + this.lineWidth = options['lineWidth'] || 80; + this.noRefs = options['noRefs'] || false; + this.noCompatMode = options['noCompatMode'] || false; + this.condenseFlow = options['condenseFlow'] || false; + + this.implicitTypes = this.schema.compiledImplicit; + this.explicitTypes = this.schema.compiledExplicit; + + this.tag = null; + this.result = ''; + + this.duplicates = []; + this.usedDuplicates = null; +} + +// Indents every line in a string. Empty lines (\n only) are not indented. +function indentString(string, spaces) { + var ind = common.repeat(' ', spaces), + position = 0, + next = -1, + result = '', + line, + length = string.length; + + while (position < length) { + next = string.indexOf('\n', position); + if (next === -1) { + line = string.slice(position); + position = length; + } else { + line = string.slice(position, next + 1); + position = next + 1; + } + + if (line.length && line !== '\n') result += ind; + + result += line; + } + + return result; +} + +function generateNextLine(state, level) { + return '\n' + common.repeat(' ', state.indent * level); +} + +function testImplicitResolving(state, str) { + var index, length, type; + + for (index = 0, length = state.implicitTypes.length; index < length; index += 1) { + type = state.implicitTypes[index]; + + if (type.resolve(str)) { + return true; + } + } + + return false; +} + +// [33] s-white ::= s-space | s-tab +function isWhitespace(c) { + return c === CHAR_SPACE || c === CHAR_TAB; +} + +// Returns true if the character can be printed without escaping. +// From YAML 1.2: "any allowed characters known to be non-printable +// should also be escaped. [However,] This isn’t mandatory" +// Derived from nb-char - \t - #x85 - #xA0 - #x2028 - #x2029. +function isPrintable(c) { + return (0x00020 <= c && c <= 0x00007E) + || ((0x000A1 <= c && c <= 0x00D7FF) && c !== 0x2028 && c !== 0x2029) + || ((0x0E000 <= c && c <= 0x00FFFD) && c !== 0xFEFF /* BOM */) + || (0x10000 <= c && c <= 0x10FFFF); +} + +// [34] ns-char ::= nb-char - s-white +// [27] nb-char ::= c-printable - b-char - c-byte-order-mark +// [26] b-char ::= b-line-feed | b-carriage-return +// [24] b-line-feed ::= #xA /* LF */ +// [25] b-carriage-return ::= #xD /* CR */ +// [3] c-byte-order-mark ::= #xFEFF +function isNsChar(c) { + return isPrintable(c) && !isWhitespace(c) + // byte-order-mark + && c !== 0xFEFF + // b-char + && c !== CHAR_CARRIAGE_RETURN + && c !== CHAR_LINE_FEED; +} + +// Simplified test for values allowed after the first character in plain style. +function isPlainSafe(c, prev) { + // Uses a subset of nb-char - c-flow-indicator - ":" - "#" + // where nb-char ::= c-printable - b-char - c-byte-order-mark. + return isPrintable(c) && c !== 0xFEFF + // - c-flow-indicator + && c !== CHAR_COMMA + && c !== CHAR_LEFT_SQUARE_BRACKET + && c !== CHAR_RIGHT_SQUARE_BRACKET + && c !== CHAR_LEFT_CURLY_BRACKET + && c !== CHAR_RIGHT_CURLY_BRACKET + // - ":" - "#" + // /* An ns-char preceding */ "#" + && c !== CHAR_COLON + && ((c !== CHAR_SHARP) || (prev && isNsChar(prev))); +} + +// Simplified test for values allowed as the first character in plain style. +function isPlainSafeFirst(c) { + // Uses a subset of ns-char - c-indicator + // where ns-char = nb-char - s-white. + return isPrintable(c) && c !== 0xFEFF + && !isWhitespace(c) // - s-white + // - (c-indicator ::= + // “-” | “?” | “:” | “,” | “[” | “]” | “{” | “}” + && c !== CHAR_MINUS + && c !== CHAR_QUESTION + && c !== CHAR_COLON + && c !== CHAR_COMMA + && c !== CHAR_LEFT_SQUARE_BRACKET + && c !== CHAR_RIGHT_SQUARE_BRACKET + && c !== CHAR_LEFT_CURLY_BRACKET + && c !== CHAR_RIGHT_CURLY_BRACKET + // | “#” | “&” | “*” | “!” | “|” | “=” | “>” | “'” | “"” + && c !== CHAR_SHARP + && c !== CHAR_AMPERSAND + && c !== CHAR_ASTERISK + && c !== CHAR_EXCLAMATION + && c !== CHAR_VERTICAL_LINE + && c !== CHAR_EQUALS + && c !== CHAR_GREATER_THAN + && c !== CHAR_SINGLE_QUOTE + && c !== CHAR_DOUBLE_QUOTE + // | “%” | “@” | “`”) + && c !== CHAR_PERCENT + && c !== CHAR_COMMERCIAL_AT + && c !== CHAR_GRAVE_ACCENT; +} + +// Determines whether block indentation indicator is required. +function needIndentIndicator(string) { + var leadingSpaceRe = /^\n* /; + return leadingSpaceRe.test(string); +} + +var STYLE_PLAIN = 1, + STYLE_SINGLE = 2, + STYLE_LITERAL = 3, + STYLE_FOLDED = 4, + STYLE_DOUBLE = 5; + +// Determines which scalar styles are possible and returns the preferred style. +// lineWidth = -1 => no limit. +// Pre-conditions: str.length > 0. +// Post-conditions: +// STYLE_PLAIN or STYLE_SINGLE => no \n are in the string. +// STYLE_LITERAL => no lines are suitable for folding (or lineWidth is -1). +// STYLE_FOLDED => a line > lineWidth and can be folded (and lineWidth != -1). +function chooseScalarStyle(string, singleLineOnly, indentPerLevel, lineWidth, testAmbiguousType) { + var i; + var char, prev_char; + var hasLineBreak = false; + var hasFoldableLine = false; // only checked if shouldTrackWidth + var shouldTrackWidth = lineWidth !== -1; + var previousLineBreak = -1; // count the first line correctly + var plain = isPlainSafeFirst(string.charCodeAt(0)) + && !isWhitespace(string.charCodeAt(string.length - 1)); + + if (singleLineOnly) { + // Case: no block styles. + // Check for disallowed characters to rule out plain and single. + for (i = 0; i < string.length; i++) { + char = string.charCodeAt(i); + if (!isPrintable(char)) { + return STYLE_DOUBLE; + } + prev_char = i > 0 ? string.charCodeAt(i - 1) : null; + plain = plain && isPlainSafe(char, prev_char); + } + } else { + // Case: block styles permitted. + for (i = 0; i < string.length; i++) { + char = string.charCodeAt(i); + if (char === CHAR_LINE_FEED) { + hasLineBreak = true; + // Check if any line can be folded. + if (shouldTrackWidth) { + hasFoldableLine = hasFoldableLine || + // Foldable line = too long, and not more-indented. + (i - previousLineBreak - 1 > lineWidth && + string[previousLineBreak + 1] !== ' '); + previousLineBreak = i; + } + } else if (!isPrintable(char)) { + return STYLE_DOUBLE; + } + prev_char = i > 0 ? string.charCodeAt(i - 1) : null; + plain = plain && isPlainSafe(char, prev_char); + } + // in case the end is missing a \n + hasFoldableLine = hasFoldableLine || (shouldTrackWidth && + (i - previousLineBreak - 1 > lineWidth && + string[previousLineBreak + 1] !== ' ')); + } + // Although every style can represent \n without escaping, prefer block styles + // for multiline, since they're more readable and they don't add empty lines. + // Also prefer folding a super-long line. + if (!hasLineBreak && !hasFoldableLine) { + // Strings interpretable as another type have to be quoted; + // e.g. the string 'true' vs. the boolean true. + return plain && !testAmbiguousType(string) + ? STYLE_PLAIN : STYLE_SINGLE; + } + // Edge case: block indentation indicator can only have one digit. + if (indentPerLevel > 9 && needIndentIndicator(string)) { + return STYLE_DOUBLE; + } + // At this point we know block styles are valid. + // Prefer literal style unless we want to fold. + return hasFoldableLine ? STYLE_FOLDED : STYLE_LITERAL; +} + +// Note: line breaking/folding is implemented for only the folded style. +// NB. We drop the last trailing newline (if any) of a returned block scalar +// since the dumper adds its own newline. This always works: +// • No ending newline => unaffected; already using strip "-" chomping. +// • Ending newline => removed then restored. +// Importantly, this keeps the "+" chomp indicator from gaining an extra line. +function writeScalar(state, string, level, iskey) { + state.dump = (function () { + if (string.length === 0) { + return "''"; + } + if (!state.noCompatMode && + DEPRECATED_BOOLEANS_SYNTAX.indexOf(string) !== -1) { + return "'" + string + "'"; + } + + var indent = state.indent * Math.max(1, level); // no 0-indent scalars + // As indentation gets deeper, let the width decrease monotonically + // to the lower bound min(state.lineWidth, 40). + // Note that this implies + // state.lineWidth ≤ 40 + state.indent: width is fixed at the lower bound. + // state.lineWidth > 40 + state.indent: width decreases until the lower bound. + // This behaves better than a constant minimum width which disallows narrower options, + // or an indent threshold which causes the width to suddenly increase. + var lineWidth = state.lineWidth === -1 + ? -1 : Math.max(Math.min(state.lineWidth, 40), state.lineWidth - indent); + + // Without knowing if keys are implicit/explicit, assume implicit for safety. + var singleLineOnly = iskey + // No block styles in flow mode. + || (state.flowLevel > -1 && level >= state.flowLevel); + function testAmbiguity(string) { + return testImplicitResolving(state, string); + } + + switch (chooseScalarStyle(string, singleLineOnly, state.indent, lineWidth, testAmbiguity)) { + case STYLE_PLAIN: + return string; + case STYLE_SINGLE: + return "'" + string.replace(/'/g, "''") + "'"; + case STYLE_LITERAL: + return '|' + blockHeader(string, state.indent) + + dropEndingNewline(indentString(string, indent)); + case STYLE_FOLDED: + return '>' + blockHeader(string, state.indent) + + dropEndingNewline(indentString(foldString(string, lineWidth), indent)); + case STYLE_DOUBLE: + return '"' + escapeString(string, lineWidth) + '"'; + default: + throw new YAMLException('impossible error: invalid scalar style'); + } + }()); +} + +// Pre-conditions: string is valid for a block scalar, 1 <= indentPerLevel <= 9. +function blockHeader(string, indentPerLevel) { + var indentIndicator = needIndentIndicator(string) ? String(indentPerLevel) : ''; + + // note the special case: the string '\n' counts as a "trailing" empty line. + var clip = string[string.length - 1] === '\n'; + var keep = clip && (string[string.length - 2] === '\n' || string === '\n'); + var chomp = keep ? '+' : (clip ? '' : '-'); + + return indentIndicator + chomp + '\n'; +} + +// (See the note for writeScalar.) +function dropEndingNewline(string) { + return string[string.length - 1] === '\n' ? string.slice(0, -1) : string; +} + +// Note: a long line without a suitable break point will exceed the width limit. +// Pre-conditions: every char in str isPrintable, str.length > 0, width > 0. +function foldString(string, width) { + // In folded style, $k$ consecutive newlines output as $k+1$ newlines— + // unless they're before or after a more-indented line, or at the very + // beginning or end, in which case $k$ maps to $k$. + // Therefore, parse each chunk as newline(s) followed by a content line. + var lineRe = /(\n+)([^\n]*)/g; + + // first line (possibly an empty line) + var result = (function () { + var nextLF = string.indexOf('\n'); + nextLF = nextLF !== -1 ? nextLF : string.length; + lineRe.lastIndex = nextLF; + return foldLine(string.slice(0, nextLF), width); + }()); + // If we haven't reached the first content line yet, don't add an extra \n. + var prevMoreIndented = string[0] === '\n' || string[0] === ' '; + var moreIndented; + + // rest of the lines + var match; + while ((match = lineRe.exec(string))) { + var prefix = match[1], line = match[2]; + moreIndented = (line[0] === ' '); + result += prefix + + (!prevMoreIndented && !moreIndented && line !== '' + ? '\n' : '') + + foldLine(line, width); + prevMoreIndented = moreIndented; + } + + return result; +} + +// Greedy line breaking. +// Picks the longest line under the limit each time, +// otherwise settles for the shortest line over the limit. +// NB. More-indented lines *cannot* be folded, as that would add an extra \n. +function foldLine(line, width) { + if (line === '' || line[0] === ' ') return line; + + // Since a more-indented line adds a \n, breaks can't be followed by a space. + var breakRe = / [^ ]/g; // note: the match index will always be <= length-2. + var match; + // start is an inclusive index. end, curr, and next are exclusive. + var start = 0, end, curr = 0, next = 0; + var result = ''; + + // Invariants: 0 <= start <= length-1. + // 0 <= curr <= next <= max(0, length-2). curr - start <= width. + // Inside the loop: + // A match implies length >= 2, so curr and next are <= length-2. + while ((match = breakRe.exec(line))) { + next = match.index; + // maintain invariant: curr - start <= width + if (next - start > width) { + end = (curr > start) ? curr : next; // derive end <= length-2 + result += '\n' + line.slice(start, end); + // skip the space that was output as \n + start = end + 1; // derive start <= length-1 + } + curr = next; + } + + // By the invariants, start <= length-1, so there is something left over. + // It is either the whole string or a part starting from non-whitespace. + result += '\n'; + // Insert a break if the remainder is too long and there is a break available. + if (line.length - start > width && curr > start) { + result += line.slice(start, curr) + '\n' + line.slice(curr + 1); + } else { + result += line.slice(start); + } + + return result.slice(1); // drop extra \n joiner +} + +// Escapes a double-quoted string. +function escapeString(string) { + var result = ''; + var char, nextChar; + var escapeSeq; + + for (var i = 0; i < string.length; i++) { + char = string.charCodeAt(i); + // Check for surrogate pairs (reference Unicode 3.0 section "3.7 Surrogates"). + if (char >= 0xD800 && char <= 0xDBFF/* high surrogate */) { + nextChar = string.charCodeAt(i + 1); + if (nextChar >= 0xDC00 && nextChar <= 0xDFFF/* low surrogate */) { + // Combine the surrogate pair and store it escaped. + result += encodeHex((char - 0xD800) * 0x400 + nextChar - 0xDC00 + 0x10000); + // Advance index one extra since we already used that char here. + i++; continue; + } + } + escapeSeq = ESCAPE_SEQUENCES[char]; + result += !escapeSeq && isPrintable(char) + ? string[i] + : escapeSeq || encodeHex(char); + } + + return result; +} + +function writeFlowSequence(state, level, object) { + var _result = '', + _tag = state.tag, + index, + length; + + for (index = 0, length = object.length; index < length; index += 1) { + // Write only valid elements. + if (writeNode(state, level, object[index], false, false)) { + if (index !== 0) _result += ',' + (!state.condenseFlow ? ' ' : ''); + _result += state.dump; + } + } + + state.tag = _tag; + state.dump = '[' + _result + ']'; +} + +function writeBlockSequence(state, level, object, compact) { + var _result = '', + _tag = state.tag, + index, + length; + + for (index = 0, length = object.length; index < length; index += 1) { + // Write only valid elements. + if (writeNode(state, level + 1, object[index], true, true)) { + if (!compact || index !== 0) { + _result += generateNextLine(state, level); + } + + if (state.dump && CHAR_LINE_FEED === state.dump.charCodeAt(0)) { + _result += '-'; + } else { + _result += '- '; + } + + _result += state.dump; + } + } + + state.tag = _tag; + state.dump = _result || '[]'; // Empty sequence if no valid values. +} + +function writeFlowMapping(state, level, object) { + var _result = '', + _tag = state.tag, + objectKeyList = Object.keys(object), + index, + length, + objectKey, + objectValue, + pairBuffer; + + for (index = 0, length = objectKeyList.length; index < length; index += 1) { + + pairBuffer = ''; + if (index !== 0) pairBuffer += ', '; + + if (state.condenseFlow) pairBuffer += '"'; + + objectKey = objectKeyList[index]; + objectValue = object[objectKey]; + + if (!writeNode(state, level, objectKey, false, false)) { + continue; // Skip this pair because of invalid key; + } + + if (state.dump.length > 1024) pairBuffer += '? '; + + pairBuffer += state.dump + (state.condenseFlow ? '"' : '') + ':' + (state.condenseFlow ? '' : ' '); + + if (!writeNode(state, level, objectValue, false, false)) { + continue; // Skip this pair because of invalid value. + } + + pairBuffer += state.dump; + + // Both key and value are valid. + _result += pairBuffer; + } + + state.tag = _tag; + state.dump = '{' + _result + '}'; +} + +function writeBlockMapping(state, level, object, compact) { + var _result = '', + _tag = state.tag, + objectKeyList = Object.keys(object), + index, + length, + objectKey, + objectValue, + explicitPair, + pairBuffer; + + // Allow sorting keys so that the output file is deterministic + if (state.sortKeys === true) { + // Default sorting + objectKeyList.sort(); + } else if (typeof state.sortKeys === 'function') { + // Custom sort function + objectKeyList.sort(state.sortKeys); + } else if (state.sortKeys) { + // Something is wrong + throw new YAMLException('sortKeys must be a boolean or a function'); + } + + for (index = 0, length = objectKeyList.length; index < length; index += 1) { + pairBuffer = ''; + + if (!compact || index !== 0) { + pairBuffer += generateNextLine(state, level); + } + + objectKey = objectKeyList[index]; + objectValue = object[objectKey]; + + if (!writeNode(state, level + 1, objectKey, true, true, true)) { + continue; // Skip this pair because of invalid key. + } + + explicitPair = (state.tag !== null && state.tag !== '?') || + (state.dump && state.dump.length > 1024); + + if (explicitPair) { + if (state.dump && CHAR_LINE_FEED === state.dump.charCodeAt(0)) { + pairBuffer += '?'; + } else { + pairBuffer += '? '; + } + } + + pairBuffer += state.dump; + + if (explicitPair) { + pairBuffer += generateNextLine(state, level); + } + + if (!writeNode(state, level + 1, objectValue, true, explicitPair)) { + continue; // Skip this pair because of invalid value. + } + + if (state.dump && CHAR_LINE_FEED === state.dump.charCodeAt(0)) { + pairBuffer += ':'; + } else { + pairBuffer += ': '; + } + + pairBuffer += state.dump; + + // Both key and value are valid. + _result += pairBuffer; + } + + state.tag = _tag; + state.dump = _result || '{}'; // Empty mapping if no valid pairs. +} + +function detectType(state, object, explicit) { + var _result, typeList, index, length, type, style; + + typeList = explicit ? state.explicitTypes : state.implicitTypes; + + for (index = 0, length = typeList.length; index < length; index += 1) { + type = typeList[index]; + + if ((type.instanceOf || type.predicate) && + (!type.instanceOf || ((typeof object === 'object') && (object instanceof type.instanceOf))) && + (!type.predicate || type.predicate(object))) { + + state.tag = explicit ? type.tag : '?'; + + if (type.represent) { + style = state.styleMap[type.tag] || type.defaultStyle; + + if (_toString.call(type.represent) === '[object Function]') { + _result = type.represent(object, style); + } else if (_hasOwnProperty.call(type.represent, style)) { + _result = type.represent[style](object, style); + } else { + throw new YAMLException('!<' + type.tag + '> tag resolver accepts not "' + style + '" style'); + } + + state.dump = _result; + } + + return true; + } + } + + return false; +} + +// Serializes `object` and writes it to global `result`. +// Returns true on success, or false on invalid object. +// +function writeNode(state, level, object, block, compact, iskey) { + state.tag = null; + state.dump = object; + + if (!detectType(state, object, false)) { + detectType(state, object, true); + } + + var type = _toString.call(state.dump); + + if (block) { + block = (state.flowLevel < 0 || state.flowLevel > level); + } + + var objectOrArray = type === '[object Object]' || type === '[object Array]', + duplicateIndex, + duplicate; + + if (objectOrArray) { + duplicateIndex = state.duplicates.indexOf(object); + duplicate = duplicateIndex !== -1; + } + + if ((state.tag !== null && state.tag !== '?') || duplicate || (state.indent !== 2 && level > 0)) { + compact = false; + } + + if (duplicate && state.usedDuplicates[duplicateIndex]) { + state.dump = '*ref_' + duplicateIndex; + } else { + if (objectOrArray && duplicate && !state.usedDuplicates[duplicateIndex]) { + state.usedDuplicates[duplicateIndex] = true; + } + if (type === '[object Object]') { + if (block && (Object.keys(state.dump).length !== 0)) { + writeBlockMapping(state, level, state.dump, compact); + if (duplicate) { + state.dump = '&ref_' + duplicateIndex + state.dump; + } + } else { + writeFlowMapping(state, level, state.dump); + if (duplicate) { + state.dump = '&ref_' + duplicateIndex + ' ' + state.dump; + } + } + } else if (type === '[object Array]') { + var arrayLevel = (state.noArrayIndent && (level > 0)) ? level - 1 : level; + if (block && (state.dump.length !== 0)) { + writeBlockSequence(state, arrayLevel, state.dump, compact); + if (duplicate) { + state.dump = '&ref_' + duplicateIndex + state.dump; + } + } else { + writeFlowSequence(state, arrayLevel, state.dump); + if (duplicate) { + state.dump = '&ref_' + duplicateIndex + ' ' + state.dump; + } + } + } else if (type === '[object String]') { + if (state.tag !== '?') { + writeScalar(state, state.dump, level, iskey); + } + } else { + if (state.skipInvalid) return false; + throw new YAMLException('unacceptable kind of an object to dump ' + type); + } + + if (state.tag !== null && state.tag !== '?') { + state.dump = '!<' + state.tag + '> ' + state.dump; + } + } + + return true; +} + +function getDuplicateReferences(object, state) { + var objects = [], + duplicatesIndexes = [], + index, + length; + + inspectNode(object, objects, duplicatesIndexes); + + for (index = 0, length = duplicatesIndexes.length; index < length; index += 1) { + state.duplicates.push(objects[duplicatesIndexes[index]]); + } + state.usedDuplicates = new Array(length); +} + +function inspectNode(object, objects, duplicatesIndexes) { + var objectKeyList, + index, + length; + + if (object !== null && typeof object === 'object') { + index = objects.indexOf(object); + if (index !== -1) { + if (duplicatesIndexes.indexOf(index) === -1) { + duplicatesIndexes.push(index); + } + } else { + objects.push(object); + + if (Array.isArray(object)) { + for (index = 0, length = object.length; index < length; index += 1) { + inspectNode(object[index], objects, duplicatesIndexes); + } + } else { + objectKeyList = Object.keys(object); + + for (index = 0, length = objectKeyList.length; index < length; index += 1) { + inspectNode(object[objectKeyList[index]], objects, duplicatesIndexes); + } + } + } + } +} + +function dump(input, options) { + options = options || {}; + + var state = new State(options); + + if (!state.noRefs) getDuplicateReferences(input, state); + + if (writeNode(state, 0, input, true, true)) return state.dump + '\n'; + + return ''; +} + +function safeDump(input, options) { + return dump(input, common.extend({ schema: DEFAULT_SAFE_SCHEMA }, options)); +} + +module.exports.dump = dump; +module.exports.safeDump = safeDump; + + +/***/ }), + +/***/ 15622: +/***/ ((module) => { + +"use strict"; +// YAML error class. http://stackoverflow.com/questions/8458984 +// + + +function YAMLException(reason, mark) { + // Super constructor + Error.call(this); + + this.name = 'YAMLException'; + this.reason = reason; + this.mark = mark; + this.message = (this.reason || '(unknown reason)') + (this.mark ? ' ' + this.mark.toString() : ''); + + // Include stack trace in error object + if (Error.captureStackTrace) { + // Chrome and NodeJS + Error.captureStackTrace(this, this.constructor); + } else { + // FF, IE 10+ and Safari 6+. Fallback for others + this.stack = (new Error()).stack || ''; + } +} + + +// Inherit from Error +YAMLException.prototype = Object.create(Error.prototype); +YAMLException.prototype.constructor = YAMLException; + + +YAMLException.prototype.toString = function toString(compact) { + var result = this.name + ': '; + + result += this.reason || '(unknown reason)'; + + if (!compact && this.mark) { + result += ' ' + this.mark.toString(); + } + + return result; +}; + + +module.exports = YAMLException; + + +/***/ }), + +/***/ 75868: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +/*eslint-disable max-len,no-use-before-define*/ + +var common = __nccwpck_require__(24206); +var YAMLException = __nccwpck_require__(15622); +var Mark = __nccwpck_require__(45622); +var DEFAULT_SAFE_SCHEMA = __nccwpck_require__(76032); +var DEFAULT_FULL_SCHEMA = __nccwpck_require__(60948); + + +var _hasOwnProperty = Object.prototype.hasOwnProperty; + + +var CONTEXT_FLOW_IN = 1; +var CONTEXT_FLOW_OUT = 2; +var CONTEXT_BLOCK_IN = 3; +var CONTEXT_BLOCK_OUT = 4; + + +var CHOMPING_CLIP = 1; +var CHOMPING_STRIP = 2; +var CHOMPING_KEEP = 3; + + +var PATTERN_NON_PRINTABLE = /[\x00-\x08\x0B\x0C\x0E-\x1F\x7F-\x84\x86-\x9F\uFFFE\uFFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF]/; +var PATTERN_NON_ASCII_LINE_BREAKS = /[\x85\u2028\u2029]/; +var PATTERN_FLOW_INDICATORS = /[,\[\]\{\}]/; +var PATTERN_TAG_HANDLE = /^(?:!|!!|![a-z\-]+!)$/i; +var PATTERN_TAG_URI = /^(?:!|[^,\[\]\{\}])(?:%[0-9a-f]{2}|[0-9a-z\-#;\/\?:@&=\+\$,_\.!~\*'\(\)\[\]])*$/i; + + +function _class(obj) { return Object.prototype.toString.call(obj); } + +function is_EOL(c) { + return (c === 0x0A/* LF */) || (c === 0x0D/* CR */); +} + +function is_WHITE_SPACE(c) { + return (c === 0x09/* Tab */) || (c === 0x20/* Space */); +} + +function is_WS_OR_EOL(c) { + return (c === 0x09/* Tab */) || + (c === 0x20/* Space */) || + (c === 0x0A/* LF */) || + (c === 0x0D/* CR */); +} + +function is_FLOW_INDICATOR(c) { + return c === 0x2C/* , */ || + c === 0x5B/* [ */ || + c === 0x5D/* ] */ || + c === 0x7B/* { */ || + c === 0x7D/* } */; +} + +function fromHexCode(c) { + var lc; + + if ((0x30/* 0 */ <= c) && (c <= 0x39/* 9 */)) { + return c - 0x30; + } + + /*eslint-disable no-bitwise*/ + lc = c | 0x20; + + if ((0x61/* a */ <= lc) && (lc <= 0x66/* f */)) { + return lc - 0x61 + 10; + } + + return -1; +} + +function escapedHexLen(c) { + if (c === 0x78/* x */) { return 2; } + if (c === 0x75/* u */) { return 4; } + if (c === 0x55/* U */) { return 8; } + return 0; +} + +function fromDecimalCode(c) { + if ((0x30/* 0 */ <= c) && (c <= 0x39/* 9 */)) { + return c - 0x30; + } + + return -1; +} + +function simpleEscapeSequence(c) { + /* eslint-disable indent */ + return (c === 0x30/* 0 */) ? '\x00' : + (c === 0x61/* a */) ? '\x07' : + (c === 0x62/* b */) ? '\x08' : + (c === 0x74/* t */) ? '\x09' : + (c === 0x09/* Tab */) ? '\x09' : + (c === 0x6E/* n */) ? '\x0A' : + (c === 0x76/* v */) ? '\x0B' : + (c === 0x66/* f */) ? '\x0C' : + (c === 0x72/* r */) ? '\x0D' : + (c === 0x65/* e */) ? '\x1B' : + (c === 0x20/* Space */) ? ' ' : + (c === 0x22/* " */) ? '\x22' : + (c === 0x2F/* / */) ? '/' : + (c === 0x5C/* \ */) ? '\x5C' : + (c === 0x4E/* N */) ? '\x85' : + (c === 0x5F/* _ */) ? '\xA0' : + (c === 0x4C/* L */) ? '\u2028' : + (c === 0x50/* P */) ? '\u2029' : ''; +} + +function charFromCodepoint(c) { + if (c <= 0xFFFF) { + return String.fromCharCode(c); + } + // Encode UTF-16 surrogate pair + // https://en.wikipedia.org/wiki/UTF-16#Code_points_U.2B010000_to_U.2B10FFFF + return String.fromCharCode( + ((c - 0x010000) >> 10) + 0xD800, + ((c - 0x010000) & 0x03FF) + 0xDC00 + ); +} + +var simpleEscapeCheck = new Array(256); // integer, for fast access +var simpleEscapeMap = new Array(256); +for (var i = 0; i < 256; i++) { + simpleEscapeCheck[i] = simpleEscapeSequence(i) ? 1 : 0; + simpleEscapeMap[i] = simpleEscapeSequence(i); +} + + +function State(input, options) { + this.input = input; + + this.filename = options['filename'] || null; + this.schema = options['schema'] || DEFAULT_FULL_SCHEMA; + this.onWarning = options['onWarning'] || null; + this.legacy = options['legacy'] || false; + this.json = options['json'] || false; + this.listener = options['listener'] || null; + + this.implicitTypes = this.schema.compiledImplicit; + this.typeMap = this.schema.compiledTypeMap; + + this.length = input.length; + this.position = 0; + this.line = 0; + this.lineStart = 0; + this.lineIndent = 0; + + this.documents = []; + + /* + this.version; + this.checkLineBreaks; + this.tagMap; + this.anchorMap; + this.tag; + this.anchor; + this.kind; + this.result;*/ + +} + + +function generateError(state, message) { + return new YAMLException( + message, + new Mark(state.filename, state.input, state.position, state.line, (state.position - state.lineStart))); +} + +function throwError(state, message) { + throw generateError(state, message); +} + +function throwWarning(state, message) { + if (state.onWarning) { + state.onWarning.call(null, generateError(state, message)); + } +} + + +var directiveHandlers = { + + YAML: function handleYamlDirective(state, name, args) { + + var match, major, minor; + + if (state.version !== null) { + throwError(state, 'duplication of %YAML directive'); + } + + if (args.length !== 1) { + throwError(state, 'YAML directive accepts exactly one argument'); + } + + match = /^([0-9]+)\.([0-9]+)$/.exec(args[0]); + + if (match === null) { + throwError(state, 'ill-formed argument of the YAML directive'); + } + + major = parseInt(match[1], 10); + minor = parseInt(match[2], 10); + + if (major !== 1) { + throwError(state, 'unacceptable YAML version of the document'); + } + + state.version = args[0]; + state.checkLineBreaks = (minor < 2); + + if (minor !== 1 && minor !== 2) { + throwWarning(state, 'unsupported YAML version of the document'); + } + }, + + TAG: function handleTagDirective(state, name, args) { + + var handle, prefix; + + if (args.length !== 2) { + throwError(state, 'TAG directive accepts exactly two arguments'); + } + + handle = args[0]; + prefix = args[1]; + + if (!PATTERN_TAG_HANDLE.test(handle)) { + throwError(state, 'ill-formed tag handle (first argument) of the TAG directive'); + } + + if (_hasOwnProperty.call(state.tagMap, handle)) { + throwError(state, 'there is a previously declared suffix for "' + handle + '" tag handle'); + } + + if (!PATTERN_TAG_URI.test(prefix)) { + throwError(state, 'ill-formed tag prefix (second argument) of the TAG directive'); + } + + state.tagMap[handle] = prefix; + } +}; + + +function captureSegment(state, start, end, checkJson) { + var _position, _length, _character, _result; + + if (start < end) { + _result = state.input.slice(start, end); + + if (checkJson) { + for (_position = 0, _length = _result.length; _position < _length; _position += 1) { + _character = _result.charCodeAt(_position); + if (!(_character === 0x09 || + (0x20 <= _character && _character <= 0x10FFFF))) { + throwError(state, 'expected valid JSON character'); + } + } + } else if (PATTERN_NON_PRINTABLE.test(_result)) { + throwError(state, 'the stream contains non-printable characters'); + } + + state.result += _result; + } +} + +function mergeMappings(state, destination, source, overridableKeys) { + var sourceKeys, key, index, quantity; + + if (!common.isObject(source)) { + throwError(state, 'cannot merge mappings; the provided source object is unacceptable'); + } + + sourceKeys = Object.keys(source); + + for (index = 0, quantity = sourceKeys.length; index < quantity; index += 1) { + key = sourceKeys[index]; + + if (!_hasOwnProperty.call(destination, key)) { + destination[key] = source[key]; + overridableKeys[key] = true; + } + } +} + +function storeMappingPair(state, _result, overridableKeys, keyTag, keyNode, valueNode, startLine, startPos) { + var index, quantity; + + // The output is a plain object here, so keys can only be strings. + // We need to convert keyNode to a string, but doing so can hang the process + // (deeply nested arrays that explode exponentially using aliases). + if (Array.isArray(keyNode)) { + keyNode = Array.prototype.slice.call(keyNode); + + for (index = 0, quantity = keyNode.length; index < quantity; index += 1) { + if (Array.isArray(keyNode[index])) { + throwError(state, 'nested arrays are not supported inside keys'); + } + + if (typeof keyNode === 'object' && _class(keyNode[index]) === '[object Object]') { + keyNode[index] = '[object Object]'; + } + } + } + + // Avoid code execution in load() via toString property + // (still use its own toString for arrays, timestamps, + // and whatever user schema extensions happen to have @@toStringTag) + if (typeof keyNode === 'object' && _class(keyNode) === '[object Object]') { + keyNode = '[object Object]'; + } + + + keyNode = String(keyNode); + + if (_result === null) { + _result = {}; + } + + if (keyTag === 'tag:yaml.org,2002:merge') { + if (Array.isArray(valueNode)) { + for (index = 0, quantity = valueNode.length; index < quantity; index += 1) { + mergeMappings(state, _result, valueNode[index], overridableKeys); + } + } else { + mergeMappings(state, _result, valueNode, overridableKeys); + } + } else { + if (!state.json && + !_hasOwnProperty.call(overridableKeys, keyNode) && + _hasOwnProperty.call(_result, keyNode)) { + state.line = startLine || state.line; + state.position = startPos || state.position; + throwError(state, 'duplicated mapping key'); + } + _result[keyNode] = valueNode; + delete overridableKeys[keyNode]; + } + + return _result; +} + +function readLineBreak(state) { + var ch; + + ch = state.input.charCodeAt(state.position); + + if (ch === 0x0A/* LF */) { + state.position++; + } else if (ch === 0x0D/* CR */) { + state.position++; + if (state.input.charCodeAt(state.position) === 0x0A/* LF */) { + state.position++; + } + } else { + throwError(state, 'a line break is expected'); + } + + state.line += 1; + state.lineStart = state.position; +} + +function skipSeparationSpace(state, allowComments, checkIndent) { + var lineBreaks = 0, + ch = state.input.charCodeAt(state.position); + + while (ch !== 0) { + while (is_WHITE_SPACE(ch)) { + ch = state.input.charCodeAt(++state.position); + } + + if (allowComments && ch === 0x23/* # */) { + do { + ch = state.input.charCodeAt(++state.position); + } while (ch !== 0x0A/* LF */ && ch !== 0x0D/* CR */ && ch !== 0); + } + + if (is_EOL(ch)) { + readLineBreak(state); + + ch = state.input.charCodeAt(state.position); + lineBreaks++; + state.lineIndent = 0; + + while (ch === 0x20/* Space */) { + state.lineIndent++; + ch = state.input.charCodeAt(++state.position); + } + } else { + break; + } + } + + if (checkIndent !== -1 && lineBreaks !== 0 && state.lineIndent < checkIndent) { + throwWarning(state, 'deficient indentation'); + } + + return lineBreaks; +} + +function testDocumentSeparator(state) { + var _position = state.position, + ch; + + ch = state.input.charCodeAt(_position); + + // Condition state.position === state.lineStart is tested + // in parent on each call, for efficiency. No needs to test here again. + if ((ch === 0x2D/* - */ || ch === 0x2E/* . */) && + ch === state.input.charCodeAt(_position + 1) && + ch === state.input.charCodeAt(_position + 2)) { + + _position += 3; + + ch = state.input.charCodeAt(_position); + + if (ch === 0 || is_WS_OR_EOL(ch)) { + return true; + } + } + + return false; +} + +function writeFoldedLines(state, count) { + if (count === 1) { + state.result += ' '; + } else if (count > 1) { + state.result += common.repeat('\n', count - 1); + } +} + + +function readPlainScalar(state, nodeIndent, withinFlowCollection) { + var preceding, + following, + captureStart, + captureEnd, + hasPendingContent, + _line, + _lineStart, + _lineIndent, + _kind = state.kind, + _result = state.result, + ch; + + ch = state.input.charCodeAt(state.position); + + if (is_WS_OR_EOL(ch) || + is_FLOW_INDICATOR(ch) || + ch === 0x23/* # */ || + ch === 0x26/* & */ || + ch === 0x2A/* * */ || + ch === 0x21/* ! */ || + ch === 0x7C/* | */ || + ch === 0x3E/* > */ || + ch === 0x27/* ' */ || + ch === 0x22/* " */ || + ch === 0x25/* % */ || + ch === 0x40/* @ */ || + ch === 0x60/* ` */) { + return false; + } + + if (ch === 0x3F/* ? */ || ch === 0x2D/* - */) { + following = state.input.charCodeAt(state.position + 1); + + if (is_WS_OR_EOL(following) || + withinFlowCollection && is_FLOW_INDICATOR(following)) { + return false; + } + } + + state.kind = 'scalar'; + state.result = ''; + captureStart = captureEnd = state.position; + hasPendingContent = false; + + while (ch !== 0) { + if (ch === 0x3A/* : */) { + following = state.input.charCodeAt(state.position + 1); + + if (is_WS_OR_EOL(following) || + withinFlowCollection && is_FLOW_INDICATOR(following)) { + break; + } + + } else if (ch === 0x23/* # */) { + preceding = state.input.charCodeAt(state.position - 1); + + if (is_WS_OR_EOL(preceding)) { + break; + } + + } else if ((state.position === state.lineStart && testDocumentSeparator(state)) || + withinFlowCollection && is_FLOW_INDICATOR(ch)) { + break; + + } else if (is_EOL(ch)) { + _line = state.line; + _lineStart = state.lineStart; + _lineIndent = state.lineIndent; + skipSeparationSpace(state, false, -1); + + if (state.lineIndent >= nodeIndent) { + hasPendingContent = true; + ch = state.input.charCodeAt(state.position); + continue; + } else { + state.position = captureEnd; + state.line = _line; + state.lineStart = _lineStart; + state.lineIndent = _lineIndent; + break; + } + } + + if (hasPendingContent) { + captureSegment(state, captureStart, captureEnd, false); + writeFoldedLines(state, state.line - _line); + captureStart = captureEnd = state.position; + hasPendingContent = false; + } + + if (!is_WHITE_SPACE(ch)) { + captureEnd = state.position + 1; + } + + ch = state.input.charCodeAt(++state.position); + } + + captureSegment(state, captureStart, captureEnd, false); + + if (state.result) { + return true; + } + + state.kind = _kind; + state.result = _result; + return false; +} + +function readSingleQuotedScalar(state, nodeIndent) { + var ch, + captureStart, captureEnd; + + ch = state.input.charCodeAt(state.position); + + if (ch !== 0x27/* ' */) { + return false; + } + + state.kind = 'scalar'; + state.result = ''; + state.position++; + captureStart = captureEnd = state.position; + + while ((ch = state.input.charCodeAt(state.position)) !== 0) { + if (ch === 0x27/* ' */) { + captureSegment(state, captureStart, state.position, true); + ch = state.input.charCodeAt(++state.position); + + if (ch === 0x27/* ' */) { + captureStart = state.position; + state.position++; + captureEnd = state.position; + } else { + return true; + } + + } else if (is_EOL(ch)) { + captureSegment(state, captureStart, captureEnd, true); + writeFoldedLines(state, skipSeparationSpace(state, false, nodeIndent)); + captureStart = captureEnd = state.position; + + } else if (state.position === state.lineStart && testDocumentSeparator(state)) { + throwError(state, 'unexpected end of the document within a single quoted scalar'); + + } else { + state.position++; + captureEnd = state.position; + } + } + + throwError(state, 'unexpected end of the stream within a single quoted scalar'); +} + +function readDoubleQuotedScalar(state, nodeIndent) { + var captureStart, + captureEnd, + hexLength, + hexResult, + tmp, + ch; + + ch = state.input.charCodeAt(state.position); + + if (ch !== 0x22/* " */) { + return false; + } + + state.kind = 'scalar'; + state.result = ''; + state.position++; + captureStart = captureEnd = state.position; + + while ((ch = state.input.charCodeAt(state.position)) !== 0) { + if (ch === 0x22/* " */) { + captureSegment(state, captureStart, state.position, true); + state.position++; + return true; + + } else if (ch === 0x5C/* \ */) { + captureSegment(state, captureStart, state.position, true); + ch = state.input.charCodeAt(++state.position); + + if (is_EOL(ch)) { + skipSeparationSpace(state, false, nodeIndent); + + // TODO: rework to inline fn with no type cast? + } else if (ch < 256 && simpleEscapeCheck[ch]) { + state.result += simpleEscapeMap[ch]; + state.position++; + + } else if ((tmp = escapedHexLen(ch)) > 0) { + hexLength = tmp; + hexResult = 0; + + for (; hexLength > 0; hexLength--) { + ch = state.input.charCodeAt(++state.position); + + if ((tmp = fromHexCode(ch)) >= 0) { + hexResult = (hexResult << 4) + tmp; + + } else { + throwError(state, 'expected hexadecimal character'); + } + } + + state.result += charFromCodepoint(hexResult); + + state.position++; + + } else { + throwError(state, 'unknown escape sequence'); + } + + captureStart = captureEnd = state.position; + + } else if (is_EOL(ch)) { + captureSegment(state, captureStart, captureEnd, true); + writeFoldedLines(state, skipSeparationSpace(state, false, nodeIndent)); + captureStart = captureEnd = state.position; + + } else if (state.position === state.lineStart && testDocumentSeparator(state)) { + throwError(state, 'unexpected end of the document within a double quoted scalar'); + + } else { + state.position++; + captureEnd = state.position; + } + } + + throwError(state, 'unexpected end of the stream within a double quoted scalar'); +} + +function readFlowCollection(state, nodeIndent) { + var readNext = true, + _line, + _tag = state.tag, + _result, + _anchor = state.anchor, + following, + terminator, + isPair, + isExplicitPair, + isMapping, + overridableKeys = {}, + keyNode, + keyTag, + valueNode, + ch; + + ch = state.input.charCodeAt(state.position); + + if (ch === 0x5B/* [ */) { + terminator = 0x5D;/* ] */ + isMapping = false; + _result = []; + } else if (ch === 0x7B/* { */) { + terminator = 0x7D;/* } */ + isMapping = true; + _result = {}; + } else { + return false; + } + + if (state.anchor !== null) { + state.anchorMap[state.anchor] = _result; + } + + ch = state.input.charCodeAt(++state.position); + + while (ch !== 0) { + skipSeparationSpace(state, true, nodeIndent); + + ch = state.input.charCodeAt(state.position); + + if (ch === terminator) { + state.position++; + state.tag = _tag; + state.anchor = _anchor; + state.kind = isMapping ? 'mapping' : 'sequence'; + state.result = _result; + return true; + } else if (!readNext) { + throwError(state, 'missed comma between flow collection entries'); + } + + keyTag = keyNode = valueNode = null; + isPair = isExplicitPair = false; + + if (ch === 0x3F/* ? */) { + following = state.input.charCodeAt(state.position + 1); + + if (is_WS_OR_EOL(following)) { + isPair = isExplicitPair = true; + state.position++; + skipSeparationSpace(state, true, nodeIndent); + } + } + + _line = state.line; + composeNode(state, nodeIndent, CONTEXT_FLOW_IN, false, true); + keyTag = state.tag; + keyNode = state.result; + skipSeparationSpace(state, true, nodeIndent); + + ch = state.input.charCodeAt(state.position); + + if ((isExplicitPair || state.line === _line) && ch === 0x3A/* : */) { + isPair = true; + ch = state.input.charCodeAt(++state.position); + skipSeparationSpace(state, true, nodeIndent); + composeNode(state, nodeIndent, CONTEXT_FLOW_IN, false, true); + valueNode = state.result; + } + + if (isMapping) { + storeMappingPair(state, _result, overridableKeys, keyTag, keyNode, valueNode); + } else if (isPair) { + _result.push(storeMappingPair(state, null, overridableKeys, keyTag, keyNode, valueNode)); + } else { + _result.push(keyNode); + } + + skipSeparationSpace(state, true, nodeIndent); + + ch = state.input.charCodeAt(state.position); + + if (ch === 0x2C/* , */) { + readNext = true; + ch = state.input.charCodeAt(++state.position); + } else { + readNext = false; + } + } + + throwError(state, 'unexpected end of the stream within a flow collection'); +} + +function readBlockScalar(state, nodeIndent) { + var captureStart, + folding, + chomping = CHOMPING_CLIP, + didReadContent = false, + detectedIndent = false, + textIndent = nodeIndent, + emptyLines = 0, + atMoreIndented = false, + tmp, + ch; + + ch = state.input.charCodeAt(state.position); + + if (ch === 0x7C/* | */) { + folding = false; + } else if (ch === 0x3E/* > */) { + folding = true; + } else { + return false; + } + + state.kind = 'scalar'; + state.result = ''; + + while (ch !== 0) { + ch = state.input.charCodeAt(++state.position); + + if (ch === 0x2B/* + */ || ch === 0x2D/* - */) { + if (CHOMPING_CLIP === chomping) { + chomping = (ch === 0x2B/* + */) ? CHOMPING_KEEP : CHOMPING_STRIP; + } else { + throwError(state, 'repeat of a chomping mode identifier'); + } + + } else if ((tmp = fromDecimalCode(ch)) >= 0) { + if (tmp === 0) { + throwError(state, 'bad explicit indentation width of a block scalar; it cannot be less than one'); + } else if (!detectedIndent) { + textIndent = nodeIndent + tmp - 1; + detectedIndent = true; + } else { + throwError(state, 'repeat of an indentation width identifier'); + } + + } else { + break; + } + } + + if (is_WHITE_SPACE(ch)) { + do { ch = state.input.charCodeAt(++state.position); } + while (is_WHITE_SPACE(ch)); + + if (ch === 0x23/* # */) { + do { ch = state.input.charCodeAt(++state.position); } + while (!is_EOL(ch) && (ch !== 0)); + } + } + + while (ch !== 0) { + readLineBreak(state); + state.lineIndent = 0; + + ch = state.input.charCodeAt(state.position); + + while ((!detectedIndent || state.lineIndent < textIndent) && + (ch === 0x20/* Space */)) { + state.lineIndent++; + ch = state.input.charCodeAt(++state.position); + } + + if (!detectedIndent && state.lineIndent > textIndent) { + textIndent = state.lineIndent; + } + + if (is_EOL(ch)) { + emptyLines++; + continue; + } + + // End of the scalar. + if (state.lineIndent < textIndent) { + + // Perform the chomping. + if (chomping === CHOMPING_KEEP) { + state.result += common.repeat('\n', didReadContent ? 1 + emptyLines : emptyLines); + } else if (chomping === CHOMPING_CLIP) { + if (didReadContent) { // i.e. only if the scalar is not empty. + state.result += '\n'; + } + } + + // Break this `while` cycle and go to the funciton's epilogue. + break; + } + + // Folded style: use fancy rules to handle line breaks. + if (folding) { + + // Lines starting with white space characters (more-indented lines) are not folded. + if (is_WHITE_SPACE(ch)) { + atMoreIndented = true; + // except for the first content line (cf. Example 8.1) + state.result += common.repeat('\n', didReadContent ? 1 + emptyLines : emptyLines); + + // End of more-indented block. + } else if (atMoreIndented) { + atMoreIndented = false; + state.result += common.repeat('\n', emptyLines + 1); + + // Just one line break - perceive as the same line. + } else if (emptyLines === 0) { + if (didReadContent) { // i.e. only if we have already read some scalar content. + state.result += ' '; + } + + // Several line breaks - perceive as different lines. + } else { + state.result += common.repeat('\n', emptyLines); + } + + // Literal style: just add exact number of line breaks between content lines. + } else { + // Keep all line breaks except the header line break. + state.result += common.repeat('\n', didReadContent ? 1 + emptyLines : emptyLines); + } + + didReadContent = true; + detectedIndent = true; + emptyLines = 0; + captureStart = state.position; + + while (!is_EOL(ch) && (ch !== 0)) { + ch = state.input.charCodeAt(++state.position); + } + + captureSegment(state, captureStart, state.position, false); + } + + return true; +} + +function readBlockSequence(state, nodeIndent) { + var _line, + _tag = state.tag, + _anchor = state.anchor, + _result = [], + following, + detected = false, + ch; + + if (state.anchor !== null) { + state.anchorMap[state.anchor] = _result; + } + + ch = state.input.charCodeAt(state.position); + + while (ch !== 0) { + + if (ch !== 0x2D/* - */) { + break; + } + + following = state.input.charCodeAt(state.position + 1); + + if (!is_WS_OR_EOL(following)) { + break; + } + + detected = true; + state.position++; + + if (skipSeparationSpace(state, true, -1)) { + if (state.lineIndent <= nodeIndent) { + _result.push(null); + ch = state.input.charCodeAt(state.position); + continue; + } + } + + _line = state.line; + composeNode(state, nodeIndent, CONTEXT_BLOCK_IN, false, true); + _result.push(state.result); + skipSeparationSpace(state, true, -1); + + ch = state.input.charCodeAt(state.position); + + if ((state.line === _line || state.lineIndent > nodeIndent) && (ch !== 0)) { + throwError(state, 'bad indentation of a sequence entry'); + } else if (state.lineIndent < nodeIndent) { + break; + } + } + + if (detected) { + state.tag = _tag; + state.anchor = _anchor; + state.kind = 'sequence'; + state.result = _result; + return true; + } + return false; +} + +function readBlockMapping(state, nodeIndent, flowIndent) { + var following, + allowCompact, + _line, + _pos, + _tag = state.tag, + _anchor = state.anchor, + _result = {}, + overridableKeys = {}, + keyTag = null, + keyNode = null, + valueNode = null, + atExplicitKey = false, + detected = false, + ch; + + if (state.anchor !== null) { + state.anchorMap[state.anchor] = _result; + } + + ch = state.input.charCodeAt(state.position); + + while (ch !== 0) { + following = state.input.charCodeAt(state.position + 1); + _line = state.line; // Save the current line. + _pos = state.position; + + // + // Explicit notation case. There are two separate blocks: + // first for the key (denoted by "?") and second for the value (denoted by ":") + // + if ((ch === 0x3F/* ? */ || ch === 0x3A/* : */) && is_WS_OR_EOL(following)) { + + if (ch === 0x3F/* ? */) { + if (atExplicitKey) { + storeMappingPair(state, _result, overridableKeys, keyTag, keyNode, null); + keyTag = keyNode = valueNode = null; + } + + detected = true; + atExplicitKey = true; + allowCompact = true; + + } else if (atExplicitKey) { + // i.e. 0x3A/* : */ === character after the explicit key. + atExplicitKey = false; + allowCompact = true; + + } else { + throwError(state, 'incomplete explicit mapping pair; a key node is missed; or followed by a non-tabulated empty line'); + } + + state.position += 1; + ch = following; + + // + // Implicit notation case. Flow-style node as the key first, then ":", and the value. + // + } else if (composeNode(state, flowIndent, CONTEXT_FLOW_OUT, false, true)) { + + if (state.line === _line) { + ch = state.input.charCodeAt(state.position); + + while (is_WHITE_SPACE(ch)) { + ch = state.input.charCodeAt(++state.position); + } + + if (ch === 0x3A/* : */) { + ch = state.input.charCodeAt(++state.position); + + if (!is_WS_OR_EOL(ch)) { + throwError(state, 'a whitespace character is expected after the key-value separator within a block mapping'); + } + + if (atExplicitKey) { + storeMappingPair(state, _result, overridableKeys, keyTag, keyNode, null); + keyTag = keyNode = valueNode = null; + } + + detected = true; + atExplicitKey = false; + allowCompact = false; + keyTag = state.tag; + keyNode = state.result; + + } else if (detected) { + throwError(state, 'can not read an implicit mapping pair; a colon is missed'); + + } else { + state.tag = _tag; + state.anchor = _anchor; + return true; // Keep the result of `composeNode`. + } + + } else if (detected) { + throwError(state, 'can not read a block mapping entry; a multiline key may not be an implicit key'); + + } else { + state.tag = _tag; + state.anchor = _anchor; + return true; // Keep the result of `composeNode`. + } + + } else { + break; // Reading is done. Go to the epilogue. + } + + // + // Common reading code for both explicit and implicit notations. + // + if (state.line === _line || state.lineIndent > nodeIndent) { + if (composeNode(state, nodeIndent, CONTEXT_BLOCK_OUT, true, allowCompact)) { + if (atExplicitKey) { + keyNode = state.result; + } else { + valueNode = state.result; + } + } + + if (!atExplicitKey) { + storeMappingPair(state, _result, overridableKeys, keyTag, keyNode, valueNode, _line, _pos); + keyTag = keyNode = valueNode = null; + } + + skipSeparationSpace(state, true, -1); + ch = state.input.charCodeAt(state.position); + } + + if (state.lineIndent > nodeIndent && (ch !== 0)) { + throwError(state, 'bad indentation of a mapping entry'); + } else if (state.lineIndent < nodeIndent) { + break; + } + } + + // + // Epilogue. + // + + // Special case: last mapping's node contains only the key in explicit notation. + if (atExplicitKey) { + storeMappingPair(state, _result, overridableKeys, keyTag, keyNode, null); + } + + // Expose the resulting mapping. + if (detected) { + state.tag = _tag; + state.anchor = _anchor; + state.kind = 'mapping'; + state.result = _result; + } + + return detected; +} + +function readTagProperty(state) { + var _position, + isVerbatim = false, + isNamed = false, + tagHandle, + tagName, + ch; + + ch = state.input.charCodeAt(state.position); + + if (ch !== 0x21/* ! */) return false; + + if (state.tag !== null) { + throwError(state, 'duplication of a tag property'); + } + + ch = state.input.charCodeAt(++state.position); + + if (ch === 0x3C/* < */) { + isVerbatim = true; + ch = state.input.charCodeAt(++state.position); + + } else if (ch === 0x21/* ! */) { + isNamed = true; + tagHandle = '!!'; + ch = state.input.charCodeAt(++state.position); + + } else { + tagHandle = '!'; + } + + _position = state.position; + + if (isVerbatim) { + do { ch = state.input.charCodeAt(++state.position); } + while (ch !== 0 && ch !== 0x3E/* > */); + + if (state.position < state.length) { + tagName = state.input.slice(_position, state.position); + ch = state.input.charCodeAt(++state.position); + } else { + throwError(state, 'unexpected end of the stream within a verbatim tag'); + } + } else { + while (ch !== 0 && !is_WS_OR_EOL(ch)) { + + if (ch === 0x21/* ! */) { + if (!isNamed) { + tagHandle = state.input.slice(_position - 1, state.position + 1); + + if (!PATTERN_TAG_HANDLE.test(tagHandle)) { + throwError(state, 'named tag handle cannot contain such characters'); + } + + isNamed = true; + _position = state.position + 1; + } else { + throwError(state, 'tag suffix cannot contain exclamation marks'); + } + } + + ch = state.input.charCodeAt(++state.position); + } + + tagName = state.input.slice(_position, state.position); + + if (PATTERN_FLOW_INDICATORS.test(tagName)) { + throwError(state, 'tag suffix cannot contain flow indicator characters'); + } + } + + if (tagName && !PATTERN_TAG_URI.test(tagName)) { + throwError(state, 'tag name cannot contain such characters: ' + tagName); + } + + if (isVerbatim) { + state.tag = tagName; + + } else if (_hasOwnProperty.call(state.tagMap, tagHandle)) { + state.tag = state.tagMap[tagHandle] + tagName; + + } else if (tagHandle === '!') { + state.tag = '!' + tagName; + + } else if (tagHandle === '!!') { + state.tag = 'tag:yaml.org,2002:' + tagName; + + } else { + throwError(state, 'undeclared tag handle "' + tagHandle + '"'); + } + + return true; +} + +function readAnchorProperty(state) { + var _position, + ch; + + ch = state.input.charCodeAt(state.position); + + if (ch !== 0x26/* & */) return false; + + if (state.anchor !== null) { + throwError(state, 'duplication of an anchor property'); + } + + ch = state.input.charCodeAt(++state.position); + _position = state.position; + + while (ch !== 0 && !is_WS_OR_EOL(ch) && !is_FLOW_INDICATOR(ch)) { + ch = state.input.charCodeAt(++state.position); + } + + if (state.position === _position) { + throwError(state, 'name of an anchor node must contain at least one character'); + } + + state.anchor = state.input.slice(_position, state.position); + return true; +} + +function readAlias(state) { + var _position, alias, + ch; + + ch = state.input.charCodeAt(state.position); + + if (ch !== 0x2A/* * */) return false; + + ch = state.input.charCodeAt(++state.position); + _position = state.position; + + while (ch !== 0 && !is_WS_OR_EOL(ch) && !is_FLOW_INDICATOR(ch)) { + ch = state.input.charCodeAt(++state.position); + } + + if (state.position === _position) { + throwError(state, 'name of an alias node must contain at least one character'); + } + + alias = state.input.slice(_position, state.position); + + if (!_hasOwnProperty.call(state.anchorMap, alias)) { + throwError(state, 'unidentified alias "' + alias + '"'); + } + + state.result = state.anchorMap[alias]; + skipSeparationSpace(state, true, -1); + return true; +} + +function composeNode(state, parentIndent, nodeContext, allowToSeek, allowCompact) { + var allowBlockStyles, + allowBlockScalars, + allowBlockCollections, + indentStatus = 1, // 1: this>parent, 0: this=parent, -1: this parentIndent) { + indentStatus = 1; + } else if (state.lineIndent === parentIndent) { + indentStatus = 0; + } else if (state.lineIndent < parentIndent) { + indentStatus = -1; + } + } + } + + if (indentStatus === 1) { + while (readTagProperty(state) || readAnchorProperty(state)) { + if (skipSeparationSpace(state, true, -1)) { + atNewLine = true; + allowBlockCollections = allowBlockStyles; + + if (state.lineIndent > parentIndent) { + indentStatus = 1; + } else if (state.lineIndent === parentIndent) { + indentStatus = 0; + } else if (state.lineIndent < parentIndent) { + indentStatus = -1; + } + } else { + allowBlockCollections = false; + } + } + } + + if (allowBlockCollections) { + allowBlockCollections = atNewLine || allowCompact; + } + + if (indentStatus === 1 || CONTEXT_BLOCK_OUT === nodeContext) { + if (CONTEXT_FLOW_IN === nodeContext || CONTEXT_FLOW_OUT === nodeContext) { + flowIndent = parentIndent; + } else { + flowIndent = parentIndent + 1; + } + + blockIndent = state.position - state.lineStart; + + if (indentStatus === 1) { + if (allowBlockCollections && + (readBlockSequence(state, blockIndent) || + readBlockMapping(state, blockIndent, flowIndent)) || + readFlowCollection(state, flowIndent)) { + hasContent = true; + } else { + if ((allowBlockScalars && readBlockScalar(state, flowIndent)) || + readSingleQuotedScalar(state, flowIndent) || + readDoubleQuotedScalar(state, flowIndent)) { + hasContent = true; + + } else if (readAlias(state)) { + hasContent = true; + + if (state.tag !== null || state.anchor !== null) { + throwError(state, 'alias node should not have any properties'); + } + + } else if (readPlainScalar(state, flowIndent, CONTEXT_FLOW_IN === nodeContext)) { + hasContent = true; + + if (state.tag === null) { + state.tag = '?'; + } + } + + if (state.anchor !== null) { + state.anchorMap[state.anchor] = state.result; + } + } + } else if (indentStatus === 0) { + // Special case: block sequences are allowed to have same indentation level as the parent. + // http://www.yaml.org/spec/1.2/spec.html#id2799784 + hasContent = allowBlockCollections && readBlockSequence(state, blockIndent); + } + } + + if (state.tag !== null && state.tag !== '!') { + if (state.tag === '?') { + // Implicit resolving is not allowed for non-scalar types, and '?' + // non-specific tag is only automatically assigned to plain scalars. + // + // We only need to check kind conformity in case user explicitly assigns '?' + // tag, for example like this: "! [0]" + // + if (state.result !== null && state.kind !== 'scalar') { + throwError(state, 'unacceptable node kind for ! tag; it should be "scalar", not "' + state.kind + '"'); + } + + for (typeIndex = 0, typeQuantity = state.implicitTypes.length; typeIndex < typeQuantity; typeIndex += 1) { + type = state.implicitTypes[typeIndex]; + + if (type.resolve(state.result)) { // `state.result` updated in resolver if matched + state.result = type.construct(state.result); + state.tag = type.tag; + if (state.anchor !== null) { + state.anchorMap[state.anchor] = state.result; + } + break; + } + } + } else if (_hasOwnProperty.call(state.typeMap[state.kind || 'fallback'], state.tag)) { + type = state.typeMap[state.kind || 'fallback'][state.tag]; + + if (state.result !== null && type.kind !== state.kind) { + throwError(state, 'unacceptable node kind for !<' + state.tag + '> tag; it should be "' + type.kind + '", not "' + state.kind + '"'); + } + + if (!type.resolve(state.result)) { // `state.result` updated in resolver if matched + throwError(state, 'cannot resolve a node with !<' + state.tag + '> explicit tag'); + } else { + state.result = type.construct(state.result); + if (state.anchor !== null) { + state.anchorMap[state.anchor] = state.result; + } + } + } else { + throwError(state, 'unknown tag !<' + state.tag + '>'); + } + } + + if (state.listener !== null) { + state.listener('close', state); + } + return state.tag !== null || state.anchor !== null || hasContent; +} + +function readDocument(state) { + var documentStart = state.position, + _position, + directiveName, + directiveArgs, + hasDirectives = false, + ch; + + state.version = null; + state.checkLineBreaks = state.legacy; + state.tagMap = {}; + state.anchorMap = {}; + + while ((ch = state.input.charCodeAt(state.position)) !== 0) { + skipSeparationSpace(state, true, -1); + + ch = state.input.charCodeAt(state.position); + + if (state.lineIndent > 0 || ch !== 0x25/* % */) { + break; + } + + hasDirectives = true; + ch = state.input.charCodeAt(++state.position); + _position = state.position; + + while (ch !== 0 && !is_WS_OR_EOL(ch)) { + ch = state.input.charCodeAt(++state.position); + } + + directiveName = state.input.slice(_position, state.position); + directiveArgs = []; + + if (directiveName.length < 1) { + throwError(state, 'directive name must not be less than one character in length'); + } + + while (ch !== 0) { + while (is_WHITE_SPACE(ch)) { + ch = state.input.charCodeAt(++state.position); + } + + if (ch === 0x23/* # */) { + do { ch = state.input.charCodeAt(++state.position); } + while (ch !== 0 && !is_EOL(ch)); + break; + } + + if (is_EOL(ch)) break; + + _position = state.position; + + while (ch !== 0 && !is_WS_OR_EOL(ch)) { + ch = state.input.charCodeAt(++state.position); + } + + directiveArgs.push(state.input.slice(_position, state.position)); + } + + if (ch !== 0) readLineBreak(state); + + if (_hasOwnProperty.call(directiveHandlers, directiveName)) { + directiveHandlers[directiveName](state, directiveName, directiveArgs); + } else { + throwWarning(state, 'unknown document directive "' + directiveName + '"'); + } + } + + skipSeparationSpace(state, true, -1); + + if (state.lineIndent === 0 && + state.input.charCodeAt(state.position) === 0x2D/* - */ && + state.input.charCodeAt(state.position + 1) === 0x2D/* - */ && + state.input.charCodeAt(state.position + 2) === 0x2D/* - */) { + state.position += 3; + skipSeparationSpace(state, true, -1); + + } else if (hasDirectives) { + throwError(state, 'directives end mark is expected'); + } + + composeNode(state, state.lineIndent - 1, CONTEXT_BLOCK_OUT, false, true); + skipSeparationSpace(state, true, -1); + + if (state.checkLineBreaks && + PATTERN_NON_ASCII_LINE_BREAKS.test(state.input.slice(documentStart, state.position))) { + throwWarning(state, 'non-ASCII line breaks are interpreted as content'); + } + + state.documents.push(state.result); + + if (state.position === state.lineStart && testDocumentSeparator(state)) { + + if (state.input.charCodeAt(state.position) === 0x2E/* . */) { + state.position += 3; + skipSeparationSpace(state, true, -1); + } + return; + } + + if (state.position < (state.length - 1)) { + throwError(state, 'end of the stream or a document separator is expected'); + } else { + return; + } +} + + +function loadDocuments(input, options) { + input = String(input); + options = options || {}; + + if (input.length !== 0) { + + // Add tailing `\n` if not exists + if (input.charCodeAt(input.length - 1) !== 0x0A/* LF */ && + input.charCodeAt(input.length - 1) !== 0x0D/* CR */) { + input += '\n'; + } + + // Strip BOM + if (input.charCodeAt(0) === 0xFEFF) { + input = input.slice(1); + } + } + + var state = new State(input, options); + + var nullpos = input.indexOf('\0'); + + if (nullpos !== -1) { + state.position = nullpos; + throwError(state, 'null byte is not allowed in input'); + } + + // Use 0 as string terminator. That significantly simplifies bounds check. + state.input += '\0'; + + while (state.input.charCodeAt(state.position) === 0x20/* Space */) { + state.lineIndent += 1; + state.position += 1; + } + + while (state.position < (state.length - 1)) { + readDocument(state); + } + + return state.documents; +} + + +function loadAll(input, iterator, options) { + if (iterator !== null && typeof iterator === 'object' && typeof options === 'undefined') { + options = iterator; + iterator = null; + } + + var documents = loadDocuments(input, options); + + if (typeof iterator !== 'function') { + return documents; + } + + for (var index = 0, length = documents.length; index < length; index += 1) { + iterator(documents[index]); + } +} + + +function load(input, options) { + var documents = loadDocuments(input, options); + + if (documents.length === 0) { + /*eslint-disable no-undefined*/ + return undefined; + } else if (documents.length === 1) { + return documents[0]; + } + throw new YAMLException('expected a single document in the stream, but found more'); +} + + +function safeLoadAll(input, iterator, options) { + if (typeof iterator === 'object' && iterator !== null && typeof options === 'undefined') { + options = iterator; + iterator = null; + } + + return loadAll(input, iterator, common.extend({ schema: DEFAULT_SAFE_SCHEMA }, options)); +} + + +function safeLoad(input, options) { + return load(input, common.extend({ schema: DEFAULT_SAFE_SCHEMA }, options)); +} + + +module.exports.loadAll = loadAll; +module.exports.load = load; +module.exports.safeLoadAll = safeLoadAll; +module.exports.safeLoad = safeLoad; + + +/***/ }), + +/***/ 45622: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + + +var common = __nccwpck_require__(24206); + + +function Mark(name, buffer, position, line, column) { + this.name = name; + this.buffer = buffer; + this.position = position; + this.line = line; + this.column = column; +} + + +Mark.prototype.getSnippet = function getSnippet(indent, maxLength) { + var head, start, tail, end, snippet; + + if (!this.buffer) return null; + + indent = indent || 4; + maxLength = maxLength || 75; + + head = ''; + start = this.position; + + while (start > 0 && '\x00\r\n\x85\u2028\u2029'.indexOf(this.buffer.charAt(start - 1)) === -1) { + start -= 1; + if (this.position - start > (maxLength / 2 - 1)) { + head = ' ... '; + start += 5; + break; + } + } + + tail = ''; + end = this.position; + + while (end < this.buffer.length && '\x00\r\n\x85\u2028\u2029'.indexOf(this.buffer.charAt(end)) === -1) { + end += 1; + if (end - this.position > (maxLength / 2 - 1)) { + tail = ' ... '; + end -= 5; + break; + } + } + + snippet = this.buffer.slice(start, end); + + return common.repeat(' ', indent) + head + snippet + tail + '\n' + + common.repeat(' ', indent + this.position - start + head.length) + '^'; +}; + + +Mark.prototype.toString = function toString(compact) { + var snippet, where = ''; + + if (this.name) { + where += 'in "' + this.name + '" '; + } + + where += 'at line ' + (this.line + 1) + ', column ' + (this.column + 1); + + if (!compact) { + snippet = this.getSnippet(); + + if (snippet) { + where += ':\n' + snippet; + } + } + + return where; +}; + + +module.exports = Mark; + + +/***/ }), + +/***/ 45868: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +/*eslint-disable max-len*/ + +var common = __nccwpck_require__(24206); +var YAMLException = __nccwpck_require__(15622); +var Type = __nccwpck_require__(50323); + + +function compileList(schema, name, result) { + var exclude = []; + + schema.include.forEach(function (includedSchema) { + result = compileList(includedSchema, name, result); + }); + + schema[name].forEach(function (currentType) { + result.forEach(function (previousType, previousIndex) { + if (previousType.tag === currentType.tag && previousType.kind === currentType.kind) { + exclude.push(previousIndex); + } + }); + + result.push(currentType); + }); + + return result.filter(function (type, index) { + return exclude.indexOf(index) === -1; + }); +} + + +function compileMap(/* lists... */) { + var result = { + scalar: {}, + sequence: {}, + mapping: {}, + fallback: {} + }, index, length; + + function collectType(type) { + result[type.kind][type.tag] = result['fallback'][type.tag] = type; + } + + for (index = 0, length = arguments.length; index < length; index += 1) { + arguments[index].forEach(collectType); + } + return result; +} + + +function Schema(definition) { + this.include = definition.include || []; + this.implicit = definition.implicit || []; + this.explicit = definition.explicit || []; + + this.implicit.forEach(function (type) { + if (type.loadKind && type.loadKind !== 'scalar') { + throw new YAMLException('There is a non-scalar type in the implicit list of a schema. Implicit resolving of such types is not supported.'); + } + }); + + this.compiledImplicit = compileList(this, 'implicit', []); + this.compiledExplicit = compileList(this, 'explicit', []); + this.compiledTypeMap = compileMap(this.compiledImplicit, this.compiledExplicit); +} + + +Schema.DEFAULT = null; + + +Schema.create = function createSchema() { + var schemas, types; + + switch (arguments.length) { + case 1: + schemas = Schema.DEFAULT; + types = arguments[0]; + break; + + case 2: + schemas = arguments[0]; + types = arguments[1]; + break; + + default: + throw new YAMLException('Wrong number of arguments for Schema.create function'); + } + + schemas = common.toArray(schemas); + types = common.toArray(types); + + if (!schemas.every(function (schema) { return schema instanceof Schema; })) { + throw new YAMLException('Specified list of super schemas (or a single Schema object) contains a non-Schema object.'); + } + + if (!types.every(function (type) { return type instanceof Type; })) { + throw new YAMLException('Specified list of YAML types (or a single Type object) contains a non-Type object.'); + } + + return new Schema({ + include: schemas, + explicit: types + }); +}; + + +module.exports = Schema; + + +/***/ }), + +/***/ 55116: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; +// Standard YAML's Core schema. +// http://www.yaml.org/spec/1.2/spec.html#id2804923 +// +// NOTE: JS-YAML does not support schema-specific tag resolution restrictions. +// So, Core schema has no distinctions from JSON schema is JS-YAML. + + + + + +var Schema = __nccwpck_require__(45868); + + +module.exports = new Schema({ + include: [ + __nccwpck_require__(96613) + ] +}); + + +/***/ }), + +/***/ 60948: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; +// JS-YAML's default schema for `load` function. +// It is not described in the YAML specification. +// +// This schema is based on JS-YAML's default safe schema and includes +// JavaScript-specific types: !!js/undefined, !!js/regexp and !!js/function. +// +// Also this schema is used as default base schema at `Schema.create` function. + + + + + +var Schema = __nccwpck_require__(45868); + + +module.exports = Schema.DEFAULT = new Schema({ + include: [ + __nccwpck_require__(76032) + ], + explicit: [ + __nccwpck_require__(49178), + __nccwpck_require__(98953), + __nccwpck_require__(31832) + ] +}); + + +/***/ }), + +/***/ 76032: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; +// JS-YAML's default schema for `safeLoad` function. +// It is not described in the YAML specification. +// +// This schema is based on standard YAML's Core schema and includes most of +// extra types described at YAML tag repository. (http://yaml.org/type/) + + + + + +var Schema = __nccwpck_require__(45868); + + +module.exports = new Schema({ + include: [ + __nccwpck_require__(55116) + ], + implicit: [ + __nccwpck_require__(37044), + __nccwpck_require__(70112) + ], + explicit: [ + __nccwpck_require__(95295), + __nccwpck_require__(47395), + __nccwpck_require__(59117), + __nccwpck_require__(17812) + ] +}); + + +/***/ }), + +/***/ 86810: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; +// Standard YAML's Failsafe schema. +// http://www.yaml.org/spec/1.2/spec.html#id2802346 + + + + + +var Schema = __nccwpck_require__(45868); + + +module.exports = new Schema({ + explicit: [ + __nccwpck_require__(94107), + __nccwpck_require__(12863), + __nccwpck_require__(54278) + ] +}); + + +/***/ }), + +/***/ 96613: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; +// Standard YAML's JSON schema. +// http://www.yaml.org/spec/1.2/spec.html#id2803231 +// +// NOTE: JS-YAML does not support schema-specific tag resolution restrictions. +// So, this schema is not such strict as defined in the YAML specification. +// It allows numbers in binary notaion, use `Null` and `NULL` as `null`, etc. + + + + + +var Schema = __nccwpck_require__(45868); + + +module.exports = new Schema({ + include: [ + __nccwpck_require__(86810) + ], + implicit: [ + __nccwpck_require__(37767), + __nccwpck_require__(23066), + __nccwpck_require__(90053), + __nccwpck_require__(13122) + ] +}); + + +/***/ }), + +/***/ 50323: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +var YAMLException = __nccwpck_require__(15622); + +var TYPE_CONSTRUCTOR_OPTIONS = [ + 'kind', + 'resolve', + 'construct', + 'instanceOf', + 'predicate', + 'represent', + 'defaultStyle', + 'styleAliases' +]; + +var YAML_NODE_KINDS = [ + 'scalar', + 'sequence', + 'mapping' +]; + +function compileStyleAliases(map) { + var result = {}; + + if (map !== null) { + Object.keys(map).forEach(function (style) { + map[style].forEach(function (alias) { + result[String(alias)] = style; + }); + }); + } + + return result; +} + +function Type(tag, options) { + options = options || {}; + + Object.keys(options).forEach(function (name) { + if (TYPE_CONSTRUCTOR_OPTIONS.indexOf(name) === -1) { + throw new YAMLException('Unknown option "' + name + '" is met in definition of "' + tag + '" YAML type.'); + } + }); + + // TODO: Add tag format check. + this.tag = tag; + this.kind = options['kind'] || null; + this.resolve = options['resolve'] || function () { return true; }; + this.construct = options['construct'] || function (data) { return data; }; + this.instanceOf = options['instanceOf'] || null; + this.predicate = options['predicate'] || null; + this.represent = options['represent'] || null; + this.defaultStyle = options['defaultStyle'] || null; + this.styleAliases = compileStyleAliases(options['styleAliases'] || null); + + if (YAML_NODE_KINDS.indexOf(this.kind) === -1) { + throw new YAMLException('Unknown kind "' + this.kind + '" is specified for "' + tag + '" YAML type.'); + } +} + +module.exports = Type; + + +/***/ }), + +/***/ 95295: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +/*eslint-disable no-bitwise*/ + +var NodeBuffer; + +try { + // A trick for browserified version, to not include `Buffer` shim + var _require = require; + NodeBuffer = _require('buffer').Buffer; +} catch (__) {} + +var Type = __nccwpck_require__(50323); + + +// [ 64, 65, 66 ] -> [ padding, CR, LF ] +var BASE64_MAP = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=\n\r'; + + +function resolveYamlBinary(data) { + if (data === null) return false; + + var code, idx, bitlen = 0, max = data.length, map = BASE64_MAP; + + // Convert one by one. + for (idx = 0; idx < max; idx++) { + code = map.indexOf(data.charAt(idx)); + + // Skip CR/LF + if (code > 64) continue; + + // Fail on illegal characters + if (code < 0) return false; + + bitlen += 6; + } + + // If there are any bits left, source was corrupted + return (bitlen % 8) === 0; +} + +function constructYamlBinary(data) { + var idx, tailbits, + input = data.replace(/[\r\n=]/g, ''), // remove CR/LF & padding to simplify scan + max = input.length, + map = BASE64_MAP, + bits = 0, + result = []; + + // Collect by 6*4 bits (3 bytes) + + for (idx = 0; idx < max; idx++) { + if ((idx % 4 === 0) && idx) { + result.push((bits >> 16) & 0xFF); + result.push((bits >> 8) & 0xFF); + result.push(bits & 0xFF); + } + + bits = (bits << 6) | map.indexOf(input.charAt(idx)); + } + + // Dump tail + + tailbits = (max % 4) * 6; + + if (tailbits === 0) { + result.push((bits >> 16) & 0xFF); + result.push((bits >> 8) & 0xFF); + result.push(bits & 0xFF); + } else if (tailbits === 18) { + result.push((bits >> 10) & 0xFF); + result.push((bits >> 2) & 0xFF); + } else if (tailbits === 12) { + result.push((bits >> 4) & 0xFF); + } + + // Wrap into Buffer for NodeJS and leave Array for browser + if (NodeBuffer) { + // Support node 6.+ Buffer API when available + return NodeBuffer.from ? NodeBuffer.from(result) : new NodeBuffer(result); + } + + return result; +} + +function representYamlBinary(object /*, style*/) { + var result = '', bits = 0, idx, tail, + max = object.length, + map = BASE64_MAP; + + // Convert every three bytes to 4 ASCII characters. + + for (idx = 0; idx < max; idx++) { + if ((idx % 3 === 0) && idx) { + result += map[(bits >> 18) & 0x3F]; + result += map[(bits >> 12) & 0x3F]; + result += map[(bits >> 6) & 0x3F]; + result += map[bits & 0x3F]; + } + + bits = (bits << 8) + object[idx]; + } + + // Dump tail + + tail = max % 3; + + if (tail === 0) { + result += map[(bits >> 18) & 0x3F]; + result += map[(bits >> 12) & 0x3F]; + result += map[(bits >> 6) & 0x3F]; + result += map[bits & 0x3F]; + } else if (tail === 2) { + result += map[(bits >> 10) & 0x3F]; + result += map[(bits >> 4) & 0x3F]; + result += map[(bits << 2) & 0x3F]; + result += map[64]; + } else if (tail === 1) { + result += map[(bits >> 2) & 0x3F]; + result += map[(bits << 4) & 0x3F]; + result += map[64]; + result += map[64]; + } + + return result; +} + +function isBinary(object) { + return NodeBuffer && NodeBuffer.isBuffer(object); +} + +module.exports = new Type('tag:yaml.org,2002:binary', { + kind: 'scalar', + resolve: resolveYamlBinary, + construct: constructYamlBinary, + predicate: isBinary, + represent: representYamlBinary +}); + + +/***/ }), + +/***/ 23066: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +var Type = __nccwpck_require__(50323); + +function resolveYamlBoolean(data) { + if (data === null) return false; + + var max = data.length; + + return (max === 4 && (data === 'true' || data === 'True' || data === 'TRUE')) || + (max === 5 && (data === 'false' || data === 'False' || data === 'FALSE')); +} + +function constructYamlBoolean(data) { + return data === 'true' || + data === 'True' || + data === 'TRUE'; +} + +function isBoolean(object) { + return Object.prototype.toString.call(object) === '[object Boolean]'; +} + +module.exports = new Type('tag:yaml.org,2002:bool', { + kind: 'scalar', + resolve: resolveYamlBoolean, + construct: constructYamlBoolean, + predicate: isBoolean, + represent: { + lowercase: function (object) { return object ? 'true' : 'false'; }, + uppercase: function (object) { return object ? 'TRUE' : 'FALSE'; }, + camelcase: function (object) { return object ? 'True' : 'False'; } + }, + defaultStyle: 'lowercase' +}); + + +/***/ }), + +/***/ 13122: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +var common = __nccwpck_require__(24206); +var Type = __nccwpck_require__(50323); + +var YAML_FLOAT_PATTERN = new RegExp( + // 2.5e4, 2.5 and integers + '^(?:[-+]?(?:0|[1-9][0-9_]*)(?:\\.[0-9_]*)?(?:[eE][-+]?[0-9]+)?' + + // .2e4, .2 + // special case, seems not from spec + '|\\.[0-9_]+(?:[eE][-+]?[0-9]+)?' + + // 20:59 + '|[-+]?[0-9][0-9_]*(?::[0-5]?[0-9])+\\.[0-9_]*' + + // .inf + '|[-+]?\\.(?:inf|Inf|INF)' + + // .nan + '|\\.(?:nan|NaN|NAN))$'); + +function resolveYamlFloat(data) { + if (data === null) return false; + + if (!YAML_FLOAT_PATTERN.test(data) || + // Quick hack to not allow integers end with `_` + // Probably should update regexp & check speed + data[data.length - 1] === '_') { + return false; + } + + return true; +} + +function constructYamlFloat(data) { + var value, sign, base, digits; + + value = data.replace(/_/g, '').toLowerCase(); + sign = value[0] === '-' ? -1 : 1; + digits = []; + + if ('+-'.indexOf(value[0]) >= 0) { + value = value.slice(1); + } + + if (value === '.inf') { + return (sign === 1) ? Number.POSITIVE_INFINITY : Number.NEGATIVE_INFINITY; + + } else if (value === '.nan') { + return NaN; + + } else if (value.indexOf(':') >= 0) { + value.split(':').forEach(function (v) { + digits.unshift(parseFloat(v, 10)); + }); + + value = 0.0; + base = 1; + + digits.forEach(function (d) { + value += d * base; + base *= 60; + }); + + return sign * value; + + } + return sign * parseFloat(value, 10); +} + + +var SCIENTIFIC_WITHOUT_DOT = /^[-+]?[0-9]+e/; + +function representYamlFloat(object, style) { + var res; + + if (isNaN(object)) { + switch (style) { + case 'lowercase': return '.nan'; + case 'uppercase': return '.NAN'; + case 'camelcase': return '.NaN'; + } + } else if (Number.POSITIVE_INFINITY === object) { + switch (style) { + case 'lowercase': return '.inf'; + case 'uppercase': return '.INF'; + case 'camelcase': return '.Inf'; + } + } else if (Number.NEGATIVE_INFINITY === object) { + switch (style) { + case 'lowercase': return '-.inf'; + case 'uppercase': return '-.INF'; + case 'camelcase': return '-.Inf'; + } + } else if (common.isNegativeZero(object)) { + return '-0.0'; + } + + res = object.toString(10); + + // JS stringifier can build scientific format without dots: 5e-100, + // while YAML requres dot: 5.e-100. Fix it with simple hack + + return SCIENTIFIC_WITHOUT_DOT.test(res) ? res.replace('e', '.e') : res; +} + +function isFloat(object) { + return (Object.prototype.toString.call(object) === '[object Number]') && + (object % 1 !== 0 || common.isNegativeZero(object)); +} + +module.exports = new Type('tag:yaml.org,2002:float', { + kind: 'scalar', + resolve: resolveYamlFloat, + construct: constructYamlFloat, + predicate: isFloat, + represent: representYamlFloat, + defaultStyle: 'lowercase' +}); + + +/***/ }), + +/***/ 90053: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +var common = __nccwpck_require__(24206); +var Type = __nccwpck_require__(50323); + +function isHexCode(c) { + return ((0x30/* 0 */ <= c) && (c <= 0x39/* 9 */)) || + ((0x41/* A */ <= c) && (c <= 0x46/* F */)) || + ((0x61/* a */ <= c) && (c <= 0x66/* f */)); +} + +function isOctCode(c) { + return ((0x30/* 0 */ <= c) && (c <= 0x37/* 7 */)); +} + +function isDecCode(c) { + return ((0x30/* 0 */ <= c) && (c <= 0x39/* 9 */)); +} + +function resolveYamlInteger(data) { + if (data === null) return false; + + var max = data.length, + index = 0, + hasDigits = false, + ch; + + if (!max) return false; + + ch = data[index]; + + // sign + if (ch === '-' || ch === '+') { + ch = data[++index]; + } + + if (ch === '0') { + // 0 + if (index + 1 === max) return true; + ch = data[++index]; + + // base 2, base 8, base 16 + + if (ch === 'b') { + // base 2 + index++; + + for (; index < max; index++) { + ch = data[index]; + if (ch === '_') continue; + if (ch !== '0' && ch !== '1') return false; + hasDigits = true; + } + return hasDigits && ch !== '_'; + } + + + if (ch === 'x') { + // base 16 + index++; + + for (; index < max; index++) { + ch = data[index]; + if (ch === '_') continue; + if (!isHexCode(data.charCodeAt(index))) return false; + hasDigits = true; + } + return hasDigits && ch !== '_'; + } + + // base 8 + for (; index < max; index++) { + ch = data[index]; + if (ch === '_') continue; + if (!isOctCode(data.charCodeAt(index))) return false; + hasDigits = true; + } + return hasDigits && ch !== '_'; + } + + // base 10 (except 0) or base 60 + + // value should not start with `_`; + if (ch === '_') return false; + + for (; index < max; index++) { + ch = data[index]; + if (ch === '_') continue; + if (ch === ':') break; + if (!isDecCode(data.charCodeAt(index))) { + return false; + } + hasDigits = true; + } + + // Should have digits and should not end with `_` + if (!hasDigits || ch === '_') return false; + + // if !base60 - done; + if (ch !== ':') return true; + + // base60 almost not used, no needs to optimize + return /^(:[0-5]?[0-9])+$/.test(data.slice(index)); +} + +function constructYamlInteger(data) { + var value = data, sign = 1, ch, base, digits = []; + + if (value.indexOf('_') !== -1) { + value = value.replace(/_/g, ''); + } + + ch = value[0]; + + if (ch === '-' || ch === '+') { + if (ch === '-') sign = -1; + value = value.slice(1); + ch = value[0]; + } + + if (value === '0') return 0; + + if (ch === '0') { + if (value[1] === 'b') return sign * parseInt(value.slice(2), 2); + if (value[1] === 'x') return sign * parseInt(value, 16); + return sign * parseInt(value, 8); + } + + if (value.indexOf(':') !== -1) { + value.split(':').forEach(function (v) { + digits.unshift(parseInt(v, 10)); + }); + + value = 0; + base = 1; + + digits.forEach(function (d) { + value += (d * base); + base *= 60; + }); + + return sign * value; + + } + + return sign * parseInt(value, 10); +} + +function isInteger(object) { + return (Object.prototype.toString.call(object)) === '[object Number]' && + (object % 1 === 0 && !common.isNegativeZero(object)); +} + +module.exports = new Type('tag:yaml.org,2002:int', { + kind: 'scalar', + resolve: resolveYamlInteger, + construct: constructYamlInteger, + predicate: isInteger, + represent: { + binary: function (obj) { return obj >= 0 ? '0b' + obj.toString(2) : '-0b' + obj.toString(2).slice(1); }, + octal: function (obj) { return obj >= 0 ? '0' + obj.toString(8) : '-0' + obj.toString(8).slice(1); }, + decimal: function (obj) { return obj.toString(10); }, + /* eslint-disable max-len */ + hexadecimal: function (obj) { return obj >= 0 ? '0x' + obj.toString(16).toUpperCase() : '-0x' + obj.toString(16).toUpperCase().slice(1); } + }, + defaultStyle: 'decimal', + styleAliases: { + binary: [ 2, 'bin' ], + octal: [ 8, 'oct' ], + decimal: [ 10, 'dec' ], + hexadecimal: [ 16, 'hex' ] + } +}); + + +/***/ }), + +/***/ 31832: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +var esprima; + +// Browserified version does not have esprima +// +// 1. For node.js just require module as deps +// 2. For browser try to require mudule via external AMD system. +// If not found - try to fallback to window.esprima. If not +// found too - then fail to parse. +// +try { + // workaround to exclude package from browserify list. + var _require = require; + esprima = _require('esprima'); +} catch (_) { + /* eslint-disable no-redeclare */ + /* global window */ + if (typeof window !== 'undefined') esprima = window.esprima; +} + +var Type = __nccwpck_require__(50323); + +function resolveJavascriptFunction(data) { + if (data === null) return false; + + try { + var source = '(' + data + ')', + ast = esprima.parse(source, { range: true }); + + if (ast.type !== 'Program' || + ast.body.length !== 1 || + ast.body[0].type !== 'ExpressionStatement' || + (ast.body[0].expression.type !== 'ArrowFunctionExpression' && + ast.body[0].expression.type !== 'FunctionExpression')) { + return false; + } + + return true; + } catch (err) { + return false; + } +} + +function constructJavascriptFunction(data) { + /*jslint evil:true*/ + + var source = '(' + data + ')', + ast = esprima.parse(source, { range: true }), + params = [], + body; + + if (ast.type !== 'Program' || + ast.body.length !== 1 || + ast.body[0].type !== 'ExpressionStatement' || + (ast.body[0].expression.type !== 'ArrowFunctionExpression' && + ast.body[0].expression.type !== 'FunctionExpression')) { + throw new Error('Failed to resolve function'); + } + + ast.body[0].expression.params.forEach(function (param) { + params.push(param.name); + }); + + body = ast.body[0].expression.body.range; + + // Esprima's ranges include the first '{' and the last '}' characters on + // function expressions. So cut them out. + if (ast.body[0].expression.body.type === 'BlockStatement') { + /*eslint-disable no-new-func*/ + return new Function(params, source.slice(body[0] + 1, body[1] - 1)); + } + // ES6 arrow functions can omit the BlockStatement. In that case, just return + // the body. + /*eslint-disable no-new-func*/ + return new Function(params, 'return ' + source.slice(body[0], body[1])); +} + +function representJavascriptFunction(object /*, style*/) { + return object.toString(); +} + +function isFunction(object) { + return Object.prototype.toString.call(object) === '[object Function]'; +} + +module.exports = new Type('tag:yaml.org,2002:js/function', { + kind: 'scalar', + resolve: resolveJavascriptFunction, + construct: constructJavascriptFunction, + predicate: isFunction, + represent: representJavascriptFunction +}); + + +/***/ }), + +/***/ 98953: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +var Type = __nccwpck_require__(50323); + +function resolveJavascriptRegExp(data) { + if (data === null) return false; + if (data.length === 0) return false; + + var regexp = data, + tail = /\/([gim]*)$/.exec(data), + modifiers = ''; + + // if regexp starts with '/' it can have modifiers and must be properly closed + // `/foo/gim` - modifiers tail can be maximum 3 chars + if (regexp[0] === '/') { + if (tail) modifiers = tail[1]; + + if (modifiers.length > 3) return false; + // if expression starts with /, is should be properly terminated + if (regexp[regexp.length - modifiers.length - 1] !== '/') return false; + } + + return true; +} + +function constructJavascriptRegExp(data) { + var regexp = data, + tail = /\/([gim]*)$/.exec(data), + modifiers = ''; + + // `/foo/gim` - tail can be maximum 4 chars + if (regexp[0] === '/') { + if (tail) modifiers = tail[1]; + regexp = regexp.slice(1, regexp.length - modifiers.length - 1); + } + + return new RegExp(regexp, modifiers); +} + +function representJavascriptRegExp(object /*, style*/) { + var result = '/' + object.source + '/'; + + if (object.global) result += 'g'; + if (object.multiline) result += 'm'; + if (object.ignoreCase) result += 'i'; + + return result; +} + +function isRegExp(object) { + return Object.prototype.toString.call(object) === '[object RegExp]'; +} + +module.exports = new Type('tag:yaml.org,2002:js/regexp', { + kind: 'scalar', + resolve: resolveJavascriptRegExp, + construct: constructJavascriptRegExp, + predicate: isRegExp, + represent: representJavascriptRegExp +}); + + +/***/ }), + +/***/ 49178: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +var Type = __nccwpck_require__(50323); + +function resolveJavascriptUndefined() { + return true; +} + +function constructJavascriptUndefined() { + /*eslint-disable no-undefined*/ + return undefined; +} + +function representJavascriptUndefined() { + return ''; +} + +function isUndefined(object) { + return typeof object === 'undefined'; +} + +module.exports = new Type('tag:yaml.org,2002:js/undefined', { + kind: 'scalar', + resolve: resolveJavascriptUndefined, + construct: constructJavascriptUndefined, + predicate: isUndefined, + represent: representJavascriptUndefined +}); + + +/***/ }), + +/***/ 54278: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +var Type = __nccwpck_require__(50323); + +module.exports = new Type('tag:yaml.org,2002:map', { + kind: 'mapping', + construct: function (data) { return data !== null ? data : {}; } +}); + + +/***/ }), + +/***/ 70112: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +var Type = __nccwpck_require__(50323); + +function resolveYamlMerge(data) { + return data === '<<' || data === null; +} + +module.exports = new Type('tag:yaml.org,2002:merge', { + kind: 'scalar', + resolve: resolveYamlMerge +}); + + +/***/ }), + +/***/ 37767: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +var Type = __nccwpck_require__(50323); + +function resolveYamlNull(data) { + if (data === null) return true; + + var max = data.length; + + return (max === 1 && data === '~') || + (max === 4 && (data === 'null' || data === 'Null' || data === 'NULL')); +} + +function constructYamlNull() { + return null; +} + +function isNull(object) { + return object === null; +} + +module.exports = new Type('tag:yaml.org,2002:null', { + kind: 'scalar', + resolve: resolveYamlNull, + construct: constructYamlNull, + predicate: isNull, + represent: { + canonical: function () { return '~'; }, + lowercase: function () { return 'null'; }, + uppercase: function () { return 'NULL'; }, + camelcase: function () { return 'Null'; } + }, + defaultStyle: 'lowercase' +}); + + +/***/ }), + +/***/ 47395: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +var Type = __nccwpck_require__(50323); + +var _hasOwnProperty = Object.prototype.hasOwnProperty; +var _toString = Object.prototype.toString; + +function resolveYamlOmap(data) { + if (data === null) return true; + + var objectKeys = [], index, length, pair, pairKey, pairHasKey, + object = data; + + for (index = 0, length = object.length; index < length; index += 1) { + pair = object[index]; + pairHasKey = false; + + if (_toString.call(pair) !== '[object Object]') return false; + + for (pairKey in pair) { + if (_hasOwnProperty.call(pair, pairKey)) { + if (!pairHasKey) pairHasKey = true; + else return false; + } + } + + if (!pairHasKey) return false; + + if (objectKeys.indexOf(pairKey) === -1) objectKeys.push(pairKey); + else return false; + } + + return true; +} + +function constructYamlOmap(data) { + return data !== null ? data : []; +} + +module.exports = new Type('tag:yaml.org,2002:omap', { + kind: 'sequence', + resolve: resolveYamlOmap, + construct: constructYamlOmap +}); + + +/***/ }), + +/***/ 59117: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +var Type = __nccwpck_require__(50323); + +var _toString = Object.prototype.toString; + +function resolveYamlPairs(data) { + if (data === null) return true; + + var index, length, pair, keys, result, + object = data; + + result = new Array(object.length); + + for (index = 0, length = object.length; index < length; index += 1) { + pair = object[index]; + + if (_toString.call(pair) !== '[object Object]') return false; + + keys = Object.keys(pair); + + if (keys.length !== 1) return false; + + result[index] = [ keys[0], pair[keys[0]] ]; + } + + return true; +} + +function constructYamlPairs(data) { + if (data === null) return []; + + var index, length, pair, keys, result, + object = data; + + result = new Array(object.length); + + for (index = 0, length = object.length; index < length; index += 1) { + pair = object[index]; + + keys = Object.keys(pair); + + result[index] = [ keys[0], pair[keys[0]] ]; + } + + return result; +} + +module.exports = new Type('tag:yaml.org,2002:pairs', { + kind: 'sequence', + resolve: resolveYamlPairs, + construct: constructYamlPairs +}); + + +/***/ }), + +/***/ 12863: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +var Type = __nccwpck_require__(50323); + +module.exports = new Type('tag:yaml.org,2002:seq', { + kind: 'sequence', + construct: function (data) { return data !== null ? data : []; } +}); + + +/***/ }), + +/***/ 17812: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +var Type = __nccwpck_require__(50323); + +var _hasOwnProperty = Object.prototype.hasOwnProperty; + +function resolveYamlSet(data) { + if (data === null) return true; + + var key, object = data; + + for (key in object) { + if (_hasOwnProperty.call(object, key)) { + if (object[key] !== null) return false; + } + } + + return true; +} + +function constructYamlSet(data) { + return data !== null ? data : {}; +} + +module.exports = new Type('tag:yaml.org,2002:set', { + kind: 'mapping', + resolve: resolveYamlSet, + construct: constructYamlSet +}); + + +/***/ }), + +/***/ 94107: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +var Type = __nccwpck_require__(50323); + +module.exports = new Type('tag:yaml.org,2002:str', { + kind: 'scalar', + construct: function (data) { return data !== null ? data : ''; } +}); + + +/***/ }), + +/***/ 37044: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +var Type = __nccwpck_require__(50323); + +var YAML_DATE_REGEXP = new RegExp( + '^([0-9][0-9][0-9][0-9])' + // [1] year + '-([0-9][0-9])' + // [2] month + '-([0-9][0-9])$'); // [3] day + +var YAML_TIMESTAMP_REGEXP = new RegExp( + '^([0-9][0-9][0-9][0-9])' + // [1] year + '-([0-9][0-9]?)' + // [2] month + '-([0-9][0-9]?)' + // [3] day + '(?:[Tt]|[ \\t]+)' + // ... + '([0-9][0-9]?)' + // [4] hour + ':([0-9][0-9])' + // [5] minute + ':([0-9][0-9])' + // [6] second + '(?:\\.([0-9]*))?' + // [7] fraction + '(?:[ \\t]*(Z|([-+])([0-9][0-9]?)' + // [8] tz [9] tz_sign [10] tz_hour + '(?::([0-9][0-9]))?))?$'); // [11] tz_minute + +function resolveYamlTimestamp(data) { + if (data === null) return false; + if (YAML_DATE_REGEXP.exec(data) !== null) return true; + if (YAML_TIMESTAMP_REGEXP.exec(data) !== null) return true; + return false; +} + +function constructYamlTimestamp(data) { + var match, year, month, day, hour, minute, second, fraction = 0, + delta = null, tz_hour, tz_minute, date; + + match = YAML_DATE_REGEXP.exec(data); + if (match === null) match = YAML_TIMESTAMP_REGEXP.exec(data); + + if (match === null) throw new Error('Date resolve error'); + + // match: [1] year [2] month [3] day + + year = +(match[1]); + month = +(match[2]) - 1; // JS month starts with 0 + day = +(match[3]); + + if (!match[4]) { // no hour + return new Date(Date.UTC(year, month, day)); + } + + // match: [4] hour [5] minute [6] second [7] fraction + + hour = +(match[4]); + minute = +(match[5]); + second = +(match[6]); + + if (match[7]) { + fraction = match[7].slice(0, 3); + while (fraction.length < 3) { // milli-seconds + fraction += '0'; + } + fraction = +fraction; + } + + // match: [8] tz [9] tz_sign [10] tz_hour [11] tz_minute + + if (match[9]) { + tz_hour = +(match[10]); + tz_minute = +(match[11] || 0); + delta = (tz_hour * 60 + tz_minute) * 60000; // delta in mili-seconds + if (match[9] === '-') delta = -delta; + } + + date = new Date(Date.UTC(year, month, day, hour, minute, second, fraction)); + + if (delta) date.setTime(date.getTime() - delta); + + return date; +} + +function representYamlTimestamp(object /*, style*/) { + return object.toISOString(); +} + +module.exports = new Type('tag:yaml.org,2002:timestamp', { + kind: 'scalar', + resolve: resolveYamlTimestamp, + construct: constructYamlTimestamp, + instanceOf: Date, + represent: representYamlTimestamp +}); + + +/***/ }), + +/***/ 59376: +/***/ ((module) => { + +"use strict"; + + +const object = {}; +const hasOwnProperty = object.hasOwnProperty; +const forOwn = (object, callback) => { + for (const key in object) { + if (hasOwnProperty.call(object, key)) { + callback(key, object[key]); + } + } +}; + +const extend = (destination, source) => { + if (!source) { + return destination; + } + forOwn(source, (key, value) => { + destination[key] = value; + }); + return destination; +}; + +const forEach = (array, callback) => { + const length = array.length; + let index = -1; + while (++index < length) { + callback(array[index]); + } +}; + +const fourHexEscape = (hex) => { + return '\\u' + ('0000' + hex).slice(-4); +} + +const hexadecimal = (code, lowercase) => { + let hexadecimal = code.toString(16); + if (lowercase) return hexadecimal; + return hexadecimal.toUpperCase(); +}; + +const toString = object.toString; +const isArray = Array.isArray; +const isBuffer = (value) => { + return typeof Buffer === 'function' && Buffer.isBuffer(value); +}; +const isObject = (value) => { + // This is a very simple check, but it’s good enough for what we need. + return toString.call(value) == '[object Object]'; +}; +const isString = (value) => { + return typeof value == 'string' || + toString.call(value) == '[object String]'; +}; +const isNumber = (value) => { + return typeof value == 'number' || + toString.call(value) == '[object Number]'; +}; +const isBigInt = (value) => { + return typeof value == 'bigint'; +}; +const isFunction = (value) => { + return typeof value == 'function'; +}; +const isMap = (value) => { + return toString.call(value) == '[object Map]'; +}; +const isSet = (value) => { + return toString.call(value) == '[object Set]'; +}; + +/*--------------------------------------------------------------------------*/ + +// https://mathiasbynens.be/notes/javascript-escapes#single +const singleEscapes = { + '\\': '\\\\', + '\b': '\\b', + '\f': '\\f', + '\n': '\\n', + '\r': '\\r', + '\t': '\\t' + // `\v` is omitted intentionally, because in IE < 9, '\v' == 'v'. + // '\v': '\\x0B' +}; +const regexSingleEscape = /[\\\b\f\n\r\t]/; + +const regexDigit = /[0-9]/; +const regexWhitespace = /[\xA0\u1680\u2000-\u200A\u2028\u2029\u202F\u205F\u3000]/; + +const escapeEverythingRegex = /([\uD800-\uDBFF][\uDC00-\uDFFF])|([\uD800-\uDFFF])|(['"`])|[^]/g; +const escapeNonAsciiRegex = /([\uD800-\uDBFF][\uDC00-\uDFFF])|([\uD800-\uDFFF])|(['"`])|[^ !#-&\(-\[\]-_a-~]/g; + +const jsesc = (argument, options) => { + const increaseIndentation = () => { + oldIndent = indent; + ++options.indentLevel; + indent = options.indent.repeat(options.indentLevel) + }; + // Handle options + const defaults = { + 'escapeEverything': false, + 'minimal': false, + 'isScriptContext': false, + 'quotes': 'single', + 'wrap': false, + 'es6': false, + 'json': false, + 'compact': true, + 'lowercaseHex': false, + 'numbers': 'decimal', + 'indent': '\t', + 'indentLevel': 0, + '__inline1__': false, + '__inline2__': false + }; + const json = options && options.json; + if (json) { + defaults.quotes = 'double'; + defaults.wrap = true; + } + options = extend(defaults, options); + if ( + options.quotes != 'single' && + options.quotes != 'double' && + options.quotes != 'backtick' + ) { + options.quotes = 'single'; + } + const quote = options.quotes == 'double' ? + '"' : + (options.quotes == 'backtick' ? + '`' : + '\'' + ); + const compact = options.compact; + const lowercaseHex = options.lowercaseHex; + let indent = options.indent.repeat(options.indentLevel); + let oldIndent = ''; + const inline1 = options.__inline1__; + const inline2 = options.__inline2__; + const newLine = compact ? '' : '\n'; + let result; + let isEmpty = true; + const useBinNumbers = options.numbers == 'binary'; + const useOctNumbers = options.numbers == 'octal'; + const useDecNumbers = options.numbers == 'decimal'; + const useHexNumbers = options.numbers == 'hexadecimal'; + + if (json && argument && isFunction(argument.toJSON)) { + argument = argument.toJSON(); + } + + if (!isString(argument)) { + if (isMap(argument)) { + if (argument.size == 0) { + return 'new Map()'; + } + if (!compact) { + options.__inline1__ = true; + options.__inline2__ = false; + } + return 'new Map(' + jsesc(Array.from(argument), options) + ')'; + } + if (isSet(argument)) { + if (argument.size == 0) { + return 'new Set()'; + } + return 'new Set(' + jsesc(Array.from(argument), options) + ')'; + } + if (isBuffer(argument)) { + if (argument.length == 0) { + return 'Buffer.from([])'; + } + return 'Buffer.from(' + jsesc(Array.from(argument), options) + ')'; + } + if (isArray(argument)) { + result = []; + options.wrap = true; + if (inline1) { + options.__inline1__ = false; + options.__inline2__ = true; + } + if (!inline2) { + increaseIndentation(); + } + forEach(argument, (value) => { + isEmpty = false; + if (inline2) { + options.__inline2__ = false; + } + result.push( + (compact || inline2 ? '' : indent) + + jsesc(value, options) + ); + }); + if (isEmpty) { + return '[]'; + } + if (inline2) { + return '[' + result.join(', ') + ']'; + } + return '[' + newLine + result.join(',' + newLine) + newLine + + (compact ? '' : oldIndent) + ']'; + } else if (isNumber(argument) || isBigInt(argument)) { + if (json) { + // Some number values (e.g. `Infinity`) cannot be represented in JSON. + // `BigInt` values less than `-Number.MAX_VALUE` or greater than + // `Number.MAX_VALUE` cannot be represented in JSON so they will become + // `-Infinity` or `Infinity`, respectively, and then become `null` when + // stringified. + return JSON.stringify(Number(argument)); + } + + let result; + if (useDecNumbers) { + result = String(argument); + } else if (useHexNumbers) { + let hexadecimal = argument.toString(16); + if (!lowercaseHex) { + hexadecimal = hexadecimal.toUpperCase(); + } + result = '0x' + hexadecimal; + } else if (useBinNumbers) { + result = '0b' + argument.toString(2); + } else if (useOctNumbers) { + result = '0o' + argument.toString(8); + } + + if (isBigInt(argument)) { + return result + 'n'; + } + return result; + } else if (isBigInt(argument)) { + if (json) { + // `BigInt` values less than `-Number.MAX_VALUE` or greater than + // `Number.MAX_VALUE` will become `-Infinity` or `Infinity`, + // respectively, and cannot be represented in JSON. + return JSON.stringify(Number(argument)); + } + return argument + 'n'; + } else if (!isObject(argument)) { + if (json) { + // For some values (e.g. `undefined`, `function` objects), + // `JSON.stringify(value)` returns `undefined` (which isn’t valid + // JSON) instead of `'null'`. + return JSON.stringify(argument) || 'null'; + } + return String(argument); + } else { // it’s an object + result = []; + options.wrap = true; + increaseIndentation(); + forOwn(argument, (key, value) => { + isEmpty = false; + result.push( + (compact ? '' : indent) + + jsesc(key, options) + ':' + + (compact ? '' : ' ') + + jsesc(value, options) + ); + }); + if (isEmpty) { + return '{}'; + } + return '{' + newLine + result.join(',' + newLine) + newLine + + (compact ? '' : oldIndent) + '}'; + } + } + + const regex = options.escapeEverything ? escapeEverythingRegex : escapeNonAsciiRegex; + result = argument.replace(regex, (char, pair, lone, quoteChar, index, string) => { + if (pair) { + if (options.minimal) return pair; + const first = pair.charCodeAt(0); + const second = pair.charCodeAt(1); + if (options.es6) { + // https://mathiasbynens.be/notes/javascript-encoding#surrogate-formulae + const codePoint = (first - 0xD800) * 0x400 + second - 0xDC00 + 0x10000; + const hex = hexadecimal(codePoint, lowercaseHex); + return '\\u{' + hex + '}'; + } + return fourHexEscape(hexadecimal(first, lowercaseHex)) + fourHexEscape(hexadecimal(second, lowercaseHex)); + } + + if (lone) { + return fourHexEscape(hexadecimal(lone.charCodeAt(0), lowercaseHex)); + } + + if ( + char == '\0' && + !json && + !regexDigit.test(string.charAt(index + 1)) + ) { + return '\\0'; + } + + if (quoteChar) { + if (quoteChar == quote || options.escapeEverything) { + return '\\' + quoteChar; + } + return quoteChar; + } + + if (regexSingleEscape.test(char)) { + // no need for a `hasOwnProperty` check here + return singleEscapes[char]; + } + + if (options.minimal && !regexWhitespace.test(char)) { + return char; + } + + const hex = hexadecimal(char.charCodeAt(0), lowercaseHex); + if (json || hex.length > 2) { + return fourHexEscape(hex); + } + + return '\\x' + ('00' + hex).slice(-2); + }); + + if (quote == '`') { + result = result.replace(/\$\{/g, '\\${'); + } + if (options.isScriptContext) { + // https://mathiasbynens.be/notes/etago + result = result + .replace(/<\/(script|style)/gi, '<\\/$1') + .replace(// +const instructionOpenExpression = /^<\?/ +const instructionCloseExpression = /\?>/ +const directiveOpenExpression = /^/ +const cdataOpenExpression = /^/ +const elementCloseExpression = /^$/ +const otherElementOpenExpression = new RegExp(openCloseTag.source + '\\s*$') +const fragmentOpenExpression = /^<>/ + +function blockHtml(eat, value, silent) { + const blocks = '[a-z\\.]*(\\.){0,1}[a-z][a-z0-9\\.]*' + const elementOpenExpression = new RegExp( + '^|$))', + 'i' + ) + + const length = value.length + let index = 0 + let next + let line + let offset + let character + let count + let sequence + let subvalue + + const sequences = [ + [rawOpenExpression, rawCloseExpression, true], + [commentOpenExpression, commentCloseExpression, true], + [instructionOpenExpression, instructionCloseExpression, true], + [directiveOpenExpression, directiveCloseExpression, true], + [cdataOpenExpression, cdataCloseExpression, true], + [elementOpenExpression, elementCloseExpression, true], + [fragmentOpenExpression, elementCloseExpression, true], + [otherElementOpenExpression, elementCloseExpression, false] + ] + + // Eat initial spacing. + while (index < length) { + character = value.charAt(index) + + if (character !== tab && character !== space) { + break + } + + index++ + } + + if (value.charAt(index) !== lessThan) { + return + } + + next = value.indexOf(lineFeed, index + 1) + next = next === -1 ? length : next + line = value.slice(index, next) + offset = -1 + count = sequences.length + + while (++offset < count) { + if (sequences[offset][0].test(line)) { + sequence = sequences[offset] + break + } + } + + if (!sequence) { + return + } + + if (silent) { + return sequence[2] + } + + index = next + + if (!sequence[1].test(line)) { + while (index < length) { + next = value.indexOf(lineFeed, index + 1) + next = next === -1 ? length : next + line = value.slice(index + 1, next) + + if (sequence[1].test(line)) { + if (line) { + index = next + } + + break + } + + index = next + } + } + + subvalue = value.slice(0, index) + + return eat(subvalue)({type: 'html', value: subvalue}) +} + + +/***/ }), + +/***/ 66226: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +const {transformSync} = __nccwpck_require__(85414) +const declare = (__nccwpck_require__(25683).declare) + +const syntaxJsxPlugin = __nccwpck_require__(47358) +const proposalObjectRestSpreadPlugin = __nccwpck_require__(16186) + +class BabelPluginExtractImportsAndExports { + constructor() { + const nodes = [] + this.state = {nodes} + + this.plugin = declare(api => { + api.assertVersion(7) + + return { + visitor: { + ExportDefaultDeclaration(path) { + const {start} = path.node + nodes.push({type: 'export', start, default: true}) + }, + ExportNamedDeclaration(path) { + const {start} = path.node + nodes.push({type: 'export', start}) + }, + ExportAllDeclaration(path) { + const {start} = path.node + nodes.push({type: 'export', start}) + }, + ImportDeclaration(path) { + const {start} = path.node + + // Imports that are used in exports can end up as + // ImportDeclarations with no start/end metadata, + // these can be ignored + if (start === undefined) { + return + } + + nodes.push({type: 'import', start}) + } + } + } + }) + } +} + +const partitionString = (str, indices) => + indices.map((val, i) => { + return str.slice(val, indices[i + 1]) + }) + +module.exports = (value, vfile) => { + const instance = new BabelPluginExtractImportsAndExports() + + transformSync(value, { + plugins: [syntaxJsxPlugin, proposalObjectRestSpreadPlugin, instance.plugin], + filename: vfile.path, + configFile: false, + babelrc: false + }) + + const sortedNodes = instance.state.nodes.sort((a, b) => a.start - b.start) + const nodeStarts = sortedNodes.map(n => n.start) + const values = partitionString(value, nodeStarts) + + const allNodes = sortedNodes.map(({start: _, ...node}, i) => { + const value = values[i] + return {...node, value} + }) + + // Group adjacent nodes of the same type so that they can be combined + // into a single node later, this also ensures that order is preserved + let currType = allNodes[0].type + const groupedNodes = allNodes.reduce( + (acc, curr) => { + // Default export nodes shouldn't be grouped with other exports + // because they're handled specially by MDX + if (curr.default) { + currType = 'default' + return [...acc, [curr]] + } + + if (curr.type === currType) { + const lastNodes = acc.pop() + return [...acc, [...lastNodes, curr]] + } + + currType = curr.type + return [...acc, [curr]] + }, + [[]] + ) + + // Combine adjacent nodes into a single node + return groupedNodes + .filter(a => a.length) + .reduce((acc, curr) => { + const node = curr.reduce((acc, curr) => ({ + ...acc, + value: acc.value + curr.value + })) + + return [...acc, node] + }, []) +} + + +/***/ }), + +/***/ 47926: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +const isAlphabetical = __nccwpck_require__(59201) +const {isImportOrExport, EMPTY_NEWLINE} = __nccwpck_require__(73654) +const extractImportsAndExports = __nccwpck_require__(66226) +const block = __nccwpck_require__(79719) +const {tag} = __nccwpck_require__(59982) + +const LESS_THAN = '<' +const GREATER_THAN = '>' +const SLASH = '/' +const EXCLAMATION = '!' + +module.exports = mdx + +mdx.default = mdx + +tokenizeEsSyntax.locator = tokenizeEsSyntaxLocator + +function mdx(_options) { + const parser = this.Parser + const compiler = this.Compiler + + if (parser && parser.prototype && parser.prototype.blockTokenizers) { + attachParser(parser) + } + + if (compiler && compiler.prototype && compiler.prototype.visitors) { + attachCompiler(compiler) + } +} + +function attachParser(parser) { + const blocks = parser.prototype.blockTokenizers + const inlines = parser.prototype.inlineTokenizers + const methods = parser.prototype.blockMethods + + blocks.esSyntax = tokenizeEsSyntax + blocks.html = wrap(block) + inlines.html = wrap(inlines.html, inlineJsx) + + tokenizeEsSyntax.notInBlock = true + + methods.splice(methods.indexOf('paragraph'), 0, 'esSyntax') + + function wrap(original, customTokenizer) { + const tokenizer = customTokenizer || tokenizeJsx + tokenizer.locator = original.locator + + return tokenizer + + function tokenizeJsx() { + const node = original.apply(this, arguments) + + if (node) { + node.type = 'jsx' + } + + return node + } + } + + function inlineJsx(eat, value) { + if (value.charAt(0) !== LESS_THAN) { + return + } + + const nextChar = value.charAt(1) + if ( + nextChar !== GREATER_THAN && + nextChar !== SLASH && + nextChar !== EXCLAMATION && + !isAlphabetical(nextChar) + ) { + return + } + + const subvalueMatches = value.match(tag) + if (!subvalueMatches) { + return + } + + const subvalue = subvalueMatches[0] + return eat(subvalue)({type: 'jsx', value: subvalue}) + } +} + +function attachCompiler(compiler) { + const proto = compiler.prototype + + proto.visitors = Object.assign({}, proto.visitors, { + import: stringifyEsSyntax, + export: stringifyEsSyntax, + jsx: stringifyEsSyntax + }) +} + +function stringifyEsSyntax(node) { + return node.value.trim() +} + +function tokenizeEsSyntax(eat, value) { + const index = value.indexOf(EMPTY_NEWLINE) + const subvalue = index !== -1 ? value.slice(0, index) : value + + if (isImportOrExport(subvalue)) { + const nodes = extractImportsAndExports(subvalue, this.file) + nodes.map(node => eat(node.value)(node)) + } +} + +function tokenizeEsSyntaxLocator(value, _fromIndex) { + return isImportOrExport(value) ? -1 : 1 +} + + +/***/ }), + +/***/ 59982: +/***/ ((__unused_webpack_module, exports) => { + +// Source copied and then modified from +// https://github.com/remarkjs/remark/blob/master/packages/remark-parse/lib/util/html.js +// +// MIT License https://github.com/remarkjs/remark/blob/master/license + +// https://github.com/DmitrySoshnikov/babel-plugin-transform-modern-regexp#dotall-s-flag +// Firefox and other browsers don't support the dotAll ("s") flag, but it can be polyfilled via this: +const dotAllPolyfill = '[\0-\uFFFF]' + +const attributeName = '[a-zA-Z_:][a-zA-Z0-9:._-]*' +const unquoted = '[^"\'=<>`\\u0000-\\u0020]+' +const singleQuoted = "'[^']*'" +const doubleQuoted = '"[^"]*"' +const jsProps = '{.*}'.replace('.', dotAllPolyfill) +const attributeValue = + '(?:' + + unquoted + + '|' + + singleQuoted + + '|' + + doubleQuoted + + '|' + + jsProps + + ')' +const attribute = + '(?:\\s+' + attributeName + '(?:\\s*=\\s*' + attributeValue + ')?)' +const openTag = '<[A-Za-z]*[A-Za-z0-9\\.\\-]*' + attribute + '*\\s*\\/?>' +const closeTag = '<\\/[A-Za-z][A-Za-z0-9\\.\\-]*\\s*>' +const comment = '|' +const processing = '<[?].*?[?]>'.replace('.', dotAllPolyfill) +const declaration = ']*>' +const cdata = '' + +exports.openCloseTag = new RegExp('^(?:' + openTag + '|' + closeTag + ')') + +exports.tag = new RegExp( + '^(?:' + + openTag + + '|' + + closeTag + + '|' + + comment + + '|' + + processing + + '|' + + declaration + + '|' + + cdata + + ')' +) + + +/***/ }), + +/***/ 24982: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +var unherit = __nccwpck_require__(25351) +var xtend = __nccwpck_require__(80869) +var Parser = __nccwpck_require__(39651) + +module.exports = parse +parse.Parser = Parser + +function parse(options) { + var settings = this.data('settings') + var Local = unherit(Parser) + + Local.prototype.options = xtend(Local.prototype.options, settings, options) + + this.Parser = Local +} + + +/***/ }), + +/***/ 42029: +/***/ ((module) => { + +"use strict"; + + +module.exports = [ + 'address', + 'article', + 'aside', + 'base', + 'basefont', + 'blockquote', + 'body', + 'caption', + 'center', + 'col', + 'colgroup', + 'dd', + 'details', + 'dialog', + 'dir', + 'div', + 'dl', + 'dt', + 'fieldset', + 'figcaption', + 'figure', + 'footer', + 'form', + 'frame', + 'frameset', + 'h1', + 'h2', + 'h3', + 'h4', + 'h5', + 'h6', + 'head', + 'header', + 'hgroup', + 'hr', + 'html', + 'iframe', + 'legend', + 'li', + 'link', + 'main', + 'menu', + 'menuitem', + 'meta', + 'nav', + 'noframes', + 'ol', + 'optgroup', + 'option', + 'p', + 'param', + 'pre', + 'section', + 'source', + 'title', + 'summary', + 'table', + 'tbody', + 'td', + 'tfoot', + 'th', + 'thead', + 'title', + 'tr', + 'track', + 'ul' +] + + +/***/ }), + +/***/ 18934: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +var xtend = __nccwpck_require__(80869) +var entities = __nccwpck_require__(75165) + +module.exports = factory + +// Factory to create an entity decoder. +function factory(ctx) { + decoder.raw = decodeRaw + + return decoder + + // Normalize `position` to add an `indent`. + function normalize(position) { + var offsets = ctx.offset + var line = position.line + var result = [] + + while (++line) { + if (!(line in offsets)) { + break + } + + result.push((offsets[line] || 0) + 1) + } + + return {start: position, indent: result} + } + + // Decode `value` (at `position`) into text-nodes. + function decoder(value, position, handler) { + entities(value, { + position: normalize(position), + warning: handleWarning, + text: handler, + reference: handler, + textContext: ctx, + referenceContext: ctx + }) + } + + // Decode `value` (at `position`) into a string. + function decodeRaw(value, position, options) { + return entities( + value, + xtend(options, {position: normalize(position), warning: handleWarning}) + ) + } + + // Handle a warning. + // See for the warnings. + function handleWarning(reason, position, code) { + if (code !== 3) { + ctx.file.message(reason, position) + } + } +} + + +/***/ }), + +/***/ 96206: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +module.exports = { + position: true, + gfm: true, + commonmark: false, + pedantic: false, + blocks: __nccwpck_require__(42029) +} + + +/***/ }), + +/***/ 91702: +/***/ ((module) => { + +"use strict"; + + +module.exports = locate + +function locate(value, fromIndex) { + var index = value.indexOf('\n', fromIndex) + + while (index > fromIndex) { + if (value.charAt(index - 1) !== ' ') { + break + } + + index-- + } + + return index +} + + +/***/ }), + +/***/ 99740: +/***/ ((module) => { + +"use strict"; + + +module.exports = locate + +function locate(value, fromIndex) { + return value.indexOf('`', fromIndex) +} + + +/***/ }), + +/***/ 49250: +/***/ ((module) => { + +"use strict"; + + +module.exports = locate + +function locate(value, fromIndex) { + return value.indexOf('~~', fromIndex) +} + + +/***/ }), + +/***/ 22637: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +var decimal = __nccwpck_require__(96734) +var alphabetical = __nccwpck_require__(59201) + +var plusSign = 43 // '+' +var dash = 45 // '-' +var dot = 46 // '.' +var underscore = 95 // '_' + +module.exports = locate + +// See: +function locate(value, fromIndex) { + var self = this + var at + var position + + if (!this.options.gfm) { + return -1 + } + + at = value.indexOf('@', fromIndex) + + if (at === -1) { + return -1 + } + + position = at + + if (position === fromIndex || !isGfmAtext(value.charCodeAt(position - 1))) { + return locate.call(self, value, at + 1) + } + + while (position > fromIndex && isGfmAtext(value.charCodeAt(position - 1))) { + position-- + } + + return position +} + +function isGfmAtext(code) { + return ( + decimal(code) || + alphabetical(code) || + code === plusSign || + code === dash || + code === dot || + code === underscore + ) +} + + +/***/ }), + +/***/ 85227: +/***/ ((module) => { + +"use strict"; + + +module.exports = locate + +function locate(value, fromIndex) { + var asterisk = value.indexOf('*', fromIndex) + var underscore = value.indexOf('_', fromIndex) + + if (underscore === -1) { + return asterisk + } + + if (asterisk === -1) { + return underscore + } + + return underscore < asterisk ? underscore : asterisk +} + + +/***/ }), + +/***/ 84330: +/***/ ((module) => { + +"use strict"; + + +module.exports = locate + +function locate(value, fromIndex) { + return value.indexOf('\\', fromIndex) +} + + +/***/ }), + +/***/ 8241: +/***/ ((module) => { + +"use strict"; + + +module.exports = locate + +function locate(value, fromIndex) { + var link = value.indexOf('[', fromIndex) + var image = value.indexOf('![', fromIndex) + + if (image === -1) { + return link + } + + // Link can never be `-1` if an image is found, so we don’t need to check + // for that :) + return link < image ? link : image +} + + +/***/ }), + +/***/ 21322: +/***/ ((module) => { + +"use strict"; + + +module.exports = locate + +function locate(value, fromIndex) { + var asterisk = value.indexOf('**', fromIndex) + var underscore = value.indexOf('__', fromIndex) + + if (underscore === -1) { + return asterisk + } + + if (asterisk === -1) { + return underscore + } + + return underscore < asterisk ? underscore : asterisk +} + + +/***/ }), + +/***/ 4225: +/***/ ((module) => { + +"use strict"; + + +module.exports = locate + +function locate(value, fromIndex) { + return value.indexOf('<', fromIndex) +} + + +/***/ }), + +/***/ 82644: +/***/ ((module) => { + +"use strict"; + + +module.exports = locate + +var values = ['www.', 'http://', 'https://'] + +function locate(value, fromIndex) { + var min = -1 + var index + var length + var position + + if (!this.options.gfm) { + return min + } + + length = values.length + index = -1 + + while (++index < length) { + position = value.indexOf(values[index], fromIndex) + + if (position !== -1 && (min === -1 || position < min)) { + min = position + } + } + + return min +} + + +/***/ }), + +/***/ 3385: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +var xtend = __nccwpck_require__(80869) +var removePosition = __nccwpck_require__(10617) + +module.exports = parse + +var lineFeed = '\n' +var lineBreaksExpression = /\r\n|\r/g + +// Parse the bound file. +function parse() { + var self = this + var value = String(self.file) + var start = {line: 1, column: 1, offset: 0} + var content = xtend(start) + var node + + // Clean non-unix newlines: `\r\n` and `\r` are all changed to `\n`. + // This should not affect positional information. + value = value.replace(lineBreaksExpression, lineFeed) + + // BOM. + if (value.charCodeAt(0) === 0xfeff) { + value = value.slice(1) + + content.column++ + content.offset++ + } + + node = { + type: 'root', + children: self.tokenizeBlock(value, content), + position: {start: start, end: self.eof || xtend(start)} + } + + if (!self.options.position) { + removePosition(node, true) + } + + return node +} + + +/***/ }), + +/***/ 39651: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +var xtend = __nccwpck_require__(80869) +var toggle = __nccwpck_require__(33600) +var vfileLocation = __nccwpck_require__(34808) +var unescape = __nccwpck_require__(57468) +var decode = __nccwpck_require__(18934) +var tokenizer = __nccwpck_require__(31975) + +module.exports = Parser + +function Parser(doc, file) { + this.file = file + this.offset = {} + this.options = xtend(this.options) + this.setOptions({}) + + this.inList = false + this.inBlock = false + this.inLink = false + this.atStart = true + + this.toOffset = vfileLocation(file).toOffset + this.unescape = unescape(this, 'escape') + this.decode = decode(this) +} + +var proto = Parser.prototype + +// Expose core. +proto.setOptions = __nccwpck_require__(3701) +proto.parse = __nccwpck_require__(3385) + +// Expose `defaults`. +proto.options = __nccwpck_require__(96206) + +// Enter and exit helpers. +proto.exitStart = toggle('atStart', true) +proto.enterList = toggle('inList', false) +proto.enterLink = toggle('inLink', false) +proto.enterBlock = toggle('inBlock', false) + +// Nodes that can interupt a paragraph: +// +// ```markdown +// A paragraph, followed by a thematic break. +// ___ +// ``` +// +// In the above example, the thematic break “interupts” the paragraph. +proto.interruptParagraph = [ + ['thematicBreak'], + ['list'], + ['atxHeading'], + ['fencedCode'], + ['blockquote'], + ['html'], + ['setextHeading', {commonmark: false}], + ['definition', {commonmark: false}] +] + +// Nodes that can interupt a list: +// +// ```markdown +// - One +// ___ +// ``` +// +// In the above example, the thematic break “interupts” the list. +proto.interruptList = [ + ['atxHeading', {pedantic: false}], + ['fencedCode', {pedantic: false}], + ['thematicBreak', {pedantic: false}], + ['definition', {commonmark: false}] +] + +// Nodes that can interupt a blockquote: +// +// ```markdown +// > A paragraph. +// ___ +// ``` +// +// In the above example, the thematic break “interupts” the blockquote. +proto.interruptBlockquote = [ + ['indentedCode', {commonmark: true}], + ['fencedCode', {commonmark: true}], + ['atxHeading', {commonmark: true}], + ['setextHeading', {commonmark: true}], + ['thematicBreak', {commonmark: true}], + ['html', {commonmark: true}], + ['list', {commonmark: true}], + ['definition', {commonmark: false}] +] + +// Handlers. +proto.blockTokenizers = { + blankLine: __nccwpck_require__(93391), + indentedCode: __nccwpck_require__(44969), + fencedCode: __nccwpck_require__(48365), + blockquote: __nccwpck_require__(69539), + atxHeading: __nccwpck_require__(50542), + thematicBreak: __nccwpck_require__(4757), + list: __nccwpck_require__(30460), + setextHeading: __nccwpck_require__(87106), + html: __nccwpck_require__(68725), + definition: __nccwpck_require__(56901), + table: __nccwpck_require__(80930), + paragraph: __nccwpck_require__(3272) +} + +proto.inlineTokenizers = { + escape: __nccwpck_require__(31943), + autoLink: __nccwpck_require__(45396), + url: __nccwpck_require__(91063), + email: __nccwpck_require__(58038), + html: __nccwpck_require__(29923), + link: __nccwpck_require__(22144), + reference: __nccwpck_require__(94419), + strong: __nccwpck_require__(79359), + emphasis: __nccwpck_require__(15698), + deletion: __nccwpck_require__(21483), + code: __nccwpck_require__(97671), + break: __nccwpck_require__(94845), + text: __nccwpck_require__(10631) +} + +// Expose precedence. +proto.blockMethods = keys(proto.blockTokenizers) +proto.inlineMethods = keys(proto.inlineTokenizers) + +// Tokenizers. +proto.tokenizeBlock = tokenizer('block') +proto.tokenizeInline = tokenizer('inline') +proto.tokenizeFactory = tokenizer + +// Get all keys in `value`. +function keys(value) { + var result = [] + var key + + for (key in value) { + result.push(key) + } + + return result +} + + +/***/ }), + +/***/ 3701: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +var xtend = __nccwpck_require__(80869) +var escapes = __nccwpck_require__(80872) +var defaults = __nccwpck_require__(96206) + +module.exports = setOptions + +function setOptions(options) { + var self = this + var current = self.options + var key + var value + + if (options == null) { + options = {} + } else if (typeof options === 'object') { + options = xtend(options) + } else { + throw new Error('Invalid value `' + options + '` for setting `options`') + } + + for (key in defaults) { + value = options[key] + + if (value == null) { + value = current[key] + } + + if ( + (key !== 'blocks' && typeof value !== 'boolean') || + (key === 'blocks' && typeof value !== 'object') + ) { + throw new Error( + 'Invalid value `' + value + '` for setting `options.' + key + '`' + ) + } + + options[key] = value + } + + self.options = options + self.escape = escapes(options) + + return self +} + + +/***/ }), + +/***/ 45396: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +var whitespace = __nccwpck_require__(96472) +var decode = __nccwpck_require__(75165) +var locate = __nccwpck_require__(4225) + +module.exports = autoLink +autoLink.locator = locate +autoLink.notInLink = true + +var lessThan = '<' +var greaterThan = '>' +var atSign = '@' +var slash = '/' +var mailto = 'mailto:' +var mailtoLength = mailto.length + +function autoLink(eat, value, silent) { + var self = this + var subvalue = '' + var length = value.length + var index = 0 + var queue = '' + var hasAtCharacter = false + var link = '' + var character + var now + var content + var tokenizers + var exit + + if (value.charAt(0) !== lessThan) { + return + } + + index++ + subvalue = lessThan + + while (index < length) { + character = value.charAt(index) + + if ( + whitespace(character) || + character === greaterThan || + character === atSign || + (character === ':' && value.charAt(index + 1) === slash) + ) { + break + } + + queue += character + index++ + } + + if (!queue) { + return + } + + link += queue + queue = '' + + character = value.charAt(index) + link += character + index++ + + if (character === atSign) { + hasAtCharacter = true + } else { + if (character !== ':' || value.charAt(index + 1) !== slash) { + return + } + + link += slash + index++ + } + + while (index < length) { + character = value.charAt(index) + + if (whitespace(character) || character === greaterThan) { + break + } + + queue += character + index++ + } + + character = value.charAt(index) + + if (!queue || character !== greaterThan) { + return + } + + /* istanbul ignore if - never used (yet) */ + if (silent) { + return true + } + + link += queue + content = link + subvalue += link + character + now = eat.now() + now.column++ + now.offset++ + + if (hasAtCharacter) { + if (link.slice(0, mailtoLength).toLowerCase() === mailto) { + content = content.slice(mailtoLength) + now.column += mailtoLength + now.offset += mailtoLength + } else { + link = mailto + link + } + } + + // Temporarily remove all tokenizers except text in autolinks. + tokenizers = self.inlineTokenizers + self.inlineTokenizers = {text: tokenizers.text} + + exit = self.enterLink() + + content = self.tokenizeInline(content, now) + + self.inlineTokenizers = tokenizers + exit() + + return eat(subvalue)({ + type: 'link', + title: null, + url: decode(link, {nonTerminated: false}), + children: content + }) +} + + +/***/ }), + +/***/ 93391: +/***/ ((module) => { + +"use strict"; + + +// A line containing no characters, or a line containing only spaces (U+0020) or +// tabs (U+0009), is called a blank line. +// See . +var reBlankLine = /^[ \t]*(\n|$)/ + +// Note that though blank lines play a special role in lists to determine +// whether the list is tight or loose +// (), it’s done by the list +// tokenizer and this blank line tokenizer does not have to be responsible for +// that. +// Therefore, configs such as `blankLine.notInList` do not have to be set here. +module.exports = blankLine + +function blankLine(eat, value, silent) { + var match + var subvalue = '' + var index = 0 + var length = value.length + + while (index < length) { + match = reBlankLine.exec(value.slice(index)) + + if (match == null) { + break + } + + index += match[0].length + subvalue += match[0] + } + + if (subvalue === '') { + return + } + + /* istanbul ignore if - never used (yet) */ + if (silent) { + return true + } + + eat(subvalue) +} + + +/***/ }), + +/***/ 69539: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +var trim = __nccwpck_require__(15464) +var interrupt = __nccwpck_require__(93808) + +module.exports = blockquote + +var lineFeed = '\n' +var tab = '\t' +var space = ' ' +var greaterThan = '>' + +function blockquote(eat, value, silent) { + var self = this + var offsets = self.offset + var tokenizers = self.blockTokenizers + var interruptors = self.interruptBlockquote + var now = eat.now() + var currentLine = now.line + var length = value.length + var values = [] + var contents = [] + var indents = [] + var add + var index = 0 + var character + var rest + var nextIndex + var content + var line + var startIndex + var prefixed + var exit + + while (index < length) { + character = value.charAt(index) + + if (character !== space && character !== tab) { + break + } + + index++ + } + + if (value.charAt(index) !== greaterThan) { + return + } + + if (silent) { + return true + } + + index = 0 + + while (index < length) { + nextIndex = value.indexOf(lineFeed, index) + startIndex = index + prefixed = false + + if (nextIndex === -1) { + nextIndex = length + } + + while (index < length) { + character = value.charAt(index) + + if (character !== space && character !== tab) { + break + } + + index++ + } + + if (value.charAt(index) === greaterThan) { + index++ + prefixed = true + + if (value.charAt(index) === space) { + index++ + } + } else { + index = startIndex + } + + content = value.slice(index, nextIndex) + + if (!prefixed && !trim(content)) { + index = startIndex + break + } + + if (!prefixed) { + rest = value.slice(index) + + // Check if the following code contains a possible block. + if (interrupt(interruptors, tokenizers, self, [eat, rest, true])) { + break + } + } + + line = startIndex === index ? content : value.slice(startIndex, nextIndex) + + indents.push(index - startIndex) + values.push(line) + contents.push(content) + + index = nextIndex + 1 + } + + index = -1 + length = indents.length + add = eat(values.join(lineFeed)) + + while (++index < length) { + offsets[currentLine] = (offsets[currentLine] || 0) + indents[index] + currentLine++ + } + + exit = self.enterBlock() + contents = self.tokenizeBlock(contents.join(lineFeed), now) + exit() + + return add({type: 'blockquote', children: contents}) +} + + +/***/ }), + +/***/ 94845: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +var locate = __nccwpck_require__(91702) + +module.exports = hardBreak +hardBreak.locator = locate + +var space = ' ' +var lineFeed = '\n' +var minBreakLength = 2 + +function hardBreak(eat, value, silent) { + var length = value.length + var index = -1 + var queue = '' + var character + + while (++index < length) { + character = value.charAt(index) + + if (character === lineFeed) { + if (index < minBreakLength) { + return + } + + /* istanbul ignore if - never used (yet) */ + if (silent) { + return true + } + + queue += character + + return eat(queue)({type: 'break'}) + } + + if (character !== space) { + return + } + + queue += character + } +} + + +/***/ }), + +/***/ 48365: +/***/ ((module) => { + +"use strict"; + + +module.exports = fencedCode + +var lineFeed = '\n' +var tab = '\t' +var space = ' ' +var tilde = '~' +var graveAccent = '`' + +var minFenceCount = 3 +var tabSize = 4 + +function fencedCode(eat, value, silent) { + var self = this + var gfm = self.options.gfm + var length = value.length + 1 + var index = 0 + var subvalue = '' + var fenceCount + var marker + var character + var flag + var lang + var meta + var queue + var content + var exdentedContent + var closing + var exdentedClosing + var indent + var now + + if (!gfm) { + return + } + + // Eat initial spacing. + while (index < length) { + character = value.charAt(index) + + if (character !== space && character !== tab) { + break + } + + subvalue += character + index++ + } + + indent = index + + // Eat the fence. + character = value.charAt(index) + + if (character !== tilde && character !== graveAccent) { + return + } + + index++ + marker = character + fenceCount = 1 + subvalue += character + + while (index < length) { + character = value.charAt(index) + + if (character !== marker) { + break + } + + subvalue += character + fenceCount++ + index++ + } + + if (fenceCount < minFenceCount) { + return + } + + // Eat spacing before flag. + while (index < length) { + character = value.charAt(index) + + if (character !== space && character !== tab) { + break + } + + subvalue += character + index++ + } + + // Eat flag. + flag = '' + queue = '' + + while (index < length) { + character = value.charAt(index) + + if ( + character === lineFeed || + (marker === graveAccent && character === marker) + ) { + break + } + + if (character === space || character === tab) { + queue += character + } else { + flag += queue + character + queue = '' + } + + index++ + } + + character = value.charAt(index) + + if (character && character !== lineFeed) { + return + } + + if (silent) { + return true + } + + now = eat.now() + now.column += subvalue.length + now.offset += subvalue.length + + subvalue += flag + flag = self.decode.raw(self.unescape(flag), now) + + if (queue) { + subvalue += queue + } + + queue = '' + closing = '' + exdentedClosing = '' + content = '' + exdentedContent = '' + var skip = true + + // Eat content. + while (index < length) { + character = value.charAt(index) + content += closing + exdentedContent += exdentedClosing + closing = '' + exdentedClosing = '' + + if (character !== lineFeed) { + content += character + exdentedClosing += character + index++ + continue + } + + // The first line feed is ignored. Others aren’t. + if (skip) { + subvalue += character + skip = false + } else { + closing += character + exdentedClosing += character + } + + queue = '' + index++ + + while (index < length) { + character = value.charAt(index) + + if (character !== space) { + break + } + + queue += character + index++ + } + + closing += queue + exdentedClosing += queue.slice(indent) + + if (queue.length >= tabSize) { + continue + } + + queue = '' + + while (index < length) { + character = value.charAt(index) + + if (character !== marker) { + break + } + + queue += character + index++ + } + + closing += queue + exdentedClosing += queue + + if (queue.length < fenceCount) { + continue + } + + queue = '' + + while (index < length) { + character = value.charAt(index) + + if (character !== space && character !== tab) { + break + } + + closing += character + exdentedClosing += character + index++ + } + + if (!character || character === lineFeed) { + break + } + } + + subvalue += content + closing + + // Get lang and meta from the flag. + index = -1 + length = flag.length + + while (++index < length) { + character = flag.charAt(index) + + if (character === space || character === tab) { + if (!lang) { + lang = flag.slice(0, index) + } + } else if (lang) { + meta = flag.slice(index) + break + } + } + + return eat(subvalue)({ + type: 'code', + lang: lang || flag || null, + meta: meta || null, + value: exdentedContent + }) +} + + +/***/ }), + +/***/ 44969: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +var repeat = __nccwpck_require__(40471) +var trim = __nccwpck_require__(75937) + +module.exports = indentedCode + +var lineFeed = '\n' +var tab = '\t' +var space = ' ' + +var tabSize = 4 +var codeIndent = repeat(space, tabSize) + +function indentedCode(eat, value, silent) { + var index = -1 + var length = value.length + var subvalue = '' + var content = '' + var subvalueQueue = '' + var contentQueue = '' + var character + var blankQueue + var indent + + while (++index < length) { + character = value.charAt(index) + + if (indent) { + indent = false + + subvalue += subvalueQueue + content += contentQueue + subvalueQueue = '' + contentQueue = '' + + if (character === lineFeed) { + subvalueQueue = character + contentQueue = character + } else { + subvalue += character + content += character + + while (++index < length) { + character = value.charAt(index) + + if (!character || character === lineFeed) { + contentQueue = character + subvalueQueue = character + break + } + + subvalue += character + content += character + } + } + } else if ( + character === space && + value.charAt(index + 1) === character && + value.charAt(index + 2) === character && + value.charAt(index + 3) === character + ) { + subvalueQueue += codeIndent + index += 3 + indent = true + } else if (character === tab) { + subvalueQueue += character + indent = true + } else { + blankQueue = '' + + while (character === tab || character === space) { + blankQueue += character + character = value.charAt(++index) + } + + if (character !== lineFeed) { + break + } + + subvalueQueue += blankQueue + character + contentQueue += character + } + } + + if (content) { + if (silent) { + return true + } + + return eat(subvalue)({ + type: 'code', + lang: null, + meta: null, + value: trim(content) + }) + } +} + + +/***/ }), + +/***/ 97671: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +var locate = __nccwpck_require__(99740) + +module.exports = inlineCode +inlineCode.locator = locate + +var lineFeed = 10 // '\n' +var space = 32 // ' ' +var graveAccent = 96 // '`' + +function inlineCode(eat, value, silent) { + var length = value.length + var index = 0 + var openingFenceEnd + var closingFenceStart + var closingFenceEnd + var code + var next + var found + + while (index < length) { + if (value.charCodeAt(index) !== graveAccent) { + break + } + + index++ + } + + if (index === 0 || index === length) { + return + } + + openingFenceEnd = index + next = value.charCodeAt(index) + + while (index < length) { + code = next + next = value.charCodeAt(index + 1) + + if (code === graveAccent) { + if (closingFenceStart === undefined) { + closingFenceStart = index + } + + closingFenceEnd = index + 1 + + if ( + next !== graveAccent && + closingFenceEnd - closingFenceStart === openingFenceEnd + ) { + found = true + break + } + } else if (closingFenceStart !== undefined) { + closingFenceStart = undefined + closingFenceEnd = undefined + } + + index++ + } + + if (!found) { + return + } + + /* istanbul ignore if - never used (yet) */ + if (silent) { + return true + } + + // Remove the initial and final space (or line feed), iff they exist and there + // are non-space characters in the content. + index = openingFenceEnd + length = closingFenceStart + code = value.charCodeAt(index) + next = value.charCodeAt(length - 1) + found = false + + if ( + length - index > 2 && + (code === space || code === lineFeed) && + (next === space || next === lineFeed) + ) { + index++ + length-- + + while (index < length) { + code = value.charCodeAt(index) + + if (code !== space && code !== lineFeed) { + found = true + break + } + + index++ + } + + if (found === true) { + openingFenceEnd++ + closingFenceStart-- + } + } + + return eat(value.slice(0, closingFenceEnd))({ + type: 'inlineCode', + value: value.slice(openingFenceEnd, closingFenceStart) + }) +} + + +/***/ }), + +/***/ 56901: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +var whitespace = __nccwpck_require__(96472) +var normalize = __nccwpck_require__(99880) + +module.exports = definition + +var quotationMark = '"' +var apostrophe = "'" +var backslash = '\\' +var lineFeed = '\n' +var tab = '\t' +var space = ' ' +var leftSquareBracket = '[' +var rightSquareBracket = ']' +var leftParenthesis = '(' +var rightParenthesis = ')' +var colon = ':' +var lessThan = '<' +var greaterThan = '>' + +function definition(eat, value, silent) { + var self = this + var commonmark = self.options.commonmark + var index = 0 + var length = value.length + var subvalue = '' + var beforeURL + var beforeTitle + var queue + var character + var test + var identifier + var url + var title + + while (index < length) { + character = value.charAt(index) + + if (character !== space && character !== tab) { + break + } + + subvalue += character + index++ + } + + character = value.charAt(index) + + if (character !== leftSquareBracket) { + return + } + + index++ + subvalue += character + queue = '' + + while (index < length) { + character = value.charAt(index) + + if (character === rightSquareBracket) { + break + } else if (character === backslash) { + queue += character + index++ + character = value.charAt(index) + } + + queue += character + index++ + } + + if ( + !queue || + value.charAt(index) !== rightSquareBracket || + value.charAt(index + 1) !== colon + ) { + return + } + + identifier = queue + subvalue += queue + rightSquareBracket + colon + index = subvalue.length + queue = '' + + while (index < length) { + character = value.charAt(index) + + if (character !== tab && character !== space && character !== lineFeed) { + break + } + + subvalue += character + index++ + } + + character = value.charAt(index) + queue = '' + beforeURL = subvalue + + if (character === lessThan) { + index++ + + while (index < length) { + character = value.charAt(index) + + if (!isEnclosedURLCharacter(character)) { + break + } + + queue += character + index++ + } + + character = value.charAt(index) + + if (character === isEnclosedURLCharacter.delimiter) { + subvalue += lessThan + queue + character + index++ + } else { + if (commonmark) { + return + } + + index -= queue.length + 1 + queue = '' + } + } + + if (!queue) { + while (index < length) { + character = value.charAt(index) + + if (!isUnclosedURLCharacter(character)) { + break + } + + queue += character + index++ + } + + subvalue += queue + } + + if (!queue) { + return + } + + url = queue + queue = '' + + while (index < length) { + character = value.charAt(index) + + if (character !== tab && character !== space && character !== lineFeed) { + break + } + + queue += character + index++ + } + + character = value.charAt(index) + test = null + + if (character === quotationMark) { + test = quotationMark + } else if (character === apostrophe) { + test = apostrophe + } else if (character === leftParenthesis) { + test = rightParenthesis + } + + if (!test) { + queue = '' + index = subvalue.length + } else if (queue) { + subvalue += queue + character + index = subvalue.length + queue = '' + + while (index < length) { + character = value.charAt(index) + + if (character === test) { + break + } + + if (character === lineFeed) { + index++ + character = value.charAt(index) + + if (character === lineFeed || character === test) { + return + } + + queue += lineFeed + } + + queue += character + index++ + } + + character = value.charAt(index) + + if (character !== test) { + return + } + + beforeTitle = subvalue + subvalue += queue + character + index++ + title = queue + queue = '' + } else { + return + } + + while (index < length) { + character = value.charAt(index) + + if (character !== tab && character !== space) { + break + } + + subvalue += character + index++ + } + + character = value.charAt(index) + + if (!character || character === lineFeed) { + if (silent) { + return true + } + + beforeURL = eat(beforeURL).test().end + url = self.decode.raw(self.unescape(url), beforeURL, {nonTerminated: false}) + + if (title) { + beforeTitle = eat(beforeTitle).test().end + title = self.decode.raw(self.unescape(title), beforeTitle) + } + + return eat(subvalue)({ + type: 'definition', + identifier: normalize(identifier), + label: identifier, + title: title || null, + url: url + }) + } +} + +// Check if `character` can be inside an enclosed URI. +function isEnclosedURLCharacter(character) { + return ( + character !== greaterThan && + character !== leftSquareBracket && + character !== rightSquareBracket + ) +} + +isEnclosedURLCharacter.delimiter = greaterThan + +// Check if `character` can be inside an unclosed URI. +function isUnclosedURLCharacter(character) { + return ( + character !== leftSquareBracket && + character !== rightSquareBracket && + !whitespace(character) + ) +} + + +/***/ }), + +/***/ 21483: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +var whitespace = __nccwpck_require__(96472) +var locate = __nccwpck_require__(49250) + +module.exports = strikethrough +strikethrough.locator = locate + +var tilde = '~' +var fence = '~~' + +function strikethrough(eat, value, silent) { + var self = this + var character = '' + var previous = '' + var preceding = '' + var subvalue = '' + var index + var length + var now + + if ( + !self.options.gfm || + value.charAt(0) !== tilde || + value.charAt(1) !== tilde || + whitespace(value.charAt(2)) + ) { + return + } + + index = 1 + length = value.length + now = eat.now() + now.column += 2 + now.offset += 2 + + while (++index < length) { + character = value.charAt(index) + + if ( + character === tilde && + previous === tilde && + (!preceding || !whitespace(preceding)) + ) { + /* istanbul ignore if - never used (yet) */ + if (silent) { + return true + } + + return eat(fence + subvalue + fence)({ + type: 'delete', + children: self.tokenizeInline(subvalue, now) + }) + } + + subvalue += previous + preceding = previous + previous = character + } +} + + +/***/ }), + +/***/ 58038: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +var decode = __nccwpck_require__(75165) +var decimal = __nccwpck_require__(96734) +var alphabetical = __nccwpck_require__(59201) +var locate = __nccwpck_require__(22637) + +module.exports = email +email.locator = locate +email.notInLink = true + +var plusSign = 43 // '+' +var dash = 45 // '-' +var dot = 46 // '.' +var atSign = 64 // '@' +var underscore = 95 // '_' + +function email(eat, value, silent) { + var self = this + var gfm = self.options.gfm + var tokenizers = self.inlineTokenizers + var index = 0 + var length = value.length + var firstDot = -1 + var code + var content + var children + var exit + + if (!gfm) { + return + } + + code = value.charCodeAt(index) + + while ( + decimal(code) || + alphabetical(code) || + code === plusSign || + code === dash || + code === dot || + code === underscore + ) { + code = value.charCodeAt(++index) + } + + if (index === 0) { + return + } + + if (code !== atSign) { + return + } + + index++ + + while (index < length) { + code = value.charCodeAt(index) + + if ( + decimal(code) || + alphabetical(code) || + code === dash || + code === dot || + code === underscore + ) { + index++ + + if (firstDot === -1 && code === dot) { + firstDot = index + } + + continue + } + + break + } + + if ( + firstDot === -1 || + firstDot === index || + code === dash || + code === underscore + ) { + return + } + + if (code === dot) { + index-- + } + + content = value.slice(0, index) + + /* istanbul ignore if - never used (yet) */ + if (silent) { + return true + } + + exit = self.enterLink() + + // Temporarily remove all tokenizers except text in url. + self.inlineTokenizers = {text: tokenizers.text} + children = self.tokenizeInline(content, eat.now()) + self.inlineTokenizers = tokenizers + + exit() + + return eat(content)({ + type: 'link', + title: null, + url: 'mailto:' + decode(content, {nonTerminated: false}), + children: children + }) +} + + +/***/ }), + +/***/ 15698: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +var trim = __nccwpck_require__(15464) +var word = __nccwpck_require__(70009) +var whitespace = __nccwpck_require__(96472) +var locate = __nccwpck_require__(85227) + +module.exports = emphasis +emphasis.locator = locate + +var asterisk = '*' +var underscore = '_' +var backslash = '\\' + +function emphasis(eat, value, silent) { + var self = this + var index = 0 + var character = value.charAt(index) + var now + var pedantic + var marker + var queue + var subvalue + var length + var previous + + if (character !== asterisk && character !== underscore) { + return + } + + pedantic = self.options.pedantic + subvalue = character + marker = character + length = value.length + index++ + queue = '' + character = '' + + if (pedantic && whitespace(value.charAt(index))) { + return + } + + while (index < length) { + previous = character + character = value.charAt(index) + + if (character === marker && (!pedantic || !whitespace(previous))) { + character = value.charAt(++index) + + if (character !== marker) { + if (!trim(queue) || previous === marker) { + return + } + + if (!pedantic && marker === underscore && word(character)) { + queue += marker + continue + } + + /* istanbul ignore if - never used (yet) */ + if (silent) { + return true + } + + now = eat.now() + now.column++ + now.offset++ + + return eat(subvalue + queue + marker)({ + type: 'emphasis', + children: self.tokenizeInline(queue, now) + }) + } + + queue += marker + } + + if (!pedantic && character === backslash) { + queue += character + character = value.charAt(++index) + } + + queue += character + index++ + } +} + + +/***/ }), + +/***/ 31943: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +var locate = __nccwpck_require__(84330) + +module.exports = escape +escape.locator = locate + +var lineFeed = '\n' +var backslash = '\\' + +function escape(eat, value, silent) { + var self = this + var character + var node + + if (value.charAt(0) === backslash) { + character = value.charAt(1) + + if (self.escape.indexOf(character) !== -1) { + /* istanbul ignore if - never used (yet) */ + if (silent) { + return true + } + + if (character === lineFeed) { + node = {type: 'break'} + } else { + node = {type: 'text', value: character} + } + + return eat(backslash + character)(node) + } + } +} + + +/***/ }), + +/***/ 50542: +/***/ ((module) => { + +"use strict"; + + +module.exports = atxHeading + +var lineFeed = '\n' +var tab = '\t' +var space = ' ' +var numberSign = '#' + +var maxFenceCount = 6 + +function atxHeading(eat, value, silent) { + var self = this + var pedantic = self.options.pedantic + var length = value.length + 1 + var index = -1 + var now = eat.now() + var subvalue = '' + var content = '' + var character + var queue + var depth + + // Eat initial spacing. + while (++index < length) { + character = value.charAt(index) + + if (character !== space && character !== tab) { + index-- + break + } + + subvalue += character + } + + // Eat hashes. + depth = 0 + + while (++index <= length) { + character = value.charAt(index) + + if (character !== numberSign) { + index-- + break + } + + subvalue += character + depth++ + } + + if (depth > maxFenceCount) { + return + } + + if (!depth || (!pedantic && value.charAt(index + 1) === numberSign)) { + return + } + + length = value.length + 1 + + // Eat intermediate white-space. + queue = '' + + while (++index < length) { + character = value.charAt(index) + + if (character !== space && character !== tab) { + index-- + break + } + + queue += character + } + + // Exit when not in pedantic mode without spacing. + if (!pedantic && queue.length === 0 && character && character !== lineFeed) { + return + } + + if (silent) { + return true + } + + // Eat content. + subvalue += queue + queue = '' + content = '' + + while (++index < length) { + character = value.charAt(index) + + if (!character || character === lineFeed) { + break + } + + if (character !== space && character !== tab && character !== numberSign) { + content += queue + character + queue = '' + continue + } + + while (character === space || character === tab) { + queue += character + character = value.charAt(++index) + } + + // `#` without a queue is part of the content. + if (!pedantic && content && !queue && character === numberSign) { + content += character + continue + } + + while (character === numberSign) { + queue += character + character = value.charAt(++index) + } + + while (character === space || character === tab) { + queue += character + character = value.charAt(++index) + } + + index-- + } + + now.column += subvalue.length + now.offset += subvalue.length + subvalue += content + queue + + return eat(subvalue)({ + type: 'heading', + depth: depth, + children: self.tokenizeInline(content, now) + }) +} + + +/***/ }), + +/***/ 87106: +/***/ ((module) => { + +"use strict"; + + +module.exports = setextHeading + +var lineFeed = '\n' +var tab = '\t' +var space = ' ' +var equalsTo = '=' +var dash = '-' + +var maxIndent = 3 + +var equalsToDepth = 1 +var dashDepth = 2 + +function setextHeading(eat, value, silent) { + var self = this + var now = eat.now() + var length = value.length + var index = -1 + var subvalue = '' + var content + var queue + var character + var marker + var depth + + // Eat initial indentation. + while (++index < length) { + character = value.charAt(index) + + if (character !== space || index >= maxIndent) { + index-- + break + } + + subvalue += character + } + + // Eat content. + content = '' + queue = '' + + while (++index < length) { + character = value.charAt(index) + + if (character === lineFeed) { + index-- + break + } + + if (character === space || character === tab) { + queue += character + } else { + content += queue + character + queue = '' + } + } + + now.column += subvalue.length + now.offset += subvalue.length + subvalue += content + queue + + // Ensure the content is followed by a newline and a valid marker. + character = value.charAt(++index) + marker = value.charAt(++index) + + if (character !== lineFeed || (marker !== equalsTo && marker !== dash)) { + return + } + + subvalue += character + + // Eat Setext-line. + queue = marker + depth = marker === equalsTo ? equalsToDepth : dashDepth + + while (++index < length) { + character = value.charAt(index) + + if (character !== marker) { + if (character !== lineFeed) { + return + } + + index-- + break + } + + queue += character + } + + if (silent) { + return true + } + + return eat(subvalue + queue)({ + type: 'heading', + depth: depth, + children: self.tokenizeInline(content, now) + }) +} + + +/***/ }), + +/***/ 68725: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +var openCloseTag = (__nccwpck_require__(76774)/* .openCloseTag */ .X) + +module.exports = blockHtml + +var tab = '\t' +var space = ' ' +var lineFeed = '\n' +var lessThan = '<' + +var rawOpenExpression = /^<(script|pre|style)(?=(\s|>|$))/i +var rawCloseExpression = /<\/(script|pre|style)>/i +var commentOpenExpression = /^/ +var instructionOpenExpression = /^<\?/ +var instructionCloseExpression = /\?>/ +var directiveOpenExpression = /^/ +var cdataOpenExpression = /^/ +var elementCloseExpression = /^$/ +var otherElementOpenExpression = new RegExp(openCloseTag.source + '\\s*$') + +function blockHtml(eat, value, silent) { + var self = this + var blocks = self.options.blocks.join('|') + var elementOpenExpression = new RegExp( + '^|$))', + 'i' + ) + var length = value.length + var index = 0 + var next + var line + var offset + var character + var count + var sequence + var subvalue + + var sequences = [ + [rawOpenExpression, rawCloseExpression, true], + [commentOpenExpression, commentCloseExpression, true], + [instructionOpenExpression, instructionCloseExpression, true], + [directiveOpenExpression, directiveCloseExpression, true], + [cdataOpenExpression, cdataCloseExpression, true], + [elementOpenExpression, elementCloseExpression, true], + [otherElementOpenExpression, elementCloseExpression, false] + ] + + // Eat initial spacing. + while (index < length) { + character = value.charAt(index) + + if (character !== tab && character !== space) { + break + } + + index++ + } + + if (value.charAt(index) !== lessThan) { + return + } + + next = value.indexOf(lineFeed, index + 1) + next = next === -1 ? length : next + line = value.slice(index, next) + offset = -1 + count = sequences.length + + while (++offset < count) { + if (sequences[offset][0].test(line)) { + sequence = sequences[offset] + break + } + } + + if (!sequence) { + return + } + + if (silent) { + return sequence[2] + } + + index = next + + if (!sequence[1].test(line)) { + while (index < length) { + next = value.indexOf(lineFeed, index + 1) + next = next === -1 ? length : next + line = value.slice(index + 1, next) + + if (sequence[1].test(line)) { + if (line) { + index = next + } + + break + } + + index = next + } + } + + subvalue = value.slice(0, index) + + return eat(subvalue)({type: 'html', value: subvalue}) +} + + +/***/ }), + +/***/ 29923: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +var alphabetical = __nccwpck_require__(59201) +var locate = __nccwpck_require__(4225) +var tag = (__nccwpck_require__(76774)/* .tag */ .T) + +module.exports = inlineHTML +inlineHTML.locator = locate + +var lessThan = '<' +var questionMark = '?' +var exclamationMark = '!' +var slash = '/' + +var htmlLinkOpenExpression = /^/i + +function inlineHTML(eat, value, silent) { + var self = this + var length = value.length + var character + var subvalue + + if (value.charAt(0) !== lessThan || length < 3) { + return + } + + character = value.charAt(1) + + if ( + !alphabetical(character) && + character !== questionMark && + character !== exclamationMark && + character !== slash + ) { + return + } + + subvalue = value.match(tag) + + if (!subvalue) { + return + } + + /* istanbul ignore if - not used yet. */ + if (silent) { + return true + } + + subvalue = subvalue[0] + + if (!self.inLink && htmlLinkOpenExpression.test(subvalue)) { + self.inLink = true + } else if (self.inLink && htmlLinkCloseExpression.test(subvalue)) { + self.inLink = false + } + + return eat(subvalue)({type: 'html', value: subvalue}) +} + + +/***/ }), + +/***/ 22144: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +var whitespace = __nccwpck_require__(96472) +var locate = __nccwpck_require__(8241) + +module.exports = link +link.locator = locate + +var lineFeed = '\n' +var exclamationMark = '!' +var quotationMark = '"' +var apostrophe = "'" +var leftParenthesis = '(' +var rightParenthesis = ')' +var lessThan = '<' +var greaterThan = '>' +var leftSquareBracket = '[' +var backslash = '\\' +var rightSquareBracket = ']' +var graveAccent = '`' + +function link(eat, value, silent) { + var self = this + var subvalue = '' + var index = 0 + var character = value.charAt(0) + var pedantic = self.options.pedantic + var commonmark = self.options.commonmark + var gfm = self.options.gfm + var closed + var count + var opening + var beforeURL + var beforeTitle + var subqueue + var hasMarker + var isImage + var content + var marker + var length + var title + var depth + var queue + var url + var now + var exit + var node + + // Detect whether this is an image. + if (character === exclamationMark) { + isImage = true + subvalue = character + character = value.charAt(++index) + } + + // Eat the opening. + if (character !== leftSquareBracket) { + return + } + + // Exit when this is a link and we’re already inside a link. + if (!isImage && self.inLink) { + return + } + + subvalue += character + queue = '' + index++ + + // Eat the content. + length = value.length + now = eat.now() + depth = 0 + + now.column += index + now.offset += index + + while (index < length) { + character = value.charAt(index) + subqueue = character + + if (character === graveAccent) { + // Inline-code in link content. + count = 1 + + while (value.charAt(index + 1) === graveAccent) { + subqueue += character + index++ + count++ + } + + if (!opening) { + opening = count + } else if (count >= opening) { + opening = 0 + } + } else if (character === backslash) { + // Allow brackets to be escaped. + index++ + subqueue += value.charAt(index) + } else if ((!opening || gfm) && character === leftSquareBracket) { + // In GFM mode, brackets in code still count. In all other modes, + // they don’t. + depth++ + } else if ((!opening || gfm) && character === rightSquareBracket) { + if (depth) { + depth-- + } else { + if (value.charAt(index + 1) !== leftParenthesis) { + return + } + + subqueue += leftParenthesis + closed = true + index++ + + break + } + } + + queue += subqueue + subqueue = '' + index++ + } + + // Eat the content closing. + if (!closed) { + return + } + + content = queue + subvalue += queue + subqueue + index++ + + // Eat white-space. + while (index < length) { + character = value.charAt(index) + + if (!whitespace(character)) { + break + } + + subvalue += character + index++ + } + + // Eat the URL. + character = value.charAt(index) + queue = '' + beforeURL = subvalue + + if (character === lessThan) { + index++ + beforeURL += lessThan + + while (index < length) { + character = value.charAt(index) + + if (character === greaterThan) { + break + } + + if (commonmark && character === lineFeed) { + return + } + + queue += character + index++ + } + + if (value.charAt(index) !== greaterThan) { + return + } + + subvalue += lessThan + queue + greaterThan + url = queue + index++ + } else { + character = null + subqueue = '' + + while (index < length) { + character = value.charAt(index) + + if ( + subqueue && + (character === quotationMark || + character === apostrophe || + (commonmark && character === leftParenthesis)) + ) { + break + } + + if (whitespace(character)) { + if (!pedantic) { + break + } + + subqueue += character + } else { + if (character === leftParenthesis) { + depth++ + } else if (character === rightParenthesis) { + if (depth === 0) { + break + } + + depth-- + } + + queue += subqueue + subqueue = '' + + if (character === backslash) { + queue += backslash + character = value.charAt(++index) + } + + queue += character + } + + index++ + } + + subvalue += queue + url = queue + index = subvalue.length + } + + // Eat white-space. + queue = '' + + while (index < length) { + character = value.charAt(index) + + if (!whitespace(character)) { + break + } + + queue += character + index++ + } + + character = value.charAt(index) + subvalue += queue + + // Eat the title. + if ( + queue && + (character === quotationMark || + character === apostrophe || + (commonmark && character === leftParenthesis)) + ) { + index++ + subvalue += character + queue = '' + marker = character === leftParenthesis ? rightParenthesis : character + beforeTitle = subvalue + + // In commonmark-mode, things are pretty easy: the marker cannot occur + // inside the title. Non-commonmark does, however, support nested + // delimiters. + if (commonmark) { + while (index < length) { + character = value.charAt(index) + + if (character === marker) { + break + } + + if (character === backslash) { + queue += backslash + character = value.charAt(++index) + } + + index++ + queue += character + } + + character = value.charAt(index) + + if (character !== marker) { + return + } + + title = queue + subvalue += queue + character + index++ + + while (index < length) { + character = value.charAt(index) + + if (!whitespace(character)) { + break + } + + subvalue += character + index++ + } + } else { + subqueue = '' + + while (index < length) { + character = value.charAt(index) + + if (character === marker) { + if (hasMarker) { + queue += marker + subqueue + subqueue = '' + } + + hasMarker = true + } else if (!hasMarker) { + queue += character + } else if (character === rightParenthesis) { + subvalue += queue + marker + subqueue + title = queue + break + } else if (whitespace(character)) { + subqueue += character + } else { + queue += marker + subqueue + character + subqueue = '' + hasMarker = false + } + + index++ + } + } + } + + if (value.charAt(index) !== rightParenthesis) { + return + } + + /* istanbul ignore if - never used (yet) */ + if (silent) { + return true + } + + subvalue += rightParenthesis + + url = self.decode.raw(self.unescape(url), eat(beforeURL).test().end, { + nonTerminated: false + }) + + if (title) { + beforeTitle = eat(beforeTitle).test().end + title = self.decode.raw(self.unescape(title), beforeTitle) + } + + node = { + type: isImage ? 'image' : 'link', + title: title || null, + url: url + } + + if (isImage) { + node.alt = self.decode.raw(self.unescape(content), now) || null + } else { + exit = self.enterLink() + node.children = self.tokenizeInline(content, now) + exit() + } + + return eat(subvalue)(node) +} + + +/***/ }), + +/***/ 30460: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +var trim = __nccwpck_require__(15464) +var repeat = __nccwpck_require__(40471) +var decimal = __nccwpck_require__(96734) +var getIndent = __nccwpck_require__(19245) +var removeIndent = __nccwpck_require__(39729) +var interrupt = __nccwpck_require__(93808) + +module.exports = list + +var asterisk = '*' +var underscore = '_' +var plusSign = '+' +var dash = '-' +var dot = '.' +var space = ' ' +var lineFeed = '\n' +var tab = '\t' +var rightParenthesis = ')' +var lowercaseX = 'x' + +var tabSize = 4 +var looseListItemExpression = /\n\n(?!\s*$)/ +var taskItemExpression = /^\[([ X\tx])][ \t]/ +var bulletExpression = /^([ \t]*)([*+-]|\d+[.)])( {1,4}(?! )| |\t|$|(?=\n))([^\n]*)/ +var pedanticBulletExpression = /^([ \t]*)([*+-]|\d+[.)])([ \t]+)/ +var initialIndentExpression = /^( {1,4}|\t)?/gm + +function list(eat, value, silent) { + var self = this + var commonmark = self.options.commonmark + var pedantic = self.options.pedantic + var tokenizers = self.blockTokenizers + var interuptors = self.interruptList + var index = 0 + var length = value.length + var start = null + var size + var queue + var ordered + var character + var marker + var nextIndex + var startIndex + var prefixed + var currentMarker + var content + var line + var previousEmpty + var empty + var items + var allLines + var emptyLines + var item + var enterTop + var exitBlockquote + var spread = false + var node + var now + var end + var indented + + while (index < length) { + character = value.charAt(index) + + if (character !== tab && character !== space) { + break + } + + index++ + } + + character = value.charAt(index) + + if (character === asterisk || character === plusSign || character === dash) { + marker = character + ordered = false + } else { + ordered = true + queue = '' + + while (index < length) { + character = value.charAt(index) + + if (!decimal(character)) { + break + } + + queue += character + index++ + } + + character = value.charAt(index) + + if ( + !queue || + !(character === dot || (commonmark && character === rightParenthesis)) + ) { + return + } + + /* Slightly abusing `silent` mode, whose goal is to make interrupting + * paragraphs work. + * Well, that’s exactly what we want to do here: don’t interrupt: + * 2. here, because the “list” doesn’t start with `1`. */ + if (silent && queue !== '1') { + return + } + + start = parseInt(queue, 10) + marker = character + } + + character = value.charAt(++index) + + if ( + character !== space && + character !== tab && + (pedantic || (character !== lineFeed && character !== '')) + ) { + return + } + + if (silent) { + return true + } + + index = 0 + items = [] + allLines = [] + emptyLines = [] + + while (index < length) { + nextIndex = value.indexOf(lineFeed, index) + startIndex = index + prefixed = false + indented = false + + if (nextIndex === -1) { + nextIndex = length + } + + size = 0 + + while (index < length) { + character = value.charAt(index) + + if (character === tab) { + size += tabSize - (size % tabSize) + } else if (character === space) { + size++ + } else { + break + } + + index++ + } + + if (item && size >= item.indent) { + indented = true + } + + character = value.charAt(index) + currentMarker = null + + if (!indented) { + if ( + character === asterisk || + character === plusSign || + character === dash + ) { + currentMarker = character + index++ + size++ + } else { + queue = '' + + while (index < length) { + character = value.charAt(index) + + if (!decimal(character)) { + break + } + + queue += character + index++ + } + + character = value.charAt(index) + index++ + + if ( + queue && + (character === dot || (commonmark && character === rightParenthesis)) + ) { + currentMarker = character + size += queue.length + 1 + } + } + + if (currentMarker) { + character = value.charAt(index) + + if (character === tab) { + size += tabSize - (size % tabSize) + index++ + } else if (character === space) { + end = index + tabSize + + while (index < end) { + if (value.charAt(index) !== space) { + break + } + + index++ + size++ + } + + if (index === end && value.charAt(index) === space) { + index -= tabSize - 1 + size -= tabSize - 1 + } + } else if (character !== lineFeed && character !== '') { + currentMarker = null + } + } + } + + if (currentMarker) { + if (!pedantic && marker !== currentMarker) { + break + } + + prefixed = true + } else { + if (!commonmark && !indented && value.charAt(startIndex) === space) { + indented = true + } else if (commonmark && item) { + indented = size >= item.indent || size > tabSize + } + + prefixed = false + index = startIndex + } + + line = value.slice(startIndex, nextIndex) + content = startIndex === index ? line : value.slice(index, nextIndex) + + if ( + currentMarker === asterisk || + currentMarker === underscore || + currentMarker === dash + ) { + if (tokenizers.thematicBreak.call(self, eat, line, true)) { + break + } + } + + previousEmpty = empty + empty = !prefixed && !trim(content).length + + if (indented && item) { + item.value = item.value.concat(emptyLines, line) + allLines = allLines.concat(emptyLines, line) + emptyLines = [] + } else if (prefixed) { + if (emptyLines.length !== 0) { + spread = true + item.value.push('') + item.trail = emptyLines.concat() + } + + item = { + value: [line], + indent: size, + trail: [] + } + + items.push(item) + allLines = allLines.concat(emptyLines, line) + emptyLines = [] + } else if (empty) { + if (previousEmpty && !commonmark) { + break + } + + emptyLines.push(line) + } else { + if (previousEmpty) { + break + } + + if (interrupt(interuptors, tokenizers, self, [eat, line, true])) { + break + } + + item.value = item.value.concat(emptyLines, line) + allLines = allLines.concat(emptyLines, line) + emptyLines = [] + } + + index = nextIndex + 1 + } + + node = eat(allLines.join(lineFeed)).reset({ + type: 'list', + ordered: ordered, + start: start, + spread: spread, + children: [] + }) + + enterTop = self.enterList() + exitBlockquote = self.enterBlock() + index = -1 + length = items.length + + while (++index < length) { + item = items[index].value.join(lineFeed) + now = eat.now() + + eat(item)(listItem(self, item, now), node) + + item = items[index].trail.join(lineFeed) + + if (index !== length - 1) { + item += lineFeed + } + + eat(item) + } + + enterTop() + exitBlockquote() + + return node +} + +function listItem(ctx, value, position) { + var offsets = ctx.offset + var fn = ctx.options.pedantic ? pedanticListItem : normalListItem + var checked = null + var task + var indent + + value = fn.apply(null, arguments) + + if (ctx.options.gfm) { + task = value.match(taskItemExpression) + + if (task) { + indent = task[0].length + checked = task[1].toLowerCase() === lowercaseX + offsets[position.line] += indent + value = value.slice(indent) + } + } + + return { + type: 'listItem', + spread: looseListItemExpression.test(value), + checked: checked, + children: ctx.tokenizeBlock(value, position) + } +} + +// Create a list-item using overly simple mechanics. +function pedanticListItem(ctx, value, position) { + var offsets = ctx.offset + var line = position.line + + // Remove the list-item’s bullet. + value = value.replace(pedanticBulletExpression, replacer) + + // The initial line was also matched by the below, so we reset the `line`. + line = position.line + + return value.replace(initialIndentExpression, replacer) + + // A simple replacer which removed all matches, and adds their length to + // `offset`. + function replacer($0) { + offsets[line] = (offsets[line] || 0) + $0.length + line++ + + return '' + } +} + +// Create a list-item using sane mechanics. +function normalListItem(ctx, value, position) { + var offsets = ctx.offset + var line = position.line + var max + var bullet + var rest + var lines + var trimmedLines + var index + var length + + // Remove the list-item’s bullet. + value = value.replace(bulletExpression, replacer) + + lines = value.split(lineFeed) + + trimmedLines = removeIndent(value, getIndent(max).indent).split(lineFeed) + + // We replaced the initial bullet with something else above, which was used + // to trick `removeIndentation` into removing some more characters when + // possible. However, that could result in the initial line to be stripped + // more than it should be. + trimmedLines[0] = rest + + offsets[line] = (offsets[line] || 0) + bullet.length + line++ + + index = 0 + length = lines.length + + while (++index < length) { + offsets[line] = + (offsets[line] || 0) + lines[index].length - trimmedLines[index].length + line++ + } + + return trimmedLines.join(lineFeed) + + /* eslint-disable-next-line max-params */ + function replacer($0, $1, $2, $3, $4) { + bullet = $1 + $2 + $3 + rest = $4 + + // Make sure that the first nine numbered list items can indent with an + // extra space. That is, when the bullet did not receive an extra final + // space. + if (Number($2) < 10 && bullet.length % 2 === 1) { + $2 = space + $2 + } + + max = $1 + repeat(space, $2.length) + $3 + + return max + rest + } +} + + +/***/ }), + +/***/ 3272: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +var trim = __nccwpck_require__(15464) +var trimTrailingLines = __nccwpck_require__(75937) +var interrupt = __nccwpck_require__(93808) + +module.exports = paragraph + +var tab = '\t' +var lineFeed = '\n' +var space = ' ' + +var tabSize = 4 + +// Tokenise paragraph. +function paragraph(eat, value, silent) { + var self = this + var settings = self.options + var commonmark = settings.commonmark + var tokenizers = self.blockTokenizers + var interruptors = self.interruptParagraph + var index = value.indexOf(lineFeed) + var length = value.length + var position + var subvalue + var character + var size + var now + + while (index < length) { + // Eat everything if there’s no following newline. + if (index === -1) { + index = length + break + } + + // Stop if the next character is NEWLINE. + if (value.charAt(index + 1) === lineFeed) { + break + } + + // In commonmark-mode, following indented lines are part of the paragraph. + if (commonmark) { + size = 0 + position = index + 1 + + while (position < length) { + character = value.charAt(position) + + if (character === tab) { + size = tabSize + break + } else if (character === space) { + size++ + } else { + break + } + + position++ + } + + if (size >= tabSize && character !== lineFeed) { + index = value.indexOf(lineFeed, index + 1) + continue + } + } + + subvalue = value.slice(index + 1) + + // Check if the following code contains a possible block. + if (interrupt(interruptors, tokenizers, self, [eat, subvalue, true])) { + break + } + + position = index + index = value.indexOf(lineFeed, index + 1) + + if (index !== -1 && trim(value.slice(position, index)) === '') { + index = position + break + } + } + + subvalue = value.slice(0, index) + + /* istanbul ignore if - never used (yet) */ + if (silent) { + return true + } + + now = eat.now() + subvalue = trimTrailingLines(subvalue) + + return eat(subvalue)({ + type: 'paragraph', + children: self.tokenizeInline(subvalue, now) + }) +} + + +/***/ }), + +/***/ 94419: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +var whitespace = __nccwpck_require__(96472) +var locate = __nccwpck_require__(8241) +var normalize = __nccwpck_require__(99880) + +module.exports = reference +reference.locator = locate + +var link = 'link' +var image = 'image' +var shortcut = 'shortcut' +var collapsed = 'collapsed' +var full = 'full' +var exclamationMark = '!' +var leftSquareBracket = '[' +var backslash = '\\' +var rightSquareBracket = ']' + +function reference(eat, value, silent) { + var self = this + var commonmark = self.options.commonmark + var character = value.charAt(0) + var index = 0 + var length = value.length + var subvalue = '' + var intro = '' + var type = link + var referenceType = shortcut + var content + var identifier + var now + var node + var exit + var queue + var bracketed + var depth + + // Check whether we’re eating an image. + if (character === exclamationMark) { + type = image + intro = character + character = value.charAt(++index) + } + + if (character !== leftSquareBracket) { + return + } + + index++ + intro += character + queue = '' + + // Eat the text. + depth = 0 + + while (index < length) { + character = value.charAt(index) + + if (character === leftSquareBracket) { + bracketed = true + depth++ + } else if (character === rightSquareBracket) { + if (!depth) { + break + } + + depth-- + } + + if (character === backslash) { + queue += backslash + character = value.charAt(++index) + } + + queue += character + index++ + } + + subvalue = queue + content = queue + character = value.charAt(index) + + if (character !== rightSquareBracket) { + return + } + + index++ + subvalue += character + queue = '' + + if (!commonmark) { + // The original markdown syntax definition explicitly allows for whitespace + // between the link text and link label; commonmark departs from this, in + // part to improve support for shortcut reference links + while (index < length) { + character = value.charAt(index) + + if (!whitespace(character)) { + break + } + + queue += character + index++ + } + } + + character = value.charAt(index) + + if (character === leftSquareBracket) { + identifier = '' + queue += character + index++ + + while (index < length) { + character = value.charAt(index) + + if (character === leftSquareBracket || character === rightSquareBracket) { + break + } + + if (character === backslash) { + identifier += backslash + character = value.charAt(++index) + } + + identifier += character + index++ + } + + character = value.charAt(index) + + if (character === rightSquareBracket) { + referenceType = identifier ? full : collapsed + queue += identifier + character + index++ + } else { + identifier = '' + } + + subvalue += queue + queue = '' + } else { + if (!content) { + return + } + + identifier = content + } + + // Brackets cannot be inside the identifier. + if (referenceType !== full && bracketed) { + return + } + + subvalue = intro + subvalue + + if (type === link && self.inLink) { + return null + } + + /* istanbul ignore if - never used (yet) */ + if (silent) { + return true + } + + now = eat.now() + now.column += intro.length + now.offset += intro.length + identifier = referenceType === full ? identifier : content + + node = { + type: type + 'Reference', + identifier: normalize(identifier), + label: identifier, + referenceType: referenceType + } + + if (type === link) { + exit = self.enterLink() + node.children = self.tokenizeInline(content, now) + exit() + } else { + node.alt = self.decode.raw(self.unescape(content), now) || null + } + + return eat(subvalue)(node) +} + + +/***/ }), + +/***/ 79359: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +var trim = __nccwpck_require__(15464) +var whitespace = __nccwpck_require__(96472) +var locate = __nccwpck_require__(21322) + +module.exports = strong +strong.locator = locate + +var backslash = '\\' +var asterisk = '*' +var underscore = '_' + +function strong(eat, value, silent) { + var self = this + var index = 0 + var character = value.charAt(index) + var now + var pedantic + var marker + var queue + var subvalue + var length + var previous + + if ( + (character !== asterisk && character !== underscore) || + value.charAt(++index) !== character + ) { + return + } + + pedantic = self.options.pedantic + marker = character + subvalue = marker + marker + length = value.length + index++ + queue = '' + character = '' + + if (pedantic && whitespace(value.charAt(index))) { + return + } + + while (index < length) { + previous = character + character = value.charAt(index) + + if ( + character === marker && + value.charAt(index + 1) === marker && + (!pedantic || !whitespace(previous)) + ) { + character = value.charAt(index + 2) + + if (character !== marker) { + if (!trim(queue)) { + return + } + + /* istanbul ignore if - never used (yet) */ + if (silent) { + return true + } + + now = eat.now() + now.column += 2 + now.offset += 2 + + return eat(subvalue + queue + subvalue)({ + type: 'strong', + children: self.tokenizeInline(queue, now) + }) + } + } + + if (!pedantic && character === backslash) { + queue += character + character = value.charAt(++index) + } + + queue += character + index++ + } +} + + +/***/ }), + +/***/ 80930: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +var whitespace = __nccwpck_require__(96472) + +module.exports = table + +var tab = '\t' +var lineFeed = '\n' +var space = ' ' +var dash = '-' +var colon = ':' +var backslash = '\\' +var verticalBar = '|' + +var minColumns = 1 +var minRows = 2 + +var left = 'left' +var center = 'center' +var right = 'right' + +function table(eat, value, silent) { + var self = this + var index + var alignments + var alignment + var subvalue + var row + var length + var lines + var queue + var character + var hasDash + var align + var cell + var preamble + var now + var position + var lineCount + var line + var rows + var table + var lineIndex + var pipeIndex + var first + + // Exit when not in gfm-mode. + if (!self.options.gfm) { + return + } + + // Get the rows. + // Detecting tables soon is hard, so there are some checks for performance + // here, such as the minimum number of rows, and allowed characters in the + // alignment row. + index = 0 + lineCount = 0 + length = value.length + 1 + lines = [] + + while (index < length) { + lineIndex = value.indexOf(lineFeed, index) + pipeIndex = value.indexOf(verticalBar, index + 1) + + if (lineIndex === -1) { + lineIndex = value.length + } + + if (pipeIndex === -1 || pipeIndex > lineIndex) { + if (lineCount < minRows) { + return + } + + break + } + + lines.push(value.slice(index, lineIndex)) + lineCount++ + index = lineIndex + 1 + } + + // Parse the alignment row. + subvalue = lines.join(lineFeed) + alignments = lines.splice(1, 1)[0] || [] + index = 0 + length = alignments.length + lineCount-- + alignment = false + align = [] + + while (index < length) { + character = alignments.charAt(index) + + if (character === verticalBar) { + hasDash = null + + if (alignment === false) { + if (first === false) { + return + } + } else { + align.push(alignment) + alignment = false + } + + first = false + } else if (character === dash) { + hasDash = true + alignment = alignment || null + } else if (character === colon) { + if (alignment === left) { + alignment = center + } else if (hasDash && alignment === null) { + alignment = right + } else { + alignment = left + } + } else if (!whitespace(character)) { + return + } + + index++ + } + + if (alignment !== false) { + align.push(alignment) + } + + // Exit when without enough columns. + if (align.length < minColumns) { + return + } + + /* istanbul ignore if - never used (yet) */ + if (silent) { + return true + } + + // Parse the rows. + position = -1 + rows = [] + + table = eat(subvalue).reset({type: 'table', align: align, children: rows}) + + while (++position < lineCount) { + line = lines[position] + row = {type: 'tableRow', children: []} + + // Eat a newline character when this is not the first row. + if (position) { + eat(lineFeed) + } + + // Eat the row. + eat(line).reset(row, table) + + length = line.length + 1 + index = 0 + queue = '' + cell = '' + preamble = true + + while (index < length) { + character = line.charAt(index) + + if (character === tab || character === space) { + if (cell) { + queue += character + } else { + eat(character) + } + + index++ + continue + } + + if (character === '' || character === verticalBar) { + if (preamble) { + eat(character) + } else { + if ((cell || character) && !preamble) { + subvalue = cell + + if (queue.length > 1) { + if (character) { + subvalue += queue.slice(0, -1) + queue = queue.charAt(queue.length - 1) + } else { + subvalue += queue + queue = '' + } + } + + now = eat.now() + + eat(subvalue)( + {type: 'tableCell', children: self.tokenizeInline(cell, now)}, + row + ) + } + + eat(queue + character) + + queue = '' + cell = '' + } + } else { + if (queue) { + cell += queue + queue = '' + } + + cell += character + + if (character === backslash && index !== length - 2) { + cell += line.charAt(index + 1) + index++ + } + } + + preamble = false + index++ + } + + // Eat the alignment row. + if (!position) { + eat(lineFeed + alignments) + } + } + + return table +} + + +/***/ }), + +/***/ 10631: +/***/ ((module) => { + +"use strict"; + + +module.exports = text + +function text(eat, value, silent) { + var self = this + var methods + var tokenizers + var index + var length + var subvalue + var position + var tokenizer + var name + var min + var now + + /* istanbul ignore if - never used (yet) */ + if (silent) { + return true + } + + methods = self.inlineMethods + length = methods.length + tokenizers = self.inlineTokenizers + index = -1 + min = value.length + + while (++index < length) { + name = methods[index] + + if (name === 'text' || !tokenizers[name]) { + continue + } + + tokenizer = tokenizers[name].locator + + if (!tokenizer) { + eat.file.fail('Missing locator: `' + name + '`') + } + + position = tokenizer.call(self, value, 1) + + if (position !== -1 && position < min) { + min = position + } + } + + subvalue = value.slice(0, min) + now = eat.now() + + self.decode(subvalue, now, handler) + + function handler(content, position, source) { + eat(source || content)({type: 'text', value: content}) + } +} + + +/***/ }), + +/***/ 4757: +/***/ ((module) => { + +"use strict"; + + +module.exports = thematicBreak + +var tab = '\t' +var lineFeed = '\n' +var space = ' ' +var asterisk = '*' +var dash = '-' +var underscore = '_' + +var maxCount = 3 + +function thematicBreak(eat, value, silent) { + var index = -1 + var length = value.length + 1 + var subvalue = '' + var character + var marker + var markerCount + var queue + + while (++index < length) { + character = value.charAt(index) + + if (character !== tab && character !== space) { + break + } + + subvalue += character + } + + if ( + character !== asterisk && + character !== dash && + character !== underscore + ) { + return + } + + marker = character + subvalue += character + markerCount = 1 + queue = '' + + while (++index < length) { + character = value.charAt(index) + + if (character === marker) { + markerCount++ + subvalue += queue + marker + queue = '' + } else if (character === space) { + queue += character + } else if ( + markerCount >= maxCount && + (!character || character === lineFeed) + ) { + subvalue += queue + + if (silent) { + return true + } + + return eat(subvalue)({type: 'thematicBreak'}) + } else { + return + } + } +} + + +/***/ }), + +/***/ 91063: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +var ccount = __nccwpck_require__(83076) +var decode = __nccwpck_require__(75165) +var decimal = __nccwpck_require__(96734) +var alphabetical = __nccwpck_require__(59201) +var whitespace = __nccwpck_require__(96472) +var locate = __nccwpck_require__(82644) + +module.exports = url +url.locator = locate +url.notInLink = true + +var exclamationMark = 33 // '!' +var ampersand = 38 // '&' +var rightParenthesis = 41 // ')' +var asterisk = 42 // '*' +var comma = 44 // ',' +var dash = 45 // '-' +var dot = 46 // '.' +var colon = 58 // ':' +var semicolon = 59 // ';' +var questionMark = 63 // '?' +var lessThan = 60 // '<' +var underscore = 95 // '_' +var tilde = 126 // '~' + +var leftParenthesisCharacter = '(' +var rightParenthesisCharacter = ')' + +function url(eat, value, silent) { + var self = this + var gfm = self.options.gfm + var tokenizers = self.inlineTokenizers + var length = value.length + var previousDot = -1 + var protocolless = false + var dots + var lastTwoPartsStart + var start + var index + var pathStart + var path + var code + var end + var leftCount + var rightCount + var content + var children + var url + var exit + + if (!gfm) { + return + } + + // `WWW.` doesn’t work. + if (value.slice(0, 4) === 'www.') { + protocolless = true + index = 4 + } else if (value.slice(0, 7).toLowerCase() === 'http://') { + index = 7 + } else if (value.slice(0, 8).toLowerCase() === 'https://') { + index = 8 + } else { + return + } + + // Act as if the starting boundary is a dot. + previousDot = index - 1 + + // Parse a valid domain. + start = index + dots = [] + + while (index < length) { + code = value.charCodeAt(index) + + if (code === dot) { + // Dots may not appear after each other. + if (previousDot === index - 1) { + break + } + + dots.push(index) + previousDot = index + index++ + continue + } + + if ( + decimal(code) || + alphabetical(code) || + code === dash || + code === underscore + ) { + index++ + continue + } + + break + } + + // Ignore a final dot: + if (code === dot) { + dots.pop() + index-- + } + + // If there are not dots, exit. + if (dots[0] === undefined) { + return + } + + // If there is an underscore in the last two domain parts, exit: + // `www.example.c_m` and `www.ex_ample.com` are not OK, but + // `www.sub_domain.example.com` is. + lastTwoPartsStart = dots.length < 2 ? start : dots[dots.length - 2] + 1 + + if (value.slice(lastTwoPartsStart, index).indexOf('_') !== -1) { + return + } + + /* istanbul ignore if - never used (yet) */ + if (silent) { + return true + } + + end = index + pathStart = index + + // Parse a path. + while (index < length) { + code = value.charCodeAt(index) + + if (whitespace(code) || code === lessThan) { + break + } + + index++ + + if ( + code === exclamationMark || + code === asterisk || + code === comma || + code === dot || + code === colon || + code === questionMark || + code === underscore || + code === tilde + ) { + // Empty + } else { + end = index + } + } + + index = end + + // If the path ends in a closing paren, and the count of closing parens is + // higher than the opening count, then remove the supefluous closing parens. + if (value.charCodeAt(index - 1) === rightParenthesis) { + path = value.slice(pathStart, index) + leftCount = ccount(path, leftParenthesisCharacter) + rightCount = ccount(path, rightParenthesisCharacter) + + while (rightCount > leftCount) { + index = pathStart + path.lastIndexOf(rightParenthesisCharacter) + path = value.slice(pathStart, index) + rightCount-- + } + } + + if (value.charCodeAt(index - 1) === semicolon) { + // GitHub doesn’t document this, but final semicolons aren’t paret of the + // URL either. + index-- + + // // If the path ends in what looks like an entity, it’s not part of the path. + if (alphabetical(value.charCodeAt(index - 1))) { + end = index - 2 + + while (alphabetical(value.charCodeAt(end))) { + end-- + } + + if (value.charCodeAt(end) === ampersand) { + index = end + } + } + } + + content = value.slice(0, index) + url = decode(content, {nonTerminated: false}) + + if (protocolless) { + url = 'http://' + url + } + + exit = self.enterLink() + + // Temporarily remove all tokenizers except text in url. + self.inlineTokenizers = {text: tokenizers.text} + children = self.tokenizeInline(content, eat.now()) + self.inlineTokenizers = tokenizers + + exit() + + return eat(content)({type: 'link', title: null, url: url, children: children}) +} + + +/***/ }), + +/***/ 31975: +/***/ ((module) => { + +"use strict"; + + +module.exports = factory + +// Construct a tokenizer. This creates both `tokenizeInline` and `tokenizeBlock`. +function factory(type) { + return tokenize + + // Tokenizer for a bound `type`. + function tokenize(value, location) { + var self = this + var offset = self.offset + var tokens = [] + var methods = self[type + 'Methods'] + var tokenizers = self[type + 'Tokenizers'] + var line = location.line + var column = location.column + var index + var length + var method + var name + var matched + var valueLength + + // Trim white space only lines. + if (!value) { + return tokens + } + + // Expose on `eat`. + eat.now = now + eat.file = self.file + + // Sync initial offset. + updatePosition('') + + // Iterate over `value`, and iterate over all tokenizers. When one eats + // something, re-iterate with the remaining value. If no tokenizer eats, + // something failed (should not happen) and an exception is thrown. + while (value) { + index = -1 + length = methods.length + matched = false + + while (++index < length) { + name = methods[index] + method = tokenizers[name] + + // Previously, we had constructs such as footnotes and YAML that used + // these properties. + // Those are now external (plus there are userland extensions), that may + // still use them. + if ( + method && + /* istanbul ignore next */ (!method.onlyAtStart || self.atStart) && + /* istanbul ignore next */ (!method.notInList || !self.inList) && + /* istanbul ignore next */ (!method.notInBlock || !self.inBlock) && + (!method.notInLink || !self.inLink) + ) { + valueLength = value.length + + method.apply(self, [eat, value]) + + matched = valueLength !== value.length + + if (matched) { + break + } + } + } + + /* istanbul ignore if */ + if (!matched) { + self.file.fail(new Error('Infinite loop'), eat.now()) + } + } + + self.eof = now() + + return tokens + + // Update line, column, and offset based on `value`. + function updatePosition(subvalue) { + var lastIndex = -1 + var index = subvalue.indexOf('\n') + + while (index !== -1) { + line++ + lastIndex = index + index = subvalue.indexOf('\n', index + 1) + } + + if (lastIndex === -1) { + column += subvalue.length + } else { + column = subvalue.length - lastIndex + } + + if (line in offset) { + if (lastIndex !== -1) { + column += offset[line] + } else if (column <= offset[line]) { + column = offset[line] + 1 + } + } + } + + // Get offset. Called before the first character is eaten to retrieve the + // range’s offsets. + function getOffset() { + var indentation = [] + var pos = line + 1 + + // Done. Called when the last character is eaten to retrieve the range’s + // offsets. + return function () { + var last = line + 1 + + while (pos < last) { + indentation.push((offset[pos] || 0) + 1) + + pos++ + } + + return indentation + } + } + + // Get the current position. + function now() { + var pos = {line: line, column: column} + + pos.offset = self.toOffset(pos) + + return pos + } + + // Store position information for a node. + function Position(start) { + this.start = start + this.end = now() + } + + // Throw when a value is incorrectly eaten. This shouldn’t happen but will + // throw on new, incorrect rules. + function validateEat(subvalue) { + /* istanbul ignore if */ + if (value.slice(0, subvalue.length) !== subvalue) { + // Capture stack-trace. + self.file.fail( + new Error( + 'Incorrectly eaten value: please report this warning on https://git.io/vg5Ft' + ), + now() + ) + } + } + + // Mark position and patch `node.position`. + function position() { + var before = now() + + return update + + // Add the position to a node. + function update(node, indent) { + var previous = node.position + var start = previous ? previous.start : before + var combined = [] + var n = previous && previous.end.line + var l = before.line + + node.position = new Position(start) + + // If there was already a `position`, this node was merged. Fixing + // `start` wasn’t hard, but the indent is different. Especially + // because some information, the indent between `n` and `l` wasn’t + // tracked. Luckily, that space is (should be?) empty, so we can + // safely check for it now. + if (previous && indent && previous.indent) { + combined = previous.indent + + if (n < l) { + while (++n < l) { + combined.push((offset[n] || 0) + 1) + } + + combined.push(before.column) + } + + indent = combined.concat(indent) + } + + node.position.indent = indent || [] + + return node + } + } + + // Add `node` to `parent`s children or to `tokens`. Performs merges where + // possible. + function add(node, parent) { + var children = parent ? parent.children : tokens + var previous = children[children.length - 1] + var fn + + if ( + previous && + node.type === previous.type && + (node.type === 'text' || node.type === 'blockquote') && + mergeable(previous) && + mergeable(node) + ) { + fn = node.type === 'text' ? mergeText : mergeBlockquote + node = fn.call(self, previous, node) + } + + if (node !== previous) { + children.push(node) + } + + if (self.atStart && tokens.length !== 0) { + self.exitStart() + } + + return node + } + + // Remove `subvalue` from `value`. `subvalue` must be at the start of + // `value`. + function eat(subvalue) { + var indent = getOffset() + var pos = position() + var current = now() + + validateEat(subvalue) + + apply.reset = reset + reset.test = test + apply.test = test + + value = value.slice(subvalue.length) + + updatePosition(subvalue) + + indent = indent() + + return apply + + // Add the given arguments, add `position` to the returned node, and + // return the node. + function apply(node, parent) { + return pos(add(pos(node), parent), indent) + } + + // Functions just like apply, but resets the content: the line and + // column are reversed, and the eaten value is re-added. This is + // useful for nodes with a single type of content, such as lists and + // tables. See `apply` above for what parameters are expected. + function reset() { + var node = apply.apply(null, arguments) + + line = current.line + column = current.column + value = subvalue + value + + return node + } + + // Test the position, after eating, and reverse to a not-eaten state. + function test() { + var result = pos({}) + + line = current.line + column = current.column + value = subvalue + value + + return result.position + } + } + } +} + +// Check whether a node is mergeable with adjacent nodes. +function mergeable(node) { + var start + var end + + if (node.type !== 'text' || !node.position) { + return true + } + + start = node.position.start + end = node.position.end + + // Only merge nodes which occupy the same size as their `value`. + return ( + start.line !== end.line || end.column - start.column === node.value.length + ) +} + +// Merge two text nodes: `node` into `prev`. +function mergeText(previous, node) { + previous.value += node.value + + return previous +} + +// Merge two blockquotes: `node` into `prev`, unless in CommonMark or gfm modes. +function mergeBlockquote(previous, node) { + if (this.options.commonmark || this.options.gfm) { + return node + } + + previous.children = previous.children.concat(node.children) + + return previous +} + + +/***/ }), + +/***/ 57468: +/***/ ((module) => { + +"use strict"; + + +module.exports = factory + +var backslash = '\\' + +// Factory to de-escape a value, based on a list at `key` in `ctx`. +function factory(ctx, key) { + return unescape + + // De-escape a string using the expression at `key` in `ctx`. + function unescape(value) { + var previous = 0 + var index = value.indexOf(backslash) + var escape = ctx[key] + var queue = [] + var character + + while (index !== -1) { + queue.push(value.slice(previous, index)) + previous = index + 1 + character = value.charAt(previous) + + // If the following character is not a valid escape, add the slash. + if (!character || escape.indexOf(character) === -1) { + queue.push(backslash) + } + + index = value.indexOf(backslash, previous + 1) + } + + queue.push(value.slice(previous)) + + return queue.join('') + } +} + + +/***/ }), + +/***/ 19245: +/***/ ((module) => { + +"use strict"; + + +module.exports = indentation + +var tab = '\t' +var space = ' ' + +var spaceSize = 1 +var tabSize = 4 + +// Gets indentation information for a line. +function indentation(value) { + var index = 0 + var indent = 0 + var character = value.charAt(index) + var stops = {} + var size + var lastIndent = 0 + + while (character === tab || character === space) { + size = character === tab ? tabSize : spaceSize + + indent += size + + if (size > 1) { + indent = Math.floor(indent / size) * size + } + + while (lastIndent < indent) { + stops[++lastIndent] = index + } + + character = value.charAt(++index) + } + + return {indent: indent, stops: stops} +} + + +/***/ }), + +/***/ 76774: +/***/ ((__unused_webpack_module, exports) => { + +"use strict"; + + +var attributeName = '[a-zA-Z_:][a-zA-Z0-9:._-]*' +var unquoted = '[^"\'=<>`\\u0000-\\u0020]+' +var singleQuoted = "'[^']*'" +var doubleQuoted = '"[^"]*"' +var attributeValue = + '(?:' + unquoted + '|' + singleQuoted + '|' + doubleQuoted + ')' +var attribute = + '(?:\\s+' + attributeName + '(?:\\s*=\\s*' + attributeValue + ')?)' +var openTag = '<[A-Za-z][A-Za-z0-9\\-]*' + attribute + '*\\s*\\/?>' +var closeTag = '<\\/[A-Za-z][A-Za-z0-9\\-]*\\s*>' +var comment = '|' +var processing = '<[?].*?[?]>' +var declaration = ']*>' +var cdata = '' + +exports.X = new RegExp('^(?:' + openTag + '|' + closeTag + ')') + +exports.T = new RegExp( + '^(?:' + + openTag + + '|' + + closeTag + + '|' + + comment + + '|' + + processing + + '|' + + declaration + + '|' + + cdata + + ')' +) + + +/***/ }), + +/***/ 93808: +/***/ ((module) => { + +"use strict"; + + +module.exports = interrupt + +function interrupt(interruptors, tokenizers, ctx, parameters) { + var length = interruptors.length + var index = -1 + var interruptor + var config + + while (++index < length) { + interruptor = interruptors[index] + config = interruptor[1] || {} + + if ( + config.pedantic !== undefined && + config.pedantic !== ctx.options.pedantic + ) { + continue + } + + if ( + config.commonmark !== undefined && + config.commonmark !== ctx.options.commonmark + ) { + continue + } + + if (tokenizers[interruptor[0]].apply(ctx, parameters)) { + return true + } + } + + return false +} + + +/***/ }), + +/***/ 99880: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +var collapseWhiteSpace = __nccwpck_require__(37352) + +module.exports = normalize + +// Normalize an identifier. Collapses multiple white space characters into a +// single space, and removes casing. +function normalize(value) { + return collapseWhiteSpace(value).toLowerCase() +} + + +/***/ }), + +/***/ 39729: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +var trim = __nccwpck_require__(15464) +var repeat = __nccwpck_require__(40471) +var getIndent = __nccwpck_require__(19245) + +module.exports = indentation + +var lineFeed = '\n' +var space = ' ' +var exclamationMark = '!' + +// Remove the minimum indent from every line in `value`. Supports both tab, +// spaced, and mixed indentation (as well as possible). +function indentation(value, maximum) { + var values = value.split(lineFeed) + var position = values.length + 1 + var minIndent = Infinity + var matrix = [] + var index + var indentation + var stops + + values.unshift(repeat(space, maximum) + exclamationMark) + + while (position--) { + indentation = getIndent(values[position]) + + matrix[position] = indentation.stops + + if (trim(values[position]).length === 0) { + continue + } + + if (indentation.indent) { + if (indentation.indent > 0 && indentation.indent < minIndent) { + minIndent = indentation.indent + } + } else { + minIndent = Infinity + + break + } + } + + if (minIndent !== Infinity) { + position = values.length + + while (position--) { + stops = matrix[position] + index = minIndent + + while (index && !(index in stops)) { + index-- + } + + values[position] = values[position].slice(stops[index] + 1) + } + } + + values.shift() + + return values.join(lineFeed) +} + + +/***/ }), + +/***/ 32780: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +var unherit = __nccwpck_require__(25351) +var xtend = __nccwpck_require__(80869) +var Compiler = __nccwpck_require__(68275) + +module.exports = stringify +stringify.Compiler = Compiler + +function stringify(options) { + var Local = unherit(Compiler) + Local.prototype.options = xtend( + Local.prototype.options, + this.data('settings'), + options + ) + this.Compiler = Local +} + + +/***/ }), + +/***/ 68275: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +var xtend = __nccwpck_require__(80869) +var toggle = __nccwpck_require__(33600) + +module.exports = Compiler + +// Construct a new compiler. +function Compiler(tree, file) { + this.inLink = false + this.inTable = false + this.tree = tree + this.file = file + this.options = xtend(this.options) + this.setOptions({}) +} + +var proto = Compiler.prototype + +// Enter and exit helpers. */ +proto.enterLink = toggle('inLink', false) +proto.enterTable = toggle('inTable', false) +proto.enterLinkReference = __nccwpck_require__(15912) + +// Configuration. +proto.options = __nccwpck_require__(58236) +proto.setOptions = __nccwpck_require__(14179) + +proto.compile = __nccwpck_require__(68882) +proto.visit = __nccwpck_require__(14327) +proto.all = __nccwpck_require__(57524) +proto.block = __nccwpck_require__(65590) +proto.visitOrderedItems = __nccwpck_require__(60859) +proto.visitUnorderedItems = __nccwpck_require__(41250) + +// Expose visitors. +proto.visitors = { + root: __nccwpck_require__(55900), + text: __nccwpck_require__(49739), + heading: __nccwpck_require__(71124), + paragraph: __nccwpck_require__(71940), + blockquote: __nccwpck_require__(1647), + list: __nccwpck_require__(71656), + listItem: __nccwpck_require__(43138), + inlineCode: __nccwpck_require__(67163), + code: __nccwpck_require__(47149), + html: __nccwpck_require__(37209), + thematicBreak: __nccwpck_require__(73345), + strong: __nccwpck_require__(86747), + emphasis: __nccwpck_require__(61822), + break: __nccwpck_require__(83129), + delete: __nccwpck_require__(50455), + link: __nccwpck_require__(10892), + linkReference: __nccwpck_require__(15536), + imageReference: __nccwpck_require__(24147), + definition: __nccwpck_require__(16921), + image: __nccwpck_require__(63423), + table: __nccwpck_require__(17518), + tableCell: __nccwpck_require__(40955) +} + + +/***/ }), + +/***/ 58236: +/***/ ((module) => { + +"use strict"; + + +module.exports = { + gfm: true, + commonmark: false, + pedantic: false, + entities: 'false', + setext: false, + closeAtx: false, + tableCellPadding: true, + tablePipeAlign: true, + stringLength: stringLength, + incrementListMarker: true, + tightDefinitions: false, + fences: false, + fence: '`', + bullet: '-', + listItemIndent: 'tab', + rule: '*', + ruleSpaces: true, + ruleRepetition: 3, + strong: '*', + emphasis: '_' +} + +function stringLength(value) { + return value.length +} + + +/***/ }), + +/***/ 25193: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +var decimal = __nccwpck_require__(96734) +var alphanumeric = __nccwpck_require__(99624) +var whitespace = __nccwpck_require__(96472) +var escapes = __nccwpck_require__(80872) +var prefix = __nccwpck_require__(12140) + +module.exports = factory + +var tab = '\t' +var lineFeed = '\n' +var space = ' ' +var numberSign = '#' +var ampersand = '&' +var leftParenthesis = '(' +var rightParenthesis = ')' +var asterisk = '*' +var plusSign = '+' +var dash = '-' +var dot = '.' +var colon = ':' +var lessThan = '<' +var greaterThan = '>' +var leftSquareBracket = '[' +var backslash = '\\' +var rightSquareBracket = ']' +var underscore = '_' +var graveAccent = '`' +var verticalBar = '|' +var tilde = '~' +var exclamationMark = '!' + +var entities = { + '<': '<', + ':': ':', + '&': '&', + '|': '|', + '~': '~' +} + +var shortcut = 'shortcut' +var mailto = 'mailto' +var https = 'https' +var http = 'http' + +var blankExpression = /\n\s*$/ + +// Factory to escape characters. +function factory(options) { + return escape + + // Escape punctuation characters in a node’s value. + function escape(value, node, parent) { + var self = this + var gfm = options.gfm + var commonmark = options.commonmark + var pedantic = options.pedantic + var markers = commonmark ? [dot, rightParenthesis] : [dot] + var siblings = parent && parent.children + var index = siblings && siblings.indexOf(node) + var previous = siblings && siblings[index - 1] + var next = siblings && siblings[index + 1] + var length = value.length + var escapable = escapes(options) + var position = -1 + var queue = [] + var escaped = queue + var afterNewLine + var character + var wordCharBefore + var wordCharAfter + var offset + var replace + + if (previous) { + afterNewLine = text(previous) && blankExpression.test(previous.value) + } else { + afterNewLine = + !parent || parent.type === 'root' || parent.type === 'paragraph' + } + + while (++position < length) { + character = value.charAt(position) + replace = false + + if (character === '\n') { + afterNewLine = true + } else if ( + character === backslash || + character === graveAccent || + character === asterisk || + character === leftSquareBracket || + character === lessThan || + (character === ampersand && prefix(value.slice(position)) > 0) || + (character === rightSquareBracket && self.inLink) || + (gfm && character === tilde && value.charAt(position + 1) === tilde) || + (gfm && + character === verticalBar && + (self.inTable || alignment(value, position))) || + (character === underscore && + // Delegate leading/trailing underscores to the multinode version below. + position > 0 && + position < length - 1 && + (pedantic || + !alphanumeric(value.charAt(position - 1)) || + !alphanumeric(value.charAt(position + 1)))) || + (gfm && !self.inLink && character === colon && protocol(queue.join(''))) + ) { + replace = true + } else if (afterNewLine) { + if ( + character === greaterThan || + character === numberSign || + character === asterisk || + character === dash || + character === plusSign + ) { + replace = true + } else if (decimal(character)) { + offset = position + 1 + + while (offset < length) { + if (!decimal(value.charAt(offset))) { + break + } + + offset++ + } + + if (markers.indexOf(value.charAt(offset)) !== -1) { + next = value.charAt(offset + 1) + + if (!next || next === space || next === tab || next === lineFeed) { + queue.push(value.slice(position, offset)) + position = offset + character = value.charAt(position) + replace = true + } + } + } + } + + if (afterNewLine && !whitespace(character)) { + afterNewLine = false + } + + queue.push(replace ? one(character) : character) + } + + // Multi-node versions. + if (siblings && text(node)) { + // Check for an opening parentheses after a link-reference (which can be + // joined by white-space). + if (previous && previous.referenceType === shortcut) { + position = -1 + length = escaped.length + + while (++position < length) { + character = escaped[position] + + if (character === space || character === tab) { + continue + } + + if (character === leftParenthesis || character === colon) { + escaped[position] = one(character) + } + + break + } + + // If the current node is all spaces / tabs, preceded by a shortcut, + // and followed by a text starting with `(`, escape it. + if ( + text(next) && + position === length && + next.value.charAt(0) === leftParenthesis + ) { + escaped.push(backslash) + } + } + + // Ensure non-auto-links are not seen as links. This pattern needs to + // check the preceding nodes too. + if ( + gfm && + !self.inLink && + text(previous) && + value.charAt(0) === colon && + protocol(previous.value.slice(-6)) + ) { + escaped[0] = one(colon) + } + + // Escape ampersand if it would otherwise start an entity. + if ( + text(next) && + value.charAt(length - 1) === ampersand && + prefix(ampersand + next.value) !== 0 + ) { + escaped[escaped.length - 1] = one(ampersand) + } + + // Escape exclamation marks immediately followed by links. + if ( + next && + next.type === 'link' && + value.charAt(length - 1) === exclamationMark + ) { + escaped[escaped.length - 1] = one(exclamationMark) + } + + // Escape double tildes in GFM. + if ( + gfm && + text(next) && + value.charAt(length - 1) === tilde && + next.value.charAt(0) === tilde + ) { + escaped.splice(-1, 0, backslash) + } + + // Escape underscores, but not mid-word (unless in pedantic mode). + wordCharBefore = text(previous) && alphanumeric(previous.value.slice(-1)) + wordCharAfter = text(next) && alphanumeric(next.value.charAt(0)) + + if (length === 1) { + if ( + value === underscore && + (pedantic || !wordCharBefore || !wordCharAfter) + ) { + escaped.unshift(backslash) + } + } else { + if ( + value.charAt(0) === underscore && + (pedantic || !wordCharBefore || !alphanumeric(value.charAt(1))) + ) { + escaped.unshift(backslash) + } + + if ( + value.charAt(length - 1) === underscore && + (pedantic || + !wordCharAfter || + !alphanumeric(value.charAt(length - 2))) + ) { + escaped.splice(-1, 0, backslash) + } + } + } + + return escaped.join('') + + function one(character) { + return escapable.indexOf(character) === -1 + ? entities[character] + : backslash + character + } + } +} + +// Check if `index` in `value` is inside an alignment row. +function alignment(value, index) { + var start = value.lastIndexOf(lineFeed, index) + var end = value.indexOf(lineFeed, index) + var char + + end = end === -1 ? value.length : end + + while (++start < end) { + char = value.charAt(start) + + if ( + char !== colon && + char !== dash && + char !== space && + char !== verticalBar + ) { + return false + } + } + + return true +} + +// Check if `node` is a text node. +function text(node) { + return node && node.type === 'text' +} + +// Check if `value` ends in a protocol. +function protocol(value) { + var tail = value.slice(-6).toLowerCase() + return tail === mailto || tail.slice(-5) === https || tail.slice(-4) === http +} + + +/***/ }), + +/***/ 57524: +/***/ ((module) => { + +"use strict"; + + +module.exports = all + +// Visit all children of `parent`. +function all(parent) { + var self = this + var children = parent.children + var length = children.length + var results = [] + var index = -1 + + while (++index < length) { + results[index] = self.visit(children[index], parent) + } + + return results +} + + +/***/ }), + +/***/ 65590: +/***/ ((module) => { + +"use strict"; + + +module.exports = block + +var lineFeed = '\n' + +var blank = lineFeed + lineFeed +var triple = blank + lineFeed +var comment = blank + '' + blank + +// Stringify a block node with block children (e.g., `root` or `blockquote`). +// Knows about code following a list, or adjacent lists with similar bullets, +// and places an extra line feed between them. +function block(node) { + var self = this + var options = self.options + var fences = options.fences + var gap = options.commonmark ? comment : triple + var definitionGap = options.tightDefinitions ? lineFeed : blank + var values = [] + var children = node.children + var length = children.length + var index = -1 + var previous + var child + + while (++index < length) { + previous = child + child = children[index] + + if (previous) { + // A list preceding another list that are equally ordered, or a + // list preceding an indented code block, need a gap between them, + // so as not to see them as one list, or content of the list, + // respectively. + // + // In commonmark, only something that breaks both up can do that, + // so we opt for an empty, invisible comment. In other flavours, + // two blank lines are fine. + if ( + previous.type === 'list' && + ((child.type === 'list' && previous.ordered === child.ordered) || + (child.type === 'code' && !child.lang && !fences)) + ) { + values.push(gap) + } else if ( + previous.type === 'definition' && + child.type === 'definition' + ) { + values.push(definitionGap) + } else { + values.push(blank) + } + } + + values.push(self.visit(child, node)) + } + + return values.join('') +} + + +/***/ }), + +/***/ 68882: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +var compact = __nccwpck_require__(56558) + +module.exports = compile + +// Stringify the given tree. +function compile() { + return this.visit(compact(this.tree, this.options.commonmark)) +} + + +/***/ }), + +/***/ 14327: +/***/ ((module) => { + +"use strict"; + + +module.exports = one + +function one(node, parent) { + var self = this + var visitors = self.visitors + + // Fail on unknown nodes. + if (typeof visitors[node.type] !== 'function') { + self.file.fail( + new Error( + 'Missing compiler for node of type `' + node.type + '`: `' + node + '`' + ), + node + ) + } + + return visitors[node.type].call(self, node, parent) +} + + +/***/ }), + +/***/ 60859: +/***/ ((module) => { + +"use strict"; + + +module.exports = orderedItems + +var lineFeed = '\n' +var dot = '.' + +var blank = lineFeed + lineFeed + +// Visit ordered list items. +// +// Starts the list with +// `node.start` and increments each following list item +// bullet by one: +// +// 2. foo +// 3. bar +// +// In `incrementListMarker: false` mode, does not increment +// each marker and stays on `node.start`: +// +// 1. foo +// 1. bar +function orderedItems(node) { + var self = this + var fn = self.visitors.listItem + var increment = self.options.incrementListMarker + var values = [] + var start = node.start + var children = node.children + var length = children.length + var index = -1 + var bullet + + start = start == null ? 1 : start + + while (++index < length) { + bullet = (increment ? start + index : start) + dot + values[index] = fn.call(self, children[index], node, index, bullet) + } + + return values.join(node.spread ? blank : lineFeed) +} + + +/***/ }), + +/***/ 41250: +/***/ ((module) => { + +"use strict"; + + +module.exports = unorderedItems + +var lineFeed = '\n' + +var blank = lineFeed + lineFeed + +// Visit unordered list items. Uses `options.bullet` as each item’s bullet. +function unorderedItems(node) { + var self = this + var bullet = self.options.bullet + var fn = self.visitors.listItem + var children = node.children + var length = children.length + var index = -1 + var values = [] + + while (++index < length) { + values[index] = fn.call(self, children[index], node, index, bullet) + } + + return values.join(node.spread ? blank : lineFeed) +} + + +/***/ }), + +/***/ 14179: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +var xtend = __nccwpck_require__(80869) +var encode = __nccwpck_require__(55655) +var defaults = __nccwpck_require__(58236) +var escapeFactory = __nccwpck_require__(25193) +var identity = __nccwpck_require__(73983) + +module.exports = setOptions + +// Map of applicable enums. +var maps = { + entities: {true: true, false: true, numbers: true, escape: true}, + bullet: {'*': true, '-': true, '+': true}, + rule: {'-': true, _: true, '*': true}, + listItemIndent: {tab: true, mixed: true, 1: true}, + emphasis: {_: true, '*': true}, + strong: {_: true, '*': true}, + fence: {'`': true, '~': true} +} + +// Expose `validate`. +var validate = { + boolean: validateBoolean, + string: validateString, + number: validateNumber, + function: validateFunction +} + +// Set options. Does not overwrite previously set options. +function setOptions(options) { + var self = this + var current = self.options + var ruleRepetition + var key + + if (options == null) { + options = {} + } else if (typeof options === 'object') { + options = xtend(options) + } else { + throw new Error('Invalid value `' + options + '` for setting `options`') + } + + for (key in defaults) { + validate[typeof defaults[key]](options, key, current[key], maps[key]) + } + + ruleRepetition = options.ruleRepetition + + if (ruleRepetition && ruleRepetition < 3) { + raise(ruleRepetition, 'options.ruleRepetition') + } + + self.encode = encodeFactory(String(options.entities)) + self.escape = escapeFactory(options) + + self.options = options + + return self +} + +// Validate a value to be boolean. Defaults to `def`. Raises an exception with +// `context[name]` when not a boolean. +function validateBoolean(context, name, def) { + var value = context[name] + + if (value == null) { + value = def + } + + if (typeof value !== 'boolean') { + raise(value, 'options.' + name) + } + + context[name] = value +} + +// Validate a value to be boolean. Defaults to `def`. Raises an exception with +// `context[name]` when not a boolean. +function validateNumber(context, name, def) { + var value = context[name] + + if (value == null) { + value = def + } + + if (isNaN(value)) { + raise(value, 'options.' + name) + } + + context[name] = value +} + +// Validate a value to be in `map`. Defaults to `def`. Raises an exception +// with `context[name]` when not in `map`. +function validateString(context, name, def, map) { + var value = context[name] + + if (value == null) { + value = def + } + + value = String(value) + + if (!(value in map)) { + raise(value, 'options.' + name) + } + + context[name] = value +} + +// Validate a value to be function. Defaults to `def`. Raises an exception +// with `context[name]` when not a function. +function validateFunction(context, name, def) { + var value = context[name] + + if (value == null) { + value = def + } + + if (typeof value !== 'function') { + raise(value, 'options.' + name) + } + + context[name] = value +} + +// Factory to encode HTML entities. Creates a no-operation function when +// `type` is `'false'`, a function which encodes using named references when +// `type` is `'true'`, and a function which encodes using numbered references +// when `type` is `'numbers'`. +function encodeFactory(type) { + var options = {} + + if (type === 'false') { + return identity + } + + if (type === 'true') { + options.useNamedReferences = true + } + + if (type === 'escape') { + options.escapeOnly = true + options.useNamedReferences = true + } + + return wrapped + + // Encode HTML entities using the bound options. + function wrapped(value) { + return encode(value, options) + } +} + +// Throw an exception with in its `message` `value` and `name`. +function raise(value, name) { + throw new Error('Invalid value `' + value + '` for setting `' + name + '`') +} + + +/***/ }), + +/***/ 65118: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +var entityPrefixLength = __nccwpck_require__(12140) + +module.exports = copy + +var ampersand = '&' + +var punctuationExppresion = /[-!"#$%&'()*+,./:;<=>?@[\\\]^`{|}~_]/ + +// For shortcut and collapsed reference links, the contents is also an +// identifier, so we need to restore the original encoding and escaping +// that were present in the source string. +// +// This function takes the unescaped & unencoded value from shortcut’s +// child nodes and the identifier and encodes the former according to +// the latter. +function copy(value, identifier) { + var length = value.length + var count = identifier.length + var result = [] + var position = 0 + var index = 0 + var start + + while (index < length) { + // Take next non-punctuation characters from `value`. + start = index + + while (index < length && !punctuationExppresion.test(value.charAt(index))) { + index += 1 + } + + result.push(value.slice(start, index)) + + // Advance `position` to the next punctuation character. + while ( + position < count && + !punctuationExppresion.test(identifier.charAt(position)) + ) { + position += 1 + } + + // Take next punctuation characters from `identifier`. + start = position + + while ( + position < count && + punctuationExppresion.test(identifier.charAt(position)) + ) { + if (identifier.charAt(position) === ampersand) { + position += entityPrefixLength(identifier.slice(position)) + } + + position += 1 + } + + result.push(identifier.slice(start, position)) + + // Advance `index` to the next non-punctuation character. + while (index < length && punctuationExppresion.test(value.charAt(index))) { + index += 1 + } + } + + return result.join('') +} + + +/***/ }), + +/***/ 2679: +/***/ ((module) => { + +"use strict"; + + +module.exports = enclose + +var quotationMark = '"' +var apostrophe = "'" + +// There is currently no way to support nested delimiters across Markdown.pl, +// CommonMark, and GitHub (RedCarpet). The following code supports Markdown.pl +// and GitHub. +// CommonMark is not supported when mixing double- and single quotes inside a +// title. +function enclose(title) { + var delimiter = + title.indexOf(quotationMark) === -1 ? quotationMark : apostrophe + return delimiter + title + delimiter +} + + +/***/ }), + +/***/ 83287: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +var count = __nccwpck_require__(83076) + +module.exports = enclose + +var leftParenthesis = '(' +var rightParenthesis = ')' +var lessThan = '<' +var greaterThan = '>' + +var expression = /\s/ + +// Wrap `url` in angle brackets when needed, or when +// forced. +// In links, images, and definitions, the URL part needs +// to be enclosed when it: +// +// - has a length of `0` +// - contains white-space +// - has more or less opening than closing parentheses +function enclose(uri, always) { + if ( + always || + uri.length === 0 || + expression.test(uri) || + count(uri, leftParenthesis) !== count(uri, rightParenthesis) + ) { + return lessThan + uri + greaterThan + } + + return uri +} + + +/***/ }), + +/***/ 15912: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +var identity = __nccwpck_require__(73983) + +module.exports = enter + +// Shortcut and collapsed link references need no escaping and encoding during +// the processing of child nodes (it must be implied from identifier). +// +// This toggler turns encoding and escaping off for shortcut and collapsed +// references. +// +// Implies `enterLink`. +function enter(compiler, node) { + var encode = compiler.encode + var escape = compiler.escape + var exitLink = compiler.enterLink() + + if (node.referenceType !== 'shortcut' && node.referenceType !== 'collapsed') { + return exitLink + } + + compiler.escape = identity + compiler.encode = identity + + return exit + + function exit() { + compiler.encode = encode + compiler.escape = escape + exitLink() + } +} + + +/***/ }), + +/***/ 12140: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +var decode = __nccwpck_require__(75165) + +module.exports = length + +var ampersand = '&' + +// Returns the length of HTML entity that is a prefix of the given string +// (excluding the ampersand), 0 if it does not start with an entity. +function length(value) { + var prefix + + /* istanbul ignore if - Currently also tested for at implemention, but we + * keep it here because that’s proper. */ + if (value.charAt(0) !== ampersand) { + return 0 + } + + prefix = value.split(ampersand, 2).join(ampersand) + + return prefix.length - decode(prefix).length +} + + +/***/ }), + +/***/ 73983: +/***/ ((module) => { + +"use strict"; + + +module.exports = identity + +function identity(value) { + return value +} + + +/***/ }), + +/***/ 37815: +/***/ ((module) => { + +"use strict"; + + +module.exports = label + +var leftSquareBracket = '[' +var rightSquareBracket = ']' + +var shortcut = 'shortcut' +var collapsed = 'collapsed' + +// Stringify a reference label. +// Because link references are easily, mistakingly, created (for example, +// `[foo]`), reference nodes have an extra property depicting how it looked in +// the original document, so stringification can cause minimal changes. +function label(node) { + var type = node.referenceType + + if (type === shortcut) { + return '' + } + + return ( + leftSquareBracket + + (type === collapsed ? '' : node.label || node.identifier) + + rightSquareBracket + ) +} + + +/***/ }), + +/***/ 21128: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +var repeat = __nccwpck_require__(40471) + +module.exports = pad + +var lineFeed = '\n' +var space = ' ' + +var tabSize = 4 + +// Pad `value` with `level * tabSize` spaces. Respects lines. Ignores empty +// lines. +function pad(value, level) { + var values = value.split(lineFeed) + var index = values.length + var padding = repeat(space, level * tabSize) + + while (index--) { + if (values[index].length !== 0) { + values[index] = padding + values[index] + } + } + + return values.join(lineFeed) +} + + +/***/ }), + +/***/ 1647: +/***/ ((module) => { + +"use strict"; + + +module.exports = blockquote + +var lineFeed = '\n' +var space = ' ' +var greaterThan = '>' + +function blockquote(node) { + var values = this.block(node).split(lineFeed) + var result = [] + var length = values.length + var index = -1 + var value + + while (++index < length) { + value = values[index] + result[index] = (value ? space : '') + value + } + + return greaterThan + result.join(lineFeed + greaterThan) +} + + +/***/ }), + +/***/ 83129: +/***/ ((module) => { + +"use strict"; + + +module.exports = lineBreak + +var backslash = '\\' +var lineFeed = '\n' +var space = ' ' + +var commonmark = backslash + lineFeed +var normal = space + space + lineFeed + +function lineBreak() { + return this.options.commonmark ? commonmark : normal +} + + +/***/ }), + +/***/ 47149: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +var streak = __nccwpck_require__(86863) +var repeat = __nccwpck_require__(40471) +var pad = __nccwpck_require__(21128) + +module.exports = code + +var lineFeed = '\n' +var space = ' ' +var tilde = '~' +var graveAccent = '`' + +// Stringify code. +// Creates indented code when: +// +// - No language tag exists +// - Not in `fences: true` mode +// - A non-empty value exists +// +// Otherwise, GFM fenced code is created: +// +// ````markdown +// ```js +// foo(); +// ``` +// ```` +// +// When in ``fence: `~` `` mode, uses tildes as fences: +// +// ```markdown +// ~~~js +// foo(); +// ~~~ +// ``` +// +// Knows about internal fences: +// +// `````markdown +// ````markdown +// ```javascript +// foo(); +// ``` +// ```` +// ````` +function code(node, parent) { + var self = this + var value = node.value + var options = self.options + var marker = options.fence + var info = node.lang || '' + var fence + + if (info && node.meta) { + info += space + node.meta + } + + info = self.encode(self.escape(info, node)) + + // Without (needed) fences. + if ( + !info && + !options.fences && + value && + value.charAt(0) !== lineFeed && + value.charAt(value.length - 1) !== lineFeed + ) { + // Throw when pedantic, in a list item which isn’t compiled using a tab. + if ( + parent && + parent.type === 'listItem' && + options.listItemIndent !== 'tab' && + options.pedantic + ) { + self.file.fail( + 'Cannot indent code properly. See https://git.io/fxKR8', + node.position + ) + } + + return pad(value, 1) + } + + // Backticks in the info string don’t work with backtick fenced code. + // Backticks (and tildes) are fine in tilde fenced code. + if (marker === graveAccent && info.indexOf(graveAccent) !== -1) { + marker = tilde + } + + fence = repeat(marker, Math.max(streak(value, marker) + 1, 3)) + + return fence + info + lineFeed + value + lineFeed + fence +} + + +/***/ }), + +/***/ 16921: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +var uri = __nccwpck_require__(83287) +var title = __nccwpck_require__(2679) + +module.exports = definition + +var space = ' ' +var colon = ':' +var leftSquareBracket = '[' +var rightSquareBracket = ']' + +// Stringify an URL definition. +// +// Is smart about enclosing `url` (see `encloseURI()`) and `title` (see +// `encloseTitle()`). +// +// ```markdown +// [foo]: 'An "example" e-mail' +// ``` +function definition(node) { + var content = uri(node.url) + + if (node.title) { + content += space + title(node.title) + } + + return ( + leftSquareBracket + + (node.label || node.identifier) + + rightSquareBracket + + colon + + space + + content + ) +} + + +/***/ }), + +/***/ 50455: +/***/ ((module) => { + +"use strict"; + + +module.exports = strikethrough + +var tilde = '~' + +var fence = tilde + tilde + +function strikethrough(node) { + return fence + this.all(node).join('') + fence +} + + +/***/ }), + +/***/ 61822: +/***/ ((module) => { + +"use strict"; + + +module.exports = emphasis + +var underscore = '_' +var asterisk = '*' + +// Stringify an `emphasis`. +// +// The marker used is configurable through `emphasis`, which defaults to an +// underscore (`'_'`) but also accepts an asterisk (`'*'`): +// +// ```markdown +// *foo* +// ``` +// +// In `pedantic` mode, text which itself contains an underscore will cause the +// marker to default to an asterisk instead: +// +// ```markdown +// *foo_bar* +// ``` +function emphasis(node) { + var marker = this.options.emphasis + var content = this.all(node).join('') + + // When in pedantic mode, prevent using underscore as the marker when there + // are underscores in the content. + if ( + this.options.pedantic && + marker === underscore && + content.indexOf(marker) !== -1 + ) { + marker = asterisk + } + + return marker + content + marker +} + + +/***/ }), + +/***/ 71124: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +var repeat = __nccwpck_require__(40471) + +module.exports = heading + +var lineFeed = '\n' +var space = ' ' +var numberSign = '#' +var dash = '-' +var equalsTo = '=' + +// Stringify a heading. +// +// In `setext: true` mode and when `depth` is smaller than three, creates a +// setext header: +// +// ```markdown +// Foo +// === +// ``` +// +// Otherwise, an ATX header is generated: +// +// ```markdown +// ### Foo +// ``` +// +// In `closeAtx: true` mode, the header is closed with hashes: +// +// ```markdown +// ### Foo ### +// ``` +function heading(node) { + var self = this + var depth = node.depth + var setext = self.options.setext + var closeAtx = self.options.closeAtx + var content = self.all(node).join('') + var prefix + + if (setext && depth < 3) { + return ( + content + lineFeed + repeat(depth === 1 ? equalsTo : dash, content.length) + ) + } + + prefix = repeat(numberSign, node.depth) + + return prefix + space + content + (closeAtx ? space + prefix : '') +} + + +/***/ }), + +/***/ 37209: +/***/ ((module) => { + +"use strict"; + + +module.exports = html + +function html(node) { + return node.value +} + + +/***/ }), + +/***/ 24147: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +var label = __nccwpck_require__(37815) + +module.exports = imageReference + +var leftSquareBracket = '[' +var rightSquareBracket = ']' +var exclamationMark = '!' + +function imageReference(node) { + return ( + exclamationMark + + leftSquareBracket + + (this.encode(node.alt, node) || '') + + rightSquareBracket + + label(node) + ) +} + + +/***/ }), + +/***/ 63423: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +var uri = __nccwpck_require__(83287) +var title = __nccwpck_require__(2679) + +module.exports = image + +var space = ' ' +var leftParenthesis = '(' +var rightParenthesis = ')' +var leftSquareBracket = '[' +var rightSquareBracket = ']' +var exclamationMark = '!' + +// Stringify an image. +// +// Is smart about enclosing `url` (see `encloseURI()`) and `title` (see +// `encloseTitle()`). +// +// ```markdown +// ![foo]( 'My "favourite" icon') +// ``` +// +// Supports named entities in `url`, `alt`, and `title` when in +// `settings.encode` mode. +function image(node) { + var self = this + var content = uri(self.encode(node.url || '', node)) + var exit = self.enterLink() + var alt = self.encode(self.escape(node.alt || '', node)) + + exit() + + if (node.title) { + content += space + title(self.encode(node.title, node)) + } + + return ( + exclamationMark + + leftSquareBracket + + alt + + rightSquareBracket + + leftParenthesis + + content + + rightParenthesis + ) +} + + +/***/ }), + +/***/ 67163: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +var streak = __nccwpck_require__(86863) +var repeat = __nccwpck_require__(40471) + +module.exports = inlineCode + +var graveAccentChar = '`' +var lineFeed = 10 // '\n' +var space = 32 // ' ' +var graveAccent = 96 // '`' + +// Stringify inline code. +// +// Knows about internal ticks (`\``), and ensures one more tick is used to +// enclose the inline code: +// +// ````markdown +// ```foo ``bar`` baz``` +// ```` +// +// Even knows about inital and final ticks: +// +// ``markdown +// `` `foo `` +// `` foo` `` +// ``` +function inlineCode(node) { + var value = node.value + var ticks = repeat(graveAccentChar, streak(value, graveAccentChar) + 1) + var start = ticks + var end = ticks + var head = value.charCodeAt(0) + var tail = value.charCodeAt(value.length - 1) + var wrap = false + var index + var length + + if (head === graveAccent || tail === graveAccent) { + wrap = true + } else if (value.length > 2 && ws(head) && ws(tail)) { + index = 1 + length = value.length - 1 + + while (++index < length) { + if (!ws(value.charCodeAt(index))) { + wrap = true + break + } + } + } + + if (wrap) { + start += ' ' + end = ' ' + end + } + + return start + value + end +} + +function ws(code) { + return code === lineFeed || code === space +} + + +/***/ }), + +/***/ 15536: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +var copy = __nccwpck_require__(65118) +var label = __nccwpck_require__(37815) + +module.exports = linkReference + +var leftSquareBracket = '[' +var rightSquareBracket = ']' + +var shortcut = 'shortcut' +var collapsed = 'collapsed' + +function linkReference(node) { + var self = this + var type = node.referenceType + var exit = self.enterLinkReference(self, node) + var value = self.all(node).join('') + + exit() + + if (type === shortcut || type === collapsed) { + value = copy(value, node.label || node.identifier) + } + + return leftSquareBracket + value + rightSquareBracket + label(node) +} + + +/***/ }), + +/***/ 10892: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +var uri = __nccwpck_require__(83287) +var title = __nccwpck_require__(2679) + +module.exports = link + +var space = ' ' +var leftSquareBracket = '[' +var rightSquareBracket = ']' +var leftParenthesis = '(' +var rightParenthesis = ')' + +// Expression for a protocol: +// See . +var protocol = /^[a-z][a-z+.-]+:\/?/i + +// Stringify a link. +// +// When no title exists, the compiled `children` equal `url`, and `url` starts +// with a protocol, an auto link is created: +// +// ```markdown +// +// ``` +// +// Otherwise, is smart about enclosing `url` (see `encloseURI()`) and `title` +// (see `encloseTitle()`). +// ``` +// +// ```markdown +// [foo]( 'An "example" e-mail') +// ``` +// +// Supports named entities in the `url` and `title` when in `settings.encode` +// mode. +function link(node) { + var self = this + var content = self.encode(node.url || '', node) + var exit = self.enterLink() + var escaped = self.encode(self.escape(node.url || '', node)) + var value = self.all(node).join('') + + exit() + + if (node.title == null && protocol.test(content) && escaped === value) { + // Backslash escapes do not work in autolinks, so we do not escape. + return uri(self.encode(node.url), true) + } + + content = uri(content) + + if (node.title) { + content += space + title(self.encode(self.escape(node.title, node), node)) + } + + return ( + leftSquareBracket + + value + + rightSquareBracket + + leftParenthesis + + content + + rightParenthesis + ) +} + + +/***/ }), + +/***/ 43138: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +var repeat = __nccwpck_require__(40471) +var pad = __nccwpck_require__(21128) + +module.exports = listItem + +var lineFeed = '\n' +var space = ' ' +var leftSquareBracket = '[' +var rightSquareBracket = ']' +var lowercaseX = 'x' + +var ceil = Math.ceil +var blank = lineFeed + lineFeed + +var tabSize = 4 + +// Stringify a list item. +// +// Prefixes the content with a checked checkbox when `checked: true`: +// +// ```markdown +// [x] foo +// ``` +// +// Prefixes the content with an unchecked checkbox when `checked: false`: +// +// ```markdown +// [ ] foo +// ``` +function listItem(node, parent, position, bullet) { + var self = this + var style = self.options.listItemIndent + var marker = bullet || self.options.bullet + var spread = node.spread == null ? true : node.spread + var checked = node.checked + var children = node.children + var length = children.length + var values = [] + var index = -1 + var value + var indent + var spacing + + while (++index < length) { + values[index] = self.visit(children[index], node) + } + + value = values.join(spread ? blank : lineFeed) + + if (typeof checked === 'boolean') { + // Note: I’d like to be able to only add the space between the check and + // the value, but unfortunately github does not support empty list-items + // with a checkbox :( + value = + leftSquareBracket + + (checked ? lowercaseX : space) + + rightSquareBracket + + space + + value + } + + if (style === '1' || (style === 'mixed' && value.indexOf(lineFeed) === -1)) { + indent = marker.length + 1 + spacing = space + } else { + indent = ceil((marker.length + 1) / tabSize) * tabSize + spacing = repeat(space, indent - marker.length) + } + + return value + ? marker + spacing + pad(value, indent / tabSize).slice(indent) + : marker +} + + +/***/ }), + +/***/ 71656: +/***/ ((module) => { + +"use strict"; + + +module.exports = list + +function list(node) { + var fn = node.ordered ? this.visitOrderedItems : this.visitUnorderedItems + return fn.call(this, node) +} + + +/***/ }), + +/***/ 71940: +/***/ ((module) => { + +"use strict"; + + +module.exports = paragraph + +function paragraph(node) { + return this.all(node).join('') +} + + +/***/ }), + +/***/ 55900: +/***/ ((module) => { + +"use strict"; + + +module.exports = root + +var lineFeed = '\n' + +// Stringify a root. +// Adds a final newline to ensure valid POSIX files. */ +function root(node) { + var doc = this.block(node) + + if (doc.charAt(doc.length - 1) !== lineFeed) { + doc += lineFeed + } + + return doc +} + + +/***/ }), + +/***/ 86747: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +var repeat = __nccwpck_require__(40471) + +module.exports = strong + +// Stringify a `strong`. +// +// The marker used is configurable by `strong`, which defaults to an asterisk +// (`'*'`) but also accepts an underscore (`'_'`): +// +// ```markdown +// __foo__ +// ``` +function strong(node) { + var marker = repeat(this.options.strong, 2) + return marker + this.all(node).join('') + marker +} + + +/***/ }), + +/***/ 40955: +/***/ ((module) => { + +"use strict"; + + +module.exports = tableCell + +var lineFeed = /\r?\n/g + +function tableCell(node) { + return this.all(node).join('').replace(lineFeed, ' ') +} + + +/***/ }), + +/***/ 17518: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +var markdownTable = __nccwpck_require__(56144) + +module.exports = table + +// Stringify table. +// +// Creates a fenced table. +// The table has aligned delimiters by default, but not in +// `tablePipeAlign: false`: +// +// ```markdown +// | Header 1 | Header 2 | +// | :-: | - | +// | Alpha | Bravo | +// ``` +// +// The table is spaced by default, but not in `tableCellPadding: false`: +// +// ```markdown +// |Foo|Bar| +// |:-:|---| +// |Baz|Qux| +// ``` +function table(node) { + var self = this + var options = self.options + var padding = options.tableCellPadding + var alignDelimiters = options.tablePipeAlign + var stringLength = options.stringLength + var rows = node.children + var index = rows.length + var exit = self.enterTable() + var result = [] + + while (index--) { + result[index] = self.all(rows[index]) + } + + exit() + + return markdownTable(result, { + align: node.align, + alignDelimiters: alignDelimiters, + padding: padding, + stringLength: stringLength + }) +} + + +/***/ }), + +/***/ 49739: +/***/ ((module) => { + +"use strict"; + + +module.exports = text + +// Stringify text. +// Supports named entities in `settings.encode: true` mode: +// +// ```markdown +// AT&T +// ``` +// +// Supports numbered entities in `settings.encode: numbers` mode: +// +// ```markdown +// AT&T +// ``` +function text(node, parent) { + return this.encode(this.escape(node.value, node, parent), node) +} + + +/***/ }), + +/***/ 73345: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +var repeat = __nccwpck_require__(40471) + +module.exports = thematic + +var space = ' ' + +// Stringify a `thematic-break`. +// The character used is configurable through `rule`: (`'_'`): +// +// ```markdown +// ___ +// ``` +// +// The number of repititions is defined through `ruleRepetition` (`6`): +// +// ```markdown +// ****** +// ``` +// +// Whether spaces delimit each character, is configured through `ruleSpaces` +// (`true`): +// ```markdown +// * * * +// ``` +function thematic() { + var options = this.options + var rule = repeat(options.rule, options.ruleRepetition) + return options.ruleSpaces ? rule.split('').join(space) : rule +} + + +/***/ }), + +/***/ 56144: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +var repeat = __nccwpck_require__(40471) + +module.exports = markdownTable + +var trailingWhitespace = / +$/ + +// Characters. +var space = ' ' +var lineFeed = '\n' +var dash = '-' +var colon = ':' +var verticalBar = '|' + +var x = 0 +var C = 67 +var L = 76 +var R = 82 +var c = 99 +var l = 108 +var r = 114 + +// Create a table from a matrix of strings. +function markdownTable(table, options) { + var settings = options || {} + var padding = settings.padding !== false + var start = settings.delimiterStart !== false + var end = settings.delimiterEnd !== false + var align = (settings.align || []).concat() + var alignDelimiters = settings.alignDelimiters !== false + var alignments = [] + var stringLength = settings.stringLength || defaultStringLength + var rowIndex = -1 + var rowLength = table.length + var cellMatrix = [] + var sizeMatrix = [] + var row = [] + var sizes = [] + var longestCellByColumn = [] + var mostCellsPerRow = 0 + var cells + var columnIndex + var columnLength + var largest + var size + var cell + var lines + var line + var before + var after + var code + + // This is a superfluous loop if we don’t align delimiters, but otherwise we’d + // do superfluous work when aligning, so optimize for aligning. + while (++rowIndex < rowLength) { + cells = table[rowIndex] + columnIndex = -1 + columnLength = cells.length + row = [] + sizes = [] + + if (columnLength > mostCellsPerRow) { + mostCellsPerRow = columnLength + } + + while (++columnIndex < columnLength) { + cell = serialize(cells[columnIndex]) + + if (alignDelimiters === true) { + size = stringLength(cell) + sizes[columnIndex] = size + + largest = longestCellByColumn[columnIndex] + + if (largest === undefined || size > largest) { + longestCellByColumn[columnIndex] = size + } + } + + row.push(cell) + } + + cellMatrix[rowIndex] = row + sizeMatrix[rowIndex] = sizes + } + + // Figure out which alignments to use. + columnIndex = -1 + columnLength = mostCellsPerRow + + if (typeof align === 'object' && 'length' in align) { + while (++columnIndex < columnLength) { + alignments[columnIndex] = toAlignment(align[columnIndex]) + } + } else { + code = toAlignment(align) + + while (++columnIndex < columnLength) { + alignments[columnIndex] = code + } + } + + // Inject the alignment row. + columnIndex = -1 + columnLength = mostCellsPerRow + row = [] + sizes = [] + + while (++columnIndex < columnLength) { + code = alignments[columnIndex] + before = '' + after = '' + + if (code === l) { + before = colon + } else if (code === r) { + after = colon + } else if (code === c) { + before = colon + after = colon + } + + // There *must* be at least one hyphen-minus in each alignment cell. + size = alignDelimiters + ? Math.max( + 1, + longestCellByColumn[columnIndex] - before.length - after.length + ) + : 1 + + cell = before + repeat(dash, size) + after + + if (alignDelimiters === true) { + size = before.length + size + after.length + + if (size > longestCellByColumn[columnIndex]) { + longestCellByColumn[columnIndex] = size + } + + sizes[columnIndex] = size + } + + row[columnIndex] = cell + } + + // Inject the alignment row. + cellMatrix.splice(1, 0, row) + sizeMatrix.splice(1, 0, sizes) + + rowIndex = -1 + rowLength = cellMatrix.length + lines = [] + + while (++rowIndex < rowLength) { + row = cellMatrix[rowIndex] + sizes = sizeMatrix[rowIndex] + columnIndex = -1 + columnLength = mostCellsPerRow + line = [] + + while (++columnIndex < columnLength) { + cell = row[columnIndex] || '' + before = '' + after = '' + + if (alignDelimiters === true) { + size = longestCellByColumn[columnIndex] - (sizes[columnIndex] || 0) + code = alignments[columnIndex] + + if (code === r) { + before = repeat(space, size) + } else if (code === c) { + if (size % 2 === 0) { + before = repeat(space, size / 2) + after = before + } else { + before = repeat(space, size / 2 + 0.5) + after = repeat(space, size / 2 - 0.5) + } + } else { + after = repeat(space, size) + } + } + + if (start === true && columnIndex === 0) { + line.push(verticalBar) + } + + if ( + padding === true && + // Don’t add the opening space if we’re not aligning and the cell is + // empty: there will be a closing space. + !(alignDelimiters === false && cell === '') && + (start === true || columnIndex !== 0) + ) { + line.push(space) + } + + if (alignDelimiters === true) { + line.push(before) + } + + line.push(cell) + + if (alignDelimiters === true) { + line.push(after) + } + + if (padding === true) { + line.push(space) + } + + if (end === true || columnIndex !== columnLength - 1) { + line.push(verticalBar) + } + } + + line = line.join('') + + if (end === false) { + line = line.replace(trailingWhitespace, '') + } + + lines.push(line) + } + + return lines.join(lineFeed) +} + +function serialize(value) { + return value === null || value === undefined ? '' : String(value) +} + +function defaultStringLength(value) { + return value.length +} + +function toAlignment(value) { + var code = typeof value === 'string' ? value.charCodeAt(0) : x + + return code === L || code === l + ? l + : code === R || code === r + ? r + : code === C || code === c + ? c + : x +} + + +/***/ }), + +/***/ 11112: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +var unified = __nccwpck_require__(23578) +var parse = __nccwpck_require__(24982) +var stringify = __nccwpck_require__(32780) + +module.exports = unified().use(parse).use(stringify).freeze() + + +/***/ }), + +/***/ 33708: +/***/ ((module) => { + +"use strict"; + + +module.exports = bail + +function bail(err) { + if (err) { + throw err + } +} + + +/***/ }), + +/***/ 79857: +/***/ ((module) => { + +/*! + * Determine if an object is a Buffer + * + * @author Feross Aboukhadijeh + * @license MIT + */ + +module.exports = function isBuffer (obj) { + return obj != null && obj.constructor != null && + typeof obj.constructor.isBuffer === 'function' && obj.constructor.isBuffer(obj) +} + + +/***/ }), + +/***/ 14805: +/***/ ((module) => { + +"use strict"; + + +module.exports = value => { + if (Object.prototype.toString.call(value) !== '[object Object]') { + return false; + } + + const prototype = Object.getPrototypeOf(value); + return prototype === null || prototype === Object.prototype; +}; + + +/***/ }), + +/***/ 39693: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +var wrap = __nccwpck_require__(96285) + +module.exports = trough + +trough.wrap = wrap + +var slice = [].slice + +// Create new middleware. +function trough() { + var fns = [] + var middleware = {} + + middleware.run = run + middleware.use = use + + return middleware + + // Run `fns`. Last argument must be a completion handler. + function run() { + var index = -1 + var input = slice.call(arguments, 0, -1) + var done = arguments[arguments.length - 1] + + if (typeof done !== 'function') { + throw new Error('Expected function as last argument, not ' + done) + } + + next.apply(null, [null].concat(input)) + + // Run the next `fn`, if any. + function next(err) { + var fn = fns[++index] + var params = slice.call(arguments, 0) + var values = params.slice(1) + var length = input.length + var pos = -1 + + if (err) { + done(err) + return + } + + // Copy non-nully input into values. + while (++pos < length) { + if (values[pos] === null || values[pos] === undefined) { + values[pos] = input[pos] + } + } + + input = values + + // Next or done. + if (fn) { + wrap(fn, next).apply(null, input) + } else { + done.apply(null, [null].concat(input)) + } + } + } + + // Add `fn` to the list. + function use(fn) { + if (typeof fn !== 'function') { + throw new Error('Expected `fn` to be a function, not ' + fn) + } + + fns.push(fn) + + return middleware + } +} + + +/***/ }), + +/***/ 96285: +/***/ ((module) => { + +"use strict"; + + +var slice = [].slice + +module.exports = wrap + +// Wrap `fn`. +// Can be sync or async; return a promise, receive a completion handler, return +// new values and errors. +function wrap(fn, callback) { + var invoked + + return wrapped + + function wrapped() { + var params = slice.call(arguments, 0) + var callback = fn.length > params.length + var result + + if (callback) { + params.push(done) + } + + try { + result = fn.apply(null, params) + } catch (error) { + // Well, this is quite the pickle. + // `fn` received a callback and invoked it (thus continuing the pipeline), + // but later also threw an error. + // We’re not about to restart the pipeline again, so the only thing left + // to do is to throw the thing instead. + if (callback && invoked) { + throw error + } + + return done(error) + } + + if (!callback) { + if (result && typeof result.then === 'function') { + result.then(then, done) + } else if (result instanceof Error) { + done(result) + } else { + then(result) + } + } + } + + // Invoke `next`, only once. + function done() { + if (!invoked) { + invoked = true + + callback.apply(null, arguments) + } + } + + // Invoke `done` with one value. + // Tracks if an error is passed, too. + function then(value) { + done(null, value) + } +} + + +/***/ }), + +/***/ 23578: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +var bail = __nccwpck_require__(33708) +var buffer = __nccwpck_require__(79857) +var extend = __nccwpck_require__(23860) +var plain = __nccwpck_require__(14805) +var trough = __nccwpck_require__(39693) +var vfile = __nccwpck_require__(61952) + +// Expose a frozen processor. +module.exports = unified().freeze() + +var slice = [].slice +var own = {}.hasOwnProperty + +// Process pipeline. +var pipeline = trough() + .use(pipelineParse) + .use(pipelineRun) + .use(pipelineStringify) + +function pipelineParse(p, ctx) { + ctx.tree = p.parse(ctx.file) +} + +function pipelineRun(p, ctx, next) { + p.run(ctx.tree, ctx.file, done) + + function done(error, tree, file) { + if (error) { + next(error) + } else { + ctx.tree = tree + ctx.file = file + next() + } + } +} + +function pipelineStringify(p, ctx) { + var result = p.stringify(ctx.tree, ctx.file) + + if (result === undefined || result === null) { + // Empty. + } else if (typeof result === 'string' || buffer(result)) { + if ('value' in ctx.file) { + ctx.file.value = result + } + + ctx.file.contents = result + } else { + ctx.file.result = result + } +} + +// Function to create the first processor. +function unified() { + var attachers = [] + var transformers = trough() + var namespace = {} + var freezeIndex = -1 + var frozen + + // Data management. + processor.data = data + + // Lock. + processor.freeze = freeze + + // Plugins. + processor.attachers = attachers + processor.use = use + + // API. + processor.parse = parse + processor.stringify = stringify + processor.run = run + processor.runSync = runSync + processor.process = process + processor.processSync = processSync + + // Expose. + return processor + + // Create a new processor based on the processor in the current scope. + function processor() { + var destination = unified() + var index = -1 + + while (++index < attachers.length) { + destination.use.apply(null, attachers[index]) + } + + destination.data(extend(true, {}, namespace)) + + return destination + } + + // Freeze: used to signal a processor that has finished configuration. + // + // For example, take unified itself: it’s frozen. + // Plugins should not be added to it. + // Rather, it should be extended, by invoking it, before modifying it. + // + // In essence, always invoke this when exporting a processor. + function freeze() { + var values + var transformer + + if (frozen) { + return processor + } + + while (++freezeIndex < attachers.length) { + values = attachers[freezeIndex] + + if (values[1] === false) { + continue + } + + if (values[1] === true) { + values[1] = undefined + } + + transformer = values[0].apply(processor, values.slice(1)) + + if (typeof transformer === 'function') { + transformers.use(transformer) + } + } + + frozen = true + freezeIndex = Infinity + + return processor + } + + // Data management. + // Getter / setter for processor-specific informtion. + function data(key, value) { + if (typeof key === 'string') { + // Set `key`. + if (arguments.length === 2) { + assertUnfrozen('data', frozen) + namespace[key] = value + return processor + } + + // Get `key`. + return (own.call(namespace, key) && namespace[key]) || null + } + + // Set space. + if (key) { + assertUnfrozen('data', frozen) + namespace = key + return processor + } + + // Get space. + return namespace + } + + // Plugin management. + // + // Pass it: + // * an attacher and options, + // * a preset, + // * a list of presets, attachers, and arguments (list of attachers and + // options). + function use(value) { + var settings + + assertUnfrozen('use', frozen) + + if (value === null || value === undefined) { + // Empty. + } else if (typeof value === 'function') { + addPlugin.apply(null, arguments) + } else if (typeof value === 'object') { + if ('length' in value) { + addList(value) + } else { + addPreset(value) + } + } else { + throw new Error('Expected usable value, not `' + value + '`') + } + + if (settings) { + namespace.settings = extend(namespace.settings || {}, settings) + } + + return processor + + function addPreset(result) { + addList(result.plugins) + + if (result.settings) { + settings = extend(settings || {}, result.settings) + } + } + + function add(value) { + if (typeof value === 'function') { + addPlugin(value) + } else if (typeof value === 'object') { + if ('length' in value) { + addPlugin.apply(null, value) + } else { + addPreset(value) + } + } else { + throw new Error('Expected usable value, not `' + value + '`') + } + } + + function addList(plugins) { + var index = -1 + + if (plugins === null || plugins === undefined) { + // Empty. + } else if (typeof plugins === 'object' && 'length' in plugins) { + while (++index < plugins.length) { + add(plugins[index]) + } + } else { + throw new Error('Expected a list of plugins, not `' + plugins + '`') + } + } + + function addPlugin(plugin, value) { + var entry = find(plugin) + + if (entry) { + if (plain(entry[1]) && plain(value)) { + value = extend(true, entry[1], value) + } + + entry[1] = value + } else { + attachers.push(slice.call(arguments)) + } + } + } + + function find(plugin) { + var index = -1 + + while (++index < attachers.length) { + if (attachers[index][0] === plugin) { + return attachers[index] + } + } + } + + // Parse a file (in string or vfile representation) into a unist node using + // the `Parser` on the processor. + function parse(doc) { + var file = vfile(doc) + var Parser + + freeze() + Parser = processor.Parser + assertParser('parse', Parser) + + if (newable(Parser, 'parse')) { + return new Parser(String(file), file).parse() + } + + return Parser(String(file), file) // eslint-disable-line new-cap + } + + // Run transforms on a unist node representation of a file (in string or + // vfile representation), async. + function run(node, file, cb) { + assertNode(node) + freeze() + + if (!cb && typeof file === 'function') { + cb = file + file = null + } + + if (!cb) { + return new Promise(executor) + } + + executor(null, cb) + + function executor(resolve, reject) { + transformers.run(node, vfile(file), done) + + function done(error, tree, file) { + tree = tree || node + if (error) { + reject(error) + } else if (resolve) { + resolve(tree) + } else { + cb(null, tree, file) + } + } + } + } + + // Run transforms on a unist node representation of a file (in string or + // vfile representation), sync. + function runSync(node, file) { + var result + var complete + + run(node, file, done) + + assertDone('runSync', 'run', complete) + + return result + + function done(error, tree) { + complete = true + result = tree + bail(error) + } + } + + // Stringify a unist node representation of a file (in string or vfile + // representation) into a string using the `Compiler` on the processor. + function stringify(node, doc) { + var file = vfile(doc) + var Compiler + + freeze() + Compiler = processor.Compiler + assertCompiler('stringify', Compiler) + assertNode(node) + + if (newable(Compiler, 'compile')) { + return new Compiler(node, file).compile() + } + + return Compiler(node, file) // eslint-disable-line new-cap + } + + // Parse a file (in string or vfile representation) into a unist node using + // the `Parser` on the processor, then run transforms on that node, and + // compile the resulting node using the `Compiler` on the processor, and + // store that result on the vfile. + function process(doc, cb) { + freeze() + assertParser('process', processor.Parser) + assertCompiler('process', processor.Compiler) + + if (!cb) { + return new Promise(executor) + } + + executor(null, cb) + + function executor(resolve, reject) { + var file = vfile(doc) + + pipeline.run(processor, {file: file}, done) + + function done(error) { + if (error) { + reject(error) + } else if (resolve) { + resolve(file) + } else { + cb(null, file) + } + } + } + } + + // Process the given document (in string or vfile representation), sync. + function processSync(doc) { + var file + var complete + + freeze() + assertParser('processSync', processor.Parser) + assertCompiler('processSync', processor.Compiler) + file = vfile(doc) + + process(file, done) + + assertDone('processSync', 'process', complete) + + return file + + function done(error) { + complete = true + bail(error) + } + } +} + +// Check if `value` is a constructor. +function newable(value, name) { + return ( + typeof value === 'function' && + value.prototype && + // A function with keys in its prototype is probably a constructor. + // Classes’ prototype methods are not enumerable, so we check if some value + // exists in the prototype. + (keys(value.prototype) || name in value.prototype) + ) +} + +// Check if `value` is an object with keys. +function keys(value) { + var key + for (key in value) { + return true + } + + return false +} + +// Assert a parser is available. +function assertParser(name, Parser) { + if (typeof Parser !== 'function') { + throw new Error('Cannot `' + name + '` without `Parser`') + } +} + +// Assert a compiler is available. +function assertCompiler(name, Compiler) { + if (typeof Compiler !== 'function') { + throw new Error('Cannot `' + name + '` without `Compiler`') + } +} + +// Assert the processor is not frozen. +function assertUnfrozen(name, frozen) { + if (frozen) { + throw new Error( + 'Cannot invoke `' + + name + + '` on a frozen processor.\nCreate a new processor first, by invoking it: use `processor()` instead of `processor`.' + ) + } +} + +// Assert `node` is a unist node. +function assertNode(node) { + if (!node || typeof node.type !== 'string') { + throw new Error('Expected node, got `' + node + '`') + } +} + +// Assert that `complete` is `true`. +function assertDone(name, asyncName, complete) { + if (!complete) { + throw new Error( + '`' + name + '` finished async. Use `' + asyncName + '` instead' + ) + } +} + + +/***/ }), + +/***/ 11340: +/***/ ((module) => { + +"use strict"; + + +var own = {}.hasOwnProperty + +module.exports = stringify + +function stringify(value) { + // Nothing. + if (!value || typeof value !== 'object') { + return '' + } + + // Node. + if (own.call(value, 'position') || own.call(value, 'type')) { + return position(value.position) + } + + // Position. + if (own.call(value, 'start') || own.call(value, 'end')) { + return position(value) + } + + // Point. + if (own.call(value, 'line') || own.call(value, 'column')) { + return point(value) + } + + // ? + return '' +} + +function point(point) { + if (!point || typeof point !== 'object') { + point = {} + } + + return index(point.line) + ':' + index(point.column) +} + +function position(pos) { + if (!pos || typeof pos !== 'object') { + pos = {} + } + + return point(pos.start) + '-' + point(pos.end) +} + +function index(value) { + return value && typeof value === 'number' ? value : 1 +} + + +/***/ }), + +/***/ 63594: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +var stringify = __nccwpck_require__(11340) + +module.exports = VMessage + +// Inherit from `Error#`. +function VMessagePrototype() {} +VMessagePrototype.prototype = Error.prototype +VMessage.prototype = new VMessagePrototype() + +// Message properties. +var proto = VMessage.prototype + +proto.file = '' +proto.name = '' +proto.reason = '' +proto.message = '' +proto.stack = '' +proto.fatal = null +proto.column = null +proto.line = null + +// Construct a new VMessage. +// +// Note: We cannot invoke `Error` on the created context, as that adds readonly +// `line` and `column` attributes on Safari 9, thus throwing and failing the +// data. +function VMessage(reason, position, origin) { + var parts + var range + var location + + if (typeof position === 'string') { + origin = position + position = null + } + + parts = parseOrigin(origin) + range = stringify(position) || '1:1' + + location = { + start: {line: null, column: null}, + end: {line: null, column: null} + } + + // Node. + if (position && position.position) { + position = position.position + } + + if (position) { + // Position. + if (position.start) { + location = position + position = position.start + } else { + // Point. + location.start = position + } + } + + if (reason.stack) { + this.stack = reason.stack + reason = reason.message + } + + this.message = reason + this.name = range + this.reason = reason + this.line = position ? position.line : null + this.column = position ? position.column : null + this.location = location + this.source = parts[0] + this.ruleId = parts[1] +} + +function parseOrigin(origin) { + var result = [null, null] + var index + + if (typeof origin === 'string') { + index = origin.indexOf(':') + + if (index === -1) { + result[1] = origin + } else { + result[0] = origin.slice(0, index) + result[1] = origin.slice(index + 1) + } + } + + return result +} + + +/***/ }), + +/***/ 61952: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +module.exports = __nccwpck_require__(13484) + + +/***/ }), + +/***/ 51669: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +var p = __nccwpck_require__(2715) +var proc = __nccwpck_require__(43584) +var buffer = __nccwpck_require__(79857) + +module.exports = VFile + +var own = {}.hasOwnProperty + +// Order of setting (least specific to most), we need this because otherwise +// `{stem: 'a', path: '~/b.js'}` would throw, as a path is needed before a +// stem can be set. +var order = ['history', 'path', 'basename', 'stem', 'extname', 'dirname'] + +VFile.prototype.toString = toString + +// Access full path (`~/index.min.js`). +Object.defineProperty(VFile.prototype, 'path', {get: getPath, set: setPath}) + +// Access parent path (`~`). +Object.defineProperty(VFile.prototype, 'dirname', { + get: getDirname, + set: setDirname +}) + +// Access basename (`index.min.js`). +Object.defineProperty(VFile.prototype, 'basename', { + get: getBasename, + set: setBasename +}) + +// Access extname (`.js`). +Object.defineProperty(VFile.prototype, 'extname', { + get: getExtname, + set: setExtname +}) + +// Access stem (`index.min`). +Object.defineProperty(VFile.prototype, 'stem', {get: getStem, set: setStem}) + +// Construct a new file. +function VFile(options) { + var prop + var index + + if (!options) { + options = {} + } else if (typeof options === 'string' || buffer(options)) { + options = {contents: options} + } else if ('message' in options && 'messages' in options) { + return options + } + + if (!(this instanceof VFile)) { + return new VFile(options) + } + + this.data = {} + this.messages = [] + this.history = [] + this.cwd = proc.cwd() + + // Set path related properties in the correct order. + index = -1 + + while (++index < order.length) { + prop = order[index] + + if (own.call(options, prop)) { + this[prop] = options[prop] + } + } + + // Set non-path related properties. + for (prop in options) { + if (order.indexOf(prop) < 0) { + this[prop] = options[prop] + } + } +} + +function getPath() { + return this.history[this.history.length - 1] +} + +function setPath(path) { + assertNonEmpty(path, 'path') + + if (this.path !== path) { + this.history.push(path) + } +} + +function getDirname() { + return typeof this.path === 'string' ? p.dirname(this.path) : undefined +} + +function setDirname(dirname) { + assertPath(this.path, 'dirname') + this.path = p.join(dirname || '', this.basename) +} + +function getBasename() { + return typeof this.path === 'string' ? p.basename(this.path) : undefined +} + +function setBasename(basename) { + assertNonEmpty(basename, 'basename') + assertPart(basename, 'basename') + this.path = p.join(this.dirname || '', basename) +} + +function getExtname() { + return typeof this.path === 'string' ? p.extname(this.path) : undefined +} + +function setExtname(extname) { + assertPart(extname, 'extname') + assertPath(this.path, 'extname') + + if (extname) { + if (extname.charCodeAt(0) !== 46 /* `.` */) { + throw new Error('`extname` must start with `.`') + } + + if (extname.indexOf('.', 1) > -1) { + throw new Error('`extname` cannot contain multiple dots') + } + } + + this.path = p.join(this.dirname, this.stem + (extname || '')) +} + +function getStem() { + return typeof this.path === 'string' + ? p.basename(this.path, this.extname) + : undefined +} + +function setStem(stem) { + assertNonEmpty(stem, 'stem') + assertPart(stem, 'stem') + this.path = p.join(this.dirname || '', stem + (this.extname || '')) +} + +// Get the value of the file. +function toString(encoding) { + return (this.contents || '').toString(encoding) +} + +// Assert that `part` is not a path (i.e., does not contain `p.sep`). +function assertPart(part, name) { + if (part && part.indexOf(p.sep) > -1) { + throw new Error( + '`' + name + '` cannot be a path: did not expect `' + p.sep + '`' + ) + } +} + +// Assert that `part` is not empty. +function assertNonEmpty(part, name) { + if (!part) { + throw new Error('`' + name + '` cannot be empty') + } +} + +// Assert `path` exists. +function assertPath(path, name) { + if (!path) { + throw new Error('Setting `' + name + '` requires `path` to be set too') + } +} + + +/***/ }), + +/***/ 13484: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +var VMessage = __nccwpck_require__(63594) +var VFile = __nccwpck_require__(51669) + +module.exports = VFile + +VFile.prototype.message = message +VFile.prototype.info = info +VFile.prototype.fail = fail + +// Create a message with `reason` at `position`. +// When an error is passed in as `reason`, copies the stack. +function message(reason, position, origin) { + var message = new VMessage(reason, position, origin) + + if (this.path) { + message.name = this.path + ':' + message.name + message.file = this.path + } + + message.fatal = false + + this.messages.push(message) + + return message +} + +// Fail: creates a vmessage, associates it with the file, and throws it. +function fail() { + var message = this.message.apply(this, arguments) + + message.fatal = true + + throw message +} + +// Info: creates a vmessage, associates it with the file, and marks the fatality +// as null. +function info() { + var message = this.message.apply(this, arguments) + + message.fatal = null + + return message +} + + +/***/ }), + +/***/ 2715: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +module.exports = __nccwpck_require__(16928) + + +/***/ }), + +/***/ 43584: +/***/ ((module) => { + +"use strict"; + + +module.exports = process + + +/***/ }), + +/***/ 40471: +/***/ ((module) => { + +"use strict"; +/*! + * repeat-string + * + * Copyright (c) 2014-2015, Jon Schlinkert. + * Licensed under the MIT License. + */ + + + +/** + * Results cache + */ + +var res = ''; +var cache; + +/** + * Expose `repeat` + */ + +module.exports = repeat; + +/** + * Repeat the given `string` the specified `number` + * of times. + * + * **Example:** + * + * ```js + * var repeat = require('repeat-string'); + * repeat('A', 5); + * //=> AAAAA + * ``` + * + * @param {String} `string` The string to repeat + * @param {Number} `number` The number of times to repeat the string + * @return {String} Repeated string + * @api public + */ + +function repeat(str, num) { + if (typeof str !== 'string') { + throw new TypeError('expected a string'); + } + + // cover common, quick use cases + if (num === 1) return str; + if (num === 2) return str + str; + + var max = str.length * num; + if (cache !== str || typeof cache === 'undefined') { + cache = str; + res = ''; + } else if (res.length >= max) { + return res.substr(0, max); + } + + while (max > res.length && num > 1) { + if (num & 1) { + res += str; + } + + num >>= 1; + str += str; + } + + res += str; + res = res.substr(0, max); + return res; +} + + +/***/ }), + +/***/ 92312: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +var async = __nccwpck_require__(56912); +async.core = __nccwpck_require__(12333); +async.isCore = __nccwpck_require__(54040); +async.sync = __nccwpck_require__(70225); + +module.exports = async; + + +/***/ }), + +/***/ 56912: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +var fs = __nccwpck_require__(79896); +var getHomedir = __nccwpck_require__(22282); +var path = __nccwpck_require__(16928); +var caller = __nccwpck_require__(87481); +var nodeModulesPaths = __nccwpck_require__(9411); +var normalizeOptions = __nccwpck_require__(29496); +var isCore = __nccwpck_require__(65223); + +var realpathFS = process.platform !== 'win32' && fs.realpath && typeof fs.realpath.native === 'function' ? fs.realpath.native : fs.realpath; + +var homedir = getHomedir(); +var defaultPaths = function () { + return [ + path.join(homedir, '.node_modules'), + path.join(homedir, '.node_libraries') + ]; +}; + +var defaultIsFile = function isFile(file, cb) { + fs.stat(file, function (err, stat) { + if (!err) { + return cb(null, stat.isFile() || stat.isFIFO()); + } + if (err.code === 'ENOENT' || err.code === 'ENOTDIR') return cb(null, false); + return cb(err); + }); +}; + +var defaultIsDir = function isDirectory(dir, cb) { + fs.stat(dir, function (err, stat) { + if (!err) { + return cb(null, stat.isDirectory()); + } + if (err.code === 'ENOENT' || err.code === 'ENOTDIR') return cb(null, false); + return cb(err); + }); +}; + +var defaultRealpath = function realpath(x, cb) { + realpathFS(x, function (realpathErr, realPath) { + if (realpathErr && realpathErr.code !== 'ENOENT') cb(realpathErr); + else cb(null, realpathErr ? x : realPath); + }); +}; + +var maybeRealpath = function maybeRealpath(realpath, x, opts, cb) { + if (opts && opts.preserveSymlinks === false) { + realpath(x, cb); + } else { + cb(null, x); + } +}; + +var defaultReadPackage = function defaultReadPackage(readFile, pkgfile, cb) { + readFile(pkgfile, function (readFileErr, body) { + if (readFileErr) cb(readFileErr); + else { + try { + var pkg = JSON.parse(body); + cb(null, pkg); + } catch (jsonErr) { + cb(null); + } + } + }); +}; + +var getPackageCandidates = function getPackageCandidates(x, start, opts) { + var dirs = nodeModulesPaths(start, opts, x); + for (var i = 0; i < dirs.length; i++) { + dirs[i] = path.join(dirs[i], x); + } + return dirs; +}; + +module.exports = function resolve(x, options, callback) { + var cb = callback; + var opts = options; + if (typeof options === 'function') { + cb = opts; + opts = {}; + } + if (typeof x !== 'string') { + var err = new TypeError('Path must be a string.'); + return process.nextTick(function () { + cb(err); + }); + } + + opts = normalizeOptions(x, opts); + + var isFile = opts.isFile || defaultIsFile; + var isDirectory = opts.isDirectory || defaultIsDir; + var readFile = opts.readFile || fs.readFile; + var realpath = opts.realpath || defaultRealpath; + var readPackage = opts.readPackage || defaultReadPackage; + if (opts.readFile && opts.readPackage) { + var conflictErr = new TypeError('`readFile` and `readPackage` are mutually exclusive.'); + return process.nextTick(function () { + cb(conflictErr); + }); + } + var packageIterator = opts.packageIterator; + + var extensions = opts.extensions || ['.js']; + var includeCoreModules = opts.includeCoreModules !== false; + var basedir = opts.basedir || path.dirname(caller()); + var parent = opts.filename || basedir; + + opts.paths = opts.paths || defaultPaths(); + + // ensure that `basedir` is an absolute path at this point, resolving against the process' current working directory + var absoluteStart = path.resolve(basedir); + + maybeRealpath( + realpath, + absoluteStart, + opts, + function (err, realStart) { + if (err) cb(err); + else init(realStart); + } + ); + + var res; + function init(basedir) { + if ((/^(?:\.\.?(?:\/|$)|\/|([A-Za-z]:)?[/\\])/).test(x)) { + res = path.resolve(basedir, x); + if (x === '.' || x === '..' || x.slice(-1) === '/') res += '/'; + if ((/\/$/).test(x) && res === basedir) { + loadAsDirectory(res, opts.package, onfile); + } else loadAsFile(res, opts.package, onfile); + } else if (includeCoreModules && isCore(x)) { + return cb(null, x); + } else loadNodeModules(x, basedir, function (err, n, pkg) { + if (err) cb(err); + else if (n) { + return maybeRealpath(realpath, n, opts, function (err, realN) { + if (err) { + cb(err); + } else { + cb(null, realN, pkg); + } + }); + } else { + var moduleError = new Error("Cannot find module '" + x + "' from '" + parent + "'"); + moduleError.code = 'MODULE_NOT_FOUND'; + cb(moduleError); + } + }); + } + + function onfile(err, m, pkg) { + if (err) cb(err); + else if (m) cb(null, m, pkg); + else loadAsDirectory(res, function (err, d, pkg) { + if (err) cb(err); + else if (d) { + maybeRealpath(realpath, d, opts, function (err, realD) { + if (err) { + cb(err); + } else { + cb(null, realD, pkg); + } + }); + } else { + var moduleError = new Error("Cannot find module '" + x + "' from '" + parent + "'"); + moduleError.code = 'MODULE_NOT_FOUND'; + cb(moduleError); + } + }); + } + + function loadAsFile(x, thePackage, callback) { + var loadAsFilePackage = thePackage; + var cb = callback; + if (typeof loadAsFilePackage === 'function') { + cb = loadAsFilePackage; + loadAsFilePackage = undefined; + } + + var exts = [''].concat(extensions); + load(exts, x, loadAsFilePackage); + + function load(exts, x, loadPackage) { + if (exts.length === 0) return cb(null, undefined, loadPackage); + var file = x + exts[0]; + + var pkg = loadPackage; + if (pkg) onpkg(null, pkg); + else loadpkg(path.dirname(file), onpkg); + + function onpkg(err, pkg_, dir) { + pkg = pkg_; + if (err) return cb(err); + if (dir && pkg && opts.pathFilter) { + var rfile = path.relative(dir, file); + var rel = rfile.slice(0, rfile.length - exts[0].length); + var r = opts.pathFilter(pkg, x, rel); + if (r) return load( + [''].concat(extensions.slice()), + path.resolve(dir, r), + pkg + ); + } + isFile(file, onex); + } + function onex(err, ex) { + if (err) return cb(err); + if (ex) return cb(null, file, pkg); + load(exts.slice(1), x, pkg); + } + } + } + + function loadpkg(dir, cb) { + if (dir === '' || dir === '/') return cb(null); + if (process.platform === 'win32' && (/^\w:[/\\]*$/).test(dir)) { + return cb(null); + } + if ((/[/\\]node_modules[/\\]*$/).test(dir)) return cb(null); + + maybeRealpath(realpath, dir, opts, function (unwrapErr, pkgdir) { + if (unwrapErr) return loadpkg(path.dirname(dir), cb); + var pkgfile = path.join(pkgdir, 'package.json'); + isFile(pkgfile, function (err, ex) { + // on err, ex is false + if (!ex) return loadpkg(path.dirname(dir), cb); + + readPackage(readFile, pkgfile, function (err, pkgParam) { + if (err) cb(err); + + var pkg = pkgParam; + + if (pkg && opts.packageFilter) { + pkg = opts.packageFilter(pkg, pkgfile); + } + cb(null, pkg, dir); + }); + }); + }); + } + + function loadAsDirectory(x, loadAsDirectoryPackage, callback) { + var cb = callback; + var fpkg = loadAsDirectoryPackage; + if (typeof fpkg === 'function') { + cb = fpkg; + fpkg = opts.package; + } + + maybeRealpath(realpath, x, opts, function (unwrapErr, pkgdir) { + if (unwrapErr) return cb(unwrapErr); + var pkgfile = path.join(pkgdir, 'package.json'); + isFile(pkgfile, function (err, ex) { + if (err) return cb(err); + if (!ex) return loadAsFile(path.join(x, 'index'), fpkg, cb); + + readPackage(readFile, pkgfile, function (err, pkgParam) { + if (err) return cb(err); + + var pkg = pkgParam; + + if (pkg && opts.packageFilter) { + pkg = opts.packageFilter(pkg, pkgfile); + } + + if (pkg && pkg.main) { + if (typeof pkg.main !== 'string') { + var mainError = new TypeError('package “' + pkg.name + '” `main` must be a string'); + mainError.code = 'INVALID_PACKAGE_MAIN'; + return cb(mainError); + } + if (pkg.main === '.' || pkg.main === './') { + pkg.main = 'index'; + } + loadAsFile(path.resolve(x, pkg.main), pkg, function (err, m, pkg) { + if (err) return cb(err); + if (m) return cb(null, m, pkg); + if (!pkg) return loadAsFile(path.join(x, 'index'), pkg, cb); + + var dir = path.resolve(x, pkg.main); + loadAsDirectory(dir, pkg, function (err, n, pkg) { + if (err) return cb(err); + if (n) return cb(null, n, pkg); + loadAsFile(path.join(x, 'index'), pkg, cb); + }); + }); + return; + } + + loadAsFile(path.join(x, '/index'), pkg, cb); + }); + }); + }); + } + + function processDirs(cb, dirs) { + if (dirs.length === 0) return cb(null, undefined); + var dir = dirs[0]; + + isDirectory(path.dirname(dir), isdir); + + function isdir(err, isdir) { + if (err) return cb(err); + if (!isdir) return processDirs(cb, dirs.slice(1)); + loadAsFile(dir, opts.package, onfile); + } + + function onfile(err, m, pkg) { + if (err) return cb(err); + if (m) return cb(null, m, pkg); + loadAsDirectory(dir, opts.package, ondir); + } + + function ondir(err, n, pkg) { + if (err) return cb(err); + if (n) return cb(null, n, pkg); + processDirs(cb, dirs.slice(1)); + } + } + function loadNodeModules(x, start, cb) { + var thunk = function () { return getPackageCandidates(x, start, opts); }; + processDirs( + cb, + packageIterator ? packageIterator(x, start, thunk, opts) : thunk() + ); + } +}; + + +/***/ }), + +/***/ 87481: +/***/ ((module) => { + +module.exports = function () { + // see https://code.google.com/p/v8/wiki/JavaScriptStackTraceApi + var origPrepareStackTrace = Error.prepareStackTrace; + Error.prepareStackTrace = function (_, stack) { return stack; }; + var stack = (new Error()).stack; + Error.prepareStackTrace = origPrepareStackTrace; + return stack[2].getFileName(); +}; + + +/***/ }), + +/***/ 12333: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +var isCoreModule = __nccwpck_require__(65223); +var data = __nccwpck_require__(62035); + +var core = {}; +for (var mod in data) { // eslint-disable-line no-restricted-syntax + if (Object.prototype.hasOwnProperty.call(data, mod)) { + core[mod] = isCoreModule(mod); + } +} +module.exports = core; + + +/***/ }), + +/***/ 22282: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +var os = __nccwpck_require__(70857); + +// adapted from https://github.com/sindresorhus/os-homedir/blob/11e089f4754db38bb535e5a8416320c4446e8cfd/index.js + +module.exports = os.homedir || function homedir() { + var home = process.env.HOME; + var user = process.env.LOGNAME || process.env.USER || process.env.LNAME || process.env.USERNAME; + + if (process.platform === 'win32') { + return process.env.USERPROFILE || process.env.HOMEDRIVE + process.env.HOMEPATH || home || null; + } + + if (process.platform === 'darwin') { + return home || (user ? '/Users/' + user : null); + } + + if (process.platform === 'linux') { + return home || (process.getuid() === 0 ? '/root' : (user ? '/home/' + user : null)); // eslint-disable-line no-extra-parens + } + + return home || null; +}; + + +/***/ }), + +/***/ 54040: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +var isCoreModule = __nccwpck_require__(65223); + +module.exports = function isCore(x) { + return isCoreModule(x); +}; + + +/***/ }), + +/***/ 9411: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +var path = __nccwpck_require__(16928); +var parse = path.parse || __nccwpck_require__(33269); // eslint-disable-line global-require + +var getNodeModulesDirs = function getNodeModulesDirs(absoluteStart, modules) { + var prefix = '/'; + if ((/^([A-Za-z]:)/).test(absoluteStart)) { + prefix = ''; + } else if ((/^\\\\/).test(absoluteStart)) { + prefix = '\\\\'; + } + + var paths = [absoluteStart]; + var parsed = parse(absoluteStart); + while (parsed.dir !== paths[paths.length - 1]) { + paths.push(parsed.dir); + parsed = parse(parsed.dir); + } + + return paths.reduce(function (dirs, aPath) { + return dirs.concat(modules.map(function (moduleDir) { + return path.resolve(prefix, aPath, moduleDir); + })); + }, []); +}; + +module.exports = function nodeModulesPaths(start, opts, request) { + var modules = opts && opts.moduleDirectory + ? [].concat(opts.moduleDirectory) + : ['node_modules']; + + if (opts && typeof opts.paths === 'function') { + return opts.paths( + request, + start, + function () { return getNodeModulesDirs(start, modules); }, + opts + ); + } + + var dirs = getNodeModulesDirs(start, modules); + return opts && opts.paths ? dirs.concat(opts.paths) : dirs; +}; + + +/***/ }), + +/***/ 29496: +/***/ ((module) => { + +module.exports = function (x, opts) { + /** + * This file is purposefully a passthrough. It's expected that third-party + * environments will override it at runtime in order to inject special logic + * into `resolve` (by manipulating the options). One such example is the PnP + * code path in Yarn. + */ + + return opts || {}; +}; + + +/***/ }), + +/***/ 70225: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +var isCore = __nccwpck_require__(65223); +var fs = __nccwpck_require__(79896); +var path = __nccwpck_require__(16928); +var getHomedir = __nccwpck_require__(22282); +var caller = __nccwpck_require__(87481); +var nodeModulesPaths = __nccwpck_require__(9411); +var normalizeOptions = __nccwpck_require__(29496); + +var realpathFS = process.platform !== 'win32' && fs.realpathSync && typeof fs.realpathSync.native === 'function' ? fs.realpathSync.native : fs.realpathSync; + +var homedir = getHomedir(); +var defaultPaths = function () { + return [ + path.join(homedir, '.node_modules'), + path.join(homedir, '.node_libraries') + ]; +}; + +var defaultIsFile = function isFile(file) { + try { + var stat = fs.statSync(file, { throwIfNoEntry: false }); + } catch (e) { + if (e && (e.code === 'ENOENT' || e.code === 'ENOTDIR')) return false; + throw e; + } + return !!stat && (stat.isFile() || stat.isFIFO()); +}; + +var defaultIsDir = function isDirectory(dir) { + try { + var stat = fs.statSync(dir, { throwIfNoEntry: false }); + } catch (e) { + if (e && (e.code === 'ENOENT' || e.code === 'ENOTDIR')) return false; + throw e; + } + return !!stat && stat.isDirectory(); +}; + +var defaultRealpathSync = function realpathSync(x) { + try { + return realpathFS(x); + } catch (realpathErr) { + if (realpathErr.code !== 'ENOENT') { + throw realpathErr; + } + } + return x; +}; + +var maybeRealpathSync = function maybeRealpathSync(realpathSync, x, opts) { + if (opts && opts.preserveSymlinks === false) { + return realpathSync(x); + } + return x; +}; + +var defaultReadPackageSync = function defaultReadPackageSync(readFileSync, pkgfile) { + var body = readFileSync(pkgfile); + try { + var pkg = JSON.parse(body); + return pkg; + } catch (jsonErr) {} +}; + +var getPackageCandidates = function getPackageCandidates(x, start, opts) { + var dirs = nodeModulesPaths(start, opts, x); + for (var i = 0; i < dirs.length; i++) { + dirs[i] = path.join(dirs[i], x); + } + return dirs; +}; + +module.exports = function resolveSync(x, options) { + if (typeof x !== 'string') { + throw new TypeError('Path must be a string.'); + } + var opts = normalizeOptions(x, options); + + var isFile = opts.isFile || defaultIsFile; + var readFileSync = opts.readFileSync || fs.readFileSync; + var isDirectory = opts.isDirectory || defaultIsDir; + var realpathSync = opts.realpathSync || defaultRealpathSync; + var readPackageSync = opts.readPackageSync || defaultReadPackageSync; + if (opts.readFileSync && opts.readPackageSync) { + throw new TypeError('`readFileSync` and `readPackageSync` are mutually exclusive.'); + } + var packageIterator = opts.packageIterator; + + var extensions = opts.extensions || ['.js']; + var includeCoreModules = opts.includeCoreModules !== false; + var basedir = opts.basedir || path.dirname(caller()); + var parent = opts.filename || basedir; + + opts.paths = opts.paths || defaultPaths(); + + // ensure that `basedir` is an absolute path at this point, resolving against the process' current working directory + var absoluteStart = maybeRealpathSync(realpathSync, path.resolve(basedir), opts); + + if ((/^(?:\.\.?(?:\/|$)|\/|([A-Za-z]:)?[/\\])/).test(x)) { + var res = path.resolve(absoluteStart, x); + if (x === '.' || x === '..' || x.slice(-1) === '/') res += '/'; + var m = loadAsFileSync(res) || loadAsDirectorySync(res); + if (m) return maybeRealpathSync(realpathSync, m, opts); + } else if (includeCoreModules && isCore(x)) { + return x; + } else { + var n = loadNodeModulesSync(x, absoluteStart); + if (n) return maybeRealpathSync(realpathSync, n, opts); + } + + var err = new Error("Cannot find module '" + x + "' from '" + parent + "'"); + err.code = 'MODULE_NOT_FOUND'; + throw err; + + function loadAsFileSync(x) { + var pkg = loadpkg(path.dirname(x)); + + if (pkg && pkg.dir && pkg.pkg && opts.pathFilter) { + var rfile = path.relative(pkg.dir, x); + var r = opts.pathFilter(pkg.pkg, x, rfile); + if (r) { + x = path.resolve(pkg.dir, r); // eslint-disable-line no-param-reassign + } + } + + if (isFile(x)) { + return x; + } + + for (var i = 0; i < extensions.length; i++) { + var file = x + extensions[i]; + if (isFile(file)) { + return file; + } + } + } + + function loadpkg(dir) { + if (dir === '' || dir === '/') return; + if (process.platform === 'win32' && (/^\w:[/\\]*$/).test(dir)) { + return; + } + if ((/[/\\]node_modules[/\\]*$/).test(dir)) return; + + var pkgfile = path.join(maybeRealpathSync(realpathSync, dir, opts), 'package.json'); + + if (!isFile(pkgfile)) { + return loadpkg(path.dirname(dir)); + } + + var pkg = readPackageSync(readFileSync, pkgfile); + + if (pkg && opts.packageFilter) { + // v2 will pass pkgfile + pkg = opts.packageFilter(pkg, /*pkgfile,*/ dir); // eslint-disable-line spaced-comment + } + + return { pkg: pkg, dir: dir }; + } + + function loadAsDirectorySync(x) { + var pkgfile = path.join(maybeRealpathSync(realpathSync, x, opts), '/package.json'); + if (isFile(pkgfile)) { + try { + var pkg = readPackageSync(readFileSync, pkgfile); + } catch (e) {} + + if (pkg && opts.packageFilter) { + // v2 will pass pkgfile + pkg = opts.packageFilter(pkg, /*pkgfile,*/ x); // eslint-disable-line spaced-comment + } + + if (pkg && pkg.main) { + if (typeof pkg.main !== 'string') { + var mainError = new TypeError('package “' + pkg.name + '” `main` must be a string'); + mainError.code = 'INVALID_PACKAGE_MAIN'; + throw mainError; + } + if (pkg.main === '.' || pkg.main === './') { + pkg.main = 'index'; + } + try { + var m = loadAsFileSync(path.resolve(x, pkg.main)); + if (m) return m; + var n = loadAsDirectorySync(path.resolve(x, pkg.main)); + if (n) return n; + } catch (e) {} + } + } + + return loadAsFileSync(path.join(x, '/index')); + } + + function loadNodeModulesSync(x, start) { + var thunk = function () { return getPackageCandidates(x, start, opts); }; + var dirs = packageIterator ? packageIterator(x, start, thunk, opts) : thunk(); + + for (var i = 0; i < dirs.length; i++) { + var dir = dirs[i]; + if (isDirectory(path.dirname(dir))) { + var m = loadAsFileSync(dir); + if (m) return m; + var n = loadAsDirectorySync(dir); + if (n) return n; + } + } + } +}; + + +/***/ }), + +/***/ 89495: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +var typeOf = __nccwpck_require__(5140); +var extend = __nccwpck_require__(48905); + +/** + * Parse sections in `input` with the given `options`. + * + * ```js + * var sections = require('{%= name %}'); + * var result = sections(input, options); + * // { content: 'Content before sections', sections: [] } + * ``` + * @param {String|Buffer|Object} `input` If input is an object, it's `content` property must be a string or buffer. + * @param {Object} options + * @return {Object} Returns an object with a `content` string and an array of `sections` objects. + * @api public + */ + +module.exports = function(input, options) { + if (typeof options === 'function') { + options = { parse: options }; + } + + var file = toObject(input); + var defaults = {section_delimiter: '---', parse: identity}; + var opts = extend({}, defaults, options); + var delim = opts.section_delimiter; + var lines = file.content.split(/\r?\n/); + var sections = null; + var section = createSection(); + var content = []; + var stack = []; + + function initSections(val) { + file.content = val; + sections = []; + content = []; + } + + function closeSection(val) { + if (stack.length) { + section.key = getKey(stack[0], delim); + section.content = val; + opts.parse(section, sections); + sections.push(section); + section = createSection(); + content = []; + stack = []; + } + } + + for (var i = 0; i < lines.length; i++) { + var line = lines[i]; + var len = stack.length; + var ln = line.trim(); + + if (isDelimiter(ln, delim)) { + if (ln.length === 3 && i !== 0) { + if (len === 0 || len === 2) { + content.push(line); + continue; + } + stack.push(ln); + section.data = content.join('\n'); + content = []; + continue; + } + + if (sections === null) { + initSections(content.join('\n')); + } + + if (len === 2) { + closeSection(content.join('\n')); + } + + stack.push(ln); + continue; + } + + content.push(line); + } + + if (sections === null) { + initSections(content.join('\n')); + } else { + closeSection(content.join('\n')); + } + + file.sections = sections; + return file; +}; + +function isDelimiter(line, delim) { + if (line.slice(0, delim.length) !== delim) { + return false; + } + if (line.charAt(delim.length + 1) === delim.slice(-1)) { + return false; + } + return true; +} + +function toObject(input) { + if (typeOf(input) !== 'object') { + input = { content: input }; + } + + if (typeof input.content !== 'string' && !isBuffer(input.content)) { + throw new TypeError('expected a buffer or string'); + } + + input.content = input.content.toString(); + input.sections = []; + return input; +} + +function getKey(val, delim) { + return val ? val.slice(delim.length).trim() : ''; +} + +function createSection() { + return { key: '', data: '', content: '' }; +} + +function identity(val) { + return val; +} + +function isBuffer(val) { + if (val && val.constructor && typeof val.constructor.isBuffer === 'function') { + return val.constructor.isBuffer(val); + } + return false; +} + + +/***/ }), + +/***/ 39318: +/***/ ((module, exports) => { + +exports = module.exports = SemVer + +var debug +/* istanbul ignore next */ +if (typeof process === 'object' && + process.env && + process.env.NODE_DEBUG && + /\bsemver\b/i.test(process.env.NODE_DEBUG)) { + debug = function () { + var args = Array.prototype.slice.call(arguments, 0) + args.unshift('SEMVER') + console.log.apply(console, args) + } +} else { + debug = function () {} +} + +// Note: this is the semver.org version of the spec that it implements +// Not necessarily the package version of this code. +exports.SEMVER_SPEC_VERSION = '2.0.0' + +var MAX_LENGTH = 256 +var MAX_SAFE_INTEGER = Number.MAX_SAFE_INTEGER || + /* istanbul ignore next */ 9007199254740991 + +// Max safe segment length for coercion. +var MAX_SAFE_COMPONENT_LENGTH = 16 + +var MAX_SAFE_BUILD_LENGTH = MAX_LENGTH - 6 + +// The actual regexps go on exports.re +var re = exports.re = [] +var safeRe = exports.safeRe = [] +var src = exports.src = [] +var R = 0 + +var LETTERDASHNUMBER = '[a-zA-Z0-9-]' + +// Replace some greedy regex tokens to prevent regex dos issues. These regex are +// used internally via the safeRe object since all inputs in this library get +// normalized first to trim and collapse all extra whitespace. The original +// regexes are exported for userland consumption and lower level usage. A +// future breaking change could export the safer regex only with a note that +// all input should have extra whitespace removed. +var safeRegexReplacements = [ + ['\\s', 1], + ['\\d', MAX_LENGTH], + [LETTERDASHNUMBER, MAX_SAFE_BUILD_LENGTH], +] + +function makeSafeRe (value) { + for (var i = 0; i < safeRegexReplacements.length; i++) { + var token = safeRegexReplacements[i][0] + var max = safeRegexReplacements[i][1] + value = value + .split(token + '*').join(token + '{0,' + max + '}') + .split(token + '+').join(token + '{1,' + max + '}') + } + return value +} + +// The following Regular Expressions can be used for tokenizing, +// validating, and parsing SemVer version strings. + +// ## Numeric Identifier +// A single `0`, or a non-zero digit followed by zero or more digits. + +var NUMERICIDENTIFIER = R++ +src[NUMERICIDENTIFIER] = '0|[1-9]\\d*' +var NUMERICIDENTIFIERLOOSE = R++ +src[NUMERICIDENTIFIERLOOSE] = '\\d+' + +// ## Non-numeric Identifier +// Zero or more digits, followed by a letter or hyphen, and then zero or +// more letters, digits, or hyphens. + +var NONNUMERICIDENTIFIER = R++ +src[NONNUMERICIDENTIFIER] = '\\d*[a-zA-Z-]' + LETTERDASHNUMBER + '*' + +// ## Main Version +// Three dot-separated numeric identifiers. + +var MAINVERSION = R++ +src[MAINVERSION] = '(' + src[NUMERICIDENTIFIER] + ')\\.' + + '(' + src[NUMERICIDENTIFIER] + ')\\.' + + '(' + src[NUMERICIDENTIFIER] + ')' + +var MAINVERSIONLOOSE = R++ +src[MAINVERSIONLOOSE] = '(' + src[NUMERICIDENTIFIERLOOSE] + ')\\.' + + '(' + src[NUMERICIDENTIFIERLOOSE] + ')\\.' + + '(' + src[NUMERICIDENTIFIERLOOSE] + ')' + +// ## Pre-release Version Identifier +// A numeric identifier, or a non-numeric identifier. + +var PRERELEASEIDENTIFIER = R++ +src[PRERELEASEIDENTIFIER] = '(?:' + src[NUMERICIDENTIFIER] + + '|' + src[NONNUMERICIDENTIFIER] + ')' + +var PRERELEASEIDENTIFIERLOOSE = R++ +src[PRERELEASEIDENTIFIERLOOSE] = '(?:' + src[NUMERICIDENTIFIERLOOSE] + + '|' + src[NONNUMERICIDENTIFIER] + ')' + +// ## Pre-release Version +// Hyphen, followed by one or more dot-separated pre-release version +// identifiers. + +var PRERELEASE = R++ +src[PRERELEASE] = '(?:-(' + src[PRERELEASEIDENTIFIER] + + '(?:\\.' + src[PRERELEASEIDENTIFIER] + ')*))' + +var PRERELEASELOOSE = R++ +src[PRERELEASELOOSE] = '(?:-?(' + src[PRERELEASEIDENTIFIERLOOSE] + + '(?:\\.' + src[PRERELEASEIDENTIFIERLOOSE] + ')*))' + +// ## Build Metadata Identifier +// Any combination of digits, letters, or hyphens. + +var BUILDIDENTIFIER = R++ +src[BUILDIDENTIFIER] = LETTERDASHNUMBER + '+' + +// ## Build Metadata +// Plus sign, followed by one or more period-separated build metadata +// identifiers. + +var BUILD = R++ +src[BUILD] = '(?:\\+(' + src[BUILDIDENTIFIER] + + '(?:\\.' + src[BUILDIDENTIFIER] + ')*))' + +// ## Full Version String +// A main version, followed optionally by a pre-release version and +// build metadata. + +// Note that the only major, minor, patch, and pre-release sections of +// the version string are capturing groups. The build metadata is not a +// capturing group, because it should not ever be used in version +// comparison. + +var FULL = R++ +var FULLPLAIN = 'v?' + src[MAINVERSION] + + src[PRERELEASE] + '?' + + src[BUILD] + '?' + +src[FULL] = '^' + FULLPLAIN + '$' + +// like full, but allows v1.2.3 and =1.2.3, which people do sometimes. +// also, 1.0.0alpha1 (prerelease without the hyphen) which is pretty +// common in the npm registry. +var LOOSEPLAIN = '[v=\\s]*' + src[MAINVERSIONLOOSE] + + src[PRERELEASELOOSE] + '?' + + src[BUILD] + '?' + +var LOOSE = R++ +src[LOOSE] = '^' + LOOSEPLAIN + '$' + +var GTLT = R++ +src[GTLT] = '((?:<|>)?=?)' + +// Something like "2.*" or "1.2.x". +// Note that "x.x" is a valid xRange identifer, meaning "any version" +// Only the first item is strictly required. +var XRANGEIDENTIFIERLOOSE = R++ +src[XRANGEIDENTIFIERLOOSE] = src[NUMERICIDENTIFIERLOOSE] + '|x|X|\\*' +var XRANGEIDENTIFIER = R++ +src[XRANGEIDENTIFIER] = src[NUMERICIDENTIFIER] + '|x|X|\\*' + +var XRANGEPLAIN = R++ +src[XRANGEPLAIN] = '[v=\\s]*(' + src[XRANGEIDENTIFIER] + ')' + + '(?:\\.(' + src[XRANGEIDENTIFIER] + ')' + + '(?:\\.(' + src[XRANGEIDENTIFIER] + ')' + + '(?:' + src[PRERELEASE] + ')?' + + src[BUILD] + '?' + + ')?)?' + +var XRANGEPLAINLOOSE = R++ +src[XRANGEPLAINLOOSE] = '[v=\\s]*(' + src[XRANGEIDENTIFIERLOOSE] + ')' + + '(?:\\.(' + src[XRANGEIDENTIFIERLOOSE] + ')' + + '(?:\\.(' + src[XRANGEIDENTIFIERLOOSE] + ')' + + '(?:' + src[PRERELEASELOOSE] + ')?' + + src[BUILD] + '?' + + ')?)?' + +var XRANGE = R++ +src[XRANGE] = '^' + src[GTLT] + '\\s*' + src[XRANGEPLAIN] + '$' +var XRANGELOOSE = R++ +src[XRANGELOOSE] = '^' + src[GTLT] + '\\s*' + src[XRANGEPLAINLOOSE] + '$' + +// Coercion. +// Extract anything that could conceivably be a part of a valid semver +var COERCE = R++ +src[COERCE] = '(?:^|[^\\d])' + + '(\\d{1,' + MAX_SAFE_COMPONENT_LENGTH + '})' + + '(?:\\.(\\d{1,' + MAX_SAFE_COMPONENT_LENGTH + '}))?' + + '(?:\\.(\\d{1,' + MAX_SAFE_COMPONENT_LENGTH + '}))?' + + '(?:$|[^\\d])' + +// Tilde ranges. +// Meaning is "reasonably at or greater than" +var LONETILDE = R++ +src[LONETILDE] = '(?:~>?)' + +var TILDETRIM = R++ +src[TILDETRIM] = '(\\s*)' + src[LONETILDE] + '\\s+' +re[TILDETRIM] = new RegExp(src[TILDETRIM], 'g') +safeRe[TILDETRIM] = new RegExp(makeSafeRe(src[TILDETRIM]), 'g') +var tildeTrimReplace = '$1~' + +var TILDE = R++ +src[TILDE] = '^' + src[LONETILDE] + src[XRANGEPLAIN] + '$' +var TILDELOOSE = R++ +src[TILDELOOSE] = '^' + src[LONETILDE] + src[XRANGEPLAINLOOSE] + '$' + +// Caret ranges. +// Meaning is "at least and backwards compatible with" +var LONECARET = R++ +src[LONECARET] = '(?:\\^)' + +var CARETTRIM = R++ +src[CARETTRIM] = '(\\s*)' + src[LONECARET] + '\\s+' +re[CARETTRIM] = new RegExp(src[CARETTRIM], 'g') +safeRe[CARETTRIM] = new RegExp(makeSafeRe(src[CARETTRIM]), 'g') +var caretTrimReplace = '$1^' + +var CARET = R++ +src[CARET] = '^' + src[LONECARET] + src[XRANGEPLAIN] + '$' +var CARETLOOSE = R++ +src[CARETLOOSE] = '^' + src[LONECARET] + src[XRANGEPLAINLOOSE] + '$' + +// A simple gt/lt/eq thing, or just "" to indicate "any version" +var COMPARATORLOOSE = R++ +src[COMPARATORLOOSE] = '^' + src[GTLT] + '\\s*(' + LOOSEPLAIN + ')$|^$' +var COMPARATOR = R++ +src[COMPARATOR] = '^' + src[GTLT] + '\\s*(' + FULLPLAIN + ')$|^$' + +// An expression to strip any whitespace between the gtlt and the thing +// it modifies, so that `> 1.2.3` ==> `>1.2.3` +var COMPARATORTRIM = R++ +src[COMPARATORTRIM] = '(\\s*)' + src[GTLT] + + '\\s*(' + LOOSEPLAIN + '|' + src[XRANGEPLAIN] + ')' + +// this one has to use the /g flag +re[COMPARATORTRIM] = new RegExp(src[COMPARATORTRIM], 'g') +safeRe[COMPARATORTRIM] = new RegExp(makeSafeRe(src[COMPARATORTRIM]), 'g') +var comparatorTrimReplace = '$1$2$3' + +// Something like `1.2.3 - 1.2.4` +// Note that these all use the loose form, because they'll be +// checked against either the strict or loose comparator form +// later. +var HYPHENRANGE = R++ +src[HYPHENRANGE] = '^\\s*(' + src[XRANGEPLAIN] + ')' + + '\\s+-\\s+' + + '(' + src[XRANGEPLAIN] + ')' + + '\\s*$' + +var HYPHENRANGELOOSE = R++ +src[HYPHENRANGELOOSE] = '^\\s*(' + src[XRANGEPLAINLOOSE] + ')' + + '\\s+-\\s+' + + '(' + src[XRANGEPLAINLOOSE] + ')' + + '\\s*$' + +// Star ranges basically just allow anything at all. +var STAR = R++ +src[STAR] = '(<|>)?=?\\s*\\*' + +// Compile to actual regexp objects. +// All are flag-free, unless they were created above with a flag. +for (var i = 0; i < R; i++) { + debug(i, src[i]) + if (!re[i]) { + re[i] = new RegExp(src[i]) + + // Replace all greedy whitespace to prevent regex dos issues. These regex are + // used internally via the safeRe object since all inputs in this library get + // normalized first to trim and collapse all extra whitespace. The original + // regexes are exported for userland consumption and lower level usage. A + // future breaking change could export the safer regex only with a note that + // all input should have extra whitespace removed. + safeRe[i] = new RegExp(makeSafeRe(src[i])) + } +} + +exports.parse = parse +function parse (version, options) { + if (!options || typeof options !== 'object') { + options = { + loose: !!options, + includePrerelease: false + } + } + + if (version instanceof SemVer) { + return version + } + + if (typeof version !== 'string') { + return null + } + + if (version.length > MAX_LENGTH) { + return null + } + + var r = options.loose ? safeRe[LOOSE] : safeRe[FULL] + if (!r.test(version)) { + return null + } + + try { + return new SemVer(version, options) + } catch (er) { + return null + } +} + +exports.valid = valid +function valid (version, options) { + var v = parse(version, options) + return v ? v.version : null +} + +exports.clean = clean +function clean (version, options) { + var s = parse(version.trim().replace(/^[=v]+/, ''), options) + return s ? s.version : null +} + +exports.SemVer = SemVer + +function SemVer (version, options) { + if (!options || typeof options !== 'object') { + options = { + loose: !!options, + includePrerelease: false + } + } + if (version instanceof SemVer) { + if (version.loose === options.loose) { + return version + } else { + version = version.version + } + } else if (typeof version !== 'string') { + throw new TypeError('Invalid Version: ' + version) + } + + if (version.length > MAX_LENGTH) { + throw new TypeError('version is longer than ' + MAX_LENGTH + ' characters') + } + + if (!(this instanceof SemVer)) { + return new SemVer(version, options) + } + + debug('SemVer', version, options) + this.options = options + this.loose = !!options.loose + + var m = version.trim().match(options.loose ? safeRe[LOOSE] : safeRe[FULL]) + + if (!m) { + throw new TypeError('Invalid Version: ' + version) + } + + this.raw = version + + // these are actually numbers + this.major = +m[1] + this.minor = +m[2] + this.patch = +m[3] + + if (this.major > MAX_SAFE_INTEGER || this.major < 0) { + throw new TypeError('Invalid major version') + } + + if (this.minor > MAX_SAFE_INTEGER || this.minor < 0) { + throw new TypeError('Invalid minor version') + } + + if (this.patch > MAX_SAFE_INTEGER || this.patch < 0) { + throw new TypeError('Invalid patch version') + } + + // numberify any prerelease numeric ids + if (!m[4]) { + this.prerelease = [] + } else { + this.prerelease = m[4].split('.').map(function (id) { + if (/^[0-9]+$/.test(id)) { + var num = +id + if (num >= 0 && num < MAX_SAFE_INTEGER) { + return num + } + } + return id + }) + } + + this.build = m[5] ? m[5].split('.') : [] + this.format() +} + +SemVer.prototype.format = function () { + this.version = this.major + '.' + this.minor + '.' + this.patch + if (this.prerelease.length) { + this.version += '-' + this.prerelease.join('.') + } + return this.version +} + +SemVer.prototype.toString = function () { + return this.version +} + +SemVer.prototype.compare = function (other) { + debug('SemVer.compare', this.version, this.options, other) + if (!(other instanceof SemVer)) { + other = new SemVer(other, this.options) + } + + return this.compareMain(other) || this.comparePre(other) +} + +SemVer.prototype.compareMain = function (other) { + if (!(other instanceof SemVer)) { + other = new SemVer(other, this.options) + } + + return compareIdentifiers(this.major, other.major) || + compareIdentifiers(this.minor, other.minor) || + compareIdentifiers(this.patch, other.patch) +} + +SemVer.prototype.comparePre = function (other) { + if (!(other instanceof SemVer)) { + other = new SemVer(other, this.options) + } + + // NOT having a prerelease is > having one + if (this.prerelease.length && !other.prerelease.length) { + return -1 + } else if (!this.prerelease.length && other.prerelease.length) { + return 1 + } else if (!this.prerelease.length && !other.prerelease.length) { + return 0 + } + + var i = 0 + do { + var a = this.prerelease[i] + var b = other.prerelease[i] + debug('prerelease compare', i, a, b) + if (a === undefined && b === undefined) { + return 0 + } else if (b === undefined) { + return 1 + } else if (a === undefined) { + return -1 + } else if (a === b) { + continue + } else { + return compareIdentifiers(a, b) + } + } while (++i) +} + +// preminor will bump the version up to the next minor release, and immediately +// down to pre-release. premajor and prepatch work the same way. +SemVer.prototype.inc = function (release, identifier) { + switch (release) { + case 'premajor': + this.prerelease.length = 0 + this.patch = 0 + this.minor = 0 + this.major++ + this.inc('pre', identifier) + break + case 'preminor': + this.prerelease.length = 0 + this.patch = 0 + this.minor++ + this.inc('pre', identifier) + break + case 'prepatch': + // If this is already a prerelease, it will bump to the next version + // drop any prereleases that might already exist, since they are not + // relevant at this point. + this.prerelease.length = 0 + this.inc('patch', identifier) + this.inc('pre', identifier) + break + // If the input is a non-prerelease version, this acts the same as + // prepatch. + case 'prerelease': + if (this.prerelease.length === 0) { + this.inc('patch', identifier) + } + this.inc('pre', identifier) + break + + case 'major': + // If this is a pre-major version, bump up to the same major version. + // Otherwise increment major. + // 1.0.0-5 bumps to 1.0.0 + // 1.1.0 bumps to 2.0.0 + if (this.minor !== 0 || + this.patch !== 0 || + this.prerelease.length === 0) { + this.major++ + } + this.minor = 0 + this.patch = 0 + this.prerelease = [] + break + case 'minor': + // If this is a pre-minor version, bump up to the same minor version. + // Otherwise increment minor. + // 1.2.0-5 bumps to 1.2.0 + // 1.2.1 bumps to 1.3.0 + if (this.patch !== 0 || this.prerelease.length === 0) { + this.minor++ + } + this.patch = 0 + this.prerelease = [] + break + case 'patch': + // If this is not a pre-release version, it will increment the patch. + // If it is a pre-release it will bump up to the same patch version. + // 1.2.0-5 patches to 1.2.0 + // 1.2.0 patches to 1.2.1 + if (this.prerelease.length === 0) { + this.patch++ + } + this.prerelease = [] + break + // This probably shouldn't be used publicly. + // 1.0.0 "pre" would become 1.0.0-0 which is the wrong direction. + case 'pre': + if (this.prerelease.length === 0) { + this.prerelease = [0] + } else { + var i = this.prerelease.length + while (--i >= 0) { + if (typeof this.prerelease[i] === 'number') { + this.prerelease[i]++ + i = -2 + } + } + if (i === -1) { + // didn't increment anything + this.prerelease.push(0) + } + } + if (identifier) { + // 1.2.0-beta.1 bumps to 1.2.0-beta.2, + // 1.2.0-beta.fooblz or 1.2.0-beta bumps to 1.2.0-beta.0 + if (this.prerelease[0] === identifier) { + if (isNaN(this.prerelease[1])) { + this.prerelease = [identifier, 0] + } + } else { + this.prerelease = [identifier, 0] + } + } + break + + default: + throw new Error('invalid increment argument: ' + release) + } + this.format() + this.raw = this.version + return this +} + +exports.inc = inc +function inc (version, release, loose, identifier) { + if (typeof (loose) === 'string') { + identifier = loose + loose = undefined + } + + try { + return new SemVer(version, loose).inc(release, identifier).version + } catch (er) { + return null + } +} + +exports.diff = diff +function diff (version1, version2) { + if (eq(version1, version2)) { + return null + } else { + var v1 = parse(version1) + var v2 = parse(version2) + var prefix = '' + if (v1.prerelease.length || v2.prerelease.length) { + prefix = 'pre' + var defaultResult = 'prerelease' + } + for (var key in v1) { + if (key === 'major' || key === 'minor' || key === 'patch') { + if (v1[key] !== v2[key]) { + return prefix + key + } + } + } + return defaultResult // may be undefined + } +} + +exports.compareIdentifiers = compareIdentifiers + +var numeric = /^[0-9]+$/ +function compareIdentifiers (a, b) { + var anum = numeric.test(a) + var bnum = numeric.test(b) + + if (anum && bnum) { + a = +a + b = +b + } + + return a === b ? 0 + : (anum && !bnum) ? -1 + : (bnum && !anum) ? 1 + : a < b ? -1 + : 1 +} + +exports.rcompareIdentifiers = rcompareIdentifiers +function rcompareIdentifiers (a, b) { + return compareIdentifiers(b, a) +} + +exports.major = major +function major (a, loose) { + return new SemVer(a, loose).major +} + +exports.minor = minor +function minor (a, loose) { + return new SemVer(a, loose).minor +} + +exports.patch = patch +function patch (a, loose) { + return new SemVer(a, loose).patch +} + +exports.compare = compare +function compare (a, b, loose) { + return new SemVer(a, loose).compare(new SemVer(b, loose)) +} + +exports.compareLoose = compareLoose +function compareLoose (a, b) { + return compare(a, b, true) +} + +exports.rcompare = rcompare +function rcompare (a, b, loose) { + return compare(b, a, loose) +} + +exports.sort = sort +function sort (list, loose) { + return list.sort(function (a, b) { + return exports.compare(a, b, loose) + }) +} + +exports.rsort = rsort +function rsort (list, loose) { + return list.sort(function (a, b) { + return exports.rcompare(a, b, loose) + }) +} + +exports.gt = gt +function gt (a, b, loose) { + return compare(a, b, loose) > 0 +} + +exports.lt = lt +function lt (a, b, loose) { + return compare(a, b, loose) < 0 +} + +exports.eq = eq +function eq (a, b, loose) { + return compare(a, b, loose) === 0 +} + +exports.neq = neq +function neq (a, b, loose) { + return compare(a, b, loose) !== 0 +} + +exports.gte = gte +function gte (a, b, loose) { + return compare(a, b, loose) >= 0 +} + +exports.lte = lte +function lte (a, b, loose) { + return compare(a, b, loose) <= 0 +} + +exports.cmp = cmp +function cmp (a, op, b, loose) { + switch (op) { + case '===': + if (typeof a === 'object') + a = a.version + if (typeof b === 'object') + b = b.version + return a === b + + case '!==': + if (typeof a === 'object') + a = a.version + if (typeof b === 'object') + b = b.version + return a !== b + + case '': + case '=': + case '==': + return eq(a, b, loose) + + case '!=': + return neq(a, b, loose) + + case '>': + return gt(a, b, loose) + + case '>=': + return gte(a, b, loose) + + case '<': + return lt(a, b, loose) + + case '<=': + return lte(a, b, loose) + + default: + throw new TypeError('Invalid operator: ' + op) + } +} + +exports.Comparator = Comparator +function Comparator (comp, options) { + if (!options || typeof options !== 'object') { + options = { + loose: !!options, + includePrerelease: false + } + } + + if (comp instanceof Comparator) { + if (comp.loose === !!options.loose) { + return comp + } else { + comp = comp.value + } + } + + if (!(this instanceof Comparator)) { + return new Comparator(comp, options) + } + + comp = comp.trim().split(/\s+/).join(' ') + debug('comparator', comp, options) + this.options = options + this.loose = !!options.loose + this.parse(comp) + + if (this.semver === ANY) { + this.value = '' + } else { + this.value = this.operator + this.semver.version + } + + debug('comp', this) +} + +var ANY = {} +Comparator.prototype.parse = function (comp) { + var r = this.options.loose ? safeRe[COMPARATORLOOSE] : safeRe[COMPARATOR] + var m = comp.match(r) + + if (!m) { + throw new TypeError('Invalid comparator: ' + comp) + } + + this.operator = m[1] + if (this.operator === '=') { + this.operator = '' + } + + // if it literally is just '>' or '' then allow anything. + if (!m[2]) { + this.semver = ANY + } else { + this.semver = new SemVer(m[2], this.options.loose) + } +} + +Comparator.prototype.toString = function () { + return this.value +} + +Comparator.prototype.test = function (version) { + debug('Comparator.test', version, this.options.loose) + + if (this.semver === ANY) { + return true + } + + if (typeof version === 'string') { + version = new SemVer(version, this.options) + } + + return cmp(version, this.operator, this.semver, this.options) +} + +Comparator.prototype.intersects = function (comp, options) { + if (!(comp instanceof Comparator)) { + throw new TypeError('a Comparator is required') + } + + if (!options || typeof options !== 'object') { + options = { + loose: !!options, + includePrerelease: false + } + } + + var rangeTmp + + if (this.operator === '') { + rangeTmp = new Range(comp.value, options) + return satisfies(this.value, rangeTmp, options) + } else if (comp.operator === '') { + rangeTmp = new Range(this.value, options) + return satisfies(comp.semver, rangeTmp, options) + } + + var sameDirectionIncreasing = + (this.operator === '>=' || this.operator === '>') && + (comp.operator === '>=' || comp.operator === '>') + var sameDirectionDecreasing = + (this.operator === '<=' || this.operator === '<') && + (comp.operator === '<=' || comp.operator === '<') + var sameSemVer = this.semver.version === comp.semver.version + var differentDirectionsInclusive = + (this.operator === '>=' || this.operator === '<=') && + (comp.operator === '>=' || comp.operator === '<=') + var oppositeDirectionsLessThan = + cmp(this.semver, '<', comp.semver, options) && + ((this.operator === '>=' || this.operator === '>') && + (comp.operator === '<=' || comp.operator === '<')) + var oppositeDirectionsGreaterThan = + cmp(this.semver, '>', comp.semver, options) && + ((this.operator === '<=' || this.operator === '<') && + (comp.operator === '>=' || comp.operator === '>')) + + return sameDirectionIncreasing || sameDirectionDecreasing || + (sameSemVer && differentDirectionsInclusive) || + oppositeDirectionsLessThan || oppositeDirectionsGreaterThan +} + +exports.Range = Range +function Range (range, options) { + if (!options || typeof options !== 'object') { + options = { + loose: !!options, + includePrerelease: false + } + } + + if (range instanceof Range) { + if (range.loose === !!options.loose && + range.includePrerelease === !!options.includePrerelease) { + return range + } else { + return new Range(range.raw, options) + } + } + + if (range instanceof Comparator) { + return new Range(range.value, options) + } + + if (!(this instanceof Range)) { + return new Range(range, options) + } + + this.options = options + this.loose = !!options.loose + this.includePrerelease = !!options.includePrerelease + + // First reduce all whitespace as much as possible so we do not have to rely + // on potentially slow regexes like \s*. This is then stored and used for + // future error messages as well. + this.raw = range + .trim() + .split(/\s+/) + .join(' ') + + // First, split based on boolean or || + this.set = this.raw.split('||').map(function (range) { + return this.parseRange(range.trim()) + }, this).filter(function (c) { + // throw out any that are not relevant for whatever reason + return c.length + }) + + if (!this.set.length) { + throw new TypeError('Invalid SemVer Range: ' + this.raw) + } + + this.format() +} + +Range.prototype.format = function () { + this.range = this.set.map(function (comps) { + return comps.join(' ').trim() + }).join('||').trim() + return this.range +} + +Range.prototype.toString = function () { + return this.range +} + +Range.prototype.parseRange = function (range) { + var loose = this.options.loose + // `1.2.3 - 1.2.4` => `>=1.2.3 <=1.2.4` + var hr = loose ? safeRe[HYPHENRANGELOOSE] : safeRe[HYPHENRANGE] + range = range.replace(hr, hyphenReplace) + debug('hyphen replace', range) + // `> 1.2.3 < 1.2.5` => `>1.2.3 <1.2.5` + range = range.replace(safeRe[COMPARATORTRIM], comparatorTrimReplace) + debug('comparator trim', range, safeRe[COMPARATORTRIM]) + + // `~ 1.2.3` => `~1.2.3` + range = range.replace(safeRe[TILDETRIM], tildeTrimReplace) + + // `^ 1.2.3` => `^1.2.3` + range = range.replace(safeRe[CARETTRIM], caretTrimReplace) + + // At this point, the range is completely trimmed and + // ready to be split into comparators. + var compRe = loose ? safeRe[COMPARATORLOOSE] : safeRe[COMPARATOR] + var set = range.split(' ').map(function (comp) { + return parseComparator(comp, this.options) + }, this).join(' ').split(/\s+/) + if (this.options.loose) { + // in loose mode, throw out any that are not valid comparators + set = set.filter(function (comp) { + return !!comp.match(compRe) + }) + } + set = set.map(function (comp) { + return new Comparator(comp, this.options) + }, this) + + return set +} + +Range.prototype.intersects = function (range, options) { + if (!(range instanceof Range)) { + throw new TypeError('a Range is required') + } + + return this.set.some(function (thisComparators) { + return thisComparators.every(function (thisComparator) { + return range.set.some(function (rangeComparators) { + return rangeComparators.every(function (rangeComparator) { + return thisComparator.intersects(rangeComparator, options) + }) + }) + }) + }) +} + +// Mostly just for testing and legacy API reasons +exports.toComparators = toComparators +function toComparators (range, options) { + return new Range(range, options).set.map(function (comp) { + return comp.map(function (c) { + return c.value + }).join(' ').trim().split(' ') + }) +} + +// comprised of xranges, tildes, stars, and gtlt's at this point. +// already replaced the hyphen ranges +// turn into a set of JUST comparators. +function parseComparator (comp, options) { + debug('comp', comp, options) + comp = replaceCarets(comp, options) + debug('caret', comp) + comp = replaceTildes(comp, options) + debug('tildes', comp) + comp = replaceXRanges(comp, options) + debug('xrange', comp) + comp = replaceStars(comp, options) + debug('stars', comp) + return comp +} + +function isX (id) { + return !id || id.toLowerCase() === 'x' || id === '*' +} + +// ~, ~> --> * (any, kinda silly) +// ~2, ~2.x, ~2.x.x, ~>2, ~>2.x ~>2.x.x --> >=2.0.0 <3.0.0 +// ~2.0, ~2.0.x, ~>2.0, ~>2.0.x --> >=2.0.0 <2.1.0 +// ~1.2, ~1.2.x, ~>1.2, ~>1.2.x --> >=1.2.0 <1.3.0 +// ~1.2.3, ~>1.2.3 --> >=1.2.3 <1.3.0 +// ~1.2.0, ~>1.2.0 --> >=1.2.0 <1.3.0 +function replaceTildes (comp, options) { + return comp.trim().split(/\s+/).map(function (comp) { + return replaceTilde(comp, options) + }).join(' ') +} + +function replaceTilde (comp, options) { + var r = options.loose ? safeRe[TILDELOOSE] : safeRe[TILDE] + return comp.replace(r, function (_, M, m, p, pr) { + debug('tilde', comp, _, M, m, p, pr) + var ret + + if (isX(M)) { + ret = '' + } else if (isX(m)) { + ret = '>=' + M + '.0.0 <' + (+M + 1) + '.0.0' + } else if (isX(p)) { + // ~1.2 == >=1.2.0 <1.3.0 + ret = '>=' + M + '.' + m + '.0 <' + M + '.' + (+m + 1) + '.0' + } else if (pr) { + debug('replaceTilde pr', pr) + ret = '>=' + M + '.' + m + '.' + p + '-' + pr + + ' <' + M + '.' + (+m + 1) + '.0' + } else { + // ~1.2.3 == >=1.2.3 <1.3.0 + ret = '>=' + M + '.' + m + '.' + p + + ' <' + M + '.' + (+m + 1) + '.0' + } + + debug('tilde return', ret) + return ret + }) +} + +// ^ --> * (any, kinda silly) +// ^2, ^2.x, ^2.x.x --> >=2.0.0 <3.0.0 +// ^2.0, ^2.0.x --> >=2.0.0 <3.0.0 +// ^1.2, ^1.2.x --> >=1.2.0 <2.0.0 +// ^1.2.3 --> >=1.2.3 <2.0.0 +// ^1.2.0 --> >=1.2.0 <2.0.0 +function replaceCarets (comp, options) { + return comp.trim().split(/\s+/).map(function (comp) { + return replaceCaret(comp, options) + }).join(' ') +} + +function replaceCaret (comp, options) { + debug('caret', comp, options) + var r = options.loose ? safeRe[CARETLOOSE] : safeRe[CARET] + return comp.replace(r, function (_, M, m, p, pr) { + debug('caret', comp, _, M, m, p, pr) + var ret + + if (isX(M)) { + ret = '' + } else if (isX(m)) { + ret = '>=' + M + '.0.0 <' + (+M + 1) + '.0.0' + } else if (isX(p)) { + if (M === '0') { + ret = '>=' + M + '.' + m + '.0 <' + M + '.' + (+m + 1) + '.0' + } else { + ret = '>=' + M + '.' + m + '.0 <' + (+M + 1) + '.0.0' + } + } else if (pr) { + debug('replaceCaret pr', pr) + if (M === '0') { + if (m === '0') { + ret = '>=' + M + '.' + m + '.' + p + '-' + pr + + ' <' + M + '.' + m + '.' + (+p + 1) + } else { + ret = '>=' + M + '.' + m + '.' + p + '-' + pr + + ' <' + M + '.' + (+m + 1) + '.0' + } + } else { + ret = '>=' + M + '.' + m + '.' + p + '-' + pr + + ' <' + (+M + 1) + '.0.0' + } + } else { + debug('no pr') + if (M === '0') { + if (m === '0') { + ret = '>=' + M + '.' + m + '.' + p + + ' <' + M + '.' + m + '.' + (+p + 1) + } else { + ret = '>=' + M + '.' + m + '.' + p + + ' <' + M + '.' + (+m + 1) + '.0' + } + } else { + ret = '>=' + M + '.' + m + '.' + p + + ' <' + (+M + 1) + '.0.0' + } + } + + debug('caret return', ret) + return ret + }) +} + +function replaceXRanges (comp, options) { + debug('replaceXRanges', comp, options) + return comp.split(/\s+/).map(function (comp) { + return replaceXRange(comp, options) + }).join(' ') +} + +function replaceXRange (comp, options) { + comp = comp.trim() + var r = options.loose ? safeRe[XRANGELOOSE] : safeRe[XRANGE] + return comp.replace(r, function (ret, gtlt, M, m, p, pr) { + debug('xRange', comp, ret, gtlt, M, m, p, pr) + var xM = isX(M) + var xm = xM || isX(m) + var xp = xm || isX(p) + var anyX = xp + + if (gtlt === '=' && anyX) { + gtlt = '' + } + + if (xM) { + if (gtlt === '>' || gtlt === '<') { + // nothing is allowed + ret = '<0.0.0' + } else { + // nothing is forbidden + ret = '*' + } + } else if (gtlt && anyX) { + // we know patch is an x, because we have any x at all. + // replace X with 0 + if (xm) { + m = 0 + } + p = 0 + + if (gtlt === '>') { + // >1 => >=2.0.0 + // >1.2 => >=1.3.0 + // >1.2.3 => >= 1.2.4 + gtlt = '>=' + if (xm) { + M = +M + 1 + m = 0 + p = 0 + } else { + m = +m + 1 + p = 0 + } + } else if (gtlt === '<=') { + // <=0.7.x is actually <0.8.0, since any 0.7.x should + // pass. Similarly, <=7.x is actually <8.0.0, etc. + gtlt = '<' + if (xm) { + M = +M + 1 + } else { + m = +m + 1 + } + } + + ret = gtlt + M + '.' + m + '.' + p + } else if (xm) { + ret = '>=' + M + '.0.0 <' + (+M + 1) + '.0.0' + } else if (xp) { + ret = '>=' + M + '.' + m + '.0 <' + M + '.' + (+m + 1) + '.0' + } + + debug('xRange return', ret) + + return ret + }) +} + +// Because * is AND-ed with everything else in the comparator, +// and '' means "any version", just remove the *s entirely. +function replaceStars (comp, options) { + debug('replaceStars', comp, options) + // Looseness is ignored here. star is always as loose as it gets! + return comp.trim().replace(safeRe[STAR], '') +} + +// This function is passed to string.replace(safeRe[HYPHENRANGE]) +// M, m, patch, prerelease, build +// 1.2 - 3.4.5 => >=1.2.0 <=3.4.5 +// 1.2.3 - 3.4 => >=1.2.0 <3.5.0 Any 3.4.x will do +// 1.2 - 3.4 => >=1.2.0 <3.5.0 +function hyphenReplace ($0, + from, fM, fm, fp, fpr, fb, + to, tM, tm, tp, tpr, tb) { + if (isX(fM)) { + from = '' + } else if (isX(fm)) { + from = '>=' + fM + '.0.0' + } else if (isX(fp)) { + from = '>=' + fM + '.' + fm + '.0' + } else { + from = '>=' + from + } + + if (isX(tM)) { + to = '' + } else if (isX(tm)) { + to = '<' + (+tM + 1) + '.0.0' + } else if (isX(tp)) { + to = '<' + tM + '.' + (+tm + 1) + '.0' + } else if (tpr) { + to = '<=' + tM + '.' + tm + '.' + tp + '-' + tpr + } else { + to = '<=' + to + } + + return (from + ' ' + to).trim() +} + +// if ANY of the sets match ALL of its comparators, then pass +Range.prototype.test = function (version) { + if (!version) { + return false + } + + if (typeof version === 'string') { + version = new SemVer(version, this.options) + } + + for (var i = 0; i < this.set.length; i++) { + if (testSet(this.set[i], version, this.options)) { + return true + } + } + return false +} + +function testSet (set, version, options) { + for (var i = 0; i < set.length; i++) { + if (!set[i].test(version)) { + return false + } + } + + if (version.prerelease.length && !options.includePrerelease) { + // Find the set of versions that are allowed to have prereleases + // For example, ^1.2.3-pr.1 desugars to >=1.2.3-pr.1 <2.0.0 + // That should allow `1.2.3-pr.2` to pass. + // However, `1.2.4-alpha.notready` should NOT be allowed, + // even though it's within the range set by the comparators. + for (i = 0; i < set.length; i++) { + debug(set[i].semver) + if (set[i].semver === ANY) { + continue + } + + if (set[i].semver.prerelease.length > 0) { + var allowed = set[i].semver + if (allowed.major === version.major && + allowed.minor === version.minor && + allowed.patch === version.patch) { + return true + } + } + } + + // Version has a -pre, but it's not one of the ones we like. + return false + } + + return true +} + +exports.satisfies = satisfies +function satisfies (version, range, options) { + try { + range = new Range(range, options) + } catch (er) { + return false + } + return range.test(version) +} + +exports.maxSatisfying = maxSatisfying +function maxSatisfying (versions, range, options) { + var max = null + var maxSV = null + try { + var rangeObj = new Range(range, options) + } catch (er) { + return null + } + versions.forEach(function (v) { + if (rangeObj.test(v)) { + // satisfies(v, range, options) + if (!max || maxSV.compare(v) === -1) { + // compare(max, v, true) + max = v + maxSV = new SemVer(max, options) + } + } + }) + return max +} + +exports.minSatisfying = minSatisfying +function minSatisfying (versions, range, options) { + var min = null + var minSV = null + try { + var rangeObj = new Range(range, options) + } catch (er) { + return null + } + versions.forEach(function (v) { + if (rangeObj.test(v)) { + // satisfies(v, range, options) + if (!min || minSV.compare(v) === 1) { + // compare(min, v, true) + min = v + minSV = new SemVer(min, options) + } + } + }) + return min +} + +exports.minVersion = minVersion +function minVersion (range, loose) { + range = new Range(range, loose) + + var minver = new SemVer('0.0.0') + if (range.test(minver)) { + return minver + } + + minver = new SemVer('0.0.0-0') + if (range.test(minver)) { + return minver + } + + minver = null + for (var i = 0; i < range.set.length; ++i) { + var comparators = range.set[i] + + comparators.forEach(function (comparator) { + // Clone to avoid manipulating the comparator's semver object. + var compver = new SemVer(comparator.semver.version) + switch (comparator.operator) { + case '>': + if (compver.prerelease.length === 0) { + compver.patch++ + } else { + compver.prerelease.push(0) + } + compver.raw = compver.format() + /* fallthrough */ + case '': + case '>=': + if (!minver || gt(minver, compver)) { + minver = compver + } + break + case '<': + case '<=': + /* Ignore maximum versions */ + break + /* istanbul ignore next */ + default: + throw new Error('Unexpected operation: ' + comparator.operator) + } + }) + } + + if (minver && range.test(minver)) { + return minver + } + + return null +} + +exports.validRange = validRange +function validRange (range, options) { + try { + // Return '*' instead of '' so that truthiness works. + // This will throw if it's invalid anyway + return new Range(range, options).range || '*' + } catch (er) { + return null + } +} + +// Determine if version is less than all the versions possible in the range +exports.ltr = ltr +function ltr (version, range, options) { + return outside(version, range, '<', options) +} + +// Determine if version is greater than all the versions possible in the range. +exports.gtr = gtr +function gtr (version, range, options) { + return outside(version, range, '>', options) +} + +exports.outside = outside +function outside (version, range, hilo, options) { + version = new SemVer(version, options) + range = new Range(range, options) + + var gtfn, ltefn, ltfn, comp, ecomp + switch (hilo) { + case '>': + gtfn = gt + ltefn = lte + ltfn = lt + comp = '>' + ecomp = '>=' + break + case '<': + gtfn = lt + ltefn = gte + ltfn = gt + comp = '<' + ecomp = '<=' + break + default: + throw new TypeError('Must provide a hilo val of "<" or ">"') + } + + // If it satisifes the range it is not outside + if (satisfies(version, range, options)) { + return false + } + + // From now on, variable terms are as if we're in "gtr" mode. + // but note that everything is flipped for the "ltr" function. + + for (var i = 0; i < range.set.length; ++i) { + var comparators = range.set[i] + + var high = null + var low = null + + comparators.forEach(function (comparator) { + if (comparator.semver === ANY) { + comparator = new Comparator('>=0.0.0') + } + high = high || comparator + low = low || comparator + if (gtfn(comparator.semver, high.semver, options)) { + high = comparator + } else if (ltfn(comparator.semver, low.semver, options)) { + low = comparator + } + }) + + // If the edge version comparator has a operator then our version + // isn't outside it + if (high.operator === comp || high.operator === ecomp) { + return false + } + + // If the lowest version comparator has an operator and our version + // is less than it then it isn't higher than the range + if ((!low.operator || low.operator === comp) && + ltefn(version, low.semver)) { + return false + } else if (low.operator === ecomp && ltfn(version, low.semver)) { + return false + } + } + return true +} + +exports.prerelease = prerelease +function prerelease (version, options) { + var parsed = parse(version, options) + return (parsed && parsed.prerelease.length) ? parsed.prerelease : null +} + +exports.intersects = intersects +function intersects (r1, r2, options) { + r1 = new Range(r1, options) + r2 = new Range(r2, options) + return r1.intersects(r2) +} + +exports.coerce = coerce +function coerce (version) { + if (version instanceof SemVer) { + return version + } + + if (typeof version !== 'string') { + return null + } + + var match = version.match(safeRe[COERCE]) + + if (match == null) { + return null + } + + return parse(match[1] + + '.' + (match[2] || '0') + + '.' + (match[3] || '0')) +} + + +/***/ }), + +/***/ 76934: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +/* -*- Mode: js; js-indent-level: 2; -*- */ +/* + * Copyright 2011 Mozilla Foundation and contributors + * Licensed under the New BSD license. See LICENSE or: + * http://opensource.org/licenses/BSD-3-Clause + */ + +var util = __nccwpck_require__(62492); +var has = Object.prototype.hasOwnProperty; +var hasNativeMap = typeof Map !== "undefined"; + +/** + * A data structure which is a combination of an array and a set. Adding a new + * member is O(1), testing for membership is O(1), and finding the index of an + * element is O(1). Removing elements from the set is not supported. Only + * strings are supported for membership. + */ +function ArraySet() { + this._array = []; + this._set = hasNativeMap ? new Map() : Object.create(null); +} + +/** + * Static method for creating ArraySet instances from an existing array. + */ +ArraySet.fromArray = function ArraySet_fromArray(aArray, aAllowDuplicates) { + var set = new ArraySet(); + for (var i = 0, len = aArray.length; i < len; i++) { + set.add(aArray[i], aAllowDuplicates); + } + return set; +}; + +/** + * Return how many unique items are in this ArraySet. If duplicates have been + * added, than those do not count towards the size. + * + * @returns Number + */ +ArraySet.prototype.size = function ArraySet_size() { + return hasNativeMap ? this._set.size : Object.getOwnPropertyNames(this._set).length; +}; + +/** + * Add the given string to this set. + * + * @param String aStr + */ +ArraySet.prototype.add = function ArraySet_add(aStr, aAllowDuplicates) { + var sStr = hasNativeMap ? aStr : util.toSetString(aStr); + var isDuplicate = hasNativeMap ? this.has(aStr) : has.call(this._set, sStr); + var idx = this._array.length; + if (!isDuplicate || aAllowDuplicates) { + this._array.push(aStr); + } + if (!isDuplicate) { + if (hasNativeMap) { + this._set.set(aStr, idx); + } else { + this._set[sStr] = idx; + } + } +}; + +/** + * Is the given string a member of this set? + * + * @param String aStr + */ +ArraySet.prototype.has = function ArraySet_has(aStr) { + if (hasNativeMap) { + return this._set.has(aStr); + } else { + var sStr = util.toSetString(aStr); + return has.call(this._set, sStr); + } +}; + +/** + * What is the index of the given string in the array? + * + * @param String aStr + */ +ArraySet.prototype.indexOf = function ArraySet_indexOf(aStr) { + if (hasNativeMap) { + var idx = this._set.get(aStr); + if (idx >= 0) { + return idx; + } + } else { + var sStr = util.toSetString(aStr); + if (has.call(this._set, sStr)) { + return this._set[sStr]; + } + } + + throw new Error('"' + aStr + '" is not in the set.'); +}; + +/** + * What is the element at the given index? + * + * @param Number aIdx + */ +ArraySet.prototype.at = function ArraySet_at(aIdx) { + if (aIdx >= 0 && aIdx < this._array.length) { + return this._array[aIdx]; + } + throw new Error('No element indexed by ' + aIdx); +}; + +/** + * Returns the array representation of this set (which has the proper indices + * indicated by indexOf). Note that this is a copy of the internal array used + * for storing the members so that no one can mess with internal state. + */ +ArraySet.prototype.toArray = function ArraySet_toArray() { + return this._array.slice(); +}; + +exports.C = ArraySet; + + +/***/ }), + +/***/ 27935: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +/* -*- Mode: js; js-indent-level: 2; -*- */ +/* + * Copyright 2011 Mozilla Foundation and contributors + * Licensed under the New BSD license. See LICENSE or: + * http://opensource.org/licenses/BSD-3-Clause + * + * Based on the Base 64 VLQ implementation in Closure Compiler: + * https://code.google.com/p/closure-compiler/source/browse/trunk/src/com/google/debugging/sourcemap/Base64VLQ.java + * + * Copyright 2011 The Closure Compiler Authors. All rights reserved. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +var base64 = __nccwpck_require__(691); + +// A single base 64 digit can contain 6 bits of data. For the base 64 variable +// length quantities we use in the source map spec, the first bit is the sign, +// the next four bits are the actual value, and the 6th bit is the +// continuation bit. The continuation bit tells us whether there are more +// digits in this value following this digit. +// +// Continuation +// | Sign +// | | +// V V +// 101011 + +var VLQ_BASE_SHIFT = 5; + +// binary: 100000 +var VLQ_BASE = 1 << VLQ_BASE_SHIFT; + +// binary: 011111 +var VLQ_BASE_MASK = VLQ_BASE - 1; + +// binary: 100000 +var VLQ_CONTINUATION_BIT = VLQ_BASE; + +/** + * Converts from a two-complement value to a value where the sign bit is + * placed in the least significant bit. For example, as decimals: + * 1 becomes 2 (10 binary), -1 becomes 3 (11 binary) + * 2 becomes 4 (100 binary), -2 becomes 5 (101 binary) + */ +function toVLQSigned(aValue) { + return aValue < 0 + ? ((-aValue) << 1) + 1 + : (aValue << 1) + 0; +} + +/** + * Converts to a two-complement value from a value where the sign bit is + * placed in the least significant bit. For example, as decimals: + * 2 (10 binary) becomes 1, 3 (11 binary) becomes -1 + * 4 (100 binary) becomes 2, 5 (101 binary) becomes -2 + */ +function fromVLQSigned(aValue) { + var isNegative = (aValue & 1) === 1; + var shifted = aValue >> 1; + return isNegative + ? -shifted + : shifted; +} + +/** + * Returns the base 64 VLQ encoded value. + */ +exports.encode = function base64VLQ_encode(aValue) { + var encoded = ""; + var digit; + + var vlq = toVLQSigned(aValue); + + do { + digit = vlq & VLQ_BASE_MASK; + vlq >>>= VLQ_BASE_SHIFT; + if (vlq > 0) { + // There are still more digits in this value, so we must make sure the + // continuation bit is marked. + digit |= VLQ_CONTINUATION_BIT; + } + encoded += base64.encode(digit); + } while (vlq > 0); + + return encoded; +}; + +/** + * Decodes the next base 64 VLQ value from the given string and returns the + * value and the rest of the string via the out parameter. + */ +exports.decode = function base64VLQ_decode(aStr, aIndex, aOutParam) { + var strLen = aStr.length; + var result = 0; + var shift = 0; + var continuation, digit; + + do { + if (aIndex >= strLen) { + throw new Error("Expected more digits in base 64 VLQ value."); + } + + digit = base64.decode(aStr.charCodeAt(aIndex++)); + if (digit === -1) { + throw new Error("Invalid base64 digit: " + aStr.charAt(aIndex - 1)); + } + + continuation = !!(digit & VLQ_CONTINUATION_BIT); + digit &= VLQ_BASE_MASK; + result = result + (digit << shift); + shift += VLQ_BASE_SHIFT; + } while (continuation); + + aOutParam.value = fromVLQSigned(result); + aOutParam.rest = aIndex; +}; + + +/***/ }), + +/***/ 691: +/***/ ((__unused_webpack_module, exports) => { + +/* -*- Mode: js; js-indent-level: 2; -*- */ +/* + * Copyright 2011 Mozilla Foundation and contributors + * Licensed under the New BSD license. See LICENSE or: + * http://opensource.org/licenses/BSD-3-Clause + */ + +var intToCharMap = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'.split(''); + +/** + * Encode an integer in the range of 0 to 63 to a single base 64 digit. + */ +exports.encode = function (number) { + if (0 <= number && number < intToCharMap.length) { + return intToCharMap[number]; + } + throw new TypeError("Must be between 0 and 63: " + number); +}; + +/** + * Decode a single base 64 character code digit to an integer. Returns -1 on + * failure. + */ +exports.decode = function (charCode) { + var bigA = 65; // 'A' + var bigZ = 90; // 'Z' + + var littleA = 97; // 'a' + var littleZ = 122; // 'z' + + var zero = 48; // '0' + var nine = 57; // '9' + + var plus = 43; // '+' + var slash = 47; // '/' + + var littleOffset = 26; + var numberOffset = 52; + + // 0 - 25: ABCDEFGHIJKLMNOPQRSTUVWXYZ + if (bigA <= charCode && charCode <= bigZ) { + return (charCode - bigA); + } + + // 26 - 51: abcdefghijklmnopqrstuvwxyz + if (littleA <= charCode && charCode <= littleZ) { + return (charCode - littleA + littleOffset); + } + + // 52 - 61: 0123456789 + if (zero <= charCode && charCode <= nine) { + return (charCode - zero + numberOffset); + } + + // 62: + + if (charCode == plus) { + return 62; + } + + // 63: / + if (charCode == slash) { + return 63; + } + + // Invalid base64 digit. + return -1; +}; + + +/***/ }), + +/***/ 72326: +/***/ ((__unused_webpack_module, exports) => { + +/* -*- Mode: js; js-indent-level: 2; -*- */ +/* + * Copyright 2011 Mozilla Foundation and contributors + * Licensed under the New BSD license. See LICENSE or: + * http://opensource.org/licenses/BSD-3-Clause + */ + +exports.GREATEST_LOWER_BOUND = 1; +exports.LEAST_UPPER_BOUND = 2; + +/** + * Recursive implementation of binary search. + * + * @param aLow Indices here and lower do not contain the needle. + * @param aHigh Indices here and higher do not contain the needle. + * @param aNeedle The element being searched for. + * @param aHaystack The non-empty array being searched. + * @param aCompare Function which takes two elements and returns -1, 0, or 1. + * @param aBias Either 'binarySearch.GREATEST_LOWER_BOUND' or + * 'binarySearch.LEAST_UPPER_BOUND'. Specifies whether to return the + * closest element that is smaller than or greater than the one we are + * searching for, respectively, if the exact element cannot be found. + */ +function recursiveSearch(aLow, aHigh, aNeedle, aHaystack, aCompare, aBias) { + // This function terminates when one of the following is true: + // + // 1. We find the exact element we are looking for. + // + // 2. We did not find the exact element, but we can return the index of + // the next-closest element. + // + // 3. We did not find the exact element, and there is no next-closest + // element than the one we are searching for, so we return -1. + var mid = Math.floor((aHigh - aLow) / 2) + aLow; + var cmp = aCompare(aNeedle, aHaystack[mid], true); + if (cmp === 0) { + // Found the element we are looking for. + return mid; + } + else if (cmp > 0) { + // Our needle is greater than aHaystack[mid]. + if (aHigh - mid > 1) { + // The element is in the upper half. + return recursiveSearch(mid, aHigh, aNeedle, aHaystack, aCompare, aBias); + } + + // The exact needle element was not found in this haystack. Determine if + // we are in termination case (3) or (2) and return the appropriate thing. + if (aBias == exports.LEAST_UPPER_BOUND) { + return aHigh < aHaystack.length ? aHigh : -1; + } else { + return mid; + } + } + else { + // Our needle is less than aHaystack[mid]. + if (mid - aLow > 1) { + // The element is in the lower half. + return recursiveSearch(aLow, mid, aNeedle, aHaystack, aCompare, aBias); + } + + // we are in termination case (3) or (2) and return the appropriate thing. + if (aBias == exports.LEAST_UPPER_BOUND) { + return mid; + } else { + return aLow < 0 ? -1 : aLow; + } + } +} + +/** + * This is an implementation of binary search which will always try and return + * the index of the closest element if there is no exact hit. This is because + * mappings between original and generated line/col pairs are single points, + * and there is an implicit region between each of them, so a miss just means + * that you aren't on the very start of a region. + * + * @param aNeedle The element you are looking for. + * @param aHaystack The array that is being searched. + * @param aCompare A function which takes the needle and an element in the + * array and returns -1, 0, or 1 depending on whether the needle is less + * than, equal to, or greater than the element, respectively. + * @param aBias Either 'binarySearch.GREATEST_LOWER_BOUND' or + * 'binarySearch.LEAST_UPPER_BOUND'. Specifies whether to return the + * closest element that is smaller than or greater than the one we are + * searching for, respectively, if the exact element cannot be found. + * Defaults to 'binarySearch.GREATEST_LOWER_BOUND'. + */ +exports.search = function search(aNeedle, aHaystack, aCompare, aBias) { + if (aHaystack.length === 0) { + return -1; + } + + var index = recursiveSearch(-1, aHaystack.length, aNeedle, aHaystack, + aCompare, aBias || exports.GREATEST_LOWER_BOUND); + if (index < 0) { + return -1; + } + + // We have found either the exact element, or the next-closest element than + // the one we are searching for. However, there may be more than one such + // element. Make sure we always return the smallest of these. + while (index - 1 >= 0) { + if (aCompare(aHaystack[index], aHaystack[index - 1], true) !== 0) { + break; + } + --index; + } + + return index; +}; + + +/***/ }), + +/***/ 12861: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +/* -*- Mode: js; js-indent-level: 2; -*- */ +/* + * Copyright 2014 Mozilla Foundation and contributors + * Licensed under the New BSD license. See LICENSE or: + * http://opensource.org/licenses/BSD-3-Clause + */ + +var util = __nccwpck_require__(62492); + +/** + * Determine whether mappingB is after mappingA with respect to generated + * position. + */ +function generatedPositionAfter(mappingA, mappingB) { + // Optimized for most common case + var lineA = mappingA.generatedLine; + var lineB = mappingB.generatedLine; + var columnA = mappingA.generatedColumn; + var columnB = mappingB.generatedColumn; + return lineB > lineA || lineB == lineA && columnB >= columnA || + util.compareByGeneratedPositionsInflated(mappingA, mappingB) <= 0; +} + +/** + * A data structure to provide a sorted view of accumulated mappings in a + * performance conscious manner. It trades a neglibable overhead in general + * case for a large speedup in case of mappings being added in order. + */ +function MappingList() { + this._array = []; + this._sorted = true; + // Serves as infimum + this._last = {generatedLine: -1, generatedColumn: 0}; +} + +/** + * Iterate through internal items. This method takes the same arguments that + * `Array.prototype.forEach` takes. + * + * NOTE: The order of the mappings is NOT guaranteed. + */ +MappingList.prototype.unsortedForEach = + function MappingList_forEach(aCallback, aThisArg) { + this._array.forEach(aCallback, aThisArg); + }; + +/** + * Add the given source mapping. + * + * @param Object aMapping + */ +MappingList.prototype.add = function MappingList_add(aMapping) { + if (generatedPositionAfter(this._last, aMapping)) { + this._last = aMapping; + this._array.push(aMapping); + } else { + this._sorted = false; + this._array.push(aMapping); + } +}; + +/** + * Returns the flat, sorted array of mappings. The mappings are sorted by + * generated position. + * + * WARNING: This method returns internal data without copying, for + * performance. The return value must NOT be mutated, and should be treated as + * an immutable borrow. If you want to take ownership, you must make your own + * copy. + */ +MappingList.prototype.toArray = function MappingList_toArray() { + if (!this._sorted) { + this._array.sort(util.compareByGeneratedPositionsInflated); + this._sorted = true; + } + return this._array; +}; + +exports.P = MappingList; + + +/***/ }), + +/***/ 9598: +/***/ ((__unused_webpack_module, exports) => { + +/* -*- Mode: js; js-indent-level: 2; -*- */ +/* + * Copyright 2011 Mozilla Foundation and contributors + * Licensed under the New BSD license. See LICENSE or: + * http://opensource.org/licenses/BSD-3-Clause + */ + +// It turns out that some (most?) JavaScript engines don't self-host +// `Array.prototype.sort`. This makes sense because C++ will likely remain +// faster than JS when doing raw CPU-intensive sorting. However, when using a +// custom comparator function, calling back and forth between the VM's C++ and +// JIT'd JS is rather slow *and* loses JIT type information, resulting in +// worse generated code for the comparator function than would be optimal. In +// fact, when sorting with a comparator, these costs outweigh the benefits of +// sorting in C++. By using our own JS-implemented Quick Sort (below), we get +// a ~3500ms mean speed-up in `bench/bench.html`. + +/** + * Swap the elements indexed by `x` and `y` in the array `ary`. + * + * @param {Array} ary + * The array. + * @param {Number} x + * The index of the first item. + * @param {Number} y + * The index of the second item. + */ +function swap(ary, x, y) { + var temp = ary[x]; + ary[x] = ary[y]; + ary[y] = temp; +} + +/** + * Returns a random integer within the range `low .. high` inclusive. + * + * @param {Number} low + * The lower bound on the range. + * @param {Number} high + * The upper bound on the range. + */ +function randomIntInRange(low, high) { + return Math.round(low + (Math.random() * (high - low))); +} + +/** + * The Quick Sort algorithm. + * + * @param {Array} ary + * An array to sort. + * @param {function} comparator + * Function to use to compare two items. + * @param {Number} p + * Start index of the array + * @param {Number} r + * End index of the array + */ +function doQuickSort(ary, comparator, p, r) { + // If our lower bound is less than our upper bound, we (1) partition the + // array into two pieces and (2) recurse on each half. If it is not, this is + // the empty array and our base case. + + if (p < r) { + // (1) Partitioning. + // + // The partitioning chooses a pivot between `p` and `r` and moves all + // elements that are less than or equal to the pivot to the before it, and + // all the elements that are greater than it after it. The effect is that + // once partition is done, the pivot is in the exact place it will be when + // the array is put in sorted order, and it will not need to be moved + // again. This runs in O(n) time. + + // Always choose a random pivot so that an input array which is reverse + // sorted does not cause O(n^2) running time. + var pivotIndex = randomIntInRange(p, r); + var i = p - 1; + + swap(ary, pivotIndex, r); + var pivot = ary[r]; + + // Immediately after `j` is incremented in this loop, the following hold + // true: + // + // * Every element in `ary[p .. i]` is less than or equal to the pivot. + // + // * Every element in `ary[i+1 .. j-1]` is greater than the pivot. + for (var j = p; j < r; j++) { + if (comparator(ary[j], pivot) <= 0) { + i += 1; + swap(ary, i, j); + } + } + + swap(ary, i + 1, j); + var q = i + 1; + + // (2) Recurse on each half. + + doQuickSort(ary, comparator, p, q - 1); + doQuickSort(ary, comparator, q + 1, r); + } +} + +/** + * Sort the given array in-place with the given comparator function. + * + * @param {Array} ary + * An array to sort. + * @param {function} comparator + * Function to use to compare two items. + */ +exports.g = function (ary, comparator) { + doQuickSort(ary, comparator, 0, ary.length - 1); +}; + + +/***/ }), + +/***/ 79907: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +var __webpack_unused_export__; +/* -*- Mode: js; js-indent-level: 2; -*- */ +/* + * Copyright 2011 Mozilla Foundation and contributors + * Licensed under the New BSD license. See LICENSE or: + * http://opensource.org/licenses/BSD-3-Clause + */ + +var util = __nccwpck_require__(62492); +var binarySearch = __nccwpck_require__(72326); +var ArraySet = (__nccwpck_require__(76934)/* .ArraySet */ .C); +var base64VLQ = __nccwpck_require__(27935); +var quickSort = (__nccwpck_require__(9598)/* .quickSort */ .g); + +function SourceMapConsumer(aSourceMap) { + var sourceMap = aSourceMap; + if (typeof aSourceMap === 'string') { + sourceMap = JSON.parse(aSourceMap.replace(/^\)\]\}'/, '')); + } + + return sourceMap.sections != null + ? new IndexedSourceMapConsumer(sourceMap) + : new BasicSourceMapConsumer(sourceMap); +} + +SourceMapConsumer.fromSourceMap = function(aSourceMap) { + return BasicSourceMapConsumer.fromSourceMap(aSourceMap); +} + +/** + * The version of the source mapping spec that we are consuming. + */ +SourceMapConsumer.prototype._version = 3; + +// `__generatedMappings` and `__originalMappings` are arrays that hold the +// parsed mapping coordinates from the source map's "mappings" attribute. They +// are lazily instantiated, accessed via the `_generatedMappings` and +// `_originalMappings` getters respectively, and we only parse the mappings +// and create these arrays once queried for a source location. We jump through +// these hoops because there can be many thousands of mappings, and parsing +// them is expensive, so we only want to do it if we must. +// +// Each object in the arrays is of the form: +// +// { +// generatedLine: The line number in the generated code, +// generatedColumn: The column number in the generated code, +// source: The path to the original source file that generated this +// chunk of code, +// originalLine: The line number in the original source that +// corresponds to this chunk of generated code, +// originalColumn: The column number in the original source that +// corresponds to this chunk of generated code, +// name: The name of the original symbol which generated this chunk of +// code. +// } +// +// All properties except for `generatedLine` and `generatedColumn` can be +// `null`. +// +// `_generatedMappings` is ordered by the generated positions. +// +// `_originalMappings` is ordered by the original positions. + +SourceMapConsumer.prototype.__generatedMappings = null; +Object.defineProperty(SourceMapConsumer.prototype, '_generatedMappings', { + get: function () { + if (!this.__generatedMappings) { + this._parseMappings(this._mappings, this.sourceRoot); + } + + return this.__generatedMappings; + } +}); + +SourceMapConsumer.prototype.__originalMappings = null; +Object.defineProperty(SourceMapConsumer.prototype, '_originalMappings', { + get: function () { + if (!this.__originalMappings) { + this._parseMappings(this._mappings, this.sourceRoot); + } + + return this.__originalMappings; + } +}); + +SourceMapConsumer.prototype._charIsMappingSeparator = + function SourceMapConsumer_charIsMappingSeparator(aStr, index) { + var c = aStr.charAt(index); + return c === ";" || c === ","; + }; + +/** + * Parse the mappings in a string in to a data structure which we can easily + * query (the ordered arrays in the `this.__generatedMappings` and + * `this.__originalMappings` properties). + */ +SourceMapConsumer.prototype._parseMappings = + function SourceMapConsumer_parseMappings(aStr, aSourceRoot) { + throw new Error("Subclasses must implement _parseMappings"); + }; + +SourceMapConsumer.GENERATED_ORDER = 1; +SourceMapConsumer.ORIGINAL_ORDER = 2; + +SourceMapConsumer.GREATEST_LOWER_BOUND = 1; +SourceMapConsumer.LEAST_UPPER_BOUND = 2; + +/** + * Iterate over each mapping between an original source/line/column and a + * generated line/column in this source map. + * + * @param Function aCallback + * The function that is called with each mapping. + * @param Object aContext + * Optional. If specified, this object will be the value of `this` every + * time that `aCallback` is called. + * @param aOrder + * Either `SourceMapConsumer.GENERATED_ORDER` or + * `SourceMapConsumer.ORIGINAL_ORDER`. Specifies whether you want to + * iterate over the mappings sorted by the generated file's line/column + * order or the original's source/line/column order, respectively. Defaults to + * `SourceMapConsumer.GENERATED_ORDER`. + */ +SourceMapConsumer.prototype.eachMapping = + function SourceMapConsumer_eachMapping(aCallback, aContext, aOrder) { + var context = aContext || null; + var order = aOrder || SourceMapConsumer.GENERATED_ORDER; + + var mappings; + switch (order) { + case SourceMapConsumer.GENERATED_ORDER: + mappings = this._generatedMappings; + break; + case SourceMapConsumer.ORIGINAL_ORDER: + mappings = this._originalMappings; + break; + default: + throw new Error("Unknown order of iteration."); + } + + var sourceRoot = this.sourceRoot; + mappings.map(function (mapping) { + var source = mapping.source === null ? null : this._sources.at(mapping.source); + if (source != null && sourceRoot != null) { + source = util.join(sourceRoot, source); + } + return { + source: source, + generatedLine: mapping.generatedLine, + generatedColumn: mapping.generatedColumn, + originalLine: mapping.originalLine, + originalColumn: mapping.originalColumn, + name: mapping.name === null ? null : this._names.at(mapping.name) + }; + }, this).forEach(aCallback, context); + }; + +/** + * Returns all generated line and column information for the original source, + * line, and column provided. If no column is provided, returns all mappings + * corresponding to a either the line we are searching for or the next + * closest line that has any mappings. Otherwise, returns all mappings + * corresponding to the given line and either the column we are searching for + * or the next closest column that has any offsets. + * + * The only argument is an object with the following properties: + * + * - source: The filename of the original source. + * - line: The line number in the original source. + * - column: Optional. the column number in the original source. + * + * and an array of objects is returned, each with the following properties: + * + * - line: The line number in the generated source, or null. + * - column: The column number in the generated source, or null. + */ +SourceMapConsumer.prototype.allGeneratedPositionsFor = + function SourceMapConsumer_allGeneratedPositionsFor(aArgs) { + var line = util.getArg(aArgs, 'line'); + + // When there is no exact match, BasicSourceMapConsumer.prototype._findMapping + // returns the index of the closest mapping less than the needle. By + // setting needle.originalColumn to 0, we thus find the last mapping for + // the given line, provided such a mapping exists. + var needle = { + source: util.getArg(aArgs, 'source'), + originalLine: line, + originalColumn: util.getArg(aArgs, 'column', 0) + }; + + if (this.sourceRoot != null) { + needle.source = util.relative(this.sourceRoot, needle.source); + } + if (!this._sources.has(needle.source)) { + return []; + } + needle.source = this._sources.indexOf(needle.source); + + var mappings = []; + + var index = this._findMapping(needle, + this._originalMappings, + "originalLine", + "originalColumn", + util.compareByOriginalPositions, + binarySearch.LEAST_UPPER_BOUND); + if (index >= 0) { + var mapping = this._originalMappings[index]; + + if (aArgs.column === undefined) { + var originalLine = mapping.originalLine; + + // Iterate until either we run out of mappings, or we run into + // a mapping for a different line than the one we found. Since + // mappings are sorted, this is guaranteed to find all mappings for + // the line we found. + while (mapping && mapping.originalLine === originalLine) { + mappings.push({ + line: util.getArg(mapping, 'generatedLine', null), + column: util.getArg(mapping, 'generatedColumn', null), + lastColumn: util.getArg(mapping, 'lastGeneratedColumn', null) + }); + + mapping = this._originalMappings[++index]; + } + } else { + var originalColumn = mapping.originalColumn; + + // Iterate until either we run out of mappings, or we run into + // a mapping for a different line than the one we were searching for. + // Since mappings are sorted, this is guaranteed to find all mappings for + // the line we are searching for. + while (mapping && + mapping.originalLine === line && + mapping.originalColumn == originalColumn) { + mappings.push({ + line: util.getArg(mapping, 'generatedLine', null), + column: util.getArg(mapping, 'generatedColumn', null), + lastColumn: util.getArg(mapping, 'lastGeneratedColumn', null) + }); + + mapping = this._originalMappings[++index]; + } + } + } + + return mappings; + }; + +exports.SourceMapConsumer = SourceMapConsumer; + +/** + * A BasicSourceMapConsumer instance represents a parsed source map which we can + * query for information about the original file positions by giving it a file + * position in the generated source. + * + * The only parameter is the raw source map (either as a JSON string, or + * already parsed to an object). According to the spec, source maps have the + * following attributes: + * + * - version: Which version of the source map spec this map is following. + * - sources: An array of URLs to the original source files. + * - names: An array of identifiers which can be referrenced by individual mappings. + * - sourceRoot: Optional. The URL root from which all sources are relative. + * - sourcesContent: Optional. An array of contents of the original source files. + * - mappings: A string of base64 VLQs which contain the actual mappings. + * - file: Optional. The generated file this source map is associated with. + * + * Here is an example source map, taken from the source map spec[0]: + * + * { + * version : 3, + * file: "out.js", + * sourceRoot : "", + * sources: ["foo.js", "bar.js"], + * names: ["src", "maps", "are", "fun"], + * mappings: "AA,AB;;ABCDE;" + * } + * + * [0]: https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit?pli=1# + */ +function BasicSourceMapConsumer(aSourceMap) { + var sourceMap = aSourceMap; + if (typeof aSourceMap === 'string') { + sourceMap = JSON.parse(aSourceMap.replace(/^\)\]\}'/, '')); + } + + var version = util.getArg(sourceMap, 'version'); + var sources = util.getArg(sourceMap, 'sources'); + // Sass 3.3 leaves out the 'names' array, so we deviate from the spec (which + // requires the array) to play nice here. + var names = util.getArg(sourceMap, 'names', []); + var sourceRoot = util.getArg(sourceMap, 'sourceRoot', null); + var sourcesContent = util.getArg(sourceMap, 'sourcesContent', null); + var mappings = util.getArg(sourceMap, 'mappings'); + var file = util.getArg(sourceMap, 'file', null); + + // Once again, Sass deviates from the spec and supplies the version as a + // string rather than a number, so we use loose equality checking here. + if (version != this._version) { + throw new Error('Unsupported version: ' + version); + } + + sources = sources + .map(String) + // Some source maps produce relative source paths like "./foo.js" instead of + // "foo.js". Normalize these first so that future comparisons will succeed. + // See bugzil.la/1090768. + .map(util.normalize) + // Always ensure that absolute sources are internally stored relative to + // the source root, if the source root is absolute. Not doing this would + // be particularly problematic when the source root is a prefix of the + // source (valid, but why??). See github issue #199 and bugzil.la/1188982. + .map(function (source) { + return sourceRoot && util.isAbsolute(sourceRoot) && util.isAbsolute(source) + ? util.relative(sourceRoot, source) + : source; + }); + + // Pass `true` below to allow duplicate names and sources. While source maps + // are intended to be compressed and deduplicated, the TypeScript compiler + // sometimes generates source maps with duplicates in them. See Github issue + // #72 and bugzil.la/889492. + this._names = ArraySet.fromArray(names.map(String), true); + this._sources = ArraySet.fromArray(sources, true); + + this.sourceRoot = sourceRoot; + this.sourcesContent = sourcesContent; + this._mappings = mappings; + this.file = file; +} + +BasicSourceMapConsumer.prototype = Object.create(SourceMapConsumer.prototype); +BasicSourceMapConsumer.prototype.consumer = SourceMapConsumer; + +/** + * Create a BasicSourceMapConsumer from a SourceMapGenerator. + * + * @param SourceMapGenerator aSourceMap + * The source map that will be consumed. + * @returns BasicSourceMapConsumer + */ +BasicSourceMapConsumer.fromSourceMap = + function SourceMapConsumer_fromSourceMap(aSourceMap) { + var smc = Object.create(BasicSourceMapConsumer.prototype); + + var names = smc._names = ArraySet.fromArray(aSourceMap._names.toArray(), true); + var sources = smc._sources = ArraySet.fromArray(aSourceMap._sources.toArray(), true); + smc.sourceRoot = aSourceMap._sourceRoot; + smc.sourcesContent = aSourceMap._generateSourcesContent(smc._sources.toArray(), + smc.sourceRoot); + smc.file = aSourceMap._file; + + // Because we are modifying the entries (by converting string sources and + // names to indices into the sources and names ArraySets), we have to make + // a copy of the entry or else bad things happen. Shared mutable state + // strikes again! See github issue #191. + + var generatedMappings = aSourceMap._mappings.toArray().slice(); + var destGeneratedMappings = smc.__generatedMappings = []; + var destOriginalMappings = smc.__originalMappings = []; + + for (var i = 0, length = generatedMappings.length; i < length; i++) { + var srcMapping = generatedMappings[i]; + var destMapping = new Mapping; + destMapping.generatedLine = srcMapping.generatedLine; + destMapping.generatedColumn = srcMapping.generatedColumn; + + if (srcMapping.source) { + destMapping.source = sources.indexOf(srcMapping.source); + destMapping.originalLine = srcMapping.originalLine; + destMapping.originalColumn = srcMapping.originalColumn; + + if (srcMapping.name) { + destMapping.name = names.indexOf(srcMapping.name); + } + + destOriginalMappings.push(destMapping); + } + + destGeneratedMappings.push(destMapping); + } + + quickSort(smc.__originalMappings, util.compareByOriginalPositions); + + return smc; + }; + +/** + * The version of the source mapping spec that we are consuming. + */ +BasicSourceMapConsumer.prototype._version = 3; + +/** + * The list of original sources. + */ +Object.defineProperty(BasicSourceMapConsumer.prototype, 'sources', { + get: function () { + return this._sources.toArray().map(function (s) { + return this.sourceRoot != null ? util.join(this.sourceRoot, s) : s; + }, this); + } +}); + +/** + * Provide the JIT with a nice shape / hidden class. + */ +function Mapping() { + this.generatedLine = 0; + this.generatedColumn = 0; + this.source = null; + this.originalLine = null; + this.originalColumn = null; + this.name = null; +} + +/** + * Parse the mappings in a string in to a data structure which we can easily + * query (the ordered arrays in the `this.__generatedMappings` and + * `this.__originalMappings` properties). + */ +BasicSourceMapConsumer.prototype._parseMappings = + function SourceMapConsumer_parseMappings(aStr, aSourceRoot) { + var generatedLine = 1; + var previousGeneratedColumn = 0; + var previousOriginalLine = 0; + var previousOriginalColumn = 0; + var previousSource = 0; + var previousName = 0; + var length = aStr.length; + var index = 0; + var cachedSegments = {}; + var temp = {}; + var originalMappings = []; + var generatedMappings = []; + var mapping, str, segment, end, value; + + while (index < length) { + if (aStr.charAt(index) === ';') { + generatedLine++; + index++; + previousGeneratedColumn = 0; + } + else if (aStr.charAt(index) === ',') { + index++; + } + else { + mapping = new Mapping(); + mapping.generatedLine = generatedLine; + + // Because each offset is encoded relative to the previous one, + // many segments often have the same encoding. We can exploit this + // fact by caching the parsed variable length fields of each segment, + // allowing us to avoid a second parse if we encounter the same + // segment again. + for (end = index; end < length; end++) { + if (this._charIsMappingSeparator(aStr, end)) { + break; + } + } + str = aStr.slice(index, end); + + segment = cachedSegments[str]; + if (segment) { + index += str.length; + } else { + segment = []; + while (index < end) { + base64VLQ.decode(aStr, index, temp); + value = temp.value; + index = temp.rest; + segment.push(value); + } + + if (segment.length === 2) { + throw new Error('Found a source, but no line and column'); + } + + if (segment.length === 3) { + throw new Error('Found a source and line, but no column'); + } + + cachedSegments[str] = segment; + } + + // Generated column. + mapping.generatedColumn = previousGeneratedColumn + segment[0]; + previousGeneratedColumn = mapping.generatedColumn; + + if (segment.length > 1) { + // Original source. + mapping.source = previousSource + segment[1]; + previousSource += segment[1]; + + // Original line. + mapping.originalLine = previousOriginalLine + segment[2]; + previousOriginalLine = mapping.originalLine; + // Lines are stored 0-based + mapping.originalLine += 1; + + // Original column. + mapping.originalColumn = previousOriginalColumn + segment[3]; + previousOriginalColumn = mapping.originalColumn; + + if (segment.length > 4) { + // Original name. + mapping.name = previousName + segment[4]; + previousName += segment[4]; + } + } + + generatedMappings.push(mapping); + if (typeof mapping.originalLine === 'number') { + originalMappings.push(mapping); + } + } + } + + quickSort(generatedMappings, util.compareByGeneratedPositionsDeflated); + this.__generatedMappings = generatedMappings; + + quickSort(originalMappings, util.compareByOriginalPositions); + this.__originalMappings = originalMappings; + }; + +/** + * Find the mapping that best matches the hypothetical "needle" mapping that + * we are searching for in the given "haystack" of mappings. + */ +BasicSourceMapConsumer.prototype._findMapping = + function SourceMapConsumer_findMapping(aNeedle, aMappings, aLineName, + aColumnName, aComparator, aBias) { + // To return the position we are searching for, we must first find the + // mapping for the given position and then return the opposite position it + // points to. Because the mappings are sorted, we can use binary search to + // find the best mapping. + + if (aNeedle[aLineName] <= 0) { + throw new TypeError('Line must be greater than or equal to 1, got ' + + aNeedle[aLineName]); + } + if (aNeedle[aColumnName] < 0) { + throw new TypeError('Column must be greater than or equal to 0, got ' + + aNeedle[aColumnName]); + } + + return binarySearch.search(aNeedle, aMappings, aComparator, aBias); + }; + +/** + * Compute the last column for each generated mapping. The last column is + * inclusive. + */ +BasicSourceMapConsumer.prototype.computeColumnSpans = + function SourceMapConsumer_computeColumnSpans() { + for (var index = 0; index < this._generatedMappings.length; ++index) { + var mapping = this._generatedMappings[index]; + + // Mappings do not contain a field for the last generated columnt. We + // can come up with an optimistic estimate, however, by assuming that + // mappings are contiguous (i.e. given two consecutive mappings, the + // first mapping ends where the second one starts). + if (index + 1 < this._generatedMappings.length) { + var nextMapping = this._generatedMappings[index + 1]; + + if (mapping.generatedLine === nextMapping.generatedLine) { + mapping.lastGeneratedColumn = nextMapping.generatedColumn - 1; + continue; + } + } + + // The last mapping for each line spans the entire line. + mapping.lastGeneratedColumn = Infinity; + } + }; + +/** + * Returns the original source, line, and column information for the generated + * source's line and column positions provided. The only argument is an object + * with the following properties: + * + * - line: The line number in the generated source. + * - column: The column number in the generated source. + * - bias: Either 'SourceMapConsumer.GREATEST_LOWER_BOUND' or + * 'SourceMapConsumer.LEAST_UPPER_BOUND'. Specifies whether to return the + * closest element that is smaller than or greater than the one we are + * searching for, respectively, if the exact element cannot be found. + * Defaults to 'SourceMapConsumer.GREATEST_LOWER_BOUND'. + * + * and an object is returned with the following properties: + * + * - source: The original source file, or null. + * - line: The line number in the original source, or null. + * - column: The column number in the original source, or null. + * - name: The original identifier, or null. + */ +BasicSourceMapConsumer.prototype.originalPositionFor = + function SourceMapConsumer_originalPositionFor(aArgs) { + var needle = { + generatedLine: util.getArg(aArgs, 'line'), + generatedColumn: util.getArg(aArgs, 'column') + }; + + var index = this._findMapping( + needle, + this._generatedMappings, + "generatedLine", + "generatedColumn", + util.compareByGeneratedPositionsDeflated, + util.getArg(aArgs, 'bias', SourceMapConsumer.GREATEST_LOWER_BOUND) + ); + + if (index >= 0) { + var mapping = this._generatedMappings[index]; + + if (mapping.generatedLine === needle.generatedLine) { + var source = util.getArg(mapping, 'source', null); + if (source !== null) { + source = this._sources.at(source); + if (this.sourceRoot != null) { + source = util.join(this.sourceRoot, source); + } + } + var name = util.getArg(mapping, 'name', null); + if (name !== null) { + name = this._names.at(name); + } + return { + source: source, + line: util.getArg(mapping, 'originalLine', null), + column: util.getArg(mapping, 'originalColumn', null), + name: name + }; + } + } + + return { + source: null, + line: null, + column: null, + name: null + }; + }; + +/** + * Return true if we have the source content for every source in the source + * map, false otherwise. + */ +BasicSourceMapConsumer.prototype.hasContentsOfAllSources = + function BasicSourceMapConsumer_hasContentsOfAllSources() { + if (!this.sourcesContent) { + return false; + } + return this.sourcesContent.length >= this._sources.size() && + !this.sourcesContent.some(function (sc) { return sc == null; }); + }; + +/** + * Returns the original source content. The only argument is the url of the + * original source file. Returns null if no original source content is + * available. + */ +BasicSourceMapConsumer.prototype.sourceContentFor = + function SourceMapConsumer_sourceContentFor(aSource, nullOnMissing) { + if (!this.sourcesContent) { + return null; + } + + if (this.sourceRoot != null) { + aSource = util.relative(this.sourceRoot, aSource); + } + + if (this._sources.has(aSource)) { + return this.sourcesContent[this._sources.indexOf(aSource)]; + } + + var url; + if (this.sourceRoot != null + && (url = util.urlParse(this.sourceRoot))) { + // XXX: file:// URIs and absolute paths lead to unexpected behavior for + // many users. We can help them out when they expect file:// URIs to + // behave like it would if they were running a local HTTP server. See + // https://bugzilla.mozilla.org/show_bug.cgi?id=885597. + var fileUriAbsPath = aSource.replace(/^file:\/\//, ""); + if (url.scheme == "file" + && this._sources.has(fileUriAbsPath)) { + return this.sourcesContent[this._sources.indexOf(fileUriAbsPath)] + } + + if ((!url.path || url.path == "/") + && this._sources.has("/" + aSource)) { + return this.sourcesContent[this._sources.indexOf("/" + aSource)]; + } + } + + // This function is used recursively from + // IndexedSourceMapConsumer.prototype.sourceContentFor. In that case, we + // don't want to throw if we can't find the source - we just want to + // return null, so we provide a flag to exit gracefully. + if (nullOnMissing) { + return null; + } + else { + throw new Error('"' + aSource + '" is not in the SourceMap.'); + } + }; + +/** + * Returns the generated line and column information for the original source, + * line, and column positions provided. The only argument is an object with + * the following properties: + * + * - source: The filename of the original source. + * - line: The line number in the original source. + * - column: The column number in the original source. + * - bias: Either 'SourceMapConsumer.GREATEST_LOWER_BOUND' or + * 'SourceMapConsumer.LEAST_UPPER_BOUND'. Specifies whether to return the + * closest element that is smaller than or greater than the one we are + * searching for, respectively, if the exact element cannot be found. + * Defaults to 'SourceMapConsumer.GREATEST_LOWER_BOUND'. + * + * and an object is returned with the following properties: + * + * - line: The line number in the generated source, or null. + * - column: The column number in the generated source, or null. + */ +BasicSourceMapConsumer.prototype.generatedPositionFor = + function SourceMapConsumer_generatedPositionFor(aArgs) { + var source = util.getArg(aArgs, 'source'); + if (this.sourceRoot != null) { + source = util.relative(this.sourceRoot, source); + } + if (!this._sources.has(source)) { + return { + line: null, + column: null, + lastColumn: null + }; + } + source = this._sources.indexOf(source); + + var needle = { + source: source, + originalLine: util.getArg(aArgs, 'line'), + originalColumn: util.getArg(aArgs, 'column') + }; + + var index = this._findMapping( + needle, + this._originalMappings, + "originalLine", + "originalColumn", + util.compareByOriginalPositions, + util.getArg(aArgs, 'bias', SourceMapConsumer.GREATEST_LOWER_BOUND) + ); + + if (index >= 0) { + var mapping = this._originalMappings[index]; + + if (mapping.source === needle.source) { + return { + line: util.getArg(mapping, 'generatedLine', null), + column: util.getArg(mapping, 'generatedColumn', null), + lastColumn: util.getArg(mapping, 'lastGeneratedColumn', null) + }; + } + } + + return { + line: null, + column: null, + lastColumn: null + }; + }; + +__webpack_unused_export__ = BasicSourceMapConsumer; + +/** + * An IndexedSourceMapConsumer instance represents a parsed source map which + * we can query for information. It differs from BasicSourceMapConsumer in + * that it takes "indexed" source maps (i.e. ones with a "sections" field) as + * input. + * + * The only parameter is a raw source map (either as a JSON string, or already + * parsed to an object). According to the spec for indexed source maps, they + * have the following attributes: + * + * - version: Which version of the source map spec this map is following. + * - file: Optional. The generated file this source map is associated with. + * - sections: A list of section definitions. + * + * Each value under the "sections" field has two fields: + * - offset: The offset into the original specified at which this section + * begins to apply, defined as an object with a "line" and "column" + * field. + * - map: A source map definition. This source map could also be indexed, + * but doesn't have to be. + * + * Instead of the "map" field, it's also possible to have a "url" field + * specifying a URL to retrieve a source map from, but that's currently + * unsupported. + * + * Here's an example source map, taken from the source map spec[0], but + * modified to omit a section which uses the "url" field. + * + * { + * version : 3, + * file: "app.js", + * sections: [{ + * offset: {line:100, column:10}, + * map: { + * version : 3, + * file: "section.js", + * sources: ["foo.js", "bar.js"], + * names: ["src", "maps", "are", "fun"], + * mappings: "AAAA,E;;ABCDE;" + * } + * }], + * } + * + * [0]: https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit#heading=h.535es3xeprgt + */ +function IndexedSourceMapConsumer(aSourceMap) { + var sourceMap = aSourceMap; + if (typeof aSourceMap === 'string') { + sourceMap = JSON.parse(aSourceMap.replace(/^\)\]\}'/, '')); + } + + var version = util.getArg(sourceMap, 'version'); + var sections = util.getArg(sourceMap, 'sections'); + + if (version != this._version) { + throw new Error('Unsupported version: ' + version); + } + + this._sources = new ArraySet(); + this._names = new ArraySet(); + + var lastOffset = { + line: -1, + column: 0 + }; + this._sections = sections.map(function (s) { + if (s.url) { + // The url field will require support for asynchronicity. + // See https://github.com/mozilla/source-map/issues/16 + throw new Error('Support for url field in sections not implemented.'); + } + var offset = util.getArg(s, 'offset'); + var offsetLine = util.getArg(offset, 'line'); + var offsetColumn = util.getArg(offset, 'column'); + + if (offsetLine < lastOffset.line || + (offsetLine === lastOffset.line && offsetColumn < lastOffset.column)) { + throw new Error('Section offsets must be ordered and non-overlapping.'); + } + lastOffset = offset; + + return { + generatedOffset: { + // The offset fields are 0-based, but we use 1-based indices when + // encoding/decoding from VLQ. + generatedLine: offsetLine + 1, + generatedColumn: offsetColumn + 1 + }, + consumer: new SourceMapConsumer(util.getArg(s, 'map')) + } + }); +} + +IndexedSourceMapConsumer.prototype = Object.create(SourceMapConsumer.prototype); +IndexedSourceMapConsumer.prototype.constructor = SourceMapConsumer; + +/** + * The version of the source mapping spec that we are consuming. + */ +IndexedSourceMapConsumer.prototype._version = 3; + +/** + * The list of original sources. + */ +Object.defineProperty(IndexedSourceMapConsumer.prototype, 'sources', { + get: function () { + var sources = []; + for (var i = 0; i < this._sections.length; i++) { + for (var j = 0; j < this._sections[i].consumer.sources.length; j++) { + sources.push(this._sections[i].consumer.sources[j]); + } + } + return sources; + } +}); + +/** + * Returns the original source, line, and column information for the generated + * source's line and column positions provided. The only argument is an object + * with the following properties: + * + * - line: The line number in the generated source. + * - column: The column number in the generated source. + * + * and an object is returned with the following properties: + * + * - source: The original source file, or null. + * - line: The line number in the original source, or null. + * - column: The column number in the original source, or null. + * - name: The original identifier, or null. + */ +IndexedSourceMapConsumer.prototype.originalPositionFor = + function IndexedSourceMapConsumer_originalPositionFor(aArgs) { + var needle = { + generatedLine: util.getArg(aArgs, 'line'), + generatedColumn: util.getArg(aArgs, 'column') + }; + + // Find the section containing the generated position we're trying to map + // to an original position. + var sectionIndex = binarySearch.search(needle, this._sections, + function(needle, section) { + var cmp = needle.generatedLine - section.generatedOffset.generatedLine; + if (cmp) { + return cmp; + } + + return (needle.generatedColumn - + section.generatedOffset.generatedColumn); + }); + var section = this._sections[sectionIndex]; + + if (!section) { + return { + source: null, + line: null, + column: null, + name: null + }; + } + + return section.consumer.originalPositionFor({ + line: needle.generatedLine - + (section.generatedOffset.generatedLine - 1), + column: needle.generatedColumn - + (section.generatedOffset.generatedLine === needle.generatedLine + ? section.generatedOffset.generatedColumn - 1 + : 0), + bias: aArgs.bias + }); + }; + +/** + * Return true if we have the source content for every source in the source + * map, false otherwise. + */ +IndexedSourceMapConsumer.prototype.hasContentsOfAllSources = + function IndexedSourceMapConsumer_hasContentsOfAllSources() { + return this._sections.every(function (s) { + return s.consumer.hasContentsOfAllSources(); + }); + }; + +/** + * Returns the original source content. The only argument is the url of the + * original source file. Returns null if no original source content is + * available. + */ +IndexedSourceMapConsumer.prototype.sourceContentFor = + function IndexedSourceMapConsumer_sourceContentFor(aSource, nullOnMissing) { + for (var i = 0; i < this._sections.length; i++) { + var section = this._sections[i]; + + var content = section.consumer.sourceContentFor(aSource, true); + if (content) { + return content; + } + } + if (nullOnMissing) { + return null; + } + else { + throw new Error('"' + aSource + '" is not in the SourceMap.'); + } + }; + +/** + * Returns the generated line and column information for the original source, + * line, and column positions provided. The only argument is an object with + * the following properties: + * + * - source: The filename of the original source. + * - line: The line number in the original source. + * - column: The column number in the original source. + * + * and an object is returned with the following properties: + * + * - line: The line number in the generated source, or null. + * - column: The column number in the generated source, or null. + */ +IndexedSourceMapConsumer.prototype.generatedPositionFor = + function IndexedSourceMapConsumer_generatedPositionFor(aArgs) { + for (var i = 0; i < this._sections.length; i++) { + var section = this._sections[i]; + + // Only consider this section if the requested source is in the list of + // sources of the consumer. + if (section.consumer.sources.indexOf(util.getArg(aArgs, 'source')) === -1) { + continue; + } + var generatedPosition = section.consumer.generatedPositionFor(aArgs); + if (generatedPosition) { + var ret = { + line: generatedPosition.line + + (section.generatedOffset.generatedLine - 1), + column: generatedPosition.column + + (section.generatedOffset.generatedLine === generatedPosition.line + ? section.generatedOffset.generatedColumn - 1 + : 0) + }; + return ret; + } + } + + return { + line: null, + column: null + }; + }; + +/** + * Parse the mappings in a string in to a data structure which we can easily + * query (the ordered arrays in the `this.__generatedMappings` and + * `this.__originalMappings` properties). + */ +IndexedSourceMapConsumer.prototype._parseMappings = + function IndexedSourceMapConsumer_parseMappings(aStr, aSourceRoot) { + this.__generatedMappings = []; + this.__originalMappings = []; + for (var i = 0; i < this._sections.length; i++) { + var section = this._sections[i]; + var sectionMappings = section.consumer._generatedMappings; + for (var j = 0; j < sectionMappings.length; j++) { + var mapping = sectionMappings[j]; + + var source = section.consumer._sources.at(mapping.source); + if (section.consumer.sourceRoot !== null) { + source = util.join(section.consumer.sourceRoot, source); + } + this._sources.add(source); + source = this._sources.indexOf(source); + + var name = section.consumer._names.at(mapping.name); + this._names.add(name); + name = this._names.indexOf(name); + + // The mappings coming from the consumer for the section have + // generated positions relative to the start of the section, so we + // need to offset them to be relative to the start of the concatenated + // generated file. + var adjustedMapping = { + source: source, + generatedLine: mapping.generatedLine + + (section.generatedOffset.generatedLine - 1), + generatedColumn: mapping.generatedColumn + + (section.generatedOffset.generatedLine === mapping.generatedLine + ? section.generatedOffset.generatedColumn - 1 + : 0), + originalLine: mapping.originalLine, + originalColumn: mapping.originalColumn, + name: name + }; + + this.__generatedMappings.push(adjustedMapping); + if (typeof adjustedMapping.originalLine === 'number') { + this.__originalMappings.push(adjustedMapping); + } + } + } + + quickSort(this.__generatedMappings, util.compareByGeneratedPositionsDeflated); + quickSort(this.__originalMappings, util.compareByOriginalPositions); + }; + +__webpack_unused_export__ = IndexedSourceMapConsumer; + + +/***/ }), + +/***/ 62574: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +/* -*- Mode: js; js-indent-level: 2; -*- */ +/* + * Copyright 2011 Mozilla Foundation and contributors + * Licensed under the New BSD license. See LICENSE or: + * http://opensource.org/licenses/BSD-3-Clause + */ + +var base64VLQ = __nccwpck_require__(27935); +var util = __nccwpck_require__(62492); +var ArraySet = (__nccwpck_require__(76934)/* .ArraySet */ .C); +var MappingList = (__nccwpck_require__(12861)/* .MappingList */ .P); + +/** + * An instance of the SourceMapGenerator represents a source map which is + * being built incrementally. You may pass an object with the following + * properties: + * + * - file: The filename of the generated source. + * - sourceRoot: A root for all relative URLs in this source map. + */ +function SourceMapGenerator(aArgs) { + if (!aArgs) { + aArgs = {}; + } + this._file = util.getArg(aArgs, 'file', null); + this._sourceRoot = util.getArg(aArgs, 'sourceRoot', null); + this._skipValidation = util.getArg(aArgs, 'skipValidation', false); + this._sources = new ArraySet(); + this._names = new ArraySet(); + this._mappings = new MappingList(); + this._sourcesContents = null; +} + +SourceMapGenerator.prototype._version = 3; + +/** + * Creates a new SourceMapGenerator based on a SourceMapConsumer + * + * @param aSourceMapConsumer The SourceMap. + */ +SourceMapGenerator.fromSourceMap = + function SourceMapGenerator_fromSourceMap(aSourceMapConsumer) { + var sourceRoot = aSourceMapConsumer.sourceRoot; + var generator = new SourceMapGenerator({ + file: aSourceMapConsumer.file, + sourceRoot: sourceRoot + }); + aSourceMapConsumer.eachMapping(function (mapping) { + var newMapping = { + generated: { + line: mapping.generatedLine, + column: mapping.generatedColumn + } + }; + + if (mapping.source != null) { + newMapping.source = mapping.source; + if (sourceRoot != null) { + newMapping.source = util.relative(sourceRoot, newMapping.source); + } + + newMapping.original = { + line: mapping.originalLine, + column: mapping.originalColumn + }; + + if (mapping.name != null) { + newMapping.name = mapping.name; + } + } + + generator.addMapping(newMapping); + }); + aSourceMapConsumer.sources.forEach(function (sourceFile) { + var content = aSourceMapConsumer.sourceContentFor(sourceFile); + if (content != null) { + generator.setSourceContent(sourceFile, content); + } + }); + return generator; + }; + +/** + * Add a single mapping from original source line and column to the generated + * source's line and column for this source map being created. The mapping + * object should have the following properties: + * + * - generated: An object with the generated line and column positions. + * - original: An object with the original line and column positions. + * - source: The original source file (relative to the sourceRoot). + * - name: An optional original token name for this mapping. + */ +SourceMapGenerator.prototype.addMapping = + function SourceMapGenerator_addMapping(aArgs) { + var generated = util.getArg(aArgs, 'generated'); + var original = util.getArg(aArgs, 'original', null); + var source = util.getArg(aArgs, 'source', null); + var name = util.getArg(aArgs, 'name', null); + + if (!this._skipValidation) { + this._validateMapping(generated, original, source, name); + } + + if (source != null) { + source = String(source); + if (!this._sources.has(source)) { + this._sources.add(source); + } + } + + if (name != null) { + name = String(name); + if (!this._names.has(name)) { + this._names.add(name); + } + } + + this._mappings.add({ + generatedLine: generated.line, + generatedColumn: generated.column, + originalLine: original != null && original.line, + originalColumn: original != null && original.column, + source: source, + name: name + }); + }; + +/** + * Set the source content for a source file. + */ +SourceMapGenerator.prototype.setSourceContent = + function SourceMapGenerator_setSourceContent(aSourceFile, aSourceContent) { + var source = aSourceFile; + if (this._sourceRoot != null) { + source = util.relative(this._sourceRoot, source); + } + + if (aSourceContent != null) { + // Add the source content to the _sourcesContents map. + // Create a new _sourcesContents map if the property is null. + if (!this._sourcesContents) { + this._sourcesContents = Object.create(null); + } + this._sourcesContents[util.toSetString(source)] = aSourceContent; + } else if (this._sourcesContents) { + // Remove the source file from the _sourcesContents map. + // If the _sourcesContents map is empty, set the property to null. + delete this._sourcesContents[util.toSetString(source)]; + if (Object.keys(this._sourcesContents).length === 0) { + this._sourcesContents = null; + } + } + }; + +/** + * Applies the mappings of a sub-source-map for a specific source file to the + * source map being generated. Each mapping to the supplied source file is + * rewritten using the supplied source map. Note: The resolution for the + * resulting mappings is the minimium of this map and the supplied map. + * + * @param aSourceMapConsumer The source map to be applied. + * @param aSourceFile Optional. The filename of the source file. + * If omitted, SourceMapConsumer's file property will be used. + * @param aSourceMapPath Optional. The dirname of the path to the source map + * to be applied. If relative, it is relative to the SourceMapConsumer. + * This parameter is needed when the two source maps aren't in the same + * directory, and the source map to be applied contains relative source + * paths. If so, those relative source paths need to be rewritten + * relative to the SourceMapGenerator. + */ +SourceMapGenerator.prototype.applySourceMap = + function SourceMapGenerator_applySourceMap(aSourceMapConsumer, aSourceFile, aSourceMapPath) { + var sourceFile = aSourceFile; + // If aSourceFile is omitted, we will use the file property of the SourceMap + if (aSourceFile == null) { + if (aSourceMapConsumer.file == null) { + throw new Error( + 'SourceMapGenerator.prototype.applySourceMap requires either an explicit source file, ' + + 'or the source map\'s "file" property. Both were omitted.' + ); + } + sourceFile = aSourceMapConsumer.file; + } + var sourceRoot = this._sourceRoot; + // Make "sourceFile" relative if an absolute Url is passed. + if (sourceRoot != null) { + sourceFile = util.relative(sourceRoot, sourceFile); + } + // Applying the SourceMap can add and remove items from the sources and + // the names array. + var newSources = new ArraySet(); + var newNames = new ArraySet(); + + // Find mappings for the "sourceFile" + this._mappings.unsortedForEach(function (mapping) { + if (mapping.source === sourceFile && mapping.originalLine != null) { + // Check if it can be mapped by the source map, then update the mapping. + var original = aSourceMapConsumer.originalPositionFor({ + line: mapping.originalLine, + column: mapping.originalColumn + }); + if (original.source != null) { + // Copy mapping + mapping.source = original.source; + if (aSourceMapPath != null) { + mapping.source = util.join(aSourceMapPath, mapping.source) + } + if (sourceRoot != null) { + mapping.source = util.relative(sourceRoot, mapping.source); + } + mapping.originalLine = original.line; + mapping.originalColumn = original.column; + if (original.name != null) { + mapping.name = original.name; + } + } + } + + var source = mapping.source; + if (source != null && !newSources.has(source)) { + newSources.add(source); + } + + var name = mapping.name; + if (name != null && !newNames.has(name)) { + newNames.add(name); + } + + }, this); + this._sources = newSources; + this._names = newNames; + + // Copy sourcesContents of applied map. + aSourceMapConsumer.sources.forEach(function (sourceFile) { + var content = aSourceMapConsumer.sourceContentFor(sourceFile); + if (content != null) { + if (aSourceMapPath != null) { + sourceFile = util.join(aSourceMapPath, sourceFile); + } + if (sourceRoot != null) { + sourceFile = util.relative(sourceRoot, sourceFile); + } + this.setSourceContent(sourceFile, content); + } + }, this); + }; + +/** + * A mapping can have one of the three levels of data: + * + * 1. Just the generated position. + * 2. The Generated position, original position, and original source. + * 3. Generated and original position, original source, as well as a name + * token. + * + * To maintain consistency, we validate that any new mapping being added falls + * in to one of these categories. + */ +SourceMapGenerator.prototype._validateMapping = + function SourceMapGenerator_validateMapping(aGenerated, aOriginal, aSource, + aName) { + // When aOriginal is truthy but has empty values for .line and .column, + // it is most likely a programmer error. In this case we throw a very + // specific error message to try to guide them the right way. + // For example: https://github.com/Polymer/polymer-bundler/pull/519 + if (aOriginal && typeof aOriginal.line !== 'number' && typeof aOriginal.column !== 'number') { + throw new Error( + 'original.line and original.column are not numbers -- you probably meant to omit ' + + 'the original mapping entirely and only map the generated position. If so, pass ' + + 'null for the original mapping instead of an object with empty or null values.' + ); + } + + if (aGenerated && 'line' in aGenerated && 'column' in aGenerated + && aGenerated.line > 0 && aGenerated.column >= 0 + && !aOriginal && !aSource && !aName) { + // Case 1. + return; + } + else if (aGenerated && 'line' in aGenerated && 'column' in aGenerated + && aOriginal && 'line' in aOriginal && 'column' in aOriginal + && aGenerated.line > 0 && aGenerated.column >= 0 + && aOriginal.line > 0 && aOriginal.column >= 0 + && aSource) { + // Cases 2 and 3. + return; + } + else { + throw new Error('Invalid mapping: ' + JSON.stringify({ + generated: aGenerated, + source: aSource, + original: aOriginal, + name: aName + })); + } + }; + +/** + * Serialize the accumulated mappings in to the stream of base 64 VLQs + * specified by the source map format. + */ +SourceMapGenerator.prototype._serializeMappings = + function SourceMapGenerator_serializeMappings() { + var previousGeneratedColumn = 0; + var previousGeneratedLine = 1; + var previousOriginalColumn = 0; + var previousOriginalLine = 0; + var previousName = 0; + var previousSource = 0; + var result = ''; + var next; + var mapping; + var nameIdx; + var sourceIdx; + + var mappings = this._mappings.toArray(); + for (var i = 0, len = mappings.length; i < len; i++) { + mapping = mappings[i]; + next = '' + + if (mapping.generatedLine !== previousGeneratedLine) { + previousGeneratedColumn = 0; + while (mapping.generatedLine !== previousGeneratedLine) { + next += ';'; + previousGeneratedLine++; + } + } + else { + if (i > 0) { + if (!util.compareByGeneratedPositionsInflated(mapping, mappings[i - 1])) { + continue; + } + next += ','; + } + } + + next += base64VLQ.encode(mapping.generatedColumn + - previousGeneratedColumn); + previousGeneratedColumn = mapping.generatedColumn; + + if (mapping.source != null) { + sourceIdx = this._sources.indexOf(mapping.source); + next += base64VLQ.encode(sourceIdx - previousSource); + previousSource = sourceIdx; + + // lines are stored 0-based in SourceMap spec version 3 + next += base64VLQ.encode(mapping.originalLine - 1 + - previousOriginalLine); + previousOriginalLine = mapping.originalLine - 1; + + next += base64VLQ.encode(mapping.originalColumn + - previousOriginalColumn); + previousOriginalColumn = mapping.originalColumn; + + if (mapping.name != null) { + nameIdx = this._names.indexOf(mapping.name); + next += base64VLQ.encode(nameIdx - previousName); + previousName = nameIdx; + } + } + + result += next; + } + + return result; + }; + +SourceMapGenerator.prototype._generateSourcesContent = + function SourceMapGenerator_generateSourcesContent(aSources, aSourceRoot) { + return aSources.map(function (source) { + if (!this._sourcesContents) { + return null; + } + if (aSourceRoot != null) { + source = util.relative(aSourceRoot, source); + } + var key = util.toSetString(source); + return Object.prototype.hasOwnProperty.call(this._sourcesContents, key) + ? this._sourcesContents[key] + : null; + }, this); + }; + +/** + * Externalize the source map. + */ +SourceMapGenerator.prototype.toJSON = + function SourceMapGenerator_toJSON() { + var map = { + version: this._version, + sources: this._sources.toArray(), + names: this._names.toArray(), + mappings: this._serializeMappings() + }; + if (this._file != null) { + map.file = this._file; + } + if (this._sourceRoot != null) { + map.sourceRoot = this._sourceRoot; + } + if (this._sourcesContents) { + map.sourcesContent = this._generateSourcesContent(map.sources, map.sourceRoot); + } + + return map; + }; + +/** + * Render the source map being generated to a string. + */ +SourceMapGenerator.prototype.toString = + function SourceMapGenerator_toString() { + return JSON.stringify(this.toJSON()); + }; + +exports.SourceMapGenerator = SourceMapGenerator; + + +/***/ }), + +/***/ 49706: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +/* -*- Mode: js; js-indent-level: 2; -*- */ +/* + * Copyright 2011 Mozilla Foundation and contributors + * Licensed under the New BSD license. See LICENSE or: + * http://opensource.org/licenses/BSD-3-Clause + */ + +var SourceMapGenerator = (__nccwpck_require__(62574).SourceMapGenerator); +var util = __nccwpck_require__(62492); + +// Matches a Windows-style `\r\n` newline or a `\n` newline used by all other +// operating systems these days (capturing the result). +var REGEX_NEWLINE = /(\r?\n)/; + +// Newline character code for charCodeAt() comparisons +var NEWLINE_CODE = 10; + +// Private symbol for identifying `SourceNode`s when multiple versions of +// the source-map library are loaded. This MUST NOT CHANGE across +// versions! +var isSourceNode = "$$$isSourceNode$$$"; + +/** + * SourceNodes provide a way to abstract over interpolating/concatenating + * snippets of generated JavaScript source code while maintaining the line and + * column information associated with the original source code. + * + * @param aLine The original line number. + * @param aColumn The original column number. + * @param aSource The original source's filename. + * @param aChunks Optional. An array of strings which are snippets of + * generated JS, or other SourceNodes. + * @param aName The original identifier. + */ +function SourceNode(aLine, aColumn, aSource, aChunks, aName) { + this.children = []; + this.sourceContents = {}; + this.line = aLine == null ? null : aLine; + this.column = aColumn == null ? null : aColumn; + this.source = aSource == null ? null : aSource; + this.name = aName == null ? null : aName; + this[isSourceNode] = true; + if (aChunks != null) this.add(aChunks); +} + +/** + * Creates a SourceNode from generated code and a SourceMapConsumer. + * + * @param aGeneratedCode The generated code + * @param aSourceMapConsumer The SourceMap for the generated code + * @param aRelativePath Optional. The path that relative sources in the + * SourceMapConsumer should be relative to. + */ +SourceNode.fromStringWithSourceMap = + function SourceNode_fromStringWithSourceMap(aGeneratedCode, aSourceMapConsumer, aRelativePath) { + // The SourceNode we want to fill with the generated code + // and the SourceMap + var node = new SourceNode(); + + // All even indices of this array are one line of the generated code, + // while all odd indices are the newlines between two adjacent lines + // (since `REGEX_NEWLINE` captures its match). + // Processed fragments are accessed by calling `shiftNextLine`. + var remainingLines = aGeneratedCode.split(REGEX_NEWLINE); + var remainingLinesIndex = 0; + var shiftNextLine = function() { + var lineContents = getNextLine(); + // The last line of a file might not have a newline. + var newLine = getNextLine() || ""; + return lineContents + newLine; + + function getNextLine() { + return remainingLinesIndex < remainingLines.length ? + remainingLines[remainingLinesIndex++] : undefined; + } + }; + + // We need to remember the position of "remainingLines" + var lastGeneratedLine = 1, lastGeneratedColumn = 0; + + // The generate SourceNodes we need a code range. + // To extract it current and last mapping is used. + // Here we store the last mapping. + var lastMapping = null; + + aSourceMapConsumer.eachMapping(function (mapping) { + if (lastMapping !== null) { + // We add the code from "lastMapping" to "mapping": + // First check if there is a new line in between. + if (lastGeneratedLine < mapping.generatedLine) { + // Associate first line with "lastMapping" + addMappingWithCode(lastMapping, shiftNextLine()); + lastGeneratedLine++; + lastGeneratedColumn = 0; + // The remaining code is added without mapping + } else { + // There is no new line in between. + // Associate the code between "lastGeneratedColumn" and + // "mapping.generatedColumn" with "lastMapping" + var nextLine = remainingLines[remainingLinesIndex]; + var code = nextLine.substr(0, mapping.generatedColumn - + lastGeneratedColumn); + remainingLines[remainingLinesIndex] = nextLine.substr(mapping.generatedColumn - + lastGeneratedColumn); + lastGeneratedColumn = mapping.generatedColumn; + addMappingWithCode(lastMapping, code); + // No more remaining code, continue + lastMapping = mapping; + return; + } + } + // We add the generated code until the first mapping + // to the SourceNode without any mapping. + // Each line is added as separate string. + while (lastGeneratedLine < mapping.generatedLine) { + node.add(shiftNextLine()); + lastGeneratedLine++; + } + if (lastGeneratedColumn < mapping.generatedColumn) { + var nextLine = remainingLines[remainingLinesIndex]; + node.add(nextLine.substr(0, mapping.generatedColumn)); + remainingLines[remainingLinesIndex] = nextLine.substr(mapping.generatedColumn); + lastGeneratedColumn = mapping.generatedColumn; + } + lastMapping = mapping; + }, this); + // We have processed all mappings. + if (remainingLinesIndex < remainingLines.length) { + if (lastMapping) { + // Associate the remaining code in the current line with "lastMapping" + addMappingWithCode(lastMapping, shiftNextLine()); + } + // and add the remaining lines without any mapping + node.add(remainingLines.splice(remainingLinesIndex).join("")); + } + + // Copy sourcesContent into SourceNode + aSourceMapConsumer.sources.forEach(function (sourceFile) { + var content = aSourceMapConsumer.sourceContentFor(sourceFile); + if (content != null) { + if (aRelativePath != null) { + sourceFile = util.join(aRelativePath, sourceFile); + } + node.setSourceContent(sourceFile, content); + } + }); + + return node; + + function addMappingWithCode(mapping, code) { + if (mapping === null || mapping.source === undefined) { + node.add(code); + } else { + var source = aRelativePath + ? util.join(aRelativePath, mapping.source) + : mapping.source; + node.add(new SourceNode(mapping.originalLine, + mapping.originalColumn, + source, + code, + mapping.name)); + } + } + }; + +/** + * Add a chunk of generated JS to this source node. + * + * @param aChunk A string snippet of generated JS code, another instance of + * SourceNode, or an array where each member is one of those things. + */ +SourceNode.prototype.add = function SourceNode_add(aChunk) { + if (Array.isArray(aChunk)) { + aChunk.forEach(function (chunk) { + this.add(chunk); + }, this); + } + else if (aChunk[isSourceNode] || typeof aChunk === "string") { + if (aChunk) { + this.children.push(aChunk); + } + } + else { + throw new TypeError( + "Expected a SourceNode, string, or an array of SourceNodes and strings. Got " + aChunk + ); + } + return this; +}; + +/** + * Add a chunk of generated JS to the beginning of this source node. + * + * @param aChunk A string snippet of generated JS code, another instance of + * SourceNode, or an array where each member is one of those things. + */ +SourceNode.prototype.prepend = function SourceNode_prepend(aChunk) { + if (Array.isArray(aChunk)) { + for (var i = aChunk.length-1; i >= 0; i--) { + this.prepend(aChunk[i]); + } + } + else if (aChunk[isSourceNode] || typeof aChunk === "string") { + this.children.unshift(aChunk); + } + else { + throw new TypeError( + "Expected a SourceNode, string, or an array of SourceNodes and strings. Got " + aChunk + ); + } + return this; +}; + +/** + * Walk over the tree of JS snippets in this node and its children. The + * walking function is called once for each snippet of JS and is passed that + * snippet and the its original associated source's line/column location. + * + * @param aFn The traversal function. + */ +SourceNode.prototype.walk = function SourceNode_walk(aFn) { + var chunk; + for (var i = 0, len = this.children.length; i < len; i++) { + chunk = this.children[i]; + if (chunk[isSourceNode]) { + chunk.walk(aFn); + } + else { + if (chunk !== '') { + aFn(chunk, { source: this.source, + line: this.line, + column: this.column, + name: this.name }); + } + } + } +}; + +/** + * Like `String.prototype.join` except for SourceNodes. Inserts `aStr` between + * each of `this.children`. + * + * @param aSep The separator. + */ +SourceNode.prototype.join = function SourceNode_join(aSep) { + var newChildren; + var i; + var len = this.children.length; + if (len > 0) { + newChildren = []; + for (i = 0; i < len-1; i++) { + newChildren.push(this.children[i]); + newChildren.push(aSep); + } + newChildren.push(this.children[i]); + this.children = newChildren; + } + return this; +}; + +/** + * Call String.prototype.replace on the very right-most source snippet. Useful + * for trimming whitespace from the end of a source node, etc. + * + * @param aPattern The pattern to replace. + * @param aReplacement The thing to replace the pattern with. + */ +SourceNode.prototype.replaceRight = function SourceNode_replaceRight(aPattern, aReplacement) { + var lastChild = this.children[this.children.length - 1]; + if (lastChild[isSourceNode]) { + lastChild.replaceRight(aPattern, aReplacement); + } + else if (typeof lastChild === 'string') { + this.children[this.children.length - 1] = lastChild.replace(aPattern, aReplacement); + } + else { + this.children.push(''.replace(aPattern, aReplacement)); + } + return this; +}; + +/** + * Set the source content for a source file. This will be added to the SourceMapGenerator + * in the sourcesContent field. + * + * @param aSourceFile The filename of the source file + * @param aSourceContent The content of the source file + */ +SourceNode.prototype.setSourceContent = + function SourceNode_setSourceContent(aSourceFile, aSourceContent) { + this.sourceContents[util.toSetString(aSourceFile)] = aSourceContent; + }; + +/** + * Walk over the tree of SourceNodes. The walking function is called for each + * source file content and is passed the filename and source content. + * + * @param aFn The traversal function. + */ +SourceNode.prototype.walkSourceContents = + function SourceNode_walkSourceContents(aFn) { + for (var i = 0, len = this.children.length; i < len; i++) { + if (this.children[i][isSourceNode]) { + this.children[i].walkSourceContents(aFn); + } + } + + var sources = Object.keys(this.sourceContents); + for (var i = 0, len = sources.length; i < len; i++) { + aFn(util.fromSetString(sources[i]), this.sourceContents[sources[i]]); + } + }; + +/** + * Return the string representation of this source node. Walks over the tree + * and concatenates all the various snippets together to one string. + */ +SourceNode.prototype.toString = function SourceNode_toString() { + var str = ""; + this.walk(function (chunk) { + str += chunk; + }); + return str; +}; + +/** + * Returns the string representation of this source node along with a source + * map. + */ +SourceNode.prototype.toStringWithSourceMap = function SourceNode_toStringWithSourceMap(aArgs) { + var generated = { + code: "", + line: 1, + column: 0 + }; + var map = new SourceMapGenerator(aArgs); + var sourceMappingActive = false; + var lastOriginalSource = null; + var lastOriginalLine = null; + var lastOriginalColumn = null; + var lastOriginalName = null; + this.walk(function (chunk, original) { + generated.code += chunk; + if (original.source !== null + && original.line !== null + && original.column !== null) { + if(lastOriginalSource !== original.source + || lastOriginalLine !== original.line + || lastOriginalColumn !== original.column + || lastOriginalName !== original.name) { + map.addMapping({ + source: original.source, + original: { + line: original.line, + column: original.column + }, + generated: { + line: generated.line, + column: generated.column + }, + name: original.name + }); + } + lastOriginalSource = original.source; + lastOriginalLine = original.line; + lastOriginalColumn = original.column; + lastOriginalName = original.name; + sourceMappingActive = true; + } else if (sourceMappingActive) { + map.addMapping({ + generated: { + line: generated.line, + column: generated.column + } + }); + lastOriginalSource = null; + sourceMappingActive = false; + } + for (var idx = 0, length = chunk.length; idx < length; idx++) { + if (chunk.charCodeAt(idx) === NEWLINE_CODE) { + generated.line++; + generated.column = 0; + // Mappings end at eol + if (idx + 1 === length) { + lastOriginalSource = null; + sourceMappingActive = false; + } else if (sourceMappingActive) { + map.addMapping({ + source: original.source, + original: { + line: original.line, + column: original.column + }, + generated: { + line: generated.line, + column: generated.column + }, + name: original.name + }); + } + } else { + generated.column++; + } + } + }); + this.walkSourceContents(function (sourceFile, sourceContent) { + map.setSourceContent(sourceFile, sourceContent); + }); + + return { code: generated.code, map: map }; +}; + +exports.SourceNode = SourceNode; + + +/***/ }), + +/***/ 62492: +/***/ ((__unused_webpack_module, exports) => { + +/* -*- Mode: js; js-indent-level: 2; -*- */ +/* + * Copyright 2011 Mozilla Foundation and contributors + * Licensed under the New BSD license. See LICENSE or: + * http://opensource.org/licenses/BSD-3-Clause + */ + +/** + * This is a helper function for getting values from parameter/options + * objects. + * + * @param args The object we are extracting values from + * @param name The name of the property we are getting. + * @param defaultValue An optional value to return if the property is missing + * from the object. If this is not specified and the property is missing, an + * error will be thrown. + */ +function getArg(aArgs, aName, aDefaultValue) { + if (aName in aArgs) { + return aArgs[aName]; + } else if (arguments.length === 3) { + return aDefaultValue; + } else { + throw new Error('"' + aName + '" is a required argument.'); + } +} +exports.getArg = getArg; + +var urlRegexp = /^(?:([\w+\-.]+):)?\/\/(?:(\w+:\w+)@)?([\w.]*)(?::(\d+))?(\S*)$/; +var dataUrlRegexp = /^data:.+\,.+$/; + +function urlParse(aUrl) { + var match = aUrl.match(urlRegexp); + if (!match) { + return null; + } + return { + scheme: match[1], + auth: match[2], + host: match[3], + port: match[4], + path: match[5] + }; +} +exports.urlParse = urlParse; + +function urlGenerate(aParsedUrl) { + var url = ''; + if (aParsedUrl.scheme) { + url += aParsedUrl.scheme + ':'; + } + url += '//'; + if (aParsedUrl.auth) { + url += aParsedUrl.auth + '@'; + } + if (aParsedUrl.host) { + url += aParsedUrl.host; + } + if (aParsedUrl.port) { + url += ":" + aParsedUrl.port + } + if (aParsedUrl.path) { + url += aParsedUrl.path; + } + return url; +} +exports.urlGenerate = urlGenerate; + +/** + * Normalizes a path, or the path portion of a URL: + * + * - Replaces consecutive slashes with one slash. + * - Removes unnecessary '.' parts. + * - Removes unnecessary '/..' parts. + * + * Based on code in the Node.js 'path' core module. + * + * @param aPath The path or url to normalize. + */ +function normalize(aPath) { + var path = aPath; + var url = urlParse(aPath); + if (url) { + if (!url.path) { + return aPath; + } + path = url.path; + } + var isAbsolute = exports.isAbsolute(path); + + var parts = path.split(/\/+/); + for (var part, up = 0, i = parts.length - 1; i >= 0; i--) { + part = parts[i]; + if (part === '.') { + parts.splice(i, 1); + } else if (part === '..') { + up++; + } else if (up > 0) { + if (part === '') { + // The first part is blank if the path is absolute. Trying to go + // above the root is a no-op. Therefore we can remove all '..' parts + // directly after the root. + parts.splice(i + 1, up); + up = 0; + } else { + parts.splice(i, 2); + up--; + } + } + } + path = parts.join('/'); + + if (path === '') { + path = isAbsolute ? '/' : '.'; + } + + if (url) { + url.path = path; + return urlGenerate(url); + } + return path; +} +exports.normalize = normalize; + +/** + * Joins two paths/URLs. + * + * @param aRoot The root path or URL. + * @param aPath The path or URL to be joined with the root. + * + * - If aPath is a URL or a data URI, aPath is returned, unless aPath is a + * scheme-relative URL: Then the scheme of aRoot, if any, is prepended + * first. + * - Otherwise aPath is a path. If aRoot is a URL, then its path portion + * is updated with the result and aRoot is returned. Otherwise the result + * is returned. + * - If aPath is absolute, the result is aPath. + * - Otherwise the two paths are joined with a slash. + * - Joining for example 'http://' and 'www.example.com' is also supported. + */ +function join(aRoot, aPath) { + if (aRoot === "") { + aRoot = "."; + } + if (aPath === "") { + aPath = "."; + } + var aPathUrl = urlParse(aPath); + var aRootUrl = urlParse(aRoot); + if (aRootUrl) { + aRoot = aRootUrl.path || '/'; + } + + // `join(foo, '//www.example.org')` + if (aPathUrl && !aPathUrl.scheme) { + if (aRootUrl) { + aPathUrl.scheme = aRootUrl.scheme; + } + return urlGenerate(aPathUrl); + } + + if (aPathUrl || aPath.match(dataUrlRegexp)) { + return aPath; + } + + // `join('http://', 'www.example.com')` + if (aRootUrl && !aRootUrl.host && !aRootUrl.path) { + aRootUrl.host = aPath; + return urlGenerate(aRootUrl); + } + + var joined = aPath.charAt(0) === '/' + ? aPath + : normalize(aRoot.replace(/\/+$/, '') + '/' + aPath); + + if (aRootUrl) { + aRootUrl.path = joined; + return urlGenerate(aRootUrl); + } + return joined; +} +exports.join = join; + +exports.isAbsolute = function (aPath) { + return aPath.charAt(0) === '/' || !!aPath.match(urlRegexp); +}; + +/** + * Make a path relative to a URL or another path. + * + * @param aRoot The root path or URL. + * @param aPath The path or URL to be made relative to aRoot. + */ +function relative(aRoot, aPath) { + if (aRoot === "") { + aRoot = "."; + } + + aRoot = aRoot.replace(/\/$/, ''); + + // It is possible for the path to be above the root. In this case, simply + // checking whether the root is a prefix of the path won't work. Instead, we + // need to remove components from the root one by one, until either we find + // a prefix that fits, or we run out of components to remove. + var level = 0; + while (aPath.indexOf(aRoot + '/') !== 0) { + var index = aRoot.lastIndexOf("/"); + if (index < 0) { + return aPath; + } + + // If the only part of the root that is left is the scheme (i.e. http://, + // file:///, etc.), one or more slashes (/), or simply nothing at all, we + // have exhausted all components, so the path is not relative to the root. + aRoot = aRoot.slice(0, index); + if (aRoot.match(/^([^\/]+:\/)?\/*$/)) { + return aPath; + } + + ++level; + } + + // Make sure we add a "../" for each component we removed from the root. + return Array(level + 1).join("../") + aPath.substr(aRoot.length + 1); +} +exports.relative = relative; + +var supportsNullProto = (function () { + var obj = Object.create(null); + return !('__proto__' in obj); +}()); + +function identity (s) { + return s; +} + +/** + * Because behavior goes wacky when you set `__proto__` on objects, we + * have to prefix all the strings in our set with an arbitrary character. + * + * See https://github.com/mozilla/source-map/pull/31 and + * https://github.com/mozilla/source-map/issues/30 + * + * @param String aStr + */ +function toSetString(aStr) { + if (isProtoString(aStr)) { + return '$' + aStr; + } + + return aStr; +} +exports.toSetString = supportsNullProto ? identity : toSetString; + +function fromSetString(aStr) { + if (isProtoString(aStr)) { + return aStr.slice(1); + } + + return aStr; +} +exports.fromSetString = supportsNullProto ? identity : fromSetString; + +function isProtoString(s) { + if (!s) { + return false; + } + + var length = s.length; + + if (length < 9 /* "__proto__".length */) { + return false; + } + + if (s.charCodeAt(length - 1) !== 95 /* '_' */ || + s.charCodeAt(length - 2) !== 95 /* '_' */ || + s.charCodeAt(length - 3) !== 111 /* 'o' */ || + s.charCodeAt(length - 4) !== 116 /* 't' */ || + s.charCodeAt(length - 5) !== 111 /* 'o' */ || + s.charCodeAt(length - 6) !== 114 /* 'r' */ || + s.charCodeAt(length - 7) !== 112 /* 'p' */ || + s.charCodeAt(length - 8) !== 95 /* '_' */ || + s.charCodeAt(length - 9) !== 95 /* '_' */) { + return false; + } + + for (var i = length - 10; i >= 0; i--) { + if (s.charCodeAt(i) !== 36 /* '$' */) { + return false; + } + } + + return true; +} + +/** + * Comparator between two mappings where the original positions are compared. + * + * Optionally pass in `true` as `onlyCompareGenerated` to consider two + * mappings with the same original source/line/column, but different generated + * line and column the same. Useful when searching for a mapping with a + * stubbed out mapping. + */ +function compareByOriginalPositions(mappingA, mappingB, onlyCompareOriginal) { + var cmp = mappingA.source - mappingB.source; + if (cmp !== 0) { + return cmp; + } + + cmp = mappingA.originalLine - mappingB.originalLine; + if (cmp !== 0) { + return cmp; + } + + cmp = mappingA.originalColumn - mappingB.originalColumn; + if (cmp !== 0 || onlyCompareOriginal) { + return cmp; + } + + cmp = mappingA.generatedColumn - mappingB.generatedColumn; + if (cmp !== 0) { + return cmp; + } + + cmp = mappingA.generatedLine - mappingB.generatedLine; + if (cmp !== 0) { + return cmp; + } + + return mappingA.name - mappingB.name; +} +exports.compareByOriginalPositions = compareByOriginalPositions; + +/** + * Comparator between two mappings with deflated source and name indices where + * the generated positions are compared. + * + * Optionally pass in `true` as `onlyCompareGenerated` to consider two + * mappings with the same generated line and column, but different + * source/name/original line and column the same. Useful when searching for a + * mapping with a stubbed out mapping. + */ +function compareByGeneratedPositionsDeflated(mappingA, mappingB, onlyCompareGenerated) { + var cmp = mappingA.generatedLine - mappingB.generatedLine; + if (cmp !== 0) { + return cmp; + } + + cmp = mappingA.generatedColumn - mappingB.generatedColumn; + if (cmp !== 0 || onlyCompareGenerated) { + return cmp; + } + + cmp = mappingA.source - mappingB.source; + if (cmp !== 0) { + return cmp; + } + + cmp = mappingA.originalLine - mappingB.originalLine; + if (cmp !== 0) { + return cmp; + } + + cmp = mappingA.originalColumn - mappingB.originalColumn; + if (cmp !== 0) { + return cmp; + } + + return mappingA.name - mappingB.name; +} +exports.compareByGeneratedPositionsDeflated = compareByGeneratedPositionsDeflated; + +function strcmp(aStr1, aStr2) { + if (aStr1 === aStr2) { + return 0; + } + + if (aStr1 > aStr2) { + return 1; + } + + return -1; +} + +/** + * Comparator between two mappings with inflated source and name strings where + * the generated positions are compared. + */ +function compareByGeneratedPositionsInflated(mappingA, mappingB) { + var cmp = mappingA.generatedLine - mappingB.generatedLine; + if (cmp !== 0) { + return cmp; + } + + cmp = mappingA.generatedColumn - mappingB.generatedColumn; + if (cmp !== 0) { + return cmp; + } + + cmp = strcmp(mappingA.source, mappingB.source); + if (cmp !== 0) { + return cmp; + } + + cmp = mappingA.originalLine - mappingB.originalLine; + if (cmp !== 0) { + return cmp; + } + + cmp = mappingA.originalColumn - mappingB.originalColumn; + if (cmp !== 0) { + return cmp; + } + + return strcmp(mappingA.name, mappingB.name); +} +exports.compareByGeneratedPositionsInflated = compareByGeneratedPositionsInflated; + + +/***/ }), + +/***/ 62618: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +/* + * Copyright 2009-2011 Mozilla Foundation and contributors + * Licensed under the New BSD license. See LICENSE.txt or: + * http://opensource.org/licenses/BSD-3-Clause + */ +exports.SourceMapGenerator = __nccwpck_require__(62574).SourceMapGenerator; +exports.SourceMapConsumer = __nccwpck_require__(79907).SourceMapConsumer; +exports.SourceNode = __nccwpck_require__(49706).SourceNode; + + +/***/ }), + +/***/ 33600: +/***/ ((module) => { + +"use strict"; + + +module.exports = factory + +// Construct a state `toggler`: a function which inverses `property` in context +// based on its current value. +// The by `toggler` returned function restores that value. +function factory(key, state, ctx) { + return enter + + function enter() { + var context = ctx || this + var current = context[key] + + context[key] = !state + + return exit + + function exit() { + context[key] = current + } + } +} + + +/***/ }), + +/***/ 55655: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +module.exports = __nccwpck_require__(22167) + + +/***/ }), + +/***/ 41050: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +var entities = __nccwpck_require__(33757) + +var characters = {} +var name + +module.exports = characters + +for (name in entities) { + characters[entities[name]] = name +} + + +/***/ }), + +/***/ 41165: +/***/ ((module) => { + +module.exports = String.fromCharCode + + +/***/ }), + +/***/ 12935: +/***/ ((module) => { + +module.exports = {}.hasOwnProperty + + +/***/ }), + +/***/ 70896: +/***/ ((module) => { + +"use strict"; + + +module.exports = encode + +// Encode special characters in `value`. +function encode(value, options) { + value = value.replace( + options.subset ? charactersToExpression(options.subset) : /["&'<>`]/g, + basic + ) + + if (options.subset || options.escapeOnly) { + return value + } + + return ( + value + // Surrogate pairs. + .replace(/[\uD800-\uDBFF][\uDC00-\uDFFF]/g, surrogate) + // BMP control characters (C0 except for LF, CR, SP; DEL; and some more + // non-ASCII ones). + .replace( + // eslint-disable-next-line no-control-regex, unicorn/no-hex-escape + /[\x01-\t\v\f\x0E-\x1F\x7F\x81\x8D\x8F\x90\x9D\xA0-\uFFFF]/g, + basic + ) + ) + + function surrogate(pair, index, all) { + return options.format( + (pair.charCodeAt(0) - 0xd800) * 0x400 + + pair.charCodeAt(1) - + 0xdc00 + + 0x10000, + all.charCodeAt(index + 2), + options + ) + } + + function basic(character, index, all) { + return options.format( + character.charCodeAt(0), + all.charCodeAt(index + 1), + options + ) + } +} + +function charactersToExpression(subset) { + var groups = [] + var index = -1 + + while (++index < subset.length) { + groups.push(subset[index].replace(/[|\\{}()[\]^$+*?.]/g, '\\$&')) + } + + return new RegExp('(?:' + groups.join('|') + ')', 'g') +} + + +/***/ }), + +/***/ 7057: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +var xtend = __nccwpck_require__(80869) +var core = __nccwpck_require__(70896) +var smart = __nccwpck_require__(58549) + +module.exports = encode + +// Encode special characters in `value`. +function encode(value, options) { + // Note: Switch to `Object.assign` next major. + return core(value, xtend(options, {format: smart})) +} + + +/***/ }), + +/***/ 94316: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +var core = __nccwpck_require__(70896) +var smart = __nccwpck_require__(58549) + +module.exports = escape + +// Shortcut to escape special characters in HTML. +function escape(value) { + return core(value, { + escapeOnly: true, + useNamedReferences: true, + format: smart + }) +} + + +/***/ }), + +/***/ 22167: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +var encode = __nccwpck_require__(7057) +var escape = __nccwpck_require__(94316) + +module.exports = encode +encode.escape = escape + + +/***/ }), + +/***/ 58549: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +module.exports = formatPretty + +var toHexadecimal = __nccwpck_require__(63849) +var toDecimal = __nccwpck_require__(98653) +var toNamed = __nccwpck_require__(89339) + +// Encode `character` according to `options`. +function formatPretty(code, next, options) { + var named + var numeric + var decimal + + if (options.useNamedReferences || options.useShortestReferences) { + named = toNamed( + code, + next, + options.omitOptionalSemicolons, + options.attribute + ) + } + + if (options.useShortestReferences || !named) { + numeric = toHexadecimal(code, next, options.omitOptionalSemicolons) + + // Use the shortest numeric reference when requested. + // A simple algorithm would use decimal for all code points under 100, as + // those are shorter than hexadecimal: + // + // * `c` vs `c` (decimal shorter) + // * `d` vs `d` (equal) + // + // However, because we take `next` into consideration when `omit` is used, + // And it would be possible that decimals are shorter on bigger values as + // well if `next` is hexadecimal but not decimal, we instead compare both. + if (options.useShortestReferences) { + decimal = toDecimal(code, next, options.omitOptionalSemicolons) + + if (decimal.length < numeric.length) { + numeric = decimal + } + } + } + + return named && + (!options.useShortestReferences || named.length < numeric.length) + ? named + : numeric +} + + +/***/ }), + +/***/ 98653: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +module.exports = toDecimalReference + +var fromCharCode = __nccwpck_require__(41165) + +// Transform `code` into a decimal character reference. +function toDecimalReference(code, next, omit) { + var value = '&#' + String(code) + return omit && next && !/\d/.test(fromCharCode(next)) ? value : value + ';' +} + + +/***/ }), + +/***/ 63849: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +module.exports = toHexReference + +var fromCharCode = __nccwpck_require__(41165) + +// Transform `code` into a hexadecimal character reference. +function toHexReference(code, next, omit) { + var value = '&#x' + code.toString(16).toUpperCase() + return omit && next && !/[\dA-Fa-f]/.test(fromCharCode(next)) + ? value + : value + ';' +} + + +/***/ }), + +/***/ 89339: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +module.exports = toNamed + +var legacy = __nccwpck_require__(82719) +var characters = __nccwpck_require__(41050) +var fromCharCode = __nccwpck_require__(41165) +var own = __nccwpck_require__(12935) +var dangerous = __nccwpck_require__(50408) + +// Transform `code` into a named character reference. +function toNamed(code, next, omit, attribute) { + var character = fromCharCode(code) + var name + var value + + if (own.call(characters, character)) { + name = characters[character] + value = '&' + name + + if ( + omit && + own.call(legacy, name) && + dangerous.indexOf(name) === -1 && + (!attribute || + (next && next !== 61 /* `=` */ && /[^\da-z]/i.test(fromCharCode(next)))) + ) { + return value + } + + return value + ';' + } + + return '' +} + + +/***/ }), + +/***/ 91389: +/***/ ((module) => { + +"use strict"; +/*! + * strip-bom-string + * + * Copyright (c) 2015, 2017, Jon Schlinkert. + * Released under the MIT License. + */ + + + +module.exports = function(str) { + if (typeof str === 'string' && str.charAt(0) === '\ufeff') { + return str.slice(1); + } + return str; +}; + + +/***/ }), + +/***/ 21450: +/***/ ((module) => { + +"use strict"; + +var argv = process.argv; + +var terminator = argv.indexOf('--'); +var hasFlag = function (flag) { + flag = '--' + flag; + var pos = argv.indexOf(flag); + return pos !== -1 && (terminator !== -1 ? pos < terminator : true); +}; + +module.exports = (function () { + if ('FORCE_COLOR' in process.env) { + return true; + } + + if (hasFlag('no-color') || + hasFlag('no-colors') || + hasFlag('color=false')) { + return false; + } + + if (hasFlag('color') || + hasFlag('colors') || + hasFlag('color=true') || + hasFlag('color=always')) { + return true; + } + + if (process.stdout && !process.stdout.isTTY) { + return false; + } + + if (process.platform === 'win32') { + return true; + } + + if ('COLORTERM' in process.env) { + return true; + } + + if (process.env.TERM === 'dumb') { + return false; + } + + if (/^screen|^xterm|^vt100|color|ansi|cygwin|linux/i.test(process.env.TERM)) { + return true; + } + + return false; +})(); + + +/***/ }), + +/***/ 75937: +/***/ ((module) => { + +"use strict"; + + +module.exports = trimTrailingLines + +// Remove final newline characters from `value`. +function trimTrailingLines(value) { + return String(value).replace(/\n+$/, '') +} + + +/***/ }), + +/***/ 15464: +/***/ ((module, exports) => { + + +exports = module.exports = trim; + +function trim(str){ + return str.replace(/^\s*|\s*$/g, ''); +} + +exports.left = function(str){ + return str.replace(/^\s*/, ''); +}; + +exports.right = function(str){ + return str.replace(/\s*$/, ''); +}; + + +/***/ }), + +/***/ 20770: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +module.exports = __nccwpck_require__(20218); + + +/***/ }), + +/***/ 20218: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +var net = __nccwpck_require__(69278); +var tls = __nccwpck_require__(64756); +var http = __nccwpck_require__(58611); +var https = __nccwpck_require__(65692); +var events = __nccwpck_require__(24434); +var assert = __nccwpck_require__(42613); +var util = __nccwpck_require__(39023); + + +exports.httpOverHttp = httpOverHttp; +exports.httpsOverHttp = httpsOverHttp; +exports.httpOverHttps = httpOverHttps; +exports.httpsOverHttps = httpsOverHttps; + + +function httpOverHttp(options) { + var agent = new TunnelingAgent(options); + agent.request = http.request; + return agent; +} + +function httpsOverHttp(options) { + var agent = new TunnelingAgent(options); + agent.request = http.request; + agent.createSocket = createSecureSocket; + agent.defaultPort = 443; + return agent; +} + +function httpOverHttps(options) { + var agent = new TunnelingAgent(options); + agent.request = https.request; + return agent; +} + +function httpsOverHttps(options) { + var agent = new TunnelingAgent(options); + agent.request = https.request; + agent.createSocket = createSecureSocket; + agent.defaultPort = 443; + return agent; +} + + +function TunnelingAgent(options) { + var self = this; + self.options = options || {}; + self.proxyOptions = self.options.proxy || {}; + self.maxSockets = self.options.maxSockets || http.Agent.defaultMaxSockets; + self.requests = []; + self.sockets = []; + + self.on('free', function onFree(socket, host, port, localAddress) { + var options = toOptions(host, port, localAddress); + for (var i = 0, len = self.requests.length; i < len; ++i) { + var pending = self.requests[i]; + if (pending.host === options.host && pending.port === options.port) { + // Detect the request to connect same origin server, + // reuse the connection. + self.requests.splice(i, 1); + pending.request.onSocket(socket); + return; + } + } + socket.destroy(); + self.removeSocket(socket); + }); +} +util.inherits(TunnelingAgent, events.EventEmitter); + +TunnelingAgent.prototype.addRequest = function addRequest(req, host, port, localAddress) { + var self = this; + var options = mergeOptions({request: req}, self.options, toOptions(host, port, localAddress)); + + if (self.sockets.length >= this.maxSockets) { + // We are over limit so we'll add it to the queue. + self.requests.push(options); + return; + } + + // If we are under maxSockets create a new one. + self.createSocket(options, function(socket) { + socket.on('free', onFree); + socket.on('close', onCloseOrRemove); + socket.on('agentRemove', onCloseOrRemove); + req.onSocket(socket); + + function onFree() { + self.emit('free', socket, options); + } + + function onCloseOrRemove(err) { + self.removeSocket(socket); + socket.removeListener('free', onFree); + socket.removeListener('close', onCloseOrRemove); + socket.removeListener('agentRemove', onCloseOrRemove); + } + }); +}; + +TunnelingAgent.prototype.createSocket = function createSocket(options, cb) { + var self = this; + var placeholder = {}; + self.sockets.push(placeholder); + + var connectOptions = mergeOptions({}, self.proxyOptions, { + method: 'CONNECT', + path: options.host + ':' + options.port, + agent: false, + headers: { + host: options.host + ':' + options.port + } + }); + if (options.localAddress) { + connectOptions.localAddress = options.localAddress; + } + if (connectOptions.proxyAuth) { + connectOptions.headers = connectOptions.headers || {}; + connectOptions.headers['Proxy-Authorization'] = 'Basic ' + + new Buffer(connectOptions.proxyAuth).toString('base64'); + } + + debug('making CONNECT request'); + var connectReq = self.request(connectOptions); + connectReq.useChunkedEncodingByDefault = false; // for v0.6 + connectReq.once('response', onResponse); // for v0.6 + connectReq.once('upgrade', onUpgrade); // for v0.6 + connectReq.once('connect', onConnect); // for v0.7 or later + connectReq.once('error', onError); + connectReq.end(); + + function onResponse(res) { + // Very hacky. This is necessary to avoid http-parser leaks. + res.upgrade = true; + } + + function onUpgrade(res, socket, head) { + // Hacky. + process.nextTick(function() { + onConnect(res, socket, head); + }); + } + + function onConnect(res, socket, head) { + connectReq.removeAllListeners(); + socket.removeAllListeners(); + + if (res.statusCode !== 200) { + debug('tunneling socket could not be established, statusCode=%d', + res.statusCode); + socket.destroy(); + var error = new Error('tunneling socket could not be established, ' + + 'statusCode=' + res.statusCode); + error.code = 'ECONNRESET'; + options.request.emit('error', error); + self.removeSocket(placeholder); + return; + } + if (head.length > 0) { + debug('got illegal response body from proxy'); + socket.destroy(); + var error = new Error('got illegal response body from proxy'); + error.code = 'ECONNRESET'; + options.request.emit('error', error); + self.removeSocket(placeholder); + return; + } + debug('tunneling connection has established'); + self.sockets[self.sockets.indexOf(placeholder)] = socket; + return cb(socket); + } + + function onError(cause) { + connectReq.removeAllListeners(); + + debug('tunneling socket could not be established, cause=%s\n', + cause.message, cause.stack); + var error = new Error('tunneling socket could not be established, ' + + 'cause=' + cause.message); + error.code = 'ECONNRESET'; + options.request.emit('error', error); + self.removeSocket(placeholder); + } +}; + +TunnelingAgent.prototype.removeSocket = function removeSocket(socket) { + var pos = this.sockets.indexOf(socket) + if (pos === -1) { + return; + } + this.sockets.splice(pos, 1); + + var pending = this.requests.shift(); + if (pending) { + // If we have pending requests and a socket gets closed a new one + // needs to be created to take over in the pool for the one that closed. + this.createSocket(pending, function(socket) { + pending.request.onSocket(socket); + }); + } +}; + +function createSecureSocket(options, cb) { + var self = this; + TunnelingAgent.prototype.createSocket.call(self, options, function(socket) { + var hostHeader = options.request.getHeader('host'); + var tlsOptions = mergeOptions({}, self.options, { + socket: socket, + servername: hostHeader ? hostHeader.replace(/:.*$/, '') : options.host + }); + + // 0 is dummy port for v0.6 + var secureSocket = tls.connect(0, tlsOptions); + self.sockets[self.sockets.indexOf(socket)] = secureSocket; + cb(secureSocket); + }); +} + + +function toOptions(host, port, localAddress) { + if (typeof host === 'string') { // since v0.10 + return { + host: host, + port: port, + localAddress: localAddress + }; + } + return host; // for v0.11 or later +} + +function mergeOptions(target) { + for (var i = 1, len = arguments.length; i < len; ++i) { + var overrides = arguments[i]; + if (typeof overrides === 'object') { + var keys = Object.keys(overrides); + for (var j = 0, keyLen = keys.length; j < keyLen; ++j) { + var k = keys[j]; + if (overrides[k] !== undefined) { + target[k] = overrides[k]; + } + } + } + } + return target; +} + + +var debug; +if (process.env.NODE_DEBUG && /\btunnel\b/.test(process.env.NODE_DEBUG)) { + debug = function() { + var args = Array.prototype.slice.call(arguments); + if (typeof args[0] === 'string') { + args[0] = 'TUNNEL: ' + args[0]; + } else { + args.unshift('TUNNEL:'); + } + console.error.apply(console, args); + } +} else { + debug = function() {}; +} +exports.debug = debug; // for test + + +/***/ }), + +/***/ 46752: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const Client = __nccwpck_require__(86197) +const Dispatcher = __nccwpck_require__(28611) +const errors = __nccwpck_require__(68707) +const Pool = __nccwpck_require__(35076) +const BalancedPool = __nccwpck_require__(81093) +const Agent = __nccwpck_require__(59965) +const util = __nccwpck_require__(3440) +const { InvalidArgumentError } = errors +const api = __nccwpck_require__(56615) +const buildConnector = __nccwpck_require__(59136) +const MockClient = __nccwpck_require__(47365) +const MockAgent = __nccwpck_require__(47501) +const MockPool = __nccwpck_require__(94004) +const mockErrors = __nccwpck_require__(52429) +const ProxyAgent = __nccwpck_require__(22720) +const RetryHandler = __nccwpck_require__(53573) +const { getGlobalDispatcher, setGlobalDispatcher } = __nccwpck_require__(32581) +const DecoratorHandler = __nccwpck_require__(78840) +const RedirectHandler = __nccwpck_require__(48299) +const createRedirectInterceptor = __nccwpck_require__(64415) + +let hasCrypto +try { + __nccwpck_require__(76982) + hasCrypto = true +} catch { + hasCrypto = false +} + +Object.assign(Dispatcher.prototype, api) + +module.exports.Dispatcher = Dispatcher +module.exports.Client = Client +module.exports.Pool = Pool +module.exports.BalancedPool = BalancedPool +module.exports.Agent = Agent +module.exports.ProxyAgent = ProxyAgent +module.exports.RetryHandler = RetryHandler + +module.exports.DecoratorHandler = DecoratorHandler +module.exports.RedirectHandler = RedirectHandler +module.exports.createRedirectInterceptor = createRedirectInterceptor + +module.exports.buildConnector = buildConnector +module.exports.errors = errors + +function makeDispatcher (fn) { + return (url, opts, handler) => { + if (typeof opts === 'function') { + handler = opts + opts = null + } + + if (!url || (typeof url !== 'string' && typeof url !== 'object' && !(url instanceof URL))) { + throw new InvalidArgumentError('invalid url') + } + + if (opts != null && typeof opts !== 'object') { + throw new InvalidArgumentError('invalid opts') + } + + if (opts && opts.path != null) { + if (typeof opts.path !== 'string') { + throw new InvalidArgumentError('invalid opts.path') + } + + let path = opts.path + if (!opts.path.startsWith('/')) { + path = `/${path}` + } + + url = new URL(util.parseOrigin(url).origin + path) + } else { + if (!opts) { + opts = typeof url === 'object' ? url : {} + } + + url = util.parseURL(url) + } + + const { agent, dispatcher = getGlobalDispatcher() } = opts + + if (agent) { + throw new InvalidArgumentError('unsupported opts.agent. Did you mean opts.client?') + } + + return fn.call(dispatcher, { + ...opts, + origin: url.origin, + path: url.search ? `${url.pathname}${url.search}` : url.pathname, + method: opts.method || (opts.body ? 'PUT' : 'GET') + }, handler) + } +} + +module.exports.setGlobalDispatcher = setGlobalDispatcher +module.exports.getGlobalDispatcher = getGlobalDispatcher + +if (util.nodeMajor > 16 || (util.nodeMajor === 16 && util.nodeMinor >= 8)) { + let fetchImpl = null + module.exports.fetch = async function fetch (resource) { + if (!fetchImpl) { + fetchImpl = (__nccwpck_require__(12315).fetch) + } + + try { + return await fetchImpl(...arguments) + } catch (err) { + if (typeof err === 'object') { + Error.captureStackTrace(err, this) + } + + throw err + } + } + module.exports.Headers = __nccwpck_require__(26349).Headers + module.exports.Response = __nccwpck_require__(48676).Response + module.exports.Request = __nccwpck_require__(25194).Request + module.exports.FormData = __nccwpck_require__(43073).FormData + module.exports.File = __nccwpck_require__(63041).File + module.exports.FileReader = __nccwpck_require__(82160).FileReader + + const { setGlobalOrigin, getGlobalOrigin } = __nccwpck_require__(75628) + + module.exports.setGlobalOrigin = setGlobalOrigin + module.exports.getGlobalOrigin = getGlobalOrigin + + const { CacheStorage } = __nccwpck_require__(44738) + const { kConstruct } = __nccwpck_require__(80296) + + // Cache & CacheStorage are tightly coupled with fetch. Even if it may run + // in an older version of Node, it doesn't have any use without fetch. + module.exports.caches = new CacheStorage(kConstruct) +} + +if (util.nodeMajor >= 16) { + const { deleteCookie, getCookies, getSetCookies, setCookie } = __nccwpck_require__(53168) + + module.exports.deleteCookie = deleteCookie + module.exports.getCookies = getCookies + module.exports.getSetCookies = getSetCookies + module.exports.setCookie = setCookie + + const { parseMIMEType, serializeAMimeType } = __nccwpck_require__(94322) + + module.exports.parseMIMEType = parseMIMEType + module.exports.serializeAMimeType = serializeAMimeType +} + +if (util.nodeMajor >= 18 && hasCrypto) { + const { WebSocket } = __nccwpck_require__(55171) + + module.exports.WebSocket = WebSocket +} + +module.exports.request = makeDispatcher(api.request) +module.exports.stream = makeDispatcher(api.stream) +module.exports.pipeline = makeDispatcher(api.pipeline) +module.exports.connect = makeDispatcher(api.connect) +module.exports.upgrade = makeDispatcher(api.upgrade) + +module.exports.MockClient = MockClient +module.exports.MockPool = MockPool +module.exports.MockAgent = MockAgent +module.exports.mockErrors = mockErrors + + +/***/ }), + +/***/ 59965: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const { InvalidArgumentError } = __nccwpck_require__(68707) +const { kClients, kRunning, kClose, kDestroy, kDispatch, kInterceptors } = __nccwpck_require__(36443) +const DispatcherBase = __nccwpck_require__(50001) +const Pool = __nccwpck_require__(35076) +const Client = __nccwpck_require__(86197) +const util = __nccwpck_require__(3440) +const createRedirectInterceptor = __nccwpck_require__(64415) +const { WeakRef, FinalizationRegistry } = __nccwpck_require__(13194)() + +const kOnConnect = Symbol('onConnect') +const kOnDisconnect = Symbol('onDisconnect') +const kOnConnectionError = Symbol('onConnectionError') +const kMaxRedirections = Symbol('maxRedirections') +const kOnDrain = Symbol('onDrain') +const kFactory = Symbol('factory') +const kFinalizer = Symbol('finalizer') +const kOptions = Symbol('options') + +function defaultFactory (origin, opts) { + return opts && opts.connections === 1 + ? new Client(origin, opts) + : new Pool(origin, opts) +} + +class Agent extends DispatcherBase { + constructor ({ factory = defaultFactory, maxRedirections = 0, connect, ...options } = {}) { + super() + + if (typeof factory !== 'function') { + throw new InvalidArgumentError('factory must be a function.') + } + + if (connect != null && typeof connect !== 'function' && typeof connect !== 'object') { + throw new InvalidArgumentError('connect must be a function or an object') + } + + if (!Number.isInteger(maxRedirections) || maxRedirections < 0) { + throw new InvalidArgumentError('maxRedirections must be a positive number') + } + + if (connect && typeof connect !== 'function') { + connect = { ...connect } + } + + this[kInterceptors] = options.interceptors && options.interceptors.Agent && Array.isArray(options.interceptors.Agent) + ? options.interceptors.Agent + : [createRedirectInterceptor({ maxRedirections })] + + this[kOptions] = { ...util.deepClone(options), connect } + this[kOptions].interceptors = options.interceptors + ? { ...options.interceptors } + : undefined + this[kMaxRedirections] = maxRedirections + this[kFactory] = factory + this[kClients] = new Map() + this[kFinalizer] = new FinalizationRegistry(/* istanbul ignore next: gc is undeterministic */ key => { + const ref = this[kClients].get(key) + if (ref !== undefined && ref.deref() === undefined) { + this[kClients].delete(key) + } + }) + + const agent = this + + this[kOnDrain] = (origin, targets) => { + agent.emit('drain', origin, [agent, ...targets]) + } + + this[kOnConnect] = (origin, targets) => { + agent.emit('connect', origin, [agent, ...targets]) + } + + this[kOnDisconnect] = (origin, targets, err) => { + agent.emit('disconnect', origin, [agent, ...targets], err) + } + + this[kOnConnectionError] = (origin, targets, err) => { + agent.emit('connectionError', origin, [agent, ...targets], err) + } + } + + get [kRunning] () { + let ret = 0 + for (const ref of this[kClients].values()) { + const client = ref.deref() + /* istanbul ignore next: gc is undeterministic */ + if (client) { + ret += client[kRunning] + } + } + return ret + } + + [kDispatch] (opts, handler) { + let key + if (opts.origin && (typeof opts.origin === 'string' || opts.origin instanceof URL)) { + key = String(opts.origin) + } else { + throw new InvalidArgumentError('opts.origin must be a non-empty string or URL.') + } + + const ref = this[kClients].get(key) + + let dispatcher = ref ? ref.deref() : null + if (!dispatcher) { + dispatcher = this[kFactory](opts.origin, this[kOptions]) + .on('drain', this[kOnDrain]) + .on('connect', this[kOnConnect]) + .on('disconnect', this[kOnDisconnect]) + .on('connectionError', this[kOnConnectionError]) + + this[kClients].set(key, new WeakRef(dispatcher)) + this[kFinalizer].register(dispatcher, key) + } + + return dispatcher.dispatch(opts, handler) + } + + async [kClose] () { + const closePromises = [] + for (const ref of this[kClients].values()) { + const client = ref.deref() + /* istanbul ignore else: gc is undeterministic */ + if (client) { + closePromises.push(client.close()) + } + } + + await Promise.all(closePromises) + } + + async [kDestroy] (err) { + const destroyPromises = [] + for (const ref of this[kClients].values()) { + const client = ref.deref() + /* istanbul ignore else: gc is undeterministic */ + if (client) { + destroyPromises.push(client.destroy(err)) + } + } + + await Promise.all(destroyPromises) + } +} + +module.exports = Agent + + +/***/ }), + +/***/ 80158: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +const { addAbortListener } = __nccwpck_require__(3440) +const { RequestAbortedError } = __nccwpck_require__(68707) + +const kListener = Symbol('kListener') +const kSignal = Symbol('kSignal') + +function abort (self) { + if (self.abort) { + self.abort() + } else { + self.onError(new RequestAbortedError()) + } +} + +function addSignal (self, signal) { + self[kSignal] = null + self[kListener] = null + + if (!signal) { + return + } + + if (signal.aborted) { + abort(self) + return + } + + self[kSignal] = signal + self[kListener] = () => { + abort(self) + } + + addAbortListener(self[kSignal], self[kListener]) +} + +function removeSignal (self) { + if (!self[kSignal]) { + return + } + + if ('removeEventListener' in self[kSignal]) { + self[kSignal].removeEventListener('abort', self[kListener]) + } else { + self[kSignal].removeListener('abort', self[kListener]) + } + + self[kSignal] = null + self[kListener] = null +} + +module.exports = { + addSignal, + removeSignal +} + + +/***/ }), + +/***/ 34660: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const { AsyncResource } = __nccwpck_require__(90290) +const { InvalidArgumentError, RequestAbortedError, SocketError } = __nccwpck_require__(68707) +const util = __nccwpck_require__(3440) +const { addSignal, removeSignal } = __nccwpck_require__(80158) + +class ConnectHandler extends AsyncResource { + constructor (opts, callback) { + if (!opts || typeof opts !== 'object') { + throw new InvalidArgumentError('invalid opts') + } + + if (typeof callback !== 'function') { + throw new InvalidArgumentError('invalid callback') + } + + const { signal, opaque, responseHeaders } = opts + + if (signal && typeof signal.on !== 'function' && typeof signal.addEventListener !== 'function') { + throw new InvalidArgumentError('signal must be an EventEmitter or EventTarget') + } + + super('UNDICI_CONNECT') + + this.opaque = opaque || null + this.responseHeaders = responseHeaders || null + this.callback = callback + this.abort = null + + addSignal(this, signal) + } + + onConnect (abort, context) { + if (!this.callback) { + throw new RequestAbortedError() + } + + this.abort = abort + this.context = context + } + + onHeaders () { + throw new SocketError('bad connect', null) + } + + onUpgrade (statusCode, rawHeaders, socket) { + const { callback, opaque, context } = this + + removeSignal(this) + + this.callback = null + + let headers = rawHeaders + // Indicates is an HTTP2Session + if (headers != null) { + headers = this.responseHeaders === 'raw' ? util.parseRawHeaders(rawHeaders) : util.parseHeaders(rawHeaders) + } + + this.runInAsyncScope(callback, null, null, { + statusCode, + headers, + socket, + opaque, + context + }) + } + + onError (err) { + const { callback, opaque } = this + + removeSignal(this) + + if (callback) { + this.callback = null + queueMicrotask(() => { + this.runInAsyncScope(callback, null, err, { opaque }) + }) + } + } +} + +function connect (opts, callback) { + if (callback === undefined) { + return new Promise((resolve, reject) => { + connect.call(this, opts, (err, data) => { + return err ? reject(err) : resolve(data) + }) + }) + } + + try { + const connectHandler = new ConnectHandler(opts, callback) + this.dispatch({ ...opts, method: 'CONNECT' }, connectHandler) + } catch (err) { + if (typeof callback !== 'function') { + throw err + } + const opaque = opts && opts.opaque + queueMicrotask(() => callback(err, { opaque })) + } +} + +module.exports = connect + + +/***/ }), + +/***/ 76862: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const { + Readable, + Duplex, + PassThrough +} = __nccwpck_require__(2203) +const { + InvalidArgumentError, + InvalidReturnValueError, + RequestAbortedError +} = __nccwpck_require__(68707) +const util = __nccwpck_require__(3440) +const { AsyncResource } = __nccwpck_require__(90290) +const { addSignal, removeSignal } = __nccwpck_require__(80158) +const assert = __nccwpck_require__(42613) + +const kResume = Symbol('resume') + +class PipelineRequest extends Readable { + constructor () { + super({ autoDestroy: true }) + + this[kResume] = null + } + + _read () { + const { [kResume]: resume } = this + + if (resume) { + this[kResume] = null + resume() + } + } + + _destroy (err, callback) { + this._read() + + callback(err) + } +} + +class PipelineResponse extends Readable { + constructor (resume) { + super({ autoDestroy: true }) + this[kResume] = resume + } + + _read () { + this[kResume]() + } + + _destroy (err, callback) { + if (!err && !this._readableState.endEmitted) { + err = new RequestAbortedError() + } + + callback(err) + } +} + +class PipelineHandler extends AsyncResource { + constructor (opts, handler) { + if (!opts || typeof opts !== 'object') { + throw new InvalidArgumentError('invalid opts') + } + + if (typeof handler !== 'function') { + throw new InvalidArgumentError('invalid handler') + } + + const { signal, method, opaque, onInfo, responseHeaders } = opts + + if (signal && typeof signal.on !== 'function' && typeof signal.addEventListener !== 'function') { + throw new InvalidArgumentError('signal must be an EventEmitter or EventTarget') + } + + if (method === 'CONNECT') { + throw new InvalidArgumentError('invalid method') + } + + if (onInfo && typeof onInfo !== 'function') { + throw new InvalidArgumentError('invalid onInfo callback') + } + + super('UNDICI_PIPELINE') + + this.opaque = opaque || null + this.responseHeaders = responseHeaders || null + this.handler = handler + this.abort = null + this.context = null + this.onInfo = onInfo || null + + this.req = new PipelineRequest().on('error', util.nop) + + this.ret = new Duplex({ + readableObjectMode: opts.objectMode, + autoDestroy: true, + read: () => { + const { body } = this + + if (body && body.resume) { + body.resume() + } + }, + write: (chunk, encoding, callback) => { + const { req } = this + + if (req.push(chunk, encoding) || req._readableState.destroyed) { + callback() + } else { + req[kResume] = callback + } + }, + destroy: (err, callback) => { + const { body, req, res, ret, abort } = this + + if (!err && !ret._readableState.endEmitted) { + err = new RequestAbortedError() + } + + if (abort && err) { + abort() + } + + util.destroy(body, err) + util.destroy(req, err) + util.destroy(res, err) + + removeSignal(this) + + callback(err) + } + }).on('prefinish', () => { + const { req } = this + + // Node < 15 does not call _final in same tick. + req.push(null) + }) + + this.res = null + + addSignal(this, signal) + } + + onConnect (abort, context) { + const { ret, res } = this + + assert(!res, 'pipeline cannot be retried') + + if (ret.destroyed) { + throw new RequestAbortedError() + } + + this.abort = abort + this.context = context + } + + onHeaders (statusCode, rawHeaders, resume) { + const { opaque, handler, context } = this + + if (statusCode < 200) { + if (this.onInfo) { + const headers = this.responseHeaders === 'raw' ? util.parseRawHeaders(rawHeaders) : util.parseHeaders(rawHeaders) + this.onInfo({ statusCode, headers }) + } + return + } + + this.res = new PipelineResponse(resume) + + let body + try { + this.handler = null + const headers = this.responseHeaders === 'raw' ? util.parseRawHeaders(rawHeaders) : util.parseHeaders(rawHeaders) + body = this.runInAsyncScope(handler, null, { + statusCode, + headers, + opaque, + body: this.res, + context + }) + } catch (err) { + this.res.on('error', util.nop) + throw err + } + + if (!body || typeof body.on !== 'function') { + throw new InvalidReturnValueError('expected Readable') + } + + body + .on('data', (chunk) => { + const { ret, body } = this + + if (!ret.push(chunk) && body.pause) { + body.pause() + } + }) + .on('error', (err) => { + const { ret } = this + + util.destroy(ret, err) + }) + .on('end', () => { + const { ret } = this + + ret.push(null) + }) + .on('close', () => { + const { ret } = this + + if (!ret._readableState.ended) { + util.destroy(ret, new RequestAbortedError()) + } + }) + + this.body = body + } + + onData (chunk) { + const { res } = this + return res.push(chunk) + } + + onComplete (trailers) { + const { res } = this + res.push(null) + } + + onError (err) { + const { ret } = this + this.handler = null + util.destroy(ret, err) + } +} + +function pipeline (opts, handler) { + try { + const pipelineHandler = new PipelineHandler(opts, handler) + this.dispatch({ ...opts, body: pipelineHandler.req }, pipelineHandler) + return pipelineHandler.ret + } catch (err) { + return new PassThrough().destroy(err) + } +} + +module.exports = pipeline + + +/***/ }), + +/***/ 14043: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const Readable = __nccwpck_require__(49927) +const { + InvalidArgumentError, + RequestAbortedError +} = __nccwpck_require__(68707) +const util = __nccwpck_require__(3440) +const { getResolveErrorBodyCallback } = __nccwpck_require__(87655) +const { AsyncResource } = __nccwpck_require__(90290) +const { addSignal, removeSignal } = __nccwpck_require__(80158) + +class RequestHandler extends AsyncResource { + constructor (opts, callback) { + if (!opts || typeof opts !== 'object') { + throw new InvalidArgumentError('invalid opts') + } + + const { signal, method, opaque, body, onInfo, responseHeaders, throwOnError, highWaterMark } = opts + + try { + if (typeof callback !== 'function') { + throw new InvalidArgumentError('invalid callback') + } + + if (highWaterMark && (typeof highWaterMark !== 'number' || highWaterMark < 0)) { + throw new InvalidArgumentError('invalid highWaterMark') + } + + if (signal && typeof signal.on !== 'function' && typeof signal.addEventListener !== 'function') { + throw new InvalidArgumentError('signal must be an EventEmitter or EventTarget') + } + + if (method === 'CONNECT') { + throw new InvalidArgumentError('invalid method') + } + + if (onInfo && typeof onInfo !== 'function') { + throw new InvalidArgumentError('invalid onInfo callback') + } + + super('UNDICI_REQUEST') + } catch (err) { + if (util.isStream(body)) { + util.destroy(body.on('error', util.nop), err) + } + throw err + } + + this.responseHeaders = responseHeaders || null + this.opaque = opaque || null + this.callback = callback + this.res = null + this.abort = null + this.body = body + this.trailers = {} + this.context = null + this.onInfo = onInfo || null + this.throwOnError = throwOnError + this.highWaterMark = highWaterMark + + if (util.isStream(body)) { + body.on('error', (err) => { + this.onError(err) + }) + } + + addSignal(this, signal) + } + + onConnect (abort, context) { + if (!this.callback) { + throw new RequestAbortedError() + } + + this.abort = abort + this.context = context + } + + onHeaders (statusCode, rawHeaders, resume, statusMessage) { + const { callback, opaque, abort, context, responseHeaders, highWaterMark } = this + + const headers = responseHeaders === 'raw' ? util.parseRawHeaders(rawHeaders) : util.parseHeaders(rawHeaders) + + if (statusCode < 200) { + if (this.onInfo) { + this.onInfo({ statusCode, headers }) + } + return + } + + const parsedHeaders = responseHeaders === 'raw' ? util.parseHeaders(rawHeaders) : headers + const contentType = parsedHeaders['content-type'] + const body = new Readable({ resume, abort, contentType, highWaterMark }) + + this.callback = null + this.res = body + if (callback !== null) { + if (this.throwOnError && statusCode >= 400) { + this.runInAsyncScope(getResolveErrorBodyCallback, null, + { callback, body, contentType, statusCode, statusMessage, headers } + ) + } else { + this.runInAsyncScope(callback, null, null, { + statusCode, + headers, + trailers: this.trailers, + opaque, + body, + context + }) + } + } + } + + onData (chunk) { + const { res } = this + return res.push(chunk) + } + + onComplete (trailers) { + const { res } = this + + removeSignal(this) + + util.parseHeaders(trailers, this.trailers) + + res.push(null) + } + + onError (err) { + const { res, callback, body, opaque } = this + + removeSignal(this) + + if (callback) { + // TODO: Does this need queueMicrotask? + this.callback = null + queueMicrotask(() => { + this.runInAsyncScope(callback, null, err, { opaque }) + }) + } + + if (res) { + this.res = null + // Ensure all queued handlers are invoked before destroying res. + queueMicrotask(() => { + util.destroy(res, err) + }) + } + + if (body) { + this.body = null + util.destroy(body, err) + } + } +} + +function request (opts, callback) { + if (callback === undefined) { + return new Promise((resolve, reject) => { + request.call(this, opts, (err, data) => { + return err ? reject(err) : resolve(data) + }) + }) + } + + try { + this.dispatch(opts, new RequestHandler(opts, callback)) + } catch (err) { + if (typeof callback !== 'function') { + throw err + } + const opaque = opts && opts.opaque + queueMicrotask(() => callback(err, { opaque })) + } +} + +module.exports = request +module.exports.RequestHandler = RequestHandler + + +/***/ }), + +/***/ 3560: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const { finished, PassThrough } = __nccwpck_require__(2203) +const { + InvalidArgumentError, + InvalidReturnValueError, + RequestAbortedError +} = __nccwpck_require__(68707) +const util = __nccwpck_require__(3440) +const { getResolveErrorBodyCallback } = __nccwpck_require__(87655) +const { AsyncResource } = __nccwpck_require__(90290) +const { addSignal, removeSignal } = __nccwpck_require__(80158) + +class StreamHandler extends AsyncResource { + constructor (opts, factory, callback) { + if (!opts || typeof opts !== 'object') { + throw new InvalidArgumentError('invalid opts') + } + + const { signal, method, opaque, body, onInfo, responseHeaders, throwOnError } = opts + + try { + if (typeof callback !== 'function') { + throw new InvalidArgumentError('invalid callback') + } + + if (typeof factory !== 'function') { + throw new InvalidArgumentError('invalid factory') + } + + if (signal && typeof signal.on !== 'function' && typeof signal.addEventListener !== 'function') { + throw new InvalidArgumentError('signal must be an EventEmitter or EventTarget') + } + + if (method === 'CONNECT') { + throw new InvalidArgumentError('invalid method') + } + + if (onInfo && typeof onInfo !== 'function') { + throw new InvalidArgumentError('invalid onInfo callback') + } + + super('UNDICI_STREAM') + } catch (err) { + if (util.isStream(body)) { + util.destroy(body.on('error', util.nop), err) + } + throw err + } + + this.responseHeaders = responseHeaders || null + this.opaque = opaque || null + this.factory = factory + this.callback = callback + this.res = null + this.abort = null + this.context = null + this.trailers = null + this.body = body + this.onInfo = onInfo || null + this.throwOnError = throwOnError || false + + if (util.isStream(body)) { + body.on('error', (err) => { + this.onError(err) + }) + } + + addSignal(this, signal) + } + + onConnect (abort, context) { + if (!this.callback) { + throw new RequestAbortedError() + } + + this.abort = abort + this.context = context + } + + onHeaders (statusCode, rawHeaders, resume, statusMessage) { + const { factory, opaque, context, callback, responseHeaders } = this + + const headers = responseHeaders === 'raw' ? util.parseRawHeaders(rawHeaders) : util.parseHeaders(rawHeaders) + + if (statusCode < 200) { + if (this.onInfo) { + this.onInfo({ statusCode, headers }) + } + return + } + + this.factory = null + + let res + + if (this.throwOnError && statusCode >= 400) { + const parsedHeaders = responseHeaders === 'raw' ? util.parseHeaders(rawHeaders) : headers + const contentType = parsedHeaders['content-type'] + res = new PassThrough() + + this.callback = null + this.runInAsyncScope(getResolveErrorBodyCallback, null, + { callback, body: res, contentType, statusCode, statusMessage, headers } + ) + } else { + if (factory === null) { + return + } + + res = this.runInAsyncScope(factory, null, { + statusCode, + headers, + opaque, + context + }) + + if ( + !res || + typeof res.write !== 'function' || + typeof res.end !== 'function' || + typeof res.on !== 'function' + ) { + throw new InvalidReturnValueError('expected Writable') + } + + // TODO: Avoid finished. It registers an unnecessary amount of listeners. + finished(res, { readable: false }, (err) => { + const { callback, res, opaque, trailers, abort } = this + + this.res = null + if (err || !res.readable) { + util.destroy(res, err) + } + + this.callback = null + this.runInAsyncScope(callback, null, err || null, { opaque, trailers }) + + if (err) { + abort() + } + }) + } + + res.on('drain', resume) + + this.res = res + + const needDrain = res.writableNeedDrain !== undefined + ? res.writableNeedDrain + : res._writableState && res._writableState.needDrain + + return needDrain !== true + } + + onData (chunk) { + const { res } = this + + return res ? res.write(chunk) : true + } + + onComplete (trailers) { + const { res } = this + + removeSignal(this) + + if (!res) { + return + } + + this.trailers = util.parseHeaders(trailers) + + res.end() + } + + onError (err) { + const { res, callback, opaque, body } = this + + removeSignal(this) + + this.factory = null + + if (res) { + this.res = null + util.destroy(res, err) + } else if (callback) { + this.callback = null + queueMicrotask(() => { + this.runInAsyncScope(callback, null, err, { opaque }) + }) + } + + if (body) { + this.body = null + util.destroy(body, err) + } + } +} + +function stream (opts, factory, callback) { + if (callback === undefined) { + return new Promise((resolve, reject) => { + stream.call(this, opts, factory, (err, data) => { + return err ? reject(err) : resolve(data) + }) + }) + } + + try { + this.dispatch(opts, new StreamHandler(opts, factory, callback)) + } catch (err) { + if (typeof callback !== 'function') { + throw err + } + const opaque = opts && opts.opaque + queueMicrotask(() => callback(err, { opaque })) + } +} + +module.exports = stream + + +/***/ }), + +/***/ 61882: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const { InvalidArgumentError, RequestAbortedError, SocketError } = __nccwpck_require__(68707) +const { AsyncResource } = __nccwpck_require__(90290) +const util = __nccwpck_require__(3440) +const { addSignal, removeSignal } = __nccwpck_require__(80158) +const assert = __nccwpck_require__(42613) + +class UpgradeHandler extends AsyncResource { + constructor (opts, callback) { + if (!opts || typeof opts !== 'object') { + throw new InvalidArgumentError('invalid opts') + } + + if (typeof callback !== 'function') { + throw new InvalidArgumentError('invalid callback') + } + + const { signal, opaque, responseHeaders } = opts + + if (signal && typeof signal.on !== 'function' && typeof signal.addEventListener !== 'function') { + throw new InvalidArgumentError('signal must be an EventEmitter or EventTarget') + } + + super('UNDICI_UPGRADE') + + this.responseHeaders = responseHeaders || null + this.opaque = opaque || null + this.callback = callback + this.abort = null + this.context = null + + addSignal(this, signal) + } + + onConnect (abort, context) { + if (!this.callback) { + throw new RequestAbortedError() + } + + this.abort = abort + this.context = null + } + + onHeaders () { + throw new SocketError('bad upgrade', null) + } + + onUpgrade (statusCode, rawHeaders, socket) { + const { callback, opaque, context } = this + + assert.strictEqual(statusCode, 101) + + removeSignal(this) + + this.callback = null + const headers = this.responseHeaders === 'raw' ? util.parseRawHeaders(rawHeaders) : util.parseHeaders(rawHeaders) + this.runInAsyncScope(callback, null, null, { + headers, + socket, + opaque, + context + }) + } + + onError (err) { + const { callback, opaque } = this + + removeSignal(this) + + if (callback) { + this.callback = null + queueMicrotask(() => { + this.runInAsyncScope(callback, null, err, { opaque }) + }) + } + } +} + +function upgrade (opts, callback) { + if (callback === undefined) { + return new Promise((resolve, reject) => { + upgrade.call(this, opts, (err, data) => { + return err ? reject(err) : resolve(data) + }) + }) + } + + try { + const upgradeHandler = new UpgradeHandler(opts, callback) + this.dispatch({ + ...opts, + method: opts.method || 'GET', + upgrade: opts.protocol || 'Websocket' + }, upgradeHandler) + } catch (err) { + if (typeof callback !== 'function') { + throw err + } + const opaque = opts && opts.opaque + queueMicrotask(() => callback(err, { opaque })) + } +} + +module.exports = upgrade + + +/***/ }), + +/***/ 56615: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +module.exports.request = __nccwpck_require__(14043) +module.exports.stream = __nccwpck_require__(3560) +module.exports.pipeline = __nccwpck_require__(76862) +module.exports.upgrade = __nccwpck_require__(61882) +module.exports.connect = __nccwpck_require__(34660) + + +/***/ }), + +/***/ 49927: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; +// Ported from https://github.com/nodejs/undici/pull/907 + + + +const assert = __nccwpck_require__(42613) +const { Readable } = __nccwpck_require__(2203) +const { RequestAbortedError, NotSupportedError, InvalidArgumentError } = __nccwpck_require__(68707) +const util = __nccwpck_require__(3440) +const { ReadableStreamFrom, toUSVString } = __nccwpck_require__(3440) + +let Blob + +const kConsume = Symbol('kConsume') +const kReading = Symbol('kReading') +const kBody = Symbol('kBody') +const kAbort = Symbol('abort') +const kContentType = Symbol('kContentType') + +const noop = () => {} + +module.exports = class BodyReadable extends Readable { + constructor ({ + resume, + abort, + contentType = '', + highWaterMark = 64 * 1024 // Same as nodejs fs streams. + }) { + super({ + autoDestroy: true, + read: resume, + highWaterMark + }) + + this._readableState.dataEmitted = false + + this[kAbort] = abort + this[kConsume] = null + this[kBody] = null + this[kContentType] = contentType + + // Is stream being consumed through Readable API? + // This is an optimization so that we avoid checking + // for 'data' and 'readable' listeners in the hot path + // inside push(). + this[kReading] = false + } + + destroy (err) { + if (this.destroyed) { + // Node < 16 + return this + } + + if (!err && !this._readableState.endEmitted) { + err = new RequestAbortedError() + } + + if (err) { + this[kAbort]() + } + + return super.destroy(err) + } + + emit (ev, ...args) { + if (ev === 'data') { + // Node < 16.7 + this._readableState.dataEmitted = true + } else if (ev === 'error') { + // Node < 16 + this._readableState.errorEmitted = true + } + return super.emit(ev, ...args) + } + + on (ev, ...args) { + if (ev === 'data' || ev === 'readable') { + this[kReading] = true + } + return super.on(ev, ...args) + } + + addListener (ev, ...args) { + return this.on(ev, ...args) + } + + off (ev, ...args) { + const ret = super.off(ev, ...args) + if (ev === 'data' || ev === 'readable') { + this[kReading] = ( + this.listenerCount('data') > 0 || + this.listenerCount('readable') > 0 + ) + } + return ret + } + + removeListener (ev, ...args) { + return this.off(ev, ...args) + } + + push (chunk) { + if (this[kConsume] && chunk !== null && this.readableLength === 0) { + consumePush(this[kConsume], chunk) + return this[kReading] ? super.push(chunk) : true + } + return super.push(chunk) + } + + // https://fetch.spec.whatwg.org/#dom-body-text + async text () { + return consume(this, 'text') + } + + // https://fetch.spec.whatwg.org/#dom-body-json + async json () { + return consume(this, 'json') + } + + // https://fetch.spec.whatwg.org/#dom-body-blob + async blob () { + return consume(this, 'blob') + } + + // https://fetch.spec.whatwg.org/#dom-body-arraybuffer + async arrayBuffer () { + return consume(this, 'arrayBuffer') + } + + // https://fetch.spec.whatwg.org/#dom-body-formdata + async formData () { + // TODO: Implement. + throw new NotSupportedError() + } + + // https://fetch.spec.whatwg.org/#dom-body-bodyused + get bodyUsed () { + return util.isDisturbed(this) + } + + // https://fetch.spec.whatwg.org/#dom-body-body + get body () { + if (!this[kBody]) { + this[kBody] = ReadableStreamFrom(this) + if (this[kConsume]) { + // TODO: Is this the best way to force a lock? + this[kBody].getReader() // Ensure stream is locked. + assert(this[kBody].locked) + } + } + return this[kBody] + } + + dump (opts) { + let limit = opts && Number.isFinite(opts.limit) ? opts.limit : 262144 + const signal = opts && opts.signal + + if (signal) { + try { + if (typeof signal !== 'object' || !('aborted' in signal)) { + throw new InvalidArgumentError('signal must be an AbortSignal') + } + util.throwIfAborted(signal) + } catch (err) { + return Promise.reject(err) + } + } + + if (this.closed) { + return Promise.resolve(null) + } + + return new Promise((resolve, reject) => { + const signalListenerCleanup = signal + ? util.addAbortListener(signal, () => { + this.destroy() + }) + : noop + + this + .on('close', function () { + signalListenerCleanup() + if (signal && signal.aborted) { + reject(signal.reason || Object.assign(new Error('The operation was aborted'), { name: 'AbortError' })) + } else { + resolve(null) + } + }) + .on('error', noop) + .on('data', function (chunk) { + limit -= chunk.length + if (limit <= 0) { + this.destroy() + } + }) + .resume() + }) + } +} + +// https://streams.spec.whatwg.org/#readablestream-locked +function isLocked (self) { + // Consume is an implicit lock. + return (self[kBody] && self[kBody].locked === true) || self[kConsume] +} + +// https://fetch.spec.whatwg.org/#body-unusable +function isUnusable (self) { + return util.isDisturbed(self) || isLocked(self) +} + +async function consume (stream, type) { + if (isUnusable(stream)) { + throw new TypeError('unusable') + } + + assert(!stream[kConsume]) + + return new Promise((resolve, reject) => { + stream[kConsume] = { + type, + stream, + resolve, + reject, + length: 0, + body: [] + } + + stream + .on('error', function (err) { + consumeFinish(this[kConsume], err) + }) + .on('close', function () { + if (this[kConsume].body !== null) { + consumeFinish(this[kConsume], new RequestAbortedError()) + } + }) + + process.nextTick(consumeStart, stream[kConsume]) + }) +} + +function consumeStart (consume) { + if (consume.body === null) { + return + } + + const { _readableState: state } = consume.stream + + for (const chunk of state.buffer) { + consumePush(consume, chunk) + } + + if (state.endEmitted) { + consumeEnd(this[kConsume]) + } else { + consume.stream.on('end', function () { + consumeEnd(this[kConsume]) + }) + } + + consume.stream.resume() + + while (consume.stream.read() != null) { + // Loop + } +} + +function consumeEnd (consume) { + const { type, body, resolve, stream, length } = consume + + try { + if (type === 'text') { + resolve(toUSVString(Buffer.concat(body))) + } else if (type === 'json') { + resolve(JSON.parse(Buffer.concat(body))) + } else if (type === 'arrayBuffer') { + const dst = new Uint8Array(length) + + let pos = 0 + for (const buf of body) { + dst.set(buf, pos) + pos += buf.byteLength + } + + resolve(dst.buffer) + } else if (type === 'blob') { + if (!Blob) { + Blob = (__nccwpck_require__(20181).Blob) + } + resolve(new Blob(body, { type: stream[kContentType] })) + } + + consumeFinish(consume) + } catch (err) { + stream.destroy(err) + } +} + +function consumePush (consume, chunk) { + consume.length += chunk.length + consume.body.push(chunk) +} + +function consumeFinish (consume, err) { + if (consume.body === null) { + return + } + + if (err) { + consume.reject(err) + } else { + consume.resolve() + } + + consume.type = null + consume.stream = null + consume.resolve = null + consume.reject = null + consume.length = 0 + consume.body = null +} + + +/***/ }), + +/***/ 87655: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +const assert = __nccwpck_require__(42613) +const { + ResponseStatusCodeError +} = __nccwpck_require__(68707) +const { toUSVString } = __nccwpck_require__(3440) + +async function getResolveErrorBodyCallback ({ callback, body, contentType, statusCode, statusMessage, headers }) { + assert(body) + + let chunks = [] + let limit = 0 + + for await (const chunk of body) { + chunks.push(chunk) + limit += chunk.length + if (limit > 128 * 1024) { + chunks = null + break + } + } + + if (statusCode === 204 || !contentType || !chunks) { + process.nextTick(callback, new ResponseStatusCodeError(`Response status code ${statusCode}${statusMessage ? `: ${statusMessage}` : ''}`, statusCode, headers)) + return + } + + try { + if (contentType.startsWith('application/json')) { + const payload = JSON.parse(toUSVString(Buffer.concat(chunks))) + process.nextTick(callback, new ResponseStatusCodeError(`Response status code ${statusCode}${statusMessage ? `: ${statusMessage}` : ''}`, statusCode, headers, payload)) + return + } + + if (contentType.startsWith('text/')) { + const payload = toUSVString(Buffer.concat(chunks)) + process.nextTick(callback, new ResponseStatusCodeError(`Response status code ${statusCode}${statusMessage ? `: ${statusMessage}` : ''}`, statusCode, headers, payload)) + return + } + } catch (err) { + // Process in a fallback if error + } + + process.nextTick(callback, new ResponseStatusCodeError(`Response status code ${statusCode}${statusMessage ? `: ${statusMessage}` : ''}`, statusCode, headers)) +} + +module.exports = { getResolveErrorBodyCallback } + + +/***/ }), + +/***/ 81093: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const { + BalancedPoolMissingUpstreamError, + InvalidArgumentError +} = __nccwpck_require__(68707) +const { + PoolBase, + kClients, + kNeedDrain, + kAddClient, + kRemoveClient, + kGetDispatcher +} = __nccwpck_require__(58640) +const Pool = __nccwpck_require__(35076) +const { kUrl, kInterceptors } = __nccwpck_require__(36443) +const { parseOrigin } = __nccwpck_require__(3440) +const kFactory = Symbol('factory') + +const kOptions = Symbol('options') +const kGreatestCommonDivisor = Symbol('kGreatestCommonDivisor') +const kCurrentWeight = Symbol('kCurrentWeight') +const kIndex = Symbol('kIndex') +const kWeight = Symbol('kWeight') +const kMaxWeightPerServer = Symbol('kMaxWeightPerServer') +const kErrorPenalty = Symbol('kErrorPenalty') + +function getGreatestCommonDivisor (a, b) { + if (b === 0) return a + return getGreatestCommonDivisor(b, a % b) +} + +function defaultFactory (origin, opts) { + return new Pool(origin, opts) +} + +class BalancedPool extends PoolBase { + constructor (upstreams = [], { factory = defaultFactory, ...opts } = {}) { + super() + + this[kOptions] = opts + this[kIndex] = -1 + this[kCurrentWeight] = 0 + + this[kMaxWeightPerServer] = this[kOptions].maxWeightPerServer || 100 + this[kErrorPenalty] = this[kOptions].errorPenalty || 15 + + if (!Array.isArray(upstreams)) { + upstreams = [upstreams] + } + + if (typeof factory !== 'function') { + throw new InvalidArgumentError('factory must be a function.') + } + + this[kInterceptors] = opts.interceptors && opts.interceptors.BalancedPool && Array.isArray(opts.interceptors.BalancedPool) + ? opts.interceptors.BalancedPool + : [] + this[kFactory] = factory + + for (const upstream of upstreams) { + this.addUpstream(upstream) + } + this._updateBalancedPoolStats() + } + + addUpstream (upstream) { + const upstreamOrigin = parseOrigin(upstream).origin + + if (this[kClients].find((pool) => ( + pool[kUrl].origin === upstreamOrigin && + pool.closed !== true && + pool.destroyed !== true + ))) { + return this + } + const pool = this[kFactory](upstreamOrigin, Object.assign({}, this[kOptions])) + + this[kAddClient](pool) + pool.on('connect', () => { + pool[kWeight] = Math.min(this[kMaxWeightPerServer], pool[kWeight] + this[kErrorPenalty]) + }) + + pool.on('connectionError', () => { + pool[kWeight] = Math.max(1, pool[kWeight] - this[kErrorPenalty]) + this._updateBalancedPoolStats() + }) + + pool.on('disconnect', (...args) => { + const err = args[2] + if (err && err.code === 'UND_ERR_SOCKET') { + // decrease the weight of the pool. + pool[kWeight] = Math.max(1, pool[kWeight] - this[kErrorPenalty]) + this._updateBalancedPoolStats() + } + }) + + for (const client of this[kClients]) { + client[kWeight] = this[kMaxWeightPerServer] + } + + this._updateBalancedPoolStats() + + return this + } + + _updateBalancedPoolStats () { + this[kGreatestCommonDivisor] = this[kClients].map(p => p[kWeight]).reduce(getGreatestCommonDivisor, 0) + } + + removeUpstream (upstream) { + const upstreamOrigin = parseOrigin(upstream).origin + + const pool = this[kClients].find((pool) => ( + pool[kUrl].origin === upstreamOrigin && + pool.closed !== true && + pool.destroyed !== true + )) + + if (pool) { + this[kRemoveClient](pool) + } + + return this + } + + get upstreams () { + return this[kClients] + .filter(dispatcher => dispatcher.closed !== true && dispatcher.destroyed !== true) + .map((p) => p[kUrl].origin) + } + + [kGetDispatcher] () { + // We validate that pools is greater than 0, + // otherwise we would have to wait until an upstream + // is added, which might never happen. + if (this[kClients].length === 0) { + throw new BalancedPoolMissingUpstreamError() + } + + const dispatcher = this[kClients].find(dispatcher => ( + !dispatcher[kNeedDrain] && + dispatcher.closed !== true && + dispatcher.destroyed !== true + )) + + if (!dispatcher) { + return + } + + const allClientsBusy = this[kClients].map(pool => pool[kNeedDrain]).reduce((a, b) => a && b, true) + + if (allClientsBusy) { + return + } + + let counter = 0 + + let maxWeightIndex = this[kClients].findIndex(pool => !pool[kNeedDrain]) + + while (counter++ < this[kClients].length) { + this[kIndex] = (this[kIndex] + 1) % this[kClients].length + const pool = this[kClients][this[kIndex]] + + // find pool index with the largest weight + if (pool[kWeight] > this[kClients][maxWeightIndex][kWeight] && !pool[kNeedDrain]) { + maxWeightIndex = this[kIndex] + } + + // decrease the current weight every `this[kClients].length`. + if (this[kIndex] === 0) { + // Set the current weight to the next lower weight. + this[kCurrentWeight] = this[kCurrentWeight] - this[kGreatestCommonDivisor] + + if (this[kCurrentWeight] <= 0) { + this[kCurrentWeight] = this[kMaxWeightPerServer] + } + } + if (pool[kWeight] >= this[kCurrentWeight] && (!pool[kNeedDrain])) { + return pool + } + } + + this[kCurrentWeight] = this[kClients][maxWeightIndex][kWeight] + this[kIndex] = maxWeightIndex + return this[kClients][maxWeightIndex] + } +} + +module.exports = BalancedPool + + +/***/ }), + +/***/ 50479: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const { kConstruct } = __nccwpck_require__(80296) +const { urlEquals, fieldValues: getFieldValues } = __nccwpck_require__(23993) +const { kEnumerableProperty, isDisturbed } = __nccwpck_require__(3440) +const { kHeadersList } = __nccwpck_require__(36443) +const { webidl } = __nccwpck_require__(74222) +const { Response, cloneResponse } = __nccwpck_require__(48676) +const { Request } = __nccwpck_require__(25194) +const { kState, kHeaders, kGuard, kRealm } = __nccwpck_require__(89710) +const { fetching } = __nccwpck_require__(12315) +const { urlIsHttpHttpsScheme, createDeferredPromise, readAllBytes } = __nccwpck_require__(15523) +const assert = __nccwpck_require__(42613) +const { getGlobalDispatcher } = __nccwpck_require__(32581) + +/** + * @see https://w3c.github.io/ServiceWorker/#dfn-cache-batch-operation + * @typedef {Object} CacheBatchOperation + * @property {'delete' | 'put'} type + * @property {any} request + * @property {any} response + * @property {import('../../types/cache').CacheQueryOptions} options + */ + +/** + * @see https://w3c.github.io/ServiceWorker/#dfn-request-response-list + * @typedef {[any, any][]} requestResponseList + */ + +class Cache { + /** + * @see https://w3c.github.io/ServiceWorker/#dfn-relevant-request-response-list + * @type {requestResponseList} + */ + #relevantRequestResponseList + + constructor () { + if (arguments[0] !== kConstruct) { + webidl.illegalConstructor() + } + + this.#relevantRequestResponseList = arguments[1] + } + + async match (request, options = {}) { + webidl.brandCheck(this, Cache) + webidl.argumentLengthCheck(arguments, 1, { header: 'Cache.match' }) + + request = webidl.converters.RequestInfo(request) + options = webidl.converters.CacheQueryOptions(options) + + const p = await this.matchAll(request, options) + + if (p.length === 0) { + return + } + + return p[0] + } + + async matchAll (request = undefined, options = {}) { + webidl.brandCheck(this, Cache) + + if (request !== undefined) request = webidl.converters.RequestInfo(request) + options = webidl.converters.CacheQueryOptions(options) + + // 1. + let r = null + + // 2. + if (request !== undefined) { + if (request instanceof Request) { + // 2.1.1 + r = request[kState] + + // 2.1.2 + if (r.method !== 'GET' && !options.ignoreMethod) { + return [] + } + } else if (typeof request === 'string') { + // 2.2.1 + r = new Request(request)[kState] + } + } + + // 5. + // 5.1 + const responses = [] + + // 5.2 + if (request === undefined) { + // 5.2.1 + for (const requestResponse of this.#relevantRequestResponseList) { + responses.push(requestResponse[1]) + } + } else { // 5.3 + // 5.3.1 + const requestResponses = this.#queryCache(r, options) + + // 5.3.2 + for (const requestResponse of requestResponses) { + responses.push(requestResponse[1]) + } + } + + // 5.4 + // We don't implement CORs so we don't need to loop over the responses, yay! + + // 5.5.1 + const responseList = [] + + // 5.5.2 + for (const response of responses) { + // 5.5.2.1 + const responseObject = new Response(response.body?.source ?? null) + const body = responseObject[kState].body + responseObject[kState] = response + responseObject[kState].body = body + responseObject[kHeaders][kHeadersList] = response.headersList + responseObject[kHeaders][kGuard] = 'immutable' + + responseList.push(responseObject) + } + + // 6. + return Object.freeze(responseList) + } + + async add (request) { + webidl.brandCheck(this, Cache) + webidl.argumentLengthCheck(arguments, 1, { header: 'Cache.add' }) + + request = webidl.converters.RequestInfo(request) + + // 1. + const requests = [request] + + // 2. + const responseArrayPromise = this.addAll(requests) + + // 3. + return await responseArrayPromise + } + + async addAll (requests) { + webidl.brandCheck(this, Cache) + webidl.argumentLengthCheck(arguments, 1, { header: 'Cache.addAll' }) + + requests = webidl.converters['sequence'](requests) + + // 1. + const responsePromises = [] + + // 2. + const requestList = [] + + // 3. + for (const request of requests) { + if (typeof request === 'string') { + continue + } + + // 3.1 + const r = request[kState] + + // 3.2 + if (!urlIsHttpHttpsScheme(r.url) || r.method !== 'GET') { + throw webidl.errors.exception({ + header: 'Cache.addAll', + message: 'Expected http/s scheme when method is not GET.' + }) + } + } + + // 4. + /** @type {ReturnType[]} */ + const fetchControllers = [] + + // 5. + for (const request of requests) { + // 5.1 + const r = new Request(request)[kState] + + // 5.2 + if (!urlIsHttpHttpsScheme(r.url)) { + throw webidl.errors.exception({ + header: 'Cache.addAll', + message: 'Expected http/s scheme.' + }) + } + + // 5.4 + r.initiator = 'fetch' + r.destination = 'subresource' + + // 5.5 + requestList.push(r) + + // 5.6 + const responsePromise = createDeferredPromise() + + // 5.7 + fetchControllers.push(fetching({ + request: r, + dispatcher: getGlobalDispatcher(), + processResponse (response) { + // 1. + if (response.type === 'error' || response.status === 206 || response.status < 200 || response.status > 299) { + responsePromise.reject(webidl.errors.exception({ + header: 'Cache.addAll', + message: 'Received an invalid status code or the request failed.' + })) + } else if (response.headersList.contains('vary')) { // 2. + // 2.1 + const fieldValues = getFieldValues(response.headersList.get('vary')) + + // 2.2 + for (const fieldValue of fieldValues) { + // 2.2.1 + if (fieldValue === '*') { + responsePromise.reject(webidl.errors.exception({ + header: 'Cache.addAll', + message: 'invalid vary field value' + })) + + for (const controller of fetchControllers) { + controller.abort() + } + + return + } + } + } + }, + processResponseEndOfBody (response) { + // 1. + if (response.aborted) { + responsePromise.reject(new DOMException('aborted', 'AbortError')) + return + } + + // 2. + responsePromise.resolve(response) + } + })) + + // 5.8 + responsePromises.push(responsePromise.promise) + } + + // 6. + const p = Promise.all(responsePromises) + + // 7. + const responses = await p + + // 7.1 + const operations = [] + + // 7.2 + let index = 0 + + // 7.3 + for (const response of responses) { + // 7.3.1 + /** @type {CacheBatchOperation} */ + const operation = { + type: 'put', // 7.3.2 + request: requestList[index], // 7.3.3 + response // 7.3.4 + } + + operations.push(operation) // 7.3.5 + + index++ // 7.3.6 + } + + // 7.5 + const cacheJobPromise = createDeferredPromise() + + // 7.6.1 + let errorData = null + + // 7.6.2 + try { + this.#batchCacheOperations(operations) + } catch (e) { + errorData = e + } + + // 7.6.3 + queueMicrotask(() => { + // 7.6.3.1 + if (errorData === null) { + cacheJobPromise.resolve(undefined) + } else { + // 7.6.3.2 + cacheJobPromise.reject(errorData) + } + }) + + // 7.7 + return cacheJobPromise.promise + } + + async put (request, response) { + webidl.brandCheck(this, Cache) + webidl.argumentLengthCheck(arguments, 2, { header: 'Cache.put' }) + + request = webidl.converters.RequestInfo(request) + response = webidl.converters.Response(response) + + // 1. + let innerRequest = null + + // 2. + if (request instanceof Request) { + innerRequest = request[kState] + } else { // 3. + innerRequest = new Request(request)[kState] + } + + // 4. + if (!urlIsHttpHttpsScheme(innerRequest.url) || innerRequest.method !== 'GET') { + throw webidl.errors.exception({ + header: 'Cache.put', + message: 'Expected an http/s scheme when method is not GET' + }) + } + + // 5. + const innerResponse = response[kState] + + // 6. + if (innerResponse.status === 206) { + throw webidl.errors.exception({ + header: 'Cache.put', + message: 'Got 206 status' + }) + } + + // 7. + if (innerResponse.headersList.contains('vary')) { + // 7.1. + const fieldValues = getFieldValues(innerResponse.headersList.get('vary')) + + // 7.2. + for (const fieldValue of fieldValues) { + // 7.2.1 + if (fieldValue === '*') { + throw webidl.errors.exception({ + header: 'Cache.put', + message: 'Got * vary field value' + }) + } + } + } + + // 8. + if (innerResponse.body && (isDisturbed(innerResponse.body.stream) || innerResponse.body.stream.locked)) { + throw webidl.errors.exception({ + header: 'Cache.put', + message: 'Response body is locked or disturbed' + }) + } + + // 9. + const clonedResponse = cloneResponse(innerResponse) + + // 10. + const bodyReadPromise = createDeferredPromise() + + // 11. + if (innerResponse.body != null) { + // 11.1 + const stream = innerResponse.body.stream + + // 11.2 + const reader = stream.getReader() + + // 11.3 + readAllBytes(reader).then(bodyReadPromise.resolve, bodyReadPromise.reject) + } else { + bodyReadPromise.resolve(undefined) + } + + // 12. + /** @type {CacheBatchOperation[]} */ + const operations = [] + + // 13. + /** @type {CacheBatchOperation} */ + const operation = { + type: 'put', // 14. + request: innerRequest, // 15. + response: clonedResponse // 16. + } + + // 17. + operations.push(operation) + + // 19. + const bytes = await bodyReadPromise.promise + + if (clonedResponse.body != null) { + clonedResponse.body.source = bytes + } + + // 19.1 + const cacheJobPromise = createDeferredPromise() + + // 19.2.1 + let errorData = null + + // 19.2.2 + try { + this.#batchCacheOperations(operations) + } catch (e) { + errorData = e + } + + // 19.2.3 + queueMicrotask(() => { + // 19.2.3.1 + if (errorData === null) { + cacheJobPromise.resolve() + } else { // 19.2.3.2 + cacheJobPromise.reject(errorData) + } + }) + + return cacheJobPromise.promise + } + + async delete (request, options = {}) { + webidl.brandCheck(this, Cache) + webidl.argumentLengthCheck(arguments, 1, { header: 'Cache.delete' }) + + request = webidl.converters.RequestInfo(request) + options = webidl.converters.CacheQueryOptions(options) + + /** + * @type {Request} + */ + let r = null + + if (request instanceof Request) { + r = request[kState] + + if (r.method !== 'GET' && !options.ignoreMethod) { + return false + } + } else { + assert(typeof request === 'string') + + r = new Request(request)[kState] + } + + /** @type {CacheBatchOperation[]} */ + const operations = [] + + /** @type {CacheBatchOperation} */ + const operation = { + type: 'delete', + request: r, + options + } + + operations.push(operation) + + const cacheJobPromise = createDeferredPromise() + + let errorData = null + let requestResponses + + try { + requestResponses = this.#batchCacheOperations(operations) + } catch (e) { + errorData = e + } + + queueMicrotask(() => { + if (errorData === null) { + cacheJobPromise.resolve(!!requestResponses?.length) + } else { + cacheJobPromise.reject(errorData) + } + }) + + return cacheJobPromise.promise + } + + /** + * @see https://w3c.github.io/ServiceWorker/#dom-cache-keys + * @param {any} request + * @param {import('../../types/cache').CacheQueryOptions} options + * @returns {readonly Request[]} + */ + async keys (request = undefined, options = {}) { + webidl.brandCheck(this, Cache) + + if (request !== undefined) request = webidl.converters.RequestInfo(request) + options = webidl.converters.CacheQueryOptions(options) + + // 1. + let r = null + + // 2. + if (request !== undefined) { + // 2.1 + if (request instanceof Request) { + // 2.1.1 + r = request[kState] + + // 2.1.2 + if (r.method !== 'GET' && !options.ignoreMethod) { + return [] + } + } else if (typeof request === 'string') { // 2.2 + r = new Request(request)[kState] + } + } + + // 4. + const promise = createDeferredPromise() + + // 5. + // 5.1 + const requests = [] + + // 5.2 + if (request === undefined) { + // 5.2.1 + for (const requestResponse of this.#relevantRequestResponseList) { + // 5.2.1.1 + requests.push(requestResponse[0]) + } + } else { // 5.3 + // 5.3.1 + const requestResponses = this.#queryCache(r, options) + + // 5.3.2 + for (const requestResponse of requestResponses) { + // 5.3.2.1 + requests.push(requestResponse[0]) + } + } + + // 5.4 + queueMicrotask(() => { + // 5.4.1 + const requestList = [] + + // 5.4.2 + for (const request of requests) { + const requestObject = new Request('https://a') + requestObject[kState] = request + requestObject[kHeaders][kHeadersList] = request.headersList + requestObject[kHeaders][kGuard] = 'immutable' + requestObject[kRealm] = request.client + + // 5.4.2.1 + requestList.push(requestObject) + } + + // 5.4.3 + promise.resolve(Object.freeze(requestList)) + }) + + return promise.promise + } + + /** + * @see https://w3c.github.io/ServiceWorker/#batch-cache-operations-algorithm + * @param {CacheBatchOperation[]} operations + * @returns {requestResponseList} + */ + #batchCacheOperations (operations) { + // 1. + const cache = this.#relevantRequestResponseList + + // 2. + const backupCache = [...cache] + + // 3. + const addedItems = [] + + // 4.1 + const resultList = [] + + try { + // 4.2 + for (const operation of operations) { + // 4.2.1 + if (operation.type !== 'delete' && operation.type !== 'put') { + throw webidl.errors.exception({ + header: 'Cache.#batchCacheOperations', + message: 'operation type does not match "delete" or "put"' + }) + } + + // 4.2.2 + if (operation.type === 'delete' && operation.response != null) { + throw webidl.errors.exception({ + header: 'Cache.#batchCacheOperations', + message: 'delete operation should not have an associated response' + }) + } + + // 4.2.3 + if (this.#queryCache(operation.request, operation.options, addedItems).length) { + throw new DOMException('???', 'InvalidStateError') + } + + // 4.2.4 + let requestResponses + + // 4.2.5 + if (operation.type === 'delete') { + // 4.2.5.1 + requestResponses = this.#queryCache(operation.request, operation.options) + + // TODO: the spec is wrong, this is needed to pass WPTs + if (requestResponses.length === 0) { + return [] + } + + // 4.2.5.2 + for (const requestResponse of requestResponses) { + const idx = cache.indexOf(requestResponse) + assert(idx !== -1) + + // 4.2.5.2.1 + cache.splice(idx, 1) + } + } else if (operation.type === 'put') { // 4.2.6 + // 4.2.6.1 + if (operation.response == null) { + throw webidl.errors.exception({ + header: 'Cache.#batchCacheOperations', + message: 'put operation should have an associated response' + }) + } + + // 4.2.6.2 + const r = operation.request + + // 4.2.6.3 + if (!urlIsHttpHttpsScheme(r.url)) { + throw webidl.errors.exception({ + header: 'Cache.#batchCacheOperations', + message: 'expected http or https scheme' + }) + } + + // 4.2.6.4 + if (r.method !== 'GET') { + throw webidl.errors.exception({ + header: 'Cache.#batchCacheOperations', + message: 'not get method' + }) + } + + // 4.2.6.5 + if (operation.options != null) { + throw webidl.errors.exception({ + header: 'Cache.#batchCacheOperations', + message: 'options must not be defined' + }) + } + + // 4.2.6.6 + requestResponses = this.#queryCache(operation.request) + + // 4.2.6.7 + for (const requestResponse of requestResponses) { + const idx = cache.indexOf(requestResponse) + assert(idx !== -1) + + // 4.2.6.7.1 + cache.splice(idx, 1) + } + + // 4.2.6.8 + cache.push([operation.request, operation.response]) + + // 4.2.6.10 + addedItems.push([operation.request, operation.response]) + } + + // 4.2.7 + resultList.push([operation.request, operation.response]) + } + + // 4.3 + return resultList + } catch (e) { // 5. + // 5.1 + this.#relevantRequestResponseList.length = 0 + + // 5.2 + this.#relevantRequestResponseList = backupCache + + // 5.3 + throw e + } + } + + /** + * @see https://w3c.github.io/ServiceWorker/#query-cache + * @param {any} requestQuery + * @param {import('../../types/cache').CacheQueryOptions} options + * @param {requestResponseList} targetStorage + * @returns {requestResponseList} + */ + #queryCache (requestQuery, options, targetStorage) { + /** @type {requestResponseList} */ + const resultList = [] + + const storage = targetStorage ?? this.#relevantRequestResponseList + + for (const requestResponse of storage) { + const [cachedRequest, cachedResponse] = requestResponse + if (this.#requestMatchesCachedItem(requestQuery, cachedRequest, cachedResponse, options)) { + resultList.push(requestResponse) + } + } + + return resultList + } + + /** + * @see https://w3c.github.io/ServiceWorker/#request-matches-cached-item-algorithm + * @param {any} requestQuery + * @param {any} request + * @param {any | null} response + * @param {import('../../types/cache').CacheQueryOptions | undefined} options + * @returns {boolean} + */ + #requestMatchesCachedItem (requestQuery, request, response = null, options) { + // if (options?.ignoreMethod === false && request.method === 'GET') { + // return false + // } + + const queryURL = new URL(requestQuery.url) + + const cachedURL = new URL(request.url) + + if (options?.ignoreSearch) { + cachedURL.search = '' + + queryURL.search = '' + } + + if (!urlEquals(queryURL, cachedURL, true)) { + return false + } + + if ( + response == null || + options?.ignoreVary || + !response.headersList.contains('vary') + ) { + return true + } + + const fieldValues = getFieldValues(response.headersList.get('vary')) + + for (const fieldValue of fieldValues) { + if (fieldValue === '*') { + return false + } + + const requestValue = request.headersList.get(fieldValue) + const queryValue = requestQuery.headersList.get(fieldValue) + + // If one has the header and the other doesn't, or one has + // a different value than the other, return false + if (requestValue !== queryValue) { + return false + } + } + + return true + } +} + +Object.defineProperties(Cache.prototype, { + [Symbol.toStringTag]: { + value: 'Cache', + configurable: true + }, + match: kEnumerableProperty, + matchAll: kEnumerableProperty, + add: kEnumerableProperty, + addAll: kEnumerableProperty, + put: kEnumerableProperty, + delete: kEnumerableProperty, + keys: kEnumerableProperty +}) + +const cacheQueryOptionConverters = [ + { + key: 'ignoreSearch', + converter: webidl.converters.boolean, + defaultValue: false + }, + { + key: 'ignoreMethod', + converter: webidl.converters.boolean, + defaultValue: false + }, + { + key: 'ignoreVary', + converter: webidl.converters.boolean, + defaultValue: false + } +] + +webidl.converters.CacheQueryOptions = webidl.dictionaryConverter(cacheQueryOptionConverters) + +webidl.converters.MultiCacheQueryOptions = webidl.dictionaryConverter([ + ...cacheQueryOptionConverters, + { + key: 'cacheName', + converter: webidl.converters.DOMString + } +]) + +webidl.converters.Response = webidl.interfaceConverter(Response) + +webidl.converters['sequence'] = webidl.sequenceConverter( + webidl.converters.RequestInfo +) + +module.exports = { + Cache +} + + +/***/ }), + +/***/ 44738: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const { kConstruct } = __nccwpck_require__(80296) +const { Cache } = __nccwpck_require__(50479) +const { webidl } = __nccwpck_require__(74222) +const { kEnumerableProperty } = __nccwpck_require__(3440) + +class CacheStorage { + /** + * @see https://w3c.github.io/ServiceWorker/#dfn-relevant-name-to-cache-map + * @type {Map} + */ + async has (cacheName) { + webidl.brandCheck(this, CacheStorage) + webidl.argumentLengthCheck(arguments, 1, { header: 'CacheStorage.has' }) + + cacheName = webidl.converters.DOMString(cacheName) + + // 2.1.1 + // 2.2 + return this.#caches.has(cacheName) + } + + /** + * @see https://w3c.github.io/ServiceWorker/#dom-cachestorage-open + * @param {string} cacheName + * @returns {Promise} + */ + async open (cacheName) { + webidl.brandCheck(this, CacheStorage) + webidl.argumentLengthCheck(arguments, 1, { header: 'CacheStorage.open' }) + + cacheName = webidl.converters.DOMString(cacheName) + + // 2.1 + if (this.#caches.has(cacheName)) { + // await caches.open('v1') !== await caches.open('v1') + + // 2.1.1 + const cache = this.#caches.get(cacheName) + + // 2.1.1.1 + return new Cache(kConstruct, cache) + } + + // 2.2 + const cache = [] + + // 2.3 + this.#caches.set(cacheName, cache) + + // 2.4 + return new Cache(kConstruct, cache) + } + + /** + * @see https://w3c.github.io/ServiceWorker/#cache-storage-delete + * @param {string} cacheName + * @returns {Promise} + */ + async delete (cacheName) { + webidl.brandCheck(this, CacheStorage) + webidl.argumentLengthCheck(arguments, 1, { header: 'CacheStorage.delete' }) + + cacheName = webidl.converters.DOMString(cacheName) + + return this.#caches.delete(cacheName) + } + + /** + * @see https://w3c.github.io/ServiceWorker/#cache-storage-keys + * @returns {string[]} + */ + async keys () { + webidl.brandCheck(this, CacheStorage) + + // 2.1 + const keys = this.#caches.keys() + + // 2.2 + return [...keys] + } +} + +Object.defineProperties(CacheStorage.prototype, { + [Symbol.toStringTag]: { + value: 'CacheStorage', + configurable: true + }, + match: kEnumerableProperty, + has: kEnumerableProperty, + open: kEnumerableProperty, + delete: kEnumerableProperty, + keys: kEnumerableProperty +}) + +module.exports = { + CacheStorage +} + + +/***/ }), + +/***/ 80296: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +module.exports = { + kConstruct: (__nccwpck_require__(36443).kConstruct) +} + + +/***/ }), + +/***/ 23993: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const assert = __nccwpck_require__(42613) +const { URLSerializer } = __nccwpck_require__(94322) +const { isValidHeaderName } = __nccwpck_require__(15523) + +/** + * @see https://url.spec.whatwg.org/#concept-url-equals + * @param {URL} A + * @param {URL} B + * @param {boolean | undefined} excludeFragment + * @returns {boolean} + */ +function urlEquals (A, B, excludeFragment = false) { + const serializedA = URLSerializer(A, excludeFragment) + + const serializedB = URLSerializer(B, excludeFragment) + + return serializedA === serializedB +} + +/** + * @see https://github.com/chromium/chromium/blob/694d20d134cb553d8d89e5500b9148012b1ba299/content/browser/cache_storage/cache_storage_cache.cc#L260-L262 + * @param {string} header + */ +function fieldValues (header) { + assert(header !== null) + + const values = [] + + for (let value of header.split(',')) { + value = value.trim() + + if (!value.length) { + continue + } else if (!isValidHeaderName(value)) { + continue + } + + values.push(value) + } + + return values +} + +module.exports = { + urlEquals, + fieldValues +} + + +/***/ }), + +/***/ 86197: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; +// @ts-check + + + +/* global WebAssembly */ + +const assert = __nccwpck_require__(42613) +const net = __nccwpck_require__(69278) +const http = __nccwpck_require__(58611) +const { pipeline } = __nccwpck_require__(2203) +const util = __nccwpck_require__(3440) +const timers = __nccwpck_require__(28804) +const Request = __nccwpck_require__(44655) +const DispatcherBase = __nccwpck_require__(50001) +const { + RequestContentLengthMismatchError, + ResponseContentLengthMismatchError, + InvalidArgumentError, + RequestAbortedError, + HeadersTimeoutError, + HeadersOverflowError, + SocketError, + InformationalError, + BodyTimeoutError, + HTTPParserError, + ResponseExceededMaxSizeError, + ClientDestroyedError +} = __nccwpck_require__(68707) +const buildConnector = __nccwpck_require__(59136) +const { + kUrl, + kReset, + kServerName, + kClient, + kBusy, + kParser, + kConnect, + kBlocking, + kResuming, + kRunning, + kPending, + kSize, + kWriting, + kQueue, + kConnected, + kConnecting, + kNeedDrain, + kNoRef, + kKeepAliveDefaultTimeout, + kHostHeader, + kPendingIdx, + kRunningIdx, + kError, + kPipelining, + kSocket, + kKeepAliveTimeoutValue, + kMaxHeadersSize, + kKeepAliveMaxTimeout, + kKeepAliveTimeoutThreshold, + kHeadersTimeout, + kBodyTimeout, + kStrictContentLength, + kConnector, + kMaxRedirections, + kMaxRequests, + kCounter, + kClose, + kDestroy, + kDispatch, + kInterceptors, + kLocalAddress, + kMaxResponseSize, + kHTTPConnVersion, + // HTTP2 + kHost, + kHTTP2Session, + kHTTP2SessionState, + kHTTP2BuildRequest, + kHTTP2CopyHeaders, + kHTTP1BuildRequest +} = __nccwpck_require__(36443) + +/** @type {import('http2')} */ +let http2 +try { + http2 = __nccwpck_require__(85675) +} catch { + // @ts-ignore + http2 = { constants: {} } +} + +const { + constants: { + HTTP2_HEADER_AUTHORITY, + HTTP2_HEADER_METHOD, + HTTP2_HEADER_PATH, + HTTP2_HEADER_SCHEME, + HTTP2_HEADER_CONTENT_LENGTH, + HTTP2_HEADER_EXPECT, + HTTP2_HEADER_STATUS + } +} = http2 + +// Experimental +let h2ExperimentalWarned = false + +const FastBuffer = Buffer[Symbol.species] + +const kClosedResolve = Symbol('kClosedResolve') + +const channels = {} + +try { + const diagnosticsChannel = __nccwpck_require__(31637) + channels.sendHeaders = diagnosticsChannel.channel('undici:client:sendHeaders') + channels.beforeConnect = diagnosticsChannel.channel('undici:client:beforeConnect') + channels.connectError = diagnosticsChannel.channel('undici:client:connectError') + channels.connected = diagnosticsChannel.channel('undici:client:connected') +} catch { + channels.sendHeaders = { hasSubscribers: false } + channels.beforeConnect = { hasSubscribers: false } + channels.connectError = { hasSubscribers: false } + channels.connected = { hasSubscribers: false } +} + +/** + * @type {import('../types/client').default} + */ +class Client extends DispatcherBase { + /** + * + * @param {string|URL} url + * @param {import('../types/client').Client.Options} options + */ + constructor (url, { + interceptors, + maxHeaderSize, + headersTimeout, + socketTimeout, + requestTimeout, + connectTimeout, + bodyTimeout, + idleTimeout, + keepAlive, + keepAliveTimeout, + maxKeepAliveTimeout, + keepAliveMaxTimeout, + keepAliveTimeoutThreshold, + socketPath, + pipelining, + tls, + strictContentLength, + maxCachedSessions, + maxRedirections, + connect, + maxRequestsPerClient, + localAddress, + maxResponseSize, + autoSelectFamily, + autoSelectFamilyAttemptTimeout, + // h2 + allowH2, + maxConcurrentStreams + } = {}) { + super() + + if (keepAlive !== undefined) { + throw new InvalidArgumentError('unsupported keepAlive, use pipelining=0 instead') + } + + if (socketTimeout !== undefined) { + throw new InvalidArgumentError('unsupported socketTimeout, use headersTimeout & bodyTimeout instead') + } + + if (requestTimeout !== undefined) { + throw new InvalidArgumentError('unsupported requestTimeout, use headersTimeout & bodyTimeout instead') + } + + if (idleTimeout !== undefined) { + throw new InvalidArgumentError('unsupported idleTimeout, use keepAliveTimeout instead') + } + + if (maxKeepAliveTimeout !== undefined) { + throw new InvalidArgumentError('unsupported maxKeepAliveTimeout, use keepAliveMaxTimeout instead') + } + + if (maxHeaderSize != null && !Number.isFinite(maxHeaderSize)) { + throw new InvalidArgumentError('invalid maxHeaderSize') + } + + if (socketPath != null && typeof socketPath !== 'string') { + throw new InvalidArgumentError('invalid socketPath') + } + + if (connectTimeout != null && (!Number.isFinite(connectTimeout) || connectTimeout < 0)) { + throw new InvalidArgumentError('invalid connectTimeout') + } + + if (keepAliveTimeout != null && (!Number.isFinite(keepAliveTimeout) || keepAliveTimeout <= 0)) { + throw new InvalidArgumentError('invalid keepAliveTimeout') + } + + if (keepAliveMaxTimeout != null && (!Number.isFinite(keepAliveMaxTimeout) || keepAliveMaxTimeout <= 0)) { + throw new InvalidArgumentError('invalid keepAliveMaxTimeout') + } + + if (keepAliveTimeoutThreshold != null && !Number.isFinite(keepAliveTimeoutThreshold)) { + throw new InvalidArgumentError('invalid keepAliveTimeoutThreshold') + } + + if (headersTimeout != null && (!Number.isInteger(headersTimeout) || headersTimeout < 0)) { + throw new InvalidArgumentError('headersTimeout must be a positive integer or zero') + } + + if (bodyTimeout != null && (!Number.isInteger(bodyTimeout) || bodyTimeout < 0)) { + throw new InvalidArgumentError('bodyTimeout must be a positive integer or zero') + } + + if (connect != null && typeof connect !== 'function' && typeof connect !== 'object') { + throw new InvalidArgumentError('connect must be a function or an object') + } + + if (maxRedirections != null && (!Number.isInteger(maxRedirections) || maxRedirections < 0)) { + throw new InvalidArgumentError('maxRedirections must be a positive number') + } + + if (maxRequestsPerClient != null && (!Number.isInteger(maxRequestsPerClient) || maxRequestsPerClient < 0)) { + throw new InvalidArgumentError('maxRequestsPerClient must be a positive number') + } + + if (localAddress != null && (typeof localAddress !== 'string' || net.isIP(localAddress) === 0)) { + throw new InvalidArgumentError('localAddress must be valid string IP address') + } + + if (maxResponseSize != null && (!Number.isInteger(maxResponseSize) || maxResponseSize < -1)) { + throw new InvalidArgumentError('maxResponseSize must be a positive number') + } + + if ( + autoSelectFamilyAttemptTimeout != null && + (!Number.isInteger(autoSelectFamilyAttemptTimeout) || autoSelectFamilyAttemptTimeout < -1) + ) { + throw new InvalidArgumentError('autoSelectFamilyAttemptTimeout must be a positive number') + } + + // h2 + if (allowH2 != null && typeof allowH2 !== 'boolean') { + throw new InvalidArgumentError('allowH2 must be a valid boolean value') + } + + if (maxConcurrentStreams != null && (typeof maxConcurrentStreams !== 'number' || maxConcurrentStreams < 1)) { + throw new InvalidArgumentError('maxConcurrentStreams must be a possitive integer, greater than 0') + } + + if (typeof connect !== 'function') { + connect = buildConnector({ + ...tls, + maxCachedSessions, + allowH2, + socketPath, + timeout: connectTimeout, + ...(util.nodeHasAutoSelectFamily && autoSelectFamily ? { autoSelectFamily, autoSelectFamilyAttemptTimeout } : undefined), + ...connect + }) + } + + this[kInterceptors] = interceptors && interceptors.Client && Array.isArray(interceptors.Client) + ? interceptors.Client + : [createRedirectInterceptor({ maxRedirections })] + this[kUrl] = util.parseOrigin(url) + this[kConnector] = connect + this[kSocket] = null + this[kPipelining] = pipelining != null ? pipelining : 1 + this[kMaxHeadersSize] = maxHeaderSize || http.maxHeaderSize + this[kKeepAliveDefaultTimeout] = keepAliveTimeout == null ? 4e3 : keepAliveTimeout + this[kKeepAliveMaxTimeout] = keepAliveMaxTimeout == null ? 600e3 : keepAliveMaxTimeout + this[kKeepAliveTimeoutThreshold] = keepAliveTimeoutThreshold == null ? 1e3 : keepAliveTimeoutThreshold + this[kKeepAliveTimeoutValue] = this[kKeepAliveDefaultTimeout] + this[kServerName] = null + this[kLocalAddress] = localAddress != null ? localAddress : null + this[kResuming] = 0 // 0, idle, 1, scheduled, 2 resuming + this[kNeedDrain] = 0 // 0, idle, 1, scheduled, 2 resuming + this[kHostHeader] = `host: ${this[kUrl].hostname}${this[kUrl].port ? `:${this[kUrl].port}` : ''}\r\n` + this[kBodyTimeout] = bodyTimeout != null ? bodyTimeout : 300e3 + this[kHeadersTimeout] = headersTimeout != null ? headersTimeout : 300e3 + this[kStrictContentLength] = strictContentLength == null ? true : strictContentLength + this[kMaxRedirections] = maxRedirections + this[kMaxRequests] = maxRequestsPerClient + this[kClosedResolve] = null + this[kMaxResponseSize] = maxResponseSize > -1 ? maxResponseSize : -1 + this[kHTTPConnVersion] = 'h1' + + // HTTP/2 + this[kHTTP2Session] = null + this[kHTTP2SessionState] = !allowH2 + ? null + : { + // streams: null, // Fixed queue of streams - For future support of `push` + openStreams: 0, // Keep track of them to decide wether or not unref the session + maxConcurrentStreams: maxConcurrentStreams != null ? maxConcurrentStreams : 100 // Max peerConcurrentStreams for a Node h2 server + } + this[kHost] = `${this[kUrl].hostname}${this[kUrl].port ? `:${this[kUrl].port}` : ''}` + + // kQueue is built up of 3 sections separated by + // the kRunningIdx and kPendingIdx indices. + // | complete | running | pending | + // ^ kRunningIdx ^ kPendingIdx ^ kQueue.length + // kRunningIdx points to the first running element. + // kPendingIdx points to the first pending element. + // This implements a fast queue with an amortized + // time of O(1). + + this[kQueue] = [] + this[kRunningIdx] = 0 + this[kPendingIdx] = 0 + } + + get pipelining () { + return this[kPipelining] + } + + set pipelining (value) { + this[kPipelining] = value + resume(this, true) + } + + get [kPending] () { + return this[kQueue].length - this[kPendingIdx] + } + + get [kRunning] () { + return this[kPendingIdx] - this[kRunningIdx] + } + + get [kSize] () { + return this[kQueue].length - this[kRunningIdx] + } + + get [kConnected] () { + return !!this[kSocket] && !this[kConnecting] && !this[kSocket].destroyed + } + + get [kBusy] () { + const socket = this[kSocket] + return ( + (socket && (socket[kReset] || socket[kWriting] || socket[kBlocking])) || + (this[kSize] >= (this[kPipelining] || 1)) || + this[kPending] > 0 + ) + } + + /* istanbul ignore: only used for test */ + [kConnect] (cb) { + connect(this) + this.once('connect', cb) + } + + [kDispatch] (opts, handler) { + const origin = opts.origin || this[kUrl].origin + + const request = this[kHTTPConnVersion] === 'h2' + ? Request[kHTTP2BuildRequest](origin, opts, handler) + : Request[kHTTP1BuildRequest](origin, opts, handler) + + this[kQueue].push(request) + if (this[kResuming]) { + // Do nothing. + } else if (util.bodyLength(request.body) == null && util.isIterable(request.body)) { + // Wait a tick in case stream/iterator is ended in the same tick. + this[kResuming] = 1 + process.nextTick(resume, this) + } else { + resume(this, true) + } + + if (this[kResuming] && this[kNeedDrain] !== 2 && this[kBusy]) { + this[kNeedDrain] = 2 + } + + return this[kNeedDrain] < 2 + } + + async [kClose] () { + // TODO: for H2 we need to gracefully flush the remaining enqueued + // request and close each stream. + return new Promise((resolve) => { + if (!this[kSize]) { + resolve(null) + } else { + this[kClosedResolve] = resolve + } + }) + } + + async [kDestroy] (err) { + return new Promise((resolve) => { + const requests = this[kQueue].splice(this[kPendingIdx]) + for (let i = 0; i < requests.length; i++) { + const request = requests[i] + errorRequest(this, request, err) + } + + const callback = () => { + if (this[kClosedResolve]) { + // TODO (fix): Should we error here with ClientDestroyedError? + this[kClosedResolve]() + this[kClosedResolve] = null + } + resolve() + } + + if (this[kHTTP2Session] != null) { + util.destroy(this[kHTTP2Session], err) + this[kHTTP2Session] = null + this[kHTTP2SessionState] = null + } + + if (!this[kSocket]) { + queueMicrotask(callback) + } else { + util.destroy(this[kSocket].on('close', callback), err) + } + + resume(this) + }) + } +} + +function onHttp2SessionError (err) { + assert(err.code !== 'ERR_TLS_CERT_ALTNAME_INVALID') + + this[kSocket][kError] = err + + onError(this[kClient], err) +} + +function onHttp2FrameError (type, code, id) { + const err = new InformationalError(`HTTP/2: "frameError" received - type ${type}, code ${code}`) + + if (id === 0) { + this[kSocket][kError] = err + onError(this[kClient], err) + } +} + +function onHttp2SessionEnd () { + util.destroy(this, new SocketError('other side closed')) + util.destroy(this[kSocket], new SocketError('other side closed')) +} + +function onHTTP2GoAway (code) { + const client = this[kClient] + const err = new InformationalError(`HTTP/2: "GOAWAY" frame received with code ${code}`) + client[kSocket] = null + client[kHTTP2Session] = null + + if (client.destroyed) { + assert(this[kPending] === 0) + + // Fail entire queue. + const requests = client[kQueue].splice(client[kRunningIdx]) + for (let i = 0; i < requests.length; i++) { + const request = requests[i] + errorRequest(this, request, err) + } + } else if (client[kRunning] > 0) { + // Fail head of pipeline. + const request = client[kQueue][client[kRunningIdx]] + client[kQueue][client[kRunningIdx]++] = null + + errorRequest(client, request, err) + } + + client[kPendingIdx] = client[kRunningIdx] + + assert(client[kRunning] === 0) + + client.emit('disconnect', + client[kUrl], + [client], + err + ) + + resume(client) +} + +const constants = __nccwpck_require__(52824) +const createRedirectInterceptor = __nccwpck_require__(64415) +const EMPTY_BUF = Buffer.alloc(0) + +async function lazyllhttp () { + const llhttpWasmData = process.env.JEST_WORKER_ID ? __nccwpck_require__(63870) : undefined + + let mod + try { + mod = await WebAssembly.compile(Buffer.from(__nccwpck_require__(53434), 'base64')) + } catch (e) { + /* istanbul ignore next */ + + // We could check if the error was caused by the simd option not + // being enabled, but the occurring of this other error + // * https://github.com/emscripten-core/emscripten/issues/11495 + // got me to remove that check to avoid breaking Node 12. + mod = await WebAssembly.compile(Buffer.from(llhttpWasmData || __nccwpck_require__(63870), 'base64')) + } + + return await WebAssembly.instantiate(mod, { + env: { + /* eslint-disable camelcase */ + + wasm_on_url: (p, at, len) => { + /* istanbul ignore next */ + return 0 + }, + wasm_on_status: (p, at, len) => { + assert.strictEqual(currentParser.ptr, p) + const start = at - currentBufferPtr + currentBufferRef.byteOffset + return currentParser.onStatus(new FastBuffer(currentBufferRef.buffer, start, len)) || 0 + }, + wasm_on_message_begin: (p) => { + assert.strictEqual(currentParser.ptr, p) + return currentParser.onMessageBegin() || 0 + }, + wasm_on_header_field: (p, at, len) => { + assert.strictEqual(currentParser.ptr, p) + const start = at - currentBufferPtr + currentBufferRef.byteOffset + return currentParser.onHeaderField(new FastBuffer(currentBufferRef.buffer, start, len)) || 0 + }, + wasm_on_header_value: (p, at, len) => { + assert.strictEqual(currentParser.ptr, p) + const start = at - currentBufferPtr + currentBufferRef.byteOffset + return currentParser.onHeaderValue(new FastBuffer(currentBufferRef.buffer, start, len)) || 0 + }, + wasm_on_headers_complete: (p, statusCode, upgrade, shouldKeepAlive) => { + assert.strictEqual(currentParser.ptr, p) + return currentParser.onHeadersComplete(statusCode, Boolean(upgrade), Boolean(shouldKeepAlive)) || 0 + }, + wasm_on_body: (p, at, len) => { + assert.strictEqual(currentParser.ptr, p) + const start = at - currentBufferPtr + currentBufferRef.byteOffset + return currentParser.onBody(new FastBuffer(currentBufferRef.buffer, start, len)) || 0 + }, + wasm_on_message_complete: (p) => { + assert.strictEqual(currentParser.ptr, p) + return currentParser.onMessageComplete() || 0 + } + + /* eslint-enable camelcase */ + } + }) +} + +let llhttpInstance = null +let llhttpPromise = lazyllhttp() +llhttpPromise.catch() + +let currentParser = null +let currentBufferRef = null +let currentBufferSize = 0 +let currentBufferPtr = null + +const TIMEOUT_HEADERS = 1 +const TIMEOUT_BODY = 2 +const TIMEOUT_IDLE = 3 + +class Parser { + constructor (client, socket, { exports }) { + assert(Number.isFinite(client[kMaxHeadersSize]) && client[kMaxHeadersSize] > 0) + + this.llhttp = exports + this.ptr = this.llhttp.llhttp_alloc(constants.TYPE.RESPONSE) + this.client = client + this.socket = socket + this.timeout = null + this.timeoutValue = null + this.timeoutType = null + this.statusCode = null + this.statusText = '' + this.upgrade = false + this.headers = [] + this.headersSize = 0 + this.headersMaxSize = client[kMaxHeadersSize] + this.shouldKeepAlive = false + this.paused = false + this.resume = this.resume.bind(this) + + this.bytesRead = 0 + + this.keepAlive = '' + this.contentLength = '' + this.connection = '' + this.maxResponseSize = client[kMaxResponseSize] + } + + setTimeout (value, type) { + this.timeoutType = type + if (value !== this.timeoutValue) { + timers.clearTimeout(this.timeout) + if (value) { + this.timeout = timers.setTimeout(onParserTimeout, value, this) + // istanbul ignore else: only for jest + if (this.timeout.unref) { + this.timeout.unref() + } + } else { + this.timeout = null + } + this.timeoutValue = value + } else if (this.timeout) { + // istanbul ignore else: only for jest + if (this.timeout.refresh) { + this.timeout.refresh() + } + } + } + + resume () { + if (this.socket.destroyed || !this.paused) { + return + } + + assert(this.ptr != null) + assert(currentParser == null) + + this.llhttp.llhttp_resume(this.ptr) + + assert(this.timeoutType === TIMEOUT_BODY) + if (this.timeout) { + // istanbul ignore else: only for jest + if (this.timeout.refresh) { + this.timeout.refresh() + } + } + + this.paused = false + this.execute(this.socket.read() || EMPTY_BUF) // Flush parser. + this.readMore() + } + + readMore () { + while (!this.paused && this.ptr) { + const chunk = this.socket.read() + if (chunk === null) { + break + } + this.execute(chunk) + } + } + + execute (data) { + assert(this.ptr != null) + assert(currentParser == null) + assert(!this.paused) + + const { socket, llhttp } = this + + if (data.length > currentBufferSize) { + if (currentBufferPtr) { + llhttp.free(currentBufferPtr) + } + currentBufferSize = Math.ceil(data.length / 4096) * 4096 + currentBufferPtr = llhttp.malloc(currentBufferSize) + } + + new Uint8Array(llhttp.memory.buffer, currentBufferPtr, currentBufferSize).set(data) + + // Call `execute` on the wasm parser. + // We pass the `llhttp_parser` pointer address, the pointer address of buffer view data, + // and finally the length of bytes to parse. + // The return value is an error code or `constants.ERROR.OK`. + try { + let ret + + try { + currentBufferRef = data + currentParser = this + ret = llhttp.llhttp_execute(this.ptr, currentBufferPtr, data.length) + /* eslint-disable-next-line no-useless-catch */ + } catch (err) { + /* istanbul ignore next: difficult to make a test case for */ + throw err + } finally { + currentParser = null + currentBufferRef = null + } + + const offset = llhttp.llhttp_get_error_pos(this.ptr) - currentBufferPtr + + if (ret === constants.ERROR.PAUSED_UPGRADE) { + this.onUpgrade(data.slice(offset)) + } else if (ret === constants.ERROR.PAUSED) { + this.paused = true + socket.unshift(data.slice(offset)) + } else if (ret !== constants.ERROR.OK) { + const ptr = llhttp.llhttp_get_error_reason(this.ptr) + let message = '' + /* istanbul ignore else: difficult to make a test case for */ + if (ptr) { + const len = new Uint8Array(llhttp.memory.buffer, ptr).indexOf(0) + message = + 'Response does not match the HTTP/1.1 protocol (' + + Buffer.from(llhttp.memory.buffer, ptr, len).toString() + + ')' + } + throw new HTTPParserError(message, constants.ERROR[ret], data.slice(offset)) + } + } catch (err) { + util.destroy(socket, err) + } + } + + destroy () { + assert(this.ptr != null) + assert(currentParser == null) + + this.llhttp.llhttp_free(this.ptr) + this.ptr = null + + timers.clearTimeout(this.timeout) + this.timeout = null + this.timeoutValue = null + this.timeoutType = null + + this.paused = false + } + + onStatus (buf) { + this.statusText = buf.toString() + } + + onMessageBegin () { + const { socket, client } = this + + /* istanbul ignore next: difficult to make a test case for */ + if (socket.destroyed) { + return -1 + } + + const request = client[kQueue][client[kRunningIdx]] + if (!request) { + return -1 + } + } + + onHeaderField (buf) { + const len = this.headers.length + + if ((len & 1) === 0) { + this.headers.push(buf) + } else { + this.headers[len - 1] = Buffer.concat([this.headers[len - 1], buf]) + } + + this.trackHeader(buf.length) + } + + onHeaderValue (buf) { + let len = this.headers.length + + if ((len & 1) === 1) { + this.headers.push(buf) + len += 1 + } else { + this.headers[len - 1] = Buffer.concat([this.headers[len - 1], buf]) + } + + const key = this.headers[len - 2] + if (key.length === 10 && key.toString().toLowerCase() === 'keep-alive') { + this.keepAlive += buf.toString() + } else if (key.length === 10 && key.toString().toLowerCase() === 'connection') { + this.connection += buf.toString() + } else if (key.length === 14 && key.toString().toLowerCase() === 'content-length') { + this.contentLength += buf.toString() + } + + this.trackHeader(buf.length) + } + + trackHeader (len) { + this.headersSize += len + if (this.headersSize >= this.headersMaxSize) { + util.destroy(this.socket, new HeadersOverflowError()) + } + } + + onUpgrade (head) { + const { upgrade, client, socket, headers, statusCode } = this + + assert(upgrade) + + const request = client[kQueue][client[kRunningIdx]] + assert(request) + + assert(!socket.destroyed) + assert(socket === client[kSocket]) + assert(!this.paused) + assert(request.upgrade || request.method === 'CONNECT') + + this.statusCode = null + this.statusText = '' + this.shouldKeepAlive = null + + assert(this.headers.length % 2 === 0) + this.headers = [] + this.headersSize = 0 + + socket.unshift(head) + + socket[kParser].destroy() + socket[kParser] = null + + socket[kClient] = null + socket[kError] = null + socket + .removeListener('error', onSocketError) + .removeListener('readable', onSocketReadable) + .removeListener('end', onSocketEnd) + .removeListener('close', onSocketClose) + + client[kSocket] = null + client[kQueue][client[kRunningIdx]++] = null + client.emit('disconnect', client[kUrl], [client], new InformationalError('upgrade')) + + try { + request.onUpgrade(statusCode, headers, socket) + } catch (err) { + util.destroy(socket, err) + } + + resume(client) + } + + onHeadersComplete (statusCode, upgrade, shouldKeepAlive) { + const { client, socket, headers, statusText } = this + + /* istanbul ignore next: difficult to make a test case for */ + if (socket.destroyed) { + return -1 + } + + const request = client[kQueue][client[kRunningIdx]] + + /* istanbul ignore next: difficult to make a test case for */ + if (!request) { + return -1 + } + + assert(!this.upgrade) + assert(this.statusCode < 200) + + if (statusCode === 100) { + util.destroy(socket, new SocketError('bad response', util.getSocketInfo(socket))) + return -1 + } + + /* this can only happen if server is misbehaving */ + if (upgrade && !request.upgrade) { + util.destroy(socket, new SocketError('bad upgrade', util.getSocketInfo(socket))) + return -1 + } + + assert.strictEqual(this.timeoutType, TIMEOUT_HEADERS) + + this.statusCode = statusCode + this.shouldKeepAlive = ( + shouldKeepAlive || + // Override llhttp value which does not allow keepAlive for HEAD. + (request.method === 'HEAD' && !socket[kReset] && this.connection.toLowerCase() === 'keep-alive') + ) + + if (this.statusCode >= 200) { + const bodyTimeout = request.bodyTimeout != null + ? request.bodyTimeout + : client[kBodyTimeout] + this.setTimeout(bodyTimeout, TIMEOUT_BODY) + } else if (this.timeout) { + // istanbul ignore else: only for jest + if (this.timeout.refresh) { + this.timeout.refresh() + } + } + + if (request.method === 'CONNECT') { + assert(client[kRunning] === 1) + this.upgrade = true + return 2 + } + + if (upgrade) { + assert(client[kRunning] === 1) + this.upgrade = true + return 2 + } + + assert(this.headers.length % 2 === 0) + this.headers = [] + this.headersSize = 0 + + if (this.shouldKeepAlive && client[kPipelining]) { + const keepAliveTimeout = this.keepAlive ? util.parseKeepAliveTimeout(this.keepAlive) : null + + if (keepAliveTimeout != null) { + const timeout = Math.min( + keepAliveTimeout - client[kKeepAliveTimeoutThreshold], + client[kKeepAliveMaxTimeout] + ) + if (timeout <= 0) { + socket[kReset] = true + } else { + client[kKeepAliveTimeoutValue] = timeout + } + } else { + client[kKeepAliveTimeoutValue] = client[kKeepAliveDefaultTimeout] + } + } else { + // Stop more requests from being dispatched. + socket[kReset] = true + } + + const pause = request.onHeaders(statusCode, headers, this.resume, statusText) === false + + if (request.aborted) { + return -1 + } + + if (request.method === 'HEAD') { + return 1 + } + + if (statusCode < 200) { + return 1 + } + + if (socket[kBlocking]) { + socket[kBlocking] = false + resume(client) + } + + return pause ? constants.ERROR.PAUSED : 0 + } + + onBody (buf) { + const { client, socket, statusCode, maxResponseSize } = this + + if (socket.destroyed) { + return -1 + } + + const request = client[kQueue][client[kRunningIdx]] + assert(request) + + assert.strictEqual(this.timeoutType, TIMEOUT_BODY) + if (this.timeout) { + // istanbul ignore else: only for jest + if (this.timeout.refresh) { + this.timeout.refresh() + } + } + + assert(statusCode >= 200) + + if (maxResponseSize > -1 && this.bytesRead + buf.length > maxResponseSize) { + util.destroy(socket, new ResponseExceededMaxSizeError()) + return -1 + } + + this.bytesRead += buf.length + + if (request.onData(buf) === false) { + return constants.ERROR.PAUSED + } + } + + onMessageComplete () { + const { client, socket, statusCode, upgrade, headers, contentLength, bytesRead, shouldKeepAlive } = this + + if (socket.destroyed && (!statusCode || shouldKeepAlive)) { + return -1 + } + + if (upgrade) { + return + } + + const request = client[kQueue][client[kRunningIdx]] + assert(request) + + assert(statusCode >= 100) + + this.statusCode = null + this.statusText = '' + this.bytesRead = 0 + this.contentLength = '' + this.keepAlive = '' + this.connection = '' + + assert(this.headers.length % 2 === 0) + this.headers = [] + this.headersSize = 0 + + if (statusCode < 200) { + return + } + + /* istanbul ignore next: should be handled by llhttp? */ + if (request.method !== 'HEAD' && contentLength && bytesRead !== parseInt(contentLength, 10)) { + util.destroy(socket, new ResponseContentLengthMismatchError()) + return -1 + } + + request.onComplete(headers) + + client[kQueue][client[kRunningIdx]++] = null + + if (socket[kWriting]) { + assert.strictEqual(client[kRunning], 0) + // Response completed before request. + util.destroy(socket, new InformationalError('reset')) + return constants.ERROR.PAUSED + } else if (!shouldKeepAlive) { + util.destroy(socket, new InformationalError('reset')) + return constants.ERROR.PAUSED + } else if (socket[kReset] && client[kRunning] === 0) { + // Destroy socket once all requests have completed. + // The request at the tail of the pipeline is the one + // that requested reset and no further requests should + // have been queued since then. + util.destroy(socket, new InformationalError('reset')) + return constants.ERROR.PAUSED + } else if (client[kPipelining] === 1) { + // We must wait a full event loop cycle to reuse this socket to make sure + // that non-spec compliant servers are not closing the connection even if they + // said they won't. + setImmediate(resume, client) + } else { + resume(client) + } + } +} + +function onParserTimeout (parser) { + const { socket, timeoutType, client } = parser + + /* istanbul ignore else */ + if (timeoutType === TIMEOUT_HEADERS) { + if (!socket[kWriting] || socket.writableNeedDrain || client[kRunning] > 1) { + assert(!parser.paused, 'cannot be paused while waiting for headers') + util.destroy(socket, new HeadersTimeoutError()) + } + } else if (timeoutType === TIMEOUT_BODY) { + if (!parser.paused) { + util.destroy(socket, new BodyTimeoutError()) + } + } else if (timeoutType === TIMEOUT_IDLE) { + assert(client[kRunning] === 0 && client[kKeepAliveTimeoutValue]) + util.destroy(socket, new InformationalError('socket idle timeout')) + } +} + +function onSocketReadable () { + const { [kParser]: parser } = this + if (parser) { + parser.readMore() + } +} + +function onSocketError (err) { + const { [kClient]: client, [kParser]: parser } = this + + assert(err.code !== 'ERR_TLS_CERT_ALTNAME_INVALID') + + if (client[kHTTPConnVersion] !== 'h2') { + // On Mac OS, we get an ECONNRESET even if there is a full body to be forwarded + // to the user. + if (err.code === 'ECONNRESET' && parser.statusCode && !parser.shouldKeepAlive) { + // We treat all incoming data so for as a valid response. + parser.onMessageComplete() + return + } + } + + this[kError] = err + + onError(this[kClient], err) +} + +function onError (client, err) { + if ( + client[kRunning] === 0 && + err.code !== 'UND_ERR_INFO' && + err.code !== 'UND_ERR_SOCKET' + ) { + // Error is not caused by running request and not a recoverable + // socket error. + + assert(client[kPendingIdx] === client[kRunningIdx]) + + const requests = client[kQueue].splice(client[kRunningIdx]) + for (let i = 0; i < requests.length; i++) { + const request = requests[i] + errorRequest(client, request, err) + } + assert(client[kSize] === 0) + } +} + +function onSocketEnd () { + const { [kParser]: parser, [kClient]: client } = this + + if (client[kHTTPConnVersion] !== 'h2') { + if (parser.statusCode && !parser.shouldKeepAlive) { + // We treat all incoming data so far as a valid response. + parser.onMessageComplete() + return + } + } + + util.destroy(this, new SocketError('other side closed', util.getSocketInfo(this))) +} + +function onSocketClose () { + const { [kClient]: client, [kParser]: parser } = this + + if (client[kHTTPConnVersion] === 'h1' && parser) { + if (!this[kError] && parser.statusCode && !parser.shouldKeepAlive) { + // We treat all incoming data so far as a valid response. + parser.onMessageComplete() + } + + this[kParser].destroy() + this[kParser] = null + } + + const err = this[kError] || new SocketError('closed', util.getSocketInfo(this)) + + client[kSocket] = null + + if (client.destroyed) { + assert(client[kPending] === 0) + + // Fail entire queue. + const requests = client[kQueue].splice(client[kRunningIdx]) + for (let i = 0; i < requests.length; i++) { + const request = requests[i] + errorRequest(client, request, err) + } + } else if (client[kRunning] > 0 && err.code !== 'UND_ERR_INFO') { + // Fail head of pipeline. + const request = client[kQueue][client[kRunningIdx]] + client[kQueue][client[kRunningIdx]++] = null + + errorRequest(client, request, err) + } + + client[kPendingIdx] = client[kRunningIdx] + + assert(client[kRunning] === 0) + + client.emit('disconnect', client[kUrl], [client], err) + + resume(client) +} + +async function connect (client) { + assert(!client[kConnecting]) + assert(!client[kSocket]) + + let { host, hostname, protocol, port } = client[kUrl] + + // Resolve ipv6 + if (hostname[0] === '[') { + const idx = hostname.indexOf(']') + + assert(idx !== -1) + const ip = hostname.substring(1, idx) + + assert(net.isIP(ip)) + hostname = ip + } + + client[kConnecting] = true + + if (channels.beforeConnect.hasSubscribers) { + channels.beforeConnect.publish({ + connectParams: { + host, + hostname, + protocol, + port, + servername: client[kServerName], + localAddress: client[kLocalAddress] + }, + connector: client[kConnector] + }) + } + + try { + const socket = await new Promise((resolve, reject) => { + client[kConnector]({ + host, + hostname, + protocol, + port, + servername: client[kServerName], + localAddress: client[kLocalAddress] + }, (err, socket) => { + if (err) { + reject(err) + } else { + resolve(socket) + } + }) + }) + + if (client.destroyed) { + util.destroy(socket.on('error', () => {}), new ClientDestroyedError()) + return + } + + client[kConnecting] = false + + assert(socket) + + const isH2 = socket.alpnProtocol === 'h2' + if (isH2) { + if (!h2ExperimentalWarned) { + h2ExperimentalWarned = true + process.emitWarning('H2 support is experimental, expect them to change at any time.', { + code: 'UNDICI-H2' + }) + } + + const session = http2.connect(client[kUrl], { + createConnection: () => socket, + peerMaxConcurrentStreams: client[kHTTP2SessionState].maxConcurrentStreams + }) + + client[kHTTPConnVersion] = 'h2' + session[kClient] = client + session[kSocket] = socket + session.on('error', onHttp2SessionError) + session.on('frameError', onHttp2FrameError) + session.on('end', onHttp2SessionEnd) + session.on('goaway', onHTTP2GoAway) + session.on('close', onSocketClose) + session.unref() + + client[kHTTP2Session] = session + socket[kHTTP2Session] = session + } else { + if (!llhttpInstance) { + llhttpInstance = await llhttpPromise + llhttpPromise = null + } + + socket[kNoRef] = false + socket[kWriting] = false + socket[kReset] = false + socket[kBlocking] = false + socket[kParser] = new Parser(client, socket, llhttpInstance) + } + + socket[kCounter] = 0 + socket[kMaxRequests] = client[kMaxRequests] + socket[kClient] = client + socket[kError] = null + + socket + .on('error', onSocketError) + .on('readable', onSocketReadable) + .on('end', onSocketEnd) + .on('close', onSocketClose) + + client[kSocket] = socket + + if (channels.connected.hasSubscribers) { + channels.connected.publish({ + connectParams: { + host, + hostname, + protocol, + port, + servername: client[kServerName], + localAddress: client[kLocalAddress] + }, + connector: client[kConnector], + socket + }) + } + client.emit('connect', client[kUrl], [client]) + } catch (err) { + if (client.destroyed) { + return + } + + client[kConnecting] = false + + if (channels.connectError.hasSubscribers) { + channels.connectError.publish({ + connectParams: { + host, + hostname, + protocol, + port, + servername: client[kServerName], + localAddress: client[kLocalAddress] + }, + connector: client[kConnector], + error: err + }) + } + + if (err.code === 'ERR_TLS_CERT_ALTNAME_INVALID') { + assert(client[kRunning] === 0) + while (client[kPending] > 0 && client[kQueue][client[kPendingIdx]].servername === client[kServerName]) { + const request = client[kQueue][client[kPendingIdx]++] + errorRequest(client, request, err) + } + } else { + onError(client, err) + } + + client.emit('connectionError', client[kUrl], [client], err) + } + + resume(client) +} + +function emitDrain (client) { + client[kNeedDrain] = 0 + client.emit('drain', client[kUrl], [client]) +} + +function resume (client, sync) { + if (client[kResuming] === 2) { + return + } + + client[kResuming] = 2 + + _resume(client, sync) + client[kResuming] = 0 + + if (client[kRunningIdx] > 256) { + client[kQueue].splice(0, client[kRunningIdx]) + client[kPendingIdx] -= client[kRunningIdx] + client[kRunningIdx] = 0 + } +} + +function _resume (client, sync) { + while (true) { + if (client.destroyed) { + assert(client[kPending] === 0) + return + } + + if (client[kClosedResolve] && !client[kSize]) { + client[kClosedResolve]() + client[kClosedResolve] = null + return + } + + const socket = client[kSocket] + + if (socket && !socket.destroyed && socket.alpnProtocol !== 'h2') { + if (client[kSize] === 0) { + if (!socket[kNoRef] && socket.unref) { + socket.unref() + socket[kNoRef] = true + } + } else if (socket[kNoRef] && socket.ref) { + socket.ref() + socket[kNoRef] = false + } + + if (client[kSize] === 0) { + if (socket[kParser].timeoutType !== TIMEOUT_IDLE) { + socket[kParser].setTimeout(client[kKeepAliveTimeoutValue], TIMEOUT_IDLE) + } + } else if (client[kRunning] > 0 && socket[kParser].statusCode < 200) { + if (socket[kParser].timeoutType !== TIMEOUT_HEADERS) { + const request = client[kQueue][client[kRunningIdx]] + const headersTimeout = request.headersTimeout != null + ? request.headersTimeout + : client[kHeadersTimeout] + socket[kParser].setTimeout(headersTimeout, TIMEOUT_HEADERS) + } + } + } + + if (client[kBusy]) { + client[kNeedDrain] = 2 + } else if (client[kNeedDrain] === 2) { + if (sync) { + client[kNeedDrain] = 1 + process.nextTick(emitDrain, client) + } else { + emitDrain(client) + } + continue + } + + if (client[kPending] === 0) { + return + } + + if (client[kRunning] >= (client[kPipelining] || 1)) { + return + } + + const request = client[kQueue][client[kPendingIdx]] + + if (client[kUrl].protocol === 'https:' && client[kServerName] !== request.servername) { + if (client[kRunning] > 0) { + return + } + + client[kServerName] = request.servername + + if (socket && socket.servername !== request.servername) { + util.destroy(socket, new InformationalError('servername changed')) + return + } + } + + if (client[kConnecting]) { + return + } + + if (!socket && !client[kHTTP2Session]) { + connect(client) + return + } + + if (socket.destroyed || socket[kWriting] || socket[kReset] || socket[kBlocking]) { + return + } + + if (client[kRunning] > 0 && !request.idempotent) { + // Non-idempotent request cannot be retried. + // Ensure that no other requests are inflight and + // could cause failure. + return + } + + if (client[kRunning] > 0 && (request.upgrade || request.method === 'CONNECT')) { + // Don't dispatch an upgrade until all preceding requests have completed. + // A misbehaving server might upgrade the connection before all pipelined + // request has completed. + return + } + + if (client[kRunning] > 0 && util.bodyLength(request.body) !== 0 && + (util.isStream(request.body) || util.isAsyncIterable(request.body))) { + // Request with stream or iterator body can error while other requests + // are inflight and indirectly error those as well. + // Ensure this doesn't happen by waiting for inflight + // to complete before dispatching. + + // Request with stream or iterator body cannot be retried. + // Ensure that no other requests are inflight and + // could cause failure. + return + } + + if (!request.aborted && write(client, request)) { + client[kPendingIdx]++ + } else { + client[kQueue].splice(client[kPendingIdx], 1) + } + } +} + +// https://www.rfc-editor.org/rfc/rfc7230#section-3.3.2 +function shouldSendContentLength (method) { + return method !== 'GET' && method !== 'HEAD' && method !== 'OPTIONS' && method !== 'TRACE' && method !== 'CONNECT' +} + +function write (client, request) { + if (client[kHTTPConnVersion] === 'h2') { + writeH2(client, client[kHTTP2Session], request) + return + } + + const { body, method, path, host, upgrade, headers, blocking, reset } = request + + // https://tools.ietf.org/html/rfc7231#section-4.3.1 + // https://tools.ietf.org/html/rfc7231#section-4.3.2 + // https://tools.ietf.org/html/rfc7231#section-4.3.5 + + // Sending a payload body on a request that does not + // expect it can cause undefined behavior on some + // servers and corrupt connection state. Do not + // re-use the connection for further requests. + + const expectsPayload = ( + method === 'PUT' || + method === 'POST' || + method === 'PATCH' + ) + + if (body && typeof body.read === 'function') { + // Try to read EOF in order to get length. + body.read(0) + } + + const bodyLength = util.bodyLength(body) + + let contentLength = bodyLength + + if (contentLength === null) { + contentLength = request.contentLength + } + + if (contentLength === 0 && !expectsPayload) { + // https://tools.ietf.org/html/rfc7230#section-3.3.2 + // A user agent SHOULD NOT send a Content-Length header field when + // the request message does not contain a payload body and the method + // semantics do not anticipate such a body. + + contentLength = null + } + + // https://github.com/nodejs/undici/issues/2046 + // A user agent may send a Content-Length header with 0 value, this should be allowed. + if (shouldSendContentLength(method) && contentLength > 0 && request.contentLength !== null && request.contentLength !== contentLength) { + if (client[kStrictContentLength]) { + errorRequest(client, request, new RequestContentLengthMismatchError()) + return false + } + + process.emitWarning(new RequestContentLengthMismatchError()) + } + + const socket = client[kSocket] + + try { + request.onConnect((err) => { + if (request.aborted || request.completed) { + return + } + + errorRequest(client, request, err || new RequestAbortedError()) + + util.destroy(socket, new InformationalError('aborted')) + }) + } catch (err) { + errorRequest(client, request, err) + } + + if (request.aborted) { + return false + } + + if (method === 'HEAD') { + // https://github.com/mcollina/undici/issues/258 + // Close after a HEAD request to interop with misbehaving servers + // that may send a body in the response. + + socket[kReset] = true + } + + if (upgrade || method === 'CONNECT') { + // On CONNECT or upgrade, block pipeline from dispatching further + // requests on this connection. + + socket[kReset] = true + } + + if (reset != null) { + socket[kReset] = reset + } + + if (client[kMaxRequests] && socket[kCounter]++ >= client[kMaxRequests]) { + socket[kReset] = true + } + + if (blocking) { + socket[kBlocking] = true + } + + let header = `${method} ${path} HTTP/1.1\r\n` + + if (typeof host === 'string') { + header += `host: ${host}\r\n` + } else { + header += client[kHostHeader] + } + + if (upgrade) { + header += `connection: upgrade\r\nupgrade: ${upgrade}\r\n` + } else if (client[kPipelining] && !socket[kReset]) { + header += 'connection: keep-alive\r\n' + } else { + header += 'connection: close\r\n' + } + + if (headers) { + header += headers + } + + if (channels.sendHeaders.hasSubscribers) { + channels.sendHeaders.publish({ request, headers: header, socket }) + } + + /* istanbul ignore else: assertion */ + if (!body || bodyLength === 0) { + if (contentLength === 0) { + socket.write(`${header}content-length: 0\r\n\r\n`, 'latin1') + } else { + assert(contentLength === null, 'no body must not have content length') + socket.write(`${header}\r\n`, 'latin1') + } + request.onRequestSent() + } else if (util.isBuffer(body)) { + assert(contentLength === body.byteLength, 'buffer body must have content length') + + socket.cork() + socket.write(`${header}content-length: ${contentLength}\r\n\r\n`, 'latin1') + socket.write(body) + socket.uncork() + request.onBodySent(body) + request.onRequestSent() + if (!expectsPayload) { + socket[kReset] = true + } + } else if (util.isBlobLike(body)) { + if (typeof body.stream === 'function') { + writeIterable({ body: body.stream(), client, request, socket, contentLength, header, expectsPayload }) + } else { + writeBlob({ body, client, request, socket, contentLength, header, expectsPayload }) + } + } else if (util.isStream(body)) { + writeStream({ body, client, request, socket, contentLength, header, expectsPayload }) + } else if (util.isIterable(body)) { + writeIterable({ body, client, request, socket, contentLength, header, expectsPayload }) + } else { + assert(false) + } + + return true +} + +function writeH2 (client, session, request) { + const { body, method, path, host, upgrade, expectContinue, signal, headers: reqHeaders } = request + + let headers + if (typeof reqHeaders === 'string') headers = Request[kHTTP2CopyHeaders](reqHeaders.trim()) + else headers = reqHeaders + + if (upgrade) { + errorRequest(client, request, new Error('Upgrade not supported for H2')) + return false + } + + try { + // TODO(HTTP/2): Should we call onConnect immediately or on stream ready event? + request.onConnect((err) => { + if (request.aborted || request.completed) { + return + } + + errorRequest(client, request, err || new RequestAbortedError()) + }) + } catch (err) { + errorRequest(client, request, err) + } + + if (request.aborted) { + return false + } + + /** @type {import('node:http2').ClientHttp2Stream} */ + let stream + const h2State = client[kHTTP2SessionState] + + headers[HTTP2_HEADER_AUTHORITY] = host || client[kHost] + headers[HTTP2_HEADER_METHOD] = method + + if (method === 'CONNECT') { + session.ref() + // we are already connected, streams are pending, first request + // will create a new stream. We trigger a request to create the stream and wait until + // `ready` event is triggered + // We disabled endStream to allow the user to write to the stream + stream = session.request(headers, { endStream: false, signal }) + + if (stream.id && !stream.pending) { + request.onUpgrade(null, null, stream) + ++h2State.openStreams + } else { + stream.once('ready', () => { + request.onUpgrade(null, null, stream) + ++h2State.openStreams + }) + } + + stream.once('close', () => { + h2State.openStreams -= 1 + // TODO(HTTP/2): unref only if current streams count is 0 + if (h2State.openStreams === 0) session.unref() + }) + + return true + } + + // https://tools.ietf.org/html/rfc7540#section-8.3 + // :path and :scheme headers must be omited when sending CONNECT + + headers[HTTP2_HEADER_PATH] = path + headers[HTTP2_HEADER_SCHEME] = 'https' + + // https://tools.ietf.org/html/rfc7231#section-4.3.1 + // https://tools.ietf.org/html/rfc7231#section-4.3.2 + // https://tools.ietf.org/html/rfc7231#section-4.3.5 + + // Sending a payload body on a request that does not + // expect it can cause undefined behavior on some + // servers and corrupt connection state. Do not + // re-use the connection for further requests. + + const expectsPayload = ( + method === 'PUT' || + method === 'POST' || + method === 'PATCH' + ) + + if (body && typeof body.read === 'function') { + // Try to read EOF in order to get length. + body.read(0) + } + + let contentLength = util.bodyLength(body) + + if (contentLength == null) { + contentLength = request.contentLength + } + + if (contentLength === 0 || !expectsPayload) { + // https://tools.ietf.org/html/rfc7230#section-3.3.2 + // A user agent SHOULD NOT send a Content-Length header field when + // the request message does not contain a payload body and the method + // semantics do not anticipate such a body. + + contentLength = null + } + + // https://github.com/nodejs/undici/issues/2046 + // A user agent may send a Content-Length header with 0 value, this should be allowed. + if (shouldSendContentLength(method) && contentLength > 0 && request.contentLength != null && request.contentLength !== contentLength) { + if (client[kStrictContentLength]) { + errorRequest(client, request, new RequestContentLengthMismatchError()) + return false + } + + process.emitWarning(new RequestContentLengthMismatchError()) + } + + if (contentLength != null) { + assert(body, 'no body must not have content length') + headers[HTTP2_HEADER_CONTENT_LENGTH] = `${contentLength}` + } + + session.ref() + + const shouldEndStream = method === 'GET' || method === 'HEAD' + if (expectContinue) { + headers[HTTP2_HEADER_EXPECT] = '100-continue' + stream = session.request(headers, { endStream: shouldEndStream, signal }) + + stream.once('continue', writeBodyH2) + } else { + stream = session.request(headers, { + endStream: shouldEndStream, + signal + }) + writeBodyH2() + } + + // Increment counter as we have new several streams open + ++h2State.openStreams + + stream.once('response', headers => { + const { [HTTP2_HEADER_STATUS]: statusCode, ...realHeaders } = headers + + if (request.onHeaders(Number(statusCode), realHeaders, stream.resume.bind(stream), '') === false) { + stream.pause() + } + }) + + stream.once('end', () => { + request.onComplete([]) + }) + + stream.on('data', (chunk) => { + if (request.onData(chunk) === false) { + stream.pause() + } + }) + + stream.once('close', () => { + h2State.openStreams -= 1 + // TODO(HTTP/2): unref only if current streams count is 0 + if (h2State.openStreams === 0) { + session.unref() + } + }) + + stream.once('error', function (err) { + if (client[kHTTP2Session] && !client[kHTTP2Session].destroyed && !this.closed && !this.destroyed) { + h2State.streams -= 1 + util.destroy(stream, err) + } + }) + + stream.once('frameError', (type, code) => { + const err = new InformationalError(`HTTP/2: "frameError" received - type ${type}, code ${code}`) + errorRequest(client, request, err) + + if (client[kHTTP2Session] && !client[kHTTP2Session].destroyed && !this.closed && !this.destroyed) { + h2State.streams -= 1 + util.destroy(stream, err) + } + }) + + // stream.on('aborted', () => { + // // TODO(HTTP/2): Support aborted + // }) + + // stream.on('timeout', () => { + // // TODO(HTTP/2): Support timeout + // }) + + // stream.on('push', headers => { + // // TODO(HTTP/2): Suppor push + // }) + + // stream.on('trailers', headers => { + // // TODO(HTTP/2): Support trailers + // }) + + return true + + function writeBodyH2 () { + /* istanbul ignore else: assertion */ + if (!body) { + request.onRequestSent() + } else if (util.isBuffer(body)) { + assert(contentLength === body.byteLength, 'buffer body must have content length') + stream.cork() + stream.write(body) + stream.uncork() + stream.end() + request.onBodySent(body) + request.onRequestSent() + } else if (util.isBlobLike(body)) { + if (typeof body.stream === 'function') { + writeIterable({ + client, + request, + contentLength, + h2stream: stream, + expectsPayload, + body: body.stream(), + socket: client[kSocket], + header: '' + }) + } else { + writeBlob({ + body, + client, + request, + contentLength, + expectsPayload, + h2stream: stream, + header: '', + socket: client[kSocket] + }) + } + } else if (util.isStream(body)) { + writeStream({ + body, + client, + request, + contentLength, + expectsPayload, + socket: client[kSocket], + h2stream: stream, + header: '' + }) + } else if (util.isIterable(body)) { + writeIterable({ + body, + client, + request, + contentLength, + expectsPayload, + header: '', + h2stream: stream, + socket: client[kSocket] + }) + } else { + assert(false) + } + } +} + +function writeStream ({ h2stream, body, client, request, socket, contentLength, header, expectsPayload }) { + assert(contentLength !== 0 || client[kRunning] === 0, 'stream body cannot be pipelined') + + if (client[kHTTPConnVersion] === 'h2') { + // For HTTP/2, is enough to pipe the stream + const pipe = pipeline( + body, + h2stream, + (err) => { + if (err) { + util.destroy(body, err) + util.destroy(h2stream, err) + } else { + request.onRequestSent() + } + } + ) + + pipe.on('data', onPipeData) + pipe.once('end', () => { + pipe.removeListener('data', onPipeData) + util.destroy(pipe) + }) + + function onPipeData (chunk) { + request.onBodySent(chunk) + } + + return + } + + let finished = false + + const writer = new AsyncWriter({ socket, request, contentLength, client, expectsPayload, header }) + + const onData = function (chunk) { + if (finished) { + return + } + + try { + if (!writer.write(chunk) && this.pause) { + this.pause() + } + } catch (err) { + util.destroy(this, err) + } + } + const onDrain = function () { + if (finished) { + return + } + + if (body.resume) { + body.resume() + } + } + const onAbort = function () { + if (finished) { + return + } + const err = new RequestAbortedError() + queueMicrotask(() => onFinished(err)) + } + const onFinished = function (err) { + if (finished) { + return + } + + finished = true + + assert(socket.destroyed || (socket[kWriting] && client[kRunning] <= 1)) + + socket + .off('drain', onDrain) + .off('error', onFinished) + + body + .removeListener('data', onData) + .removeListener('end', onFinished) + .removeListener('error', onFinished) + .removeListener('close', onAbort) + + if (!err) { + try { + writer.end() + } catch (er) { + err = er + } + } + + writer.destroy(err) + + if (err && (err.code !== 'UND_ERR_INFO' || err.message !== 'reset')) { + util.destroy(body, err) + } else { + util.destroy(body) + } + } + + body + .on('data', onData) + .on('end', onFinished) + .on('error', onFinished) + .on('close', onAbort) + + if (body.resume) { + body.resume() + } + + socket + .on('drain', onDrain) + .on('error', onFinished) +} + +async function writeBlob ({ h2stream, body, client, request, socket, contentLength, header, expectsPayload }) { + assert(contentLength === body.size, 'blob body must have content length') + + const isH2 = client[kHTTPConnVersion] === 'h2' + try { + if (contentLength != null && contentLength !== body.size) { + throw new RequestContentLengthMismatchError() + } + + const buffer = Buffer.from(await body.arrayBuffer()) + + if (isH2) { + h2stream.cork() + h2stream.write(buffer) + h2stream.uncork() + } else { + socket.cork() + socket.write(`${header}content-length: ${contentLength}\r\n\r\n`, 'latin1') + socket.write(buffer) + socket.uncork() + } + + request.onBodySent(buffer) + request.onRequestSent() + + if (!expectsPayload) { + socket[kReset] = true + } + + resume(client) + } catch (err) { + util.destroy(isH2 ? h2stream : socket, err) + } +} + +async function writeIterable ({ h2stream, body, client, request, socket, contentLength, header, expectsPayload }) { + assert(contentLength !== 0 || client[kRunning] === 0, 'iterator body cannot be pipelined') + + let callback = null + function onDrain () { + if (callback) { + const cb = callback + callback = null + cb() + } + } + + const waitForDrain = () => new Promise((resolve, reject) => { + assert(callback === null) + + if (socket[kError]) { + reject(socket[kError]) + } else { + callback = resolve + } + }) + + if (client[kHTTPConnVersion] === 'h2') { + h2stream + .on('close', onDrain) + .on('drain', onDrain) + + try { + // It's up to the user to somehow abort the async iterable. + for await (const chunk of body) { + if (socket[kError]) { + throw socket[kError] + } + + const res = h2stream.write(chunk) + request.onBodySent(chunk) + if (!res) { + await waitForDrain() + } + } + } catch (err) { + h2stream.destroy(err) + } finally { + request.onRequestSent() + h2stream.end() + h2stream + .off('close', onDrain) + .off('drain', onDrain) + } + + return + } + + socket + .on('close', onDrain) + .on('drain', onDrain) + + const writer = new AsyncWriter({ socket, request, contentLength, client, expectsPayload, header }) + try { + // It's up to the user to somehow abort the async iterable. + for await (const chunk of body) { + if (socket[kError]) { + throw socket[kError] + } + + if (!writer.write(chunk)) { + await waitForDrain() + } + } + + writer.end() + } catch (err) { + writer.destroy(err) + } finally { + socket + .off('close', onDrain) + .off('drain', onDrain) + } +} + +class AsyncWriter { + constructor ({ socket, request, contentLength, client, expectsPayload, header }) { + this.socket = socket + this.request = request + this.contentLength = contentLength + this.client = client + this.bytesWritten = 0 + this.expectsPayload = expectsPayload + this.header = header + + socket[kWriting] = true + } + + write (chunk) { + const { socket, request, contentLength, client, bytesWritten, expectsPayload, header } = this + + if (socket[kError]) { + throw socket[kError] + } + + if (socket.destroyed) { + return false + } + + const len = Buffer.byteLength(chunk) + if (!len) { + return true + } + + // We should defer writing chunks. + if (contentLength !== null && bytesWritten + len > contentLength) { + if (client[kStrictContentLength]) { + throw new RequestContentLengthMismatchError() + } + + process.emitWarning(new RequestContentLengthMismatchError()) + } + + socket.cork() + + if (bytesWritten === 0) { + if (!expectsPayload) { + socket[kReset] = true + } + + if (contentLength === null) { + socket.write(`${header}transfer-encoding: chunked\r\n`, 'latin1') + } else { + socket.write(`${header}content-length: ${contentLength}\r\n\r\n`, 'latin1') + } + } + + if (contentLength === null) { + socket.write(`\r\n${len.toString(16)}\r\n`, 'latin1') + } + + this.bytesWritten += len + + const ret = socket.write(chunk) + + socket.uncork() + + request.onBodySent(chunk) + + if (!ret) { + if (socket[kParser].timeout && socket[kParser].timeoutType === TIMEOUT_HEADERS) { + // istanbul ignore else: only for jest + if (socket[kParser].timeout.refresh) { + socket[kParser].timeout.refresh() + } + } + } + + return ret + } + + end () { + const { socket, contentLength, client, bytesWritten, expectsPayload, header, request } = this + request.onRequestSent() + + socket[kWriting] = false + + if (socket[kError]) { + throw socket[kError] + } + + if (socket.destroyed) { + return + } + + if (bytesWritten === 0) { + if (expectsPayload) { + // https://tools.ietf.org/html/rfc7230#section-3.3.2 + // A user agent SHOULD send a Content-Length in a request message when + // no Transfer-Encoding is sent and the request method defines a meaning + // for an enclosed payload body. + + socket.write(`${header}content-length: 0\r\n\r\n`, 'latin1') + } else { + socket.write(`${header}\r\n`, 'latin1') + } + } else if (contentLength === null) { + socket.write('\r\n0\r\n\r\n', 'latin1') + } + + if (contentLength !== null && bytesWritten !== contentLength) { + if (client[kStrictContentLength]) { + throw new RequestContentLengthMismatchError() + } else { + process.emitWarning(new RequestContentLengthMismatchError()) + } + } + + if (socket[kParser].timeout && socket[kParser].timeoutType === TIMEOUT_HEADERS) { + // istanbul ignore else: only for jest + if (socket[kParser].timeout.refresh) { + socket[kParser].timeout.refresh() + } + } + + resume(client) + } + + destroy (err) { + const { socket, client } = this + + socket[kWriting] = false + + if (err) { + assert(client[kRunning] <= 1, 'pipeline should only contain this request') + util.destroy(socket, err) + } + } +} + +function errorRequest (client, request, err) { + try { + request.onError(err) + assert(request.aborted) + } catch (err) { + client.emit('error', err) + } +} + +module.exports = Client + + +/***/ }), + +/***/ 13194: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +/* istanbul ignore file: only for Node 12 */ + +const { kConnected, kSize } = __nccwpck_require__(36443) + +class CompatWeakRef { + constructor (value) { + this.value = value + } + + deref () { + return this.value[kConnected] === 0 && this.value[kSize] === 0 + ? undefined + : this.value + } +} + +class CompatFinalizer { + constructor (finalizer) { + this.finalizer = finalizer + } + + register (dispatcher, key) { + if (dispatcher.on) { + dispatcher.on('disconnect', () => { + if (dispatcher[kConnected] === 0 && dispatcher[kSize] === 0) { + this.finalizer(key) + } + }) + } + } +} + +module.exports = function () { + // FIXME: remove workaround when the Node bug is fixed + // https://github.com/nodejs/node/issues/49344#issuecomment-1741776308 + if (process.env.NODE_V8_COVERAGE) { + return { + WeakRef: CompatWeakRef, + FinalizationRegistry: CompatFinalizer + } + } + return { + WeakRef: global.WeakRef || CompatWeakRef, + FinalizationRegistry: global.FinalizationRegistry || CompatFinalizer + } +} + + +/***/ }), + +/***/ 19237: +/***/ ((module) => { + +"use strict"; + + +// https://wicg.github.io/cookie-store/#cookie-maximum-attribute-value-size +const maxAttributeValueSize = 1024 + +// https://wicg.github.io/cookie-store/#cookie-maximum-name-value-pair-size +const maxNameValuePairSize = 4096 + +module.exports = { + maxAttributeValueSize, + maxNameValuePairSize +} + + +/***/ }), + +/***/ 53168: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const { parseSetCookie } = __nccwpck_require__(8915) +const { stringify } = __nccwpck_require__(3834) +const { webidl } = __nccwpck_require__(74222) +const { Headers } = __nccwpck_require__(26349) + +/** + * @typedef {Object} Cookie + * @property {string} name + * @property {string} value + * @property {Date|number|undefined} expires + * @property {number|undefined} maxAge + * @property {string|undefined} domain + * @property {string|undefined} path + * @property {boolean|undefined} secure + * @property {boolean|undefined} httpOnly + * @property {'Strict'|'Lax'|'None'} sameSite + * @property {string[]} unparsed + */ + +/** + * @param {Headers} headers + * @returns {Record} + */ +function getCookies (headers) { + webidl.argumentLengthCheck(arguments, 1, { header: 'getCookies' }) + + webidl.brandCheck(headers, Headers, { strict: false }) + + const cookie = headers.get('cookie') + const out = {} + + if (!cookie) { + return out + } + + for (const piece of cookie.split(';')) { + const [name, ...value] = piece.split('=') + + out[name.trim()] = value.join('=') + } + + return out +} + +/** + * @param {Headers} headers + * @param {string} name + * @param {{ path?: string, domain?: string }|undefined} attributes + * @returns {void} + */ +function deleteCookie (headers, name, attributes) { + webidl.argumentLengthCheck(arguments, 2, { header: 'deleteCookie' }) + + webidl.brandCheck(headers, Headers, { strict: false }) + + name = webidl.converters.DOMString(name) + attributes = webidl.converters.DeleteCookieAttributes(attributes) + + // Matches behavior of + // https://github.com/denoland/deno_std/blob/63827b16330b82489a04614027c33b7904e08be5/http/cookie.ts#L278 + setCookie(headers, { + name, + value: '', + expires: new Date(0), + ...attributes + }) +} + +/** + * @param {Headers} headers + * @returns {Cookie[]} + */ +function getSetCookies (headers) { + webidl.argumentLengthCheck(arguments, 1, { header: 'getSetCookies' }) + + webidl.brandCheck(headers, Headers, { strict: false }) + + const cookies = headers.getSetCookie() + + if (!cookies) { + return [] + } + + return cookies.map((pair) => parseSetCookie(pair)) +} + +/** + * @param {Headers} headers + * @param {Cookie} cookie + * @returns {void} + */ +function setCookie (headers, cookie) { + webidl.argumentLengthCheck(arguments, 2, { header: 'setCookie' }) + + webidl.brandCheck(headers, Headers, { strict: false }) + + cookie = webidl.converters.Cookie(cookie) + + const str = stringify(cookie) + + if (str) { + headers.append('Set-Cookie', stringify(cookie)) + } +} + +webidl.converters.DeleteCookieAttributes = webidl.dictionaryConverter([ + { + converter: webidl.nullableConverter(webidl.converters.DOMString), + key: 'path', + defaultValue: null + }, + { + converter: webidl.nullableConverter(webidl.converters.DOMString), + key: 'domain', + defaultValue: null + } +]) + +webidl.converters.Cookie = webidl.dictionaryConverter([ + { + converter: webidl.converters.DOMString, + key: 'name' + }, + { + converter: webidl.converters.DOMString, + key: 'value' + }, + { + converter: webidl.nullableConverter((value) => { + if (typeof value === 'number') { + return webidl.converters['unsigned long long'](value) + } + + return new Date(value) + }), + key: 'expires', + defaultValue: null + }, + { + converter: webidl.nullableConverter(webidl.converters['long long']), + key: 'maxAge', + defaultValue: null + }, + { + converter: webidl.nullableConverter(webidl.converters.DOMString), + key: 'domain', + defaultValue: null + }, + { + converter: webidl.nullableConverter(webidl.converters.DOMString), + key: 'path', + defaultValue: null + }, + { + converter: webidl.nullableConverter(webidl.converters.boolean), + key: 'secure', + defaultValue: null + }, + { + converter: webidl.nullableConverter(webidl.converters.boolean), + key: 'httpOnly', + defaultValue: null + }, + { + converter: webidl.converters.USVString, + key: 'sameSite', + allowedValues: ['Strict', 'Lax', 'None'] + }, + { + converter: webidl.sequenceConverter(webidl.converters.DOMString), + key: 'unparsed', + defaultValue: [] + } +]) + +module.exports = { + getCookies, + deleteCookie, + getSetCookies, + setCookie +} + + +/***/ }), + +/***/ 8915: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const { maxNameValuePairSize, maxAttributeValueSize } = __nccwpck_require__(19237) +const { isCTLExcludingHtab } = __nccwpck_require__(3834) +const { collectASequenceOfCodePointsFast } = __nccwpck_require__(94322) +const assert = __nccwpck_require__(42613) + +/** + * @description Parses the field-value attributes of a set-cookie header string. + * @see https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis#section-5.4 + * @param {string} header + * @returns if the header is invalid, null will be returned + */ +function parseSetCookie (header) { + // 1. If the set-cookie-string contains a %x00-08 / %x0A-1F / %x7F + // character (CTL characters excluding HTAB): Abort these steps and + // ignore the set-cookie-string entirely. + if (isCTLExcludingHtab(header)) { + return null + } + + let nameValuePair = '' + let unparsedAttributes = '' + let name = '' + let value = '' + + // 2. If the set-cookie-string contains a %x3B (";") character: + if (header.includes(';')) { + // 1. The name-value-pair string consists of the characters up to, + // but not including, the first %x3B (";"), and the unparsed- + // attributes consist of the remainder of the set-cookie-string + // (including the %x3B (";") in question). + const position = { position: 0 } + + nameValuePair = collectASequenceOfCodePointsFast(';', header, position) + unparsedAttributes = header.slice(position.position) + } else { + // Otherwise: + + // 1. The name-value-pair string consists of all the characters + // contained in the set-cookie-string, and the unparsed- + // attributes is the empty string. + nameValuePair = header + } + + // 3. If the name-value-pair string lacks a %x3D ("=") character, then + // the name string is empty, and the value string is the value of + // name-value-pair. + if (!nameValuePair.includes('=')) { + value = nameValuePair + } else { + // Otherwise, the name string consists of the characters up to, but + // not including, the first %x3D ("=") character, and the (possibly + // empty) value string consists of the characters after the first + // %x3D ("=") character. + const position = { position: 0 } + name = collectASequenceOfCodePointsFast( + '=', + nameValuePair, + position + ) + value = nameValuePair.slice(position.position + 1) + } + + // 4. Remove any leading or trailing WSP characters from the name + // string and the value string. + name = name.trim() + value = value.trim() + + // 5. If the sum of the lengths of the name string and the value string + // is more than 4096 octets, abort these steps and ignore the set- + // cookie-string entirely. + if (name.length + value.length > maxNameValuePairSize) { + return null + } + + // 6. The cookie-name is the name string, and the cookie-value is the + // value string. + return { + name, value, ...parseUnparsedAttributes(unparsedAttributes) + } +} + +/** + * Parses the remaining attributes of a set-cookie header + * @see https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis#section-5.4 + * @param {string} unparsedAttributes + * @param {[Object.]={}} cookieAttributeList + */ +function parseUnparsedAttributes (unparsedAttributes, cookieAttributeList = {}) { + // 1. If the unparsed-attributes string is empty, skip the rest of + // these steps. + if (unparsedAttributes.length === 0) { + return cookieAttributeList + } + + // 2. Discard the first character of the unparsed-attributes (which + // will be a %x3B (";") character). + assert(unparsedAttributes[0] === ';') + unparsedAttributes = unparsedAttributes.slice(1) + + let cookieAv = '' + + // 3. If the remaining unparsed-attributes contains a %x3B (";") + // character: + if (unparsedAttributes.includes(';')) { + // 1. Consume the characters of the unparsed-attributes up to, but + // not including, the first %x3B (";") character. + cookieAv = collectASequenceOfCodePointsFast( + ';', + unparsedAttributes, + { position: 0 } + ) + unparsedAttributes = unparsedAttributes.slice(cookieAv.length) + } else { + // Otherwise: + + // 1. Consume the remainder of the unparsed-attributes. + cookieAv = unparsedAttributes + unparsedAttributes = '' + } + + // Let the cookie-av string be the characters consumed in this step. + + let attributeName = '' + let attributeValue = '' + + // 4. If the cookie-av string contains a %x3D ("=") character: + if (cookieAv.includes('=')) { + // 1. The (possibly empty) attribute-name string consists of the + // characters up to, but not including, the first %x3D ("=") + // character, and the (possibly empty) attribute-value string + // consists of the characters after the first %x3D ("=") + // character. + const position = { position: 0 } + + attributeName = collectASequenceOfCodePointsFast( + '=', + cookieAv, + position + ) + attributeValue = cookieAv.slice(position.position + 1) + } else { + // Otherwise: + + // 1. The attribute-name string consists of the entire cookie-av + // string, and the attribute-value string is empty. + attributeName = cookieAv + } + + // 5. Remove any leading or trailing WSP characters from the attribute- + // name string and the attribute-value string. + attributeName = attributeName.trim() + attributeValue = attributeValue.trim() + + // 6. If the attribute-value is longer than 1024 octets, ignore the + // cookie-av string and return to Step 1 of this algorithm. + if (attributeValue.length > maxAttributeValueSize) { + return parseUnparsedAttributes(unparsedAttributes, cookieAttributeList) + } + + // 7. Process the attribute-name and attribute-value according to the + // requirements in the following subsections. (Notice that + // attributes with unrecognized attribute-names are ignored.) + const attributeNameLowercase = attributeName.toLowerCase() + + // https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis#section-5.4.1 + // If the attribute-name case-insensitively matches the string + // "Expires", the user agent MUST process the cookie-av as follows. + if (attributeNameLowercase === 'expires') { + // 1. Let the expiry-time be the result of parsing the attribute-value + // as cookie-date (see Section 5.1.1). + const expiryTime = new Date(attributeValue) + + // 2. If the attribute-value failed to parse as a cookie date, ignore + // the cookie-av. + + cookieAttributeList.expires = expiryTime + } else if (attributeNameLowercase === 'max-age') { + // https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis#section-5.4.2 + // If the attribute-name case-insensitively matches the string "Max- + // Age", the user agent MUST process the cookie-av as follows. + + // 1. If the first character of the attribute-value is not a DIGIT or a + // "-" character, ignore the cookie-av. + const charCode = attributeValue.charCodeAt(0) + + if ((charCode < 48 || charCode > 57) && attributeValue[0] !== '-') { + return parseUnparsedAttributes(unparsedAttributes, cookieAttributeList) + } + + // 2. If the remainder of attribute-value contains a non-DIGIT + // character, ignore the cookie-av. + if (!/^\d+$/.test(attributeValue)) { + return parseUnparsedAttributes(unparsedAttributes, cookieAttributeList) + } + + // 3. Let delta-seconds be the attribute-value converted to an integer. + const deltaSeconds = Number(attributeValue) + + // 4. Let cookie-age-limit be the maximum age of the cookie (which + // SHOULD be 400 days or less, see Section 4.1.2.2). + + // 5. Set delta-seconds to the smaller of its present value and cookie- + // age-limit. + // deltaSeconds = Math.min(deltaSeconds * 1000, maxExpiresMs) + + // 6. If delta-seconds is less than or equal to zero (0), let expiry- + // time be the earliest representable date and time. Otherwise, let + // the expiry-time be the current date and time plus delta-seconds + // seconds. + // const expiryTime = deltaSeconds <= 0 ? Date.now() : Date.now() + deltaSeconds + + // 7. Append an attribute to the cookie-attribute-list with an + // attribute-name of Max-Age and an attribute-value of expiry-time. + cookieAttributeList.maxAge = deltaSeconds + } else if (attributeNameLowercase === 'domain') { + // https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis#section-5.4.3 + // If the attribute-name case-insensitively matches the string "Domain", + // the user agent MUST process the cookie-av as follows. + + // 1. Let cookie-domain be the attribute-value. + let cookieDomain = attributeValue + + // 2. If cookie-domain starts with %x2E ("."), let cookie-domain be + // cookie-domain without its leading %x2E ("."). + if (cookieDomain[0] === '.') { + cookieDomain = cookieDomain.slice(1) + } + + // 3. Convert the cookie-domain to lower case. + cookieDomain = cookieDomain.toLowerCase() + + // 4. Append an attribute to the cookie-attribute-list with an + // attribute-name of Domain and an attribute-value of cookie-domain. + cookieAttributeList.domain = cookieDomain + } else if (attributeNameLowercase === 'path') { + // https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis#section-5.4.4 + // If the attribute-name case-insensitively matches the string "Path", + // the user agent MUST process the cookie-av as follows. + + // 1. If the attribute-value is empty or if the first character of the + // attribute-value is not %x2F ("/"): + let cookiePath = '' + if (attributeValue.length === 0 || attributeValue[0] !== '/') { + // 1. Let cookie-path be the default-path. + cookiePath = '/' + } else { + // Otherwise: + + // 1. Let cookie-path be the attribute-value. + cookiePath = attributeValue + } + + // 2. Append an attribute to the cookie-attribute-list with an + // attribute-name of Path and an attribute-value of cookie-path. + cookieAttributeList.path = cookiePath + } else if (attributeNameLowercase === 'secure') { + // https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis#section-5.4.5 + // If the attribute-name case-insensitively matches the string "Secure", + // the user agent MUST append an attribute to the cookie-attribute-list + // with an attribute-name of Secure and an empty attribute-value. + + cookieAttributeList.secure = true + } else if (attributeNameLowercase === 'httponly') { + // https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis#section-5.4.6 + // If the attribute-name case-insensitively matches the string + // "HttpOnly", the user agent MUST append an attribute to the cookie- + // attribute-list with an attribute-name of HttpOnly and an empty + // attribute-value. + + cookieAttributeList.httpOnly = true + } else if (attributeNameLowercase === 'samesite') { + // https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis#section-5.4.7 + // If the attribute-name case-insensitively matches the string + // "SameSite", the user agent MUST process the cookie-av as follows: + + // 1. Let enforcement be "Default". + let enforcement = 'Default' + + const attributeValueLowercase = attributeValue.toLowerCase() + // 2. If cookie-av's attribute-value is a case-insensitive match for + // "None", set enforcement to "None". + if (attributeValueLowercase.includes('none')) { + enforcement = 'None' + } + + // 3. If cookie-av's attribute-value is a case-insensitive match for + // "Strict", set enforcement to "Strict". + if (attributeValueLowercase.includes('strict')) { + enforcement = 'Strict' + } + + // 4. If cookie-av's attribute-value is a case-insensitive match for + // "Lax", set enforcement to "Lax". + if (attributeValueLowercase.includes('lax')) { + enforcement = 'Lax' + } + + // 5. Append an attribute to the cookie-attribute-list with an + // attribute-name of "SameSite" and an attribute-value of + // enforcement. + cookieAttributeList.sameSite = enforcement + } else { + cookieAttributeList.unparsed ??= [] + + cookieAttributeList.unparsed.push(`${attributeName}=${attributeValue}`) + } + + // 8. Return to Step 1 of this algorithm. + return parseUnparsedAttributes(unparsedAttributes, cookieAttributeList) +} + +module.exports = { + parseSetCookie, + parseUnparsedAttributes +} + + +/***/ }), + +/***/ 3834: +/***/ ((module) => { + +"use strict"; + + +/** + * @param {string} value + * @returns {boolean} + */ +function isCTLExcludingHtab (value) { + if (value.length === 0) { + return false + } + + for (const char of value) { + const code = char.charCodeAt(0) + + if ( + (code >= 0x00 || code <= 0x08) || + (code >= 0x0A || code <= 0x1F) || + code === 0x7F + ) { + return false + } + } +} + +/** + CHAR = + token = 1* + separators = "(" | ")" | "<" | ">" | "@" + | "," | ";" | ":" | "\" | <"> + | "/" | "[" | "]" | "?" | "=" + | "{" | "}" | SP | HT + * @param {string} name + */ +function validateCookieName (name) { + for (const char of name) { + const code = char.charCodeAt(0) + + if ( + (code <= 0x20 || code > 0x7F) || + char === '(' || + char === ')' || + char === '>' || + char === '<' || + char === '@' || + char === ',' || + char === ';' || + char === ':' || + char === '\\' || + char === '"' || + char === '/' || + char === '[' || + char === ']' || + char === '?' || + char === '=' || + char === '{' || + char === '}' + ) { + throw new Error('Invalid cookie name') + } + } +} + +/** + cookie-value = *cookie-octet / ( DQUOTE *cookie-octet DQUOTE ) + cookie-octet = %x21 / %x23-2B / %x2D-3A / %x3C-5B / %x5D-7E + ; US-ASCII characters excluding CTLs, + ; whitespace DQUOTE, comma, semicolon, + ; and backslash + * @param {string} value + */ +function validateCookieValue (value) { + for (const char of value) { + const code = char.charCodeAt(0) + + if ( + code < 0x21 || // exclude CTLs (0-31) + code === 0x22 || + code === 0x2C || + code === 0x3B || + code === 0x5C || + code > 0x7E // non-ascii + ) { + throw new Error('Invalid header value') + } + } +} + +/** + * path-value = + * @param {string} path + */ +function validateCookiePath (path) { + for (const char of path) { + const code = char.charCodeAt(0) + + if (code < 0x21 || char === ';') { + throw new Error('Invalid cookie path') + } + } +} + +/** + * I have no idea why these values aren't allowed to be honest, + * but Deno tests these. - Khafra + * @param {string} domain + */ +function validateCookieDomain (domain) { + if ( + domain.startsWith('-') || + domain.endsWith('.') || + domain.endsWith('-') + ) { + throw new Error('Invalid cookie domain') + } +} + +/** + * @see https://www.rfc-editor.org/rfc/rfc7231#section-7.1.1.1 + * @param {number|Date} date + IMF-fixdate = day-name "," SP date1 SP time-of-day SP GMT + ; fixed length/zone/capitalization subset of the format + ; see Section 3.3 of [RFC5322] + + day-name = %x4D.6F.6E ; "Mon", case-sensitive + / %x54.75.65 ; "Tue", case-sensitive + / %x57.65.64 ; "Wed", case-sensitive + / %x54.68.75 ; "Thu", case-sensitive + / %x46.72.69 ; "Fri", case-sensitive + / %x53.61.74 ; "Sat", case-sensitive + / %x53.75.6E ; "Sun", case-sensitive + date1 = day SP month SP year + ; e.g., 02 Jun 1982 + + day = 2DIGIT + month = %x4A.61.6E ; "Jan", case-sensitive + / %x46.65.62 ; "Feb", case-sensitive + / %x4D.61.72 ; "Mar", case-sensitive + / %x41.70.72 ; "Apr", case-sensitive + / %x4D.61.79 ; "May", case-sensitive + / %x4A.75.6E ; "Jun", case-sensitive + / %x4A.75.6C ; "Jul", case-sensitive + / %x41.75.67 ; "Aug", case-sensitive + / %x53.65.70 ; "Sep", case-sensitive + / %x4F.63.74 ; "Oct", case-sensitive + / %x4E.6F.76 ; "Nov", case-sensitive + / %x44.65.63 ; "Dec", case-sensitive + year = 4DIGIT + + GMT = %x47.4D.54 ; "GMT", case-sensitive + + time-of-day = hour ":" minute ":" second + ; 00:00:00 - 23:59:60 (leap second) + + hour = 2DIGIT + minute = 2DIGIT + second = 2DIGIT + */ +function toIMFDate (date) { + if (typeof date === 'number') { + date = new Date(date) + } + + const days = [ + 'Sun', 'Mon', 'Tue', 'Wed', + 'Thu', 'Fri', 'Sat' + ] + + const months = [ + 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', + 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec' + ] + + const dayName = days[date.getUTCDay()] + const day = date.getUTCDate().toString().padStart(2, '0') + const month = months[date.getUTCMonth()] + const year = date.getUTCFullYear() + const hour = date.getUTCHours().toString().padStart(2, '0') + const minute = date.getUTCMinutes().toString().padStart(2, '0') + const second = date.getUTCSeconds().toString().padStart(2, '0') + + return `${dayName}, ${day} ${month} ${year} ${hour}:${minute}:${second} GMT` +} + +/** + max-age-av = "Max-Age=" non-zero-digit *DIGIT + ; In practice, both expires-av and max-age-av + ; are limited to dates representable by the + ; user agent. + * @param {number} maxAge + */ +function validateCookieMaxAge (maxAge) { + if (maxAge < 0) { + throw new Error('Invalid cookie max-age') + } +} + +/** + * @see https://www.rfc-editor.org/rfc/rfc6265#section-4.1.1 + * @param {import('./index').Cookie} cookie + */ +function stringify (cookie) { + if (cookie.name.length === 0) { + return null + } + + validateCookieName(cookie.name) + validateCookieValue(cookie.value) + + const out = [`${cookie.name}=${cookie.value}`] + + // https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-cookie-prefixes-00#section-3.1 + // https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-cookie-prefixes-00#section-3.2 + if (cookie.name.startsWith('__Secure-')) { + cookie.secure = true + } + + if (cookie.name.startsWith('__Host-')) { + cookie.secure = true + cookie.domain = null + cookie.path = '/' + } + + if (cookie.secure) { + out.push('Secure') + } + + if (cookie.httpOnly) { + out.push('HttpOnly') + } + + if (typeof cookie.maxAge === 'number') { + validateCookieMaxAge(cookie.maxAge) + out.push(`Max-Age=${cookie.maxAge}`) + } + + if (cookie.domain) { + validateCookieDomain(cookie.domain) + out.push(`Domain=${cookie.domain}`) + } + + if (cookie.path) { + validateCookiePath(cookie.path) + out.push(`Path=${cookie.path}`) + } + + if (cookie.expires && cookie.expires.toString() !== 'Invalid Date') { + out.push(`Expires=${toIMFDate(cookie.expires)}`) + } + + if (cookie.sameSite) { + out.push(`SameSite=${cookie.sameSite}`) + } + + for (const part of cookie.unparsed) { + if (!part.includes('=')) { + throw new Error('Invalid unparsed') + } + + const [key, ...value] = part.split('=') + + out.push(`${key.trim()}=${value.join('=')}`) + } + + return out.join('; ') +} + +module.exports = { + isCTLExcludingHtab, + validateCookieName, + validateCookiePath, + validateCookieValue, + toIMFDate, + stringify +} + + +/***/ }), + +/***/ 59136: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const net = __nccwpck_require__(69278) +const assert = __nccwpck_require__(42613) +const util = __nccwpck_require__(3440) +const { InvalidArgumentError, ConnectTimeoutError } = __nccwpck_require__(68707) + +let tls // include tls conditionally since it is not always available + +// TODO: session re-use does not wait for the first +// connection to resolve the session and might therefore +// resolve the same servername multiple times even when +// re-use is enabled. + +let SessionCache +// FIXME: remove workaround when the Node bug is fixed +// https://github.com/nodejs/node/issues/49344#issuecomment-1741776308 +if (global.FinalizationRegistry && !process.env.NODE_V8_COVERAGE) { + SessionCache = class WeakSessionCache { + constructor (maxCachedSessions) { + this._maxCachedSessions = maxCachedSessions + this._sessionCache = new Map() + this._sessionRegistry = new global.FinalizationRegistry((key) => { + if (this._sessionCache.size < this._maxCachedSessions) { + return + } + + const ref = this._sessionCache.get(key) + if (ref !== undefined && ref.deref() === undefined) { + this._sessionCache.delete(key) + } + }) + } + + get (sessionKey) { + const ref = this._sessionCache.get(sessionKey) + return ref ? ref.deref() : null + } + + set (sessionKey, session) { + if (this._maxCachedSessions === 0) { + return + } + + this._sessionCache.set(sessionKey, new WeakRef(session)) + this._sessionRegistry.register(session, sessionKey) + } + } +} else { + SessionCache = class SimpleSessionCache { + constructor (maxCachedSessions) { + this._maxCachedSessions = maxCachedSessions + this._sessionCache = new Map() + } + + get (sessionKey) { + return this._sessionCache.get(sessionKey) + } + + set (sessionKey, session) { + if (this._maxCachedSessions === 0) { + return + } + + if (this._sessionCache.size >= this._maxCachedSessions) { + // remove the oldest session + const { value: oldestKey } = this._sessionCache.keys().next() + this._sessionCache.delete(oldestKey) + } + + this._sessionCache.set(sessionKey, session) + } + } +} + +function buildConnector ({ allowH2, maxCachedSessions, socketPath, timeout, ...opts }) { + if (maxCachedSessions != null && (!Number.isInteger(maxCachedSessions) || maxCachedSessions < 0)) { + throw new InvalidArgumentError('maxCachedSessions must be a positive integer or zero') + } + + const options = { path: socketPath, ...opts } + const sessionCache = new SessionCache(maxCachedSessions == null ? 100 : maxCachedSessions) + timeout = timeout == null ? 10e3 : timeout + allowH2 = allowH2 != null ? allowH2 : false + return function connect ({ hostname, host, protocol, port, servername, localAddress, httpSocket }, callback) { + let socket + if (protocol === 'https:') { + if (!tls) { + tls = __nccwpck_require__(64756) + } + servername = servername || options.servername || util.getServerName(host) || null + + const sessionKey = servername || hostname + const session = sessionCache.get(sessionKey) || null + + assert(sessionKey) + + socket = tls.connect({ + highWaterMark: 16384, // TLS in node can't have bigger HWM anyway... + ...options, + servername, + session, + localAddress, + // TODO(HTTP/2): Add support for h2c + ALPNProtocols: allowH2 ? ['http/1.1', 'h2'] : ['http/1.1'], + socket: httpSocket, // upgrade socket connection + port: port || 443, + host: hostname + }) + + socket + .on('session', function (session) { + // TODO (fix): Can a session become invalid once established? Don't think so? + sessionCache.set(sessionKey, session) + }) + } else { + assert(!httpSocket, 'httpSocket can only be sent on TLS update') + socket = net.connect({ + highWaterMark: 64 * 1024, // Same as nodejs fs streams. + ...options, + localAddress, + port: port || 80, + host: hostname + }) + } + + // Set TCP keep alive options on the socket here instead of in connect() for the case of assigning the socket + if (options.keepAlive == null || options.keepAlive) { + const keepAliveInitialDelay = options.keepAliveInitialDelay === undefined ? 60e3 : options.keepAliveInitialDelay + socket.setKeepAlive(true, keepAliveInitialDelay) + } + + const cancelTimeout = setupTimeout(() => onConnectTimeout(socket), timeout) + + socket + .setNoDelay(true) + .once(protocol === 'https:' ? 'secureConnect' : 'connect', function () { + cancelTimeout() + + if (callback) { + const cb = callback + callback = null + cb(null, this) + } + }) + .on('error', function (err) { + cancelTimeout() + + if (callback) { + const cb = callback + callback = null + cb(err) + } + }) + + return socket + } +} + +function setupTimeout (onConnectTimeout, timeout) { + if (!timeout) { + return () => {} + } + + let s1 = null + let s2 = null + const timeoutId = setTimeout(() => { + // setImmediate is added to make sure that we priotorise socket error events over timeouts + s1 = setImmediate(() => { + if (process.platform === 'win32') { + // Windows needs an extra setImmediate probably due to implementation differences in the socket logic + s2 = setImmediate(() => onConnectTimeout()) + } else { + onConnectTimeout() + } + }) + }, timeout) + return () => { + clearTimeout(timeoutId) + clearImmediate(s1) + clearImmediate(s2) + } +} + +function onConnectTimeout (socket) { + util.destroy(socket, new ConnectTimeoutError()) +} + +module.exports = buildConnector + + +/***/ }), + +/***/ 10735: +/***/ ((module) => { + +"use strict"; + + +/** @type {Record} */ +const headerNameLowerCasedRecord = {} + +// https://developer.mozilla.org/docs/Web/HTTP/Headers +const wellknownHeaderNames = [ + 'Accept', + 'Accept-Encoding', + 'Accept-Language', + 'Accept-Ranges', + 'Access-Control-Allow-Credentials', + 'Access-Control-Allow-Headers', + 'Access-Control-Allow-Methods', + 'Access-Control-Allow-Origin', + 'Access-Control-Expose-Headers', + 'Access-Control-Max-Age', + 'Access-Control-Request-Headers', + 'Access-Control-Request-Method', + 'Age', + 'Allow', + 'Alt-Svc', + 'Alt-Used', + 'Authorization', + 'Cache-Control', + 'Clear-Site-Data', + 'Connection', + 'Content-Disposition', + 'Content-Encoding', + 'Content-Language', + 'Content-Length', + 'Content-Location', + 'Content-Range', + 'Content-Security-Policy', + 'Content-Security-Policy-Report-Only', + 'Content-Type', + 'Cookie', + 'Cross-Origin-Embedder-Policy', + 'Cross-Origin-Opener-Policy', + 'Cross-Origin-Resource-Policy', + 'Date', + 'Device-Memory', + 'Downlink', + 'ECT', + 'ETag', + 'Expect', + 'Expect-CT', + 'Expires', + 'Forwarded', + 'From', + 'Host', + 'If-Match', + 'If-Modified-Since', + 'If-None-Match', + 'If-Range', + 'If-Unmodified-Since', + 'Keep-Alive', + 'Last-Modified', + 'Link', + 'Location', + 'Max-Forwards', + 'Origin', + 'Permissions-Policy', + 'Pragma', + 'Proxy-Authenticate', + 'Proxy-Authorization', + 'RTT', + 'Range', + 'Referer', + 'Referrer-Policy', + 'Refresh', + 'Retry-After', + 'Sec-WebSocket-Accept', + 'Sec-WebSocket-Extensions', + 'Sec-WebSocket-Key', + 'Sec-WebSocket-Protocol', + 'Sec-WebSocket-Version', + 'Server', + 'Server-Timing', + 'Service-Worker-Allowed', + 'Service-Worker-Navigation-Preload', + 'Set-Cookie', + 'SourceMap', + 'Strict-Transport-Security', + 'Supports-Loading-Mode', + 'TE', + 'Timing-Allow-Origin', + 'Trailer', + 'Transfer-Encoding', + 'Upgrade', + 'Upgrade-Insecure-Requests', + 'User-Agent', + 'Vary', + 'Via', + 'WWW-Authenticate', + 'X-Content-Type-Options', + 'X-DNS-Prefetch-Control', + 'X-Frame-Options', + 'X-Permitted-Cross-Domain-Policies', + 'X-Powered-By', + 'X-Requested-With', + 'X-XSS-Protection' +] + +for (let i = 0; i < wellknownHeaderNames.length; ++i) { + const key = wellknownHeaderNames[i] + const lowerCasedKey = key.toLowerCase() + headerNameLowerCasedRecord[key] = headerNameLowerCasedRecord[lowerCasedKey] = + lowerCasedKey +} + +// Note: object prototypes should not be able to be referenced. e.g. `Object#hasOwnProperty`. +Object.setPrototypeOf(headerNameLowerCasedRecord, null) + +module.exports = { + wellknownHeaderNames, + headerNameLowerCasedRecord +} + + +/***/ }), + +/***/ 68707: +/***/ ((module) => { + +"use strict"; + + +class UndiciError extends Error { + constructor (message) { + super(message) + this.name = 'UndiciError' + this.code = 'UND_ERR' + } +} + +class ConnectTimeoutError extends UndiciError { + constructor (message) { + super(message) + Error.captureStackTrace(this, ConnectTimeoutError) + this.name = 'ConnectTimeoutError' + this.message = message || 'Connect Timeout Error' + this.code = 'UND_ERR_CONNECT_TIMEOUT' + } +} + +class HeadersTimeoutError extends UndiciError { + constructor (message) { + super(message) + Error.captureStackTrace(this, HeadersTimeoutError) + this.name = 'HeadersTimeoutError' + this.message = message || 'Headers Timeout Error' + this.code = 'UND_ERR_HEADERS_TIMEOUT' + } +} + +class HeadersOverflowError extends UndiciError { + constructor (message) { + super(message) + Error.captureStackTrace(this, HeadersOverflowError) + this.name = 'HeadersOverflowError' + this.message = message || 'Headers Overflow Error' + this.code = 'UND_ERR_HEADERS_OVERFLOW' + } +} + +class BodyTimeoutError extends UndiciError { + constructor (message) { + super(message) + Error.captureStackTrace(this, BodyTimeoutError) + this.name = 'BodyTimeoutError' + this.message = message || 'Body Timeout Error' + this.code = 'UND_ERR_BODY_TIMEOUT' + } +} + +class ResponseStatusCodeError extends UndiciError { + constructor (message, statusCode, headers, body) { + super(message) + Error.captureStackTrace(this, ResponseStatusCodeError) + this.name = 'ResponseStatusCodeError' + this.message = message || 'Response Status Code Error' + this.code = 'UND_ERR_RESPONSE_STATUS_CODE' + this.body = body + this.status = statusCode + this.statusCode = statusCode + this.headers = headers + } +} + +class InvalidArgumentError extends UndiciError { + constructor (message) { + super(message) + Error.captureStackTrace(this, InvalidArgumentError) + this.name = 'InvalidArgumentError' + this.message = message || 'Invalid Argument Error' + this.code = 'UND_ERR_INVALID_ARG' + } +} + +class InvalidReturnValueError extends UndiciError { + constructor (message) { + super(message) + Error.captureStackTrace(this, InvalidReturnValueError) + this.name = 'InvalidReturnValueError' + this.message = message || 'Invalid Return Value Error' + this.code = 'UND_ERR_INVALID_RETURN_VALUE' + } +} + +class RequestAbortedError extends UndiciError { + constructor (message) { + super(message) + Error.captureStackTrace(this, RequestAbortedError) + this.name = 'AbortError' + this.message = message || 'Request aborted' + this.code = 'UND_ERR_ABORTED' + } +} + +class InformationalError extends UndiciError { + constructor (message) { + super(message) + Error.captureStackTrace(this, InformationalError) + this.name = 'InformationalError' + this.message = message || 'Request information' + this.code = 'UND_ERR_INFO' + } +} + +class RequestContentLengthMismatchError extends UndiciError { + constructor (message) { + super(message) + Error.captureStackTrace(this, RequestContentLengthMismatchError) + this.name = 'RequestContentLengthMismatchError' + this.message = message || 'Request body length does not match content-length header' + this.code = 'UND_ERR_REQ_CONTENT_LENGTH_MISMATCH' + } +} + +class ResponseContentLengthMismatchError extends UndiciError { + constructor (message) { + super(message) + Error.captureStackTrace(this, ResponseContentLengthMismatchError) + this.name = 'ResponseContentLengthMismatchError' + this.message = message || 'Response body length does not match content-length header' + this.code = 'UND_ERR_RES_CONTENT_LENGTH_MISMATCH' + } +} + +class ClientDestroyedError extends UndiciError { + constructor (message) { + super(message) + Error.captureStackTrace(this, ClientDestroyedError) + this.name = 'ClientDestroyedError' + this.message = message || 'The client is destroyed' + this.code = 'UND_ERR_DESTROYED' + } +} + +class ClientClosedError extends UndiciError { + constructor (message) { + super(message) + Error.captureStackTrace(this, ClientClosedError) + this.name = 'ClientClosedError' + this.message = message || 'The client is closed' + this.code = 'UND_ERR_CLOSED' + } +} + +class SocketError extends UndiciError { + constructor (message, socket) { + super(message) + Error.captureStackTrace(this, SocketError) + this.name = 'SocketError' + this.message = message || 'Socket error' + this.code = 'UND_ERR_SOCKET' + this.socket = socket + } +} + +class NotSupportedError extends UndiciError { + constructor (message) { + super(message) + Error.captureStackTrace(this, NotSupportedError) + this.name = 'NotSupportedError' + this.message = message || 'Not supported error' + this.code = 'UND_ERR_NOT_SUPPORTED' + } +} + +class BalancedPoolMissingUpstreamError extends UndiciError { + constructor (message) { + super(message) + Error.captureStackTrace(this, NotSupportedError) + this.name = 'MissingUpstreamError' + this.message = message || 'No upstream has been added to the BalancedPool' + this.code = 'UND_ERR_BPL_MISSING_UPSTREAM' + } +} + +class HTTPParserError extends Error { + constructor (message, code, data) { + super(message) + Error.captureStackTrace(this, HTTPParserError) + this.name = 'HTTPParserError' + this.code = code ? `HPE_${code}` : undefined + this.data = data ? data.toString() : undefined + } +} + +class ResponseExceededMaxSizeError extends UndiciError { + constructor (message) { + super(message) + Error.captureStackTrace(this, ResponseExceededMaxSizeError) + this.name = 'ResponseExceededMaxSizeError' + this.message = message || 'Response content exceeded max size' + this.code = 'UND_ERR_RES_EXCEEDED_MAX_SIZE' + } +} + +class RequestRetryError extends UndiciError { + constructor (message, code, { headers, data }) { + super(message) + Error.captureStackTrace(this, RequestRetryError) + this.name = 'RequestRetryError' + this.message = message || 'Request retry error' + this.code = 'UND_ERR_REQ_RETRY' + this.statusCode = code + this.data = data + this.headers = headers + } +} + +module.exports = { + HTTPParserError, + UndiciError, + HeadersTimeoutError, + HeadersOverflowError, + BodyTimeoutError, + RequestContentLengthMismatchError, + ConnectTimeoutError, + ResponseStatusCodeError, + InvalidArgumentError, + InvalidReturnValueError, + RequestAbortedError, + ClientDestroyedError, + ClientClosedError, + InformationalError, + SocketError, + NotSupportedError, + ResponseContentLengthMismatchError, + BalancedPoolMissingUpstreamError, + ResponseExceededMaxSizeError, + RequestRetryError +} + + +/***/ }), + +/***/ 44655: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const { + InvalidArgumentError, + NotSupportedError +} = __nccwpck_require__(68707) +const assert = __nccwpck_require__(42613) +const { kHTTP2BuildRequest, kHTTP2CopyHeaders, kHTTP1BuildRequest } = __nccwpck_require__(36443) +const util = __nccwpck_require__(3440) + +// tokenRegExp and headerCharRegex have been lifted from +// https://github.com/nodejs/node/blob/main/lib/_http_common.js + +/** + * Verifies that the given val is a valid HTTP token + * per the rules defined in RFC 7230 + * See https://tools.ietf.org/html/rfc7230#section-3.2.6 + */ +const tokenRegExp = /^[\^_`a-zA-Z\-0-9!#$%&'*+.|~]+$/ + +/** + * Matches if val contains an invalid field-vchar + * field-value = *( field-content / obs-fold ) + * field-content = field-vchar [ 1*( SP / HTAB ) field-vchar ] + * field-vchar = VCHAR / obs-text + */ +const headerCharRegex = /[^\t\x20-\x7e\x80-\xff]/ + +// Verifies that a given path is valid does not contain control chars \x00 to \x20 +const invalidPathRegex = /[^\u0021-\u00ff]/ + +const kHandler = Symbol('handler') + +const channels = {} + +let extractBody + +try { + const diagnosticsChannel = __nccwpck_require__(31637) + channels.create = diagnosticsChannel.channel('undici:request:create') + channels.bodySent = diagnosticsChannel.channel('undici:request:bodySent') + channels.headers = diagnosticsChannel.channel('undici:request:headers') + channels.trailers = diagnosticsChannel.channel('undici:request:trailers') + channels.error = diagnosticsChannel.channel('undici:request:error') +} catch { + channels.create = { hasSubscribers: false } + channels.bodySent = { hasSubscribers: false } + channels.headers = { hasSubscribers: false } + channels.trailers = { hasSubscribers: false } + channels.error = { hasSubscribers: false } +} + +class Request { + constructor (origin, { + path, + method, + body, + headers, + query, + idempotent, + blocking, + upgrade, + headersTimeout, + bodyTimeout, + reset, + throwOnError, + expectContinue + }, handler) { + if (typeof path !== 'string') { + throw new InvalidArgumentError('path must be a string') + } else if ( + path[0] !== '/' && + !(path.startsWith('http://') || path.startsWith('https://')) && + method !== 'CONNECT' + ) { + throw new InvalidArgumentError('path must be an absolute URL or start with a slash') + } else if (invalidPathRegex.exec(path) !== null) { + throw new InvalidArgumentError('invalid request path') + } + + if (typeof method !== 'string') { + throw new InvalidArgumentError('method must be a string') + } else if (tokenRegExp.exec(method) === null) { + throw new InvalidArgumentError('invalid request method') + } + + if (upgrade && typeof upgrade !== 'string') { + throw new InvalidArgumentError('upgrade must be a string') + } + + if (headersTimeout != null && (!Number.isFinite(headersTimeout) || headersTimeout < 0)) { + throw new InvalidArgumentError('invalid headersTimeout') + } + + if (bodyTimeout != null && (!Number.isFinite(bodyTimeout) || bodyTimeout < 0)) { + throw new InvalidArgumentError('invalid bodyTimeout') + } + + if (reset != null && typeof reset !== 'boolean') { + throw new InvalidArgumentError('invalid reset') + } + + if (expectContinue != null && typeof expectContinue !== 'boolean') { + throw new InvalidArgumentError('invalid expectContinue') + } + + this.headersTimeout = headersTimeout + + this.bodyTimeout = bodyTimeout + + this.throwOnError = throwOnError === true + + this.method = method + + this.abort = null + + if (body == null) { + this.body = null + } else if (util.isStream(body)) { + this.body = body + + const rState = this.body._readableState + if (!rState || !rState.autoDestroy) { + this.endHandler = function autoDestroy () { + util.destroy(this) + } + this.body.on('end', this.endHandler) + } + + this.errorHandler = err => { + if (this.abort) { + this.abort(err) + } else { + this.error = err + } + } + this.body.on('error', this.errorHandler) + } else if (util.isBuffer(body)) { + this.body = body.byteLength ? body : null + } else if (ArrayBuffer.isView(body)) { + this.body = body.buffer.byteLength ? Buffer.from(body.buffer, body.byteOffset, body.byteLength) : null + } else if (body instanceof ArrayBuffer) { + this.body = body.byteLength ? Buffer.from(body) : null + } else if (typeof body === 'string') { + this.body = body.length ? Buffer.from(body) : null + } else if (util.isFormDataLike(body) || util.isIterable(body) || util.isBlobLike(body)) { + this.body = body + } else { + throw new InvalidArgumentError('body must be a string, a Buffer, a Readable stream, an iterable, or an async iterable') + } + + this.completed = false + + this.aborted = false + + this.upgrade = upgrade || null + + this.path = query ? util.buildURL(path, query) : path + + this.origin = origin + + this.idempotent = idempotent == null + ? method === 'HEAD' || method === 'GET' + : idempotent + + this.blocking = blocking == null ? false : blocking + + this.reset = reset == null ? null : reset + + this.host = null + + this.contentLength = null + + this.contentType = null + + this.headers = '' + + // Only for H2 + this.expectContinue = expectContinue != null ? expectContinue : false + + if (Array.isArray(headers)) { + if (headers.length % 2 !== 0) { + throw new InvalidArgumentError('headers array must be even') + } + for (let i = 0; i < headers.length; i += 2) { + processHeader(this, headers[i], headers[i + 1]) + } + } else if (headers && typeof headers === 'object') { + const keys = Object.keys(headers) + for (let i = 0; i < keys.length; i++) { + const key = keys[i] + processHeader(this, key, headers[key]) + } + } else if (headers != null) { + throw new InvalidArgumentError('headers must be an object or an array') + } + + if (util.isFormDataLike(this.body)) { + if (util.nodeMajor < 16 || (util.nodeMajor === 16 && util.nodeMinor < 8)) { + throw new InvalidArgumentError('Form-Data bodies are only supported in node v16.8 and newer.') + } + + if (!extractBody) { + extractBody = (__nccwpck_require__(8923).extractBody) + } + + const [bodyStream, contentType] = extractBody(body) + if (this.contentType == null) { + this.contentType = contentType + this.headers += `content-type: ${contentType}\r\n` + } + this.body = bodyStream.stream + this.contentLength = bodyStream.length + } else if (util.isBlobLike(body) && this.contentType == null && body.type) { + this.contentType = body.type + this.headers += `content-type: ${body.type}\r\n` + } + + util.validateHandler(handler, method, upgrade) + + this.servername = util.getServerName(this.host) + + this[kHandler] = handler + + if (channels.create.hasSubscribers) { + channels.create.publish({ request: this }) + } + } + + onBodySent (chunk) { + if (this[kHandler].onBodySent) { + try { + return this[kHandler].onBodySent(chunk) + } catch (err) { + this.abort(err) + } + } + } + + onRequestSent () { + if (channels.bodySent.hasSubscribers) { + channels.bodySent.publish({ request: this }) + } + + if (this[kHandler].onRequestSent) { + try { + return this[kHandler].onRequestSent() + } catch (err) { + this.abort(err) + } + } + } + + onConnect (abort) { + assert(!this.aborted) + assert(!this.completed) + + if (this.error) { + abort(this.error) + } else { + this.abort = abort + return this[kHandler].onConnect(abort) + } + } + + onHeaders (statusCode, headers, resume, statusText) { + assert(!this.aborted) + assert(!this.completed) + + if (channels.headers.hasSubscribers) { + channels.headers.publish({ request: this, response: { statusCode, headers, statusText } }) + } + + try { + return this[kHandler].onHeaders(statusCode, headers, resume, statusText) + } catch (err) { + this.abort(err) + } + } + + onData (chunk) { + assert(!this.aborted) + assert(!this.completed) + + try { + return this[kHandler].onData(chunk) + } catch (err) { + this.abort(err) + return false + } + } + + onUpgrade (statusCode, headers, socket) { + assert(!this.aborted) + assert(!this.completed) + + return this[kHandler].onUpgrade(statusCode, headers, socket) + } + + onComplete (trailers) { + this.onFinally() + + assert(!this.aborted) + + this.completed = true + if (channels.trailers.hasSubscribers) { + channels.trailers.publish({ request: this, trailers }) + } + + try { + return this[kHandler].onComplete(trailers) + } catch (err) { + // TODO (fix): This might be a bad idea? + this.onError(err) + } + } + + onError (error) { + this.onFinally() + + if (channels.error.hasSubscribers) { + channels.error.publish({ request: this, error }) + } + + if (this.aborted) { + return + } + this.aborted = true + + return this[kHandler].onError(error) + } + + onFinally () { + if (this.errorHandler) { + this.body.off('error', this.errorHandler) + this.errorHandler = null + } + + if (this.endHandler) { + this.body.off('end', this.endHandler) + this.endHandler = null + } + } + + // TODO: adjust to support H2 + addHeader (key, value) { + processHeader(this, key, value) + return this + } + + static [kHTTP1BuildRequest] (origin, opts, handler) { + // TODO: Migrate header parsing here, to make Requests + // HTTP agnostic + return new Request(origin, opts, handler) + } + + static [kHTTP2BuildRequest] (origin, opts, handler) { + const headers = opts.headers + opts = { ...opts, headers: null } + + const request = new Request(origin, opts, handler) + + request.headers = {} + + if (Array.isArray(headers)) { + if (headers.length % 2 !== 0) { + throw new InvalidArgumentError('headers array must be even') + } + for (let i = 0; i < headers.length; i += 2) { + processHeader(request, headers[i], headers[i + 1], true) + } + } else if (headers && typeof headers === 'object') { + const keys = Object.keys(headers) + for (let i = 0; i < keys.length; i++) { + const key = keys[i] + processHeader(request, key, headers[key], true) + } + } else if (headers != null) { + throw new InvalidArgumentError('headers must be an object or an array') + } + + return request + } + + static [kHTTP2CopyHeaders] (raw) { + const rawHeaders = raw.split('\r\n') + const headers = {} + + for (const header of rawHeaders) { + const [key, value] = header.split(': ') + + if (value == null || value.length === 0) continue + + if (headers[key]) headers[key] += `,${value}` + else headers[key] = value + } + + return headers + } +} + +function processHeaderValue (key, val, skipAppend) { + if (val && typeof val === 'object') { + throw new InvalidArgumentError(`invalid ${key} header`) + } + + val = val != null ? `${val}` : '' + + if (headerCharRegex.exec(val) !== null) { + throw new InvalidArgumentError(`invalid ${key} header`) + } + + return skipAppend ? val : `${key}: ${val}\r\n` +} + +function processHeader (request, key, val, skipAppend = false) { + if (val && (typeof val === 'object' && !Array.isArray(val))) { + throw new InvalidArgumentError(`invalid ${key} header`) + } else if (val === undefined) { + return + } + + if ( + request.host === null && + key.length === 4 && + key.toLowerCase() === 'host' + ) { + if (headerCharRegex.exec(val) !== null) { + throw new InvalidArgumentError(`invalid ${key} header`) + } + // Consumed by Client + request.host = val + } else if ( + request.contentLength === null && + key.length === 14 && + key.toLowerCase() === 'content-length' + ) { + request.contentLength = parseInt(val, 10) + if (!Number.isFinite(request.contentLength)) { + throw new InvalidArgumentError('invalid content-length header') + } + } else if ( + request.contentType === null && + key.length === 12 && + key.toLowerCase() === 'content-type' + ) { + request.contentType = val + if (skipAppend) request.headers[key] = processHeaderValue(key, val, skipAppend) + else request.headers += processHeaderValue(key, val) + } else if ( + key.length === 17 && + key.toLowerCase() === 'transfer-encoding' + ) { + throw new InvalidArgumentError('invalid transfer-encoding header') + } else if ( + key.length === 10 && + key.toLowerCase() === 'connection' + ) { + const value = typeof val === 'string' ? val.toLowerCase() : null + if (value !== 'close' && value !== 'keep-alive') { + throw new InvalidArgumentError('invalid connection header') + } else if (value === 'close') { + request.reset = true + } + } else if ( + key.length === 10 && + key.toLowerCase() === 'keep-alive' + ) { + throw new InvalidArgumentError('invalid keep-alive header') + } else if ( + key.length === 7 && + key.toLowerCase() === 'upgrade' + ) { + throw new InvalidArgumentError('invalid upgrade header') + } else if ( + key.length === 6 && + key.toLowerCase() === 'expect' + ) { + throw new NotSupportedError('expect header not supported') + } else if (tokenRegExp.exec(key) === null) { + throw new InvalidArgumentError('invalid header key') + } else { + if (Array.isArray(val)) { + for (let i = 0; i < val.length; i++) { + if (skipAppend) { + if (request.headers[key]) request.headers[key] += `,${processHeaderValue(key, val[i], skipAppend)}` + else request.headers[key] = processHeaderValue(key, val[i], skipAppend) + } else { + request.headers += processHeaderValue(key, val[i]) + } + } + } else { + if (skipAppend) request.headers[key] = processHeaderValue(key, val, skipAppend) + else request.headers += processHeaderValue(key, val) + } + } +} + +module.exports = Request + + +/***/ }), + +/***/ 36443: +/***/ ((module) => { + +module.exports = { + kClose: Symbol('close'), + kDestroy: Symbol('destroy'), + kDispatch: Symbol('dispatch'), + kUrl: Symbol('url'), + kWriting: Symbol('writing'), + kResuming: Symbol('resuming'), + kQueue: Symbol('queue'), + kConnect: Symbol('connect'), + kConnecting: Symbol('connecting'), + kHeadersList: Symbol('headers list'), + kKeepAliveDefaultTimeout: Symbol('default keep alive timeout'), + kKeepAliveMaxTimeout: Symbol('max keep alive timeout'), + kKeepAliveTimeoutThreshold: Symbol('keep alive timeout threshold'), + kKeepAliveTimeoutValue: Symbol('keep alive timeout'), + kKeepAlive: Symbol('keep alive'), + kHeadersTimeout: Symbol('headers timeout'), + kBodyTimeout: Symbol('body timeout'), + kServerName: Symbol('server name'), + kLocalAddress: Symbol('local address'), + kHost: Symbol('host'), + kNoRef: Symbol('no ref'), + kBodyUsed: Symbol('used'), + kRunning: Symbol('running'), + kBlocking: Symbol('blocking'), + kPending: Symbol('pending'), + kSize: Symbol('size'), + kBusy: Symbol('busy'), + kQueued: Symbol('queued'), + kFree: Symbol('free'), + kConnected: Symbol('connected'), + kClosed: Symbol('closed'), + kNeedDrain: Symbol('need drain'), + kReset: Symbol('reset'), + kDestroyed: Symbol.for('nodejs.stream.destroyed'), + kMaxHeadersSize: Symbol('max headers size'), + kRunningIdx: Symbol('running index'), + kPendingIdx: Symbol('pending index'), + kError: Symbol('error'), + kClients: Symbol('clients'), + kClient: Symbol('client'), + kParser: Symbol('parser'), + kOnDestroyed: Symbol('destroy callbacks'), + kPipelining: Symbol('pipelining'), + kSocket: Symbol('socket'), + kHostHeader: Symbol('host header'), + kConnector: Symbol('connector'), + kStrictContentLength: Symbol('strict content length'), + kMaxRedirections: Symbol('maxRedirections'), + kMaxRequests: Symbol('maxRequestsPerClient'), + kProxy: Symbol('proxy agent options'), + kCounter: Symbol('socket request counter'), + kInterceptors: Symbol('dispatch interceptors'), + kMaxResponseSize: Symbol('max response size'), + kHTTP2Session: Symbol('http2Session'), + kHTTP2SessionState: Symbol('http2Session state'), + kHTTP2BuildRequest: Symbol('http2 build request'), + kHTTP1BuildRequest: Symbol('http1 build request'), + kHTTP2CopyHeaders: Symbol('http2 copy headers'), + kHTTPConnVersion: Symbol('http connection version'), + kRetryHandlerDefaultRetry: Symbol('retry agent default retry'), + kConstruct: Symbol('constructable') +} + + +/***/ }), + +/***/ 3440: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const assert = __nccwpck_require__(42613) +const { kDestroyed, kBodyUsed } = __nccwpck_require__(36443) +const { IncomingMessage } = __nccwpck_require__(58611) +const stream = __nccwpck_require__(2203) +const net = __nccwpck_require__(69278) +const { InvalidArgumentError } = __nccwpck_require__(68707) +const { Blob } = __nccwpck_require__(20181) +const nodeUtil = __nccwpck_require__(39023) +const { stringify } = __nccwpck_require__(83480) +const { headerNameLowerCasedRecord } = __nccwpck_require__(10735) + +const [nodeMajor, nodeMinor] = process.versions.node.split('.').map(v => Number(v)) + +function nop () {} + +function isStream (obj) { + return obj && typeof obj === 'object' && typeof obj.pipe === 'function' && typeof obj.on === 'function' +} + +// based on https://github.com/node-fetch/fetch-blob/blob/8ab587d34080de94140b54f07168451e7d0b655e/index.js#L229-L241 (MIT License) +function isBlobLike (object) { + return (Blob && object instanceof Blob) || ( + object && + typeof object === 'object' && + (typeof object.stream === 'function' || + typeof object.arrayBuffer === 'function') && + /^(Blob|File)$/.test(object[Symbol.toStringTag]) + ) +} + +function buildURL (url, queryParams) { + if (url.includes('?') || url.includes('#')) { + throw new Error('Query params cannot be passed when url already contains "?" or "#".') + } + + const stringified = stringify(queryParams) + + if (stringified) { + url += '?' + stringified + } + + return url +} + +function parseURL (url) { + if (typeof url === 'string') { + url = new URL(url) + + if (!/^https?:/.test(url.origin || url.protocol)) { + throw new InvalidArgumentError('Invalid URL protocol: the URL must start with `http:` or `https:`.') + } + + return url + } + + if (!url || typeof url !== 'object') { + throw new InvalidArgumentError('Invalid URL: The URL argument must be a non-null object.') + } + + if (!/^https?:/.test(url.origin || url.protocol)) { + throw new InvalidArgumentError('Invalid URL protocol: the URL must start with `http:` or `https:`.') + } + + if (!(url instanceof URL)) { + if (url.port != null && url.port !== '' && !Number.isFinite(parseInt(url.port))) { + throw new InvalidArgumentError('Invalid URL: port must be a valid integer or a string representation of an integer.') + } + + if (url.path != null && typeof url.path !== 'string') { + throw new InvalidArgumentError('Invalid URL path: the path must be a string or null/undefined.') + } + + if (url.pathname != null && typeof url.pathname !== 'string') { + throw new InvalidArgumentError('Invalid URL pathname: the pathname must be a string or null/undefined.') + } + + if (url.hostname != null && typeof url.hostname !== 'string') { + throw new InvalidArgumentError('Invalid URL hostname: the hostname must be a string or null/undefined.') + } + + if (url.origin != null && typeof url.origin !== 'string') { + throw new InvalidArgumentError('Invalid URL origin: the origin must be a string or null/undefined.') + } + + const port = url.port != null + ? url.port + : (url.protocol === 'https:' ? 443 : 80) + let origin = url.origin != null + ? url.origin + : `${url.protocol}//${url.hostname}:${port}` + let path = url.path != null + ? url.path + : `${url.pathname || ''}${url.search || ''}` + + if (origin.endsWith('/')) { + origin = origin.substring(0, origin.length - 1) + } + + if (path && !path.startsWith('/')) { + path = `/${path}` + } + // new URL(path, origin) is unsafe when `path` contains an absolute URL + // From https://developer.mozilla.org/en-US/docs/Web/API/URL/URL: + // If first parameter is a relative URL, second param is required, and will be used as the base URL. + // If first parameter is an absolute URL, a given second param will be ignored. + url = new URL(origin + path) + } + + return url +} + +function parseOrigin (url) { + url = parseURL(url) + + if (url.pathname !== '/' || url.search || url.hash) { + throw new InvalidArgumentError('invalid url') + } + + return url +} + +function getHostname (host) { + if (host[0] === '[') { + const idx = host.indexOf(']') + + assert(idx !== -1) + return host.substring(1, idx) + } + + const idx = host.indexOf(':') + if (idx === -1) return host + + return host.substring(0, idx) +} + +// IP addresses are not valid server names per RFC6066 +// > Currently, the only server names supported are DNS hostnames +function getServerName (host) { + if (!host) { + return null + } + + assert.strictEqual(typeof host, 'string') + + const servername = getHostname(host) + if (net.isIP(servername)) { + return '' + } + + return servername +} + +function deepClone (obj) { + return JSON.parse(JSON.stringify(obj)) +} + +function isAsyncIterable (obj) { + return !!(obj != null && typeof obj[Symbol.asyncIterator] === 'function') +} + +function isIterable (obj) { + return !!(obj != null && (typeof obj[Symbol.iterator] === 'function' || typeof obj[Symbol.asyncIterator] === 'function')) +} + +function bodyLength (body) { + if (body == null) { + return 0 + } else if (isStream(body)) { + const state = body._readableState + return state && state.objectMode === false && state.ended === true && Number.isFinite(state.length) + ? state.length + : null + } else if (isBlobLike(body)) { + return body.size != null ? body.size : null + } else if (isBuffer(body)) { + return body.byteLength + } + + return null +} + +function isDestroyed (stream) { + return !stream || !!(stream.destroyed || stream[kDestroyed]) +} + +function isReadableAborted (stream) { + const state = stream && stream._readableState + return isDestroyed(stream) && state && !state.endEmitted +} + +function destroy (stream, err) { + if (stream == null || !isStream(stream) || isDestroyed(stream)) { + return + } + + if (typeof stream.destroy === 'function') { + if (Object.getPrototypeOf(stream).constructor === IncomingMessage) { + // See: https://github.com/nodejs/node/pull/38505/files + stream.socket = null + } + + stream.destroy(err) + } else if (err) { + process.nextTick((stream, err) => { + stream.emit('error', err) + }, stream, err) + } + + if (stream.destroyed !== true) { + stream[kDestroyed] = true + } +} + +const KEEPALIVE_TIMEOUT_EXPR = /timeout=(\d+)/ +function parseKeepAliveTimeout (val) { + const m = val.toString().match(KEEPALIVE_TIMEOUT_EXPR) + return m ? parseInt(m[1], 10) * 1000 : null +} + +/** + * Retrieves a header name and returns its lowercase value. + * @param {string | Buffer} value Header name + * @returns {string} + */ +function headerNameToString (value) { + return headerNameLowerCasedRecord[value] || value.toLowerCase() +} + +function parseHeaders (headers, obj = {}) { + // For H2 support + if (!Array.isArray(headers)) return headers + + for (let i = 0; i < headers.length; i += 2) { + const key = headers[i].toString().toLowerCase() + let val = obj[key] + + if (!val) { + if (Array.isArray(headers[i + 1])) { + obj[key] = headers[i + 1].map(x => x.toString('utf8')) + } else { + obj[key] = headers[i + 1].toString('utf8') + } + } else { + if (!Array.isArray(val)) { + val = [val] + obj[key] = val + } + val.push(headers[i + 1].toString('utf8')) + } + } + + // See https://github.com/nodejs/node/pull/46528 + if ('content-length' in obj && 'content-disposition' in obj) { + obj['content-disposition'] = Buffer.from(obj['content-disposition']).toString('latin1') + } + + return obj +} + +function parseRawHeaders (headers) { + const ret = [] + let hasContentLength = false + let contentDispositionIdx = -1 + + for (let n = 0; n < headers.length; n += 2) { + const key = headers[n + 0].toString() + const val = headers[n + 1].toString('utf8') + + if (key.length === 14 && (key === 'content-length' || key.toLowerCase() === 'content-length')) { + ret.push(key, val) + hasContentLength = true + } else if (key.length === 19 && (key === 'content-disposition' || key.toLowerCase() === 'content-disposition')) { + contentDispositionIdx = ret.push(key, val) - 1 + } else { + ret.push(key, val) + } + } + + // See https://github.com/nodejs/node/pull/46528 + if (hasContentLength && contentDispositionIdx !== -1) { + ret[contentDispositionIdx] = Buffer.from(ret[contentDispositionIdx]).toString('latin1') + } + + return ret +} + +function isBuffer (buffer) { + // See, https://github.com/mcollina/undici/pull/319 + return buffer instanceof Uint8Array || Buffer.isBuffer(buffer) +} + +function validateHandler (handler, method, upgrade) { + if (!handler || typeof handler !== 'object') { + throw new InvalidArgumentError('handler must be an object') + } + + if (typeof handler.onConnect !== 'function') { + throw new InvalidArgumentError('invalid onConnect method') + } + + if (typeof handler.onError !== 'function') { + throw new InvalidArgumentError('invalid onError method') + } + + if (typeof handler.onBodySent !== 'function' && handler.onBodySent !== undefined) { + throw new InvalidArgumentError('invalid onBodySent method') + } + + if (upgrade || method === 'CONNECT') { + if (typeof handler.onUpgrade !== 'function') { + throw new InvalidArgumentError('invalid onUpgrade method') + } + } else { + if (typeof handler.onHeaders !== 'function') { + throw new InvalidArgumentError('invalid onHeaders method') + } + + if (typeof handler.onData !== 'function') { + throw new InvalidArgumentError('invalid onData method') + } + + if (typeof handler.onComplete !== 'function') { + throw new InvalidArgumentError('invalid onComplete method') + } + } +} + +// A body is disturbed if it has been read from and it cannot +// be re-used without losing state or data. +function isDisturbed (body) { + return !!(body && ( + stream.isDisturbed + ? stream.isDisturbed(body) || body[kBodyUsed] // TODO (fix): Why is body[kBodyUsed] needed? + : body[kBodyUsed] || + body.readableDidRead || + (body._readableState && body._readableState.dataEmitted) || + isReadableAborted(body) + )) +} + +function isErrored (body) { + return !!(body && ( + stream.isErrored + ? stream.isErrored(body) + : /state: 'errored'/.test(nodeUtil.inspect(body) + ))) +} + +function isReadable (body) { + return !!(body && ( + stream.isReadable + ? stream.isReadable(body) + : /state: 'readable'/.test(nodeUtil.inspect(body) + ))) +} + +function getSocketInfo (socket) { + return { + localAddress: socket.localAddress, + localPort: socket.localPort, + remoteAddress: socket.remoteAddress, + remotePort: socket.remotePort, + remoteFamily: socket.remoteFamily, + timeout: socket.timeout, + bytesWritten: socket.bytesWritten, + bytesRead: socket.bytesRead + } +} + +async function * convertIterableToBuffer (iterable) { + for await (const chunk of iterable) { + yield Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk) + } +} + +let ReadableStream +function ReadableStreamFrom (iterable) { + if (!ReadableStream) { + ReadableStream = (__nccwpck_require__(63774).ReadableStream) + } + + if (ReadableStream.from) { + return ReadableStream.from(convertIterableToBuffer(iterable)) + } + + let iterator + return new ReadableStream( + { + async start () { + iterator = iterable[Symbol.asyncIterator]() + }, + async pull (controller) { + const { done, value } = await iterator.next() + if (done) { + queueMicrotask(() => { + controller.close() + }) + } else { + const buf = Buffer.isBuffer(value) ? value : Buffer.from(value) + controller.enqueue(new Uint8Array(buf)) + } + return controller.desiredSize > 0 + }, + async cancel (reason) { + await iterator.return() + } + }, + 0 + ) +} + +// The chunk should be a FormData instance and contains +// all the required methods. +function isFormDataLike (object) { + return ( + object && + typeof object === 'object' && + typeof object.append === 'function' && + typeof object.delete === 'function' && + typeof object.get === 'function' && + typeof object.getAll === 'function' && + typeof object.has === 'function' && + typeof object.set === 'function' && + object[Symbol.toStringTag] === 'FormData' + ) +} + +function throwIfAborted (signal) { + if (!signal) { return } + if (typeof signal.throwIfAborted === 'function') { + signal.throwIfAborted() + } else { + if (signal.aborted) { + // DOMException not available < v17.0.0 + const err = new Error('The operation was aborted') + err.name = 'AbortError' + throw err + } + } +} + +function addAbortListener (signal, listener) { + if ('addEventListener' in signal) { + signal.addEventListener('abort', listener, { once: true }) + return () => signal.removeEventListener('abort', listener) + } + signal.addListener('abort', listener) + return () => signal.removeListener('abort', listener) +} + +const hasToWellFormed = !!String.prototype.toWellFormed + +/** + * @param {string} val + */ +function toUSVString (val) { + if (hasToWellFormed) { + return `${val}`.toWellFormed() + } else if (nodeUtil.toUSVString) { + return nodeUtil.toUSVString(val) + } + + return `${val}` +} + +// Parsed accordingly to RFC 9110 +// https://www.rfc-editor.org/rfc/rfc9110#field.content-range +function parseRangeHeader (range) { + if (range == null || range === '') return { start: 0, end: null, size: null } + + const m = range ? range.match(/^bytes (\d+)-(\d+)\/(\d+)?$/) : null + return m + ? { + start: parseInt(m[1]), + end: m[2] ? parseInt(m[2]) : null, + size: m[3] ? parseInt(m[3]) : null + } + : null +} + +const kEnumerableProperty = Object.create(null) +kEnumerableProperty.enumerable = true + +module.exports = { + kEnumerableProperty, + nop, + isDisturbed, + isErrored, + isReadable, + toUSVString, + isReadableAborted, + isBlobLike, + parseOrigin, + parseURL, + getServerName, + isStream, + isIterable, + isAsyncIterable, + isDestroyed, + headerNameToString, + parseRawHeaders, + parseHeaders, + parseKeepAliveTimeout, + destroy, + bodyLength, + deepClone, + ReadableStreamFrom, + isBuffer, + validateHandler, + getSocketInfo, + isFormDataLike, + buildURL, + throwIfAborted, + addAbortListener, + parseRangeHeader, + nodeMajor, + nodeMinor, + nodeHasAutoSelectFamily: nodeMajor > 18 || (nodeMajor === 18 && nodeMinor >= 13), + safeHTTPMethods: ['GET', 'HEAD', 'OPTIONS', 'TRACE'] +} + + +/***/ }), + +/***/ 50001: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const Dispatcher = __nccwpck_require__(28611) +const { + ClientDestroyedError, + ClientClosedError, + InvalidArgumentError +} = __nccwpck_require__(68707) +const { kDestroy, kClose, kDispatch, kInterceptors } = __nccwpck_require__(36443) + +const kDestroyed = Symbol('destroyed') +const kClosed = Symbol('closed') +const kOnDestroyed = Symbol('onDestroyed') +const kOnClosed = Symbol('onClosed') +const kInterceptedDispatch = Symbol('Intercepted Dispatch') + +class DispatcherBase extends Dispatcher { + constructor () { + super() + + this[kDestroyed] = false + this[kOnDestroyed] = null + this[kClosed] = false + this[kOnClosed] = [] + } + + get destroyed () { + return this[kDestroyed] + } + + get closed () { + return this[kClosed] + } + + get interceptors () { + return this[kInterceptors] + } + + set interceptors (newInterceptors) { + if (newInterceptors) { + for (let i = newInterceptors.length - 1; i >= 0; i--) { + const interceptor = this[kInterceptors][i] + if (typeof interceptor !== 'function') { + throw new InvalidArgumentError('interceptor must be an function') + } + } + } + + this[kInterceptors] = newInterceptors + } + + close (callback) { + if (callback === undefined) { + return new Promise((resolve, reject) => { + this.close((err, data) => { + return err ? reject(err) : resolve(data) + }) + }) + } + + if (typeof callback !== 'function') { + throw new InvalidArgumentError('invalid callback') + } + + if (this[kDestroyed]) { + queueMicrotask(() => callback(new ClientDestroyedError(), null)) + return + } + + if (this[kClosed]) { + if (this[kOnClosed]) { + this[kOnClosed].push(callback) + } else { + queueMicrotask(() => callback(null, null)) + } + return + } + + this[kClosed] = true + this[kOnClosed].push(callback) + + const onClosed = () => { + const callbacks = this[kOnClosed] + this[kOnClosed] = null + for (let i = 0; i < callbacks.length; i++) { + callbacks[i](null, null) + } + } + + // Should not error. + this[kClose]() + .then(() => this.destroy()) + .then(() => { + queueMicrotask(onClosed) + }) + } + + destroy (err, callback) { + if (typeof err === 'function') { + callback = err + err = null + } + + if (callback === undefined) { + return new Promise((resolve, reject) => { + this.destroy(err, (err, data) => { + return err ? /* istanbul ignore next: should never error */ reject(err) : resolve(data) + }) + }) + } + + if (typeof callback !== 'function') { + throw new InvalidArgumentError('invalid callback') + } + + if (this[kDestroyed]) { + if (this[kOnDestroyed]) { + this[kOnDestroyed].push(callback) + } else { + queueMicrotask(() => callback(null, null)) + } + return + } + + if (!err) { + err = new ClientDestroyedError() + } + + this[kDestroyed] = true + this[kOnDestroyed] = this[kOnDestroyed] || [] + this[kOnDestroyed].push(callback) + + const onDestroyed = () => { + const callbacks = this[kOnDestroyed] + this[kOnDestroyed] = null + for (let i = 0; i < callbacks.length; i++) { + callbacks[i](null, null) + } + } + + // Should not error. + this[kDestroy](err).then(() => { + queueMicrotask(onDestroyed) + }) + } + + [kInterceptedDispatch] (opts, handler) { + if (!this[kInterceptors] || this[kInterceptors].length === 0) { + this[kInterceptedDispatch] = this[kDispatch] + return this[kDispatch](opts, handler) + } + + let dispatch = this[kDispatch].bind(this) + for (let i = this[kInterceptors].length - 1; i >= 0; i--) { + dispatch = this[kInterceptors][i](dispatch) + } + this[kInterceptedDispatch] = dispatch + return dispatch(opts, handler) + } + + dispatch (opts, handler) { + if (!handler || typeof handler !== 'object') { + throw new InvalidArgumentError('handler must be an object') + } + + try { + if (!opts || typeof opts !== 'object') { + throw new InvalidArgumentError('opts must be an object.') + } + + if (this[kDestroyed] || this[kOnDestroyed]) { + throw new ClientDestroyedError() + } + + if (this[kClosed]) { + throw new ClientClosedError() + } + + return this[kInterceptedDispatch](opts, handler) + } catch (err) { + if (typeof handler.onError !== 'function') { + throw new InvalidArgumentError('invalid onError method') + } + + handler.onError(err) + + return false + } + } +} + +module.exports = DispatcherBase + + +/***/ }), + +/***/ 28611: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const EventEmitter = __nccwpck_require__(24434) + +class Dispatcher extends EventEmitter { + dispatch () { + throw new Error('not implemented') + } + + close () { + throw new Error('not implemented') + } + + destroy () { + throw new Error('not implemented') + } +} + +module.exports = Dispatcher + + +/***/ }), + +/***/ 8923: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const Busboy = __nccwpck_require__(89581) +const util = __nccwpck_require__(3440) +const { + ReadableStreamFrom, + isBlobLike, + isReadableStreamLike, + readableStreamClose, + createDeferredPromise, + fullyReadBody +} = __nccwpck_require__(15523) +const { FormData } = __nccwpck_require__(43073) +const { kState } = __nccwpck_require__(89710) +const { webidl } = __nccwpck_require__(74222) +const { DOMException, structuredClone } = __nccwpck_require__(87326) +const { Blob, File: NativeFile } = __nccwpck_require__(20181) +const { kBodyUsed } = __nccwpck_require__(36443) +const assert = __nccwpck_require__(42613) +const { isErrored } = __nccwpck_require__(3440) +const { isUint8Array, isArrayBuffer } = __nccwpck_require__(98253) +const { File: UndiciFile } = __nccwpck_require__(63041) +const { parseMIMEType, serializeAMimeType } = __nccwpck_require__(94322) + +let random +try { + const crypto = __nccwpck_require__(77598) + random = (max) => crypto.randomInt(0, max) +} catch { + random = (max) => Math.floor(Math.random(max)) +} + +let ReadableStream = globalThis.ReadableStream + +/** @type {globalThis['File']} */ +const File = NativeFile ?? UndiciFile +const textEncoder = new TextEncoder() +const textDecoder = new TextDecoder() + +// https://fetch.spec.whatwg.org/#concept-bodyinit-extract +function extractBody (object, keepalive = false) { + if (!ReadableStream) { + ReadableStream = (__nccwpck_require__(63774).ReadableStream) + } + + // 1. Let stream be null. + let stream = null + + // 2. If object is a ReadableStream object, then set stream to object. + if (object instanceof ReadableStream) { + stream = object + } else if (isBlobLike(object)) { + // 3. Otherwise, if object is a Blob object, set stream to the + // result of running object’s get stream. + stream = object.stream() + } else { + // 4. Otherwise, set stream to a new ReadableStream object, and set + // up stream. + stream = new ReadableStream({ + async pull (controller) { + controller.enqueue( + typeof source === 'string' ? textEncoder.encode(source) : source + ) + queueMicrotask(() => readableStreamClose(controller)) + }, + start () {}, + type: undefined + }) + } + + // 5. Assert: stream is a ReadableStream object. + assert(isReadableStreamLike(stream)) + + // 6. Let action be null. + let action = null + + // 7. Let source be null. + let source = null + + // 8. Let length be null. + let length = null + + // 9. Let type be null. + let type = null + + // 10. Switch on object: + if (typeof object === 'string') { + // Set source to the UTF-8 encoding of object. + // Note: setting source to a Uint8Array here breaks some mocking assumptions. + source = object + + // Set type to `text/plain;charset=UTF-8`. + type = 'text/plain;charset=UTF-8' + } else if (object instanceof URLSearchParams) { + // URLSearchParams + + // spec says to run application/x-www-form-urlencoded on body.list + // this is implemented in Node.js as apart of an URLSearchParams instance toString method + // See: https://github.com/nodejs/node/blob/e46c680bf2b211bbd52cf959ca17ee98c7f657f5/lib/internal/url.js#L490 + // and https://github.com/nodejs/node/blob/e46c680bf2b211bbd52cf959ca17ee98c7f657f5/lib/internal/url.js#L1100 + + // Set source to the result of running the application/x-www-form-urlencoded serializer with object’s list. + source = object.toString() + + // Set type to `application/x-www-form-urlencoded;charset=UTF-8`. + type = 'application/x-www-form-urlencoded;charset=UTF-8' + } else if (isArrayBuffer(object)) { + // BufferSource/ArrayBuffer + + // Set source to a copy of the bytes held by object. + source = new Uint8Array(object.slice()) + } else if (ArrayBuffer.isView(object)) { + // BufferSource/ArrayBufferView + + // Set source to a copy of the bytes held by object. + source = new Uint8Array(object.buffer.slice(object.byteOffset, object.byteOffset + object.byteLength)) + } else if (util.isFormDataLike(object)) { + const boundary = `----formdata-undici-0${`${random(1e11)}`.padStart(11, '0')}` + const prefix = `--${boundary}\r\nContent-Disposition: form-data` + + /*! formdata-polyfill. MIT License. Jimmy Wärting */ + const escape = (str) => + str.replace(/\n/g, '%0A').replace(/\r/g, '%0D').replace(/"/g, '%22') + const normalizeLinefeeds = (value) => value.replace(/\r?\n|\r/g, '\r\n') + + // Set action to this step: run the multipart/form-data + // encoding algorithm, with object’s entry list and UTF-8. + // - This ensures that the body is immutable and can't be changed afterwords + // - That the content-length is calculated in advance. + // - And that all parts are pre-encoded and ready to be sent. + + const blobParts = [] + const rn = new Uint8Array([13, 10]) // '\r\n' + length = 0 + let hasUnknownSizeValue = false + + for (const [name, value] of object) { + if (typeof value === 'string') { + const chunk = textEncoder.encode(prefix + + `; name="${escape(normalizeLinefeeds(name))}"` + + `\r\n\r\n${normalizeLinefeeds(value)}\r\n`) + blobParts.push(chunk) + length += chunk.byteLength + } else { + const chunk = textEncoder.encode(`${prefix}; name="${escape(normalizeLinefeeds(name))}"` + + (value.name ? `; filename="${escape(value.name)}"` : '') + '\r\n' + + `Content-Type: ${ + value.type || 'application/octet-stream' + }\r\n\r\n`) + blobParts.push(chunk, value, rn) + if (typeof value.size === 'number') { + length += chunk.byteLength + value.size + rn.byteLength + } else { + hasUnknownSizeValue = true + } + } + } + + const chunk = textEncoder.encode(`--${boundary}--`) + blobParts.push(chunk) + length += chunk.byteLength + if (hasUnknownSizeValue) { + length = null + } + + // Set source to object. + source = object + + action = async function * () { + for (const part of blobParts) { + if (part.stream) { + yield * part.stream() + } else { + yield part + } + } + } + + // Set type to `multipart/form-data; boundary=`, + // followed by the multipart/form-data boundary string generated + // by the multipart/form-data encoding algorithm. + type = 'multipart/form-data; boundary=' + boundary + } else if (isBlobLike(object)) { + // Blob + + // Set source to object. + source = object + + // Set length to object’s size. + length = object.size + + // If object’s type attribute is not the empty byte sequence, set + // type to its value. + if (object.type) { + type = object.type + } + } else if (typeof object[Symbol.asyncIterator] === 'function') { + // If keepalive is true, then throw a TypeError. + if (keepalive) { + throw new TypeError('keepalive') + } + + // If object is disturbed or locked, then throw a TypeError. + if (util.isDisturbed(object) || object.locked) { + throw new TypeError( + 'Response body object should not be disturbed or locked' + ) + } + + stream = + object instanceof ReadableStream ? object : ReadableStreamFrom(object) + } + + // 11. If source is a byte sequence, then set action to a + // step that returns source and length to source’s length. + if (typeof source === 'string' || util.isBuffer(source)) { + length = Buffer.byteLength(source) + } + + // 12. If action is non-null, then run these steps in in parallel: + if (action != null) { + // Run action. + let iterator + stream = new ReadableStream({ + async start () { + iterator = action(object)[Symbol.asyncIterator]() + }, + async pull (controller) { + const { value, done } = await iterator.next() + if (done) { + // When running action is done, close stream. + queueMicrotask(() => { + controller.close() + }) + } else { + // Whenever one or more bytes are available and stream is not errored, + // enqueue a Uint8Array wrapping an ArrayBuffer containing the available + // bytes into stream. + if (!isErrored(stream)) { + controller.enqueue(new Uint8Array(value)) + } + } + return controller.desiredSize > 0 + }, + async cancel (reason) { + await iterator.return() + }, + type: undefined + }) + } + + // 13. Let body be a body whose stream is stream, source is source, + // and length is length. + const body = { stream, source, length } + + // 14. Return (body, type). + return [body, type] +} + +// https://fetch.spec.whatwg.org/#bodyinit-safely-extract +function safelyExtractBody (object, keepalive = false) { + if (!ReadableStream) { + // istanbul ignore next + ReadableStream = (__nccwpck_require__(63774).ReadableStream) + } + + // To safely extract a body and a `Content-Type` value from + // a byte sequence or BodyInit object object, run these steps: + + // 1. If object is a ReadableStream object, then: + if (object instanceof ReadableStream) { + // Assert: object is neither disturbed nor locked. + // istanbul ignore next + assert(!util.isDisturbed(object), 'The body has already been consumed.') + // istanbul ignore next + assert(!object.locked, 'The stream is locked.') + } + + // 2. Return the results of extracting object. + return extractBody(object, keepalive) +} + +function cloneBody (body) { + // To clone a body body, run these steps: + + // https://fetch.spec.whatwg.org/#concept-body-clone + + // 1. Let « out1, out2 » be the result of teeing body’s stream. + const [out1, out2] = body.stream.tee() + const out2Clone = structuredClone(out2, { transfer: [out2] }) + // This, for whatever reasons, unrefs out2Clone which allows + // the process to exit by itself. + const [, finalClone] = out2Clone.tee() + + // 2. Set body’s stream to out1. + body.stream = out1 + + // 3. Return a body whose stream is out2 and other members are copied from body. + return { + stream: finalClone, + length: body.length, + source: body.source + } +} + +async function * consumeBody (body) { + if (body) { + if (isUint8Array(body)) { + yield body + } else { + const stream = body.stream + + if (util.isDisturbed(stream)) { + throw new TypeError('The body has already been consumed.') + } + + if (stream.locked) { + throw new TypeError('The stream is locked.') + } + + // Compat. + stream[kBodyUsed] = true + + yield * stream + } + } +} + +function throwIfAborted (state) { + if (state.aborted) { + throw new DOMException('The operation was aborted.', 'AbortError') + } +} + +function bodyMixinMethods (instance) { + const methods = { + blob () { + // The blob() method steps are to return the result of + // running consume body with this and the following step + // given a byte sequence bytes: return a Blob whose + // contents are bytes and whose type attribute is this’s + // MIME type. + return specConsumeBody(this, (bytes) => { + let mimeType = bodyMimeType(this) + + if (mimeType === 'failure') { + mimeType = '' + } else if (mimeType) { + mimeType = serializeAMimeType(mimeType) + } + + // Return a Blob whose contents are bytes and type attribute + // is mimeType. + return new Blob([bytes], { type: mimeType }) + }, instance) + }, + + arrayBuffer () { + // The arrayBuffer() method steps are to return the result + // of running consume body with this and the following step + // given a byte sequence bytes: return a new ArrayBuffer + // whose contents are bytes. + return specConsumeBody(this, (bytes) => { + return new Uint8Array(bytes).buffer + }, instance) + }, + + text () { + // The text() method steps are to return the result of running + // consume body with this and UTF-8 decode. + return specConsumeBody(this, utf8DecodeBytes, instance) + }, + + json () { + // The json() method steps are to return the result of running + // consume body with this and parse JSON from bytes. + return specConsumeBody(this, parseJSONFromBytes, instance) + }, + + async formData () { + webidl.brandCheck(this, instance) + + throwIfAborted(this[kState]) + + const contentType = this.headers.get('Content-Type') + + // If mimeType’s essence is "multipart/form-data", then: + if (/multipart\/form-data/.test(contentType)) { + const headers = {} + for (const [key, value] of this.headers) headers[key.toLowerCase()] = value + + const responseFormData = new FormData() + + let busboy + + try { + busboy = new Busboy({ + headers, + preservePath: true + }) + } catch (err) { + throw new DOMException(`${err}`, 'AbortError') + } + + busboy.on('field', (name, value) => { + responseFormData.append(name, value) + }) + busboy.on('file', (name, value, filename, encoding, mimeType) => { + const chunks = [] + + if (encoding === 'base64' || encoding.toLowerCase() === 'base64') { + let base64chunk = '' + + value.on('data', (chunk) => { + base64chunk += chunk.toString().replace(/[\r\n]/gm, '') + + const end = base64chunk.length - base64chunk.length % 4 + chunks.push(Buffer.from(base64chunk.slice(0, end), 'base64')) + + base64chunk = base64chunk.slice(end) + }) + value.on('end', () => { + chunks.push(Buffer.from(base64chunk, 'base64')) + responseFormData.append(name, new File(chunks, filename, { type: mimeType })) + }) + } else { + value.on('data', (chunk) => { + chunks.push(chunk) + }) + value.on('end', () => { + responseFormData.append(name, new File(chunks, filename, { type: mimeType })) + }) + } + }) + + const busboyResolve = new Promise((resolve, reject) => { + busboy.on('finish', resolve) + busboy.on('error', (err) => reject(new TypeError(err))) + }) + + if (this.body !== null) for await (const chunk of consumeBody(this[kState].body)) busboy.write(chunk) + busboy.end() + await busboyResolve + + return responseFormData + } else if (/application\/x-www-form-urlencoded/.test(contentType)) { + // Otherwise, if mimeType’s essence is "application/x-www-form-urlencoded", then: + + // 1. Let entries be the result of parsing bytes. + let entries + try { + let text = '' + // application/x-www-form-urlencoded parser will keep the BOM. + // https://url.spec.whatwg.org/#concept-urlencoded-parser + // Note that streaming decoder is stateful and cannot be reused + const streamingDecoder = new TextDecoder('utf-8', { ignoreBOM: true }) + + for await (const chunk of consumeBody(this[kState].body)) { + if (!isUint8Array(chunk)) { + throw new TypeError('Expected Uint8Array chunk') + } + text += streamingDecoder.decode(chunk, { stream: true }) + } + text += streamingDecoder.decode() + entries = new URLSearchParams(text) + } catch (err) { + // istanbul ignore next: Unclear when new URLSearchParams can fail on a string. + // 2. If entries is failure, then throw a TypeError. + throw Object.assign(new TypeError(), { cause: err }) + } + + // 3. Return a new FormData object whose entries are entries. + const formData = new FormData() + for (const [name, value] of entries) { + formData.append(name, value) + } + return formData + } else { + // Wait a tick before checking if the request has been aborted. + // Otherwise, a TypeError can be thrown when an AbortError should. + await Promise.resolve() + + throwIfAborted(this[kState]) + + // Otherwise, throw a TypeError. + throw webidl.errors.exception({ + header: `${instance.name}.formData`, + message: 'Could not parse content as FormData.' + }) + } + } + } + + return methods +} + +function mixinBody (prototype) { + Object.assign(prototype.prototype, bodyMixinMethods(prototype)) +} + +/** + * @see https://fetch.spec.whatwg.org/#concept-body-consume-body + * @param {Response|Request} object + * @param {(value: unknown) => unknown} convertBytesToJSValue + * @param {Response|Request} instance + */ +async function specConsumeBody (object, convertBytesToJSValue, instance) { + webidl.brandCheck(object, instance) + + throwIfAborted(object[kState]) + + // 1. If object is unusable, then return a promise rejected + // with a TypeError. + if (bodyUnusable(object[kState].body)) { + throw new TypeError('Body is unusable') + } + + // 2. Let promise be a new promise. + const promise = createDeferredPromise() + + // 3. Let errorSteps given error be to reject promise with error. + const errorSteps = (error) => promise.reject(error) + + // 4. Let successSteps given a byte sequence data be to resolve + // promise with the result of running convertBytesToJSValue + // with data. If that threw an exception, then run errorSteps + // with that exception. + const successSteps = (data) => { + try { + promise.resolve(convertBytesToJSValue(data)) + } catch (e) { + errorSteps(e) + } + } + + // 5. If object’s body is null, then run successSteps with an + // empty byte sequence. + if (object[kState].body == null) { + successSteps(new Uint8Array()) + return promise.promise + } + + // 6. Otherwise, fully read object’s body given successSteps, + // errorSteps, and object’s relevant global object. + await fullyReadBody(object[kState].body, successSteps, errorSteps) + + // 7. Return promise. + return promise.promise +} + +// https://fetch.spec.whatwg.org/#body-unusable +function bodyUnusable (body) { + // An object including the Body interface mixin is + // said to be unusable if its body is non-null and + // its body’s stream is disturbed or locked. + return body != null && (body.stream.locked || util.isDisturbed(body.stream)) +} + +/** + * @see https://encoding.spec.whatwg.org/#utf-8-decode + * @param {Buffer} buffer + */ +function utf8DecodeBytes (buffer) { + if (buffer.length === 0) { + return '' + } + + // 1. Let buffer be the result of peeking three bytes from + // ioQueue, converted to a byte sequence. + + // 2. If buffer is 0xEF 0xBB 0xBF, then read three + // bytes from ioQueue. (Do nothing with those bytes.) + if (buffer[0] === 0xEF && buffer[1] === 0xBB && buffer[2] === 0xBF) { + buffer = buffer.subarray(3) + } + + // 3. Process a queue with an instance of UTF-8’s + // decoder, ioQueue, output, and "replacement". + const output = textDecoder.decode(buffer) + + // 4. Return output. + return output +} + +/** + * @see https://infra.spec.whatwg.org/#parse-json-bytes-to-a-javascript-value + * @param {Uint8Array} bytes + */ +function parseJSONFromBytes (bytes) { + return JSON.parse(utf8DecodeBytes(bytes)) +} + +/** + * @see https://fetch.spec.whatwg.org/#concept-body-mime-type + * @param {import('./response').Response|import('./request').Request} object + */ +function bodyMimeType (object) { + const { headersList } = object[kState] + const contentType = headersList.get('content-type') + + if (contentType === null) { + return 'failure' + } + + return parseMIMEType(contentType) +} + +module.exports = { + extractBody, + safelyExtractBody, + cloneBody, + mixinBody +} + + +/***/ }), + +/***/ 87326: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const { MessageChannel, receiveMessageOnPort } = __nccwpck_require__(28167) + +const corsSafeListedMethods = ['GET', 'HEAD', 'POST'] +const corsSafeListedMethodsSet = new Set(corsSafeListedMethods) + +const nullBodyStatus = [101, 204, 205, 304] + +const redirectStatus = [301, 302, 303, 307, 308] +const redirectStatusSet = new Set(redirectStatus) + +// https://fetch.spec.whatwg.org/#block-bad-port +const badPorts = [ + '1', '7', '9', '11', '13', '15', '17', '19', '20', '21', '22', '23', '25', '37', '42', '43', '53', '69', '77', '79', + '87', '95', '101', '102', '103', '104', '109', '110', '111', '113', '115', '117', '119', '123', '135', '137', + '139', '143', '161', '179', '389', '427', '465', '512', '513', '514', '515', '526', '530', '531', '532', + '540', '548', '554', '556', '563', '587', '601', '636', '989', '990', '993', '995', '1719', '1720', '1723', + '2049', '3659', '4045', '5060', '5061', '6000', '6566', '6665', '6666', '6667', '6668', '6669', '6697', + '10080' +] + +const badPortsSet = new Set(badPorts) + +// https://w3c.github.io/webappsec-referrer-policy/#referrer-policies +const referrerPolicy = [ + '', + 'no-referrer', + 'no-referrer-when-downgrade', + 'same-origin', + 'origin', + 'strict-origin', + 'origin-when-cross-origin', + 'strict-origin-when-cross-origin', + 'unsafe-url' +] +const referrerPolicySet = new Set(referrerPolicy) + +const requestRedirect = ['follow', 'manual', 'error'] + +const safeMethods = ['GET', 'HEAD', 'OPTIONS', 'TRACE'] +const safeMethodsSet = new Set(safeMethods) + +const requestMode = ['navigate', 'same-origin', 'no-cors', 'cors'] + +const requestCredentials = ['omit', 'same-origin', 'include'] + +const requestCache = [ + 'default', + 'no-store', + 'reload', + 'no-cache', + 'force-cache', + 'only-if-cached' +] + +// https://fetch.spec.whatwg.org/#request-body-header-name +const requestBodyHeader = [ + 'content-encoding', + 'content-language', + 'content-location', + 'content-type', + // See https://github.com/nodejs/undici/issues/2021 + // 'Content-Length' is a forbidden header name, which is typically + // removed in the Headers implementation. However, undici doesn't + // filter out headers, so we add it here. + 'content-length' +] + +// https://fetch.spec.whatwg.org/#enumdef-requestduplex +const requestDuplex = [ + 'half' +] + +// http://fetch.spec.whatwg.org/#forbidden-method +const forbiddenMethods = ['CONNECT', 'TRACE', 'TRACK'] +const forbiddenMethodsSet = new Set(forbiddenMethods) + +const subresource = [ + 'audio', + 'audioworklet', + 'font', + 'image', + 'manifest', + 'paintworklet', + 'script', + 'style', + 'track', + 'video', + 'xslt', + '' +] +const subresourceSet = new Set(subresource) + +/** @type {globalThis['DOMException']} */ +const DOMException = globalThis.DOMException ?? (() => { + // DOMException was only made a global in Node v17.0.0, + // but fetch supports >= v16.8. + try { + atob('~') + } catch (err) { + return Object.getPrototypeOf(err).constructor + } +})() + +let channel + +/** @type {globalThis['structuredClone']} */ +const structuredClone = + globalThis.structuredClone ?? + // https://github.com/nodejs/node/blob/b27ae24dcc4251bad726d9d84baf678d1f707fed/lib/internal/structured_clone.js + // structuredClone was added in v17.0.0, but fetch supports v16.8 + function structuredClone (value, options = undefined) { + if (arguments.length === 0) { + throw new TypeError('missing argument') + } + + if (!channel) { + channel = new MessageChannel() + } + channel.port1.unref() + channel.port2.unref() + channel.port1.postMessage(value, options?.transfer) + return receiveMessageOnPort(channel.port2).message + } + +module.exports = { + DOMException, + structuredClone, + subresource, + forbiddenMethods, + requestBodyHeader, + referrerPolicy, + requestRedirect, + requestMode, + requestCredentials, + requestCache, + redirectStatus, + corsSafeListedMethods, + nullBodyStatus, + safeMethods, + badPorts, + requestDuplex, + subresourceSet, + badPortsSet, + redirectStatusSet, + corsSafeListedMethodsSet, + safeMethodsSet, + forbiddenMethodsSet, + referrerPolicySet +} + + +/***/ }), + +/***/ 94322: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +const assert = __nccwpck_require__(42613) +const { atob } = __nccwpck_require__(20181) +const { isomorphicDecode } = __nccwpck_require__(15523) + +const encoder = new TextEncoder() + +/** + * @see https://mimesniff.spec.whatwg.org/#http-token-code-point + */ +const HTTP_TOKEN_CODEPOINTS = /^[!#$%&'*+-.^_|~A-Za-z0-9]+$/ +const HTTP_WHITESPACE_REGEX = /(\u000A|\u000D|\u0009|\u0020)/ // eslint-disable-line +/** + * @see https://mimesniff.spec.whatwg.org/#http-quoted-string-token-code-point + */ +const HTTP_QUOTED_STRING_TOKENS = /[\u0009|\u0020-\u007E|\u0080-\u00FF]/ // eslint-disable-line + +// https://fetch.spec.whatwg.org/#data-url-processor +/** @param {URL} dataURL */ +function dataURLProcessor (dataURL) { + // 1. Assert: dataURL’s scheme is "data". + assert(dataURL.protocol === 'data:') + + // 2. Let input be the result of running the URL + // serializer on dataURL with exclude fragment + // set to true. + let input = URLSerializer(dataURL, true) + + // 3. Remove the leading "data:" string from input. + input = input.slice(5) + + // 4. Let position point at the start of input. + const position = { position: 0 } + + // 5. Let mimeType be the result of collecting a + // sequence of code points that are not equal + // to U+002C (,), given position. + let mimeType = collectASequenceOfCodePointsFast( + ',', + input, + position + ) + + // 6. Strip leading and trailing ASCII whitespace + // from mimeType. + // Undici implementation note: we need to store the + // length because if the mimetype has spaces removed, + // the wrong amount will be sliced from the input in + // step #9 + const mimeTypeLength = mimeType.length + mimeType = removeASCIIWhitespace(mimeType, true, true) + + // 7. If position is past the end of input, then + // return failure + if (position.position >= input.length) { + return 'failure' + } + + // 8. Advance position by 1. + position.position++ + + // 9. Let encodedBody be the remainder of input. + const encodedBody = input.slice(mimeTypeLength + 1) + + // 10. Let body be the percent-decoding of encodedBody. + let body = stringPercentDecode(encodedBody) + + // 11. If mimeType ends with U+003B (;), followed by + // zero or more U+0020 SPACE, followed by an ASCII + // case-insensitive match for "base64", then: + if (/;(\u0020){0,}base64$/i.test(mimeType)) { + // 1. Let stringBody be the isomorphic decode of body. + const stringBody = isomorphicDecode(body) + + // 2. Set body to the forgiving-base64 decode of + // stringBody. + body = forgivingBase64(stringBody) + + // 3. If body is failure, then return failure. + if (body === 'failure') { + return 'failure' + } + + // 4. Remove the last 6 code points from mimeType. + mimeType = mimeType.slice(0, -6) + + // 5. Remove trailing U+0020 SPACE code points from mimeType, + // if any. + mimeType = mimeType.replace(/(\u0020)+$/, '') + + // 6. Remove the last U+003B (;) code point from mimeType. + mimeType = mimeType.slice(0, -1) + } + + // 12. If mimeType starts with U+003B (;), then prepend + // "text/plain" to mimeType. + if (mimeType.startsWith(';')) { + mimeType = 'text/plain' + mimeType + } + + // 13. Let mimeTypeRecord be the result of parsing + // mimeType. + let mimeTypeRecord = parseMIMEType(mimeType) + + // 14. If mimeTypeRecord is failure, then set + // mimeTypeRecord to text/plain;charset=US-ASCII. + if (mimeTypeRecord === 'failure') { + mimeTypeRecord = parseMIMEType('text/plain;charset=US-ASCII') + } + + // 15. Return a new data: URL struct whose MIME + // type is mimeTypeRecord and body is body. + // https://fetch.spec.whatwg.org/#data-url-struct + return { mimeType: mimeTypeRecord, body } +} + +// https://url.spec.whatwg.org/#concept-url-serializer +/** + * @param {URL} url + * @param {boolean} excludeFragment + */ +function URLSerializer (url, excludeFragment = false) { + if (!excludeFragment) { + return url.href + } + + const href = url.href + const hashLength = url.hash.length + + return hashLength === 0 ? href : href.substring(0, href.length - hashLength) +} + +// https://infra.spec.whatwg.org/#collect-a-sequence-of-code-points +/** + * @param {(char: string) => boolean} condition + * @param {string} input + * @param {{ position: number }} position + */ +function collectASequenceOfCodePoints (condition, input, position) { + // 1. Let result be the empty string. + let result = '' + + // 2. While position doesn’t point past the end of input and the + // code point at position within input meets the condition condition: + while (position.position < input.length && condition(input[position.position])) { + // 1. Append that code point to the end of result. + result += input[position.position] + + // 2. Advance position by 1. + position.position++ + } + + // 3. Return result. + return result +} + +/** + * A faster collectASequenceOfCodePoints that only works when comparing a single character. + * @param {string} char + * @param {string} input + * @param {{ position: number }} position + */ +function collectASequenceOfCodePointsFast (char, input, position) { + const idx = input.indexOf(char, position.position) + const start = position.position + + if (idx === -1) { + position.position = input.length + return input.slice(start) + } + + position.position = idx + return input.slice(start, position.position) +} + +// https://url.spec.whatwg.org/#string-percent-decode +/** @param {string} input */ +function stringPercentDecode (input) { + // 1. Let bytes be the UTF-8 encoding of input. + const bytes = encoder.encode(input) + + // 2. Return the percent-decoding of bytes. + return percentDecode(bytes) +} + +// https://url.spec.whatwg.org/#percent-decode +/** @param {Uint8Array} input */ +function percentDecode (input) { + // 1. Let output be an empty byte sequence. + /** @type {number[]} */ + const output = [] + + // 2. For each byte byte in input: + for (let i = 0; i < input.length; i++) { + const byte = input[i] + + // 1. If byte is not 0x25 (%), then append byte to output. + if (byte !== 0x25) { + output.push(byte) + + // 2. Otherwise, if byte is 0x25 (%) and the next two bytes + // after byte in input are not in the ranges + // 0x30 (0) to 0x39 (9), 0x41 (A) to 0x46 (F), + // and 0x61 (a) to 0x66 (f), all inclusive, append byte + // to output. + } else if ( + byte === 0x25 && + !/^[0-9A-Fa-f]{2}$/i.test(String.fromCharCode(input[i + 1], input[i + 2])) + ) { + output.push(0x25) + + // 3. Otherwise: + } else { + // 1. Let bytePoint be the two bytes after byte in input, + // decoded, and then interpreted as hexadecimal number. + const nextTwoBytes = String.fromCharCode(input[i + 1], input[i + 2]) + const bytePoint = Number.parseInt(nextTwoBytes, 16) + + // 2. Append a byte whose value is bytePoint to output. + output.push(bytePoint) + + // 3. Skip the next two bytes in input. + i += 2 + } + } + + // 3. Return output. + return Uint8Array.from(output) +} + +// https://mimesniff.spec.whatwg.org/#parse-a-mime-type +/** @param {string} input */ +function parseMIMEType (input) { + // 1. Remove any leading and trailing HTTP whitespace + // from input. + input = removeHTTPWhitespace(input, true, true) + + // 2. Let position be a position variable for input, + // initially pointing at the start of input. + const position = { position: 0 } + + // 3. Let type be the result of collecting a sequence + // of code points that are not U+002F (/) from + // input, given position. + const type = collectASequenceOfCodePointsFast( + '/', + input, + position + ) + + // 4. If type is the empty string or does not solely + // contain HTTP token code points, then return failure. + // https://mimesniff.spec.whatwg.org/#http-token-code-point + if (type.length === 0 || !HTTP_TOKEN_CODEPOINTS.test(type)) { + return 'failure' + } + + // 5. If position is past the end of input, then return + // failure + if (position.position > input.length) { + return 'failure' + } + + // 6. Advance position by 1. (This skips past U+002F (/).) + position.position++ + + // 7. Let subtype be the result of collecting a sequence of + // code points that are not U+003B (;) from input, given + // position. + let subtype = collectASequenceOfCodePointsFast( + ';', + input, + position + ) + + // 8. Remove any trailing HTTP whitespace from subtype. + subtype = removeHTTPWhitespace(subtype, false, true) + + // 9. If subtype is the empty string or does not solely + // contain HTTP token code points, then return failure. + if (subtype.length === 0 || !HTTP_TOKEN_CODEPOINTS.test(subtype)) { + return 'failure' + } + + const typeLowercase = type.toLowerCase() + const subtypeLowercase = subtype.toLowerCase() + + // 10. Let mimeType be a new MIME type record whose type + // is type, in ASCII lowercase, and subtype is subtype, + // in ASCII lowercase. + // https://mimesniff.spec.whatwg.org/#mime-type + const mimeType = { + type: typeLowercase, + subtype: subtypeLowercase, + /** @type {Map} */ + parameters: new Map(), + // https://mimesniff.spec.whatwg.org/#mime-type-essence + essence: `${typeLowercase}/${subtypeLowercase}` + } + + // 11. While position is not past the end of input: + while (position.position < input.length) { + // 1. Advance position by 1. (This skips past U+003B (;).) + position.position++ + + // 2. Collect a sequence of code points that are HTTP + // whitespace from input given position. + collectASequenceOfCodePoints( + // https://fetch.spec.whatwg.org/#http-whitespace + char => HTTP_WHITESPACE_REGEX.test(char), + input, + position + ) + + // 3. Let parameterName be the result of collecting a + // sequence of code points that are not U+003B (;) + // or U+003D (=) from input, given position. + let parameterName = collectASequenceOfCodePoints( + (char) => char !== ';' && char !== '=', + input, + position + ) + + // 4. Set parameterName to parameterName, in ASCII + // lowercase. + parameterName = parameterName.toLowerCase() + + // 5. If position is not past the end of input, then: + if (position.position < input.length) { + // 1. If the code point at position within input is + // U+003B (;), then continue. + if (input[position.position] === ';') { + continue + } + + // 2. Advance position by 1. (This skips past U+003D (=).) + position.position++ + } + + // 6. If position is past the end of input, then break. + if (position.position > input.length) { + break + } + + // 7. Let parameterValue be null. + let parameterValue = null + + // 8. If the code point at position within input is + // U+0022 ("), then: + if (input[position.position] === '"') { + // 1. Set parameterValue to the result of collecting + // an HTTP quoted string from input, given position + // and the extract-value flag. + parameterValue = collectAnHTTPQuotedString(input, position, true) + + // 2. Collect a sequence of code points that are not + // U+003B (;) from input, given position. + collectASequenceOfCodePointsFast( + ';', + input, + position + ) + + // 9. Otherwise: + } else { + // 1. Set parameterValue to the result of collecting + // a sequence of code points that are not U+003B (;) + // from input, given position. + parameterValue = collectASequenceOfCodePointsFast( + ';', + input, + position + ) + + // 2. Remove any trailing HTTP whitespace from parameterValue. + parameterValue = removeHTTPWhitespace(parameterValue, false, true) + + // 3. If parameterValue is the empty string, then continue. + if (parameterValue.length === 0) { + continue + } + } + + // 10. If all of the following are true + // - parameterName is not the empty string + // - parameterName solely contains HTTP token code points + // - parameterValue solely contains HTTP quoted-string token code points + // - mimeType’s parameters[parameterName] does not exist + // then set mimeType’s parameters[parameterName] to parameterValue. + if ( + parameterName.length !== 0 && + HTTP_TOKEN_CODEPOINTS.test(parameterName) && + (parameterValue.length === 0 || HTTP_QUOTED_STRING_TOKENS.test(parameterValue)) && + !mimeType.parameters.has(parameterName) + ) { + mimeType.parameters.set(parameterName, parameterValue) + } + } + + // 12. Return mimeType. + return mimeType +} + +// https://infra.spec.whatwg.org/#forgiving-base64-decode +/** @param {string} data */ +function forgivingBase64 (data) { + // 1. Remove all ASCII whitespace from data. + data = data.replace(/[\u0009\u000A\u000C\u000D\u0020]/g, '') // eslint-disable-line + + // 2. If data’s code point length divides by 4 leaving + // no remainder, then: + if (data.length % 4 === 0) { + // 1. If data ends with one or two U+003D (=) code points, + // then remove them from data. + data = data.replace(/=?=$/, '') + } + + // 3. If data’s code point length divides by 4 leaving + // a remainder of 1, then return failure. + if (data.length % 4 === 1) { + return 'failure' + } + + // 4. If data contains a code point that is not one of + // U+002B (+) + // U+002F (/) + // ASCII alphanumeric + // then return failure. + if (/[^+/0-9A-Za-z]/.test(data)) { + return 'failure' + } + + const binary = atob(data) + const bytes = new Uint8Array(binary.length) + + for (let byte = 0; byte < binary.length; byte++) { + bytes[byte] = binary.charCodeAt(byte) + } + + return bytes +} + +// https://fetch.spec.whatwg.org/#collect-an-http-quoted-string +// tests: https://fetch.spec.whatwg.org/#example-http-quoted-string +/** + * @param {string} input + * @param {{ position: number }} position + * @param {boolean?} extractValue + */ +function collectAnHTTPQuotedString (input, position, extractValue) { + // 1. Let positionStart be position. + const positionStart = position.position + + // 2. Let value be the empty string. + let value = '' + + // 3. Assert: the code point at position within input + // is U+0022 ("). + assert(input[position.position] === '"') + + // 4. Advance position by 1. + position.position++ + + // 5. While true: + while (true) { + // 1. Append the result of collecting a sequence of code points + // that are not U+0022 (") or U+005C (\) from input, given + // position, to value. + value += collectASequenceOfCodePoints( + (char) => char !== '"' && char !== '\\', + input, + position + ) + + // 2. If position is past the end of input, then break. + if (position.position >= input.length) { + break + } + + // 3. Let quoteOrBackslash be the code point at position within + // input. + const quoteOrBackslash = input[position.position] + + // 4. Advance position by 1. + position.position++ + + // 5. If quoteOrBackslash is U+005C (\), then: + if (quoteOrBackslash === '\\') { + // 1. If position is past the end of input, then append + // U+005C (\) to value and break. + if (position.position >= input.length) { + value += '\\' + break + } + + // 2. Append the code point at position within input to value. + value += input[position.position] + + // 3. Advance position by 1. + position.position++ + + // 6. Otherwise: + } else { + // 1. Assert: quoteOrBackslash is U+0022 ("). + assert(quoteOrBackslash === '"') + + // 2. Break. + break + } + } + + // 6. If the extract-value flag is set, then return value. + if (extractValue) { + return value + } + + // 7. Return the code points from positionStart to position, + // inclusive, within input. + return input.slice(positionStart, position.position) +} + +/** + * @see https://mimesniff.spec.whatwg.org/#serialize-a-mime-type + */ +function serializeAMimeType (mimeType) { + assert(mimeType !== 'failure') + const { parameters, essence } = mimeType + + // 1. Let serialization be the concatenation of mimeType’s + // type, U+002F (/), and mimeType’s subtype. + let serialization = essence + + // 2. For each name → value of mimeType’s parameters: + for (let [name, value] of parameters.entries()) { + // 1. Append U+003B (;) to serialization. + serialization += ';' + + // 2. Append name to serialization. + serialization += name + + // 3. Append U+003D (=) to serialization. + serialization += '=' + + // 4. If value does not solely contain HTTP token code + // points or value is the empty string, then: + if (!HTTP_TOKEN_CODEPOINTS.test(value)) { + // 1. Precede each occurence of U+0022 (") or + // U+005C (\) in value with U+005C (\). + value = value.replace(/(\\|")/g, '\\$1') + + // 2. Prepend U+0022 (") to value. + value = '"' + value + + // 3. Append U+0022 (") to value. + value += '"' + } + + // 5. Append value to serialization. + serialization += value + } + + // 3. Return serialization. + return serialization +} + +/** + * @see https://fetch.spec.whatwg.org/#http-whitespace + * @param {string} char + */ +function isHTTPWhiteSpace (char) { + return char === '\r' || char === '\n' || char === '\t' || char === ' ' +} + +/** + * @see https://fetch.spec.whatwg.org/#http-whitespace + * @param {string} str + */ +function removeHTTPWhitespace (str, leading = true, trailing = true) { + let lead = 0 + let trail = str.length - 1 + + if (leading) { + for (; lead < str.length && isHTTPWhiteSpace(str[lead]); lead++); + } + + if (trailing) { + for (; trail > 0 && isHTTPWhiteSpace(str[trail]); trail--); + } + + return str.slice(lead, trail + 1) +} + +/** + * @see https://infra.spec.whatwg.org/#ascii-whitespace + * @param {string} char + */ +function isASCIIWhitespace (char) { + return char === '\r' || char === '\n' || char === '\t' || char === '\f' || char === ' ' +} + +/** + * @see https://infra.spec.whatwg.org/#strip-leading-and-trailing-ascii-whitespace + */ +function removeASCIIWhitespace (str, leading = true, trailing = true) { + let lead = 0 + let trail = str.length - 1 + + if (leading) { + for (; lead < str.length && isASCIIWhitespace(str[lead]); lead++); + } + + if (trailing) { + for (; trail > 0 && isASCIIWhitespace(str[trail]); trail--); + } + + return str.slice(lead, trail + 1) +} + +module.exports = { + dataURLProcessor, + URLSerializer, + collectASequenceOfCodePoints, + collectASequenceOfCodePointsFast, + stringPercentDecode, + parseMIMEType, + collectAnHTTPQuotedString, + serializeAMimeType +} + + +/***/ }), + +/***/ 63041: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const { Blob, File: NativeFile } = __nccwpck_require__(20181) +const { types } = __nccwpck_require__(39023) +const { kState } = __nccwpck_require__(89710) +const { isBlobLike } = __nccwpck_require__(15523) +const { webidl } = __nccwpck_require__(74222) +const { parseMIMEType, serializeAMimeType } = __nccwpck_require__(94322) +const { kEnumerableProperty } = __nccwpck_require__(3440) +const encoder = new TextEncoder() + +class File extends Blob { + constructor (fileBits, fileName, options = {}) { + // The File constructor is invoked with two or three parameters, depending + // on whether the optional dictionary parameter is used. When the File() + // constructor is invoked, user agents must run the following steps: + webidl.argumentLengthCheck(arguments, 2, { header: 'File constructor' }) + + fileBits = webidl.converters['sequence'](fileBits) + fileName = webidl.converters.USVString(fileName) + options = webidl.converters.FilePropertyBag(options) + + // 1. Let bytes be the result of processing blob parts given fileBits and + // options. + // Note: Blob handles this for us + + // 2. Let n be the fileName argument to the constructor. + const n = fileName + + // 3. Process FilePropertyBag dictionary argument by running the following + // substeps: + + // 1. If the type member is provided and is not the empty string, let t + // be set to the type dictionary member. If t contains any characters + // outside the range U+0020 to U+007E, then set t to the empty string + // and return from these substeps. + // 2. Convert every character in t to ASCII lowercase. + let t = options.type + let d + + // eslint-disable-next-line no-labels + substep: { + if (t) { + t = parseMIMEType(t) + + if (t === 'failure') { + t = '' + // eslint-disable-next-line no-labels + break substep + } + + t = serializeAMimeType(t).toLowerCase() + } + + // 3. If the lastModified member is provided, let d be set to the + // lastModified dictionary member. If it is not provided, set d to the + // current date and time represented as the number of milliseconds since + // the Unix Epoch (which is the equivalent of Date.now() [ECMA-262]). + d = options.lastModified + } + + // 4. Return a new File object F such that: + // F refers to the bytes byte sequence. + // F.size is set to the number of total bytes in bytes. + // F.name is set to n. + // F.type is set to t. + // F.lastModified is set to d. + + super(processBlobParts(fileBits, options), { type: t }) + this[kState] = { + name: n, + lastModified: d, + type: t + } + } + + get name () { + webidl.brandCheck(this, File) + + return this[kState].name + } + + get lastModified () { + webidl.brandCheck(this, File) + + return this[kState].lastModified + } + + get type () { + webidl.brandCheck(this, File) + + return this[kState].type + } +} + +class FileLike { + constructor (blobLike, fileName, options = {}) { + // TODO: argument idl type check + + // The File constructor is invoked with two or three parameters, depending + // on whether the optional dictionary parameter is used. When the File() + // constructor is invoked, user agents must run the following steps: + + // 1. Let bytes be the result of processing blob parts given fileBits and + // options. + + // 2. Let n be the fileName argument to the constructor. + const n = fileName + + // 3. Process FilePropertyBag dictionary argument by running the following + // substeps: + + // 1. If the type member is provided and is not the empty string, let t + // be set to the type dictionary member. If t contains any characters + // outside the range U+0020 to U+007E, then set t to the empty string + // and return from these substeps. + // TODO + const t = options.type + + // 2. Convert every character in t to ASCII lowercase. + // TODO + + // 3. If the lastModified member is provided, let d be set to the + // lastModified dictionary member. If it is not provided, set d to the + // current date and time represented as the number of milliseconds since + // the Unix Epoch (which is the equivalent of Date.now() [ECMA-262]). + const d = options.lastModified ?? Date.now() + + // 4. Return a new File object F such that: + // F refers to the bytes byte sequence. + // F.size is set to the number of total bytes in bytes. + // F.name is set to n. + // F.type is set to t. + // F.lastModified is set to d. + + this[kState] = { + blobLike, + name: n, + type: t, + lastModified: d + } + } + + stream (...args) { + webidl.brandCheck(this, FileLike) + + return this[kState].blobLike.stream(...args) + } + + arrayBuffer (...args) { + webidl.brandCheck(this, FileLike) + + return this[kState].blobLike.arrayBuffer(...args) + } + + slice (...args) { + webidl.brandCheck(this, FileLike) + + return this[kState].blobLike.slice(...args) + } + + text (...args) { + webidl.brandCheck(this, FileLike) + + return this[kState].blobLike.text(...args) + } + + get size () { + webidl.brandCheck(this, FileLike) + + return this[kState].blobLike.size + } + + get type () { + webidl.brandCheck(this, FileLike) + + return this[kState].blobLike.type + } + + get name () { + webidl.brandCheck(this, FileLike) + + return this[kState].name + } + + get lastModified () { + webidl.brandCheck(this, FileLike) + + return this[kState].lastModified + } + + get [Symbol.toStringTag] () { + return 'File' + } +} + +Object.defineProperties(File.prototype, { + [Symbol.toStringTag]: { + value: 'File', + configurable: true + }, + name: kEnumerableProperty, + lastModified: kEnumerableProperty +}) + +webidl.converters.Blob = webidl.interfaceConverter(Blob) + +webidl.converters.BlobPart = function (V, opts) { + if (webidl.util.Type(V) === 'Object') { + if (isBlobLike(V)) { + return webidl.converters.Blob(V, { strict: false }) + } + + if ( + ArrayBuffer.isView(V) || + types.isAnyArrayBuffer(V) + ) { + return webidl.converters.BufferSource(V, opts) + } + } + + return webidl.converters.USVString(V, opts) +} + +webidl.converters['sequence'] = webidl.sequenceConverter( + webidl.converters.BlobPart +) + +// https://www.w3.org/TR/FileAPI/#dfn-FilePropertyBag +webidl.converters.FilePropertyBag = webidl.dictionaryConverter([ + { + key: 'lastModified', + converter: webidl.converters['long long'], + get defaultValue () { + return Date.now() + } + }, + { + key: 'type', + converter: webidl.converters.DOMString, + defaultValue: '' + }, + { + key: 'endings', + converter: (value) => { + value = webidl.converters.DOMString(value) + value = value.toLowerCase() + + if (value !== 'native') { + value = 'transparent' + } + + return value + }, + defaultValue: 'transparent' + } +]) + +/** + * @see https://www.w3.org/TR/FileAPI/#process-blob-parts + * @param {(NodeJS.TypedArray|Blob|string)[]} parts + * @param {{ type: string, endings: string }} options + */ +function processBlobParts (parts, options) { + // 1. Let bytes be an empty sequence of bytes. + /** @type {NodeJS.TypedArray[]} */ + const bytes = [] + + // 2. For each element in parts: + for (const element of parts) { + // 1. If element is a USVString, run the following substeps: + if (typeof element === 'string') { + // 1. Let s be element. + let s = element + + // 2. If the endings member of options is "native", set s + // to the result of converting line endings to native + // of element. + if (options.endings === 'native') { + s = convertLineEndingsNative(s) + } + + // 3. Append the result of UTF-8 encoding s to bytes. + bytes.push(encoder.encode(s)) + } else if ( + types.isAnyArrayBuffer(element) || + types.isTypedArray(element) + ) { + // 2. If element is a BufferSource, get a copy of the + // bytes held by the buffer source, and append those + // bytes to bytes. + if (!element.buffer) { // ArrayBuffer + bytes.push(new Uint8Array(element)) + } else { + bytes.push( + new Uint8Array(element.buffer, element.byteOffset, element.byteLength) + ) + } + } else if (isBlobLike(element)) { + // 3. If element is a Blob, append the bytes it represents + // to bytes. + bytes.push(element) + } + } + + // 3. Return bytes. + return bytes +} + +/** + * @see https://www.w3.org/TR/FileAPI/#convert-line-endings-to-native + * @param {string} s + */ +function convertLineEndingsNative (s) { + // 1. Let native line ending be be the code point U+000A LF. + let nativeLineEnding = '\n' + + // 2. If the underlying platform’s conventions are to + // represent newlines as a carriage return and line feed + // sequence, set native line ending to the code point + // U+000D CR followed by the code point U+000A LF. + if (process.platform === 'win32') { + nativeLineEnding = '\r\n' + } + + return s.replace(/\r?\n/g, nativeLineEnding) +} + +// If this function is moved to ./util.js, some tools (such as +// rollup) will warn about circular dependencies. See: +// https://github.com/nodejs/undici/issues/1629 +function isFileLike (object) { + return ( + (NativeFile && object instanceof NativeFile) || + object instanceof File || ( + object && + (typeof object.stream === 'function' || + typeof object.arrayBuffer === 'function') && + object[Symbol.toStringTag] === 'File' + ) + ) +} + +module.exports = { File, FileLike, isFileLike } + + +/***/ }), + +/***/ 43073: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const { isBlobLike, toUSVString, makeIterator } = __nccwpck_require__(15523) +const { kState } = __nccwpck_require__(89710) +const { File: UndiciFile, FileLike, isFileLike } = __nccwpck_require__(63041) +const { webidl } = __nccwpck_require__(74222) +const { Blob, File: NativeFile } = __nccwpck_require__(20181) + +/** @type {globalThis['File']} */ +const File = NativeFile ?? UndiciFile + +// https://xhr.spec.whatwg.org/#formdata +class FormData { + constructor (form) { + if (form !== undefined) { + throw webidl.errors.conversionFailed({ + prefix: 'FormData constructor', + argument: 'Argument 1', + types: ['undefined'] + }) + } + + this[kState] = [] + } + + append (name, value, filename = undefined) { + webidl.brandCheck(this, FormData) + + webidl.argumentLengthCheck(arguments, 2, { header: 'FormData.append' }) + + if (arguments.length === 3 && !isBlobLike(value)) { + throw new TypeError( + "Failed to execute 'append' on 'FormData': parameter 2 is not of type 'Blob'" + ) + } + + // 1. Let value be value if given; otherwise blobValue. + + name = webidl.converters.USVString(name) + value = isBlobLike(value) + ? webidl.converters.Blob(value, { strict: false }) + : webidl.converters.USVString(value) + filename = arguments.length === 3 + ? webidl.converters.USVString(filename) + : undefined + + // 2. Let entry be the result of creating an entry with + // name, value, and filename if given. + const entry = makeEntry(name, value, filename) + + // 3. Append entry to this’s entry list. + this[kState].push(entry) + } + + delete (name) { + webidl.brandCheck(this, FormData) + + webidl.argumentLengthCheck(arguments, 1, { header: 'FormData.delete' }) + + name = webidl.converters.USVString(name) + + // The delete(name) method steps are to remove all entries whose name + // is name from this’s entry list. + this[kState] = this[kState].filter(entry => entry.name !== name) + } + + get (name) { + webidl.brandCheck(this, FormData) + + webidl.argumentLengthCheck(arguments, 1, { header: 'FormData.get' }) + + name = webidl.converters.USVString(name) + + // 1. If there is no entry whose name is name in this’s entry list, + // then return null. + const idx = this[kState].findIndex((entry) => entry.name === name) + if (idx === -1) { + return null + } + + // 2. Return the value of the first entry whose name is name from + // this’s entry list. + return this[kState][idx].value + } + + getAll (name) { + webidl.brandCheck(this, FormData) + + webidl.argumentLengthCheck(arguments, 1, { header: 'FormData.getAll' }) + + name = webidl.converters.USVString(name) + + // 1. If there is no entry whose name is name in this’s entry list, + // then return the empty list. + // 2. Return the values of all entries whose name is name, in order, + // from this’s entry list. + return this[kState] + .filter((entry) => entry.name === name) + .map((entry) => entry.value) + } + + has (name) { + webidl.brandCheck(this, FormData) + + webidl.argumentLengthCheck(arguments, 1, { header: 'FormData.has' }) + + name = webidl.converters.USVString(name) + + // The has(name) method steps are to return true if there is an entry + // whose name is name in this’s entry list; otherwise false. + return this[kState].findIndex((entry) => entry.name === name) !== -1 + } + + set (name, value, filename = undefined) { + webidl.brandCheck(this, FormData) + + webidl.argumentLengthCheck(arguments, 2, { header: 'FormData.set' }) + + if (arguments.length === 3 && !isBlobLike(value)) { + throw new TypeError( + "Failed to execute 'set' on 'FormData': parameter 2 is not of type 'Blob'" + ) + } + + // The set(name, value) and set(name, blobValue, filename) method steps + // are: + + // 1. Let value be value if given; otherwise blobValue. + + name = webidl.converters.USVString(name) + value = isBlobLike(value) + ? webidl.converters.Blob(value, { strict: false }) + : webidl.converters.USVString(value) + filename = arguments.length === 3 + ? toUSVString(filename) + : undefined + + // 2. Let entry be the result of creating an entry with name, value, and + // filename if given. + const entry = makeEntry(name, value, filename) + + // 3. If there are entries in this’s entry list whose name is name, then + // replace the first such entry with entry and remove the others. + const idx = this[kState].findIndex((entry) => entry.name === name) + if (idx !== -1) { + this[kState] = [ + ...this[kState].slice(0, idx), + entry, + ...this[kState].slice(idx + 1).filter((entry) => entry.name !== name) + ] + } else { + // 4. Otherwise, append entry to this’s entry list. + this[kState].push(entry) + } + } + + entries () { + webidl.brandCheck(this, FormData) + + return makeIterator( + () => this[kState].map(pair => [pair.name, pair.value]), + 'FormData', + 'key+value' + ) + } + + keys () { + webidl.brandCheck(this, FormData) + + return makeIterator( + () => this[kState].map(pair => [pair.name, pair.value]), + 'FormData', + 'key' + ) + } + + values () { + webidl.brandCheck(this, FormData) + + return makeIterator( + () => this[kState].map(pair => [pair.name, pair.value]), + 'FormData', + 'value' + ) + } + + /** + * @param {(value: string, key: string, self: FormData) => void} callbackFn + * @param {unknown} thisArg + */ + forEach (callbackFn, thisArg = globalThis) { + webidl.brandCheck(this, FormData) + + webidl.argumentLengthCheck(arguments, 1, { header: 'FormData.forEach' }) + + if (typeof callbackFn !== 'function') { + throw new TypeError( + "Failed to execute 'forEach' on 'FormData': parameter 1 is not of type 'Function'." + ) + } + + for (const [key, value] of this) { + callbackFn.apply(thisArg, [value, key, this]) + } + } +} + +FormData.prototype[Symbol.iterator] = FormData.prototype.entries + +Object.defineProperties(FormData.prototype, { + [Symbol.toStringTag]: { + value: 'FormData', + configurable: true + } +}) + +/** + * @see https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#create-an-entry + * @param {string} name + * @param {string|Blob} value + * @param {?string} filename + * @returns + */ +function makeEntry (name, value, filename) { + // 1. Set name to the result of converting name into a scalar value string. + // "To convert a string into a scalar value string, replace any surrogates + // with U+FFFD." + // see: https://nodejs.org/dist/latest-v18.x/docs/api/buffer.html#buftostringencoding-start-end + name = Buffer.from(name).toString('utf8') + + // 2. If value is a string, then set value to the result of converting + // value into a scalar value string. + if (typeof value === 'string') { + value = Buffer.from(value).toString('utf8') + } else { + // 3. Otherwise: + + // 1. If value is not a File object, then set value to a new File object, + // representing the same bytes, whose name attribute value is "blob" + if (!isFileLike(value)) { + value = value instanceof Blob + ? new File([value], 'blob', { type: value.type }) + : new FileLike(value, 'blob', { type: value.type }) + } + + // 2. If filename is given, then set value to a new File object, + // representing the same bytes, whose name attribute is filename. + if (filename !== undefined) { + /** @type {FilePropertyBag} */ + const options = { + type: value.type, + lastModified: value.lastModified + } + + value = (NativeFile && value instanceof NativeFile) || value instanceof UndiciFile + ? new File([value], filename, options) + : new FileLike(value, filename, options) + } + } + + // 4. Return an entry whose name is name and whose value is value. + return { name, value } +} + +module.exports = { FormData } + + +/***/ }), + +/***/ 75628: +/***/ ((module) => { + +"use strict"; + + +// In case of breaking changes, increase the version +// number to avoid conflicts. +const globalOrigin = Symbol.for('undici.globalOrigin.1') + +function getGlobalOrigin () { + return globalThis[globalOrigin] +} + +function setGlobalOrigin (newOrigin) { + if (newOrigin === undefined) { + Object.defineProperty(globalThis, globalOrigin, { + value: undefined, + writable: true, + enumerable: false, + configurable: false + }) + + return + } + + const parsedURL = new URL(newOrigin) + + if (parsedURL.protocol !== 'http:' && parsedURL.protocol !== 'https:') { + throw new TypeError(`Only http & https urls are allowed, received ${parsedURL.protocol}`) + } + + Object.defineProperty(globalThis, globalOrigin, { + value: parsedURL, + writable: true, + enumerable: false, + configurable: false + }) +} + +module.exports = { + getGlobalOrigin, + setGlobalOrigin +} + + +/***/ }), + +/***/ 26349: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; +// https://github.com/Ethan-Arrowood/undici-fetch + + + +const { kHeadersList, kConstruct } = __nccwpck_require__(36443) +const { kGuard } = __nccwpck_require__(89710) +const { kEnumerableProperty } = __nccwpck_require__(3440) +const { + makeIterator, + isValidHeaderName, + isValidHeaderValue +} = __nccwpck_require__(15523) +const util = __nccwpck_require__(39023) +const { webidl } = __nccwpck_require__(74222) +const assert = __nccwpck_require__(42613) + +const kHeadersMap = Symbol('headers map') +const kHeadersSortedMap = Symbol('headers map sorted') + +/** + * @param {number} code + */ +function isHTTPWhiteSpaceCharCode (code) { + return code === 0x00a || code === 0x00d || code === 0x009 || code === 0x020 +} + +/** + * @see https://fetch.spec.whatwg.org/#concept-header-value-normalize + * @param {string} potentialValue + */ +function headerValueNormalize (potentialValue) { + // To normalize a byte sequence potentialValue, remove + // any leading and trailing HTTP whitespace bytes from + // potentialValue. + let i = 0; let j = potentialValue.length + + while (j > i && isHTTPWhiteSpaceCharCode(potentialValue.charCodeAt(j - 1))) --j + while (j > i && isHTTPWhiteSpaceCharCode(potentialValue.charCodeAt(i))) ++i + + return i === 0 && j === potentialValue.length ? potentialValue : potentialValue.substring(i, j) +} + +function fill (headers, object) { + // To fill a Headers object headers with a given object object, run these steps: + + // 1. If object is a sequence, then for each header in object: + // Note: webidl conversion to array has already been done. + if (Array.isArray(object)) { + for (let i = 0; i < object.length; ++i) { + const header = object[i] + // 1. If header does not contain exactly two items, then throw a TypeError. + if (header.length !== 2) { + throw webidl.errors.exception({ + header: 'Headers constructor', + message: `expected name/value pair to be length 2, found ${header.length}.` + }) + } + + // 2. Append (header’s first item, header’s second item) to headers. + appendHeader(headers, header[0], header[1]) + } + } else if (typeof object === 'object' && object !== null) { + // Note: null should throw + + // 2. Otherwise, object is a record, then for each key → value in object, + // append (key, value) to headers + const keys = Object.keys(object) + for (let i = 0; i < keys.length; ++i) { + appendHeader(headers, keys[i], object[keys[i]]) + } + } else { + throw webidl.errors.conversionFailed({ + prefix: 'Headers constructor', + argument: 'Argument 1', + types: ['sequence>', 'record'] + }) + } +} + +/** + * @see https://fetch.spec.whatwg.org/#concept-headers-append + */ +function appendHeader (headers, name, value) { + // 1. Normalize value. + value = headerValueNormalize(value) + + // 2. If name is not a header name or value is not a + // header value, then throw a TypeError. + if (!isValidHeaderName(name)) { + throw webidl.errors.invalidArgument({ + prefix: 'Headers.append', + value: name, + type: 'header name' + }) + } else if (!isValidHeaderValue(value)) { + throw webidl.errors.invalidArgument({ + prefix: 'Headers.append', + value, + type: 'header value' + }) + } + + // 3. If headers’s guard is "immutable", then throw a TypeError. + // 4. Otherwise, if headers’s guard is "request" and name is a + // forbidden header name, return. + // Note: undici does not implement forbidden header names + if (headers[kGuard] === 'immutable') { + throw new TypeError('immutable') + } else if (headers[kGuard] === 'request-no-cors') { + // 5. Otherwise, if headers’s guard is "request-no-cors": + // TODO + } + + // 6. Otherwise, if headers’s guard is "response" and name is a + // forbidden response-header name, return. + + // 7. Append (name, value) to headers’s header list. + return headers[kHeadersList].append(name, value) + + // 8. If headers’s guard is "request-no-cors", then remove + // privileged no-CORS request headers from headers +} + +class HeadersList { + /** @type {[string, string][]|null} */ + cookies = null + + constructor (init) { + if (init instanceof HeadersList) { + this[kHeadersMap] = new Map(init[kHeadersMap]) + this[kHeadersSortedMap] = init[kHeadersSortedMap] + this.cookies = init.cookies === null ? null : [...init.cookies] + } else { + this[kHeadersMap] = new Map(init) + this[kHeadersSortedMap] = null + } + } + + // https://fetch.spec.whatwg.org/#header-list-contains + contains (name) { + // A header list list contains a header name name if list + // contains a header whose name is a byte-case-insensitive + // match for name. + name = name.toLowerCase() + + return this[kHeadersMap].has(name) + } + + clear () { + this[kHeadersMap].clear() + this[kHeadersSortedMap] = null + this.cookies = null + } + + // https://fetch.spec.whatwg.org/#concept-header-list-append + append (name, value) { + this[kHeadersSortedMap] = null + + // 1. If list contains name, then set name to the first such + // header’s name. + const lowercaseName = name.toLowerCase() + const exists = this[kHeadersMap].get(lowercaseName) + + // 2. Append (name, value) to list. + if (exists) { + const delimiter = lowercaseName === 'cookie' ? '; ' : ', ' + this[kHeadersMap].set(lowercaseName, { + name: exists.name, + value: `${exists.value}${delimiter}${value}` + }) + } else { + this[kHeadersMap].set(lowercaseName, { name, value }) + } + + if (lowercaseName === 'set-cookie') { + this.cookies ??= [] + this.cookies.push(value) + } + } + + // https://fetch.spec.whatwg.org/#concept-header-list-set + set (name, value) { + this[kHeadersSortedMap] = null + const lowercaseName = name.toLowerCase() + + if (lowercaseName === 'set-cookie') { + this.cookies = [value] + } + + // 1. If list contains name, then set the value of + // the first such header to value and remove the + // others. + // 2. Otherwise, append header (name, value) to list. + this[kHeadersMap].set(lowercaseName, { name, value }) + } + + // https://fetch.spec.whatwg.org/#concept-header-list-delete + delete (name) { + this[kHeadersSortedMap] = null + + name = name.toLowerCase() + + if (name === 'set-cookie') { + this.cookies = null + } + + this[kHeadersMap].delete(name) + } + + // https://fetch.spec.whatwg.org/#concept-header-list-get + get (name) { + const value = this[kHeadersMap].get(name.toLowerCase()) + + // 1. If list does not contain name, then return null. + // 2. Return the values of all headers in list whose name + // is a byte-case-insensitive match for name, + // separated from each other by 0x2C 0x20, in order. + return value === undefined ? null : value.value + } + + * [Symbol.iterator] () { + // use the lowercased name + for (const [name, { value }] of this[kHeadersMap]) { + yield [name, value] + } + } + + get entries () { + const headers = {} + + if (this[kHeadersMap].size) { + for (const { name, value } of this[kHeadersMap].values()) { + headers[name] = value + } + } + + return headers + } +} + +// https://fetch.spec.whatwg.org/#headers-class +class Headers { + constructor (init = undefined) { + if (init === kConstruct) { + return + } + this[kHeadersList] = new HeadersList() + + // The new Headers(init) constructor steps are: + + // 1. Set this’s guard to "none". + this[kGuard] = 'none' + + // 2. If init is given, then fill this with init. + if (init !== undefined) { + init = webidl.converters.HeadersInit(init) + fill(this, init) + } + } + + // https://fetch.spec.whatwg.org/#dom-headers-append + append (name, value) { + webidl.brandCheck(this, Headers) + + webidl.argumentLengthCheck(arguments, 2, { header: 'Headers.append' }) + + name = webidl.converters.ByteString(name) + value = webidl.converters.ByteString(value) + + return appendHeader(this, name, value) + } + + // https://fetch.spec.whatwg.org/#dom-headers-delete + delete (name) { + webidl.brandCheck(this, Headers) + + webidl.argumentLengthCheck(arguments, 1, { header: 'Headers.delete' }) + + name = webidl.converters.ByteString(name) + + // 1. If name is not a header name, then throw a TypeError. + if (!isValidHeaderName(name)) { + throw webidl.errors.invalidArgument({ + prefix: 'Headers.delete', + value: name, + type: 'header name' + }) + } + + // 2. If this’s guard is "immutable", then throw a TypeError. + // 3. Otherwise, if this’s guard is "request" and name is a + // forbidden header name, return. + // 4. Otherwise, if this’s guard is "request-no-cors", name + // is not a no-CORS-safelisted request-header name, and + // name is not a privileged no-CORS request-header name, + // return. + // 5. Otherwise, if this’s guard is "response" and name is + // a forbidden response-header name, return. + // Note: undici does not implement forbidden header names + if (this[kGuard] === 'immutable') { + throw new TypeError('immutable') + } else if (this[kGuard] === 'request-no-cors') { + // TODO + } + + // 6. If this’s header list does not contain name, then + // return. + if (!this[kHeadersList].contains(name)) { + return + } + + // 7. Delete name from this’s header list. + // 8. If this’s guard is "request-no-cors", then remove + // privileged no-CORS request headers from this. + this[kHeadersList].delete(name) + } + + // https://fetch.spec.whatwg.org/#dom-headers-get + get (name) { + webidl.brandCheck(this, Headers) + + webidl.argumentLengthCheck(arguments, 1, { header: 'Headers.get' }) + + name = webidl.converters.ByteString(name) + + // 1. If name is not a header name, then throw a TypeError. + if (!isValidHeaderName(name)) { + throw webidl.errors.invalidArgument({ + prefix: 'Headers.get', + value: name, + type: 'header name' + }) + } + + // 2. Return the result of getting name from this’s header + // list. + return this[kHeadersList].get(name) + } + + // https://fetch.spec.whatwg.org/#dom-headers-has + has (name) { + webidl.brandCheck(this, Headers) + + webidl.argumentLengthCheck(arguments, 1, { header: 'Headers.has' }) + + name = webidl.converters.ByteString(name) + + // 1. If name is not a header name, then throw a TypeError. + if (!isValidHeaderName(name)) { + throw webidl.errors.invalidArgument({ + prefix: 'Headers.has', + value: name, + type: 'header name' + }) + } + + // 2. Return true if this’s header list contains name; + // otherwise false. + return this[kHeadersList].contains(name) + } + + // https://fetch.spec.whatwg.org/#dom-headers-set + set (name, value) { + webidl.brandCheck(this, Headers) + + webidl.argumentLengthCheck(arguments, 2, { header: 'Headers.set' }) + + name = webidl.converters.ByteString(name) + value = webidl.converters.ByteString(value) + + // 1. Normalize value. + value = headerValueNormalize(value) + + // 2. If name is not a header name or value is not a + // header value, then throw a TypeError. + if (!isValidHeaderName(name)) { + throw webidl.errors.invalidArgument({ + prefix: 'Headers.set', + value: name, + type: 'header name' + }) + } else if (!isValidHeaderValue(value)) { + throw webidl.errors.invalidArgument({ + prefix: 'Headers.set', + value, + type: 'header value' + }) + } + + // 3. If this’s guard is "immutable", then throw a TypeError. + // 4. Otherwise, if this’s guard is "request" and name is a + // forbidden header name, return. + // 5. Otherwise, if this’s guard is "request-no-cors" and + // name/value is not a no-CORS-safelisted request-header, + // return. + // 6. Otherwise, if this’s guard is "response" and name is a + // forbidden response-header name, return. + // Note: undici does not implement forbidden header names + if (this[kGuard] === 'immutable') { + throw new TypeError('immutable') + } else if (this[kGuard] === 'request-no-cors') { + // TODO + } + + // 7. Set (name, value) in this’s header list. + // 8. If this’s guard is "request-no-cors", then remove + // privileged no-CORS request headers from this + this[kHeadersList].set(name, value) + } + + // https://fetch.spec.whatwg.org/#dom-headers-getsetcookie + getSetCookie () { + webidl.brandCheck(this, Headers) + + // 1. If this’s header list does not contain `Set-Cookie`, then return « ». + // 2. Return the values of all headers in this’s header list whose name is + // a byte-case-insensitive match for `Set-Cookie`, in order. + + const list = this[kHeadersList].cookies + + if (list) { + return [...list] + } + + return [] + } + + // https://fetch.spec.whatwg.org/#concept-header-list-sort-and-combine + get [kHeadersSortedMap] () { + if (this[kHeadersList][kHeadersSortedMap]) { + return this[kHeadersList][kHeadersSortedMap] + } + + // 1. Let headers be an empty list of headers with the key being the name + // and value the value. + const headers = [] + + // 2. Let names be the result of convert header names to a sorted-lowercase + // set with all the names of the headers in list. + const names = [...this[kHeadersList]].sort((a, b) => a[0] < b[0] ? -1 : 1) + const cookies = this[kHeadersList].cookies + + // 3. For each name of names: + for (let i = 0; i < names.length; ++i) { + const [name, value] = names[i] + // 1. If name is `set-cookie`, then: + if (name === 'set-cookie') { + // 1. Let values be a list of all values of headers in list whose name + // is a byte-case-insensitive match for name, in order. + + // 2. For each value of values: + // 1. Append (name, value) to headers. + for (let j = 0; j < cookies.length; ++j) { + headers.push([name, cookies[j]]) + } + } else { + // 2. Otherwise: + + // 1. Let value be the result of getting name from list. + + // 2. Assert: value is non-null. + assert(value !== null) + + // 3. Append (name, value) to headers. + headers.push([name, value]) + } + } + + this[kHeadersList][kHeadersSortedMap] = headers + + // 4. Return headers. + return headers + } + + keys () { + webidl.brandCheck(this, Headers) + + if (this[kGuard] === 'immutable') { + const value = this[kHeadersSortedMap] + return makeIterator(() => value, 'Headers', + 'key') + } + + return makeIterator( + () => [...this[kHeadersSortedMap].values()], + 'Headers', + 'key' + ) + } + + values () { + webidl.brandCheck(this, Headers) + + if (this[kGuard] === 'immutable') { + const value = this[kHeadersSortedMap] + return makeIterator(() => value, 'Headers', + 'value') + } + + return makeIterator( + () => [...this[kHeadersSortedMap].values()], + 'Headers', + 'value' + ) + } + + entries () { + webidl.brandCheck(this, Headers) + + if (this[kGuard] === 'immutable') { + const value = this[kHeadersSortedMap] + return makeIterator(() => value, 'Headers', + 'key+value') + } + + return makeIterator( + () => [...this[kHeadersSortedMap].values()], + 'Headers', + 'key+value' + ) + } + + /** + * @param {(value: string, key: string, self: Headers) => void} callbackFn + * @param {unknown} thisArg + */ + forEach (callbackFn, thisArg = globalThis) { + webidl.brandCheck(this, Headers) + + webidl.argumentLengthCheck(arguments, 1, { header: 'Headers.forEach' }) + + if (typeof callbackFn !== 'function') { + throw new TypeError( + "Failed to execute 'forEach' on 'Headers': parameter 1 is not of type 'Function'." + ) + } + + for (const [key, value] of this) { + callbackFn.apply(thisArg, [value, key, this]) + } + } + + [Symbol.for('nodejs.util.inspect.custom')] () { + webidl.brandCheck(this, Headers) + + return this[kHeadersList] + } +} + +Headers.prototype[Symbol.iterator] = Headers.prototype.entries + +Object.defineProperties(Headers.prototype, { + append: kEnumerableProperty, + delete: kEnumerableProperty, + get: kEnumerableProperty, + has: kEnumerableProperty, + set: kEnumerableProperty, + getSetCookie: kEnumerableProperty, + keys: kEnumerableProperty, + values: kEnumerableProperty, + entries: kEnumerableProperty, + forEach: kEnumerableProperty, + [Symbol.iterator]: { enumerable: false }, + [Symbol.toStringTag]: { + value: 'Headers', + configurable: true + }, + [util.inspect.custom]: { + enumerable: false + } +}) + +webidl.converters.HeadersInit = function (V) { + if (webidl.util.Type(V) === 'Object') { + if (V[Symbol.iterator]) { + return webidl.converters['sequence>'](V) + } + + return webidl.converters['record'](V) + } + + throw webidl.errors.conversionFailed({ + prefix: 'Headers constructor', + argument: 'Argument 1', + types: ['sequence>', 'record'] + }) +} + +module.exports = { + fill, + Headers, + HeadersList +} + + +/***/ }), + +/***/ 12315: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; +// https://github.com/Ethan-Arrowood/undici-fetch + + + +const { + Response, + makeNetworkError, + makeAppropriateNetworkError, + filterResponse, + makeResponse +} = __nccwpck_require__(48676) +const { Headers } = __nccwpck_require__(26349) +const { Request, makeRequest } = __nccwpck_require__(25194) +const zlib = __nccwpck_require__(43106) +const { + bytesMatch, + makePolicyContainer, + clonePolicyContainer, + requestBadPort, + TAOCheck, + appendRequestOriginHeader, + responseLocationURL, + requestCurrentURL, + setRequestReferrerPolicyOnRedirect, + tryUpgradeRequestToAPotentiallyTrustworthyURL, + createOpaqueTimingInfo, + appendFetchMetadata, + corsCheck, + crossOriginResourcePolicyCheck, + determineRequestsReferrer, + coarsenedSharedCurrentTime, + createDeferredPromise, + isBlobLike, + sameOrigin, + isCancelled, + isAborted, + isErrorLike, + fullyReadBody, + readableStreamClose, + isomorphicEncode, + urlIsLocal, + urlIsHttpHttpsScheme, + urlHasHttpsScheme +} = __nccwpck_require__(15523) +const { kState, kHeaders, kGuard, kRealm } = __nccwpck_require__(89710) +const assert = __nccwpck_require__(42613) +const { safelyExtractBody } = __nccwpck_require__(8923) +const { + redirectStatusSet, + nullBodyStatus, + safeMethodsSet, + requestBodyHeader, + subresourceSet, + DOMException +} = __nccwpck_require__(87326) +const { kHeadersList } = __nccwpck_require__(36443) +const EE = __nccwpck_require__(24434) +const { Readable, pipeline } = __nccwpck_require__(2203) +const { addAbortListener, isErrored, isReadable, nodeMajor, nodeMinor } = __nccwpck_require__(3440) +const { dataURLProcessor, serializeAMimeType } = __nccwpck_require__(94322) +const { TransformStream } = __nccwpck_require__(63774) +const { getGlobalDispatcher } = __nccwpck_require__(32581) +const { webidl } = __nccwpck_require__(74222) +const { STATUS_CODES } = __nccwpck_require__(58611) +const GET_OR_HEAD = ['GET', 'HEAD'] + +/** @type {import('buffer').resolveObjectURL} */ +let resolveObjectURL +let ReadableStream = globalThis.ReadableStream + +class Fetch extends EE { + constructor (dispatcher) { + super() + + this.dispatcher = dispatcher + this.connection = null + this.dump = false + this.state = 'ongoing' + // 2 terminated listeners get added per request, + // but only 1 gets removed. If there are 20 redirects, + // 21 listeners will be added. + // See https://github.com/nodejs/undici/issues/1711 + // TODO (fix): Find and fix root cause for leaked listener. + this.setMaxListeners(21) + } + + terminate (reason) { + if (this.state !== 'ongoing') { + return + } + + this.state = 'terminated' + this.connection?.destroy(reason) + this.emit('terminated', reason) + } + + // https://fetch.spec.whatwg.org/#fetch-controller-abort + abort (error) { + if (this.state !== 'ongoing') { + return + } + + // 1. Set controller’s state to "aborted". + this.state = 'aborted' + + // 2. Let fallbackError be an "AbortError" DOMException. + // 3. Set error to fallbackError if it is not given. + if (!error) { + error = new DOMException('The operation was aborted.', 'AbortError') + } + + // 4. Let serializedError be StructuredSerialize(error). + // If that threw an exception, catch it, and let + // serializedError be StructuredSerialize(fallbackError). + + // 5. Set controller’s serialized abort reason to serializedError. + this.serializedAbortReason = error + + this.connection?.destroy(error) + this.emit('terminated', error) + } +} + +// https://fetch.spec.whatwg.org/#fetch-method +function fetch (input, init = {}) { + webidl.argumentLengthCheck(arguments, 1, { header: 'globalThis.fetch' }) + + // 1. Let p be a new promise. + const p = createDeferredPromise() + + // 2. Let requestObject be the result of invoking the initial value of + // Request as constructor with input and init as arguments. If this throws + // an exception, reject p with it and return p. + let requestObject + + try { + requestObject = new Request(input, init) + } catch (e) { + p.reject(e) + return p.promise + } + + // 3. Let request be requestObject’s request. + const request = requestObject[kState] + + // 4. If requestObject’s signal’s aborted flag is set, then: + if (requestObject.signal.aborted) { + // 1. Abort the fetch() call with p, request, null, and + // requestObject’s signal’s abort reason. + abortFetch(p, request, null, requestObject.signal.reason) + + // 2. Return p. + return p.promise + } + + // 5. Let globalObject be request’s client’s global object. + const globalObject = request.client.globalObject + + // 6. If globalObject is a ServiceWorkerGlobalScope object, then set + // request’s service-workers mode to "none". + if (globalObject?.constructor?.name === 'ServiceWorkerGlobalScope') { + request.serviceWorkers = 'none' + } + + // 7. Let responseObject be null. + let responseObject = null + + // 8. Let relevantRealm be this’s relevant Realm. + const relevantRealm = null + + // 9. Let locallyAborted be false. + let locallyAborted = false + + // 10. Let controller be null. + let controller = null + + // 11. Add the following abort steps to requestObject’s signal: + addAbortListener( + requestObject.signal, + () => { + // 1. Set locallyAborted to true. + locallyAborted = true + + // 2. Assert: controller is non-null. + assert(controller != null) + + // 3. Abort controller with requestObject’s signal’s abort reason. + controller.abort(requestObject.signal.reason) + + // 4. Abort the fetch() call with p, request, responseObject, + // and requestObject’s signal’s abort reason. + abortFetch(p, request, responseObject, requestObject.signal.reason) + } + ) + + // 12. Let handleFetchDone given response response be to finalize and + // report timing with response, globalObject, and "fetch". + const handleFetchDone = (response) => + finalizeAndReportTiming(response, 'fetch') + + // 13. Set controller to the result of calling fetch given request, + // with processResponseEndOfBody set to handleFetchDone, and processResponse + // given response being these substeps: + + const processResponse = (response) => { + // 1. If locallyAborted is true, terminate these substeps. + if (locallyAborted) { + return Promise.resolve() + } + + // 2. If response’s aborted flag is set, then: + if (response.aborted) { + // 1. Let deserializedError be the result of deserialize a serialized + // abort reason given controller’s serialized abort reason and + // relevantRealm. + + // 2. Abort the fetch() call with p, request, responseObject, and + // deserializedError. + + abortFetch(p, request, responseObject, controller.serializedAbortReason) + return Promise.resolve() + } + + // 3. If response is a network error, then reject p with a TypeError + // and terminate these substeps. + if (response.type === 'error') { + p.reject( + Object.assign(new TypeError('fetch failed'), { cause: response.error }) + ) + return Promise.resolve() + } + + // 4. Set responseObject to the result of creating a Response object, + // given response, "immutable", and relevantRealm. + responseObject = new Response() + responseObject[kState] = response + responseObject[kRealm] = relevantRealm + responseObject[kHeaders][kHeadersList] = response.headersList + responseObject[kHeaders][kGuard] = 'immutable' + responseObject[kHeaders][kRealm] = relevantRealm + + // 5. Resolve p with responseObject. + p.resolve(responseObject) + } + + controller = fetching({ + request, + processResponseEndOfBody: handleFetchDone, + processResponse, + dispatcher: init.dispatcher ?? getGlobalDispatcher() // undici + }) + + // 14. Return p. + return p.promise +} + +// https://fetch.spec.whatwg.org/#finalize-and-report-timing +function finalizeAndReportTiming (response, initiatorType = 'other') { + // 1. If response is an aborted network error, then return. + if (response.type === 'error' && response.aborted) { + return + } + + // 2. If response’s URL list is null or empty, then return. + if (!response.urlList?.length) { + return + } + + // 3. Let originalURL be response’s URL list[0]. + const originalURL = response.urlList[0] + + // 4. Let timingInfo be response’s timing info. + let timingInfo = response.timingInfo + + // 5. Let cacheState be response’s cache state. + let cacheState = response.cacheState + + // 6. If originalURL’s scheme is not an HTTP(S) scheme, then return. + if (!urlIsHttpHttpsScheme(originalURL)) { + return + } + + // 7. If timingInfo is null, then return. + if (timingInfo === null) { + return + } + + // 8. If response’s timing allow passed flag is not set, then: + if (!response.timingAllowPassed) { + // 1. Set timingInfo to a the result of creating an opaque timing info for timingInfo. + timingInfo = createOpaqueTimingInfo({ + startTime: timingInfo.startTime + }) + + // 2. Set cacheState to the empty string. + cacheState = '' + } + + // 9. Set timingInfo’s end time to the coarsened shared current time + // given global’s relevant settings object’s cross-origin isolated + // capability. + // TODO: given global’s relevant settings object’s cross-origin isolated + // capability? + timingInfo.endTime = coarsenedSharedCurrentTime() + + // 10. Set response’s timing info to timingInfo. + response.timingInfo = timingInfo + + // 11. Mark resource timing for timingInfo, originalURL, initiatorType, + // global, and cacheState. + markResourceTiming( + timingInfo, + originalURL, + initiatorType, + globalThis, + cacheState + ) +} + +// https://w3c.github.io/resource-timing/#dfn-mark-resource-timing +function markResourceTiming (timingInfo, originalURL, initiatorType, globalThis, cacheState) { + if (nodeMajor > 18 || (nodeMajor === 18 && nodeMinor >= 2)) { + performance.markResourceTiming(timingInfo, originalURL.href, initiatorType, globalThis, cacheState) + } +} + +// https://fetch.spec.whatwg.org/#abort-fetch +function abortFetch (p, request, responseObject, error) { + // Note: AbortSignal.reason was added in node v17.2.0 + // which would give us an undefined error to reject with. + // Remove this once node v16 is no longer supported. + if (!error) { + error = new DOMException('The operation was aborted.', 'AbortError') + } + + // 1. Reject promise with error. + p.reject(error) + + // 2. If request’s body is not null and is readable, then cancel request’s + // body with error. + if (request.body != null && isReadable(request.body?.stream)) { + request.body.stream.cancel(error).catch((err) => { + if (err.code === 'ERR_INVALID_STATE') { + // Node bug? + return + } + throw err + }) + } + + // 3. If responseObject is null, then return. + if (responseObject == null) { + return + } + + // 4. Let response be responseObject’s response. + const response = responseObject[kState] + + // 5. If response’s body is not null and is readable, then error response’s + // body with error. + if (response.body != null && isReadable(response.body?.stream)) { + response.body.stream.cancel(error).catch((err) => { + if (err.code === 'ERR_INVALID_STATE') { + // Node bug? + return + } + throw err + }) + } +} + +// https://fetch.spec.whatwg.org/#fetching +function fetching ({ + request, + processRequestBodyChunkLength, + processRequestEndOfBody, + processResponse, + processResponseEndOfBody, + processResponseConsumeBody, + useParallelQueue = false, + dispatcher // undici +}) { + // 1. Let taskDestination be null. + let taskDestination = null + + // 2. Let crossOriginIsolatedCapability be false. + let crossOriginIsolatedCapability = false + + // 3. If request’s client is non-null, then: + if (request.client != null) { + // 1. Set taskDestination to request’s client’s global object. + taskDestination = request.client.globalObject + + // 2. Set crossOriginIsolatedCapability to request’s client’s cross-origin + // isolated capability. + crossOriginIsolatedCapability = + request.client.crossOriginIsolatedCapability + } + + // 4. If useParallelQueue is true, then set taskDestination to the result of + // starting a new parallel queue. + // TODO + + // 5. Let timingInfo be a new fetch timing info whose start time and + // post-redirect start time are the coarsened shared current time given + // crossOriginIsolatedCapability. + const currenTime = coarsenedSharedCurrentTime(crossOriginIsolatedCapability) + const timingInfo = createOpaqueTimingInfo({ + startTime: currenTime + }) + + // 6. Let fetchParams be a new fetch params whose + // request is request, + // timing info is timingInfo, + // process request body chunk length is processRequestBodyChunkLength, + // process request end-of-body is processRequestEndOfBody, + // process response is processResponse, + // process response consume body is processResponseConsumeBody, + // process response end-of-body is processResponseEndOfBody, + // task destination is taskDestination, + // and cross-origin isolated capability is crossOriginIsolatedCapability. + const fetchParams = { + controller: new Fetch(dispatcher), + request, + timingInfo, + processRequestBodyChunkLength, + processRequestEndOfBody, + processResponse, + processResponseConsumeBody, + processResponseEndOfBody, + taskDestination, + crossOriginIsolatedCapability + } + + // 7. If request’s body is a byte sequence, then set request’s body to + // request’s body as a body. + // NOTE: Since fetching is only called from fetch, body should already be + // extracted. + assert(!request.body || request.body.stream) + + // 8. If request’s window is "client", then set request’s window to request’s + // client, if request’s client’s global object is a Window object; otherwise + // "no-window". + if (request.window === 'client') { + // TODO: What if request.client is null? + request.window = + request.client?.globalObject?.constructor?.name === 'Window' + ? request.client + : 'no-window' + } + + // 9. If request’s origin is "client", then set request’s origin to request’s + // client’s origin. + if (request.origin === 'client') { + // TODO: What if request.client is null? + request.origin = request.client?.origin + } + + // 10. If all of the following conditions are true: + // TODO + + // 11. If request’s policy container is "client", then: + if (request.policyContainer === 'client') { + // 1. If request’s client is non-null, then set request’s policy + // container to a clone of request’s client’s policy container. [HTML] + if (request.client != null) { + request.policyContainer = clonePolicyContainer( + request.client.policyContainer + ) + } else { + // 2. Otherwise, set request’s policy container to a new policy + // container. + request.policyContainer = makePolicyContainer() + } + } + + // 12. If request’s header list does not contain `Accept`, then: + if (!request.headersList.contains('accept')) { + // 1. Let value be `*/*`. + const value = '*/*' + + // 2. A user agent should set value to the first matching statement, if + // any, switching on request’s destination: + // "document" + // "frame" + // "iframe" + // `text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8` + // "image" + // `image/png,image/svg+xml,image/*;q=0.8,*/*;q=0.5` + // "style" + // `text/css,*/*;q=0.1` + // TODO + + // 3. Append `Accept`/value to request’s header list. + request.headersList.append('accept', value) + } + + // 13. If request’s header list does not contain `Accept-Language`, then + // user agents should append `Accept-Language`/an appropriate value to + // request’s header list. + if (!request.headersList.contains('accept-language')) { + request.headersList.append('accept-language', '*') + } + + // 14. If request’s priority is null, then use request’s initiator and + // destination appropriately in setting request’s priority to a + // user-agent-defined object. + if (request.priority === null) { + // TODO + } + + // 15. If request is a subresource request, then: + if (subresourceSet.has(request.destination)) { + // TODO + } + + // 16. Run main fetch given fetchParams. + mainFetch(fetchParams) + .catch(err => { + fetchParams.controller.terminate(err) + }) + + // 17. Return fetchParam's controller + return fetchParams.controller +} + +// https://fetch.spec.whatwg.org/#concept-main-fetch +async function mainFetch (fetchParams, recursive = false) { + // 1. Let request be fetchParams’s request. + const request = fetchParams.request + + // 2. Let response be null. + let response = null + + // 3. If request’s local-URLs-only flag is set and request’s current URL is + // not local, then set response to a network error. + if (request.localURLsOnly && !urlIsLocal(requestCurrentURL(request))) { + response = makeNetworkError('local URLs only') + } + + // 4. Run report Content Security Policy violations for request. + // TODO + + // 5. Upgrade request to a potentially trustworthy URL, if appropriate. + tryUpgradeRequestToAPotentiallyTrustworthyURL(request) + + // 6. If should request be blocked due to a bad port, should fetching request + // be blocked as mixed content, or should request be blocked by Content + // Security Policy returns blocked, then set response to a network error. + if (requestBadPort(request) === 'blocked') { + response = makeNetworkError('bad port') + } + // TODO: should fetching request be blocked as mixed content? + // TODO: should request be blocked by Content Security Policy? + + // 7. If request’s referrer policy is the empty string, then set request’s + // referrer policy to request’s policy container’s referrer policy. + if (request.referrerPolicy === '') { + request.referrerPolicy = request.policyContainer.referrerPolicy + } + + // 8. If request’s referrer is not "no-referrer", then set request’s + // referrer to the result of invoking determine request’s referrer. + if (request.referrer !== 'no-referrer') { + request.referrer = determineRequestsReferrer(request) + } + + // 9. Set request’s current URL’s scheme to "https" if all of the following + // conditions are true: + // - request’s current URL’s scheme is "http" + // - request’s current URL’s host is a domain + // - Matching request’s current URL’s host per Known HSTS Host Domain Name + // Matching results in either a superdomain match with an asserted + // includeSubDomains directive or a congruent match (with or without an + // asserted includeSubDomains directive). [HSTS] + // TODO + + // 10. If recursive is false, then run the remaining steps in parallel. + // TODO + + // 11. If response is null, then set response to the result of running + // the steps corresponding to the first matching statement: + if (response === null) { + response = await (async () => { + const currentURL = requestCurrentURL(request) + + if ( + // - request’s current URL’s origin is same origin with request’s origin, + // and request’s response tainting is "basic" + (sameOrigin(currentURL, request.url) && request.responseTainting === 'basic') || + // request’s current URL’s scheme is "data" + (currentURL.protocol === 'data:') || + // - request’s mode is "navigate" or "websocket" + (request.mode === 'navigate' || request.mode === 'websocket') + ) { + // 1. Set request’s response tainting to "basic". + request.responseTainting = 'basic' + + // 2. Return the result of running scheme fetch given fetchParams. + return await schemeFetch(fetchParams) + } + + // request’s mode is "same-origin" + if (request.mode === 'same-origin') { + // 1. Return a network error. + return makeNetworkError('request mode cannot be "same-origin"') + } + + // request’s mode is "no-cors" + if (request.mode === 'no-cors') { + // 1. If request’s redirect mode is not "follow", then return a network + // error. + if (request.redirect !== 'follow') { + return makeNetworkError( + 'redirect mode cannot be "follow" for "no-cors" request' + ) + } + + // 2. Set request’s response tainting to "opaque". + request.responseTainting = 'opaque' + + // 3. Return the result of running scheme fetch given fetchParams. + return await schemeFetch(fetchParams) + } + + // request’s current URL’s scheme is not an HTTP(S) scheme + if (!urlIsHttpHttpsScheme(requestCurrentURL(request))) { + // Return a network error. + return makeNetworkError('URL scheme must be a HTTP(S) scheme') + } + + // - request’s use-CORS-preflight flag is set + // - request’s unsafe-request flag is set and either request’s method is + // not a CORS-safelisted method or CORS-unsafe request-header names with + // request’s header list is not empty + // 1. Set request’s response tainting to "cors". + // 2. Let corsWithPreflightResponse be the result of running HTTP fetch + // given fetchParams and true. + // 3. If corsWithPreflightResponse is a network error, then clear cache + // entries using request. + // 4. Return corsWithPreflightResponse. + // TODO + + // Otherwise + // 1. Set request’s response tainting to "cors". + request.responseTainting = 'cors' + + // 2. Return the result of running HTTP fetch given fetchParams. + return await httpFetch(fetchParams) + })() + } + + // 12. If recursive is true, then return response. + if (recursive) { + return response + } + + // 13. If response is not a network error and response is not a filtered + // response, then: + if (response.status !== 0 && !response.internalResponse) { + // If request’s response tainting is "cors", then: + if (request.responseTainting === 'cors') { + // 1. Let headerNames be the result of extracting header list values + // given `Access-Control-Expose-Headers` and response’s header list. + // TODO + // 2. If request’s credentials mode is not "include" and headerNames + // contains `*`, then set response’s CORS-exposed header-name list to + // all unique header names in response’s header list. + // TODO + // 3. Otherwise, if headerNames is not null or failure, then set + // response’s CORS-exposed header-name list to headerNames. + // TODO + } + + // Set response to the following filtered response with response as its + // internal response, depending on request’s response tainting: + if (request.responseTainting === 'basic') { + response = filterResponse(response, 'basic') + } else if (request.responseTainting === 'cors') { + response = filterResponse(response, 'cors') + } else if (request.responseTainting === 'opaque') { + response = filterResponse(response, 'opaque') + } else { + assert(false) + } + } + + // 14. Let internalResponse be response, if response is a network error, + // and response’s internal response otherwise. + let internalResponse = + response.status === 0 ? response : response.internalResponse + + // 15. If internalResponse’s URL list is empty, then set it to a clone of + // request’s URL list. + if (internalResponse.urlList.length === 0) { + internalResponse.urlList.push(...request.urlList) + } + + // 16. If request’s timing allow failed flag is unset, then set + // internalResponse’s timing allow passed flag. + if (!request.timingAllowFailed) { + response.timingAllowPassed = true + } + + // 17. If response is not a network error and any of the following returns + // blocked + // - should internalResponse to request be blocked as mixed content + // - should internalResponse to request be blocked by Content Security Policy + // - should internalResponse to request be blocked due to its MIME type + // - should internalResponse to request be blocked due to nosniff + // TODO + + // 18. If response’s type is "opaque", internalResponse’s status is 206, + // internalResponse’s range-requested flag is set, and request’s header + // list does not contain `Range`, then set response and internalResponse + // to a network error. + if ( + response.type === 'opaque' && + internalResponse.status === 206 && + internalResponse.rangeRequested && + !request.headers.contains('range') + ) { + response = internalResponse = makeNetworkError() + } + + // 19. If response is not a network error and either request’s method is + // `HEAD` or `CONNECT`, or internalResponse’s status is a null body status, + // set internalResponse’s body to null and disregard any enqueuing toward + // it (if any). + if ( + response.status !== 0 && + (request.method === 'HEAD' || + request.method === 'CONNECT' || + nullBodyStatus.includes(internalResponse.status)) + ) { + internalResponse.body = null + fetchParams.controller.dump = true + } + + // 20. If request’s integrity metadata is not the empty string, then: + if (request.integrity) { + // 1. Let processBodyError be this step: run fetch finale given fetchParams + // and a network error. + const processBodyError = (reason) => + fetchFinale(fetchParams, makeNetworkError(reason)) + + // 2. If request’s response tainting is "opaque", or response’s body is null, + // then run processBodyError and abort these steps. + if (request.responseTainting === 'opaque' || response.body == null) { + processBodyError(response.error) + return + } + + // 3. Let processBody given bytes be these steps: + const processBody = (bytes) => { + // 1. If bytes do not match request’s integrity metadata, + // then run processBodyError and abort these steps. [SRI] + if (!bytesMatch(bytes, request.integrity)) { + processBodyError('integrity mismatch') + return + } + + // 2. Set response’s body to bytes as a body. + response.body = safelyExtractBody(bytes)[0] + + // 3. Run fetch finale given fetchParams and response. + fetchFinale(fetchParams, response) + } + + // 4. Fully read response’s body given processBody and processBodyError. + await fullyReadBody(response.body, processBody, processBodyError) + } else { + // 21. Otherwise, run fetch finale given fetchParams and response. + fetchFinale(fetchParams, response) + } +} + +// https://fetch.spec.whatwg.org/#concept-scheme-fetch +// given a fetch params fetchParams +function schemeFetch (fetchParams) { + // Note: since the connection is destroyed on redirect, which sets fetchParams to a + // cancelled state, we do not want this condition to trigger *unless* there have been + // no redirects. See https://github.com/nodejs/undici/issues/1776 + // 1. If fetchParams is canceled, then return the appropriate network error for fetchParams. + if (isCancelled(fetchParams) && fetchParams.request.redirectCount === 0) { + return Promise.resolve(makeAppropriateNetworkError(fetchParams)) + } + + // 2. Let request be fetchParams’s request. + const { request } = fetchParams + + const { protocol: scheme } = requestCurrentURL(request) + + // 3. Switch on request’s current URL’s scheme and run the associated steps: + switch (scheme) { + case 'about:': { + // If request’s current URL’s path is the string "blank", then return a new response + // whose status message is `OK`, header list is « (`Content-Type`, `text/html;charset=utf-8`) », + // and body is the empty byte sequence as a body. + + // Otherwise, return a network error. + return Promise.resolve(makeNetworkError('about scheme is not supported')) + } + case 'blob:': { + if (!resolveObjectURL) { + resolveObjectURL = (__nccwpck_require__(20181).resolveObjectURL) + } + + // 1. Let blobURLEntry be request’s current URL’s blob URL entry. + const blobURLEntry = requestCurrentURL(request) + + // https://github.com/web-platform-tests/wpt/blob/7b0ebaccc62b566a1965396e5be7bb2bc06f841f/FileAPI/url/resources/fetch-tests.js#L52-L56 + // Buffer.resolveObjectURL does not ignore URL queries. + if (blobURLEntry.search.length !== 0) { + return Promise.resolve(makeNetworkError('NetworkError when attempting to fetch resource.')) + } + + const blobURLEntryObject = resolveObjectURL(blobURLEntry.toString()) + + // 2. If request’s method is not `GET`, blobURLEntry is null, or blobURLEntry’s + // object is not a Blob object, then return a network error. + if (request.method !== 'GET' || !isBlobLike(blobURLEntryObject)) { + return Promise.resolve(makeNetworkError('invalid method')) + } + + // 3. Let bodyWithType be the result of safely extracting blobURLEntry’s object. + const bodyWithType = safelyExtractBody(blobURLEntryObject) + + // 4. Let body be bodyWithType’s body. + const body = bodyWithType[0] + + // 5. Let length be body’s length, serialized and isomorphic encoded. + const length = isomorphicEncode(`${body.length}`) + + // 6. Let type be bodyWithType’s type if it is non-null; otherwise the empty byte sequence. + const type = bodyWithType[1] ?? '' + + // 7. Return a new response whose status message is `OK`, header list is + // « (`Content-Length`, length), (`Content-Type`, type) », and body is body. + const response = makeResponse({ + statusText: 'OK', + headersList: [ + ['content-length', { name: 'Content-Length', value: length }], + ['content-type', { name: 'Content-Type', value: type }] + ] + }) + + response.body = body + + return Promise.resolve(response) + } + case 'data:': { + // 1. Let dataURLStruct be the result of running the + // data: URL processor on request’s current URL. + const currentURL = requestCurrentURL(request) + const dataURLStruct = dataURLProcessor(currentURL) + + // 2. If dataURLStruct is failure, then return a + // network error. + if (dataURLStruct === 'failure') { + return Promise.resolve(makeNetworkError('failed to fetch the data URL')) + } + + // 3. Let mimeType be dataURLStruct’s MIME type, serialized. + const mimeType = serializeAMimeType(dataURLStruct.mimeType) + + // 4. Return a response whose status message is `OK`, + // header list is « (`Content-Type`, mimeType) », + // and body is dataURLStruct’s body as a body. + return Promise.resolve(makeResponse({ + statusText: 'OK', + headersList: [ + ['content-type', { name: 'Content-Type', value: mimeType }] + ], + body: safelyExtractBody(dataURLStruct.body)[0] + })) + } + case 'file:': { + // For now, unfortunate as it is, file URLs are left as an exercise for the reader. + // When in doubt, return a network error. + return Promise.resolve(makeNetworkError('not implemented... yet...')) + } + case 'http:': + case 'https:': { + // Return the result of running HTTP fetch given fetchParams. + + return httpFetch(fetchParams) + .catch((err) => makeNetworkError(err)) + } + default: { + return Promise.resolve(makeNetworkError('unknown scheme')) + } + } +} + +// https://fetch.spec.whatwg.org/#finalize-response +function finalizeResponse (fetchParams, response) { + // 1. Set fetchParams’s request’s done flag. + fetchParams.request.done = true + + // 2, If fetchParams’s process response done is not null, then queue a fetch + // task to run fetchParams’s process response done given response, with + // fetchParams’s task destination. + if (fetchParams.processResponseDone != null) { + queueMicrotask(() => fetchParams.processResponseDone(response)) + } +} + +// https://fetch.spec.whatwg.org/#fetch-finale +function fetchFinale (fetchParams, response) { + // 1. If response is a network error, then: + if (response.type === 'error') { + // 1. Set response’s URL list to « fetchParams’s request’s URL list[0] ». + response.urlList = [fetchParams.request.urlList[0]] + + // 2. Set response’s timing info to the result of creating an opaque timing + // info for fetchParams’s timing info. + response.timingInfo = createOpaqueTimingInfo({ + startTime: fetchParams.timingInfo.startTime + }) + } + + // 2. Let processResponseEndOfBody be the following steps: + const processResponseEndOfBody = () => { + // 1. Set fetchParams’s request’s done flag. + fetchParams.request.done = true + + // If fetchParams’s process response end-of-body is not null, + // then queue a fetch task to run fetchParams’s process response + // end-of-body given response with fetchParams’s task destination. + if (fetchParams.processResponseEndOfBody != null) { + queueMicrotask(() => fetchParams.processResponseEndOfBody(response)) + } + } + + // 3. If fetchParams’s process response is non-null, then queue a fetch task + // to run fetchParams’s process response given response, with fetchParams’s + // task destination. + if (fetchParams.processResponse != null) { + queueMicrotask(() => fetchParams.processResponse(response)) + } + + // 4. If response’s body is null, then run processResponseEndOfBody. + if (response.body == null) { + processResponseEndOfBody() + } else { + // 5. Otherwise: + + // 1. Let transformStream be a new a TransformStream. + + // 2. Let identityTransformAlgorithm be an algorithm which, given chunk, + // enqueues chunk in transformStream. + const identityTransformAlgorithm = (chunk, controller) => { + controller.enqueue(chunk) + } + + // 3. Set up transformStream with transformAlgorithm set to identityTransformAlgorithm + // and flushAlgorithm set to processResponseEndOfBody. + const transformStream = new TransformStream({ + start () {}, + transform: identityTransformAlgorithm, + flush: processResponseEndOfBody + }, { + size () { + return 1 + } + }, { + size () { + return 1 + } + }) + + // 4. Set response’s body to the result of piping response’s body through transformStream. + response.body = { stream: response.body.stream.pipeThrough(transformStream) } + } + + // 6. If fetchParams’s process response consume body is non-null, then: + if (fetchParams.processResponseConsumeBody != null) { + // 1. Let processBody given nullOrBytes be this step: run fetchParams’s + // process response consume body given response and nullOrBytes. + const processBody = (nullOrBytes) => fetchParams.processResponseConsumeBody(response, nullOrBytes) + + // 2. Let processBodyError be this step: run fetchParams’s process + // response consume body given response and failure. + const processBodyError = (failure) => fetchParams.processResponseConsumeBody(response, failure) + + // 3. If response’s body is null, then queue a fetch task to run processBody + // given null, with fetchParams’s task destination. + if (response.body == null) { + queueMicrotask(() => processBody(null)) + } else { + // 4. Otherwise, fully read response’s body given processBody, processBodyError, + // and fetchParams’s task destination. + return fullyReadBody(response.body, processBody, processBodyError) + } + return Promise.resolve() + } +} + +// https://fetch.spec.whatwg.org/#http-fetch +async function httpFetch (fetchParams) { + // 1. Let request be fetchParams’s request. + const request = fetchParams.request + + // 2. Let response be null. + let response = null + + // 3. Let actualResponse be null. + let actualResponse = null + + // 4. Let timingInfo be fetchParams’s timing info. + const timingInfo = fetchParams.timingInfo + + // 5. If request’s service-workers mode is "all", then: + if (request.serviceWorkers === 'all') { + // TODO + } + + // 6. If response is null, then: + if (response === null) { + // 1. If makeCORSPreflight is true and one of these conditions is true: + // TODO + + // 2. If request’s redirect mode is "follow", then set request’s + // service-workers mode to "none". + if (request.redirect === 'follow') { + request.serviceWorkers = 'none' + } + + // 3. Set response and actualResponse to the result of running + // HTTP-network-or-cache fetch given fetchParams. + actualResponse = response = await httpNetworkOrCacheFetch(fetchParams) + + // 4. If request’s response tainting is "cors" and a CORS check + // for request and response returns failure, then return a network error. + if ( + request.responseTainting === 'cors' && + corsCheck(request, response) === 'failure' + ) { + return makeNetworkError('cors failure') + } + + // 5. If the TAO check for request and response returns failure, then set + // request’s timing allow failed flag. + if (TAOCheck(request, response) === 'failure') { + request.timingAllowFailed = true + } + } + + // 7. If either request’s response tainting or response’s type + // is "opaque", and the cross-origin resource policy check with + // request’s origin, request’s client, request’s destination, + // and actualResponse returns blocked, then return a network error. + if ( + (request.responseTainting === 'opaque' || response.type === 'opaque') && + crossOriginResourcePolicyCheck( + request.origin, + request.client, + request.destination, + actualResponse + ) === 'blocked' + ) { + return makeNetworkError('blocked') + } + + // 8. If actualResponse’s status is a redirect status, then: + if (redirectStatusSet.has(actualResponse.status)) { + // 1. If actualResponse’s status is not 303, request’s body is not null, + // and the connection uses HTTP/2, then user agents may, and are even + // encouraged to, transmit an RST_STREAM frame. + // See, https://github.com/whatwg/fetch/issues/1288 + if (request.redirect !== 'manual') { + fetchParams.controller.connection.destroy() + } + + // 2. Switch on request’s redirect mode: + if (request.redirect === 'error') { + // Set response to a network error. + response = makeNetworkError('unexpected redirect') + } else if (request.redirect === 'manual') { + // Set response to an opaque-redirect filtered response whose internal + // response is actualResponse. + // NOTE(spec): On the web this would return an `opaqueredirect` response, + // but that doesn't make sense server side. + // See https://github.com/nodejs/undici/issues/1193. + response = actualResponse + } else if (request.redirect === 'follow') { + // Set response to the result of running HTTP-redirect fetch given + // fetchParams and response. + response = await httpRedirectFetch(fetchParams, response) + } else { + assert(false) + } + } + + // 9. Set response’s timing info to timingInfo. + response.timingInfo = timingInfo + + // 10. Return response. + return response +} + +// https://fetch.spec.whatwg.org/#http-redirect-fetch +function httpRedirectFetch (fetchParams, response) { + // 1. Let request be fetchParams’s request. + const request = fetchParams.request + + // 2. Let actualResponse be response, if response is not a filtered response, + // and response’s internal response otherwise. + const actualResponse = response.internalResponse + ? response.internalResponse + : response + + // 3. Let locationURL be actualResponse’s location URL given request’s current + // URL’s fragment. + let locationURL + + try { + locationURL = responseLocationURL( + actualResponse, + requestCurrentURL(request).hash + ) + + // 4. If locationURL is null, then return response. + if (locationURL == null) { + return response + } + } catch (err) { + // 5. If locationURL is failure, then return a network error. + return Promise.resolve(makeNetworkError(err)) + } + + // 6. If locationURL’s scheme is not an HTTP(S) scheme, then return a network + // error. + if (!urlIsHttpHttpsScheme(locationURL)) { + return Promise.resolve(makeNetworkError('URL scheme must be a HTTP(S) scheme')) + } + + // 7. If request’s redirect count is 20, then return a network error. + if (request.redirectCount === 20) { + return Promise.resolve(makeNetworkError('redirect count exceeded')) + } + + // 8. Increase request’s redirect count by 1. + request.redirectCount += 1 + + // 9. If request’s mode is "cors", locationURL includes credentials, and + // request’s origin is not same origin with locationURL’s origin, then return + // a network error. + if ( + request.mode === 'cors' && + (locationURL.username || locationURL.password) && + !sameOrigin(request, locationURL) + ) { + return Promise.resolve(makeNetworkError('cross origin not allowed for request mode "cors"')) + } + + // 10. If request’s response tainting is "cors" and locationURL includes + // credentials, then return a network error. + if ( + request.responseTainting === 'cors' && + (locationURL.username || locationURL.password) + ) { + return Promise.resolve(makeNetworkError( + 'URL cannot contain credentials for request mode "cors"' + )) + } + + // 11. If actualResponse’s status is not 303, request’s body is non-null, + // and request’s body’s source is null, then return a network error. + if ( + actualResponse.status !== 303 && + request.body != null && + request.body.source == null + ) { + return Promise.resolve(makeNetworkError()) + } + + // 12. If one of the following is true + // - actualResponse’s status is 301 or 302 and request’s method is `POST` + // - actualResponse’s status is 303 and request’s method is not `GET` or `HEAD` + if ( + ([301, 302].includes(actualResponse.status) && request.method === 'POST') || + (actualResponse.status === 303 && + !GET_OR_HEAD.includes(request.method)) + ) { + // then: + // 1. Set request’s method to `GET` and request’s body to null. + request.method = 'GET' + request.body = null + + // 2. For each headerName of request-body-header name, delete headerName from + // request’s header list. + for (const headerName of requestBodyHeader) { + request.headersList.delete(headerName) + } + } + + // 13. If request’s current URL’s origin is not same origin with locationURL’s + // origin, then for each headerName of CORS non-wildcard request-header name, + // delete headerName from request’s header list. + if (!sameOrigin(requestCurrentURL(request), locationURL)) { + // https://fetch.spec.whatwg.org/#cors-non-wildcard-request-header-name + request.headersList.delete('authorization') + + // https://fetch.spec.whatwg.org/#authentication-entries + request.headersList.delete('proxy-authorization', true) + + // "Cookie" and "Host" are forbidden request-headers, which undici doesn't implement. + request.headersList.delete('cookie') + request.headersList.delete('host') + } + + // 14. If request’s body is non-null, then set request’s body to the first return + // value of safely extracting request’s body’s source. + if (request.body != null) { + assert(request.body.source != null) + request.body = safelyExtractBody(request.body.source)[0] + } + + // 15. Let timingInfo be fetchParams’s timing info. + const timingInfo = fetchParams.timingInfo + + // 16. Set timingInfo’s redirect end time and post-redirect start time to the + // coarsened shared current time given fetchParams’s cross-origin isolated + // capability. + timingInfo.redirectEndTime = timingInfo.postRedirectStartTime = + coarsenedSharedCurrentTime(fetchParams.crossOriginIsolatedCapability) + + // 17. If timingInfo’s redirect start time is 0, then set timingInfo’s + // redirect start time to timingInfo’s start time. + if (timingInfo.redirectStartTime === 0) { + timingInfo.redirectStartTime = timingInfo.startTime + } + + // 18. Append locationURL to request’s URL list. + request.urlList.push(locationURL) + + // 19. Invoke set request’s referrer policy on redirect on request and + // actualResponse. + setRequestReferrerPolicyOnRedirect(request, actualResponse) + + // 20. Return the result of running main fetch given fetchParams and true. + return mainFetch(fetchParams, true) +} + +// https://fetch.spec.whatwg.org/#http-network-or-cache-fetch +async function httpNetworkOrCacheFetch ( + fetchParams, + isAuthenticationFetch = false, + isNewConnectionFetch = false +) { + // 1. Let request be fetchParams’s request. + const request = fetchParams.request + + // 2. Let httpFetchParams be null. + let httpFetchParams = null + + // 3. Let httpRequest be null. + let httpRequest = null + + // 4. Let response be null. + let response = null + + // 5. Let storedResponse be null. + // TODO: cache + + // 6. Let httpCache be null. + const httpCache = null + + // 7. Let the revalidatingFlag be unset. + const revalidatingFlag = false + + // 8. Run these steps, but abort when the ongoing fetch is terminated: + + // 1. If request’s window is "no-window" and request’s redirect mode is + // "error", then set httpFetchParams to fetchParams and httpRequest to + // request. + if (request.window === 'no-window' && request.redirect === 'error') { + httpFetchParams = fetchParams + httpRequest = request + } else { + // Otherwise: + + // 1. Set httpRequest to a clone of request. + httpRequest = makeRequest(request) + + // 2. Set httpFetchParams to a copy of fetchParams. + httpFetchParams = { ...fetchParams } + + // 3. Set httpFetchParams’s request to httpRequest. + httpFetchParams.request = httpRequest + } + + // 3. Let includeCredentials be true if one of + const includeCredentials = + request.credentials === 'include' || + (request.credentials === 'same-origin' && + request.responseTainting === 'basic') + + // 4. Let contentLength be httpRequest’s body’s length, if httpRequest’s + // body is non-null; otherwise null. + const contentLength = httpRequest.body ? httpRequest.body.length : null + + // 5. Let contentLengthHeaderValue be null. + let contentLengthHeaderValue = null + + // 6. If httpRequest’s body is null and httpRequest’s method is `POST` or + // `PUT`, then set contentLengthHeaderValue to `0`. + if ( + httpRequest.body == null && + ['POST', 'PUT'].includes(httpRequest.method) + ) { + contentLengthHeaderValue = '0' + } + + // 7. If contentLength is non-null, then set contentLengthHeaderValue to + // contentLength, serialized and isomorphic encoded. + if (contentLength != null) { + contentLengthHeaderValue = isomorphicEncode(`${contentLength}`) + } + + // 8. If contentLengthHeaderValue is non-null, then append + // `Content-Length`/contentLengthHeaderValue to httpRequest’s header + // list. + if (contentLengthHeaderValue != null) { + httpRequest.headersList.append('content-length', contentLengthHeaderValue) + } + + // 9. If contentLengthHeaderValue is non-null, then append (`Content-Length`, + // contentLengthHeaderValue) to httpRequest’s header list. + + // 10. If contentLength is non-null and httpRequest’s keepalive is true, + // then: + if (contentLength != null && httpRequest.keepalive) { + // NOTE: keepalive is a noop outside of browser context. + } + + // 11. If httpRequest’s referrer is a URL, then append + // `Referer`/httpRequest’s referrer, serialized and isomorphic encoded, + // to httpRequest’s header list. + if (httpRequest.referrer instanceof URL) { + httpRequest.headersList.append('referer', isomorphicEncode(httpRequest.referrer.href)) + } + + // 12. Append a request `Origin` header for httpRequest. + appendRequestOriginHeader(httpRequest) + + // 13. Append the Fetch metadata headers for httpRequest. [FETCH-METADATA] + appendFetchMetadata(httpRequest) + + // 14. If httpRequest’s header list does not contain `User-Agent`, then + // user agents should append `User-Agent`/default `User-Agent` value to + // httpRequest’s header list. + if (!httpRequest.headersList.contains('user-agent')) { + httpRequest.headersList.append('user-agent', typeof esbuildDetection === 'undefined' ? 'undici' : 'node') + } + + // 15. If httpRequest’s cache mode is "default" and httpRequest’s header + // list contains `If-Modified-Since`, `If-None-Match`, + // `If-Unmodified-Since`, `If-Match`, or `If-Range`, then set + // httpRequest’s cache mode to "no-store". + if ( + httpRequest.cache === 'default' && + (httpRequest.headersList.contains('if-modified-since') || + httpRequest.headersList.contains('if-none-match') || + httpRequest.headersList.contains('if-unmodified-since') || + httpRequest.headersList.contains('if-match') || + httpRequest.headersList.contains('if-range')) + ) { + httpRequest.cache = 'no-store' + } + + // 16. If httpRequest’s cache mode is "no-cache", httpRequest’s prevent + // no-cache cache-control header modification flag is unset, and + // httpRequest’s header list does not contain `Cache-Control`, then append + // `Cache-Control`/`max-age=0` to httpRequest’s header list. + if ( + httpRequest.cache === 'no-cache' && + !httpRequest.preventNoCacheCacheControlHeaderModification && + !httpRequest.headersList.contains('cache-control') + ) { + httpRequest.headersList.append('cache-control', 'max-age=0') + } + + // 17. If httpRequest’s cache mode is "no-store" or "reload", then: + if (httpRequest.cache === 'no-store' || httpRequest.cache === 'reload') { + // 1. If httpRequest’s header list does not contain `Pragma`, then append + // `Pragma`/`no-cache` to httpRequest’s header list. + if (!httpRequest.headersList.contains('pragma')) { + httpRequest.headersList.append('pragma', 'no-cache') + } + + // 2. If httpRequest’s header list does not contain `Cache-Control`, + // then append `Cache-Control`/`no-cache` to httpRequest’s header list. + if (!httpRequest.headersList.contains('cache-control')) { + httpRequest.headersList.append('cache-control', 'no-cache') + } + } + + // 18. If httpRequest’s header list contains `Range`, then append + // `Accept-Encoding`/`identity` to httpRequest’s header list. + if (httpRequest.headersList.contains('range')) { + httpRequest.headersList.append('accept-encoding', 'identity') + } + + // 19. Modify httpRequest’s header list per HTTP. Do not append a given + // header if httpRequest’s header list contains that header’s name. + // TODO: https://github.com/whatwg/fetch/issues/1285#issuecomment-896560129 + if (!httpRequest.headersList.contains('accept-encoding')) { + if (urlHasHttpsScheme(requestCurrentURL(httpRequest))) { + httpRequest.headersList.append('accept-encoding', 'br, gzip, deflate') + } else { + httpRequest.headersList.append('accept-encoding', 'gzip, deflate') + } + } + + httpRequest.headersList.delete('host') + + // 20. If includeCredentials is true, then: + if (includeCredentials) { + // 1. If the user agent is not configured to block cookies for httpRequest + // (see section 7 of [COOKIES]), then: + // TODO: credentials + // 2. If httpRequest’s header list does not contain `Authorization`, then: + // TODO: credentials + } + + // 21. If there’s a proxy-authentication entry, use it as appropriate. + // TODO: proxy-authentication + + // 22. Set httpCache to the result of determining the HTTP cache + // partition, given httpRequest. + // TODO: cache + + // 23. If httpCache is null, then set httpRequest’s cache mode to + // "no-store". + if (httpCache == null) { + httpRequest.cache = 'no-store' + } + + // 24. If httpRequest’s cache mode is neither "no-store" nor "reload", + // then: + if (httpRequest.mode !== 'no-store' && httpRequest.mode !== 'reload') { + // TODO: cache + } + + // 9. If aborted, then return the appropriate network error for fetchParams. + // TODO + + // 10. If response is null, then: + if (response == null) { + // 1. If httpRequest’s cache mode is "only-if-cached", then return a + // network error. + if (httpRequest.mode === 'only-if-cached') { + return makeNetworkError('only if cached') + } + + // 2. Let forwardResponse be the result of running HTTP-network fetch + // given httpFetchParams, includeCredentials, and isNewConnectionFetch. + const forwardResponse = await httpNetworkFetch( + httpFetchParams, + includeCredentials, + isNewConnectionFetch + ) + + // 3. If httpRequest’s method is unsafe and forwardResponse’s status is + // in the range 200 to 399, inclusive, invalidate appropriate stored + // responses in httpCache, as per the "Invalidation" chapter of HTTP + // Caching, and set storedResponse to null. [HTTP-CACHING] + if ( + !safeMethodsSet.has(httpRequest.method) && + forwardResponse.status >= 200 && + forwardResponse.status <= 399 + ) { + // TODO: cache + } + + // 4. If the revalidatingFlag is set and forwardResponse’s status is 304, + // then: + if (revalidatingFlag && forwardResponse.status === 304) { + // TODO: cache + } + + // 5. If response is null, then: + if (response == null) { + // 1. Set response to forwardResponse. + response = forwardResponse + + // 2. Store httpRequest and forwardResponse in httpCache, as per the + // "Storing Responses in Caches" chapter of HTTP Caching. [HTTP-CACHING] + // TODO: cache + } + } + + // 11. Set response’s URL list to a clone of httpRequest’s URL list. + response.urlList = [...httpRequest.urlList] + + // 12. If httpRequest’s header list contains `Range`, then set response’s + // range-requested flag. + if (httpRequest.headersList.contains('range')) { + response.rangeRequested = true + } + + // 13. Set response’s request-includes-credentials to includeCredentials. + response.requestIncludesCredentials = includeCredentials + + // 14. If response’s status is 401, httpRequest’s response tainting is not + // "cors", includeCredentials is true, and request’s window is an environment + // settings object, then: + // TODO + + // 15. If response’s status is 407, then: + if (response.status === 407) { + // 1. If request’s window is "no-window", then return a network error. + if (request.window === 'no-window') { + return makeNetworkError() + } + + // 2. ??? + + // 3. If fetchParams is canceled, then return the appropriate network error for fetchParams. + if (isCancelled(fetchParams)) { + return makeAppropriateNetworkError(fetchParams) + } + + // 4. Prompt the end user as appropriate in request’s window and store + // the result as a proxy-authentication entry. [HTTP-AUTH] + // TODO: Invoke some kind of callback? + + // 5. Set response to the result of running HTTP-network-or-cache fetch given + // fetchParams. + // TODO + return makeNetworkError('proxy authentication required') + } + + // 16. If all of the following are true + if ( + // response’s status is 421 + response.status === 421 && + // isNewConnectionFetch is false + !isNewConnectionFetch && + // request’s body is null, or request’s body is non-null and request’s body’s source is non-null + (request.body == null || request.body.source != null) + ) { + // then: + + // 1. If fetchParams is canceled, then return the appropriate network error for fetchParams. + if (isCancelled(fetchParams)) { + return makeAppropriateNetworkError(fetchParams) + } + + // 2. Set response to the result of running HTTP-network-or-cache + // fetch given fetchParams, isAuthenticationFetch, and true. + + // TODO (spec): The spec doesn't specify this but we need to cancel + // the active response before we can start a new one. + // https://github.com/whatwg/fetch/issues/1293 + fetchParams.controller.connection.destroy() + + response = await httpNetworkOrCacheFetch( + fetchParams, + isAuthenticationFetch, + true + ) + } + + // 17. If isAuthenticationFetch is true, then create an authentication entry + if (isAuthenticationFetch) { + // TODO + } + + // 18. Return response. + return response +} + +// https://fetch.spec.whatwg.org/#http-network-fetch +async function httpNetworkFetch ( + fetchParams, + includeCredentials = false, + forceNewConnection = false +) { + assert(!fetchParams.controller.connection || fetchParams.controller.connection.destroyed) + + fetchParams.controller.connection = { + abort: null, + destroyed: false, + destroy (err) { + if (!this.destroyed) { + this.destroyed = true + this.abort?.(err ?? new DOMException('The operation was aborted.', 'AbortError')) + } + } + } + + // 1. Let request be fetchParams’s request. + const request = fetchParams.request + + // 2. Let response be null. + let response = null + + // 3. Let timingInfo be fetchParams’s timing info. + const timingInfo = fetchParams.timingInfo + + // 4. Let httpCache be the result of determining the HTTP cache partition, + // given request. + // TODO: cache + const httpCache = null + + // 5. If httpCache is null, then set request’s cache mode to "no-store". + if (httpCache == null) { + request.cache = 'no-store' + } + + // 6. Let networkPartitionKey be the result of determining the network + // partition key given request. + // TODO + + // 7. Let newConnection be "yes" if forceNewConnection is true; otherwise + // "no". + const newConnection = forceNewConnection ? 'yes' : 'no' // eslint-disable-line no-unused-vars + + // 8. Switch on request’s mode: + if (request.mode === 'websocket') { + // Let connection be the result of obtaining a WebSocket connection, + // given request’s current URL. + // TODO + } else { + // Let connection be the result of obtaining a connection, given + // networkPartitionKey, request’s current URL’s origin, + // includeCredentials, and forceNewConnection. + // TODO + } + + // 9. Run these steps, but abort when the ongoing fetch is terminated: + + // 1. If connection is failure, then return a network error. + + // 2. Set timingInfo’s final connection timing info to the result of + // calling clamp and coarsen connection timing info with connection’s + // timing info, timingInfo’s post-redirect start time, and fetchParams’s + // cross-origin isolated capability. + + // 3. If connection is not an HTTP/2 connection, request’s body is non-null, + // and request’s body’s source is null, then append (`Transfer-Encoding`, + // `chunked`) to request’s header list. + + // 4. Set timingInfo’s final network-request start time to the coarsened + // shared current time given fetchParams’s cross-origin isolated + // capability. + + // 5. Set response to the result of making an HTTP request over connection + // using request with the following caveats: + + // - Follow the relevant requirements from HTTP. [HTTP] [HTTP-SEMANTICS] + // [HTTP-COND] [HTTP-CACHING] [HTTP-AUTH] + + // - If request’s body is non-null, and request’s body’s source is null, + // then the user agent may have a buffer of up to 64 kibibytes and store + // a part of request’s body in that buffer. If the user agent reads from + // request’s body beyond that buffer’s size and the user agent needs to + // resend request, then instead return a network error. + + // - Set timingInfo’s final network-response start time to the coarsened + // shared current time given fetchParams’s cross-origin isolated capability, + // immediately after the user agent’s HTTP parser receives the first byte + // of the response (e.g., frame header bytes for HTTP/2 or response status + // line for HTTP/1.x). + + // - Wait until all the headers are transmitted. + + // - Any responses whose status is in the range 100 to 199, inclusive, + // and is not 101, are to be ignored, except for the purposes of setting + // timingInfo’s final network-response start time above. + + // - If request’s header list contains `Transfer-Encoding`/`chunked` and + // response is transferred via HTTP/1.0 or older, then return a network + // error. + + // - If the HTTP request results in a TLS client certificate dialog, then: + + // 1. If request’s window is an environment settings object, make the + // dialog available in request’s window. + + // 2. Otherwise, return a network error. + + // To transmit request’s body body, run these steps: + let requestBody = null + // 1. If body is null and fetchParams’s process request end-of-body is + // non-null, then queue a fetch task given fetchParams’s process request + // end-of-body and fetchParams’s task destination. + if (request.body == null && fetchParams.processRequestEndOfBody) { + queueMicrotask(() => fetchParams.processRequestEndOfBody()) + } else if (request.body != null) { + // 2. Otherwise, if body is non-null: + + // 1. Let processBodyChunk given bytes be these steps: + const processBodyChunk = async function * (bytes) { + // 1. If the ongoing fetch is terminated, then abort these steps. + if (isCancelled(fetchParams)) { + return + } + + // 2. Run this step in parallel: transmit bytes. + yield bytes + + // 3. If fetchParams’s process request body is non-null, then run + // fetchParams’s process request body given bytes’s length. + fetchParams.processRequestBodyChunkLength?.(bytes.byteLength) + } + + // 2. Let processEndOfBody be these steps: + const processEndOfBody = () => { + // 1. If fetchParams is canceled, then abort these steps. + if (isCancelled(fetchParams)) { + return + } + + // 2. If fetchParams’s process request end-of-body is non-null, + // then run fetchParams’s process request end-of-body. + if (fetchParams.processRequestEndOfBody) { + fetchParams.processRequestEndOfBody() + } + } + + // 3. Let processBodyError given e be these steps: + const processBodyError = (e) => { + // 1. If fetchParams is canceled, then abort these steps. + if (isCancelled(fetchParams)) { + return + } + + // 2. If e is an "AbortError" DOMException, then abort fetchParams’s controller. + if (e.name === 'AbortError') { + fetchParams.controller.abort() + } else { + fetchParams.controller.terminate(e) + } + } + + // 4. Incrementally read request’s body given processBodyChunk, processEndOfBody, + // processBodyError, and fetchParams’s task destination. + requestBody = (async function * () { + try { + for await (const bytes of request.body.stream) { + yield * processBodyChunk(bytes) + } + processEndOfBody() + } catch (err) { + processBodyError(err) + } + })() + } + + try { + // socket is only provided for websockets + const { body, status, statusText, headersList, socket } = await dispatch({ body: requestBody }) + + if (socket) { + response = makeResponse({ status, statusText, headersList, socket }) + } else { + const iterator = body[Symbol.asyncIterator]() + fetchParams.controller.next = () => iterator.next() + + response = makeResponse({ status, statusText, headersList }) + } + } catch (err) { + // 10. If aborted, then: + if (err.name === 'AbortError') { + // 1. If connection uses HTTP/2, then transmit an RST_STREAM frame. + fetchParams.controller.connection.destroy() + + // 2. Return the appropriate network error for fetchParams. + return makeAppropriateNetworkError(fetchParams, err) + } + + return makeNetworkError(err) + } + + // 11. Let pullAlgorithm be an action that resumes the ongoing fetch + // if it is suspended. + const pullAlgorithm = () => { + fetchParams.controller.resume() + } + + // 12. Let cancelAlgorithm be an algorithm that aborts fetchParams’s + // controller with reason, given reason. + const cancelAlgorithm = (reason) => { + fetchParams.controller.abort(reason) + } + + // 13. Let highWaterMark be a non-negative, non-NaN number, chosen by + // the user agent. + // TODO + + // 14. Let sizeAlgorithm be an algorithm that accepts a chunk object + // and returns a non-negative, non-NaN, non-infinite number, chosen by the user agent. + // TODO + + // 15. Let stream be a new ReadableStream. + // 16. Set up stream with pullAlgorithm set to pullAlgorithm, + // cancelAlgorithm set to cancelAlgorithm, highWaterMark set to + // highWaterMark, and sizeAlgorithm set to sizeAlgorithm. + if (!ReadableStream) { + ReadableStream = (__nccwpck_require__(63774).ReadableStream) + } + + const stream = new ReadableStream( + { + async start (controller) { + fetchParams.controller.controller = controller + }, + async pull (controller) { + await pullAlgorithm(controller) + }, + async cancel (reason) { + await cancelAlgorithm(reason) + } + }, + { + highWaterMark: 0, + size () { + return 1 + } + } + ) + + // 17. Run these steps, but abort when the ongoing fetch is terminated: + + // 1. Set response’s body to a new body whose stream is stream. + response.body = { stream } + + // 2. If response is not a network error and request’s cache mode is + // not "no-store", then update response in httpCache for request. + // TODO + + // 3. If includeCredentials is true and the user agent is not configured + // to block cookies for request (see section 7 of [COOKIES]), then run the + // "set-cookie-string" parsing algorithm (see section 5.2 of [COOKIES]) on + // the value of each header whose name is a byte-case-insensitive match for + // `Set-Cookie` in response’s header list, if any, and request’s current URL. + // TODO + + // 18. If aborted, then: + // TODO + + // 19. Run these steps in parallel: + + // 1. Run these steps, but abort when fetchParams is canceled: + fetchParams.controller.on('terminated', onAborted) + fetchParams.controller.resume = async () => { + // 1. While true + while (true) { + // 1-3. See onData... + + // 4. Set bytes to the result of handling content codings given + // codings and bytes. + let bytes + let isFailure + try { + const { done, value } = await fetchParams.controller.next() + + if (isAborted(fetchParams)) { + break + } + + bytes = done ? undefined : value + } catch (err) { + if (fetchParams.controller.ended && !timingInfo.encodedBodySize) { + // zlib doesn't like empty streams. + bytes = undefined + } else { + bytes = err + + // err may be propagated from the result of calling readablestream.cancel, + // which might not be an error. https://github.com/nodejs/undici/issues/2009 + isFailure = true + } + } + + if (bytes === undefined) { + // 2. Otherwise, if the bytes transmission for response’s message + // body is done normally and stream is readable, then close + // stream, finalize response for fetchParams and response, and + // abort these in-parallel steps. + readableStreamClose(fetchParams.controller.controller) + + finalizeResponse(fetchParams, response) + + return + } + + // 5. Increase timingInfo’s decoded body size by bytes’s length. + timingInfo.decodedBodySize += bytes?.byteLength ?? 0 + + // 6. If bytes is failure, then terminate fetchParams’s controller. + if (isFailure) { + fetchParams.controller.terminate(bytes) + return + } + + // 7. Enqueue a Uint8Array wrapping an ArrayBuffer containing bytes + // into stream. + fetchParams.controller.controller.enqueue(new Uint8Array(bytes)) + + // 8. If stream is errored, then terminate the ongoing fetch. + if (isErrored(stream)) { + fetchParams.controller.terminate() + return + } + + // 9. If stream doesn’t need more data ask the user agent to suspend + // the ongoing fetch. + if (!fetchParams.controller.controller.desiredSize) { + return + } + } + } + + // 2. If aborted, then: + function onAborted (reason) { + // 2. If fetchParams is aborted, then: + if (isAborted(fetchParams)) { + // 1. Set response’s aborted flag. + response.aborted = true + + // 2. If stream is readable, then error stream with the result of + // deserialize a serialized abort reason given fetchParams’s + // controller’s serialized abort reason and an + // implementation-defined realm. + if (isReadable(stream)) { + fetchParams.controller.controller.error( + fetchParams.controller.serializedAbortReason + ) + } + } else { + // 3. Otherwise, if stream is readable, error stream with a TypeError. + if (isReadable(stream)) { + fetchParams.controller.controller.error(new TypeError('terminated', { + cause: isErrorLike(reason) ? reason : undefined + })) + } + } + + // 4. If connection uses HTTP/2, then transmit an RST_STREAM frame. + // 5. Otherwise, the user agent should close connection unless it would be bad for performance to do so. + fetchParams.controller.connection.destroy() + } + + // 20. Return response. + return response + + async function dispatch ({ body }) { + const url = requestCurrentURL(request) + /** @type {import('../..').Agent} */ + const agent = fetchParams.controller.dispatcher + + return new Promise((resolve, reject) => agent.dispatch( + { + path: url.pathname + url.search, + origin: url.origin, + method: request.method, + body: fetchParams.controller.dispatcher.isMockActive ? request.body && (request.body.source || request.body.stream) : body, + headers: request.headersList.entries, + maxRedirections: 0, + upgrade: request.mode === 'websocket' ? 'websocket' : undefined + }, + { + body: null, + abort: null, + + onConnect (abort) { + // TODO (fix): Do we need connection here? + const { connection } = fetchParams.controller + + if (connection.destroyed) { + abort(new DOMException('The operation was aborted.', 'AbortError')) + } else { + fetchParams.controller.on('terminated', abort) + this.abort = connection.abort = abort + } + }, + + onHeaders (status, headersList, resume, statusText) { + if (status < 200) { + return + } + + let codings = [] + let location = '' + + const headers = new Headers() + + // For H2, the headers are a plain JS object + // We distinguish between them and iterate accordingly + if (Array.isArray(headersList)) { + for (let n = 0; n < headersList.length; n += 2) { + const key = headersList[n + 0].toString('latin1') + const val = headersList[n + 1].toString('latin1') + if (key.toLowerCase() === 'content-encoding') { + // https://www.rfc-editor.org/rfc/rfc7231#section-3.1.2.1 + // "All content-coding values are case-insensitive..." + codings = val.toLowerCase().split(',').map((x) => x.trim()) + } else if (key.toLowerCase() === 'location') { + location = val + } + + headers[kHeadersList].append(key, val) + } + } else { + const keys = Object.keys(headersList) + for (const key of keys) { + const val = headersList[key] + if (key.toLowerCase() === 'content-encoding') { + // https://www.rfc-editor.org/rfc/rfc7231#section-3.1.2.1 + // "All content-coding values are case-insensitive..." + codings = val.toLowerCase().split(',').map((x) => x.trim()).reverse() + } else if (key.toLowerCase() === 'location') { + location = val + } + + headers[kHeadersList].append(key, val) + } + } + + this.body = new Readable({ read: resume }) + + const decoders = [] + + const willFollow = request.redirect === 'follow' && + location && + redirectStatusSet.has(status) + + // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Encoding + if (request.method !== 'HEAD' && request.method !== 'CONNECT' && !nullBodyStatus.includes(status) && !willFollow) { + for (const coding of codings) { + // https://www.rfc-editor.org/rfc/rfc9112.html#section-7.2 + if (coding === 'x-gzip' || coding === 'gzip') { + decoders.push(zlib.createGunzip({ + // Be less strict when decoding compressed responses, since sometimes + // servers send slightly invalid responses that are still accepted + // by common browsers. + // Always using Z_SYNC_FLUSH is what cURL does. + flush: zlib.constants.Z_SYNC_FLUSH, + finishFlush: zlib.constants.Z_SYNC_FLUSH + })) + } else if (coding === 'deflate') { + decoders.push(zlib.createInflate()) + } else if (coding === 'br') { + decoders.push(zlib.createBrotliDecompress()) + } else { + decoders.length = 0 + break + } + } + } + + resolve({ + status, + statusText, + headersList: headers[kHeadersList], + body: decoders.length + ? pipeline(this.body, ...decoders, () => { }) + : this.body.on('error', () => {}) + }) + + return true + }, + + onData (chunk) { + if (fetchParams.controller.dump) { + return + } + + // 1. If one or more bytes have been transmitted from response’s + // message body, then: + + // 1. Let bytes be the transmitted bytes. + const bytes = chunk + + // 2. Let codings be the result of extracting header list values + // given `Content-Encoding` and response’s header list. + // See pullAlgorithm. + + // 3. Increase timingInfo’s encoded body size by bytes’s length. + timingInfo.encodedBodySize += bytes.byteLength + + // 4. See pullAlgorithm... + + return this.body.push(bytes) + }, + + onComplete () { + if (this.abort) { + fetchParams.controller.off('terminated', this.abort) + } + + fetchParams.controller.ended = true + + this.body.push(null) + }, + + onError (error) { + if (this.abort) { + fetchParams.controller.off('terminated', this.abort) + } + + this.body?.destroy(error) + + fetchParams.controller.terminate(error) + + reject(error) + }, + + onUpgrade (status, headersList, socket) { + if (status !== 101) { + return + } + + const headers = new Headers() + + for (let n = 0; n < headersList.length; n += 2) { + const key = headersList[n + 0].toString('latin1') + const val = headersList[n + 1].toString('latin1') + + headers[kHeadersList].append(key, val) + } + + resolve({ + status, + statusText: STATUS_CODES[status], + headersList: headers[kHeadersList], + socket + }) + + return true + } + } + )) + } +} + +module.exports = { + fetch, + Fetch, + fetching, + finalizeAndReportTiming +} + + +/***/ }), + +/***/ 25194: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; +/* globals AbortController */ + + + +const { extractBody, mixinBody, cloneBody } = __nccwpck_require__(8923) +const { Headers, fill: fillHeaders, HeadersList } = __nccwpck_require__(26349) +const { FinalizationRegistry } = __nccwpck_require__(13194)() +const util = __nccwpck_require__(3440) +const { + isValidHTTPToken, + sameOrigin, + normalizeMethod, + makePolicyContainer, + normalizeMethodRecord +} = __nccwpck_require__(15523) +const { + forbiddenMethodsSet, + corsSafeListedMethodsSet, + referrerPolicy, + requestRedirect, + requestMode, + requestCredentials, + requestCache, + requestDuplex +} = __nccwpck_require__(87326) +const { kEnumerableProperty } = util +const { kHeaders, kSignal, kState, kGuard, kRealm } = __nccwpck_require__(89710) +const { webidl } = __nccwpck_require__(74222) +const { getGlobalOrigin } = __nccwpck_require__(75628) +const { URLSerializer } = __nccwpck_require__(94322) +const { kHeadersList, kConstruct } = __nccwpck_require__(36443) +const assert = __nccwpck_require__(42613) +const { getMaxListeners, setMaxListeners, getEventListeners, defaultMaxListeners } = __nccwpck_require__(24434) + +let TransformStream = globalThis.TransformStream + +const kAbortController = Symbol('abortController') + +const requestFinalizer = new FinalizationRegistry(({ signal, abort }) => { + signal.removeEventListener('abort', abort) +}) + +// https://fetch.spec.whatwg.org/#request-class +class Request { + // https://fetch.spec.whatwg.org/#dom-request + constructor (input, init = {}) { + if (input === kConstruct) { + return + } + + webidl.argumentLengthCheck(arguments, 1, { header: 'Request constructor' }) + + input = webidl.converters.RequestInfo(input) + init = webidl.converters.RequestInit(init) + + // https://html.spec.whatwg.org/multipage/webappapis.html#environment-settings-object + this[kRealm] = { + settingsObject: { + baseUrl: getGlobalOrigin(), + get origin () { + return this.baseUrl?.origin + }, + policyContainer: makePolicyContainer() + } + } + + // 1. Let request be null. + let request = null + + // 2. Let fallbackMode be null. + let fallbackMode = null + + // 3. Let baseURL be this’s relevant settings object’s API base URL. + const baseUrl = this[kRealm].settingsObject.baseUrl + + // 4. Let signal be null. + let signal = null + + // 5. If input is a string, then: + if (typeof input === 'string') { + // 1. Let parsedURL be the result of parsing input with baseURL. + // 2. If parsedURL is failure, then throw a TypeError. + let parsedURL + try { + parsedURL = new URL(input, baseUrl) + } catch (err) { + throw new TypeError('Failed to parse URL from ' + input, { cause: err }) + } + + // 3. If parsedURL includes credentials, then throw a TypeError. + if (parsedURL.username || parsedURL.password) { + throw new TypeError( + 'Request cannot be constructed from a URL that includes credentials: ' + + input + ) + } + + // 4. Set request to a new request whose URL is parsedURL. + request = makeRequest({ urlList: [parsedURL] }) + + // 5. Set fallbackMode to "cors". + fallbackMode = 'cors' + } else { + // 6. Otherwise: + + // 7. Assert: input is a Request object. + assert(input instanceof Request) + + // 8. Set request to input’s request. + request = input[kState] + + // 9. Set signal to input’s signal. + signal = input[kSignal] + } + + // 7. Let origin be this’s relevant settings object’s origin. + const origin = this[kRealm].settingsObject.origin + + // 8. Let window be "client". + let window = 'client' + + // 9. If request’s window is an environment settings object and its origin + // is same origin with origin, then set window to request’s window. + if ( + request.window?.constructor?.name === 'EnvironmentSettingsObject' && + sameOrigin(request.window, origin) + ) { + window = request.window + } + + // 10. If init["window"] exists and is non-null, then throw a TypeError. + if (init.window != null) { + throw new TypeError(`'window' option '${window}' must be null`) + } + + // 11. If init["window"] exists, then set window to "no-window". + if ('window' in init) { + window = 'no-window' + } + + // 12. Set request to a new request with the following properties: + request = makeRequest({ + // URL request’s URL. + // undici implementation note: this is set as the first item in request's urlList in makeRequest + // method request’s method. + method: request.method, + // header list A copy of request’s header list. + // undici implementation note: headersList is cloned in makeRequest + headersList: request.headersList, + // unsafe-request flag Set. + unsafeRequest: request.unsafeRequest, + // client This’s relevant settings object. + client: this[kRealm].settingsObject, + // window window. + window, + // priority request’s priority. + priority: request.priority, + // origin request’s origin. The propagation of the origin is only significant for navigation requests + // being handled by a service worker. In this scenario a request can have an origin that is different + // from the current client. + origin: request.origin, + // referrer request’s referrer. + referrer: request.referrer, + // referrer policy request’s referrer policy. + referrerPolicy: request.referrerPolicy, + // mode request’s mode. + mode: request.mode, + // credentials mode request’s credentials mode. + credentials: request.credentials, + // cache mode request’s cache mode. + cache: request.cache, + // redirect mode request’s redirect mode. + redirect: request.redirect, + // integrity metadata request’s integrity metadata. + integrity: request.integrity, + // keepalive request’s keepalive. + keepalive: request.keepalive, + // reload-navigation flag request’s reload-navigation flag. + reloadNavigation: request.reloadNavigation, + // history-navigation flag request’s history-navigation flag. + historyNavigation: request.historyNavigation, + // URL list A clone of request’s URL list. + urlList: [...request.urlList] + }) + + const initHasKey = Object.keys(init).length !== 0 + + // 13. If init is not empty, then: + if (initHasKey) { + // 1. If request’s mode is "navigate", then set it to "same-origin". + if (request.mode === 'navigate') { + request.mode = 'same-origin' + } + + // 2. Unset request’s reload-navigation flag. + request.reloadNavigation = false + + // 3. Unset request’s history-navigation flag. + request.historyNavigation = false + + // 4. Set request’s origin to "client". + request.origin = 'client' + + // 5. Set request’s referrer to "client" + request.referrer = 'client' + + // 6. Set request’s referrer policy to the empty string. + request.referrerPolicy = '' + + // 7. Set request’s URL to request’s current URL. + request.url = request.urlList[request.urlList.length - 1] + + // 8. Set request’s URL list to « request’s URL ». + request.urlList = [request.url] + } + + // 14. If init["referrer"] exists, then: + if (init.referrer !== undefined) { + // 1. Let referrer be init["referrer"]. + const referrer = init.referrer + + // 2. If referrer is the empty string, then set request’s referrer to "no-referrer". + if (referrer === '') { + request.referrer = 'no-referrer' + } else { + // 1. Let parsedReferrer be the result of parsing referrer with + // baseURL. + // 2. If parsedReferrer is failure, then throw a TypeError. + let parsedReferrer + try { + parsedReferrer = new URL(referrer, baseUrl) + } catch (err) { + throw new TypeError(`Referrer "${referrer}" is not a valid URL.`, { cause: err }) + } + + // 3. If one of the following is true + // - parsedReferrer’s scheme is "about" and path is the string "client" + // - parsedReferrer’s origin is not same origin with origin + // then set request’s referrer to "client". + if ( + (parsedReferrer.protocol === 'about:' && parsedReferrer.hostname === 'client') || + (origin && !sameOrigin(parsedReferrer, this[kRealm].settingsObject.baseUrl)) + ) { + request.referrer = 'client' + } else { + // 4. Otherwise, set request’s referrer to parsedReferrer. + request.referrer = parsedReferrer + } + } + } + + // 15. If init["referrerPolicy"] exists, then set request’s referrer policy + // to it. + if (init.referrerPolicy !== undefined) { + request.referrerPolicy = init.referrerPolicy + } + + // 16. Let mode be init["mode"] if it exists, and fallbackMode otherwise. + let mode + if (init.mode !== undefined) { + mode = init.mode + } else { + mode = fallbackMode + } + + // 17. If mode is "navigate", then throw a TypeError. + if (mode === 'navigate') { + throw webidl.errors.exception({ + header: 'Request constructor', + message: 'invalid request mode navigate.' + }) + } + + // 18. If mode is non-null, set request’s mode to mode. + if (mode != null) { + request.mode = mode + } + + // 19. If init["credentials"] exists, then set request’s credentials mode + // to it. + if (init.credentials !== undefined) { + request.credentials = init.credentials + } + + // 18. If init["cache"] exists, then set request’s cache mode to it. + if (init.cache !== undefined) { + request.cache = init.cache + } + + // 21. If request’s cache mode is "only-if-cached" and request’s mode is + // not "same-origin", then throw a TypeError. + if (request.cache === 'only-if-cached' && request.mode !== 'same-origin') { + throw new TypeError( + "'only-if-cached' can be set only with 'same-origin' mode" + ) + } + + // 22. If init["redirect"] exists, then set request’s redirect mode to it. + if (init.redirect !== undefined) { + request.redirect = init.redirect + } + + // 23. If init["integrity"] exists, then set request’s integrity metadata to it. + if (init.integrity != null) { + request.integrity = String(init.integrity) + } + + // 24. If init["keepalive"] exists, then set request’s keepalive to it. + if (init.keepalive !== undefined) { + request.keepalive = Boolean(init.keepalive) + } + + // 25. If init["method"] exists, then: + if (init.method !== undefined) { + // 1. Let method be init["method"]. + let method = init.method + + // 2. If method is not a method or method is a forbidden method, then + // throw a TypeError. + if (!isValidHTTPToken(method)) { + throw new TypeError(`'${method}' is not a valid HTTP method.`) + } + + if (forbiddenMethodsSet.has(method.toUpperCase())) { + throw new TypeError(`'${method}' HTTP method is unsupported.`) + } + + // 3. Normalize method. + method = normalizeMethodRecord[method] ?? normalizeMethod(method) + + // 4. Set request’s method to method. + request.method = method + } + + // 26. If init["signal"] exists, then set signal to it. + if (init.signal !== undefined) { + signal = init.signal + } + + // 27. Set this’s request to request. + this[kState] = request + + // 28. Set this’s signal to a new AbortSignal object with this’s relevant + // Realm. + // TODO: could this be simplified with AbortSignal.any + // (https://dom.spec.whatwg.org/#dom-abortsignal-any) + const ac = new AbortController() + this[kSignal] = ac.signal + this[kSignal][kRealm] = this[kRealm] + + // 29. If signal is not null, then make this’s signal follow signal. + if (signal != null) { + if ( + !signal || + typeof signal.aborted !== 'boolean' || + typeof signal.addEventListener !== 'function' + ) { + throw new TypeError( + "Failed to construct 'Request': member signal is not of type AbortSignal." + ) + } + + if (signal.aborted) { + ac.abort(signal.reason) + } else { + // Keep a strong ref to ac while request object + // is alive. This is needed to prevent AbortController + // from being prematurely garbage collected. + // See, https://github.com/nodejs/undici/issues/1926. + this[kAbortController] = ac + + const acRef = new WeakRef(ac) + const abort = function () { + const ac = acRef.deref() + if (ac !== undefined) { + ac.abort(this.reason) + } + } + + // Third-party AbortControllers may not work with these. + // See, https://github.com/nodejs/undici/pull/1910#issuecomment-1464495619. + try { + // If the max amount of listeners is equal to the default, increase it + // This is only available in node >= v19.9.0 + if (typeof getMaxListeners === 'function' && getMaxListeners(signal) === defaultMaxListeners) { + setMaxListeners(100, signal) + } else if (getEventListeners(signal, 'abort').length >= defaultMaxListeners) { + setMaxListeners(100, signal) + } + } catch {} + + util.addAbortListener(signal, abort) + requestFinalizer.register(ac, { signal, abort }) + } + } + + // 30. Set this’s headers to a new Headers object with this’s relevant + // Realm, whose header list is request’s header list and guard is + // "request". + this[kHeaders] = new Headers(kConstruct) + this[kHeaders][kHeadersList] = request.headersList + this[kHeaders][kGuard] = 'request' + this[kHeaders][kRealm] = this[kRealm] + + // 31. If this’s request’s mode is "no-cors", then: + if (mode === 'no-cors') { + // 1. If this’s request’s method is not a CORS-safelisted method, + // then throw a TypeError. + if (!corsSafeListedMethodsSet.has(request.method)) { + throw new TypeError( + `'${request.method} is unsupported in no-cors mode.` + ) + } + + // 2. Set this’s headers’s guard to "request-no-cors". + this[kHeaders][kGuard] = 'request-no-cors' + } + + // 32. If init is not empty, then: + if (initHasKey) { + /** @type {HeadersList} */ + const headersList = this[kHeaders][kHeadersList] + // 1. Let headers be a copy of this’s headers and its associated header + // list. + // 2. If init["headers"] exists, then set headers to init["headers"]. + const headers = init.headers !== undefined ? init.headers : new HeadersList(headersList) + + // 3. Empty this’s headers’s header list. + headersList.clear() + + // 4. If headers is a Headers object, then for each header in its header + // list, append header’s name/header’s value to this’s headers. + if (headers instanceof HeadersList) { + for (const [key, val] of headers) { + headersList.append(key, val) + } + // Note: Copy the `set-cookie` meta-data. + headersList.cookies = headers.cookies + } else { + // 5. Otherwise, fill this’s headers with headers. + fillHeaders(this[kHeaders], headers) + } + } + + // 33. Let inputBody be input’s request’s body if input is a Request + // object; otherwise null. + const inputBody = input instanceof Request ? input[kState].body : null + + // 34. If either init["body"] exists and is non-null or inputBody is + // non-null, and request’s method is `GET` or `HEAD`, then throw a + // TypeError. + if ( + (init.body != null || inputBody != null) && + (request.method === 'GET' || request.method === 'HEAD') + ) { + throw new TypeError('Request with GET/HEAD method cannot have body.') + } + + // 35. Let initBody be null. + let initBody = null + + // 36. If init["body"] exists and is non-null, then: + if (init.body != null) { + // 1. Let Content-Type be null. + // 2. Set initBody and Content-Type to the result of extracting + // init["body"], with keepalive set to request’s keepalive. + const [extractedBody, contentType] = extractBody( + init.body, + request.keepalive + ) + initBody = extractedBody + + // 3, If Content-Type is non-null and this’s headers’s header list does + // not contain `Content-Type`, then append `Content-Type`/Content-Type to + // this’s headers. + if (contentType && !this[kHeaders][kHeadersList].contains('content-type')) { + this[kHeaders].append('content-type', contentType) + } + } + + // 37. Let inputOrInitBody be initBody if it is non-null; otherwise + // inputBody. + const inputOrInitBody = initBody ?? inputBody + + // 38. If inputOrInitBody is non-null and inputOrInitBody’s source is + // null, then: + if (inputOrInitBody != null && inputOrInitBody.source == null) { + // 1. If initBody is non-null and init["duplex"] does not exist, + // then throw a TypeError. + if (initBody != null && init.duplex == null) { + throw new TypeError('RequestInit: duplex option is required when sending a body.') + } + + // 2. If this’s request’s mode is neither "same-origin" nor "cors", + // then throw a TypeError. + if (request.mode !== 'same-origin' && request.mode !== 'cors') { + throw new TypeError( + 'If request is made from ReadableStream, mode should be "same-origin" or "cors"' + ) + } + + // 3. Set this’s request’s use-CORS-preflight flag. + request.useCORSPreflightFlag = true + } + + // 39. Let finalBody be inputOrInitBody. + let finalBody = inputOrInitBody + + // 40. If initBody is null and inputBody is non-null, then: + if (initBody == null && inputBody != null) { + // 1. If input is unusable, then throw a TypeError. + if (util.isDisturbed(inputBody.stream) || inputBody.stream.locked) { + throw new TypeError( + 'Cannot construct a Request with a Request object that has already been used.' + ) + } + + // 2. Set finalBody to the result of creating a proxy for inputBody. + if (!TransformStream) { + TransformStream = (__nccwpck_require__(63774).TransformStream) + } + + // https://streams.spec.whatwg.org/#readablestream-create-a-proxy + const identityTransform = new TransformStream() + inputBody.stream.pipeThrough(identityTransform) + finalBody = { + source: inputBody.source, + length: inputBody.length, + stream: identityTransform.readable + } + } + + // 41. Set this’s request’s body to finalBody. + this[kState].body = finalBody + } + + // Returns request’s HTTP method, which is "GET" by default. + get method () { + webidl.brandCheck(this, Request) + + // The method getter steps are to return this’s request’s method. + return this[kState].method + } + + // Returns the URL of request as a string. + get url () { + webidl.brandCheck(this, Request) + + // The url getter steps are to return this’s request’s URL, serialized. + return URLSerializer(this[kState].url) + } + + // Returns a Headers object consisting of the headers associated with request. + // Note that headers added in the network layer by the user agent will not + // be accounted for in this object, e.g., the "Host" header. + get headers () { + webidl.brandCheck(this, Request) + + // The headers getter steps are to return this’s headers. + return this[kHeaders] + } + + // Returns the kind of resource requested by request, e.g., "document" + // or "script". + get destination () { + webidl.brandCheck(this, Request) + + // The destination getter are to return this’s request’s destination. + return this[kState].destination + } + + // Returns the referrer of request. Its value can be a same-origin URL if + // explicitly set in init, the empty string to indicate no referrer, and + // "about:client" when defaulting to the global’s default. This is used + // during fetching to determine the value of the `Referer` header of the + // request being made. + get referrer () { + webidl.brandCheck(this, Request) + + // 1. If this’s request’s referrer is "no-referrer", then return the + // empty string. + if (this[kState].referrer === 'no-referrer') { + return '' + } + + // 2. If this’s request’s referrer is "client", then return + // "about:client". + if (this[kState].referrer === 'client') { + return 'about:client' + } + + // Return this’s request’s referrer, serialized. + return this[kState].referrer.toString() + } + + // Returns the referrer policy associated with request. + // This is used during fetching to compute the value of the request’s + // referrer. + get referrerPolicy () { + webidl.brandCheck(this, Request) + + // The referrerPolicy getter steps are to return this’s request’s referrer policy. + return this[kState].referrerPolicy + } + + // Returns the mode associated with request, which is a string indicating + // whether the request will use CORS, or will be restricted to same-origin + // URLs. + get mode () { + webidl.brandCheck(this, Request) + + // The mode getter steps are to return this’s request’s mode. + return this[kState].mode + } + + // Returns the credentials mode associated with request, + // which is a string indicating whether credentials will be sent with the + // request always, never, or only when sent to a same-origin URL. + get credentials () { + // The credentials getter steps are to return this’s request’s credentials mode. + return this[kState].credentials + } + + // Returns the cache mode associated with request, + // which is a string indicating how the request will + // interact with the browser’s cache when fetching. + get cache () { + webidl.brandCheck(this, Request) + + // The cache getter steps are to return this’s request’s cache mode. + return this[kState].cache + } + + // Returns the redirect mode associated with request, + // which is a string indicating how redirects for the + // request will be handled during fetching. A request + // will follow redirects by default. + get redirect () { + webidl.brandCheck(this, Request) + + // The redirect getter steps are to return this’s request’s redirect mode. + return this[kState].redirect + } + + // Returns request’s subresource integrity metadata, which is a + // cryptographic hash of the resource being fetched. Its value + // consists of multiple hashes separated by whitespace. [SRI] + get integrity () { + webidl.brandCheck(this, Request) + + // The integrity getter steps are to return this’s request’s integrity + // metadata. + return this[kState].integrity + } + + // Returns a boolean indicating whether or not request can outlive the + // global in which it was created. + get keepalive () { + webidl.brandCheck(this, Request) + + // The keepalive getter steps are to return this’s request’s keepalive. + return this[kState].keepalive + } + + // Returns a boolean indicating whether or not request is for a reload + // navigation. + get isReloadNavigation () { + webidl.brandCheck(this, Request) + + // The isReloadNavigation getter steps are to return true if this’s + // request’s reload-navigation flag is set; otherwise false. + return this[kState].reloadNavigation + } + + // Returns a boolean indicating whether or not request is for a history + // navigation (a.k.a. back-foward navigation). + get isHistoryNavigation () { + webidl.brandCheck(this, Request) + + // The isHistoryNavigation getter steps are to return true if this’s request’s + // history-navigation flag is set; otherwise false. + return this[kState].historyNavigation + } + + // Returns the signal associated with request, which is an AbortSignal + // object indicating whether or not request has been aborted, and its + // abort event handler. + get signal () { + webidl.brandCheck(this, Request) + + // The signal getter steps are to return this’s signal. + return this[kSignal] + } + + get body () { + webidl.brandCheck(this, Request) + + return this[kState].body ? this[kState].body.stream : null + } + + get bodyUsed () { + webidl.brandCheck(this, Request) + + return !!this[kState].body && util.isDisturbed(this[kState].body.stream) + } + + get duplex () { + webidl.brandCheck(this, Request) + + return 'half' + } + + // Returns a clone of request. + clone () { + webidl.brandCheck(this, Request) + + // 1. If this is unusable, then throw a TypeError. + if (this.bodyUsed || this.body?.locked) { + throw new TypeError('unusable') + } + + // 2. Let clonedRequest be the result of cloning this’s request. + const clonedRequest = cloneRequest(this[kState]) + + // 3. Let clonedRequestObject be the result of creating a Request object, + // given clonedRequest, this’s headers’s guard, and this’s relevant Realm. + const clonedRequestObject = new Request(kConstruct) + clonedRequestObject[kState] = clonedRequest + clonedRequestObject[kRealm] = this[kRealm] + clonedRequestObject[kHeaders] = new Headers(kConstruct) + clonedRequestObject[kHeaders][kHeadersList] = clonedRequest.headersList + clonedRequestObject[kHeaders][kGuard] = this[kHeaders][kGuard] + clonedRequestObject[kHeaders][kRealm] = this[kHeaders][kRealm] + + // 4. Make clonedRequestObject’s signal follow this’s signal. + const ac = new AbortController() + if (this.signal.aborted) { + ac.abort(this.signal.reason) + } else { + util.addAbortListener( + this.signal, + () => { + ac.abort(this.signal.reason) + } + ) + } + clonedRequestObject[kSignal] = ac.signal + + // 4. Return clonedRequestObject. + return clonedRequestObject + } +} + +mixinBody(Request) + +function makeRequest (init) { + // https://fetch.spec.whatwg.org/#requests + const request = { + method: 'GET', + localURLsOnly: false, + unsafeRequest: false, + body: null, + client: null, + reservedClient: null, + replacesClientId: '', + window: 'client', + keepalive: false, + serviceWorkers: 'all', + initiator: '', + destination: '', + priority: null, + origin: 'client', + policyContainer: 'client', + referrer: 'client', + referrerPolicy: '', + mode: 'no-cors', + useCORSPreflightFlag: false, + credentials: 'same-origin', + useCredentials: false, + cache: 'default', + redirect: 'follow', + integrity: '', + cryptoGraphicsNonceMetadata: '', + parserMetadata: '', + reloadNavigation: false, + historyNavigation: false, + userActivation: false, + taintedOrigin: false, + redirectCount: 0, + responseTainting: 'basic', + preventNoCacheCacheControlHeaderModification: false, + done: false, + timingAllowFailed: false, + ...init, + headersList: init.headersList + ? new HeadersList(init.headersList) + : new HeadersList() + } + request.url = request.urlList[0] + return request +} + +// https://fetch.spec.whatwg.org/#concept-request-clone +function cloneRequest (request) { + // To clone a request request, run these steps: + + // 1. Let newRequest be a copy of request, except for its body. + const newRequest = makeRequest({ ...request, body: null }) + + // 2. If request’s body is non-null, set newRequest’s body to the + // result of cloning request’s body. + if (request.body != null) { + newRequest.body = cloneBody(request.body) + } + + // 3. Return newRequest. + return newRequest +} + +Object.defineProperties(Request.prototype, { + method: kEnumerableProperty, + url: kEnumerableProperty, + headers: kEnumerableProperty, + redirect: kEnumerableProperty, + clone: kEnumerableProperty, + signal: kEnumerableProperty, + duplex: kEnumerableProperty, + destination: kEnumerableProperty, + body: kEnumerableProperty, + bodyUsed: kEnumerableProperty, + isHistoryNavigation: kEnumerableProperty, + isReloadNavigation: kEnumerableProperty, + keepalive: kEnumerableProperty, + integrity: kEnumerableProperty, + cache: kEnumerableProperty, + credentials: kEnumerableProperty, + attribute: kEnumerableProperty, + referrerPolicy: kEnumerableProperty, + referrer: kEnumerableProperty, + mode: kEnumerableProperty, + [Symbol.toStringTag]: { + value: 'Request', + configurable: true + } +}) + +webidl.converters.Request = webidl.interfaceConverter( + Request +) + +// https://fetch.spec.whatwg.org/#requestinfo +webidl.converters.RequestInfo = function (V) { + if (typeof V === 'string') { + return webidl.converters.USVString(V) + } + + if (V instanceof Request) { + return webidl.converters.Request(V) + } + + return webidl.converters.USVString(V) +} + +webidl.converters.AbortSignal = webidl.interfaceConverter( + AbortSignal +) + +// https://fetch.spec.whatwg.org/#requestinit +webidl.converters.RequestInit = webidl.dictionaryConverter([ + { + key: 'method', + converter: webidl.converters.ByteString + }, + { + key: 'headers', + converter: webidl.converters.HeadersInit + }, + { + key: 'body', + converter: webidl.nullableConverter( + webidl.converters.BodyInit + ) + }, + { + key: 'referrer', + converter: webidl.converters.USVString + }, + { + key: 'referrerPolicy', + converter: webidl.converters.DOMString, + // https://w3c.github.io/webappsec-referrer-policy/#referrer-policy + allowedValues: referrerPolicy + }, + { + key: 'mode', + converter: webidl.converters.DOMString, + // https://fetch.spec.whatwg.org/#concept-request-mode + allowedValues: requestMode + }, + { + key: 'credentials', + converter: webidl.converters.DOMString, + // https://fetch.spec.whatwg.org/#requestcredentials + allowedValues: requestCredentials + }, + { + key: 'cache', + converter: webidl.converters.DOMString, + // https://fetch.spec.whatwg.org/#requestcache + allowedValues: requestCache + }, + { + key: 'redirect', + converter: webidl.converters.DOMString, + // https://fetch.spec.whatwg.org/#requestredirect + allowedValues: requestRedirect + }, + { + key: 'integrity', + converter: webidl.converters.DOMString + }, + { + key: 'keepalive', + converter: webidl.converters.boolean + }, + { + key: 'signal', + converter: webidl.nullableConverter( + (signal) => webidl.converters.AbortSignal( + signal, + { strict: false } + ) + ) + }, + { + key: 'window', + converter: webidl.converters.any + }, + { + key: 'duplex', + converter: webidl.converters.DOMString, + allowedValues: requestDuplex + } +]) + +module.exports = { Request, makeRequest } + + +/***/ }), + +/***/ 48676: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const { Headers, HeadersList, fill } = __nccwpck_require__(26349) +const { extractBody, cloneBody, mixinBody } = __nccwpck_require__(8923) +const util = __nccwpck_require__(3440) +const { kEnumerableProperty } = util +const { + isValidReasonPhrase, + isCancelled, + isAborted, + isBlobLike, + serializeJavascriptValueToJSONString, + isErrorLike, + isomorphicEncode +} = __nccwpck_require__(15523) +const { + redirectStatusSet, + nullBodyStatus, + DOMException +} = __nccwpck_require__(87326) +const { kState, kHeaders, kGuard, kRealm } = __nccwpck_require__(89710) +const { webidl } = __nccwpck_require__(74222) +const { FormData } = __nccwpck_require__(43073) +const { getGlobalOrigin } = __nccwpck_require__(75628) +const { URLSerializer } = __nccwpck_require__(94322) +const { kHeadersList, kConstruct } = __nccwpck_require__(36443) +const assert = __nccwpck_require__(42613) +const { types } = __nccwpck_require__(39023) + +const ReadableStream = globalThis.ReadableStream || (__nccwpck_require__(63774).ReadableStream) +const textEncoder = new TextEncoder('utf-8') + +// https://fetch.spec.whatwg.org/#response-class +class Response { + // Creates network error Response. + static error () { + // TODO + const relevantRealm = { settingsObject: {} } + + // The static error() method steps are to return the result of creating a + // Response object, given a new network error, "immutable", and this’s + // relevant Realm. + const responseObject = new Response() + responseObject[kState] = makeNetworkError() + responseObject[kRealm] = relevantRealm + responseObject[kHeaders][kHeadersList] = responseObject[kState].headersList + responseObject[kHeaders][kGuard] = 'immutable' + responseObject[kHeaders][kRealm] = relevantRealm + return responseObject + } + + // https://fetch.spec.whatwg.org/#dom-response-json + static json (data, init = {}) { + webidl.argumentLengthCheck(arguments, 1, { header: 'Response.json' }) + + if (init !== null) { + init = webidl.converters.ResponseInit(init) + } + + // 1. Let bytes the result of running serialize a JavaScript value to JSON bytes on data. + const bytes = textEncoder.encode( + serializeJavascriptValueToJSONString(data) + ) + + // 2. Let body be the result of extracting bytes. + const body = extractBody(bytes) + + // 3. Let responseObject be the result of creating a Response object, given a new response, + // "response", and this’s relevant Realm. + const relevantRealm = { settingsObject: {} } + const responseObject = new Response() + responseObject[kRealm] = relevantRealm + responseObject[kHeaders][kGuard] = 'response' + responseObject[kHeaders][kRealm] = relevantRealm + + // 4. Perform initialize a response given responseObject, init, and (body, "application/json"). + initializeResponse(responseObject, init, { body: body[0], type: 'application/json' }) + + // 5. Return responseObject. + return responseObject + } + + // Creates a redirect Response that redirects to url with status status. + static redirect (url, status = 302) { + const relevantRealm = { settingsObject: {} } + + webidl.argumentLengthCheck(arguments, 1, { header: 'Response.redirect' }) + + url = webidl.converters.USVString(url) + status = webidl.converters['unsigned short'](status) + + // 1. Let parsedURL be the result of parsing url with current settings + // object’s API base URL. + // 2. If parsedURL is failure, then throw a TypeError. + // TODO: base-URL? + let parsedURL + try { + parsedURL = new URL(url, getGlobalOrigin()) + } catch (err) { + throw Object.assign(new TypeError('Failed to parse URL from ' + url), { + cause: err + }) + } + + // 3. If status is not a redirect status, then throw a RangeError. + if (!redirectStatusSet.has(status)) { + throw new RangeError('Invalid status code ' + status) + } + + // 4. Let responseObject be the result of creating a Response object, + // given a new response, "immutable", and this’s relevant Realm. + const responseObject = new Response() + responseObject[kRealm] = relevantRealm + responseObject[kHeaders][kGuard] = 'immutable' + responseObject[kHeaders][kRealm] = relevantRealm + + // 5. Set responseObject’s response’s status to status. + responseObject[kState].status = status + + // 6. Let value be parsedURL, serialized and isomorphic encoded. + const value = isomorphicEncode(URLSerializer(parsedURL)) + + // 7. Append `Location`/value to responseObject’s response’s header list. + responseObject[kState].headersList.append('location', value) + + // 8. Return responseObject. + return responseObject + } + + // https://fetch.spec.whatwg.org/#dom-response + constructor (body = null, init = {}) { + if (body !== null) { + body = webidl.converters.BodyInit(body) + } + + init = webidl.converters.ResponseInit(init) + + // TODO + this[kRealm] = { settingsObject: {} } + + // 1. Set this’s response to a new response. + this[kState] = makeResponse({}) + + // 2. Set this’s headers to a new Headers object with this’s relevant + // Realm, whose header list is this’s response’s header list and guard + // is "response". + this[kHeaders] = new Headers(kConstruct) + this[kHeaders][kGuard] = 'response' + this[kHeaders][kHeadersList] = this[kState].headersList + this[kHeaders][kRealm] = this[kRealm] + + // 3. Let bodyWithType be null. + let bodyWithType = null + + // 4. If body is non-null, then set bodyWithType to the result of extracting body. + if (body != null) { + const [extractedBody, type] = extractBody(body) + bodyWithType = { body: extractedBody, type } + } + + // 5. Perform initialize a response given this, init, and bodyWithType. + initializeResponse(this, init, bodyWithType) + } + + // Returns response’s type, e.g., "cors". + get type () { + webidl.brandCheck(this, Response) + + // The type getter steps are to return this’s response’s type. + return this[kState].type + } + + // Returns response’s URL, if it has one; otherwise the empty string. + get url () { + webidl.brandCheck(this, Response) + + const urlList = this[kState].urlList + + // The url getter steps are to return the empty string if this’s + // response’s URL is null; otherwise this’s response’s URL, + // serialized with exclude fragment set to true. + const url = urlList[urlList.length - 1] ?? null + + if (url === null) { + return '' + } + + return URLSerializer(url, true) + } + + // Returns whether response was obtained through a redirect. + get redirected () { + webidl.brandCheck(this, Response) + + // The redirected getter steps are to return true if this’s response’s URL + // list has more than one item; otherwise false. + return this[kState].urlList.length > 1 + } + + // Returns response’s status. + get status () { + webidl.brandCheck(this, Response) + + // The status getter steps are to return this’s response’s status. + return this[kState].status + } + + // Returns whether response’s status is an ok status. + get ok () { + webidl.brandCheck(this, Response) + + // The ok getter steps are to return true if this’s response’s status is an + // ok status; otherwise false. + return this[kState].status >= 200 && this[kState].status <= 299 + } + + // Returns response’s status message. + get statusText () { + webidl.brandCheck(this, Response) + + // The statusText getter steps are to return this’s response’s status + // message. + return this[kState].statusText + } + + // Returns response’s headers as Headers. + get headers () { + webidl.brandCheck(this, Response) + + // The headers getter steps are to return this’s headers. + return this[kHeaders] + } + + get body () { + webidl.brandCheck(this, Response) + + return this[kState].body ? this[kState].body.stream : null + } + + get bodyUsed () { + webidl.brandCheck(this, Response) + + return !!this[kState].body && util.isDisturbed(this[kState].body.stream) + } + + // Returns a clone of response. + clone () { + webidl.brandCheck(this, Response) + + // 1. If this is unusable, then throw a TypeError. + if (this.bodyUsed || (this.body && this.body.locked)) { + throw webidl.errors.exception({ + header: 'Response.clone', + message: 'Body has already been consumed.' + }) + } + + // 2. Let clonedResponse be the result of cloning this’s response. + const clonedResponse = cloneResponse(this[kState]) + + // 3. Return the result of creating a Response object, given + // clonedResponse, this’s headers’s guard, and this’s relevant Realm. + const clonedResponseObject = new Response() + clonedResponseObject[kState] = clonedResponse + clonedResponseObject[kRealm] = this[kRealm] + clonedResponseObject[kHeaders][kHeadersList] = clonedResponse.headersList + clonedResponseObject[kHeaders][kGuard] = this[kHeaders][kGuard] + clonedResponseObject[kHeaders][kRealm] = this[kHeaders][kRealm] + + return clonedResponseObject + } +} + +mixinBody(Response) + +Object.defineProperties(Response.prototype, { + type: kEnumerableProperty, + url: kEnumerableProperty, + status: kEnumerableProperty, + ok: kEnumerableProperty, + redirected: kEnumerableProperty, + statusText: kEnumerableProperty, + headers: kEnumerableProperty, + clone: kEnumerableProperty, + body: kEnumerableProperty, + bodyUsed: kEnumerableProperty, + [Symbol.toStringTag]: { + value: 'Response', + configurable: true + } +}) + +Object.defineProperties(Response, { + json: kEnumerableProperty, + redirect: kEnumerableProperty, + error: kEnumerableProperty +}) + +// https://fetch.spec.whatwg.org/#concept-response-clone +function cloneResponse (response) { + // To clone a response response, run these steps: + + // 1. If response is a filtered response, then return a new identical + // filtered response whose internal response is a clone of response’s + // internal response. + if (response.internalResponse) { + return filterResponse( + cloneResponse(response.internalResponse), + response.type + ) + } + + // 2. Let newResponse be a copy of response, except for its body. + const newResponse = makeResponse({ ...response, body: null }) + + // 3. If response’s body is non-null, then set newResponse’s body to the + // result of cloning response’s body. + if (response.body != null) { + newResponse.body = cloneBody(response.body) + } + + // 4. Return newResponse. + return newResponse +} + +function makeResponse (init) { + return { + aborted: false, + rangeRequested: false, + timingAllowPassed: false, + requestIncludesCredentials: false, + type: 'default', + status: 200, + timingInfo: null, + cacheState: '', + statusText: '', + ...init, + headersList: init.headersList + ? new HeadersList(init.headersList) + : new HeadersList(), + urlList: init.urlList ? [...init.urlList] : [] + } +} + +function makeNetworkError (reason) { + const isError = isErrorLike(reason) + return makeResponse({ + type: 'error', + status: 0, + error: isError + ? reason + : new Error(reason ? String(reason) : reason), + aborted: reason && reason.name === 'AbortError' + }) +} + +function makeFilteredResponse (response, state) { + state = { + internalResponse: response, + ...state + } + + return new Proxy(response, { + get (target, p) { + return p in state ? state[p] : target[p] + }, + set (target, p, value) { + assert(!(p in state)) + target[p] = value + return true + } + }) +} + +// https://fetch.spec.whatwg.org/#concept-filtered-response +function filterResponse (response, type) { + // Set response to the following filtered response with response as its + // internal response, depending on request’s response tainting: + if (type === 'basic') { + // A basic filtered response is a filtered response whose type is "basic" + // and header list excludes any headers in internal response’s header list + // whose name is a forbidden response-header name. + + // Note: undici does not implement forbidden response-header names + return makeFilteredResponse(response, { + type: 'basic', + headersList: response.headersList + }) + } else if (type === 'cors') { + // A CORS filtered response is a filtered response whose type is "cors" + // and header list excludes any headers in internal response’s header + // list whose name is not a CORS-safelisted response-header name, given + // internal response’s CORS-exposed header-name list. + + // Note: undici does not implement CORS-safelisted response-header names + return makeFilteredResponse(response, { + type: 'cors', + headersList: response.headersList + }) + } else if (type === 'opaque') { + // An opaque filtered response is a filtered response whose type is + // "opaque", URL list is the empty list, status is 0, status message + // is the empty byte sequence, header list is empty, and body is null. + + return makeFilteredResponse(response, { + type: 'opaque', + urlList: Object.freeze([]), + status: 0, + statusText: '', + body: null + }) + } else if (type === 'opaqueredirect') { + // An opaque-redirect filtered response is a filtered response whose type + // is "opaqueredirect", status is 0, status message is the empty byte + // sequence, header list is empty, and body is null. + + return makeFilteredResponse(response, { + type: 'opaqueredirect', + status: 0, + statusText: '', + headersList: [], + body: null + }) + } else { + assert(false) + } +} + +// https://fetch.spec.whatwg.org/#appropriate-network-error +function makeAppropriateNetworkError (fetchParams, err = null) { + // 1. Assert: fetchParams is canceled. + assert(isCancelled(fetchParams)) + + // 2. Return an aborted network error if fetchParams is aborted; + // otherwise return a network error. + return isAborted(fetchParams) + ? makeNetworkError(Object.assign(new DOMException('The operation was aborted.', 'AbortError'), { cause: err })) + : makeNetworkError(Object.assign(new DOMException('Request was cancelled.'), { cause: err })) +} + +// https://whatpr.org/fetch/1392.html#initialize-a-response +function initializeResponse (response, init, body) { + // 1. If init["status"] is not in the range 200 to 599, inclusive, then + // throw a RangeError. + if (init.status !== null && (init.status < 200 || init.status > 599)) { + throw new RangeError('init["status"] must be in the range of 200 to 599, inclusive.') + } + + // 2. If init["statusText"] does not match the reason-phrase token production, + // then throw a TypeError. + if ('statusText' in init && init.statusText != null) { + // See, https://datatracker.ietf.org/doc/html/rfc7230#section-3.1.2: + // reason-phrase = *( HTAB / SP / VCHAR / obs-text ) + if (!isValidReasonPhrase(String(init.statusText))) { + throw new TypeError('Invalid statusText') + } + } + + // 3. Set response’s response’s status to init["status"]. + if ('status' in init && init.status != null) { + response[kState].status = init.status + } + + // 4. Set response’s response’s status message to init["statusText"]. + if ('statusText' in init && init.statusText != null) { + response[kState].statusText = init.statusText + } + + // 5. If init["headers"] exists, then fill response’s headers with init["headers"]. + if ('headers' in init && init.headers != null) { + fill(response[kHeaders], init.headers) + } + + // 6. If body was given, then: + if (body) { + // 1. If response's status is a null body status, then throw a TypeError. + if (nullBodyStatus.includes(response.status)) { + throw webidl.errors.exception({ + header: 'Response constructor', + message: 'Invalid response status code ' + response.status + }) + } + + // 2. Set response's body to body's body. + response[kState].body = body.body + + // 3. If body's type is non-null and response's header list does not contain + // `Content-Type`, then append (`Content-Type`, body's type) to response's header list. + if (body.type != null && !response[kState].headersList.contains('Content-Type')) { + response[kState].headersList.append('content-type', body.type) + } + } +} + +webidl.converters.ReadableStream = webidl.interfaceConverter( + ReadableStream +) + +webidl.converters.FormData = webidl.interfaceConverter( + FormData +) + +webidl.converters.URLSearchParams = webidl.interfaceConverter( + URLSearchParams +) + +// https://fetch.spec.whatwg.org/#typedefdef-xmlhttprequestbodyinit +webidl.converters.XMLHttpRequestBodyInit = function (V) { + if (typeof V === 'string') { + return webidl.converters.USVString(V) + } + + if (isBlobLike(V)) { + return webidl.converters.Blob(V, { strict: false }) + } + + if (types.isArrayBuffer(V) || types.isTypedArray(V) || types.isDataView(V)) { + return webidl.converters.BufferSource(V) + } + + if (util.isFormDataLike(V)) { + return webidl.converters.FormData(V, { strict: false }) + } + + if (V instanceof URLSearchParams) { + return webidl.converters.URLSearchParams(V) + } + + return webidl.converters.DOMString(V) +} + +// https://fetch.spec.whatwg.org/#bodyinit +webidl.converters.BodyInit = function (V) { + if (V instanceof ReadableStream) { + return webidl.converters.ReadableStream(V) + } + + // Note: the spec doesn't include async iterables, + // this is an undici extension. + if (V?.[Symbol.asyncIterator]) { + return V + } + + return webidl.converters.XMLHttpRequestBodyInit(V) +} + +webidl.converters.ResponseInit = webidl.dictionaryConverter([ + { + key: 'status', + converter: webidl.converters['unsigned short'], + defaultValue: 200 + }, + { + key: 'statusText', + converter: webidl.converters.ByteString, + defaultValue: '' + }, + { + key: 'headers', + converter: webidl.converters.HeadersInit + } +]) + +module.exports = { + makeNetworkError, + makeResponse, + makeAppropriateNetworkError, + filterResponse, + Response, + cloneResponse +} + + +/***/ }), + +/***/ 89710: +/***/ ((module) => { + +"use strict"; + + +module.exports = { + kUrl: Symbol('url'), + kHeaders: Symbol('headers'), + kSignal: Symbol('signal'), + kState: Symbol('state'), + kGuard: Symbol('guard'), + kRealm: Symbol('realm') +} + + +/***/ }), + +/***/ 15523: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const { redirectStatusSet, referrerPolicySet: referrerPolicyTokens, badPortsSet } = __nccwpck_require__(87326) +const { getGlobalOrigin } = __nccwpck_require__(75628) +const { performance } = __nccwpck_require__(82987) +const { isBlobLike, toUSVString, ReadableStreamFrom } = __nccwpck_require__(3440) +const assert = __nccwpck_require__(42613) +const { isUint8Array } = __nccwpck_require__(98253) + +let supportedHashes = [] + +// https://nodejs.org/api/crypto.html#determining-if-crypto-support-is-unavailable +/** @type {import('crypto')|undefined} */ +let crypto + +try { + crypto = __nccwpck_require__(76982) + const possibleRelevantHashes = ['sha256', 'sha384', 'sha512'] + supportedHashes = crypto.getHashes().filter((hash) => possibleRelevantHashes.includes(hash)) +/* c8 ignore next 3 */ +} catch { +} + +function responseURL (response) { + // https://fetch.spec.whatwg.org/#responses + // A response has an associated URL. It is a pointer to the last URL + // in response’s URL list and null if response’s URL list is empty. + const urlList = response.urlList + const length = urlList.length + return length === 0 ? null : urlList[length - 1].toString() +} + +// https://fetch.spec.whatwg.org/#concept-response-location-url +function responseLocationURL (response, requestFragment) { + // 1. If response’s status is not a redirect status, then return null. + if (!redirectStatusSet.has(response.status)) { + return null + } + + // 2. Let location be the result of extracting header list values given + // `Location` and response’s header list. + let location = response.headersList.get('location') + + // 3. If location is a header value, then set location to the result of + // parsing location with response’s URL. + if (location !== null && isValidHeaderValue(location)) { + location = new URL(location, responseURL(response)) + } + + // 4. If location is a URL whose fragment is null, then set location’s + // fragment to requestFragment. + if (location && !location.hash) { + location.hash = requestFragment + } + + // 5. Return location. + return location +} + +/** @returns {URL} */ +function requestCurrentURL (request) { + return request.urlList[request.urlList.length - 1] +} + +function requestBadPort (request) { + // 1. Let url be request’s current URL. + const url = requestCurrentURL(request) + + // 2. If url’s scheme is an HTTP(S) scheme and url’s port is a bad port, + // then return blocked. + if (urlIsHttpHttpsScheme(url) && badPortsSet.has(url.port)) { + return 'blocked' + } + + // 3. Return allowed. + return 'allowed' +} + +function isErrorLike (object) { + return object instanceof Error || ( + object?.constructor?.name === 'Error' || + object?.constructor?.name === 'DOMException' + ) +} + +// Check whether |statusText| is a ByteString and +// matches the Reason-Phrase token production. +// RFC 2616: https://tools.ietf.org/html/rfc2616 +// RFC 7230: https://tools.ietf.org/html/rfc7230 +// "reason-phrase = *( HTAB / SP / VCHAR / obs-text )" +// https://github.com/chromium/chromium/blob/94.0.4604.1/third_party/blink/renderer/core/fetch/response.cc#L116 +function isValidReasonPhrase (statusText) { + for (let i = 0; i < statusText.length; ++i) { + const c = statusText.charCodeAt(i) + if ( + !( + ( + c === 0x09 || // HTAB + (c >= 0x20 && c <= 0x7e) || // SP / VCHAR + (c >= 0x80 && c <= 0xff) + ) // obs-text + ) + ) { + return false + } + } + return true +} + +/** + * @see https://tools.ietf.org/html/rfc7230#section-3.2.6 + * @param {number} c + */ +function isTokenCharCode (c) { + switch (c) { + case 0x22: + case 0x28: + case 0x29: + case 0x2c: + case 0x2f: + case 0x3a: + case 0x3b: + case 0x3c: + case 0x3d: + case 0x3e: + case 0x3f: + case 0x40: + case 0x5b: + case 0x5c: + case 0x5d: + case 0x7b: + case 0x7d: + // DQUOTE and "(),/:;<=>?@[\]{}" + return false + default: + // VCHAR %x21-7E + return c >= 0x21 && c <= 0x7e + } +} + +/** + * @param {string} characters + */ +function isValidHTTPToken (characters) { + if (characters.length === 0) { + return false + } + for (let i = 0; i < characters.length; ++i) { + if (!isTokenCharCode(characters.charCodeAt(i))) { + return false + } + } + return true +} + +/** + * @see https://fetch.spec.whatwg.org/#header-name + * @param {string} potentialValue + */ +function isValidHeaderName (potentialValue) { + return isValidHTTPToken(potentialValue) +} + +/** + * @see https://fetch.spec.whatwg.org/#header-value + * @param {string} potentialValue + */ +function isValidHeaderValue (potentialValue) { + // - Has no leading or trailing HTTP tab or space bytes. + // - Contains no 0x00 (NUL) or HTTP newline bytes. + if ( + potentialValue.startsWith('\t') || + potentialValue.startsWith(' ') || + potentialValue.endsWith('\t') || + potentialValue.endsWith(' ') + ) { + return false + } + + if ( + potentialValue.includes('\0') || + potentialValue.includes('\r') || + potentialValue.includes('\n') + ) { + return false + } + + return true +} + +// https://w3c.github.io/webappsec-referrer-policy/#set-requests-referrer-policy-on-redirect +function setRequestReferrerPolicyOnRedirect (request, actualResponse) { + // Given a request request and a response actualResponse, this algorithm + // updates request’s referrer policy according to the Referrer-Policy + // header (if any) in actualResponse. + + // 1. Let policy be the result of executing § 8.1 Parse a referrer policy + // from a Referrer-Policy header on actualResponse. + + // 8.1 Parse a referrer policy from a Referrer-Policy header + // 1. Let policy-tokens be the result of extracting header list values given `Referrer-Policy` and response’s header list. + const { headersList } = actualResponse + // 2. Let policy be the empty string. + // 3. For each token in policy-tokens, if token is a referrer policy and token is not the empty string, then set policy to token. + // 4. Return policy. + const policyHeader = (headersList.get('referrer-policy') ?? '').split(',') + + // Note: As the referrer-policy can contain multiple policies + // separated by comma, we need to loop through all of them + // and pick the first valid one. + // Ref: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy#specify_a_fallback_policy + let policy = '' + if (policyHeader.length > 0) { + // The right-most policy takes precedence. + // The left-most policy is the fallback. + for (let i = policyHeader.length; i !== 0; i--) { + const token = policyHeader[i - 1].trim() + if (referrerPolicyTokens.has(token)) { + policy = token + break + } + } + } + + // 2. If policy is not the empty string, then set request’s referrer policy to policy. + if (policy !== '') { + request.referrerPolicy = policy + } +} + +// https://fetch.spec.whatwg.org/#cross-origin-resource-policy-check +function crossOriginResourcePolicyCheck () { + // TODO + return 'allowed' +} + +// https://fetch.spec.whatwg.org/#concept-cors-check +function corsCheck () { + // TODO + return 'success' +} + +// https://fetch.spec.whatwg.org/#concept-tao-check +function TAOCheck () { + // TODO + return 'success' +} + +function appendFetchMetadata (httpRequest) { + // https://w3c.github.io/webappsec-fetch-metadata/#sec-fetch-dest-header + // TODO + + // https://w3c.github.io/webappsec-fetch-metadata/#sec-fetch-mode-header + + // 1. Assert: r’s url is a potentially trustworthy URL. + // TODO + + // 2. Let header be a Structured Header whose value is a token. + let header = null + + // 3. Set header’s value to r’s mode. + header = httpRequest.mode + + // 4. Set a structured field value `Sec-Fetch-Mode`/header in r’s header list. + httpRequest.headersList.set('sec-fetch-mode', header) + + // https://w3c.github.io/webappsec-fetch-metadata/#sec-fetch-site-header + // TODO + + // https://w3c.github.io/webappsec-fetch-metadata/#sec-fetch-user-header + // TODO +} + +// https://fetch.spec.whatwg.org/#append-a-request-origin-header +function appendRequestOriginHeader (request) { + // 1. Let serializedOrigin be the result of byte-serializing a request origin with request. + let serializedOrigin = request.origin + + // 2. If request’s response tainting is "cors" or request’s mode is "websocket", then append (`Origin`, serializedOrigin) to request’s header list. + if (request.responseTainting === 'cors' || request.mode === 'websocket') { + if (serializedOrigin) { + request.headersList.append('origin', serializedOrigin) + } + + // 3. Otherwise, if request’s method is neither `GET` nor `HEAD`, then: + } else if (request.method !== 'GET' && request.method !== 'HEAD') { + // 1. Switch on request’s referrer policy: + switch (request.referrerPolicy) { + case 'no-referrer': + // Set serializedOrigin to `null`. + serializedOrigin = null + break + case 'no-referrer-when-downgrade': + case 'strict-origin': + case 'strict-origin-when-cross-origin': + // If request’s origin is a tuple origin, its scheme is "https", and request’s current URL’s scheme is not "https", then set serializedOrigin to `null`. + if (request.origin && urlHasHttpsScheme(request.origin) && !urlHasHttpsScheme(requestCurrentURL(request))) { + serializedOrigin = null + } + break + case 'same-origin': + // If request’s origin is not same origin with request’s current URL’s origin, then set serializedOrigin to `null`. + if (!sameOrigin(request, requestCurrentURL(request))) { + serializedOrigin = null + } + break + default: + // Do nothing. + } + + if (serializedOrigin) { + // 2. Append (`Origin`, serializedOrigin) to request’s header list. + request.headersList.append('origin', serializedOrigin) + } + } +} + +function coarsenedSharedCurrentTime (crossOriginIsolatedCapability) { + // TODO + return performance.now() +} + +// https://fetch.spec.whatwg.org/#create-an-opaque-timing-info +function createOpaqueTimingInfo (timingInfo) { + return { + startTime: timingInfo.startTime ?? 0, + redirectStartTime: 0, + redirectEndTime: 0, + postRedirectStartTime: timingInfo.startTime ?? 0, + finalServiceWorkerStartTime: 0, + finalNetworkResponseStartTime: 0, + finalNetworkRequestStartTime: 0, + endTime: 0, + encodedBodySize: 0, + decodedBodySize: 0, + finalConnectionTimingInfo: null + } +} + +// https://html.spec.whatwg.org/multipage/origin.html#policy-container +function makePolicyContainer () { + // Note: the fetch spec doesn't make use of embedder policy or CSP list + return { + referrerPolicy: 'strict-origin-when-cross-origin' + } +} + +// https://html.spec.whatwg.org/multipage/origin.html#clone-a-policy-container +function clonePolicyContainer (policyContainer) { + return { + referrerPolicy: policyContainer.referrerPolicy + } +} + +// https://w3c.github.io/webappsec-referrer-policy/#determine-requests-referrer +function determineRequestsReferrer (request) { + // 1. Let policy be request's referrer policy. + const policy = request.referrerPolicy + + // Note: policy cannot (shouldn't) be null or an empty string. + assert(policy) + + // 2. Let environment be request’s client. + + let referrerSource = null + + // 3. Switch on request’s referrer: + if (request.referrer === 'client') { + // Note: node isn't a browser and doesn't implement document/iframes, + // so we bypass this step and replace it with our own. + + const globalOrigin = getGlobalOrigin() + + if (!globalOrigin || globalOrigin.origin === 'null') { + return 'no-referrer' + } + + // note: we need to clone it as it's mutated + referrerSource = new URL(globalOrigin) + } else if (request.referrer instanceof URL) { + // Let referrerSource be request’s referrer. + referrerSource = request.referrer + } + + // 4. Let request’s referrerURL be the result of stripping referrerSource for + // use as a referrer. + let referrerURL = stripURLForReferrer(referrerSource) + + // 5. Let referrerOrigin be the result of stripping referrerSource for use as + // a referrer, with the origin-only flag set to true. + const referrerOrigin = stripURLForReferrer(referrerSource, true) + + // 6. If the result of serializing referrerURL is a string whose length is + // greater than 4096, set referrerURL to referrerOrigin. + if (referrerURL.toString().length > 4096) { + referrerURL = referrerOrigin + } + + const areSameOrigin = sameOrigin(request, referrerURL) + const isNonPotentiallyTrustWorthy = isURLPotentiallyTrustworthy(referrerURL) && + !isURLPotentiallyTrustworthy(request.url) + + // 8. Execute the switch statements corresponding to the value of policy: + switch (policy) { + case 'origin': return referrerOrigin != null ? referrerOrigin : stripURLForReferrer(referrerSource, true) + case 'unsafe-url': return referrerURL + case 'same-origin': + return areSameOrigin ? referrerOrigin : 'no-referrer' + case 'origin-when-cross-origin': + return areSameOrigin ? referrerURL : referrerOrigin + case 'strict-origin-when-cross-origin': { + const currentURL = requestCurrentURL(request) + + // 1. If the origin of referrerURL and the origin of request’s current + // URL are the same, then return referrerURL. + if (sameOrigin(referrerURL, currentURL)) { + return referrerURL + } + + // 2. If referrerURL is a potentially trustworthy URL and request’s + // current URL is not a potentially trustworthy URL, then return no + // referrer. + if (isURLPotentiallyTrustworthy(referrerURL) && !isURLPotentiallyTrustworthy(currentURL)) { + return 'no-referrer' + } + + // 3. Return referrerOrigin. + return referrerOrigin + } + case 'strict-origin': // eslint-disable-line + /** + * 1. If referrerURL is a potentially trustworthy URL and + * request’s current URL is not a potentially trustworthy URL, + * then return no referrer. + * 2. Return referrerOrigin + */ + case 'no-referrer-when-downgrade': // eslint-disable-line + /** + * 1. If referrerURL is a potentially trustworthy URL and + * request’s current URL is not a potentially trustworthy URL, + * then return no referrer. + * 2. Return referrerOrigin + */ + + default: // eslint-disable-line + return isNonPotentiallyTrustWorthy ? 'no-referrer' : referrerOrigin + } +} + +/** + * @see https://w3c.github.io/webappsec-referrer-policy/#strip-url + * @param {URL} url + * @param {boolean|undefined} originOnly + */ +function stripURLForReferrer (url, originOnly) { + // 1. Assert: url is a URL. + assert(url instanceof URL) + + // 2. If url’s scheme is a local scheme, then return no referrer. + if (url.protocol === 'file:' || url.protocol === 'about:' || url.protocol === 'blank:') { + return 'no-referrer' + } + + // 3. Set url’s username to the empty string. + url.username = '' + + // 4. Set url’s password to the empty string. + url.password = '' + + // 5. Set url’s fragment to null. + url.hash = '' + + // 6. If the origin-only flag is true, then: + if (originOnly) { + // 1. Set url’s path to « the empty string ». + url.pathname = '' + + // 2. Set url’s query to null. + url.search = '' + } + + // 7. Return url. + return url +} + +function isURLPotentiallyTrustworthy (url) { + if (!(url instanceof URL)) { + return false + } + + // If child of about, return true + if (url.href === 'about:blank' || url.href === 'about:srcdoc') { + return true + } + + // If scheme is data, return true + if (url.protocol === 'data:') return true + + // If file, return true + if (url.protocol === 'file:') return true + + return isOriginPotentiallyTrustworthy(url.origin) + + function isOriginPotentiallyTrustworthy (origin) { + // If origin is explicitly null, return false + if (origin == null || origin === 'null') return false + + const originAsURL = new URL(origin) + + // If secure, return true + if (originAsURL.protocol === 'https:' || originAsURL.protocol === 'wss:') { + return true + } + + // If localhost or variants, return true + if (/^127(?:\.[0-9]+){0,2}\.[0-9]+$|^\[(?:0*:)*?:?0*1\]$/.test(originAsURL.hostname) || + (originAsURL.hostname === 'localhost' || originAsURL.hostname.includes('localhost.')) || + (originAsURL.hostname.endsWith('.localhost'))) { + return true + } + + // If any other, return false + return false + } +} + +/** + * @see https://w3c.github.io/webappsec-subresource-integrity/#does-response-match-metadatalist + * @param {Uint8Array} bytes + * @param {string} metadataList + */ +function bytesMatch (bytes, metadataList) { + // If node is not built with OpenSSL support, we cannot check + // a request's integrity, so allow it by default (the spec will + // allow requests if an invalid hash is given, as precedence). + /* istanbul ignore if: only if node is built with --without-ssl */ + if (crypto === undefined) { + return true + } + + // 1. Let parsedMetadata be the result of parsing metadataList. + const parsedMetadata = parseMetadata(metadataList) + + // 2. If parsedMetadata is no metadata, return true. + if (parsedMetadata === 'no metadata') { + return true + } + + // 3. If response is not eligible for integrity validation, return false. + // TODO + + // 4. If parsedMetadata is the empty set, return true. + if (parsedMetadata.length === 0) { + return true + } + + // 5. Let metadata be the result of getting the strongest + // metadata from parsedMetadata. + const strongest = getStrongestMetadata(parsedMetadata) + const metadata = filterMetadataListByAlgorithm(parsedMetadata, strongest) + + // 6. For each item in metadata: + for (const item of metadata) { + // 1. Let algorithm be the alg component of item. + const algorithm = item.algo + + // 2. Let expectedValue be the val component of item. + const expectedValue = item.hash + + // See https://github.com/web-platform-tests/wpt/commit/e4c5cc7a5e48093220528dfdd1c4012dc3837a0e + // "be liberal with padding". This is annoying, and it's not even in the spec. + + // 3. Let actualValue be the result of applying algorithm to bytes. + let actualValue = crypto.createHash(algorithm).update(bytes).digest('base64') + + if (actualValue[actualValue.length - 1] === '=') { + if (actualValue[actualValue.length - 2] === '=') { + actualValue = actualValue.slice(0, -2) + } else { + actualValue = actualValue.slice(0, -1) + } + } + + // 4. If actualValue is a case-sensitive match for expectedValue, + // return true. + if (compareBase64Mixed(actualValue, expectedValue)) { + return true + } + } + + // 7. Return false. + return false +} + +// https://w3c.github.io/webappsec-subresource-integrity/#grammardef-hash-with-options +// https://www.w3.org/TR/CSP2/#source-list-syntax +// https://www.rfc-editor.org/rfc/rfc5234#appendix-B.1 +const parseHashWithOptions = /(?sha256|sha384|sha512)-((?[A-Za-z0-9+/]+|[A-Za-z0-9_-]+)={0,2}(?:\s|$)( +[!-~]*)?)?/i + +/** + * @see https://w3c.github.io/webappsec-subresource-integrity/#parse-metadata + * @param {string} metadata + */ +function parseMetadata (metadata) { + // 1. Let result be the empty set. + /** @type {{ algo: string, hash: string }[]} */ + const result = [] + + // 2. Let empty be equal to true. + let empty = true + + // 3. For each token returned by splitting metadata on spaces: + for (const token of metadata.split(' ')) { + // 1. Set empty to false. + empty = false + + // 2. Parse token as a hash-with-options. + const parsedToken = parseHashWithOptions.exec(token) + + // 3. If token does not parse, continue to the next token. + if ( + parsedToken === null || + parsedToken.groups === undefined || + parsedToken.groups.algo === undefined + ) { + // Note: Chromium blocks the request at this point, but Firefox + // gives a warning that an invalid integrity was given. The + // correct behavior is to ignore these, and subsequently not + // check the integrity of the resource. + continue + } + + // 4. Let algorithm be the hash-algo component of token. + const algorithm = parsedToken.groups.algo.toLowerCase() + + // 5. If algorithm is a hash function recognized by the user + // agent, add the parsed token to result. + if (supportedHashes.includes(algorithm)) { + result.push(parsedToken.groups) + } + } + + // 4. Return no metadata if empty is true, otherwise return result. + if (empty === true) { + return 'no metadata' + } + + return result +} + +/** + * @param {{ algo: 'sha256' | 'sha384' | 'sha512' }[]} metadataList + */ +function getStrongestMetadata (metadataList) { + // Let algorithm be the algo component of the first item in metadataList. + // Can be sha256 + let algorithm = metadataList[0].algo + // If the algorithm is sha512, then it is the strongest + // and we can return immediately + if (algorithm[3] === '5') { + return algorithm + } + + for (let i = 1; i < metadataList.length; ++i) { + const metadata = metadataList[i] + // If the algorithm is sha512, then it is the strongest + // and we can break the loop immediately + if (metadata.algo[3] === '5') { + algorithm = 'sha512' + break + // If the algorithm is sha384, then a potential sha256 or sha384 is ignored + } else if (algorithm[3] === '3') { + continue + // algorithm is sha256, check if algorithm is sha384 and if so, set it as + // the strongest + } else if (metadata.algo[3] === '3') { + algorithm = 'sha384' + } + } + return algorithm +} + +function filterMetadataListByAlgorithm (metadataList, algorithm) { + if (metadataList.length === 1) { + return metadataList + } + + let pos = 0 + for (let i = 0; i < metadataList.length; ++i) { + if (metadataList[i].algo === algorithm) { + metadataList[pos++] = metadataList[i] + } + } + + metadataList.length = pos + + return metadataList +} + +/** + * Compares two base64 strings, allowing for base64url + * in the second string. + * +* @param {string} actualValue always base64 + * @param {string} expectedValue base64 or base64url + * @returns {boolean} + */ +function compareBase64Mixed (actualValue, expectedValue) { + if (actualValue.length !== expectedValue.length) { + return false + } + for (let i = 0; i < actualValue.length; ++i) { + if (actualValue[i] !== expectedValue[i]) { + if ( + (actualValue[i] === '+' && expectedValue[i] === '-') || + (actualValue[i] === '/' && expectedValue[i] === '_') + ) { + continue + } + return false + } + } + + return true +} + +// https://w3c.github.io/webappsec-upgrade-insecure-requests/#upgrade-request +function tryUpgradeRequestToAPotentiallyTrustworthyURL (request) { + // TODO +} + +/** + * @link {https://html.spec.whatwg.org/multipage/origin.html#same-origin} + * @param {URL} A + * @param {URL} B + */ +function sameOrigin (A, B) { + // 1. If A and B are the same opaque origin, then return true. + if (A.origin === B.origin && A.origin === 'null') { + return true + } + + // 2. If A and B are both tuple origins and their schemes, + // hosts, and port are identical, then return true. + if (A.protocol === B.protocol && A.hostname === B.hostname && A.port === B.port) { + return true + } + + // 3. Return false. + return false +} + +function createDeferredPromise () { + let res + let rej + const promise = new Promise((resolve, reject) => { + res = resolve + rej = reject + }) + + return { promise, resolve: res, reject: rej } +} + +function isAborted (fetchParams) { + return fetchParams.controller.state === 'aborted' +} + +function isCancelled (fetchParams) { + return fetchParams.controller.state === 'aborted' || + fetchParams.controller.state === 'terminated' +} + +const normalizeMethodRecord = { + delete: 'DELETE', + DELETE: 'DELETE', + get: 'GET', + GET: 'GET', + head: 'HEAD', + HEAD: 'HEAD', + options: 'OPTIONS', + OPTIONS: 'OPTIONS', + post: 'POST', + POST: 'POST', + put: 'PUT', + PUT: 'PUT' +} + +// Note: object prototypes should not be able to be referenced. e.g. `Object#hasOwnProperty`. +Object.setPrototypeOf(normalizeMethodRecord, null) + +/** + * @see https://fetch.spec.whatwg.org/#concept-method-normalize + * @param {string} method + */ +function normalizeMethod (method) { + return normalizeMethodRecord[method.toLowerCase()] ?? method +} + +// https://infra.spec.whatwg.org/#serialize-a-javascript-value-to-a-json-string +function serializeJavascriptValueToJSONString (value) { + // 1. Let result be ? Call(%JSON.stringify%, undefined, « value »). + const result = JSON.stringify(value) + + // 2. If result is undefined, then throw a TypeError. + if (result === undefined) { + throw new TypeError('Value is not JSON serializable') + } + + // 3. Assert: result is a string. + assert(typeof result === 'string') + + // 4. Return result. + return result +} + +// https://tc39.es/ecma262/#sec-%25iteratorprototype%25-object +const esIteratorPrototype = Object.getPrototypeOf(Object.getPrototypeOf([][Symbol.iterator]())) + +/** + * @see https://webidl.spec.whatwg.org/#dfn-iterator-prototype-object + * @param {() => unknown[]} iterator + * @param {string} name name of the instance + * @param {'key'|'value'|'key+value'} kind + */ +function makeIterator (iterator, name, kind) { + const object = { + index: 0, + kind, + target: iterator + } + + const i = { + next () { + // 1. Let interface be the interface for which the iterator prototype object exists. + + // 2. Let thisValue be the this value. + + // 3. Let object be ? ToObject(thisValue). + + // 4. If object is a platform object, then perform a security + // check, passing: + + // 5. If object is not a default iterator object for interface, + // then throw a TypeError. + if (Object.getPrototypeOf(this) !== i) { + throw new TypeError( + `'next' called on an object that does not implement interface ${name} Iterator.` + ) + } + + // 6. Let index be object’s index. + // 7. Let kind be object’s kind. + // 8. Let values be object’s target's value pairs to iterate over. + const { index, kind, target } = object + const values = target() + + // 9. Let len be the length of values. + const len = values.length + + // 10. If index is greater than or equal to len, then return + // CreateIterResultObject(undefined, true). + if (index >= len) { + return { value: undefined, done: true } + } + + // 11. Let pair be the entry in values at index index. + const pair = values[index] + + // 12. Set object’s index to index + 1. + object.index = index + 1 + + // 13. Return the iterator result for pair and kind. + return iteratorResult(pair, kind) + }, + // The class string of an iterator prototype object for a given interface is the + // result of concatenating the identifier of the interface and the string " Iterator". + [Symbol.toStringTag]: `${name} Iterator` + } + + // The [[Prototype]] internal slot of an iterator prototype object must be %IteratorPrototype%. + Object.setPrototypeOf(i, esIteratorPrototype) + // esIteratorPrototype needs to be the prototype of i + // which is the prototype of an empty object. Yes, it's confusing. + return Object.setPrototypeOf({}, i) +} + +// https://webidl.spec.whatwg.org/#iterator-result +function iteratorResult (pair, kind) { + let result + + // 1. Let result be a value determined by the value of kind: + switch (kind) { + case 'key': { + // 1. Let idlKey be pair’s key. + // 2. Let key be the result of converting idlKey to an + // ECMAScript value. + // 3. result is key. + result = pair[0] + break + } + case 'value': { + // 1. Let idlValue be pair’s value. + // 2. Let value be the result of converting idlValue to + // an ECMAScript value. + // 3. result is value. + result = pair[1] + break + } + case 'key+value': { + // 1. Let idlKey be pair’s key. + // 2. Let idlValue be pair’s value. + // 3. Let key be the result of converting idlKey to an + // ECMAScript value. + // 4. Let value be the result of converting idlValue to + // an ECMAScript value. + // 5. Let array be ! ArrayCreate(2). + // 6. Call ! CreateDataProperty(array, "0", key). + // 7. Call ! CreateDataProperty(array, "1", value). + // 8. result is array. + result = pair + break + } + } + + // 2. Return CreateIterResultObject(result, false). + return { value: result, done: false } +} + +/** + * @see https://fetch.spec.whatwg.org/#body-fully-read + */ +async function fullyReadBody (body, processBody, processBodyError) { + // 1. If taskDestination is null, then set taskDestination to + // the result of starting a new parallel queue. + + // 2. Let successSteps given a byte sequence bytes be to queue a + // fetch task to run processBody given bytes, with taskDestination. + const successSteps = processBody + + // 3. Let errorSteps be to queue a fetch task to run processBodyError, + // with taskDestination. + const errorSteps = processBodyError + + // 4. Let reader be the result of getting a reader for body’s stream. + // If that threw an exception, then run errorSteps with that + // exception and return. + let reader + + try { + reader = body.stream.getReader() + } catch (e) { + errorSteps(e) + return + } + + // 5. Read all bytes from reader, given successSteps and errorSteps. + try { + const result = await readAllBytes(reader) + successSteps(result) + } catch (e) { + errorSteps(e) + } +} + +/** @type {ReadableStream} */ +let ReadableStream = globalThis.ReadableStream + +function isReadableStreamLike (stream) { + if (!ReadableStream) { + ReadableStream = (__nccwpck_require__(63774).ReadableStream) + } + + return stream instanceof ReadableStream || ( + stream[Symbol.toStringTag] === 'ReadableStream' && + typeof stream.tee === 'function' + ) +} + +const MAXIMUM_ARGUMENT_LENGTH = 65535 + +/** + * @see https://infra.spec.whatwg.org/#isomorphic-decode + * @param {number[]|Uint8Array} input + */ +function isomorphicDecode (input) { + // 1. To isomorphic decode a byte sequence input, return a string whose code point + // length is equal to input’s length and whose code points have the same values + // as the values of input’s bytes, in the same order. + + if (input.length < MAXIMUM_ARGUMENT_LENGTH) { + return String.fromCharCode(...input) + } + + return input.reduce((previous, current) => previous + String.fromCharCode(current), '') +} + +/** + * @param {ReadableStreamController} controller + */ +function readableStreamClose (controller) { + try { + controller.close() + } catch (err) { + // TODO: add comment explaining why this error occurs. + if (!err.message.includes('Controller is already closed')) { + throw err + } + } +} + +/** + * @see https://infra.spec.whatwg.org/#isomorphic-encode + * @param {string} input + */ +function isomorphicEncode (input) { + // 1. Assert: input contains no code points greater than U+00FF. + for (let i = 0; i < input.length; i++) { + assert(input.charCodeAt(i) <= 0xFF) + } + + // 2. Return a byte sequence whose length is equal to input’s code + // point length and whose bytes have the same values as the + // values of input’s code points, in the same order + return input +} + +/** + * @see https://streams.spec.whatwg.org/#readablestreamdefaultreader-read-all-bytes + * @see https://streams.spec.whatwg.org/#read-loop + * @param {ReadableStreamDefaultReader} reader + */ +async function readAllBytes (reader) { + const bytes = [] + let byteLength = 0 + + while (true) { + const { done, value: chunk } = await reader.read() + + if (done) { + // 1. Call successSteps with bytes. + return Buffer.concat(bytes, byteLength) + } + + // 1. If chunk is not a Uint8Array object, call failureSteps + // with a TypeError and abort these steps. + if (!isUint8Array(chunk)) { + throw new TypeError('Received non-Uint8Array chunk') + } + + // 2. Append the bytes represented by chunk to bytes. + bytes.push(chunk) + byteLength += chunk.length + + // 3. Read-loop given reader, bytes, successSteps, and failureSteps. + } +} + +/** + * @see https://fetch.spec.whatwg.org/#is-local + * @param {URL} url + */ +function urlIsLocal (url) { + assert('protocol' in url) // ensure it's a url object + + const protocol = url.protocol + + return protocol === 'about:' || protocol === 'blob:' || protocol === 'data:' +} + +/** + * @param {string|URL} url + */ +function urlHasHttpsScheme (url) { + if (typeof url === 'string') { + return url.startsWith('https:') + } + + return url.protocol === 'https:' +} + +/** + * @see https://fetch.spec.whatwg.org/#http-scheme + * @param {URL} url + */ +function urlIsHttpHttpsScheme (url) { + assert('protocol' in url) // ensure it's a url object + + const protocol = url.protocol + + return protocol === 'http:' || protocol === 'https:' +} + +/** + * Fetch supports node >= 16.8.0, but Object.hasOwn was added in v16.9.0. + */ +const hasOwn = Object.hasOwn || ((dict, key) => Object.prototype.hasOwnProperty.call(dict, key)) + +module.exports = { + isAborted, + isCancelled, + createDeferredPromise, + ReadableStreamFrom, + toUSVString, + tryUpgradeRequestToAPotentiallyTrustworthyURL, + coarsenedSharedCurrentTime, + determineRequestsReferrer, + makePolicyContainer, + clonePolicyContainer, + appendFetchMetadata, + appendRequestOriginHeader, + TAOCheck, + corsCheck, + crossOriginResourcePolicyCheck, + createOpaqueTimingInfo, + setRequestReferrerPolicyOnRedirect, + isValidHTTPToken, + requestBadPort, + requestCurrentURL, + responseURL, + responseLocationURL, + isBlobLike, + isURLPotentiallyTrustworthy, + isValidReasonPhrase, + sameOrigin, + normalizeMethod, + serializeJavascriptValueToJSONString, + makeIterator, + isValidHeaderName, + isValidHeaderValue, + hasOwn, + isErrorLike, + fullyReadBody, + bytesMatch, + isReadableStreamLike, + readableStreamClose, + isomorphicEncode, + isomorphicDecode, + urlIsLocal, + urlHasHttpsScheme, + urlIsHttpHttpsScheme, + readAllBytes, + normalizeMethodRecord, + parseMetadata +} + + +/***/ }), + +/***/ 74222: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const { types } = __nccwpck_require__(39023) +const { hasOwn, toUSVString } = __nccwpck_require__(15523) + +/** @type {import('../../types/webidl').Webidl} */ +const webidl = {} +webidl.converters = {} +webidl.util = {} +webidl.errors = {} + +webidl.errors.exception = function (message) { + return new TypeError(`${message.header}: ${message.message}`) +} + +webidl.errors.conversionFailed = function (context) { + const plural = context.types.length === 1 ? '' : ' one of' + const message = + `${context.argument} could not be converted to` + + `${plural}: ${context.types.join(', ')}.` + + return webidl.errors.exception({ + header: context.prefix, + message + }) +} + +webidl.errors.invalidArgument = function (context) { + return webidl.errors.exception({ + header: context.prefix, + message: `"${context.value}" is an invalid ${context.type}.` + }) +} + +// https://webidl.spec.whatwg.org/#implements +webidl.brandCheck = function (V, I, opts = undefined) { + if (opts?.strict !== false && !(V instanceof I)) { + throw new TypeError('Illegal invocation') + } else { + return V?.[Symbol.toStringTag] === I.prototype[Symbol.toStringTag] + } +} + +webidl.argumentLengthCheck = function ({ length }, min, ctx) { + if (length < min) { + throw webidl.errors.exception({ + message: `${min} argument${min !== 1 ? 's' : ''} required, ` + + `but${length ? ' only' : ''} ${length} found.`, + ...ctx + }) + } +} + +webidl.illegalConstructor = function () { + throw webidl.errors.exception({ + header: 'TypeError', + message: 'Illegal constructor' + }) +} + +// https://tc39.es/ecma262/#sec-ecmascript-data-types-and-values +webidl.util.Type = function (V) { + switch (typeof V) { + case 'undefined': return 'Undefined' + case 'boolean': return 'Boolean' + case 'string': return 'String' + case 'symbol': return 'Symbol' + case 'number': return 'Number' + case 'bigint': return 'BigInt' + case 'function': + case 'object': { + if (V === null) { + return 'Null' + } + + return 'Object' + } + } +} + +// https://webidl.spec.whatwg.org/#abstract-opdef-converttoint +webidl.util.ConvertToInt = function (V, bitLength, signedness, opts = {}) { + let upperBound + let lowerBound + + // 1. If bitLength is 64, then: + if (bitLength === 64) { + // 1. Let upperBound be 2^53 − 1. + upperBound = Math.pow(2, 53) - 1 + + // 2. If signedness is "unsigned", then let lowerBound be 0. + if (signedness === 'unsigned') { + lowerBound = 0 + } else { + // 3. Otherwise let lowerBound be −2^53 + 1. + lowerBound = Math.pow(-2, 53) + 1 + } + } else if (signedness === 'unsigned') { + // 2. Otherwise, if signedness is "unsigned", then: + + // 1. Let lowerBound be 0. + lowerBound = 0 + + // 2. Let upperBound be 2^bitLength − 1. + upperBound = Math.pow(2, bitLength) - 1 + } else { + // 3. Otherwise: + + // 1. Let lowerBound be -2^bitLength − 1. + lowerBound = Math.pow(-2, bitLength) - 1 + + // 2. Let upperBound be 2^bitLength − 1 − 1. + upperBound = Math.pow(2, bitLength - 1) - 1 + } + + // 4. Let x be ? ToNumber(V). + let x = Number(V) + + // 5. If x is −0, then set x to +0. + if (x === 0) { + x = 0 + } + + // 6. If the conversion is to an IDL type associated + // with the [EnforceRange] extended attribute, then: + if (opts.enforceRange === true) { + // 1. If x is NaN, +∞, or −∞, then throw a TypeError. + if ( + Number.isNaN(x) || + x === Number.POSITIVE_INFINITY || + x === Number.NEGATIVE_INFINITY + ) { + throw webidl.errors.exception({ + header: 'Integer conversion', + message: `Could not convert ${V} to an integer.` + }) + } + + // 2. Set x to IntegerPart(x). + x = webidl.util.IntegerPart(x) + + // 3. If x < lowerBound or x > upperBound, then + // throw a TypeError. + if (x < lowerBound || x > upperBound) { + throw webidl.errors.exception({ + header: 'Integer conversion', + message: `Value must be between ${lowerBound}-${upperBound}, got ${x}.` + }) + } + + // 4. Return x. + return x + } + + // 7. If x is not NaN and the conversion is to an IDL + // type associated with the [Clamp] extended + // attribute, then: + if (!Number.isNaN(x) && opts.clamp === true) { + // 1. Set x to min(max(x, lowerBound), upperBound). + x = Math.min(Math.max(x, lowerBound), upperBound) + + // 2. Round x to the nearest integer, choosing the + // even integer if it lies halfway between two, + // and choosing +0 rather than −0. + if (Math.floor(x) % 2 === 0) { + x = Math.floor(x) + } else { + x = Math.ceil(x) + } + + // 3. Return x. + return x + } + + // 8. If x is NaN, +0, +∞, or −∞, then return +0. + if ( + Number.isNaN(x) || + (x === 0 && Object.is(0, x)) || + x === Number.POSITIVE_INFINITY || + x === Number.NEGATIVE_INFINITY + ) { + return 0 + } + + // 9. Set x to IntegerPart(x). + x = webidl.util.IntegerPart(x) + + // 10. Set x to x modulo 2^bitLength. + x = x % Math.pow(2, bitLength) + + // 11. If signedness is "signed" and x ≥ 2^bitLength − 1, + // then return x − 2^bitLength. + if (signedness === 'signed' && x >= Math.pow(2, bitLength) - 1) { + return x - Math.pow(2, bitLength) + } + + // 12. Otherwise, return x. + return x +} + +// https://webidl.spec.whatwg.org/#abstract-opdef-integerpart +webidl.util.IntegerPart = function (n) { + // 1. Let r be floor(abs(n)). + const r = Math.floor(Math.abs(n)) + + // 2. If n < 0, then return -1 × r. + if (n < 0) { + return -1 * r + } + + // 3. Otherwise, return r. + return r +} + +// https://webidl.spec.whatwg.org/#es-sequence +webidl.sequenceConverter = function (converter) { + return (V) => { + // 1. If Type(V) is not Object, throw a TypeError. + if (webidl.util.Type(V) !== 'Object') { + throw webidl.errors.exception({ + header: 'Sequence', + message: `Value of type ${webidl.util.Type(V)} is not an Object.` + }) + } + + // 2. Let method be ? GetMethod(V, @@iterator). + /** @type {Generator} */ + const method = V?.[Symbol.iterator]?.() + const seq = [] + + // 3. If method is undefined, throw a TypeError. + if ( + method === undefined || + typeof method.next !== 'function' + ) { + throw webidl.errors.exception({ + header: 'Sequence', + message: 'Object is not an iterator.' + }) + } + + // https://webidl.spec.whatwg.org/#create-sequence-from-iterable + while (true) { + const { done, value } = method.next() + + if (done) { + break + } + + seq.push(converter(value)) + } + + return seq + } +} + +// https://webidl.spec.whatwg.org/#es-to-record +webidl.recordConverter = function (keyConverter, valueConverter) { + return (O) => { + // 1. If Type(O) is not Object, throw a TypeError. + if (webidl.util.Type(O) !== 'Object') { + throw webidl.errors.exception({ + header: 'Record', + message: `Value of type ${webidl.util.Type(O)} is not an Object.` + }) + } + + // 2. Let result be a new empty instance of record. + const result = {} + + if (!types.isProxy(O)) { + // Object.keys only returns enumerable properties + const keys = Object.keys(O) + + for (const key of keys) { + // 1. Let typedKey be key converted to an IDL value of type K. + const typedKey = keyConverter(key) + + // 2. Let value be ? Get(O, key). + // 3. Let typedValue be value converted to an IDL value of type V. + const typedValue = valueConverter(O[key]) + + // 4. Set result[typedKey] to typedValue. + result[typedKey] = typedValue + } + + // 5. Return result. + return result + } + + // 3. Let keys be ? O.[[OwnPropertyKeys]](). + const keys = Reflect.ownKeys(O) + + // 4. For each key of keys. + for (const key of keys) { + // 1. Let desc be ? O.[[GetOwnProperty]](key). + const desc = Reflect.getOwnPropertyDescriptor(O, key) + + // 2. If desc is not undefined and desc.[[Enumerable]] is true: + if (desc?.enumerable) { + // 1. Let typedKey be key converted to an IDL value of type K. + const typedKey = keyConverter(key) + + // 2. Let value be ? Get(O, key). + // 3. Let typedValue be value converted to an IDL value of type V. + const typedValue = valueConverter(O[key]) + + // 4. Set result[typedKey] to typedValue. + result[typedKey] = typedValue + } + } + + // 5. Return result. + return result + } +} + +webidl.interfaceConverter = function (i) { + return (V, opts = {}) => { + if (opts.strict !== false && !(V instanceof i)) { + throw webidl.errors.exception({ + header: i.name, + message: `Expected ${V} to be an instance of ${i.name}.` + }) + } + + return V + } +} + +webidl.dictionaryConverter = function (converters) { + return (dictionary) => { + const type = webidl.util.Type(dictionary) + const dict = {} + + if (type === 'Null' || type === 'Undefined') { + return dict + } else if (type !== 'Object') { + throw webidl.errors.exception({ + header: 'Dictionary', + message: `Expected ${dictionary} to be one of: Null, Undefined, Object.` + }) + } + + for (const options of converters) { + const { key, defaultValue, required, converter } = options + + if (required === true) { + if (!hasOwn(dictionary, key)) { + throw webidl.errors.exception({ + header: 'Dictionary', + message: `Missing required key "${key}".` + }) + } + } + + let value = dictionary[key] + const hasDefault = hasOwn(options, 'defaultValue') + + // Only use defaultValue if value is undefined and + // a defaultValue options was provided. + if (hasDefault && value !== null) { + value = value ?? defaultValue + } + + // A key can be optional and have no default value. + // When this happens, do not perform a conversion, + // and do not assign the key a value. + if (required || hasDefault || value !== undefined) { + value = converter(value) + + if ( + options.allowedValues && + !options.allowedValues.includes(value) + ) { + throw webidl.errors.exception({ + header: 'Dictionary', + message: `${value} is not an accepted type. Expected one of ${options.allowedValues.join(', ')}.` + }) + } + + dict[key] = value + } + } + + return dict + } +} + +webidl.nullableConverter = function (converter) { + return (V) => { + if (V === null) { + return V + } + + return converter(V) + } +} + +// https://webidl.spec.whatwg.org/#es-DOMString +webidl.converters.DOMString = function (V, opts = {}) { + // 1. If V is null and the conversion is to an IDL type + // associated with the [LegacyNullToEmptyString] + // extended attribute, then return the DOMString value + // that represents the empty string. + if (V === null && opts.legacyNullToEmptyString) { + return '' + } + + // 2. Let x be ? ToString(V). + if (typeof V === 'symbol') { + throw new TypeError('Could not convert argument of type symbol to string.') + } + + // 3. Return the IDL DOMString value that represents the + // same sequence of code units as the one the + // ECMAScript String value x represents. + return String(V) +} + +// https://webidl.spec.whatwg.org/#es-ByteString +webidl.converters.ByteString = function (V) { + // 1. Let x be ? ToString(V). + // Note: DOMString converter perform ? ToString(V) + const x = webidl.converters.DOMString(V) + + // 2. If the value of any element of x is greater than + // 255, then throw a TypeError. + for (let index = 0; index < x.length; index++) { + if (x.charCodeAt(index) > 255) { + throw new TypeError( + 'Cannot convert argument to a ByteString because the character at ' + + `index ${index} has a value of ${x.charCodeAt(index)} which is greater than 255.` + ) + } + } + + // 3. Return an IDL ByteString value whose length is the + // length of x, and where the value of each element is + // the value of the corresponding element of x. + return x +} + +// https://webidl.spec.whatwg.org/#es-USVString +webidl.converters.USVString = toUSVString + +// https://webidl.spec.whatwg.org/#es-boolean +webidl.converters.boolean = function (V) { + // 1. Let x be the result of computing ToBoolean(V). + const x = Boolean(V) + + // 2. Return the IDL boolean value that is the one that represents + // the same truth value as the ECMAScript Boolean value x. + return x +} + +// https://webidl.spec.whatwg.org/#es-any +webidl.converters.any = function (V) { + return V +} + +// https://webidl.spec.whatwg.org/#es-long-long +webidl.converters['long long'] = function (V) { + // 1. Let x be ? ConvertToInt(V, 64, "signed"). + const x = webidl.util.ConvertToInt(V, 64, 'signed') + + // 2. Return the IDL long long value that represents + // the same numeric value as x. + return x +} + +// https://webidl.spec.whatwg.org/#es-unsigned-long-long +webidl.converters['unsigned long long'] = function (V) { + // 1. Let x be ? ConvertToInt(V, 64, "unsigned"). + const x = webidl.util.ConvertToInt(V, 64, 'unsigned') + + // 2. Return the IDL unsigned long long value that + // represents the same numeric value as x. + return x +} + +// https://webidl.spec.whatwg.org/#es-unsigned-long +webidl.converters['unsigned long'] = function (V) { + // 1. Let x be ? ConvertToInt(V, 32, "unsigned"). + const x = webidl.util.ConvertToInt(V, 32, 'unsigned') + + // 2. Return the IDL unsigned long value that + // represents the same numeric value as x. + return x +} + +// https://webidl.spec.whatwg.org/#es-unsigned-short +webidl.converters['unsigned short'] = function (V, opts) { + // 1. Let x be ? ConvertToInt(V, 16, "unsigned"). + const x = webidl.util.ConvertToInt(V, 16, 'unsigned', opts) + + // 2. Return the IDL unsigned short value that represents + // the same numeric value as x. + return x +} + +// https://webidl.spec.whatwg.org/#idl-ArrayBuffer +webidl.converters.ArrayBuffer = function (V, opts = {}) { + // 1. If Type(V) is not Object, or V does not have an + // [[ArrayBufferData]] internal slot, then throw a + // TypeError. + // see: https://tc39.es/ecma262/#sec-properties-of-the-arraybuffer-instances + // see: https://tc39.es/ecma262/#sec-properties-of-the-sharedarraybuffer-instances + if ( + webidl.util.Type(V) !== 'Object' || + !types.isAnyArrayBuffer(V) + ) { + throw webidl.errors.conversionFailed({ + prefix: `${V}`, + argument: `${V}`, + types: ['ArrayBuffer'] + }) + } + + // 2. If the conversion is not to an IDL type associated + // with the [AllowShared] extended attribute, and + // IsSharedArrayBuffer(V) is true, then throw a + // TypeError. + if (opts.allowShared === false && types.isSharedArrayBuffer(V)) { + throw webidl.errors.exception({ + header: 'ArrayBuffer', + message: 'SharedArrayBuffer is not allowed.' + }) + } + + // 3. If the conversion is not to an IDL type associated + // with the [AllowResizable] extended attribute, and + // IsResizableArrayBuffer(V) is true, then throw a + // TypeError. + // Note: resizable ArrayBuffers are currently a proposal. + + // 4. Return the IDL ArrayBuffer value that is a + // reference to the same object as V. + return V +} + +webidl.converters.TypedArray = function (V, T, opts = {}) { + // 1. Let T be the IDL type V is being converted to. + + // 2. If Type(V) is not Object, or V does not have a + // [[TypedArrayName]] internal slot with a value + // equal to T’s name, then throw a TypeError. + if ( + webidl.util.Type(V) !== 'Object' || + !types.isTypedArray(V) || + V.constructor.name !== T.name + ) { + throw webidl.errors.conversionFailed({ + prefix: `${T.name}`, + argument: `${V}`, + types: [T.name] + }) + } + + // 3. If the conversion is not to an IDL type associated + // with the [AllowShared] extended attribute, and + // IsSharedArrayBuffer(V.[[ViewedArrayBuffer]]) is + // true, then throw a TypeError. + if (opts.allowShared === false && types.isSharedArrayBuffer(V.buffer)) { + throw webidl.errors.exception({ + header: 'ArrayBuffer', + message: 'SharedArrayBuffer is not allowed.' + }) + } + + // 4. If the conversion is not to an IDL type associated + // with the [AllowResizable] extended attribute, and + // IsResizableArrayBuffer(V.[[ViewedArrayBuffer]]) is + // true, then throw a TypeError. + // Note: resizable array buffers are currently a proposal + + // 5. Return the IDL value of type T that is a reference + // to the same object as V. + return V +} + +webidl.converters.DataView = function (V, opts = {}) { + // 1. If Type(V) is not Object, or V does not have a + // [[DataView]] internal slot, then throw a TypeError. + if (webidl.util.Type(V) !== 'Object' || !types.isDataView(V)) { + throw webidl.errors.exception({ + header: 'DataView', + message: 'Object is not a DataView.' + }) + } + + // 2. If the conversion is not to an IDL type associated + // with the [AllowShared] extended attribute, and + // IsSharedArrayBuffer(V.[[ViewedArrayBuffer]]) is true, + // then throw a TypeError. + if (opts.allowShared === false && types.isSharedArrayBuffer(V.buffer)) { + throw webidl.errors.exception({ + header: 'ArrayBuffer', + message: 'SharedArrayBuffer is not allowed.' + }) + } + + // 3. If the conversion is not to an IDL type associated + // with the [AllowResizable] extended attribute, and + // IsResizableArrayBuffer(V.[[ViewedArrayBuffer]]) is + // true, then throw a TypeError. + // Note: resizable ArrayBuffers are currently a proposal + + // 4. Return the IDL DataView value that is a reference + // to the same object as V. + return V +} + +// https://webidl.spec.whatwg.org/#BufferSource +webidl.converters.BufferSource = function (V, opts = {}) { + if (types.isAnyArrayBuffer(V)) { + return webidl.converters.ArrayBuffer(V, opts) + } + + if (types.isTypedArray(V)) { + return webidl.converters.TypedArray(V, V.constructor) + } + + if (types.isDataView(V)) { + return webidl.converters.DataView(V, opts) + } + + throw new TypeError(`Could not convert ${V} to a BufferSource.`) +} + +webidl.converters['sequence'] = webidl.sequenceConverter( + webidl.converters.ByteString +) + +webidl.converters['sequence>'] = webidl.sequenceConverter( + webidl.converters['sequence'] +) + +webidl.converters['record'] = webidl.recordConverter( + webidl.converters.ByteString, + webidl.converters.ByteString +) + +module.exports = { + webidl +} + + +/***/ }), + +/***/ 40396: +/***/ ((module) => { + +"use strict"; + + +/** + * @see https://encoding.spec.whatwg.org/#concept-encoding-get + * @param {string|undefined} label + */ +function getEncoding (label) { + if (!label) { + return 'failure' + } + + // 1. Remove any leading and trailing ASCII whitespace from label. + // 2. If label is an ASCII case-insensitive match for any of the + // labels listed in the table below, then return the + // corresponding encoding; otherwise return failure. + switch (label.trim().toLowerCase()) { + case 'unicode-1-1-utf-8': + case 'unicode11utf8': + case 'unicode20utf8': + case 'utf-8': + case 'utf8': + case 'x-unicode20utf8': + return 'UTF-8' + case '866': + case 'cp866': + case 'csibm866': + case 'ibm866': + return 'IBM866' + case 'csisolatin2': + case 'iso-8859-2': + case 'iso-ir-101': + case 'iso8859-2': + case 'iso88592': + case 'iso_8859-2': + case 'iso_8859-2:1987': + case 'l2': + case 'latin2': + return 'ISO-8859-2' + case 'csisolatin3': + case 'iso-8859-3': + case 'iso-ir-109': + case 'iso8859-3': + case 'iso88593': + case 'iso_8859-3': + case 'iso_8859-3:1988': + case 'l3': + case 'latin3': + return 'ISO-8859-3' + case 'csisolatin4': + case 'iso-8859-4': + case 'iso-ir-110': + case 'iso8859-4': + case 'iso88594': + case 'iso_8859-4': + case 'iso_8859-4:1988': + case 'l4': + case 'latin4': + return 'ISO-8859-4' + case 'csisolatincyrillic': + case 'cyrillic': + case 'iso-8859-5': + case 'iso-ir-144': + case 'iso8859-5': + case 'iso88595': + case 'iso_8859-5': + case 'iso_8859-5:1988': + return 'ISO-8859-5' + case 'arabic': + case 'asmo-708': + case 'csiso88596e': + case 'csiso88596i': + case 'csisolatinarabic': + case 'ecma-114': + case 'iso-8859-6': + case 'iso-8859-6-e': + case 'iso-8859-6-i': + case 'iso-ir-127': + case 'iso8859-6': + case 'iso88596': + case 'iso_8859-6': + case 'iso_8859-6:1987': + return 'ISO-8859-6' + case 'csisolatingreek': + case 'ecma-118': + case 'elot_928': + case 'greek': + case 'greek8': + case 'iso-8859-7': + case 'iso-ir-126': + case 'iso8859-7': + case 'iso88597': + case 'iso_8859-7': + case 'iso_8859-7:1987': + case 'sun_eu_greek': + return 'ISO-8859-7' + case 'csiso88598e': + case 'csisolatinhebrew': + case 'hebrew': + case 'iso-8859-8': + case 'iso-8859-8-e': + case 'iso-ir-138': + case 'iso8859-8': + case 'iso88598': + case 'iso_8859-8': + case 'iso_8859-8:1988': + case 'visual': + return 'ISO-8859-8' + case 'csiso88598i': + case 'iso-8859-8-i': + case 'logical': + return 'ISO-8859-8-I' + case 'csisolatin6': + case 'iso-8859-10': + case 'iso-ir-157': + case 'iso8859-10': + case 'iso885910': + case 'l6': + case 'latin6': + return 'ISO-8859-10' + case 'iso-8859-13': + case 'iso8859-13': + case 'iso885913': + return 'ISO-8859-13' + case 'iso-8859-14': + case 'iso8859-14': + case 'iso885914': + return 'ISO-8859-14' + case 'csisolatin9': + case 'iso-8859-15': + case 'iso8859-15': + case 'iso885915': + case 'iso_8859-15': + case 'l9': + return 'ISO-8859-15' + case 'iso-8859-16': + return 'ISO-8859-16' + case 'cskoi8r': + case 'koi': + case 'koi8': + case 'koi8-r': + case 'koi8_r': + return 'KOI8-R' + case 'koi8-ru': + case 'koi8-u': + return 'KOI8-U' + case 'csmacintosh': + case 'mac': + case 'macintosh': + case 'x-mac-roman': + return 'macintosh' + case 'iso-8859-11': + case 'iso8859-11': + case 'iso885911': + case 'tis-620': + case 'windows-874': + return 'windows-874' + case 'cp1250': + case 'windows-1250': + case 'x-cp1250': + return 'windows-1250' + case 'cp1251': + case 'windows-1251': + case 'x-cp1251': + return 'windows-1251' + case 'ansi_x3.4-1968': + case 'ascii': + case 'cp1252': + case 'cp819': + case 'csisolatin1': + case 'ibm819': + case 'iso-8859-1': + case 'iso-ir-100': + case 'iso8859-1': + case 'iso88591': + case 'iso_8859-1': + case 'iso_8859-1:1987': + case 'l1': + case 'latin1': + case 'us-ascii': + case 'windows-1252': + case 'x-cp1252': + return 'windows-1252' + case 'cp1253': + case 'windows-1253': + case 'x-cp1253': + return 'windows-1253' + case 'cp1254': + case 'csisolatin5': + case 'iso-8859-9': + case 'iso-ir-148': + case 'iso8859-9': + case 'iso88599': + case 'iso_8859-9': + case 'iso_8859-9:1989': + case 'l5': + case 'latin5': + case 'windows-1254': + case 'x-cp1254': + return 'windows-1254' + case 'cp1255': + case 'windows-1255': + case 'x-cp1255': + return 'windows-1255' + case 'cp1256': + case 'windows-1256': + case 'x-cp1256': + return 'windows-1256' + case 'cp1257': + case 'windows-1257': + case 'x-cp1257': + return 'windows-1257' + case 'cp1258': + case 'windows-1258': + case 'x-cp1258': + return 'windows-1258' + case 'x-mac-cyrillic': + case 'x-mac-ukrainian': + return 'x-mac-cyrillic' + case 'chinese': + case 'csgb2312': + case 'csiso58gb231280': + case 'gb2312': + case 'gb_2312': + case 'gb_2312-80': + case 'gbk': + case 'iso-ir-58': + case 'x-gbk': + return 'GBK' + case 'gb18030': + return 'gb18030' + case 'big5': + case 'big5-hkscs': + case 'cn-big5': + case 'csbig5': + case 'x-x-big5': + return 'Big5' + case 'cseucpkdfmtjapanese': + case 'euc-jp': + case 'x-euc-jp': + return 'EUC-JP' + case 'csiso2022jp': + case 'iso-2022-jp': + return 'ISO-2022-JP' + case 'csshiftjis': + case 'ms932': + case 'ms_kanji': + case 'shift-jis': + case 'shift_jis': + case 'sjis': + case 'windows-31j': + case 'x-sjis': + return 'Shift_JIS' + case 'cseuckr': + case 'csksc56011987': + case 'euc-kr': + case 'iso-ir-149': + case 'korean': + case 'ks_c_5601-1987': + case 'ks_c_5601-1989': + case 'ksc5601': + case 'ksc_5601': + case 'windows-949': + return 'EUC-KR' + case 'csiso2022kr': + case 'hz-gb-2312': + case 'iso-2022-cn': + case 'iso-2022-cn-ext': + case 'iso-2022-kr': + case 'replacement': + return 'replacement' + case 'unicodefffe': + case 'utf-16be': + return 'UTF-16BE' + case 'csunicode': + case 'iso-10646-ucs-2': + case 'ucs-2': + case 'unicode': + case 'unicodefeff': + case 'utf-16': + case 'utf-16le': + return 'UTF-16LE' + case 'x-user-defined': + return 'x-user-defined' + default: return 'failure' + } +} + +module.exports = { + getEncoding +} + + +/***/ }), + +/***/ 82160: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const { + staticPropertyDescriptors, + readOperation, + fireAProgressEvent +} = __nccwpck_require__(10165) +const { + kState, + kError, + kResult, + kEvents, + kAborted +} = __nccwpck_require__(86812) +const { webidl } = __nccwpck_require__(74222) +const { kEnumerableProperty } = __nccwpck_require__(3440) + +class FileReader extends EventTarget { + constructor () { + super() + + this[kState] = 'empty' + this[kResult] = null + this[kError] = null + this[kEvents] = { + loadend: null, + error: null, + abort: null, + load: null, + progress: null, + loadstart: null + } + } + + /** + * @see https://w3c.github.io/FileAPI/#dfn-readAsArrayBuffer + * @param {import('buffer').Blob} blob + */ + readAsArrayBuffer (blob) { + webidl.brandCheck(this, FileReader) + + webidl.argumentLengthCheck(arguments, 1, { header: 'FileReader.readAsArrayBuffer' }) + + blob = webidl.converters.Blob(blob, { strict: false }) + + // The readAsArrayBuffer(blob) method, when invoked, + // must initiate a read operation for blob with ArrayBuffer. + readOperation(this, blob, 'ArrayBuffer') + } + + /** + * @see https://w3c.github.io/FileAPI/#readAsBinaryString + * @param {import('buffer').Blob} blob + */ + readAsBinaryString (blob) { + webidl.brandCheck(this, FileReader) + + webidl.argumentLengthCheck(arguments, 1, { header: 'FileReader.readAsBinaryString' }) + + blob = webidl.converters.Blob(blob, { strict: false }) + + // The readAsBinaryString(blob) method, when invoked, + // must initiate a read operation for blob with BinaryString. + readOperation(this, blob, 'BinaryString') + } + + /** + * @see https://w3c.github.io/FileAPI/#readAsDataText + * @param {import('buffer').Blob} blob + * @param {string?} encoding + */ + readAsText (blob, encoding = undefined) { + webidl.brandCheck(this, FileReader) + + webidl.argumentLengthCheck(arguments, 1, { header: 'FileReader.readAsText' }) + + blob = webidl.converters.Blob(blob, { strict: false }) + + if (encoding !== undefined) { + encoding = webidl.converters.DOMString(encoding) + } + + // The readAsText(blob, encoding) method, when invoked, + // must initiate a read operation for blob with Text and encoding. + readOperation(this, blob, 'Text', encoding) + } + + /** + * @see https://w3c.github.io/FileAPI/#dfn-readAsDataURL + * @param {import('buffer').Blob} blob + */ + readAsDataURL (blob) { + webidl.brandCheck(this, FileReader) + + webidl.argumentLengthCheck(arguments, 1, { header: 'FileReader.readAsDataURL' }) + + blob = webidl.converters.Blob(blob, { strict: false }) + + // The readAsDataURL(blob) method, when invoked, must + // initiate a read operation for blob with DataURL. + readOperation(this, blob, 'DataURL') + } + + /** + * @see https://w3c.github.io/FileAPI/#dfn-abort + */ + abort () { + // 1. If this's state is "empty" or if this's state is + // "done" set this's result to null and terminate + // this algorithm. + if (this[kState] === 'empty' || this[kState] === 'done') { + this[kResult] = null + return + } + + // 2. If this's state is "loading" set this's state to + // "done" and set this's result to null. + if (this[kState] === 'loading') { + this[kState] = 'done' + this[kResult] = null + } + + // 3. If there are any tasks from this on the file reading + // task source in an affiliated task queue, then remove + // those tasks from that task queue. + this[kAborted] = true + + // 4. Terminate the algorithm for the read method being processed. + // TODO + + // 5. Fire a progress event called abort at this. + fireAProgressEvent('abort', this) + + // 6. If this's state is not "loading", fire a progress + // event called loadend at this. + if (this[kState] !== 'loading') { + fireAProgressEvent('loadend', this) + } + } + + /** + * @see https://w3c.github.io/FileAPI/#dom-filereader-readystate + */ + get readyState () { + webidl.brandCheck(this, FileReader) + + switch (this[kState]) { + case 'empty': return this.EMPTY + case 'loading': return this.LOADING + case 'done': return this.DONE + } + } + + /** + * @see https://w3c.github.io/FileAPI/#dom-filereader-result + */ + get result () { + webidl.brandCheck(this, FileReader) + + // The result attribute’s getter, when invoked, must return + // this's result. + return this[kResult] + } + + /** + * @see https://w3c.github.io/FileAPI/#dom-filereader-error + */ + get error () { + webidl.brandCheck(this, FileReader) + + // The error attribute’s getter, when invoked, must return + // this's error. + return this[kError] + } + + get onloadend () { + webidl.brandCheck(this, FileReader) + + return this[kEvents].loadend + } + + set onloadend (fn) { + webidl.brandCheck(this, FileReader) + + if (this[kEvents].loadend) { + this.removeEventListener('loadend', this[kEvents].loadend) + } + + if (typeof fn === 'function') { + this[kEvents].loadend = fn + this.addEventListener('loadend', fn) + } else { + this[kEvents].loadend = null + } + } + + get onerror () { + webidl.brandCheck(this, FileReader) + + return this[kEvents].error + } + + set onerror (fn) { + webidl.brandCheck(this, FileReader) + + if (this[kEvents].error) { + this.removeEventListener('error', this[kEvents].error) + } + + if (typeof fn === 'function') { + this[kEvents].error = fn + this.addEventListener('error', fn) + } else { + this[kEvents].error = null + } + } + + get onloadstart () { + webidl.brandCheck(this, FileReader) + + return this[kEvents].loadstart + } + + set onloadstart (fn) { + webidl.brandCheck(this, FileReader) + + if (this[kEvents].loadstart) { + this.removeEventListener('loadstart', this[kEvents].loadstart) + } + + if (typeof fn === 'function') { + this[kEvents].loadstart = fn + this.addEventListener('loadstart', fn) + } else { + this[kEvents].loadstart = null + } + } + + get onprogress () { + webidl.brandCheck(this, FileReader) + + return this[kEvents].progress + } + + set onprogress (fn) { + webidl.brandCheck(this, FileReader) + + if (this[kEvents].progress) { + this.removeEventListener('progress', this[kEvents].progress) + } + + if (typeof fn === 'function') { + this[kEvents].progress = fn + this.addEventListener('progress', fn) + } else { + this[kEvents].progress = null + } + } + + get onload () { + webidl.brandCheck(this, FileReader) + + return this[kEvents].load + } + + set onload (fn) { + webidl.brandCheck(this, FileReader) + + if (this[kEvents].load) { + this.removeEventListener('load', this[kEvents].load) + } + + if (typeof fn === 'function') { + this[kEvents].load = fn + this.addEventListener('load', fn) + } else { + this[kEvents].load = null + } + } + + get onabort () { + webidl.brandCheck(this, FileReader) + + return this[kEvents].abort + } + + set onabort (fn) { + webidl.brandCheck(this, FileReader) + + if (this[kEvents].abort) { + this.removeEventListener('abort', this[kEvents].abort) + } + + if (typeof fn === 'function') { + this[kEvents].abort = fn + this.addEventListener('abort', fn) + } else { + this[kEvents].abort = null + } + } +} + +// https://w3c.github.io/FileAPI/#dom-filereader-empty +FileReader.EMPTY = FileReader.prototype.EMPTY = 0 +// https://w3c.github.io/FileAPI/#dom-filereader-loading +FileReader.LOADING = FileReader.prototype.LOADING = 1 +// https://w3c.github.io/FileAPI/#dom-filereader-done +FileReader.DONE = FileReader.prototype.DONE = 2 + +Object.defineProperties(FileReader.prototype, { + EMPTY: staticPropertyDescriptors, + LOADING: staticPropertyDescriptors, + DONE: staticPropertyDescriptors, + readAsArrayBuffer: kEnumerableProperty, + readAsBinaryString: kEnumerableProperty, + readAsText: kEnumerableProperty, + readAsDataURL: kEnumerableProperty, + abort: kEnumerableProperty, + readyState: kEnumerableProperty, + result: kEnumerableProperty, + error: kEnumerableProperty, + onloadstart: kEnumerableProperty, + onprogress: kEnumerableProperty, + onload: kEnumerableProperty, + onabort: kEnumerableProperty, + onerror: kEnumerableProperty, + onloadend: kEnumerableProperty, + [Symbol.toStringTag]: { + value: 'FileReader', + writable: false, + enumerable: false, + configurable: true + } +}) + +Object.defineProperties(FileReader, { + EMPTY: staticPropertyDescriptors, + LOADING: staticPropertyDescriptors, + DONE: staticPropertyDescriptors +}) + +module.exports = { + FileReader +} + + +/***/ }), + +/***/ 15976: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const { webidl } = __nccwpck_require__(74222) + +const kState = Symbol('ProgressEvent state') + +/** + * @see https://xhr.spec.whatwg.org/#progressevent + */ +class ProgressEvent extends Event { + constructor (type, eventInitDict = {}) { + type = webidl.converters.DOMString(type) + eventInitDict = webidl.converters.ProgressEventInit(eventInitDict ?? {}) + + super(type, eventInitDict) + + this[kState] = { + lengthComputable: eventInitDict.lengthComputable, + loaded: eventInitDict.loaded, + total: eventInitDict.total + } + } + + get lengthComputable () { + webidl.brandCheck(this, ProgressEvent) + + return this[kState].lengthComputable + } + + get loaded () { + webidl.brandCheck(this, ProgressEvent) + + return this[kState].loaded + } + + get total () { + webidl.brandCheck(this, ProgressEvent) + + return this[kState].total + } +} + +webidl.converters.ProgressEventInit = webidl.dictionaryConverter([ + { + key: 'lengthComputable', + converter: webidl.converters.boolean, + defaultValue: false + }, + { + key: 'loaded', + converter: webidl.converters['unsigned long long'], + defaultValue: 0 + }, + { + key: 'total', + converter: webidl.converters['unsigned long long'], + defaultValue: 0 + }, + { + key: 'bubbles', + converter: webidl.converters.boolean, + defaultValue: false + }, + { + key: 'cancelable', + converter: webidl.converters.boolean, + defaultValue: false + }, + { + key: 'composed', + converter: webidl.converters.boolean, + defaultValue: false + } +]) + +module.exports = { + ProgressEvent +} + + +/***/ }), + +/***/ 86812: +/***/ ((module) => { + +"use strict"; + + +module.exports = { + kState: Symbol('FileReader state'), + kResult: Symbol('FileReader result'), + kError: Symbol('FileReader error'), + kLastProgressEventFired: Symbol('FileReader last progress event fired timestamp'), + kEvents: Symbol('FileReader events'), + kAborted: Symbol('FileReader aborted') +} + + +/***/ }), + +/***/ 10165: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const { + kState, + kError, + kResult, + kAborted, + kLastProgressEventFired +} = __nccwpck_require__(86812) +const { ProgressEvent } = __nccwpck_require__(15976) +const { getEncoding } = __nccwpck_require__(40396) +const { DOMException } = __nccwpck_require__(87326) +const { serializeAMimeType, parseMIMEType } = __nccwpck_require__(94322) +const { types } = __nccwpck_require__(39023) +const { StringDecoder } = __nccwpck_require__(13193) +const { btoa } = __nccwpck_require__(20181) + +/** @type {PropertyDescriptor} */ +const staticPropertyDescriptors = { + enumerable: true, + writable: false, + configurable: false +} + +/** + * @see https://w3c.github.io/FileAPI/#readOperation + * @param {import('./filereader').FileReader} fr + * @param {import('buffer').Blob} blob + * @param {string} type + * @param {string?} encodingName + */ +function readOperation (fr, blob, type, encodingName) { + // 1. If fr’s state is "loading", throw an InvalidStateError + // DOMException. + if (fr[kState] === 'loading') { + throw new DOMException('Invalid state', 'InvalidStateError') + } + + // 2. Set fr’s state to "loading". + fr[kState] = 'loading' + + // 3. Set fr’s result to null. + fr[kResult] = null + + // 4. Set fr’s error to null. + fr[kError] = null + + // 5. Let stream be the result of calling get stream on blob. + /** @type {import('stream/web').ReadableStream} */ + const stream = blob.stream() + + // 6. Let reader be the result of getting a reader from stream. + const reader = stream.getReader() + + // 7. Let bytes be an empty byte sequence. + /** @type {Uint8Array[]} */ + const bytes = [] + + // 8. Let chunkPromise be the result of reading a chunk from + // stream with reader. + let chunkPromise = reader.read() + + // 9. Let isFirstChunk be true. + let isFirstChunk = true + + // 10. In parallel, while true: + // Note: "In parallel" just means non-blocking + // Note 2: readOperation itself cannot be async as double + // reading the body would then reject the promise, instead + // of throwing an error. + ;(async () => { + while (!fr[kAborted]) { + // 1. Wait for chunkPromise to be fulfilled or rejected. + try { + const { done, value } = await chunkPromise + + // 2. If chunkPromise is fulfilled, and isFirstChunk is + // true, queue a task to fire a progress event called + // loadstart at fr. + if (isFirstChunk && !fr[kAborted]) { + queueMicrotask(() => { + fireAProgressEvent('loadstart', fr) + }) + } + + // 3. Set isFirstChunk to false. + isFirstChunk = false + + // 4. If chunkPromise is fulfilled with an object whose + // done property is false and whose value property is + // a Uint8Array object, run these steps: + if (!done && types.isUint8Array(value)) { + // 1. Let bs be the byte sequence represented by the + // Uint8Array object. + + // 2. Append bs to bytes. + bytes.push(value) + + // 3. If roughly 50ms have passed since these steps + // were last invoked, queue a task to fire a + // progress event called progress at fr. + if ( + ( + fr[kLastProgressEventFired] === undefined || + Date.now() - fr[kLastProgressEventFired] >= 50 + ) && + !fr[kAborted] + ) { + fr[kLastProgressEventFired] = Date.now() + queueMicrotask(() => { + fireAProgressEvent('progress', fr) + }) + } + + // 4. Set chunkPromise to the result of reading a + // chunk from stream with reader. + chunkPromise = reader.read() + } else if (done) { + // 5. Otherwise, if chunkPromise is fulfilled with an + // object whose done property is true, queue a task + // to run the following steps and abort this algorithm: + queueMicrotask(() => { + // 1. Set fr’s state to "done". + fr[kState] = 'done' + + // 2. Let result be the result of package data given + // bytes, type, blob’s type, and encodingName. + try { + const result = packageData(bytes, type, blob.type, encodingName) + + // 4. Else: + + if (fr[kAborted]) { + return + } + + // 1. Set fr’s result to result. + fr[kResult] = result + + // 2. Fire a progress event called load at the fr. + fireAProgressEvent('load', fr) + } catch (error) { + // 3. If package data threw an exception error: + + // 1. Set fr’s error to error. + fr[kError] = error + + // 2. Fire a progress event called error at fr. + fireAProgressEvent('error', fr) + } + + // 5. If fr’s state is not "loading", fire a progress + // event called loadend at the fr. + if (fr[kState] !== 'loading') { + fireAProgressEvent('loadend', fr) + } + }) + + break + } + } catch (error) { + if (fr[kAborted]) { + return + } + + // 6. Otherwise, if chunkPromise is rejected with an + // error error, queue a task to run the following + // steps and abort this algorithm: + queueMicrotask(() => { + // 1. Set fr’s state to "done". + fr[kState] = 'done' + + // 2. Set fr’s error to error. + fr[kError] = error + + // 3. Fire a progress event called error at fr. + fireAProgressEvent('error', fr) + + // 4. If fr’s state is not "loading", fire a progress + // event called loadend at fr. + if (fr[kState] !== 'loading') { + fireAProgressEvent('loadend', fr) + } + }) + + break + } + } + })() +} + +/** + * @see https://w3c.github.io/FileAPI/#fire-a-progress-event + * @see https://dom.spec.whatwg.org/#concept-event-fire + * @param {string} e The name of the event + * @param {import('./filereader').FileReader} reader + */ +function fireAProgressEvent (e, reader) { + // The progress event e does not bubble. e.bubbles must be false + // The progress event e is NOT cancelable. e.cancelable must be false + const event = new ProgressEvent(e, { + bubbles: false, + cancelable: false + }) + + reader.dispatchEvent(event) +} + +/** + * @see https://w3c.github.io/FileAPI/#blob-package-data + * @param {Uint8Array[]} bytes + * @param {string} type + * @param {string?} mimeType + * @param {string?} encodingName + */ +function packageData (bytes, type, mimeType, encodingName) { + // 1. A Blob has an associated package data algorithm, given + // bytes, a type, a optional mimeType, and a optional + // encodingName, which switches on type and runs the + // associated steps: + + switch (type) { + case 'DataURL': { + // 1. Return bytes as a DataURL [RFC2397] subject to + // the considerations below: + // * Use mimeType as part of the Data URL if it is + // available in keeping with the Data URL + // specification [RFC2397]. + // * If mimeType is not available return a Data URL + // without a media-type. [RFC2397]. + + // https://datatracker.ietf.org/doc/html/rfc2397#section-3 + // dataurl := "data:" [ mediatype ] [ ";base64" ] "," data + // mediatype := [ type "/" subtype ] *( ";" parameter ) + // data := *urlchar + // parameter := attribute "=" value + let dataURL = 'data:' + + const parsed = parseMIMEType(mimeType || 'application/octet-stream') + + if (parsed !== 'failure') { + dataURL += serializeAMimeType(parsed) + } + + dataURL += ';base64,' + + const decoder = new StringDecoder('latin1') + + for (const chunk of bytes) { + dataURL += btoa(decoder.write(chunk)) + } + + dataURL += btoa(decoder.end()) + + return dataURL + } + case 'Text': { + // 1. Let encoding be failure + let encoding = 'failure' + + // 2. If the encodingName is present, set encoding to the + // result of getting an encoding from encodingName. + if (encodingName) { + encoding = getEncoding(encodingName) + } + + // 3. If encoding is failure, and mimeType is present: + if (encoding === 'failure' && mimeType) { + // 1. Let type be the result of parse a MIME type + // given mimeType. + const type = parseMIMEType(mimeType) + + // 2. If type is not failure, set encoding to the result + // of getting an encoding from type’s parameters["charset"]. + if (type !== 'failure') { + encoding = getEncoding(type.parameters.get('charset')) + } + } + + // 4. If encoding is failure, then set encoding to UTF-8. + if (encoding === 'failure') { + encoding = 'UTF-8' + } + + // 5. Decode bytes using fallback encoding encoding, and + // return the result. + return decode(bytes, encoding) + } + case 'ArrayBuffer': { + // Return a new ArrayBuffer whose contents are bytes. + const sequence = combineByteSequences(bytes) + + return sequence.buffer + } + case 'BinaryString': { + // Return bytes as a binary string, in which every byte + // is represented by a code unit of equal value [0..255]. + let binaryString = '' + + const decoder = new StringDecoder('latin1') + + for (const chunk of bytes) { + binaryString += decoder.write(chunk) + } + + binaryString += decoder.end() + + return binaryString + } + } +} + +/** + * @see https://encoding.spec.whatwg.org/#decode + * @param {Uint8Array[]} ioQueue + * @param {string} encoding + */ +function decode (ioQueue, encoding) { + const bytes = combineByteSequences(ioQueue) + + // 1. Let BOMEncoding be the result of BOM sniffing ioQueue. + const BOMEncoding = BOMSniffing(bytes) + + let slice = 0 + + // 2. If BOMEncoding is non-null: + if (BOMEncoding !== null) { + // 1. Set encoding to BOMEncoding. + encoding = BOMEncoding + + // 2. Read three bytes from ioQueue, if BOMEncoding is + // UTF-8; otherwise read two bytes. + // (Do nothing with those bytes.) + slice = BOMEncoding === 'UTF-8' ? 3 : 2 + } + + // 3. Process a queue with an instance of encoding’s + // decoder, ioQueue, output, and "replacement". + + // 4. Return output. + + const sliced = bytes.slice(slice) + return new TextDecoder(encoding).decode(sliced) +} + +/** + * @see https://encoding.spec.whatwg.org/#bom-sniff + * @param {Uint8Array} ioQueue + */ +function BOMSniffing (ioQueue) { + // 1. Let BOM be the result of peeking 3 bytes from ioQueue, + // converted to a byte sequence. + const [a, b, c] = ioQueue + + // 2. For each of the rows in the table below, starting with + // the first one and going down, if BOM starts with the + // bytes given in the first column, then return the + // encoding given in the cell in the second column of that + // row. Otherwise, return null. + if (a === 0xEF && b === 0xBB && c === 0xBF) { + return 'UTF-8' + } else if (a === 0xFE && b === 0xFF) { + return 'UTF-16BE' + } else if (a === 0xFF && b === 0xFE) { + return 'UTF-16LE' + } + + return null +} + +/** + * @param {Uint8Array[]} sequences + */ +function combineByteSequences (sequences) { + const size = sequences.reduce((a, b) => { + return a + b.byteLength + }, 0) + + let offset = 0 + + return sequences.reduce((a, b) => { + a.set(b, offset) + offset += b.byteLength + return a + }, new Uint8Array(size)) +} + +module.exports = { + staticPropertyDescriptors, + readOperation, + fireAProgressEvent +} + + +/***/ }), + +/***/ 32581: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +// We include a version number for the Dispatcher API. In case of breaking changes, +// this version number must be increased to avoid conflicts. +const globalDispatcher = Symbol.for('undici.globalDispatcher.1') +const { InvalidArgumentError } = __nccwpck_require__(68707) +const Agent = __nccwpck_require__(59965) + +if (getGlobalDispatcher() === undefined) { + setGlobalDispatcher(new Agent()) +} + +function setGlobalDispatcher (agent) { + if (!agent || typeof agent.dispatch !== 'function') { + throw new InvalidArgumentError('Argument agent must implement Agent') + } + Object.defineProperty(globalThis, globalDispatcher, { + value: agent, + writable: true, + enumerable: false, + configurable: false + }) +} + +function getGlobalDispatcher () { + return globalThis[globalDispatcher] +} + +module.exports = { + setGlobalDispatcher, + getGlobalDispatcher +} + + +/***/ }), + +/***/ 78840: +/***/ ((module) => { + +"use strict"; + + +module.exports = class DecoratorHandler { + constructor (handler) { + this.handler = handler + } + + onConnect (...args) { + return this.handler.onConnect(...args) + } + + onError (...args) { + return this.handler.onError(...args) + } + + onUpgrade (...args) { + return this.handler.onUpgrade(...args) + } + + onHeaders (...args) { + return this.handler.onHeaders(...args) + } + + onData (...args) { + return this.handler.onData(...args) + } + + onComplete (...args) { + return this.handler.onComplete(...args) + } + + onBodySent (...args) { + return this.handler.onBodySent(...args) + } +} + + +/***/ }), + +/***/ 48299: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const util = __nccwpck_require__(3440) +const { kBodyUsed } = __nccwpck_require__(36443) +const assert = __nccwpck_require__(42613) +const { InvalidArgumentError } = __nccwpck_require__(68707) +const EE = __nccwpck_require__(24434) + +const redirectableStatusCodes = [300, 301, 302, 303, 307, 308] + +const kBody = Symbol('body') + +class BodyAsyncIterable { + constructor (body) { + this[kBody] = body + this[kBodyUsed] = false + } + + async * [Symbol.asyncIterator] () { + assert(!this[kBodyUsed], 'disturbed') + this[kBodyUsed] = true + yield * this[kBody] + } +} + +class RedirectHandler { + constructor (dispatch, maxRedirections, opts, handler) { + if (maxRedirections != null && (!Number.isInteger(maxRedirections) || maxRedirections < 0)) { + throw new InvalidArgumentError('maxRedirections must be a positive number') + } + + util.validateHandler(handler, opts.method, opts.upgrade) + + this.dispatch = dispatch + this.location = null + this.abort = null + this.opts = { ...opts, maxRedirections: 0 } // opts must be a copy + this.maxRedirections = maxRedirections + this.handler = handler + this.history = [] + + if (util.isStream(this.opts.body)) { + // TODO (fix): Provide some way for the user to cache the file to e.g. /tmp + // so that it can be dispatched again? + // TODO (fix): Do we need 100-expect support to provide a way to do this properly? + if (util.bodyLength(this.opts.body) === 0) { + this.opts.body + .on('data', function () { + assert(false) + }) + } + + if (typeof this.opts.body.readableDidRead !== 'boolean') { + this.opts.body[kBodyUsed] = false + EE.prototype.on.call(this.opts.body, 'data', function () { + this[kBodyUsed] = true + }) + } + } else if (this.opts.body && typeof this.opts.body.pipeTo === 'function') { + // TODO (fix): We can't access ReadableStream internal state + // to determine whether or not it has been disturbed. This is just + // a workaround. + this.opts.body = new BodyAsyncIterable(this.opts.body) + } else if ( + this.opts.body && + typeof this.opts.body !== 'string' && + !ArrayBuffer.isView(this.opts.body) && + util.isIterable(this.opts.body) + ) { + // TODO: Should we allow re-using iterable if !this.opts.idempotent + // or through some other flag? + this.opts.body = new BodyAsyncIterable(this.opts.body) + } + } + + onConnect (abort) { + this.abort = abort + this.handler.onConnect(abort, { history: this.history }) + } + + onUpgrade (statusCode, headers, socket) { + this.handler.onUpgrade(statusCode, headers, socket) + } + + onError (error) { + this.handler.onError(error) + } + + onHeaders (statusCode, headers, resume, statusText) { + this.location = this.history.length >= this.maxRedirections || util.isDisturbed(this.opts.body) + ? null + : parseLocation(statusCode, headers) + + if (this.opts.origin) { + this.history.push(new URL(this.opts.path, this.opts.origin)) + } + + if (!this.location) { + return this.handler.onHeaders(statusCode, headers, resume, statusText) + } + + const { origin, pathname, search } = util.parseURL(new URL(this.location, this.opts.origin && new URL(this.opts.path, this.opts.origin))) + const path = search ? `${pathname}${search}` : pathname + + // Remove headers referring to the original URL. + // By default it is Host only, unless it's a 303 (see below), which removes also all Content-* headers. + // https://tools.ietf.org/html/rfc7231#section-6.4 + this.opts.headers = cleanRequestHeaders(this.opts.headers, statusCode === 303, this.opts.origin !== origin) + this.opts.path = path + this.opts.origin = origin + this.opts.maxRedirections = 0 + this.opts.query = null + + // https://tools.ietf.org/html/rfc7231#section-6.4.4 + // In case of HTTP 303, always replace method to be either HEAD or GET + if (statusCode === 303 && this.opts.method !== 'HEAD') { + this.opts.method = 'GET' + this.opts.body = null + } + } + + onData (chunk) { + if (this.location) { + /* + https://tools.ietf.org/html/rfc7231#section-6.4 + + TLDR: undici always ignores 3xx response bodies. + + Redirection is used to serve the requested resource from another URL, so it is assumes that + no body is generated (and thus can be ignored). Even though generating a body is not prohibited. + + For status 301, 302, 303, 307 and 308 (the latter from RFC 7238), the specs mention that the body usually + (which means it's optional and not mandated) contain just an hyperlink to the value of + the Location response header, so the body can be ignored safely. + + For status 300, which is "Multiple Choices", the spec mentions both generating a Location + response header AND a response body with the other possible location to follow. + Since the spec explicitily chooses not to specify a format for such body and leave it to + servers and browsers implementors, we ignore the body as there is no specified way to eventually parse it. + */ + } else { + return this.handler.onData(chunk) + } + } + + onComplete (trailers) { + if (this.location) { + /* + https://tools.ietf.org/html/rfc7231#section-6.4 + + TLDR: undici always ignores 3xx response trailers as they are not expected in case of redirections + and neither are useful if present. + + See comment on onData method above for more detailed informations. + */ + + this.location = null + this.abort = null + + this.dispatch(this.opts, this) + } else { + this.handler.onComplete(trailers) + } + } + + onBodySent (chunk) { + if (this.handler.onBodySent) { + this.handler.onBodySent(chunk) + } + } +} + +function parseLocation (statusCode, headers) { + if (redirectableStatusCodes.indexOf(statusCode) === -1) { + return null + } + + for (let i = 0; i < headers.length; i += 2) { + if (headers[i].toString().toLowerCase() === 'location') { + return headers[i + 1] + } + } +} + +// https://tools.ietf.org/html/rfc7231#section-6.4.4 +function shouldRemoveHeader (header, removeContent, unknownOrigin) { + if (header.length === 4) { + return util.headerNameToString(header) === 'host' + } + if (removeContent && util.headerNameToString(header).startsWith('content-')) { + return true + } + if (unknownOrigin && (header.length === 13 || header.length === 6 || header.length === 19)) { + const name = util.headerNameToString(header) + return name === 'authorization' || name === 'cookie' || name === 'proxy-authorization' + } + return false +} + +// https://tools.ietf.org/html/rfc7231#section-6.4 +function cleanRequestHeaders (headers, removeContent, unknownOrigin) { + const ret = [] + if (Array.isArray(headers)) { + for (let i = 0; i < headers.length; i += 2) { + if (!shouldRemoveHeader(headers[i], removeContent, unknownOrigin)) { + ret.push(headers[i], headers[i + 1]) + } + } + } else if (headers && typeof headers === 'object') { + for (const key of Object.keys(headers)) { + if (!shouldRemoveHeader(key, removeContent, unknownOrigin)) { + ret.push(key, headers[key]) + } + } + } else { + assert(headers == null, 'headers must be an object or an array') + } + return ret +} + +module.exports = RedirectHandler + + +/***/ }), + +/***/ 53573: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +const assert = __nccwpck_require__(42613) + +const { kRetryHandlerDefaultRetry } = __nccwpck_require__(36443) +const { RequestRetryError } = __nccwpck_require__(68707) +const { isDisturbed, parseHeaders, parseRangeHeader } = __nccwpck_require__(3440) + +function calculateRetryAfterHeader (retryAfter) { + const current = Date.now() + const diff = new Date(retryAfter).getTime() - current + + return diff +} + +class RetryHandler { + constructor (opts, handlers) { + const { retryOptions, ...dispatchOpts } = opts + const { + // Retry scoped + retry: retryFn, + maxRetries, + maxTimeout, + minTimeout, + timeoutFactor, + // Response scoped + methods, + errorCodes, + retryAfter, + statusCodes + } = retryOptions ?? {} + + this.dispatch = handlers.dispatch + this.handler = handlers.handler + this.opts = dispatchOpts + this.abort = null + this.aborted = false + this.retryOpts = { + retry: retryFn ?? RetryHandler[kRetryHandlerDefaultRetry], + retryAfter: retryAfter ?? true, + maxTimeout: maxTimeout ?? 30 * 1000, // 30s, + timeout: minTimeout ?? 500, // .5s + timeoutFactor: timeoutFactor ?? 2, + maxRetries: maxRetries ?? 5, + // What errors we should retry + methods: methods ?? ['GET', 'HEAD', 'OPTIONS', 'PUT', 'DELETE', 'TRACE'], + // Indicates which errors to retry + statusCodes: statusCodes ?? [500, 502, 503, 504, 429], + // List of errors to retry + errorCodes: errorCodes ?? [ + 'ECONNRESET', + 'ECONNREFUSED', + 'ENOTFOUND', + 'ENETDOWN', + 'ENETUNREACH', + 'EHOSTDOWN', + 'EHOSTUNREACH', + 'EPIPE' + ] + } + + this.retryCount = 0 + this.start = 0 + this.end = null + this.etag = null + this.resume = null + + // Handle possible onConnect duplication + this.handler.onConnect(reason => { + this.aborted = true + if (this.abort) { + this.abort(reason) + } else { + this.reason = reason + } + }) + } + + onRequestSent () { + if (this.handler.onRequestSent) { + this.handler.onRequestSent() + } + } + + onUpgrade (statusCode, headers, socket) { + if (this.handler.onUpgrade) { + this.handler.onUpgrade(statusCode, headers, socket) + } + } + + onConnect (abort) { + if (this.aborted) { + abort(this.reason) + } else { + this.abort = abort + } + } + + onBodySent (chunk) { + if (this.handler.onBodySent) return this.handler.onBodySent(chunk) + } + + static [kRetryHandlerDefaultRetry] (err, { state, opts }, cb) { + const { statusCode, code, headers } = err + const { method, retryOptions } = opts + const { + maxRetries, + timeout, + maxTimeout, + timeoutFactor, + statusCodes, + errorCodes, + methods + } = retryOptions + let { counter, currentTimeout } = state + + currentTimeout = + currentTimeout != null && currentTimeout > 0 ? currentTimeout : timeout + + // Any code that is not a Undici's originated and allowed to retry + if ( + code && + code !== 'UND_ERR_REQ_RETRY' && + code !== 'UND_ERR_SOCKET' && + !errorCodes.includes(code) + ) { + cb(err) + return + } + + // If a set of method are provided and the current method is not in the list + if (Array.isArray(methods) && !methods.includes(method)) { + cb(err) + return + } + + // If a set of status code are provided and the current status code is not in the list + if ( + statusCode != null && + Array.isArray(statusCodes) && + !statusCodes.includes(statusCode) + ) { + cb(err) + return + } + + // If we reached the max number of retries + if (counter > maxRetries) { + cb(err) + return + } + + let retryAfterHeader = headers != null && headers['retry-after'] + if (retryAfterHeader) { + retryAfterHeader = Number(retryAfterHeader) + retryAfterHeader = isNaN(retryAfterHeader) + ? calculateRetryAfterHeader(retryAfterHeader) + : retryAfterHeader * 1e3 // Retry-After is in seconds + } + + const retryTimeout = + retryAfterHeader > 0 + ? Math.min(retryAfterHeader, maxTimeout) + : Math.min(currentTimeout * timeoutFactor ** counter, maxTimeout) + + state.currentTimeout = retryTimeout + + setTimeout(() => cb(null), retryTimeout) + } + + onHeaders (statusCode, rawHeaders, resume, statusMessage) { + const headers = parseHeaders(rawHeaders) + + this.retryCount += 1 + + if (statusCode >= 300) { + this.abort( + new RequestRetryError('Request failed', statusCode, { + headers, + count: this.retryCount + }) + ) + return false + } + + // Checkpoint for resume from where we left it + if (this.resume != null) { + this.resume = null + + if (statusCode !== 206) { + return true + } + + const contentRange = parseRangeHeader(headers['content-range']) + // If no content range + if (!contentRange) { + this.abort( + new RequestRetryError('Content-Range mismatch', statusCode, { + headers, + count: this.retryCount + }) + ) + return false + } + + // Let's start with a weak etag check + if (this.etag != null && this.etag !== headers.etag) { + this.abort( + new RequestRetryError('ETag mismatch', statusCode, { + headers, + count: this.retryCount + }) + ) + return false + } + + const { start, size, end = size } = contentRange + + assert(this.start === start, 'content-range mismatch') + assert(this.end == null || this.end === end, 'content-range mismatch') + + this.resume = resume + return true + } + + if (this.end == null) { + if (statusCode === 206) { + // First time we receive 206 + const range = parseRangeHeader(headers['content-range']) + + if (range == null) { + return this.handler.onHeaders( + statusCode, + rawHeaders, + resume, + statusMessage + ) + } + + const { start, size, end = size } = range + + assert( + start != null && Number.isFinite(start) && this.start !== start, + 'content-range mismatch' + ) + assert(Number.isFinite(start)) + assert( + end != null && Number.isFinite(end) && this.end !== end, + 'invalid content-length' + ) + + this.start = start + this.end = end + } + + // We make our best to checkpoint the body for further range headers + if (this.end == null) { + const contentLength = headers['content-length'] + this.end = contentLength != null ? Number(contentLength) : null + } + + assert(Number.isFinite(this.start)) + assert( + this.end == null || Number.isFinite(this.end), + 'invalid content-length' + ) + + this.resume = resume + this.etag = headers.etag != null ? headers.etag : null + + return this.handler.onHeaders( + statusCode, + rawHeaders, + resume, + statusMessage + ) + } + + const err = new RequestRetryError('Request failed', statusCode, { + headers, + count: this.retryCount + }) + + this.abort(err) + + return false + } + + onData (chunk) { + this.start += chunk.length + + return this.handler.onData(chunk) + } + + onComplete (rawTrailers) { + this.retryCount = 0 + return this.handler.onComplete(rawTrailers) + } + + onError (err) { + if (this.aborted || isDisturbed(this.opts.body)) { + return this.handler.onError(err) + } + + this.retryOpts.retry( + err, + { + state: { counter: this.retryCount++, currentTimeout: this.retryAfter }, + opts: { retryOptions: this.retryOpts, ...this.opts } + }, + onRetry.bind(this) + ) + + function onRetry (err) { + if (err != null || this.aborted || isDisturbed(this.opts.body)) { + return this.handler.onError(err) + } + + if (this.start !== 0) { + this.opts = { + ...this.opts, + headers: { + ...this.opts.headers, + range: `bytes=${this.start}-${this.end ?? ''}` + } + } + } + + try { + this.dispatch(this.opts, this) + } catch (err) { + this.handler.onError(err) + } + } + } +} + +module.exports = RetryHandler + + +/***/ }), + +/***/ 64415: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const RedirectHandler = __nccwpck_require__(48299) + +function createRedirectInterceptor ({ maxRedirections: defaultMaxRedirections }) { + return (dispatch) => { + return function Intercept (opts, handler) { + const { maxRedirections = defaultMaxRedirections } = opts + + if (!maxRedirections) { + return dispatch(opts, handler) + } + + const redirectHandler = new RedirectHandler(dispatch, maxRedirections, opts, handler) + opts = { ...opts, maxRedirections: 0 } // Stop sub dispatcher from also redirecting. + return dispatch(opts, redirectHandler) + } + } +} + +module.exports = createRedirectInterceptor + + +/***/ }), + +/***/ 52824: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.SPECIAL_HEADERS = exports.HEADER_STATE = exports.MINOR = exports.MAJOR = exports.CONNECTION_TOKEN_CHARS = exports.HEADER_CHARS = exports.TOKEN = exports.STRICT_TOKEN = exports.HEX = exports.URL_CHAR = exports.STRICT_URL_CHAR = exports.USERINFO_CHARS = exports.MARK = exports.ALPHANUM = exports.NUM = exports.HEX_MAP = exports.NUM_MAP = exports.ALPHA = exports.FINISH = exports.H_METHOD_MAP = exports.METHOD_MAP = exports.METHODS_RTSP = exports.METHODS_ICE = exports.METHODS_HTTP = exports.METHODS = exports.LENIENT_FLAGS = exports.FLAGS = exports.TYPE = exports.ERROR = void 0; +const utils_1 = __nccwpck_require__(50172); +// C headers +var ERROR; +(function (ERROR) { + ERROR[ERROR["OK"] = 0] = "OK"; + ERROR[ERROR["INTERNAL"] = 1] = "INTERNAL"; + ERROR[ERROR["STRICT"] = 2] = "STRICT"; + ERROR[ERROR["LF_EXPECTED"] = 3] = "LF_EXPECTED"; + ERROR[ERROR["UNEXPECTED_CONTENT_LENGTH"] = 4] = "UNEXPECTED_CONTENT_LENGTH"; + ERROR[ERROR["CLOSED_CONNECTION"] = 5] = "CLOSED_CONNECTION"; + ERROR[ERROR["INVALID_METHOD"] = 6] = "INVALID_METHOD"; + ERROR[ERROR["INVALID_URL"] = 7] = "INVALID_URL"; + ERROR[ERROR["INVALID_CONSTANT"] = 8] = "INVALID_CONSTANT"; + ERROR[ERROR["INVALID_VERSION"] = 9] = "INVALID_VERSION"; + ERROR[ERROR["INVALID_HEADER_TOKEN"] = 10] = "INVALID_HEADER_TOKEN"; + ERROR[ERROR["INVALID_CONTENT_LENGTH"] = 11] = "INVALID_CONTENT_LENGTH"; + ERROR[ERROR["INVALID_CHUNK_SIZE"] = 12] = "INVALID_CHUNK_SIZE"; + ERROR[ERROR["INVALID_STATUS"] = 13] = "INVALID_STATUS"; + ERROR[ERROR["INVALID_EOF_STATE"] = 14] = "INVALID_EOF_STATE"; + ERROR[ERROR["INVALID_TRANSFER_ENCODING"] = 15] = "INVALID_TRANSFER_ENCODING"; + ERROR[ERROR["CB_MESSAGE_BEGIN"] = 16] = "CB_MESSAGE_BEGIN"; + ERROR[ERROR["CB_HEADERS_COMPLETE"] = 17] = "CB_HEADERS_COMPLETE"; + ERROR[ERROR["CB_MESSAGE_COMPLETE"] = 18] = "CB_MESSAGE_COMPLETE"; + ERROR[ERROR["CB_CHUNK_HEADER"] = 19] = "CB_CHUNK_HEADER"; + ERROR[ERROR["CB_CHUNK_COMPLETE"] = 20] = "CB_CHUNK_COMPLETE"; + ERROR[ERROR["PAUSED"] = 21] = "PAUSED"; + ERROR[ERROR["PAUSED_UPGRADE"] = 22] = "PAUSED_UPGRADE"; + ERROR[ERROR["PAUSED_H2_UPGRADE"] = 23] = "PAUSED_H2_UPGRADE"; + ERROR[ERROR["USER"] = 24] = "USER"; +})(ERROR = exports.ERROR || (exports.ERROR = {})); +var TYPE; +(function (TYPE) { + TYPE[TYPE["BOTH"] = 0] = "BOTH"; + TYPE[TYPE["REQUEST"] = 1] = "REQUEST"; + TYPE[TYPE["RESPONSE"] = 2] = "RESPONSE"; +})(TYPE = exports.TYPE || (exports.TYPE = {})); +var FLAGS; +(function (FLAGS) { + FLAGS[FLAGS["CONNECTION_KEEP_ALIVE"] = 1] = "CONNECTION_KEEP_ALIVE"; + FLAGS[FLAGS["CONNECTION_CLOSE"] = 2] = "CONNECTION_CLOSE"; + FLAGS[FLAGS["CONNECTION_UPGRADE"] = 4] = "CONNECTION_UPGRADE"; + FLAGS[FLAGS["CHUNKED"] = 8] = "CHUNKED"; + FLAGS[FLAGS["UPGRADE"] = 16] = "UPGRADE"; + FLAGS[FLAGS["CONTENT_LENGTH"] = 32] = "CONTENT_LENGTH"; + FLAGS[FLAGS["SKIPBODY"] = 64] = "SKIPBODY"; + FLAGS[FLAGS["TRAILING"] = 128] = "TRAILING"; + // 1 << 8 is unused + FLAGS[FLAGS["TRANSFER_ENCODING"] = 512] = "TRANSFER_ENCODING"; +})(FLAGS = exports.FLAGS || (exports.FLAGS = {})); +var LENIENT_FLAGS; +(function (LENIENT_FLAGS) { + LENIENT_FLAGS[LENIENT_FLAGS["HEADERS"] = 1] = "HEADERS"; + LENIENT_FLAGS[LENIENT_FLAGS["CHUNKED_LENGTH"] = 2] = "CHUNKED_LENGTH"; + LENIENT_FLAGS[LENIENT_FLAGS["KEEP_ALIVE"] = 4] = "KEEP_ALIVE"; +})(LENIENT_FLAGS = exports.LENIENT_FLAGS || (exports.LENIENT_FLAGS = {})); +var METHODS; +(function (METHODS) { + METHODS[METHODS["DELETE"] = 0] = "DELETE"; + METHODS[METHODS["GET"] = 1] = "GET"; + METHODS[METHODS["HEAD"] = 2] = "HEAD"; + METHODS[METHODS["POST"] = 3] = "POST"; + METHODS[METHODS["PUT"] = 4] = "PUT"; + /* pathological */ + METHODS[METHODS["CONNECT"] = 5] = "CONNECT"; + METHODS[METHODS["OPTIONS"] = 6] = "OPTIONS"; + METHODS[METHODS["TRACE"] = 7] = "TRACE"; + /* WebDAV */ + METHODS[METHODS["COPY"] = 8] = "COPY"; + METHODS[METHODS["LOCK"] = 9] = "LOCK"; + METHODS[METHODS["MKCOL"] = 10] = "MKCOL"; + METHODS[METHODS["MOVE"] = 11] = "MOVE"; + METHODS[METHODS["PROPFIND"] = 12] = "PROPFIND"; + METHODS[METHODS["PROPPATCH"] = 13] = "PROPPATCH"; + METHODS[METHODS["SEARCH"] = 14] = "SEARCH"; + METHODS[METHODS["UNLOCK"] = 15] = "UNLOCK"; + METHODS[METHODS["BIND"] = 16] = "BIND"; + METHODS[METHODS["REBIND"] = 17] = "REBIND"; + METHODS[METHODS["UNBIND"] = 18] = "UNBIND"; + METHODS[METHODS["ACL"] = 19] = "ACL"; + /* subversion */ + METHODS[METHODS["REPORT"] = 20] = "REPORT"; + METHODS[METHODS["MKACTIVITY"] = 21] = "MKACTIVITY"; + METHODS[METHODS["CHECKOUT"] = 22] = "CHECKOUT"; + METHODS[METHODS["MERGE"] = 23] = "MERGE"; + /* upnp */ + METHODS[METHODS["M-SEARCH"] = 24] = "M-SEARCH"; + METHODS[METHODS["NOTIFY"] = 25] = "NOTIFY"; + METHODS[METHODS["SUBSCRIBE"] = 26] = "SUBSCRIBE"; + METHODS[METHODS["UNSUBSCRIBE"] = 27] = "UNSUBSCRIBE"; + /* RFC-5789 */ + METHODS[METHODS["PATCH"] = 28] = "PATCH"; + METHODS[METHODS["PURGE"] = 29] = "PURGE"; + /* CalDAV */ + METHODS[METHODS["MKCALENDAR"] = 30] = "MKCALENDAR"; + /* RFC-2068, section 19.6.1.2 */ + METHODS[METHODS["LINK"] = 31] = "LINK"; + METHODS[METHODS["UNLINK"] = 32] = "UNLINK"; + /* icecast */ + METHODS[METHODS["SOURCE"] = 33] = "SOURCE"; + /* RFC-7540, section 11.6 */ + METHODS[METHODS["PRI"] = 34] = "PRI"; + /* RFC-2326 RTSP */ + METHODS[METHODS["DESCRIBE"] = 35] = "DESCRIBE"; + METHODS[METHODS["ANNOUNCE"] = 36] = "ANNOUNCE"; + METHODS[METHODS["SETUP"] = 37] = "SETUP"; + METHODS[METHODS["PLAY"] = 38] = "PLAY"; + METHODS[METHODS["PAUSE"] = 39] = "PAUSE"; + METHODS[METHODS["TEARDOWN"] = 40] = "TEARDOWN"; + METHODS[METHODS["GET_PARAMETER"] = 41] = "GET_PARAMETER"; + METHODS[METHODS["SET_PARAMETER"] = 42] = "SET_PARAMETER"; + METHODS[METHODS["REDIRECT"] = 43] = "REDIRECT"; + METHODS[METHODS["RECORD"] = 44] = "RECORD"; + /* RAOP */ + METHODS[METHODS["FLUSH"] = 45] = "FLUSH"; +})(METHODS = exports.METHODS || (exports.METHODS = {})); +exports.METHODS_HTTP = [ + METHODS.DELETE, + METHODS.GET, + METHODS.HEAD, + METHODS.POST, + METHODS.PUT, + METHODS.CONNECT, + METHODS.OPTIONS, + METHODS.TRACE, + METHODS.COPY, + METHODS.LOCK, + METHODS.MKCOL, + METHODS.MOVE, + METHODS.PROPFIND, + METHODS.PROPPATCH, + METHODS.SEARCH, + METHODS.UNLOCK, + METHODS.BIND, + METHODS.REBIND, + METHODS.UNBIND, + METHODS.ACL, + METHODS.REPORT, + METHODS.MKACTIVITY, + METHODS.CHECKOUT, + METHODS.MERGE, + METHODS['M-SEARCH'], + METHODS.NOTIFY, + METHODS.SUBSCRIBE, + METHODS.UNSUBSCRIBE, + METHODS.PATCH, + METHODS.PURGE, + METHODS.MKCALENDAR, + METHODS.LINK, + METHODS.UNLINK, + METHODS.PRI, + // TODO(indutny): should we allow it with HTTP? + METHODS.SOURCE, +]; +exports.METHODS_ICE = [ + METHODS.SOURCE, +]; +exports.METHODS_RTSP = [ + METHODS.OPTIONS, + METHODS.DESCRIBE, + METHODS.ANNOUNCE, + METHODS.SETUP, + METHODS.PLAY, + METHODS.PAUSE, + METHODS.TEARDOWN, + METHODS.GET_PARAMETER, + METHODS.SET_PARAMETER, + METHODS.REDIRECT, + METHODS.RECORD, + METHODS.FLUSH, + // For AirPlay + METHODS.GET, + METHODS.POST, +]; +exports.METHOD_MAP = utils_1.enumToMap(METHODS); +exports.H_METHOD_MAP = {}; +Object.keys(exports.METHOD_MAP).forEach((key) => { + if (/^H/.test(key)) { + exports.H_METHOD_MAP[key] = exports.METHOD_MAP[key]; + } +}); +var FINISH; +(function (FINISH) { + FINISH[FINISH["SAFE"] = 0] = "SAFE"; + FINISH[FINISH["SAFE_WITH_CB"] = 1] = "SAFE_WITH_CB"; + FINISH[FINISH["UNSAFE"] = 2] = "UNSAFE"; +})(FINISH = exports.FINISH || (exports.FINISH = {})); +exports.ALPHA = []; +for (let i = 'A'.charCodeAt(0); i <= 'Z'.charCodeAt(0); i++) { + // Upper case + exports.ALPHA.push(String.fromCharCode(i)); + // Lower case + exports.ALPHA.push(String.fromCharCode(i + 0x20)); +} +exports.NUM_MAP = { + 0: 0, 1: 1, 2: 2, 3: 3, 4: 4, + 5: 5, 6: 6, 7: 7, 8: 8, 9: 9, +}; +exports.HEX_MAP = { + 0: 0, 1: 1, 2: 2, 3: 3, 4: 4, + 5: 5, 6: 6, 7: 7, 8: 8, 9: 9, + A: 0XA, B: 0XB, C: 0XC, D: 0XD, E: 0XE, F: 0XF, + a: 0xa, b: 0xb, c: 0xc, d: 0xd, e: 0xe, f: 0xf, +}; +exports.NUM = [ + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', +]; +exports.ALPHANUM = exports.ALPHA.concat(exports.NUM); +exports.MARK = ['-', '_', '.', '!', '~', '*', '\'', '(', ')']; +exports.USERINFO_CHARS = exports.ALPHANUM + .concat(exports.MARK) + .concat(['%', ';', ':', '&', '=', '+', '$', ',']); +// TODO(indutny): use RFC +exports.STRICT_URL_CHAR = [ + '!', '"', '$', '%', '&', '\'', + '(', ')', '*', '+', ',', '-', '.', '/', + ':', ';', '<', '=', '>', + '@', '[', '\\', ']', '^', '_', + '`', + '{', '|', '}', '~', +].concat(exports.ALPHANUM); +exports.URL_CHAR = exports.STRICT_URL_CHAR + .concat(['\t', '\f']); +// All characters with 0x80 bit set to 1 +for (let i = 0x80; i <= 0xff; i++) { + exports.URL_CHAR.push(i); +} +exports.HEX = exports.NUM.concat(['a', 'b', 'c', 'd', 'e', 'f', 'A', 'B', 'C', 'D', 'E', 'F']); +/* Tokens as defined by rfc 2616. Also lowercases them. + * token = 1* + * separators = "(" | ")" | "<" | ">" | "@" + * | "," | ";" | ":" | "\" | <"> + * | "/" | "[" | "]" | "?" | "=" + * | "{" | "}" | SP | HT + */ +exports.STRICT_TOKEN = [ + '!', '#', '$', '%', '&', '\'', + '*', '+', '-', '.', + '^', '_', '`', + '|', '~', +].concat(exports.ALPHANUM); +exports.TOKEN = exports.STRICT_TOKEN.concat([' ']); +/* + * Verify that a char is a valid visible (printable) US-ASCII + * character or %x80-FF + */ +exports.HEADER_CHARS = ['\t']; +for (let i = 32; i <= 255; i++) { + if (i !== 127) { + exports.HEADER_CHARS.push(i); + } +} +// ',' = \x44 +exports.CONNECTION_TOKEN_CHARS = exports.HEADER_CHARS.filter((c) => c !== 44); +exports.MAJOR = exports.NUM_MAP; +exports.MINOR = exports.MAJOR; +var HEADER_STATE; +(function (HEADER_STATE) { + HEADER_STATE[HEADER_STATE["GENERAL"] = 0] = "GENERAL"; + HEADER_STATE[HEADER_STATE["CONNECTION"] = 1] = "CONNECTION"; + HEADER_STATE[HEADER_STATE["CONTENT_LENGTH"] = 2] = "CONTENT_LENGTH"; + HEADER_STATE[HEADER_STATE["TRANSFER_ENCODING"] = 3] = "TRANSFER_ENCODING"; + HEADER_STATE[HEADER_STATE["UPGRADE"] = 4] = "UPGRADE"; + HEADER_STATE[HEADER_STATE["CONNECTION_KEEP_ALIVE"] = 5] = "CONNECTION_KEEP_ALIVE"; + HEADER_STATE[HEADER_STATE["CONNECTION_CLOSE"] = 6] = "CONNECTION_CLOSE"; + HEADER_STATE[HEADER_STATE["CONNECTION_UPGRADE"] = 7] = "CONNECTION_UPGRADE"; + HEADER_STATE[HEADER_STATE["TRANSFER_ENCODING_CHUNKED"] = 8] = "TRANSFER_ENCODING_CHUNKED"; +})(HEADER_STATE = exports.HEADER_STATE || (exports.HEADER_STATE = {})); +exports.SPECIAL_HEADERS = { + 'connection': HEADER_STATE.CONNECTION, + 'content-length': HEADER_STATE.CONTENT_LENGTH, + 'proxy-connection': HEADER_STATE.CONNECTION, + 'transfer-encoding': HEADER_STATE.TRANSFER_ENCODING, + 'upgrade': HEADER_STATE.UPGRADE, +}; +//# sourceMappingURL=constants.js.map + +/***/ }), + +/***/ 63870: +/***/ ((module) => { + +module.exports = 'AGFzbQEAAAABMAhgAX8Bf2ADf39/AX9gBH9/f38Bf2AAAGADf39/AGABfwBgAn9/AGAGf39/f39/AALLAQgDZW52GHdhc21fb25faGVhZGVyc19jb21wbGV0ZQACA2VudhV3YXNtX29uX21lc3NhZ2VfYmVnaW4AAANlbnYLd2FzbV9vbl91cmwAAQNlbnYOd2FzbV9vbl9zdGF0dXMAAQNlbnYUd2FzbV9vbl9oZWFkZXJfZmllbGQAAQNlbnYUd2FzbV9vbl9oZWFkZXJfdmFsdWUAAQNlbnYMd2FzbV9vbl9ib2R5AAEDZW52GHdhc21fb25fbWVzc2FnZV9jb21wbGV0ZQAAA0ZFAwMEAAAFAAAAAAAABQEFAAUFBQAABgAAAAAGBgYGAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQABAAABAQcAAAUFAwABBAUBcAESEgUDAQACBggBfwFBgNQECwfRBSIGbWVtb3J5AgALX2luaXRpYWxpemUACRlfX2luZGlyZWN0X2Z1bmN0aW9uX3RhYmxlAQALbGxodHRwX2luaXQAChhsbGh0dHBfc2hvdWxkX2tlZXBfYWxpdmUAQQxsbGh0dHBfYWxsb2MADAZtYWxsb2MARgtsbGh0dHBfZnJlZQANBGZyZWUASA9sbGh0dHBfZ2V0X3R5cGUADhVsbGh0dHBfZ2V0X2h0dHBfbWFqb3IADxVsbGh0dHBfZ2V0X2h0dHBfbWlub3IAEBFsbGh0dHBfZ2V0X21ldGhvZAARFmxsaHR0cF9nZXRfc3RhdHVzX2NvZGUAEhJsbGh0dHBfZ2V0X3VwZ3JhZGUAEwxsbGh0dHBfcmVzZXQAFA5sbGh0dHBfZXhlY3V0ZQAVFGxsaHR0cF9zZXR0aW5nc19pbml0ABYNbGxodHRwX2ZpbmlzaAAXDGxsaHR0cF9wYXVzZQAYDWxsaHR0cF9yZXN1bWUAGRtsbGh0dHBfcmVzdW1lX2FmdGVyX3VwZ3JhZGUAGhBsbGh0dHBfZ2V0X2Vycm5vABsXbGxodHRwX2dldF9lcnJvcl9yZWFzb24AHBdsbGh0dHBfc2V0X2Vycm9yX3JlYXNvbgAdFGxsaHR0cF9nZXRfZXJyb3JfcG9zAB4RbGxodHRwX2Vycm5vX25hbWUAHxJsbGh0dHBfbWV0aG9kX25hbWUAIBJsbGh0dHBfc3RhdHVzX25hbWUAIRpsbGh0dHBfc2V0X2xlbmllbnRfaGVhZGVycwAiIWxsaHR0cF9zZXRfbGVuaWVudF9jaHVua2VkX2xlbmd0aAAjHWxsaHR0cF9zZXRfbGVuaWVudF9rZWVwX2FsaXZlACQkbGxodHRwX3NldF9sZW5pZW50X3RyYW5zZmVyX2VuY29kaW5nACUYbGxodHRwX21lc3NhZ2VfbmVlZHNfZW9mAD8JFwEAQQELEQECAwQFCwYHNTk3MS8tJyspCsLgAkUCAAsIABCIgICAAAsZACAAEMKAgIAAGiAAIAI2AjggACABOgAoCxwAIAAgAC8BMiAALQAuIAAQwYCAgAAQgICAgAALKgEBf0HAABDGgICAACIBEMKAgIAAGiABQYCIgIAANgI4IAEgADoAKCABCwoAIAAQyICAgAALBwAgAC0AKAsHACAALQAqCwcAIAAtACsLBwAgAC0AKQsHACAALwEyCwcAIAAtAC4LRQEEfyAAKAIYIQEgAC0ALSECIAAtACghAyAAKAI4IQQgABDCgICAABogACAENgI4IAAgAzoAKCAAIAI6AC0gACABNgIYCxEAIAAgASABIAJqEMOAgIAACxAAIABBAEHcABDMgICAABoLZwEBf0EAIQECQCAAKAIMDQACQAJAAkACQCAALQAvDgMBAAMCCyAAKAI4IgFFDQAgASgCLCIBRQ0AIAAgARGAgICAAAAiAQ0DC0EADwsQyoCAgAAACyAAQcOWgIAANgIQQQ4hAQsgAQseAAJAIAAoAgwNACAAQdGbgIAANgIQIABBFTYCDAsLFgACQCAAKAIMQRVHDQAgAEEANgIMCwsWAAJAIAAoAgxBFkcNACAAQQA2AgwLCwcAIAAoAgwLBwAgACgCEAsJACAAIAE2AhALBwAgACgCFAsiAAJAIABBJEkNABDKgICAAAALIABBAnRBoLOAgABqKAIACyIAAkAgAEEuSQ0AEMqAgIAAAAsgAEECdEGwtICAAGooAgAL7gsBAX9B66iAgAAhAQJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAIABBnH9qDvQDY2IAAWFhYWFhYQIDBAVhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhBgcICQoLDA0OD2FhYWFhEGFhYWFhYWFhYWFhEWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYRITFBUWFxgZGhthYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhHB0eHyAhIiMkJSYnKCkqKywtLi8wMTIzNDU2YTc4OTphYWFhYWFhYTthYWE8YWFhYT0+P2FhYWFhYWFhQGFhQWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYUJDREVGR0hJSktMTU5PUFFSU2FhYWFhYWFhVFVWV1hZWlthXF1hYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFeYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhX2BhC0Hhp4CAAA8LQaShgIAADwtBy6yAgAAPC0H+sYCAAA8LQcCkgIAADwtBq6SAgAAPC0GNqICAAA8LQeKmgIAADwtBgLCAgAAPC0G5r4CAAA8LQdekgIAADwtB75+AgAAPC0Hhn4CAAA8LQfqfgIAADwtB8qCAgAAPC0Gor4CAAA8LQa6ygIAADwtBiLCAgAAPC0Hsp4CAAA8LQYKigIAADwtBjp2AgAAPC0HQroCAAA8LQcqjgIAADwtBxbKAgAAPC0HfnICAAA8LQdKcgIAADwtBxKCAgAAPC0HXoICAAA8LQaKfgIAADwtB7a6AgAAPC0GrsICAAA8LQdSlgIAADwtBzK6AgAAPC0H6roCAAA8LQfyrgIAADwtB0rCAgAAPC0HxnYCAAA8LQbuggIAADwtB96uAgAAPC0GQsYCAAA8LQdexgIAADwtBoq2AgAAPC0HUp4CAAA8LQeCrgIAADwtBn6yAgAAPC0HrsYCAAA8LQdWfgIAADwtByrGAgAAPC0HepYCAAA8LQdSegIAADwtB9JyAgAAPC0GnsoCAAA8LQbGdgIAADwtBoJ2AgAAPC0G5sYCAAA8LQbywgIAADwtBkqGAgAAPC0GzpoCAAA8LQemsgIAADwtBrJ6AgAAPC0HUq4CAAA8LQfemgIAADwtBgKaAgAAPC0GwoYCAAA8LQf6egIAADwtBjaOAgAAPC0GJrYCAAA8LQfeigIAADwtBoLGAgAAPC0Gun4CAAA8LQcalgIAADwtB6J6AgAAPC0GTooCAAA8LQcKvgIAADwtBw52AgAAPC0GLrICAAA8LQeGdgIAADwtBja+AgAAPC0HqoYCAAA8LQbStgIAADwtB0q+AgAAPC0HfsoCAAA8LQdKygIAADwtB8LCAgAAPC0GpooCAAA8LQfmjgIAADwtBmZ6AgAAPC0G1rICAAA8LQZuwgIAADwtBkrKAgAAPC0G2q4CAAA8LQcKigIAADwtB+LKAgAAPC0GepYCAAA8LQdCigIAADwtBup6AgAAPC0GBnoCAAA8LEMqAgIAAAAtB1qGAgAAhAQsgAQsWACAAIAAtAC1B/gFxIAFBAEdyOgAtCxkAIAAgAC0ALUH9AXEgAUEAR0EBdHI6AC0LGQAgACAALQAtQfsBcSABQQBHQQJ0cjoALQsZACAAIAAtAC1B9wFxIAFBAEdBA3RyOgAtCy4BAn9BACEDAkAgACgCOCIERQ0AIAQoAgAiBEUNACAAIAQRgICAgAAAIQMLIAMLSQECf0EAIQMCQCAAKAI4IgRFDQAgBCgCBCIERQ0AIAAgASACIAFrIAQRgYCAgAAAIgNBf0cNACAAQcaRgIAANgIQQRghAwsgAwsuAQJ/QQAhAwJAIAAoAjgiBEUNACAEKAIwIgRFDQAgACAEEYCAgIAAACEDCyADC0kBAn9BACEDAkAgACgCOCIERQ0AIAQoAggiBEUNACAAIAEgAiABayAEEYGAgIAAACIDQX9HDQAgAEH2ioCAADYCEEEYIQMLIAMLLgECf0EAIQMCQCAAKAI4IgRFDQAgBCgCNCIERQ0AIAAgBBGAgICAAAAhAwsgAwtJAQJ/QQAhAwJAIAAoAjgiBEUNACAEKAIMIgRFDQAgACABIAIgAWsgBBGBgICAAAAiA0F/Rw0AIABB7ZqAgAA2AhBBGCEDCyADCy4BAn9BACEDAkAgACgCOCIERQ0AIAQoAjgiBEUNACAAIAQRgICAgAAAIQMLIAMLSQECf0EAIQMCQCAAKAI4IgRFDQAgBCgCECIERQ0AIAAgASACIAFrIAQRgYCAgAAAIgNBf0cNACAAQZWQgIAANgIQQRghAwsgAwsuAQJ/QQAhAwJAIAAoAjgiBEUNACAEKAI8IgRFDQAgACAEEYCAgIAAACEDCyADC0kBAn9BACEDAkAgACgCOCIERQ0AIAQoAhQiBEUNACAAIAEgAiABayAEEYGAgIAAACIDQX9HDQAgAEGqm4CAADYCEEEYIQMLIAMLLgECf0EAIQMCQCAAKAI4IgRFDQAgBCgCQCIERQ0AIAAgBBGAgICAAAAhAwsgAwtJAQJ/QQAhAwJAIAAoAjgiBEUNACAEKAIYIgRFDQAgACABIAIgAWsgBBGBgICAAAAiA0F/Rw0AIABB7ZOAgAA2AhBBGCEDCyADCy4BAn9BACEDAkAgACgCOCIERQ0AIAQoAkQiBEUNACAAIAQRgICAgAAAIQMLIAMLLgECf0EAIQMCQCAAKAI4IgRFDQAgBCgCJCIERQ0AIAAgBBGAgICAAAAhAwsgAwsuAQJ/QQAhAwJAIAAoAjgiBEUNACAEKAIsIgRFDQAgACAEEYCAgIAAACEDCyADC0kBAn9BACEDAkAgACgCOCIERQ0AIAQoAigiBEUNACAAIAEgAiABayAEEYGAgIAAACIDQX9HDQAgAEH2iICAADYCEEEYIQMLIAMLLgECf0EAIQMCQCAAKAI4IgRFDQAgBCgCUCIERQ0AIAAgBBGAgICAAAAhAwsgAwtJAQJ/QQAhAwJAIAAoAjgiBEUNACAEKAIcIgRFDQAgACABIAIgAWsgBBGBgICAAAAiA0F/Rw0AIABBwpmAgAA2AhBBGCEDCyADCy4BAn9BACEDAkAgACgCOCIERQ0AIAQoAkgiBEUNACAAIAQRgICAgAAAIQMLIAMLSQECf0EAIQMCQCAAKAI4IgRFDQAgBCgCICIERQ0AIAAgASACIAFrIAQRgYCAgAAAIgNBf0cNACAAQZSUgIAANgIQQRghAwsgAwsuAQJ/QQAhAwJAIAAoAjgiBEUNACAEKAJMIgRFDQAgACAEEYCAgIAAACEDCyADCy4BAn9BACEDAkAgACgCOCIERQ0AIAQoAlQiBEUNACAAIAQRgICAgAAAIQMLIAMLLgECf0EAIQMCQCAAKAI4IgRFDQAgBCgCWCIERQ0AIAAgBBGAgICAAAAhAwsgAwtFAQF/AkACQCAALwEwQRRxQRRHDQBBASEDIAAtAChBAUYNASAALwEyQeUARiEDDAELIAAtAClBBUYhAwsgACADOgAuQQAL/gEBA39BASEDAkAgAC8BMCIEQQhxDQAgACkDIEIAUiEDCwJAAkAgAC0ALkUNAEEBIQUgAC0AKUEFRg0BQQEhBSAEQcAAcUUgA3FBAUcNAQtBACEFIARBwABxDQBBAiEFIARB//8DcSIDQQhxDQACQCADQYAEcUUNAAJAIAAtAChBAUcNACAALQAtQQpxDQBBBQ8LQQQPCwJAIANBIHENAAJAIAAtAChBAUYNACAALwEyQf//A3EiAEGcf2pB5ABJDQAgAEHMAUYNACAAQbACRg0AQQQhBSAEQShxRQ0CIANBiARxQYAERg0CC0EADwtBAEEDIAApAyBQGyEFCyAFC2IBAn9BACEBAkAgAC0AKEEBRg0AIAAvATJB//8DcSICQZx/akHkAEkNACACQcwBRg0AIAJBsAJGDQAgAC8BMCIAQcAAcQ0AQQEhASAAQYgEcUGABEYNACAAQShxRSEBCyABC6cBAQN/AkACQAJAIAAtACpFDQAgAC0AK0UNAEEAIQMgAC8BMCIEQQJxRQ0BDAILQQAhAyAALwEwIgRBAXFFDQELQQEhAyAALQAoQQFGDQAgAC8BMkH//wNxIgVBnH9qQeQASQ0AIAVBzAFGDQAgBUGwAkYNACAEQcAAcQ0AQQAhAyAEQYgEcUGABEYNACAEQShxQQBHIQMLIABBADsBMCAAQQA6AC8gAwuZAQECfwJAAkACQCAALQAqRQ0AIAAtACtFDQBBACEBIAAvATAiAkECcUUNAQwCC0EAIQEgAC8BMCICQQFxRQ0BC0EBIQEgAC0AKEEBRg0AIAAvATJB//8DcSIAQZx/akHkAEkNACAAQcwBRg0AIABBsAJGDQAgAkHAAHENAEEAIQEgAkGIBHFBgARGDQAgAkEocUEARyEBCyABC1kAIABBGGpCADcDACAAQgA3AwAgAEE4akIANwMAIABBMGpCADcDACAAQShqQgA3AwAgAEEgakIANwMAIABBEGpCADcDACAAQQhqQgA3AwAgAEHdATYCHEEAC3sBAX8CQCAAKAIMIgMNAAJAIAAoAgRFDQAgACABNgIECwJAIAAgASACEMSAgIAAIgMNACAAKAIMDwsgACADNgIcQQAhAyAAKAIEIgFFDQAgACABIAIgACgCCBGBgICAAAAiAUUNACAAIAI2AhQgACABNgIMIAEhAwsgAwvk8wEDDn8DfgR/I4CAgIAAQRBrIgMkgICAgAAgASEEIAEhBSABIQYgASEHIAEhCCABIQkgASEKIAEhCyABIQwgASENIAEhDiABIQ8CQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkAgACgCHCIQQX9qDt0B2gEB2QECAwQFBgcICQoLDA0O2AEPENcBERLWARMUFRYXGBkaG+AB3wEcHR7VAR8gISIjJCXUASYnKCkqKyzTAdIBLS7RAdABLzAxMjM0NTY3ODk6Ozw9Pj9AQUJDREVG2wFHSElKzwHOAUvNAUzMAU1OT1BRUlNUVVZXWFlaW1xdXl9gYWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXp7fH1+f4ABgQGCAYMBhAGFAYYBhwGIAYkBigGLAYwBjQGOAY8BkAGRAZIBkwGUAZUBlgGXAZgBmQGaAZsBnAGdAZ4BnwGgAaEBogGjAaQBpQGmAacBqAGpAaoBqwGsAa0BrgGvAbABsQGyAbMBtAG1AbYBtwHLAcoBuAHJAbkByAG6AbsBvAG9Ab4BvwHAAcEBwgHDAcQBxQHGAQDcAQtBACEQDMYBC0EOIRAMxQELQQ0hEAzEAQtBDyEQDMMBC0EQIRAMwgELQRMhEAzBAQtBFCEQDMABC0EVIRAMvwELQRYhEAy+AQtBFyEQDL0BC0EYIRAMvAELQRkhEAy7AQtBGiEQDLoBC0EbIRAMuQELQRwhEAy4AQtBCCEQDLcBC0EdIRAMtgELQSAhEAy1AQtBHyEQDLQBC0EHIRAMswELQSEhEAyyAQtBIiEQDLEBC0EeIRAMsAELQSMhEAyvAQtBEiEQDK4BC0ERIRAMrQELQSQhEAysAQtBJSEQDKsBC0EmIRAMqgELQSchEAypAQtBwwEhEAyoAQtBKSEQDKcBC0ErIRAMpgELQSwhEAylAQtBLSEQDKQBC0EuIRAMowELQS8hEAyiAQtBxAEhEAyhAQtBMCEQDKABC0E0IRAMnwELQQwhEAyeAQtBMSEQDJ0BC0EyIRAMnAELQTMhEAybAQtBOSEQDJoBC0E1IRAMmQELQcUBIRAMmAELQQshEAyXAQtBOiEQDJYBC0E2IRAMlQELQQohEAyUAQtBNyEQDJMBC0E4IRAMkgELQTwhEAyRAQtBOyEQDJABC0E9IRAMjwELQQkhEAyOAQtBKCEQDI0BC0E+IRAMjAELQT8hEAyLAQtBwAAhEAyKAQtBwQAhEAyJAQtBwgAhEAyIAQtBwwAhEAyHAQtBxAAhEAyGAQtBxQAhEAyFAQtBxgAhEAyEAQtBKiEQDIMBC0HHACEQDIIBC0HIACEQDIEBC0HJACEQDIABC0HKACEQDH8LQcsAIRAMfgtBzQAhEAx9C0HMACEQDHwLQc4AIRAMewtBzwAhEAx6C0HQACEQDHkLQdEAIRAMeAtB0gAhEAx3C0HTACEQDHYLQdQAIRAMdQtB1gAhEAx0C0HVACEQDHMLQQYhEAxyC0HXACEQDHELQQUhEAxwC0HYACEQDG8LQQQhEAxuC0HZACEQDG0LQdoAIRAMbAtB2wAhEAxrC0HcACEQDGoLQQMhEAxpC0HdACEQDGgLQd4AIRAMZwtB3wAhEAxmC0HhACEQDGULQeAAIRAMZAtB4gAhEAxjC0HjACEQDGILQQIhEAxhC0HkACEQDGALQeUAIRAMXwtB5gAhEAxeC0HnACEQDF0LQegAIRAMXAtB6QAhEAxbC0HqACEQDFoLQesAIRAMWQtB7AAhEAxYC0HtACEQDFcLQe4AIRAMVgtB7wAhEAxVC0HwACEQDFQLQfEAIRAMUwtB8gAhEAxSC0HzACEQDFELQfQAIRAMUAtB9QAhEAxPC0H2ACEQDE4LQfcAIRAMTQtB+AAhEAxMC0H5ACEQDEsLQfoAIRAMSgtB+wAhEAxJC0H8ACEQDEgLQf0AIRAMRwtB/gAhEAxGC0H/ACEQDEULQYABIRAMRAtBgQEhEAxDC0GCASEQDEILQYMBIRAMQQtBhAEhEAxAC0GFASEQDD8LQYYBIRAMPgtBhwEhEAw9C0GIASEQDDwLQYkBIRAMOwtBigEhEAw6C0GLASEQDDkLQYwBIRAMOAtBjQEhEAw3C0GOASEQDDYLQY8BIRAMNQtBkAEhEAw0C0GRASEQDDMLQZIBIRAMMgtBkwEhEAwxC0GUASEQDDALQZUBIRAMLwtBlgEhEAwuC0GXASEQDC0LQZgBIRAMLAtBmQEhEAwrC0GaASEQDCoLQZsBIRAMKQtBnAEhEAwoC0GdASEQDCcLQZ4BIRAMJgtBnwEhEAwlC0GgASEQDCQLQaEBIRAMIwtBogEhEAwiC0GjASEQDCELQaQBIRAMIAtBpQEhEAwfC0GmASEQDB4LQacBIRAMHQtBqAEhEAwcC0GpASEQDBsLQaoBIRAMGgtBqwEhEAwZC0GsASEQDBgLQa0BIRAMFwtBrgEhEAwWC0EBIRAMFQtBrwEhEAwUC0GwASEQDBMLQbEBIRAMEgtBswEhEAwRC0GyASEQDBALQbQBIRAMDwtBtQEhEAwOC0G2ASEQDA0LQbcBIRAMDAtBuAEhEAwLC0G5ASEQDAoLQboBIRAMCQtBuwEhEAwIC0HGASEQDAcLQbwBIRAMBgtBvQEhEAwFC0G+ASEQDAQLQb8BIRAMAwtBwAEhEAwCC0HCASEQDAELQcEBIRALA0ACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQCAQDscBAAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxweHyAhIyUoP0BBREVGR0hJSktMTU9QUVJT3gNXWVtcXWBiZWZnaGlqa2xtb3BxcnN0dXZ3eHl6e3x9foABggGFAYYBhwGJAYsBjAGNAY4BjwGQAZEBlAGVAZYBlwGYAZkBmgGbAZwBnQGeAZ8BoAGhAaIBowGkAaUBpgGnAagBqQGqAasBrAGtAa4BrwGwAbEBsgGzAbQBtQG2AbcBuAG5AboBuwG8Ab0BvgG/AcABwQHCAcMBxAHFAcYBxwHIAckBygHLAcwBzQHOAc8B0AHRAdIB0wHUAdUB1gHXAdgB2QHaAdsB3AHdAd4B4AHhAeIB4wHkAeUB5gHnAegB6QHqAesB7AHtAe4B7wHwAfEB8gHzAZkCpAKwAv4C/gILIAEiBCACRw3zAUHdASEQDP8DCyABIhAgAkcN3QFBwwEhEAz+AwsgASIBIAJHDZABQfcAIRAM/QMLIAEiASACRw2GAUHvACEQDPwDCyABIgEgAkcNf0HqACEQDPsDCyABIgEgAkcNe0HoACEQDPoDCyABIgEgAkcNeEHmACEQDPkDCyABIgEgAkcNGkEYIRAM+AMLIAEiASACRw0UQRIhEAz3AwsgASIBIAJHDVlBxQAhEAz2AwsgASIBIAJHDUpBPyEQDPUDCyABIgEgAkcNSEE8IRAM9AMLIAEiASACRw1BQTEhEAzzAwsgAC0ALkEBRg3rAwyHAgsgACABIgEgAhDAgICAAEEBRw3mASAAQgA3AyAM5wELIAAgASIBIAIQtICAgAAiEA3nASABIQEM9QILAkAgASIBIAJHDQBBBiEQDPADCyAAIAFBAWoiASACELuAgIAAIhAN6AEgASEBDDELIABCADcDIEESIRAM1QMLIAEiECACRw0rQR0hEAztAwsCQCABIgEgAkYNACABQQFqIQFBECEQDNQDC0EHIRAM7AMLIABCACAAKQMgIhEgAiABIhBrrSISfSITIBMgEVYbNwMgIBEgElYiFEUN5QFBCCEQDOsDCwJAIAEiASACRg0AIABBiYCAgAA2AgggACABNgIEIAEhAUEUIRAM0gMLQQkhEAzqAwsgASEBIAApAyBQDeQBIAEhAQzyAgsCQCABIgEgAkcNAEELIRAM6QMLIAAgAUEBaiIBIAIQtoCAgAAiEA3lASABIQEM8gILIAAgASIBIAIQuICAgAAiEA3lASABIQEM8gILIAAgASIBIAIQuICAgAAiEA3mASABIQEMDQsgACABIgEgAhC6gICAACIQDecBIAEhAQzwAgsCQCABIgEgAkcNAEEPIRAM5QMLIAEtAAAiEEE7Rg0IIBBBDUcN6AEgAUEBaiEBDO8CCyAAIAEiASACELqAgIAAIhAN6AEgASEBDPICCwNAAkAgAS0AAEHwtYCAAGotAAAiEEEBRg0AIBBBAkcN6wEgACgCBCEQIABBADYCBCAAIBAgAUEBaiIBELmAgIAAIhAN6gEgASEBDPQCCyABQQFqIgEgAkcNAAtBEiEQDOIDCyAAIAEiASACELqAgIAAIhAN6QEgASEBDAoLIAEiASACRw0GQRshEAzgAwsCQCABIgEgAkcNAEEWIRAM4AMLIABBioCAgAA2AgggACABNgIEIAAgASACELiAgIAAIhAN6gEgASEBQSAhEAzGAwsCQCABIgEgAkYNAANAAkAgAS0AAEHwt4CAAGotAAAiEEECRg0AAkAgEEF/ag4E5QHsAQDrAewBCyABQQFqIQFBCCEQDMgDCyABQQFqIgEgAkcNAAtBFSEQDN8DC0EVIRAM3gMLA0ACQCABLQAAQfC5gIAAai0AACIQQQJGDQAgEEF/ag4E3gHsAeAB6wHsAQsgAUEBaiIBIAJHDQALQRghEAzdAwsCQCABIgEgAkYNACAAQYuAgIAANgIIIAAgATYCBCABIQFBByEQDMQDC0EZIRAM3AMLIAFBAWohAQwCCwJAIAEiFCACRw0AQRohEAzbAwsgFCEBAkAgFC0AAEFzag4U3QLuAu4C7gLuAu4C7gLuAu4C7gLuAu4C7gLuAu4C7gLuAu4C7gIA7gILQQAhECAAQQA2AhwgAEGvi4CAADYCECAAQQI2AgwgACAUQQFqNgIUDNoDCwJAIAEtAAAiEEE7Rg0AIBBBDUcN6AEgAUEBaiEBDOUCCyABQQFqIQELQSIhEAy/AwsCQCABIhAgAkcNAEEcIRAM2AMLQgAhESAQIQEgEC0AAEFQag435wHmAQECAwQFBgcIAAAAAAAAAAkKCwwNDgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADxAREhMUAAtBHiEQDL0DC0ICIREM5QELQgMhEQzkAQtCBCERDOMBC0IFIREM4gELQgYhEQzhAQtCByERDOABC0IIIREM3wELQgkhEQzeAQtCCiERDN0BC0ILIREM3AELQgwhEQzbAQtCDSERDNoBC0IOIREM2QELQg8hEQzYAQtCCiERDNcBC0ILIREM1gELQgwhEQzVAQtCDSERDNQBC0IOIREM0wELQg8hEQzSAQtCACERAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQCAQLQAAQVBqDjflAeQBAAECAwQFBgfmAeYB5gHmAeYB5gHmAQgJCgsMDeYB5gHmAeYB5gHmAeYB5gHmAeYB5gHmAeYB5gHmAeYB5gHmAeYB5gHmAeYB5gHmAeYB5gEODxAREhPmAQtCAiERDOQBC0IDIREM4wELQgQhEQziAQtCBSERDOEBC0IGIREM4AELQgchEQzfAQtCCCERDN4BC0IJIREM3QELQgohEQzcAQtCCyERDNsBC0IMIREM2gELQg0hEQzZAQtCDiERDNgBC0IPIREM1wELQgohEQzWAQtCCyERDNUBC0IMIREM1AELQg0hEQzTAQtCDiERDNIBC0IPIREM0QELIABCACAAKQMgIhEgAiABIhBrrSISfSITIBMgEVYbNwMgIBEgElYiFEUN0gFBHyEQDMADCwJAIAEiASACRg0AIABBiYCAgAA2AgggACABNgIEIAEhAUEkIRAMpwMLQSAhEAy/AwsgACABIhAgAhC+gICAAEF/ag4FtgEAxQIB0QHSAQtBESEQDKQDCyAAQQE6AC8gECEBDLsDCyABIgEgAkcN0gFBJCEQDLsDCyABIg0gAkcNHkHGACEQDLoDCyAAIAEiASACELKAgIAAIhAN1AEgASEBDLUBCyABIhAgAkcNJkHQACEQDLgDCwJAIAEiASACRw0AQSghEAy4AwsgAEEANgIEIABBjICAgAA2AgggACABIAEQsYCAgAAiEA3TASABIQEM2AELAkAgASIQIAJHDQBBKSEQDLcDCyAQLQAAIgFBIEYNFCABQQlHDdMBIBBBAWohAQwVCwJAIAEiASACRg0AIAFBAWohAQwXC0EqIRAMtQMLAkAgASIQIAJHDQBBKyEQDLUDCwJAIBAtAAAiAUEJRg0AIAFBIEcN1QELIAAtACxBCEYN0wEgECEBDJEDCwJAIAEiASACRw0AQSwhEAy0AwsgAS0AAEEKRw3VASABQQFqIQEMyQILIAEiDiACRw3VAUEvIRAMsgMLA0ACQCABLQAAIhBBIEYNAAJAIBBBdmoOBADcAdwBANoBCyABIQEM4AELIAFBAWoiASACRw0AC0ExIRAMsQMLQTIhECABIhQgAkYNsAMgAiAUayAAKAIAIgFqIRUgFCABa0EDaiEWAkADQCAULQAAIhdBIHIgFyAXQb9/akH/AXFBGkkbQf8BcSABQfC7gIAAai0AAEcNAQJAIAFBA0cNAEEGIQEMlgMLIAFBAWohASAUQQFqIhQgAkcNAAsgACAVNgIADLEDCyAAQQA2AgAgFCEBDNkBC0EzIRAgASIUIAJGDa8DIAIgFGsgACgCACIBaiEVIBQgAWtBCGohFgJAA0AgFC0AACIXQSByIBcgF0G/f2pB/wFxQRpJG0H/AXEgAUH0u4CAAGotAABHDQECQCABQQhHDQBBBSEBDJUDCyABQQFqIQEgFEEBaiIUIAJHDQALIAAgFTYCAAywAwsgAEEANgIAIBQhAQzYAQtBNCEQIAEiFCACRg2uAyACIBRrIAAoAgAiAWohFSAUIAFrQQVqIRYCQANAIBQtAAAiF0EgciAXIBdBv39qQf8BcUEaSRtB/wFxIAFB0MKAgABqLQAARw0BAkAgAUEFRw0AQQchAQyUAwsgAUEBaiEBIBRBAWoiFCACRw0ACyAAIBU2AgAMrwMLIABBADYCACAUIQEM1wELAkAgASIBIAJGDQADQAJAIAEtAABBgL6AgABqLQAAIhBBAUYNACAQQQJGDQogASEBDN0BCyABQQFqIgEgAkcNAAtBMCEQDK4DC0EwIRAMrQMLAkAgASIBIAJGDQADQAJAIAEtAAAiEEEgRg0AIBBBdmoOBNkB2gHaAdkB2gELIAFBAWoiASACRw0AC0E4IRAMrQMLQTghEAysAwsDQAJAIAEtAAAiEEEgRg0AIBBBCUcNAwsgAUEBaiIBIAJHDQALQTwhEAyrAwsDQAJAIAEtAAAiEEEgRg0AAkACQCAQQXZqDgTaAQEB2gEACyAQQSxGDdsBCyABIQEMBAsgAUEBaiIBIAJHDQALQT8hEAyqAwsgASEBDNsBC0HAACEQIAEiFCACRg2oAyACIBRrIAAoAgAiAWohFiAUIAFrQQZqIRcCQANAIBQtAABBIHIgAUGAwICAAGotAABHDQEgAUEGRg2OAyABQQFqIQEgFEEBaiIUIAJHDQALIAAgFjYCAAypAwsgAEEANgIAIBQhAQtBNiEQDI4DCwJAIAEiDyACRw0AQcEAIRAMpwMLIABBjICAgAA2AgggACAPNgIEIA8hASAALQAsQX9qDgTNAdUB1wHZAYcDCyABQQFqIQEMzAELAkAgASIBIAJGDQADQAJAIAEtAAAiEEEgciAQIBBBv39qQf8BcUEaSRtB/wFxIhBBCUYNACAQQSBGDQACQAJAAkACQCAQQZ1/ag4TAAMDAwMDAwMBAwMDAwMDAwMDAgMLIAFBAWohAUExIRAMkQMLIAFBAWohAUEyIRAMkAMLIAFBAWohAUEzIRAMjwMLIAEhAQzQAQsgAUEBaiIBIAJHDQALQTUhEAylAwtBNSEQDKQDCwJAIAEiASACRg0AA0ACQCABLQAAQYC8gIAAai0AAEEBRg0AIAEhAQzTAQsgAUEBaiIBIAJHDQALQT0hEAykAwtBPSEQDKMDCyAAIAEiASACELCAgIAAIhAN1gEgASEBDAELIBBBAWohAQtBPCEQDIcDCwJAIAEiASACRw0AQcIAIRAMoAMLAkADQAJAIAEtAABBd2oOGAAC/gL+AoQD/gL+Av4C/gL+Av4C/gL+Av4C/gL+Av4C/gL+Av4C/gL+Av4CAP4CCyABQQFqIgEgAkcNAAtBwgAhEAygAwsgAUEBaiEBIAAtAC1BAXFFDb0BIAEhAQtBLCEQDIUDCyABIgEgAkcN0wFBxAAhEAydAwsDQAJAIAEtAABBkMCAgABqLQAAQQFGDQAgASEBDLcCCyABQQFqIgEgAkcNAAtBxQAhEAycAwsgDS0AACIQQSBGDbMBIBBBOkcNgQMgACgCBCEBIABBADYCBCAAIAEgDRCvgICAACIBDdABIA1BAWohAQyzAgtBxwAhECABIg0gAkYNmgMgAiANayAAKAIAIgFqIRYgDSABa0EFaiEXA0AgDS0AACIUQSByIBQgFEG/f2pB/wFxQRpJG0H/AXEgAUGQwoCAAGotAABHDYADIAFBBUYN9AIgAUEBaiEBIA1BAWoiDSACRw0ACyAAIBY2AgAMmgMLQcgAIRAgASINIAJGDZkDIAIgDWsgACgCACIBaiEWIA0gAWtBCWohFwNAIA0tAAAiFEEgciAUIBRBv39qQf8BcUEaSRtB/wFxIAFBlsKAgABqLQAARw3/AgJAIAFBCUcNAEECIQEM9QILIAFBAWohASANQQFqIg0gAkcNAAsgACAWNgIADJkDCwJAIAEiDSACRw0AQckAIRAMmQMLAkACQCANLQAAIgFBIHIgASABQb9/akH/AXFBGkkbQf8BcUGSf2oOBwCAA4ADgAOAA4ADAYADCyANQQFqIQFBPiEQDIADCyANQQFqIQFBPyEQDP8CC0HKACEQIAEiDSACRg2XAyACIA1rIAAoAgAiAWohFiANIAFrQQFqIRcDQCANLQAAIhRBIHIgFCAUQb9/akH/AXFBGkkbQf8BcSABQaDCgIAAai0AAEcN/QIgAUEBRg3wAiABQQFqIQEgDUEBaiINIAJHDQALIAAgFjYCAAyXAwtBywAhECABIg0gAkYNlgMgAiANayAAKAIAIgFqIRYgDSABa0EOaiEXA0AgDS0AACIUQSByIBQgFEG/f2pB/wFxQRpJG0H/AXEgAUGiwoCAAGotAABHDfwCIAFBDkYN8AIgAUEBaiEBIA1BAWoiDSACRw0ACyAAIBY2AgAMlgMLQcwAIRAgASINIAJGDZUDIAIgDWsgACgCACIBaiEWIA0gAWtBD2ohFwNAIA0tAAAiFEEgciAUIBRBv39qQf8BcUEaSRtB/wFxIAFBwMKAgABqLQAARw37AgJAIAFBD0cNAEEDIQEM8QILIAFBAWohASANQQFqIg0gAkcNAAsgACAWNgIADJUDC0HNACEQIAEiDSACRg2UAyACIA1rIAAoAgAiAWohFiANIAFrQQVqIRcDQCANLQAAIhRBIHIgFCAUQb9/akH/AXFBGkkbQf8BcSABQdDCgIAAai0AAEcN+gICQCABQQVHDQBBBCEBDPACCyABQQFqIQEgDUEBaiINIAJHDQALIAAgFjYCAAyUAwsCQCABIg0gAkcNAEHOACEQDJQDCwJAAkACQAJAIA0tAAAiAUEgciABIAFBv39qQf8BcUEaSRtB/wFxQZ1/ag4TAP0C/QL9Av0C/QL9Av0C/QL9Av0C/QL9AgH9Av0C/QICA/0CCyANQQFqIQFBwQAhEAz9AgsgDUEBaiEBQcIAIRAM/AILIA1BAWohAUHDACEQDPsCCyANQQFqIQFBxAAhEAz6AgsCQCABIgEgAkYNACAAQY2AgIAANgIIIAAgATYCBCABIQFBxQAhEAz6AgtBzwAhEAySAwsgECEBAkACQCAQLQAAQXZqDgQBqAKoAgCoAgsgEEEBaiEBC0EnIRAM+AILAkAgASIBIAJHDQBB0QAhEAyRAwsCQCABLQAAQSBGDQAgASEBDI0BCyABQQFqIQEgAC0ALUEBcUUNxwEgASEBDIwBCyABIhcgAkcNyAFB0gAhEAyPAwtB0wAhECABIhQgAkYNjgMgAiAUayAAKAIAIgFqIRYgFCABa0EBaiEXA0AgFC0AACABQdbCgIAAai0AAEcNzAEgAUEBRg3HASABQQFqIQEgFEEBaiIUIAJHDQALIAAgFjYCAAyOAwsCQCABIgEgAkcNAEHVACEQDI4DCyABLQAAQQpHDcwBIAFBAWohAQzHAQsCQCABIgEgAkcNAEHWACEQDI0DCwJAAkAgAS0AAEF2ag4EAM0BzQEBzQELIAFBAWohAQzHAQsgAUEBaiEBQcoAIRAM8wILIAAgASIBIAIQroCAgAAiEA3LASABIQFBzQAhEAzyAgsgAC0AKUEiRg2FAwymAgsCQCABIgEgAkcNAEHbACEQDIoDC0EAIRRBASEXQQEhFkEAIRACQAJAAkACQAJAAkACQAJAAkAgAS0AAEFQag4K1AHTAQABAgMEBQYI1QELQQIhEAwGC0EDIRAMBQtBBCEQDAQLQQUhEAwDC0EGIRAMAgtBByEQDAELQQghEAtBACEXQQAhFkEAIRQMzAELQQkhEEEBIRRBACEXQQAhFgzLAQsCQCABIgEgAkcNAEHdACEQDIkDCyABLQAAQS5HDcwBIAFBAWohAQymAgsgASIBIAJHDcwBQd8AIRAMhwMLAkAgASIBIAJGDQAgAEGOgICAADYCCCAAIAE2AgQgASEBQdAAIRAM7gILQeAAIRAMhgMLQeEAIRAgASIBIAJGDYUDIAIgAWsgACgCACIUaiEWIAEgFGtBA2ohFwNAIAEtAAAgFEHiwoCAAGotAABHDc0BIBRBA0YNzAEgFEEBaiEUIAFBAWoiASACRw0ACyAAIBY2AgAMhQMLQeIAIRAgASIBIAJGDYQDIAIgAWsgACgCACIUaiEWIAEgFGtBAmohFwNAIAEtAAAgFEHmwoCAAGotAABHDcwBIBRBAkYNzgEgFEEBaiEUIAFBAWoiASACRw0ACyAAIBY2AgAMhAMLQeMAIRAgASIBIAJGDYMDIAIgAWsgACgCACIUaiEWIAEgFGtBA2ohFwNAIAEtAAAgFEHpwoCAAGotAABHDcsBIBRBA0YNzgEgFEEBaiEUIAFBAWoiASACRw0ACyAAIBY2AgAMgwMLAkAgASIBIAJHDQBB5QAhEAyDAwsgACABQQFqIgEgAhCogICAACIQDc0BIAEhAUHWACEQDOkCCwJAIAEiASACRg0AA0ACQCABLQAAIhBBIEYNAAJAAkACQCAQQbh/ag4LAAHPAc8BzwHPAc8BzwHPAc8BAs8BCyABQQFqIQFB0gAhEAztAgsgAUEBaiEBQdMAIRAM7AILIAFBAWohAUHUACEQDOsCCyABQQFqIgEgAkcNAAtB5AAhEAyCAwtB5AAhEAyBAwsDQAJAIAEtAABB8MKAgABqLQAAIhBBAUYNACAQQX5qDgPPAdAB0QHSAQsgAUEBaiIBIAJHDQALQeYAIRAMgAMLAkAgASIBIAJGDQAgAUEBaiEBDAMLQecAIRAM/wILA0ACQCABLQAAQfDEgIAAai0AACIQQQFGDQACQCAQQX5qDgTSAdMB1AEA1QELIAEhAUHXACEQDOcCCyABQQFqIgEgAkcNAAtB6AAhEAz+AgsCQCABIgEgAkcNAEHpACEQDP4CCwJAIAEtAAAiEEF2ag4augHVAdUBvAHVAdUB1QHVAdUB1QHVAdUB1QHVAdUB1QHVAdUB1QHVAdUB1QHKAdUB1QEA0wELIAFBAWohAQtBBiEQDOMCCwNAAkAgAS0AAEHwxoCAAGotAABBAUYNACABIQEMngILIAFBAWoiASACRw0AC0HqACEQDPsCCwJAIAEiASACRg0AIAFBAWohAQwDC0HrACEQDPoCCwJAIAEiASACRw0AQewAIRAM+gILIAFBAWohAQwBCwJAIAEiASACRw0AQe0AIRAM+QILIAFBAWohAQtBBCEQDN4CCwJAIAEiFCACRw0AQe4AIRAM9wILIBQhAQJAAkACQCAULQAAQfDIgIAAai0AAEF/ag4H1AHVAdYBAJwCAQLXAQsgFEEBaiEBDAoLIBRBAWohAQzNAQtBACEQIABBADYCHCAAQZuSgIAANgIQIABBBzYCDCAAIBRBAWo2AhQM9gILAkADQAJAIAEtAABB8MiAgABqLQAAIhBBBEYNAAJAAkAgEEF/ag4H0gHTAdQB2QEABAHZAQsgASEBQdoAIRAM4AILIAFBAWohAUHcACEQDN8CCyABQQFqIgEgAkcNAAtB7wAhEAz2AgsgAUEBaiEBDMsBCwJAIAEiFCACRw0AQfAAIRAM9QILIBQtAABBL0cN1AEgFEEBaiEBDAYLAkAgASIUIAJHDQBB8QAhEAz0AgsCQCAULQAAIgFBL0cNACAUQQFqIQFB3QAhEAzbAgsgAUF2aiIEQRZLDdMBQQEgBHRBiYCAAnFFDdMBDMoCCwJAIAEiASACRg0AIAFBAWohAUHeACEQDNoCC0HyACEQDPICCwJAIAEiFCACRw0AQfQAIRAM8gILIBQhAQJAIBQtAABB8MyAgABqLQAAQX9qDgPJApQCANQBC0HhACEQDNgCCwJAIAEiFCACRg0AA0ACQCAULQAAQfDKgIAAai0AACIBQQNGDQACQCABQX9qDgLLAgDVAQsgFCEBQd8AIRAM2gILIBRBAWoiFCACRw0AC0HzACEQDPECC0HzACEQDPACCwJAIAEiASACRg0AIABBj4CAgAA2AgggACABNgIEIAEhAUHgACEQDNcCC0H1ACEQDO8CCwJAIAEiASACRw0AQfYAIRAM7wILIABBj4CAgAA2AgggACABNgIEIAEhAQtBAyEQDNQCCwNAIAEtAABBIEcNwwIgAUEBaiIBIAJHDQALQfcAIRAM7AILAkAgASIBIAJHDQBB+AAhEAzsAgsgAS0AAEEgRw3OASABQQFqIQEM7wELIAAgASIBIAIQrICAgAAiEA3OASABIQEMjgILAkAgASIEIAJHDQBB+gAhEAzqAgsgBC0AAEHMAEcN0QEgBEEBaiEBQRMhEAzPAQsCQCABIgQgAkcNAEH7ACEQDOkCCyACIARrIAAoAgAiAWohFCAEIAFrQQVqIRADQCAELQAAIAFB8M6AgABqLQAARw3QASABQQVGDc4BIAFBAWohASAEQQFqIgQgAkcNAAsgACAUNgIAQfsAIRAM6AILAkAgASIEIAJHDQBB/AAhEAzoAgsCQAJAIAQtAABBvX9qDgwA0QHRAdEB0QHRAdEB0QHRAdEB0QEB0QELIARBAWohAUHmACEQDM8CCyAEQQFqIQFB5wAhEAzOAgsCQCABIgQgAkcNAEH9ACEQDOcCCyACIARrIAAoAgAiAWohFCAEIAFrQQJqIRACQANAIAQtAAAgAUHtz4CAAGotAABHDc8BIAFBAkYNASABQQFqIQEgBEEBaiIEIAJHDQALIAAgFDYCAEH9ACEQDOcCCyAAQQA2AgAgEEEBaiEBQRAhEAzMAQsCQCABIgQgAkcNAEH+ACEQDOYCCyACIARrIAAoAgAiAWohFCAEIAFrQQVqIRACQANAIAQtAAAgAUH2zoCAAGotAABHDc4BIAFBBUYNASABQQFqIQEgBEEBaiIEIAJHDQALIAAgFDYCAEH+ACEQDOYCCyAAQQA2AgAgEEEBaiEBQRYhEAzLAQsCQCABIgQgAkcNAEH/ACEQDOUCCyACIARrIAAoAgAiAWohFCAEIAFrQQNqIRACQANAIAQtAAAgAUH8zoCAAGotAABHDc0BIAFBA0YNASABQQFqIQEgBEEBaiIEIAJHDQALIAAgFDYCAEH/ACEQDOUCCyAAQQA2AgAgEEEBaiEBQQUhEAzKAQsCQCABIgQgAkcNAEGAASEQDOQCCyAELQAAQdkARw3LASAEQQFqIQFBCCEQDMkBCwJAIAEiBCACRw0AQYEBIRAM4wILAkACQCAELQAAQbJ/ag4DAMwBAcwBCyAEQQFqIQFB6wAhEAzKAgsgBEEBaiEBQewAIRAMyQILAkAgASIEIAJHDQBBggEhEAziAgsCQAJAIAQtAABBuH9qDggAywHLAcsBywHLAcsBAcsBCyAEQQFqIQFB6gAhEAzJAgsgBEEBaiEBQe0AIRAMyAILAkAgASIEIAJHDQBBgwEhEAzhAgsgAiAEayAAKAIAIgFqIRAgBCABa0ECaiEUAkADQCAELQAAIAFBgM+AgABqLQAARw3JASABQQJGDQEgAUEBaiEBIARBAWoiBCACRw0ACyAAIBA2AgBBgwEhEAzhAgtBACEQIABBADYCACAUQQFqIQEMxgELAkAgASIEIAJHDQBBhAEhEAzgAgsgAiAEayAAKAIAIgFqIRQgBCABa0EEaiEQAkADQCAELQAAIAFBg8+AgABqLQAARw3IASABQQRGDQEgAUEBaiEBIARBAWoiBCACRw0ACyAAIBQ2AgBBhAEhEAzgAgsgAEEANgIAIBBBAWohAUEjIRAMxQELAkAgASIEIAJHDQBBhQEhEAzfAgsCQAJAIAQtAABBtH9qDggAyAHIAcgByAHIAcgBAcgBCyAEQQFqIQFB7wAhEAzGAgsgBEEBaiEBQfAAIRAMxQILAkAgASIEIAJHDQBBhgEhEAzeAgsgBC0AAEHFAEcNxQEgBEEBaiEBDIMCCwJAIAEiBCACRw0AQYcBIRAM3QILIAIgBGsgACgCACIBaiEUIAQgAWtBA2ohEAJAA0AgBC0AACABQYjPgIAAai0AAEcNxQEgAUEDRg0BIAFBAWohASAEQQFqIgQgAkcNAAsgACAUNgIAQYcBIRAM3QILIABBADYCACAQQQFqIQFBLSEQDMIBCwJAIAEiBCACRw0AQYgBIRAM3AILIAIgBGsgACgCACIBaiEUIAQgAWtBCGohEAJAA0AgBC0AACABQdDPgIAAai0AAEcNxAEgAUEIRg0BIAFBAWohASAEQQFqIgQgAkcNAAsgACAUNgIAQYgBIRAM3AILIABBADYCACAQQQFqIQFBKSEQDMEBCwJAIAEiASACRw0AQYkBIRAM2wILQQEhECABLQAAQd8ARw3AASABQQFqIQEMgQILAkAgASIEIAJHDQBBigEhEAzaAgsgAiAEayAAKAIAIgFqIRQgBCABa0EBaiEQA0AgBC0AACABQYzPgIAAai0AAEcNwQEgAUEBRg2vAiABQQFqIQEgBEEBaiIEIAJHDQALIAAgFDYCAEGKASEQDNkCCwJAIAEiBCACRw0AQYsBIRAM2QILIAIgBGsgACgCACIBaiEUIAQgAWtBAmohEAJAA0AgBC0AACABQY7PgIAAai0AAEcNwQEgAUECRg0BIAFBAWohASAEQQFqIgQgAkcNAAsgACAUNgIAQYsBIRAM2QILIABBADYCACAQQQFqIQFBAiEQDL4BCwJAIAEiBCACRw0AQYwBIRAM2AILIAIgBGsgACgCACIBaiEUIAQgAWtBAWohEAJAA0AgBC0AACABQfDPgIAAai0AAEcNwAEgAUEBRg0BIAFBAWohASAEQQFqIgQgAkcNAAsgACAUNgIAQYwBIRAM2AILIABBADYCACAQQQFqIQFBHyEQDL0BCwJAIAEiBCACRw0AQY0BIRAM1wILIAIgBGsgACgCACIBaiEUIAQgAWtBAWohEAJAA0AgBC0AACABQfLPgIAAai0AAEcNvwEgAUEBRg0BIAFBAWohASAEQQFqIgQgAkcNAAsgACAUNgIAQY0BIRAM1wILIABBADYCACAQQQFqIQFBCSEQDLwBCwJAIAEiBCACRw0AQY4BIRAM1gILAkACQCAELQAAQbd/ag4HAL8BvwG/Ab8BvwEBvwELIARBAWohAUH4ACEQDL0CCyAEQQFqIQFB+QAhEAy8AgsCQCABIgQgAkcNAEGPASEQDNUCCyACIARrIAAoAgAiAWohFCAEIAFrQQVqIRACQANAIAQtAAAgAUGRz4CAAGotAABHDb0BIAFBBUYNASABQQFqIQEgBEEBaiIEIAJHDQALIAAgFDYCAEGPASEQDNUCCyAAQQA2AgAgEEEBaiEBQRghEAy6AQsCQCABIgQgAkcNAEGQASEQDNQCCyACIARrIAAoAgAiAWohFCAEIAFrQQJqIRACQANAIAQtAAAgAUGXz4CAAGotAABHDbwBIAFBAkYNASABQQFqIQEgBEEBaiIEIAJHDQALIAAgFDYCAEGQASEQDNQCCyAAQQA2AgAgEEEBaiEBQRchEAy5AQsCQCABIgQgAkcNAEGRASEQDNMCCyACIARrIAAoAgAiAWohFCAEIAFrQQZqIRACQANAIAQtAAAgAUGaz4CAAGotAABHDbsBIAFBBkYNASABQQFqIQEgBEEBaiIEIAJHDQALIAAgFDYCAEGRASEQDNMCCyAAQQA2AgAgEEEBaiEBQRUhEAy4AQsCQCABIgQgAkcNAEGSASEQDNICCyACIARrIAAoAgAiAWohFCAEIAFrQQVqIRACQANAIAQtAAAgAUGhz4CAAGotAABHDboBIAFBBUYNASABQQFqIQEgBEEBaiIEIAJHDQALIAAgFDYCAEGSASEQDNICCyAAQQA2AgAgEEEBaiEBQR4hEAy3AQsCQCABIgQgAkcNAEGTASEQDNECCyAELQAAQcwARw24ASAEQQFqIQFBCiEQDLYBCwJAIAQgAkcNAEGUASEQDNACCwJAAkAgBC0AAEG/f2oODwC5AbkBuQG5AbkBuQG5AbkBuQG5AbkBuQG5AQG5AQsgBEEBaiEBQf4AIRAMtwILIARBAWohAUH/ACEQDLYCCwJAIAQgAkcNAEGVASEQDM8CCwJAAkAgBC0AAEG/f2oOAwC4AQG4AQsgBEEBaiEBQf0AIRAMtgILIARBAWohBEGAASEQDLUCCwJAIAQgAkcNAEGWASEQDM4CCyACIARrIAAoAgAiAWohFCAEIAFrQQFqIRACQANAIAQtAAAgAUGnz4CAAGotAABHDbYBIAFBAUYNASABQQFqIQEgBEEBaiIEIAJHDQALIAAgFDYCAEGWASEQDM4CCyAAQQA2AgAgEEEBaiEBQQshEAyzAQsCQCAEIAJHDQBBlwEhEAzNAgsCQAJAAkACQCAELQAAQVNqDiMAuAG4AbgBuAG4AbgBuAG4AbgBuAG4AbgBuAG4AbgBuAG4AbgBuAG4AbgBuAG4AQG4AbgBuAG4AbgBArgBuAG4AQO4AQsgBEEBaiEBQfsAIRAMtgILIARBAWohAUH8ACEQDLUCCyAEQQFqIQRBgQEhEAy0AgsgBEEBaiEEQYIBIRAMswILAkAgBCACRw0AQZgBIRAMzAILIAIgBGsgACgCACIBaiEUIAQgAWtBBGohEAJAA0AgBC0AACABQanPgIAAai0AAEcNtAEgAUEERg0BIAFBAWohASAEQQFqIgQgAkcNAAsgACAUNgIAQZgBIRAMzAILIABBADYCACAQQQFqIQFBGSEQDLEBCwJAIAQgAkcNAEGZASEQDMsCCyACIARrIAAoAgAiAWohFCAEIAFrQQVqIRACQANAIAQtAAAgAUGuz4CAAGotAABHDbMBIAFBBUYNASABQQFqIQEgBEEBaiIEIAJHDQALIAAgFDYCAEGZASEQDMsCCyAAQQA2AgAgEEEBaiEBQQYhEAywAQsCQCAEIAJHDQBBmgEhEAzKAgsgAiAEayAAKAIAIgFqIRQgBCABa0EBaiEQAkADQCAELQAAIAFBtM+AgABqLQAARw2yASABQQFGDQEgAUEBaiEBIARBAWoiBCACRw0ACyAAIBQ2AgBBmgEhEAzKAgsgAEEANgIAIBBBAWohAUEcIRAMrwELAkAgBCACRw0AQZsBIRAMyQILIAIgBGsgACgCACIBaiEUIAQgAWtBAWohEAJAA0AgBC0AACABQbbPgIAAai0AAEcNsQEgAUEBRg0BIAFBAWohASAEQQFqIgQgAkcNAAsgACAUNgIAQZsBIRAMyQILIABBADYCACAQQQFqIQFBJyEQDK4BCwJAIAQgAkcNAEGcASEQDMgCCwJAAkAgBC0AAEGsf2oOAgABsQELIARBAWohBEGGASEQDK8CCyAEQQFqIQRBhwEhEAyuAgsCQCAEIAJHDQBBnQEhEAzHAgsgAiAEayAAKAIAIgFqIRQgBCABa0EBaiEQAkADQCAELQAAIAFBuM+AgABqLQAARw2vASABQQFGDQEgAUEBaiEBIARBAWoiBCACRw0ACyAAIBQ2AgBBnQEhEAzHAgsgAEEANgIAIBBBAWohAUEmIRAMrAELAkAgBCACRw0AQZ4BIRAMxgILIAIgBGsgACgCACIBaiEUIAQgAWtBAWohEAJAA0AgBC0AACABQbrPgIAAai0AAEcNrgEgAUEBRg0BIAFBAWohASAEQQFqIgQgAkcNAAsgACAUNgIAQZ4BIRAMxgILIABBADYCACAQQQFqIQFBAyEQDKsBCwJAIAQgAkcNAEGfASEQDMUCCyACIARrIAAoAgAiAWohFCAEIAFrQQJqIRACQANAIAQtAAAgAUHtz4CAAGotAABHDa0BIAFBAkYNASABQQFqIQEgBEEBaiIEIAJHDQALIAAgFDYCAEGfASEQDMUCCyAAQQA2AgAgEEEBaiEBQQwhEAyqAQsCQCAEIAJHDQBBoAEhEAzEAgsgAiAEayAAKAIAIgFqIRQgBCABa0EDaiEQAkADQCAELQAAIAFBvM+AgABqLQAARw2sASABQQNGDQEgAUEBaiEBIARBAWoiBCACRw0ACyAAIBQ2AgBBoAEhEAzEAgsgAEEANgIAIBBBAWohAUENIRAMqQELAkAgBCACRw0AQaEBIRAMwwILAkACQCAELQAAQbp/ag4LAKwBrAGsAawBrAGsAawBrAGsAQGsAQsgBEEBaiEEQYsBIRAMqgILIARBAWohBEGMASEQDKkCCwJAIAQgAkcNAEGiASEQDMICCyAELQAAQdAARw2pASAEQQFqIQQM6QELAkAgBCACRw0AQaMBIRAMwQILAkACQCAELQAAQbd/ag4HAaoBqgGqAaoBqgEAqgELIARBAWohBEGOASEQDKgCCyAEQQFqIQFBIiEQDKYBCwJAIAQgAkcNAEGkASEQDMACCyACIARrIAAoAgAiAWohFCAEIAFrQQFqIRACQANAIAQtAAAgAUHAz4CAAGotAABHDagBIAFBAUYNASABQQFqIQEgBEEBaiIEIAJHDQALIAAgFDYCAEGkASEQDMACCyAAQQA2AgAgEEEBaiEBQR0hEAylAQsCQCAEIAJHDQBBpQEhEAy/AgsCQAJAIAQtAABBrn9qDgMAqAEBqAELIARBAWohBEGQASEQDKYCCyAEQQFqIQFBBCEQDKQBCwJAIAQgAkcNAEGmASEQDL4CCwJAAkACQAJAAkAgBC0AAEG/f2oOFQCqAaoBqgGqAaoBqgGqAaoBqgGqAQGqAaoBAqoBqgEDqgGqAQSqAQsgBEEBaiEEQYgBIRAMqAILIARBAWohBEGJASEQDKcCCyAEQQFqIQRBigEhEAymAgsgBEEBaiEEQY8BIRAMpQILIARBAWohBEGRASEQDKQCCwJAIAQgAkcNAEGnASEQDL0CCyACIARrIAAoAgAiAWohFCAEIAFrQQJqIRACQANAIAQtAAAgAUHtz4CAAGotAABHDaUBIAFBAkYNASABQQFqIQEgBEEBaiIEIAJHDQALIAAgFDYCAEGnASEQDL0CCyAAQQA2AgAgEEEBaiEBQREhEAyiAQsCQCAEIAJHDQBBqAEhEAy8AgsgAiAEayAAKAIAIgFqIRQgBCABa0ECaiEQAkADQCAELQAAIAFBws+AgABqLQAARw2kASABQQJGDQEgAUEBaiEBIARBAWoiBCACRw0ACyAAIBQ2AgBBqAEhEAy8AgsgAEEANgIAIBBBAWohAUEsIRAMoQELAkAgBCACRw0AQakBIRAMuwILIAIgBGsgACgCACIBaiEUIAQgAWtBBGohEAJAA0AgBC0AACABQcXPgIAAai0AAEcNowEgAUEERg0BIAFBAWohASAEQQFqIgQgAkcNAAsgACAUNgIAQakBIRAMuwILIABBADYCACAQQQFqIQFBKyEQDKABCwJAIAQgAkcNAEGqASEQDLoCCyACIARrIAAoAgAiAWohFCAEIAFrQQJqIRACQANAIAQtAAAgAUHKz4CAAGotAABHDaIBIAFBAkYNASABQQFqIQEgBEEBaiIEIAJHDQALIAAgFDYCAEGqASEQDLoCCyAAQQA2AgAgEEEBaiEBQRQhEAyfAQsCQCAEIAJHDQBBqwEhEAy5AgsCQAJAAkACQCAELQAAQb5/ag4PAAECpAGkAaQBpAGkAaQBpAGkAaQBpAGkAQOkAQsgBEEBaiEEQZMBIRAMogILIARBAWohBEGUASEQDKECCyAEQQFqIQRBlQEhEAygAgsgBEEBaiEEQZYBIRAMnwILAkAgBCACRw0AQawBIRAMuAILIAQtAABBxQBHDZ8BIARBAWohBAzgAQsCQCAEIAJHDQBBrQEhEAy3AgsgAiAEayAAKAIAIgFqIRQgBCABa0ECaiEQAkADQCAELQAAIAFBzc+AgABqLQAARw2fASABQQJGDQEgAUEBaiEBIARBAWoiBCACRw0ACyAAIBQ2AgBBrQEhEAy3AgsgAEEANgIAIBBBAWohAUEOIRAMnAELAkAgBCACRw0AQa4BIRAMtgILIAQtAABB0ABHDZ0BIARBAWohAUElIRAMmwELAkAgBCACRw0AQa8BIRAMtQILIAIgBGsgACgCACIBaiEUIAQgAWtBCGohEAJAA0AgBC0AACABQdDPgIAAai0AAEcNnQEgAUEIRg0BIAFBAWohASAEQQFqIgQgAkcNAAsgACAUNgIAQa8BIRAMtQILIABBADYCACAQQQFqIQFBKiEQDJoBCwJAIAQgAkcNAEGwASEQDLQCCwJAAkAgBC0AAEGrf2oOCwCdAZ0BnQGdAZ0BnQGdAZ0BnQEBnQELIARBAWohBEGaASEQDJsCCyAEQQFqIQRBmwEhEAyaAgsCQCAEIAJHDQBBsQEhEAyzAgsCQAJAIAQtAABBv39qDhQAnAGcAZwBnAGcAZwBnAGcAZwBnAGcAZwBnAGcAZwBnAGcAZwBAZwBCyAEQQFqIQRBmQEhEAyaAgsgBEEBaiEEQZwBIRAMmQILAkAgBCACRw0AQbIBIRAMsgILIAIgBGsgACgCACIBaiEUIAQgAWtBA2ohEAJAA0AgBC0AACABQdnPgIAAai0AAEcNmgEgAUEDRg0BIAFBAWohASAEQQFqIgQgAkcNAAsgACAUNgIAQbIBIRAMsgILIABBADYCACAQQQFqIQFBISEQDJcBCwJAIAQgAkcNAEGzASEQDLECCyACIARrIAAoAgAiAWohFCAEIAFrQQZqIRACQANAIAQtAAAgAUHdz4CAAGotAABHDZkBIAFBBkYNASABQQFqIQEgBEEBaiIEIAJHDQALIAAgFDYCAEGzASEQDLECCyAAQQA2AgAgEEEBaiEBQRohEAyWAQsCQCAEIAJHDQBBtAEhEAywAgsCQAJAAkAgBC0AAEG7f2oOEQCaAZoBmgGaAZoBmgGaAZoBmgEBmgGaAZoBmgGaAQKaAQsgBEEBaiEEQZ0BIRAMmAILIARBAWohBEGeASEQDJcCCyAEQQFqIQRBnwEhEAyWAgsCQCAEIAJHDQBBtQEhEAyvAgsgAiAEayAAKAIAIgFqIRQgBCABa0EFaiEQAkADQCAELQAAIAFB5M+AgABqLQAARw2XASABQQVGDQEgAUEBaiEBIARBAWoiBCACRw0ACyAAIBQ2AgBBtQEhEAyvAgsgAEEANgIAIBBBAWohAUEoIRAMlAELAkAgBCACRw0AQbYBIRAMrgILIAIgBGsgACgCACIBaiEUIAQgAWtBAmohEAJAA0AgBC0AACABQerPgIAAai0AAEcNlgEgAUECRg0BIAFBAWohASAEQQFqIgQgAkcNAAsgACAUNgIAQbYBIRAMrgILIABBADYCACAQQQFqIQFBByEQDJMBCwJAIAQgAkcNAEG3ASEQDK0CCwJAAkAgBC0AAEG7f2oODgCWAZYBlgGWAZYBlgGWAZYBlgGWAZYBlgEBlgELIARBAWohBEGhASEQDJQCCyAEQQFqIQRBogEhEAyTAgsCQCAEIAJHDQBBuAEhEAysAgsgAiAEayAAKAIAIgFqIRQgBCABa0ECaiEQAkADQCAELQAAIAFB7c+AgABqLQAARw2UASABQQJGDQEgAUEBaiEBIARBAWoiBCACRw0ACyAAIBQ2AgBBuAEhEAysAgsgAEEANgIAIBBBAWohAUESIRAMkQELAkAgBCACRw0AQbkBIRAMqwILIAIgBGsgACgCACIBaiEUIAQgAWtBAWohEAJAA0AgBC0AACABQfDPgIAAai0AAEcNkwEgAUEBRg0BIAFBAWohASAEQQFqIgQgAkcNAAsgACAUNgIAQbkBIRAMqwILIABBADYCACAQQQFqIQFBICEQDJABCwJAIAQgAkcNAEG6ASEQDKoCCyACIARrIAAoAgAiAWohFCAEIAFrQQFqIRACQANAIAQtAAAgAUHyz4CAAGotAABHDZIBIAFBAUYNASABQQFqIQEgBEEBaiIEIAJHDQALIAAgFDYCAEG6ASEQDKoCCyAAQQA2AgAgEEEBaiEBQQ8hEAyPAQsCQCAEIAJHDQBBuwEhEAypAgsCQAJAIAQtAABBt39qDgcAkgGSAZIBkgGSAQGSAQsgBEEBaiEEQaUBIRAMkAILIARBAWohBEGmASEQDI8CCwJAIAQgAkcNAEG8ASEQDKgCCyACIARrIAAoAgAiAWohFCAEIAFrQQdqIRACQANAIAQtAAAgAUH0z4CAAGotAABHDZABIAFBB0YNASABQQFqIQEgBEEBaiIEIAJHDQALIAAgFDYCAEG8ASEQDKgCCyAAQQA2AgAgEEEBaiEBQRshEAyNAQsCQCAEIAJHDQBBvQEhEAynAgsCQAJAAkAgBC0AAEG+f2oOEgCRAZEBkQGRAZEBkQGRAZEBkQEBkQGRAZEBkQGRAZEBApEBCyAEQQFqIQRBpAEhEAyPAgsgBEEBaiEEQacBIRAMjgILIARBAWohBEGoASEQDI0CCwJAIAQgAkcNAEG+ASEQDKYCCyAELQAAQc4ARw2NASAEQQFqIQQMzwELAkAgBCACRw0AQb8BIRAMpQILAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkAgBC0AAEG/f2oOFQABAgOcAQQFBpwBnAGcAQcICQoLnAEMDQ4PnAELIARBAWohAUHoACEQDJoCCyAEQQFqIQFB6QAhEAyZAgsgBEEBaiEBQe4AIRAMmAILIARBAWohAUHyACEQDJcCCyAEQQFqIQFB8wAhEAyWAgsgBEEBaiEBQfYAIRAMlQILIARBAWohAUH3ACEQDJQCCyAEQQFqIQFB+gAhEAyTAgsgBEEBaiEEQYMBIRAMkgILIARBAWohBEGEASEQDJECCyAEQQFqIQRBhQEhEAyQAgsgBEEBaiEEQZIBIRAMjwILIARBAWohBEGYASEQDI4CCyAEQQFqIQRBoAEhEAyNAgsgBEEBaiEEQaMBIRAMjAILIARBAWohBEGqASEQDIsCCwJAIAQgAkYNACAAQZCAgIAANgIIIAAgBDYCBEGrASEQDIsCC0HAASEQDKMCCyAAIAUgAhCqgICAACIBDYsBIAUhAQxcCwJAIAYgAkYNACAGQQFqIQUMjQELQcIBIRAMoQILA0ACQCAQLQAAQXZqDgSMAQAAjwEACyAQQQFqIhAgAkcNAAtBwwEhEAygAgsCQCAHIAJGDQAgAEGRgICAADYCCCAAIAc2AgQgByEBQQEhEAyHAgtBxAEhEAyfAgsCQCAHIAJHDQBBxQEhEAyfAgsCQAJAIActAABBdmoOBAHOAc4BAM4BCyAHQQFqIQYMjQELIAdBAWohBQyJAQsCQCAHIAJHDQBBxgEhEAyeAgsCQAJAIActAABBdmoOFwGPAY8BAY8BjwGPAY8BjwGPAY8BjwGPAY8BjwGPAY8BjwGPAY8BjwGPAQCPAQsgB0EBaiEHC0GwASEQDIQCCwJAIAggAkcNAEHIASEQDJ0CCyAILQAAQSBHDY0BIABBADsBMiAIQQFqIQFBswEhEAyDAgsgASEXAkADQCAXIgcgAkYNASAHLQAAQVBqQf8BcSIQQQpPDcwBAkAgAC8BMiIUQZkzSw0AIAAgFEEKbCIUOwEyIBBB//8DcyAUQf7/A3FJDQAgB0EBaiEXIAAgFCAQaiIQOwEyIBBB//8DcUHoB0kNAQsLQQAhECAAQQA2AhwgAEHBiYCAADYCECAAQQ02AgwgACAHQQFqNgIUDJwCC0HHASEQDJsCCyAAIAggAhCugICAACIQRQ3KASAQQRVHDYwBIABByAE2AhwgACAINgIUIABByZeAgAA2AhAgAEEVNgIMQQAhEAyaAgsCQCAJIAJHDQBBzAEhEAyaAgtBACEUQQEhF0EBIRZBACEQAkACQAJAAkACQAJAAkACQAJAIAktAABBUGoOCpYBlQEAAQIDBAUGCJcBC0ECIRAMBgtBAyEQDAULQQQhEAwEC0EFIRAMAwtBBiEQDAILQQchEAwBC0EIIRALQQAhF0EAIRZBACEUDI4BC0EJIRBBASEUQQAhF0EAIRYMjQELAkAgCiACRw0AQc4BIRAMmQILIAotAABBLkcNjgEgCkEBaiEJDMoBCyALIAJHDY4BQdABIRAMlwILAkAgCyACRg0AIABBjoCAgAA2AgggACALNgIEQbcBIRAM/gELQdEBIRAMlgILAkAgBCACRw0AQdIBIRAMlgILIAIgBGsgACgCACIQaiEUIAQgEGtBBGohCwNAIAQtAAAgEEH8z4CAAGotAABHDY4BIBBBBEYN6QEgEEEBaiEQIARBAWoiBCACRw0ACyAAIBQ2AgBB0gEhEAyVAgsgACAMIAIQrICAgAAiAQ2NASAMIQEMuAELAkAgBCACRw0AQdQBIRAMlAILIAIgBGsgACgCACIQaiEUIAQgEGtBAWohDANAIAQtAAAgEEGB0ICAAGotAABHDY8BIBBBAUYNjgEgEEEBaiEQIARBAWoiBCACRw0ACyAAIBQ2AgBB1AEhEAyTAgsCQCAEIAJHDQBB1gEhEAyTAgsgAiAEayAAKAIAIhBqIRQgBCAQa0ECaiELA0AgBC0AACAQQYPQgIAAai0AAEcNjgEgEEECRg2QASAQQQFqIRAgBEEBaiIEIAJHDQALIAAgFDYCAEHWASEQDJICCwJAIAQgAkcNAEHXASEQDJICCwJAAkAgBC0AAEG7f2oOEACPAY8BjwGPAY8BjwGPAY8BjwGPAY8BjwGPAY8BAY8BCyAEQQFqIQRBuwEhEAz5AQsgBEEBaiEEQbwBIRAM+AELAkAgBCACRw0AQdgBIRAMkQILIAQtAABByABHDYwBIARBAWohBAzEAQsCQCAEIAJGDQAgAEGQgICAADYCCCAAIAQ2AgRBvgEhEAz3AQtB2QEhEAyPAgsCQCAEIAJHDQBB2gEhEAyPAgsgBC0AAEHIAEYNwwEgAEEBOgAoDLkBCyAAQQI6AC8gACAEIAIQpoCAgAAiEA2NAUHCASEQDPQBCyAALQAoQX9qDgK3AbkBuAELA0ACQCAELQAAQXZqDgQAjgGOAQCOAQsgBEEBaiIEIAJHDQALQd0BIRAMiwILIABBADoALyAALQAtQQRxRQ2EAgsgAEEAOgAvIABBAToANCABIQEMjAELIBBBFUYN2gEgAEEANgIcIAAgATYCFCAAQaeOgIAANgIQIABBEjYCDEEAIRAMiAILAkAgACAQIAIQtICAgAAiBA0AIBAhAQyBAgsCQCAEQRVHDQAgAEEDNgIcIAAgEDYCFCAAQbCYgIAANgIQIABBFTYCDEEAIRAMiAILIABBADYCHCAAIBA2AhQgAEGnjoCAADYCECAAQRI2AgxBACEQDIcCCyAQQRVGDdYBIABBADYCHCAAIAE2AhQgAEHajYCAADYCECAAQRQ2AgxBACEQDIYCCyAAKAIEIRcgAEEANgIEIBAgEadqIhYhASAAIBcgECAWIBQbIhAQtYCAgAAiFEUNjQEgAEEHNgIcIAAgEDYCFCAAIBQ2AgxBACEQDIUCCyAAIAAvATBBgAFyOwEwIAEhAQtBKiEQDOoBCyAQQRVGDdEBIABBADYCHCAAIAE2AhQgAEGDjICAADYCECAAQRM2AgxBACEQDIICCyAQQRVGDc8BIABBADYCHCAAIAE2AhQgAEGaj4CAADYCECAAQSI2AgxBACEQDIECCyAAKAIEIRAgAEEANgIEAkAgACAQIAEQt4CAgAAiEA0AIAFBAWohAQyNAQsgAEEMNgIcIAAgEDYCDCAAIAFBAWo2AhRBACEQDIACCyAQQRVGDcwBIABBADYCHCAAIAE2AhQgAEGaj4CAADYCECAAQSI2AgxBACEQDP8BCyAAKAIEIRAgAEEANgIEAkAgACAQIAEQt4CAgAAiEA0AIAFBAWohAQyMAQsgAEENNgIcIAAgEDYCDCAAIAFBAWo2AhRBACEQDP4BCyAQQRVGDckBIABBADYCHCAAIAE2AhQgAEHGjICAADYCECAAQSM2AgxBACEQDP0BCyAAKAIEIRAgAEEANgIEAkAgACAQIAEQuYCAgAAiEA0AIAFBAWohAQyLAQsgAEEONgIcIAAgEDYCDCAAIAFBAWo2AhRBACEQDPwBCyAAQQA2AhwgACABNgIUIABBwJWAgAA2AhAgAEECNgIMQQAhEAz7AQsgEEEVRg3FASAAQQA2AhwgACABNgIUIABBxoyAgAA2AhAgAEEjNgIMQQAhEAz6AQsgAEEQNgIcIAAgATYCFCAAIBA2AgxBACEQDPkBCyAAKAIEIQQgAEEANgIEAkAgACAEIAEQuYCAgAAiBA0AIAFBAWohAQzxAQsgAEERNgIcIAAgBDYCDCAAIAFBAWo2AhRBACEQDPgBCyAQQRVGDcEBIABBADYCHCAAIAE2AhQgAEHGjICAADYCECAAQSM2AgxBACEQDPcBCyAAKAIEIRAgAEEANgIEAkAgACAQIAEQuYCAgAAiEA0AIAFBAWohAQyIAQsgAEETNgIcIAAgEDYCDCAAIAFBAWo2AhRBACEQDPYBCyAAKAIEIQQgAEEANgIEAkAgACAEIAEQuYCAgAAiBA0AIAFBAWohAQztAQsgAEEUNgIcIAAgBDYCDCAAIAFBAWo2AhRBACEQDPUBCyAQQRVGDb0BIABBADYCHCAAIAE2AhQgAEGaj4CAADYCECAAQSI2AgxBACEQDPQBCyAAKAIEIRAgAEEANgIEAkAgACAQIAEQt4CAgAAiEA0AIAFBAWohAQyGAQsgAEEWNgIcIAAgEDYCDCAAIAFBAWo2AhRBACEQDPMBCyAAKAIEIQQgAEEANgIEAkAgACAEIAEQt4CAgAAiBA0AIAFBAWohAQzpAQsgAEEXNgIcIAAgBDYCDCAAIAFBAWo2AhRBACEQDPIBCyAAQQA2AhwgACABNgIUIABBzZOAgAA2AhAgAEEMNgIMQQAhEAzxAQtCASERCyAQQQFqIQECQCAAKQMgIhJC//////////8PVg0AIAAgEkIEhiARhDcDICABIQEMhAELIABBADYCHCAAIAE2AhQgAEGtiYCAADYCECAAQQw2AgxBACEQDO8BCyAAQQA2AhwgACAQNgIUIABBzZOAgAA2AhAgAEEMNgIMQQAhEAzuAQsgACgCBCEXIABBADYCBCAQIBGnaiIWIQEgACAXIBAgFiAUGyIQELWAgIAAIhRFDXMgAEEFNgIcIAAgEDYCFCAAIBQ2AgxBACEQDO0BCyAAQQA2AhwgACAQNgIUIABBqpyAgAA2AhAgAEEPNgIMQQAhEAzsAQsgACAQIAIQtICAgAAiAQ0BIBAhAQtBDiEQDNEBCwJAIAFBFUcNACAAQQI2AhwgACAQNgIUIABBsJiAgAA2AhAgAEEVNgIMQQAhEAzqAQsgAEEANgIcIAAgEDYCFCAAQaeOgIAANgIQIABBEjYCDEEAIRAM6QELIAFBAWohEAJAIAAvATAiAUGAAXFFDQACQCAAIBAgAhC7gICAACIBDQAgECEBDHALIAFBFUcNugEgAEEFNgIcIAAgEDYCFCAAQfmXgIAANgIQIABBFTYCDEEAIRAM6QELAkAgAUGgBHFBoARHDQAgAC0ALUECcQ0AIABBADYCHCAAIBA2AhQgAEGWk4CAADYCECAAQQQ2AgxBACEQDOkBCyAAIBAgAhC9gICAABogECEBAkACQAJAAkACQCAAIBAgAhCzgICAAA4WAgEABAQEBAQEBAQEBAQEBAQEBAQEAwQLIABBAToALgsgACAALwEwQcAAcjsBMCAQIQELQSYhEAzRAQsgAEEjNgIcIAAgEDYCFCAAQaWWgIAANgIQIABBFTYCDEEAIRAM6QELIABBADYCHCAAIBA2AhQgAEHVi4CAADYCECAAQRE2AgxBACEQDOgBCyAALQAtQQFxRQ0BQcMBIRAMzgELAkAgDSACRg0AA0ACQCANLQAAQSBGDQAgDSEBDMQBCyANQQFqIg0gAkcNAAtBJSEQDOcBC0ElIRAM5gELIAAoAgQhBCAAQQA2AgQgACAEIA0Qr4CAgAAiBEUNrQEgAEEmNgIcIAAgBDYCDCAAIA1BAWo2AhRBACEQDOUBCyAQQRVGDasBIABBADYCHCAAIAE2AhQgAEH9jYCAADYCECAAQR02AgxBACEQDOQBCyAAQSc2AhwgACABNgIUIAAgEDYCDEEAIRAM4wELIBAhAUEBIRQCQAJAAkACQAJAAkACQCAALQAsQX5qDgcGBQUDAQIABQsgACAALwEwQQhyOwEwDAMLQQIhFAwBC0EEIRQLIABBAToALCAAIAAvATAgFHI7ATALIBAhAQtBKyEQDMoBCyAAQQA2AhwgACAQNgIUIABBq5KAgAA2AhAgAEELNgIMQQAhEAziAQsgAEEANgIcIAAgATYCFCAAQeGPgIAANgIQIABBCjYCDEEAIRAM4QELIABBADoALCAQIQEMvQELIBAhAUEBIRQCQAJAAkACQAJAIAAtACxBe2oOBAMBAgAFCyAAIAAvATBBCHI7ATAMAwtBAiEUDAELQQQhFAsgAEEBOgAsIAAgAC8BMCAUcjsBMAsgECEBC0EpIRAMxQELIABBADYCHCAAIAE2AhQgAEHwlICAADYCECAAQQM2AgxBACEQDN0BCwJAIA4tAABBDUcNACAAKAIEIQEgAEEANgIEAkAgACABIA4QsYCAgAAiAQ0AIA5BAWohAQx1CyAAQSw2AhwgACABNgIMIAAgDkEBajYCFEEAIRAM3QELIAAtAC1BAXFFDQFBxAEhEAzDAQsCQCAOIAJHDQBBLSEQDNwBCwJAAkADQAJAIA4tAABBdmoOBAIAAAMACyAOQQFqIg4gAkcNAAtBLSEQDN0BCyAAKAIEIQEgAEEANgIEAkAgACABIA4QsYCAgAAiAQ0AIA4hAQx0CyAAQSw2AhwgACAONgIUIAAgATYCDEEAIRAM3AELIAAoAgQhASAAQQA2AgQCQCAAIAEgDhCxgICAACIBDQAgDkEBaiEBDHMLIABBLDYCHCAAIAE2AgwgACAOQQFqNgIUQQAhEAzbAQsgACgCBCEEIABBADYCBCAAIAQgDhCxgICAACIEDaABIA4hAQzOAQsgEEEsRw0BIAFBAWohEEEBIQECQAJAAkACQAJAIAAtACxBe2oOBAMBAgQACyAQIQEMBAtBAiEBDAELQQQhAQsgAEEBOgAsIAAgAC8BMCABcjsBMCAQIQEMAQsgACAALwEwQQhyOwEwIBAhAQtBOSEQDL8BCyAAQQA6ACwgASEBC0E0IRAMvQELIAAgAC8BMEEgcjsBMCABIQEMAgsgACgCBCEEIABBADYCBAJAIAAgBCABELGAgIAAIgQNACABIQEMxwELIABBNzYCHCAAIAE2AhQgACAENgIMQQAhEAzUAQsgAEEIOgAsIAEhAQtBMCEQDLkBCwJAIAAtAChBAUYNACABIQEMBAsgAC0ALUEIcUUNkwEgASEBDAMLIAAtADBBIHENlAFBxQEhEAy3AQsCQCAPIAJGDQACQANAAkAgDy0AAEFQaiIBQf8BcUEKSQ0AIA8hAUE1IRAMugELIAApAyAiEUKZs+bMmbPmzBlWDQEgACARQgp+IhE3AyAgESABrUL/AYMiEkJ/hVYNASAAIBEgEnw3AyAgD0EBaiIPIAJHDQALQTkhEAzRAQsgACgCBCECIABBADYCBCAAIAIgD0EBaiIEELGAgIAAIgINlQEgBCEBDMMBC0E5IRAMzwELAkAgAC8BMCIBQQhxRQ0AIAAtAChBAUcNACAALQAtQQhxRQ2QAQsgACABQff7A3FBgARyOwEwIA8hAQtBNyEQDLQBCyAAIAAvATBBEHI7ATAMqwELIBBBFUYNiwEgAEEANgIcIAAgATYCFCAAQfCOgIAANgIQIABBHDYCDEEAIRAMywELIABBwwA2AhwgACABNgIMIAAgDUEBajYCFEEAIRAMygELAkAgAS0AAEE6Rw0AIAAoAgQhECAAQQA2AgQCQCAAIBAgARCvgICAACIQDQAgAUEBaiEBDGMLIABBwwA2AhwgACAQNgIMIAAgAUEBajYCFEEAIRAMygELIABBADYCHCAAIAE2AhQgAEGxkYCAADYCECAAQQo2AgxBACEQDMkBCyAAQQA2AhwgACABNgIUIABBoJmAgAA2AhAgAEEeNgIMQQAhEAzIAQsgAEEANgIACyAAQYASOwEqIAAgF0EBaiIBIAIQqICAgAAiEA0BIAEhAQtBxwAhEAysAQsgEEEVRw2DASAAQdEANgIcIAAgATYCFCAAQeOXgIAANgIQIABBFTYCDEEAIRAMxAELIAAoAgQhECAAQQA2AgQCQCAAIBAgARCngICAACIQDQAgASEBDF4LIABB0gA2AhwgACABNgIUIAAgEDYCDEEAIRAMwwELIABBADYCHCAAIBQ2AhQgAEHBqICAADYCECAAQQc2AgwgAEEANgIAQQAhEAzCAQsgACgCBCEQIABBADYCBAJAIAAgECABEKeAgIAAIhANACABIQEMXQsgAEHTADYCHCAAIAE2AhQgACAQNgIMQQAhEAzBAQtBACEQIABBADYCHCAAIAE2AhQgAEGAkYCAADYCECAAQQk2AgwMwAELIBBBFUYNfSAAQQA2AhwgACABNgIUIABBlI2AgAA2AhAgAEEhNgIMQQAhEAy/AQtBASEWQQAhF0EAIRRBASEQCyAAIBA6ACsgAUEBaiEBAkACQCAALQAtQRBxDQACQAJAAkAgAC0AKg4DAQACBAsgFkUNAwwCCyAUDQEMAgsgF0UNAQsgACgCBCEQIABBADYCBAJAIAAgECABEK2AgIAAIhANACABIQEMXAsgAEHYADYCHCAAIAE2AhQgACAQNgIMQQAhEAy+AQsgACgCBCEEIABBADYCBAJAIAAgBCABEK2AgIAAIgQNACABIQEMrQELIABB2QA2AhwgACABNgIUIAAgBDYCDEEAIRAMvQELIAAoAgQhBCAAQQA2AgQCQCAAIAQgARCtgICAACIEDQAgASEBDKsBCyAAQdoANgIcIAAgATYCFCAAIAQ2AgxBACEQDLwBCyAAKAIEIQQgAEEANgIEAkAgACAEIAEQrYCAgAAiBA0AIAEhAQypAQsgAEHcADYCHCAAIAE2AhQgACAENgIMQQAhEAy7AQsCQCABLQAAQVBqIhBB/wFxQQpPDQAgACAQOgAqIAFBAWohAUHPACEQDKIBCyAAKAIEIQQgAEEANgIEAkAgACAEIAEQrYCAgAAiBA0AIAEhAQynAQsgAEHeADYCHCAAIAE2AhQgACAENgIMQQAhEAy6AQsgAEEANgIAIBdBAWohAQJAIAAtAClBI08NACABIQEMWQsgAEEANgIcIAAgATYCFCAAQdOJgIAANgIQIABBCDYCDEEAIRAMuQELIABBADYCAAtBACEQIABBADYCHCAAIAE2AhQgAEGQs4CAADYCECAAQQg2AgwMtwELIABBADYCACAXQQFqIQECQCAALQApQSFHDQAgASEBDFYLIABBADYCHCAAIAE2AhQgAEGbioCAADYCECAAQQg2AgxBACEQDLYBCyAAQQA2AgAgF0EBaiEBAkAgAC0AKSIQQV1qQQtPDQAgASEBDFULAkAgEEEGSw0AQQEgEHRBygBxRQ0AIAEhAQxVC0EAIRAgAEEANgIcIAAgATYCFCAAQfeJgIAANgIQIABBCDYCDAy1AQsgEEEVRg1xIABBADYCHCAAIAE2AhQgAEG5jYCAADYCECAAQRo2AgxBACEQDLQBCyAAKAIEIRAgAEEANgIEAkAgACAQIAEQp4CAgAAiEA0AIAEhAQxUCyAAQeUANgIcIAAgATYCFCAAIBA2AgxBACEQDLMBCyAAKAIEIRAgAEEANgIEAkAgACAQIAEQp4CAgAAiEA0AIAEhAQxNCyAAQdIANgIcIAAgATYCFCAAIBA2AgxBACEQDLIBCyAAKAIEIRAgAEEANgIEAkAgACAQIAEQp4CAgAAiEA0AIAEhAQxNCyAAQdMANgIcIAAgATYCFCAAIBA2AgxBACEQDLEBCyAAKAIEIRAgAEEANgIEAkAgACAQIAEQp4CAgAAiEA0AIAEhAQxRCyAAQeUANgIcIAAgATYCFCAAIBA2AgxBACEQDLABCyAAQQA2AhwgACABNgIUIABBxoqAgAA2AhAgAEEHNgIMQQAhEAyvAQsgACgCBCEQIABBADYCBAJAIAAgECABEKeAgIAAIhANACABIQEMSQsgAEHSADYCHCAAIAE2AhQgACAQNgIMQQAhEAyuAQsgACgCBCEQIABBADYCBAJAIAAgECABEKeAgIAAIhANACABIQEMSQsgAEHTADYCHCAAIAE2AhQgACAQNgIMQQAhEAytAQsgACgCBCEQIABBADYCBAJAIAAgECABEKeAgIAAIhANACABIQEMTQsgAEHlADYCHCAAIAE2AhQgACAQNgIMQQAhEAysAQsgAEEANgIcIAAgATYCFCAAQdyIgIAANgIQIABBBzYCDEEAIRAMqwELIBBBP0cNASABQQFqIQELQQUhEAyQAQtBACEQIABBADYCHCAAIAE2AhQgAEH9koCAADYCECAAQQc2AgwMqAELIAAoAgQhECAAQQA2AgQCQCAAIBAgARCngICAACIQDQAgASEBDEILIABB0gA2AhwgACABNgIUIAAgEDYCDEEAIRAMpwELIAAoAgQhECAAQQA2AgQCQCAAIBAgARCngICAACIQDQAgASEBDEILIABB0wA2AhwgACABNgIUIAAgEDYCDEEAIRAMpgELIAAoAgQhECAAQQA2AgQCQCAAIBAgARCngICAACIQDQAgASEBDEYLIABB5QA2AhwgACABNgIUIAAgEDYCDEEAIRAMpQELIAAoAgQhASAAQQA2AgQCQCAAIAEgFBCngICAACIBDQAgFCEBDD8LIABB0gA2AhwgACAUNgIUIAAgATYCDEEAIRAMpAELIAAoAgQhASAAQQA2AgQCQCAAIAEgFBCngICAACIBDQAgFCEBDD8LIABB0wA2AhwgACAUNgIUIAAgATYCDEEAIRAMowELIAAoAgQhASAAQQA2AgQCQCAAIAEgFBCngICAACIBDQAgFCEBDEMLIABB5QA2AhwgACAUNgIUIAAgATYCDEEAIRAMogELIABBADYCHCAAIBQ2AhQgAEHDj4CAADYCECAAQQc2AgxBACEQDKEBCyAAQQA2AhwgACABNgIUIABBw4+AgAA2AhAgAEEHNgIMQQAhEAygAQtBACEQIABBADYCHCAAIBQ2AhQgAEGMnICAADYCECAAQQc2AgwMnwELIABBADYCHCAAIBQ2AhQgAEGMnICAADYCECAAQQc2AgxBACEQDJ4BCyAAQQA2AhwgACAUNgIUIABB/pGAgAA2AhAgAEEHNgIMQQAhEAydAQsgAEEANgIcIAAgATYCFCAAQY6bgIAANgIQIABBBjYCDEEAIRAMnAELIBBBFUYNVyAAQQA2AhwgACABNgIUIABBzI6AgAA2AhAgAEEgNgIMQQAhEAybAQsgAEEANgIAIBBBAWohAUEkIRALIAAgEDoAKSAAKAIEIRAgAEEANgIEIAAgECABEKuAgIAAIhANVCABIQEMPgsgAEEANgIAC0EAIRAgAEEANgIcIAAgBDYCFCAAQfGbgIAANgIQIABBBjYCDAyXAQsgAUEVRg1QIABBADYCHCAAIAU2AhQgAEHwjICAADYCECAAQRs2AgxBACEQDJYBCyAAKAIEIQUgAEEANgIEIAAgBSAQEKmAgIAAIgUNASAQQQFqIQULQa0BIRAMewsgAEHBATYCHCAAIAU2AgwgACAQQQFqNgIUQQAhEAyTAQsgACgCBCEGIABBADYCBCAAIAYgEBCpgICAACIGDQEgEEEBaiEGC0GuASEQDHgLIABBwgE2AhwgACAGNgIMIAAgEEEBajYCFEEAIRAMkAELIABBADYCHCAAIAc2AhQgAEGXi4CAADYCECAAQQ02AgxBACEQDI8BCyAAQQA2AhwgACAINgIUIABB45CAgAA2AhAgAEEJNgIMQQAhEAyOAQsgAEEANgIcIAAgCDYCFCAAQZSNgIAANgIQIABBITYCDEEAIRAMjQELQQEhFkEAIRdBACEUQQEhEAsgACAQOgArIAlBAWohCAJAAkAgAC0ALUEQcQ0AAkACQAJAIAAtACoOAwEAAgQLIBZFDQMMAgsgFA0BDAILIBdFDQELIAAoAgQhECAAQQA2AgQgACAQIAgQrYCAgAAiEEUNPSAAQckBNgIcIAAgCDYCFCAAIBA2AgxBACEQDIwBCyAAKAIEIQQgAEEANgIEIAAgBCAIEK2AgIAAIgRFDXYgAEHKATYCHCAAIAg2AhQgACAENgIMQQAhEAyLAQsgACgCBCEEIABBADYCBCAAIAQgCRCtgICAACIERQ10IABBywE2AhwgACAJNgIUIAAgBDYCDEEAIRAMigELIAAoAgQhBCAAQQA2AgQgACAEIAoQrYCAgAAiBEUNciAAQc0BNgIcIAAgCjYCFCAAIAQ2AgxBACEQDIkBCwJAIAstAABBUGoiEEH/AXFBCk8NACAAIBA6ACogC0EBaiEKQbYBIRAMcAsgACgCBCEEIABBADYCBCAAIAQgCxCtgICAACIERQ1wIABBzwE2AhwgACALNgIUIAAgBDYCDEEAIRAMiAELIABBADYCHCAAIAQ2AhQgAEGQs4CAADYCECAAQQg2AgwgAEEANgIAQQAhEAyHAQsgAUEVRg0/IABBADYCHCAAIAw2AhQgAEHMjoCAADYCECAAQSA2AgxBACEQDIYBCyAAQYEEOwEoIAAoAgQhECAAQgA3AwAgACAQIAxBAWoiDBCrgICAACIQRQ04IABB0wE2AhwgACAMNgIUIAAgEDYCDEEAIRAMhQELIABBADYCAAtBACEQIABBADYCHCAAIAQ2AhQgAEHYm4CAADYCECAAQQg2AgwMgwELIAAoAgQhECAAQgA3AwAgACAQIAtBAWoiCxCrgICAACIQDQFBxgEhEAxpCyAAQQI6ACgMVQsgAEHVATYCHCAAIAs2AhQgACAQNgIMQQAhEAyAAQsgEEEVRg03IABBADYCHCAAIAQ2AhQgAEGkjICAADYCECAAQRA2AgxBACEQDH8LIAAtADRBAUcNNCAAIAQgAhC8gICAACIQRQ00IBBBFUcNNSAAQdwBNgIcIAAgBDYCFCAAQdWWgIAANgIQIABBFTYCDEEAIRAMfgtBACEQIABBADYCHCAAQa+LgIAANgIQIABBAjYCDCAAIBRBAWo2AhQMfQtBACEQDGMLQQIhEAxiC0ENIRAMYQtBDyEQDGALQSUhEAxfC0ETIRAMXgtBFSEQDF0LQRYhEAxcC0EXIRAMWwtBGCEQDFoLQRkhEAxZC0EaIRAMWAtBGyEQDFcLQRwhEAxWC0EdIRAMVQtBHyEQDFQLQSEhEAxTC0EjIRAMUgtBxgAhEAxRC0EuIRAMUAtBLyEQDE8LQTshEAxOC0E9IRAMTQtByAAhEAxMC0HJACEQDEsLQcsAIRAMSgtBzAAhEAxJC0HOACEQDEgLQdEAIRAMRwtB1QAhEAxGC0HYACEQDEULQdkAIRAMRAtB2wAhEAxDC0HkACEQDEILQeUAIRAMQQtB8QAhEAxAC0H0ACEQDD8LQY0BIRAMPgtBlwEhEAw9C0GpASEQDDwLQawBIRAMOwtBwAEhEAw6C0G5ASEQDDkLQa8BIRAMOAtBsQEhEAw3C0GyASEQDDYLQbQBIRAMNQtBtQEhEAw0C0G6ASEQDDMLQb0BIRAMMgtBvwEhEAwxC0HBASEQDDALIABBADYCHCAAIAQ2AhQgAEHpi4CAADYCECAAQR82AgxBACEQDEgLIABB2wE2AhwgACAENgIUIABB+paAgAA2AhAgAEEVNgIMQQAhEAxHCyAAQfgANgIcIAAgDDYCFCAAQcqYgIAANgIQIABBFTYCDEEAIRAMRgsgAEHRADYCHCAAIAU2AhQgAEGwl4CAADYCECAAQRU2AgxBACEQDEULIABB+QA2AhwgACABNgIUIAAgEDYCDEEAIRAMRAsgAEH4ADYCHCAAIAE2AhQgAEHKmICAADYCECAAQRU2AgxBACEQDEMLIABB5AA2AhwgACABNgIUIABB45eAgAA2AhAgAEEVNgIMQQAhEAxCCyAAQdcANgIcIAAgATYCFCAAQcmXgIAANgIQIABBFTYCDEEAIRAMQQsgAEEANgIcIAAgATYCFCAAQbmNgIAANgIQIABBGjYCDEEAIRAMQAsgAEHCADYCHCAAIAE2AhQgAEHjmICAADYCECAAQRU2AgxBACEQDD8LIABBADYCBCAAIA8gDxCxgICAACIERQ0BIABBOjYCHCAAIAQ2AgwgACAPQQFqNgIUQQAhEAw+CyAAKAIEIQQgAEEANgIEAkAgACAEIAEQsYCAgAAiBEUNACAAQTs2AhwgACAENgIMIAAgAUEBajYCFEEAIRAMPgsgAUEBaiEBDC0LIA9BAWohAQwtCyAAQQA2AhwgACAPNgIUIABB5JKAgAA2AhAgAEEENgIMQQAhEAw7CyAAQTY2AhwgACAENgIUIAAgAjYCDEEAIRAMOgsgAEEuNgIcIAAgDjYCFCAAIAQ2AgxBACEQDDkLIABB0AA2AhwgACABNgIUIABBkZiAgAA2AhAgAEEVNgIMQQAhEAw4CyANQQFqIQEMLAsgAEEVNgIcIAAgATYCFCAAQYKZgIAANgIQIABBFTYCDEEAIRAMNgsgAEEbNgIcIAAgATYCFCAAQZGXgIAANgIQIABBFTYCDEEAIRAMNQsgAEEPNgIcIAAgATYCFCAAQZGXgIAANgIQIABBFTYCDEEAIRAMNAsgAEELNgIcIAAgATYCFCAAQZGXgIAANgIQIABBFTYCDEEAIRAMMwsgAEEaNgIcIAAgATYCFCAAQYKZgIAANgIQIABBFTYCDEEAIRAMMgsgAEELNgIcIAAgATYCFCAAQYKZgIAANgIQIABBFTYCDEEAIRAMMQsgAEEKNgIcIAAgATYCFCAAQeSWgIAANgIQIABBFTYCDEEAIRAMMAsgAEEeNgIcIAAgATYCFCAAQfmXgIAANgIQIABBFTYCDEEAIRAMLwsgAEEANgIcIAAgEDYCFCAAQdqNgIAANgIQIABBFDYCDEEAIRAMLgsgAEEENgIcIAAgATYCFCAAQbCYgIAANgIQIABBFTYCDEEAIRAMLQsgAEEANgIAIAtBAWohCwtBuAEhEAwSCyAAQQA2AgAgEEEBaiEBQfUAIRAMEQsgASEBAkAgAC0AKUEFRw0AQeMAIRAMEQtB4gAhEAwQC0EAIRAgAEEANgIcIABB5JGAgAA2AhAgAEEHNgIMIAAgFEEBajYCFAwoCyAAQQA2AgAgF0EBaiEBQcAAIRAMDgtBASEBCyAAIAE6ACwgAEEANgIAIBdBAWohAQtBKCEQDAsLIAEhAQtBOCEQDAkLAkAgASIPIAJGDQADQAJAIA8tAABBgL6AgABqLQAAIgFBAUYNACABQQJHDQMgD0EBaiEBDAQLIA9BAWoiDyACRw0AC0E+IRAMIgtBPiEQDCELIABBADoALCAPIQEMAQtBCyEQDAYLQTohEAwFCyABQQFqIQFBLSEQDAQLIAAgAToALCAAQQA2AgAgFkEBaiEBQQwhEAwDCyAAQQA2AgAgF0EBaiEBQQohEAwCCyAAQQA2AgALIABBADoALCANIQFBCSEQDAALC0EAIRAgAEEANgIcIAAgCzYCFCAAQc2QgIAANgIQIABBCTYCDAwXC0EAIRAgAEEANgIcIAAgCjYCFCAAQemKgIAANgIQIABBCTYCDAwWC0EAIRAgAEEANgIcIAAgCTYCFCAAQbeQgIAANgIQIABBCTYCDAwVC0EAIRAgAEEANgIcIAAgCDYCFCAAQZyRgIAANgIQIABBCTYCDAwUC0EAIRAgAEEANgIcIAAgATYCFCAAQc2QgIAANgIQIABBCTYCDAwTC0EAIRAgAEEANgIcIAAgATYCFCAAQemKgIAANgIQIABBCTYCDAwSC0EAIRAgAEEANgIcIAAgATYCFCAAQbeQgIAANgIQIABBCTYCDAwRC0EAIRAgAEEANgIcIAAgATYCFCAAQZyRgIAANgIQIABBCTYCDAwQC0EAIRAgAEEANgIcIAAgATYCFCAAQZeVgIAANgIQIABBDzYCDAwPC0EAIRAgAEEANgIcIAAgATYCFCAAQZeVgIAANgIQIABBDzYCDAwOC0EAIRAgAEEANgIcIAAgATYCFCAAQcCSgIAANgIQIABBCzYCDAwNC0EAIRAgAEEANgIcIAAgATYCFCAAQZWJgIAANgIQIABBCzYCDAwMC0EAIRAgAEEANgIcIAAgATYCFCAAQeGPgIAANgIQIABBCjYCDAwLC0EAIRAgAEEANgIcIAAgATYCFCAAQfuPgIAANgIQIABBCjYCDAwKC0EAIRAgAEEANgIcIAAgATYCFCAAQfGZgIAANgIQIABBAjYCDAwJC0EAIRAgAEEANgIcIAAgATYCFCAAQcSUgIAANgIQIABBAjYCDAwIC0EAIRAgAEEANgIcIAAgATYCFCAAQfKVgIAANgIQIABBAjYCDAwHCyAAQQI2AhwgACABNgIUIABBnJqAgAA2AhAgAEEWNgIMQQAhEAwGC0EBIRAMBQtB1AAhECABIgQgAkYNBCADQQhqIAAgBCACQdjCgIAAQQoQxYCAgAAgAygCDCEEIAMoAggOAwEEAgALEMqAgIAAAAsgAEEANgIcIABBtZqAgAA2AhAgAEEXNgIMIAAgBEEBajYCFEEAIRAMAgsgAEEANgIcIAAgBDYCFCAAQcqagIAANgIQIABBCTYCDEEAIRAMAQsCQCABIgQgAkcNAEEiIRAMAQsgAEGJgICAADYCCCAAIAQ2AgRBISEQCyADQRBqJICAgIAAIBALrwEBAn8gASgCACEGAkACQCACIANGDQAgBCAGaiEEIAYgA2ogAmshByACIAZBf3MgBWoiBmohBQNAAkAgAi0AACAELQAARg0AQQIhBAwDCwJAIAYNAEEAIQQgBSECDAMLIAZBf2ohBiAEQQFqIQQgAkEBaiICIANHDQALIAchBiADIQILIABBATYCACABIAY2AgAgACACNgIEDwsgAUEANgIAIAAgBDYCACAAIAI2AgQLCgAgABDHgICAAAvyNgELfyOAgICAAEEQayIBJICAgIAAAkBBACgCoNCAgAANAEEAEMuAgIAAQYDUhIAAayICQdkASQ0AQQAhAwJAQQAoAuDTgIAAIgQNAEEAQn83AuzTgIAAQQBCgICEgICAwAA3AuTTgIAAQQAgAUEIakFwcUHYqtWqBXMiBDYC4NOAgABBAEEANgL004CAAEEAQQA2AsTTgIAAC0EAIAI2AszTgIAAQQBBgNSEgAA2AsjTgIAAQQBBgNSEgAA2ApjQgIAAQQAgBDYCrNCAgABBAEF/NgKo0ICAAANAIANBxNCAgABqIANBuNCAgABqIgQ2AgAgBCADQbDQgIAAaiIFNgIAIANBvNCAgABqIAU2AgAgA0HM0ICAAGogA0HA0ICAAGoiBTYCACAFIAQ2AgAgA0HU0ICAAGogA0HI0ICAAGoiBDYCACAEIAU2AgAgA0HQ0ICAAGogBDYCACADQSBqIgNBgAJHDQALQYDUhIAAQXhBgNSEgABrQQ9xQQBBgNSEgABBCGpBD3EbIgNqIgRBBGogAkFIaiIFIANrIgNBAXI2AgBBAEEAKALw04CAADYCpNCAgABBACADNgKU0ICAAEEAIAQ2AqDQgIAAQYDUhIAAIAVqQTg2AgQLAkACQAJAAkACQAJAAkACQAJAAkACQAJAIABB7AFLDQACQEEAKAKI0ICAACIGQRAgAEETakFwcSAAQQtJGyICQQN2IgR2IgNBA3FFDQACQAJAIANBAXEgBHJBAXMiBUEDdCIEQbDQgIAAaiIDIARBuNCAgABqKAIAIgQoAggiAkcNAEEAIAZBfiAFd3E2AojQgIAADAELIAMgAjYCCCACIAM2AgwLIARBCGohAyAEIAVBA3QiBUEDcjYCBCAEIAVqIgQgBCgCBEEBcjYCBAwMCyACQQAoApDQgIAAIgdNDQECQCADRQ0AAkACQCADIAR0QQIgBHQiA0EAIANrcnEiA0EAIANrcUF/aiIDIANBDHZBEHEiA3YiBEEFdkEIcSIFIANyIAQgBXYiA0ECdkEEcSIEciADIAR2IgNBAXZBAnEiBHIgAyAEdiIDQQF2QQFxIgRyIAMgBHZqIgRBA3QiA0Gw0ICAAGoiBSADQbjQgIAAaigCACIDKAIIIgBHDQBBACAGQX4gBHdxIgY2AojQgIAADAELIAUgADYCCCAAIAU2AgwLIAMgAkEDcjYCBCADIARBA3QiBGogBCACayIFNgIAIAMgAmoiACAFQQFyNgIEAkAgB0UNACAHQXhxQbDQgIAAaiECQQAoApzQgIAAIQQCQAJAIAZBASAHQQN2dCIIcQ0AQQAgBiAIcjYCiNCAgAAgAiEIDAELIAIoAgghCAsgCCAENgIMIAIgBDYCCCAEIAI2AgwgBCAINgIICyADQQhqIQNBACAANgKc0ICAAEEAIAU2ApDQgIAADAwLQQAoAozQgIAAIglFDQEgCUEAIAlrcUF/aiIDIANBDHZBEHEiA3YiBEEFdkEIcSIFIANyIAQgBXYiA0ECdkEEcSIEciADIAR2IgNBAXZBAnEiBHIgAyAEdiIDQQF2QQFxIgRyIAMgBHZqQQJ0QbjSgIAAaigCACIAKAIEQXhxIAJrIQQgACEFAkADQAJAIAUoAhAiAw0AIAVBFGooAgAiA0UNAgsgAygCBEF4cSACayIFIAQgBSAESSIFGyEEIAMgACAFGyEAIAMhBQwACwsgACgCGCEKAkAgACgCDCIIIABGDQAgACgCCCIDQQAoApjQgIAASRogCCADNgIIIAMgCDYCDAwLCwJAIABBFGoiBSgCACIDDQAgACgCECIDRQ0DIABBEGohBQsDQCAFIQsgAyIIQRRqIgUoAgAiAw0AIAhBEGohBSAIKAIQIgMNAAsgC0EANgIADAoLQX8hAiAAQb9/Sw0AIABBE2oiA0FwcSECQQAoAozQgIAAIgdFDQBBACELAkAgAkGAAkkNAEEfIQsgAkH///8HSw0AIANBCHYiAyADQYD+P2pBEHZBCHEiA3QiBCAEQYDgH2pBEHZBBHEiBHQiBSAFQYCAD2pBEHZBAnEiBXRBD3YgAyAEciAFcmsiA0EBdCACIANBFWp2QQFxckEcaiELC0EAIAJrIQQCQAJAAkACQCALQQJ0QbjSgIAAaigCACIFDQBBACEDQQAhCAwBC0EAIQMgAkEAQRkgC0EBdmsgC0EfRht0IQBBACEIA0ACQCAFKAIEQXhxIAJrIgYgBE8NACAGIQQgBSEIIAYNAEEAIQQgBSEIIAUhAwwDCyADIAVBFGooAgAiBiAGIAUgAEEddkEEcWpBEGooAgAiBUYbIAMgBhshAyAAQQF0IQAgBQ0ACwsCQCADIAhyDQBBACEIQQIgC3QiA0EAIANrciAHcSIDRQ0DIANBACADa3FBf2oiAyADQQx2QRBxIgN2IgVBBXZBCHEiACADciAFIAB2IgNBAnZBBHEiBXIgAyAFdiIDQQF2QQJxIgVyIAMgBXYiA0EBdkEBcSIFciADIAV2akECdEG40oCAAGooAgAhAwsgA0UNAQsDQCADKAIEQXhxIAJrIgYgBEkhAAJAIAMoAhAiBQ0AIANBFGooAgAhBQsgBiAEIAAbIQQgAyAIIAAbIQggBSEDIAUNAAsLIAhFDQAgBEEAKAKQ0ICAACACa08NACAIKAIYIQsCQCAIKAIMIgAgCEYNACAIKAIIIgNBACgCmNCAgABJGiAAIAM2AgggAyAANgIMDAkLAkAgCEEUaiIFKAIAIgMNACAIKAIQIgNFDQMgCEEQaiEFCwNAIAUhBiADIgBBFGoiBSgCACIDDQAgAEEQaiEFIAAoAhAiAw0ACyAGQQA2AgAMCAsCQEEAKAKQ0ICAACIDIAJJDQBBACgCnNCAgAAhBAJAAkAgAyACayIFQRBJDQAgBCACaiIAIAVBAXI2AgRBACAFNgKQ0ICAAEEAIAA2ApzQgIAAIAQgA2ogBTYCACAEIAJBA3I2AgQMAQsgBCADQQNyNgIEIAQgA2oiAyADKAIEQQFyNgIEQQBBADYCnNCAgABBAEEANgKQ0ICAAAsgBEEIaiEDDAoLAkBBACgClNCAgAAiACACTQ0AQQAoAqDQgIAAIgMgAmoiBCAAIAJrIgVBAXI2AgRBACAFNgKU0ICAAEEAIAQ2AqDQgIAAIAMgAkEDcjYCBCADQQhqIQMMCgsCQAJAQQAoAuDTgIAARQ0AQQAoAujTgIAAIQQMAQtBAEJ/NwLs04CAAEEAQoCAhICAgMAANwLk04CAAEEAIAFBDGpBcHFB2KrVqgVzNgLg04CAAEEAQQA2AvTTgIAAQQBBADYCxNOAgABBgIAEIQQLQQAhAwJAIAQgAkHHAGoiB2oiBkEAIARrIgtxIgggAksNAEEAQTA2AvjTgIAADAoLAkBBACgCwNOAgAAiA0UNAAJAQQAoArjTgIAAIgQgCGoiBSAETQ0AIAUgA00NAQtBACEDQQBBMDYC+NOAgAAMCgtBAC0AxNOAgABBBHENBAJAAkACQEEAKAKg0ICAACIERQ0AQcjTgIAAIQMDQAJAIAMoAgAiBSAESw0AIAUgAygCBGogBEsNAwsgAygCCCIDDQALC0EAEMuAgIAAIgBBf0YNBSAIIQYCQEEAKALk04CAACIDQX9qIgQgAHFFDQAgCCAAayAEIABqQQAgA2txaiEGCyAGIAJNDQUgBkH+////B0sNBQJAQQAoAsDTgIAAIgNFDQBBACgCuNOAgAAiBCAGaiIFIARNDQYgBSADSw0GCyAGEMuAgIAAIgMgAEcNAQwHCyAGIABrIAtxIgZB/v///wdLDQQgBhDLgICAACIAIAMoAgAgAygCBGpGDQMgACEDCwJAIANBf0YNACACQcgAaiAGTQ0AAkAgByAGa0EAKALo04CAACIEakEAIARrcSIEQf7///8HTQ0AIAMhAAwHCwJAIAQQy4CAgABBf0YNACAEIAZqIQYgAyEADAcLQQAgBmsQy4CAgAAaDAQLIAMhACADQX9HDQUMAwtBACEIDAcLQQAhAAwFCyAAQX9HDQILQQBBACgCxNOAgABBBHI2AsTTgIAACyAIQf7///8HSw0BIAgQy4CAgAAhAEEAEMuAgIAAIQMgAEF/Rg0BIANBf0YNASAAIANPDQEgAyAAayIGIAJBOGpNDQELQQBBACgCuNOAgAAgBmoiAzYCuNOAgAACQCADQQAoArzTgIAATQ0AQQAgAzYCvNOAgAALAkACQAJAAkBBACgCoNCAgAAiBEUNAEHI04CAACEDA0AgACADKAIAIgUgAygCBCIIakYNAiADKAIIIgMNAAwDCwsCQAJAQQAoApjQgIAAIgNFDQAgACADTw0BC0EAIAA2ApjQgIAAC0EAIQNBACAGNgLM04CAAEEAIAA2AsjTgIAAQQBBfzYCqNCAgABBAEEAKALg04CAADYCrNCAgABBAEEANgLU04CAAANAIANBxNCAgABqIANBuNCAgABqIgQ2AgAgBCADQbDQgIAAaiIFNgIAIANBvNCAgABqIAU2AgAgA0HM0ICAAGogA0HA0ICAAGoiBTYCACAFIAQ2AgAgA0HU0ICAAGogA0HI0ICAAGoiBDYCACAEIAU2AgAgA0HQ0ICAAGogBDYCACADQSBqIgNBgAJHDQALIABBeCAAa0EPcUEAIABBCGpBD3EbIgNqIgQgBkFIaiIFIANrIgNBAXI2AgRBAEEAKALw04CAADYCpNCAgABBACADNgKU0ICAAEEAIAQ2AqDQgIAAIAAgBWpBODYCBAwCCyADLQAMQQhxDQAgBCAFSQ0AIAQgAE8NACAEQXggBGtBD3FBACAEQQhqQQ9xGyIFaiIAQQAoApTQgIAAIAZqIgsgBWsiBUEBcjYCBCADIAggBmo2AgRBAEEAKALw04CAADYCpNCAgABBACAFNgKU0ICAAEEAIAA2AqDQgIAAIAQgC2pBODYCBAwBCwJAIABBACgCmNCAgAAiCE8NAEEAIAA2ApjQgIAAIAAhCAsgACAGaiEFQcjTgIAAIQMCQAJAAkACQAJAAkACQANAIAMoAgAgBUYNASADKAIIIgMNAAwCCwsgAy0ADEEIcUUNAQtByNOAgAAhAwNAAkAgAygCACIFIARLDQAgBSADKAIEaiIFIARLDQMLIAMoAgghAwwACwsgAyAANgIAIAMgAygCBCAGajYCBCAAQXggAGtBD3FBACAAQQhqQQ9xG2oiCyACQQNyNgIEIAVBeCAFa0EPcUEAIAVBCGpBD3EbaiIGIAsgAmoiAmshAwJAIAYgBEcNAEEAIAI2AqDQgIAAQQBBACgClNCAgAAgA2oiAzYClNCAgAAgAiADQQFyNgIEDAMLAkAgBkEAKAKc0ICAAEcNAEEAIAI2ApzQgIAAQQBBACgCkNCAgAAgA2oiAzYCkNCAgAAgAiADQQFyNgIEIAIgA2ogAzYCAAwDCwJAIAYoAgQiBEEDcUEBRw0AIARBeHEhBwJAAkAgBEH/AUsNACAGKAIIIgUgBEEDdiIIQQN0QbDQgIAAaiIARhoCQCAGKAIMIgQgBUcNAEEAQQAoAojQgIAAQX4gCHdxNgKI0ICAAAwCCyAEIABGGiAEIAU2AgggBSAENgIMDAELIAYoAhghCQJAAkAgBigCDCIAIAZGDQAgBigCCCIEIAhJGiAAIAQ2AgggBCAANgIMDAELAkAgBkEUaiIEKAIAIgUNACAGQRBqIgQoAgAiBQ0AQQAhAAwBCwNAIAQhCCAFIgBBFGoiBCgCACIFDQAgAEEQaiEEIAAoAhAiBQ0ACyAIQQA2AgALIAlFDQACQAJAIAYgBigCHCIFQQJ0QbjSgIAAaiIEKAIARw0AIAQgADYCACAADQFBAEEAKAKM0ICAAEF+IAV3cTYCjNCAgAAMAgsgCUEQQRQgCSgCECAGRhtqIAA2AgAgAEUNAQsgACAJNgIYAkAgBigCECIERQ0AIAAgBDYCECAEIAA2AhgLIAYoAhQiBEUNACAAQRRqIAQ2AgAgBCAANgIYCyAHIANqIQMgBiAHaiIGKAIEIQQLIAYgBEF+cTYCBCACIANqIAM2AgAgAiADQQFyNgIEAkAgA0H/AUsNACADQXhxQbDQgIAAaiEEAkACQEEAKAKI0ICAACIFQQEgA0EDdnQiA3ENAEEAIAUgA3I2AojQgIAAIAQhAwwBCyAEKAIIIQMLIAMgAjYCDCAEIAI2AgggAiAENgIMIAIgAzYCCAwDC0EfIQQCQCADQf///wdLDQAgA0EIdiIEIARBgP4/akEQdkEIcSIEdCIFIAVBgOAfakEQdkEEcSIFdCIAIABBgIAPakEQdkECcSIAdEEPdiAEIAVyIAByayIEQQF0IAMgBEEVanZBAXFyQRxqIQQLIAIgBDYCHCACQgA3AhAgBEECdEG40oCAAGohBQJAQQAoAozQgIAAIgBBASAEdCIIcQ0AIAUgAjYCAEEAIAAgCHI2AozQgIAAIAIgBTYCGCACIAI2AgggAiACNgIMDAMLIANBAEEZIARBAXZrIARBH0YbdCEEIAUoAgAhAANAIAAiBSgCBEF4cSADRg0CIARBHXYhACAEQQF0IQQgBSAAQQRxakEQaiIIKAIAIgANAAsgCCACNgIAIAIgBTYCGCACIAI2AgwgAiACNgIIDAILIABBeCAAa0EPcUEAIABBCGpBD3EbIgNqIgsgBkFIaiIIIANrIgNBAXI2AgQgACAIakE4NgIEIAQgBUE3IAVrQQ9xQQAgBUFJakEPcRtqQUFqIgggCCAEQRBqSRsiCEEjNgIEQQBBACgC8NOAgAA2AqTQgIAAQQAgAzYClNCAgABBACALNgKg0ICAACAIQRBqQQApAtDTgIAANwIAIAhBACkCyNOAgAA3AghBACAIQQhqNgLQ04CAAEEAIAY2AszTgIAAQQAgADYCyNOAgABBAEEANgLU04CAACAIQSRqIQMDQCADQQc2AgAgA0EEaiIDIAVJDQALIAggBEYNAyAIIAgoAgRBfnE2AgQgCCAIIARrIgA2AgAgBCAAQQFyNgIEAkAgAEH/AUsNACAAQXhxQbDQgIAAaiEDAkACQEEAKAKI0ICAACIFQQEgAEEDdnQiAHENAEEAIAUgAHI2AojQgIAAIAMhBQwBCyADKAIIIQULIAUgBDYCDCADIAQ2AgggBCADNgIMIAQgBTYCCAwEC0EfIQMCQCAAQf///wdLDQAgAEEIdiIDIANBgP4/akEQdkEIcSIDdCIFIAVBgOAfakEQdkEEcSIFdCIIIAhBgIAPakEQdkECcSIIdEEPdiADIAVyIAhyayIDQQF0IAAgA0EVanZBAXFyQRxqIQMLIAQgAzYCHCAEQgA3AhAgA0ECdEG40oCAAGohBQJAQQAoAozQgIAAIghBASADdCIGcQ0AIAUgBDYCAEEAIAggBnI2AozQgIAAIAQgBTYCGCAEIAQ2AgggBCAENgIMDAQLIABBAEEZIANBAXZrIANBH0YbdCEDIAUoAgAhCANAIAgiBSgCBEF4cSAARg0DIANBHXYhCCADQQF0IQMgBSAIQQRxakEQaiIGKAIAIggNAAsgBiAENgIAIAQgBTYCGCAEIAQ2AgwgBCAENgIIDAMLIAUoAggiAyACNgIMIAUgAjYCCCACQQA2AhggAiAFNgIMIAIgAzYCCAsgC0EIaiEDDAULIAUoAggiAyAENgIMIAUgBDYCCCAEQQA2AhggBCAFNgIMIAQgAzYCCAtBACgClNCAgAAiAyACTQ0AQQAoAqDQgIAAIgQgAmoiBSADIAJrIgNBAXI2AgRBACADNgKU0ICAAEEAIAU2AqDQgIAAIAQgAkEDcjYCBCAEQQhqIQMMAwtBACEDQQBBMDYC+NOAgAAMAgsCQCALRQ0AAkACQCAIIAgoAhwiBUECdEG40oCAAGoiAygCAEcNACADIAA2AgAgAA0BQQAgB0F+IAV3cSIHNgKM0ICAAAwCCyALQRBBFCALKAIQIAhGG2ogADYCACAARQ0BCyAAIAs2AhgCQCAIKAIQIgNFDQAgACADNgIQIAMgADYCGAsgCEEUaigCACIDRQ0AIABBFGogAzYCACADIAA2AhgLAkACQCAEQQ9LDQAgCCAEIAJqIgNBA3I2AgQgCCADaiIDIAMoAgRBAXI2AgQMAQsgCCACaiIAIARBAXI2AgQgCCACQQNyNgIEIAAgBGogBDYCAAJAIARB/wFLDQAgBEF4cUGw0ICAAGohAwJAAkBBACgCiNCAgAAiBUEBIARBA3Z0IgRxDQBBACAFIARyNgKI0ICAACADIQQMAQsgAygCCCEECyAEIAA2AgwgAyAANgIIIAAgAzYCDCAAIAQ2AggMAQtBHyEDAkAgBEH///8HSw0AIARBCHYiAyADQYD+P2pBEHZBCHEiA3QiBSAFQYDgH2pBEHZBBHEiBXQiAiACQYCAD2pBEHZBAnEiAnRBD3YgAyAFciACcmsiA0EBdCAEIANBFWp2QQFxckEcaiEDCyAAIAM2AhwgAEIANwIQIANBAnRBuNKAgABqIQUCQCAHQQEgA3QiAnENACAFIAA2AgBBACAHIAJyNgKM0ICAACAAIAU2AhggACAANgIIIAAgADYCDAwBCyAEQQBBGSADQQF2ayADQR9GG3QhAyAFKAIAIQICQANAIAIiBSgCBEF4cSAERg0BIANBHXYhAiADQQF0IQMgBSACQQRxakEQaiIGKAIAIgINAAsgBiAANgIAIAAgBTYCGCAAIAA2AgwgACAANgIIDAELIAUoAggiAyAANgIMIAUgADYCCCAAQQA2AhggACAFNgIMIAAgAzYCCAsgCEEIaiEDDAELAkAgCkUNAAJAAkAgACAAKAIcIgVBAnRBuNKAgABqIgMoAgBHDQAgAyAINgIAIAgNAUEAIAlBfiAFd3E2AozQgIAADAILIApBEEEUIAooAhAgAEYbaiAINgIAIAhFDQELIAggCjYCGAJAIAAoAhAiA0UNACAIIAM2AhAgAyAINgIYCyAAQRRqKAIAIgNFDQAgCEEUaiADNgIAIAMgCDYCGAsCQAJAIARBD0sNACAAIAQgAmoiA0EDcjYCBCAAIANqIgMgAygCBEEBcjYCBAwBCyAAIAJqIgUgBEEBcjYCBCAAIAJBA3I2AgQgBSAEaiAENgIAAkAgB0UNACAHQXhxQbDQgIAAaiECQQAoApzQgIAAIQMCQAJAQQEgB0EDdnQiCCAGcQ0AQQAgCCAGcjYCiNCAgAAgAiEIDAELIAIoAgghCAsgCCADNgIMIAIgAzYCCCADIAI2AgwgAyAINgIIC0EAIAU2ApzQgIAAQQAgBDYCkNCAgAALIABBCGohAwsgAUEQaiSAgICAACADCwoAIAAQyYCAgAAL4g0BB38CQCAARQ0AIABBeGoiASAAQXxqKAIAIgJBeHEiAGohAwJAIAJBAXENACACQQNxRQ0BIAEgASgCACICayIBQQAoApjQgIAAIgRJDQEgAiAAaiEAAkAgAUEAKAKc0ICAAEYNAAJAIAJB/wFLDQAgASgCCCIEIAJBA3YiBUEDdEGw0ICAAGoiBkYaAkAgASgCDCICIARHDQBBAEEAKAKI0ICAAEF+IAV3cTYCiNCAgAAMAwsgAiAGRhogAiAENgIIIAQgAjYCDAwCCyABKAIYIQcCQAJAIAEoAgwiBiABRg0AIAEoAggiAiAESRogBiACNgIIIAIgBjYCDAwBCwJAIAFBFGoiAigCACIEDQAgAUEQaiICKAIAIgQNAEEAIQYMAQsDQCACIQUgBCIGQRRqIgIoAgAiBA0AIAZBEGohAiAGKAIQIgQNAAsgBUEANgIACyAHRQ0BAkACQCABIAEoAhwiBEECdEG40oCAAGoiAigCAEcNACACIAY2AgAgBg0BQQBBACgCjNCAgABBfiAEd3E2AozQgIAADAMLIAdBEEEUIAcoAhAgAUYbaiAGNgIAIAZFDQILIAYgBzYCGAJAIAEoAhAiAkUNACAGIAI2AhAgAiAGNgIYCyABKAIUIgJFDQEgBkEUaiACNgIAIAIgBjYCGAwBCyADKAIEIgJBA3FBA0cNACADIAJBfnE2AgRBACAANgKQ0ICAACABIABqIAA2AgAgASAAQQFyNgIEDwsgASADTw0AIAMoAgQiAkEBcUUNAAJAAkAgAkECcQ0AAkAgA0EAKAKg0ICAAEcNAEEAIAE2AqDQgIAAQQBBACgClNCAgAAgAGoiADYClNCAgAAgASAAQQFyNgIEIAFBACgCnNCAgABHDQNBAEEANgKQ0ICAAEEAQQA2ApzQgIAADwsCQCADQQAoApzQgIAARw0AQQAgATYCnNCAgABBAEEAKAKQ0ICAACAAaiIANgKQ0ICAACABIABBAXI2AgQgASAAaiAANgIADwsgAkF4cSAAaiEAAkACQCACQf8BSw0AIAMoAggiBCACQQN2IgVBA3RBsNCAgABqIgZGGgJAIAMoAgwiAiAERw0AQQBBACgCiNCAgABBfiAFd3E2AojQgIAADAILIAIgBkYaIAIgBDYCCCAEIAI2AgwMAQsgAygCGCEHAkACQCADKAIMIgYgA0YNACADKAIIIgJBACgCmNCAgABJGiAGIAI2AgggAiAGNgIMDAELAkAgA0EUaiICKAIAIgQNACADQRBqIgIoAgAiBA0AQQAhBgwBCwNAIAIhBSAEIgZBFGoiAigCACIEDQAgBkEQaiECIAYoAhAiBA0ACyAFQQA2AgALIAdFDQACQAJAIAMgAygCHCIEQQJ0QbjSgIAAaiICKAIARw0AIAIgBjYCACAGDQFBAEEAKAKM0ICAAEF+IAR3cTYCjNCAgAAMAgsgB0EQQRQgBygCECADRhtqIAY2AgAgBkUNAQsgBiAHNgIYAkAgAygCECICRQ0AIAYgAjYCECACIAY2AhgLIAMoAhQiAkUNACAGQRRqIAI2AgAgAiAGNgIYCyABIABqIAA2AgAgASAAQQFyNgIEIAFBACgCnNCAgABHDQFBACAANgKQ0ICAAA8LIAMgAkF+cTYCBCABIABqIAA2AgAgASAAQQFyNgIECwJAIABB/wFLDQAgAEF4cUGw0ICAAGohAgJAAkBBACgCiNCAgAAiBEEBIABBA3Z0IgBxDQBBACAEIAByNgKI0ICAACACIQAMAQsgAigCCCEACyAAIAE2AgwgAiABNgIIIAEgAjYCDCABIAA2AggPC0EfIQICQCAAQf///wdLDQAgAEEIdiICIAJBgP4/akEQdkEIcSICdCIEIARBgOAfakEQdkEEcSIEdCIGIAZBgIAPakEQdkECcSIGdEEPdiACIARyIAZyayICQQF0IAAgAkEVanZBAXFyQRxqIQILIAEgAjYCHCABQgA3AhAgAkECdEG40oCAAGohBAJAAkBBACgCjNCAgAAiBkEBIAJ0IgNxDQAgBCABNgIAQQAgBiADcjYCjNCAgAAgASAENgIYIAEgATYCCCABIAE2AgwMAQsgAEEAQRkgAkEBdmsgAkEfRht0IQIgBCgCACEGAkADQCAGIgQoAgRBeHEgAEYNASACQR12IQYgAkEBdCECIAQgBkEEcWpBEGoiAygCACIGDQALIAMgATYCACABIAQ2AhggASABNgIMIAEgATYCCAwBCyAEKAIIIgAgATYCDCAEIAE2AgggAUEANgIYIAEgBDYCDCABIAA2AggLQQBBACgCqNCAgABBf2oiAUF/IAEbNgKo0ICAAAsLBAAAAAtOAAJAIAANAD8AQRB0DwsCQCAAQf//A3ENACAAQX9MDQACQCAAQRB2QAAiAEF/Rw0AQQBBMDYC+NOAgABBfw8LIABBEHQPCxDKgICAAAAL8gICA38BfgJAIAJFDQAgACABOgAAIAIgAGoiA0F/aiABOgAAIAJBA0kNACAAIAE6AAIgACABOgABIANBfWogAToAACADQX5qIAE6AAAgAkEHSQ0AIAAgAToAAyADQXxqIAE6AAAgAkEJSQ0AIABBACAAa0EDcSIEaiIDIAFB/wFxQYGChAhsIgE2AgAgAyACIARrQXxxIgRqIgJBfGogATYCACAEQQlJDQAgAyABNgIIIAMgATYCBCACQXhqIAE2AgAgAkF0aiABNgIAIARBGUkNACADIAE2AhggAyABNgIUIAMgATYCECADIAE2AgwgAkFwaiABNgIAIAJBbGogATYCACACQWhqIAE2AgAgAkFkaiABNgIAIAQgA0EEcUEYciIFayICQSBJDQAgAa1CgYCAgBB+IQYgAyAFaiEBA0AgASAGNwMYIAEgBjcDECABIAY3AwggASAGNwMAIAFBIGohASACQWBqIgJBH0sNAAsLIAALC45IAQBBgAgLhkgBAAAAAgAAAAMAAAAAAAAAAAAAAAQAAAAFAAAAAAAAAAAAAAAGAAAABwAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEludmFsaWQgY2hhciBpbiB1cmwgcXVlcnkAU3BhbiBjYWxsYmFjayBlcnJvciBpbiBvbl9ib2R5AENvbnRlbnQtTGVuZ3RoIG92ZXJmbG93AENodW5rIHNpemUgb3ZlcmZsb3cAUmVzcG9uc2Ugb3ZlcmZsb3cASW52YWxpZCBtZXRob2QgZm9yIEhUVFAveC54IHJlcXVlc3QASW52YWxpZCBtZXRob2QgZm9yIFJUU1AveC54IHJlcXVlc3QARXhwZWN0ZWQgU09VUkNFIG1ldGhvZCBmb3IgSUNFL3gueCByZXF1ZXN0AEludmFsaWQgY2hhciBpbiB1cmwgZnJhZ21lbnQgc3RhcnQARXhwZWN0ZWQgZG90AFNwYW4gY2FsbGJhY2sgZXJyb3IgaW4gb25fc3RhdHVzAEludmFsaWQgcmVzcG9uc2Ugc3RhdHVzAEludmFsaWQgY2hhcmFjdGVyIGluIGNodW5rIGV4dGVuc2lvbnMAVXNlciBjYWxsYmFjayBlcnJvcgBgb25fcmVzZXRgIGNhbGxiYWNrIGVycm9yAGBvbl9jaHVua19oZWFkZXJgIGNhbGxiYWNrIGVycm9yAGBvbl9tZXNzYWdlX2JlZ2luYCBjYWxsYmFjayBlcnJvcgBgb25fY2h1bmtfZXh0ZW5zaW9uX3ZhbHVlYCBjYWxsYmFjayBlcnJvcgBgb25fc3RhdHVzX2NvbXBsZXRlYCBjYWxsYmFjayBlcnJvcgBgb25fdmVyc2lvbl9jb21wbGV0ZWAgY2FsbGJhY2sgZXJyb3IAYG9uX3VybF9jb21wbGV0ZWAgY2FsbGJhY2sgZXJyb3IAYG9uX2NodW5rX2NvbXBsZXRlYCBjYWxsYmFjayBlcnJvcgBgb25faGVhZGVyX3ZhbHVlX2NvbXBsZXRlYCBjYWxsYmFjayBlcnJvcgBgb25fbWVzc2FnZV9jb21wbGV0ZWAgY2FsbGJhY2sgZXJyb3IAYG9uX21ldGhvZF9jb21wbGV0ZWAgY2FsbGJhY2sgZXJyb3IAYG9uX2hlYWRlcl9maWVsZF9jb21wbGV0ZWAgY2FsbGJhY2sgZXJyb3IAYG9uX2NodW5rX2V4dGVuc2lvbl9uYW1lYCBjYWxsYmFjayBlcnJvcgBVbmV4cGVjdGVkIGNoYXIgaW4gdXJsIHNlcnZlcgBJbnZhbGlkIGhlYWRlciB2YWx1ZSBjaGFyAEludmFsaWQgaGVhZGVyIGZpZWxkIGNoYXIAU3BhbiBjYWxsYmFjayBlcnJvciBpbiBvbl92ZXJzaW9uAEludmFsaWQgbWlub3IgdmVyc2lvbgBJbnZhbGlkIG1ham9yIHZlcnNpb24ARXhwZWN0ZWQgc3BhY2UgYWZ0ZXIgdmVyc2lvbgBFeHBlY3RlZCBDUkxGIGFmdGVyIHZlcnNpb24ASW52YWxpZCBIVFRQIHZlcnNpb24ASW52YWxpZCBoZWFkZXIgdG9rZW4AU3BhbiBjYWxsYmFjayBlcnJvciBpbiBvbl91cmwASW52YWxpZCBjaGFyYWN0ZXJzIGluIHVybABVbmV4cGVjdGVkIHN0YXJ0IGNoYXIgaW4gdXJsAERvdWJsZSBAIGluIHVybABFbXB0eSBDb250ZW50LUxlbmd0aABJbnZhbGlkIGNoYXJhY3RlciBpbiBDb250ZW50LUxlbmd0aABEdXBsaWNhdGUgQ29udGVudC1MZW5ndGgASW52YWxpZCBjaGFyIGluIHVybCBwYXRoAENvbnRlbnQtTGVuZ3RoIGNhbid0IGJlIHByZXNlbnQgd2l0aCBUcmFuc2Zlci1FbmNvZGluZwBJbnZhbGlkIGNoYXJhY3RlciBpbiBjaHVuayBzaXplAFNwYW4gY2FsbGJhY2sgZXJyb3IgaW4gb25faGVhZGVyX3ZhbHVlAFNwYW4gY2FsbGJhY2sgZXJyb3IgaW4gb25fY2h1bmtfZXh0ZW5zaW9uX3ZhbHVlAEludmFsaWQgY2hhcmFjdGVyIGluIGNodW5rIGV4dGVuc2lvbnMgdmFsdWUATWlzc2luZyBleHBlY3RlZCBMRiBhZnRlciBoZWFkZXIgdmFsdWUASW52YWxpZCBgVHJhbnNmZXItRW5jb2RpbmdgIGhlYWRlciB2YWx1ZQBJbnZhbGlkIGNoYXJhY3RlciBpbiBjaHVuayBleHRlbnNpb25zIHF1b3RlIHZhbHVlAEludmFsaWQgY2hhcmFjdGVyIGluIGNodW5rIGV4dGVuc2lvbnMgcXVvdGVkIHZhbHVlAFBhdXNlZCBieSBvbl9oZWFkZXJzX2NvbXBsZXRlAEludmFsaWQgRU9GIHN0YXRlAG9uX3Jlc2V0IHBhdXNlAG9uX2NodW5rX2hlYWRlciBwYXVzZQBvbl9tZXNzYWdlX2JlZ2luIHBhdXNlAG9uX2NodW5rX2V4dGVuc2lvbl92YWx1ZSBwYXVzZQBvbl9zdGF0dXNfY29tcGxldGUgcGF1c2UAb25fdmVyc2lvbl9jb21wbGV0ZSBwYXVzZQBvbl91cmxfY29tcGxldGUgcGF1c2UAb25fY2h1bmtfY29tcGxldGUgcGF1c2UAb25faGVhZGVyX3ZhbHVlX2NvbXBsZXRlIHBhdXNlAG9uX21lc3NhZ2VfY29tcGxldGUgcGF1c2UAb25fbWV0aG9kX2NvbXBsZXRlIHBhdXNlAG9uX2hlYWRlcl9maWVsZF9jb21wbGV0ZSBwYXVzZQBvbl9jaHVua19leHRlbnNpb25fbmFtZSBwYXVzZQBVbmV4cGVjdGVkIHNwYWNlIGFmdGVyIHN0YXJ0IGxpbmUAU3BhbiBjYWxsYmFjayBlcnJvciBpbiBvbl9jaHVua19leHRlbnNpb25fbmFtZQBJbnZhbGlkIGNoYXJhY3RlciBpbiBjaHVuayBleHRlbnNpb25zIG5hbWUAUGF1c2Ugb24gQ09OTkVDVC9VcGdyYWRlAFBhdXNlIG9uIFBSSS9VcGdyYWRlAEV4cGVjdGVkIEhUVFAvMiBDb25uZWN0aW9uIFByZWZhY2UAU3BhbiBjYWxsYmFjayBlcnJvciBpbiBvbl9tZXRob2QARXhwZWN0ZWQgc3BhY2UgYWZ0ZXIgbWV0aG9kAFNwYW4gY2FsbGJhY2sgZXJyb3IgaW4gb25faGVhZGVyX2ZpZWxkAFBhdXNlZABJbnZhbGlkIHdvcmQgZW5jb3VudGVyZWQASW52YWxpZCBtZXRob2QgZW5jb3VudGVyZWQAVW5leHBlY3RlZCBjaGFyIGluIHVybCBzY2hlbWEAUmVxdWVzdCBoYXMgaW52YWxpZCBgVHJhbnNmZXItRW5jb2RpbmdgAFNXSVRDSF9QUk9YWQBVU0VfUFJPWFkATUtBQ1RJVklUWQBVTlBST0NFU1NBQkxFX0VOVElUWQBDT1BZAE1PVkVEX1BFUk1BTkVOVExZAFRPT19FQVJMWQBOT1RJRlkARkFJTEVEX0RFUEVOREVOQ1kAQkFEX0dBVEVXQVkAUExBWQBQVVQAQ0hFQ0tPVVQAR0FURVdBWV9USU1FT1VUAFJFUVVFU1RfVElNRU9VVABORVRXT1JLX0NPTk5FQ1RfVElNRU9VVABDT05ORUNUSU9OX1RJTUVPVVQATE9HSU5fVElNRU9VVABORVRXT1JLX1JFQURfVElNRU9VVABQT1NUAE1JU0RJUkVDVEVEX1JFUVVFU1QAQ0xJRU5UX0NMT1NFRF9SRVFVRVNUAENMSUVOVF9DTE9TRURfTE9BRF9CQUxBTkNFRF9SRVFVRVNUAEJBRF9SRVFVRVNUAEhUVFBfUkVRVUVTVF9TRU5UX1RPX0hUVFBTX1BPUlQAUkVQT1JUAElNX0FfVEVBUE9UAFJFU0VUX0NPTlRFTlQATk9fQ09OVEVOVABQQVJUSUFMX0NPTlRFTlQASFBFX0lOVkFMSURfQ09OU1RBTlQASFBFX0NCX1JFU0VUAEdFVABIUEVfU1RSSUNUAENPTkZMSUNUAFRFTVBPUkFSWV9SRURJUkVDVABQRVJNQU5FTlRfUkVESVJFQ1QAQ09OTkVDVABNVUxUSV9TVEFUVVMASFBFX0lOVkFMSURfU1RBVFVTAFRPT19NQU5ZX1JFUVVFU1RTAEVBUkxZX0hJTlRTAFVOQVZBSUxBQkxFX0ZPUl9MRUdBTF9SRUFTT05TAE9QVElPTlMAU1dJVENISU5HX1BST1RPQ09MUwBWQVJJQU5UX0FMU09fTkVHT1RJQVRFUwBNVUxUSVBMRV9DSE9JQ0VTAElOVEVSTkFMX1NFUlZFUl9FUlJPUgBXRUJfU0VSVkVSX1VOS05PV05fRVJST1IAUkFJTEdVTl9FUlJPUgBJREVOVElUWV9QUk9WSURFUl9BVVRIRU5USUNBVElPTl9FUlJPUgBTU0xfQ0VSVElGSUNBVEVfRVJST1IASU5WQUxJRF9YX0ZPUldBUkRFRF9GT1IAU0VUX1BBUkFNRVRFUgBHRVRfUEFSQU1FVEVSAEhQRV9VU0VSAFNFRV9PVEhFUgBIUEVfQ0JfQ0hVTktfSEVBREVSAE1LQ0FMRU5EQVIAU0VUVVAAV0VCX1NFUlZFUl9JU19ET1dOAFRFQVJET1dOAEhQRV9DTE9TRURfQ09OTkVDVElPTgBIRVVSSVNUSUNfRVhQSVJBVElPTgBESVNDT05ORUNURURfT1BFUkFUSU9OAE5PTl9BVVRIT1JJVEFUSVZFX0lORk9STUFUSU9OAEhQRV9JTlZBTElEX1ZFUlNJT04ASFBFX0NCX01FU1NBR0VfQkVHSU4AU0lURV9JU19GUk9aRU4ASFBFX0lOVkFMSURfSEVBREVSX1RPS0VOAElOVkFMSURfVE9LRU4ARk9SQklEREVOAEVOSEFOQ0VfWU9VUl9DQUxNAEhQRV9JTlZBTElEX1VSTABCTE9DS0VEX0JZX1BBUkVOVEFMX0NPTlRST0wATUtDT0wAQUNMAEhQRV9JTlRFUk5BTABSRVFVRVNUX0hFQURFUl9GSUVMRFNfVE9PX0xBUkdFX1VOT0ZGSUNJQUwASFBFX09LAFVOTElOSwBVTkxPQ0sAUFJJAFJFVFJZX1dJVEgASFBFX0lOVkFMSURfQ09OVEVOVF9MRU5HVEgASFBFX1VORVhQRUNURURfQ09OVEVOVF9MRU5HVEgARkxVU0gAUFJPUFBBVENIAE0tU0VBUkNIAFVSSV9UT09fTE9ORwBQUk9DRVNTSU5HAE1JU0NFTExBTkVPVVNfUEVSU0lTVEVOVF9XQVJOSU5HAE1JU0NFTExBTkVPVVNfV0FSTklORwBIUEVfSU5WQUxJRF9UUkFOU0ZFUl9FTkNPRElORwBFeHBlY3RlZCBDUkxGAEhQRV9JTlZBTElEX0NIVU5LX1NJWkUATU9WRQBDT05USU5VRQBIUEVfQ0JfU1RBVFVTX0NPTVBMRVRFAEhQRV9DQl9IRUFERVJTX0NPTVBMRVRFAEhQRV9DQl9WRVJTSU9OX0NPTVBMRVRFAEhQRV9DQl9VUkxfQ09NUExFVEUASFBFX0NCX0NIVU5LX0NPTVBMRVRFAEhQRV9DQl9IRUFERVJfVkFMVUVfQ09NUExFVEUASFBFX0NCX0NIVU5LX0VYVEVOU0lPTl9WQUxVRV9DT01QTEVURQBIUEVfQ0JfQ0hVTktfRVhURU5TSU9OX05BTUVfQ09NUExFVEUASFBFX0NCX01FU1NBR0VfQ09NUExFVEUASFBFX0NCX01FVEhPRF9DT01QTEVURQBIUEVfQ0JfSEVBREVSX0ZJRUxEX0NPTVBMRVRFAERFTEVURQBIUEVfSU5WQUxJRF9FT0ZfU1RBVEUASU5WQUxJRF9TU0xfQ0VSVElGSUNBVEUAUEFVU0UATk9fUkVTUE9OU0UAVU5TVVBQT1JURURfTUVESUFfVFlQRQBHT05FAE5PVF9BQ0NFUFRBQkxFAFNFUlZJQ0VfVU5BVkFJTEFCTEUAUkFOR0VfTk9UX1NBVElTRklBQkxFAE9SSUdJTl9JU19VTlJFQUNIQUJMRQBSRVNQT05TRV9JU19TVEFMRQBQVVJHRQBNRVJHRQBSRVFVRVNUX0hFQURFUl9GSUVMRFNfVE9PX0xBUkdFAFJFUVVFU1RfSEVBREVSX1RPT19MQVJHRQBQQVlMT0FEX1RPT19MQVJHRQBJTlNVRkZJQ0lFTlRfU1RPUkFHRQBIUEVfUEFVU0VEX1VQR1JBREUASFBFX1BBVVNFRF9IMl9VUEdSQURFAFNPVVJDRQBBTk5PVU5DRQBUUkFDRQBIUEVfVU5FWFBFQ1RFRF9TUEFDRQBERVNDUklCRQBVTlNVQlNDUklCRQBSRUNPUkQASFBFX0lOVkFMSURfTUVUSE9EAE5PVF9GT1VORABQUk9QRklORABVTkJJTkQAUkVCSU5EAFVOQVVUSE9SSVpFRABNRVRIT0RfTk9UX0FMTE9XRUQASFRUUF9WRVJTSU9OX05PVF9TVVBQT1JURUQAQUxSRUFEWV9SRVBPUlRFRABBQ0NFUFRFRABOT1RfSU1QTEVNRU5URUQATE9PUF9ERVRFQ1RFRABIUEVfQ1JfRVhQRUNURUQASFBFX0xGX0VYUEVDVEVEAENSRUFURUQASU1fVVNFRABIUEVfUEFVU0VEAFRJTUVPVVRfT0NDVVJFRABQQVlNRU5UX1JFUVVJUkVEAFBSRUNPTkRJVElPTl9SRVFVSVJFRABQUk9YWV9BVVRIRU5USUNBVElPTl9SRVFVSVJFRABORVRXT1JLX0FVVEhFTlRJQ0FUSU9OX1JFUVVJUkVEAExFTkdUSF9SRVFVSVJFRABTU0xfQ0VSVElGSUNBVEVfUkVRVUlSRUQAVVBHUkFERV9SRVFVSVJFRABQQUdFX0VYUElSRUQAUFJFQ09ORElUSU9OX0ZBSUxFRABFWFBFQ1RBVElPTl9GQUlMRUQAUkVWQUxJREFUSU9OX0ZBSUxFRABTU0xfSEFORFNIQUtFX0ZBSUxFRABMT0NLRUQAVFJBTlNGT1JNQVRJT05fQVBQTElFRABOT1RfTU9ESUZJRUQATk9UX0VYVEVOREVEAEJBTkRXSURUSF9MSU1JVF9FWENFRURFRABTSVRFX0lTX09WRVJMT0FERUQASEVBRABFeHBlY3RlZCBIVFRQLwAAXhMAACYTAAAwEAAA8BcAAJ0TAAAVEgAAORcAAPASAAAKEAAAdRIAAK0SAACCEwAATxQAAH8QAACgFQAAIxQAAIkSAACLFAAATRUAANQRAADPFAAAEBgAAMkWAADcFgAAwREAAOAXAAC7FAAAdBQAAHwVAADlFAAACBcAAB8QAABlFQAAoxQAACgVAAACFQAAmRUAACwQAACLGQAATw8AANQOAABqEAAAzhAAAAIXAACJDgAAbhMAABwTAABmFAAAVhcAAMETAADNEwAAbBMAAGgXAABmFwAAXxcAACITAADODwAAaQ4AANgOAABjFgAAyxMAAKoOAAAoFwAAJhcAAMUTAABdFgAA6BEAAGcTAABlEwAA8hYAAHMTAAAdFwAA+RYAAPMRAADPDgAAzhUAAAwSAACzEQAApREAAGEQAAAyFwAAuxMAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAQIBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEAAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAIDAgICAgIAAAICAAICAAICAgICAgICAgIABAAAAAAAAgICAgICAgICAgICAgICAgICAgICAgICAgIAAAACAgICAgICAgICAgICAgICAgICAgICAgICAgICAgACAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAACAAICAgICAAACAgACAgACAgICAgICAgICAAMABAAAAAICAgICAgICAgICAgICAgICAgICAgICAgICAAAAAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAAgACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAbG9zZWVlcC1hbGl2ZQAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEAAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAQEBAQEBAQEBAQIBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBY2h1bmtlZAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQEAAQEBAQEAAAEBAAEBAAEBAQEBAQEBAQEAAAAAAAAAAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEAAAABAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQABAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABlY3Rpb25lbnQtbGVuZ3Rob25yb3h5LWNvbm5lY3Rpb24AAAAAAAAAAAAAAAAAAAByYW5zZmVyLWVuY29kaW5ncGdyYWRlDQoNCg0KU00NCg0KVFRQL0NFL1RTUC8AAAAAAAAAAAAAAAABAgABAwAAAAAAAAAAAAAAAAAAAAAAAAQBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAAAAAAAAAAAAAQIAAQMAAAAAAAAAAAAAAAAAAAAAAAAEAQEFAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQABAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQAAAAAAAAAAAAEAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQABAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEAAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEAAAAAAAAAAAAAAQAAAgAAAAAAAAAAAAAAAAAAAAAAAAMEAAAEBAQEBAQEBAQEBAUEBAQEBAQEBAQEBAQABAAGBwQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAAEAAQABAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAEAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAADAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwAAAAAAAAMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAABAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAIAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMAAAAAAAADAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABOT1VOQ0VFQ0tPVVRORUNURVRFQ1JJQkVMVVNIRVRFQURTRUFSQ0hSR0VDVElWSVRZTEVOREFSVkVPVElGWVBUSU9OU0NIU0VBWVNUQVRDSEdFT1JESVJFQ1RPUlRSQ0hQQVJBTUVURVJVUkNFQlNDUklCRUFSRE9XTkFDRUlORE5LQ0tVQlNDUklCRUhUVFAvQURUUC8=' + + +/***/ }), + +/***/ 53434: +/***/ ((module) => { + +module.exports = 'AGFzbQEAAAABMAhgAX8Bf2ADf39/AX9gBH9/f38Bf2AAAGADf39/AGABfwBgAn9/AGAGf39/f39/AALLAQgDZW52GHdhc21fb25faGVhZGVyc19jb21wbGV0ZQACA2VudhV3YXNtX29uX21lc3NhZ2VfYmVnaW4AAANlbnYLd2FzbV9vbl91cmwAAQNlbnYOd2FzbV9vbl9zdGF0dXMAAQNlbnYUd2FzbV9vbl9oZWFkZXJfZmllbGQAAQNlbnYUd2FzbV9vbl9oZWFkZXJfdmFsdWUAAQNlbnYMd2FzbV9vbl9ib2R5AAEDZW52GHdhc21fb25fbWVzc2FnZV9jb21wbGV0ZQAAA0ZFAwMEAAAFAAAAAAAABQEFAAUFBQAABgAAAAAGBgYGAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQABAAABAQcAAAUFAwABBAUBcAESEgUDAQACBggBfwFBgNQECwfRBSIGbWVtb3J5AgALX2luaXRpYWxpemUACRlfX2luZGlyZWN0X2Z1bmN0aW9uX3RhYmxlAQALbGxodHRwX2luaXQAChhsbGh0dHBfc2hvdWxkX2tlZXBfYWxpdmUAQQxsbGh0dHBfYWxsb2MADAZtYWxsb2MARgtsbGh0dHBfZnJlZQANBGZyZWUASA9sbGh0dHBfZ2V0X3R5cGUADhVsbGh0dHBfZ2V0X2h0dHBfbWFqb3IADxVsbGh0dHBfZ2V0X2h0dHBfbWlub3IAEBFsbGh0dHBfZ2V0X21ldGhvZAARFmxsaHR0cF9nZXRfc3RhdHVzX2NvZGUAEhJsbGh0dHBfZ2V0X3VwZ3JhZGUAEwxsbGh0dHBfcmVzZXQAFA5sbGh0dHBfZXhlY3V0ZQAVFGxsaHR0cF9zZXR0aW5nc19pbml0ABYNbGxodHRwX2ZpbmlzaAAXDGxsaHR0cF9wYXVzZQAYDWxsaHR0cF9yZXN1bWUAGRtsbGh0dHBfcmVzdW1lX2FmdGVyX3VwZ3JhZGUAGhBsbGh0dHBfZ2V0X2Vycm5vABsXbGxodHRwX2dldF9lcnJvcl9yZWFzb24AHBdsbGh0dHBfc2V0X2Vycm9yX3JlYXNvbgAdFGxsaHR0cF9nZXRfZXJyb3JfcG9zAB4RbGxodHRwX2Vycm5vX25hbWUAHxJsbGh0dHBfbWV0aG9kX25hbWUAIBJsbGh0dHBfc3RhdHVzX25hbWUAIRpsbGh0dHBfc2V0X2xlbmllbnRfaGVhZGVycwAiIWxsaHR0cF9zZXRfbGVuaWVudF9jaHVua2VkX2xlbmd0aAAjHWxsaHR0cF9zZXRfbGVuaWVudF9rZWVwX2FsaXZlACQkbGxodHRwX3NldF9sZW5pZW50X3RyYW5zZmVyX2VuY29kaW5nACUYbGxodHRwX21lc3NhZ2VfbmVlZHNfZW9mAD8JFwEAQQELEQECAwQFCwYHNTk3MS8tJyspCrLgAkUCAAsIABCIgICAAAsZACAAEMKAgIAAGiAAIAI2AjggACABOgAoCxwAIAAgAC8BMiAALQAuIAAQwYCAgAAQgICAgAALKgEBf0HAABDGgICAACIBEMKAgIAAGiABQYCIgIAANgI4IAEgADoAKCABCwoAIAAQyICAgAALBwAgAC0AKAsHACAALQAqCwcAIAAtACsLBwAgAC0AKQsHACAALwEyCwcAIAAtAC4LRQEEfyAAKAIYIQEgAC0ALSECIAAtACghAyAAKAI4IQQgABDCgICAABogACAENgI4IAAgAzoAKCAAIAI6AC0gACABNgIYCxEAIAAgASABIAJqEMOAgIAACxAAIABBAEHcABDMgICAABoLZwEBf0EAIQECQCAAKAIMDQACQAJAAkACQCAALQAvDgMBAAMCCyAAKAI4IgFFDQAgASgCLCIBRQ0AIAAgARGAgICAAAAiAQ0DC0EADwsQyoCAgAAACyAAQcOWgIAANgIQQQ4hAQsgAQseAAJAIAAoAgwNACAAQdGbgIAANgIQIABBFTYCDAsLFgACQCAAKAIMQRVHDQAgAEEANgIMCwsWAAJAIAAoAgxBFkcNACAAQQA2AgwLCwcAIAAoAgwLBwAgACgCEAsJACAAIAE2AhALBwAgACgCFAsiAAJAIABBJEkNABDKgICAAAALIABBAnRBoLOAgABqKAIACyIAAkAgAEEuSQ0AEMqAgIAAAAsgAEECdEGwtICAAGooAgAL7gsBAX9B66iAgAAhAQJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAIABBnH9qDvQDY2IAAWFhYWFhYQIDBAVhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhBgcICQoLDA0OD2FhYWFhEGFhYWFhYWFhYWFhEWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYRITFBUWFxgZGhthYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhHB0eHyAhIiMkJSYnKCkqKywtLi8wMTIzNDU2YTc4OTphYWFhYWFhYTthYWE8YWFhYT0+P2FhYWFhYWFhQGFhQWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYUJDREVGR0hJSktMTU5PUFFSU2FhYWFhYWFhVFVWV1hZWlthXF1hYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFeYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhX2BhC0Hhp4CAAA8LQaShgIAADwtBy6yAgAAPC0H+sYCAAA8LQcCkgIAADwtBq6SAgAAPC0GNqICAAA8LQeKmgIAADwtBgLCAgAAPC0G5r4CAAA8LQdekgIAADwtB75+AgAAPC0Hhn4CAAA8LQfqfgIAADwtB8qCAgAAPC0Gor4CAAA8LQa6ygIAADwtBiLCAgAAPC0Hsp4CAAA8LQYKigIAADwtBjp2AgAAPC0HQroCAAA8LQcqjgIAADwtBxbKAgAAPC0HfnICAAA8LQdKcgIAADwtBxKCAgAAPC0HXoICAAA8LQaKfgIAADwtB7a6AgAAPC0GrsICAAA8LQdSlgIAADwtBzK6AgAAPC0H6roCAAA8LQfyrgIAADwtB0rCAgAAPC0HxnYCAAA8LQbuggIAADwtB96uAgAAPC0GQsYCAAA8LQdexgIAADwtBoq2AgAAPC0HUp4CAAA8LQeCrgIAADwtBn6yAgAAPC0HrsYCAAA8LQdWfgIAADwtByrGAgAAPC0HepYCAAA8LQdSegIAADwtB9JyAgAAPC0GnsoCAAA8LQbGdgIAADwtBoJ2AgAAPC0G5sYCAAA8LQbywgIAADwtBkqGAgAAPC0GzpoCAAA8LQemsgIAADwtBrJ6AgAAPC0HUq4CAAA8LQfemgIAADwtBgKaAgAAPC0GwoYCAAA8LQf6egIAADwtBjaOAgAAPC0GJrYCAAA8LQfeigIAADwtBoLGAgAAPC0Gun4CAAA8LQcalgIAADwtB6J6AgAAPC0GTooCAAA8LQcKvgIAADwtBw52AgAAPC0GLrICAAA8LQeGdgIAADwtBja+AgAAPC0HqoYCAAA8LQbStgIAADwtB0q+AgAAPC0HfsoCAAA8LQdKygIAADwtB8LCAgAAPC0GpooCAAA8LQfmjgIAADwtBmZ6AgAAPC0G1rICAAA8LQZuwgIAADwtBkrKAgAAPC0G2q4CAAA8LQcKigIAADwtB+LKAgAAPC0GepYCAAA8LQdCigIAADwtBup6AgAAPC0GBnoCAAA8LEMqAgIAAAAtB1qGAgAAhAQsgAQsWACAAIAAtAC1B/gFxIAFBAEdyOgAtCxkAIAAgAC0ALUH9AXEgAUEAR0EBdHI6AC0LGQAgACAALQAtQfsBcSABQQBHQQJ0cjoALQsZACAAIAAtAC1B9wFxIAFBAEdBA3RyOgAtCy4BAn9BACEDAkAgACgCOCIERQ0AIAQoAgAiBEUNACAAIAQRgICAgAAAIQMLIAMLSQECf0EAIQMCQCAAKAI4IgRFDQAgBCgCBCIERQ0AIAAgASACIAFrIAQRgYCAgAAAIgNBf0cNACAAQcaRgIAANgIQQRghAwsgAwsuAQJ/QQAhAwJAIAAoAjgiBEUNACAEKAIwIgRFDQAgACAEEYCAgIAAACEDCyADC0kBAn9BACEDAkAgACgCOCIERQ0AIAQoAggiBEUNACAAIAEgAiABayAEEYGAgIAAACIDQX9HDQAgAEH2ioCAADYCEEEYIQMLIAMLLgECf0EAIQMCQCAAKAI4IgRFDQAgBCgCNCIERQ0AIAAgBBGAgICAAAAhAwsgAwtJAQJ/QQAhAwJAIAAoAjgiBEUNACAEKAIMIgRFDQAgACABIAIgAWsgBBGBgICAAAAiA0F/Rw0AIABB7ZqAgAA2AhBBGCEDCyADCy4BAn9BACEDAkAgACgCOCIERQ0AIAQoAjgiBEUNACAAIAQRgICAgAAAIQMLIAMLSQECf0EAIQMCQCAAKAI4IgRFDQAgBCgCECIERQ0AIAAgASACIAFrIAQRgYCAgAAAIgNBf0cNACAAQZWQgIAANgIQQRghAwsgAwsuAQJ/QQAhAwJAIAAoAjgiBEUNACAEKAI8IgRFDQAgACAEEYCAgIAAACEDCyADC0kBAn9BACEDAkAgACgCOCIERQ0AIAQoAhQiBEUNACAAIAEgAiABayAEEYGAgIAAACIDQX9HDQAgAEGqm4CAADYCEEEYIQMLIAMLLgECf0EAIQMCQCAAKAI4IgRFDQAgBCgCQCIERQ0AIAAgBBGAgICAAAAhAwsgAwtJAQJ/QQAhAwJAIAAoAjgiBEUNACAEKAIYIgRFDQAgACABIAIgAWsgBBGBgICAAAAiA0F/Rw0AIABB7ZOAgAA2AhBBGCEDCyADCy4BAn9BACEDAkAgACgCOCIERQ0AIAQoAkQiBEUNACAAIAQRgICAgAAAIQMLIAMLLgECf0EAIQMCQCAAKAI4IgRFDQAgBCgCJCIERQ0AIAAgBBGAgICAAAAhAwsgAwsuAQJ/QQAhAwJAIAAoAjgiBEUNACAEKAIsIgRFDQAgACAEEYCAgIAAACEDCyADC0kBAn9BACEDAkAgACgCOCIERQ0AIAQoAigiBEUNACAAIAEgAiABayAEEYGAgIAAACIDQX9HDQAgAEH2iICAADYCEEEYIQMLIAMLLgECf0EAIQMCQCAAKAI4IgRFDQAgBCgCUCIERQ0AIAAgBBGAgICAAAAhAwsgAwtJAQJ/QQAhAwJAIAAoAjgiBEUNACAEKAIcIgRFDQAgACABIAIgAWsgBBGBgICAAAAiA0F/Rw0AIABBwpmAgAA2AhBBGCEDCyADCy4BAn9BACEDAkAgACgCOCIERQ0AIAQoAkgiBEUNACAAIAQRgICAgAAAIQMLIAMLSQECf0EAIQMCQCAAKAI4IgRFDQAgBCgCICIERQ0AIAAgASACIAFrIAQRgYCAgAAAIgNBf0cNACAAQZSUgIAANgIQQRghAwsgAwsuAQJ/QQAhAwJAIAAoAjgiBEUNACAEKAJMIgRFDQAgACAEEYCAgIAAACEDCyADCy4BAn9BACEDAkAgACgCOCIERQ0AIAQoAlQiBEUNACAAIAQRgICAgAAAIQMLIAMLLgECf0EAIQMCQCAAKAI4IgRFDQAgBCgCWCIERQ0AIAAgBBGAgICAAAAhAwsgAwtFAQF/AkACQCAALwEwQRRxQRRHDQBBASEDIAAtAChBAUYNASAALwEyQeUARiEDDAELIAAtAClBBUYhAwsgACADOgAuQQAL/gEBA39BASEDAkAgAC8BMCIEQQhxDQAgACkDIEIAUiEDCwJAAkAgAC0ALkUNAEEBIQUgAC0AKUEFRg0BQQEhBSAEQcAAcUUgA3FBAUcNAQtBACEFIARBwABxDQBBAiEFIARB//8DcSIDQQhxDQACQCADQYAEcUUNAAJAIAAtAChBAUcNACAALQAtQQpxDQBBBQ8LQQQPCwJAIANBIHENAAJAIAAtAChBAUYNACAALwEyQf//A3EiAEGcf2pB5ABJDQAgAEHMAUYNACAAQbACRg0AQQQhBSAEQShxRQ0CIANBiARxQYAERg0CC0EADwtBAEEDIAApAyBQGyEFCyAFC2IBAn9BACEBAkAgAC0AKEEBRg0AIAAvATJB//8DcSICQZx/akHkAEkNACACQcwBRg0AIAJBsAJGDQAgAC8BMCIAQcAAcQ0AQQEhASAAQYgEcUGABEYNACAAQShxRSEBCyABC6cBAQN/AkACQAJAIAAtACpFDQAgAC0AK0UNAEEAIQMgAC8BMCIEQQJxRQ0BDAILQQAhAyAALwEwIgRBAXFFDQELQQEhAyAALQAoQQFGDQAgAC8BMkH//wNxIgVBnH9qQeQASQ0AIAVBzAFGDQAgBUGwAkYNACAEQcAAcQ0AQQAhAyAEQYgEcUGABEYNACAEQShxQQBHIQMLIABBADsBMCAAQQA6AC8gAwuZAQECfwJAAkACQCAALQAqRQ0AIAAtACtFDQBBACEBIAAvATAiAkECcUUNAQwCC0EAIQEgAC8BMCICQQFxRQ0BC0EBIQEgAC0AKEEBRg0AIAAvATJB//8DcSIAQZx/akHkAEkNACAAQcwBRg0AIABBsAJGDQAgAkHAAHENAEEAIQEgAkGIBHFBgARGDQAgAkEocUEARyEBCyABC0kBAXsgAEEQav0MAAAAAAAAAAAAAAAAAAAAACIB/QsDACAAIAH9CwMAIABBMGogAf0LAwAgAEEgaiAB/QsDACAAQd0BNgIcQQALewEBfwJAIAAoAgwiAw0AAkAgACgCBEUNACAAIAE2AgQLAkAgACABIAIQxICAgAAiAw0AIAAoAgwPCyAAIAM2AhxBACEDIAAoAgQiAUUNACAAIAEgAiAAKAIIEYGAgIAAACIBRQ0AIAAgAjYCFCAAIAE2AgwgASEDCyADC+TzAQMOfwN+BH8jgICAgABBEGsiAySAgICAACABIQQgASEFIAEhBiABIQcgASEIIAEhCSABIQogASELIAEhDCABIQ0gASEOIAEhDwJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQCAAKAIcIhBBf2oO3QHaAQHZAQIDBAUGBwgJCgsMDQ7YAQ8Q1wEREtYBExQVFhcYGRob4AHfARwdHtUBHyAhIiMkJdQBJicoKSorLNMB0gEtLtEB0AEvMDEyMzQ1Njc4OTo7PD0+P0BBQkNERUbbAUdISUrPAc4BS80BTMwBTU5PUFFSU1RVVldYWVpbXF1eX2BhYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5ent8fX5/gAGBAYIBgwGEAYUBhgGHAYgBiQGKAYsBjAGNAY4BjwGQAZEBkgGTAZQBlQGWAZcBmAGZAZoBmwGcAZ0BngGfAaABoQGiAaMBpAGlAaYBpwGoAakBqgGrAawBrQGuAa8BsAGxAbIBswG0AbUBtgG3AcsBygG4AckBuQHIAboBuwG8Ab0BvgG/AcABwQHCAcMBxAHFAcYBANwBC0EAIRAMxgELQQ4hEAzFAQtBDSEQDMQBC0EPIRAMwwELQRAhEAzCAQtBEyEQDMEBC0EUIRAMwAELQRUhEAy/AQtBFiEQDL4BC0EXIRAMvQELQRghEAy8AQtBGSEQDLsBC0EaIRAMugELQRshEAy5AQtBHCEQDLgBC0EIIRAMtwELQR0hEAy2AQtBICEQDLUBC0EfIRAMtAELQQchEAyzAQtBISEQDLIBC0EiIRAMsQELQR4hEAywAQtBIyEQDK8BC0ESIRAMrgELQREhEAytAQtBJCEQDKwBC0ElIRAMqwELQSYhEAyqAQtBJyEQDKkBC0HDASEQDKgBC0EpIRAMpwELQSshEAymAQtBLCEQDKUBC0EtIRAMpAELQS4hEAyjAQtBLyEQDKIBC0HEASEQDKEBC0EwIRAMoAELQTQhEAyfAQtBDCEQDJ4BC0ExIRAMnQELQTIhEAycAQtBMyEQDJsBC0E5IRAMmgELQTUhEAyZAQtBxQEhEAyYAQtBCyEQDJcBC0E6IRAMlgELQTYhEAyVAQtBCiEQDJQBC0E3IRAMkwELQTghEAySAQtBPCEQDJEBC0E7IRAMkAELQT0hEAyPAQtBCSEQDI4BC0EoIRAMjQELQT4hEAyMAQtBPyEQDIsBC0HAACEQDIoBC0HBACEQDIkBC0HCACEQDIgBC0HDACEQDIcBC0HEACEQDIYBC0HFACEQDIUBC0HGACEQDIQBC0EqIRAMgwELQccAIRAMggELQcgAIRAMgQELQckAIRAMgAELQcoAIRAMfwtBywAhEAx+C0HNACEQDH0LQcwAIRAMfAtBzgAhEAx7C0HPACEQDHoLQdAAIRAMeQtB0QAhEAx4C0HSACEQDHcLQdMAIRAMdgtB1AAhEAx1C0HWACEQDHQLQdUAIRAMcwtBBiEQDHILQdcAIRAMcQtBBSEQDHALQdgAIRAMbwtBBCEQDG4LQdkAIRAMbQtB2gAhEAxsC0HbACEQDGsLQdwAIRAMagtBAyEQDGkLQd0AIRAMaAtB3gAhEAxnC0HfACEQDGYLQeEAIRAMZQtB4AAhEAxkC0HiACEQDGMLQeMAIRAMYgtBAiEQDGELQeQAIRAMYAtB5QAhEAxfC0HmACEQDF4LQecAIRAMXQtB6AAhEAxcC0HpACEQDFsLQeoAIRAMWgtB6wAhEAxZC0HsACEQDFgLQe0AIRAMVwtB7gAhEAxWC0HvACEQDFULQfAAIRAMVAtB8QAhEAxTC0HyACEQDFILQfMAIRAMUQtB9AAhEAxQC0H1ACEQDE8LQfYAIRAMTgtB9wAhEAxNC0H4ACEQDEwLQfkAIRAMSwtB+gAhEAxKC0H7ACEQDEkLQfwAIRAMSAtB/QAhEAxHC0H+ACEQDEYLQf8AIRAMRQtBgAEhEAxEC0GBASEQDEMLQYIBIRAMQgtBgwEhEAxBC0GEASEQDEALQYUBIRAMPwtBhgEhEAw+C0GHASEQDD0LQYgBIRAMPAtBiQEhEAw7C0GKASEQDDoLQYsBIRAMOQtBjAEhEAw4C0GNASEQDDcLQY4BIRAMNgtBjwEhEAw1C0GQASEQDDQLQZEBIRAMMwtBkgEhEAwyC0GTASEQDDELQZQBIRAMMAtBlQEhEAwvC0GWASEQDC4LQZcBIRAMLQtBmAEhEAwsC0GZASEQDCsLQZoBIRAMKgtBmwEhEAwpC0GcASEQDCgLQZ0BIRAMJwtBngEhEAwmC0GfASEQDCULQaABIRAMJAtBoQEhEAwjC0GiASEQDCILQaMBIRAMIQtBpAEhEAwgC0GlASEQDB8LQaYBIRAMHgtBpwEhEAwdC0GoASEQDBwLQakBIRAMGwtBqgEhEAwaC0GrASEQDBkLQawBIRAMGAtBrQEhEAwXC0GuASEQDBYLQQEhEAwVC0GvASEQDBQLQbABIRAMEwtBsQEhEAwSC0GzASEQDBELQbIBIRAMEAtBtAEhEAwPC0G1ASEQDA4LQbYBIRAMDQtBtwEhEAwMC0G4ASEQDAsLQbkBIRAMCgtBugEhEAwJC0G7ASEQDAgLQcYBIRAMBwtBvAEhEAwGC0G9ASEQDAULQb4BIRAMBAtBvwEhEAwDC0HAASEQDAILQcIBIRAMAQtBwQEhEAsDQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAIBAOxwEAAQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRobHB4fICEjJSg/QEFERUZHSElKS0xNT1BRUlPeA1dZW1xdYGJlZmdoaWprbG1vcHFyc3R1dnd4eXp7fH1+gAGCAYUBhgGHAYkBiwGMAY0BjgGPAZABkQGUAZUBlgGXAZgBmQGaAZsBnAGdAZ4BnwGgAaEBogGjAaQBpQGmAacBqAGpAaoBqwGsAa0BrgGvAbABsQGyAbMBtAG1AbYBtwG4AbkBugG7AbwBvQG+Ab8BwAHBAcIBwwHEAcUBxgHHAcgByQHKAcsBzAHNAc4BzwHQAdEB0gHTAdQB1QHWAdcB2AHZAdoB2wHcAd0B3gHgAeEB4gHjAeQB5QHmAecB6AHpAeoB6wHsAe0B7gHvAfAB8QHyAfMBmQKkArAC/gL+AgsgASIEIAJHDfMBQd0BIRAM/wMLIAEiECACRw3dAUHDASEQDP4DCyABIgEgAkcNkAFB9wAhEAz9AwsgASIBIAJHDYYBQe8AIRAM/AMLIAEiASACRw1/QeoAIRAM+wMLIAEiASACRw17QegAIRAM+gMLIAEiASACRw14QeYAIRAM+QMLIAEiASACRw0aQRghEAz4AwsgASIBIAJHDRRBEiEQDPcDCyABIgEgAkcNWUHFACEQDPYDCyABIgEgAkcNSkE/IRAM9QMLIAEiASACRw1IQTwhEAz0AwsgASIBIAJHDUFBMSEQDPMDCyAALQAuQQFGDesDDIcCCyAAIAEiASACEMCAgIAAQQFHDeYBIABCADcDIAznAQsgACABIgEgAhC0gICAACIQDecBIAEhAQz1AgsCQCABIgEgAkcNAEEGIRAM8AMLIAAgAUEBaiIBIAIQu4CAgAAiEA3oASABIQEMMQsgAEIANwMgQRIhEAzVAwsgASIQIAJHDStBHSEQDO0DCwJAIAEiASACRg0AIAFBAWohAUEQIRAM1AMLQQchEAzsAwsgAEIAIAApAyAiESACIAEiEGutIhJ9IhMgEyARVhs3AyAgESASViIURQ3lAUEIIRAM6wMLAkAgASIBIAJGDQAgAEGJgICAADYCCCAAIAE2AgQgASEBQRQhEAzSAwtBCSEQDOoDCyABIQEgACkDIFAN5AEgASEBDPICCwJAIAEiASACRw0AQQshEAzpAwsgACABQQFqIgEgAhC2gICAACIQDeUBIAEhAQzyAgsgACABIgEgAhC4gICAACIQDeUBIAEhAQzyAgsgACABIgEgAhC4gICAACIQDeYBIAEhAQwNCyAAIAEiASACELqAgIAAIhAN5wEgASEBDPACCwJAIAEiASACRw0AQQ8hEAzlAwsgAS0AACIQQTtGDQggEEENRw3oASABQQFqIQEM7wILIAAgASIBIAIQuoCAgAAiEA3oASABIQEM8gILA0ACQCABLQAAQfC1gIAAai0AACIQQQFGDQAgEEECRw3rASAAKAIEIRAgAEEANgIEIAAgECABQQFqIgEQuYCAgAAiEA3qASABIQEM9AILIAFBAWoiASACRw0AC0ESIRAM4gMLIAAgASIBIAIQuoCAgAAiEA3pASABIQEMCgsgASIBIAJHDQZBGyEQDOADCwJAIAEiASACRw0AQRYhEAzgAwsgAEGKgICAADYCCCAAIAE2AgQgACABIAIQuICAgAAiEA3qASABIQFBICEQDMYDCwJAIAEiASACRg0AA0ACQCABLQAAQfC3gIAAai0AACIQQQJGDQACQCAQQX9qDgTlAewBAOsB7AELIAFBAWohAUEIIRAMyAMLIAFBAWoiASACRw0AC0EVIRAM3wMLQRUhEAzeAwsDQAJAIAEtAABB8LmAgABqLQAAIhBBAkYNACAQQX9qDgTeAewB4AHrAewBCyABQQFqIgEgAkcNAAtBGCEQDN0DCwJAIAEiASACRg0AIABBi4CAgAA2AgggACABNgIEIAEhAUEHIRAMxAMLQRkhEAzcAwsgAUEBaiEBDAILAkAgASIUIAJHDQBBGiEQDNsDCyAUIQECQCAULQAAQXNqDhTdAu4C7gLuAu4C7gLuAu4C7gLuAu4C7gLuAu4C7gLuAu4C7gLuAgDuAgtBACEQIABBADYCHCAAQa+LgIAANgIQIABBAjYCDCAAIBRBAWo2AhQM2gMLAkAgAS0AACIQQTtGDQAgEEENRw3oASABQQFqIQEM5QILIAFBAWohAQtBIiEQDL8DCwJAIAEiECACRw0AQRwhEAzYAwtCACERIBAhASAQLQAAQVBqDjfnAeYBAQIDBAUGBwgAAAAAAAAACQoLDA0OAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPEBESExQAC0EeIRAMvQMLQgIhEQzlAQtCAyERDOQBC0IEIREM4wELQgUhEQziAQtCBiERDOEBC0IHIREM4AELQgghEQzfAQtCCSERDN4BC0IKIREM3QELQgshEQzcAQtCDCERDNsBC0INIREM2gELQg4hEQzZAQtCDyERDNgBC0IKIREM1wELQgshEQzWAQtCDCERDNUBC0INIREM1AELQg4hEQzTAQtCDyERDNIBC0IAIRECQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAIBAtAABBUGoON+UB5AEAAQIDBAUGB+YB5gHmAeYB5gHmAeYBCAkKCwwN5gHmAeYB5gHmAeYB5gHmAeYB5gHmAeYB5gHmAeYB5gHmAeYB5gHmAeYB5gHmAeYB5gHmAQ4PEBESE+YBC0ICIREM5AELQgMhEQzjAQtCBCERDOIBC0IFIREM4QELQgYhEQzgAQtCByERDN8BC0IIIREM3gELQgkhEQzdAQtCCiERDNwBC0ILIREM2wELQgwhEQzaAQtCDSERDNkBC0IOIREM2AELQg8hEQzXAQtCCiERDNYBC0ILIREM1QELQgwhEQzUAQtCDSERDNMBC0IOIREM0gELQg8hEQzRAQsgAEIAIAApAyAiESACIAEiEGutIhJ9IhMgEyARVhs3AyAgESASViIURQ3SAUEfIRAMwAMLAkAgASIBIAJGDQAgAEGJgICAADYCCCAAIAE2AgQgASEBQSQhEAynAwtBICEQDL8DCyAAIAEiECACEL6AgIAAQX9qDgW2AQDFAgHRAdIBC0ERIRAMpAMLIABBAToALyAQIQEMuwMLIAEiASACRw3SAUEkIRAMuwMLIAEiDSACRw0eQcYAIRAMugMLIAAgASIBIAIQsoCAgAAiEA3UASABIQEMtQELIAEiECACRw0mQdAAIRAMuAMLAkAgASIBIAJHDQBBKCEQDLgDCyAAQQA2AgQgAEGMgICAADYCCCAAIAEgARCxgICAACIQDdMBIAEhAQzYAQsCQCABIhAgAkcNAEEpIRAMtwMLIBAtAAAiAUEgRg0UIAFBCUcN0wEgEEEBaiEBDBULAkAgASIBIAJGDQAgAUEBaiEBDBcLQSohEAy1AwsCQCABIhAgAkcNAEErIRAMtQMLAkAgEC0AACIBQQlGDQAgAUEgRw3VAQsgAC0ALEEIRg3TASAQIQEMkQMLAkAgASIBIAJHDQBBLCEQDLQDCyABLQAAQQpHDdUBIAFBAWohAQzJAgsgASIOIAJHDdUBQS8hEAyyAwsDQAJAIAEtAAAiEEEgRg0AAkAgEEF2ag4EANwB3AEA2gELIAEhAQzgAQsgAUEBaiIBIAJHDQALQTEhEAyxAwtBMiEQIAEiFCACRg2wAyACIBRrIAAoAgAiAWohFSAUIAFrQQNqIRYCQANAIBQtAAAiF0EgciAXIBdBv39qQf8BcUEaSRtB/wFxIAFB8LuAgABqLQAARw0BAkAgAUEDRw0AQQYhAQyWAwsgAUEBaiEBIBRBAWoiFCACRw0ACyAAIBU2AgAMsQMLIABBADYCACAUIQEM2QELQTMhECABIhQgAkYNrwMgAiAUayAAKAIAIgFqIRUgFCABa0EIaiEWAkADQCAULQAAIhdBIHIgFyAXQb9/akH/AXFBGkkbQf8BcSABQfS7gIAAai0AAEcNAQJAIAFBCEcNAEEFIQEMlQMLIAFBAWohASAUQQFqIhQgAkcNAAsgACAVNgIADLADCyAAQQA2AgAgFCEBDNgBC0E0IRAgASIUIAJGDa4DIAIgFGsgACgCACIBaiEVIBQgAWtBBWohFgJAA0AgFC0AACIXQSByIBcgF0G/f2pB/wFxQRpJG0H/AXEgAUHQwoCAAGotAABHDQECQCABQQVHDQBBByEBDJQDCyABQQFqIQEgFEEBaiIUIAJHDQALIAAgFTYCAAyvAwsgAEEANgIAIBQhAQzXAQsCQCABIgEgAkYNAANAAkAgAS0AAEGAvoCAAGotAAAiEEEBRg0AIBBBAkYNCiABIQEM3QELIAFBAWoiASACRw0AC0EwIRAMrgMLQTAhEAytAwsCQCABIgEgAkYNAANAAkAgAS0AACIQQSBGDQAgEEF2ag4E2QHaAdoB2QHaAQsgAUEBaiIBIAJHDQALQTghEAytAwtBOCEQDKwDCwNAAkAgAS0AACIQQSBGDQAgEEEJRw0DCyABQQFqIgEgAkcNAAtBPCEQDKsDCwNAAkAgAS0AACIQQSBGDQACQAJAIBBBdmoOBNoBAQHaAQALIBBBLEYN2wELIAEhAQwECyABQQFqIgEgAkcNAAtBPyEQDKoDCyABIQEM2wELQcAAIRAgASIUIAJGDagDIAIgFGsgACgCACIBaiEWIBQgAWtBBmohFwJAA0AgFC0AAEEgciABQYDAgIAAai0AAEcNASABQQZGDY4DIAFBAWohASAUQQFqIhQgAkcNAAsgACAWNgIADKkDCyAAQQA2AgAgFCEBC0E2IRAMjgMLAkAgASIPIAJHDQBBwQAhEAynAwsgAEGMgICAADYCCCAAIA82AgQgDyEBIAAtACxBf2oOBM0B1QHXAdkBhwMLIAFBAWohAQzMAQsCQCABIgEgAkYNAANAAkAgAS0AACIQQSByIBAgEEG/f2pB/wFxQRpJG0H/AXEiEEEJRg0AIBBBIEYNAAJAAkACQAJAIBBBnX9qDhMAAwMDAwMDAwEDAwMDAwMDAwMCAwsgAUEBaiEBQTEhEAyRAwsgAUEBaiEBQTIhEAyQAwsgAUEBaiEBQTMhEAyPAwsgASEBDNABCyABQQFqIgEgAkcNAAtBNSEQDKUDC0E1IRAMpAMLAkAgASIBIAJGDQADQAJAIAEtAABBgLyAgABqLQAAQQFGDQAgASEBDNMBCyABQQFqIgEgAkcNAAtBPSEQDKQDC0E9IRAMowMLIAAgASIBIAIQsICAgAAiEA3WASABIQEMAQsgEEEBaiEBC0E8IRAMhwMLAkAgASIBIAJHDQBBwgAhEAygAwsCQANAAkAgAS0AAEF3ag4YAAL+Av4ChAP+Av4C/gL+Av4C/gL+Av4C/gL+Av4C/gL+Av4C/gL+Av4C/gIA/gILIAFBAWoiASACRw0AC0HCACEQDKADCyABQQFqIQEgAC0ALUEBcUUNvQEgASEBC0EsIRAMhQMLIAEiASACRw3TAUHEACEQDJ0DCwNAAkAgAS0AAEGQwICAAGotAABBAUYNACABIQEMtwILIAFBAWoiASACRw0AC0HFACEQDJwDCyANLQAAIhBBIEYNswEgEEE6Rw2BAyAAKAIEIQEgAEEANgIEIAAgASANEK+AgIAAIgEN0AEgDUEBaiEBDLMCC0HHACEQIAEiDSACRg2aAyACIA1rIAAoAgAiAWohFiANIAFrQQVqIRcDQCANLQAAIhRBIHIgFCAUQb9/akH/AXFBGkkbQf8BcSABQZDCgIAAai0AAEcNgAMgAUEFRg30AiABQQFqIQEgDUEBaiINIAJHDQALIAAgFjYCAAyaAwtByAAhECABIg0gAkYNmQMgAiANayAAKAIAIgFqIRYgDSABa0EJaiEXA0AgDS0AACIUQSByIBQgFEG/f2pB/wFxQRpJG0H/AXEgAUGWwoCAAGotAABHDf8CAkAgAUEJRw0AQQIhAQz1AgsgAUEBaiEBIA1BAWoiDSACRw0ACyAAIBY2AgAMmQMLAkAgASINIAJHDQBByQAhEAyZAwsCQAJAIA0tAAAiAUEgciABIAFBv39qQf8BcUEaSRtB/wFxQZJ/ag4HAIADgAOAA4ADgAMBgAMLIA1BAWohAUE+IRAMgAMLIA1BAWohAUE/IRAM/wILQcoAIRAgASINIAJGDZcDIAIgDWsgACgCACIBaiEWIA0gAWtBAWohFwNAIA0tAAAiFEEgciAUIBRBv39qQf8BcUEaSRtB/wFxIAFBoMKAgABqLQAARw39AiABQQFGDfACIAFBAWohASANQQFqIg0gAkcNAAsgACAWNgIADJcDC0HLACEQIAEiDSACRg2WAyACIA1rIAAoAgAiAWohFiANIAFrQQ5qIRcDQCANLQAAIhRBIHIgFCAUQb9/akH/AXFBGkkbQf8BcSABQaLCgIAAai0AAEcN/AIgAUEORg3wAiABQQFqIQEgDUEBaiINIAJHDQALIAAgFjYCAAyWAwtBzAAhECABIg0gAkYNlQMgAiANayAAKAIAIgFqIRYgDSABa0EPaiEXA0AgDS0AACIUQSByIBQgFEG/f2pB/wFxQRpJG0H/AXEgAUHAwoCAAGotAABHDfsCAkAgAUEPRw0AQQMhAQzxAgsgAUEBaiEBIA1BAWoiDSACRw0ACyAAIBY2AgAMlQMLQc0AIRAgASINIAJGDZQDIAIgDWsgACgCACIBaiEWIA0gAWtBBWohFwNAIA0tAAAiFEEgciAUIBRBv39qQf8BcUEaSRtB/wFxIAFB0MKAgABqLQAARw36AgJAIAFBBUcNAEEEIQEM8AILIAFBAWohASANQQFqIg0gAkcNAAsgACAWNgIADJQDCwJAIAEiDSACRw0AQc4AIRAMlAMLAkACQAJAAkAgDS0AACIBQSByIAEgAUG/f2pB/wFxQRpJG0H/AXFBnX9qDhMA/QL9Av0C/QL9Av0C/QL9Av0C/QL9Av0CAf0C/QL9AgID/QILIA1BAWohAUHBACEQDP0CCyANQQFqIQFBwgAhEAz8AgsgDUEBaiEBQcMAIRAM+wILIA1BAWohAUHEACEQDPoCCwJAIAEiASACRg0AIABBjYCAgAA2AgggACABNgIEIAEhAUHFACEQDPoCC0HPACEQDJIDCyAQIQECQAJAIBAtAABBdmoOBAGoAqgCAKgCCyAQQQFqIQELQSchEAz4AgsCQCABIgEgAkcNAEHRACEQDJEDCwJAIAEtAABBIEYNACABIQEMjQELIAFBAWohASAALQAtQQFxRQ3HASABIQEMjAELIAEiFyACRw3IAUHSACEQDI8DC0HTACEQIAEiFCACRg2OAyACIBRrIAAoAgAiAWohFiAUIAFrQQFqIRcDQCAULQAAIAFB1sKAgABqLQAARw3MASABQQFGDccBIAFBAWohASAUQQFqIhQgAkcNAAsgACAWNgIADI4DCwJAIAEiASACRw0AQdUAIRAMjgMLIAEtAABBCkcNzAEgAUEBaiEBDMcBCwJAIAEiASACRw0AQdYAIRAMjQMLAkACQCABLQAAQXZqDgQAzQHNAQHNAQsgAUEBaiEBDMcBCyABQQFqIQFBygAhEAzzAgsgACABIgEgAhCugICAACIQDcsBIAEhAUHNACEQDPICCyAALQApQSJGDYUDDKYCCwJAIAEiASACRw0AQdsAIRAMigMLQQAhFEEBIRdBASEWQQAhEAJAAkACQAJAAkACQAJAAkACQCABLQAAQVBqDgrUAdMBAAECAwQFBgjVAQtBAiEQDAYLQQMhEAwFC0EEIRAMBAtBBSEQDAMLQQYhEAwCC0EHIRAMAQtBCCEQC0EAIRdBACEWQQAhFAzMAQtBCSEQQQEhFEEAIRdBACEWDMsBCwJAIAEiASACRw0AQd0AIRAMiQMLIAEtAABBLkcNzAEgAUEBaiEBDKYCCyABIgEgAkcNzAFB3wAhEAyHAwsCQCABIgEgAkYNACAAQY6AgIAANgIIIAAgATYCBCABIQFB0AAhEAzuAgtB4AAhEAyGAwtB4QAhECABIgEgAkYNhQMgAiABayAAKAIAIhRqIRYgASAUa0EDaiEXA0AgAS0AACAUQeLCgIAAai0AAEcNzQEgFEEDRg3MASAUQQFqIRQgAUEBaiIBIAJHDQALIAAgFjYCAAyFAwtB4gAhECABIgEgAkYNhAMgAiABayAAKAIAIhRqIRYgASAUa0ECaiEXA0AgAS0AACAUQebCgIAAai0AAEcNzAEgFEECRg3OASAUQQFqIRQgAUEBaiIBIAJHDQALIAAgFjYCAAyEAwtB4wAhECABIgEgAkYNgwMgAiABayAAKAIAIhRqIRYgASAUa0EDaiEXA0AgAS0AACAUQenCgIAAai0AAEcNywEgFEEDRg3OASAUQQFqIRQgAUEBaiIBIAJHDQALIAAgFjYCAAyDAwsCQCABIgEgAkcNAEHlACEQDIMDCyAAIAFBAWoiASACEKiAgIAAIhANzQEgASEBQdYAIRAM6QILAkAgASIBIAJGDQADQAJAIAEtAAAiEEEgRg0AAkACQAJAIBBBuH9qDgsAAc8BzwHPAc8BzwHPAc8BzwECzwELIAFBAWohAUHSACEQDO0CCyABQQFqIQFB0wAhEAzsAgsgAUEBaiEBQdQAIRAM6wILIAFBAWoiASACRw0AC0HkACEQDIIDC0HkACEQDIEDCwNAAkAgAS0AAEHwwoCAAGotAAAiEEEBRg0AIBBBfmoOA88B0AHRAdIBCyABQQFqIgEgAkcNAAtB5gAhEAyAAwsCQCABIgEgAkYNACABQQFqIQEMAwtB5wAhEAz/AgsDQAJAIAEtAABB8MSAgABqLQAAIhBBAUYNAAJAIBBBfmoOBNIB0wHUAQDVAQsgASEBQdcAIRAM5wILIAFBAWoiASACRw0AC0HoACEQDP4CCwJAIAEiASACRw0AQekAIRAM/gILAkAgAS0AACIQQXZqDhq6AdUB1QG8AdUB1QHVAdUB1QHVAdUB1QHVAdUB1QHVAdUB1QHVAdUB1QHVAcoB1QHVAQDTAQsgAUEBaiEBC0EGIRAM4wILA0ACQCABLQAAQfDGgIAAai0AAEEBRg0AIAEhAQyeAgsgAUEBaiIBIAJHDQALQeoAIRAM+wILAkAgASIBIAJGDQAgAUEBaiEBDAMLQesAIRAM+gILAkAgASIBIAJHDQBB7AAhEAz6AgsgAUEBaiEBDAELAkAgASIBIAJHDQBB7QAhEAz5AgsgAUEBaiEBC0EEIRAM3gILAkAgASIUIAJHDQBB7gAhEAz3AgsgFCEBAkACQAJAIBQtAABB8MiAgABqLQAAQX9qDgfUAdUB1gEAnAIBAtcBCyAUQQFqIQEMCgsgFEEBaiEBDM0BC0EAIRAgAEEANgIcIABBm5KAgAA2AhAgAEEHNgIMIAAgFEEBajYCFAz2AgsCQANAAkAgAS0AAEHwyICAAGotAAAiEEEERg0AAkACQCAQQX9qDgfSAdMB1AHZAQAEAdkBCyABIQFB2gAhEAzgAgsgAUEBaiEBQdwAIRAM3wILIAFBAWoiASACRw0AC0HvACEQDPYCCyABQQFqIQEMywELAkAgASIUIAJHDQBB8AAhEAz1AgsgFC0AAEEvRw3UASAUQQFqIQEMBgsCQCABIhQgAkcNAEHxACEQDPQCCwJAIBQtAAAiAUEvRw0AIBRBAWohAUHdACEQDNsCCyABQXZqIgRBFksN0wFBASAEdEGJgIACcUUN0wEMygILAkAgASIBIAJGDQAgAUEBaiEBQd4AIRAM2gILQfIAIRAM8gILAkAgASIUIAJHDQBB9AAhEAzyAgsgFCEBAkAgFC0AAEHwzICAAGotAABBf2oOA8kClAIA1AELQeEAIRAM2AILAkAgASIUIAJGDQADQAJAIBQtAABB8MqAgABqLQAAIgFBA0YNAAJAIAFBf2oOAssCANUBCyAUIQFB3wAhEAzaAgsgFEEBaiIUIAJHDQALQfMAIRAM8QILQfMAIRAM8AILAkAgASIBIAJGDQAgAEGPgICAADYCCCAAIAE2AgQgASEBQeAAIRAM1wILQfUAIRAM7wILAkAgASIBIAJHDQBB9gAhEAzvAgsgAEGPgICAADYCCCAAIAE2AgQgASEBC0EDIRAM1AILA0AgAS0AAEEgRw3DAiABQQFqIgEgAkcNAAtB9wAhEAzsAgsCQCABIgEgAkcNAEH4ACEQDOwCCyABLQAAQSBHDc4BIAFBAWohAQzvAQsgACABIgEgAhCsgICAACIQDc4BIAEhAQyOAgsCQCABIgQgAkcNAEH6ACEQDOoCCyAELQAAQcwARw3RASAEQQFqIQFBEyEQDM8BCwJAIAEiBCACRw0AQfsAIRAM6QILIAIgBGsgACgCACIBaiEUIAQgAWtBBWohEANAIAQtAAAgAUHwzoCAAGotAABHDdABIAFBBUYNzgEgAUEBaiEBIARBAWoiBCACRw0ACyAAIBQ2AgBB+wAhEAzoAgsCQCABIgQgAkcNAEH8ACEQDOgCCwJAAkAgBC0AAEG9f2oODADRAdEB0QHRAdEB0QHRAdEB0QHRAQHRAQsgBEEBaiEBQeYAIRAMzwILIARBAWohAUHnACEQDM4CCwJAIAEiBCACRw0AQf0AIRAM5wILIAIgBGsgACgCACIBaiEUIAQgAWtBAmohEAJAA0AgBC0AACABQe3PgIAAai0AAEcNzwEgAUECRg0BIAFBAWohASAEQQFqIgQgAkcNAAsgACAUNgIAQf0AIRAM5wILIABBADYCACAQQQFqIQFBECEQDMwBCwJAIAEiBCACRw0AQf4AIRAM5gILIAIgBGsgACgCACIBaiEUIAQgAWtBBWohEAJAA0AgBC0AACABQfbOgIAAai0AAEcNzgEgAUEFRg0BIAFBAWohASAEQQFqIgQgAkcNAAsgACAUNgIAQf4AIRAM5gILIABBADYCACAQQQFqIQFBFiEQDMsBCwJAIAEiBCACRw0AQf8AIRAM5QILIAIgBGsgACgCACIBaiEUIAQgAWtBA2ohEAJAA0AgBC0AACABQfzOgIAAai0AAEcNzQEgAUEDRg0BIAFBAWohASAEQQFqIgQgAkcNAAsgACAUNgIAQf8AIRAM5QILIABBADYCACAQQQFqIQFBBSEQDMoBCwJAIAEiBCACRw0AQYABIRAM5AILIAQtAABB2QBHDcsBIARBAWohAUEIIRAMyQELAkAgASIEIAJHDQBBgQEhEAzjAgsCQAJAIAQtAABBsn9qDgMAzAEBzAELIARBAWohAUHrACEQDMoCCyAEQQFqIQFB7AAhEAzJAgsCQCABIgQgAkcNAEGCASEQDOICCwJAAkAgBC0AAEG4f2oOCADLAcsBywHLAcsBywEBywELIARBAWohAUHqACEQDMkCCyAEQQFqIQFB7QAhEAzIAgsCQCABIgQgAkcNAEGDASEQDOECCyACIARrIAAoAgAiAWohECAEIAFrQQJqIRQCQANAIAQtAAAgAUGAz4CAAGotAABHDckBIAFBAkYNASABQQFqIQEgBEEBaiIEIAJHDQALIAAgEDYCAEGDASEQDOECC0EAIRAgAEEANgIAIBRBAWohAQzGAQsCQCABIgQgAkcNAEGEASEQDOACCyACIARrIAAoAgAiAWohFCAEIAFrQQRqIRACQANAIAQtAAAgAUGDz4CAAGotAABHDcgBIAFBBEYNASABQQFqIQEgBEEBaiIEIAJHDQALIAAgFDYCAEGEASEQDOACCyAAQQA2AgAgEEEBaiEBQSMhEAzFAQsCQCABIgQgAkcNAEGFASEQDN8CCwJAAkAgBC0AAEG0f2oOCADIAcgByAHIAcgByAEByAELIARBAWohAUHvACEQDMYCCyAEQQFqIQFB8AAhEAzFAgsCQCABIgQgAkcNAEGGASEQDN4CCyAELQAAQcUARw3FASAEQQFqIQEMgwILAkAgASIEIAJHDQBBhwEhEAzdAgsgAiAEayAAKAIAIgFqIRQgBCABa0EDaiEQAkADQCAELQAAIAFBiM+AgABqLQAARw3FASABQQNGDQEgAUEBaiEBIARBAWoiBCACRw0ACyAAIBQ2AgBBhwEhEAzdAgsgAEEANgIAIBBBAWohAUEtIRAMwgELAkAgASIEIAJHDQBBiAEhEAzcAgsgAiAEayAAKAIAIgFqIRQgBCABa0EIaiEQAkADQCAELQAAIAFB0M+AgABqLQAARw3EASABQQhGDQEgAUEBaiEBIARBAWoiBCACRw0ACyAAIBQ2AgBBiAEhEAzcAgsgAEEANgIAIBBBAWohAUEpIRAMwQELAkAgASIBIAJHDQBBiQEhEAzbAgtBASEQIAEtAABB3wBHDcABIAFBAWohAQyBAgsCQCABIgQgAkcNAEGKASEQDNoCCyACIARrIAAoAgAiAWohFCAEIAFrQQFqIRADQCAELQAAIAFBjM+AgABqLQAARw3BASABQQFGDa8CIAFBAWohASAEQQFqIgQgAkcNAAsgACAUNgIAQYoBIRAM2QILAkAgASIEIAJHDQBBiwEhEAzZAgsgAiAEayAAKAIAIgFqIRQgBCABa0ECaiEQAkADQCAELQAAIAFBjs+AgABqLQAARw3BASABQQJGDQEgAUEBaiEBIARBAWoiBCACRw0ACyAAIBQ2AgBBiwEhEAzZAgsgAEEANgIAIBBBAWohAUECIRAMvgELAkAgASIEIAJHDQBBjAEhEAzYAgsgAiAEayAAKAIAIgFqIRQgBCABa0EBaiEQAkADQCAELQAAIAFB8M+AgABqLQAARw3AASABQQFGDQEgAUEBaiEBIARBAWoiBCACRw0ACyAAIBQ2AgBBjAEhEAzYAgsgAEEANgIAIBBBAWohAUEfIRAMvQELAkAgASIEIAJHDQBBjQEhEAzXAgsgAiAEayAAKAIAIgFqIRQgBCABa0EBaiEQAkADQCAELQAAIAFB8s+AgABqLQAARw2/ASABQQFGDQEgAUEBaiEBIARBAWoiBCACRw0ACyAAIBQ2AgBBjQEhEAzXAgsgAEEANgIAIBBBAWohAUEJIRAMvAELAkAgASIEIAJHDQBBjgEhEAzWAgsCQAJAIAQtAABBt39qDgcAvwG/Ab8BvwG/AQG/AQsgBEEBaiEBQfgAIRAMvQILIARBAWohAUH5ACEQDLwCCwJAIAEiBCACRw0AQY8BIRAM1QILIAIgBGsgACgCACIBaiEUIAQgAWtBBWohEAJAA0AgBC0AACABQZHPgIAAai0AAEcNvQEgAUEFRg0BIAFBAWohASAEQQFqIgQgAkcNAAsgACAUNgIAQY8BIRAM1QILIABBADYCACAQQQFqIQFBGCEQDLoBCwJAIAEiBCACRw0AQZABIRAM1AILIAIgBGsgACgCACIBaiEUIAQgAWtBAmohEAJAA0AgBC0AACABQZfPgIAAai0AAEcNvAEgAUECRg0BIAFBAWohASAEQQFqIgQgAkcNAAsgACAUNgIAQZABIRAM1AILIABBADYCACAQQQFqIQFBFyEQDLkBCwJAIAEiBCACRw0AQZEBIRAM0wILIAIgBGsgACgCACIBaiEUIAQgAWtBBmohEAJAA0AgBC0AACABQZrPgIAAai0AAEcNuwEgAUEGRg0BIAFBAWohASAEQQFqIgQgAkcNAAsgACAUNgIAQZEBIRAM0wILIABBADYCACAQQQFqIQFBFSEQDLgBCwJAIAEiBCACRw0AQZIBIRAM0gILIAIgBGsgACgCACIBaiEUIAQgAWtBBWohEAJAA0AgBC0AACABQaHPgIAAai0AAEcNugEgAUEFRg0BIAFBAWohASAEQQFqIgQgAkcNAAsgACAUNgIAQZIBIRAM0gILIABBADYCACAQQQFqIQFBHiEQDLcBCwJAIAEiBCACRw0AQZMBIRAM0QILIAQtAABBzABHDbgBIARBAWohAUEKIRAMtgELAkAgBCACRw0AQZQBIRAM0AILAkACQCAELQAAQb9/ag4PALkBuQG5AbkBuQG5AbkBuQG5AbkBuQG5AbkBAbkBCyAEQQFqIQFB/gAhEAy3AgsgBEEBaiEBQf8AIRAMtgILAkAgBCACRw0AQZUBIRAMzwILAkACQCAELQAAQb9/ag4DALgBAbgBCyAEQQFqIQFB/QAhEAy2AgsgBEEBaiEEQYABIRAMtQILAkAgBCACRw0AQZYBIRAMzgILIAIgBGsgACgCACIBaiEUIAQgAWtBAWohEAJAA0AgBC0AACABQafPgIAAai0AAEcNtgEgAUEBRg0BIAFBAWohASAEQQFqIgQgAkcNAAsgACAUNgIAQZYBIRAMzgILIABBADYCACAQQQFqIQFBCyEQDLMBCwJAIAQgAkcNAEGXASEQDM0CCwJAAkACQAJAIAQtAABBU2oOIwC4AbgBuAG4AbgBuAG4AbgBuAG4AbgBuAG4AbgBuAG4AbgBuAG4AbgBuAG4AbgBAbgBuAG4AbgBuAECuAG4AbgBA7gBCyAEQQFqIQFB+wAhEAy2AgsgBEEBaiEBQfwAIRAMtQILIARBAWohBEGBASEQDLQCCyAEQQFqIQRBggEhEAyzAgsCQCAEIAJHDQBBmAEhEAzMAgsgAiAEayAAKAIAIgFqIRQgBCABa0EEaiEQAkADQCAELQAAIAFBqc+AgABqLQAARw20ASABQQRGDQEgAUEBaiEBIARBAWoiBCACRw0ACyAAIBQ2AgBBmAEhEAzMAgsgAEEANgIAIBBBAWohAUEZIRAMsQELAkAgBCACRw0AQZkBIRAMywILIAIgBGsgACgCACIBaiEUIAQgAWtBBWohEAJAA0AgBC0AACABQa7PgIAAai0AAEcNswEgAUEFRg0BIAFBAWohASAEQQFqIgQgAkcNAAsgACAUNgIAQZkBIRAMywILIABBADYCACAQQQFqIQFBBiEQDLABCwJAIAQgAkcNAEGaASEQDMoCCyACIARrIAAoAgAiAWohFCAEIAFrQQFqIRACQANAIAQtAAAgAUG0z4CAAGotAABHDbIBIAFBAUYNASABQQFqIQEgBEEBaiIEIAJHDQALIAAgFDYCAEGaASEQDMoCCyAAQQA2AgAgEEEBaiEBQRwhEAyvAQsCQCAEIAJHDQBBmwEhEAzJAgsgAiAEayAAKAIAIgFqIRQgBCABa0EBaiEQAkADQCAELQAAIAFBts+AgABqLQAARw2xASABQQFGDQEgAUEBaiEBIARBAWoiBCACRw0ACyAAIBQ2AgBBmwEhEAzJAgsgAEEANgIAIBBBAWohAUEnIRAMrgELAkAgBCACRw0AQZwBIRAMyAILAkACQCAELQAAQax/ag4CAAGxAQsgBEEBaiEEQYYBIRAMrwILIARBAWohBEGHASEQDK4CCwJAIAQgAkcNAEGdASEQDMcCCyACIARrIAAoAgAiAWohFCAEIAFrQQFqIRACQANAIAQtAAAgAUG4z4CAAGotAABHDa8BIAFBAUYNASABQQFqIQEgBEEBaiIEIAJHDQALIAAgFDYCAEGdASEQDMcCCyAAQQA2AgAgEEEBaiEBQSYhEAysAQsCQCAEIAJHDQBBngEhEAzGAgsgAiAEayAAKAIAIgFqIRQgBCABa0EBaiEQAkADQCAELQAAIAFBus+AgABqLQAARw2uASABQQFGDQEgAUEBaiEBIARBAWoiBCACRw0ACyAAIBQ2AgBBngEhEAzGAgsgAEEANgIAIBBBAWohAUEDIRAMqwELAkAgBCACRw0AQZ8BIRAMxQILIAIgBGsgACgCACIBaiEUIAQgAWtBAmohEAJAA0AgBC0AACABQe3PgIAAai0AAEcNrQEgAUECRg0BIAFBAWohASAEQQFqIgQgAkcNAAsgACAUNgIAQZ8BIRAMxQILIABBADYCACAQQQFqIQFBDCEQDKoBCwJAIAQgAkcNAEGgASEQDMQCCyACIARrIAAoAgAiAWohFCAEIAFrQQNqIRACQANAIAQtAAAgAUG8z4CAAGotAABHDawBIAFBA0YNASABQQFqIQEgBEEBaiIEIAJHDQALIAAgFDYCAEGgASEQDMQCCyAAQQA2AgAgEEEBaiEBQQ0hEAypAQsCQCAEIAJHDQBBoQEhEAzDAgsCQAJAIAQtAABBun9qDgsArAGsAawBrAGsAawBrAGsAawBAawBCyAEQQFqIQRBiwEhEAyqAgsgBEEBaiEEQYwBIRAMqQILAkAgBCACRw0AQaIBIRAMwgILIAQtAABB0ABHDakBIARBAWohBAzpAQsCQCAEIAJHDQBBowEhEAzBAgsCQAJAIAQtAABBt39qDgcBqgGqAaoBqgGqAQCqAQsgBEEBaiEEQY4BIRAMqAILIARBAWohAUEiIRAMpgELAkAgBCACRw0AQaQBIRAMwAILIAIgBGsgACgCACIBaiEUIAQgAWtBAWohEAJAA0AgBC0AACABQcDPgIAAai0AAEcNqAEgAUEBRg0BIAFBAWohASAEQQFqIgQgAkcNAAsgACAUNgIAQaQBIRAMwAILIABBADYCACAQQQFqIQFBHSEQDKUBCwJAIAQgAkcNAEGlASEQDL8CCwJAAkAgBC0AAEGuf2oOAwCoAQGoAQsgBEEBaiEEQZABIRAMpgILIARBAWohAUEEIRAMpAELAkAgBCACRw0AQaYBIRAMvgILAkACQAJAAkACQCAELQAAQb9/ag4VAKoBqgGqAaoBqgGqAaoBqgGqAaoBAaoBqgECqgGqAQOqAaoBBKoBCyAEQQFqIQRBiAEhEAyoAgsgBEEBaiEEQYkBIRAMpwILIARBAWohBEGKASEQDKYCCyAEQQFqIQRBjwEhEAylAgsgBEEBaiEEQZEBIRAMpAILAkAgBCACRw0AQacBIRAMvQILIAIgBGsgACgCACIBaiEUIAQgAWtBAmohEAJAA0AgBC0AACABQe3PgIAAai0AAEcNpQEgAUECRg0BIAFBAWohASAEQQFqIgQgAkcNAAsgACAUNgIAQacBIRAMvQILIABBADYCACAQQQFqIQFBESEQDKIBCwJAIAQgAkcNAEGoASEQDLwCCyACIARrIAAoAgAiAWohFCAEIAFrQQJqIRACQANAIAQtAAAgAUHCz4CAAGotAABHDaQBIAFBAkYNASABQQFqIQEgBEEBaiIEIAJHDQALIAAgFDYCAEGoASEQDLwCCyAAQQA2AgAgEEEBaiEBQSwhEAyhAQsCQCAEIAJHDQBBqQEhEAy7AgsgAiAEayAAKAIAIgFqIRQgBCABa0EEaiEQAkADQCAELQAAIAFBxc+AgABqLQAARw2jASABQQRGDQEgAUEBaiEBIARBAWoiBCACRw0ACyAAIBQ2AgBBqQEhEAy7AgsgAEEANgIAIBBBAWohAUErIRAMoAELAkAgBCACRw0AQaoBIRAMugILIAIgBGsgACgCACIBaiEUIAQgAWtBAmohEAJAA0AgBC0AACABQcrPgIAAai0AAEcNogEgAUECRg0BIAFBAWohASAEQQFqIgQgAkcNAAsgACAUNgIAQaoBIRAMugILIABBADYCACAQQQFqIQFBFCEQDJ8BCwJAIAQgAkcNAEGrASEQDLkCCwJAAkACQAJAIAQtAABBvn9qDg8AAQKkAaQBpAGkAaQBpAGkAaQBpAGkAaQBA6QBCyAEQQFqIQRBkwEhEAyiAgsgBEEBaiEEQZQBIRAMoQILIARBAWohBEGVASEQDKACCyAEQQFqIQRBlgEhEAyfAgsCQCAEIAJHDQBBrAEhEAy4AgsgBC0AAEHFAEcNnwEgBEEBaiEEDOABCwJAIAQgAkcNAEGtASEQDLcCCyACIARrIAAoAgAiAWohFCAEIAFrQQJqIRACQANAIAQtAAAgAUHNz4CAAGotAABHDZ8BIAFBAkYNASABQQFqIQEgBEEBaiIEIAJHDQALIAAgFDYCAEGtASEQDLcCCyAAQQA2AgAgEEEBaiEBQQ4hEAycAQsCQCAEIAJHDQBBrgEhEAy2AgsgBC0AAEHQAEcNnQEgBEEBaiEBQSUhEAybAQsCQCAEIAJHDQBBrwEhEAy1AgsgAiAEayAAKAIAIgFqIRQgBCABa0EIaiEQAkADQCAELQAAIAFB0M+AgABqLQAARw2dASABQQhGDQEgAUEBaiEBIARBAWoiBCACRw0ACyAAIBQ2AgBBrwEhEAy1AgsgAEEANgIAIBBBAWohAUEqIRAMmgELAkAgBCACRw0AQbABIRAMtAILAkACQCAELQAAQat/ag4LAJ0BnQGdAZ0BnQGdAZ0BnQGdAQGdAQsgBEEBaiEEQZoBIRAMmwILIARBAWohBEGbASEQDJoCCwJAIAQgAkcNAEGxASEQDLMCCwJAAkAgBC0AAEG/f2oOFACcAZwBnAGcAZwBnAGcAZwBnAGcAZwBnAGcAZwBnAGcAZwBnAEBnAELIARBAWohBEGZASEQDJoCCyAEQQFqIQRBnAEhEAyZAgsCQCAEIAJHDQBBsgEhEAyyAgsgAiAEayAAKAIAIgFqIRQgBCABa0EDaiEQAkADQCAELQAAIAFB2c+AgABqLQAARw2aASABQQNGDQEgAUEBaiEBIARBAWoiBCACRw0ACyAAIBQ2AgBBsgEhEAyyAgsgAEEANgIAIBBBAWohAUEhIRAMlwELAkAgBCACRw0AQbMBIRAMsQILIAIgBGsgACgCACIBaiEUIAQgAWtBBmohEAJAA0AgBC0AACABQd3PgIAAai0AAEcNmQEgAUEGRg0BIAFBAWohASAEQQFqIgQgAkcNAAsgACAUNgIAQbMBIRAMsQILIABBADYCACAQQQFqIQFBGiEQDJYBCwJAIAQgAkcNAEG0ASEQDLACCwJAAkACQCAELQAAQbt/ag4RAJoBmgGaAZoBmgGaAZoBmgGaAQGaAZoBmgGaAZoBApoBCyAEQQFqIQRBnQEhEAyYAgsgBEEBaiEEQZ4BIRAMlwILIARBAWohBEGfASEQDJYCCwJAIAQgAkcNAEG1ASEQDK8CCyACIARrIAAoAgAiAWohFCAEIAFrQQVqIRACQANAIAQtAAAgAUHkz4CAAGotAABHDZcBIAFBBUYNASABQQFqIQEgBEEBaiIEIAJHDQALIAAgFDYCAEG1ASEQDK8CCyAAQQA2AgAgEEEBaiEBQSghEAyUAQsCQCAEIAJHDQBBtgEhEAyuAgsgAiAEayAAKAIAIgFqIRQgBCABa0ECaiEQAkADQCAELQAAIAFB6s+AgABqLQAARw2WASABQQJGDQEgAUEBaiEBIARBAWoiBCACRw0ACyAAIBQ2AgBBtgEhEAyuAgsgAEEANgIAIBBBAWohAUEHIRAMkwELAkAgBCACRw0AQbcBIRAMrQILAkACQCAELQAAQbt/ag4OAJYBlgGWAZYBlgGWAZYBlgGWAZYBlgGWAQGWAQsgBEEBaiEEQaEBIRAMlAILIARBAWohBEGiASEQDJMCCwJAIAQgAkcNAEG4ASEQDKwCCyACIARrIAAoAgAiAWohFCAEIAFrQQJqIRACQANAIAQtAAAgAUHtz4CAAGotAABHDZQBIAFBAkYNASABQQFqIQEgBEEBaiIEIAJHDQALIAAgFDYCAEG4ASEQDKwCCyAAQQA2AgAgEEEBaiEBQRIhEAyRAQsCQCAEIAJHDQBBuQEhEAyrAgsgAiAEayAAKAIAIgFqIRQgBCABa0EBaiEQAkADQCAELQAAIAFB8M+AgABqLQAARw2TASABQQFGDQEgAUEBaiEBIARBAWoiBCACRw0ACyAAIBQ2AgBBuQEhEAyrAgsgAEEANgIAIBBBAWohAUEgIRAMkAELAkAgBCACRw0AQboBIRAMqgILIAIgBGsgACgCACIBaiEUIAQgAWtBAWohEAJAA0AgBC0AACABQfLPgIAAai0AAEcNkgEgAUEBRg0BIAFBAWohASAEQQFqIgQgAkcNAAsgACAUNgIAQboBIRAMqgILIABBADYCACAQQQFqIQFBDyEQDI8BCwJAIAQgAkcNAEG7ASEQDKkCCwJAAkAgBC0AAEG3f2oOBwCSAZIBkgGSAZIBAZIBCyAEQQFqIQRBpQEhEAyQAgsgBEEBaiEEQaYBIRAMjwILAkAgBCACRw0AQbwBIRAMqAILIAIgBGsgACgCACIBaiEUIAQgAWtBB2ohEAJAA0AgBC0AACABQfTPgIAAai0AAEcNkAEgAUEHRg0BIAFBAWohASAEQQFqIgQgAkcNAAsgACAUNgIAQbwBIRAMqAILIABBADYCACAQQQFqIQFBGyEQDI0BCwJAIAQgAkcNAEG9ASEQDKcCCwJAAkACQCAELQAAQb5/ag4SAJEBkQGRAZEBkQGRAZEBkQGRAQGRAZEBkQGRAZEBkQECkQELIARBAWohBEGkASEQDI8CCyAEQQFqIQRBpwEhEAyOAgsgBEEBaiEEQagBIRAMjQILAkAgBCACRw0AQb4BIRAMpgILIAQtAABBzgBHDY0BIARBAWohBAzPAQsCQCAEIAJHDQBBvwEhEAylAgsCQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQCAELQAAQb9/ag4VAAECA5wBBAUGnAGcAZwBBwgJCgucAQwNDg+cAQsgBEEBaiEBQegAIRAMmgILIARBAWohAUHpACEQDJkCCyAEQQFqIQFB7gAhEAyYAgsgBEEBaiEBQfIAIRAMlwILIARBAWohAUHzACEQDJYCCyAEQQFqIQFB9gAhEAyVAgsgBEEBaiEBQfcAIRAMlAILIARBAWohAUH6ACEQDJMCCyAEQQFqIQRBgwEhEAySAgsgBEEBaiEEQYQBIRAMkQILIARBAWohBEGFASEQDJACCyAEQQFqIQRBkgEhEAyPAgsgBEEBaiEEQZgBIRAMjgILIARBAWohBEGgASEQDI0CCyAEQQFqIQRBowEhEAyMAgsgBEEBaiEEQaoBIRAMiwILAkAgBCACRg0AIABBkICAgAA2AgggACAENgIEQasBIRAMiwILQcABIRAMowILIAAgBSACEKqAgIAAIgENiwEgBSEBDFwLAkAgBiACRg0AIAZBAWohBQyNAQtBwgEhEAyhAgsDQAJAIBAtAABBdmoOBIwBAACPAQALIBBBAWoiECACRw0AC0HDASEQDKACCwJAIAcgAkYNACAAQZGAgIAANgIIIAAgBzYCBCAHIQFBASEQDIcCC0HEASEQDJ8CCwJAIAcgAkcNAEHFASEQDJ8CCwJAAkAgBy0AAEF2ag4EAc4BzgEAzgELIAdBAWohBgyNAQsgB0EBaiEFDIkBCwJAIAcgAkcNAEHGASEQDJ4CCwJAAkAgBy0AAEF2ag4XAY8BjwEBjwGPAY8BjwGPAY8BjwGPAY8BjwGPAY8BjwGPAY8BjwGPAY8BAI8BCyAHQQFqIQcLQbABIRAMhAILAkAgCCACRw0AQcgBIRAMnQILIAgtAABBIEcNjQEgAEEAOwEyIAhBAWohAUGzASEQDIMCCyABIRcCQANAIBciByACRg0BIActAABBUGpB/wFxIhBBCk8NzAECQCAALwEyIhRBmTNLDQAgACAUQQpsIhQ7ATIgEEH//wNzIBRB/v8DcUkNACAHQQFqIRcgACAUIBBqIhA7ATIgEEH//wNxQegHSQ0BCwtBACEQIABBADYCHCAAQcGJgIAANgIQIABBDTYCDCAAIAdBAWo2AhQMnAILQccBIRAMmwILIAAgCCACEK6AgIAAIhBFDcoBIBBBFUcNjAEgAEHIATYCHCAAIAg2AhQgAEHJl4CAADYCECAAQRU2AgxBACEQDJoCCwJAIAkgAkcNAEHMASEQDJoCC0EAIRRBASEXQQEhFkEAIRACQAJAAkACQAJAAkACQAJAAkAgCS0AAEFQag4KlgGVAQABAgMEBQYIlwELQQIhEAwGC0EDIRAMBQtBBCEQDAQLQQUhEAwDC0EGIRAMAgtBByEQDAELQQghEAtBACEXQQAhFkEAIRQMjgELQQkhEEEBIRRBACEXQQAhFgyNAQsCQCAKIAJHDQBBzgEhEAyZAgsgCi0AAEEuRw2OASAKQQFqIQkMygELIAsgAkcNjgFB0AEhEAyXAgsCQCALIAJGDQAgAEGOgICAADYCCCAAIAs2AgRBtwEhEAz+AQtB0QEhEAyWAgsCQCAEIAJHDQBB0gEhEAyWAgsgAiAEayAAKAIAIhBqIRQgBCAQa0EEaiELA0AgBC0AACAQQfzPgIAAai0AAEcNjgEgEEEERg3pASAQQQFqIRAgBEEBaiIEIAJHDQALIAAgFDYCAEHSASEQDJUCCyAAIAwgAhCsgICAACIBDY0BIAwhAQy4AQsCQCAEIAJHDQBB1AEhEAyUAgsgAiAEayAAKAIAIhBqIRQgBCAQa0EBaiEMA0AgBC0AACAQQYHQgIAAai0AAEcNjwEgEEEBRg2OASAQQQFqIRAgBEEBaiIEIAJHDQALIAAgFDYCAEHUASEQDJMCCwJAIAQgAkcNAEHWASEQDJMCCyACIARrIAAoAgAiEGohFCAEIBBrQQJqIQsDQCAELQAAIBBBg9CAgABqLQAARw2OASAQQQJGDZABIBBBAWohECAEQQFqIgQgAkcNAAsgACAUNgIAQdYBIRAMkgILAkAgBCACRw0AQdcBIRAMkgILAkACQCAELQAAQbt/ag4QAI8BjwGPAY8BjwGPAY8BjwGPAY8BjwGPAY8BjwEBjwELIARBAWohBEG7ASEQDPkBCyAEQQFqIQRBvAEhEAz4AQsCQCAEIAJHDQBB2AEhEAyRAgsgBC0AAEHIAEcNjAEgBEEBaiEEDMQBCwJAIAQgAkYNACAAQZCAgIAANgIIIAAgBDYCBEG+ASEQDPcBC0HZASEQDI8CCwJAIAQgAkcNAEHaASEQDI8CCyAELQAAQcgARg3DASAAQQE6ACgMuQELIABBAjoALyAAIAQgAhCmgICAACIQDY0BQcIBIRAM9AELIAAtAChBf2oOArcBuQG4AQsDQAJAIAQtAABBdmoOBACOAY4BAI4BCyAEQQFqIgQgAkcNAAtB3QEhEAyLAgsgAEEAOgAvIAAtAC1BBHFFDYQCCyAAQQA6AC8gAEEBOgA0IAEhAQyMAQsgEEEVRg3aASAAQQA2AhwgACABNgIUIABBp46AgAA2AhAgAEESNgIMQQAhEAyIAgsCQCAAIBAgAhC0gICAACIEDQAgECEBDIECCwJAIARBFUcNACAAQQM2AhwgACAQNgIUIABBsJiAgAA2AhAgAEEVNgIMQQAhEAyIAgsgAEEANgIcIAAgEDYCFCAAQaeOgIAANgIQIABBEjYCDEEAIRAMhwILIBBBFUYN1gEgAEEANgIcIAAgATYCFCAAQdqNgIAANgIQIABBFDYCDEEAIRAMhgILIAAoAgQhFyAAQQA2AgQgECARp2oiFiEBIAAgFyAQIBYgFBsiEBC1gICAACIURQ2NASAAQQc2AhwgACAQNgIUIAAgFDYCDEEAIRAMhQILIAAgAC8BMEGAAXI7ATAgASEBC0EqIRAM6gELIBBBFUYN0QEgAEEANgIcIAAgATYCFCAAQYOMgIAANgIQIABBEzYCDEEAIRAMggILIBBBFUYNzwEgAEEANgIcIAAgATYCFCAAQZqPgIAANgIQIABBIjYCDEEAIRAMgQILIAAoAgQhECAAQQA2AgQCQCAAIBAgARC3gICAACIQDQAgAUEBaiEBDI0BCyAAQQw2AhwgACAQNgIMIAAgAUEBajYCFEEAIRAMgAILIBBBFUYNzAEgAEEANgIcIAAgATYCFCAAQZqPgIAANgIQIABBIjYCDEEAIRAM/wELIAAoAgQhECAAQQA2AgQCQCAAIBAgARC3gICAACIQDQAgAUEBaiEBDIwBCyAAQQ02AhwgACAQNgIMIAAgAUEBajYCFEEAIRAM/gELIBBBFUYNyQEgAEEANgIcIAAgATYCFCAAQcaMgIAANgIQIABBIzYCDEEAIRAM/QELIAAoAgQhECAAQQA2AgQCQCAAIBAgARC5gICAACIQDQAgAUEBaiEBDIsBCyAAQQ42AhwgACAQNgIMIAAgAUEBajYCFEEAIRAM/AELIABBADYCHCAAIAE2AhQgAEHAlYCAADYCECAAQQI2AgxBACEQDPsBCyAQQRVGDcUBIABBADYCHCAAIAE2AhQgAEHGjICAADYCECAAQSM2AgxBACEQDPoBCyAAQRA2AhwgACABNgIUIAAgEDYCDEEAIRAM+QELIAAoAgQhBCAAQQA2AgQCQCAAIAQgARC5gICAACIEDQAgAUEBaiEBDPEBCyAAQRE2AhwgACAENgIMIAAgAUEBajYCFEEAIRAM+AELIBBBFUYNwQEgAEEANgIcIAAgATYCFCAAQcaMgIAANgIQIABBIzYCDEEAIRAM9wELIAAoAgQhECAAQQA2AgQCQCAAIBAgARC5gICAACIQDQAgAUEBaiEBDIgBCyAAQRM2AhwgACAQNgIMIAAgAUEBajYCFEEAIRAM9gELIAAoAgQhBCAAQQA2AgQCQCAAIAQgARC5gICAACIEDQAgAUEBaiEBDO0BCyAAQRQ2AhwgACAENgIMIAAgAUEBajYCFEEAIRAM9QELIBBBFUYNvQEgAEEANgIcIAAgATYCFCAAQZqPgIAANgIQIABBIjYCDEEAIRAM9AELIAAoAgQhECAAQQA2AgQCQCAAIBAgARC3gICAACIQDQAgAUEBaiEBDIYBCyAAQRY2AhwgACAQNgIMIAAgAUEBajYCFEEAIRAM8wELIAAoAgQhBCAAQQA2AgQCQCAAIAQgARC3gICAACIEDQAgAUEBaiEBDOkBCyAAQRc2AhwgACAENgIMIAAgAUEBajYCFEEAIRAM8gELIABBADYCHCAAIAE2AhQgAEHNk4CAADYCECAAQQw2AgxBACEQDPEBC0IBIRELIBBBAWohAQJAIAApAyAiEkL//////////w9WDQAgACASQgSGIBGENwMgIAEhAQyEAQsgAEEANgIcIAAgATYCFCAAQa2JgIAANgIQIABBDDYCDEEAIRAM7wELIABBADYCHCAAIBA2AhQgAEHNk4CAADYCECAAQQw2AgxBACEQDO4BCyAAKAIEIRcgAEEANgIEIBAgEadqIhYhASAAIBcgECAWIBQbIhAQtYCAgAAiFEUNcyAAQQU2AhwgACAQNgIUIAAgFDYCDEEAIRAM7QELIABBADYCHCAAIBA2AhQgAEGqnICAADYCECAAQQ82AgxBACEQDOwBCyAAIBAgAhC0gICAACIBDQEgECEBC0EOIRAM0QELAkAgAUEVRw0AIABBAjYCHCAAIBA2AhQgAEGwmICAADYCECAAQRU2AgxBACEQDOoBCyAAQQA2AhwgACAQNgIUIABBp46AgAA2AhAgAEESNgIMQQAhEAzpAQsgAUEBaiEQAkAgAC8BMCIBQYABcUUNAAJAIAAgECACELuAgIAAIgENACAQIQEMcAsgAUEVRw26ASAAQQU2AhwgACAQNgIUIABB+ZeAgAA2AhAgAEEVNgIMQQAhEAzpAQsCQCABQaAEcUGgBEcNACAALQAtQQJxDQAgAEEANgIcIAAgEDYCFCAAQZaTgIAANgIQIABBBDYCDEEAIRAM6QELIAAgECACEL2AgIAAGiAQIQECQAJAAkACQAJAIAAgECACELOAgIAADhYCAQAEBAQEBAQEBAQEBAQEBAQEBAQDBAsgAEEBOgAuCyAAIAAvATBBwAByOwEwIBAhAQtBJiEQDNEBCyAAQSM2AhwgACAQNgIUIABBpZaAgAA2AhAgAEEVNgIMQQAhEAzpAQsgAEEANgIcIAAgEDYCFCAAQdWLgIAANgIQIABBETYCDEEAIRAM6AELIAAtAC1BAXFFDQFBwwEhEAzOAQsCQCANIAJGDQADQAJAIA0tAABBIEYNACANIQEMxAELIA1BAWoiDSACRw0AC0ElIRAM5wELQSUhEAzmAQsgACgCBCEEIABBADYCBCAAIAQgDRCvgICAACIERQ2tASAAQSY2AhwgACAENgIMIAAgDUEBajYCFEEAIRAM5QELIBBBFUYNqwEgAEEANgIcIAAgATYCFCAAQf2NgIAANgIQIABBHTYCDEEAIRAM5AELIABBJzYCHCAAIAE2AhQgACAQNgIMQQAhEAzjAQsgECEBQQEhFAJAAkACQAJAAkACQAJAIAAtACxBfmoOBwYFBQMBAgAFCyAAIAAvATBBCHI7ATAMAwtBAiEUDAELQQQhFAsgAEEBOgAsIAAgAC8BMCAUcjsBMAsgECEBC0ErIRAMygELIABBADYCHCAAIBA2AhQgAEGrkoCAADYCECAAQQs2AgxBACEQDOIBCyAAQQA2AhwgACABNgIUIABB4Y+AgAA2AhAgAEEKNgIMQQAhEAzhAQsgAEEAOgAsIBAhAQy9AQsgECEBQQEhFAJAAkACQAJAAkAgAC0ALEF7ag4EAwECAAULIAAgAC8BMEEIcjsBMAwDC0ECIRQMAQtBBCEUCyAAQQE6ACwgACAALwEwIBRyOwEwCyAQIQELQSkhEAzFAQsgAEEANgIcIAAgATYCFCAAQfCUgIAANgIQIABBAzYCDEEAIRAM3QELAkAgDi0AAEENRw0AIAAoAgQhASAAQQA2AgQCQCAAIAEgDhCxgICAACIBDQAgDkEBaiEBDHULIABBLDYCHCAAIAE2AgwgACAOQQFqNgIUQQAhEAzdAQsgAC0ALUEBcUUNAUHEASEQDMMBCwJAIA4gAkcNAEEtIRAM3AELAkACQANAAkAgDi0AAEF2ag4EAgAAAwALIA5BAWoiDiACRw0AC0EtIRAM3QELIAAoAgQhASAAQQA2AgQCQCAAIAEgDhCxgICAACIBDQAgDiEBDHQLIABBLDYCHCAAIA42AhQgACABNgIMQQAhEAzcAQsgACgCBCEBIABBADYCBAJAIAAgASAOELGAgIAAIgENACAOQQFqIQEMcwsgAEEsNgIcIAAgATYCDCAAIA5BAWo2AhRBACEQDNsBCyAAKAIEIQQgAEEANgIEIAAgBCAOELGAgIAAIgQNoAEgDiEBDM4BCyAQQSxHDQEgAUEBaiEQQQEhAQJAAkACQAJAAkAgAC0ALEF7ag4EAwECBAALIBAhAQwEC0ECIQEMAQtBBCEBCyAAQQE6ACwgACAALwEwIAFyOwEwIBAhAQwBCyAAIAAvATBBCHI7ATAgECEBC0E5IRAMvwELIABBADoALCABIQELQTQhEAy9AQsgACAALwEwQSByOwEwIAEhAQwCCyAAKAIEIQQgAEEANgIEAkAgACAEIAEQsYCAgAAiBA0AIAEhAQzHAQsgAEE3NgIcIAAgATYCFCAAIAQ2AgxBACEQDNQBCyAAQQg6ACwgASEBC0EwIRAMuQELAkAgAC0AKEEBRg0AIAEhAQwECyAALQAtQQhxRQ2TASABIQEMAwsgAC0AMEEgcQ2UAUHFASEQDLcBCwJAIA8gAkYNAAJAA0ACQCAPLQAAQVBqIgFB/wFxQQpJDQAgDyEBQTUhEAy6AQsgACkDICIRQpmz5syZs+bMGVYNASAAIBFCCn4iETcDICARIAGtQv8BgyISQn+FVg0BIAAgESASfDcDICAPQQFqIg8gAkcNAAtBOSEQDNEBCyAAKAIEIQIgAEEANgIEIAAgAiAPQQFqIgQQsYCAgAAiAg2VASAEIQEMwwELQTkhEAzPAQsCQCAALwEwIgFBCHFFDQAgAC0AKEEBRw0AIAAtAC1BCHFFDZABCyAAIAFB9/sDcUGABHI7ATAgDyEBC0E3IRAMtAELIAAgAC8BMEEQcjsBMAyrAQsgEEEVRg2LASAAQQA2AhwgACABNgIUIABB8I6AgAA2AhAgAEEcNgIMQQAhEAzLAQsgAEHDADYCHCAAIAE2AgwgACANQQFqNgIUQQAhEAzKAQsCQCABLQAAQTpHDQAgACgCBCEQIABBADYCBAJAIAAgECABEK+AgIAAIhANACABQQFqIQEMYwsgAEHDADYCHCAAIBA2AgwgACABQQFqNgIUQQAhEAzKAQsgAEEANgIcIAAgATYCFCAAQbGRgIAANgIQIABBCjYCDEEAIRAMyQELIABBADYCHCAAIAE2AhQgAEGgmYCAADYCECAAQR42AgxBACEQDMgBCyAAQQA2AgALIABBgBI7ASogACAXQQFqIgEgAhCogICAACIQDQEgASEBC0HHACEQDKwBCyAQQRVHDYMBIABB0QA2AhwgACABNgIUIABB45eAgAA2AhAgAEEVNgIMQQAhEAzEAQsgACgCBCEQIABBADYCBAJAIAAgECABEKeAgIAAIhANACABIQEMXgsgAEHSADYCHCAAIAE2AhQgACAQNgIMQQAhEAzDAQsgAEEANgIcIAAgFDYCFCAAQcGogIAANgIQIABBBzYCDCAAQQA2AgBBACEQDMIBCyAAKAIEIRAgAEEANgIEAkAgACAQIAEQp4CAgAAiEA0AIAEhAQxdCyAAQdMANgIcIAAgATYCFCAAIBA2AgxBACEQDMEBC0EAIRAgAEEANgIcIAAgATYCFCAAQYCRgIAANgIQIABBCTYCDAzAAQsgEEEVRg19IABBADYCHCAAIAE2AhQgAEGUjYCAADYCECAAQSE2AgxBACEQDL8BC0EBIRZBACEXQQAhFEEBIRALIAAgEDoAKyABQQFqIQECQAJAIAAtAC1BEHENAAJAAkACQCAALQAqDgMBAAIECyAWRQ0DDAILIBQNAQwCCyAXRQ0BCyAAKAIEIRAgAEEANgIEAkAgACAQIAEQrYCAgAAiEA0AIAEhAQxcCyAAQdgANgIcIAAgATYCFCAAIBA2AgxBACEQDL4BCyAAKAIEIQQgAEEANgIEAkAgACAEIAEQrYCAgAAiBA0AIAEhAQytAQsgAEHZADYCHCAAIAE2AhQgACAENgIMQQAhEAy9AQsgACgCBCEEIABBADYCBAJAIAAgBCABEK2AgIAAIgQNACABIQEMqwELIABB2gA2AhwgACABNgIUIAAgBDYCDEEAIRAMvAELIAAoAgQhBCAAQQA2AgQCQCAAIAQgARCtgICAACIEDQAgASEBDKkBCyAAQdwANgIcIAAgATYCFCAAIAQ2AgxBACEQDLsBCwJAIAEtAABBUGoiEEH/AXFBCk8NACAAIBA6ACogAUEBaiEBQc8AIRAMogELIAAoAgQhBCAAQQA2AgQCQCAAIAQgARCtgICAACIEDQAgASEBDKcBCyAAQd4ANgIcIAAgATYCFCAAIAQ2AgxBACEQDLoBCyAAQQA2AgAgF0EBaiEBAkAgAC0AKUEjTw0AIAEhAQxZCyAAQQA2AhwgACABNgIUIABB04mAgAA2AhAgAEEINgIMQQAhEAy5AQsgAEEANgIAC0EAIRAgAEEANgIcIAAgATYCFCAAQZCzgIAANgIQIABBCDYCDAy3AQsgAEEANgIAIBdBAWohAQJAIAAtAClBIUcNACABIQEMVgsgAEEANgIcIAAgATYCFCAAQZuKgIAANgIQIABBCDYCDEEAIRAMtgELIABBADYCACAXQQFqIQECQCAALQApIhBBXWpBC08NACABIQEMVQsCQCAQQQZLDQBBASAQdEHKAHFFDQAgASEBDFULQQAhECAAQQA2AhwgACABNgIUIABB94mAgAA2AhAgAEEINgIMDLUBCyAQQRVGDXEgAEEANgIcIAAgATYCFCAAQbmNgIAANgIQIABBGjYCDEEAIRAMtAELIAAoAgQhECAAQQA2AgQCQCAAIBAgARCngICAACIQDQAgASEBDFQLIABB5QA2AhwgACABNgIUIAAgEDYCDEEAIRAMswELIAAoAgQhECAAQQA2AgQCQCAAIBAgARCngICAACIQDQAgASEBDE0LIABB0gA2AhwgACABNgIUIAAgEDYCDEEAIRAMsgELIAAoAgQhECAAQQA2AgQCQCAAIBAgARCngICAACIQDQAgASEBDE0LIABB0wA2AhwgACABNgIUIAAgEDYCDEEAIRAMsQELIAAoAgQhECAAQQA2AgQCQCAAIBAgARCngICAACIQDQAgASEBDFELIABB5QA2AhwgACABNgIUIAAgEDYCDEEAIRAMsAELIABBADYCHCAAIAE2AhQgAEHGioCAADYCECAAQQc2AgxBACEQDK8BCyAAKAIEIRAgAEEANgIEAkAgACAQIAEQp4CAgAAiEA0AIAEhAQxJCyAAQdIANgIcIAAgATYCFCAAIBA2AgxBACEQDK4BCyAAKAIEIRAgAEEANgIEAkAgACAQIAEQp4CAgAAiEA0AIAEhAQxJCyAAQdMANgIcIAAgATYCFCAAIBA2AgxBACEQDK0BCyAAKAIEIRAgAEEANgIEAkAgACAQIAEQp4CAgAAiEA0AIAEhAQxNCyAAQeUANgIcIAAgATYCFCAAIBA2AgxBACEQDKwBCyAAQQA2AhwgACABNgIUIABB3IiAgAA2AhAgAEEHNgIMQQAhEAyrAQsgEEE/Rw0BIAFBAWohAQtBBSEQDJABC0EAIRAgAEEANgIcIAAgATYCFCAAQf2SgIAANgIQIABBBzYCDAyoAQsgACgCBCEQIABBADYCBAJAIAAgECABEKeAgIAAIhANACABIQEMQgsgAEHSADYCHCAAIAE2AhQgACAQNgIMQQAhEAynAQsgACgCBCEQIABBADYCBAJAIAAgECABEKeAgIAAIhANACABIQEMQgsgAEHTADYCHCAAIAE2AhQgACAQNgIMQQAhEAymAQsgACgCBCEQIABBADYCBAJAIAAgECABEKeAgIAAIhANACABIQEMRgsgAEHlADYCHCAAIAE2AhQgACAQNgIMQQAhEAylAQsgACgCBCEBIABBADYCBAJAIAAgASAUEKeAgIAAIgENACAUIQEMPwsgAEHSADYCHCAAIBQ2AhQgACABNgIMQQAhEAykAQsgACgCBCEBIABBADYCBAJAIAAgASAUEKeAgIAAIgENACAUIQEMPwsgAEHTADYCHCAAIBQ2AhQgACABNgIMQQAhEAyjAQsgACgCBCEBIABBADYCBAJAIAAgASAUEKeAgIAAIgENACAUIQEMQwsgAEHlADYCHCAAIBQ2AhQgACABNgIMQQAhEAyiAQsgAEEANgIcIAAgFDYCFCAAQcOPgIAANgIQIABBBzYCDEEAIRAMoQELIABBADYCHCAAIAE2AhQgAEHDj4CAADYCECAAQQc2AgxBACEQDKABC0EAIRAgAEEANgIcIAAgFDYCFCAAQYycgIAANgIQIABBBzYCDAyfAQsgAEEANgIcIAAgFDYCFCAAQYycgIAANgIQIABBBzYCDEEAIRAMngELIABBADYCHCAAIBQ2AhQgAEH+kYCAADYCECAAQQc2AgxBACEQDJ0BCyAAQQA2AhwgACABNgIUIABBjpuAgAA2AhAgAEEGNgIMQQAhEAycAQsgEEEVRg1XIABBADYCHCAAIAE2AhQgAEHMjoCAADYCECAAQSA2AgxBACEQDJsBCyAAQQA2AgAgEEEBaiEBQSQhEAsgACAQOgApIAAoAgQhECAAQQA2AgQgACAQIAEQq4CAgAAiEA1UIAEhAQw+CyAAQQA2AgALQQAhECAAQQA2AhwgACAENgIUIABB8ZuAgAA2AhAgAEEGNgIMDJcBCyABQRVGDVAgAEEANgIcIAAgBTYCFCAAQfCMgIAANgIQIABBGzYCDEEAIRAMlgELIAAoAgQhBSAAQQA2AgQgACAFIBAQqYCAgAAiBQ0BIBBBAWohBQtBrQEhEAx7CyAAQcEBNgIcIAAgBTYCDCAAIBBBAWo2AhRBACEQDJMBCyAAKAIEIQYgAEEANgIEIAAgBiAQEKmAgIAAIgYNASAQQQFqIQYLQa4BIRAMeAsgAEHCATYCHCAAIAY2AgwgACAQQQFqNgIUQQAhEAyQAQsgAEEANgIcIAAgBzYCFCAAQZeLgIAANgIQIABBDTYCDEEAIRAMjwELIABBADYCHCAAIAg2AhQgAEHjkICAADYCECAAQQk2AgxBACEQDI4BCyAAQQA2AhwgACAINgIUIABBlI2AgAA2AhAgAEEhNgIMQQAhEAyNAQtBASEWQQAhF0EAIRRBASEQCyAAIBA6ACsgCUEBaiEIAkACQCAALQAtQRBxDQACQAJAAkAgAC0AKg4DAQACBAsgFkUNAwwCCyAUDQEMAgsgF0UNAQsgACgCBCEQIABBADYCBCAAIBAgCBCtgICAACIQRQ09IABByQE2AhwgACAINgIUIAAgEDYCDEEAIRAMjAELIAAoAgQhBCAAQQA2AgQgACAEIAgQrYCAgAAiBEUNdiAAQcoBNgIcIAAgCDYCFCAAIAQ2AgxBACEQDIsBCyAAKAIEIQQgAEEANgIEIAAgBCAJEK2AgIAAIgRFDXQgAEHLATYCHCAAIAk2AhQgACAENgIMQQAhEAyKAQsgACgCBCEEIABBADYCBCAAIAQgChCtgICAACIERQ1yIABBzQE2AhwgACAKNgIUIAAgBDYCDEEAIRAMiQELAkAgCy0AAEFQaiIQQf8BcUEKTw0AIAAgEDoAKiALQQFqIQpBtgEhEAxwCyAAKAIEIQQgAEEANgIEIAAgBCALEK2AgIAAIgRFDXAgAEHPATYCHCAAIAs2AhQgACAENgIMQQAhEAyIAQsgAEEANgIcIAAgBDYCFCAAQZCzgIAANgIQIABBCDYCDCAAQQA2AgBBACEQDIcBCyABQRVGDT8gAEEANgIcIAAgDDYCFCAAQcyOgIAANgIQIABBIDYCDEEAIRAMhgELIABBgQQ7ASggACgCBCEQIABCADcDACAAIBAgDEEBaiIMEKuAgIAAIhBFDTggAEHTATYCHCAAIAw2AhQgACAQNgIMQQAhEAyFAQsgAEEANgIAC0EAIRAgAEEANgIcIAAgBDYCFCAAQdibgIAANgIQIABBCDYCDAyDAQsgACgCBCEQIABCADcDACAAIBAgC0EBaiILEKuAgIAAIhANAUHGASEQDGkLIABBAjoAKAxVCyAAQdUBNgIcIAAgCzYCFCAAIBA2AgxBACEQDIABCyAQQRVGDTcgAEEANgIcIAAgBDYCFCAAQaSMgIAANgIQIABBEDYCDEEAIRAMfwsgAC0ANEEBRw00IAAgBCACELyAgIAAIhBFDTQgEEEVRw01IABB3AE2AhwgACAENgIUIABB1ZaAgAA2AhAgAEEVNgIMQQAhEAx+C0EAIRAgAEEANgIcIABBr4uAgAA2AhAgAEECNgIMIAAgFEEBajYCFAx9C0EAIRAMYwtBAiEQDGILQQ0hEAxhC0EPIRAMYAtBJSEQDF8LQRMhEAxeC0EVIRAMXQtBFiEQDFwLQRchEAxbC0EYIRAMWgtBGSEQDFkLQRohEAxYC0EbIRAMVwtBHCEQDFYLQR0hEAxVC0EfIRAMVAtBISEQDFMLQSMhEAxSC0HGACEQDFELQS4hEAxQC0EvIRAMTwtBOyEQDE4LQT0hEAxNC0HIACEQDEwLQckAIRAMSwtBywAhEAxKC0HMACEQDEkLQc4AIRAMSAtB0QAhEAxHC0HVACEQDEYLQdgAIRAMRQtB2QAhEAxEC0HbACEQDEMLQeQAIRAMQgtB5QAhEAxBC0HxACEQDEALQfQAIRAMPwtBjQEhEAw+C0GXASEQDD0LQakBIRAMPAtBrAEhEAw7C0HAASEQDDoLQbkBIRAMOQtBrwEhEAw4C0GxASEQDDcLQbIBIRAMNgtBtAEhEAw1C0G1ASEQDDQLQboBIRAMMwtBvQEhEAwyC0G/ASEQDDELQcEBIRAMMAsgAEEANgIcIAAgBDYCFCAAQemLgIAANgIQIABBHzYCDEEAIRAMSAsgAEHbATYCHCAAIAQ2AhQgAEH6loCAADYCECAAQRU2AgxBACEQDEcLIABB+AA2AhwgACAMNgIUIABBypiAgAA2AhAgAEEVNgIMQQAhEAxGCyAAQdEANgIcIAAgBTYCFCAAQbCXgIAANgIQIABBFTYCDEEAIRAMRQsgAEH5ADYCHCAAIAE2AhQgACAQNgIMQQAhEAxECyAAQfgANgIcIAAgATYCFCAAQcqYgIAANgIQIABBFTYCDEEAIRAMQwsgAEHkADYCHCAAIAE2AhQgAEHjl4CAADYCECAAQRU2AgxBACEQDEILIABB1wA2AhwgACABNgIUIABByZeAgAA2AhAgAEEVNgIMQQAhEAxBCyAAQQA2AhwgACABNgIUIABBuY2AgAA2AhAgAEEaNgIMQQAhEAxACyAAQcIANgIcIAAgATYCFCAAQeOYgIAANgIQIABBFTYCDEEAIRAMPwsgAEEANgIEIAAgDyAPELGAgIAAIgRFDQEgAEE6NgIcIAAgBDYCDCAAIA9BAWo2AhRBACEQDD4LIAAoAgQhBCAAQQA2AgQCQCAAIAQgARCxgICAACIERQ0AIABBOzYCHCAAIAQ2AgwgACABQQFqNgIUQQAhEAw+CyABQQFqIQEMLQsgD0EBaiEBDC0LIABBADYCHCAAIA82AhQgAEHkkoCAADYCECAAQQQ2AgxBACEQDDsLIABBNjYCHCAAIAQ2AhQgACACNgIMQQAhEAw6CyAAQS42AhwgACAONgIUIAAgBDYCDEEAIRAMOQsgAEHQADYCHCAAIAE2AhQgAEGRmICAADYCECAAQRU2AgxBACEQDDgLIA1BAWohAQwsCyAAQRU2AhwgACABNgIUIABBgpmAgAA2AhAgAEEVNgIMQQAhEAw2CyAAQRs2AhwgACABNgIUIABBkZeAgAA2AhAgAEEVNgIMQQAhEAw1CyAAQQ82AhwgACABNgIUIABBkZeAgAA2AhAgAEEVNgIMQQAhEAw0CyAAQQs2AhwgACABNgIUIABBkZeAgAA2AhAgAEEVNgIMQQAhEAwzCyAAQRo2AhwgACABNgIUIABBgpmAgAA2AhAgAEEVNgIMQQAhEAwyCyAAQQs2AhwgACABNgIUIABBgpmAgAA2AhAgAEEVNgIMQQAhEAwxCyAAQQo2AhwgACABNgIUIABB5JaAgAA2AhAgAEEVNgIMQQAhEAwwCyAAQR42AhwgACABNgIUIABB+ZeAgAA2AhAgAEEVNgIMQQAhEAwvCyAAQQA2AhwgACAQNgIUIABB2o2AgAA2AhAgAEEUNgIMQQAhEAwuCyAAQQQ2AhwgACABNgIUIABBsJiAgAA2AhAgAEEVNgIMQQAhEAwtCyAAQQA2AgAgC0EBaiELC0G4ASEQDBILIABBADYCACAQQQFqIQFB9QAhEAwRCyABIQECQCAALQApQQVHDQBB4wAhEAwRC0HiACEQDBALQQAhECAAQQA2AhwgAEHkkYCAADYCECAAQQc2AgwgACAUQQFqNgIUDCgLIABBADYCACAXQQFqIQFBwAAhEAwOC0EBIQELIAAgAToALCAAQQA2AgAgF0EBaiEBC0EoIRAMCwsgASEBC0E4IRAMCQsCQCABIg8gAkYNAANAAkAgDy0AAEGAvoCAAGotAAAiAUEBRg0AIAFBAkcNAyAPQQFqIQEMBAsgD0EBaiIPIAJHDQALQT4hEAwiC0E+IRAMIQsgAEEAOgAsIA8hAQwBC0ELIRAMBgtBOiEQDAULIAFBAWohAUEtIRAMBAsgACABOgAsIABBADYCACAWQQFqIQFBDCEQDAMLIABBADYCACAXQQFqIQFBCiEQDAILIABBADYCAAsgAEEAOgAsIA0hAUEJIRAMAAsLQQAhECAAQQA2AhwgACALNgIUIABBzZCAgAA2AhAgAEEJNgIMDBcLQQAhECAAQQA2AhwgACAKNgIUIABB6YqAgAA2AhAgAEEJNgIMDBYLQQAhECAAQQA2AhwgACAJNgIUIABBt5CAgAA2AhAgAEEJNgIMDBULQQAhECAAQQA2AhwgACAINgIUIABBnJGAgAA2AhAgAEEJNgIMDBQLQQAhECAAQQA2AhwgACABNgIUIABBzZCAgAA2AhAgAEEJNgIMDBMLQQAhECAAQQA2AhwgACABNgIUIABB6YqAgAA2AhAgAEEJNgIMDBILQQAhECAAQQA2AhwgACABNgIUIABBt5CAgAA2AhAgAEEJNgIMDBELQQAhECAAQQA2AhwgACABNgIUIABBnJGAgAA2AhAgAEEJNgIMDBALQQAhECAAQQA2AhwgACABNgIUIABBl5WAgAA2AhAgAEEPNgIMDA8LQQAhECAAQQA2AhwgACABNgIUIABBl5WAgAA2AhAgAEEPNgIMDA4LQQAhECAAQQA2AhwgACABNgIUIABBwJKAgAA2AhAgAEELNgIMDA0LQQAhECAAQQA2AhwgACABNgIUIABBlYmAgAA2AhAgAEELNgIMDAwLQQAhECAAQQA2AhwgACABNgIUIABB4Y+AgAA2AhAgAEEKNgIMDAsLQQAhECAAQQA2AhwgACABNgIUIABB+4+AgAA2AhAgAEEKNgIMDAoLQQAhECAAQQA2AhwgACABNgIUIABB8ZmAgAA2AhAgAEECNgIMDAkLQQAhECAAQQA2AhwgACABNgIUIABBxJSAgAA2AhAgAEECNgIMDAgLQQAhECAAQQA2AhwgACABNgIUIABB8pWAgAA2AhAgAEECNgIMDAcLIABBAjYCHCAAIAE2AhQgAEGcmoCAADYCECAAQRY2AgxBACEQDAYLQQEhEAwFC0HUACEQIAEiBCACRg0EIANBCGogACAEIAJB2MKAgABBChDFgICAACADKAIMIQQgAygCCA4DAQQCAAsQyoCAgAAACyAAQQA2AhwgAEG1moCAADYCECAAQRc2AgwgACAEQQFqNgIUQQAhEAwCCyAAQQA2AhwgACAENgIUIABBypqAgAA2AhAgAEEJNgIMQQAhEAwBCwJAIAEiBCACRw0AQSIhEAwBCyAAQYmAgIAANgIIIAAgBDYCBEEhIRALIANBEGokgICAgAAgEAuvAQECfyABKAIAIQYCQAJAIAIgA0YNACAEIAZqIQQgBiADaiACayEHIAIgBkF/cyAFaiIGaiEFA0ACQCACLQAAIAQtAABGDQBBAiEEDAMLAkAgBg0AQQAhBCAFIQIMAwsgBkF/aiEGIARBAWohBCACQQFqIgIgA0cNAAsgByEGIAMhAgsgAEEBNgIAIAEgBjYCACAAIAI2AgQPCyABQQA2AgAgACAENgIAIAAgAjYCBAsKACAAEMeAgIAAC/I2AQt/I4CAgIAAQRBrIgEkgICAgAACQEEAKAKg0ICAAA0AQQAQy4CAgABBgNSEgABrIgJB2QBJDQBBACEDAkBBACgC4NOAgAAiBA0AQQBCfzcC7NOAgABBAEKAgISAgIDAADcC5NOAgABBACABQQhqQXBxQdiq1aoFcyIENgLg04CAAEEAQQA2AvTTgIAAQQBBADYCxNOAgAALQQAgAjYCzNOAgABBAEGA1ISAADYCyNOAgABBAEGA1ISAADYCmNCAgABBACAENgKs0ICAAEEAQX82AqjQgIAAA0AgA0HE0ICAAGogA0G40ICAAGoiBDYCACAEIANBsNCAgABqIgU2AgAgA0G80ICAAGogBTYCACADQczQgIAAaiADQcDQgIAAaiIFNgIAIAUgBDYCACADQdTQgIAAaiADQcjQgIAAaiIENgIAIAQgBTYCACADQdDQgIAAaiAENgIAIANBIGoiA0GAAkcNAAtBgNSEgABBeEGA1ISAAGtBD3FBAEGA1ISAAEEIakEPcRsiA2oiBEEEaiACQUhqIgUgA2siA0EBcjYCAEEAQQAoAvDTgIAANgKk0ICAAEEAIAM2ApTQgIAAQQAgBDYCoNCAgABBgNSEgAAgBWpBODYCBAsCQAJAAkACQAJAAkACQAJAAkACQAJAAkAgAEHsAUsNAAJAQQAoAojQgIAAIgZBECAAQRNqQXBxIABBC0kbIgJBA3YiBHYiA0EDcUUNAAJAAkAgA0EBcSAEckEBcyIFQQN0IgRBsNCAgABqIgMgBEG40ICAAGooAgAiBCgCCCICRw0AQQAgBkF+IAV3cTYCiNCAgAAMAQsgAyACNgIIIAIgAzYCDAsgBEEIaiEDIAQgBUEDdCIFQQNyNgIEIAQgBWoiBCAEKAIEQQFyNgIEDAwLIAJBACgCkNCAgAAiB00NAQJAIANFDQACQAJAIAMgBHRBAiAEdCIDQQAgA2tycSIDQQAgA2txQX9qIgMgA0EMdkEQcSIDdiIEQQV2QQhxIgUgA3IgBCAFdiIDQQJ2QQRxIgRyIAMgBHYiA0EBdkECcSIEciADIAR2IgNBAXZBAXEiBHIgAyAEdmoiBEEDdCIDQbDQgIAAaiIFIANBuNCAgABqKAIAIgMoAggiAEcNAEEAIAZBfiAEd3EiBjYCiNCAgAAMAQsgBSAANgIIIAAgBTYCDAsgAyACQQNyNgIEIAMgBEEDdCIEaiAEIAJrIgU2AgAgAyACaiIAIAVBAXI2AgQCQCAHRQ0AIAdBeHFBsNCAgABqIQJBACgCnNCAgAAhBAJAAkAgBkEBIAdBA3Z0IghxDQBBACAGIAhyNgKI0ICAACACIQgMAQsgAigCCCEICyAIIAQ2AgwgAiAENgIIIAQgAjYCDCAEIAg2AggLIANBCGohA0EAIAA2ApzQgIAAQQAgBTYCkNCAgAAMDAtBACgCjNCAgAAiCUUNASAJQQAgCWtxQX9qIgMgA0EMdkEQcSIDdiIEQQV2QQhxIgUgA3IgBCAFdiIDQQJ2QQRxIgRyIAMgBHYiA0EBdkECcSIEciADIAR2IgNBAXZBAXEiBHIgAyAEdmpBAnRBuNKAgABqKAIAIgAoAgRBeHEgAmshBCAAIQUCQANAAkAgBSgCECIDDQAgBUEUaigCACIDRQ0CCyADKAIEQXhxIAJrIgUgBCAFIARJIgUbIQQgAyAAIAUbIQAgAyEFDAALCyAAKAIYIQoCQCAAKAIMIgggAEYNACAAKAIIIgNBACgCmNCAgABJGiAIIAM2AgggAyAINgIMDAsLAkAgAEEUaiIFKAIAIgMNACAAKAIQIgNFDQMgAEEQaiEFCwNAIAUhCyADIghBFGoiBSgCACIDDQAgCEEQaiEFIAgoAhAiAw0ACyALQQA2AgAMCgtBfyECIABBv39LDQAgAEETaiIDQXBxIQJBACgCjNCAgAAiB0UNAEEAIQsCQCACQYACSQ0AQR8hCyACQf///wdLDQAgA0EIdiIDIANBgP4/akEQdkEIcSIDdCIEIARBgOAfakEQdkEEcSIEdCIFIAVBgIAPakEQdkECcSIFdEEPdiADIARyIAVyayIDQQF0IAIgA0EVanZBAXFyQRxqIQsLQQAgAmshBAJAAkACQAJAIAtBAnRBuNKAgABqKAIAIgUNAEEAIQNBACEIDAELQQAhAyACQQBBGSALQQF2ayALQR9GG3QhAEEAIQgDQAJAIAUoAgRBeHEgAmsiBiAETw0AIAYhBCAFIQggBg0AQQAhBCAFIQggBSEDDAMLIAMgBUEUaigCACIGIAYgBSAAQR12QQRxakEQaigCACIFRhsgAyAGGyEDIABBAXQhACAFDQALCwJAIAMgCHINAEEAIQhBAiALdCIDQQAgA2tyIAdxIgNFDQMgA0EAIANrcUF/aiIDIANBDHZBEHEiA3YiBUEFdkEIcSIAIANyIAUgAHYiA0ECdkEEcSIFciADIAV2IgNBAXZBAnEiBXIgAyAFdiIDQQF2QQFxIgVyIAMgBXZqQQJ0QbjSgIAAaigCACEDCyADRQ0BCwNAIAMoAgRBeHEgAmsiBiAESSEAAkAgAygCECIFDQAgA0EUaigCACEFCyAGIAQgABshBCADIAggABshCCAFIQMgBQ0ACwsgCEUNACAEQQAoApDQgIAAIAJrTw0AIAgoAhghCwJAIAgoAgwiACAIRg0AIAgoAggiA0EAKAKY0ICAAEkaIAAgAzYCCCADIAA2AgwMCQsCQCAIQRRqIgUoAgAiAw0AIAgoAhAiA0UNAyAIQRBqIQULA0AgBSEGIAMiAEEUaiIFKAIAIgMNACAAQRBqIQUgACgCECIDDQALIAZBADYCAAwICwJAQQAoApDQgIAAIgMgAkkNAEEAKAKc0ICAACEEAkACQCADIAJrIgVBEEkNACAEIAJqIgAgBUEBcjYCBEEAIAU2ApDQgIAAQQAgADYCnNCAgAAgBCADaiAFNgIAIAQgAkEDcjYCBAwBCyAEIANBA3I2AgQgBCADaiIDIAMoAgRBAXI2AgRBAEEANgKc0ICAAEEAQQA2ApDQgIAACyAEQQhqIQMMCgsCQEEAKAKU0ICAACIAIAJNDQBBACgCoNCAgAAiAyACaiIEIAAgAmsiBUEBcjYCBEEAIAU2ApTQgIAAQQAgBDYCoNCAgAAgAyACQQNyNgIEIANBCGohAwwKCwJAAkBBACgC4NOAgABFDQBBACgC6NOAgAAhBAwBC0EAQn83AuzTgIAAQQBCgICEgICAwAA3AuTTgIAAQQAgAUEMakFwcUHYqtWqBXM2AuDTgIAAQQBBADYC9NOAgABBAEEANgLE04CAAEGAgAQhBAtBACEDAkAgBCACQccAaiIHaiIGQQAgBGsiC3EiCCACSw0AQQBBMDYC+NOAgAAMCgsCQEEAKALA04CAACIDRQ0AAkBBACgCuNOAgAAiBCAIaiIFIARNDQAgBSADTQ0BC0EAIQNBAEEwNgL404CAAAwKC0EALQDE04CAAEEEcQ0EAkACQAJAQQAoAqDQgIAAIgRFDQBByNOAgAAhAwNAAkAgAygCACIFIARLDQAgBSADKAIEaiAESw0DCyADKAIIIgMNAAsLQQAQy4CAgAAiAEF/Rg0FIAghBgJAQQAoAuTTgIAAIgNBf2oiBCAAcUUNACAIIABrIAQgAGpBACADa3FqIQYLIAYgAk0NBSAGQf7///8HSw0FAkBBACgCwNOAgAAiA0UNAEEAKAK404CAACIEIAZqIgUgBE0NBiAFIANLDQYLIAYQy4CAgAAiAyAARw0BDAcLIAYgAGsgC3EiBkH+////B0sNBCAGEMuAgIAAIgAgAygCACADKAIEakYNAyAAIQMLAkAgA0F/Rg0AIAJByABqIAZNDQACQCAHIAZrQQAoAujTgIAAIgRqQQAgBGtxIgRB/v///wdNDQAgAyEADAcLAkAgBBDLgICAAEF/Rg0AIAQgBmohBiADIQAMBwtBACAGaxDLgICAABoMBAsgAyEAIANBf0cNBQwDC0EAIQgMBwtBACEADAULIABBf0cNAgtBAEEAKALE04CAAEEEcjYCxNOAgAALIAhB/v///wdLDQEgCBDLgICAACEAQQAQy4CAgAAhAyAAQX9GDQEgA0F/Rg0BIAAgA08NASADIABrIgYgAkE4ak0NAQtBAEEAKAK404CAACAGaiIDNgK404CAAAJAIANBACgCvNOAgABNDQBBACADNgK804CAAAsCQAJAAkACQEEAKAKg0ICAACIERQ0AQcjTgIAAIQMDQCAAIAMoAgAiBSADKAIEIghqRg0CIAMoAggiAw0ADAMLCwJAAkBBACgCmNCAgAAiA0UNACAAIANPDQELQQAgADYCmNCAgAALQQAhA0EAIAY2AszTgIAAQQAgADYCyNOAgABBAEF/NgKo0ICAAEEAQQAoAuDTgIAANgKs0ICAAEEAQQA2AtTTgIAAA0AgA0HE0ICAAGogA0G40ICAAGoiBDYCACAEIANBsNCAgABqIgU2AgAgA0G80ICAAGogBTYCACADQczQgIAAaiADQcDQgIAAaiIFNgIAIAUgBDYCACADQdTQgIAAaiADQcjQgIAAaiIENgIAIAQgBTYCACADQdDQgIAAaiAENgIAIANBIGoiA0GAAkcNAAsgAEF4IABrQQ9xQQAgAEEIakEPcRsiA2oiBCAGQUhqIgUgA2siA0EBcjYCBEEAQQAoAvDTgIAANgKk0ICAAEEAIAM2ApTQgIAAQQAgBDYCoNCAgAAgACAFakE4NgIEDAILIAMtAAxBCHENACAEIAVJDQAgBCAATw0AIARBeCAEa0EPcUEAIARBCGpBD3EbIgVqIgBBACgClNCAgAAgBmoiCyAFayIFQQFyNgIEIAMgCCAGajYCBEEAQQAoAvDTgIAANgKk0ICAAEEAIAU2ApTQgIAAQQAgADYCoNCAgAAgBCALakE4NgIEDAELAkAgAEEAKAKY0ICAACIITw0AQQAgADYCmNCAgAAgACEICyAAIAZqIQVByNOAgAAhAwJAAkACQAJAAkACQAJAA0AgAygCACAFRg0BIAMoAggiAw0ADAILCyADLQAMQQhxRQ0BC0HI04CAACEDA0ACQCADKAIAIgUgBEsNACAFIAMoAgRqIgUgBEsNAwsgAygCCCEDDAALCyADIAA2AgAgAyADKAIEIAZqNgIEIABBeCAAa0EPcUEAIABBCGpBD3EbaiILIAJBA3I2AgQgBUF4IAVrQQ9xQQAgBUEIakEPcRtqIgYgCyACaiICayEDAkAgBiAERw0AQQAgAjYCoNCAgABBAEEAKAKU0ICAACADaiIDNgKU0ICAACACIANBAXI2AgQMAwsCQCAGQQAoApzQgIAARw0AQQAgAjYCnNCAgABBAEEAKAKQ0ICAACADaiIDNgKQ0ICAACACIANBAXI2AgQgAiADaiADNgIADAMLAkAgBigCBCIEQQNxQQFHDQAgBEF4cSEHAkACQCAEQf8BSw0AIAYoAggiBSAEQQN2IghBA3RBsNCAgABqIgBGGgJAIAYoAgwiBCAFRw0AQQBBACgCiNCAgABBfiAId3E2AojQgIAADAILIAQgAEYaIAQgBTYCCCAFIAQ2AgwMAQsgBigCGCEJAkACQCAGKAIMIgAgBkYNACAGKAIIIgQgCEkaIAAgBDYCCCAEIAA2AgwMAQsCQCAGQRRqIgQoAgAiBQ0AIAZBEGoiBCgCACIFDQBBACEADAELA0AgBCEIIAUiAEEUaiIEKAIAIgUNACAAQRBqIQQgACgCECIFDQALIAhBADYCAAsgCUUNAAJAAkAgBiAGKAIcIgVBAnRBuNKAgABqIgQoAgBHDQAgBCAANgIAIAANAUEAQQAoAozQgIAAQX4gBXdxNgKM0ICAAAwCCyAJQRBBFCAJKAIQIAZGG2ogADYCACAARQ0BCyAAIAk2AhgCQCAGKAIQIgRFDQAgACAENgIQIAQgADYCGAsgBigCFCIERQ0AIABBFGogBDYCACAEIAA2AhgLIAcgA2ohAyAGIAdqIgYoAgQhBAsgBiAEQX5xNgIEIAIgA2ogAzYCACACIANBAXI2AgQCQCADQf8BSw0AIANBeHFBsNCAgABqIQQCQAJAQQAoAojQgIAAIgVBASADQQN2dCIDcQ0AQQAgBSADcjYCiNCAgAAgBCEDDAELIAQoAgghAwsgAyACNgIMIAQgAjYCCCACIAQ2AgwgAiADNgIIDAMLQR8hBAJAIANB////B0sNACADQQh2IgQgBEGA/j9qQRB2QQhxIgR0IgUgBUGA4B9qQRB2QQRxIgV0IgAgAEGAgA9qQRB2QQJxIgB0QQ92IAQgBXIgAHJrIgRBAXQgAyAEQRVqdkEBcXJBHGohBAsgAiAENgIcIAJCADcCECAEQQJ0QbjSgIAAaiEFAkBBACgCjNCAgAAiAEEBIAR0IghxDQAgBSACNgIAQQAgACAIcjYCjNCAgAAgAiAFNgIYIAIgAjYCCCACIAI2AgwMAwsgA0EAQRkgBEEBdmsgBEEfRht0IQQgBSgCACEAA0AgACIFKAIEQXhxIANGDQIgBEEddiEAIARBAXQhBCAFIABBBHFqQRBqIggoAgAiAA0ACyAIIAI2AgAgAiAFNgIYIAIgAjYCDCACIAI2AggMAgsgAEF4IABrQQ9xQQAgAEEIakEPcRsiA2oiCyAGQUhqIgggA2siA0EBcjYCBCAAIAhqQTg2AgQgBCAFQTcgBWtBD3FBACAFQUlqQQ9xG2pBQWoiCCAIIARBEGpJGyIIQSM2AgRBAEEAKALw04CAADYCpNCAgABBACADNgKU0ICAAEEAIAs2AqDQgIAAIAhBEGpBACkC0NOAgAA3AgAgCEEAKQLI04CAADcCCEEAIAhBCGo2AtDTgIAAQQAgBjYCzNOAgABBACAANgLI04CAAEEAQQA2AtTTgIAAIAhBJGohAwNAIANBBzYCACADQQRqIgMgBUkNAAsgCCAERg0DIAggCCgCBEF+cTYCBCAIIAggBGsiADYCACAEIABBAXI2AgQCQCAAQf8BSw0AIABBeHFBsNCAgABqIQMCQAJAQQAoAojQgIAAIgVBASAAQQN2dCIAcQ0AQQAgBSAAcjYCiNCAgAAgAyEFDAELIAMoAgghBQsgBSAENgIMIAMgBDYCCCAEIAM2AgwgBCAFNgIIDAQLQR8hAwJAIABB////B0sNACAAQQh2IgMgA0GA/j9qQRB2QQhxIgN0IgUgBUGA4B9qQRB2QQRxIgV0IgggCEGAgA9qQRB2QQJxIgh0QQ92IAMgBXIgCHJrIgNBAXQgACADQRVqdkEBcXJBHGohAwsgBCADNgIcIARCADcCECADQQJ0QbjSgIAAaiEFAkBBACgCjNCAgAAiCEEBIAN0IgZxDQAgBSAENgIAQQAgCCAGcjYCjNCAgAAgBCAFNgIYIAQgBDYCCCAEIAQ2AgwMBAsgAEEAQRkgA0EBdmsgA0EfRht0IQMgBSgCACEIA0AgCCIFKAIEQXhxIABGDQMgA0EddiEIIANBAXQhAyAFIAhBBHFqQRBqIgYoAgAiCA0ACyAGIAQ2AgAgBCAFNgIYIAQgBDYCDCAEIAQ2AggMAwsgBSgCCCIDIAI2AgwgBSACNgIIIAJBADYCGCACIAU2AgwgAiADNgIICyALQQhqIQMMBQsgBSgCCCIDIAQ2AgwgBSAENgIIIARBADYCGCAEIAU2AgwgBCADNgIIC0EAKAKU0ICAACIDIAJNDQBBACgCoNCAgAAiBCACaiIFIAMgAmsiA0EBcjYCBEEAIAM2ApTQgIAAQQAgBTYCoNCAgAAgBCACQQNyNgIEIARBCGohAwwDC0EAIQNBAEEwNgL404CAAAwCCwJAIAtFDQACQAJAIAggCCgCHCIFQQJ0QbjSgIAAaiIDKAIARw0AIAMgADYCACAADQFBACAHQX4gBXdxIgc2AozQgIAADAILIAtBEEEUIAsoAhAgCEYbaiAANgIAIABFDQELIAAgCzYCGAJAIAgoAhAiA0UNACAAIAM2AhAgAyAANgIYCyAIQRRqKAIAIgNFDQAgAEEUaiADNgIAIAMgADYCGAsCQAJAIARBD0sNACAIIAQgAmoiA0EDcjYCBCAIIANqIgMgAygCBEEBcjYCBAwBCyAIIAJqIgAgBEEBcjYCBCAIIAJBA3I2AgQgACAEaiAENgIAAkAgBEH/AUsNACAEQXhxQbDQgIAAaiEDAkACQEEAKAKI0ICAACIFQQEgBEEDdnQiBHENAEEAIAUgBHI2AojQgIAAIAMhBAwBCyADKAIIIQQLIAQgADYCDCADIAA2AgggACADNgIMIAAgBDYCCAwBC0EfIQMCQCAEQf///wdLDQAgBEEIdiIDIANBgP4/akEQdkEIcSIDdCIFIAVBgOAfakEQdkEEcSIFdCICIAJBgIAPakEQdkECcSICdEEPdiADIAVyIAJyayIDQQF0IAQgA0EVanZBAXFyQRxqIQMLIAAgAzYCHCAAQgA3AhAgA0ECdEG40oCAAGohBQJAIAdBASADdCICcQ0AIAUgADYCAEEAIAcgAnI2AozQgIAAIAAgBTYCGCAAIAA2AgggACAANgIMDAELIARBAEEZIANBAXZrIANBH0YbdCEDIAUoAgAhAgJAA0AgAiIFKAIEQXhxIARGDQEgA0EddiECIANBAXQhAyAFIAJBBHFqQRBqIgYoAgAiAg0ACyAGIAA2AgAgACAFNgIYIAAgADYCDCAAIAA2AggMAQsgBSgCCCIDIAA2AgwgBSAANgIIIABBADYCGCAAIAU2AgwgACADNgIICyAIQQhqIQMMAQsCQCAKRQ0AAkACQCAAIAAoAhwiBUECdEG40oCAAGoiAygCAEcNACADIAg2AgAgCA0BQQAgCUF+IAV3cTYCjNCAgAAMAgsgCkEQQRQgCigCECAARhtqIAg2AgAgCEUNAQsgCCAKNgIYAkAgACgCECIDRQ0AIAggAzYCECADIAg2AhgLIABBFGooAgAiA0UNACAIQRRqIAM2AgAgAyAINgIYCwJAAkAgBEEPSw0AIAAgBCACaiIDQQNyNgIEIAAgA2oiAyADKAIEQQFyNgIEDAELIAAgAmoiBSAEQQFyNgIEIAAgAkEDcjYCBCAFIARqIAQ2AgACQCAHRQ0AIAdBeHFBsNCAgABqIQJBACgCnNCAgAAhAwJAAkBBASAHQQN2dCIIIAZxDQBBACAIIAZyNgKI0ICAACACIQgMAQsgAigCCCEICyAIIAM2AgwgAiADNgIIIAMgAjYCDCADIAg2AggLQQAgBTYCnNCAgABBACAENgKQ0ICAAAsgAEEIaiEDCyABQRBqJICAgIAAIAMLCgAgABDJgICAAAviDQEHfwJAIABFDQAgAEF4aiIBIABBfGooAgAiAkF4cSIAaiEDAkAgAkEBcQ0AIAJBA3FFDQEgASABKAIAIgJrIgFBACgCmNCAgAAiBEkNASACIABqIQACQCABQQAoApzQgIAARg0AAkAgAkH/AUsNACABKAIIIgQgAkEDdiIFQQN0QbDQgIAAaiIGRhoCQCABKAIMIgIgBEcNAEEAQQAoAojQgIAAQX4gBXdxNgKI0ICAAAwDCyACIAZGGiACIAQ2AgggBCACNgIMDAILIAEoAhghBwJAAkAgASgCDCIGIAFGDQAgASgCCCICIARJGiAGIAI2AgggAiAGNgIMDAELAkAgAUEUaiICKAIAIgQNACABQRBqIgIoAgAiBA0AQQAhBgwBCwNAIAIhBSAEIgZBFGoiAigCACIEDQAgBkEQaiECIAYoAhAiBA0ACyAFQQA2AgALIAdFDQECQAJAIAEgASgCHCIEQQJ0QbjSgIAAaiICKAIARw0AIAIgBjYCACAGDQFBAEEAKAKM0ICAAEF+IAR3cTYCjNCAgAAMAwsgB0EQQRQgBygCECABRhtqIAY2AgAgBkUNAgsgBiAHNgIYAkAgASgCECICRQ0AIAYgAjYCECACIAY2AhgLIAEoAhQiAkUNASAGQRRqIAI2AgAgAiAGNgIYDAELIAMoAgQiAkEDcUEDRw0AIAMgAkF+cTYCBEEAIAA2ApDQgIAAIAEgAGogADYCACABIABBAXI2AgQPCyABIANPDQAgAygCBCICQQFxRQ0AAkACQCACQQJxDQACQCADQQAoAqDQgIAARw0AQQAgATYCoNCAgABBAEEAKAKU0ICAACAAaiIANgKU0ICAACABIABBAXI2AgQgAUEAKAKc0ICAAEcNA0EAQQA2ApDQgIAAQQBBADYCnNCAgAAPCwJAIANBACgCnNCAgABHDQBBACABNgKc0ICAAEEAQQAoApDQgIAAIABqIgA2ApDQgIAAIAEgAEEBcjYCBCABIABqIAA2AgAPCyACQXhxIABqIQACQAJAIAJB/wFLDQAgAygCCCIEIAJBA3YiBUEDdEGw0ICAAGoiBkYaAkAgAygCDCICIARHDQBBAEEAKAKI0ICAAEF+IAV3cTYCiNCAgAAMAgsgAiAGRhogAiAENgIIIAQgAjYCDAwBCyADKAIYIQcCQAJAIAMoAgwiBiADRg0AIAMoAggiAkEAKAKY0ICAAEkaIAYgAjYCCCACIAY2AgwMAQsCQCADQRRqIgIoAgAiBA0AIANBEGoiAigCACIEDQBBACEGDAELA0AgAiEFIAQiBkEUaiICKAIAIgQNACAGQRBqIQIgBigCECIEDQALIAVBADYCAAsgB0UNAAJAAkAgAyADKAIcIgRBAnRBuNKAgABqIgIoAgBHDQAgAiAGNgIAIAYNAUEAQQAoAozQgIAAQX4gBHdxNgKM0ICAAAwCCyAHQRBBFCAHKAIQIANGG2ogBjYCACAGRQ0BCyAGIAc2AhgCQCADKAIQIgJFDQAgBiACNgIQIAIgBjYCGAsgAygCFCICRQ0AIAZBFGogAjYCACACIAY2AhgLIAEgAGogADYCACABIABBAXI2AgQgAUEAKAKc0ICAAEcNAUEAIAA2ApDQgIAADwsgAyACQX5xNgIEIAEgAGogADYCACABIABBAXI2AgQLAkAgAEH/AUsNACAAQXhxQbDQgIAAaiECAkACQEEAKAKI0ICAACIEQQEgAEEDdnQiAHENAEEAIAQgAHI2AojQgIAAIAIhAAwBCyACKAIIIQALIAAgATYCDCACIAE2AgggASACNgIMIAEgADYCCA8LQR8hAgJAIABB////B0sNACAAQQh2IgIgAkGA/j9qQRB2QQhxIgJ0IgQgBEGA4B9qQRB2QQRxIgR0IgYgBkGAgA9qQRB2QQJxIgZ0QQ92IAIgBHIgBnJrIgJBAXQgACACQRVqdkEBcXJBHGohAgsgASACNgIcIAFCADcCECACQQJ0QbjSgIAAaiEEAkACQEEAKAKM0ICAACIGQQEgAnQiA3ENACAEIAE2AgBBACAGIANyNgKM0ICAACABIAQ2AhggASABNgIIIAEgATYCDAwBCyAAQQBBGSACQQF2ayACQR9GG3QhAiAEKAIAIQYCQANAIAYiBCgCBEF4cSAARg0BIAJBHXYhBiACQQF0IQIgBCAGQQRxakEQaiIDKAIAIgYNAAsgAyABNgIAIAEgBDYCGCABIAE2AgwgASABNgIIDAELIAQoAggiACABNgIMIAQgATYCCCABQQA2AhggASAENgIMIAEgADYCCAtBAEEAKAKo0ICAAEF/aiIBQX8gARs2AqjQgIAACwsEAAAAC04AAkAgAA0APwBBEHQPCwJAIABB//8DcQ0AIABBf0wNAAJAIABBEHZAACIAQX9HDQBBAEEwNgL404CAAEF/DwsgAEEQdA8LEMqAgIAAAAvyAgIDfwF+AkAgAkUNACAAIAE6AAAgAiAAaiIDQX9qIAE6AAAgAkEDSQ0AIAAgAToAAiAAIAE6AAEgA0F9aiABOgAAIANBfmogAToAACACQQdJDQAgACABOgADIANBfGogAToAACACQQlJDQAgAEEAIABrQQNxIgRqIgMgAUH/AXFBgYKECGwiATYCACADIAIgBGtBfHEiBGoiAkF8aiABNgIAIARBCUkNACADIAE2AgggAyABNgIEIAJBeGogATYCACACQXRqIAE2AgAgBEEZSQ0AIAMgATYCGCADIAE2AhQgAyABNgIQIAMgATYCDCACQXBqIAE2AgAgAkFsaiABNgIAIAJBaGogATYCACACQWRqIAE2AgAgBCADQQRxQRhyIgVrIgJBIEkNACABrUKBgICAEH4hBiADIAVqIQEDQCABIAY3AxggASAGNwMQIAEgBjcDCCABIAY3AwAgAUEgaiEBIAJBYGoiAkEfSw0ACwsgAAsLjkgBAEGACAuGSAEAAAACAAAAAwAAAAAAAAAAAAAABAAAAAUAAAAAAAAAAAAAAAYAAAAHAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAASW52YWxpZCBjaGFyIGluIHVybCBxdWVyeQBTcGFuIGNhbGxiYWNrIGVycm9yIGluIG9uX2JvZHkAQ29udGVudC1MZW5ndGggb3ZlcmZsb3cAQ2h1bmsgc2l6ZSBvdmVyZmxvdwBSZXNwb25zZSBvdmVyZmxvdwBJbnZhbGlkIG1ldGhvZCBmb3IgSFRUUC94LnggcmVxdWVzdABJbnZhbGlkIG1ldGhvZCBmb3IgUlRTUC94LnggcmVxdWVzdABFeHBlY3RlZCBTT1VSQ0UgbWV0aG9kIGZvciBJQ0UveC54IHJlcXVlc3QASW52YWxpZCBjaGFyIGluIHVybCBmcmFnbWVudCBzdGFydABFeHBlY3RlZCBkb3QAU3BhbiBjYWxsYmFjayBlcnJvciBpbiBvbl9zdGF0dXMASW52YWxpZCByZXNwb25zZSBzdGF0dXMASW52YWxpZCBjaGFyYWN0ZXIgaW4gY2h1bmsgZXh0ZW5zaW9ucwBVc2VyIGNhbGxiYWNrIGVycm9yAGBvbl9yZXNldGAgY2FsbGJhY2sgZXJyb3IAYG9uX2NodW5rX2hlYWRlcmAgY2FsbGJhY2sgZXJyb3IAYG9uX21lc3NhZ2VfYmVnaW5gIGNhbGxiYWNrIGVycm9yAGBvbl9jaHVua19leHRlbnNpb25fdmFsdWVgIGNhbGxiYWNrIGVycm9yAGBvbl9zdGF0dXNfY29tcGxldGVgIGNhbGxiYWNrIGVycm9yAGBvbl92ZXJzaW9uX2NvbXBsZXRlYCBjYWxsYmFjayBlcnJvcgBgb25fdXJsX2NvbXBsZXRlYCBjYWxsYmFjayBlcnJvcgBgb25fY2h1bmtfY29tcGxldGVgIGNhbGxiYWNrIGVycm9yAGBvbl9oZWFkZXJfdmFsdWVfY29tcGxldGVgIGNhbGxiYWNrIGVycm9yAGBvbl9tZXNzYWdlX2NvbXBsZXRlYCBjYWxsYmFjayBlcnJvcgBgb25fbWV0aG9kX2NvbXBsZXRlYCBjYWxsYmFjayBlcnJvcgBgb25faGVhZGVyX2ZpZWxkX2NvbXBsZXRlYCBjYWxsYmFjayBlcnJvcgBgb25fY2h1bmtfZXh0ZW5zaW9uX25hbWVgIGNhbGxiYWNrIGVycm9yAFVuZXhwZWN0ZWQgY2hhciBpbiB1cmwgc2VydmVyAEludmFsaWQgaGVhZGVyIHZhbHVlIGNoYXIASW52YWxpZCBoZWFkZXIgZmllbGQgY2hhcgBTcGFuIGNhbGxiYWNrIGVycm9yIGluIG9uX3ZlcnNpb24ASW52YWxpZCBtaW5vciB2ZXJzaW9uAEludmFsaWQgbWFqb3IgdmVyc2lvbgBFeHBlY3RlZCBzcGFjZSBhZnRlciB2ZXJzaW9uAEV4cGVjdGVkIENSTEYgYWZ0ZXIgdmVyc2lvbgBJbnZhbGlkIEhUVFAgdmVyc2lvbgBJbnZhbGlkIGhlYWRlciB0b2tlbgBTcGFuIGNhbGxiYWNrIGVycm9yIGluIG9uX3VybABJbnZhbGlkIGNoYXJhY3RlcnMgaW4gdXJsAFVuZXhwZWN0ZWQgc3RhcnQgY2hhciBpbiB1cmwARG91YmxlIEAgaW4gdXJsAEVtcHR5IENvbnRlbnQtTGVuZ3RoAEludmFsaWQgY2hhcmFjdGVyIGluIENvbnRlbnQtTGVuZ3RoAER1cGxpY2F0ZSBDb250ZW50LUxlbmd0aABJbnZhbGlkIGNoYXIgaW4gdXJsIHBhdGgAQ29udGVudC1MZW5ndGggY2FuJ3QgYmUgcHJlc2VudCB3aXRoIFRyYW5zZmVyLUVuY29kaW5nAEludmFsaWQgY2hhcmFjdGVyIGluIGNodW5rIHNpemUAU3BhbiBjYWxsYmFjayBlcnJvciBpbiBvbl9oZWFkZXJfdmFsdWUAU3BhbiBjYWxsYmFjayBlcnJvciBpbiBvbl9jaHVua19leHRlbnNpb25fdmFsdWUASW52YWxpZCBjaGFyYWN0ZXIgaW4gY2h1bmsgZXh0ZW5zaW9ucyB2YWx1ZQBNaXNzaW5nIGV4cGVjdGVkIExGIGFmdGVyIGhlYWRlciB2YWx1ZQBJbnZhbGlkIGBUcmFuc2Zlci1FbmNvZGluZ2AgaGVhZGVyIHZhbHVlAEludmFsaWQgY2hhcmFjdGVyIGluIGNodW5rIGV4dGVuc2lvbnMgcXVvdGUgdmFsdWUASW52YWxpZCBjaGFyYWN0ZXIgaW4gY2h1bmsgZXh0ZW5zaW9ucyBxdW90ZWQgdmFsdWUAUGF1c2VkIGJ5IG9uX2hlYWRlcnNfY29tcGxldGUASW52YWxpZCBFT0Ygc3RhdGUAb25fcmVzZXQgcGF1c2UAb25fY2h1bmtfaGVhZGVyIHBhdXNlAG9uX21lc3NhZ2VfYmVnaW4gcGF1c2UAb25fY2h1bmtfZXh0ZW5zaW9uX3ZhbHVlIHBhdXNlAG9uX3N0YXR1c19jb21wbGV0ZSBwYXVzZQBvbl92ZXJzaW9uX2NvbXBsZXRlIHBhdXNlAG9uX3VybF9jb21wbGV0ZSBwYXVzZQBvbl9jaHVua19jb21wbGV0ZSBwYXVzZQBvbl9oZWFkZXJfdmFsdWVfY29tcGxldGUgcGF1c2UAb25fbWVzc2FnZV9jb21wbGV0ZSBwYXVzZQBvbl9tZXRob2RfY29tcGxldGUgcGF1c2UAb25faGVhZGVyX2ZpZWxkX2NvbXBsZXRlIHBhdXNlAG9uX2NodW5rX2V4dGVuc2lvbl9uYW1lIHBhdXNlAFVuZXhwZWN0ZWQgc3BhY2UgYWZ0ZXIgc3RhcnQgbGluZQBTcGFuIGNhbGxiYWNrIGVycm9yIGluIG9uX2NodW5rX2V4dGVuc2lvbl9uYW1lAEludmFsaWQgY2hhcmFjdGVyIGluIGNodW5rIGV4dGVuc2lvbnMgbmFtZQBQYXVzZSBvbiBDT05ORUNUL1VwZ3JhZGUAUGF1c2Ugb24gUFJJL1VwZ3JhZGUARXhwZWN0ZWQgSFRUUC8yIENvbm5lY3Rpb24gUHJlZmFjZQBTcGFuIGNhbGxiYWNrIGVycm9yIGluIG9uX21ldGhvZABFeHBlY3RlZCBzcGFjZSBhZnRlciBtZXRob2QAU3BhbiBjYWxsYmFjayBlcnJvciBpbiBvbl9oZWFkZXJfZmllbGQAUGF1c2VkAEludmFsaWQgd29yZCBlbmNvdW50ZXJlZABJbnZhbGlkIG1ldGhvZCBlbmNvdW50ZXJlZABVbmV4cGVjdGVkIGNoYXIgaW4gdXJsIHNjaGVtYQBSZXF1ZXN0IGhhcyBpbnZhbGlkIGBUcmFuc2Zlci1FbmNvZGluZ2AAU1dJVENIX1BST1hZAFVTRV9QUk9YWQBNS0FDVElWSVRZAFVOUFJPQ0VTU0FCTEVfRU5USVRZAENPUFkATU9WRURfUEVSTUFORU5UTFkAVE9PX0VBUkxZAE5PVElGWQBGQUlMRURfREVQRU5ERU5DWQBCQURfR0FURVdBWQBQTEFZAFBVVABDSEVDS09VVABHQVRFV0FZX1RJTUVPVVQAUkVRVUVTVF9USU1FT1VUAE5FVFdPUktfQ09OTkVDVF9USU1FT1VUAENPTk5FQ1RJT05fVElNRU9VVABMT0dJTl9USU1FT1VUAE5FVFdPUktfUkVBRF9USU1FT1VUAFBPU1QATUlTRElSRUNURURfUkVRVUVTVABDTElFTlRfQ0xPU0VEX1JFUVVFU1QAQ0xJRU5UX0NMT1NFRF9MT0FEX0JBTEFOQ0VEX1JFUVVFU1QAQkFEX1JFUVVFU1QASFRUUF9SRVFVRVNUX1NFTlRfVE9fSFRUUFNfUE9SVABSRVBPUlQASU1fQV9URUFQT1QAUkVTRVRfQ09OVEVOVABOT19DT05URU5UAFBBUlRJQUxfQ09OVEVOVABIUEVfSU5WQUxJRF9DT05TVEFOVABIUEVfQ0JfUkVTRVQAR0VUAEhQRV9TVFJJQ1QAQ09ORkxJQ1QAVEVNUE9SQVJZX1JFRElSRUNUAFBFUk1BTkVOVF9SRURJUkVDVABDT05ORUNUAE1VTFRJX1NUQVRVUwBIUEVfSU5WQUxJRF9TVEFUVVMAVE9PX01BTllfUkVRVUVTVFMARUFSTFlfSElOVFMAVU5BVkFJTEFCTEVfRk9SX0xFR0FMX1JFQVNPTlMAT1BUSU9OUwBTV0lUQ0hJTkdfUFJPVE9DT0xTAFZBUklBTlRfQUxTT19ORUdPVElBVEVTAE1VTFRJUExFX0NIT0lDRVMASU5URVJOQUxfU0VSVkVSX0VSUk9SAFdFQl9TRVJWRVJfVU5LTk9XTl9FUlJPUgBSQUlMR1VOX0VSUk9SAElERU5USVRZX1BST1ZJREVSX0FVVEhFTlRJQ0FUSU9OX0VSUk9SAFNTTF9DRVJUSUZJQ0FURV9FUlJPUgBJTlZBTElEX1hfRk9SV0FSREVEX0ZPUgBTRVRfUEFSQU1FVEVSAEdFVF9QQVJBTUVURVIASFBFX1VTRVIAU0VFX09USEVSAEhQRV9DQl9DSFVOS19IRUFERVIATUtDQUxFTkRBUgBTRVRVUABXRUJfU0VSVkVSX0lTX0RPV04AVEVBUkRPV04ASFBFX0NMT1NFRF9DT05ORUNUSU9OAEhFVVJJU1RJQ19FWFBJUkFUSU9OAERJU0NPTk5FQ1RFRF9PUEVSQVRJT04ATk9OX0FVVEhPUklUQVRJVkVfSU5GT1JNQVRJT04ASFBFX0lOVkFMSURfVkVSU0lPTgBIUEVfQ0JfTUVTU0FHRV9CRUdJTgBTSVRFX0lTX0ZST1pFTgBIUEVfSU5WQUxJRF9IRUFERVJfVE9LRU4ASU5WQUxJRF9UT0tFTgBGT1JCSURERU4ARU5IQU5DRV9ZT1VSX0NBTE0ASFBFX0lOVkFMSURfVVJMAEJMT0NLRURfQllfUEFSRU5UQUxfQ09OVFJPTABNS0NPTABBQ0wASFBFX0lOVEVSTkFMAFJFUVVFU1RfSEVBREVSX0ZJRUxEU19UT09fTEFSR0VfVU5PRkZJQ0lBTABIUEVfT0sAVU5MSU5LAFVOTE9DSwBQUkkAUkVUUllfV0lUSABIUEVfSU5WQUxJRF9DT05URU5UX0xFTkdUSABIUEVfVU5FWFBFQ1RFRF9DT05URU5UX0xFTkdUSABGTFVTSABQUk9QUEFUQ0gATS1TRUFSQ0gAVVJJX1RPT19MT05HAFBST0NFU1NJTkcATUlTQ0VMTEFORU9VU19QRVJTSVNURU5UX1dBUk5JTkcATUlTQ0VMTEFORU9VU19XQVJOSU5HAEhQRV9JTlZBTElEX1RSQU5TRkVSX0VOQ09ESU5HAEV4cGVjdGVkIENSTEYASFBFX0lOVkFMSURfQ0hVTktfU0laRQBNT1ZFAENPTlRJTlVFAEhQRV9DQl9TVEFUVVNfQ09NUExFVEUASFBFX0NCX0hFQURFUlNfQ09NUExFVEUASFBFX0NCX1ZFUlNJT05fQ09NUExFVEUASFBFX0NCX1VSTF9DT01QTEVURQBIUEVfQ0JfQ0hVTktfQ09NUExFVEUASFBFX0NCX0hFQURFUl9WQUxVRV9DT01QTEVURQBIUEVfQ0JfQ0hVTktfRVhURU5TSU9OX1ZBTFVFX0NPTVBMRVRFAEhQRV9DQl9DSFVOS19FWFRFTlNJT05fTkFNRV9DT01QTEVURQBIUEVfQ0JfTUVTU0FHRV9DT01QTEVURQBIUEVfQ0JfTUVUSE9EX0NPTVBMRVRFAEhQRV9DQl9IRUFERVJfRklFTERfQ09NUExFVEUAREVMRVRFAEhQRV9JTlZBTElEX0VPRl9TVEFURQBJTlZBTElEX1NTTF9DRVJUSUZJQ0FURQBQQVVTRQBOT19SRVNQT05TRQBVTlNVUFBPUlRFRF9NRURJQV9UWVBFAEdPTkUATk9UX0FDQ0VQVEFCTEUAU0VSVklDRV9VTkFWQUlMQUJMRQBSQU5HRV9OT1RfU0FUSVNGSUFCTEUAT1JJR0lOX0lTX1VOUkVBQ0hBQkxFAFJFU1BPTlNFX0lTX1NUQUxFAFBVUkdFAE1FUkdFAFJFUVVFU1RfSEVBREVSX0ZJRUxEU19UT09fTEFSR0UAUkVRVUVTVF9IRUFERVJfVE9PX0xBUkdFAFBBWUxPQURfVE9PX0xBUkdFAElOU1VGRklDSUVOVF9TVE9SQUdFAEhQRV9QQVVTRURfVVBHUkFERQBIUEVfUEFVU0VEX0gyX1VQR1JBREUAU09VUkNFAEFOTk9VTkNFAFRSQUNFAEhQRV9VTkVYUEVDVEVEX1NQQUNFAERFU0NSSUJFAFVOU1VCU0NSSUJFAFJFQ09SRABIUEVfSU5WQUxJRF9NRVRIT0QATk9UX0ZPVU5EAFBST1BGSU5EAFVOQklORABSRUJJTkQAVU5BVVRIT1JJWkVEAE1FVEhPRF9OT1RfQUxMT1dFRABIVFRQX1ZFUlNJT05fTk9UX1NVUFBPUlRFRABBTFJFQURZX1JFUE9SVEVEAEFDQ0VQVEVEAE5PVF9JTVBMRU1FTlRFRABMT09QX0RFVEVDVEVEAEhQRV9DUl9FWFBFQ1RFRABIUEVfTEZfRVhQRUNURUQAQ1JFQVRFRABJTV9VU0VEAEhQRV9QQVVTRUQAVElNRU9VVF9PQ0NVUkVEAFBBWU1FTlRfUkVRVUlSRUQAUFJFQ09ORElUSU9OX1JFUVVJUkVEAFBST1hZX0FVVEhFTlRJQ0FUSU9OX1JFUVVJUkVEAE5FVFdPUktfQVVUSEVOVElDQVRJT05fUkVRVUlSRUQATEVOR1RIX1JFUVVJUkVEAFNTTF9DRVJUSUZJQ0FURV9SRVFVSVJFRABVUEdSQURFX1JFUVVJUkVEAFBBR0VfRVhQSVJFRABQUkVDT05ESVRJT05fRkFJTEVEAEVYUEVDVEFUSU9OX0ZBSUxFRABSRVZBTElEQVRJT05fRkFJTEVEAFNTTF9IQU5EU0hBS0VfRkFJTEVEAExPQ0tFRABUUkFOU0ZPUk1BVElPTl9BUFBMSUVEAE5PVF9NT0RJRklFRABOT1RfRVhURU5ERUQAQkFORFdJRFRIX0xJTUlUX0VYQ0VFREVEAFNJVEVfSVNfT1ZFUkxPQURFRABIRUFEAEV4cGVjdGVkIEhUVFAvAABeEwAAJhMAADAQAADwFwAAnRMAABUSAAA5FwAA8BIAAAoQAAB1EgAArRIAAIITAABPFAAAfxAAAKAVAAAjFAAAiRIAAIsUAABNFQAA1BEAAM8UAAAQGAAAyRYAANwWAADBEQAA4BcAALsUAAB0FAAAfBUAAOUUAAAIFwAAHxAAAGUVAACjFAAAKBUAAAIVAACZFQAALBAAAIsZAABPDwAA1A4AAGoQAADOEAAAAhcAAIkOAABuEwAAHBMAAGYUAABWFwAAwRMAAM0TAABsEwAAaBcAAGYXAABfFwAAIhMAAM4PAABpDgAA2A4AAGMWAADLEwAAqg4AACgXAAAmFwAAxRMAAF0WAADoEQAAZxMAAGUTAADyFgAAcxMAAB0XAAD5FgAA8xEAAM8OAADOFQAADBIAALMRAAClEQAAYRAAADIXAAC7EwAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAgEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQABAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAgMCAgICAgAAAgIAAgIAAgICAgICAgICAgAEAAAAAAACAgICAgICAgICAgICAgICAgICAgICAgICAgAAAAICAgICAgICAgICAgICAgICAgICAgICAgICAgICAAIAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAIAAgICAgIAAAICAAICAAICAgICAgICAgIAAwAEAAAAAgICAgICAgICAgICAgICAgICAgICAgICAgIAAAACAgICAgICAgICAgICAgICAgICAgICAgICAgICAgACAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABsb3NlZWVwLWFsaXZlAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQABAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQEBAQEBAQEBAQEBAgEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEAAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQFjaHVua2VkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAQABAQEBAQAAAQEAAQEAAQEBAQEBAQEBAQAAAAAAAAABAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQAAAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAAEAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGVjdGlvbmVudC1sZW5ndGhvbnJveHktY29ubmVjdGlvbgAAAAAAAAAAAAAAAAAAAHJhbnNmZXItZW5jb2RpbmdwZ3JhZGUNCg0KDQpTTQ0KDQpUVFAvQ0UvVFNQLwAAAAAAAAAAAAAAAAECAAEDAAAAAAAAAAAAAAAAAAAAAAAABAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEAAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEAAAAAAAAAAAABAgABAwAAAAAAAAAAAAAAAAAAAAAAAAQBAQUBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAAAAAAAAAAAAAQAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAQEAAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQABAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQAAAAAAAAAAAAABAAACAAAAAAAAAAAAAAAAAAAAAAAAAwQAAAQEBAQEBAQEBAQEBQQEBAQEBAQEBAQEBAAEAAYHBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEAAQABAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAQAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAAAAAAAAAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAEAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAgAAAAACAAAAAAAAAAAAAAAAAAAAAAADAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwAAAAAAAAMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAE5PVU5DRUVDS09VVE5FQ1RFVEVDUklCRUxVU0hFVEVBRFNFQVJDSFJHRUNUSVZJVFlMRU5EQVJWRU9USUZZUFRJT05TQ0hTRUFZU1RBVENIR0VPUkRJUkVDVE9SVFJDSFBBUkFNRVRFUlVSQ0VCU0NSSUJFQVJET1dOQUNFSU5ETktDS1VCU0NSSUJFSFRUUC9BRFRQLw==' + + +/***/ }), + +/***/ 50172: +/***/ ((__unused_webpack_module, exports) => { + +"use strict"; + +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.enumToMap = void 0; +function enumToMap(obj) { + const res = {}; + Object.keys(obj).forEach((key) => { + const value = obj[key]; + if (typeof value === 'number') { + res[key] = value; + } + }); + return res; +} +exports.enumToMap = enumToMap; +//# sourceMappingURL=utils.js.map + +/***/ }), + +/***/ 47501: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const { kClients } = __nccwpck_require__(36443) +const Agent = __nccwpck_require__(59965) +const { + kAgent, + kMockAgentSet, + kMockAgentGet, + kDispatches, + kIsMockActive, + kNetConnect, + kGetNetConnect, + kOptions, + kFactory +} = __nccwpck_require__(91117) +const MockClient = __nccwpck_require__(47365) +const MockPool = __nccwpck_require__(94004) +const { matchValue, buildMockOptions } = __nccwpck_require__(53397) +const { InvalidArgumentError, UndiciError } = __nccwpck_require__(68707) +const Dispatcher = __nccwpck_require__(28611) +const Pluralizer = __nccwpck_require__(91529) +const PendingInterceptorsFormatter = __nccwpck_require__(56142) + +class FakeWeakRef { + constructor (value) { + this.value = value + } + + deref () { + return this.value + } +} + +class MockAgent extends Dispatcher { + constructor (opts) { + super(opts) + + this[kNetConnect] = true + this[kIsMockActive] = true + + // Instantiate Agent and encapsulate + if ((opts && opts.agent && typeof opts.agent.dispatch !== 'function')) { + throw new InvalidArgumentError('Argument opts.agent must implement Agent') + } + const agent = opts && opts.agent ? opts.agent : new Agent(opts) + this[kAgent] = agent + + this[kClients] = agent[kClients] + this[kOptions] = buildMockOptions(opts) + } + + get (origin) { + let dispatcher = this[kMockAgentGet](origin) + + if (!dispatcher) { + dispatcher = this[kFactory](origin) + this[kMockAgentSet](origin, dispatcher) + } + return dispatcher + } + + dispatch (opts, handler) { + // Call MockAgent.get to perform additional setup before dispatching as normal + this.get(opts.origin) + return this[kAgent].dispatch(opts, handler) + } + + async close () { + await this[kAgent].close() + this[kClients].clear() + } + + deactivate () { + this[kIsMockActive] = false + } + + activate () { + this[kIsMockActive] = true + } + + enableNetConnect (matcher) { + if (typeof matcher === 'string' || typeof matcher === 'function' || matcher instanceof RegExp) { + if (Array.isArray(this[kNetConnect])) { + this[kNetConnect].push(matcher) + } else { + this[kNetConnect] = [matcher] + } + } else if (typeof matcher === 'undefined') { + this[kNetConnect] = true + } else { + throw new InvalidArgumentError('Unsupported matcher. Must be one of String|Function|RegExp.') + } + } + + disableNetConnect () { + this[kNetConnect] = false + } + + // This is required to bypass issues caused by using global symbols - see: + // https://github.com/nodejs/undici/issues/1447 + get isMockActive () { + return this[kIsMockActive] + } + + [kMockAgentSet] (origin, dispatcher) { + this[kClients].set(origin, new FakeWeakRef(dispatcher)) + } + + [kFactory] (origin) { + const mockOptions = Object.assign({ agent: this }, this[kOptions]) + return this[kOptions] && this[kOptions].connections === 1 + ? new MockClient(origin, mockOptions) + : new MockPool(origin, mockOptions) + } + + [kMockAgentGet] (origin) { + // First check if we can immediately find it + const ref = this[kClients].get(origin) + if (ref) { + return ref.deref() + } + + // If the origin is not a string create a dummy parent pool and return to user + if (typeof origin !== 'string') { + const dispatcher = this[kFactory]('http://localhost:9999') + this[kMockAgentSet](origin, dispatcher) + return dispatcher + } + + // If we match, create a pool and assign the same dispatches + for (const [keyMatcher, nonExplicitRef] of Array.from(this[kClients])) { + const nonExplicitDispatcher = nonExplicitRef.deref() + if (nonExplicitDispatcher && typeof keyMatcher !== 'string' && matchValue(keyMatcher, origin)) { + const dispatcher = this[kFactory](origin) + this[kMockAgentSet](origin, dispatcher) + dispatcher[kDispatches] = nonExplicitDispatcher[kDispatches] + return dispatcher + } + } + } + + [kGetNetConnect] () { + return this[kNetConnect] + } + + pendingInterceptors () { + const mockAgentClients = this[kClients] + + return Array.from(mockAgentClients.entries()) + .flatMap(([origin, scope]) => scope.deref()[kDispatches].map(dispatch => ({ ...dispatch, origin }))) + .filter(({ pending }) => pending) + } + + assertNoPendingInterceptors ({ pendingInterceptorsFormatter = new PendingInterceptorsFormatter() } = {}) { + const pending = this.pendingInterceptors() + + if (pending.length === 0) { + return + } + + const pluralizer = new Pluralizer('interceptor', 'interceptors').pluralize(pending.length) + + throw new UndiciError(` +${pluralizer.count} ${pluralizer.noun} ${pluralizer.is} pending: + +${pendingInterceptorsFormatter.format(pending)} +`.trim()) + } +} + +module.exports = MockAgent + + +/***/ }), + +/***/ 47365: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const { promisify } = __nccwpck_require__(39023) +const Client = __nccwpck_require__(86197) +const { buildMockDispatch } = __nccwpck_require__(53397) +const { + kDispatches, + kMockAgent, + kClose, + kOriginalClose, + kOrigin, + kOriginalDispatch, + kConnected +} = __nccwpck_require__(91117) +const { MockInterceptor } = __nccwpck_require__(31511) +const Symbols = __nccwpck_require__(36443) +const { InvalidArgumentError } = __nccwpck_require__(68707) + +/** + * MockClient provides an API that extends the Client to influence the mockDispatches. + */ +class MockClient extends Client { + constructor (origin, opts) { + super(origin, opts) + + if (!opts || !opts.agent || typeof opts.agent.dispatch !== 'function') { + throw new InvalidArgumentError('Argument opts.agent must implement Agent') + } + + this[kMockAgent] = opts.agent + this[kOrigin] = origin + this[kDispatches] = [] + this[kConnected] = 1 + this[kOriginalDispatch] = this.dispatch + this[kOriginalClose] = this.close.bind(this) + + this.dispatch = buildMockDispatch.call(this) + this.close = this[kClose] + } + + get [Symbols.kConnected] () { + return this[kConnected] + } + + /** + * Sets up the base interceptor for mocking replies from undici. + */ + intercept (opts) { + return new MockInterceptor(opts, this[kDispatches]) + } + + async [kClose] () { + await promisify(this[kOriginalClose])() + this[kConnected] = 0 + this[kMockAgent][Symbols.kClients].delete(this[kOrigin]) + } +} + +module.exports = MockClient + + +/***/ }), + +/***/ 52429: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const { UndiciError } = __nccwpck_require__(68707) + +class MockNotMatchedError extends UndiciError { + constructor (message) { + super(message) + Error.captureStackTrace(this, MockNotMatchedError) + this.name = 'MockNotMatchedError' + this.message = message || 'The request does not match any registered mock dispatches' + this.code = 'UND_MOCK_ERR_MOCK_NOT_MATCHED' + } +} + +module.exports = { + MockNotMatchedError +} + + +/***/ }), + +/***/ 31511: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const { getResponseData, buildKey, addMockDispatch } = __nccwpck_require__(53397) +const { + kDispatches, + kDispatchKey, + kDefaultHeaders, + kDefaultTrailers, + kContentLength, + kMockDispatch +} = __nccwpck_require__(91117) +const { InvalidArgumentError } = __nccwpck_require__(68707) +const { buildURL } = __nccwpck_require__(3440) + +/** + * Defines the scope API for an interceptor reply + */ +class MockScope { + constructor (mockDispatch) { + this[kMockDispatch] = mockDispatch + } + + /** + * Delay a reply by a set amount in ms. + */ + delay (waitInMs) { + if (typeof waitInMs !== 'number' || !Number.isInteger(waitInMs) || waitInMs <= 0) { + throw new InvalidArgumentError('waitInMs must be a valid integer > 0') + } + + this[kMockDispatch].delay = waitInMs + return this + } + + /** + * For a defined reply, never mark as consumed. + */ + persist () { + this[kMockDispatch].persist = true + return this + } + + /** + * Allow one to define a reply for a set amount of matching requests. + */ + times (repeatTimes) { + if (typeof repeatTimes !== 'number' || !Number.isInteger(repeatTimes) || repeatTimes <= 0) { + throw new InvalidArgumentError('repeatTimes must be a valid integer > 0') + } + + this[kMockDispatch].times = repeatTimes + return this + } +} + +/** + * Defines an interceptor for a Mock + */ +class MockInterceptor { + constructor (opts, mockDispatches) { + if (typeof opts !== 'object') { + throw new InvalidArgumentError('opts must be an object') + } + if (typeof opts.path === 'undefined') { + throw new InvalidArgumentError('opts.path must be defined') + } + if (typeof opts.method === 'undefined') { + opts.method = 'GET' + } + // See https://github.com/nodejs/undici/issues/1245 + // As per RFC 3986, clients are not supposed to send URI + // fragments to servers when they retrieve a document, + if (typeof opts.path === 'string') { + if (opts.query) { + opts.path = buildURL(opts.path, opts.query) + } else { + // Matches https://github.com/nodejs/undici/blob/main/lib/fetch/index.js#L1811 + const parsedURL = new URL(opts.path, 'data://') + opts.path = parsedURL.pathname + parsedURL.search + } + } + if (typeof opts.method === 'string') { + opts.method = opts.method.toUpperCase() + } + + this[kDispatchKey] = buildKey(opts) + this[kDispatches] = mockDispatches + this[kDefaultHeaders] = {} + this[kDefaultTrailers] = {} + this[kContentLength] = false + } + + createMockScopeDispatchData (statusCode, data, responseOptions = {}) { + const responseData = getResponseData(data) + const contentLength = this[kContentLength] ? { 'content-length': responseData.length } : {} + const headers = { ...this[kDefaultHeaders], ...contentLength, ...responseOptions.headers } + const trailers = { ...this[kDefaultTrailers], ...responseOptions.trailers } + + return { statusCode, data, headers, trailers } + } + + validateReplyParameters (statusCode, data, responseOptions) { + if (typeof statusCode === 'undefined') { + throw new InvalidArgumentError('statusCode must be defined') + } + if (typeof data === 'undefined') { + throw new InvalidArgumentError('data must be defined') + } + if (typeof responseOptions !== 'object') { + throw new InvalidArgumentError('responseOptions must be an object') + } + } + + /** + * Mock an undici request with a defined reply. + */ + reply (replyData) { + // Values of reply aren't available right now as they + // can only be available when the reply callback is invoked. + if (typeof replyData === 'function') { + // We'll first wrap the provided callback in another function, + // this function will properly resolve the data from the callback + // when invoked. + const wrappedDefaultsCallback = (opts) => { + // Our reply options callback contains the parameter for statusCode, data and options. + const resolvedData = replyData(opts) + + // Check if it is in the right format + if (typeof resolvedData !== 'object') { + throw new InvalidArgumentError('reply options callback must return an object') + } + + const { statusCode, data = '', responseOptions = {} } = resolvedData + this.validateReplyParameters(statusCode, data, responseOptions) + // Since the values can be obtained immediately we return them + // from this higher order function that will be resolved later. + return { + ...this.createMockScopeDispatchData(statusCode, data, responseOptions) + } + } + + // Add usual dispatch data, but this time set the data parameter to function that will eventually provide data. + const newMockDispatch = addMockDispatch(this[kDispatches], this[kDispatchKey], wrappedDefaultsCallback) + return new MockScope(newMockDispatch) + } + + // We can have either one or three parameters, if we get here, + // we should have 1-3 parameters. So we spread the arguments of + // this function to obtain the parameters, since replyData will always + // just be the statusCode. + const [statusCode, data = '', responseOptions = {}] = [...arguments] + this.validateReplyParameters(statusCode, data, responseOptions) + + // Send in-already provided data like usual + const dispatchData = this.createMockScopeDispatchData(statusCode, data, responseOptions) + const newMockDispatch = addMockDispatch(this[kDispatches], this[kDispatchKey], dispatchData) + return new MockScope(newMockDispatch) + } + + /** + * Mock an undici request with a defined error. + */ + replyWithError (error) { + if (typeof error === 'undefined') { + throw new InvalidArgumentError('error must be defined') + } + + const newMockDispatch = addMockDispatch(this[kDispatches], this[kDispatchKey], { error }) + return new MockScope(newMockDispatch) + } + + /** + * Set default reply headers on the interceptor for subsequent replies + */ + defaultReplyHeaders (headers) { + if (typeof headers === 'undefined') { + throw new InvalidArgumentError('headers must be defined') + } + + this[kDefaultHeaders] = headers + return this + } + + /** + * Set default reply trailers on the interceptor for subsequent replies + */ + defaultReplyTrailers (trailers) { + if (typeof trailers === 'undefined') { + throw new InvalidArgumentError('trailers must be defined') + } + + this[kDefaultTrailers] = trailers + return this + } + + /** + * Set reply content length header for replies on the interceptor + */ + replyContentLength () { + this[kContentLength] = true + return this + } +} + +module.exports.MockInterceptor = MockInterceptor +module.exports.MockScope = MockScope + + +/***/ }), + +/***/ 94004: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const { promisify } = __nccwpck_require__(39023) +const Pool = __nccwpck_require__(35076) +const { buildMockDispatch } = __nccwpck_require__(53397) +const { + kDispatches, + kMockAgent, + kClose, + kOriginalClose, + kOrigin, + kOriginalDispatch, + kConnected +} = __nccwpck_require__(91117) +const { MockInterceptor } = __nccwpck_require__(31511) +const Symbols = __nccwpck_require__(36443) +const { InvalidArgumentError } = __nccwpck_require__(68707) + +/** + * MockPool provides an API that extends the Pool to influence the mockDispatches. + */ +class MockPool extends Pool { + constructor (origin, opts) { + super(origin, opts) + + if (!opts || !opts.agent || typeof opts.agent.dispatch !== 'function') { + throw new InvalidArgumentError('Argument opts.agent must implement Agent') + } + + this[kMockAgent] = opts.agent + this[kOrigin] = origin + this[kDispatches] = [] + this[kConnected] = 1 + this[kOriginalDispatch] = this.dispatch + this[kOriginalClose] = this.close.bind(this) + + this.dispatch = buildMockDispatch.call(this) + this.close = this[kClose] + } + + get [Symbols.kConnected] () { + return this[kConnected] + } + + /** + * Sets up the base interceptor for mocking replies from undici. + */ + intercept (opts) { + return new MockInterceptor(opts, this[kDispatches]) + } + + async [kClose] () { + await promisify(this[kOriginalClose])() + this[kConnected] = 0 + this[kMockAgent][Symbols.kClients].delete(this[kOrigin]) + } +} + +module.exports = MockPool + + +/***/ }), + +/***/ 91117: +/***/ ((module) => { + +"use strict"; + + +module.exports = { + kAgent: Symbol('agent'), + kOptions: Symbol('options'), + kFactory: Symbol('factory'), + kDispatches: Symbol('dispatches'), + kDispatchKey: Symbol('dispatch key'), + kDefaultHeaders: Symbol('default headers'), + kDefaultTrailers: Symbol('default trailers'), + kContentLength: Symbol('content length'), + kMockAgent: Symbol('mock agent'), + kMockAgentSet: Symbol('mock agent set'), + kMockAgentGet: Symbol('mock agent get'), + kMockDispatch: Symbol('mock dispatch'), + kClose: Symbol('close'), + kOriginalClose: Symbol('original agent close'), + kOrigin: Symbol('origin'), + kIsMockActive: Symbol('is mock active'), + kNetConnect: Symbol('net connect'), + kGetNetConnect: Symbol('get net connect'), + kConnected: Symbol('connected') +} + + +/***/ }), + +/***/ 53397: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const { MockNotMatchedError } = __nccwpck_require__(52429) +const { + kDispatches, + kMockAgent, + kOriginalDispatch, + kOrigin, + kGetNetConnect +} = __nccwpck_require__(91117) +const { buildURL, nop } = __nccwpck_require__(3440) +const { STATUS_CODES } = __nccwpck_require__(58611) +const { + types: { + isPromise + } +} = __nccwpck_require__(39023) + +function matchValue (match, value) { + if (typeof match === 'string') { + return match === value + } + if (match instanceof RegExp) { + return match.test(value) + } + if (typeof match === 'function') { + return match(value) === true + } + return false +} + +function lowerCaseEntries (headers) { + return Object.fromEntries( + Object.entries(headers).map(([headerName, headerValue]) => { + return [headerName.toLocaleLowerCase(), headerValue] + }) + ) +} + +/** + * @param {import('../../index').Headers|string[]|Record} headers + * @param {string} key + */ +function getHeaderByName (headers, key) { + if (Array.isArray(headers)) { + for (let i = 0; i < headers.length; i += 2) { + if (headers[i].toLocaleLowerCase() === key.toLocaleLowerCase()) { + return headers[i + 1] + } + } + + return undefined + } else if (typeof headers.get === 'function') { + return headers.get(key) + } else { + return lowerCaseEntries(headers)[key.toLocaleLowerCase()] + } +} + +/** @param {string[]} headers */ +function buildHeadersFromArray (headers) { // fetch HeadersList + const clone = headers.slice() + const entries = [] + for (let index = 0; index < clone.length; index += 2) { + entries.push([clone[index], clone[index + 1]]) + } + return Object.fromEntries(entries) +} + +function matchHeaders (mockDispatch, headers) { + if (typeof mockDispatch.headers === 'function') { + if (Array.isArray(headers)) { // fetch HeadersList + headers = buildHeadersFromArray(headers) + } + return mockDispatch.headers(headers ? lowerCaseEntries(headers) : {}) + } + if (typeof mockDispatch.headers === 'undefined') { + return true + } + if (typeof headers !== 'object' || typeof mockDispatch.headers !== 'object') { + return false + } + + for (const [matchHeaderName, matchHeaderValue] of Object.entries(mockDispatch.headers)) { + const headerValue = getHeaderByName(headers, matchHeaderName) + + if (!matchValue(matchHeaderValue, headerValue)) { + return false + } + } + return true +} + +function safeUrl (path) { + if (typeof path !== 'string') { + return path + } + + const pathSegments = path.split('?') + + if (pathSegments.length !== 2) { + return path + } + + const qp = new URLSearchParams(pathSegments.pop()) + qp.sort() + return [...pathSegments, qp.toString()].join('?') +} + +function matchKey (mockDispatch, { path, method, body, headers }) { + const pathMatch = matchValue(mockDispatch.path, path) + const methodMatch = matchValue(mockDispatch.method, method) + const bodyMatch = typeof mockDispatch.body !== 'undefined' ? matchValue(mockDispatch.body, body) : true + const headersMatch = matchHeaders(mockDispatch, headers) + return pathMatch && methodMatch && bodyMatch && headersMatch +} + +function getResponseData (data) { + if (Buffer.isBuffer(data)) { + return data + } else if (typeof data === 'object') { + return JSON.stringify(data) + } else { + return data.toString() + } +} + +function getMockDispatch (mockDispatches, key) { + const basePath = key.query ? buildURL(key.path, key.query) : key.path + const resolvedPath = typeof basePath === 'string' ? safeUrl(basePath) : basePath + + // Match path + let matchedMockDispatches = mockDispatches.filter(({ consumed }) => !consumed).filter(({ path }) => matchValue(safeUrl(path), resolvedPath)) + if (matchedMockDispatches.length === 0) { + throw new MockNotMatchedError(`Mock dispatch not matched for path '${resolvedPath}'`) + } + + // Match method + matchedMockDispatches = matchedMockDispatches.filter(({ method }) => matchValue(method, key.method)) + if (matchedMockDispatches.length === 0) { + throw new MockNotMatchedError(`Mock dispatch not matched for method '${key.method}'`) + } + + // Match body + matchedMockDispatches = matchedMockDispatches.filter(({ body }) => typeof body !== 'undefined' ? matchValue(body, key.body) : true) + if (matchedMockDispatches.length === 0) { + throw new MockNotMatchedError(`Mock dispatch not matched for body '${key.body}'`) + } + + // Match headers + matchedMockDispatches = matchedMockDispatches.filter((mockDispatch) => matchHeaders(mockDispatch, key.headers)) + if (matchedMockDispatches.length === 0) { + throw new MockNotMatchedError(`Mock dispatch not matched for headers '${typeof key.headers === 'object' ? JSON.stringify(key.headers) : key.headers}'`) + } + + return matchedMockDispatches[0] +} + +function addMockDispatch (mockDispatches, key, data) { + const baseData = { timesInvoked: 0, times: 1, persist: false, consumed: false } + const replyData = typeof data === 'function' ? { callback: data } : { ...data } + const newMockDispatch = { ...baseData, ...key, pending: true, data: { error: null, ...replyData } } + mockDispatches.push(newMockDispatch) + return newMockDispatch +} + +function deleteMockDispatch (mockDispatches, key) { + const index = mockDispatches.findIndex(dispatch => { + if (!dispatch.consumed) { + return false + } + return matchKey(dispatch, key) + }) + if (index !== -1) { + mockDispatches.splice(index, 1) + } +} + +function buildKey (opts) { + const { path, method, body, headers, query } = opts + return { + path, + method, + body, + headers, + query + } +} + +function generateKeyValues (data) { + return Object.entries(data).reduce((keyValuePairs, [key, value]) => [ + ...keyValuePairs, + Buffer.from(`${key}`), + Array.isArray(value) ? value.map(x => Buffer.from(`${x}`)) : Buffer.from(`${value}`) + ], []) +} + +/** + * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Status + * @param {number} statusCode + */ +function getStatusText (statusCode) { + return STATUS_CODES[statusCode] || 'unknown' +} + +async function getResponse (body) { + const buffers = [] + for await (const data of body) { + buffers.push(data) + } + return Buffer.concat(buffers).toString('utf8') +} + +/** + * Mock dispatch function used to simulate undici dispatches + */ +function mockDispatch (opts, handler) { + // Get mock dispatch from built key + const key = buildKey(opts) + const mockDispatch = getMockDispatch(this[kDispatches], key) + + mockDispatch.timesInvoked++ + + // Here's where we resolve a callback if a callback is present for the dispatch data. + if (mockDispatch.data.callback) { + mockDispatch.data = { ...mockDispatch.data, ...mockDispatch.data.callback(opts) } + } + + // Parse mockDispatch data + const { data: { statusCode, data, headers, trailers, error }, delay, persist } = mockDispatch + const { timesInvoked, times } = mockDispatch + + // If it's used up and not persistent, mark as consumed + mockDispatch.consumed = !persist && timesInvoked >= times + mockDispatch.pending = timesInvoked < times + + // If specified, trigger dispatch error + if (error !== null) { + deleteMockDispatch(this[kDispatches], key) + handler.onError(error) + return true + } + + // Handle the request with a delay if necessary + if (typeof delay === 'number' && delay > 0) { + setTimeout(() => { + handleReply(this[kDispatches]) + }, delay) + } else { + handleReply(this[kDispatches]) + } + + function handleReply (mockDispatches, _data = data) { + // fetch's HeadersList is a 1D string array + const optsHeaders = Array.isArray(opts.headers) + ? buildHeadersFromArray(opts.headers) + : opts.headers + const body = typeof _data === 'function' + ? _data({ ...opts, headers: optsHeaders }) + : _data + + // util.types.isPromise is likely needed for jest. + if (isPromise(body)) { + // If handleReply is asynchronous, throwing an error + // in the callback will reject the promise, rather than + // synchronously throw the error, which breaks some tests. + // Rather, we wait for the callback to resolve if it is a + // promise, and then re-run handleReply with the new body. + body.then((newData) => handleReply(mockDispatches, newData)) + return + } + + const responseData = getResponseData(body) + const responseHeaders = generateKeyValues(headers) + const responseTrailers = generateKeyValues(trailers) + + handler.abort = nop + handler.onHeaders(statusCode, responseHeaders, resume, getStatusText(statusCode)) + handler.onData(Buffer.from(responseData)) + handler.onComplete(responseTrailers) + deleteMockDispatch(mockDispatches, key) + } + + function resume () {} + + return true +} + +function buildMockDispatch () { + const agent = this[kMockAgent] + const origin = this[kOrigin] + const originalDispatch = this[kOriginalDispatch] + + return function dispatch (opts, handler) { + if (agent.isMockActive) { + try { + mockDispatch.call(this, opts, handler) + } catch (error) { + if (error instanceof MockNotMatchedError) { + const netConnect = agent[kGetNetConnect]() + if (netConnect === false) { + throw new MockNotMatchedError(`${error.message}: subsequent request to origin ${origin} was not allowed (net.connect disabled)`) + } + if (checkNetConnect(netConnect, origin)) { + originalDispatch.call(this, opts, handler) + } else { + throw new MockNotMatchedError(`${error.message}: subsequent request to origin ${origin} was not allowed (net.connect is not enabled for this origin)`) + } + } else { + throw error + } + } + } else { + originalDispatch.call(this, opts, handler) + } + } +} + +function checkNetConnect (netConnect, origin) { + const url = new URL(origin) + if (netConnect === true) { + return true + } else if (Array.isArray(netConnect) && netConnect.some((matcher) => matchValue(matcher, url.host))) { + return true + } + return false +} + +function buildMockOptions (opts) { + if (opts) { + const { agent, ...mockOptions } = opts + return mockOptions + } +} + +module.exports = { + getResponseData, + getMockDispatch, + addMockDispatch, + deleteMockDispatch, + buildKey, + generateKeyValues, + matchValue, + getResponse, + getStatusText, + mockDispatch, + buildMockDispatch, + checkNetConnect, + buildMockOptions, + getHeaderByName +} + + +/***/ }), + +/***/ 56142: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const { Transform } = __nccwpck_require__(2203) +const { Console } = __nccwpck_require__(64236) + +/** + * Gets the output of `console.table(…)` as a string. + */ +module.exports = class PendingInterceptorsFormatter { + constructor ({ disableColors } = {}) { + this.transform = new Transform({ + transform (chunk, _enc, cb) { + cb(null, chunk) + } + }) + + this.logger = new Console({ + stdout: this.transform, + inspectOptions: { + colors: !disableColors && !process.env.CI + } + }) + } + + format (pendingInterceptors) { + const withPrettyHeaders = pendingInterceptors.map( + ({ method, path, data: { statusCode }, persist, times, timesInvoked, origin }) => ({ + Method: method, + Origin: origin, + Path: path, + 'Status code': statusCode, + Persistent: persist ? '✅' : '❌', + Invocations: timesInvoked, + Remaining: persist ? Infinity : times - timesInvoked + })) + + this.logger.table(withPrettyHeaders) + return this.transform.read().toString() + } +} + + +/***/ }), + +/***/ 91529: +/***/ ((module) => { + +"use strict"; + + +const singulars = { + pronoun: 'it', + is: 'is', + was: 'was', + this: 'this' +} + +const plurals = { + pronoun: 'they', + is: 'are', + was: 'were', + this: 'these' +} + +module.exports = class Pluralizer { + constructor (singular, plural) { + this.singular = singular + this.plural = plural + } + + pluralize (count) { + const one = count === 1 + const keys = one ? singulars : plurals + const noun = one ? this.singular : this.plural + return { ...keys, count, noun } + } +} + + +/***/ }), + +/***/ 34869: +/***/ ((module) => { + +"use strict"; +/* eslint-disable */ + + + +// Extracted from node/lib/internal/fixed_queue.js + +// Currently optimal queue size, tested on V8 6.0 - 6.6. Must be power of two. +const kSize = 2048; +const kMask = kSize - 1; + +// The FixedQueue is implemented as a singly-linked list of fixed-size +// circular buffers. It looks something like this: +// +// head tail +// | | +// v v +// +-----------+ <-----\ +-----------+ <------\ +-----------+ +// | [null] | \----- | next | \------- | next | +// +-----------+ +-----------+ +-----------+ +// | item | <-- bottom | item | <-- bottom | [empty] | +// | item | | item | | [empty] | +// | item | | item | | [empty] | +// | item | | item | | [empty] | +// | item | | item | bottom --> | item | +// | item | | item | | item | +// | ... | | ... | | ... | +// | item | | item | | item | +// | item | | item | | item | +// | [empty] | <-- top | item | | item | +// | [empty] | | item | | item | +// | [empty] | | [empty] | <-- top top --> | [empty] | +// +-----------+ +-----------+ +-----------+ +// +// Or, if there is only one circular buffer, it looks something +// like either of these: +// +// head tail head tail +// | | | | +// v v v v +// +-----------+ +-----------+ +// | [null] | | [null] | +// +-----------+ +-----------+ +// | [empty] | | item | +// | [empty] | | item | +// | item | <-- bottom top --> | [empty] | +// | item | | [empty] | +// | [empty] | <-- top bottom --> | item | +// | [empty] | | item | +// +-----------+ +-----------+ +// +// Adding a value means moving `top` forward by one, removing means +// moving `bottom` forward by one. After reaching the end, the queue +// wraps around. +// +// When `top === bottom` the current queue is empty and when +// `top + 1 === bottom` it's full. This wastes a single space of storage +// but allows much quicker checks. + +class FixedCircularBuffer { + constructor() { + this.bottom = 0; + this.top = 0; + this.list = new Array(kSize); + this.next = null; + } + + isEmpty() { + return this.top === this.bottom; + } + + isFull() { + return ((this.top + 1) & kMask) === this.bottom; + } + + push(data) { + this.list[this.top] = data; + this.top = (this.top + 1) & kMask; + } + + shift() { + const nextItem = this.list[this.bottom]; + if (nextItem === undefined) + return null; + this.list[this.bottom] = undefined; + this.bottom = (this.bottom + 1) & kMask; + return nextItem; + } +} + +module.exports = class FixedQueue { + constructor() { + this.head = this.tail = new FixedCircularBuffer(); + } + + isEmpty() { + return this.head.isEmpty(); + } + + push(data) { + if (this.head.isFull()) { + // Head is full: Creates a new queue, sets the old queue's `.next` to it, + // and sets it as the new main queue. + this.head = this.head.next = new FixedCircularBuffer(); + } + this.head.push(data); + } + + shift() { + const tail = this.tail; + const next = tail.shift(); + if (tail.isEmpty() && tail.next !== null) { + // If there is another queue, it forms the new tail. + this.tail = tail.next; + } + return next; + } +}; + + +/***/ }), + +/***/ 58640: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const DispatcherBase = __nccwpck_require__(50001) +const FixedQueue = __nccwpck_require__(34869) +const { kConnected, kSize, kRunning, kPending, kQueued, kBusy, kFree, kUrl, kClose, kDestroy, kDispatch } = __nccwpck_require__(36443) +const PoolStats = __nccwpck_require__(24622) + +const kClients = Symbol('clients') +const kNeedDrain = Symbol('needDrain') +const kQueue = Symbol('queue') +const kClosedResolve = Symbol('closed resolve') +const kOnDrain = Symbol('onDrain') +const kOnConnect = Symbol('onConnect') +const kOnDisconnect = Symbol('onDisconnect') +const kOnConnectionError = Symbol('onConnectionError') +const kGetDispatcher = Symbol('get dispatcher') +const kAddClient = Symbol('add client') +const kRemoveClient = Symbol('remove client') +const kStats = Symbol('stats') + +class PoolBase extends DispatcherBase { + constructor () { + super() + + this[kQueue] = new FixedQueue() + this[kClients] = [] + this[kQueued] = 0 + + const pool = this + + this[kOnDrain] = function onDrain (origin, targets) { + const queue = pool[kQueue] + + let needDrain = false + + while (!needDrain) { + const item = queue.shift() + if (!item) { + break + } + pool[kQueued]-- + needDrain = !this.dispatch(item.opts, item.handler) + } + + this[kNeedDrain] = needDrain + + if (!this[kNeedDrain] && pool[kNeedDrain]) { + pool[kNeedDrain] = false + pool.emit('drain', origin, [pool, ...targets]) + } + + if (pool[kClosedResolve] && queue.isEmpty()) { + Promise + .all(pool[kClients].map(c => c.close())) + .then(pool[kClosedResolve]) + } + } + + this[kOnConnect] = (origin, targets) => { + pool.emit('connect', origin, [pool, ...targets]) + } + + this[kOnDisconnect] = (origin, targets, err) => { + pool.emit('disconnect', origin, [pool, ...targets], err) + } + + this[kOnConnectionError] = (origin, targets, err) => { + pool.emit('connectionError', origin, [pool, ...targets], err) + } + + this[kStats] = new PoolStats(this) + } + + get [kBusy] () { + return this[kNeedDrain] + } + + get [kConnected] () { + return this[kClients].filter(client => client[kConnected]).length + } + + get [kFree] () { + return this[kClients].filter(client => client[kConnected] && !client[kNeedDrain]).length + } + + get [kPending] () { + let ret = this[kQueued] + for (const { [kPending]: pending } of this[kClients]) { + ret += pending + } + return ret + } + + get [kRunning] () { + let ret = 0 + for (const { [kRunning]: running } of this[kClients]) { + ret += running + } + return ret + } + + get [kSize] () { + let ret = this[kQueued] + for (const { [kSize]: size } of this[kClients]) { + ret += size + } + return ret + } + + get stats () { + return this[kStats] + } + + async [kClose] () { + if (this[kQueue].isEmpty()) { + return Promise.all(this[kClients].map(c => c.close())) + } else { + return new Promise((resolve) => { + this[kClosedResolve] = resolve + }) + } + } + + async [kDestroy] (err) { + while (true) { + const item = this[kQueue].shift() + if (!item) { + break + } + item.handler.onError(err) + } + + return Promise.all(this[kClients].map(c => c.destroy(err))) + } + + [kDispatch] (opts, handler) { + const dispatcher = this[kGetDispatcher]() + + if (!dispatcher) { + this[kNeedDrain] = true + this[kQueue].push({ opts, handler }) + this[kQueued]++ + } else if (!dispatcher.dispatch(opts, handler)) { + dispatcher[kNeedDrain] = true + this[kNeedDrain] = !this[kGetDispatcher]() + } + + return !this[kNeedDrain] + } + + [kAddClient] (client) { + client + .on('drain', this[kOnDrain]) + .on('connect', this[kOnConnect]) + .on('disconnect', this[kOnDisconnect]) + .on('connectionError', this[kOnConnectionError]) + + this[kClients].push(client) + + if (this[kNeedDrain]) { + process.nextTick(() => { + if (this[kNeedDrain]) { + this[kOnDrain](client[kUrl], [this, client]) + } + }) + } + + return this + } + + [kRemoveClient] (client) { + client.close(() => { + const idx = this[kClients].indexOf(client) + if (idx !== -1) { + this[kClients].splice(idx, 1) + } + }) + + this[kNeedDrain] = this[kClients].some(dispatcher => ( + !dispatcher[kNeedDrain] && + dispatcher.closed !== true && + dispatcher.destroyed !== true + )) + } +} + +module.exports = { + PoolBase, + kClients, + kNeedDrain, + kAddClient, + kRemoveClient, + kGetDispatcher +} + + +/***/ }), + +/***/ 24622: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +const { kFree, kConnected, kPending, kQueued, kRunning, kSize } = __nccwpck_require__(36443) +const kPool = Symbol('pool') + +class PoolStats { + constructor (pool) { + this[kPool] = pool + } + + get connected () { + return this[kPool][kConnected] + } + + get free () { + return this[kPool][kFree] + } + + get pending () { + return this[kPool][kPending] + } + + get queued () { + return this[kPool][kQueued] + } + + get running () { + return this[kPool][kRunning] + } + + get size () { + return this[kPool][kSize] + } +} + +module.exports = PoolStats + + +/***/ }), + +/***/ 35076: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const { + PoolBase, + kClients, + kNeedDrain, + kAddClient, + kGetDispatcher +} = __nccwpck_require__(58640) +const Client = __nccwpck_require__(86197) +const { + InvalidArgumentError +} = __nccwpck_require__(68707) +const util = __nccwpck_require__(3440) +const { kUrl, kInterceptors } = __nccwpck_require__(36443) +const buildConnector = __nccwpck_require__(59136) + +const kOptions = Symbol('options') +const kConnections = Symbol('connections') +const kFactory = Symbol('factory') + +function defaultFactory (origin, opts) { + return new Client(origin, opts) +} + +class Pool extends PoolBase { + constructor (origin, { + connections, + factory = defaultFactory, + connect, + connectTimeout, + tls, + maxCachedSessions, + socketPath, + autoSelectFamily, + autoSelectFamilyAttemptTimeout, + allowH2, + ...options + } = {}) { + super() + + if (connections != null && (!Number.isFinite(connections) || connections < 0)) { + throw new InvalidArgumentError('invalid connections') + } + + if (typeof factory !== 'function') { + throw new InvalidArgumentError('factory must be a function.') + } + + if (connect != null && typeof connect !== 'function' && typeof connect !== 'object') { + throw new InvalidArgumentError('connect must be a function or an object') + } + + if (typeof connect !== 'function') { + connect = buildConnector({ + ...tls, + maxCachedSessions, + allowH2, + socketPath, + timeout: connectTimeout, + ...(util.nodeHasAutoSelectFamily && autoSelectFamily ? { autoSelectFamily, autoSelectFamilyAttemptTimeout } : undefined), + ...connect + }) + } + + this[kInterceptors] = options.interceptors && options.interceptors.Pool && Array.isArray(options.interceptors.Pool) + ? options.interceptors.Pool + : [] + this[kConnections] = connections || null + this[kUrl] = util.parseOrigin(origin) + this[kOptions] = { ...util.deepClone(options), connect, allowH2 } + this[kOptions].interceptors = options.interceptors + ? { ...options.interceptors } + : undefined + this[kFactory] = factory + + this.on('connectionError', (origin, targets, error) => { + // If a connection error occurs, we remove the client from the pool, + // and emit a connectionError event. They will not be re-used. + // Fixes https://github.com/nodejs/undici/issues/3895 + for (const target of targets) { + // Do not use kRemoveClient here, as it will close the client, + // but the client cannot be closed in this state. + const idx = this[kClients].indexOf(target) + if (idx !== -1) { + this[kClients].splice(idx, 1) + } + } + }) + } + + [kGetDispatcher] () { + let dispatcher = this[kClients].find(dispatcher => !dispatcher[kNeedDrain]) + + if (dispatcher) { + return dispatcher + } + + if (!this[kConnections] || this[kClients].length < this[kConnections]) { + dispatcher = this[kFactory](this[kUrl], this[kOptions]) + this[kAddClient](dispatcher) + } + + return dispatcher + } +} + +module.exports = Pool + + +/***/ }), + +/***/ 22720: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const { kProxy, kClose, kDestroy, kInterceptors } = __nccwpck_require__(36443) +const { URL } = __nccwpck_require__(87016) +const Agent = __nccwpck_require__(59965) +const Pool = __nccwpck_require__(35076) +const DispatcherBase = __nccwpck_require__(50001) +const { InvalidArgumentError, RequestAbortedError } = __nccwpck_require__(68707) +const buildConnector = __nccwpck_require__(59136) + +const kAgent = Symbol('proxy agent') +const kClient = Symbol('proxy client') +const kProxyHeaders = Symbol('proxy headers') +const kRequestTls = Symbol('request tls settings') +const kProxyTls = Symbol('proxy tls settings') +const kConnectEndpoint = Symbol('connect endpoint function') + +function defaultProtocolPort (protocol) { + return protocol === 'https:' ? 443 : 80 +} + +function buildProxyOptions (opts) { + if (typeof opts === 'string') { + opts = { uri: opts } + } + + if (!opts || !opts.uri) { + throw new InvalidArgumentError('Proxy opts.uri is mandatory') + } + + return { + uri: opts.uri, + protocol: opts.protocol || 'https' + } +} + +function defaultFactory (origin, opts) { + return new Pool(origin, opts) +} + +class ProxyAgent extends DispatcherBase { + constructor (opts) { + super(opts) + this[kProxy] = buildProxyOptions(opts) + this[kAgent] = new Agent(opts) + this[kInterceptors] = opts.interceptors && opts.interceptors.ProxyAgent && Array.isArray(opts.interceptors.ProxyAgent) + ? opts.interceptors.ProxyAgent + : [] + + if (typeof opts === 'string') { + opts = { uri: opts } + } + + if (!opts || !opts.uri) { + throw new InvalidArgumentError('Proxy opts.uri is mandatory') + } + + const { clientFactory = defaultFactory } = opts + + if (typeof clientFactory !== 'function') { + throw new InvalidArgumentError('Proxy opts.clientFactory must be a function.') + } + + this[kRequestTls] = opts.requestTls + this[kProxyTls] = opts.proxyTls + this[kProxyHeaders] = opts.headers || {} + + const resolvedUrl = new URL(opts.uri) + const { origin, port, host, username, password } = resolvedUrl + + if (opts.auth && opts.token) { + throw new InvalidArgumentError('opts.auth cannot be used in combination with opts.token') + } else if (opts.auth) { + /* @deprecated in favour of opts.token */ + this[kProxyHeaders]['proxy-authorization'] = `Basic ${opts.auth}` + } else if (opts.token) { + this[kProxyHeaders]['proxy-authorization'] = opts.token + } else if (username && password) { + this[kProxyHeaders]['proxy-authorization'] = `Basic ${Buffer.from(`${decodeURIComponent(username)}:${decodeURIComponent(password)}`).toString('base64')}` + } + + const connect = buildConnector({ ...opts.proxyTls }) + this[kConnectEndpoint] = buildConnector({ ...opts.requestTls }) + this[kClient] = clientFactory(resolvedUrl, { connect }) + this[kAgent] = new Agent({ + ...opts, + connect: async (opts, callback) => { + let requestedHost = opts.host + if (!opts.port) { + requestedHost += `:${defaultProtocolPort(opts.protocol)}` + } + try { + const { socket, statusCode } = await this[kClient].connect({ + origin, + port, + path: requestedHost, + signal: opts.signal, + headers: { + ...this[kProxyHeaders], + host + } + }) + if (statusCode !== 200) { + socket.on('error', () => {}).destroy() + callback(new RequestAbortedError(`Proxy response (${statusCode}) !== 200 when HTTP Tunneling`)) + } + if (opts.protocol !== 'https:') { + callback(null, socket) + return + } + let servername + if (this[kRequestTls]) { + servername = this[kRequestTls].servername + } else { + servername = opts.servername + } + this[kConnectEndpoint]({ ...opts, servername, httpSocket: socket }, callback) + } catch (err) { + callback(err) + } + } + }) + } + + dispatch (opts, handler) { + const { host } = new URL(opts.origin) + const headers = buildHeaders(opts.headers) + throwIfProxyAuthIsSent(headers) + return this[kAgent].dispatch( + { + ...opts, + headers: { + ...headers, + host + } + }, + handler + ) + } + + async [kClose] () { + await this[kAgent].close() + await this[kClient].close() + } + + async [kDestroy] () { + await this[kAgent].destroy() + await this[kClient].destroy() + } +} + +/** + * @param {string[] | Record} headers + * @returns {Record} + */ +function buildHeaders (headers) { + // When using undici.fetch, the headers list is stored + // as an array. + if (Array.isArray(headers)) { + /** @type {Record} */ + const headersPair = {} + + for (let i = 0; i < headers.length; i += 2) { + headersPair[headers[i]] = headers[i + 1] + } + + return headersPair + } + + return headers +} + +/** + * @param {Record} headers + * + * Previous versions of ProxyAgent suggests the Proxy-Authorization in request headers + * Nevertheless, it was changed and to avoid a security vulnerability by end users + * this check was created. + * It should be removed in the next major version for performance reasons + */ +function throwIfProxyAuthIsSent (headers) { + const existProxyAuth = headers && Object.keys(headers) + .find((key) => key.toLowerCase() === 'proxy-authorization') + if (existProxyAuth) { + throw new InvalidArgumentError('Proxy-Authorization should be sent in ProxyAgent constructor') + } +} + +module.exports = ProxyAgent + + +/***/ }), + +/***/ 28804: +/***/ ((module) => { + +"use strict"; + + +let fastNow = Date.now() +let fastNowTimeout + +const fastTimers = [] + +function onTimeout () { + fastNow = Date.now() + + let len = fastTimers.length + let idx = 0 + while (idx < len) { + const timer = fastTimers[idx] + + if (timer.state === 0) { + timer.state = fastNow + timer.delay + } else if (timer.state > 0 && fastNow >= timer.state) { + timer.state = -1 + timer.callback(timer.opaque) + } + + if (timer.state === -1) { + timer.state = -2 + if (idx !== len - 1) { + fastTimers[idx] = fastTimers.pop() + } else { + fastTimers.pop() + } + len -= 1 + } else { + idx += 1 + } + } + + if (fastTimers.length > 0) { + refreshTimeout() + } +} + +function refreshTimeout () { + if (fastNowTimeout && fastNowTimeout.refresh) { + fastNowTimeout.refresh() + } else { + clearTimeout(fastNowTimeout) + fastNowTimeout = setTimeout(onTimeout, 1e3) + if (fastNowTimeout.unref) { + fastNowTimeout.unref() + } + } +} + +class Timeout { + constructor (callback, delay, opaque) { + this.callback = callback + this.delay = delay + this.opaque = opaque + + // -2 not in timer list + // -1 in timer list but inactive + // 0 in timer list waiting for time + // > 0 in timer list waiting for time to expire + this.state = -2 + + this.refresh() + } + + refresh () { + if (this.state === -2) { + fastTimers.push(this) + if (!fastNowTimeout || fastTimers.length === 1) { + refreshTimeout() + } + } + + this.state = 0 + } + + clear () { + this.state = -1 + } +} + +module.exports = { + setTimeout (callback, delay, opaque) { + return delay < 1e3 + ? setTimeout(callback, delay, opaque) + : new Timeout(callback, delay, opaque) + }, + clearTimeout (timeout) { + if (timeout instanceof Timeout) { + timeout.clear() + } else { + clearTimeout(timeout) + } + } +} + + +/***/ }), + +/***/ 68550: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const diagnosticsChannel = __nccwpck_require__(31637) +const { uid, states } = __nccwpck_require__(45913) +const { + kReadyState, + kSentClose, + kByteParser, + kReceivedClose +} = __nccwpck_require__(62933) +const { fireEvent, failWebsocketConnection } = __nccwpck_require__(3574) +const { CloseEvent } = __nccwpck_require__(46255) +const { makeRequest } = __nccwpck_require__(25194) +const { fetching } = __nccwpck_require__(12315) +const { Headers } = __nccwpck_require__(26349) +const { getGlobalDispatcher } = __nccwpck_require__(32581) +const { kHeadersList } = __nccwpck_require__(36443) + +const channels = {} +channels.open = diagnosticsChannel.channel('undici:websocket:open') +channels.close = diagnosticsChannel.channel('undici:websocket:close') +channels.socketError = diagnosticsChannel.channel('undici:websocket:socket_error') + +/** @type {import('crypto')} */ +let crypto +try { + crypto = __nccwpck_require__(76982) +} catch { + +} + +/** + * @see https://websockets.spec.whatwg.org/#concept-websocket-establish + * @param {URL} url + * @param {string|string[]} protocols + * @param {import('./websocket').WebSocket} ws + * @param {(response: any) => void} onEstablish + * @param {Partial} options + */ +function establishWebSocketConnection (url, protocols, ws, onEstablish, options) { + // 1. Let requestURL be a copy of url, with its scheme set to "http", if url’s + // scheme is "ws", and to "https" otherwise. + const requestURL = url + + requestURL.protocol = url.protocol === 'ws:' ? 'http:' : 'https:' + + // 2. Let request be a new request, whose URL is requestURL, client is client, + // service-workers mode is "none", referrer is "no-referrer", mode is + // "websocket", credentials mode is "include", cache mode is "no-store" , + // and redirect mode is "error". + const request = makeRequest({ + urlList: [requestURL], + serviceWorkers: 'none', + referrer: 'no-referrer', + mode: 'websocket', + credentials: 'include', + cache: 'no-store', + redirect: 'error' + }) + + // Note: undici extension, allow setting custom headers. + if (options.headers) { + const headersList = new Headers(options.headers)[kHeadersList] + + request.headersList = headersList + } + + // 3. Append (`Upgrade`, `websocket`) to request’s header list. + // 4. Append (`Connection`, `Upgrade`) to request’s header list. + // Note: both of these are handled by undici currently. + // https://github.com/nodejs/undici/blob/68c269c4144c446f3f1220951338daef4a6b5ec4/lib/client.js#L1397 + + // 5. Let keyValue be a nonce consisting of a randomly selected + // 16-byte value that has been forgiving-base64-encoded and + // isomorphic encoded. + const keyValue = crypto.randomBytes(16).toString('base64') + + // 6. Append (`Sec-WebSocket-Key`, keyValue) to request’s + // header list. + request.headersList.append('sec-websocket-key', keyValue) + + // 7. Append (`Sec-WebSocket-Version`, `13`) to request’s + // header list. + request.headersList.append('sec-websocket-version', '13') + + // 8. For each protocol in protocols, combine + // (`Sec-WebSocket-Protocol`, protocol) in request’s header + // list. + for (const protocol of protocols) { + request.headersList.append('sec-websocket-protocol', protocol) + } + + // 9. Let permessageDeflate be a user-agent defined + // "permessage-deflate" extension header value. + // https://github.com/mozilla/gecko-dev/blob/ce78234f5e653a5d3916813ff990f053510227bc/netwerk/protocol/websocket/WebSocketChannel.cpp#L2673 + // TODO: enable once permessage-deflate is supported + const permessageDeflate = '' // 'permessage-deflate; 15' + + // 10. Append (`Sec-WebSocket-Extensions`, permessageDeflate) to + // request’s header list. + // request.headersList.append('sec-websocket-extensions', permessageDeflate) + + // 11. Fetch request with useParallelQueue set to true, and + // processResponse given response being these steps: + const controller = fetching({ + request, + useParallelQueue: true, + dispatcher: options.dispatcher ?? getGlobalDispatcher(), + processResponse (response) { + // 1. If response is a network error or its status is not 101, + // fail the WebSocket connection. + if (response.type === 'error' || response.status !== 101) { + failWebsocketConnection(ws, 'Received network error or non-101 status code.') + return + } + + // 2. If protocols is not the empty list and extracting header + // list values given `Sec-WebSocket-Protocol` and response’s + // header list results in null, failure, or the empty byte + // sequence, then fail the WebSocket connection. + if (protocols.length !== 0 && !response.headersList.get('Sec-WebSocket-Protocol')) { + failWebsocketConnection(ws, 'Server did not respond with sent protocols.') + return + } + + // 3. Follow the requirements stated step 2 to step 6, inclusive, + // of the last set of steps in section 4.1 of The WebSocket + // Protocol to validate response. This either results in fail + // the WebSocket connection or the WebSocket connection is + // established. + + // 2. If the response lacks an |Upgrade| header field or the |Upgrade| + // header field contains a value that is not an ASCII case- + // insensitive match for the value "websocket", the client MUST + // _Fail the WebSocket Connection_. + if (response.headersList.get('Upgrade')?.toLowerCase() !== 'websocket') { + failWebsocketConnection(ws, 'Server did not set Upgrade header to "websocket".') + return + } + + // 3. If the response lacks a |Connection| header field or the + // |Connection| header field doesn't contain a token that is an + // ASCII case-insensitive match for the value "Upgrade", the client + // MUST _Fail the WebSocket Connection_. + if (response.headersList.get('Connection')?.toLowerCase() !== 'upgrade') { + failWebsocketConnection(ws, 'Server did not set Connection header to "upgrade".') + return + } + + // 4. If the response lacks a |Sec-WebSocket-Accept| header field or + // the |Sec-WebSocket-Accept| contains a value other than the + // base64-encoded SHA-1 of the concatenation of the |Sec-WebSocket- + // Key| (as a string, not base64-decoded) with the string "258EAFA5- + // E914-47DA-95CA-C5AB0DC85B11" but ignoring any leading and + // trailing whitespace, the client MUST _Fail the WebSocket + // Connection_. + const secWSAccept = response.headersList.get('Sec-WebSocket-Accept') + const digest = crypto.createHash('sha1').update(keyValue + uid).digest('base64') + if (secWSAccept !== digest) { + failWebsocketConnection(ws, 'Incorrect hash received in Sec-WebSocket-Accept header.') + return + } + + // 5. If the response includes a |Sec-WebSocket-Extensions| header + // field and this header field indicates the use of an extension + // that was not present in the client's handshake (the server has + // indicated an extension not requested by the client), the client + // MUST _Fail the WebSocket Connection_. (The parsing of this + // header field to determine which extensions are requested is + // discussed in Section 9.1.) + const secExtension = response.headersList.get('Sec-WebSocket-Extensions') + + if (secExtension !== null && secExtension !== permessageDeflate) { + failWebsocketConnection(ws, 'Received different permessage-deflate than the one set.') + return + } + + // 6. If the response includes a |Sec-WebSocket-Protocol| header field + // and this header field indicates the use of a subprotocol that was + // not present in the client's handshake (the server has indicated a + // subprotocol not requested by the client), the client MUST _Fail + // the WebSocket Connection_. + const secProtocol = response.headersList.get('Sec-WebSocket-Protocol') + + if (secProtocol !== null && secProtocol !== request.headersList.get('Sec-WebSocket-Protocol')) { + failWebsocketConnection(ws, 'Protocol was not set in the opening handshake.') + return + } + + response.socket.on('data', onSocketData) + response.socket.on('close', onSocketClose) + response.socket.on('error', onSocketError) + + if (channels.open.hasSubscribers) { + channels.open.publish({ + address: response.socket.address(), + protocol: secProtocol, + extensions: secExtension + }) + } + + onEstablish(response) + } + }) + + return controller +} + +/** + * @param {Buffer} chunk + */ +function onSocketData (chunk) { + if (!this.ws[kByteParser].write(chunk)) { + this.pause() + } +} + +/** + * @see https://websockets.spec.whatwg.org/#feedback-from-the-protocol + * @see https://datatracker.ietf.org/doc/html/rfc6455#section-7.1.4 + */ +function onSocketClose () { + const { ws } = this + + // If the TCP connection was closed after the + // WebSocket closing handshake was completed, the WebSocket connection + // is said to have been closed _cleanly_. + const wasClean = ws[kSentClose] && ws[kReceivedClose] + + let code = 1005 + let reason = '' + + const result = ws[kByteParser].closingInfo + + if (result) { + code = result.code ?? 1005 + reason = result.reason + } else if (!ws[kSentClose]) { + // If _The WebSocket + // Connection is Closed_ and no Close control frame was received by the + // endpoint (such as could occur if the underlying transport connection + // is lost), _The WebSocket Connection Close Code_ is considered to be + // 1006. + code = 1006 + } + + // 1. Change the ready state to CLOSED (3). + ws[kReadyState] = states.CLOSED + + // 2. If the user agent was required to fail the WebSocket + // connection, or if the WebSocket connection was closed + // after being flagged as full, fire an event named error + // at the WebSocket object. + // TODO + + // 3. Fire an event named close at the WebSocket object, + // using CloseEvent, with the wasClean attribute + // initialized to true if the connection closed cleanly + // and false otherwise, the code attribute initialized to + // the WebSocket connection close code, and the reason + // attribute initialized to the result of applying UTF-8 + // decode without BOM to the WebSocket connection close + // reason. + fireEvent('close', ws, CloseEvent, { + wasClean, code, reason + }) + + if (channels.close.hasSubscribers) { + channels.close.publish({ + websocket: ws, + code, + reason + }) + } +} + +function onSocketError (error) { + const { ws } = this + + ws[kReadyState] = states.CLOSING + + if (channels.socketError.hasSubscribers) { + channels.socketError.publish(error) + } + + this.destroy() +} + +module.exports = { + establishWebSocketConnection +} + + +/***/ }), + +/***/ 45913: +/***/ ((module) => { + +"use strict"; + + +// This is a Globally Unique Identifier unique used +// to validate that the endpoint accepts websocket +// connections. +// See https://www.rfc-editor.org/rfc/rfc6455.html#section-1.3 +const uid = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11' + +/** @type {PropertyDescriptor} */ +const staticPropertyDescriptors = { + enumerable: true, + writable: false, + configurable: false +} + +const states = { + CONNECTING: 0, + OPEN: 1, + CLOSING: 2, + CLOSED: 3 +} + +const opcodes = { + CONTINUATION: 0x0, + TEXT: 0x1, + BINARY: 0x2, + CLOSE: 0x8, + PING: 0x9, + PONG: 0xA +} + +const maxUnsigned16Bit = 2 ** 16 - 1 // 65535 + +const parserStates = { + INFO: 0, + PAYLOADLENGTH_16: 2, + PAYLOADLENGTH_64: 3, + READ_DATA: 4 +} + +const emptyBuffer = Buffer.allocUnsafe(0) + +module.exports = { + uid, + staticPropertyDescriptors, + states, + opcodes, + maxUnsigned16Bit, + parserStates, + emptyBuffer +} + + +/***/ }), + +/***/ 46255: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const { webidl } = __nccwpck_require__(74222) +const { kEnumerableProperty } = __nccwpck_require__(3440) +const { MessagePort } = __nccwpck_require__(28167) + +/** + * @see https://html.spec.whatwg.org/multipage/comms.html#messageevent + */ +class MessageEvent extends Event { + #eventInit + + constructor (type, eventInitDict = {}) { + webidl.argumentLengthCheck(arguments, 1, { header: 'MessageEvent constructor' }) + + type = webidl.converters.DOMString(type) + eventInitDict = webidl.converters.MessageEventInit(eventInitDict) + + super(type, eventInitDict) + + this.#eventInit = eventInitDict + } + + get data () { + webidl.brandCheck(this, MessageEvent) + + return this.#eventInit.data + } + + get origin () { + webidl.brandCheck(this, MessageEvent) + + return this.#eventInit.origin + } + + get lastEventId () { + webidl.brandCheck(this, MessageEvent) + + return this.#eventInit.lastEventId + } + + get source () { + webidl.brandCheck(this, MessageEvent) + + return this.#eventInit.source + } + + get ports () { + webidl.brandCheck(this, MessageEvent) + + if (!Object.isFrozen(this.#eventInit.ports)) { + Object.freeze(this.#eventInit.ports) + } + + return this.#eventInit.ports + } + + initMessageEvent ( + type, + bubbles = false, + cancelable = false, + data = null, + origin = '', + lastEventId = '', + source = null, + ports = [] + ) { + webidl.brandCheck(this, MessageEvent) + + webidl.argumentLengthCheck(arguments, 1, { header: 'MessageEvent.initMessageEvent' }) + + return new MessageEvent(type, { + bubbles, cancelable, data, origin, lastEventId, source, ports + }) + } +} + +/** + * @see https://websockets.spec.whatwg.org/#the-closeevent-interface + */ +class CloseEvent extends Event { + #eventInit + + constructor (type, eventInitDict = {}) { + webidl.argumentLengthCheck(arguments, 1, { header: 'CloseEvent constructor' }) + + type = webidl.converters.DOMString(type) + eventInitDict = webidl.converters.CloseEventInit(eventInitDict) + + super(type, eventInitDict) + + this.#eventInit = eventInitDict + } + + get wasClean () { + webidl.brandCheck(this, CloseEvent) + + return this.#eventInit.wasClean + } + + get code () { + webidl.brandCheck(this, CloseEvent) + + return this.#eventInit.code + } + + get reason () { + webidl.brandCheck(this, CloseEvent) + + return this.#eventInit.reason + } +} + +// https://html.spec.whatwg.org/multipage/webappapis.html#the-errorevent-interface +class ErrorEvent extends Event { + #eventInit + + constructor (type, eventInitDict) { + webidl.argumentLengthCheck(arguments, 1, { header: 'ErrorEvent constructor' }) + + super(type, eventInitDict) + + type = webidl.converters.DOMString(type) + eventInitDict = webidl.converters.ErrorEventInit(eventInitDict ?? {}) + + this.#eventInit = eventInitDict + } + + get message () { + webidl.brandCheck(this, ErrorEvent) + + return this.#eventInit.message + } + + get filename () { + webidl.brandCheck(this, ErrorEvent) + + return this.#eventInit.filename + } + + get lineno () { + webidl.brandCheck(this, ErrorEvent) + + return this.#eventInit.lineno + } + + get colno () { + webidl.brandCheck(this, ErrorEvent) + + return this.#eventInit.colno + } + + get error () { + webidl.brandCheck(this, ErrorEvent) + + return this.#eventInit.error + } +} + +Object.defineProperties(MessageEvent.prototype, { + [Symbol.toStringTag]: { + value: 'MessageEvent', + configurable: true + }, + data: kEnumerableProperty, + origin: kEnumerableProperty, + lastEventId: kEnumerableProperty, + source: kEnumerableProperty, + ports: kEnumerableProperty, + initMessageEvent: kEnumerableProperty +}) + +Object.defineProperties(CloseEvent.prototype, { + [Symbol.toStringTag]: { + value: 'CloseEvent', + configurable: true + }, + reason: kEnumerableProperty, + code: kEnumerableProperty, + wasClean: kEnumerableProperty +}) + +Object.defineProperties(ErrorEvent.prototype, { + [Symbol.toStringTag]: { + value: 'ErrorEvent', + configurable: true + }, + message: kEnumerableProperty, + filename: kEnumerableProperty, + lineno: kEnumerableProperty, + colno: kEnumerableProperty, + error: kEnumerableProperty +}) + +webidl.converters.MessagePort = webidl.interfaceConverter(MessagePort) + +webidl.converters['sequence'] = webidl.sequenceConverter( + webidl.converters.MessagePort +) + +const eventInit = [ + { + key: 'bubbles', + converter: webidl.converters.boolean, + defaultValue: false + }, + { + key: 'cancelable', + converter: webidl.converters.boolean, + defaultValue: false + }, + { + key: 'composed', + converter: webidl.converters.boolean, + defaultValue: false + } +] + +webidl.converters.MessageEventInit = webidl.dictionaryConverter([ + ...eventInit, + { + key: 'data', + converter: webidl.converters.any, + defaultValue: null + }, + { + key: 'origin', + converter: webidl.converters.USVString, + defaultValue: '' + }, + { + key: 'lastEventId', + converter: webidl.converters.DOMString, + defaultValue: '' + }, + { + key: 'source', + // Node doesn't implement WindowProxy or ServiceWorker, so the only + // valid value for source is a MessagePort. + converter: webidl.nullableConverter(webidl.converters.MessagePort), + defaultValue: null + }, + { + key: 'ports', + converter: webidl.converters['sequence'], + get defaultValue () { + return [] + } + } +]) + +webidl.converters.CloseEventInit = webidl.dictionaryConverter([ + ...eventInit, + { + key: 'wasClean', + converter: webidl.converters.boolean, + defaultValue: false + }, + { + key: 'code', + converter: webidl.converters['unsigned short'], + defaultValue: 0 + }, + { + key: 'reason', + converter: webidl.converters.USVString, + defaultValue: '' + } +]) + +webidl.converters.ErrorEventInit = webidl.dictionaryConverter([ + ...eventInit, + { + key: 'message', + converter: webidl.converters.DOMString, + defaultValue: '' + }, + { + key: 'filename', + converter: webidl.converters.USVString, + defaultValue: '' + }, + { + key: 'lineno', + converter: webidl.converters['unsigned long'], + defaultValue: 0 + }, + { + key: 'colno', + converter: webidl.converters['unsigned long'], + defaultValue: 0 + }, + { + key: 'error', + converter: webidl.converters.any + } +]) + +module.exports = { + MessageEvent, + CloseEvent, + ErrorEvent +} + + +/***/ }), + +/***/ 31237: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const { maxUnsigned16Bit } = __nccwpck_require__(45913) + +/** @type {import('crypto')} */ +let crypto +try { + crypto = __nccwpck_require__(76982) +} catch { + +} + +class WebsocketFrameSend { + /** + * @param {Buffer|undefined} data + */ + constructor (data) { + this.frameData = data + this.maskKey = crypto.randomBytes(4) + } + + createFrame (opcode) { + const bodyLength = this.frameData?.byteLength ?? 0 + + /** @type {number} */ + let payloadLength = bodyLength // 0-125 + let offset = 6 + + if (bodyLength > maxUnsigned16Bit) { + offset += 8 // payload length is next 8 bytes + payloadLength = 127 + } else if (bodyLength > 125) { + offset += 2 // payload length is next 2 bytes + payloadLength = 126 + } + + const buffer = Buffer.allocUnsafe(bodyLength + offset) + + // Clear first 2 bytes, everything else is overwritten + buffer[0] = buffer[1] = 0 + buffer[0] |= 0x80 // FIN + buffer[0] = (buffer[0] & 0xF0) + opcode // opcode + + /*! ws. MIT License. Einar Otto Stangvik */ + buffer[offset - 4] = this.maskKey[0] + buffer[offset - 3] = this.maskKey[1] + buffer[offset - 2] = this.maskKey[2] + buffer[offset - 1] = this.maskKey[3] + + buffer[1] = payloadLength + + if (payloadLength === 126) { + buffer.writeUInt16BE(bodyLength, 2) + } else if (payloadLength === 127) { + // Clear extended payload length + buffer[2] = buffer[3] = 0 + buffer.writeUIntBE(bodyLength, 4, 6) + } + + buffer[1] |= 0x80 // MASK + + // mask body + for (let i = 0; i < bodyLength; i++) { + buffer[offset + i] = this.frameData[i] ^ this.maskKey[i % 4] + } + + return buffer + } +} + +module.exports = { + WebsocketFrameSend +} + + +/***/ }), + +/***/ 43171: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const { Writable } = __nccwpck_require__(2203) +const diagnosticsChannel = __nccwpck_require__(31637) +const { parserStates, opcodes, states, emptyBuffer } = __nccwpck_require__(45913) +const { kReadyState, kSentClose, kResponse, kReceivedClose } = __nccwpck_require__(62933) +const { isValidStatusCode, failWebsocketConnection, websocketMessageReceived } = __nccwpck_require__(3574) +const { WebsocketFrameSend } = __nccwpck_require__(31237) + +// This code was influenced by ws released under the MIT license. +// Copyright (c) 2011 Einar Otto Stangvik +// Copyright (c) 2013 Arnout Kazemier and contributors +// Copyright (c) 2016 Luigi Pinca and contributors + +const channels = {} +channels.ping = diagnosticsChannel.channel('undici:websocket:ping') +channels.pong = diagnosticsChannel.channel('undici:websocket:pong') + +class ByteParser extends Writable { + #buffers = [] + #byteOffset = 0 + + #state = parserStates.INFO + + #info = {} + #fragments = [] + + constructor (ws) { + super() + + this.ws = ws + } + + /** + * @param {Buffer} chunk + * @param {() => void} callback + */ + _write (chunk, _, callback) { + this.#buffers.push(chunk) + this.#byteOffset += chunk.length + + this.run(callback) + } + + /** + * Runs whenever a new chunk is received. + * Callback is called whenever there are no more chunks buffering, + * or not enough bytes are buffered to parse. + */ + run (callback) { + while (true) { + if (this.#state === parserStates.INFO) { + // If there aren't enough bytes to parse the payload length, etc. + if (this.#byteOffset < 2) { + return callback() + } + + const buffer = this.consume(2) + + this.#info.fin = (buffer[0] & 0x80) !== 0 + this.#info.opcode = buffer[0] & 0x0F + + // If we receive a fragmented message, we use the type of the first + // frame to parse the full message as binary/text, when it's terminated + this.#info.originalOpcode ??= this.#info.opcode + + this.#info.fragmented = !this.#info.fin && this.#info.opcode !== opcodes.CONTINUATION + + if (this.#info.fragmented && this.#info.opcode !== opcodes.BINARY && this.#info.opcode !== opcodes.TEXT) { + // Only text and binary frames can be fragmented + failWebsocketConnection(this.ws, 'Invalid frame type was fragmented.') + return + } + + const payloadLength = buffer[1] & 0x7F + + if (payloadLength <= 125) { + this.#info.payloadLength = payloadLength + this.#state = parserStates.READ_DATA + } else if (payloadLength === 126) { + this.#state = parserStates.PAYLOADLENGTH_16 + } else if (payloadLength === 127) { + this.#state = parserStates.PAYLOADLENGTH_64 + } + + if (this.#info.fragmented && payloadLength > 125) { + // A fragmented frame can't be fragmented itself + failWebsocketConnection(this.ws, 'Fragmented frame exceeded 125 bytes.') + return + } else if ( + (this.#info.opcode === opcodes.PING || + this.#info.opcode === opcodes.PONG || + this.#info.opcode === opcodes.CLOSE) && + payloadLength > 125 + ) { + // Control frames can have a payload length of 125 bytes MAX + failWebsocketConnection(this.ws, 'Payload length for control frame exceeded 125 bytes.') + return + } else if (this.#info.opcode === opcodes.CLOSE) { + if (payloadLength === 1) { + failWebsocketConnection(this.ws, 'Received close frame with a 1-byte body.') + return + } + + const body = this.consume(payloadLength) + + this.#info.closeInfo = this.parseCloseBody(false, body) + + if (!this.ws[kSentClose]) { + // If an endpoint receives a Close frame and did not previously send a + // Close frame, the endpoint MUST send a Close frame in response. (When + // sending a Close frame in response, the endpoint typically echos the + // status code it received.) + const body = Buffer.allocUnsafe(2) + body.writeUInt16BE(this.#info.closeInfo.code, 0) + const closeFrame = new WebsocketFrameSend(body) + + this.ws[kResponse].socket.write( + closeFrame.createFrame(opcodes.CLOSE), + (err) => { + if (!err) { + this.ws[kSentClose] = true + } + } + ) + } + + // Upon either sending or receiving a Close control frame, it is said + // that _The WebSocket Closing Handshake is Started_ and that the + // WebSocket connection is in the CLOSING state. + this.ws[kReadyState] = states.CLOSING + this.ws[kReceivedClose] = true + + this.end() + + return + } else if (this.#info.opcode === opcodes.PING) { + // Upon receipt of a Ping frame, an endpoint MUST send a Pong frame in + // response, unless it already received a Close frame. + // A Pong frame sent in response to a Ping frame must have identical + // "Application data" + + const body = this.consume(payloadLength) + + if (!this.ws[kReceivedClose]) { + const frame = new WebsocketFrameSend(body) + + this.ws[kResponse].socket.write(frame.createFrame(opcodes.PONG)) + + if (channels.ping.hasSubscribers) { + channels.ping.publish({ + payload: body + }) + } + } + + this.#state = parserStates.INFO + + if (this.#byteOffset > 0) { + continue + } else { + callback() + return + } + } else if (this.#info.opcode === opcodes.PONG) { + // A Pong frame MAY be sent unsolicited. This serves as a + // unidirectional heartbeat. A response to an unsolicited Pong frame is + // not expected. + + const body = this.consume(payloadLength) + + if (channels.pong.hasSubscribers) { + channels.pong.publish({ + payload: body + }) + } + + if (this.#byteOffset > 0) { + continue + } else { + callback() + return + } + } + } else if (this.#state === parserStates.PAYLOADLENGTH_16) { + if (this.#byteOffset < 2) { + return callback() + } + + const buffer = this.consume(2) + + this.#info.payloadLength = buffer.readUInt16BE(0) + this.#state = parserStates.READ_DATA + } else if (this.#state === parserStates.PAYLOADLENGTH_64) { + if (this.#byteOffset < 8) { + return callback() + } + + const buffer = this.consume(8) + const upper = buffer.readUInt32BE(0) + + // 2^31 is the maxinimum bytes an arraybuffer can contain + // on 32-bit systems. Although, on 64-bit systems, this is + // 2^53-1 bytes. + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Invalid_array_length + // https://source.chromium.org/chromium/chromium/src/+/main:v8/src/common/globals.h;drc=1946212ac0100668f14eb9e2843bdd846e510a1e;bpv=1;bpt=1;l=1275 + // https://source.chromium.org/chromium/chromium/src/+/main:v8/src/objects/js-array-buffer.h;l=34;drc=1946212ac0100668f14eb9e2843bdd846e510a1e + if (upper > 2 ** 31 - 1) { + failWebsocketConnection(this.ws, 'Received payload length > 2^31 bytes.') + return + } + + const lower = buffer.readUInt32BE(4) + + this.#info.payloadLength = (upper << 8) + lower + this.#state = parserStates.READ_DATA + } else if (this.#state === parserStates.READ_DATA) { + if (this.#byteOffset < this.#info.payloadLength) { + // If there is still more data in this chunk that needs to be read + return callback() + } else if (this.#byteOffset >= this.#info.payloadLength) { + // If the server sent multiple frames in a single chunk + + const body = this.consume(this.#info.payloadLength) + + this.#fragments.push(body) + + // If the frame is unfragmented, or a fragmented frame was terminated, + // a message was received + if (!this.#info.fragmented || (this.#info.fin && this.#info.opcode === opcodes.CONTINUATION)) { + const fullMessage = Buffer.concat(this.#fragments) + + websocketMessageReceived(this.ws, this.#info.originalOpcode, fullMessage) + + this.#info = {} + this.#fragments.length = 0 + } + + this.#state = parserStates.INFO + } + } + + if (this.#byteOffset > 0) { + continue + } else { + callback() + break + } + } + } + + /** + * Take n bytes from the buffered Buffers + * @param {number} n + * @returns {Buffer|null} + */ + consume (n) { + if (n > this.#byteOffset) { + return null + } else if (n === 0) { + return emptyBuffer + } + + if (this.#buffers[0].length === n) { + this.#byteOffset -= this.#buffers[0].length + return this.#buffers.shift() + } + + const buffer = Buffer.allocUnsafe(n) + let offset = 0 + + while (offset !== n) { + const next = this.#buffers[0] + const { length } = next + + if (length + offset === n) { + buffer.set(this.#buffers.shift(), offset) + break + } else if (length + offset > n) { + buffer.set(next.subarray(0, n - offset), offset) + this.#buffers[0] = next.subarray(n - offset) + break + } else { + buffer.set(this.#buffers.shift(), offset) + offset += next.length + } + } + + this.#byteOffset -= n + + return buffer + } + + parseCloseBody (onlyCode, data) { + // https://datatracker.ietf.org/doc/html/rfc6455#section-7.1.5 + /** @type {number|undefined} */ + let code + + if (data.length >= 2) { + // _The WebSocket Connection Close Code_ is + // defined as the status code (Section 7.4) contained in the first Close + // control frame received by the application + code = data.readUInt16BE(0) + } + + if (onlyCode) { + if (!isValidStatusCode(code)) { + return null + } + + return { code } + } + + // https://datatracker.ietf.org/doc/html/rfc6455#section-7.1.6 + /** @type {Buffer} */ + let reason = data.subarray(2) + + // Remove BOM + if (reason[0] === 0xEF && reason[1] === 0xBB && reason[2] === 0xBF) { + reason = reason.subarray(3) + } + + if (code !== undefined && !isValidStatusCode(code)) { + return null + } + + try { + // TODO: optimize this + reason = new TextDecoder('utf-8', { fatal: true }).decode(reason) + } catch { + return null + } + + return { code, reason } + } + + get closingInfo () { + return this.#info.closeInfo + } +} + +module.exports = { + ByteParser +} + + +/***/ }), + +/***/ 62933: +/***/ ((module) => { + +"use strict"; + + +module.exports = { + kWebSocketURL: Symbol('url'), + kReadyState: Symbol('ready state'), + kController: Symbol('controller'), + kResponse: Symbol('response'), + kBinaryType: Symbol('binary type'), + kSentClose: Symbol('sent close'), + kReceivedClose: Symbol('received close'), + kByteParser: Symbol('byte parser') +} + + +/***/ }), + +/***/ 3574: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const { kReadyState, kController, kResponse, kBinaryType, kWebSocketURL } = __nccwpck_require__(62933) +const { states, opcodes } = __nccwpck_require__(45913) +const { MessageEvent, ErrorEvent } = __nccwpck_require__(46255) + +/* globals Blob */ + +/** + * @param {import('./websocket').WebSocket} ws + */ +function isEstablished (ws) { + // If the server's response is validated as provided for above, it is + // said that _The WebSocket Connection is Established_ and that the + // WebSocket Connection is in the OPEN state. + return ws[kReadyState] === states.OPEN +} + +/** + * @param {import('./websocket').WebSocket} ws + */ +function isClosing (ws) { + // Upon either sending or receiving a Close control frame, it is said + // that _The WebSocket Closing Handshake is Started_ and that the + // WebSocket connection is in the CLOSING state. + return ws[kReadyState] === states.CLOSING +} + +/** + * @param {import('./websocket').WebSocket} ws + */ +function isClosed (ws) { + return ws[kReadyState] === states.CLOSED +} + +/** + * @see https://dom.spec.whatwg.org/#concept-event-fire + * @param {string} e + * @param {EventTarget} target + * @param {EventInit | undefined} eventInitDict + */ +function fireEvent (e, target, eventConstructor = Event, eventInitDict) { + // 1. If eventConstructor is not given, then let eventConstructor be Event. + + // 2. Let event be the result of creating an event given eventConstructor, + // in the relevant realm of target. + // 3. Initialize event’s type attribute to e. + const event = new eventConstructor(e, eventInitDict) // eslint-disable-line new-cap + + // 4. Initialize any other IDL attributes of event as described in the + // invocation of this algorithm. + + // 5. Return the result of dispatching event at target, with legacy target + // override flag set if set. + target.dispatchEvent(event) +} + +/** + * @see https://websockets.spec.whatwg.org/#feedback-from-the-protocol + * @param {import('./websocket').WebSocket} ws + * @param {number} type Opcode + * @param {Buffer} data application data + */ +function websocketMessageReceived (ws, type, data) { + // 1. If ready state is not OPEN (1), then return. + if (ws[kReadyState] !== states.OPEN) { + return + } + + // 2. Let dataForEvent be determined by switching on type and binary type: + let dataForEvent + + if (type === opcodes.TEXT) { + // -> type indicates that the data is Text + // a new DOMString containing data + try { + dataForEvent = new TextDecoder('utf-8', { fatal: true }).decode(data) + } catch { + failWebsocketConnection(ws, 'Received invalid UTF-8 in text frame.') + return + } + } else if (type === opcodes.BINARY) { + if (ws[kBinaryType] === 'blob') { + // -> type indicates that the data is Binary and binary type is "blob" + // a new Blob object, created in the relevant Realm of the WebSocket + // object, that represents data as its raw data + dataForEvent = new Blob([data]) + } else { + // -> type indicates that the data is Binary and binary type is "arraybuffer" + // a new ArrayBuffer object, created in the relevant Realm of the + // WebSocket object, whose contents are data + dataForEvent = new Uint8Array(data).buffer + } + } + + // 3. Fire an event named message at the WebSocket object, using MessageEvent, + // with the origin attribute initialized to the serialization of the WebSocket + // object’s url's origin, and the data attribute initialized to dataForEvent. + fireEvent('message', ws, MessageEvent, { + origin: ws[kWebSocketURL].origin, + data: dataForEvent + }) +} + +/** + * @see https://datatracker.ietf.org/doc/html/rfc6455 + * @see https://datatracker.ietf.org/doc/html/rfc2616 + * @see https://bugs.chromium.org/p/chromium/issues/detail?id=398407 + * @param {string} protocol + */ +function isValidSubprotocol (protocol) { + // If present, this value indicates one + // or more comma-separated subprotocol the client wishes to speak, + // ordered by preference. The elements that comprise this value + // MUST be non-empty strings with characters in the range U+0021 to + // U+007E not including separator characters as defined in + // [RFC2616] and MUST all be unique strings. + if (protocol.length === 0) { + return false + } + + for (const char of protocol) { + const code = char.charCodeAt(0) + + if ( + code < 0x21 || + code > 0x7E || + char === '(' || + char === ')' || + char === '<' || + char === '>' || + char === '@' || + char === ',' || + char === ';' || + char === ':' || + char === '\\' || + char === '"' || + char === '/' || + char === '[' || + char === ']' || + char === '?' || + char === '=' || + char === '{' || + char === '}' || + code === 32 || // SP + code === 9 // HT + ) { + return false + } + } + + return true +} + +/** + * @see https://datatracker.ietf.org/doc/html/rfc6455#section-7-4 + * @param {number} code + */ +function isValidStatusCode (code) { + if (code >= 1000 && code < 1015) { + return ( + code !== 1004 && // reserved + code !== 1005 && // "MUST NOT be set as a status code" + code !== 1006 // "MUST NOT be set as a status code" + ) + } + + return code >= 3000 && code <= 4999 +} + +/** + * @param {import('./websocket').WebSocket} ws + * @param {string|undefined} reason + */ +function failWebsocketConnection (ws, reason) { + const { [kController]: controller, [kResponse]: response } = ws + + controller.abort() + + if (response?.socket && !response.socket.destroyed) { + response.socket.destroy() + } + + if (reason) { + fireEvent('error', ws, ErrorEvent, { + error: new Error(reason) + }) + } +} + +module.exports = { + isEstablished, + isClosing, + isClosed, + fireEvent, + isValidSubprotocol, + isValidStatusCode, + failWebsocketConnection, + websocketMessageReceived +} + + +/***/ }), + +/***/ 55171: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const { webidl } = __nccwpck_require__(74222) +const { DOMException } = __nccwpck_require__(87326) +const { URLSerializer } = __nccwpck_require__(94322) +const { getGlobalOrigin } = __nccwpck_require__(75628) +const { staticPropertyDescriptors, states, opcodes, emptyBuffer } = __nccwpck_require__(45913) +const { + kWebSocketURL, + kReadyState, + kController, + kBinaryType, + kResponse, + kSentClose, + kByteParser +} = __nccwpck_require__(62933) +const { isEstablished, isClosing, isValidSubprotocol, failWebsocketConnection, fireEvent } = __nccwpck_require__(3574) +const { establishWebSocketConnection } = __nccwpck_require__(68550) +const { WebsocketFrameSend } = __nccwpck_require__(31237) +const { ByteParser } = __nccwpck_require__(43171) +const { kEnumerableProperty, isBlobLike } = __nccwpck_require__(3440) +const { getGlobalDispatcher } = __nccwpck_require__(32581) +const { types } = __nccwpck_require__(39023) + +let experimentalWarned = false + +// https://websockets.spec.whatwg.org/#interface-definition +class WebSocket extends EventTarget { + #events = { + open: null, + error: null, + close: null, + message: null + } + + #bufferedAmount = 0 + #protocol = '' + #extensions = '' + + /** + * @param {string} url + * @param {string|string[]} protocols + */ + constructor (url, protocols = []) { + super() + + webidl.argumentLengthCheck(arguments, 1, { header: 'WebSocket constructor' }) + + if (!experimentalWarned) { + experimentalWarned = true + process.emitWarning('WebSockets are experimental, expect them to change at any time.', { + code: 'UNDICI-WS' + }) + } + + const options = webidl.converters['DOMString or sequence or WebSocketInit'](protocols) + + url = webidl.converters.USVString(url) + protocols = options.protocols + + // 1. Let baseURL be this's relevant settings object's API base URL. + const baseURL = getGlobalOrigin() + + // 1. Let urlRecord be the result of applying the URL parser to url with baseURL. + let urlRecord + + try { + urlRecord = new URL(url, baseURL) + } catch (e) { + // 3. If urlRecord is failure, then throw a "SyntaxError" DOMException. + throw new DOMException(e, 'SyntaxError') + } + + // 4. If urlRecord’s scheme is "http", then set urlRecord’s scheme to "ws". + if (urlRecord.protocol === 'http:') { + urlRecord.protocol = 'ws:' + } else if (urlRecord.protocol === 'https:') { + // 5. Otherwise, if urlRecord’s scheme is "https", set urlRecord’s scheme to "wss". + urlRecord.protocol = 'wss:' + } + + // 6. If urlRecord’s scheme is not "ws" or "wss", then throw a "SyntaxError" DOMException. + if (urlRecord.protocol !== 'ws:' && urlRecord.protocol !== 'wss:') { + throw new DOMException( + `Expected a ws: or wss: protocol, got ${urlRecord.protocol}`, + 'SyntaxError' + ) + } + + // 7. If urlRecord’s fragment is non-null, then throw a "SyntaxError" + // DOMException. + if (urlRecord.hash || urlRecord.href.endsWith('#')) { + throw new DOMException('Got fragment', 'SyntaxError') + } + + // 8. If protocols is a string, set protocols to a sequence consisting + // of just that string. + if (typeof protocols === 'string') { + protocols = [protocols] + } + + // 9. If any of the values in protocols occur more than once or otherwise + // fail to match the requirements for elements that comprise the value + // of `Sec-WebSocket-Protocol` fields as defined by The WebSocket + // protocol, then throw a "SyntaxError" DOMException. + if (protocols.length !== new Set(protocols.map(p => p.toLowerCase())).size) { + throw new DOMException('Invalid Sec-WebSocket-Protocol value', 'SyntaxError') + } + + if (protocols.length > 0 && !protocols.every(p => isValidSubprotocol(p))) { + throw new DOMException('Invalid Sec-WebSocket-Protocol value', 'SyntaxError') + } + + // 10. Set this's url to urlRecord. + this[kWebSocketURL] = new URL(urlRecord.href) + + // 11. Let client be this's relevant settings object. + + // 12. Run this step in parallel: + + // 1. Establish a WebSocket connection given urlRecord, protocols, + // and client. + this[kController] = establishWebSocketConnection( + urlRecord, + protocols, + this, + (response) => this.#onConnectionEstablished(response), + options + ) + + // Each WebSocket object has an associated ready state, which is a + // number representing the state of the connection. Initially it must + // be CONNECTING (0). + this[kReadyState] = WebSocket.CONNECTING + + // The extensions attribute must initially return the empty string. + + // The protocol attribute must initially return the empty string. + + // Each WebSocket object has an associated binary type, which is a + // BinaryType. Initially it must be "blob". + this[kBinaryType] = 'blob' + } + + /** + * @see https://websockets.spec.whatwg.org/#dom-websocket-close + * @param {number|undefined} code + * @param {string|undefined} reason + */ + close (code = undefined, reason = undefined) { + webidl.brandCheck(this, WebSocket) + + if (code !== undefined) { + code = webidl.converters['unsigned short'](code, { clamp: true }) + } + + if (reason !== undefined) { + reason = webidl.converters.USVString(reason) + } + + // 1. If code is present, but is neither an integer equal to 1000 nor an + // integer in the range 3000 to 4999, inclusive, throw an + // "InvalidAccessError" DOMException. + if (code !== undefined) { + if (code !== 1000 && (code < 3000 || code > 4999)) { + throw new DOMException('invalid code', 'InvalidAccessError') + } + } + + let reasonByteLength = 0 + + // 2. If reason is present, then run these substeps: + if (reason !== undefined) { + // 1. Let reasonBytes be the result of encoding reason. + // 2. If reasonBytes is longer than 123 bytes, then throw a + // "SyntaxError" DOMException. + reasonByteLength = Buffer.byteLength(reason) + + if (reasonByteLength > 123) { + throw new DOMException( + `Reason must be less than 123 bytes; received ${reasonByteLength}`, + 'SyntaxError' + ) + } + } + + // 3. Run the first matching steps from the following list: + if (this[kReadyState] === WebSocket.CLOSING || this[kReadyState] === WebSocket.CLOSED) { + // If this's ready state is CLOSING (2) or CLOSED (3) + // Do nothing. + } else if (!isEstablished(this)) { + // If the WebSocket connection is not yet established + // Fail the WebSocket connection and set this's ready state + // to CLOSING (2). + failWebsocketConnection(this, 'Connection was closed before it was established.') + this[kReadyState] = WebSocket.CLOSING + } else if (!isClosing(this)) { + // If the WebSocket closing handshake has not yet been started + // Start the WebSocket closing handshake and set this's ready + // state to CLOSING (2). + // - If neither code nor reason is present, the WebSocket Close + // message must not have a body. + // - If code is present, then the status code to use in the + // WebSocket Close message must be the integer given by code. + // - If reason is also present, then reasonBytes must be + // provided in the Close message after the status code. + + const frame = new WebsocketFrameSend() + + // If neither code nor reason is present, the WebSocket Close + // message must not have a body. + + // If code is present, then the status code to use in the + // WebSocket Close message must be the integer given by code. + if (code !== undefined && reason === undefined) { + frame.frameData = Buffer.allocUnsafe(2) + frame.frameData.writeUInt16BE(code, 0) + } else if (code !== undefined && reason !== undefined) { + // If reason is also present, then reasonBytes must be + // provided in the Close message after the status code. + frame.frameData = Buffer.allocUnsafe(2 + reasonByteLength) + frame.frameData.writeUInt16BE(code, 0) + // the body MAY contain UTF-8-encoded data with value /reason/ + frame.frameData.write(reason, 2, 'utf-8') + } else { + frame.frameData = emptyBuffer + } + + /** @type {import('stream').Duplex} */ + const socket = this[kResponse].socket + + socket.write(frame.createFrame(opcodes.CLOSE), (err) => { + if (!err) { + this[kSentClose] = true + } + }) + + // Upon either sending or receiving a Close control frame, it is said + // that _The WebSocket Closing Handshake is Started_ and that the + // WebSocket connection is in the CLOSING state. + this[kReadyState] = states.CLOSING + } else { + // Otherwise + // Set this's ready state to CLOSING (2). + this[kReadyState] = WebSocket.CLOSING + } + } + + /** + * @see https://websockets.spec.whatwg.org/#dom-websocket-send + * @param {NodeJS.TypedArray|ArrayBuffer|Blob|string} data + */ + send (data) { + webidl.brandCheck(this, WebSocket) + + webidl.argumentLengthCheck(arguments, 1, { header: 'WebSocket.send' }) + + data = webidl.converters.WebSocketSendData(data) + + // 1. If this's ready state is CONNECTING, then throw an + // "InvalidStateError" DOMException. + if (this[kReadyState] === WebSocket.CONNECTING) { + throw new DOMException('Sent before connected.', 'InvalidStateError') + } + + // 2. Run the appropriate set of steps from the following list: + // https://datatracker.ietf.org/doc/html/rfc6455#section-6.1 + // https://datatracker.ietf.org/doc/html/rfc6455#section-5.2 + + if (!isEstablished(this) || isClosing(this)) { + return + } + + /** @type {import('stream').Duplex} */ + const socket = this[kResponse].socket + + // If data is a string + if (typeof data === 'string') { + // If the WebSocket connection is established and the WebSocket + // closing handshake has not yet started, then the user agent + // must send a WebSocket Message comprised of the data argument + // using a text frame opcode; if the data cannot be sent, e.g. + // because it would need to be buffered but the buffer is full, + // the user agent must flag the WebSocket as full and then close + // the WebSocket connection. Any invocation of this method with a + // string argument that does not throw an exception must increase + // the bufferedAmount attribute by the number of bytes needed to + // express the argument as UTF-8. + + const value = Buffer.from(data) + const frame = new WebsocketFrameSend(value) + const buffer = frame.createFrame(opcodes.TEXT) + + this.#bufferedAmount += value.byteLength + socket.write(buffer, () => { + this.#bufferedAmount -= value.byteLength + }) + } else if (types.isArrayBuffer(data)) { + // If the WebSocket connection is established, and the WebSocket + // closing handshake has not yet started, then the user agent must + // send a WebSocket Message comprised of data using a binary frame + // opcode; if the data cannot be sent, e.g. because it would need + // to be buffered but the buffer is full, the user agent must flag + // the WebSocket as full and then close the WebSocket connection. + // The data to be sent is the data stored in the buffer described + // by the ArrayBuffer object. Any invocation of this method with an + // ArrayBuffer argument that does not throw an exception must + // increase the bufferedAmount attribute by the length of the + // ArrayBuffer in bytes. + + const value = Buffer.from(data) + const frame = new WebsocketFrameSend(value) + const buffer = frame.createFrame(opcodes.BINARY) + + this.#bufferedAmount += value.byteLength + socket.write(buffer, () => { + this.#bufferedAmount -= value.byteLength + }) + } else if (ArrayBuffer.isView(data)) { + // If the WebSocket connection is established, and the WebSocket + // closing handshake has not yet started, then the user agent must + // send a WebSocket Message comprised of data using a binary frame + // opcode; if the data cannot be sent, e.g. because it would need to + // be buffered but the buffer is full, the user agent must flag the + // WebSocket as full and then close the WebSocket connection. The + // data to be sent is the data stored in the section of the buffer + // described by the ArrayBuffer object that data references. Any + // invocation of this method with this kind of argument that does + // not throw an exception must increase the bufferedAmount attribute + // by the length of data’s buffer in bytes. + + const ab = Buffer.from(data, data.byteOffset, data.byteLength) + + const frame = new WebsocketFrameSend(ab) + const buffer = frame.createFrame(opcodes.BINARY) + + this.#bufferedAmount += ab.byteLength + socket.write(buffer, () => { + this.#bufferedAmount -= ab.byteLength + }) + } else if (isBlobLike(data)) { + // If the WebSocket connection is established, and the WebSocket + // closing handshake has not yet started, then the user agent must + // send a WebSocket Message comprised of data using a binary frame + // opcode; if the data cannot be sent, e.g. because it would need to + // be buffered but the buffer is full, the user agent must flag the + // WebSocket as full and then close the WebSocket connection. The data + // to be sent is the raw data represented by the Blob object. Any + // invocation of this method with a Blob argument that does not throw + // an exception must increase the bufferedAmount attribute by the size + // of the Blob object’s raw data, in bytes. + + const frame = new WebsocketFrameSend() + + data.arrayBuffer().then((ab) => { + const value = Buffer.from(ab) + frame.frameData = value + const buffer = frame.createFrame(opcodes.BINARY) + + this.#bufferedAmount += value.byteLength + socket.write(buffer, () => { + this.#bufferedAmount -= value.byteLength + }) + }) + } + } + + get readyState () { + webidl.brandCheck(this, WebSocket) + + // The readyState getter steps are to return this's ready state. + return this[kReadyState] + } + + get bufferedAmount () { + webidl.brandCheck(this, WebSocket) + + return this.#bufferedAmount + } + + get url () { + webidl.brandCheck(this, WebSocket) + + // The url getter steps are to return this's url, serialized. + return URLSerializer(this[kWebSocketURL]) + } + + get extensions () { + webidl.brandCheck(this, WebSocket) + + return this.#extensions + } + + get protocol () { + webidl.brandCheck(this, WebSocket) + + return this.#protocol + } + + get onopen () { + webidl.brandCheck(this, WebSocket) + + return this.#events.open + } + + set onopen (fn) { + webidl.brandCheck(this, WebSocket) + + if (this.#events.open) { + this.removeEventListener('open', this.#events.open) + } + + if (typeof fn === 'function') { + this.#events.open = fn + this.addEventListener('open', fn) + } else { + this.#events.open = null + } + } + + get onerror () { + webidl.brandCheck(this, WebSocket) + + return this.#events.error + } + + set onerror (fn) { + webidl.brandCheck(this, WebSocket) + + if (this.#events.error) { + this.removeEventListener('error', this.#events.error) + } + + if (typeof fn === 'function') { + this.#events.error = fn + this.addEventListener('error', fn) + } else { + this.#events.error = null + } + } + + get onclose () { + webidl.brandCheck(this, WebSocket) + + return this.#events.close + } + + set onclose (fn) { + webidl.brandCheck(this, WebSocket) + + if (this.#events.close) { + this.removeEventListener('close', this.#events.close) + } + + if (typeof fn === 'function') { + this.#events.close = fn + this.addEventListener('close', fn) + } else { + this.#events.close = null + } + } + + get onmessage () { + webidl.brandCheck(this, WebSocket) + + return this.#events.message + } + + set onmessage (fn) { + webidl.brandCheck(this, WebSocket) + + if (this.#events.message) { + this.removeEventListener('message', this.#events.message) + } + + if (typeof fn === 'function') { + this.#events.message = fn + this.addEventListener('message', fn) + } else { + this.#events.message = null + } + } + + get binaryType () { + webidl.brandCheck(this, WebSocket) + + return this[kBinaryType] + } + + set binaryType (type) { + webidl.brandCheck(this, WebSocket) + + if (type !== 'blob' && type !== 'arraybuffer') { + this[kBinaryType] = 'blob' + } else { + this[kBinaryType] = type + } + } + + /** + * @see https://websockets.spec.whatwg.org/#feedback-from-the-protocol + */ + #onConnectionEstablished (response) { + // processResponse is called when the "response’s header list has been received and initialized." + // once this happens, the connection is open + this[kResponse] = response + + const parser = new ByteParser(this) + parser.on('drain', function onParserDrain () { + this.ws[kResponse].socket.resume() + }) + + response.socket.ws = this + this[kByteParser] = parser + + // 1. Change the ready state to OPEN (1). + this[kReadyState] = states.OPEN + + // 2. Change the extensions attribute’s value to the extensions in use, if + // it is not the null value. + // https://datatracker.ietf.org/doc/html/rfc6455#section-9.1 + const extensions = response.headersList.get('sec-websocket-extensions') + + if (extensions !== null) { + this.#extensions = extensions + } + + // 3. Change the protocol attribute’s value to the subprotocol in use, if + // it is not the null value. + // https://datatracker.ietf.org/doc/html/rfc6455#section-1.9 + const protocol = response.headersList.get('sec-websocket-protocol') + + if (protocol !== null) { + this.#protocol = protocol + } + + // 4. Fire an event named open at the WebSocket object. + fireEvent('open', this) + } +} + +// https://websockets.spec.whatwg.org/#dom-websocket-connecting +WebSocket.CONNECTING = WebSocket.prototype.CONNECTING = states.CONNECTING +// https://websockets.spec.whatwg.org/#dom-websocket-open +WebSocket.OPEN = WebSocket.prototype.OPEN = states.OPEN +// https://websockets.spec.whatwg.org/#dom-websocket-closing +WebSocket.CLOSING = WebSocket.prototype.CLOSING = states.CLOSING +// https://websockets.spec.whatwg.org/#dom-websocket-closed +WebSocket.CLOSED = WebSocket.prototype.CLOSED = states.CLOSED + +Object.defineProperties(WebSocket.prototype, { + CONNECTING: staticPropertyDescriptors, + OPEN: staticPropertyDescriptors, + CLOSING: staticPropertyDescriptors, + CLOSED: staticPropertyDescriptors, + url: kEnumerableProperty, + readyState: kEnumerableProperty, + bufferedAmount: kEnumerableProperty, + onopen: kEnumerableProperty, + onerror: kEnumerableProperty, + onclose: kEnumerableProperty, + close: kEnumerableProperty, + onmessage: kEnumerableProperty, + binaryType: kEnumerableProperty, + send: kEnumerableProperty, + extensions: kEnumerableProperty, + protocol: kEnumerableProperty, + [Symbol.toStringTag]: { + value: 'WebSocket', + writable: false, + enumerable: false, + configurable: true + } +}) + +Object.defineProperties(WebSocket, { + CONNECTING: staticPropertyDescriptors, + OPEN: staticPropertyDescriptors, + CLOSING: staticPropertyDescriptors, + CLOSED: staticPropertyDescriptors +}) + +webidl.converters['sequence'] = webidl.sequenceConverter( + webidl.converters.DOMString +) + +webidl.converters['DOMString or sequence'] = function (V) { + if (webidl.util.Type(V) === 'Object' && Symbol.iterator in V) { + return webidl.converters['sequence'](V) + } + + return webidl.converters.DOMString(V) +} + +// This implements the propsal made in https://github.com/whatwg/websockets/issues/42 +webidl.converters.WebSocketInit = webidl.dictionaryConverter([ + { + key: 'protocols', + converter: webidl.converters['DOMString or sequence'], + get defaultValue () { + return [] + } + }, + { + key: 'dispatcher', + converter: (V) => V, + get defaultValue () { + return getGlobalDispatcher() + } + }, + { + key: 'headers', + converter: webidl.nullableConverter(webidl.converters.HeadersInit) + } +]) + +webidl.converters['DOMString or sequence or WebSocketInit'] = function (V) { + if (webidl.util.Type(V) === 'Object' && !(Symbol.iterator in V)) { + return webidl.converters.WebSocketInit(V) + } + + return { protocols: webidl.converters['DOMString or sequence'](V) } +} + +webidl.converters.WebSocketSendData = function (V) { + if (webidl.util.Type(V) === 'Object') { + if (isBlobLike(V)) { + return webidl.converters.Blob(V, { strict: false }) + } + + if (ArrayBuffer.isView(V) || types.isAnyArrayBuffer(V)) { + return webidl.converters.BufferSource(V) + } + } + + return webidl.converters.USVString(V) +} + +module.exports = { + WebSocket +} + + +/***/ }), + +/***/ 25351: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +var xtend = __nccwpck_require__(80869) +var inherits = __nccwpck_require__(39598) + +module.exports = unherit + +// Create a custom constructor which can be modified without affecting the +// original class. +function unherit(Super) { + var result + var key + var value + + inherits(Of, Super) + inherits(From, Of) + + // Clone values. + result = Of.prototype + + for (key in result) { + value = result[key] + + if (value && typeof value === 'object') { + result[key] = 'concat' in value ? value.concat() : xtend(value) + } + } + + return Of + + // Constructor accepting a single argument, which itself is an `arguments` + // object. + function From(parameters) { + return Super.apply(this, parameters) + } + + // Constructor accepting variadic arguments. + function Of() { + if (!(this instanceof Of)) { + return new From(arguments) + } + + return Super.apply(this, arguments) + } +} + + +/***/ }), + +/***/ 10617: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +var visit = __nccwpck_require__(53267) + +module.exports = removePosition + +function removePosition(node, force) { + visit(node, force ? hard : soft) + return node +} + +function hard(node) { + delete node.position +} + +function soft(node) { + node.position = undefined +} + + +/***/ }), + +/***/ 65475: +/***/ ((module) => { + +"use strict"; + + +module.exports = convert + +function convert(test) { + if (test == null) { + return ok + } + + if (typeof test === 'string') { + return typeFactory(test) + } + + if (typeof test === 'object') { + return 'length' in test ? anyFactory(test) : allFactory(test) + } + + if (typeof test === 'function') { + return test + } + + throw new Error('Expected function, string, or object as test') +} + +// Utility assert each property in `test` is represented in `node`, and each +// values are strictly equal. +function allFactory(test) { + return all + + function all(node) { + var key + + for (key in test) { + if (node[key] !== test[key]) return false + } + + return true + } +} + +function anyFactory(tests) { + var checks = [] + var index = -1 + + while (++index < tests.length) { + checks[index] = convert(tests[index]) + } + + return any + + function any() { + var index = -1 + + while (++index < checks.length) { + if (checks[index].apply(this, arguments)) { + return true + } + } + + return false + } +} + +// Utility to convert a string into a function which checks a given node’s type +// for said string. +function typeFactory(test) { + return type + + function type(node) { + return Boolean(node && node.type === test) + } +} + +// Utility to return true. +function ok() { + return true +} + + +/***/ }), + +/***/ 8692: +/***/ ((module) => { + +module.exports = color +function color(d) { + return '\u001B[33m' + d + '\u001B[39m' +} + + +/***/ }), + +/***/ 56035: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +module.exports = visitParents + +var convert = __nccwpck_require__(65475) +var color = __nccwpck_require__(8692) + +var CONTINUE = true +var SKIP = 'skip' +var EXIT = false + +visitParents.CONTINUE = CONTINUE +visitParents.SKIP = SKIP +visitParents.EXIT = EXIT + +function visitParents(tree, test, visitor, reverse) { + var step + var is + + if (typeof test === 'function' && typeof visitor !== 'function') { + reverse = visitor + visitor = test + test = null + } + + is = convert(test) + step = reverse ? -1 : 1 + + factory(tree, null, [])() + + function factory(node, index, parents) { + var value = typeof node === 'object' && node !== null ? node : {} + var name + + if (typeof value.type === 'string') { + name = + typeof value.tagName === 'string' + ? value.tagName + : typeof value.name === 'string' + ? value.name + : undefined + + visit.displayName = + 'node (' + color(value.type + (name ? '<' + name + '>' : '')) + ')' + } + + return visit + + function visit() { + var grandparents = parents.concat(node) + var result = [] + var subresult + var offset + + if (!test || is(node, index, parents[parents.length - 1] || null)) { + result = toResult(visitor(node, parents)) + + if (result[0] === EXIT) { + return result + } + } + + if (node.children && result[0] !== SKIP) { + offset = (reverse ? node.children.length : -1) + step + + while (offset > -1 && offset < node.children.length) { + subresult = factory(node.children[offset], offset, grandparents)() + + if (subresult[0] === EXIT) { + return subresult + } + + offset = + typeof subresult[1] === 'number' ? subresult[1] : offset + step + } + } + + return result + } + } +} + +function toResult(value) { + if (value !== null && typeof value === 'object' && 'length' in value) { + return value + } + + if (typeof value === 'number') { + return [CONTINUE, value] + } + + return [value] +} + + +/***/ }), + +/***/ 53267: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +module.exports = visit + +var visitParents = __nccwpck_require__(56035) + +var CONTINUE = visitParents.CONTINUE +var SKIP = visitParents.SKIP +var EXIT = visitParents.EXIT + +visit.CONTINUE = CONTINUE +visit.SKIP = SKIP +visit.EXIT = EXIT + +function visit(tree, test, visitor, reverse) { + if (typeof test === 'function' && typeof visitor !== 'function') { + reverse = visitor + visitor = test + test = null + } + + visitParents(tree, test, overload, reverse) + + function overload(node, parents) { + var parent = parents[parents.length - 1] + var index = parent ? parent.children.indexOf(node) : null + return visitor(node, index, parent) + } +} + + +/***/ }), + +/***/ 34808: +/***/ ((module) => { + +"use strict"; + + +module.exports = factory + +function factory(file) { + var value = String(file) + var indices = [] + var search = /\r?\n|\r/g + + while (search.exec(value)) { + indices.push(search.lastIndex) + } + + indices.push(value.length + 1) + + return { + toPoint: offsetToPoint, + toPosition: offsetToPoint, + toOffset: pointToOffset + } + + // Get the line and column-based `point` for `offset` in the bound indices. + function offsetToPoint(offset) { + var index = -1 + + if (offset > -1 && offset < indices[indices.length - 1]) { + while (++index < indices.length) { + if (indices[index] > offset) { + return { + line: index + 1, + column: offset - (indices[index - 1] || 0) + 1, + offset: offset + } + } + } + } + + return {} + } + + // Get the `offset` for a line and column-based `point` in the bound + // indices. + function pointToOffset(point) { + var line = point && point.line + var column = point && point.column + var offset + + if (!isNaN(line) && !isNaN(column) && line - 1 in indices) { + offset = (indices[line - 2] || 0) + column - 1 || 0 + } + + return offset > -1 && offset < indices[indices.length - 1] ? offset : -1 + } +} + + +/***/ }), + +/***/ 80869: +/***/ ((module) => { + +module.exports = extend + +var hasOwnProperty = Object.prototype.hasOwnProperty; + +function extend() { + var target = {} + + for (var i = 0; i < arguments.length; i++) { + var source = arguments[i] + + for (var key in source) { + if (hasOwnProperty.call(source, key)) { + target[key] = source[key] + } + } + } + + return target +} + + +/***/ }), + +/***/ 42613: +/***/ ((module) => { + +"use strict"; +module.exports = require("assert"); + +/***/ }), + +/***/ 90290: +/***/ ((module) => { + +"use strict"; +module.exports = require("async_hooks"); + +/***/ }), + +/***/ 20181: +/***/ ((module) => { + +"use strict"; +module.exports = require("buffer"); + +/***/ }), + +/***/ 35317: +/***/ ((module) => { + +"use strict"; +module.exports = require("child_process"); + +/***/ }), + +/***/ 64236: +/***/ ((module) => { + +"use strict"; +module.exports = require("console"); + +/***/ }), + +/***/ 49140: +/***/ ((module) => { + +"use strict"; +module.exports = require("constants"); + +/***/ }), + +/***/ 76982: +/***/ ((module) => { + +"use strict"; +module.exports = require("crypto"); + +/***/ }), + +/***/ 31637: +/***/ ((module) => { + +"use strict"; +module.exports = require("diagnostics_channel"); + +/***/ }), + +/***/ 24434: +/***/ ((module) => { + +"use strict"; +module.exports = require("events"); + +/***/ }), + +/***/ 79896: +/***/ ((module) => { + +"use strict"; +module.exports = require("fs"); + +/***/ }), + +/***/ 58611: +/***/ ((module) => { + +"use strict"; +module.exports = require("http"); + +/***/ }), + +/***/ 85675: +/***/ ((module) => { + +"use strict"; +module.exports = require("http2"); + +/***/ }), + +/***/ 65692: +/***/ ((module) => { + +"use strict"; +module.exports = require("https"); + +/***/ }), + +/***/ 69278: +/***/ ((module) => { + +"use strict"; +module.exports = require("net"); + +/***/ }), + +/***/ 77598: +/***/ ((module) => { + +"use strict"; +module.exports = require("node:crypto"); + +/***/ }), + +/***/ 78474: +/***/ ((module) => { + +"use strict"; +module.exports = require("node:events"); + +/***/ }), + +/***/ 57075: +/***/ ((module) => { + +"use strict"; +module.exports = require("node:stream"); + +/***/ }), + +/***/ 57975: +/***/ ((module) => { + +"use strict"; +module.exports = require("node:util"); + +/***/ }), + +/***/ 70857: +/***/ ((module) => { + +"use strict"; +module.exports = require("os"); + +/***/ }), + +/***/ 16928: +/***/ ((module) => { + +"use strict"; +module.exports = require("path"); + +/***/ }), + +/***/ 82987: +/***/ ((module) => { + +"use strict"; +module.exports = require("perf_hooks"); + +/***/ }), + +/***/ 83480: +/***/ ((module) => { + +"use strict"; +module.exports = require("querystring"); + +/***/ }), + +/***/ 2203: +/***/ ((module) => { + +"use strict"; +module.exports = require("stream"); + +/***/ }), + +/***/ 63774: +/***/ ((module) => { + +"use strict"; +module.exports = require("stream/web"); + +/***/ }), + +/***/ 13193: +/***/ ((module) => { + +"use strict"; +module.exports = require("string_decoder"); + +/***/ }), + +/***/ 53557: +/***/ ((module) => { + +"use strict"; +module.exports = require("timers"); + +/***/ }), + +/***/ 64756: +/***/ ((module) => { + +"use strict"; +module.exports = require("tls"); + +/***/ }), + +/***/ 52018: +/***/ ((module) => { + +"use strict"; +module.exports = require("tty"); + +/***/ }), + +/***/ 87016: +/***/ ((module) => { + +"use strict"; +module.exports = require("url"); + +/***/ }), + +/***/ 39023: +/***/ ((module) => { + +"use strict"; +module.exports = require("util"); + +/***/ }), + +/***/ 98253: +/***/ ((module) => { + +"use strict"; +module.exports = require("util/types"); + +/***/ }), + +/***/ 28167: +/***/ ((module) => { + +"use strict"; +module.exports = require("worker_threads"); + +/***/ }), + +/***/ 43106: +/***/ ((module) => { + +"use strict"; +module.exports = require("zlib"); + +/***/ }), + +/***/ 90147: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ value: true })); + +var picocolors = __nccwpck_require__(57336); +var jsTokens = __nccwpck_require__(85756); +var helperValidatorIdentifier = __nccwpck_require__(76599); + +function isColorSupported() { + return (typeof process === "object" && (process.env.FORCE_COLOR === "0" || process.env.FORCE_COLOR === "false") ? false : picocolors.isColorSupported + ); +} +const compose = (f, g) => v => f(g(v)); +function buildDefs(colors) { + return { + keyword: colors.cyan, + capitalized: colors.yellow, + jsxIdentifier: colors.yellow, + punctuator: colors.yellow, + number: colors.magenta, + string: colors.green, + regex: colors.magenta, + comment: colors.gray, + invalid: compose(compose(colors.white, colors.bgRed), colors.bold), + gutter: colors.gray, + marker: compose(colors.red, colors.bold), + message: compose(colors.red, colors.bold), + reset: colors.reset + }; +} +const defsOn = buildDefs(picocolors.createColors(true)); +const defsOff = buildDefs(picocolors.createColors(false)); +function getDefs(enabled) { + return enabled ? defsOn : defsOff; +} + +const sometimesKeywords = new Set(["as", "async", "from", "get", "of", "set"]); +const NEWLINE$1 = /\r\n|[\n\r\u2028\u2029]/; +const BRACKET = /^[()[\]{}]$/; +let tokenize; +{ + const JSX_TAG = /^[a-z][\w-]*$/i; + const getTokenType = function (token, offset, text) { + if (token.type === "name") { + if (helperValidatorIdentifier.isKeyword(token.value) || helperValidatorIdentifier.isStrictReservedWord(token.value, true) || sometimesKeywords.has(token.value)) { + return "keyword"; + } + if (JSX_TAG.test(token.value) && (text[offset - 1] === "<" || text.slice(offset - 2, offset) === " defs[type](str)).join("\n"); + } else { + highlighted += value; + } + } + return highlighted; +} + +let deprecationWarningShown = false; +const NEWLINE = /\r\n|[\n\r\u2028\u2029]/; +function getMarkerLines(loc, source, opts) { + const startLoc = Object.assign({ + column: 0, + line: -1 + }, loc.start); + const endLoc = Object.assign({}, startLoc, loc.end); + const { + linesAbove = 2, + linesBelow = 3 + } = opts || {}; + const startLine = startLoc.line; + const startColumn = startLoc.column; + const endLine = endLoc.line; + const endColumn = endLoc.column; + let start = Math.max(startLine - (linesAbove + 1), 0); + let end = Math.min(source.length, endLine + linesBelow); + if (startLine === -1) { + start = 0; + } + if (endLine === -1) { + end = source.length; + } + const lineDiff = endLine - startLine; + const markerLines = {}; + if (lineDiff) { + for (let i = 0; i <= lineDiff; i++) { + const lineNumber = i + startLine; + if (!startColumn) { + markerLines[lineNumber] = true; + } else if (i === 0) { + const sourceLength = source[lineNumber - 1].length; + markerLines[lineNumber] = [startColumn, sourceLength - startColumn + 1]; + } else if (i === lineDiff) { + markerLines[lineNumber] = [0, endColumn]; + } else { + const sourceLength = source[lineNumber - i].length; + markerLines[lineNumber] = [0, sourceLength]; + } + } + } else { + if (startColumn === endColumn) { + if (startColumn) { + markerLines[startLine] = [startColumn, 0]; + } else { + markerLines[startLine] = true; + } + } else { + markerLines[startLine] = [startColumn, endColumn - startColumn]; + } + } + return { + start, + end, + markerLines + }; +} +function codeFrameColumns(rawLines, loc, opts = {}) { + const shouldHighlight = opts.forceColor || isColorSupported() && opts.highlightCode; + const defs = getDefs(shouldHighlight); + const lines = rawLines.split(NEWLINE); + const { + start, + end, + markerLines + } = getMarkerLines(loc, lines, opts); + const hasColumns = loc.start && typeof loc.start.column === "number"; + const numberMaxWidth = String(end).length; + const highlightedLines = shouldHighlight ? highlight(rawLines) : rawLines; + let frame = highlightedLines.split(NEWLINE, end).slice(start, end).map((line, index) => { + const number = start + 1 + index; + const paddedNumber = ` ${number}`.slice(-numberMaxWidth); + const gutter = ` ${paddedNumber} |`; + const hasMarker = markerLines[number]; + const lastMarkerLine = !markerLines[number + 1]; + if (hasMarker) { + let markerLine = ""; + if (Array.isArray(hasMarker)) { + const markerSpacing = line.slice(0, Math.max(hasMarker[0] - 1, 0)).replace(/[^\t]/g, " "); + const numberOfMarkers = hasMarker[1] || 1; + markerLine = ["\n ", defs.gutter(gutter.replace(/\d/g, " ")), " ", markerSpacing, defs.marker("^").repeat(numberOfMarkers)].join(""); + if (lastMarkerLine && opts.message) { + markerLine += " " + defs.message(opts.message); + } + } + return [defs.marker(">"), defs.gutter(gutter), line.length > 0 ? ` ${line}` : "", markerLine].join(""); + } else { + return ` ${defs.gutter(gutter)}${line.length > 0 ? ` ${line}` : ""}`; + } + }).join("\n"); + if (opts.message && !hasColumns) { + frame = `${" ".repeat(numberMaxWidth + 1)}${opts.message}\n${frame}`; + } + if (shouldHighlight) { + return defs.reset(frame); + } else { + return frame; + } +} +function index (rawLines, lineNumber, colNumber, opts = {}) { + if (!deprecationWarningShown) { + deprecationWarningShown = true; + const message = "Passing lineNumber and colNumber is deprecated to @babel/code-frame. Please use `codeFrameColumns`."; + if (process.emitWarning) { + process.emitWarning(message, "DeprecationWarning"); + } else { + const deprecationError = new Error(message); + deprecationError.name = "DeprecationWarning"; + console.warn(new Error(message)); + } + } + colNumber = Math.max(colNumber, 0); + const location = { + start: { + column: colNumber, + line: lineNumber + } + }; + return codeFrameColumns(rawLines, location, opts); +} + +exports.codeFrameColumns = codeFrameColumns; +exports["default"] = index; +exports.highlight = highlight; +//# sourceMappingURL=index.js.map + + +/***/ }), + +/***/ 11669: +/***/ ((__unused_webpack_module, exports) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports["default"] = void 0; +class Buffer { + constructor(map, indentChar) { + this._map = null; + this._buf = ""; + this._str = ""; + this._appendCount = 0; + this._last = 0; + this._queue = []; + this._queueCursor = 0; + this._canMarkIdName = true; + this._indentChar = ""; + this._fastIndentations = []; + this._position = { + line: 1, + column: 0 + }; + this._sourcePosition = { + identifierName: undefined, + identifierNamePos: undefined, + line: undefined, + column: undefined, + filename: undefined + }; + this._map = map; + this._indentChar = indentChar; + for (let i = 0; i < 64; i++) { + this._fastIndentations.push(indentChar.repeat(i)); + } + this._allocQueue(); + } + _allocQueue() { + const queue = this._queue; + for (let i = 0; i < 16; i++) { + queue.push({ + char: 0, + repeat: 1, + line: undefined, + column: undefined, + identifierName: undefined, + identifierNamePos: undefined, + filename: "" + }); + } + } + _pushQueue(char, repeat, line, column, filename) { + const cursor = this._queueCursor; + if (cursor === this._queue.length) { + this._allocQueue(); + } + const item = this._queue[cursor]; + item.char = char; + item.repeat = repeat; + item.line = line; + item.column = column; + item.filename = filename; + this._queueCursor++; + } + _popQueue() { + if (this._queueCursor === 0) { + throw new Error("Cannot pop from empty queue"); + } + return this._queue[--this._queueCursor]; + } + get() { + this._flush(); + const map = this._map; + const result = { + code: (this._buf + this._str).trimRight(), + decodedMap: map == null ? void 0 : map.getDecoded(), + get __mergedMap() { + return this.map; + }, + get map() { + const resultMap = map ? map.get() : null; + result.map = resultMap; + return resultMap; + }, + set map(value) { + Object.defineProperty(result, "map", { + value, + writable: true + }); + }, + get rawMappings() { + const mappings = map == null ? void 0 : map.getRawMappings(); + result.rawMappings = mappings; + return mappings; + }, + set rawMappings(value) { + Object.defineProperty(result, "rawMappings", { + value, + writable: true + }); + } + }; + return result; + } + append(str, maybeNewline) { + this._flush(); + this._append(str, this._sourcePosition, maybeNewline); + } + appendChar(char) { + this._flush(); + this._appendChar(char, 1, this._sourcePosition); + } + queue(char) { + if (char === 10) { + while (this._queueCursor !== 0) { + const char = this._queue[this._queueCursor - 1].char; + if (char !== 32 && char !== 9) { + break; + } + this._queueCursor--; + } + } + const sourcePosition = this._sourcePosition; + this._pushQueue(char, 1, sourcePosition.line, sourcePosition.column, sourcePosition.filename); + } + queueIndentation(repeat) { + if (repeat === 0) return; + this._pushQueue(-1, repeat, undefined, undefined, undefined); + } + _flush() { + const queueCursor = this._queueCursor; + const queue = this._queue; + for (let i = 0; i < queueCursor; i++) { + const item = queue[i]; + this._appendChar(item.char, item.repeat, item); + } + this._queueCursor = 0; + } + _appendChar(char, repeat, sourcePos) { + this._last = char; + if (char === -1) { + const fastIndentation = this._fastIndentations[repeat]; + if (fastIndentation !== undefined) { + this._str += fastIndentation; + } else { + this._str += repeat > 1 ? this._indentChar.repeat(repeat) : this._indentChar; + } + } else { + this._str += repeat > 1 ? String.fromCharCode(char).repeat(repeat) : String.fromCharCode(char); + } + if (char !== 10) { + this._mark(sourcePos.line, sourcePos.column, sourcePos.identifierName, sourcePos.identifierNamePos, sourcePos.filename); + this._position.column += repeat; + } else { + this._position.line++; + this._position.column = 0; + } + if (this._canMarkIdName) { + sourcePos.identifierName = undefined; + sourcePos.identifierNamePos = undefined; + } + } + _append(str, sourcePos, maybeNewline) { + const len = str.length; + const position = this._position; + this._last = str.charCodeAt(len - 1); + if (++this._appendCount > 4096) { + +this._str; + this._buf += this._str; + this._str = str; + this._appendCount = 0; + } else { + this._str += str; + } + if (!maybeNewline && !this._map) { + position.column += len; + return; + } + const { + column, + identifierName, + identifierNamePos, + filename + } = sourcePos; + let line = sourcePos.line; + if ((identifierName != null || identifierNamePos != null) && this._canMarkIdName) { + sourcePos.identifierName = undefined; + sourcePos.identifierNamePos = undefined; + } + let i = str.indexOf("\n"); + let last = 0; + if (i !== 0) { + this._mark(line, column, identifierName, identifierNamePos, filename); + } + while (i !== -1) { + position.line++; + position.column = 0; + last = i + 1; + if (last < len && line !== undefined) { + this._mark(++line, 0, null, null, filename); + } + i = str.indexOf("\n", last); + } + position.column += len - last; + } + _mark(line, column, identifierName, identifierNamePos, filename) { + var _this$_map; + (_this$_map = this._map) == null || _this$_map.mark(this._position, line, column, identifierName, identifierNamePos, filename); + } + removeTrailingNewline() { + const queueCursor = this._queueCursor; + if (queueCursor !== 0 && this._queue[queueCursor - 1].char === 10) { + this._queueCursor--; + } + } + removeLastSemicolon() { + const queueCursor = this._queueCursor; + if (queueCursor !== 0 && this._queue[queueCursor - 1].char === 59) { + this._queueCursor--; + } + } + getLastChar() { + const queueCursor = this._queueCursor; + return queueCursor !== 0 ? this._queue[queueCursor - 1].char : this._last; + } + getNewlineCount() { + const queueCursor = this._queueCursor; + let count = 0; + if (queueCursor === 0) return this._last === 10 ? 1 : 0; + for (let i = queueCursor - 1; i >= 0; i--) { + if (this._queue[i].char !== 10) { + break; + } + count++; + } + return count === queueCursor && this._last === 10 ? count + 1 : count; + } + endsWithCharAndNewline() { + const queue = this._queue; + const queueCursor = this._queueCursor; + if (queueCursor !== 0) { + const lastCp = queue[queueCursor - 1].char; + if (lastCp !== 10) return; + if (queueCursor > 1) { + return queue[queueCursor - 2].char; + } else { + return this._last; + } + } + } + hasContent() { + return this._queueCursor !== 0 || !!this._last; + } + exactSource(loc, cb) { + if (!this._map) { + cb(); + return; + } + this.source("start", loc); + const identifierName = loc.identifierName; + const sourcePos = this._sourcePosition; + if (identifierName) { + this._canMarkIdName = false; + sourcePos.identifierName = identifierName; + } + cb(); + if (identifierName) { + this._canMarkIdName = true; + sourcePos.identifierName = undefined; + sourcePos.identifierNamePos = undefined; + } + this.source("end", loc); + } + source(prop, loc) { + if (!this._map) return; + this._normalizePosition(prop, loc, 0); + } + sourceWithOffset(prop, loc, columnOffset) { + if (!this._map) return; + this._normalizePosition(prop, loc, columnOffset); + } + _normalizePosition(prop, loc, columnOffset) { + const pos = loc[prop]; + const target = this._sourcePosition; + if (pos) { + target.line = pos.line; + target.column = Math.max(pos.column + columnOffset, 0); + target.filename = loc.filename; + } + } + getCurrentColumn() { + const queue = this._queue; + const queueCursor = this._queueCursor; + let lastIndex = -1; + let len = 0; + for (let i = 0; i < queueCursor; i++) { + const item = queue[i]; + if (item.char === 10) { + lastIndex = len; + } + len += item.repeat; + } + return lastIndex === -1 ? this._position.column + len : len - 1 - lastIndex; + } + getCurrentLine() { + let count = 0; + const queue = this._queue; + for (let i = 0; i < this._queueCursor; i++) { + if (queue[i].char === 10) { + count++; + } + } + return this._position.line + count; + } +} +exports["default"] = Buffer; + +//# sourceMappingURL=buffer.js.map + + +/***/ }), + +/***/ 82933: +/***/ ((__unused_webpack_module, exports) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports.BlockStatement = BlockStatement; +exports.Directive = Directive; +exports.DirectiveLiteral = DirectiveLiteral; +exports.File = File; +exports.InterpreterDirective = InterpreterDirective; +exports.Placeholder = Placeholder; +exports.Program = Program; +function File(node) { + if (node.program) { + this.print(node.program.interpreter); + } + this.print(node.program); +} +function Program(node) { + var _node$directives; + this.noIndentInnerCommentsHere(); + this.printInnerComments(); + const directivesLen = (_node$directives = node.directives) == null ? void 0 : _node$directives.length; + if (directivesLen) { + var _node$directives$trai; + const newline = node.body.length ? 2 : 1; + this.printSequence(node.directives, undefined, newline); + if (!((_node$directives$trai = node.directives[directivesLen - 1].trailingComments) != null && _node$directives$trai.length)) { + this.newline(newline); + } + } + this.printSequence(node.body); +} +function BlockStatement(node) { + var _node$directives2; + this.tokenChar(123); + const exit = this.enterDelimited(); + const directivesLen = (_node$directives2 = node.directives) == null ? void 0 : _node$directives2.length; + if (directivesLen) { + var _node$directives$trai2; + const newline = node.body.length ? 2 : 1; + this.printSequence(node.directives, true, newline); + if (!((_node$directives$trai2 = node.directives[directivesLen - 1].trailingComments) != null && _node$directives$trai2.length)) { + this.newline(newline); + } + } + this.printSequence(node.body, true); + exit(); + this.rightBrace(node); +} +function Directive(node) { + this.print(node.value); + this.semicolon(); +} +const unescapedSingleQuoteRE = /(?:^|[^\\])(?:\\\\)*'/; +const unescapedDoubleQuoteRE = /(?:^|[^\\])(?:\\\\)*"/; +function DirectiveLiteral(node) { + const raw = this.getPossibleRaw(node); + if (!this.format.minified && raw !== undefined) { + this.token(raw); + return; + } + const { + value + } = node; + if (!unescapedDoubleQuoteRE.test(value)) { + this.token(`"${value}"`); + } else if (!unescapedSingleQuoteRE.test(value)) { + this.token(`'${value}'`); + } else { + throw new Error("Malformed AST: it is not possible to print a directive containing" + " both unescaped single and double quotes."); + } +} +function InterpreterDirective(node) { + this.token(`#!${node.value}`); + this.newline(1, true); +} +function Placeholder(node) { + this.token("%%"); + this.print(node.name); + this.token("%%"); + if (node.expectedNode === "Statement") { + this.semicolon(); + } +} + +//# sourceMappingURL=base.js.map + + +/***/ }), + +/***/ 70926: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports.ClassAccessorProperty = ClassAccessorProperty; +exports.ClassBody = ClassBody; +exports.ClassExpression = exports.ClassDeclaration = ClassDeclaration; +exports.ClassMethod = ClassMethod; +exports.ClassPrivateMethod = ClassPrivateMethod; +exports.ClassPrivateProperty = ClassPrivateProperty; +exports.ClassProperty = ClassProperty; +exports.StaticBlock = StaticBlock; +exports._classMethodHead = _classMethodHead; +var _t = __nccwpck_require__(16535); +const { + isExportDefaultDeclaration, + isExportNamedDeclaration +} = _t; +function ClassDeclaration(node, parent) { + const inExport = isExportDefaultDeclaration(parent) || isExportNamedDeclaration(parent); + if (!inExport || !this._shouldPrintDecoratorsBeforeExport(parent)) { + this.printJoin(node.decorators); + } + if (node.declare) { + this.word("declare"); + this.space(); + } + if (node.abstract) { + this.word("abstract"); + this.space(); + } + this.word("class"); + if (node.id) { + this.space(); + this.print(node.id); + } + this.print(node.typeParameters); + if (node.superClass) { + this.space(); + this.word("extends"); + this.space(); + this.print(node.superClass); + this.print(node.superTypeParameters); + } + if (node.implements) { + this.space(); + this.word("implements"); + this.space(); + this.printList(node.implements); + } + this.space(); + this.print(node.body); +} +function ClassBody(node) { + this.tokenChar(123); + if (node.body.length === 0) { + this.tokenChar(125); + } else { + this.newline(); + const separator = classBodyEmptySemicolonsPrinter(this, node); + separator == null || separator(-1); + const exit = this.enterDelimited(); + this.printJoin(node.body, true, true, separator, true); + exit(); + if (!this.endsWith(10)) this.newline(); + this.rightBrace(node); + } +} +function classBodyEmptySemicolonsPrinter(printer, node) { + if (!printer.tokenMap || node.start == null || node.end == null) { + return null; + } + const indexes = printer.tokenMap.getIndexes(node); + if (!indexes) return null; + let k = 1; + let occurrenceCount = 0; + let nextLocIndex = 0; + const advanceNextLocIndex = () => { + while (nextLocIndex < node.body.length && node.body[nextLocIndex].start == null) { + nextLocIndex++; + } + }; + advanceNextLocIndex(); + return i => { + if (nextLocIndex <= i) { + nextLocIndex = i + 1; + advanceNextLocIndex(); + } + const end = nextLocIndex === node.body.length ? node.end : node.body[nextLocIndex].start; + let tok; + while (k < indexes.length && printer.tokenMap.matchesOriginal(tok = printer._tokens[indexes[k]], ";") && tok.start < end) { + printer.token(";", undefined, occurrenceCount++); + k++; + } + }; +} +function ClassProperty(node) { + this.printJoin(node.decorators); + if (!node.static && !this.format.preserveFormat) { + var _node$key$loc; + const endLine = (_node$key$loc = node.key.loc) == null || (_node$key$loc = _node$key$loc.end) == null ? void 0 : _node$key$loc.line; + if (endLine) this.catchUp(endLine); + } + this.tsPrintClassMemberModifiers(node); + if (node.computed) { + this.tokenChar(91); + this.print(node.key); + this.tokenChar(93); + } else { + this._variance(node); + this.print(node.key); + } + if (node.optional) { + this.tokenChar(63); + } + if (node.definite) { + this.tokenChar(33); + } + this.print(node.typeAnnotation); + if (node.value) { + this.space(); + this.tokenChar(61); + this.space(); + this.print(node.value); + } + this.semicolon(); +} +function ClassAccessorProperty(node) { + var _node$key$loc2; + this.printJoin(node.decorators); + const endLine = (_node$key$loc2 = node.key.loc) == null || (_node$key$loc2 = _node$key$loc2.end) == null ? void 0 : _node$key$loc2.line; + if (endLine) this.catchUp(endLine); + this.tsPrintClassMemberModifiers(node); + this.word("accessor", true); + this.space(); + if (node.computed) { + this.tokenChar(91); + this.print(node.key); + this.tokenChar(93); + } else { + this._variance(node); + this.print(node.key); + } + if (node.optional) { + this.tokenChar(63); + } + if (node.definite) { + this.tokenChar(33); + } + this.print(node.typeAnnotation); + if (node.value) { + this.space(); + this.tokenChar(61); + this.space(); + this.print(node.value); + } + this.semicolon(); +} +function ClassPrivateProperty(node) { + this.printJoin(node.decorators); + this.tsPrintClassMemberModifiers(node); + this.print(node.key); + if (node.optional) { + this.tokenChar(63); + } + if (node.definite) { + this.tokenChar(33); + } + this.print(node.typeAnnotation); + if (node.value) { + this.space(); + this.tokenChar(61); + this.space(); + this.print(node.value); + } + this.semicolon(); +} +function ClassMethod(node) { + this._classMethodHead(node); + this.space(); + this.print(node.body); +} +function ClassPrivateMethod(node) { + this._classMethodHead(node); + this.space(); + this.print(node.body); +} +function _classMethodHead(node) { + this.printJoin(node.decorators); + if (!this.format.preserveFormat) { + var _node$key$loc3; + const endLine = (_node$key$loc3 = node.key.loc) == null || (_node$key$loc3 = _node$key$loc3.end) == null ? void 0 : _node$key$loc3.line; + if (endLine) this.catchUp(endLine); + } + this.tsPrintClassMemberModifiers(node); + this._methodHead(node); +} +function StaticBlock(node) { + this.word("static"); + this.space(); + this.tokenChar(123); + if (node.body.length === 0) { + this.tokenChar(125); + } else { + this.newline(); + this.printSequence(node.body, true); + this.rightBrace(node); + } +} + +//# sourceMappingURL=classes.js.map + + +/***/ }), + +/***/ 60617: +/***/ ((__unused_webpack_module, exports) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports.addDeprecatedGenerators = addDeprecatedGenerators; +function addDeprecatedGenerators(PrinterClass) { + { + const deprecatedBabel7Generators = { + Noop() {}, + TSExpressionWithTypeArguments(node) { + this.print(node.expression); + this.print(node.typeParameters); + }, + DecimalLiteral(node) { + const raw = this.getPossibleRaw(node); + if (!this.format.minified && raw !== undefined) { + this.word(raw); + return; + } + this.word(node.value + "m"); + } + }; + Object.assign(PrinterClass.prototype, deprecatedBabel7Generators); + } +} + +//# sourceMappingURL=deprecated.js.map + + +/***/ }), + +/***/ 33361: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports.LogicalExpression = exports.BinaryExpression = exports.AssignmentExpression = AssignmentExpression; +exports.AssignmentPattern = AssignmentPattern; +exports.AwaitExpression = AwaitExpression; +exports.BindExpression = BindExpression; +exports.CallExpression = CallExpression; +exports.ConditionalExpression = ConditionalExpression; +exports.Decorator = Decorator; +exports.DoExpression = DoExpression; +exports.EmptyStatement = EmptyStatement; +exports.ExpressionStatement = ExpressionStatement; +exports.Import = Import; +exports.MemberExpression = MemberExpression; +exports.MetaProperty = MetaProperty; +exports.ModuleExpression = ModuleExpression; +exports.NewExpression = NewExpression; +exports.OptionalCallExpression = OptionalCallExpression; +exports.OptionalMemberExpression = OptionalMemberExpression; +exports.ParenthesizedExpression = ParenthesizedExpression; +exports.PrivateName = PrivateName; +exports.SequenceExpression = SequenceExpression; +exports.Super = Super; +exports.ThisExpression = ThisExpression; +exports.UnaryExpression = UnaryExpression; +exports.UpdateExpression = UpdateExpression; +exports.V8IntrinsicIdentifier = V8IntrinsicIdentifier; +exports.YieldExpression = YieldExpression; +exports._shouldPrintDecoratorsBeforeExport = _shouldPrintDecoratorsBeforeExport; +var _t = __nccwpck_require__(16535); +var _index = __nccwpck_require__(95460); +const { + isCallExpression, + isLiteral, + isMemberExpression, + isNewExpression, + isPattern +} = _t; +function UnaryExpression(node) { + const { + operator + } = node; + if (operator === "void" || operator === "delete" || operator === "typeof" || operator === "throw") { + this.word(operator); + this.space(); + } else { + this.token(operator); + } + this.print(node.argument); +} +function DoExpression(node) { + if (node.async) { + this.word("async", true); + this.space(); + } + this.word("do"); + this.space(); + this.print(node.body); +} +function ParenthesizedExpression(node) { + this.tokenChar(40); + const exit = this.enterDelimited(); + this.print(node.expression); + exit(); + this.rightParens(node); +} +function UpdateExpression(node) { + if (node.prefix) { + this.token(node.operator); + this.print(node.argument); + } else { + this.print(node.argument, true); + this.token(node.operator); + } +} +function ConditionalExpression(node) { + this.print(node.test); + this.space(); + this.tokenChar(63); + this.space(); + this.print(node.consequent); + this.space(); + this.tokenChar(58); + this.space(); + this.print(node.alternate); +} +function NewExpression(node, parent) { + this.word("new"); + this.space(); + this.print(node.callee); + if (this.format.minified && node.arguments.length === 0 && !node.optional && !isCallExpression(parent, { + callee: node + }) && !isMemberExpression(parent) && !isNewExpression(parent)) { + return; + } + this.print(node.typeArguments); + { + this.print(node.typeParameters); + } + if (node.optional) { + this.token("?."); + } + if (node.arguments.length === 0 && this.tokenMap && !this.tokenMap.endMatches(node, ")")) { + return; + } + this.tokenChar(40); + const exit = this.enterDelimited(); + this.printList(node.arguments, this.shouldPrintTrailingComma(")")); + exit(); + this.rightParens(node); +} +function SequenceExpression(node) { + this.printList(node.expressions); +} +function ThisExpression() { + this.word("this"); +} +function Super() { + this.word("super"); +} +function _shouldPrintDecoratorsBeforeExport(node) { + if (typeof this.format.decoratorsBeforeExport === "boolean") { + return this.format.decoratorsBeforeExport; + } + return typeof node.start === "number" && node.start === node.declaration.start; +} +function Decorator(node) { + this.tokenChar(64); + this.print(node.expression); + this.newline(); +} +function OptionalMemberExpression(node) { + let { + computed + } = node; + const { + optional, + property + } = node; + this.print(node.object); + if (!computed && isMemberExpression(property)) { + throw new TypeError("Got a MemberExpression for MemberExpression property"); + } + if (isLiteral(property) && typeof property.value === "number") { + computed = true; + } + if (optional) { + this.token("?."); + } + if (computed) { + this.tokenChar(91); + this.print(property); + this.tokenChar(93); + } else { + if (!optional) { + this.tokenChar(46); + } + this.print(property); + } +} +function OptionalCallExpression(node) { + this.print(node.callee); + { + this.print(node.typeParameters); + } + if (node.optional) { + this.token("?."); + } + this.print(node.typeArguments); + this.tokenChar(40); + const exit = this.enterDelimited(); + this.printList(node.arguments); + exit(); + this.rightParens(node); +} +function CallExpression(node) { + this.print(node.callee); + this.print(node.typeArguments); + { + this.print(node.typeParameters); + } + this.tokenChar(40); + const exit = this.enterDelimited(); + this.printList(node.arguments, this.shouldPrintTrailingComma(")")); + exit(); + this.rightParens(node); +} +function Import() { + this.word("import"); +} +function AwaitExpression(node) { + this.word("await"); + if (node.argument) { + this.space(); + this.printTerminatorless(node.argument); + } +} +function YieldExpression(node) { + this.word("yield", true); + if (node.delegate) { + this.tokenChar(42); + if (node.argument) { + this.space(); + this.print(node.argument); + } + } else { + if (node.argument) { + this.space(); + this.printTerminatorless(node.argument); + } + } +} +function EmptyStatement() { + this.semicolon(true); +} +function ExpressionStatement(node) { + this.tokenContext |= _index.TokenContext.expressionStatement; + this.print(node.expression); + this.semicolon(); +} +function AssignmentPattern(node) { + this.print(node.left); + if (node.left.type === "Identifier" || isPattern(node.left)) { + if (node.left.optional) this.tokenChar(63); + this.print(node.left.typeAnnotation); + } + this.space(); + this.tokenChar(61); + this.space(); + this.print(node.right); +} +function AssignmentExpression(node) { + this.print(node.left); + this.space(); + if (node.operator === "in" || node.operator === "instanceof") { + this.word(node.operator); + } else { + this.token(node.operator); + this._endsWithDiv = node.operator === "/"; + } + this.space(); + this.print(node.right); +} +function BindExpression(node) { + this.print(node.object); + this.token("::"); + this.print(node.callee); +} +function MemberExpression(node) { + this.print(node.object); + if (!node.computed && isMemberExpression(node.property)) { + throw new TypeError("Got a MemberExpression for MemberExpression property"); + } + let computed = node.computed; + if (isLiteral(node.property) && typeof node.property.value === "number") { + computed = true; + } + if (computed) { + const exit = this.enterDelimited(); + this.tokenChar(91); + this.print(node.property); + this.tokenChar(93); + exit(); + } else { + this.tokenChar(46); + this.print(node.property); + } +} +function MetaProperty(node) { + this.print(node.meta); + this.tokenChar(46); + this.print(node.property); +} +function PrivateName(node) { + this.tokenChar(35); + this.print(node.id); +} +function V8IntrinsicIdentifier(node) { + this.tokenChar(37); + this.word(node.name); +} +function ModuleExpression(node) { + this.word("module", true); + this.space(); + this.tokenChar(123); + this.indent(); + const { + body + } = node; + if (body.body.length || body.directives.length) { + this.newline(); + } + this.print(body); + this.dedent(); + this.rightBrace(node); +} + +//# sourceMappingURL=expressions.js.map + + +/***/ }), + +/***/ 88408: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports.AnyTypeAnnotation = AnyTypeAnnotation; +exports.ArrayTypeAnnotation = ArrayTypeAnnotation; +exports.BooleanLiteralTypeAnnotation = BooleanLiteralTypeAnnotation; +exports.BooleanTypeAnnotation = BooleanTypeAnnotation; +exports.DeclareClass = DeclareClass; +exports.DeclareExportAllDeclaration = DeclareExportAllDeclaration; +exports.DeclareExportDeclaration = DeclareExportDeclaration; +exports.DeclareFunction = DeclareFunction; +exports.DeclareInterface = DeclareInterface; +exports.DeclareModule = DeclareModule; +exports.DeclareModuleExports = DeclareModuleExports; +exports.DeclareOpaqueType = DeclareOpaqueType; +exports.DeclareTypeAlias = DeclareTypeAlias; +exports.DeclareVariable = DeclareVariable; +exports.DeclaredPredicate = DeclaredPredicate; +exports.EmptyTypeAnnotation = EmptyTypeAnnotation; +exports.EnumBooleanBody = EnumBooleanBody; +exports.EnumBooleanMember = EnumBooleanMember; +exports.EnumDeclaration = EnumDeclaration; +exports.EnumDefaultedMember = EnumDefaultedMember; +exports.EnumNumberBody = EnumNumberBody; +exports.EnumNumberMember = EnumNumberMember; +exports.EnumStringBody = EnumStringBody; +exports.EnumStringMember = EnumStringMember; +exports.EnumSymbolBody = EnumSymbolBody; +exports.ExistsTypeAnnotation = ExistsTypeAnnotation; +exports.FunctionTypeAnnotation = FunctionTypeAnnotation; +exports.FunctionTypeParam = FunctionTypeParam; +exports.IndexedAccessType = IndexedAccessType; +exports.InferredPredicate = InferredPredicate; +exports.InterfaceDeclaration = InterfaceDeclaration; +exports.GenericTypeAnnotation = exports.ClassImplements = exports.InterfaceExtends = InterfaceExtends; +exports.InterfaceTypeAnnotation = InterfaceTypeAnnotation; +exports.IntersectionTypeAnnotation = IntersectionTypeAnnotation; +exports.MixedTypeAnnotation = MixedTypeAnnotation; +exports.NullLiteralTypeAnnotation = NullLiteralTypeAnnotation; +exports.NullableTypeAnnotation = NullableTypeAnnotation; +Object.defineProperty(exports, "NumberLiteralTypeAnnotation", ({ + enumerable: true, + get: function () { + return _types2.NumericLiteral; + } +})); +exports.NumberTypeAnnotation = NumberTypeAnnotation; +exports.ObjectTypeAnnotation = ObjectTypeAnnotation; +exports.ObjectTypeCallProperty = ObjectTypeCallProperty; +exports.ObjectTypeIndexer = ObjectTypeIndexer; +exports.ObjectTypeInternalSlot = ObjectTypeInternalSlot; +exports.ObjectTypeProperty = ObjectTypeProperty; +exports.ObjectTypeSpreadProperty = ObjectTypeSpreadProperty; +exports.OpaqueType = OpaqueType; +exports.OptionalIndexedAccessType = OptionalIndexedAccessType; +exports.QualifiedTypeIdentifier = QualifiedTypeIdentifier; +Object.defineProperty(exports, "StringLiteralTypeAnnotation", ({ + enumerable: true, + get: function () { + return _types2.StringLiteral; + } +})); +exports.StringTypeAnnotation = StringTypeAnnotation; +exports.SymbolTypeAnnotation = SymbolTypeAnnotation; +exports.ThisTypeAnnotation = ThisTypeAnnotation; +exports.TupleTypeAnnotation = TupleTypeAnnotation; +exports.TypeAlias = TypeAlias; +exports.TypeAnnotation = TypeAnnotation; +exports.TypeCastExpression = TypeCastExpression; +exports.TypeParameter = TypeParameter; +exports.TypeParameterDeclaration = exports.TypeParameterInstantiation = TypeParameterInstantiation; +exports.TypeofTypeAnnotation = TypeofTypeAnnotation; +exports.UnionTypeAnnotation = UnionTypeAnnotation; +exports.Variance = Variance; +exports.VoidTypeAnnotation = VoidTypeAnnotation; +exports._interfaceish = _interfaceish; +exports._variance = _variance; +var _t = __nccwpck_require__(16535); +var _modules = __nccwpck_require__(24119); +var _index = __nccwpck_require__(95460); +var _types2 = __nccwpck_require__(26659); +const { + isDeclareExportDeclaration, + isStatement +} = _t; +function AnyTypeAnnotation() { + this.word("any"); +} +function ArrayTypeAnnotation(node) { + this.print(node.elementType, true); + this.tokenChar(91); + this.tokenChar(93); +} +function BooleanTypeAnnotation() { + this.word("boolean"); +} +function BooleanLiteralTypeAnnotation(node) { + this.word(node.value ? "true" : "false"); +} +function NullLiteralTypeAnnotation() { + this.word("null"); +} +function DeclareClass(node, parent) { + if (!isDeclareExportDeclaration(parent)) { + this.word("declare"); + this.space(); + } + this.word("class"); + this.space(); + this._interfaceish(node); +} +function DeclareFunction(node, parent) { + if (!isDeclareExportDeclaration(parent)) { + this.word("declare"); + this.space(); + } + this.word("function"); + this.space(); + this.print(node.id); + this.print(node.id.typeAnnotation.typeAnnotation); + if (node.predicate) { + this.space(); + this.print(node.predicate); + } + this.semicolon(); +} +function InferredPredicate() { + this.tokenChar(37); + this.word("checks"); +} +function DeclaredPredicate(node) { + this.tokenChar(37); + this.word("checks"); + this.tokenChar(40); + this.print(node.value); + this.tokenChar(41); +} +function DeclareInterface(node) { + this.word("declare"); + this.space(); + this.InterfaceDeclaration(node); +} +function DeclareModule(node) { + this.word("declare"); + this.space(); + this.word("module"); + this.space(); + this.print(node.id); + this.space(); + this.print(node.body); +} +function DeclareModuleExports(node) { + this.word("declare"); + this.space(); + this.word("module"); + this.tokenChar(46); + this.word("exports"); + this.print(node.typeAnnotation); +} +function DeclareTypeAlias(node) { + this.word("declare"); + this.space(); + this.TypeAlias(node); +} +function DeclareOpaqueType(node, parent) { + if (!isDeclareExportDeclaration(parent)) { + this.word("declare"); + this.space(); + } + this.OpaqueType(node); +} +function DeclareVariable(node, parent) { + if (!isDeclareExportDeclaration(parent)) { + this.word("declare"); + this.space(); + } + this.word("var"); + this.space(); + this.print(node.id); + this.print(node.id.typeAnnotation); + this.semicolon(); +} +function DeclareExportDeclaration(node) { + this.word("declare"); + this.space(); + this.word("export"); + this.space(); + if (node.default) { + this.word("default"); + this.space(); + } + FlowExportDeclaration.call(this, node); +} +function DeclareExportAllDeclaration(node) { + this.word("declare"); + this.space(); + _modules.ExportAllDeclaration.call(this, node); +} +function EnumDeclaration(node) { + const { + id, + body + } = node; + this.word("enum"); + this.space(); + this.print(id); + this.print(body); +} +function enumExplicitType(context, name, hasExplicitType) { + if (hasExplicitType) { + context.space(); + context.word("of"); + context.space(); + context.word(name); + } + context.space(); +} +function enumBody(context, node) { + const { + members + } = node; + context.token("{"); + context.indent(); + context.newline(); + for (const member of members) { + context.print(member); + context.newline(); + } + if (node.hasUnknownMembers) { + context.token("..."); + context.newline(); + } + context.dedent(); + context.token("}"); +} +function EnumBooleanBody(node) { + const { + explicitType + } = node; + enumExplicitType(this, "boolean", explicitType); + enumBody(this, node); +} +function EnumNumberBody(node) { + const { + explicitType + } = node; + enumExplicitType(this, "number", explicitType); + enumBody(this, node); +} +function EnumStringBody(node) { + const { + explicitType + } = node; + enumExplicitType(this, "string", explicitType); + enumBody(this, node); +} +function EnumSymbolBody(node) { + enumExplicitType(this, "symbol", true); + enumBody(this, node); +} +function EnumDefaultedMember(node) { + const { + id + } = node; + this.print(id); + this.tokenChar(44); +} +function enumInitializedMember(context, node) { + context.print(node.id); + context.space(); + context.token("="); + context.space(); + context.print(node.init); + context.token(","); +} +function EnumBooleanMember(node) { + enumInitializedMember(this, node); +} +function EnumNumberMember(node) { + enumInitializedMember(this, node); +} +function EnumStringMember(node) { + enumInitializedMember(this, node); +} +function FlowExportDeclaration(node) { + if (node.declaration) { + const declar = node.declaration; + this.print(declar); + if (!isStatement(declar)) this.semicolon(); + } else { + this.tokenChar(123); + if (node.specifiers.length) { + this.space(); + this.printList(node.specifiers); + this.space(); + } + this.tokenChar(125); + if (node.source) { + this.space(); + this.word("from"); + this.space(); + this.print(node.source); + } + this.semicolon(); + } +} +function ExistsTypeAnnotation() { + this.tokenChar(42); +} +function FunctionTypeAnnotation(node, parent) { + this.print(node.typeParameters); + this.tokenChar(40); + if (node.this) { + this.word("this"); + this.tokenChar(58); + this.space(); + this.print(node.this.typeAnnotation); + if (node.params.length || node.rest) { + this.tokenChar(44); + this.space(); + } + } + this.printList(node.params); + if (node.rest) { + if (node.params.length) { + this.tokenChar(44); + this.space(); + } + this.token("..."); + this.print(node.rest); + } + this.tokenChar(41); + const type = parent == null ? void 0 : parent.type; + if (type != null && (type === "ObjectTypeCallProperty" || type === "ObjectTypeInternalSlot" || type === "DeclareFunction" || type === "ObjectTypeProperty" && parent.method)) { + this.tokenChar(58); + } else { + this.space(); + this.token("=>"); + } + this.space(); + this.print(node.returnType); +} +function FunctionTypeParam(node) { + this.print(node.name); + if (node.optional) this.tokenChar(63); + if (node.name) { + this.tokenChar(58); + this.space(); + } + this.print(node.typeAnnotation); +} +function InterfaceExtends(node) { + this.print(node.id); + this.print(node.typeParameters, true); +} +function _interfaceish(node) { + var _node$extends; + this.print(node.id); + this.print(node.typeParameters); + if ((_node$extends = node.extends) != null && _node$extends.length) { + this.space(); + this.word("extends"); + this.space(); + this.printList(node.extends); + } + if (node.type === "DeclareClass") { + var _node$mixins, _node$implements; + if ((_node$mixins = node.mixins) != null && _node$mixins.length) { + this.space(); + this.word("mixins"); + this.space(); + this.printList(node.mixins); + } + if ((_node$implements = node.implements) != null && _node$implements.length) { + this.space(); + this.word("implements"); + this.space(); + this.printList(node.implements); + } + } + this.space(); + this.print(node.body); +} +function _variance(node) { + var _node$variance; + const kind = (_node$variance = node.variance) == null ? void 0 : _node$variance.kind; + if (kind != null) { + if (kind === "plus") { + this.tokenChar(43); + } else if (kind === "minus") { + this.tokenChar(45); + } + } +} +function InterfaceDeclaration(node) { + this.word("interface"); + this.space(); + this._interfaceish(node); +} +function andSeparator(occurrenceCount) { + this.space(); + this.token("&", false, occurrenceCount); + this.space(); +} +function InterfaceTypeAnnotation(node) { + var _node$extends2; + this.word("interface"); + if ((_node$extends2 = node.extends) != null && _node$extends2.length) { + this.space(); + this.word("extends"); + this.space(); + this.printList(node.extends); + } + this.space(); + this.print(node.body); +} +function IntersectionTypeAnnotation(node) { + this.printJoin(node.types, undefined, undefined, andSeparator); +} +function MixedTypeAnnotation() { + this.word("mixed"); +} +function EmptyTypeAnnotation() { + this.word("empty"); +} +function NullableTypeAnnotation(node) { + this.tokenChar(63); + this.print(node.typeAnnotation); +} +function NumberTypeAnnotation() { + this.word("number"); +} +function StringTypeAnnotation() { + this.word("string"); +} +function ThisTypeAnnotation() { + this.word("this"); +} +function TupleTypeAnnotation(node) { + this.tokenChar(91); + this.printList(node.types); + this.tokenChar(93); +} +function TypeofTypeAnnotation(node) { + this.word("typeof"); + this.space(); + this.print(node.argument); +} +function TypeAlias(node) { + this.word("type"); + this.space(); + this.print(node.id); + this.print(node.typeParameters); + this.space(); + this.tokenChar(61); + this.space(); + this.print(node.right); + this.semicolon(); +} +function TypeAnnotation(node, parent) { + this.tokenChar(58); + this.space(); + if (parent.type === "ArrowFunctionExpression") { + this.tokenContext |= _index.TokenContext.arrowFlowReturnType; + } else if (node.optional) { + this.tokenChar(63); + } + this.print(node.typeAnnotation); +} +function TypeParameterInstantiation(node) { + this.tokenChar(60); + this.printList(node.params); + this.tokenChar(62); +} +function TypeParameter(node) { + this._variance(node); + this.word(node.name); + if (node.bound) { + this.print(node.bound); + } + if (node.default) { + this.space(); + this.tokenChar(61); + this.space(); + this.print(node.default); + } +} +function OpaqueType(node) { + this.word("opaque"); + this.space(); + this.word("type"); + this.space(); + this.print(node.id); + this.print(node.typeParameters); + if (node.supertype) { + this.tokenChar(58); + this.space(); + this.print(node.supertype); + } + if (node.impltype) { + this.space(); + this.tokenChar(61); + this.space(); + this.print(node.impltype); + } + this.semicolon(); +} +function ObjectTypeAnnotation(node) { + if (node.exact) { + this.token("{|"); + } else { + this.tokenChar(123); + } + const props = [...node.properties, ...(node.callProperties || []), ...(node.indexers || []), ...(node.internalSlots || [])]; + if (props.length) { + this.newline(); + this.space(); + this.printJoin(props, true, true, undefined, undefined, function addNewlines(leading) { + if (leading && !props[0]) return 1; + }, () => { + if (props.length !== 1 || node.inexact) { + this.tokenChar(44); + this.space(); + } + }); + this.space(); + } + if (node.inexact) { + this.indent(); + this.token("..."); + if (props.length) { + this.newline(); + } + this.dedent(); + } + if (node.exact) { + this.token("|}"); + } else { + this.tokenChar(125); + } +} +function ObjectTypeInternalSlot(node) { + if (node.static) { + this.word("static"); + this.space(); + } + this.tokenChar(91); + this.tokenChar(91); + this.print(node.id); + this.tokenChar(93); + this.tokenChar(93); + if (node.optional) this.tokenChar(63); + if (!node.method) { + this.tokenChar(58); + this.space(); + } + this.print(node.value); +} +function ObjectTypeCallProperty(node) { + if (node.static) { + this.word("static"); + this.space(); + } + this.print(node.value); +} +function ObjectTypeIndexer(node) { + if (node.static) { + this.word("static"); + this.space(); + } + this._variance(node); + this.tokenChar(91); + if (node.id) { + this.print(node.id); + this.tokenChar(58); + this.space(); + } + this.print(node.key); + this.tokenChar(93); + this.tokenChar(58); + this.space(); + this.print(node.value); +} +function ObjectTypeProperty(node) { + if (node.proto) { + this.word("proto"); + this.space(); + } + if (node.static) { + this.word("static"); + this.space(); + } + if (node.kind === "get" || node.kind === "set") { + this.word(node.kind); + this.space(); + } + this._variance(node); + this.print(node.key); + if (node.optional) this.tokenChar(63); + if (!node.method) { + this.tokenChar(58); + this.space(); + } + this.print(node.value); +} +function ObjectTypeSpreadProperty(node) { + this.token("..."); + this.print(node.argument); +} +function QualifiedTypeIdentifier(node) { + this.print(node.qualification); + this.tokenChar(46); + this.print(node.id); +} +function SymbolTypeAnnotation() { + this.word("symbol"); +} +function orSeparator(occurrenceCount) { + this.space(); + this.token("|", false, occurrenceCount); + this.space(); +} +function UnionTypeAnnotation(node) { + this.printJoin(node.types, undefined, undefined, orSeparator); +} +function TypeCastExpression(node) { + this.tokenChar(40); + this.print(node.expression); + this.print(node.typeAnnotation); + this.tokenChar(41); +} +function Variance(node) { + if (node.kind === "plus") { + this.tokenChar(43); + } else { + this.tokenChar(45); + } +} +function VoidTypeAnnotation() { + this.word("void"); +} +function IndexedAccessType(node) { + this.print(node.objectType, true); + this.tokenChar(91); + this.print(node.indexType); + this.tokenChar(93); +} +function OptionalIndexedAccessType(node) { + this.print(node.objectType); + if (node.optional) { + this.token("?."); + } + this.tokenChar(91); + this.print(node.indexType); + this.tokenChar(93); +} + +//# sourceMappingURL=flow.js.map + + +/***/ }), + +/***/ 39662: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +var _templateLiterals = __nccwpck_require__(97571); +Object.keys(_templateLiterals).forEach(function (key) { + if (key === "default" || key === "__esModule") return; + if (key in exports && exports[key] === _templateLiterals[key]) return; + Object.defineProperty(exports, key, { + enumerable: true, + get: function () { + return _templateLiterals[key]; + } + }); +}); +var _expressions = __nccwpck_require__(33361); +Object.keys(_expressions).forEach(function (key) { + if (key === "default" || key === "__esModule") return; + if (key in exports && exports[key] === _expressions[key]) return; + Object.defineProperty(exports, key, { + enumerable: true, + get: function () { + return _expressions[key]; + } + }); +}); +var _statements = __nccwpck_require__(40102); +Object.keys(_statements).forEach(function (key) { + if (key === "default" || key === "__esModule") return; + if (key in exports && exports[key] === _statements[key]) return; + Object.defineProperty(exports, key, { + enumerable: true, + get: function () { + return _statements[key]; + } + }); +}); +var _classes = __nccwpck_require__(70926); +Object.keys(_classes).forEach(function (key) { + if (key === "default" || key === "__esModule") return; + if (key in exports && exports[key] === _classes[key]) return; + Object.defineProperty(exports, key, { + enumerable: true, + get: function () { + return _classes[key]; + } + }); +}); +var _methods = __nccwpck_require__(31748); +Object.keys(_methods).forEach(function (key) { + if (key === "default" || key === "__esModule") return; + if (key in exports && exports[key] === _methods[key]) return; + Object.defineProperty(exports, key, { + enumerable: true, + get: function () { + return _methods[key]; + } + }); +}); +var _modules = __nccwpck_require__(24119); +Object.keys(_modules).forEach(function (key) { + if (key === "default" || key === "__esModule") return; + if (key in exports && exports[key] === _modules[key]) return; + Object.defineProperty(exports, key, { + enumerable: true, + get: function () { + return _modules[key]; + } + }); +}); +var _types = __nccwpck_require__(26659); +Object.keys(_types).forEach(function (key) { + if (key === "default" || key === "__esModule") return; + if (key in exports && exports[key] === _types[key]) return; + Object.defineProperty(exports, key, { + enumerable: true, + get: function () { + return _types[key]; + } + }); +}); +var _flow = __nccwpck_require__(88408); +Object.keys(_flow).forEach(function (key) { + if (key === "default" || key === "__esModule") return; + if (key in exports && exports[key] === _flow[key]) return; + Object.defineProperty(exports, key, { + enumerable: true, + get: function () { + return _flow[key]; + } + }); +}); +var _base = __nccwpck_require__(82933); +Object.keys(_base).forEach(function (key) { + if (key === "default" || key === "__esModule") return; + if (key in exports && exports[key] === _base[key]) return; + Object.defineProperty(exports, key, { + enumerable: true, + get: function () { + return _base[key]; + } + }); +}); +var _jsx = __nccwpck_require__(27099); +Object.keys(_jsx).forEach(function (key) { + if (key === "default" || key === "__esModule") return; + if (key in exports && exports[key] === _jsx[key]) return; + Object.defineProperty(exports, key, { + enumerable: true, + get: function () { + return _jsx[key]; + } + }); +}); +var _typescript = __nccwpck_require__(16975); +Object.keys(_typescript).forEach(function (key) { + if (key === "default" || key === "__esModule") return; + if (key in exports && exports[key] === _typescript[key]) return; + Object.defineProperty(exports, key, { + enumerable: true, + get: function () { + return _typescript[key]; + } + }); +}); + +//# sourceMappingURL=index.js.map + + +/***/ }), + +/***/ 27099: +/***/ ((__unused_webpack_module, exports) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports.JSXAttribute = JSXAttribute; +exports.JSXClosingElement = JSXClosingElement; +exports.JSXClosingFragment = JSXClosingFragment; +exports.JSXElement = JSXElement; +exports.JSXEmptyExpression = JSXEmptyExpression; +exports.JSXExpressionContainer = JSXExpressionContainer; +exports.JSXFragment = JSXFragment; +exports.JSXIdentifier = JSXIdentifier; +exports.JSXMemberExpression = JSXMemberExpression; +exports.JSXNamespacedName = JSXNamespacedName; +exports.JSXOpeningElement = JSXOpeningElement; +exports.JSXOpeningFragment = JSXOpeningFragment; +exports.JSXSpreadAttribute = JSXSpreadAttribute; +exports.JSXSpreadChild = JSXSpreadChild; +exports.JSXText = JSXText; +function JSXAttribute(node) { + this.print(node.name); + if (node.value) { + this.tokenChar(61); + this.print(node.value); + } +} +function JSXIdentifier(node) { + this.word(node.name); +} +function JSXNamespacedName(node) { + this.print(node.namespace); + this.tokenChar(58); + this.print(node.name); +} +function JSXMemberExpression(node) { + this.print(node.object); + this.tokenChar(46); + this.print(node.property); +} +function JSXSpreadAttribute(node) { + this.tokenChar(123); + this.token("..."); + this.print(node.argument); + this.rightBrace(node); +} +function JSXExpressionContainer(node) { + this.tokenChar(123); + this.print(node.expression); + this.rightBrace(node); +} +function JSXSpreadChild(node) { + this.tokenChar(123); + this.token("..."); + this.print(node.expression); + this.rightBrace(node); +} +function JSXText(node) { + const raw = this.getPossibleRaw(node); + if (raw !== undefined) { + this.token(raw, true); + } else { + this.token(node.value, true); + } +} +function JSXElement(node) { + const open = node.openingElement; + this.print(open); + if (open.selfClosing) return; + this.indent(); + for (const child of node.children) { + this.print(child); + } + this.dedent(); + this.print(node.closingElement); +} +function spaceSeparator() { + this.space(); +} +function JSXOpeningElement(node) { + this.tokenChar(60); + this.print(node.name); + { + if (node.typeArguments) { + this.print(node.typeArguments); + } + this.print(node.typeParameters); + } + if (node.attributes.length > 0) { + this.space(); + this.printJoin(node.attributes, undefined, undefined, spaceSeparator); + } + if (node.selfClosing) { + this.space(); + this.tokenChar(47); + } + this.tokenChar(62); +} +function JSXClosingElement(node) { + this.tokenChar(60); + this.tokenChar(47); + this.print(node.name); + this.tokenChar(62); +} +function JSXEmptyExpression() { + this.printInnerComments(); +} +function JSXFragment(node) { + this.print(node.openingFragment); + this.indent(); + for (const child of node.children) { + this.print(child); + } + this.dedent(); + this.print(node.closingFragment); +} +function JSXOpeningFragment() { + this.tokenChar(60); + this.tokenChar(62); +} +function JSXClosingFragment() { + this.token(" { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports.ArrowFunctionExpression = ArrowFunctionExpression; +exports.FunctionDeclaration = exports.FunctionExpression = FunctionExpression; +exports._functionHead = _functionHead; +exports._methodHead = _methodHead; +exports._param = _param; +exports._parameters = _parameters; +exports._params = _params; +exports._predicate = _predicate; +exports._shouldPrintArrowParamsParens = _shouldPrintArrowParamsParens; +var _t = __nccwpck_require__(16535); +var _index = __nccwpck_require__(95460); +const { + isIdentifier +} = _t; +function _params(node, idNode, parentNode) { + this.print(node.typeParameters); + const nameInfo = _getFuncIdName.call(this, idNode, parentNode); + if (nameInfo) { + this.sourceIdentifierName(nameInfo.name, nameInfo.pos); + } + this.tokenChar(40); + this._parameters(node.params, ")"); + const noLineTerminator = node.type === "ArrowFunctionExpression"; + this.print(node.returnType, noLineTerminator); + this._noLineTerminator = noLineTerminator; +} +function _parameters(parameters, endToken) { + const exit = this.enterDelimited(); + const trailingComma = this.shouldPrintTrailingComma(endToken); + const paramLength = parameters.length; + for (let i = 0; i < paramLength; i++) { + this._param(parameters[i]); + if (trailingComma || i < paramLength - 1) { + this.token(",", null, i); + this.space(); + } + } + this.token(endToken); + exit(); +} +function _param(parameter) { + this.printJoin(parameter.decorators); + this.print(parameter); + if (parameter.optional) { + this.tokenChar(63); + } + this.print(parameter.typeAnnotation); +} +function _methodHead(node) { + const kind = node.kind; + const key = node.key; + if (kind === "get" || kind === "set") { + this.word(kind); + this.space(); + } + if (node.async) { + this.word("async", true); + this.space(); + } + if (kind === "method" || kind === "init") { + if (node.generator) { + this.tokenChar(42); + } + } + if (node.computed) { + this.tokenChar(91); + this.print(key); + this.tokenChar(93); + } else { + this.print(key); + } + if (node.optional) { + this.tokenChar(63); + } + this._params(node, node.computed && node.key.type !== "StringLiteral" ? undefined : node.key, undefined); +} +function _predicate(node, noLineTerminatorAfter) { + if (node.predicate) { + if (!node.returnType) { + this.tokenChar(58); + } + this.space(); + this.print(node.predicate, noLineTerminatorAfter); + } +} +function _functionHead(node, parent) { + if (node.async) { + this.word("async"); + if (!this.format.preserveFormat) { + this._endsWithInnerRaw = false; + } + this.space(); + } + this.word("function"); + if (node.generator) { + if (!this.format.preserveFormat) { + this._endsWithInnerRaw = false; + } + this.tokenChar(42); + } + this.space(); + if (node.id) { + this.print(node.id); + } + this._params(node, node.id, parent); + if (node.type !== "TSDeclareFunction") { + this._predicate(node); + } +} +function FunctionExpression(node, parent) { + this._functionHead(node, parent); + this.space(); + this.print(node.body); +} +function ArrowFunctionExpression(node, parent) { + if (node.async) { + this.word("async", true); + this.space(); + } + if (this._shouldPrintArrowParamsParens(node)) { + this._params(node, undefined, parent); + } else { + this.print(node.params[0], true); + } + this._predicate(node, true); + this.space(); + this.printInnerComments(); + this.token("=>"); + this.space(); + this.tokenContext |= _index.TokenContext.arrowBody; + this.print(node.body); +} +function _shouldPrintArrowParamsParens(node) { + var _firstParam$leadingCo, _firstParam$trailingC; + if (node.params.length !== 1) return true; + if (node.typeParameters || node.returnType || node.predicate) { + return true; + } + const firstParam = node.params[0]; + if (!isIdentifier(firstParam) || firstParam.typeAnnotation || firstParam.optional || (_firstParam$leadingCo = firstParam.leadingComments) != null && _firstParam$leadingCo.length || (_firstParam$trailingC = firstParam.trailingComments) != null && _firstParam$trailingC.length) { + return true; + } + if (this.tokenMap) { + if (node.loc == null) return true; + if (this.tokenMap.findMatching(node, "(") !== null) return true; + const arrowToken = this.tokenMap.findMatching(node, "=>"); + if ((arrowToken == null ? void 0 : arrowToken.loc) == null) return true; + return arrowToken.loc.start.line !== node.loc.start.line; + } + if (this.format.retainLines) return true; + return false; +} +function _getFuncIdName(idNode, parent) { + let id = idNode; + if (!id && parent) { + const parentType = parent.type; + if (parentType === "VariableDeclarator") { + id = parent.id; + } else if (parentType === "AssignmentExpression" || parentType === "AssignmentPattern") { + id = parent.left; + } else if (parentType === "ObjectProperty" || parentType === "ClassProperty") { + if (!parent.computed || parent.key.type === "StringLiteral") { + id = parent.key; + } + } else if (parentType === "ClassPrivateProperty" || parentType === "ClassAccessorProperty") { + id = parent.key; + } + } + if (!id) return; + let nameInfo; + if (id.type === "Identifier") { + var _id$loc, _id$loc2; + nameInfo = { + pos: (_id$loc = id.loc) == null ? void 0 : _id$loc.start, + name: ((_id$loc2 = id.loc) == null ? void 0 : _id$loc2.identifierName) || id.name + }; + } else if (id.type === "PrivateName") { + var _id$loc3; + nameInfo = { + pos: (_id$loc3 = id.loc) == null ? void 0 : _id$loc3.start, + name: "#" + id.id.name + }; + } else if (id.type === "StringLiteral") { + var _id$loc4; + nameInfo = { + pos: (_id$loc4 = id.loc) == null ? void 0 : _id$loc4.start, + name: id.value + }; + } + return nameInfo; +} + +//# sourceMappingURL=methods.js.map + + +/***/ }), + +/***/ 24119: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports.ExportAllDeclaration = ExportAllDeclaration; +exports.ExportDefaultDeclaration = ExportDefaultDeclaration; +exports.ExportDefaultSpecifier = ExportDefaultSpecifier; +exports.ExportNamedDeclaration = ExportNamedDeclaration; +exports.ExportNamespaceSpecifier = ExportNamespaceSpecifier; +exports.ExportSpecifier = ExportSpecifier; +exports.ImportAttribute = ImportAttribute; +exports.ImportDeclaration = ImportDeclaration; +exports.ImportDefaultSpecifier = ImportDefaultSpecifier; +exports.ImportExpression = ImportExpression; +exports.ImportNamespaceSpecifier = ImportNamespaceSpecifier; +exports.ImportSpecifier = ImportSpecifier; +exports._printAttributes = _printAttributes; +var _t = __nccwpck_require__(16535); +var _index = __nccwpck_require__(95460); +const { + isClassDeclaration, + isExportDefaultSpecifier, + isExportNamespaceSpecifier, + isImportDefaultSpecifier, + isImportNamespaceSpecifier, + isStatement +} = _t; +function ImportSpecifier(node) { + if (node.importKind === "type" || node.importKind === "typeof") { + this.word(node.importKind); + this.space(); + } + this.print(node.imported); + if (node.local && node.local.name !== node.imported.name) { + this.space(); + this.word("as"); + this.space(); + this.print(node.local); + } +} +function ImportDefaultSpecifier(node) { + this.print(node.local); +} +function ExportDefaultSpecifier(node) { + this.print(node.exported); +} +function ExportSpecifier(node) { + if (node.exportKind === "type") { + this.word("type"); + this.space(); + } + this.print(node.local); + if (node.exported && node.local.name !== node.exported.name) { + this.space(); + this.word("as"); + this.space(); + this.print(node.exported); + } +} +function ExportNamespaceSpecifier(node) { + this.tokenChar(42); + this.space(); + this.word("as"); + this.space(); + this.print(node.exported); +} +let warningShown = false; +function _printAttributes(node, hasPreviousBrace) { + var _node$extra; + const { + importAttributesKeyword + } = this.format; + const { + attributes, + assertions + } = node; + if (attributes && !importAttributesKeyword && node.extra && (node.extra.deprecatedAssertSyntax || node.extra.deprecatedWithLegacySyntax) && !warningShown) { + warningShown = true; + console.warn(`\ +You are using import attributes, without specifying the desired output syntax. +Please specify the "importAttributesKeyword" generator option, whose value can be one of: + - "with" : \`import { a } from "b" with { type: "json" };\` + - "assert" : \`import { a } from "b" assert { type: "json" };\` + - "with-legacy" : \`import { a } from "b" with type: "json";\` +`); + } + const useAssertKeyword = importAttributesKeyword === "assert" || !importAttributesKeyword && assertions; + this.word(useAssertKeyword ? "assert" : "with"); + this.space(); + if (!useAssertKeyword && (importAttributesKeyword === "with-legacy" || !importAttributesKeyword && (_node$extra = node.extra) != null && _node$extra.deprecatedWithLegacySyntax)) { + this.printList(attributes || assertions); + return; + } + const occurrenceCount = hasPreviousBrace ? 1 : 0; + this.token("{", null, occurrenceCount); + this.space(); + this.printList(attributes || assertions, this.shouldPrintTrailingComma("}")); + this.space(); + this.token("}", null, occurrenceCount); +} +function ExportAllDeclaration(node) { + var _node$attributes, _node$assertions; + this.word("export"); + this.space(); + if (node.exportKind === "type") { + this.word("type"); + this.space(); + } + this.tokenChar(42); + this.space(); + this.word("from"); + this.space(); + if ((_node$attributes = node.attributes) != null && _node$attributes.length || (_node$assertions = node.assertions) != null && _node$assertions.length) { + this.print(node.source, true); + this.space(); + this._printAttributes(node, false); + } else { + this.print(node.source); + } + this.semicolon(); +} +function maybePrintDecoratorsBeforeExport(printer, node) { + if (isClassDeclaration(node.declaration) && printer._shouldPrintDecoratorsBeforeExport(node)) { + printer.printJoin(node.declaration.decorators); + } +} +function ExportNamedDeclaration(node) { + maybePrintDecoratorsBeforeExport(this, node); + this.word("export"); + this.space(); + if (node.declaration) { + const declar = node.declaration; + this.print(declar); + if (!isStatement(declar)) this.semicolon(); + } else { + if (node.exportKind === "type") { + this.word("type"); + this.space(); + } + const specifiers = node.specifiers.slice(0); + let hasSpecial = false; + for (;;) { + const first = specifiers[0]; + if (isExportDefaultSpecifier(first) || isExportNamespaceSpecifier(first)) { + hasSpecial = true; + this.print(specifiers.shift()); + if (specifiers.length) { + this.tokenChar(44); + this.space(); + } + } else { + break; + } + } + let hasBrace = false; + if (specifiers.length || !specifiers.length && !hasSpecial) { + hasBrace = true; + this.tokenChar(123); + if (specifiers.length) { + this.space(); + this.printList(specifiers, this.shouldPrintTrailingComma("}")); + this.space(); + } + this.tokenChar(125); + } + if (node.source) { + var _node$attributes2, _node$assertions2; + this.space(); + this.word("from"); + this.space(); + if ((_node$attributes2 = node.attributes) != null && _node$attributes2.length || (_node$assertions2 = node.assertions) != null && _node$assertions2.length) { + this.print(node.source, true); + this.space(); + this._printAttributes(node, hasBrace); + } else { + this.print(node.source); + } + } + this.semicolon(); + } +} +function ExportDefaultDeclaration(node) { + maybePrintDecoratorsBeforeExport(this, node); + this.word("export"); + this.noIndentInnerCommentsHere(); + this.space(); + this.word("default"); + this.space(); + this.tokenContext |= _index.TokenContext.exportDefault; + const declar = node.declaration; + this.print(declar); + if (!isStatement(declar)) this.semicolon(); +} +function ImportDeclaration(node) { + var _node$attributes3, _node$assertions3; + this.word("import"); + this.space(); + const isTypeKind = node.importKind === "type" || node.importKind === "typeof"; + if (isTypeKind) { + this.noIndentInnerCommentsHere(); + this.word(node.importKind); + this.space(); + } else if (node.module) { + this.noIndentInnerCommentsHere(); + this.word("module"); + this.space(); + } else if (node.phase) { + this.noIndentInnerCommentsHere(); + this.word(node.phase); + this.space(); + } + const specifiers = node.specifiers.slice(0); + const hasSpecifiers = !!specifiers.length; + while (hasSpecifiers) { + const first = specifiers[0]; + if (isImportDefaultSpecifier(first) || isImportNamespaceSpecifier(first)) { + this.print(specifiers.shift()); + if (specifiers.length) { + this.tokenChar(44); + this.space(); + } + } else { + break; + } + } + let hasBrace = false; + if (specifiers.length) { + hasBrace = true; + this.tokenChar(123); + this.space(); + this.printList(specifiers, this.shouldPrintTrailingComma("}")); + this.space(); + this.tokenChar(125); + } else if (isTypeKind && !hasSpecifiers) { + hasBrace = true; + this.tokenChar(123); + this.tokenChar(125); + } + if (hasSpecifiers || isTypeKind) { + this.space(); + this.word("from"); + this.space(); + } + if ((_node$attributes3 = node.attributes) != null && _node$attributes3.length || (_node$assertions3 = node.assertions) != null && _node$assertions3.length) { + this.print(node.source, true); + this.space(); + this._printAttributes(node, hasBrace); + } else { + this.print(node.source); + } + this.semicolon(); +} +function ImportAttribute(node) { + this.print(node.key); + this.tokenChar(58); + this.space(); + this.print(node.value); +} +function ImportNamespaceSpecifier(node) { + this.tokenChar(42); + this.space(); + this.word("as"); + this.space(); + this.print(node.local); +} +function ImportExpression(node) { + this.word("import"); + if (node.phase) { + this.tokenChar(46); + this.word(node.phase); + } + this.tokenChar(40); + this.print(node.source); + if (node.options != null) { + this.tokenChar(44); + this.space(); + this.print(node.options); + } + this.tokenChar(41); +} + +//# sourceMappingURL=modules.js.map + + +/***/ }), + +/***/ 40102: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports.BreakStatement = BreakStatement; +exports.CatchClause = CatchClause; +exports.ContinueStatement = ContinueStatement; +exports.DebuggerStatement = DebuggerStatement; +exports.DoWhileStatement = DoWhileStatement; +exports.ForOfStatement = exports.ForInStatement = void 0; +exports.ForStatement = ForStatement; +exports.IfStatement = IfStatement; +exports.LabeledStatement = LabeledStatement; +exports.ReturnStatement = ReturnStatement; +exports.SwitchCase = SwitchCase; +exports.SwitchStatement = SwitchStatement; +exports.ThrowStatement = ThrowStatement; +exports.TryStatement = TryStatement; +exports.VariableDeclaration = VariableDeclaration; +exports.VariableDeclarator = VariableDeclarator; +exports.WhileStatement = WhileStatement; +exports.WithStatement = WithStatement; +var _t = __nccwpck_require__(16535); +var _index = __nccwpck_require__(95460); +const { + isFor, + isForStatement, + isIfStatement, + isStatement +} = _t; +function WithStatement(node) { + this.word("with"); + this.space(); + this.tokenChar(40); + this.print(node.object); + this.tokenChar(41); + this.printBlock(node); +} +function IfStatement(node) { + this.word("if"); + this.space(); + this.tokenChar(40); + this.print(node.test); + this.tokenChar(41); + this.space(); + const needsBlock = node.alternate && isIfStatement(getLastStatement(node.consequent)); + if (needsBlock) { + this.tokenChar(123); + this.newline(); + this.indent(); + } + this.printAndIndentOnComments(node.consequent); + if (needsBlock) { + this.dedent(); + this.newline(); + this.tokenChar(125); + } + if (node.alternate) { + if (this.endsWith(125)) this.space(); + this.word("else"); + this.space(); + this.printAndIndentOnComments(node.alternate); + } +} +function getLastStatement(statement) { + const { + body + } = statement; + if (isStatement(body) === false) { + return statement; + } + return getLastStatement(body); +} +function ForStatement(node) { + this.word("for"); + this.space(); + this.tokenChar(40); + { + const exit = this.enterForStatementInit(); + this.tokenContext |= _index.TokenContext.forHead; + this.print(node.init); + exit(); + } + this.tokenChar(59); + if (node.test) { + this.space(); + this.print(node.test); + } + this.token(";", false, 1); + if (node.update) { + this.space(); + this.print(node.update); + } + this.tokenChar(41); + this.printBlock(node); +} +function WhileStatement(node) { + this.word("while"); + this.space(); + this.tokenChar(40); + this.print(node.test); + this.tokenChar(41); + this.printBlock(node); +} +function ForXStatement(node) { + this.word("for"); + this.space(); + const isForOf = node.type === "ForOfStatement"; + if (isForOf && node.await) { + this.word("await"); + this.space(); + } + this.noIndentInnerCommentsHere(); + this.tokenChar(40); + { + const exit = isForOf ? null : this.enterForStatementInit(); + this.tokenContext |= isForOf ? _index.TokenContext.forOfHead : _index.TokenContext.forInHead; + this.print(node.left); + exit == null || exit(); + } + this.space(); + this.word(isForOf ? "of" : "in"); + this.space(); + this.print(node.right); + this.tokenChar(41); + this.printBlock(node); +} +const ForInStatement = exports.ForInStatement = ForXStatement; +const ForOfStatement = exports.ForOfStatement = ForXStatement; +function DoWhileStatement(node) { + this.word("do"); + this.space(); + this.print(node.body); + this.space(); + this.word("while"); + this.space(); + this.tokenChar(40); + this.print(node.test); + this.tokenChar(41); + this.semicolon(); +} +function printStatementAfterKeyword(printer, node) { + if (node) { + printer.space(); + printer.printTerminatorless(node); + } + printer.semicolon(); +} +function BreakStatement(node) { + this.word("break"); + printStatementAfterKeyword(this, node.label); +} +function ContinueStatement(node) { + this.word("continue"); + printStatementAfterKeyword(this, node.label); +} +function ReturnStatement(node) { + this.word("return"); + printStatementAfterKeyword(this, node.argument); +} +function ThrowStatement(node) { + this.word("throw"); + printStatementAfterKeyword(this, node.argument); +} +function LabeledStatement(node) { + this.print(node.label); + this.tokenChar(58); + this.space(); + this.print(node.body); +} +function TryStatement(node) { + this.word("try"); + this.space(); + this.print(node.block); + this.space(); + if (node.handlers) { + this.print(node.handlers[0]); + } else { + this.print(node.handler); + } + if (node.finalizer) { + this.space(); + this.word("finally"); + this.space(); + this.print(node.finalizer); + } +} +function CatchClause(node) { + this.word("catch"); + this.space(); + if (node.param) { + this.tokenChar(40); + this.print(node.param); + this.print(node.param.typeAnnotation); + this.tokenChar(41); + this.space(); + } + this.print(node.body); +} +function SwitchStatement(node) { + this.word("switch"); + this.space(); + this.tokenChar(40); + this.print(node.discriminant); + this.tokenChar(41); + this.space(); + this.tokenChar(123); + this.printSequence(node.cases, true, undefined, function addNewlines(leading, cas) { + if (!leading && node.cases[node.cases.length - 1] === cas) return -1; + }); + this.rightBrace(node); +} +function SwitchCase(node) { + if (node.test) { + this.word("case"); + this.space(); + this.print(node.test); + this.tokenChar(58); + } else { + this.word("default"); + this.tokenChar(58); + } + if (node.consequent.length) { + this.newline(); + this.printSequence(node.consequent, true); + } +} +function DebuggerStatement() { + this.word("debugger"); + this.semicolon(); +} +function VariableDeclaration(node, parent) { + if (node.declare) { + this.word("declare"); + this.space(); + } + const { + kind + } = node; + if (kind === "await using") { + this.word("await"); + this.space(); + this.word("using", true); + } else { + this.word(kind, kind === "using"); + } + this.space(); + let hasInits = false; + if (!isFor(parent)) { + for (const declar of node.declarations) { + if (declar.init) { + hasInits = true; + } + } + } + this.printList(node.declarations, undefined, undefined, node.declarations.length > 1, hasInits ? function (occurrenceCount) { + this.token(",", false, occurrenceCount); + this.newline(); + } : undefined); + if (isFor(parent)) { + if (isForStatement(parent)) { + if (parent.init === node) return; + } else { + if (parent.left === node) return; + } + } + this.semicolon(); +} +function VariableDeclarator(node) { + this.print(node.id); + if (node.definite) this.tokenChar(33); + this.print(node.id.typeAnnotation); + if (node.init) { + this.space(); + this.tokenChar(61); + this.space(); + this.print(node.init); + } +} + +//# sourceMappingURL=statements.js.map + + +/***/ }), + +/***/ 97571: +/***/ ((__unused_webpack_module, exports) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports.TaggedTemplateExpression = TaggedTemplateExpression; +exports.TemplateElement = TemplateElement; +exports.TemplateLiteral = TemplateLiteral; +exports._printTemplate = _printTemplate; +function TaggedTemplateExpression(node) { + this.print(node.tag); + { + this.print(node.typeParameters); + } + this.print(node.quasi); +} +function TemplateElement() { + throw new Error("TemplateElement printing is handled in TemplateLiteral"); +} +function _printTemplate(node, substitutions) { + const quasis = node.quasis; + let partRaw = "`"; + for (let i = 0; i < quasis.length - 1; i++) { + partRaw += quasis[i].value.raw; + this.token(partRaw + "${", true); + this.print(substitutions[i]); + partRaw = "}"; + if (this.tokenMap) { + const token = this.tokenMap.findMatching(node, "}", i); + if (token) this._catchUpTo(token.loc.start); + } + } + partRaw += quasis[quasis.length - 1].value.raw; + this.token(partRaw + "`", true); +} +function TemplateLiteral(node) { + this._printTemplate(node, node.expressions); +} + +//# sourceMappingURL=template-literals.js.map + + +/***/ }), + +/***/ 26659: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports.ArgumentPlaceholder = ArgumentPlaceholder; +exports.ArrayPattern = exports.ArrayExpression = ArrayExpression; +exports.BigIntLiteral = BigIntLiteral; +exports.BooleanLiteral = BooleanLiteral; +exports.Identifier = Identifier; +exports.NullLiteral = NullLiteral; +exports.NumericLiteral = NumericLiteral; +exports.ObjectPattern = exports.ObjectExpression = ObjectExpression; +exports.ObjectMethod = ObjectMethod; +exports.ObjectProperty = ObjectProperty; +exports.PipelineBareFunction = PipelineBareFunction; +exports.PipelinePrimaryTopicReference = PipelinePrimaryTopicReference; +exports.PipelineTopicExpression = PipelineTopicExpression; +exports.RecordExpression = RecordExpression; +exports.RegExpLiteral = RegExpLiteral; +exports.SpreadElement = exports.RestElement = RestElement; +exports.StringLiteral = StringLiteral; +exports.TopicReference = TopicReference; +exports.TupleExpression = TupleExpression; +exports._getRawIdentifier = _getRawIdentifier; +var _t = __nccwpck_require__(16535); +var _jsesc = __nccwpck_require__(59376); +const { + isAssignmentPattern, + isIdentifier +} = _t; +let lastRawIdentNode = null; +let lastRawIdentResult = ""; +function _getRawIdentifier(node) { + if (node === lastRawIdentNode) return lastRawIdentResult; + lastRawIdentNode = node; + const { + name + } = node; + const token = this.tokenMap.find(node, tok => tok.value === name); + if (token) { + lastRawIdentResult = this._originalCode.slice(token.start, token.end); + return lastRawIdentResult; + } + return lastRawIdentResult = node.name; +} +function Identifier(node) { + var _node$loc; + this.sourceIdentifierName(((_node$loc = node.loc) == null ? void 0 : _node$loc.identifierName) || node.name); + this.word(this.tokenMap ? this._getRawIdentifier(node) : node.name); +} +function ArgumentPlaceholder() { + this.tokenChar(63); +} +function RestElement(node) { + this.token("..."); + this.print(node.argument); +} +function ObjectExpression(node) { + const props = node.properties; + this.tokenChar(123); + if (props.length) { + const exit = this.enterDelimited(); + this.space(); + this.printList(props, this.shouldPrintTrailingComma("}"), true, true); + this.space(); + exit(); + } + this.sourceWithOffset("end", node.loc, -1); + this.tokenChar(125); +} +function ObjectMethod(node) { + this.printJoin(node.decorators); + this._methodHead(node); + this.space(); + this.print(node.body); +} +function ObjectProperty(node) { + this.printJoin(node.decorators); + if (node.computed) { + this.tokenChar(91); + this.print(node.key); + this.tokenChar(93); + } else { + if (isAssignmentPattern(node.value) && isIdentifier(node.key) && node.key.name === node.value.left.name) { + this.print(node.value); + return; + } + this.print(node.key); + if (node.shorthand && isIdentifier(node.key) && isIdentifier(node.value) && node.key.name === node.value.name) { + return; + } + } + this.tokenChar(58); + this.space(); + this.print(node.value); +} +function ArrayExpression(node) { + const elems = node.elements; + const len = elems.length; + this.tokenChar(91); + const exit = this.enterDelimited(); + for (let i = 0; i < elems.length; i++) { + const elem = elems[i]; + if (elem) { + if (i > 0) this.space(); + this.print(elem); + if (i < len - 1 || this.shouldPrintTrailingComma("]")) { + this.token(",", false, i); + } + } else { + this.token(",", false, i); + } + } + exit(); + this.tokenChar(93); +} +function RecordExpression(node) { + const props = node.properties; + let startToken; + let endToken; + { + if (this.format.recordAndTupleSyntaxType === "bar") { + startToken = "{|"; + endToken = "|}"; + } else if (this.format.recordAndTupleSyntaxType !== "hash" && this.format.recordAndTupleSyntaxType != null) { + throw new Error(`The "recordAndTupleSyntaxType" generator option must be "bar" or "hash" (${JSON.stringify(this.format.recordAndTupleSyntaxType)} received).`); + } else { + startToken = "#{"; + endToken = "}"; + } + } + this.token(startToken); + if (props.length) { + this.space(); + this.printList(props, this.shouldPrintTrailingComma(endToken), true, true); + this.space(); + } + this.token(endToken); +} +function TupleExpression(node) { + const elems = node.elements; + const len = elems.length; + let startToken; + let endToken; + { + if (this.format.recordAndTupleSyntaxType === "bar") { + startToken = "[|"; + endToken = "|]"; + } else if (this.format.recordAndTupleSyntaxType === "hash") { + startToken = "#["; + endToken = "]"; + } else { + throw new Error(`${this.format.recordAndTupleSyntaxType} is not a valid recordAndTuple syntax type`); + } + } + this.token(startToken); + for (let i = 0; i < elems.length; i++) { + const elem = elems[i]; + if (elem) { + if (i > 0) this.space(); + this.print(elem); + if (i < len - 1 || this.shouldPrintTrailingComma(endToken)) { + this.token(",", false, i); + } + } + } + this.token(endToken); +} +function RegExpLiteral(node) { + this.word(`/${node.pattern}/${node.flags}`); +} +function BooleanLiteral(node) { + this.word(node.value ? "true" : "false"); +} +function NullLiteral() { + this.word("null"); +} +function NumericLiteral(node) { + const raw = this.getPossibleRaw(node); + const opts = this.format.jsescOption; + const value = node.value; + const str = value + ""; + if (opts.numbers) { + this.number(_jsesc(value, opts), value); + } else if (raw == null) { + this.number(str, value); + } else if (this.format.minified) { + this.number(raw.length < str.length ? raw : str, value); + } else { + this.number(raw, value); + } +} +function StringLiteral(node) { + const raw = this.getPossibleRaw(node); + if (!this.format.minified && raw !== undefined) { + this.token(raw); + return; + } + const val = _jsesc(node.value, this.format.jsescOption); + this.token(val); +} +function BigIntLiteral(node) { + const raw = this.getPossibleRaw(node); + if (!this.format.minified && raw !== undefined) { + this.word(raw); + return; + } + this.word(node.value + "n"); +} +const validTopicTokenSet = new Set(["^^", "@@", "^", "%", "#"]); +function TopicReference() { + const { + topicToken + } = this.format; + if (validTopicTokenSet.has(topicToken)) { + this.token(topicToken); + } else { + const givenTopicTokenJSON = JSON.stringify(topicToken); + const validTopics = Array.from(validTopicTokenSet, v => JSON.stringify(v)); + throw new Error(`The "topicToken" generator option must be one of ` + `${validTopics.join(", ")} (${givenTopicTokenJSON} received instead).`); + } +} +function PipelineTopicExpression(node) { + this.print(node.expression); +} +function PipelineBareFunction(node) { + this.print(node.callee); +} +function PipelinePrimaryTopicReference() { + this.tokenChar(35); +} + +//# sourceMappingURL=types.js.map + + +/***/ }), + +/***/ 16975: +/***/ ((__unused_webpack_module, exports) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports.TSAnyKeyword = TSAnyKeyword; +exports.TSArrayType = TSArrayType; +exports.TSSatisfiesExpression = exports.TSAsExpression = TSTypeExpression; +exports.TSBigIntKeyword = TSBigIntKeyword; +exports.TSBooleanKeyword = TSBooleanKeyword; +exports.TSCallSignatureDeclaration = TSCallSignatureDeclaration; +exports.TSInterfaceHeritage = exports.TSClassImplements = TSClassImplements; +exports.TSConditionalType = TSConditionalType; +exports.TSConstructSignatureDeclaration = TSConstructSignatureDeclaration; +exports.TSConstructorType = TSConstructorType; +exports.TSDeclareFunction = TSDeclareFunction; +exports.TSDeclareMethod = TSDeclareMethod; +exports.TSEnumBody = TSEnumBody; +exports.TSEnumDeclaration = TSEnumDeclaration; +exports.TSEnumMember = TSEnumMember; +exports.TSExportAssignment = TSExportAssignment; +exports.TSExternalModuleReference = TSExternalModuleReference; +exports.TSFunctionType = TSFunctionType; +exports.TSImportEqualsDeclaration = TSImportEqualsDeclaration; +exports.TSImportType = TSImportType; +exports.TSIndexSignature = TSIndexSignature; +exports.TSIndexedAccessType = TSIndexedAccessType; +exports.TSInferType = TSInferType; +exports.TSInstantiationExpression = TSInstantiationExpression; +exports.TSInterfaceBody = TSInterfaceBody; +exports.TSInterfaceDeclaration = TSInterfaceDeclaration; +exports.TSIntersectionType = TSIntersectionType; +exports.TSIntrinsicKeyword = TSIntrinsicKeyword; +exports.TSLiteralType = TSLiteralType; +exports.TSMappedType = TSMappedType; +exports.TSMethodSignature = TSMethodSignature; +exports.TSModuleBlock = TSModuleBlock; +exports.TSModuleDeclaration = TSModuleDeclaration; +exports.TSNamedTupleMember = TSNamedTupleMember; +exports.TSNamespaceExportDeclaration = TSNamespaceExportDeclaration; +exports.TSNeverKeyword = TSNeverKeyword; +exports.TSNonNullExpression = TSNonNullExpression; +exports.TSNullKeyword = TSNullKeyword; +exports.TSNumberKeyword = TSNumberKeyword; +exports.TSObjectKeyword = TSObjectKeyword; +exports.TSOptionalType = TSOptionalType; +exports.TSParameterProperty = TSParameterProperty; +exports.TSParenthesizedType = TSParenthesizedType; +exports.TSPropertySignature = TSPropertySignature; +exports.TSQualifiedName = TSQualifiedName; +exports.TSRestType = TSRestType; +exports.TSStringKeyword = TSStringKeyword; +exports.TSSymbolKeyword = TSSymbolKeyword; +exports.TSTemplateLiteralType = TSTemplateLiteralType; +exports.TSThisType = TSThisType; +exports.TSTupleType = TSTupleType; +exports.TSTypeAliasDeclaration = TSTypeAliasDeclaration; +exports.TSTypeAnnotation = TSTypeAnnotation; +exports.TSTypeAssertion = TSTypeAssertion; +exports.TSTypeLiteral = TSTypeLiteral; +exports.TSTypeOperator = TSTypeOperator; +exports.TSTypeParameter = TSTypeParameter; +exports.TSTypeParameterDeclaration = exports.TSTypeParameterInstantiation = TSTypeParameterInstantiation; +exports.TSTypePredicate = TSTypePredicate; +exports.TSTypeQuery = TSTypeQuery; +exports.TSTypeReference = TSTypeReference; +exports.TSUndefinedKeyword = TSUndefinedKeyword; +exports.TSUnionType = TSUnionType; +exports.TSUnknownKeyword = TSUnknownKeyword; +exports.TSVoidKeyword = TSVoidKeyword; +exports.tsPrintClassMemberModifiers = tsPrintClassMemberModifiers; +exports.tsPrintFunctionOrConstructorType = tsPrintFunctionOrConstructorType; +exports.tsPrintPropertyOrMethodName = tsPrintPropertyOrMethodName; +exports.tsPrintSignatureDeclarationBase = tsPrintSignatureDeclarationBase; +function TSTypeAnnotation(node, parent) { + this.token((parent.type === "TSFunctionType" || parent.type === "TSConstructorType") && parent.typeAnnotation === node ? "=>" : ":"); + this.space(); + if (node.optional) this.tokenChar(63); + this.print(node.typeAnnotation); +} +function TSTypeParameterInstantiation(node, parent) { + this.tokenChar(60); + let printTrailingSeparator = parent.type === "ArrowFunctionExpression" && node.params.length === 1; + if (this.tokenMap && node.start != null && node.end != null) { + printTrailingSeparator && (printTrailingSeparator = !!this.tokenMap.find(node, t => this.tokenMap.matchesOriginal(t, ","))); + printTrailingSeparator || (printTrailingSeparator = this.shouldPrintTrailingComma(">")); + } + this.printList(node.params, printTrailingSeparator); + this.tokenChar(62); +} +function TSTypeParameter(node) { + if (node.const) { + this.word("const"); + this.space(); + } + if (node.in) { + this.word("in"); + this.space(); + } + if (node.out) { + this.word("out"); + this.space(); + } + this.word(node.name); + if (node.constraint) { + this.space(); + this.word("extends"); + this.space(); + this.print(node.constraint); + } + if (node.default) { + this.space(); + this.tokenChar(61); + this.space(); + this.print(node.default); + } +} +function TSParameterProperty(node) { + if (node.accessibility) { + this.word(node.accessibility); + this.space(); + } + if (node.readonly) { + this.word("readonly"); + this.space(); + } + this._param(node.parameter); +} +function TSDeclareFunction(node, parent) { + if (node.declare) { + this.word("declare"); + this.space(); + } + this._functionHead(node, parent); + this.semicolon(); +} +function TSDeclareMethod(node) { + this._classMethodHead(node); + this.semicolon(); +} +function TSQualifiedName(node) { + this.print(node.left); + this.tokenChar(46); + this.print(node.right); +} +function TSCallSignatureDeclaration(node) { + this.tsPrintSignatureDeclarationBase(node); + maybePrintTrailingCommaOrSemicolon(this, node); +} +function maybePrintTrailingCommaOrSemicolon(printer, node) { + if (!printer.tokenMap || !node.start || !node.end) { + printer.semicolon(); + return; + } + if (printer.tokenMap.endMatches(node, ",")) { + printer.token(","); + } else if (printer.tokenMap.endMatches(node, ";")) { + printer.semicolon(); + } +} +function TSConstructSignatureDeclaration(node) { + this.word("new"); + this.space(); + this.tsPrintSignatureDeclarationBase(node); + maybePrintTrailingCommaOrSemicolon(this, node); +} +function TSPropertySignature(node) { + const { + readonly + } = node; + if (readonly) { + this.word("readonly"); + this.space(); + } + this.tsPrintPropertyOrMethodName(node); + this.print(node.typeAnnotation); + maybePrintTrailingCommaOrSemicolon(this, node); +} +function tsPrintPropertyOrMethodName(node) { + if (node.computed) { + this.tokenChar(91); + } + this.print(node.key); + if (node.computed) { + this.tokenChar(93); + } + if (node.optional) { + this.tokenChar(63); + } +} +function TSMethodSignature(node) { + const { + kind + } = node; + if (kind === "set" || kind === "get") { + this.word(kind); + this.space(); + } + this.tsPrintPropertyOrMethodName(node); + this.tsPrintSignatureDeclarationBase(node); + maybePrintTrailingCommaOrSemicolon(this, node); +} +function TSIndexSignature(node) { + const { + readonly, + static: isStatic + } = node; + if (isStatic) { + this.word("static"); + this.space(); + } + if (readonly) { + this.word("readonly"); + this.space(); + } + this.tokenChar(91); + this._parameters(node.parameters, "]"); + this.print(node.typeAnnotation); + maybePrintTrailingCommaOrSemicolon(this, node); +} +function TSAnyKeyword() { + this.word("any"); +} +function TSBigIntKeyword() { + this.word("bigint"); +} +function TSUnknownKeyword() { + this.word("unknown"); +} +function TSNumberKeyword() { + this.word("number"); +} +function TSObjectKeyword() { + this.word("object"); +} +function TSBooleanKeyword() { + this.word("boolean"); +} +function TSStringKeyword() { + this.word("string"); +} +function TSSymbolKeyword() { + this.word("symbol"); +} +function TSVoidKeyword() { + this.word("void"); +} +function TSUndefinedKeyword() { + this.word("undefined"); +} +function TSNullKeyword() { + this.word("null"); +} +function TSNeverKeyword() { + this.word("never"); +} +function TSIntrinsicKeyword() { + this.word("intrinsic"); +} +function TSThisType() { + this.word("this"); +} +function TSFunctionType(node) { + this.tsPrintFunctionOrConstructorType(node); +} +function TSConstructorType(node) { + if (node.abstract) { + this.word("abstract"); + this.space(); + } + this.word("new"); + this.space(); + this.tsPrintFunctionOrConstructorType(node); +} +function tsPrintFunctionOrConstructorType(node) { + const { + typeParameters + } = node; + const parameters = node.parameters; + this.print(typeParameters); + this.tokenChar(40); + this._parameters(parameters, ")"); + this.space(); + const returnType = node.typeAnnotation; + this.print(returnType); +} +function TSTypeReference(node) { + const typeArguments = node.typeParameters; + this.print(node.typeName, !!typeArguments); + this.print(typeArguments); +} +function TSTypePredicate(node) { + if (node.asserts) { + this.word("asserts"); + this.space(); + } + this.print(node.parameterName); + if (node.typeAnnotation) { + this.space(); + this.word("is"); + this.space(); + this.print(node.typeAnnotation.typeAnnotation); + } +} +function TSTypeQuery(node) { + this.word("typeof"); + this.space(); + this.print(node.exprName); + const typeArguments = node.typeParameters; + if (typeArguments) { + this.print(typeArguments); + } +} +function TSTypeLiteral(node) { + printBraced(this, node, () => this.printJoin(node.members, true, true)); +} +function TSArrayType(node) { + this.print(node.elementType, true); + this.tokenChar(91); + this.tokenChar(93); +} +function TSTupleType(node) { + this.tokenChar(91); + this.printList(node.elementTypes, this.shouldPrintTrailingComma("]")); + this.tokenChar(93); +} +function TSOptionalType(node) { + this.print(node.typeAnnotation); + this.tokenChar(63); +} +function TSRestType(node) { + this.token("..."); + this.print(node.typeAnnotation); +} +function TSNamedTupleMember(node) { + this.print(node.label); + if (node.optional) this.tokenChar(63); + this.tokenChar(58); + this.space(); + this.print(node.elementType); +} +function TSUnionType(node) { + tsPrintUnionOrIntersectionType(this, node, "|"); +} +function TSIntersectionType(node) { + tsPrintUnionOrIntersectionType(this, node, "&"); +} +function tsPrintUnionOrIntersectionType(printer, node, sep) { + var _printer$tokenMap; + let hasLeadingToken = 0; + if ((_printer$tokenMap = printer.tokenMap) != null && _printer$tokenMap.startMatches(node, sep)) { + hasLeadingToken = 1; + printer.token(sep); + } + printer.printJoin(node.types, undefined, undefined, function (i) { + this.space(); + this.token(sep, null, i + hasLeadingToken); + this.space(); + }); +} +function TSConditionalType(node) { + this.print(node.checkType); + this.space(); + this.word("extends"); + this.space(); + this.print(node.extendsType); + this.space(); + this.tokenChar(63); + this.space(); + this.print(node.trueType); + this.space(); + this.tokenChar(58); + this.space(); + this.print(node.falseType); +} +function TSInferType(node) { + this.word("infer"); + this.print(node.typeParameter); +} +function TSParenthesizedType(node) { + this.tokenChar(40); + this.print(node.typeAnnotation); + this.tokenChar(41); +} +function TSTypeOperator(node) { + this.word(node.operator); + this.space(); + this.print(node.typeAnnotation); +} +function TSIndexedAccessType(node) { + this.print(node.objectType, true); + this.tokenChar(91); + this.print(node.indexType); + this.tokenChar(93); +} +function TSMappedType(node) { + const { + nameType, + optional, + readonly, + typeAnnotation + } = node; + this.tokenChar(123); + const exit = this.enterDelimited(); + this.space(); + if (readonly) { + tokenIfPlusMinus(this, readonly); + this.word("readonly"); + this.space(); + } + this.tokenChar(91); + { + this.word(node.typeParameter.name); + } + this.space(); + this.word("in"); + this.space(); + { + this.print(node.typeParameter.constraint); + } + if (nameType) { + this.space(); + this.word("as"); + this.space(); + this.print(nameType); + } + this.tokenChar(93); + if (optional) { + tokenIfPlusMinus(this, optional); + this.tokenChar(63); + } + if (typeAnnotation) { + this.tokenChar(58); + this.space(); + this.print(typeAnnotation); + } + this.space(); + exit(); + this.tokenChar(125); +} +function tokenIfPlusMinus(self, tok) { + if (tok !== true) { + self.token(tok); + } +} +function TSTemplateLiteralType(node) { + this._printTemplate(node, node.types); +} +function TSLiteralType(node) { + this.print(node.literal); +} +function TSClassImplements(node) { + this.print(node.expression); + this.print(node.typeArguments); +} +function TSInterfaceDeclaration(node) { + const { + declare, + id, + typeParameters, + extends: extendz, + body + } = node; + if (declare) { + this.word("declare"); + this.space(); + } + this.word("interface"); + this.space(); + this.print(id); + this.print(typeParameters); + if (extendz != null && extendz.length) { + this.space(); + this.word("extends"); + this.space(); + this.printList(extendz); + } + this.space(); + this.print(body); +} +function TSInterfaceBody(node) { + printBraced(this, node, () => this.printJoin(node.body, true, true)); +} +function TSTypeAliasDeclaration(node) { + const { + declare, + id, + typeParameters, + typeAnnotation + } = node; + if (declare) { + this.word("declare"); + this.space(); + } + this.word("type"); + this.space(); + this.print(id); + this.print(typeParameters); + this.space(); + this.tokenChar(61); + this.space(); + this.print(typeAnnotation); + this.semicolon(); +} +function TSTypeExpression(node) { + const { + type, + expression, + typeAnnotation + } = node; + this.print(expression, true); + this.space(); + this.word(type === "TSAsExpression" ? "as" : "satisfies"); + this.space(); + this.print(typeAnnotation); +} +function TSTypeAssertion(node) { + const { + typeAnnotation, + expression + } = node; + this.tokenChar(60); + this.print(typeAnnotation); + this.tokenChar(62); + this.space(); + this.print(expression); +} +function TSInstantiationExpression(node) { + this.print(node.expression); + { + this.print(node.typeParameters); + } +} +function TSEnumDeclaration(node) { + const { + declare, + const: isConst, + id + } = node; + if (declare) { + this.word("declare"); + this.space(); + } + if (isConst) { + this.word("const"); + this.space(); + } + this.word("enum"); + this.space(); + this.print(id); + this.space(); + { + TSEnumBody.call(this, node); + } +} +function TSEnumBody(node) { + printBraced(this, node, () => { + var _this$shouldPrintTrai; + return this.printList(node.members, (_this$shouldPrintTrai = this.shouldPrintTrailingComma("}")) != null ? _this$shouldPrintTrai : true, true, true); + }); +} +function TSEnumMember(node) { + const { + id, + initializer + } = node; + this.print(id); + if (initializer) { + this.space(); + this.tokenChar(61); + this.space(); + this.print(initializer); + } +} +function TSModuleDeclaration(node) { + const { + declare, + id, + kind + } = node; + if (declare) { + this.word("declare"); + this.space(); + } + { + if (!node.global) { + this.word(kind != null ? kind : id.type === "Identifier" ? "namespace" : "module"); + this.space(); + } + this.print(id); + if (!node.body) { + this.semicolon(); + return; + } + let body = node.body; + while (body.type === "TSModuleDeclaration") { + this.tokenChar(46); + this.print(body.id); + body = body.body; + } + this.space(); + this.print(body); + } +} +function TSModuleBlock(node) { + printBraced(this, node, () => this.printSequence(node.body, true)); +} +function TSImportType(node) { + const { + argument, + qualifier, + options + } = node; + this.word("import"); + this.tokenChar(40); + this.print(argument); + if (options) { + this.tokenChar(44); + this.print(options); + } + this.tokenChar(41); + if (qualifier) { + this.tokenChar(46); + this.print(qualifier); + } + const typeArguments = node.typeParameters; + if (typeArguments) { + this.print(typeArguments); + } +} +function TSImportEqualsDeclaration(node) { + const { + id, + moduleReference + } = node; + if (node.isExport) { + this.word("export"); + this.space(); + } + this.word("import"); + this.space(); + this.print(id); + this.space(); + this.tokenChar(61); + this.space(); + this.print(moduleReference); + this.semicolon(); +} +function TSExternalModuleReference(node) { + this.token("require("); + this.print(node.expression); + this.tokenChar(41); +} +function TSNonNullExpression(node) { + this.print(node.expression); + this.tokenChar(33); +} +function TSExportAssignment(node) { + this.word("export"); + this.space(); + this.tokenChar(61); + this.space(); + this.print(node.expression); + this.semicolon(); +} +function TSNamespaceExportDeclaration(node) { + this.word("export"); + this.space(); + this.word("as"); + this.space(); + this.word("namespace"); + this.space(); + this.print(node.id); + this.semicolon(); +} +function tsPrintSignatureDeclarationBase(node) { + const { + typeParameters + } = node; + const parameters = node.parameters; + this.print(typeParameters); + this.tokenChar(40); + this._parameters(parameters, ")"); + const returnType = node.typeAnnotation; + this.print(returnType); +} +function tsPrintClassMemberModifiers(node) { + const isPrivateField = node.type === "ClassPrivateProperty"; + const isPublicField = node.type === "ClassAccessorProperty" || node.type === "ClassProperty"; + printModifiersList(this, node, [isPublicField && node.declare && "declare", !isPrivateField && node.accessibility]); + if (node.static) { + this.word("static"); + this.space(); + } + printModifiersList(this, node, [!isPrivateField && node.abstract && "abstract", !isPrivateField && node.override && "override", (isPublicField || isPrivateField) && node.readonly && "readonly"]); +} +function printBraced(printer, node, cb) { + printer.token("{"); + const exit = printer.enterDelimited(); + cb(); + exit(); + printer.rightBrace(node); +} +function printModifiersList(printer, node, modifiers) { + var _printer$tokenMap2; + const modifiersSet = new Set(); + for (const modifier of modifiers) { + if (modifier) modifiersSet.add(modifier); + } + (_printer$tokenMap2 = printer.tokenMap) == null || _printer$tokenMap2.find(node, tok => { + if (modifiersSet.has(tok.value)) { + printer.token(tok.value); + printer.space(); + modifiersSet.delete(tok.value); + return modifiersSet.size === 0; + } + }); + for (const modifier of modifiersSet) { + printer.word(modifier); + printer.space(); + } +} + +//# sourceMappingURL=typescript.js.map + + +/***/ }), + +/***/ 12123: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports["default"] = void 0; +exports.generate = generate; +var _sourceMap = __nccwpck_require__(18201); +var _printer = __nccwpck_require__(81163); +function normalizeOptions(code, opts, ast) { + if (opts.experimental_preserveFormat) { + if (typeof code !== "string") { + throw new Error("`experimental_preserveFormat` requires the original `code` to be passed to @babel/generator as a string"); + } + if (!opts.retainLines) { + throw new Error("`experimental_preserveFormat` requires `retainLines` to be set to `true`"); + } + if (opts.compact && opts.compact !== "auto") { + throw new Error("`experimental_preserveFormat` is not compatible with the `compact` option"); + } + if (opts.minified) { + throw new Error("`experimental_preserveFormat` is not compatible with the `minified` option"); + } + if (opts.jsescOption) { + throw new Error("`experimental_preserveFormat` is not compatible with the `jsescOption` option"); + } + if (!Array.isArray(ast.tokens)) { + throw new Error("`experimental_preserveFormat` requires the AST to have attatched the token of the input code. Make sure to enable the `tokens: true` parser option."); + } + } + const format = { + auxiliaryCommentBefore: opts.auxiliaryCommentBefore, + auxiliaryCommentAfter: opts.auxiliaryCommentAfter, + shouldPrintComment: opts.shouldPrintComment, + preserveFormat: opts.experimental_preserveFormat, + retainLines: opts.retainLines, + retainFunctionParens: opts.retainFunctionParens, + comments: opts.comments == null || opts.comments, + compact: opts.compact, + minified: opts.minified, + concise: opts.concise, + indent: { + adjustMultilineComment: true, + style: " " + }, + jsescOption: Object.assign({ + quotes: "double", + wrap: true, + minimal: false + }, opts.jsescOption), + topicToken: opts.topicToken, + importAttributesKeyword: opts.importAttributesKeyword + }; + { + var _opts$recordAndTupleS; + format.decoratorsBeforeExport = opts.decoratorsBeforeExport; + format.jsescOption.json = opts.jsonCompatibleStrings; + format.recordAndTupleSyntaxType = (_opts$recordAndTupleS = opts.recordAndTupleSyntaxType) != null ? _opts$recordAndTupleS : "hash"; + } + if (format.minified) { + format.compact = true; + format.shouldPrintComment = format.shouldPrintComment || (() => format.comments); + } else { + format.shouldPrintComment = format.shouldPrintComment || (value => format.comments || value.includes("@license") || value.includes("@preserve")); + } + if (format.compact === "auto") { + format.compact = typeof code === "string" && code.length > 500000; + if (format.compact) { + console.error("[BABEL] Note: The code generator has deoptimised the styling of " + `${opts.filename} as it exceeds the max of ${"500KB"}.`); + } + } + if (format.compact || format.preserveFormat) { + format.indent.adjustMultilineComment = false; + } + const { + auxiliaryCommentBefore, + auxiliaryCommentAfter, + shouldPrintComment + } = format; + if (auxiliaryCommentBefore && !shouldPrintComment(auxiliaryCommentBefore)) { + format.auxiliaryCommentBefore = undefined; + } + if (auxiliaryCommentAfter && !shouldPrintComment(auxiliaryCommentAfter)) { + format.auxiliaryCommentAfter = undefined; + } + return format; +} +{ + exports.CodeGenerator = class CodeGenerator { + constructor(ast, opts = {}, code) { + this._ast = void 0; + this._format = void 0; + this._map = void 0; + this._ast = ast; + this._format = normalizeOptions(code, opts, ast); + this._map = opts.sourceMaps ? new _sourceMap.default(opts, code) : null; + } + generate() { + const printer = new _printer.default(this._format, this._map); + return printer.generate(this._ast); + } + }; +} +function generate(ast, opts = {}, code) { + const format = normalizeOptions(code, opts, ast); + const map = opts.sourceMaps ? new _sourceMap.default(opts, code) : null; + const printer = new _printer.default(format, map, ast.tokens, typeof code === "string" ? code : null); + return printer.generate(ast); +} +var _default = exports["default"] = generate; + +//# sourceMappingURL=index.js.map + + +/***/ }), + +/***/ 95460: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports.TokenContext = void 0; +exports.isLastChild = isLastChild; +exports.needsParens = needsParens; +exports.needsWhitespace = needsWhitespace; +exports.needsWhitespaceAfter = needsWhitespaceAfter; +exports.needsWhitespaceBefore = needsWhitespaceBefore; +var whitespace = __nccwpck_require__(77971); +var parens = __nccwpck_require__(19504); +var _t = __nccwpck_require__(16535); +const { + FLIPPED_ALIAS_KEYS, + VISITOR_KEYS, + isCallExpression, + isDecorator, + isExpressionStatement, + isMemberExpression, + isNewExpression, + isParenthesizedExpression +} = _t; +const TokenContext = exports.TokenContext = { + expressionStatement: 1, + arrowBody: 2, + exportDefault: 4, + forHead: 8, + forInHead: 16, + forOfHead: 32, + arrowFlowReturnType: 64 +}; +function expandAliases(obj) { + const map = new Map(); + function add(type, func) { + const fn = map.get(type); + map.set(type, fn ? function (node, parent, stack, inForInit, getRawIdentifier) { + var _fn; + return (_fn = fn(node, parent, stack, inForInit, getRawIdentifier)) != null ? _fn : func(node, parent, stack, inForInit, getRawIdentifier); + } : func); + } + for (const type of Object.keys(obj)) { + const aliases = FLIPPED_ALIAS_KEYS[type]; + if (aliases) { + for (const alias of aliases) { + add(alias, obj[type]); + } + } else { + add(type, obj[type]); + } + } + return map; +} +const expandedParens = expandAliases(parens); +const expandedWhitespaceNodes = expandAliases(whitespace.nodes); +function isOrHasCallExpression(node) { + if (isCallExpression(node)) { + return true; + } + return isMemberExpression(node) && isOrHasCallExpression(node.object); +} +function needsWhitespace(node, parent, type) { + var _expandedWhitespaceNo; + if (!node) return false; + if (isExpressionStatement(node)) { + node = node.expression; + } + const flag = (_expandedWhitespaceNo = expandedWhitespaceNodes.get(node.type)) == null ? void 0 : _expandedWhitespaceNo(node, parent); + if (typeof flag === "number") { + return (flag & type) !== 0; + } + return false; +} +function needsWhitespaceBefore(node, parent) { + return needsWhitespace(node, parent, 1); +} +function needsWhitespaceAfter(node, parent) { + return needsWhitespace(node, parent, 2); +} +function needsParens(node, parent, tokenContext, inForInit, getRawIdentifier) { + var _expandedParens$get; + if (!parent) return false; + if (isNewExpression(parent) && parent.callee === node) { + if (isOrHasCallExpression(node)) return true; + } + if (isDecorator(parent)) { + return !isDecoratorMemberExpression(node) && !(isCallExpression(node) && isDecoratorMemberExpression(node.callee)) && !isParenthesizedExpression(node); + } + return (_expandedParens$get = expandedParens.get(node.type)) == null ? void 0 : _expandedParens$get(node, parent, tokenContext, inForInit, getRawIdentifier); +} +function isDecoratorMemberExpression(node) { + switch (node.type) { + case "Identifier": + return true; + case "MemberExpression": + return !node.computed && node.property.type === "Identifier" && isDecoratorMemberExpression(node.object); + default: + return false; + } +} +function isLastChild(parent, child) { + const visitorKeys = VISITOR_KEYS[parent.type]; + for (let i = visitorKeys.length - 1; i >= 0; i--) { + const val = parent[visitorKeys[i]]; + if (val === child) { + return true; + } else if (Array.isArray(val)) { + let j = val.length - 1; + while (j >= 0 && val[j] === null) j--; + return j >= 0 && val[j] === child; + } else if (val) { + return false; + } + } + return false; +} + +//# sourceMappingURL=index.js.map + + +/***/ }), + +/***/ 19504: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports.AssignmentExpression = AssignmentExpression; +exports.Binary = Binary; +exports.BinaryExpression = BinaryExpression; +exports.ClassExpression = ClassExpression; +exports.ArrowFunctionExpression = exports.ConditionalExpression = ConditionalExpression; +exports.DoExpression = DoExpression; +exports.FunctionExpression = FunctionExpression; +exports.FunctionTypeAnnotation = FunctionTypeAnnotation; +exports.Identifier = Identifier; +exports.LogicalExpression = LogicalExpression; +exports.NullableTypeAnnotation = NullableTypeAnnotation; +exports.ObjectExpression = ObjectExpression; +exports.OptionalIndexedAccessType = OptionalIndexedAccessType; +exports.OptionalCallExpression = exports.OptionalMemberExpression = OptionalMemberExpression; +exports.SequenceExpression = SequenceExpression; +exports.TSSatisfiesExpression = exports.TSAsExpression = TSAsExpression; +exports.TSConditionalType = TSConditionalType; +exports.TSConstructorType = exports.TSFunctionType = TSFunctionType; +exports.TSInferType = TSInferType; +exports.TSInstantiationExpression = TSInstantiationExpression; +exports.TSIntersectionType = TSIntersectionType; +exports.UnaryLike = exports.TSTypeAssertion = UnaryLike; +exports.TSTypeOperator = TSTypeOperator; +exports.TSUnionType = TSUnionType; +exports.IntersectionTypeAnnotation = exports.UnionTypeAnnotation = UnionTypeAnnotation; +exports.UpdateExpression = UpdateExpression; +exports.AwaitExpression = exports.YieldExpression = YieldExpression; +var _t = __nccwpck_require__(16535); +var _index = __nccwpck_require__(95460); +const { + isArrayTypeAnnotation, + isBinaryExpression, + isCallExpression, + isForOfStatement, + isIndexedAccessType, + isMemberExpression, + isObjectPattern, + isOptionalMemberExpression, + isYieldExpression, + isStatement +} = _t; +const PRECEDENCE = new Map([["||", 0], ["??", 0], ["|>", 0], ["&&", 1], ["|", 2], ["^", 3], ["&", 4], ["==", 5], ["===", 5], ["!=", 5], ["!==", 5], ["<", 6], [">", 6], ["<=", 6], [">=", 6], ["in", 6], ["instanceof", 6], [">>", 7], ["<<", 7], [">>>", 7], ["+", 8], ["-", 8], ["*", 9], ["/", 9], ["%", 9], ["**", 10]]); +function getBinaryPrecedence(node, nodeType) { + if (nodeType === "BinaryExpression" || nodeType === "LogicalExpression") { + return PRECEDENCE.get(node.operator); + } + if (nodeType === "TSAsExpression" || nodeType === "TSSatisfiesExpression") { + return PRECEDENCE.get("in"); + } +} +function isTSTypeExpression(nodeType) { + return nodeType === "TSAsExpression" || nodeType === "TSSatisfiesExpression" || nodeType === "TSTypeAssertion"; +} +const isClassExtendsClause = (node, parent) => { + const parentType = parent.type; + return (parentType === "ClassDeclaration" || parentType === "ClassExpression") && parent.superClass === node; +}; +const hasPostfixPart = (node, parent) => { + const parentType = parent.type; + return (parentType === "MemberExpression" || parentType === "OptionalMemberExpression") && parent.object === node || (parentType === "CallExpression" || parentType === "OptionalCallExpression" || parentType === "NewExpression") && parent.callee === node || parentType === "TaggedTemplateExpression" && parent.tag === node || parentType === "TSNonNullExpression"; +}; +function NullableTypeAnnotation(node, parent) { + return isArrayTypeAnnotation(parent); +} +function FunctionTypeAnnotation(node, parent, tokenContext) { + const parentType = parent.type; + return (parentType === "UnionTypeAnnotation" || parentType === "IntersectionTypeAnnotation" || parentType === "ArrayTypeAnnotation" || Boolean(tokenContext & _index.TokenContext.arrowFlowReturnType) + ); +} +function UpdateExpression(node, parent) { + return hasPostfixPart(node, parent) || isClassExtendsClause(node, parent); +} +function needsParenBeforeExpressionBrace(tokenContext) { + return Boolean(tokenContext & (_index.TokenContext.expressionStatement | _index.TokenContext.arrowBody)); +} +function ObjectExpression(node, parent, tokenContext) { + return needsParenBeforeExpressionBrace(tokenContext); +} +function DoExpression(node, parent, tokenContext) { + return !node.async && Boolean(tokenContext & _index.TokenContext.expressionStatement); +} +function Binary(node, parent) { + const parentType = parent.type; + if (node.type === "BinaryExpression" && node.operator === "**" && parentType === "BinaryExpression" && parent.operator === "**") { + return parent.left === node; + } + if (isClassExtendsClause(node, parent)) { + return true; + } + if (hasPostfixPart(node, parent) || parentType === "UnaryExpression" || parentType === "SpreadElement" || parentType === "AwaitExpression") { + return true; + } + const parentPos = getBinaryPrecedence(parent, parentType); + if (parentPos != null) { + const nodePos = getBinaryPrecedence(node, node.type); + if (parentPos === nodePos && parentType === "BinaryExpression" && parent.right === node || parentPos > nodePos) { + return true; + } + } + return undefined; +} +function UnionTypeAnnotation(node, parent) { + const parentType = parent.type; + return parentType === "ArrayTypeAnnotation" || parentType === "NullableTypeAnnotation" || parentType === "IntersectionTypeAnnotation" || parentType === "UnionTypeAnnotation"; +} +function OptionalIndexedAccessType(node, parent) { + return isIndexedAccessType(parent) && parent.objectType === node; +} +function TSAsExpression(node, parent) { + if ((parent.type === "AssignmentExpression" || parent.type === "AssignmentPattern") && parent.left === node) { + return true; + } + if (parent.type === "BinaryExpression" && (parent.operator === "|" || parent.operator === "&") && node === parent.left) { + return true; + } + return Binary(node, parent); +} +function TSConditionalType(node, parent) { + const parentType = parent.type; + if (parentType === "TSArrayType" || parentType === "TSIndexedAccessType" && parent.objectType === node || parentType === "TSOptionalType" || parentType === "TSTypeOperator" || parentType === "TSTypeParameter") { + return true; + } + if ((parentType === "TSIntersectionType" || parentType === "TSUnionType") && parent.types[0] === node) { + return true; + } + if (parentType === "TSConditionalType" && (parent.checkType === node || parent.extendsType === node)) { + return true; + } + return false; +} +function TSUnionType(node, parent) { + const parentType = parent.type; + return parentType === "TSIntersectionType" || parentType === "TSTypeOperator" || parentType === "TSArrayType" || parentType === "TSIndexedAccessType" && parent.objectType === node || parentType === "TSOptionalType"; +} +function TSIntersectionType(node, parent) { + const parentType = parent.type; + return parentType === "TSTypeOperator" || parentType === "TSArrayType" || parentType === "TSIndexedAccessType" && parent.objectType === node || parentType === "TSOptionalType"; +} +function TSInferType(node, parent) { + const parentType = parent.type; + if (parentType === "TSArrayType" || parentType === "TSIndexedAccessType" && parent.objectType === node || parentType === "TSOptionalType") { + return true; + } + if (node.typeParameter.constraint) { + if ((parentType === "TSIntersectionType" || parentType === "TSUnionType") && parent.types[0] === node) { + return true; + } + } + return false; +} +function TSTypeOperator(node, parent) { + const parentType = parent.type; + return parentType === "TSArrayType" || parentType === "TSIndexedAccessType" && parent.objectType === node || parentType === "TSOptionalType"; +} +function TSInstantiationExpression(node, parent) { + const parentType = parent.type; + return (parentType === "CallExpression" || parentType === "OptionalCallExpression" || parentType === "NewExpression" || parentType === "TSInstantiationExpression") && !!parent.typeParameters; +} +function TSFunctionType(node, parent) { + const parentType = parent.type; + return parentType === "TSIntersectionType" || parentType === "TSUnionType" || parentType === "TSTypeOperator" || parentType === "TSOptionalType" || parentType === "TSArrayType" || parentType === "TSIndexedAccessType" && parent.objectType === node || parentType === "TSConditionalType" && (parent.checkType === node || parent.extendsType === node); +} +function BinaryExpression(node, parent, tokenContext, inForStatementInit) { + return node.operator === "in" && inForStatementInit; +} +function SequenceExpression(node, parent) { + const parentType = parent.type; + if (parentType === "SequenceExpression" || parentType === "ParenthesizedExpression" || parentType === "MemberExpression" && parent.property === node || parentType === "OptionalMemberExpression" && parent.property === node || parentType === "TemplateLiteral") { + return false; + } + if (parentType === "ClassDeclaration") { + return true; + } + if (parentType === "ForOfStatement") { + return parent.right === node; + } + if (parentType === "ExportDefaultDeclaration") { + return true; + } + return !isStatement(parent); +} +function YieldExpression(node, parent) { + const parentType = parent.type; + return parentType === "BinaryExpression" || parentType === "LogicalExpression" || parentType === "UnaryExpression" || parentType === "SpreadElement" || hasPostfixPart(node, parent) || parentType === "AwaitExpression" && isYieldExpression(node) || parentType === "ConditionalExpression" && node === parent.test || isClassExtendsClause(node, parent) || isTSTypeExpression(parentType); +} +function ClassExpression(node, parent, tokenContext) { + return Boolean(tokenContext & (_index.TokenContext.expressionStatement | _index.TokenContext.exportDefault)); +} +function UnaryLike(node, parent) { + return hasPostfixPart(node, parent) || isBinaryExpression(parent) && parent.operator === "**" && parent.left === node || isClassExtendsClause(node, parent); +} +function FunctionExpression(node, parent, tokenContext) { + return Boolean(tokenContext & (_index.TokenContext.expressionStatement | _index.TokenContext.exportDefault)); +} +function ConditionalExpression(node, parent) { + const parentType = parent.type; + if (parentType === "UnaryExpression" || parentType === "SpreadElement" || parentType === "BinaryExpression" || parentType === "LogicalExpression" || parentType === "ConditionalExpression" && parent.test === node || parentType === "AwaitExpression" || isTSTypeExpression(parentType)) { + return true; + } + return UnaryLike(node, parent); +} +function OptionalMemberExpression(node, parent) { + return isCallExpression(parent) && parent.callee === node || isMemberExpression(parent) && parent.object === node; +} +function AssignmentExpression(node, parent, tokenContext) { + if (needsParenBeforeExpressionBrace(tokenContext) && isObjectPattern(node.left)) { + return true; + } else { + return ConditionalExpression(node, parent); + } +} +function LogicalExpression(node, parent) { + const parentType = parent.type; + if (isTSTypeExpression(parentType)) return true; + if (parentType !== "LogicalExpression") return false; + switch (node.operator) { + case "||": + return parent.operator === "??" || parent.operator === "&&"; + case "&&": + return parent.operator === "??"; + case "??": + return parent.operator !== "??"; + } +} +function Identifier(node, parent, tokenContext, _inForInit, getRawIdentifier) { + var _node$extra; + const parentType = parent.type; + if ((_node$extra = node.extra) != null && _node$extra.parenthesized && parentType === "AssignmentExpression" && parent.left === node) { + const rightType = parent.right.type; + if ((rightType === "FunctionExpression" || rightType === "ClassExpression") && parent.right.id == null) { + return true; + } + } + if (getRawIdentifier && getRawIdentifier(node) !== node.name) { + return false; + } + if (node.name === "let") { + const isFollowedByBracket = isMemberExpression(parent, { + object: node, + computed: true + }) || isOptionalMemberExpression(parent, { + object: node, + computed: true, + optional: false + }); + if (isFollowedByBracket && tokenContext & (_index.TokenContext.expressionStatement | _index.TokenContext.forHead | _index.TokenContext.forInHead)) { + return true; + } + return Boolean(tokenContext & _index.TokenContext.forOfHead); + } + return node.name === "async" && isForOfStatement(parent, { + left: node, + await: false + }); +} + +//# sourceMappingURL=parentheses.js.map + + +/***/ }), + +/***/ 77971: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports.nodes = void 0; +var _t = __nccwpck_require__(16535); +const { + FLIPPED_ALIAS_KEYS, + isArrayExpression, + isAssignmentExpression, + isBinary, + isBlockStatement, + isCallExpression, + isFunction, + isIdentifier, + isLiteral, + isMemberExpression, + isObjectExpression, + isOptionalCallExpression, + isOptionalMemberExpression, + isStringLiteral +} = _t; +function crawlInternal(node, state) { + if (!node) return state; + if (isMemberExpression(node) || isOptionalMemberExpression(node)) { + crawlInternal(node.object, state); + if (node.computed) crawlInternal(node.property, state); + } else if (isBinary(node) || isAssignmentExpression(node)) { + crawlInternal(node.left, state); + crawlInternal(node.right, state); + } else if (isCallExpression(node) || isOptionalCallExpression(node)) { + state.hasCall = true; + crawlInternal(node.callee, state); + } else if (isFunction(node)) { + state.hasFunction = true; + } else if (isIdentifier(node)) { + state.hasHelper = state.hasHelper || node.callee && isHelper(node.callee); + } + return state; +} +function crawl(node) { + return crawlInternal(node, { + hasCall: false, + hasFunction: false, + hasHelper: false + }); +} +function isHelper(node) { + if (!node) return false; + if (isMemberExpression(node)) { + return isHelper(node.object) || isHelper(node.property); + } else if (isIdentifier(node)) { + return node.name === "require" || node.name.charCodeAt(0) === 95; + } else if (isCallExpression(node)) { + return isHelper(node.callee); + } else if (isBinary(node) || isAssignmentExpression(node)) { + return isIdentifier(node.left) && isHelper(node.left) || isHelper(node.right); + } else { + return false; + } +} +function isType(node) { + return isLiteral(node) || isObjectExpression(node) || isArrayExpression(node) || isIdentifier(node) || isMemberExpression(node); +} +const nodes = exports.nodes = { + AssignmentExpression(node) { + const state = crawl(node.right); + if (state.hasCall && state.hasHelper || state.hasFunction) { + return state.hasFunction ? 1 | 2 : 2; + } + }, + SwitchCase(node, parent) { + return (!!node.consequent.length || parent.cases[0] === node ? 1 : 0) | (!node.consequent.length && parent.cases[parent.cases.length - 1] === node ? 2 : 0); + }, + LogicalExpression(node) { + if (isFunction(node.left) || isFunction(node.right)) { + return 2; + } + }, + Literal(node) { + if (isStringLiteral(node) && node.value === "use strict") { + return 2; + } + }, + CallExpression(node) { + if (isFunction(node.callee) || isHelper(node)) { + return 1 | 2; + } + }, + OptionalCallExpression(node) { + if (isFunction(node.callee)) { + return 1 | 2; + } + }, + VariableDeclaration(node) { + for (let i = 0; i < node.declarations.length; i++) { + const declar = node.declarations[i]; + let enabled = isHelper(declar.id) && !isType(declar.init); + if (!enabled && declar.init) { + const state = crawl(declar.init); + enabled = isHelper(declar.init) && state.hasCall || state.hasFunction; + } + if (enabled) { + return 1 | 2; + } + } + }, + IfStatement(node) { + if (isBlockStatement(node.consequent)) { + return 1 | 2; + } + } +}; +nodes.ObjectProperty = nodes.ObjectTypeProperty = nodes.ObjectMethod = function (node, parent) { + if (parent.properties[0] === node) { + return 1; + } +}; +nodes.ObjectTypeCallProperty = function (node, parent) { + var _parent$properties; + if (parent.callProperties[0] === node && !((_parent$properties = parent.properties) != null && _parent$properties.length)) { + return 1; + } +}; +nodes.ObjectTypeIndexer = function (node, parent) { + var _parent$properties2, _parent$callPropertie; + if (parent.indexers[0] === node && !((_parent$properties2 = parent.properties) != null && _parent$properties2.length) && !((_parent$callPropertie = parent.callProperties) != null && _parent$callPropertie.length)) { + return 1; + } +}; +nodes.ObjectTypeInternalSlot = function (node, parent) { + var _parent$properties3, _parent$callPropertie2, _parent$indexers; + if (parent.internalSlots[0] === node && !((_parent$properties3 = parent.properties) != null && _parent$properties3.length) && !((_parent$callPropertie2 = parent.callProperties) != null && _parent$callPropertie2.length) && !((_parent$indexers = parent.indexers) != null && _parent$indexers.length)) { + return 1; + } +}; +[["Function", true], ["Class", true], ["Loop", true], ["LabeledStatement", true], ["SwitchStatement", true], ["TryStatement", true]].forEach(function ([type, amounts]) { + [type].concat(FLIPPED_ALIAS_KEYS[type] || []).forEach(function (type) { + const ret = amounts ? 1 | 2 : 0; + nodes[type] = () => ret; + }); +}); + +//# sourceMappingURL=whitespace.js.map + + +/***/ }), + +/***/ 81163: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports["default"] = void 0; +var _buffer = __nccwpck_require__(11669); +var n = __nccwpck_require__(95460); +var _t = __nccwpck_require__(16535); +var _tokenMap = __nccwpck_require__(93849); +var generatorFunctions = __nccwpck_require__(39662); +var _deprecated = __nccwpck_require__(60617); +const { + isExpression, + isFunction, + isStatement, + isClassBody, + isTSInterfaceBody, + isTSEnumMember +} = _t; +const SCIENTIFIC_NOTATION = /e/i; +const ZERO_DECIMAL_INTEGER = /\.0+$/; +const HAS_NEWLINE = /[\n\r\u2028\u2029]/; +const HAS_NEWLINE_OR_BlOCK_COMMENT_END = /[\n\r\u2028\u2029]|\*\//; +function commentIsNewline(c) { + return c.type === "CommentLine" || HAS_NEWLINE.test(c.value); +} +const { + needsParens +} = n; +class Printer { + constructor(format, map, tokens, originalCode) { + this.inForStatementInit = false; + this.tokenContext = 0; + this._tokens = null; + this._originalCode = null; + this._currentNode = null; + this._indent = 0; + this._indentRepeat = 0; + this._insideAux = false; + this._noLineTerminator = false; + this._noLineTerminatorAfterNode = null; + this._printAuxAfterOnNextUserNode = false; + this._printedComments = new Set(); + this._endsWithInteger = false; + this._endsWithWord = false; + this._endsWithDiv = false; + this._lastCommentLine = 0; + this._endsWithInnerRaw = false; + this._indentInnerComments = true; + this.tokenMap = null; + this._boundGetRawIdentifier = this._getRawIdentifier.bind(this); + this._printSemicolonBeforeNextNode = -1; + this._printSemicolonBeforeNextToken = -1; + this.format = format; + this._tokens = tokens; + this._originalCode = originalCode; + this._indentRepeat = format.indent.style.length; + this._inputMap = map == null ? void 0 : map._inputMap; + this._buf = new _buffer.default(map, format.indent.style[0]); + } + enterForStatementInit() { + if (this.inForStatementInit) return () => {}; + this.inForStatementInit = true; + return () => { + this.inForStatementInit = false; + }; + } + enterDelimited() { + const oldInForStatementInit = this.inForStatementInit; + const oldNoLineTerminatorAfterNode = this._noLineTerminatorAfterNode; + if (oldInForStatementInit === false && oldNoLineTerminatorAfterNode === null) { + return () => {}; + } + this.inForStatementInit = false; + this._noLineTerminatorAfterNode = null; + return () => { + this.inForStatementInit = oldInForStatementInit; + this._noLineTerminatorAfterNode = oldNoLineTerminatorAfterNode; + }; + } + generate(ast) { + if (this.format.preserveFormat) { + this.tokenMap = new _tokenMap.TokenMap(ast, this._tokens, this._originalCode); + } + this.print(ast); + this._maybeAddAuxComment(); + return this._buf.get(); + } + indent() { + const { + format + } = this; + if (format.preserveFormat || format.compact || format.concise) { + return; + } + this._indent++; + } + dedent() { + const { + format + } = this; + if (format.preserveFormat || format.compact || format.concise) { + return; + } + this._indent--; + } + semicolon(force = false) { + this._maybeAddAuxComment(); + if (force) { + this._appendChar(59); + this._noLineTerminator = false; + return; + } + if (this.tokenMap) { + const node = this._currentNode; + if (node.start != null && node.end != null) { + if (!this.tokenMap.endMatches(node, ";")) { + this._printSemicolonBeforeNextNode = this._buf.getCurrentLine(); + return; + } + const indexes = this.tokenMap.getIndexes(this._currentNode); + this._catchUpTo(this._tokens[indexes[indexes.length - 1]].loc.start); + } + } + this._queue(59); + this._noLineTerminator = false; + } + rightBrace(node) { + if (this.format.minified) { + this._buf.removeLastSemicolon(); + } + this.sourceWithOffset("end", node.loc, -1); + this.tokenChar(125); + } + rightParens(node) { + this.sourceWithOffset("end", node.loc, -1); + this.tokenChar(41); + } + space(force = false) { + const { + format + } = this; + if (format.compact || format.preserveFormat) return; + if (force) { + this._space(); + } else if (this._buf.hasContent()) { + const lastCp = this.getLastChar(); + if (lastCp !== 32 && lastCp !== 10) { + this._space(); + } + } + } + word(str, noLineTerminatorAfter = false) { + this.tokenContext = 0; + this._maybePrintInnerComments(str); + this._maybeAddAuxComment(); + if (this.tokenMap) this._catchUpToCurrentToken(str); + if (this._endsWithWord || this._endsWithDiv && str.charCodeAt(0) === 47) { + this._space(); + } + this._append(str, false); + this._endsWithWord = true; + this._noLineTerminator = noLineTerminatorAfter; + } + number(str, number) { + function isNonDecimalLiteral(str) { + if (str.length > 2 && str.charCodeAt(0) === 48) { + const secondChar = str.charCodeAt(1); + return secondChar === 98 || secondChar === 111 || secondChar === 120; + } + return false; + } + this.word(str); + this._endsWithInteger = Number.isInteger(number) && !isNonDecimalLiteral(str) && !SCIENTIFIC_NOTATION.test(str) && !ZERO_DECIMAL_INTEGER.test(str) && str.charCodeAt(str.length - 1) !== 46; + } + token(str, maybeNewline = false, occurrenceCount = 0) { + this.tokenContext = 0; + this._maybePrintInnerComments(str, occurrenceCount); + this._maybeAddAuxComment(); + if (this.tokenMap) this._catchUpToCurrentToken(str, occurrenceCount); + const lastChar = this.getLastChar(); + const strFirst = str.charCodeAt(0); + if (lastChar === 33 && (str === "--" || strFirst === 61) || strFirst === 43 && lastChar === 43 || strFirst === 45 && lastChar === 45 || strFirst === 46 && this._endsWithInteger) { + this._space(); + } + this._append(str, maybeNewline); + this._noLineTerminator = false; + } + tokenChar(char) { + this.tokenContext = 0; + const str = String.fromCharCode(char); + this._maybePrintInnerComments(str); + this._maybeAddAuxComment(); + if (this.tokenMap) this._catchUpToCurrentToken(str); + const lastChar = this.getLastChar(); + if (char === 43 && lastChar === 43 || char === 45 && lastChar === 45 || char === 46 && this._endsWithInteger) { + this._space(); + } + this._appendChar(char); + this._noLineTerminator = false; + } + newline(i = 1, force) { + if (i <= 0) return; + if (!force) { + if (this.format.retainLines || this.format.compact) return; + if (this.format.concise) { + this.space(); + return; + } + } + if (i > 2) i = 2; + i -= this._buf.getNewlineCount(); + for (let j = 0; j < i; j++) { + this._newline(); + } + return; + } + endsWith(char) { + return this.getLastChar() === char; + } + getLastChar() { + return this._buf.getLastChar(); + } + endsWithCharAndNewline() { + return this._buf.endsWithCharAndNewline(); + } + removeTrailingNewline() { + this._buf.removeTrailingNewline(); + } + exactSource(loc, cb) { + if (!loc) { + cb(); + return; + } + this._catchUp("start", loc); + this._buf.exactSource(loc, cb); + } + source(prop, loc) { + if (!loc) return; + this._catchUp(prop, loc); + this._buf.source(prop, loc); + } + sourceWithOffset(prop, loc, columnOffset) { + if (!loc || this.format.preserveFormat) return; + this._catchUp(prop, loc); + this._buf.sourceWithOffset(prop, loc, columnOffset); + } + sourceIdentifierName(identifierName, pos) { + if (!this._buf._canMarkIdName) return; + const sourcePosition = this._buf._sourcePosition; + sourcePosition.identifierNamePos = pos; + sourcePosition.identifierName = identifierName; + } + _space() { + this._queue(32); + } + _newline() { + this._queue(10); + } + _catchUpToCurrentToken(str, occurrenceCount = 0) { + const token = this.tokenMap.findMatching(this._currentNode, str, occurrenceCount); + if (token) this._catchUpTo(token.loc.start); + if (this._printSemicolonBeforeNextToken !== -1 && this._printSemicolonBeforeNextToken === this._buf.getCurrentLine()) { + this._buf.appendChar(59); + this._endsWithWord = false; + this._endsWithInteger = false; + this._endsWithDiv = false; + } + this._printSemicolonBeforeNextToken = -1; + this._printSemicolonBeforeNextNode = -1; + } + _append(str, maybeNewline) { + this._maybeIndent(str.charCodeAt(0)); + this._buf.append(str, maybeNewline); + this._endsWithWord = false; + this._endsWithInteger = false; + this._endsWithDiv = false; + } + _appendChar(char) { + this._maybeIndent(char); + this._buf.appendChar(char); + this._endsWithWord = false; + this._endsWithInteger = false; + this._endsWithDiv = false; + } + _queue(char) { + this._maybeIndent(char); + this._buf.queue(char); + this._endsWithWord = false; + this._endsWithInteger = false; + } + _maybeIndent(firstChar) { + if (this._indent && firstChar !== 10 && this.endsWith(10)) { + this._buf.queueIndentation(this._getIndent()); + } + } + _shouldIndent(firstChar) { + if (this._indent && firstChar !== 10 && this.endsWith(10)) { + return true; + } + } + catchUp(line) { + if (!this.format.retainLines) return; + const count = line - this._buf.getCurrentLine(); + for (let i = 0; i < count; i++) { + this._newline(); + } + } + _catchUp(prop, loc) { + const { + format + } = this; + if (!format.preserveFormat) { + if (format.retainLines && loc != null && loc[prop]) { + this.catchUp(loc[prop].line); + } + return; + } + const pos = loc == null ? void 0 : loc[prop]; + if (pos != null) this._catchUpTo(pos); + } + _catchUpTo({ + line, + column, + index + }) { + const count = line - this._buf.getCurrentLine(); + if (count > 0 && this._noLineTerminator) { + return; + } + for (let i = 0; i < count; i++) { + this._newline(); + } + const spacesCount = count > 0 ? column : column - this._buf.getCurrentColumn(); + if (spacesCount > 0) { + const spaces = this._originalCode ? this._originalCode.slice(index - spacesCount, index).replace(/[^\t\x0B\f \xA0\u1680\u2000-\u200A\u202F\u205F\u3000\uFEFF]/gu, " ") : " ".repeat(spacesCount); + this._append(spaces, false); + } + } + _getIndent() { + return this._indentRepeat * this._indent; + } + printTerminatorless(node) { + this._noLineTerminator = true; + this.print(node); + } + print(node, noLineTerminatorAfter, trailingCommentsLineOffset) { + var _node$extra, _node$leadingComments, _node$leadingComments2; + if (!node) return; + this._endsWithInnerRaw = false; + const nodeType = node.type; + const format = this.format; + const oldConcise = format.concise; + if (node._compact) { + format.concise = true; + } + const printMethod = this[nodeType]; + if (printMethod === undefined) { + throw new ReferenceError(`unknown node of type ${JSON.stringify(nodeType)} with constructor ${JSON.stringify(node.constructor.name)}`); + } + const parent = this._currentNode; + this._currentNode = node; + if (this.tokenMap) { + this._printSemicolonBeforeNextToken = this._printSemicolonBeforeNextNode; + } + const oldInAux = this._insideAux; + this._insideAux = node.loc == null; + this._maybeAddAuxComment(this._insideAux && !oldInAux); + const parenthesized = (_node$extra = node.extra) == null ? void 0 : _node$extra.parenthesized; + let shouldPrintParens = parenthesized && format.preserveFormat || parenthesized && format.retainFunctionParens && nodeType === "FunctionExpression" || needsParens(node, parent, this.tokenContext, this.inForStatementInit, format.preserveFormat ? this._boundGetRawIdentifier : undefined); + if (!shouldPrintParens && parenthesized && (_node$leadingComments = node.leadingComments) != null && _node$leadingComments.length && node.leadingComments[0].type === "CommentBlock") { + const parentType = parent == null ? void 0 : parent.type; + switch (parentType) { + case "ExpressionStatement": + case "VariableDeclarator": + case "AssignmentExpression": + case "ReturnStatement": + break; + case "CallExpression": + case "OptionalCallExpression": + case "NewExpression": + if (parent.callee !== node) break; + default: + shouldPrintParens = true; + } + } + let indentParenthesized = false; + if (!shouldPrintParens && this._noLineTerminator && ((_node$leadingComments2 = node.leadingComments) != null && _node$leadingComments2.some(commentIsNewline) || this.format.retainLines && node.loc && node.loc.start.line > this._buf.getCurrentLine())) { + shouldPrintParens = true; + indentParenthesized = true; + } + let oldNoLineTerminatorAfterNode; + let oldInForStatementInitWasTrue; + if (!shouldPrintParens) { + noLineTerminatorAfter || (noLineTerminatorAfter = parent && this._noLineTerminatorAfterNode === parent && n.isLastChild(parent, node)); + if (noLineTerminatorAfter) { + var _node$trailingComment; + if ((_node$trailingComment = node.trailingComments) != null && _node$trailingComment.some(commentIsNewline)) { + if (isExpression(node)) shouldPrintParens = true; + } else { + oldNoLineTerminatorAfterNode = this._noLineTerminatorAfterNode; + this._noLineTerminatorAfterNode = node; + } + } + } + if (shouldPrintParens) { + this.tokenChar(40); + if (indentParenthesized) this.indent(); + this._endsWithInnerRaw = false; + if (this.inForStatementInit) { + oldInForStatementInitWasTrue = true; + this.inForStatementInit = false; + } + oldNoLineTerminatorAfterNode = this._noLineTerminatorAfterNode; + this._noLineTerminatorAfterNode = null; + } + this._lastCommentLine = 0; + this._printLeadingComments(node, parent); + const loc = nodeType === "Program" || nodeType === "File" ? null : node.loc; + this.exactSource(loc, printMethod.bind(this, node, parent)); + if (shouldPrintParens) { + this._printTrailingComments(node, parent); + if (indentParenthesized) { + this.dedent(); + this.newline(); + } + this.tokenChar(41); + this._noLineTerminator = noLineTerminatorAfter; + if (oldInForStatementInitWasTrue) this.inForStatementInit = true; + } else if (noLineTerminatorAfter && !this._noLineTerminator) { + this._noLineTerminator = true; + this._printTrailingComments(node, parent); + } else { + this._printTrailingComments(node, parent, trailingCommentsLineOffset); + } + this._currentNode = parent; + format.concise = oldConcise; + this._insideAux = oldInAux; + if (oldNoLineTerminatorAfterNode !== undefined) { + this._noLineTerminatorAfterNode = oldNoLineTerminatorAfterNode; + } + this._endsWithInnerRaw = false; + } + _maybeAddAuxComment(enteredPositionlessNode) { + if (enteredPositionlessNode) this._printAuxBeforeComment(); + if (!this._insideAux) this._printAuxAfterComment(); + } + _printAuxBeforeComment() { + if (this._printAuxAfterOnNextUserNode) return; + this._printAuxAfterOnNextUserNode = true; + const comment = this.format.auxiliaryCommentBefore; + if (comment) { + this._printComment({ + type: "CommentBlock", + value: comment + }, 0); + } + } + _printAuxAfterComment() { + if (!this._printAuxAfterOnNextUserNode) return; + this._printAuxAfterOnNextUserNode = false; + const comment = this.format.auxiliaryCommentAfter; + if (comment) { + this._printComment({ + type: "CommentBlock", + value: comment + }, 0); + } + } + getPossibleRaw(node) { + const extra = node.extra; + if ((extra == null ? void 0 : extra.raw) != null && extra.rawValue != null && node.value === extra.rawValue) { + return extra.raw; + } + } + printJoin(nodes, statement, indent, separator, printTrailingSeparator, addNewlines, iterator, trailingCommentsLineOffset) { + if (!(nodes != null && nodes.length)) return; + if (indent == null && this.format.retainLines) { + var _nodes$0$loc; + const startLine = (_nodes$0$loc = nodes[0].loc) == null ? void 0 : _nodes$0$loc.start.line; + if (startLine != null && startLine !== this._buf.getCurrentLine()) { + indent = true; + } + } + if (indent) this.indent(); + const newlineOpts = { + addNewlines: addNewlines, + nextNodeStartLine: 0 + }; + const boundSeparator = separator == null ? void 0 : separator.bind(this); + const len = nodes.length; + for (let i = 0; i < len; i++) { + const node = nodes[i]; + if (!node) continue; + if (statement) this._printNewline(i === 0, newlineOpts); + this.print(node, undefined, trailingCommentsLineOffset || 0); + iterator == null || iterator(node, i); + if (boundSeparator != null) { + if (i < len - 1) boundSeparator(i, false);else if (printTrailingSeparator) boundSeparator(i, true); + } + if (statement) { + var _node$trailingComment2; + if (!((_node$trailingComment2 = node.trailingComments) != null && _node$trailingComment2.length)) { + this._lastCommentLine = 0; + } + if (i + 1 === len) { + this.newline(1); + } else { + var _nextNode$loc; + const nextNode = nodes[i + 1]; + newlineOpts.nextNodeStartLine = ((_nextNode$loc = nextNode.loc) == null ? void 0 : _nextNode$loc.start.line) || 0; + this._printNewline(true, newlineOpts); + } + } + } + if (indent) this.dedent(); + } + printAndIndentOnComments(node) { + const indent = node.leadingComments && node.leadingComments.length > 0; + if (indent) this.indent(); + this.print(node); + if (indent) this.dedent(); + } + printBlock(parent) { + const node = parent.body; + if (node.type !== "EmptyStatement") { + this.space(); + } + this.print(node); + } + _printTrailingComments(node, parent, lineOffset) { + const { + innerComments, + trailingComments + } = node; + if (innerComments != null && innerComments.length) { + this._printComments(2, innerComments, node, parent, lineOffset); + } + if (trailingComments != null && trailingComments.length) { + this._printComments(2, trailingComments, node, parent, lineOffset); + } + } + _printLeadingComments(node, parent) { + const comments = node.leadingComments; + if (!(comments != null && comments.length)) return; + this._printComments(0, comments, node, parent); + } + _maybePrintInnerComments(nextTokenStr, nextTokenOccurrenceCount) { + if (this._endsWithInnerRaw) { + var _this$tokenMap; + this.printInnerComments((_this$tokenMap = this.tokenMap) == null ? void 0 : _this$tokenMap.findMatching(this._currentNode, nextTokenStr, nextTokenOccurrenceCount)); + } + this._endsWithInnerRaw = true; + this._indentInnerComments = true; + } + printInnerComments(nextToken) { + const node = this._currentNode; + const comments = node.innerComments; + if (!(comments != null && comments.length)) return; + const hasSpace = this.endsWith(32); + const indent = this._indentInnerComments; + const printedCommentsCount = this._printedComments.size; + if (indent) this.indent(); + this._printComments(1, comments, node, undefined, undefined, nextToken); + if (hasSpace && printedCommentsCount !== this._printedComments.size) { + this.space(); + } + if (indent) this.dedent(); + } + noIndentInnerCommentsHere() { + this._indentInnerComments = false; + } + printSequence(nodes, indent, trailingCommentsLineOffset, addNewlines) { + this.printJoin(nodes, true, indent != null ? indent : false, undefined, undefined, addNewlines, undefined, trailingCommentsLineOffset); + } + printList(items, printTrailingSeparator, statement, indent, separator, iterator) { + this.printJoin(items, statement, indent, separator != null ? separator : commaSeparator, printTrailingSeparator, undefined, iterator); + } + shouldPrintTrailingComma(listEnd) { + if (!this.tokenMap) return null; + const listEndIndex = this.tokenMap.findLastIndex(this._currentNode, token => this.tokenMap.matchesOriginal(token, listEnd)); + if (listEndIndex <= 0) return null; + return this.tokenMap.matchesOriginal(this._tokens[listEndIndex - 1], ","); + } + _printNewline(newLine, opts) { + const format = this.format; + if (format.retainLines || format.compact) return; + if (format.concise) { + this.space(); + return; + } + if (!newLine) { + return; + } + const startLine = opts.nextNodeStartLine; + const lastCommentLine = this._lastCommentLine; + if (startLine > 0 && lastCommentLine > 0) { + const offset = startLine - lastCommentLine; + if (offset >= 0) { + this.newline(offset || 1); + return; + } + } + if (this._buf.hasContent()) { + this.newline(1); + } + } + _shouldPrintComment(comment, nextToken) { + if (comment.ignore) return 0; + if (this._printedComments.has(comment)) return 0; + if (this._noLineTerminator && HAS_NEWLINE_OR_BlOCK_COMMENT_END.test(comment.value)) { + return 2; + } + if (nextToken && this.tokenMap) { + const commentTok = this.tokenMap.find(this._currentNode, token => token.value === comment.value); + if (commentTok && commentTok.start > nextToken.start) { + return 2; + } + } + this._printedComments.add(comment); + if (!this.format.shouldPrintComment(comment.value)) { + return 0; + } + return 1; + } + _printComment(comment, skipNewLines) { + const noLineTerminator = this._noLineTerminator; + const isBlockComment = comment.type === "CommentBlock"; + const printNewLines = isBlockComment && skipNewLines !== 1 && !this._noLineTerminator; + if (printNewLines && this._buf.hasContent() && skipNewLines !== 2) { + this.newline(1); + } + const lastCharCode = this.getLastChar(); + if (lastCharCode !== 91 && lastCharCode !== 123 && lastCharCode !== 40) { + this.space(); + } + let val; + if (isBlockComment) { + val = `/*${comment.value}*/`; + if (this.format.indent.adjustMultilineComment) { + var _comment$loc; + const offset = (_comment$loc = comment.loc) == null ? void 0 : _comment$loc.start.column; + if (offset) { + const newlineRegex = new RegExp("\\n\\s{1," + offset + "}", "g"); + val = val.replace(newlineRegex, "\n"); + } + if (this.format.concise) { + val = val.replace(/\n(?!$)/g, `\n`); + } else { + let indentSize = this.format.retainLines ? 0 : this._buf.getCurrentColumn(); + if (this._shouldIndent(47) || this.format.retainLines) { + indentSize += this._getIndent(); + } + val = val.replace(/\n(?!$)/g, `\n${" ".repeat(indentSize)}`); + } + } + } else if (!noLineTerminator) { + val = `//${comment.value}`; + } else { + val = `/*${comment.value}*/`; + } + if (this._endsWithDiv) this._space(); + if (this.tokenMap) { + const { + _printSemicolonBeforeNextToken, + _printSemicolonBeforeNextNode + } = this; + this._printSemicolonBeforeNextToken = -1; + this._printSemicolonBeforeNextNode = -1; + this.source("start", comment.loc); + this._append(val, isBlockComment); + this._printSemicolonBeforeNextNode = _printSemicolonBeforeNextNode; + this._printSemicolonBeforeNextToken = _printSemicolonBeforeNextToken; + } else { + this.source("start", comment.loc); + this._append(val, isBlockComment); + } + if (!isBlockComment && !noLineTerminator) { + this.newline(1, true); + } + if (printNewLines && skipNewLines !== 3) { + this.newline(1); + } + } + _printComments(type, comments, node, parent, lineOffset = 0, nextToken) { + const nodeLoc = node.loc; + const len = comments.length; + let hasLoc = !!nodeLoc; + const nodeStartLine = hasLoc ? nodeLoc.start.line : 0; + const nodeEndLine = hasLoc ? nodeLoc.end.line : 0; + let lastLine = 0; + let leadingCommentNewline = 0; + const maybeNewline = this._noLineTerminator ? function () {} : this.newline.bind(this); + for (let i = 0; i < len; i++) { + const comment = comments[i]; + const shouldPrint = this._shouldPrintComment(comment, nextToken); + if (shouldPrint === 2) { + hasLoc = false; + break; + } + if (hasLoc && comment.loc && shouldPrint === 1) { + const commentStartLine = comment.loc.start.line; + const commentEndLine = comment.loc.end.line; + if (type === 0) { + let offset = 0; + if (i === 0) { + if (this._buf.hasContent() && (comment.type === "CommentLine" || commentStartLine !== commentEndLine)) { + offset = leadingCommentNewline = 1; + } + } else { + offset = commentStartLine - lastLine; + } + lastLine = commentEndLine; + maybeNewline(offset); + this._printComment(comment, 1); + if (i + 1 === len) { + maybeNewline(Math.max(nodeStartLine - lastLine, leadingCommentNewline)); + lastLine = nodeStartLine; + } + } else if (type === 1) { + const offset = commentStartLine - (i === 0 ? nodeStartLine : lastLine); + lastLine = commentEndLine; + maybeNewline(offset); + this._printComment(comment, 1); + if (i + 1 === len) { + maybeNewline(Math.min(1, nodeEndLine - lastLine)); + lastLine = nodeEndLine; + } + } else { + const offset = commentStartLine - (i === 0 ? nodeEndLine - lineOffset : lastLine); + lastLine = commentEndLine; + maybeNewline(offset); + this._printComment(comment, 1); + } + } else { + hasLoc = false; + if (shouldPrint !== 1) { + continue; + } + if (len === 1) { + const singleLine = comment.loc ? comment.loc.start.line === comment.loc.end.line : !HAS_NEWLINE.test(comment.value); + const shouldSkipNewline = singleLine && !isStatement(node) && !isClassBody(parent) && !isTSInterfaceBody(parent) && !isTSEnumMember(node); + if (type === 0) { + this._printComment(comment, shouldSkipNewline && node.type !== "ObjectExpression" || singleLine && isFunction(parent, { + body: node + }) ? 1 : 0); + } else if (shouldSkipNewline && type === 2) { + this._printComment(comment, 1); + } else { + this._printComment(comment, 0); + } + } else if (type === 1 && !(node.type === "ObjectExpression" && node.properties.length > 1) && node.type !== "ClassBody" && node.type !== "TSInterfaceBody") { + this._printComment(comment, i === 0 ? 2 : i === len - 1 ? 3 : 0); + } else { + this._printComment(comment, 0); + } + } + } + if (type === 2 && hasLoc && lastLine) { + this._lastCommentLine = lastLine; + } + } +} +Object.assign(Printer.prototype, generatorFunctions); +{ + (0, _deprecated.addDeprecatedGenerators)(Printer); +} +var _default = exports["default"] = Printer; +function commaSeparator(occurrenceCount, last) { + this.token(",", false, occurrenceCount); + if (!last) this.space(); +} + +//# sourceMappingURL=printer.js.map + + +/***/ }), + +/***/ 18201: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports["default"] = void 0; +var _genMapping = __nccwpck_require__(36153); +var _traceMapping = __nccwpck_require__(99535); +class SourceMap { + constructor(opts, code) { + var _opts$sourceFileName; + this._map = void 0; + this._rawMappings = void 0; + this._sourceFileName = void 0; + this._lastGenLine = 0; + this._lastSourceLine = 0; + this._lastSourceColumn = 0; + this._inputMap = void 0; + const map = this._map = new _genMapping.GenMapping({ + sourceRoot: opts.sourceRoot + }); + this._sourceFileName = (_opts$sourceFileName = opts.sourceFileName) == null ? void 0 : _opts$sourceFileName.replace(/\\/g, "/"); + this._rawMappings = undefined; + if (opts.inputSourceMap) { + this._inputMap = new _traceMapping.TraceMap(opts.inputSourceMap); + const resolvedSources = this._inputMap.resolvedSources; + if (resolvedSources.length) { + for (let i = 0; i < resolvedSources.length; i++) { + var _this$_inputMap$sourc; + (0, _genMapping.setSourceContent)(map, resolvedSources[i], (_this$_inputMap$sourc = this._inputMap.sourcesContent) == null ? void 0 : _this$_inputMap$sourc[i]); + } + } + } + if (typeof code === "string" && !opts.inputSourceMap) { + (0, _genMapping.setSourceContent)(map, this._sourceFileName, code); + } else if (typeof code === "object") { + for (const sourceFileName of Object.keys(code)) { + (0, _genMapping.setSourceContent)(map, sourceFileName.replace(/\\/g, "/"), code[sourceFileName]); + } + } + } + get() { + return (0, _genMapping.toEncodedMap)(this._map); + } + getDecoded() { + return (0, _genMapping.toDecodedMap)(this._map); + } + getRawMappings() { + return this._rawMappings || (this._rawMappings = (0, _genMapping.allMappings)(this._map)); + } + mark(generated, line, column, identifierName, identifierNamePos, filename) { + var _originalMapping; + this._rawMappings = undefined; + let originalMapping; + if (line != null) { + if (this._inputMap) { + originalMapping = (0, _traceMapping.originalPositionFor)(this._inputMap, { + line, + column + }); + if (!originalMapping.name && identifierNamePos) { + const originalIdentifierMapping = (0, _traceMapping.originalPositionFor)(this._inputMap, identifierNamePos); + if (originalIdentifierMapping.name) { + identifierName = originalIdentifierMapping.name; + } + } + } else { + originalMapping = { + source: (filename == null ? void 0 : filename.replace(/\\/g, "/")) || this._sourceFileName, + line: line, + column: column + }; + } + } + (0, _genMapping.maybeAddMapping)(this._map, { + name: identifierName, + generated, + source: (_originalMapping = originalMapping) == null ? void 0 : _originalMapping.source, + original: originalMapping + }); + } +} +exports["default"] = SourceMap; + +//# sourceMappingURL=source-map.js.map + + +/***/ }), + +/***/ 93849: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports.TokenMap = void 0; +var _t = __nccwpck_require__(16535); +const { + traverseFast, + VISITOR_KEYS +} = _t; +class TokenMap { + constructor(ast, tokens, source) { + this._tokens = void 0; + this._source = void 0; + this._nodesToTokenIndexes = new Map(); + this._nodesOccurrencesCountCache = new Map(); + this._tokensCache = new Map(); + this._tokens = tokens; + this._source = source; + traverseFast(ast, node => { + const indexes = this._getTokensIndexesOfNode(node); + if (indexes.length > 0) this._nodesToTokenIndexes.set(node, indexes); + }); + this._tokensCache = null; + } + has(node) { + return this._nodesToTokenIndexes.has(node); + } + getIndexes(node) { + return this._nodesToTokenIndexes.get(node); + } + find(node, condition) { + const indexes = this._nodesToTokenIndexes.get(node); + if (indexes) { + for (let k = 0; k < indexes.length; k++) { + const index = indexes[k]; + const tok = this._tokens[index]; + if (condition(tok, index)) return tok; + } + } + return null; + } + findLastIndex(node, condition) { + const indexes = this._nodesToTokenIndexes.get(node); + if (indexes) { + for (let k = indexes.length - 1; k >= 0; k--) { + const index = indexes[k]; + const tok = this._tokens[index]; + if (condition(tok, index)) return index; + } + } + return -1; + } + findMatching(node, test, occurrenceCount = 0) { + const indexes = this._nodesToTokenIndexes.get(node); + if (indexes) { + let i = 0; + const count = occurrenceCount; + if (count > 1) { + const cache = this._nodesOccurrencesCountCache.get(node); + if (cache && cache.test === test && cache.count < count) { + i = cache.i + 1; + occurrenceCount -= cache.count + 1; + } + } + for (; i < indexes.length; i++) { + const tok = this._tokens[indexes[i]]; + if (this.matchesOriginal(tok, test)) { + if (occurrenceCount === 0) { + if (count > 0) { + this._nodesOccurrencesCountCache.set(node, { + test, + count, + i + }); + } + return tok; + } + occurrenceCount--; + } + } + } + return null; + } + matchesOriginal(token, test) { + if (token.end - token.start !== test.length) return false; + if (token.value != null) return token.value === test; + return this._source.startsWith(test, token.start); + } + startMatches(node, test) { + const indexes = this._nodesToTokenIndexes.get(node); + if (!indexes) return false; + const tok = this._tokens[indexes[0]]; + if (tok.start !== node.start) return false; + return this.matchesOriginal(tok, test); + } + endMatches(node, test) { + const indexes = this._nodesToTokenIndexes.get(node); + if (!indexes) return false; + const tok = this._tokens[indexes[indexes.length - 1]]; + if (tok.end !== node.end) return false; + return this.matchesOriginal(tok, test); + } + _getTokensIndexesOfNode(node) { + if (node.start == null || node.end == null) return []; + const { + first, + last + } = this._findTokensOfNode(node, 0, this._tokens.length - 1); + let low = first; + const children = childrenIterator(node); + if ((node.type === "ExportNamedDeclaration" || node.type === "ExportDefaultDeclaration") && node.declaration && node.declaration.type === "ClassDeclaration") { + children.next(); + } + const indexes = []; + for (const child of children) { + if (child == null) continue; + if (child.start == null || child.end == null) continue; + const childTok = this._findTokensOfNode(child, low, last); + const high = childTok.first; + for (let k = low; k < high; k++) indexes.push(k); + low = childTok.last + 1; + } + for (let k = low; k <= last; k++) indexes.push(k); + return indexes; + } + _findTokensOfNode(node, low, high) { + const cached = this._tokensCache.get(node); + if (cached) return cached; + const first = this._findFirstTokenOfNode(node.start, low, high); + const last = this._findLastTokenOfNode(node.end, first, high); + this._tokensCache.set(node, { + first, + last + }); + return { + first, + last + }; + } + _findFirstTokenOfNode(start, low, high) { + while (low <= high) { + const mid = high + low >> 1; + if (start < this._tokens[mid].start) { + high = mid - 1; + } else if (start > this._tokens[mid].start) { + low = mid + 1; + } else { + return mid; + } + } + return low; + } + _findLastTokenOfNode(end, low, high) { + while (low <= high) { + const mid = high + low >> 1; + if (end < this._tokens[mid].end) { + high = mid - 1; + } else if (end > this._tokens[mid].end) { + low = mid + 1; + } else { + return mid; + } + } + return high; + } +} +exports.TokenMap = TokenMap; +function* childrenIterator(node) { + if (node.type === "TemplateLiteral") { + yield node.quasis[0]; + for (let i = 1; i < node.quasis.length; i++) { + yield node.expressions[i - 1]; + yield node.quasis[i]; + } + return; + } + const keys = VISITOR_KEYS[node.type]; + for (const key of keys) { + const child = node[key]; + if (!child) continue; + if (Array.isArray(child)) { + yield* child; + } else { + yield child; + } + } +} + +//# sourceMappingURL=token-map.js.map + + +/***/ }), + +/***/ 91235: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports["default"] = void 0; +var _assert = __nccwpck_require__(42613); +var _t = __nccwpck_require__(16535); +const { + callExpression, + cloneNode, + expressionStatement, + identifier, + importDeclaration, + importDefaultSpecifier, + importNamespaceSpecifier, + importSpecifier, + memberExpression, + stringLiteral, + variableDeclaration, + variableDeclarator +} = _t; +class ImportBuilder { + constructor(importedSource, scope, hub) { + this._statements = []; + this._resultName = null; + this._importedSource = void 0; + this._scope = scope; + this._hub = hub; + this._importedSource = importedSource; + } + done() { + return { + statements: this._statements, + resultName: this._resultName + }; + } + import() { + this._statements.push(importDeclaration([], stringLiteral(this._importedSource))); + return this; + } + require() { + this._statements.push(expressionStatement(callExpression(identifier("require"), [stringLiteral(this._importedSource)]))); + return this; + } + namespace(name = "namespace") { + const local = this._scope.generateUidIdentifier(name); + const statement = this._statements[this._statements.length - 1]; + _assert(statement.type === "ImportDeclaration"); + _assert(statement.specifiers.length === 0); + statement.specifiers = [importNamespaceSpecifier(local)]; + this._resultName = cloneNode(local); + return this; + } + default(name) { + const id = this._scope.generateUidIdentifier(name); + const statement = this._statements[this._statements.length - 1]; + _assert(statement.type === "ImportDeclaration"); + _assert(statement.specifiers.length === 0); + statement.specifiers = [importDefaultSpecifier(id)]; + this._resultName = cloneNode(id); + return this; + } + named(name, importName) { + if (importName === "default") return this.default(name); + const id = this._scope.generateUidIdentifier(name); + const statement = this._statements[this._statements.length - 1]; + _assert(statement.type === "ImportDeclaration"); + _assert(statement.specifiers.length === 0); + statement.specifiers = [importSpecifier(id, identifier(importName))]; + this._resultName = cloneNode(id); + return this; + } + var(name) { + const id = this._scope.generateUidIdentifier(name); + let statement = this._statements[this._statements.length - 1]; + if (statement.type !== "ExpressionStatement") { + _assert(this._resultName); + statement = expressionStatement(this._resultName); + this._statements.push(statement); + } + this._statements[this._statements.length - 1] = variableDeclaration("var", [variableDeclarator(id, statement.expression)]); + this._resultName = cloneNode(id); + return this; + } + defaultInterop() { + return this._interop(this._hub.addHelper("interopRequireDefault")); + } + wildcardInterop() { + return this._interop(this._hub.addHelper("interopRequireWildcard")); + } + _interop(callee) { + const statement = this._statements[this._statements.length - 1]; + if (statement.type === "ExpressionStatement") { + statement.expression = callExpression(callee, [statement.expression]); + } else if (statement.type === "VariableDeclaration") { + _assert(statement.declarations.length === 1); + statement.declarations[0].init = callExpression(callee, [statement.declarations[0].init]); + } else { + _assert.fail("Unexpected type."); + } + return this; + } + prop(name) { + const statement = this._statements[this._statements.length - 1]; + if (statement.type === "ExpressionStatement") { + statement.expression = memberExpression(statement.expression, identifier(name)); + } else if (statement.type === "VariableDeclaration") { + _assert(statement.declarations.length === 1); + statement.declarations[0].init = memberExpression(statement.declarations[0].init, identifier(name)); + } else { + _assert.fail("Unexpected type:" + statement.type); + } + return this; + } + read(name) { + this._resultName = memberExpression(this._resultName, identifier(name)); + } +} +exports["default"] = ImportBuilder; + +//# sourceMappingURL=import-builder.js.map + + +/***/ }), + +/***/ 56358: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports["default"] = void 0; +var _assert = __nccwpck_require__(42613); +var _t = __nccwpck_require__(16535); +var _importBuilder = __nccwpck_require__(91235); +var _isModule = __nccwpck_require__(97585); +const { + identifier, + importSpecifier, + numericLiteral, + sequenceExpression, + isImportDeclaration +} = _t; +class ImportInjector { + constructor(path, importedSource, opts) { + this._defaultOpts = { + importedSource: null, + importedType: "commonjs", + importedInterop: "babel", + importingInterop: "babel", + ensureLiveReference: false, + ensureNoContext: false, + importPosition: "before" + }; + const programPath = path.find(p => p.isProgram()); + this._programPath = programPath; + this._programScope = programPath.scope; + this._hub = programPath.hub; + this._defaultOpts = this._applyDefaults(importedSource, opts, true); + } + addDefault(importedSourceIn, opts) { + return this.addNamed("default", importedSourceIn, opts); + } + addNamed(importName, importedSourceIn, opts) { + _assert(typeof importName === "string"); + return this._generateImport(this._applyDefaults(importedSourceIn, opts), importName); + } + addNamespace(importedSourceIn, opts) { + return this._generateImport(this._applyDefaults(importedSourceIn, opts), null); + } + addSideEffect(importedSourceIn, opts) { + return this._generateImport(this._applyDefaults(importedSourceIn, opts), void 0); + } + _applyDefaults(importedSource, opts, isInit = false) { + let newOpts; + if (typeof importedSource === "string") { + newOpts = Object.assign({}, this._defaultOpts, { + importedSource + }, opts); + } else { + _assert(!opts, "Unexpected secondary arguments."); + newOpts = Object.assign({}, this._defaultOpts, importedSource); + } + if (!isInit && opts) { + if (opts.nameHint !== undefined) newOpts.nameHint = opts.nameHint; + if (opts.blockHoist !== undefined) newOpts.blockHoist = opts.blockHoist; + } + return newOpts; + } + _generateImport(opts, importName) { + const isDefault = importName === "default"; + const isNamed = !!importName && !isDefault; + const isNamespace = importName === null; + const { + importedSource, + importedType, + importedInterop, + importingInterop, + ensureLiveReference, + ensureNoContext, + nameHint, + importPosition, + blockHoist + } = opts; + let name = nameHint || importName; + const isMod = (0, _isModule.default)(this._programPath); + const isModuleForNode = isMod && importingInterop === "node"; + const isModuleForBabel = isMod && importingInterop === "babel"; + if (importPosition === "after" && !isMod) { + throw new Error(`"importPosition": "after" is only supported in modules`); + } + const builder = new _importBuilder.default(importedSource, this._programScope, this._hub); + if (importedType === "es6") { + if (!isModuleForNode && !isModuleForBabel) { + throw new Error("Cannot import an ES6 module from CommonJS"); + } + builder.import(); + if (isNamespace) { + builder.namespace(nameHint || importedSource); + } else if (isDefault || isNamed) { + builder.named(name, importName); + } + } else if (importedType !== "commonjs") { + throw new Error(`Unexpected interopType "${importedType}"`); + } else if (importedInterop === "babel") { + if (isModuleForNode) { + name = name !== "default" ? name : importedSource; + const es6Default = `${importedSource}$es6Default`; + builder.import(); + if (isNamespace) { + builder.default(es6Default).var(name || importedSource).wildcardInterop(); + } else if (isDefault) { + if (ensureLiveReference) { + builder.default(es6Default).var(name || importedSource).defaultInterop().read("default"); + } else { + builder.default(es6Default).var(name).defaultInterop().prop(importName); + } + } else if (isNamed) { + builder.default(es6Default).read(importName); + } + } else if (isModuleForBabel) { + builder.import(); + if (isNamespace) { + builder.namespace(name || importedSource); + } else if (isDefault || isNamed) { + builder.named(name, importName); + } + } else { + builder.require(); + if (isNamespace) { + builder.var(name || importedSource).wildcardInterop(); + } else if ((isDefault || isNamed) && ensureLiveReference) { + if (isDefault) { + name = name !== "default" ? name : importedSource; + builder.var(name).read(importName); + builder.defaultInterop(); + } else { + builder.var(importedSource).read(importName); + } + } else if (isDefault) { + builder.var(name).defaultInterop().prop(importName); + } else if (isNamed) { + builder.var(name).prop(importName); + } + } + } else if (importedInterop === "compiled") { + if (isModuleForNode) { + builder.import(); + if (isNamespace) { + builder.default(name || importedSource); + } else if (isDefault || isNamed) { + builder.default(importedSource).read(name); + } + } else if (isModuleForBabel) { + builder.import(); + if (isNamespace) { + builder.namespace(name || importedSource); + } else if (isDefault || isNamed) { + builder.named(name, importName); + } + } else { + builder.require(); + if (isNamespace) { + builder.var(name || importedSource); + } else if (isDefault || isNamed) { + if (ensureLiveReference) { + builder.var(importedSource).read(name); + } else { + builder.prop(importName).var(name); + } + } + } + } else if (importedInterop === "uncompiled") { + if (isDefault && ensureLiveReference) { + throw new Error("No live reference for commonjs default"); + } + if (isModuleForNode) { + builder.import(); + if (isNamespace) { + builder.default(name || importedSource); + } else if (isDefault) { + builder.default(name); + } else if (isNamed) { + builder.default(importedSource).read(name); + } + } else if (isModuleForBabel) { + builder.import(); + if (isNamespace) { + builder.default(name || importedSource); + } else if (isDefault) { + builder.default(name); + } else if (isNamed) { + builder.named(name, importName); + } + } else { + builder.require(); + if (isNamespace) { + builder.var(name || importedSource); + } else if (isDefault) { + builder.var(name); + } else if (isNamed) { + if (ensureLiveReference) { + builder.var(importedSource).read(name); + } else { + builder.var(name).prop(importName); + } + } + } + } else { + throw new Error(`Unknown importedInterop "${importedInterop}".`); + } + const { + statements, + resultName + } = builder.done(); + this._insertStatements(statements, importPosition, blockHoist); + if ((isDefault || isNamed) && ensureNoContext && resultName.type !== "Identifier") { + return sequenceExpression([numericLiteral(0), resultName]); + } + return resultName; + } + _insertStatements(statements, importPosition = "before", blockHoist = 3) { + if (importPosition === "after") { + if (this._insertStatementsAfter(statements)) return; + } else { + if (this._insertStatementsBefore(statements, blockHoist)) return; + } + this._programPath.unshiftContainer("body", statements); + } + _insertStatementsBefore(statements, blockHoist) { + if (statements.length === 1 && isImportDeclaration(statements[0]) && isValueImport(statements[0])) { + const firstImportDecl = this._programPath.get("body").find(p => { + return p.isImportDeclaration() && isValueImport(p.node); + }); + if ((firstImportDecl == null ? void 0 : firstImportDecl.node.source.value) === statements[0].source.value && maybeAppendImportSpecifiers(firstImportDecl.node, statements[0])) { + return true; + } + } + statements.forEach(node => { + node._blockHoist = blockHoist; + }); + const targetPath = this._programPath.get("body").find(p => { + const val = p.node._blockHoist; + return Number.isFinite(val) && val < 4; + }); + if (targetPath) { + targetPath.insertBefore(statements); + return true; + } + return false; + } + _insertStatementsAfter(statements) { + const statementsSet = new Set(statements); + const importDeclarations = new Map(); + for (const statement of statements) { + if (isImportDeclaration(statement) && isValueImport(statement)) { + const source = statement.source.value; + if (!importDeclarations.has(source)) importDeclarations.set(source, []); + importDeclarations.get(source).push(statement); + } + } + let lastImportPath = null; + for (const bodyStmt of this._programPath.get("body")) { + if (bodyStmt.isImportDeclaration() && isValueImport(bodyStmt.node)) { + lastImportPath = bodyStmt; + const source = bodyStmt.node.source.value; + const newImports = importDeclarations.get(source); + if (!newImports) continue; + for (const decl of newImports) { + if (!statementsSet.has(decl)) continue; + if (maybeAppendImportSpecifiers(bodyStmt.node, decl)) { + statementsSet.delete(decl); + } + } + } + } + if (statementsSet.size === 0) return true; + if (lastImportPath) lastImportPath.insertAfter(Array.from(statementsSet)); + return !!lastImportPath; + } +} +exports["default"] = ImportInjector; +function isValueImport(node) { + return node.importKind !== "type" && node.importKind !== "typeof"; +} +function hasNamespaceImport(node) { + return node.specifiers.length === 1 && node.specifiers[0].type === "ImportNamespaceSpecifier" || node.specifiers.length === 2 && node.specifiers[1].type === "ImportNamespaceSpecifier"; +} +function hasDefaultImport(node) { + return node.specifiers.length > 0 && node.specifiers[0].type === "ImportDefaultSpecifier"; +} +function maybeAppendImportSpecifiers(target, source) { + if (!target.specifiers.length) { + target.specifiers = source.specifiers; + return true; + } + if (!source.specifiers.length) return true; + if (hasNamespaceImport(target) || hasNamespaceImport(source)) return false; + if (hasDefaultImport(source)) { + if (hasDefaultImport(target)) { + source.specifiers[0] = importSpecifier(source.specifiers[0].local, identifier("default")); + } else { + target.specifiers.unshift(source.specifiers.shift()); + } + } + target.specifiers.push(...source.specifiers); + return true; +} + +//# sourceMappingURL=import-injector.js.map + + +/***/ }), + +/***/ 10678: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +Object.defineProperty(exports, "ImportInjector", ({ + enumerable: true, + get: function () { + return _importInjector.default; + } +})); +exports.addDefault = addDefault; +exports.addNamed = addNamed; +exports.addNamespace = addNamespace; +exports.addSideEffect = addSideEffect; +Object.defineProperty(exports, "isModule", ({ + enumerable: true, + get: function () { + return _isModule.default; + } +})); +var _importInjector = __nccwpck_require__(56358); +var _isModule = __nccwpck_require__(97585); +function addDefault(path, importedSource, opts) { + return new _importInjector.default(path).addDefault(importedSource, opts); +} +function addNamed(path, name, importedSource, opts) { + return new _importInjector.default(path).addNamed(name, importedSource, opts); +} +function addNamespace(path, importedSource, opts) { + return new _importInjector.default(path).addNamespace(importedSource, opts); +} +function addSideEffect(path, importedSource, opts) { + return new _importInjector.default(path).addSideEffect(importedSource, opts); +} + +//# sourceMappingURL=index.js.map + + +/***/ }), + +/***/ 97585: +/***/ ((__unused_webpack_module, exports) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports["default"] = isModule; +function isModule(path) { + return path.node.sourceType === "module"; +} + +//# sourceMappingURL=is-module.js.map + + +/***/ }), + +/***/ 92840: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports.buildDynamicImport = buildDynamicImport; +var _core = __nccwpck_require__(85414); +{ + exports.getDynamicImportSource = function getDynamicImportSource(node) { + const [source] = node.arguments; + return _core.types.isStringLiteral(source) || _core.types.isTemplateLiteral(source) ? source : _core.template.expression.ast`\`\${${source}}\``; + }; +} +function buildDynamicImport(node, deferToThen, wrapWithPromise, builder) { + const specifier = _core.types.isCallExpression(node) ? node.arguments[0] : node.source; + if (_core.types.isStringLiteral(specifier) || _core.types.isTemplateLiteral(specifier) && specifier.quasis.length === 0) { + if (deferToThen) { + return _core.template.expression.ast` + Promise.resolve().then(() => ${builder(specifier)}) + `; + } else return builder(specifier); + } + const specifierToString = _core.types.isTemplateLiteral(specifier) ? _core.types.identifier("specifier") : _core.types.templateLiteral([_core.types.templateElement({ + raw: "" + }), _core.types.templateElement({ + raw: "" + })], [_core.types.identifier("specifier")]); + if (deferToThen) { + return _core.template.expression.ast` + (specifier => + new Promise(r => r(${specifierToString})) + .then(s => ${builder(_core.types.identifier("s"))}) + )(${specifier}) + `; + } else if (wrapWithPromise) { + return _core.template.expression.ast` + (specifier => + new Promise(r => r(${builder(specifierToString)})) + )(${specifier}) + `; + } else { + return _core.template.expression.ast` + (specifier => ${builder(specifierToString)})(${specifier}) + `; + } +} + +//# sourceMappingURL=dynamic-import.js.map + + +/***/ }), + +/***/ 94036: +/***/ ((__unused_webpack_module, exports) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports["default"] = getModuleName; +{ + const originalGetModuleName = getModuleName; + exports["default"] = getModuleName = function getModuleName(rootOpts, pluginOpts) { + var _pluginOpts$moduleId, _pluginOpts$moduleIds, _pluginOpts$getModule, _pluginOpts$moduleRoo; + return originalGetModuleName(rootOpts, { + moduleId: (_pluginOpts$moduleId = pluginOpts.moduleId) != null ? _pluginOpts$moduleId : rootOpts.moduleId, + moduleIds: (_pluginOpts$moduleIds = pluginOpts.moduleIds) != null ? _pluginOpts$moduleIds : rootOpts.moduleIds, + getModuleId: (_pluginOpts$getModule = pluginOpts.getModuleId) != null ? _pluginOpts$getModule : rootOpts.getModuleId, + moduleRoot: (_pluginOpts$moduleRoo = pluginOpts.moduleRoot) != null ? _pluginOpts$moduleRoo : rootOpts.moduleRoot + }); + }; +} +function getModuleName(rootOpts, pluginOpts) { + const { + filename, + filenameRelative = filename, + sourceRoot = pluginOpts.moduleRoot + } = rootOpts; + const { + moduleId, + moduleIds = !!moduleId, + getModuleId, + moduleRoot = sourceRoot + } = pluginOpts; + if (!moduleIds) return null; + if (moduleId != null && !getModuleId) { + return moduleId; + } + let moduleName = moduleRoot != null ? moduleRoot + "/" : ""; + if (filenameRelative) { + const sourceRootReplacer = sourceRoot != null ? new RegExp("^" + sourceRoot + "/?") : ""; + moduleName += filenameRelative.replace(sourceRootReplacer, "").replace(/\.\w*$/, ""); + } + moduleName = moduleName.replace(/\\/g, "/"); + if (getModuleId) { + return getModuleId(moduleName) || moduleName; + } else { + return moduleName; + } +} + +//# sourceMappingURL=get-module-name.js.map + + +/***/ }), + +/***/ 3665: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +Object.defineProperty(exports, "buildDynamicImport", ({ + enumerable: true, + get: function () { + return _dynamicImport.buildDynamicImport; + } +})); +exports.buildNamespaceInitStatements = buildNamespaceInitStatements; +exports.ensureStatementsHoisted = ensureStatementsHoisted; +Object.defineProperty(exports, "getModuleName", ({ + enumerable: true, + get: function () { + return _getModuleName.default; + } +})); +Object.defineProperty(exports, "hasExports", ({ + enumerable: true, + get: function () { + return _normalizeAndLoadMetadata.hasExports; + } +})); +Object.defineProperty(exports, "isModule", ({ + enumerable: true, + get: function () { + return _helperModuleImports.isModule; + } +})); +Object.defineProperty(exports, "isSideEffectImport", ({ + enumerable: true, + get: function () { + return _normalizeAndLoadMetadata.isSideEffectImport; + } +})); +exports.rewriteModuleStatementsAndPrepareHeader = rewriteModuleStatementsAndPrepareHeader; +Object.defineProperty(exports, "rewriteThis", ({ + enumerable: true, + get: function () { + return _rewriteThis.default; + } +})); +exports.wrapInterop = wrapInterop; +var _assert = __nccwpck_require__(42613); +var _core = __nccwpck_require__(85414); +var _helperModuleImports = __nccwpck_require__(10678); +var _rewriteThis = __nccwpck_require__(92338); +var _rewriteLiveReferences = __nccwpck_require__(78775); +var _normalizeAndLoadMetadata = __nccwpck_require__(64193); +var Lazy = __nccwpck_require__(80247); +var _dynamicImport = __nccwpck_require__(92840); +var _getModuleName = __nccwpck_require__(94036); +{ + exports.getDynamicImportSource = __nccwpck_require__(92840).getDynamicImportSource; +} +function rewriteModuleStatementsAndPrepareHeader(path, { + exportName, + strict, + allowTopLevelThis, + strictMode, + noInterop, + importInterop = noInterop ? "none" : "babel", + lazy, + getWrapperPayload = Lazy.toGetWrapperPayload(lazy != null ? lazy : false), + wrapReference = Lazy.wrapReference, + esNamespaceOnly, + filename, + constantReexports = arguments[1].loose, + enumerableModuleMeta = arguments[1].loose, + noIncompleteNsImportDetection +}) { + (0, _normalizeAndLoadMetadata.validateImportInteropOption)(importInterop); + _assert((0, _helperModuleImports.isModule)(path), "Cannot process module statements in a script"); + path.node.sourceType = "script"; + const meta = (0, _normalizeAndLoadMetadata.default)(path, exportName, { + importInterop, + initializeReexports: constantReexports, + getWrapperPayload, + esNamespaceOnly, + filename + }); + if (!allowTopLevelThis) { + (0, _rewriteThis.default)(path); + } + (0, _rewriteLiveReferences.default)(path, meta, wrapReference); + if (strictMode !== false) { + const hasStrict = path.node.directives.some(directive => { + return directive.value.value === "use strict"; + }); + if (!hasStrict) { + path.unshiftContainer("directives", _core.types.directive(_core.types.directiveLiteral("use strict"))); + } + } + const headers = []; + if ((0, _normalizeAndLoadMetadata.hasExports)(meta) && !strict) { + headers.push(buildESModuleHeader(meta, enumerableModuleMeta)); + } + const nameList = buildExportNameListDeclaration(path, meta); + if (nameList) { + meta.exportNameListName = nameList.name; + headers.push(nameList.statement); + } + headers.push(...buildExportInitializationStatements(path, meta, wrapReference, constantReexports, noIncompleteNsImportDetection)); + return { + meta, + headers + }; +} +function ensureStatementsHoisted(statements) { + statements.forEach(header => { + header._blockHoist = 3; + }); +} +function wrapInterop(programPath, expr, type) { + if (type === "none") { + return null; + } + if (type === "node-namespace") { + return _core.types.callExpression(programPath.hub.addHelper("interopRequireWildcard"), [expr, _core.types.booleanLiteral(true)]); + } else if (type === "node-default") { + return null; + } + let helper; + if (type === "default") { + helper = "interopRequireDefault"; + } else if (type === "namespace") { + helper = "interopRequireWildcard"; + } else { + throw new Error(`Unknown interop: ${type}`); + } + return _core.types.callExpression(programPath.hub.addHelper(helper), [expr]); +} +function buildNamespaceInitStatements(metadata, sourceMetadata, constantReexports = false, wrapReference = Lazy.wrapReference) { + var _wrapReference; + const statements = []; + const srcNamespaceId = _core.types.identifier(sourceMetadata.name); + for (const localName of sourceMetadata.importsNamespace) { + if (localName === sourceMetadata.name) continue; + statements.push(_core.template.statement`var NAME = SOURCE;`({ + NAME: localName, + SOURCE: _core.types.cloneNode(srcNamespaceId) + })); + } + const srcNamespace = (_wrapReference = wrapReference(srcNamespaceId, sourceMetadata.wrap)) != null ? _wrapReference : srcNamespaceId; + if (constantReexports) { + statements.push(...buildReexportsFromMeta(metadata, sourceMetadata, true, wrapReference)); + } + for (const exportName of sourceMetadata.reexportNamespace) { + statements.push((!_core.types.isIdentifier(srcNamespace) ? _core.template.statement` + Object.defineProperty(EXPORTS, "NAME", { + enumerable: true, + get: function() { + return NAMESPACE; + } + }); + ` : _core.template.statement`EXPORTS.NAME = NAMESPACE;`)({ + EXPORTS: metadata.exportName, + NAME: exportName, + NAMESPACE: _core.types.cloneNode(srcNamespace) + })); + } + if (sourceMetadata.reexportAll) { + const statement = buildNamespaceReexport(metadata, _core.types.cloneNode(srcNamespace), constantReexports); + statement.loc = sourceMetadata.reexportAll.loc; + statements.push(statement); + } + return statements; +} +const ReexportTemplate = { + constant: ({ + exports, + exportName, + namespaceImport + }) => _core.template.statement.ast` + ${exports}.${exportName} = ${namespaceImport}; + `, + constantComputed: ({ + exports, + exportName, + namespaceImport + }) => _core.template.statement.ast` + ${exports}["${exportName}"] = ${namespaceImport}; + `, + spec: ({ + exports, + exportName, + namespaceImport + }) => _core.template.statement.ast` + Object.defineProperty(${exports}, "${exportName}", { + enumerable: true, + get: function() { + return ${namespaceImport}; + }, + }); + ` +}; +function buildReexportsFromMeta(meta, metadata, constantReexports, wrapReference) { + var _wrapReference2; + let namespace = _core.types.identifier(metadata.name); + namespace = (_wrapReference2 = wrapReference(namespace, metadata.wrap)) != null ? _wrapReference2 : namespace; + const { + stringSpecifiers + } = meta; + return Array.from(metadata.reexports, ([exportName, importName]) => { + let namespaceImport = _core.types.cloneNode(namespace); + if (importName === "default" && metadata.interop === "node-default") {} else if (stringSpecifiers.has(importName)) { + namespaceImport = _core.types.memberExpression(namespaceImport, _core.types.stringLiteral(importName), true); + } else { + namespaceImport = _core.types.memberExpression(namespaceImport, _core.types.identifier(importName)); + } + const astNodes = { + exports: meta.exportName, + exportName, + namespaceImport + }; + if (constantReexports || _core.types.isIdentifier(namespaceImport)) { + if (stringSpecifiers.has(exportName)) { + return ReexportTemplate.constantComputed(astNodes); + } else { + return ReexportTemplate.constant(astNodes); + } + } else { + return ReexportTemplate.spec(astNodes); + } + }); +} +function buildESModuleHeader(metadata, enumerableModuleMeta = false) { + return (enumerableModuleMeta ? _core.template.statement` + EXPORTS.__esModule = true; + ` : _core.template.statement` + Object.defineProperty(EXPORTS, "__esModule", { + value: true, + }); + `)({ + EXPORTS: metadata.exportName + }); +} +function buildNamespaceReexport(metadata, namespace, constantReexports) { + return (constantReexports ? _core.template.statement` + Object.keys(NAMESPACE).forEach(function(key) { + if (key === "default" || key === "__esModule") return; + VERIFY_NAME_LIST; + if (key in EXPORTS && EXPORTS[key] === NAMESPACE[key]) return; + + EXPORTS[key] = NAMESPACE[key]; + }); + ` : _core.template.statement` + Object.keys(NAMESPACE).forEach(function(key) { + if (key === "default" || key === "__esModule") return; + VERIFY_NAME_LIST; + if (key in EXPORTS && EXPORTS[key] === NAMESPACE[key]) return; + + Object.defineProperty(EXPORTS, key, { + enumerable: true, + get: function() { + return NAMESPACE[key]; + }, + }); + }); + `)({ + NAMESPACE: namespace, + EXPORTS: metadata.exportName, + VERIFY_NAME_LIST: metadata.exportNameListName ? (0, _core.template)` + if (Object.prototype.hasOwnProperty.call(EXPORTS_LIST, key)) return; + `({ + EXPORTS_LIST: metadata.exportNameListName + }) : null + }); +} +function buildExportNameListDeclaration(programPath, metadata) { + const exportedVars = Object.create(null); + for (const data of metadata.local.values()) { + for (const name of data.names) { + exportedVars[name] = true; + } + } + let hasReexport = false; + for (const data of metadata.source.values()) { + for (const exportName of data.reexports.keys()) { + exportedVars[exportName] = true; + } + for (const exportName of data.reexportNamespace) { + exportedVars[exportName] = true; + } + hasReexport = hasReexport || !!data.reexportAll; + } + if (!hasReexport || Object.keys(exportedVars).length === 0) return null; + const name = programPath.scope.generateUidIdentifier("exportNames"); + delete exportedVars.default; + return { + name: name.name, + statement: _core.types.variableDeclaration("var", [_core.types.variableDeclarator(name, _core.types.valueToNode(exportedVars))]) + }; +} +function buildExportInitializationStatements(programPath, metadata, wrapReference, constantReexports = false, noIncompleteNsImportDetection = false) { + const initStatements = []; + for (const [localName, data] of metadata.local) { + if (data.kind === "import") {} else if (data.kind === "hoisted") { + initStatements.push([data.names[0], buildInitStatement(metadata, data.names, _core.types.identifier(localName))]); + } else if (!noIncompleteNsImportDetection) { + for (const exportName of data.names) { + initStatements.push([exportName, null]); + } + } + } + for (const data of metadata.source.values()) { + if (!constantReexports) { + const reexportsStatements = buildReexportsFromMeta(metadata, data, false, wrapReference); + const reexports = [...data.reexports.keys()]; + for (let i = 0; i < reexportsStatements.length; i++) { + initStatements.push([reexports[i], reexportsStatements[i]]); + } + } + if (!noIncompleteNsImportDetection) { + for (const exportName of data.reexportNamespace) { + initStatements.push([exportName, null]); + } + } + } + initStatements.sort(([a], [b]) => { + if (a < b) return -1; + if (b < a) return 1; + return 0; + }); + const results = []; + if (noIncompleteNsImportDetection) { + for (const [, initStatement] of initStatements) { + results.push(initStatement); + } + } else { + const chunkSize = 100; + for (let i = 0; i < initStatements.length; i += chunkSize) { + let uninitializedExportNames = []; + for (let j = 0; j < chunkSize && i + j < initStatements.length; j++) { + const [exportName, initStatement] = initStatements[i + j]; + if (initStatement !== null) { + if (uninitializedExportNames.length > 0) { + results.push(buildInitStatement(metadata, uninitializedExportNames, programPath.scope.buildUndefinedNode())); + uninitializedExportNames = []; + } + results.push(initStatement); + } else { + uninitializedExportNames.push(exportName); + } + } + if (uninitializedExportNames.length > 0) { + results.push(buildInitStatement(metadata, uninitializedExportNames, programPath.scope.buildUndefinedNode())); + } + } + } + return results; +} +const InitTemplate = { + computed: ({ + exports, + name, + value + }) => _core.template.expression.ast`${exports}["${name}"] = ${value}`, + default: ({ + exports, + name, + value + }) => _core.template.expression.ast`${exports}.${name} = ${value}`, + define: ({ + exports, + name, + value + }) => _core.template.expression.ast` + Object.defineProperty(${exports}, "${name}", { + enumerable: true, + value: void 0, + writable: true + })["${name}"] = ${value}` +}; +function buildInitStatement(metadata, exportNames, initExpr) { + const { + stringSpecifiers, + exportName: exports + } = metadata; + return _core.types.expressionStatement(exportNames.reduce((value, name) => { + const params = { + exports, + name, + value + }; + if (name === "__proto__") { + return InitTemplate.define(params); + } + if (stringSpecifiers.has(name)) { + return InitTemplate.computed(params); + } + return InitTemplate.default(params); + }, initExpr)); +} + +//# sourceMappingURL=index.js.map + + +/***/ }), + +/***/ 80247: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports.toGetWrapperPayload = toGetWrapperPayload; +exports.wrapReference = wrapReference; +var _core = __nccwpck_require__(85414); +var _normalizeAndLoadMetadata = __nccwpck_require__(64193); +function toGetWrapperPayload(lazy) { + return (source, metadata) => { + if (lazy === false) return null; + if ((0, _normalizeAndLoadMetadata.isSideEffectImport)(metadata) || metadata.reexportAll) return null; + if (lazy === true) { + return source.includes(".") ? null : "lazy"; + } + if (Array.isArray(lazy)) { + return !lazy.includes(source) ? null : "lazy"; + } + if (typeof lazy === "function") { + return lazy(source) ? "lazy" : null; + } + throw new Error(`.lazy must be a boolean, string array, or function`); + }; +} +function wrapReference(ref, payload) { + if (payload === "lazy") return _core.types.callExpression(ref, []); + return null; +} + +//# sourceMappingURL=lazy-modules.js.map + + +/***/ }), + +/***/ 64193: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports["default"] = normalizeModuleAndLoadMetadata; +exports.hasExports = hasExports; +exports.isSideEffectImport = isSideEffectImport; +exports.validateImportInteropOption = validateImportInteropOption; +var _path = __nccwpck_require__(16928); +var _helperValidatorIdentifier = __nccwpck_require__(76599); +function hasExports(metadata) { + return metadata.hasExports; +} +function isSideEffectImport(source) { + return source.imports.size === 0 && source.importsNamespace.size === 0 && source.reexports.size === 0 && source.reexportNamespace.size === 0 && !source.reexportAll; +} +function validateImportInteropOption(importInterop) { + if (typeof importInterop !== "function" && importInterop !== "none" && importInterop !== "babel" && importInterop !== "node") { + throw new Error(`.importInterop must be one of "none", "babel", "node", or a function returning one of those values (received ${importInterop}).`); + } + return importInterop; +} +function resolveImportInterop(importInterop, source, filename) { + if (typeof importInterop === "function") { + return validateImportInteropOption(importInterop(source, filename)); + } + return importInterop; +} +function normalizeModuleAndLoadMetadata(programPath, exportName, { + importInterop, + initializeReexports = false, + getWrapperPayload, + esNamespaceOnly = false, + filename +}) { + if (!exportName) { + exportName = programPath.scope.generateUidIdentifier("exports").name; + } + const stringSpecifiers = new Set(); + nameAnonymousExports(programPath); + const { + local, + sources, + hasExports + } = getModuleMetadata(programPath, { + initializeReexports, + getWrapperPayload + }, stringSpecifiers); + removeImportExportDeclarations(programPath); + for (const [source, metadata] of sources) { + const { + importsNamespace, + imports + } = metadata; + if (importsNamespace.size > 0 && imports.size === 0) { + const [nameOfnamespace] = importsNamespace; + metadata.name = nameOfnamespace; + } + const resolvedInterop = resolveImportInterop(importInterop, source, filename); + if (resolvedInterop === "none") { + metadata.interop = "none"; + } else if (resolvedInterop === "node" && metadata.interop === "namespace") { + metadata.interop = "node-namespace"; + } else if (resolvedInterop === "node" && metadata.interop === "default") { + metadata.interop = "node-default"; + } else if (esNamespaceOnly && metadata.interop === "namespace") { + metadata.interop = "default"; + } + } + return { + exportName, + exportNameListName: null, + hasExports, + local, + source: sources, + stringSpecifiers + }; +} +function getExportSpecifierName(path, stringSpecifiers) { + if (path.isIdentifier()) { + return path.node.name; + } else if (path.isStringLiteral()) { + const stringValue = path.node.value; + if (!(0, _helperValidatorIdentifier.isIdentifierName)(stringValue)) { + stringSpecifiers.add(stringValue); + } + return stringValue; + } else { + throw new Error(`Expected export specifier to be either Identifier or StringLiteral, got ${path.node.type}`); + } +} +function assertExportSpecifier(path) { + if (path.isExportSpecifier()) { + return; + } else if (path.isExportNamespaceSpecifier()) { + throw path.buildCodeFrameError("Export namespace should be first transformed by `@babel/plugin-transform-export-namespace-from`."); + } else { + throw path.buildCodeFrameError("Unexpected export specifier type"); + } +} +function getModuleMetadata(programPath, { + getWrapperPayload, + initializeReexports +}, stringSpecifiers) { + const localData = getLocalExportMetadata(programPath, initializeReexports, stringSpecifiers); + const importNodes = new Map(); + const sourceData = new Map(); + const getData = (sourceNode, node) => { + const source = sourceNode.value; + let data = sourceData.get(source); + if (!data) { + data = { + name: programPath.scope.generateUidIdentifier((0, _path.basename)(source, (0, _path.extname)(source))).name, + interop: "none", + loc: null, + imports: new Map(), + importsNamespace: new Set(), + reexports: new Map(), + reexportNamespace: new Set(), + reexportAll: null, + wrap: null, + get lazy() { + return this.wrap === "lazy"; + }, + referenced: false + }; + sourceData.set(source, data); + importNodes.set(source, [node]); + } else { + importNodes.get(source).push(node); + } + return data; + }; + let hasExports = false; + programPath.get("body").forEach(child => { + if (child.isImportDeclaration()) { + const data = getData(child.node.source, child.node); + if (!data.loc) data.loc = child.node.loc; + child.get("specifiers").forEach(spec => { + if (spec.isImportDefaultSpecifier()) { + const localName = spec.get("local").node.name; + data.imports.set(localName, "default"); + const reexport = localData.get(localName); + if (reexport) { + localData.delete(localName); + reexport.names.forEach(name => { + data.reexports.set(name, "default"); + }); + data.referenced = true; + } + } else if (spec.isImportNamespaceSpecifier()) { + const localName = spec.get("local").node.name; + data.importsNamespace.add(localName); + const reexport = localData.get(localName); + if (reexport) { + localData.delete(localName); + reexport.names.forEach(name => { + data.reexportNamespace.add(name); + }); + data.referenced = true; + } + } else if (spec.isImportSpecifier()) { + const importName = getExportSpecifierName(spec.get("imported"), stringSpecifiers); + const localName = spec.get("local").node.name; + data.imports.set(localName, importName); + const reexport = localData.get(localName); + if (reexport) { + localData.delete(localName); + reexport.names.forEach(name => { + data.reexports.set(name, importName); + }); + data.referenced = true; + } + } + }); + } else if (child.isExportAllDeclaration()) { + hasExports = true; + const data = getData(child.node.source, child.node); + if (!data.loc) data.loc = child.node.loc; + data.reexportAll = { + loc: child.node.loc + }; + data.referenced = true; + } else if (child.isExportNamedDeclaration() && child.node.source) { + hasExports = true; + const data = getData(child.node.source, child.node); + if (!data.loc) data.loc = child.node.loc; + child.get("specifiers").forEach(spec => { + assertExportSpecifier(spec); + const importName = getExportSpecifierName(spec.get("local"), stringSpecifiers); + const exportName = getExportSpecifierName(spec.get("exported"), stringSpecifiers); + data.reexports.set(exportName, importName); + data.referenced = true; + if (exportName === "__esModule") { + throw spec.get("exported").buildCodeFrameError('Illegal export "__esModule".'); + } + }); + } else if (child.isExportNamedDeclaration() || child.isExportDefaultDeclaration()) { + hasExports = true; + } + }); + for (const metadata of sourceData.values()) { + let needsDefault = false; + let needsNamed = false; + if (metadata.importsNamespace.size > 0) { + needsDefault = true; + needsNamed = true; + } + if (metadata.reexportAll) { + needsNamed = true; + } + for (const importName of metadata.imports.values()) { + if (importName === "default") needsDefault = true;else needsNamed = true; + } + for (const importName of metadata.reexports.values()) { + if (importName === "default") needsDefault = true;else needsNamed = true; + } + if (needsDefault && needsNamed) { + metadata.interop = "namespace"; + } else if (needsDefault) { + metadata.interop = "default"; + } + } + if (getWrapperPayload) { + for (const [source, metadata] of sourceData) { + metadata.wrap = getWrapperPayload(source, metadata, importNodes.get(source)); + } + } + return { + hasExports, + local: localData, + sources: sourceData + }; +} +function getLocalExportMetadata(programPath, initializeReexports, stringSpecifiers) { + const bindingKindLookup = new Map(); + programPath.get("body").forEach(child => { + let kind; + if (child.isImportDeclaration()) { + kind = "import"; + } else { + if (child.isExportDefaultDeclaration()) { + child = child.get("declaration"); + } + if (child.isExportNamedDeclaration()) { + if (child.node.declaration) { + child = child.get("declaration"); + } else if (initializeReexports && child.node.source && child.get("source").isStringLiteral()) { + child.get("specifiers").forEach(spec => { + assertExportSpecifier(spec); + bindingKindLookup.set(spec.get("local").node.name, "block"); + }); + return; + } + } + if (child.isFunctionDeclaration()) { + kind = "hoisted"; + } else if (child.isClassDeclaration()) { + kind = "block"; + } else if (child.isVariableDeclaration({ + kind: "var" + })) { + kind = "var"; + } else if (child.isVariableDeclaration()) { + kind = "block"; + } else { + return; + } + } + Object.keys(child.getOuterBindingIdentifiers()).forEach(name => { + bindingKindLookup.set(name, kind); + }); + }); + const localMetadata = new Map(); + const getLocalMetadata = idPath => { + const localName = idPath.node.name; + let metadata = localMetadata.get(localName); + if (!metadata) { + const kind = bindingKindLookup.get(localName); + if (kind === undefined) { + throw idPath.buildCodeFrameError(`Exporting local "${localName}", which is not declared.`); + } + metadata = { + names: [], + kind + }; + localMetadata.set(localName, metadata); + } + return metadata; + }; + programPath.get("body").forEach(child => { + if (child.isExportNamedDeclaration() && (initializeReexports || !child.node.source)) { + if (child.node.declaration) { + const declaration = child.get("declaration"); + const ids = declaration.getOuterBindingIdentifierPaths(); + Object.keys(ids).forEach(name => { + if (name === "__esModule") { + throw declaration.buildCodeFrameError('Illegal export "__esModule".'); + } + getLocalMetadata(ids[name]).names.push(name); + }); + } else { + child.get("specifiers").forEach(spec => { + const local = spec.get("local"); + const exported = spec.get("exported"); + const localMetadata = getLocalMetadata(local); + const exportName = getExportSpecifierName(exported, stringSpecifiers); + if (exportName === "__esModule") { + throw exported.buildCodeFrameError('Illegal export "__esModule".'); + } + localMetadata.names.push(exportName); + }); + } + } else if (child.isExportDefaultDeclaration()) { + const declaration = child.get("declaration"); + if (declaration.isFunctionDeclaration() || declaration.isClassDeclaration()) { + getLocalMetadata(declaration.get("id")).names.push("default"); + } else { + throw declaration.buildCodeFrameError("Unexpected default expression export."); + } + } + }); + return localMetadata; +} +function nameAnonymousExports(programPath) { + programPath.get("body").forEach(child => { + if (!child.isExportDefaultDeclaration()) return; + { + var _child$splitExportDec; + (_child$splitExportDec = child.splitExportDeclaration) != null ? _child$splitExportDec : child.splitExportDeclaration = (__nccwpck_require__(50148).NodePath).prototype.splitExportDeclaration; + } + child.splitExportDeclaration(); + }); +} +function removeImportExportDeclarations(programPath) { + programPath.get("body").forEach(child => { + if (child.isImportDeclaration()) { + child.remove(); + } else if (child.isExportNamedDeclaration()) { + if (child.node.declaration) { + child.node.declaration._blockHoist = child.node._blockHoist; + child.replaceWith(child.node.declaration); + } else { + child.remove(); + } + } else if (child.isExportDefaultDeclaration()) { + const declaration = child.get("declaration"); + if (declaration.isFunctionDeclaration() || declaration.isClassDeclaration()) { + declaration._blockHoist = child.node._blockHoist; + child.replaceWith(declaration); + } else { + throw declaration.buildCodeFrameError("Unexpected default expression export."); + } + } else if (child.isExportAllDeclaration()) { + child.remove(); + } + }); +} + +//# sourceMappingURL=normalize-and-load-metadata.js.map + + +/***/ }), + +/***/ 78775: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports["default"] = rewriteLiveReferences; +var _core = __nccwpck_require__(85414); +function isInType(path) { + do { + switch (path.parent.type) { + case "TSTypeAnnotation": + case "TSTypeAliasDeclaration": + case "TSTypeReference": + case "TypeAnnotation": + case "TypeAlias": + return true; + case "ExportSpecifier": + return path.parentPath.parent.exportKind === "type"; + default: + if (path.parentPath.isStatement() || path.parentPath.isExpression()) { + return false; + } + } + } while (path = path.parentPath); +} +function rewriteLiveReferences(programPath, metadata, wrapReference) { + const imported = new Map(); + const exported = new Map(); + const requeueInParent = path => { + programPath.requeue(path); + }; + for (const [source, data] of metadata.source) { + for (const [localName, importName] of data.imports) { + imported.set(localName, [source, importName, null]); + } + for (const localName of data.importsNamespace) { + imported.set(localName, [source, null, localName]); + } + } + for (const [local, data] of metadata.local) { + let exportMeta = exported.get(local); + if (!exportMeta) { + exportMeta = []; + exported.set(local, exportMeta); + } + exportMeta.push(...data.names); + } + const rewriteBindingInitVisitorState = { + metadata, + requeueInParent, + scope: programPath.scope, + exported + }; + programPath.traverse(rewriteBindingInitVisitor, rewriteBindingInitVisitorState); + const rewriteReferencesVisitorState = { + seen: new WeakSet(), + metadata, + requeueInParent, + scope: programPath.scope, + imported, + exported, + buildImportReference([source, importName, localName], identNode) { + const meta = metadata.source.get(source); + meta.referenced = true; + if (localName) { + if (meta.wrap) { + var _wrapReference; + identNode = (_wrapReference = wrapReference(identNode, meta.wrap)) != null ? _wrapReference : identNode; + } + return identNode; + } + let namespace = _core.types.identifier(meta.name); + if (meta.wrap) { + var _wrapReference2; + namespace = (_wrapReference2 = wrapReference(namespace, meta.wrap)) != null ? _wrapReference2 : namespace; + } + if (importName === "default" && meta.interop === "node-default") { + return namespace; + } + const computed = metadata.stringSpecifiers.has(importName); + return _core.types.memberExpression(namespace, computed ? _core.types.stringLiteral(importName) : _core.types.identifier(importName), computed); + } + }; + programPath.traverse(rewriteReferencesVisitor, rewriteReferencesVisitorState); +} +const rewriteBindingInitVisitor = { + Scope(path) { + path.skip(); + }, + ClassDeclaration(path) { + const { + requeueInParent, + exported, + metadata + } = this; + const { + id + } = path.node; + if (!id) throw new Error("Expected class to have a name"); + const localName = id.name; + const exportNames = exported.get(localName) || []; + if (exportNames.length > 0) { + const statement = _core.types.expressionStatement(buildBindingExportAssignmentExpression(metadata, exportNames, _core.types.identifier(localName), path.scope)); + statement._blockHoist = path.node._blockHoist; + requeueInParent(path.insertAfter(statement)[0]); + } + }, + VariableDeclaration(path) { + const { + requeueInParent, + exported, + metadata + } = this; + const isVar = path.node.kind === "var"; + for (const decl of path.get("declarations")) { + const { + id + } = decl.node; + let { + init + } = decl.node; + if (_core.types.isIdentifier(id) && exported.has(id.name) && !_core.types.isArrowFunctionExpression(init) && (!_core.types.isFunctionExpression(init) || init.id) && (!_core.types.isClassExpression(init) || init.id)) { + if (!init) { + if (isVar) { + continue; + } else { + init = path.scope.buildUndefinedNode(); + } + } + decl.node.init = buildBindingExportAssignmentExpression(metadata, exported.get(id.name), init, path.scope); + requeueInParent(decl.get("init")); + } else { + for (const localName of Object.keys(decl.getOuterBindingIdentifiers())) { + if (exported.has(localName)) { + const statement = _core.types.expressionStatement(buildBindingExportAssignmentExpression(metadata, exported.get(localName), _core.types.identifier(localName), path.scope)); + statement._blockHoist = path.node._blockHoist; + requeueInParent(path.insertAfter(statement)[0]); + } + } + } + } + } +}; +const buildBindingExportAssignmentExpression = (metadata, exportNames, localExpr, scope) => { + const exportsObjectName = metadata.exportName; + for (let currentScope = scope; currentScope != null; currentScope = currentScope.parent) { + if (currentScope.hasOwnBinding(exportsObjectName)) { + currentScope.rename(exportsObjectName); + } + } + return (exportNames || []).reduce((expr, exportName) => { + const { + stringSpecifiers + } = metadata; + const computed = stringSpecifiers.has(exportName); + return _core.types.assignmentExpression("=", _core.types.memberExpression(_core.types.identifier(exportsObjectName), computed ? _core.types.stringLiteral(exportName) : _core.types.identifier(exportName), computed), expr); + }, localExpr); +}; +const buildImportThrow = localName => { + return _core.template.expression.ast` + (function() { + throw new Error('"' + '${localName}' + '" is read-only.'); + })() + `; +}; +const rewriteReferencesVisitor = { + ReferencedIdentifier(path) { + const { + seen, + buildImportReference, + scope, + imported, + requeueInParent + } = this; + if (seen.has(path.node)) return; + seen.add(path.node); + const localName = path.node.name; + const importData = imported.get(localName); + if (importData) { + if (isInType(path)) { + throw path.buildCodeFrameError(`Cannot transform the imported binding "${localName}" since it's also used in a type annotation. ` + `Please strip type annotations using @babel/preset-typescript or @babel/preset-flow.`); + } + const localBinding = path.scope.getBinding(localName); + const rootBinding = scope.getBinding(localName); + if (rootBinding !== localBinding) return; + const ref = buildImportReference(importData, path.node); + ref.loc = path.node.loc; + if ((path.parentPath.isCallExpression({ + callee: path.node + }) || path.parentPath.isOptionalCallExpression({ + callee: path.node + }) || path.parentPath.isTaggedTemplateExpression({ + tag: path.node + })) && _core.types.isMemberExpression(ref)) { + path.replaceWith(_core.types.sequenceExpression([_core.types.numericLiteral(0), ref])); + } else if (path.isJSXIdentifier() && _core.types.isMemberExpression(ref)) { + const { + object, + property + } = ref; + path.replaceWith(_core.types.jsxMemberExpression(_core.types.jsxIdentifier(object.name), _core.types.jsxIdentifier(property.name))); + } else { + path.replaceWith(ref); + } + requeueInParent(path); + path.skip(); + } + }, + UpdateExpression(path) { + const { + scope, + seen, + imported, + exported, + requeueInParent, + buildImportReference + } = this; + if (seen.has(path.node)) return; + seen.add(path.node); + const arg = path.get("argument"); + if (arg.isMemberExpression()) return; + const update = path.node; + if (arg.isIdentifier()) { + const localName = arg.node.name; + if (scope.getBinding(localName) !== path.scope.getBinding(localName)) { + return; + } + const exportedNames = exported.get(localName); + const importData = imported.get(localName); + if ((exportedNames == null ? void 0 : exportedNames.length) > 0 || importData) { + if (importData) { + path.replaceWith(_core.types.assignmentExpression(update.operator[0] + "=", buildImportReference(importData, arg.node), buildImportThrow(localName))); + } else if (update.prefix) { + path.replaceWith(buildBindingExportAssignmentExpression(this.metadata, exportedNames, _core.types.cloneNode(update), path.scope)); + } else { + const ref = scope.generateDeclaredUidIdentifier(localName); + path.replaceWith(_core.types.sequenceExpression([_core.types.assignmentExpression("=", _core.types.cloneNode(ref), _core.types.cloneNode(update)), buildBindingExportAssignmentExpression(this.metadata, exportedNames, _core.types.identifier(localName), path.scope), _core.types.cloneNode(ref)])); + } + } + } + requeueInParent(path); + path.skip(); + }, + AssignmentExpression: { + exit(path) { + const { + scope, + seen, + imported, + exported, + requeueInParent, + buildImportReference + } = this; + if (seen.has(path.node)) return; + seen.add(path.node); + const left = path.get("left"); + if (left.isMemberExpression()) return; + if (left.isIdentifier()) { + const localName = left.node.name; + if (scope.getBinding(localName) !== path.scope.getBinding(localName)) { + return; + } + const exportedNames = exported.get(localName); + const importData = imported.get(localName); + if ((exportedNames == null ? void 0 : exportedNames.length) > 0 || importData) { + const assignment = path.node; + if (importData) { + assignment.left = buildImportReference(importData, left.node); + assignment.right = _core.types.sequenceExpression([assignment.right, buildImportThrow(localName)]); + } + const { + operator + } = assignment; + let newExpr; + if (operator === "=") { + newExpr = assignment; + } else if (operator === "&&=" || operator === "||=" || operator === "??=") { + newExpr = _core.types.assignmentExpression("=", assignment.left, _core.types.logicalExpression(operator.slice(0, -1), _core.types.cloneNode(assignment.left), assignment.right)); + } else { + newExpr = _core.types.assignmentExpression("=", assignment.left, _core.types.binaryExpression(operator.slice(0, -1), _core.types.cloneNode(assignment.left), assignment.right)); + } + path.replaceWith(buildBindingExportAssignmentExpression(this.metadata, exportedNames, newExpr, path.scope)); + requeueInParent(path); + path.skip(); + } + } else { + const ids = left.getOuterBindingIdentifiers(); + const programScopeIds = Object.keys(ids).filter(localName => scope.getBinding(localName) === path.scope.getBinding(localName)); + const id = programScopeIds.find(localName => imported.has(localName)); + if (id) { + path.node.right = _core.types.sequenceExpression([path.node.right, buildImportThrow(id)]); + } + const items = []; + programScopeIds.forEach(localName => { + const exportedNames = exported.get(localName) || []; + if (exportedNames.length > 0) { + items.push(buildBindingExportAssignmentExpression(this.metadata, exportedNames, _core.types.identifier(localName), path.scope)); + } + }); + if (items.length > 0) { + let node = _core.types.sequenceExpression(items); + if (path.parentPath.isExpressionStatement()) { + node = _core.types.expressionStatement(node); + node._blockHoist = path.parentPath.node._blockHoist; + } + const statement = path.insertAfter(node)[0]; + requeueInParent(statement); + } + } + } + }, + ForXStatement(path) { + const { + scope, + node + } = path; + const { + left + } = node; + const { + exported, + imported, + scope: programScope + } = this; + if (!_core.types.isVariableDeclaration(left)) { + let didTransformExport = false, + importConstViolationName; + const loopBodyScope = path.get("body").scope; + for (const name of Object.keys(_core.types.getOuterBindingIdentifiers(left))) { + if (programScope.getBinding(name) === scope.getBinding(name)) { + if (exported.has(name)) { + didTransformExport = true; + if (loopBodyScope.hasOwnBinding(name)) { + loopBodyScope.rename(name); + } + } + if (imported.has(name) && !importConstViolationName) { + importConstViolationName = name; + } + } + } + if (!didTransformExport && !importConstViolationName) { + return; + } + path.ensureBlock(); + const bodyPath = path.get("body"); + const newLoopId = scope.generateUidIdentifierBasedOnNode(left); + path.get("left").replaceWith(_core.types.variableDeclaration("let", [_core.types.variableDeclarator(_core.types.cloneNode(newLoopId))])); + scope.registerDeclaration(path.get("left")); + if (didTransformExport) { + bodyPath.unshiftContainer("body", _core.types.expressionStatement(_core.types.assignmentExpression("=", left, newLoopId))); + } + if (importConstViolationName) { + bodyPath.unshiftContainer("body", _core.types.expressionStatement(buildImportThrow(importConstViolationName))); + } + } + } +}; + +//# sourceMappingURL=rewrite-live-references.js.map + + +/***/ }), + +/***/ 92338: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports["default"] = rewriteThis; +var _core = __nccwpck_require__(85414); +var _traverse = __nccwpck_require__(50148); +let rewriteThisVisitor; +function rewriteThis(programPath) { + if (!rewriteThisVisitor) { + rewriteThisVisitor = _traverse.visitors.environmentVisitor({ + ThisExpression(path) { + path.replaceWith(_core.types.unaryExpression("void", _core.types.numericLiteral(0), true)); + } + }); + rewriteThisVisitor.noScope = true; + } + (0, _traverse.default)(programPath.node, rewriteThisVisitor); +} + +//# sourceMappingURL=rewrite-this.js.map + + +/***/ }), + +/***/ 73728: +/***/ ((__unused_webpack_module, exports) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports.readCodePoint = readCodePoint; +exports.readInt = readInt; +exports.readStringContents = readStringContents; +var _isDigit = function isDigit(code) { + return code >= 48 && code <= 57; +}; +const forbiddenNumericSeparatorSiblings = { + decBinOct: new Set([46, 66, 69, 79, 95, 98, 101, 111]), + hex: new Set([46, 88, 95, 120]) +}; +const isAllowedNumericSeparatorSibling = { + bin: ch => ch === 48 || ch === 49, + oct: ch => ch >= 48 && ch <= 55, + dec: ch => ch >= 48 && ch <= 57, + hex: ch => ch >= 48 && ch <= 57 || ch >= 65 && ch <= 70 || ch >= 97 && ch <= 102 +}; +function readStringContents(type, input, pos, lineStart, curLine, errors) { + const initialPos = pos; + const initialLineStart = lineStart; + const initialCurLine = curLine; + let out = ""; + let firstInvalidLoc = null; + let chunkStart = pos; + const { + length + } = input; + for (;;) { + if (pos >= length) { + errors.unterminated(initialPos, initialLineStart, initialCurLine); + out += input.slice(chunkStart, pos); + break; + } + const ch = input.charCodeAt(pos); + if (isStringEnd(type, ch, input, pos)) { + out += input.slice(chunkStart, pos); + break; + } + if (ch === 92) { + out += input.slice(chunkStart, pos); + const res = readEscapedChar(input, pos, lineStart, curLine, type === "template", errors); + if (res.ch === null && !firstInvalidLoc) { + firstInvalidLoc = { + pos, + lineStart, + curLine + }; + } else { + out += res.ch; + } + ({ + pos, + lineStart, + curLine + } = res); + chunkStart = pos; + } else if (ch === 8232 || ch === 8233) { + ++pos; + ++curLine; + lineStart = pos; + } else if (ch === 10 || ch === 13) { + if (type === "template") { + out += input.slice(chunkStart, pos) + "\n"; + ++pos; + if (ch === 13 && input.charCodeAt(pos) === 10) { + ++pos; + } + ++curLine; + chunkStart = lineStart = pos; + } else { + errors.unterminated(initialPos, initialLineStart, initialCurLine); + } + } else { + ++pos; + } + } + return { + pos, + str: out, + firstInvalidLoc, + lineStart, + curLine, + containsInvalid: !!firstInvalidLoc + }; +} +function isStringEnd(type, ch, input, pos) { + if (type === "template") { + return ch === 96 || ch === 36 && input.charCodeAt(pos + 1) === 123; + } + return ch === (type === "double" ? 34 : 39); +} +function readEscapedChar(input, pos, lineStart, curLine, inTemplate, errors) { + const throwOnInvalid = !inTemplate; + pos++; + const res = ch => ({ + pos, + ch, + lineStart, + curLine + }); + const ch = input.charCodeAt(pos++); + switch (ch) { + case 110: + return res("\n"); + case 114: + return res("\r"); + case 120: + { + let code; + ({ + code, + pos + } = readHexChar(input, pos, lineStart, curLine, 2, false, throwOnInvalid, errors)); + return res(code === null ? null : String.fromCharCode(code)); + } + case 117: + { + let code; + ({ + code, + pos + } = readCodePoint(input, pos, lineStart, curLine, throwOnInvalid, errors)); + return res(code === null ? null : String.fromCodePoint(code)); + } + case 116: + return res("\t"); + case 98: + return res("\b"); + case 118: + return res("\u000b"); + case 102: + return res("\f"); + case 13: + if (input.charCodeAt(pos) === 10) { + ++pos; + } + case 10: + lineStart = pos; + ++curLine; + case 8232: + case 8233: + return res(""); + case 56: + case 57: + if (inTemplate) { + return res(null); + } else { + errors.strictNumericEscape(pos - 1, lineStart, curLine); + } + default: + if (ch >= 48 && ch <= 55) { + const startPos = pos - 1; + const match = /^[0-7]+/.exec(input.slice(startPos, pos + 2)); + let octalStr = match[0]; + let octal = parseInt(octalStr, 8); + if (octal > 255) { + octalStr = octalStr.slice(0, -1); + octal = parseInt(octalStr, 8); + } + pos += octalStr.length - 1; + const next = input.charCodeAt(pos); + if (octalStr !== "0" || next === 56 || next === 57) { + if (inTemplate) { + return res(null); + } else { + errors.strictNumericEscape(startPos, lineStart, curLine); + } + } + return res(String.fromCharCode(octal)); + } + return res(String.fromCharCode(ch)); + } +} +function readHexChar(input, pos, lineStart, curLine, len, forceLen, throwOnInvalid, errors) { + const initialPos = pos; + let n; + ({ + n, + pos + } = readInt(input, pos, lineStart, curLine, 16, len, forceLen, false, errors, !throwOnInvalid)); + if (n === null) { + if (throwOnInvalid) { + errors.invalidEscapeSequence(initialPos, lineStart, curLine); + } else { + pos = initialPos - 1; + } + } + return { + code: n, + pos + }; +} +function readInt(input, pos, lineStart, curLine, radix, len, forceLen, allowNumSeparator, errors, bailOnError) { + const start = pos; + const forbiddenSiblings = radix === 16 ? forbiddenNumericSeparatorSiblings.hex : forbiddenNumericSeparatorSiblings.decBinOct; + const isAllowedSibling = radix === 16 ? isAllowedNumericSeparatorSibling.hex : radix === 10 ? isAllowedNumericSeparatorSibling.dec : radix === 8 ? isAllowedNumericSeparatorSibling.oct : isAllowedNumericSeparatorSibling.bin; + let invalid = false; + let total = 0; + for (let i = 0, e = len == null ? Infinity : len; i < e; ++i) { + const code = input.charCodeAt(pos); + let val; + if (code === 95 && allowNumSeparator !== "bail") { + const prev = input.charCodeAt(pos - 1); + const next = input.charCodeAt(pos + 1); + if (!allowNumSeparator) { + if (bailOnError) return { + n: null, + pos + }; + errors.numericSeparatorInEscapeSequence(pos, lineStart, curLine); + } else if (Number.isNaN(next) || !isAllowedSibling(next) || forbiddenSiblings.has(prev) || forbiddenSiblings.has(next)) { + if (bailOnError) return { + n: null, + pos + }; + errors.unexpectedNumericSeparator(pos, lineStart, curLine); + } + ++pos; + continue; + } + if (code >= 97) { + val = code - 97 + 10; + } else if (code >= 65) { + val = code - 65 + 10; + } else if (_isDigit(code)) { + val = code - 48; + } else { + val = Infinity; + } + if (val >= radix) { + if (val <= 9 && bailOnError) { + return { + n: null, + pos + }; + } else if (val <= 9 && errors.invalidDigit(pos, lineStart, curLine, radix)) { + val = 0; + } else if (forceLen) { + val = 0; + invalid = true; + } else { + break; + } + } + ++pos; + total = total * radix + val; + } + if (pos === start || len != null && pos - start !== len || invalid) { + return { + n: null, + pos + }; + } + return { + n: total, + pos + }; +} +function readCodePoint(input, pos, lineStart, curLine, throwOnInvalid, errors) { + const ch = input.charCodeAt(pos); + let code; + if (ch === 123) { + ++pos; + ({ + code, + pos + } = readHexChar(input, pos, lineStart, curLine, input.indexOf("}", pos) - pos, true, throwOnInvalid, errors)); + ++pos; + if (code !== null && code > 0x10ffff) { + if (throwOnInvalid) { + errors.invalidCodePoint(pos, lineStart, curLine); + } else { + return { + code: null, + pos + }; + } + } + } else { + ({ + code, + pos + } = readHexChar(input, pos, lineStart, curLine, 4, false, throwOnInvalid, errors)); + } + return { + code, + pos + }; +} + +//# sourceMappingURL=index.js.map + + +/***/ }), + +/***/ 92924: +/***/ ((__unused_webpack_module, exports) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports.isIdentifierChar = isIdentifierChar; +exports.isIdentifierName = isIdentifierName; +exports.isIdentifierStart = isIdentifierStart; +let nonASCIIidentifierStartChars = "\xaa\xb5\xba\xc0-\xd6\xd8-\xf6\xf8-\u02c1\u02c6-\u02d1\u02e0-\u02e4\u02ec\u02ee\u0370-\u0374\u0376\u0377\u037a-\u037d\u037f\u0386\u0388-\u038a\u038c\u038e-\u03a1\u03a3-\u03f5\u03f7-\u0481\u048a-\u052f\u0531-\u0556\u0559\u0560-\u0588\u05d0-\u05ea\u05ef-\u05f2\u0620-\u064a\u066e\u066f\u0671-\u06d3\u06d5\u06e5\u06e6\u06ee\u06ef\u06fa-\u06fc\u06ff\u0710\u0712-\u072f\u074d-\u07a5\u07b1\u07ca-\u07ea\u07f4\u07f5\u07fa\u0800-\u0815\u081a\u0824\u0828\u0840-\u0858\u0860-\u086a\u0870-\u0887\u0889-\u088e\u08a0-\u08c9\u0904-\u0939\u093d\u0950\u0958-\u0961\u0971-\u0980\u0985-\u098c\u098f\u0990\u0993-\u09a8\u09aa-\u09b0\u09b2\u09b6-\u09b9\u09bd\u09ce\u09dc\u09dd\u09df-\u09e1\u09f0\u09f1\u09fc\u0a05-\u0a0a\u0a0f\u0a10\u0a13-\u0a28\u0a2a-\u0a30\u0a32\u0a33\u0a35\u0a36\u0a38\u0a39\u0a59-\u0a5c\u0a5e\u0a72-\u0a74\u0a85-\u0a8d\u0a8f-\u0a91\u0a93-\u0aa8\u0aaa-\u0ab0\u0ab2\u0ab3\u0ab5-\u0ab9\u0abd\u0ad0\u0ae0\u0ae1\u0af9\u0b05-\u0b0c\u0b0f\u0b10\u0b13-\u0b28\u0b2a-\u0b30\u0b32\u0b33\u0b35-\u0b39\u0b3d\u0b5c\u0b5d\u0b5f-\u0b61\u0b71\u0b83\u0b85-\u0b8a\u0b8e-\u0b90\u0b92-\u0b95\u0b99\u0b9a\u0b9c\u0b9e\u0b9f\u0ba3\u0ba4\u0ba8-\u0baa\u0bae-\u0bb9\u0bd0\u0c05-\u0c0c\u0c0e-\u0c10\u0c12-\u0c28\u0c2a-\u0c39\u0c3d\u0c58-\u0c5a\u0c5d\u0c60\u0c61\u0c80\u0c85-\u0c8c\u0c8e-\u0c90\u0c92-\u0ca8\u0caa-\u0cb3\u0cb5-\u0cb9\u0cbd\u0cdd\u0cde\u0ce0\u0ce1\u0cf1\u0cf2\u0d04-\u0d0c\u0d0e-\u0d10\u0d12-\u0d3a\u0d3d\u0d4e\u0d54-\u0d56\u0d5f-\u0d61\u0d7a-\u0d7f\u0d85-\u0d96\u0d9a-\u0db1\u0db3-\u0dbb\u0dbd\u0dc0-\u0dc6\u0e01-\u0e30\u0e32\u0e33\u0e40-\u0e46\u0e81\u0e82\u0e84\u0e86-\u0e8a\u0e8c-\u0ea3\u0ea5\u0ea7-\u0eb0\u0eb2\u0eb3\u0ebd\u0ec0-\u0ec4\u0ec6\u0edc-\u0edf\u0f00\u0f40-\u0f47\u0f49-\u0f6c\u0f88-\u0f8c\u1000-\u102a\u103f\u1050-\u1055\u105a-\u105d\u1061\u1065\u1066\u106e-\u1070\u1075-\u1081\u108e\u10a0-\u10c5\u10c7\u10cd\u10d0-\u10fa\u10fc-\u1248\u124a-\u124d\u1250-\u1256\u1258\u125a-\u125d\u1260-\u1288\u128a-\u128d\u1290-\u12b0\u12b2-\u12b5\u12b8-\u12be\u12c0\u12c2-\u12c5\u12c8-\u12d6\u12d8-\u1310\u1312-\u1315\u1318-\u135a\u1380-\u138f\u13a0-\u13f5\u13f8-\u13fd\u1401-\u166c\u166f-\u167f\u1681-\u169a\u16a0-\u16ea\u16ee-\u16f8\u1700-\u1711\u171f-\u1731\u1740-\u1751\u1760-\u176c\u176e-\u1770\u1780-\u17b3\u17d7\u17dc\u1820-\u1878\u1880-\u18a8\u18aa\u18b0-\u18f5\u1900-\u191e\u1950-\u196d\u1970-\u1974\u1980-\u19ab\u19b0-\u19c9\u1a00-\u1a16\u1a20-\u1a54\u1aa7\u1b05-\u1b33\u1b45-\u1b4c\u1b83-\u1ba0\u1bae\u1baf\u1bba-\u1be5\u1c00-\u1c23\u1c4d-\u1c4f\u1c5a-\u1c7d\u1c80-\u1c8a\u1c90-\u1cba\u1cbd-\u1cbf\u1ce9-\u1cec\u1cee-\u1cf3\u1cf5\u1cf6\u1cfa\u1d00-\u1dbf\u1e00-\u1f15\u1f18-\u1f1d\u1f20-\u1f45\u1f48-\u1f4d\u1f50-\u1f57\u1f59\u1f5b\u1f5d\u1f5f-\u1f7d\u1f80-\u1fb4\u1fb6-\u1fbc\u1fbe\u1fc2-\u1fc4\u1fc6-\u1fcc\u1fd0-\u1fd3\u1fd6-\u1fdb\u1fe0-\u1fec\u1ff2-\u1ff4\u1ff6-\u1ffc\u2071\u207f\u2090-\u209c\u2102\u2107\u210a-\u2113\u2115\u2118-\u211d\u2124\u2126\u2128\u212a-\u2139\u213c-\u213f\u2145-\u2149\u214e\u2160-\u2188\u2c00-\u2ce4\u2ceb-\u2cee\u2cf2\u2cf3\u2d00-\u2d25\u2d27\u2d2d\u2d30-\u2d67\u2d6f\u2d80-\u2d96\u2da0-\u2da6\u2da8-\u2dae\u2db0-\u2db6\u2db8-\u2dbe\u2dc0-\u2dc6\u2dc8-\u2dce\u2dd0-\u2dd6\u2dd8-\u2dde\u3005-\u3007\u3021-\u3029\u3031-\u3035\u3038-\u303c\u3041-\u3096\u309b-\u309f\u30a1-\u30fa\u30fc-\u30ff\u3105-\u312f\u3131-\u318e\u31a0-\u31bf\u31f0-\u31ff\u3400-\u4dbf\u4e00-\ua48c\ua4d0-\ua4fd\ua500-\ua60c\ua610-\ua61f\ua62a\ua62b\ua640-\ua66e\ua67f-\ua69d\ua6a0-\ua6ef\ua717-\ua71f\ua722-\ua788\ua78b-\ua7cd\ua7d0\ua7d1\ua7d3\ua7d5-\ua7dc\ua7f2-\ua801\ua803-\ua805\ua807-\ua80a\ua80c-\ua822\ua840-\ua873\ua882-\ua8b3\ua8f2-\ua8f7\ua8fb\ua8fd\ua8fe\ua90a-\ua925\ua930-\ua946\ua960-\ua97c\ua984-\ua9b2\ua9cf\ua9e0-\ua9e4\ua9e6-\ua9ef\ua9fa-\ua9fe\uaa00-\uaa28\uaa40-\uaa42\uaa44-\uaa4b\uaa60-\uaa76\uaa7a\uaa7e-\uaaaf\uaab1\uaab5\uaab6\uaab9-\uaabd\uaac0\uaac2\uaadb-\uaadd\uaae0-\uaaea\uaaf2-\uaaf4\uab01-\uab06\uab09-\uab0e\uab11-\uab16\uab20-\uab26\uab28-\uab2e\uab30-\uab5a\uab5c-\uab69\uab70-\uabe2\uac00-\ud7a3\ud7b0-\ud7c6\ud7cb-\ud7fb\uf900-\ufa6d\ufa70-\ufad9\ufb00-\ufb06\ufb13-\ufb17\ufb1d\ufb1f-\ufb28\ufb2a-\ufb36\ufb38-\ufb3c\ufb3e\ufb40\ufb41\ufb43\ufb44\ufb46-\ufbb1\ufbd3-\ufd3d\ufd50-\ufd8f\ufd92-\ufdc7\ufdf0-\ufdfb\ufe70-\ufe74\ufe76-\ufefc\uff21-\uff3a\uff41-\uff5a\uff66-\uffbe\uffc2-\uffc7\uffca-\uffcf\uffd2-\uffd7\uffda-\uffdc"; +let nonASCIIidentifierChars = "\xb7\u0300-\u036f\u0387\u0483-\u0487\u0591-\u05bd\u05bf\u05c1\u05c2\u05c4\u05c5\u05c7\u0610-\u061a\u064b-\u0669\u0670\u06d6-\u06dc\u06df-\u06e4\u06e7\u06e8\u06ea-\u06ed\u06f0-\u06f9\u0711\u0730-\u074a\u07a6-\u07b0\u07c0-\u07c9\u07eb-\u07f3\u07fd\u0816-\u0819\u081b-\u0823\u0825-\u0827\u0829-\u082d\u0859-\u085b\u0897-\u089f\u08ca-\u08e1\u08e3-\u0903\u093a-\u093c\u093e-\u094f\u0951-\u0957\u0962\u0963\u0966-\u096f\u0981-\u0983\u09bc\u09be-\u09c4\u09c7\u09c8\u09cb-\u09cd\u09d7\u09e2\u09e3\u09e6-\u09ef\u09fe\u0a01-\u0a03\u0a3c\u0a3e-\u0a42\u0a47\u0a48\u0a4b-\u0a4d\u0a51\u0a66-\u0a71\u0a75\u0a81-\u0a83\u0abc\u0abe-\u0ac5\u0ac7-\u0ac9\u0acb-\u0acd\u0ae2\u0ae3\u0ae6-\u0aef\u0afa-\u0aff\u0b01-\u0b03\u0b3c\u0b3e-\u0b44\u0b47\u0b48\u0b4b-\u0b4d\u0b55-\u0b57\u0b62\u0b63\u0b66-\u0b6f\u0b82\u0bbe-\u0bc2\u0bc6-\u0bc8\u0bca-\u0bcd\u0bd7\u0be6-\u0bef\u0c00-\u0c04\u0c3c\u0c3e-\u0c44\u0c46-\u0c48\u0c4a-\u0c4d\u0c55\u0c56\u0c62\u0c63\u0c66-\u0c6f\u0c81-\u0c83\u0cbc\u0cbe-\u0cc4\u0cc6-\u0cc8\u0cca-\u0ccd\u0cd5\u0cd6\u0ce2\u0ce3\u0ce6-\u0cef\u0cf3\u0d00-\u0d03\u0d3b\u0d3c\u0d3e-\u0d44\u0d46-\u0d48\u0d4a-\u0d4d\u0d57\u0d62\u0d63\u0d66-\u0d6f\u0d81-\u0d83\u0dca\u0dcf-\u0dd4\u0dd6\u0dd8-\u0ddf\u0de6-\u0def\u0df2\u0df3\u0e31\u0e34-\u0e3a\u0e47-\u0e4e\u0e50-\u0e59\u0eb1\u0eb4-\u0ebc\u0ec8-\u0ece\u0ed0-\u0ed9\u0f18\u0f19\u0f20-\u0f29\u0f35\u0f37\u0f39\u0f3e\u0f3f\u0f71-\u0f84\u0f86\u0f87\u0f8d-\u0f97\u0f99-\u0fbc\u0fc6\u102b-\u103e\u1040-\u1049\u1056-\u1059\u105e-\u1060\u1062-\u1064\u1067-\u106d\u1071-\u1074\u1082-\u108d\u108f-\u109d\u135d-\u135f\u1369-\u1371\u1712-\u1715\u1732-\u1734\u1752\u1753\u1772\u1773\u17b4-\u17d3\u17dd\u17e0-\u17e9\u180b-\u180d\u180f-\u1819\u18a9\u1920-\u192b\u1930-\u193b\u1946-\u194f\u19d0-\u19da\u1a17-\u1a1b\u1a55-\u1a5e\u1a60-\u1a7c\u1a7f-\u1a89\u1a90-\u1a99\u1ab0-\u1abd\u1abf-\u1ace\u1b00-\u1b04\u1b34-\u1b44\u1b50-\u1b59\u1b6b-\u1b73\u1b80-\u1b82\u1ba1-\u1bad\u1bb0-\u1bb9\u1be6-\u1bf3\u1c24-\u1c37\u1c40-\u1c49\u1c50-\u1c59\u1cd0-\u1cd2\u1cd4-\u1ce8\u1ced\u1cf4\u1cf7-\u1cf9\u1dc0-\u1dff\u200c\u200d\u203f\u2040\u2054\u20d0-\u20dc\u20e1\u20e5-\u20f0\u2cef-\u2cf1\u2d7f\u2de0-\u2dff\u302a-\u302f\u3099\u309a\u30fb\ua620-\ua629\ua66f\ua674-\ua67d\ua69e\ua69f\ua6f0\ua6f1\ua802\ua806\ua80b\ua823-\ua827\ua82c\ua880\ua881\ua8b4-\ua8c5\ua8d0-\ua8d9\ua8e0-\ua8f1\ua8ff-\ua909\ua926-\ua92d\ua947-\ua953\ua980-\ua983\ua9b3-\ua9c0\ua9d0-\ua9d9\ua9e5\ua9f0-\ua9f9\uaa29-\uaa36\uaa43\uaa4c\uaa4d\uaa50-\uaa59\uaa7b-\uaa7d\uaab0\uaab2-\uaab4\uaab7\uaab8\uaabe\uaabf\uaac1\uaaeb-\uaaef\uaaf5\uaaf6\uabe3-\uabea\uabec\uabed\uabf0-\uabf9\ufb1e\ufe00-\ufe0f\ufe20-\ufe2f\ufe33\ufe34\ufe4d-\ufe4f\uff10-\uff19\uff3f\uff65"; +const nonASCIIidentifierStart = new RegExp("[" + nonASCIIidentifierStartChars + "]"); +const nonASCIIidentifier = new RegExp("[" + nonASCIIidentifierStartChars + nonASCIIidentifierChars + "]"); +nonASCIIidentifierStartChars = nonASCIIidentifierChars = null; +const astralIdentifierStartCodes = [0, 11, 2, 25, 2, 18, 2, 1, 2, 14, 3, 13, 35, 122, 70, 52, 268, 28, 4, 48, 48, 31, 14, 29, 6, 37, 11, 29, 3, 35, 5, 7, 2, 4, 43, 157, 19, 35, 5, 35, 5, 39, 9, 51, 13, 10, 2, 14, 2, 6, 2, 1, 2, 10, 2, 14, 2, 6, 2, 1, 4, 51, 13, 310, 10, 21, 11, 7, 25, 5, 2, 41, 2, 8, 70, 5, 3, 0, 2, 43, 2, 1, 4, 0, 3, 22, 11, 22, 10, 30, 66, 18, 2, 1, 11, 21, 11, 25, 71, 55, 7, 1, 65, 0, 16, 3, 2, 2, 2, 28, 43, 28, 4, 28, 36, 7, 2, 27, 28, 53, 11, 21, 11, 18, 14, 17, 111, 72, 56, 50, 14, 50, 14, 35, 39, 27, 10, 22, 251, 41, 7, 1, 17, 2, 60, 28, 11, 0, 9, 21, 43, 17, 47, 20, 28, 22, 13, 52, 58, 1, 3, 0, 14, 44, 33, 24, 27, 35, 30, 0, 3, 0, 9, 34, 4, 0, 13, 47, 15, 3, 22, 0, 2, 0, 36, 17, 2, 24, 20, 1, 64, 6, 2, 0, 2, 3, 2, 14, 2, 9, 8, 46, 39, 7, 3, 1, 3, 21, 2, 6, 2, 1, 2, 4, 4, 0, 19, 0, 13, 4, 31, 9, 2, 0, 3, 0, 2, 37, 2, 0, 26, 0, 2, 0, 45, 52, 19, 3, 21, 2, 31, 47, 21, 1, 2, 0, 185, 46, 42, 3, 37, 47, 21, 0, 60, 42, 14, 0, 72, 26, 38, 6, 186, 43, 117, 63, 32, 7, 3, 0, 3, 7, 2, 1, 2, 23, 16, 0, 2, 0, 95, 7, 3, 38, 17, 0, 2, 0, 29, 0, 11, 39, 8, 0, 22, 0, 12, 45, 20, 0, 19, 72, 200, 32, 32, 8, 2, 36, 18, 0, 50, 29, 113, 6, 2, 1, 2, 37, 22, 0, 26, 5, 2, 1, 2, 31, 15, 0, 328, 18, 16, 0, 2, 12, 2, 33, 125, 0, 80, 921, 103, 110, 18, 195, 2637, 96, 16, 1071, 18, 5, 26, 3994, 6, 582, 6842, 29, 1763, 568, 8, 30, 18, 78, 18, 29, 19, 47, 17, 3, 32, 20, 6, 18, 433, 44, 212, 63, 129, 74, 6, 0, 67, 12, 65, 1, 2, 0, 29, 6135, 9, 1237, 42, 9, 8936, 3, 2, 6, 2, 1, 2, 290, 16, 0, 30, 2, 3, 0, 15, 3, 9, 395, 2309, 106, 6, 12, 4, 8, 8, 9, 5991, 84, 2, 70, 2, 1, 3, 0, 3, 1, 3, 3, 2, 11, 2, 0, 2, 6, 2, 64, 2, 3, 3, 7, 2, 6, 2, 27, 2, 3, 2, 4, 2, 0, 4, 6, 2, 339, 3, 24, 2, 24, 2, 30, 2, 24, 2, 30, 2, 24, 2, 30, 2, 24, 2, 30, 2, 24, 2, 7, 1845, 30, 7, 5, 262, 61, 147, 44, 11, 6, 17, 0, 322, 29, 19, 43, 485, 27, 229, 29, 3, 0, 496, 6, 2, 3, 2, 1, 2, 14, 2, 196, 60, 67, 8, 0, 1205, 3, 2, 26, 2, 1, 2, 0, 3, 0, 2, 9, 2, 3, 2, 0, 2, 0, 7, 0, 5, 0, 2, 0, 2, 0, 2, 2, 2, 1, 2, 0, 3, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 1, 2, 0, 3, 3, 2, 6, 2, 3, 2, 3, 2, 0, 2, 9, 2, 16, 6, 2, 2, 4, 2, 16, 4421, 42719, 33, 4153, 7, 221, 3, 5761, 15, 7472, 16, 621, 2467, 541, 1507, 4938, 6, 4191]; +const astralIdentifierCodes = [509, 0, 227, 0, 150, 4, 294, 9, 1368, 2, 2, 1, 6, 3, 41, 2, 5, 0, 166, 1, 574, 3, 9, 9, 7, 9, 32, 4, 318, 1, 80, 3, 71, 10, 50, 3, 123, 2, 54, 14, 32, 10, 3, 1, 11, 3, 46, 10, 8, 0, 46, 9, 7, 2, 37, 13, 2, 9, 6, 1, 45, 0, 13, 2, 49, 13, 9, 3, 2, 11, 83, 11, 7, 0, 3, 0, 158, 11, 6, 9, 7, 3, 56, 1, 2, 6, 3, 1, 3, 2, 10, 0, 11, 1, 3, 6, 4, 4, 68, 8, 2, 0, 3, 0, 2, 3, 2, 4, 2, 0, 15, 1, 83, 17, 10, 9, 5, 0, 82, 19, 13, 9, 214, 6, 3, 8, 28, 1, 83, 16, 16, 9, 82, 12, 9, 9, 7, 19, 58, 14, 5, 9, 243, 14, 166, 9, 71, 5, 2, 1, 3, 3, 2, 0, 2, 1, 13, 9, 120, 6, 3, 6, 4, 0, 29, 9, 41, 6, 2, 3, 9, 0, 10, 10, 47, 15, 343, 9, 54, 7, 2, 7, 17, 9, 57, 21, 2, 13, 123, 5, 4, 0, 2, 1, 2, 6, 2, 0, 9, 9, 49, 4, 2, 1, 2, 4, 9, 9, 330, 3, 10, 1, 2, 0, 49, 6, 4, 4, 14, 10, 5350, 0, 7, 14, 11465, 27, 2343, 9, 87, 9, 39, 4, 60, 6, 26, 9, 535, 9, 470, 0, 2, 54, 8, 3, 82, 0, 12, 1, 19628, 1, 4178, 9, 519, 45, 3, 22, 543, 4, 4, 5, 9, 7, 3, 6, 31, 3, 149, 2, 1418, 49, 513, 54, 5, 49, 9, 0, 15, 0, 23, 4, 2, 14, 1361, 6, 2, 16, 3, 6, 2, 1, 2, 4, 101, 0, 161, 6, 10, 9, 357, 0, 62, 13, 499, 13, 245, 1, 2, 9, 726, 6, 110, 6, 6, 9, 4759, 9, 787719, 239]; +function isInAstralSet(code, set) { + let pos = 0x10000; + for (let i = 0, length = set.length; i < length; i += 2) { + pos += set[i]; + if (pos > code) return false; + pos += set[i + 1]; + if (pos >= code) return true; + } + return false; +} +function isIdentifierStart(code) { + if (code < 65) return code === 36; + if (code <= 90) return true; + if (code < 97) return code === 95; + if (code <= 122) return true; + if (code <= 0xffff) { + return code >= 0xaa && nonASCIIidentifierStart.test(String.fromCharCode(code)); + } + return isInAstralSet(code, astralIdentifierStartCodes); +} +function isIdentifierChar(code) { + if (code < 48) return code === 36; + if (code < 58) return true; + if (code < 65) return false; + if (code <= 90) return true; + if (code < 97) return code === 95; + if (code <= 122) return true; + if (code <= 0xffff) { + return code >= 0xaa && nonASCIIidentifier.test(String.fromCharCode(code)); + } + return isInAstralSet(code, astralIdentifierStartCodes) || isInAstralSet(code, astralIdentifierCodes); +} +function isIdentifierName(name) { + let isFirst = true; + for (let i = 0; i < name.length; i++) { + let cp = name.charCodeAt(i); + if ((cp & 0xfc00) === 0xd800 && i + 1 < name.length) { + const trail = name.charCodeAt(++i); + if ((trail & 0xfc00) === 0xdc00) { + cp = 0x10000 + ((cp & 0x3ff) << 10) + (trail & 0x3ff); + } + } + if (isFirst) { + isFirst = false; + if (!isIdentifierStart(cp)) { + return false; + } + } else if (!isIdentifierChar(cp)) { + return false; + } + } + return !isFirst; +} + +//# sourceMappingURL=identifier.js.map + + +/***/ }), + +/***/ 76599: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +Object.defineProperty(exports, "isIdentifierChar", ({ + enumerable: true, + get: function () { + return _identifier.isIdentifierChar; + } +})); +Object.defineProperty(exports, "isIdentifierName", ({ + enumerable: true, + get: function () { + return _identifier.isIdentifierName; + } +})); +Object.defineProperty(exports, "isIdentifierStart", ({ + enumerable: true, + get: function () { + return _identifier.isIdentifierStart; + } +})); +Object.defineProperty(exports, "isKeyword", ({ + enumerable: true, + get: function () { + return _keyword.isKeyword; + } +})); +Object.defineProperty(exports, "isReservedWord", ({ + enumerable: true, + get: function () { + return _keyword.isReservedWord; + } +})); +Object.defineProperty(exports, "isStrictBindOnlyReservedWord", ({ + enumerable: true, + get: function () { + return _keyword.isStrictBindOnlyReservedWord; + } +})); +Object.defineProperty(exports, "isStrictBindReservedWord", ({ + enumerable: true, + get: function () { + return _keyword.isStrictBindReservedWord; + } +})); +Object.defineProperty(exports, "isStrictReservedWord", ({ + enumerable: true, + get: function () { + return _keyword.isStrictReservedWord; + } +})); +var _identifier = __nccwpck_require__(92924); +var _keyword = __nccwpck_require__(49884); + +//# sourceMappingURL=index.js.map + + +/***/ }), + +/***/ 49884: +/***/ ((__unused_webpack_module, exports) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports.isKeyword = isKeyword; +exports.isReservedWord = isReservedWord; +exports.isStrictBindOnlyReservedWord = isStrictBindOnlyReservedWord; +exports.isStrictBindReservedWord = isStrictBindReservedWord; +exports.isStrictReservedWord = isStrictReservedWord; +const reservedWords = { + keyword: ["break", "case", "catch", "continue", "debugger", "default", "do", "else", "finally", "for", "function", "if", "return", "switch", "throw", "try", "var", "const", "while", "with", "new", "this", "super", "class", "extends", "export", "import", "null", "true", "false", "in", "instanceof", "typeof", "void", "delete"], + strict: ["implements", "interface", "let", "package", "private", "protected", "public", "static", "yield"], + strictBind: ["eval", "arguments"] +}; +const keywords = new Set(reservedWords.keyword); +const reservedWordsStrictSet = new Set(reservedWords.strict); +const reservedWordsStrictBindSet = new Set(reservedWords.strictBind); +function isReservedWord(word, inModule) { + return inModule && word === "await" || word === "enum"; +} +function isStrictReservedWord(word, inModule) { + return isReservedWord(word, inModule) || reservedWordsStrictSet.has(word); +} +function isStrictBindOnlyReservedWord(word) { + return reservedWordsStrictBindSet.has(word); +} +function isStrictBindReservedWord(word, inModule) { + return isStrictReservedWord(word, inModule) || isStrictBindOnlyReservedWord(word); +} +function isKeyword(word) { + return keywords.has(word); +} + +//# sourceMappingURL=keyword.js.map + + +/***/ }), + +/***/ 21214: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports["default"] = void 0; +var _template = __nccwpck_require__(19648); +function helper(minVersion, source, metadata) { + return Object.freeze({ + minVersion, + ast: () => _template.default.program.ast(source, { + preserveComments: true + }), + metadata + }); +} +const helpers = exports["default"] = { + __proto__: null, + OverloadYield: helper("7.18.14", "function _OverloadYield(e,d){this.v=e,this.k=d}", { + globals: [], + locals: { + _OverloadYield: ["body.0.id"] + }, + exportBindingAssignments: [], + exportName: "_OverloadYield", + dependencies: {} + }), + applyDecoratedDescriptor: helper("7.0.0-beta.0", 'function _applyDecoratedDescriptor(i,e,r,n,l){var a={};return Object.keys(n).forEach((function(i){a[i]=n[i]})),a.enumerable=!!a.enumerable,a.configurable=!!a.configurable,("value"in a||a.initializer)&&(a.writable=!0),a=r.slice().reverse().reduce((function(r,n){return n(i,e,r)||r}),a),l&&void 0!==a.initializer&&(a.value=a.initializer?a.initializer.call(l):void 0,a.initializer=void 0),void 0===a.initializer?(Object.defineProperty(i,e,a),null):a}', { + globals: ["Object"], + locals: { + _applyDecoratedDescriptor: ["body.0.id"] + }, + exportBindingAssignments: [], + exportName: "_applyDecoratedDescriptor", + dependencies: {} + }), + applyDecs2311: helper("7.24.0", 'function applyDecs2311(e,t,n,r,o,i){var a,c,u,s,f,l,p,d=Symbol.metadata||Symbol.for("Symbol.metadata"),m=Object.defineProperty,h=Object.create,y=[h(null),h(null)],v=t.length;function g(t,n,r){return function(o,i){n&&(i=o,o=e);for(var a=0;a=0;O-=n?2:1){var T=b(h[O],"A decorator","be",!0),z=n?h[O-1]:void 0,A={},H={kind:["field","accessor","method","getter","setter","class"][o],name:r,metadata:a,addInitializer:function(e,t){if(e.v)throw new TypeError("attempted to call addInitializer after decoration was finished");b(t,"An initializer","be",!0),i.push(t)}.bind(null,A)};if(w)c=T.call(z,N,H),A.v=1,b(c,"class decorators","return")&&(N=c);else if(H.static=s,H.private=f,c=H.access={has:f?p.bind():function(e){return r in e}},j||(c.get=f?E?function(e){return d(e),P.value}:I("get",0,d):function(e){return e[r]}),E||S||(c.set=f?I("set",0,d):function(e,t){e[r]=t}),N=T.call(z,D?{get:P.get,set:P.set}:P[F],H),A.v=1,D){if("object"==typeof N&&N)(c=b(N.get,"accessor.get"))&&(P.get=c),(c=b(N.set,"accessor.set"))&&(P.set=c),(c=b(N.init,"accessor.init"))&&k.unshift(c);else if(void 0!==N)throw new TypeError("accessor decorators must return an object with get, set, or init properties or undefined")}else b(N,(l?"field":"method")+" decorators","return")&&(l?k.unshift(N):P[F]=N)}return o<2&&u.push(g(k,s,1),g(i,s,0)),l||w||(f?D?u.splice(-1,0,I("get",s),I("set",s)):u.push(E?P[F]:b.call.bind(P[F])):m(e,r,P)),N}function w(e){return m(e,d,{configurable:!0,enumerable:!0,value:a})}return void 0!==i&&(a=i[d]),a=h(null==a?null:a),f=[],l=function(e){e&&f.push(g(e))},p=function(t,r){for(var i=0;ir.length)&&(a=r.length);for(var e=0,n=Array(a);e=r.length?{done:!0}:{done:!1,value:r[n++]}},e:function(r){throw r},f:F}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}var o,a=!0,u=!1;return{s:function(){t=t.call(r)},n:function(){var r=t.next();return a=r.done,r},e:function(r){u=!0,o=r},f:function(){try{a||null==t.return||t.return()}finally{if(u)throw o}}}}', { + globals: ["Symbol", "Array", "TypeError"], + locals: { + _createForOfIteratorHelper: ["body.0.id"] + }, + exportBindingAssignments: [], + exportName: "_createForOfIteratorHelper", + dependencies: { + unsupportedIterableToArray: ["body.0.body.body.1.consequent.body.0.test.left.right.right.callee"] + } + }), + createForOfIteratorHelperLoose: helper("7.9.0", 'function _createForOfIteratorHelperLoose(r,e){var t="undefined"!=typeof Symbol&&r[Symbol.iterator]||r["@@iterator"];if(t)return(t=t.call(r)).next.bind(t);if(Array.isArray(r)||(t=unsupportedIterableToArray(r))||e&&r&&"number"==typeof r.length){t&&(r=t);var o=0;return function(){return o>=r.length?{done:!0}:{done:!1,value:r[o++]}}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}', { + globals: ["Symbol", "Array", "TypeError"], + locals: { + _createForOfIteratorHelperLoose: ["body.0.id"] + }, + exportBindingAssignments: [], + exportName: "_createForOfIteratorHelperLoose", + dependencies: { + unsupportedIterableToArray: ["body.0.body.body.2.test.left.right.right.callee"] + } + }), + createSuper: helper("7.9.0", "function _createSuper(t){var r=isNativeReflectConstruct();return function(){var e,o=getPrototypeOf(t);if(r){var s=getPrototypeOf(this).constructor;e=Reflect.construct(o,arguments,s)}else e=o.apply(this,arguments);return possibleConstructorReturn(this,e)}}", { + globals: ["Reflect"], + locals: { + _createSuper: ["body.0.id"] + }, + exportBindingAssignments: [], + exportName: "_createSuper", + dependencies: { + getPrototypeOf: ["body.0.body.body.1.argument.body.body.0.declarations.1.init.callee", "body.0.body.body.1.argument.body.body.1.consequent.body.0.declarations.0.init.object.callee"], + isNativeReflectConstruct: ["body.0.body.body.0.declarations.0.init.callee"], + possibleConstructorReturn: ["body.0.body.body.1.argument.body.body.2.argument.callee"] + } + }), + decorate: helper("7.1.5", 'function _decorate(e,r,t,i){var o=_getDecoratorsApi();if(i)for(var n=0;n=0;n--){var s=r[e.placement];s.splice(s.indexOf(e.key),1);var a=this.fromElementDescriptor(e),l=this.toElementFinisherExtras((0,o[n])(a)||a);e=l.element,this.addElementPlacement(e,r),l.finisher&&i.push(l.finisher);var c=l.extras;if(c){for(var p=0;p=0;i--){var o=this.fromClassDescriptor(e),n=this.toClassDescriptor((0,r[i])(o)||o);if(void 0!==n.finisher&&t.push(n.finisher),void 0!==n.elements){e=n.elements;for(var s=0;s1){for(var t=Array(n),f=0;f=0;--o){var i=this.tryEntries[o],a=i[4],u=this.prev,c=i[1],h=i[2];if(-1===i[0])return n("end"),!1;if(!c&&!h)throw Error("try statement without catch or finally");if(null!=i[0]&&i[0]<=u){if(u=0;--e){var n=this.tryEntries[e];if(n[0]>-1&&n[0]<=this.prev&&this.prev=0;--r){var e=this.tryEntries[r];if(e[2]===t)return this.complete(e[4],e[3]),m(e),f}},catch:function(t){for(var r=this.tryEntries.length-1;r>=0;--r){var e=this.tryEntries[r];if(e[0]===t){var n=e[4];if("throw"===n.type){var o=n.arg;m(e)}return o}}throw Error("illegal catch attempt")},delegateYield:function(r,e,n){return this.delegate={i:x(r),r:e,n:n},"next"===this.method&&(this.arg=t),f}},r}', { + globals: ["Object", "Symbol", "Error", "TypeError", "isNaN", "Promise"], + locals: { + _regeneratorRuntime: ["body.0.id", "body.0.body.body.0.expression.left"] + }, + exportBindingAssignments: ["body.0.body.body.0.expression"], + exportName: "_regeneratorRuntime", + dependencies: {} + }), + set: helper("7.0.0-beta.0", 'function set(e,r,t,o){return set="undefined"!=typeof Reflect&&Reflect.set?Reflect.set:function(e,r,t,o){var f,i=superPropBase(e,r);if(i){if((f=Object.getOwnPropertyDescriptor(i,r)).set)return f.set.call(o,t),!0;if(!f.writable)return!1}if(f=Object.getOwnPropertyDescriptor(o,r)){if(!f.writable)return!1;f.value=t,Object.defineProperty(o,r,f)}else defineProperty(o,r,t);return!0},set(e,r,t,o)}function _set(e,r,t,o,f){if(!set(e,r,t,o||e)&&f)throw new TypeError("failed to set property");return t}', { + globals: ["Reflect", "Object", "TypeError"], + locals: { + set: ["body.0.id", "body.0.body.body.0.argument.expressions.1.callee", "body.1.body.body.0.test.left.argument.callee", "body.0.body.body.0.argument.expressions.0.left"], + _set: ["body.1.id"] + }, + exportBindingAssignments: [], + exportName: "_set", + dependencies: { + superPropBase: ["body.0.body.body.0.argument.expressions.0.right.alternate.body.body.0.declarations.1.init.callee"], + defineProperty: ["body.0.body.body.0.argument.expressions.0.right.alternate.body.body.2.alternate.expression.callee"] + } + }), + setFunctionName: helper("7.23.6", 'function setFunctionName(e,t,n){"symbol"==typeof t&&(t=(t=t.description)?"["+t+"]":"");try{Object.defineProperty(e,"name",{configurable:!0,value:n?n+" "+t:t})}catch(e){}return e}', { + globals: ["Object"], + locals: { + setFunctionName: ["body.0.id"] + }, + exportBindingAssignments: [], + exportName: "setFunctionName", + dependencies: {} + }), + setPrototypeOf: helper("7.0.0-beta.0", "function _setPrototypeOf(t,e){return _setPrototypeOf=Object.setPrototypeOf?Object.setPrototypeOf.bind():function(t,e){return t.__proto__=e,t},_setPrototypeOf(t,e)}", { + globals: ["Object"], + locals: { + _setPrototypeOf: ["body.0.id", "body.0.body.body.0.argument.expressions.1.callee", "body.0.body.body.0.argument.expressions.0.left"] + }, + exportBindingAssignments: ["body.0.body.body.0.argument.expressions.0"], + exportName: "_setPrototypeOf", + dependencies: {} + }), + skipFirstGeneratorNext: helper("7.0.0-beta.0", "function _skipFirstGeneratorNext(t){return function(){var r=t.apply(this,arguments);return r.next(),r}}", { + globals: [], + locals: { + _skipFirstGeneratorNext: ["body.0.id"] + }, + exportBindingAssignments: [], + exportName: "_skipFirstGeneratorNext", + dependencies: {} + }), + slicedToArray: helper("7.0.0-beta.0", "function _slicedToArray(r,e){return arrayWithHoles(r)||iterableToArrayLimit(r,e)||unsupportedIterableToArray(r,e)||nonIterableRest()}", { + globals: [], + locals: { + _slicedToArray: ["body.0.id"] + }, + exportBindingAssignments: [], + exportName: "_slicedToArray", + dependencies: { + arrayWithHoles: ["body.0.body.body.0.argument.left.left.left.callee"], + iterableToArrayLimit: ["body.0.body.body.0.argument.left.left.right.callee"], + unsupportedIterableToArray: ["body.0.body.body.0.argument.left.right.callee"], + nonIterableRest: ["body.0.body.body.0.argument.right.callee"] + } + }), + superPropBase: helper("7.0.0-beta.0", "function _superPropBase(t,o){for(;!{}.hasOwnProperty.call(t,o)&&null!==(t=getPrototypeOf(t)););return t}", { + globals: [], + locals: { + _superPropBase: ["body.0.id"] + }, + exportBindingAssignments: [], + exportName: "_superPropBase", + dependencies: { + getPrototypeOf: ["body.0.body.body.0.test.right.right.right.callee"] + } + }), + superPropGet: helper("7.25.0", 'function _superPropGet(t,o,e,r){var p=get(getPrototypeOf(1&r?t.prototype:t),o,e);return 2&r&&"function"==typeof p?function(t){return p.apply(e,t)}:p}', { + globals: [], + locals: { + _superPropGet: ["body.0.id"] + }, + exportBindingAssignments: [], + exportName: "_superPropGet", + dependencies: { + get: ["body.0.body.body.0.declarations.0.init.callee"], + getPrototypeOf: ["body.0.body.body.0.declarations.0.init.arguments.0.callee"] + } + }), + superPropSet: helper("7.25.0", "function _superPropSet(t,e,o,r,p,f){return set(getPrototypeOf(f?t.prototype:t),e,o,r,p)}", { + globals: [], + locals: { + _superPropSet: ["body.0.id"] + }, + exportBindingAssignments: [], + exportName: "_superPropSet", + dependencies: { + set: ["body.0.body.body.0.argument.callee"], + getPrototypeOf: ["body.0.body.body.0.argument.arguments.0.callee"] + } + }), + taggedTemplateLiteral: helper("7.0.0-beta.0", "function _taggedTemplateLiteral(e,t){return t||(t=e.slice(0)),Object.freeze(Object.defineProperties(e,{raw:{value:Object.freeze(t)}}))}", { + globals: ["Object"], + locals: { + _taggedTemplateLiteral: ["body.0.id"] + }, + exportBindingAssignments: [], + exportName: "_taggedTemplateLiteral", + dependencies: {} + }), + taggedTemplateLiteralLoose: helper("7.0.0-beta.0", "function _taggedTemplateLiteralLoose(e,t){return t||(t=e.slice(0)),e.raw=t,e}", { + globals: [], + locals: { + _taggedTemplateLiteralLoose: ["body.0.id"] + }, + exportBindingAssignments: [], + exportName: "_taggedTemplateLiteralLoose", + dependencies: {} + }), + tdz: helper("7.5.5", 'function _tdzError(e){throw new ReferenceError(e+" is not defined - temporal dead zone")}', { + globals: ["ReferenceError"], + locals: { + _tdzError: ["body.0.id"] + }, + exportBindingAssignments: [], + exportName: "_tdzError", + dependencies: {} + }), + temporalRef: helper("7.0.0-beta.0", "function _temporalRef(r,e){return r===undef?err(e):r}", { + globals: [], + locals: { + _temporalRef: ["body.0.id"] + }, + exportBindingAssignments: [], + exportName: "_temporalRef", + dependencies: { + temporalUndefined: ["body.0.body.body.0.argument.test.right"], + tdz: ["body.0.body.body.0.argument.consequent.callee"] + } + }), + temporalUndefined: helper("7.0.0-beta.0", "function _temporalUndefined(){}", { + globals: [], + locals: { + _temporalUndefined: ["body.0.id"] + }, + exportBindingAssignments: [], + exportName: "_temporalUndefined", + dependencies: {} + }), + toArray: helper("7.0.0-beta.0", "function _toArray(r){return arrayWithHoles(r)||iterableToArray(r)||unsupportedIterableToArray(r)||nonIterableRest()}", { + globals: [], + locals: { + _toArray: ["body.0.id"] + }, + exportBindingAssignments: [], + exportName: "_toArray", + dependencies: { + arrayWithHoles: ["body.0.body.body.0.argument.left.left.left.callee"], + iterableToArray: ["body.0.body.body.0.argument.left.left.right.callee"], + unsupportedIterableToArray: ["body.0.body.body.0.argument.left.right.callee"], + nonIterableRest: ["body.0.body.body.0.argument.right.callee"] + } + }), + toConsumableArray: helper("7.0.0-beta.0", "function _toConsumableArray(r){return arrayWithoutHoles(r)||iterableToArray(r)||unsupportedIterableToArray(r)||nonIterableSpread()}", { + globals: [], + locals: { + _toConsumableArray: ["body.0.id"] + }, + exportBindingAssignments: [], + exportName: "_toConsumableArray", + dependencies: { + arrayWithoutHoles: ["body.0.body.body.0.argument.left.left.left.callee"], + iterableToArray: ["body.0.body.body.0.argument.left.left.right.callee"], + unsupportedIterableToArray: ["body.0.body.body.0.argument.left.right.callee"], + nonIterableSpread: ["body.0.body.body.0.argument.right.callee"] + } + }), + toPrimitive: helper("7.1.5", 'function toPrimitive(t,r){if("object"!=typeof t||!t)return t;var e=t[Symbol.toPrimitive];if(void 0!==e){var i=e.call(t,r||"default");if("object"!=typeof i)return i;throw new TypeError("@@toPrimitive must return a primitive value.")}return("string"===r?String:Number)(t)}', { + globals: ["Symbol", "TypeError", "String", "Number"], + locals: { + toPrimitive: ["body.0.id"] + }, + exportBindingAssignments: [], + exportName: "toPrimitive", + dependencies: {} + }), + toPropertyKey: helper("7.1.5", 'function toPropertyKey(t){var i=toPrimitive(t,"string");return"symbol"==typeof i?i:i+""}', { + globals: [], + locals: { + toPropertyKey: ["body.0.id"] + }, + exportBindingAssignments: [], + exportName: "toPropertyKey", + dependencies: { + toPrimitive: ["body.0.body.body.0.declarations.0.init.callee"] + } + }), + toSetter: helper("7.24.0", 'function _toSetter(t,e,n){e||(e=[]);var r=e.length++;return Object.defineProperty({},"_",{set:function(o){e[r]=o,t.apply(n,e)}})}', { + globals: ["Object"], + locals: { + _toSetter: ["body.0.id"] + }, + exportBindingAssignments: [], + exportName: "_toSetter", + dependencies: {} + }), + tsRewriteRelativeImportExtensions: helper("7.27.0", 'function tsRewriteRelativeImportExtensions(t,e){return"string"==typeof t&&/^\\.\\.?\\//.test(t)?t.replace(/\\.(tsx)$|((?:\\.d)?)((?:\\.[^./]+)?)\\.([cm]?)ts$/i,(function(t,s,r,n,o){return s?e?".jsx":".js":!r||n&&o?r+n+"."+o.toLowerCase()+"js":t})):t}', { + globals: [], + locals: { + tsRewriteRelativeImportExtensions: ["body.0.id"] + }, + exportBindingAssignments: [], + exportName: "tsRewriteRelativeImportExtensions", + dependencies: {} + }), + typeof: helper("7.0.0-beta.0", 'function _typeof(o){"@babel/helpers - typeof";return _typeof="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(o){return typeof o}:function(o){return o&&"function"==typeof Symbol&&o.constructor===Symbol&&o!==Symbol.prototype?"symbol":typeof o},_typeof(o)}', { + globals: ["Symbol"], + locals: { + _typeof: ["body.0.id", "body.0.body.body.0.argument.expressions.1.callee", "body.0.body.body.0.argument.expressions.0.left"] + }, + exportBindingAssignments: ["body.0.body.body.0.argument.expressions.0"], + exportName: "_typeof", + dependencies: {} + }), + unsupportedIterableToArray: helper("7.9.0", 'function _unsupportedIterableToArray(r,a){if(r){if("string"==typeof r)return arrayLikeToArray(r,a);var t={}.toString.call(r).slice(8,-1);return"Object"===t&&r.constructor&&(t=r.constructor.name),"Map"===t||"Set"===t?Array.from(r):"Arguments"===t||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t)?arrayLikeToArray(r,a):void 0}}', { + globals: ["Array"], + locals: { + _unsupportedIterableToArray: ["body.0.id"] + }, + exportBindingAssignments: [], + exportName: "_unsupportedIterableToArray", + dependencies: { + arrayLikeToArray: ["body.0.body.body.0.consequent.body.0.consequent.argument.callee", "body.0.body.body.0.consequent.body.2.argument.expressions.1.alternate.consequent.callee"] + } + }), + usingCtx: helper("7.23.9", 'function _usingCtx(){var r="function"==typeof SuppressedError?SuppressedError:function(r,e){var n=Error();return n.name="SuppressedError",n.error=r,n.suppressed=e,n},e={},n=[];function using(r,e){if(null!=e){if(Object(e)!==e)throw new TypeError("using declarations can only be used with objects, functions, null, or undefined.");if(r)var o=e[Symbol.asyncDispose||Symbol.for("Symbol.asyncDispose")];if(void 0===o&&(o=e[Symbol.dispose||Symbol.for("Symbol.dispose")],r))var t=o;if("function"!=typeof o)throw new TypeError("Object is not disposable.");t&&(o=function(){try{t.call(e)}catch(r){return Promise.reject(r)}}),n.push({v:e,d:o,a:r})}else r&&n.push({d:e,a:r});return e}return{e:e,u:using.bind(null,!1),a:using.bind(null,!0),d:function(){var o,t=this.e,s=0;function next(){for(;o=n.pop();)try{if(!o.a&&1===s)return s=0,n.push(o),Promise.resolve().then(next);if(o.d){var r=o.d.call(o.v);if(o.a)return s|=2,Promise.resolve(r).then(next,err)}else s|=1}catch(r){return err(r)}if(1===s)return t!==e?Promise.reject(t):Promise.resolve();if(t!==e)throw t}function err(n){return t=t!==e?new r(n,t):n,next()}return next()}}}', { + globals: ["SuppressedError", "Error", "Object", "TypeError", "Symbol", "Promise"], + locals: { + _usingCtx: ["body.0.id"] + }, + exportBindingAssignments: [], + exportName: "_usingCtx", + dependencies: {} + }), + wrapAsyncGenerator: helper("7.0.0-beta.0", 'function _wrapAsyncGenerator(e){return function(){return new AsyncGenerator(e.apply(this,arguments))}}function AsyncGenerator(e){var r,t;function resume(r,t){try{var n=e[r](t),o=n.value,u=o instanceof OverloadYield;Promise.resolve(u?o.v:o).then((function(t){if(u){var i="return"===r?"return":"next";if(!o.k||t.done)return resume(i,t);t=e[i](t).value}settle(n.done?"return":"normal",t)}),(function(e){resume("throw",e)}))}catch(e){settle("throw",e)}}function settle(e,n){switch(e){case"return":r.resolve({value:n,done:!0});break;case"throw":r.reject(n);break;default:r.resolve({value:n,done:!1})}(r=r.next)?resume(r.key,r.arg):t=null}this._invoke=function(e,n){return new Promise((function(o,u){var i={key:e,arg:n,resolve:o,reject:u,next:null};t?t=t.next=i:(r=t=i,resume(e,n))}))},"function"!=typeof e.return&&(this.return=void 0)}AsyncGenerator.prototype["function"==typeof Symbol&&Symbol.asyncIterator||"@@asyncIterator"]=function(){return this},AsyncGenerator.prototype.next=function(e){return this._invoke("next",e)},AsyncGenerator.prototype.throw=function(e){return this._invoke("throw",e)},AsyncGenerator.prototype.return=function(e){return this._invoke("return",e)};', { + globals: ["Promise", "Symbol"], + locals: { + _wrapAsyncGenerator: ["body.0.id"], + AsyncGenerator: ["body.1.id", "body.0.body.body.0.argument.body.body.0.argument.callee", "body.2.expression.expressions.0.left.object.object", "body.2.expression.expressions.1.left.object.object", "body.2.expression.expressions.2.left.object.object", "body.2.expression.expressions.3.left.object.object"] + }, + exportBindingAssignments: [], + exportName: "_wrapAsyncGenerator", + dependencies: { + OverloadYield: ["body.1.body.body.1.body.body.0.block.body.0.declarations.2.init.right"] + } + }), + wrapNativeSuper: helper("7.0.0-beta.0", 'function _wrapNativeSuper(t){var r="function"==typeof Map?new Map:void 0;return _wrapNativeSuper=function(t){if(null===t||!isNativeFunction(t))return t;if("function"!=typeof t)throw new TypeError("Super expression must either be null or a function");if(void 0!==r){if(r.has(t))return r.get(t);r.set(t,Wrapper)}function Wrapper(){return construct(t,arguments,getPrototypeOf(this).constructor)}return Wrapper.prototype=Object.create(t.prototype,{constructor:{value:Wrapper,enumerable:!1,writable:!0,configurable:!0}}),setPrototypeOf(Wrapper,t)},_wrapNativeSuper(t)}', { + globals: ["Map", "TypeError", "Object"], + locals: { + _wrapNativeSuper: ["body.0.id", "body.0.body.body.1.argument.expressions.1.callee", "body.0.body.body.1.argument.expressions.0.left"] + }, + exportBindingAssignments: ["body.0.body.body.1.argument.expressions.0"], + exportName: "_wrapNativeSuper", + dependencies: { + getPrototypeOf: ["body.0.body.body.1.argument.expressions.0.right.body.body.3.body.body.0.argument.arguments.2.object.callee"], + setPrototypeOf: ["body.0.body.body.1.argument.expressions.0.right.body.body.4.argument.expressions.1.callee"], + isNativeFunction: ["body.0.body.body.1.argument.expressions.0.right.body.body.0.test.right.argument.callee"], + construct: ["body.0.body.body.1.argument.expressions.0.right.body.body.3.body.body.0.argument.callee"] + } + }), + wrapRegExp: helper("7.19.0", 'function _wrapRegExp(){_wrapRegExp=function(e,r){return new BabelRegExp(e,void 0,r)};var e=RegExp.prototype,r=new WeakMap;function BabelRegExp(e,t,p){var o=RegExp(e,t);return r.set(o,p||r.get(e)),setPrototypeOf(o,BabelRegExp.prototype)}function buildGroups(e,t){var p=r.get(t);return Object.keys(p).reduce((function(r,t){var o=p[t];if("number"==typeof o)r[t]=e[o];else{for(var i=0;void 0===e[o[i]]&&i+1]+)(>|$)/g,(function(e,r,t){if(""===t)return e;var p=o[r];return Array.isArray(p)?"$"+p.join("$"):"number"==typeof p?"$"+p:""})))}if("function"==typeof p){var i=this;return e[Symbol.replace].call(this,t,(function(){var e=arguments;return"object"!=typeof e[e.length-1]&&(e=[].slice.call(e)).push(buildGroups(e,i)),p.apply(this,e)}))}return e[Symbol.replace].call(this,t,p)},_wrapRegExp.apply(this,arguments)}', { + globals: ["RegExp", "WeakMap", "Object", "Symbol", "Array"], + locals: { + _wrapRegExp: ["body.0.id", "body.0.body.body.4.argument.expressions.3.callee.object", "body.0.body.body.0.expression.left"] + }, + exportBindingAssignments: ["body.0.body.body.0.expression"], + exportName: "_wrapRegExp", + dependencies: { + setPrototypeOf: ["body.0.body.body.2.body.body.1.argument.expressions.1.callee"], + inherits: ["body.0.body.body.4.argument.expressions.0.callee"] + } + }), + writeOnlyError: helper("7.12.13", "function _writeOnlyError(r){throw new TypeError('\"'+r+'\" is write-only')}", { + globals: ["TypeError"], + locals: { + _writeOnlyError: ["body.0.id"] + }, + exportBindingAssignments: [], + exportName: "_writeOnlyError", + dependencies: {} + }) +}; +{ + Object.assign(helpers, { + AwaitValue: helper("7.0.0-beta.0", "function _AwaitValue(t){this.wrapped=t}", { + globals: [], + locals: { + _AwaitValue: ["body.0.id"] + }, + exportBindingAssignments: [], + exportName: "_AwaitValue", + dependencies: {} + }), + applyDecs: helper("7.17.8", 'function old_createMetadataMethodsForProperty(e,t,a,r){return{getMetadata:function(o){old_assertNotFinished(r,"getMetadata"),old_assertMetadataKey(o);var i=e[o];if(void 0!==i)if(1===t){var n=i.public;if(void 0!==n)return n[a]}else if(2===t){var l=i.private;if(void 0!==l)return l.get(a)}else if(Object.hasOwnProperty.call(i,"constructor"))return i.constructor},setMetadata:function(o,i){old_assertNotFinished(r,"setMetadata"),old_assertMetadataKey(o);var n=e[o];if(void 0===n&&(n=e[o]={}),1===t){var l=n.public;void 0===l&&(l=n.public={}),l[a]=i}else if(2===t){var s=n.priv;void 0===s&&(s=n.private=new Map),s.set(a,i)}else n.constructor=i}}}function old_convertMetadataMapToFinal(e,t){var a=e[Symbol.metadata||Symbol.for("Symbol.metadata")],r=Object.getOwnPropertySymbols(t);if(0!==r.length){for(var o=0;o=0;m--){var b;void 0!==(p=old_memberDec(h[m],r,c,l,s,o,i,n,f))&&(old_assertValidReturnValue(o,p),0===o?b=p:1===o?(b=old_getInit(p),v=p.get||f.get,y=p.set||f.set,f={get:v,set:y}):f=p,void 0!==b&&(void 0===d?d=b:"function"==typeof d?d=[d,b]:d.push(b)))}if(0===o||1===o){if(void 0===d)d=function(e,t){return t};else if("function"!=typeof d){var g=d;d=function(e,t){for(var a=t,r=0;r3,m=v>=5;if(m?(u=t,f=r,0!=(v-=5)&&(p=n=n||[])):(u=t.prototype,f=a,0!==v&&(p=i=i||[])),0!==v&&!h){var b=m?s:l,g=b.get(y)||0;if(!0===g||3===g&&4!==v||4===g&&3!==v)throw Error("Attempted to decorate a public method/accessor that has the same name as a previously decorated public method/accessor. This is not currently supported by the decorators plugin. Property name was: "+y);!g&&v>2?b.set(y,v):b.set(y,!0)}old_applyMemberDec(e,u,d,y,v,m,h,f,p)}}old_pushInitializers(e,i),old_pushInitializers(e,n)}function old_pushInitializers(e,t){t&&e.push((function(e){for(var a=0;a0){for(var o=[],i=t,n=t.name,l=r.length-1;l>=0;l--){var s={v:!1};try{var c=Object.assign({kind:"class",name:n,addInitializer:old_createAddInitializerMethod(o,s)},old_createMetadataMethodsForProperty(a,0,n,s)),d=r[l](i,c)}finally{s.v=!0}void 0!==d&&(old_assertValidReturnValue(10,d),i=d)}e.push(i,(function(){for(var e=0;e=0;v--){var g;void 0!==(f=memberDec(h[v],a,c,o,n,i,s,u))&&(assertValidReturnValue(n,f),0===n?g=f:1===n?(g=f.init,p=f.get||u.get,d=f.set||u.set,u={get:p,set:d}):u=f,void 0!==g&&(void 0===l?l=g:"function"==typeof l?l=[l,g]:l.push(g)))}if(0===n||1===n){if(void 0===l)l=function(e,t){return t};else if("function"!=typeof l){var y=l;l=function(e,t){for(var r=t,a=0;a3,h=f>=5;if(h?(l=t,0!=(f-=5)&&(u=n=n||[])):(l=t.prototype,0!==f&&(u=a=a||[])),0!==f&&!d){var v=h?s:i,g=v.get(p)||0;if(!0===g||3===g&&4!==f||4===g&&3!==f)throw Error("Attempted to decorate a public method/accessor that has the same name as a previously decorated public method/accessor. This is not currently supported by the decorators plugin. Property name was: "+p);!g&&f>2?v.set(p,f):v.set(p,!0)}applyMemberDec(e,l,c,p,f,h,d,u)}}pushInitializers(e,a),pushInitializers(e,n)}(a,e,t),function(e,t,r){if(r.length>0){for(var a=[],n=t,i=t.name,s=r.length-1;s>=0;s--){var o={v:!1};try{var c=r[s](n,{kind:"class",name:i,addInitializer:createAddInitializerMethod(a,o)})}finally{o.v=!0}void 0!==c&&(assertValidReturnValue(10,c),n=c)}e.push(n,(function(){for(var e=0;e=0;g--){var y;void 0!==(p=memberDec(v[g],n,c,s,a,i,o,f))&&(assertValidReturnValue(a,p),0===a?y=p:1===a?(y=p.init,d=p.get||f.get,h=p.set||f.set,f={get:d,set:h}):f=p,void 0!==y&&(void 0===l?l=y:"function"==typeof l?l=[l,y]:l.push(y)))}if(0===a||1===a){if(void 0===l)l=function(e,t){return t};else if("function"!=typeof l){var m=l;l=function(e,t){for(var r=t,n=0;n3,h=f>=5;if(h?(l=e,0!=(f-=5)&&(u=n=n||[])):(l=e.prototype,0!==f&&(u=r=r||[])),0!==f&&!d){var v=h?o:i,g=v.get(p)||0;if(!0===g||3===g&&4!==f||4===g&&3!==f)throw Error("Attempted to decorate a public method/accessor that has the same name as a previously decorated public method/accessor. This is not currently supported by the decorators plugin. Property name was: "+p);!g&&f>2?v.set(p,f):v.set(p,!0)}applyMemberDec(a,l,c,p,f,h,d,u)}}return pushInitializers(a,r),pushInitializers(a,n),a}function pushInitializers(e,t){t&&e.push((function(e){for(var r=0;r0){for(var r=[],n=e,a=e.name,i=t.length-1;i>=0;i--){var o={v:!1};try{var s=t[i](n,{kind:"class",name:a,addInitializer:createAddInitializerMethod(r,o)})}finally{o.v=!0}void 0!==s&&(assertValidReturnValue(10,s),n=s)}return[n,function(){for(var e=0;e=0;m--){var b;void 0!==(h=memberDec(g[m],n,u,o,a,i,s,p,c))&&(assertValidReturnValue(a,h),0===a?b=h:1===a?(b=h.init,v=h.get||p.get,y=h.set||p.set,p={get:v,set:y}):p=h,void 0!==b&&(void 0===l?l=b:"function"==typeof l?l=[l,b]:l.push(b)))}if(0===a||1===a){if(void 0===l)l=function(e,t){return t};else if("function"!=typeof l){var I=l;l=function(e,t){for(var r=t,n=0;n3,y=d>=5,g=r;if(y?(f=e,0!=(d-=5)&&(p=a=a||[]),v&&!i&&(i=function(t){return checkInRHS(t)===e}),g=i):(f=e.prototype,0!==d&&(p=n=n||[])),0!==d&&!v){var m=y?c:o,b=m.get(h)||0;if(!0===b||3===b&&4!==d||4===b&&3!==d)throw Error("Attempted to decorate a public method/accessor that has the same name as a previously decorated public method/accessor. This is not currently supported by the decorators plugin. Property name was: "+h);!b&&d>2?m.set(h,d):m.set(h,!0)}applyMemberDec(s,f,l,h,d,y,v,p,g)}}return pushInitializers(s,n),pushInitializers(s,a),s}function pushInitializers(e,t){t&&e.push((function(e){for(var r=0;r0){for(var r=[],n=e,a=e.name,i=t.length-1;i>=0;i--){var s={v:!1};try{var o=t[i](n,{kind:"class",name:a,addInitializer:createAddInitializerMethod(r,s)})}finally{s.v=!0}void 0!==o&&(assertValidReturnValue(10,o),n=o)}return[n,function(){for(var e=0;e=0;j-=r?2:1){var D=v[j],E=r?v[j-1]:void 0,I={},O={kind:["field","accessor","method","getter","setter","class"][o],name:n,metadata:a,addInitializer:function(e,t){if(e.v)throw Error("attempted to call addInitializer after decoration was finished");s(t,"An initializer","be",!0),c.push(t)}.bind(null,I)};try{if(b)(y=s(D.call(E,P,O),"class decorators","return"))&&(P=y);else{var k,F;O.static=l,O.private=f,f?2===o?k=function(e){return m(e),w.value}:(o<4&&(k=i(w,"get",m)),3!==o&&(F=i(w,"set",m))):(k=function(e){return e[n]},(o<2||4===o)&&(F=function(e,t){e[n]=t}));var N=O.access={has:f?h.bind():function(e){return n in e}};if(k&&(N.get=k),F&&(N.set=F),P=D.call(E,d?{get:w.get,set:w.set}:w[A],O),d){if("object"==typeof P&&P)(y=s(P.get,"accessor.get"))&&(w.get=y),(y=s(P.set,"accessor.set"))&&(w.set=y),(y=s(P.init,"accessor.init"))&&S.push(y);else if(void 0!==P)throw new TypeError("accessor decorators must return an object with get, set, or init properties or void 0")}else s(P,(p?"field":"method")+" decorators","return")&&(p?S.push(P):w[A]=P)}}finally{I.v=!0}}return(p||d)&&u.push((function(e,t){for(var r=S.length-1;r>=0;r--)t=S[r].call(e,t);return t})),p||b||(f?d?u.push(i(w,"get"),i(w,"set")):u.push(2===o?w[A]:i.call.bind(w[A])):Object.defineProperty(e,n,w)),P}function u(e,t){return Object.defineProperty(e,Symbol.metadata||Symbol.for("Symbol.metadata"),{configurable:!0,enumerable:!0,value:t})}if(arguments.length>=6)var l=a[Symbol.metadata||Symbol.for("Symbol.metadata")];var f=Object.create(null==l?null:l),p=function(e,t,r,n){var o,a,i=[],s=function(t){return checkInRHS(t)===e},u=new Map;function l(e){e&&i.push(c.bind(null,e))}for(var f=0;f3,y=16&d,v=!!(8&d),g=0==(d&=7),b=h+"/"+v;if(!g&&!m){var w=u.get(b);if(!0===w||3===w&&4!==d||4===w&&3!==d)throw Error("Attempted to decorate a public method/accessor that has the same name as a previously decorated public method/accessor. This is not currently supported by the decorators plugin. Property name was: "+h);u.set(b,!(d>2)||d)}applyDec(v?e:e.prototype,p,y,m?"#"+h:toPropertyKey(h),d,n,v?a=a||[]:o=o||[],i,v,m,g,1===d,v&&m?s:r)}}return l(o),l(a),i}(e,t,o,f);return r.length||u(e,f),{e:p,get c(){var t=[];return r.length&&[u(applyDec(e,[r],n,e.name,5,f,t),f),c.bind(null,t,e)]}}}', { + globals: ["TypeError", "Array", "Object", "Error", "Symbol", "Map"], + locals: { + applyDecs2305: ["body.0.id"] + }, + exportBindingAssignments: [], + exportName: "applyDecs2305", + dependencies: { + checkInRHS: ["body.0.body.body.6.declarations.1.init.callee.body.body.0.declarations.3.init.body.body.0.argument.left.callee"], + setFunctionName: ["body.0.body.body.3.body.body.2.consequent.body.2.expression.consequent.expressions.0.consequent.right.properties.0.value.callee", "body.0.body.body.3.body.body.2.consequent.body.2.expression.consequent.expressions.1.right.callee"], + toPropertyKey: ["body.0.body.body.6.declarations.1.init.callee.body.body.2.body.body.1.consequent.body.2.expression.arguments.3.alternate.callee"] + } + }), + classApplyDescriptorDestructureSet: helper("7.13.10", 'function _classApplyDescriptorDestructureSet(e,t){if(t.set)return"__destrObj"in t||(t.__destrObj={set value(r){t.set.call(e,r)}}),t.__destrObj;if(!t.writable)throw new TypeError("attempted to set read only private field");return t}', { + globals: ["TypeError"], + locals: { + _classApplyDescriptorDestructureSet: ["body.0.id"] + }, + exportBindingAssignments: [], + exportName: "_classApplyDescriptorDestructureSet", + dependencies: {} + }), + classApplyDescriptorGet: helper("7.13.10", "function _classApplyDescriptorGet(e,t){return t.get?t.get.call(e):t.value}", { + globals: [], + locals: { + _classApplyDescriptorGet: ["body.0.id"] + }, + exportBindingAssignments: [], + exportName: "_classApplyDescriptorGet", + dependencies: {} + }), + classApplyDescriptorSet: helper("7.13.10", 'function _classApplyDescriptorSet(e,t,l){if(t.set)t.set.call(e,l);else{if(!t.writable)throw new TypeError("attempted to set read only private field");t.value=l}}', { + globals: ["TypeError"], + locals: { + _classApplyDescriptorSet: ["body.0.id"] + }, + exportBindingAssignments: [], + exportName: "_classApplyDescriptorSet", + dependencies: {} + }), + classCheckPrivateStaticAccess: helper("7.13.10", "function _classCheckPrivateStaticAccess(s,a,r){return assertClassBrand(a,s,r)}", { + globals: [], + locals: { + _classCheckPrivateStaticAccess: ["body.0.id"] + }, + exportBindingAssignments: [], + exportName: "_classCheckPrivateStaticAccess", + dependencies: { + assertClassBrand: ["body.0.body.body.0.argument.callee"] + } + }), + classCheckPrivateStaticFieldDescriptor: helper("7.13.10", 'function _classCheckPrivateStaticFieldDescriptor(t,e){if(void 0===t)throw new TypeError("attempted to "+e+" private static field before its declaration")}', { + globals: ["TypeError"], + locals: { + _classCheckPrivateStaticFieldDescriptor: ["body.0.id"] + }, + exportBindingAssignments: [], + exportName: "_classCheckPrivateStaticFieldDescriptor", + dependencies: {} + }), + classExtractFieldDescriptor: helper("7.13.10", "function _classExtractFieldDescriptor(e,t){return classPrivateFieldGet2(t,e)}", { + globals: [], + locals: { + _classExtractFieldDescriptor: ["body.0.id"] + }, + exportBindingAssignments: [], + exportName: "_classExtractFieldDescriptor", + dependencies: { + classPrivateFieldGet2: ["body.0.body.body.0.argument.callee"] + } + }), + classPrivateFieldDestructureSet: helper("7.4.4", "function _classPrivateFieldDestructureSet(e,t){var r=classPrivateFieldGet2(t,e);return classApplyDescriptorDestructureSet(e,r)}", { + globals: [], + locals: { + _classPrivateFieldDestructureSet: ["body.0.id"] + }, + exportBindingAssignments: [], + exportName: "_classPrivateFieldDestructureSet", + dependencies: { + classApplyDescriptorDestructureSet: ["body.0.body.body.1.argument.callee"], + classPrivateFieldGet2: ["body.0.body.body.0.declarations.0.init.callee"] + } + }), + classPrivateFieldGet: helper("7.0.0-beta.0", "function _classPrivateFieldGet(e,t){var r=classPrivateFieldGet2(t,e);return classApplyDescriptorGet(e,r)}", { + globals: [], + locals: { + _classPrivateFieldGet: ["body.0.id"] + }, + exportBindingAssignments: [], + exportName: "_classPrivateFieldGet", + dependencies: { + classApplyDescriptorGet: ["body.0.body.body.1.argument.callee"], + classPrivateFieldGet2: ["body.0.body.body.0.declarations.0.init.callee"] + } + }), + classPrivateFieldSet: helper("7.0.0-beta.0", "function _classPrivateFieldSet(e,t,r){var s=classPrivateFieldGet2(t,e);return classApplyDescriptorSet(e,s,r),r}", { + globals: [], + locals: { + _classPrivateFieldSet: ["body.0.id"] + }, + exportBindingAssignments: [], + exportName: "_classPrivateFieldSet", + dependencies: { + classApplyDescriptorSet: ["body.0.body.body.1.argument.expressions.0.callee"], + classPrivateFieldGet2: ["body.0.body.body.0.declarations.0.init.callee"] + } + }), + classPrivateMethodGet: helper("7.1.6", "function _classPrivateMethodGet(s,a,r){return assertClassBrand(a,s),r}", { + globals: [], + locals: { + _classPrivateMethodGet: ["body.0.id"] + }, + exportBindingAssignments: [], + exportName: "_classPrivateMethodGet", + dependencies: { + assertClassBrand: ["body.0.body.body.0.argument.expressions.0.callee"] + } + }), + classPrivateMethodSet: helper("7.1.6", 'function _classPrivateMethodSet(){throw new TypeError("attempted to reassign private method")}', { + globals: ["TypeError"], + locals: { + _classPrivateMethodSet: ["body.0.id"] + }, + exportBindingAssignments: [], + exportName: "_classPrivateMethodSet", + dependencies: {} + }), + classStaticPrivateFieldDestructureSet: helper("7.13.10", 'function _classStaticPrivateFieldDestructureSet(t,r,s){return assertClassBrand(r,t),classCheckPrivateStaticFieldDescriptor(s,"set"),classApplyDescriptorDestructureSet(t,s)}', { + globals: [], + locals: { + _classStaticPrivateFieldDestructureSet: ["body.0.id"] + }, + exportBindingAssignments: [], + exportName: "_classStaticPrivateFieldDestructureSet", + dependencies: { + classApplyDescriptorDestructureSet: ["body.0.body.body.0.argument.expressions.2.callee"], + assertClassBrand: ["body.0.body.body.0.argument.expressions.0.callee"], + classCheckPrivateStaticFieldDescriptor: ["body.0.body.body.0.argument.expressions.1.callee"] + } + }), + classStaticPrivateFieldSpecGet: helper("7.0.2", 'function _classStaticPrivateFieldSpecGet(t,s,r){return assertClassBrand(s,t),classCheckPrivateStaticFieldDescriptor(r,"get"),classApplyDescriptorGet(t,r)}', { + globals: [], + locals: { + _classStaticPrivateFieldSpecGet: ["body.0.id"] + }, + exportBindingAssignments: [], + exportName: "_classStaticPrivateFieldSpecGet", + dependencies: { + classApplyDescriptorGet: ["body.0.body.body.0.argument.expressions.2.callee"], + assertClassBrand: ["body.0.body.body.0.argument.expressions.0.callee"], + classCheckPrivateStaticFieldDescriptor: ["body.0.body.body.0.argument.expressions.1.callee"] + } + }), + classStaticPrivateFieldSpecSet: helper("7.0.2", 'function _classStaticPrivateFieldSpecSet(s,t,r,e){return assertClassBrand(t,s),classCheckPrivateStaticFieldDescriptor(r,"set"),classApplyDescriptorSet(s,r,e),e}', { + globals: [], + locals: { + _classStaticPrivateFieldSpecSet: ["body.0.id"] + }, + exportBindingAssignments: [], + exportName: "_classStaticPrivateFieldSpecSet", + dependencies: { + classApplyDescriptorSet: ["body.0.body.body.0.argument.expressions.2.callee"], + assertClassBrand: ["body.0.body.body.0.argument.expressions.0.callee"], + classCheckPrivateStaticFieldDescriptor: ["body.0.body.body.0.argument.expressions.1.callee"] + } + }), + classStaticPrivateMethodSet: helper("7.3.2", 'function _classStaticPrivateMethodSet(){throw new TypeError("attempted to set read only static private field")}', { + globals: ["TypeError"], + locals: { + _classStaticPrivateMethodSet: ["body.0.id"] + }, + exportBindingAssignments: [], + exportName: "_classStaticPrivateMethodSet", + dependencies: {} + }), + defineEnumerableProperties: helper("7.0.0-beta.0", 'function _defineEnumerableProperties(e,r){for(var t in r){var n=r[t];n.configurable=n.enumerable=!0,"value"in n&&(n.writable=!0),Object.defineProperty(e,t,n)}if(Object.getOwnPropertySymbols)for(var a=Object.getOwnPropertySymbols(r),b=0;b0;)try{var o=r.pop(),p=o.d.call(o.v);if(o.a)return Promise.resolve(p).then(next,err)}catch(r){return err(r)}if(s)throw e}function err(r){return e=s?new dispose_SuppressedError(e,r):r,s=!0,next()}return next()}', { + globals: ["SuppressedError", "Error", "Object", "Promise"], + locals: { + dispose_SuppressedError: ["body.0.id", "body.0.body.body.0.argument.expressions.0.alternate.expressions.1.left.object", "body.0.body.body.0.argument.expressions.0.alternate.expressions.1.right.arguments.1.properties.0.value.properties.0.value", "body.0.body.body.0.argument.expressions.1.callee", "body.1.body.body.1.body.body.0.argument.expressions.0.right.consequent.callee", "body.0.body.body.0.argument.expressions.0.consequent.left", "body.0.body.body.0.argument.expressions.0.alternate.expressions.0.left"], + _dispose: ["body.1.id"] + }, + exportBindingAssignments: [], + exportName: "_dispose", + dependencies: {} + }), + objectSpread: helper("7.0.0-beta.0", 'function _objectSpread(e){for(var r=1;r { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports["default"] = void 0; +exports.get = get; +exports.getDependencies = getDependencies; +exports.list = void 0; +exports.minVersion = minVersion; +var _t = __nccwpck_require__(16535); +var _helpersGenerated = __nccwpck_require__(21214); +const { + cloneNode, + identifier +} = _t; +function deep(obj, path, value) { + try { + const parts = path.split("."); + let last = parts.shift(); + while (parts.length > 0) { + obj = obj[last]; + last = parts.shift(); + } + if (arguments.length > 2) { + obj[last] = value; + } else { + return obj[last]; + } + } catch (e) { + e.message += ` (when accessing ${path})`; + throw e; + } +} +function permuteHelperAST(ast, metadata, bindingName, localBindings, getDependency, adjustAst) { + const { + locals, + dependencies, + exportBindingAssignments, + exportName + } = metadata; + const bindings = new Set(localBindings || []); + if (bindingName) bindings.add(bindingName); + for (const [name, paths] of (Object.entries || (o => Object.keys(o).map(k => [k, o[k]])))(locals)) { + let newName = name; + if (bindingName && name === exportName) { + newName = bindingName; + } else { + while (bindings.has(newName)) newName = "_" + newName; + } + if (newName !== name) { + for (const path of paths) { + deep(ast, path, identifier(newName)); + } + } + } + for (const [name, paths] of (Object.entries || (o => Object.keys(o).map(k => [k, o[k]])))(dependencies)) { + const ref = typeof getDependency === "function" && getDependency(name) || identifier(name); + for (const path of paths) { + deep(ast, path, cloneNode(ref)); + } + } + adjustAst == null || adjustAst(ast, exportName, map => { + exportBindingAssignments.forEach(p => deep(ast, p, map(deep(ast, p)))); + }); +} +const helperData = Object.create(null); +function loadHelper(name) { + if (!helperData[name]) { + const helper = _helpersGenerated.default[name]; + if (!helper) { + throw Object.assign(new ReferenceError(`Unknown helper ${name}`), { + code: "BABEL_HELPER_UNKNOWN", + helper: name + }); + } + helperData[name] = { + minVersion: helper.minVersion, + build(getDependency, bindingName, localBindings, adjustAst) { + const ast = helper.ast(); + permuteHelperAST(ast, helper.metadata, bindingName, localBindings, getDependency, adjustAst); + return { + nodes: ast.body, + globals: helper.metadata.globals + }; + }, + getDependencies() { + return Object.keys(helper.metadata.dependencies); + } + }; + } + return helperData[name]; +} +function get(name, getDependency, bindingName, localBindings, adjustAst) { + { + if (typeof bindingName === "object") { + const id = bindingName; + if ((id == null ? void 0 : id.type) === "Identifier") { + bindingName = id.name; + } else { + bindingName = undefined; + } + } + } + return loadHelper(name).build(getDependency, bindingName, localBindings, adjustAst); +} +function minVersion(name) { + return loadHelper(name).minVersion; +} +function getDependencies(name) { + return loadHelper(name).getDependencies(); +} +{ + exports.ensure = name => { + loadHelper(name); + }; +} +const list = exports.list = Object.keys(_helpersGenerated.default).map(name => name.replace(/^_/, "")); +var _default = exports["default"] = get; + +//# sourceMappingURL=index.js.map + + +/***/ }), + +/***/ 5429: +/***/ ((__unused_webpack_module, exports) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +function _objectWithoutPropertiesLoose(r, e) { + if (null == r) return {}; + var t = {}; + for (var n in r) if ({}.hasOwnProperty.call(r, n)) { + if (-1 !== e.indexOf(n)) continue; + t[n] = r[n]; + } + return t; +} +class Position { + constructor(line, col, index) { + this.line = void 0; + this.column = void 0; + this.index = void 0; + this.line = line; + this.column = col; + this.index = index; + } +} +class SourceLocation { + constructor(start, end) { + this.start = void 0; + this.end = void 0; + this.filename = void 0; + this.identifierName = void 0; + this.start = start; + this.end = end; + } +} +function createPositionWithColumnOffset(position, columnOffset) { + const { + line, + column, + index + } = position; + return new Position(line, column + columnOffset, index + columnOffset); +} +const code = "BABEL_PARSER_SOURCETYPE_MODULE_REQUIRED"; +var ModuleErrors = { + ImportMetaOutsideModule: { + message: `import.meta may appear only with 'sourceType: "module"'`, + code + }, + ImportOutsideModule: { + message: `'import' and 'export' may appear only with 'sourceType: "module"'`, + code + } +}; +const NodeDescriptions = { + ArrayPattern: "array destructuring pattern", + AssignmentExpression: "assignment expression", + AssignmentPattern: "assignment expression", + ArrowFunctionExpression: "arrow function expression", + ConditionalExpression: "conditional expression", + CatchClause: "catch clause", + ForOfStatement: "for-of statement", + ForInStatement: "for-in statement", + ForStatement: "for-loop", + FormalParameters: "function parameter list", + Identifier: "identifier", + ImportSpecifier: "import specifier", + ImportDefaultSpecifier: "import default specifier", + ImportNamespaceSpecifier: "import namespace specifier", + ObjectPattern: "object destructuring pattern", + ParenthesizedExpression: "parenthesized expression", + RestElement: "rest element", + UpdateExpression: { + true: "prefix operation", + false: "postfix operation" + }, + VariableDeclarator: "variable declaration", + YieldExpression: "yield expression" +}; +const toNodeDescription = node => node.type === "UpdateExpression" ? NodeDescriptions.UpdateExpression[`${node.prefix}`] : NodeDescriptions[node.type]; +var StandardErrors = { + AccessorIsGenerator: ({ + kind + }) => `A ${kind}ter cannot be a generator.`, + ArgumentsInClass: "'arguments' is only allowed in functions and class methods.", + AsyncFunctionInSingleStatementContext: "Async functions can only be declared at the top level or inside a block.", + AwaitBindingIdentifier: "Can not use 'await' as identifier inside an async function.", + AwaitBindingIdentifierInStaticBlock: "Can not use 'await' as identifier inside a static block.", + AwaitExpressionFormalParameter: "'await' is not allowed in async function parameters.", + AwaitUsingNotInAsyncContext: "'await using' is only allowed within async functions and at the top levels of modules.", + AwaitNotInAsyncContext: "'await' is only allowed within async functions and at the top levels of modules.", + BadGetterArity: "A 'get' accessor must not have any formal parameters.", + BadSetterArity: "A 'set' accessor must have exactly one formal parameter.", + BadSetterRestParameter: "A 'set' accessor function argument must not be a rest parameter.", + ConstructorClassField: "Classes may not have a field named 'constructor'.", + ConstructorClassPrivateField: "Classes may not have a private field named '#constructor'.", + ConstructorIsAccessor: "Class constructor may not be an accessor.", + ConstructorIsAsync: "Constructor can't be an async function.", + ConstructorIsGenerator: "Constructor can't be a generator.", + DeclarationMissingInitializer: ({ + kind + }) => `Missing initializer in ${kind} declaration.`, + DecoratorArgumentsOutsideParentheses: "Decorator arguments must be moved inside parentheses: use '@(decorator(args))' instead of '@(decorator)(args)'.", + DecoratorBeforeExport: "Decorators must be placed *before* the 'export' keyword. Remove the 'decoratorsBeforeExport: true' option to use the 'export @decorator class {}' syntax.", + DecoratorsBeforeAfterExport: "Decorators can be placed *either* before or after the 'export' keyword, but not in both locations at the same time.", + DecoratorConstructor: "Decorators can't be used with a constructor. Did you mean '@dec class { ... }'?", + DecoratorExportClass: "Decorators must be placed *after* the 'export' keyword. Remove the 'decoratorsBeforeExport: false' option to use the '@decorator export class {}' syntax.", + DecoratorSemicolon: "Decorators must not be followed by a semicolon.", + DecoratorStaticBlock: "Decorators can't be used with a static block.", + DeferImportRequiresNamespace: 'Only `import defer * as x from "./module"` is valid.', + DeletePrivateField: "Deleting a private field is not allowed.", + DestructureNamedImport: "ES2015 named imports do not destructure. Use another statement for destructuring after the import.", + DuplicateConstructor: "Duplicate constructor in the same class.", + DuplicateDefaultExport: "Only one default export allowed per module.", + DuplicateExport: ({ + exportName + }) => `\`${exportName}\` has already been exported. Exported identifiers must be unique.`, + DuplicateProto: "Redefinition of __proto__ property.", + DuplicateRegExpFlags: "Duplicate regular expression flag.", + DynamicImportPhaseRequiresImportExpressions: ({ + phase + }) => `'import.${phase}(...)' can only be parsed when using the 'createImportExpressions' option.`, + ElementAfterRest: "Rest element must be last element.", + EscapedCharNotAnIdentifier: "Invalid Unicode escape.", + ExportBindingIsString: ({ + localName, + exportName + }) => `A string literal cannot be used as an exported binding without \`from\`.\n- Did you mean \`export { '${localName}' as '${exportName}' } from 'some-module'\`?`, + ExportDefaultFromAsIdentifier: "'from' is not allowed as an identifier after 'export default'.", + ForInOfLoopInitializer: ({ + type + }) => `'${type === "ForInStatement" ? "for-in" : "for-of"}' loop variable declaration may not have an initializer.`, + ForInUsing: "For-in loop may not start with 'using' declaration.", + ForOfAsync: "The left-hand side of a for-of loop may not be 'async'.", + ForOfLet: "The left-hand side of a for-of loop may not start with 'let'.", + GeneratorInSingleStatementContext: "Generators can only be declared at the top level or inside a block.", + IllegalBreakContinue: ({ + type + }) => `Unsyntactic ${type === "BreakStatement" ? "break" : "continue"}.`, + IllegalLanguageModeDirective: "Illegal 'use strict' directive in function with non-simple parameter list.", + IllegalReturn: "'return' outside of function.", + ImportAttributesUseAssert: "The `assert` keyword in import attributes is deprecated and it has been replaced by the `with` keyword. You can enable the `deprecatedImportAssert` parser plugin to suppress this error.", + ImportBindingIsString: ({ + importName + }) => `A string literal cannot be used as an imported binding.\n- Did you mean \`import { "${importName}" as foo }\`?`, + ImportCallArity: `\`import()\` requires exactly one or two arguments.`, + ImportCallNotNewExpression: "Cannot use new with import(...).", + ImportCallSpreadArgument: "`...` is not allowed in `import()`.", + ImportJSONBindingNotDefault: "A JSON module can only be imported with `default`.", + ImportReflectionHasAssertion: "`import module x` cannot have assertions.", + ImportReflectionNotBinding: 'Only `import module x from "./module"` is valid.', + IncompatibleRegExpUVFlags: "The 'u' and 'v' regular expression flags cannot be enabled at the same time.", + InvalidBigIntLiteral: "Invalid BigIntLiteral.", + InvalidCodePoint: "Code point out of bounds.", + InvalidCoverInitializedName: "Invalid shorthand property initializer.", + InvalidDecimal: "Invalid decimal.", + InvalidDigit: ({ + radix + }) => `Expected number in radix ${radix}.`, + InvalidEscapeSequence: "Bad character escape sequence.", + InvalidEscapeSequenceTemplate: "Invalid escape sequence in template.", + InvalidEscapedReservedWord: ({ + reservedWord + }) => `Escape sequence in keyword ${reservedWord}.`, + InvalidIdentifier: ({ + identifierName + }) => `Invalid identifier ${identifierName}.`, + InvalidLhs: ({ + ancestor + }) => `Invalid left-hand side in ${toNodeDescription(ancestor)}.`, + InvalidLhsBinding: ({ + ancestor + }) => `Binding invalid left-hand side in ${toNodeDescription(ancestor)}.`, + InvalidLhsOptionalChaining: ({ + ancestor + }) => `Invalid optional chaining in the left-hand side of ${toNodeDescription(ancestor)}.`, + InvalidNumber: "Invalid number.", + InvalidOrMissingExponent: "Floating-point numbers require a valid exponent after the 'e'.", + InvalidOrUnexpectedToken: ({ + unexpected + }) => `Unexpected character '${unexpected}'.`, + InvalidParenthesizedAssignment: "Invalid parenthesized assignment pattern.", + InvalidPrivateFieldResolution: ({ + identifierName + }) => `Private name #${identifierName} is not defined.`, + InvalidPropertyBindingPattern: "Binding member expression.", + InvalidRecordProperty: "Only properties and spread elements are allowed in record definitions.", + InvalidRestAssignmentPattern: "Invalid rest operator's argument.", + LabelRedeclaration: ({ + labelName + }) => `Label '${labelName}' is already declared.`, + LetInLexicalBinding: "'let' is disallowed as a lexically bound name.", + LineTerminatorBeforeArrow: "No line break is allowed before '=>'.", + MalformedRegExpFlags: "Invalid regular expression flag.", + MissingClassName: "A class name is required.", + MissingEqInAssignment: "Only '=' operator can be used for specifying default value.", + MissingSemicolon: "Missing semicolon.", + MissingPlugin: ({ + missingPlugin + }) => `This experimental syntax requires enabling the parser plugin: ${missingPlugin.map(name => JSON.stringify(name)).join(", ")}.`, + MissingOneOfPlugins: ({ + missingPlugin + }) => `This experimental syntax requires enabling one of the following parser plugin(s): ${missingPlugin.map(name => JSON.stringify(name)).join(", ")}.`, + MissingUnicodeEscape: "Expecting Unicode escape sequence \\uXXXX.", + MixingCoalesceWithLogical: "Nullish coalescing operator(??) requires parens when mixing with logical operators.", + ModuleAttributeDifferentFromType: "The only accepted module attribute is `type`.", + ModuleAttributeInvalidValue: "Only string literals are allowed as module attribute values.", + ModuleAttributesWithDuplicateKeys: ({ + key + }) => `Duplicate key "${key}" is not allowed in module attributes.`, + ModuleExportNameHasLoneSurrogate: ({ + surrogateCharCode + }) => `An export name cannot include a lone surrogate, found '\\u${surrogateCharCode.toString(16)}'.`, + ModuleExportUndefined: ({ + localName + }) => `Export '${localName}' is not defined.`, + MultipleDefaultsInSwitch: "Multiple default clauses.", + NewlineAfterThrow: "Illegal newline after throw.", + NoCatchOrFinally: "Missing catch or finally clause.", + NumberIdentifier: "Identifier directly after number.", + NumericSeparatorInEscapeSequence: "Numeric separators are not allowed inside unicode escape sequences or hex escape sequences.", + ObsoleteAwaitStar: "'await*' has been removed from the async functions proposal. Use Promise.all() instead.", + OptionalChainingNoNew: "Constructors in/after an Optional Chain are not allowed.", + OptionalChainingNoTemplate: "Tagged Template Literals are not allowed in optionalChain.", + OverrideOnConstructor: "'override' modifier cannot appear on a constructor declaration.", + ParamDupe: "Argument name clash.", + PatternHasAccessor: "Object pattern can't contain getter or setter.", + PatternHasMethod: "Object pattern can't contain methods.", + PrivateInExpectedIn: ({ + identifierName + }) => `Private names are only allowed in property accesses (\`obj.#${identifierName}\`) or in \`in\` expressions (\`#${identifierName} in obj\`).`, + PrivateNameRedeclaration: ({ + identifierName + }) => `Duplicate private name #${identifierName}.`, + RecordExpressionBarIncorrectEndSyntaxType: "Record expressions ending with '|}' are only allowed when the 'syntaxType' option of the 'recordAndTuple' plugin is set to 'bar'.", + RecordExpressionBarIncorrectStartSyntaxType: "Record expressions starting with '{|' are only allowed when the 'syntaxType' option of the 'recordAndTuple' plugin is set to 'bar'.", + RecordExpressionHashIncorrectStartSyntaxType: "Record expressions starting with '#{' are only allowed when the 'syntaxType' option of the 'recordAndTuple' plugin is set to 'hash'.", + RecordNoProto: "'__proto__' is not allowed in Record expressions.", + RestTrailingComma: "Unexpected trailing comma after rest element.", + SloppyFunction: "In non-strict mode code, functions can only be declared at top level or inside a block.", + SloppyFunctionAnnexB: "In non-strict mode code, functions can only be declared at top level, inside a block, or as the body of an if statement.", + SourcePhaseImportRequiresDefault: 'Only `import source x from "./module"` is valid.', + StaticPrototype: "Classes may not have static property named prototype.", + SuperNotAllowed: "`super()` is only valid inside a class constructor of a subclass. Maybe a typo in the method name ('constructor') or not extending another class?", + SuperPrivateField: "Private fields can't be accessed on super.", + TrailingDecorator: "Decorators must be attached to a class element.", + TupleExpressionBarIncorrectEndSyntaxType: "Tuple expressions ending with '|]' are only allowed when the 'syntaxType' option of the 'recordAndTuple' plugin is set to 'bar'.", + TupleExpressionBarIncorrectStartSyntaxType: "Tuple expressions starting with '[|' are only allowed when the 'syntaxType' option of the 'recordAndTuple' plugin is set to 'bar'.", + TupleExpressionHashIncorrectStartSyntaxType: "Tuple expressions starting with '#[' are only allowed when the 'syntaxType' option of the 'recordAndTuple' plugin is set to 'hash'.", + UnexpectedArgumentPlaceholder: "Unexpected argument placeholder.", + UnexpectedAwaitAfterPipelineBody: 'Unexpected "await" after pipeline body; await must have parentheses in minimal proposal.', + UnexpectedDigitAfterHash: "Unexpected digit after hash token.", + UnexpectedImportExport: "'import' and 'export' may only appear at the top level.", + UnexpectedKeyword: ({ + keyword + }) => `Unexpected keyword '${keyword}'.`, + UnexpectedLeadingDecorator: "Leading decorators must be attached to a class declaration.", + UnexpectedLexicalDeclaration: "Lexical declaration cannot appear in a single-statement context.", + UnexpectedNewTarget: "`new.target` can only be used in functions or class properties.", + UnexpectedNumericSeparator: "A numeric separator is only allowed between two digits.", + UnexpectedPrivateField: "Unexpected private name.", + UnexpectedReservedWord: ({ + reservedWord + }) => `Unexpected reserved word '${reservedWord}'.`, + UnexpectedSuper: "'super' is only allowed in object methods and classes.", + UnexpectedToken: ({ + expected, + unexpected + }) => `Unexpected token${unexpected ? ` '${unexpected}'.` : ""}${expected ? `, expected "${expected}"` : ""}`, + UnexpectedTokenUnaryExponentiation: "Illegal expression. Wrap left hand side or entire exponentiation in parentheses.", + UnexpectedUsingDeclaration: "Using declaration cannot appear in the top level when source type is `script`.", + UnsupportedBind: "Binding should be performed on object property.", + UnsupportedDecoratorExport: "A decorated export must export a class declaration.", + UnsupportedDefaultExport: "Only expressions, functions or classes are allowed as the `default` export.", + UnsupportedImport: "`import` can only be used in `import()` or `import.meta`.", + UnsupportedMetaProperty: ({ + target, + onlyValidPropertyName + }) => `The only valid meta property for ${target} is ${target}.${onlyValidPropertyName}.`, + UnsupportedParameterDecorator: "Decorators cannot be used to decorate parameters.", + UnsupportedPropertyDecorator: "Decorators cannot be used to decorate object literal properties.", + UnsupportedSuper: "'super' can only be used with function calls (i.e. super()) or in property accesses (i.e. super.prop or super[prop]).", + UnterminatedComment: "Unterminated comment.", + UnterminatedRegExp: "Unterminated regular expression.", + UnterminatedString: "Unterminated string constant.", + UnterminatedTemplate: "Unterminated template.", + UsingDeclarationExport: "Using declaration cannot be exported.", + UsingDeclarationHasBindingPattern: "Using declaration cannot have destructuring patterns.", + VarRedeclaration: ({ + identifierName + }) => `Identifier '${identifierName}' has already been declared.`, + YieldBindingIdentifier: "Can not use 'yield' as identifier inside a generator.", + YieldInParameter: "Yield expression is not allowed in formal parameters.", + YieldNotInGeneratorFunction: "'yield' is only allowed within generator functions.", + ZeroDigitNumericSeparator: "Numeric separator can not be used after leading 0." +}; +var StrictModeErrors = { + StrictDelete: "Deleting local variable in strict mode.", + StrictEvalArguments: ({ + referenceName + }) => `Assigning to '${referenceName}' in strict mode.`, + StrictEvalArgumentsBinding: ({ + bindingName + }) => `Binding '${bindingName}' in strict mode.`, + StrictFunction: "In strict mode code, functions can only be declared at top level or inside a block.", + StrictNumericEscape: "The only valid numeric escape in strict mode is '\\0'.", + StrictOctalLiteral: "Legacy octal literals are not allowed in strict mode.", + StrictWith: "'with' in strict mode." +}; +const UnparenthesizedPipeBodyDescriptions = new Set(["ArrowFunctionExpression", "AssignmentExpression", "ConditionalExpression", "YieldExpression"]); +var PipelineOperatorErrors = Object.assign({ + PipeBodyIsTighter: "Unexpected yield after pipeline body; any yield expression acting as Hack-style pipe body must be parenthesized due to its loose operator precedence.", + PipeTopicRequiresHackPipes: 'Topic reference is used, but the pipelineOperator plugin was not passed a "proposal": "hack" or "smart" option.', + PipeTopicUnbound: "Topic reference is unbound; it must be inside a pipe body.", + PipeTopicUnconfiguredToken: ({ + token + }) => `Invalid topic token ${token}. In order to use ${token} as a topic reference, the pipelineOperator plugin must be configured with { "proposal": "hack", "topicToken": "${token}" }.`, + PipeTopicUnused: "Hack-style pipe body does not contain a topic reference; Hack-style pipes must use topic at least once.", + PipeUnparenthesizedBody: ({ + type + }) => `Hack-style pipe body cannot be an unparenthesized ${toNodeDescription({ + type + })}; please wrap it in parentheses.` +}, { + PipelineBodyNoArrow: 'Unexpected arrow "=>" after pipeline body; arrow function in pipeline body must be parenthesized.', + PipelineBodySequenceExpression: "Pipeline body may not be a comma-separated sequence expression.", + PipelineHeadSequenceExpression: "Pipeline head should not be a comma-separated sequence expression.", + PipelineTopicUnused: "Pipeline is in topic style but does not use topic reference.", + PrimaryTopicNotAllowed: "Topic reference was used in a lexical context without topic binding.", + PrimaryTopicRequiresSmartPipeline: 'Topic reference is used, but the pipelineOperator plugin was not passed a "proposal": "hack" or "smart" option.' +}); +const _excluded = ["message"]; +function defineHidden(obj, key, value) { + Object.defineProperty(obj, key, { + enumerable: false, + configurable: true, + value + }); +} +function toParseErrorConstructor({ + toMessage, + code, + reasonCode, + syntaxPlugin +}) { + const hasMissingPlugin = reasonCode === "MissingPlugin" || reasonCode === "MissingOneOfPlugins"; + { + const oldReasonCodes = { + AccessorCannotDeclareThisParameter: "AccesorCannotDeclareThisParameter", + AccessorCannotHaveTypeParameters: "AccesorCannotHaveTypeParameters", + ConstInitializerMustBeStringOrNumericLiteralOrLiteralEnumReference: "ConstInitiailizerMustBeStringOrNumericLiteralOrLiteralEnumReference", + SetAccessorCannotHaveOptionalParameter: "SetAccesorCannotHaveOptionalParameter", + SetAccessorCannotHaveRestParameter: "SetAccesorCannotHaveRestParameter", + SetAccessorCannotHaveReturnType: "SetAccesorCannotHaveReturnType" + }; + if (oldReasonCodes[reasonCode]) { + reasonCode = oldReasonCodes[reasonCode]; + } + } + return function constructor(loc, details) { + const error = new SyntaxError(); + error.code = code; + error.reasonCode = reasonCode; + error.loc = loc; + error.pos = loc.index; + error.syntaxPlugin = syntaxPlugin; + if (hasMissingPlugin) { + error.missingPlugin = details.missingPlugin; + } + defineHidden(error, "clone", function clone(overrides = {}) { + var _overrides$loc; + const { + line, + column, + index + } = (_overrides$loc = overrides.loc) != null ? _overrides$loc : loc; + return constructor(new Position(line, column, index), Object.assign({}, details, overrides.details)); + }); + defineHidden(error, "details", details); + Object.defineProperty(error, "message", { + configurable: true, + get() { + const message = `${toMessage(details)} (${loc.line}:${loc.column})`; + this.message = message; + return message; + }, + set(value) { + Object.defineProperty(this, "message", { + value, + writable: true + }); + } + }); + return error; + }; +} +function ParseErrorEnum(argument, syntaxPlugin) { + if (Array.isArray(argument)) { + return parseErrorTemplates => ParseErrorEnum(parseErrorTemplates, argument[0]); + } + const ParseErrorConstructors = {}; + for (const reasonCode of Object.keys(argument)) { + const template = argument[reasonCode]; + const _ref = typeof template === "string" ? { + message: () => template + } : typeof template === "function" ? { + message: template + } : template, + { + message + } = _ref, + rest = _objectWithoutPropertiesLoose(_ref, _excluded); + const toMessage = typeof message === "string" ? () => message : message; + ParseErrorConstructors[reasonCode] = toParseErrorConstructor(Object.assign({ + code: "BABEL_PARSER_SYNTAX_ERROR", + reasonCode, + toMessage + }, syntaxPlugin ? { + syntaxPlugin + } : {}, rest)); + } + return ParseErrorConstructors; +} +const Errors = Object.assign({}, ParseErrorEnum(ModuleErrors), ParseErrorEnum(StandardErrors), ParseErrorEnum(StrictModeErrors), ParseErrorEnum`pipelineOperator`(PipelineOperatorErrors)); +function createDefaultOptions() { + return { + sourceType: "script", + sourceFilename: undefined, + startIndex: 0, + startColumn: 0, + startLine: 1, + allowAwaitOutsideFunction: false, + allowReturnOutsideFunction: false, + allowNewTargetOutsideFunction: false, + allowImportExportEverywhere: false, + allowSuperOutsideMethod: false, + allowUndeclaredExports: false, + allowYieldOutsideFunction: false, + plugins: [], + strictMode: null, + ranges: false, + tokens: false, + createImportExpressions: false, + createParenthesizedExpressions: false, + errorRecovery: false, + attachComment: true, + annexB: true + }; +} +function getOptions(opts) { + const options = createDefaultOptions(); + if (opts == null) { + return options; + } + if (opts.annexB != null && opts.annexB !== false) { + throw new Error("The `annexB` option can only be set to `false`."); + } + for (const key of Object.keys(options)) { + if (opts[key] != null) options[key] = opts[key]; + } + if (options.startLine === 1) { + if (opts.startIndex == null && options.startColumn > 0) { + options.startIndex = options.startColumn; + } else if (opts.startColumn == null && options.startIndex > 0) { + options.startColumn = options.startIndex; + } + } else if (opts.startColumn == null || opts.startIndex == null) { + if (opts.startIndex != null) { + throw new Error("With a `startLine > 1` you must also specify `startIndex` and `startColumn`."); + } + } + return options; +} +const { + defineProperty +} = Object; +const toUnenumerable = (object, key) => { + if (object) { + defineProperty(object, key, { + enumerable: false, + value: object[key] + }); + } +}; +function toESTreeLocation(node) { + toUnenumerable(node.loc.start, "index"); + toUnenumerable(node.loc.end, "index"); + return node; +} +var estree = superClass => class ESTreeParserMixin extends superClass { + parse() { + const file = toESTreeLocation(super.parse()); + if (this.optionFlags & 256) { + file.tokens = file.tokens.map(toESTreeLocation); + } + return file; + } + parseRegExpLiteral({ + pattern, + flags + }) { + let regex = null; + try { + regex = new RegExp(pattern, flags); + } catch (_) {} + const node = this.estreeParseLiteral(regex); + node.regex = { + pattern, + flags + }; + return node; + } + parseBigIntLiteral(value) { + let bigInt; + try { + bigInt = BigInt(value); + } catch (_unused) { + bigInt = null; + } + const node = this.estreeParseLiteral(bigInt); + node.bigint = String(node.value || value); + return node; + } + parseDecimalLiteral(value) { + const decimal = null; + const node = this.estreeParseLiteral(decimal); + node.decimal = String(node.value || value); + return node; + } + estreeParseLiteral(value) { + return this.parseLiteral(value, "Literal"); + } + parseStringLiteral(value) { + return this.estreeParseLiteral(value); + } + parseNumericLiteral(value) { + return this.estreeParseLiteral(value); + } + parseNullLiteral() { + return this.estreeParseLiteral(null); + } + parseBooleanLiteral(value) { + return this.estreeParseLiteral(value); + } + estreeParseChainExpression(node, endLoc) { + const chain = this.startNodeAtNode(node); + chain.expression = node; + return this.finishNodeAt(chain, "ChainExpression", endLoc); + } + directiveToStmt(directive) { + const expression = directive.value; + delete directive.value; + this.castNodeTo(expression, "Literal"); + expression.raw = expression.extra.raw; + expression.value = expression.extra.expressionValue; + const stmt = this.castNodeTo(directive, "ExpressionStatement"); + stmt.expression = expression; + stmt.directive = expression.extra.rawValue; + delete expression.extra; + return stmt; + } + fillOptionalPropertiesForTSESLint(node) {} + cloneEstreeStringLiteral(node) { + const { + start, + end, + loc, + range, + raw, + value + } = node; + const cloned = Object.create(node.constructor.prototype); + cloned.type = "Literal"; + cloned.start = start; + cloned.end = end; + cloned.loc = loc; + cloned.range = range; + cloned.raw = raw; + cloned.value = value; + return cloned; + } + initFunction(node, isAsync) { + super.initFunction(node, isAsync); + node.expression = false; + } + checkDeclaration(node) { + if (node != null && this.isObjectProperty(node)) { + this.checkDeclaration(node.value); + } else { + super.checkDeclaration(node); + } + } + getObjectOrClassMethodParams(method) { + return method.value.params; + } + isValidDirective(stmt) { + var _stmt$expression$extr; + return stmt.type === "ExpressionStatement" && stmt.expression.type === "Literal" && typeof stmt.expression.value === "string" && !((_stmt$expression$extr = stmt.expression.extra) != null && _stmt$expression$extr.parenthesized); + } + parseBlockBody(node, allowDirectives, topLevel, end, afterBlockParse) { + super.parseBlockBody(node, allowDirectives, topLevel, end, afterBlockParse); + const directiveStatements = node.directives.map(d => this.directiveToStmt(d)); + node.body = directiveStatements.concat(node.body); + delete node.directives; + } + parsePrivateName() { + const node = super.parsePrivateName(); + { + if (!this.getPluginOption("estree", "classFeatures")) { + return node; + } + } + return this.convertPrivateNameToPrivateIdentifier(node); + } + convertPrivateNameToPrivateIdentifier(node) { + const name = super.getPrivateNameSV(node); + node = node; + delete node.id; + node.name = name; + return this.castNodeTo(node, "PrivateIdentifier"); + } + isPrivateName(node) { + { + if (!this.getPluginOption("estree", "classFeatures")) { + return super.isPrivateName(node); + } + } + return node.type === "PrivateIdentifier"; + } + getPrivateNameSV(node) { + { + if (!this.getPluginOption("estree", "classFeatures")) { + return super.getPrivateNameSV(node); + } + } + return node.name; + } + parseLiteral(value, type) { + const node = super.parseLiteral(value, type); + node.raw = node.extra.raw; + delete node.extra; + return node; + } + parseFunctionBody(node, allowExpression, isMethod = false) { + super.parseFunctionBody(node, allowExpression, isMethod); + node.expression = node.body.type !== "BlockStatement"; + } + parseMethod(node, isGenerator, isAsync, isConstructor, allowDirectSuper, type, inClassScope = false) { + let funcNode = this.startNode(); + funcNode.kind = node.kind; + funcNode = super.parseMethod(funcNode, isGenerator, isAsync, isConstructor, allowDirectSuper, type, inClassScope); + delete funcNode.kind; + const { + typeParameters + } = node; + if (typeParameters) { + delete node.typeParameters; + funcNode.typeParameters = typeParameters; + this.resetStartLocationFromNode(funcNode, typeParameters); + } + const valueNode = this.castNodeTo(funcNode, "FunctionExpression"); + node.value = valueNode; + if (type === "ClassPrivateMethod") { + node.computed = false; + } + if (type === "ObjectMethod") { + if (node.kind === "method") { + node.kind = "init"; + } + node.shorthand = false; + return this.finishNode(node, "Property"); + } else { + return this.finishNode(node, "MethodDefinition"); + } + } + nameIsConstructor(key) { + if (key.type === "Literal") return key.value === "constructor"; + return super.nameIsConstructor(key); + } + parseClassProperty(...args) { + const propertyNode = super.parseClassProperty(...args); + { + if (!this.getPluginOption("estree", "classFeatures")) { + return propertyNode; + } + } + { + this.castNodeTo(propertyNode, "PropertyDefinition"); + } + return propertyNode; + } + parseClassPrivateProperty(...args) { + const propertyNode = super.parseClassPrivateProperty(...args); + { + if (!this.getPluginOption("estree", "classFeatures")) { + return propertyNode; + } + } + { + this.castNodeTo(propertyNode, "PropertyDefinition"); + } + propertyNode.computed = false; + return propertyNode; + } + parseClassAccessorProperty(node) { + const accessorPropertyNode = super.parseClassAccessorProperty(node); + { + if (!this.getPluginOption("estree", "classFeatures")) { + return accessorPropertyNode; + } + } + if (accessorPropertyNode.abstract && this.hasPlugin("typescript")) { + delete accessorPropertyNode.abstract; + this.castNodeTo(accessorPropertyNode, "TSAbstractAccessorProperty"); + } else { + this.castNodeTo(accessorPropertyNode, "AccessorProperty"); + } + return accessorPropertyNode; + } + parseObjectProperty(prop, startLoc, isPattern, refExpressionErrors) { + const node = super.parseObjectProperty(prop, startLoc, isPattern, refExpressionErrors); + if (node) { + node.kind = "init"; + this.castNodeTo(node, "Property"); + } + return node; + } + finishObjectProperty(node) { + node.kind = "init"; + return this.finishNode(node, "Property"); + } + isValidLVal(type, isUnparenthesizedInAssign, binding) { + return type === "Property" ? "value" : super.isValidLVal(type, isUnparenthesizedInAssign, binding); + } + isAssignable(node, isBinding) { + if (node != null && this.isObjectProperty(node)) { + return this.isAssignable(node.value, isBinding); + } + return super.isAssignable(node, isBinding); + } + toAssignable(node, isLHS = false) { + if (node != null && this.isObjectProperty(node)) { + const { + key, + value + } = node; + if (this.isPrivateName(key)) { + this.classScope.usePrivateName(this.getPrivateNameSV(key), key.loc.start); + } + this.toAssignable(value, isLHS); + } else { + super.toAssignable(node, isLHS); + } + } + toAssignableObjectExpressionProp(prop, isLast, isLHS) { + if (prop.type === "Property" && (prop.kind === "get" || prop.kind === "set")) { + this.raise(Errors.PatternHasAccessor, prop.key); + } else if (prop.type === "Property" && prop.method) { + this.raise(Errors.PatternHasMethod, prop.key); + } else { + super.toAssignableObjectExpressionProp(prop, isLast, isLHS); + } + } + finishCallExpression(unfinished, optional) { + const node = super.finishCallExpression(unfinished, optional); + if (node.callee.type === "Import") { + var _ref, _ref2; + this.castNodeTo(node, "ImportExpression"); + node.source = node.arguments[0]; + node.options = (_ref = node.arguments[1]) != null ? _ref : null; + node.attributes = (_ref2 = node.arguments[1]) != null ? _ref2 : null; + delete node.arguments; + delete node.callee; + } else if (node.type === "OptionalCallExpression") { + this.castNodeTo(node, "CallExpression"); + } else { + node.optional = false; + } + return node; + } + toReferencedArguments(node) { + if (node.type === "ImportExpression") { + return; + } + super.toReferencedArguments(node); + } + parseExport(unfinished, decorators) { + const exportStartLoc = this.state.lastTokStartLoc; + const node = super.parseExport(unfinished, decorators); + switch (node.type) { + case "ExportAllDeclaration": + node.exported = null; + break; + case "ExportNamedDeclaration": + if (node.specifiers.length === 1 && node.specifiers[0].type === "ExportNamespaceSpecifier") { + this.castNodeTo(node, "ExportAllDeclaration"); + node.exported = node.specifiers[0].exported; + delete node.specifiers; + } + case "ExportDefaultDeclaration": + { + var _declaration$decorato; + const { + declaration + } = node; + if ((declaration == null ? void 0 : declaration.type) === "ClassDeclaration" && ((_declaration$decorato = declaration.decorators) == null ? void 0 : _declaration$decorato.length) > 0 && declaration.start === node.start) { + this.resetStartLocation(node, exportStartLoc); + } + } + break; + } + return node; + } + stopParseSubscript(base, state) { + const node = super.stopParseSubscript(base, state); + if (state.optionalChainMember) { + return this.estreeParseChainExpression(node, base.loc.end); + } + return node; + } + parseMember(base, startLoc, state, computed, optional) { + const node = super.parseMember(base, startLoc, state, computed, optional); + if (node.type === "OptionalMemberExpression") { + this.castNodeTo(node, "MemberExpression"); + } else { + node.optional = false; + } + return node; + } + isOptionalMemberExpression(node) { + if (node.type === "ChainExpression") { + return node.expression.type === "MemberExpression"; + } + return super.isOptionalMemberExpression(node); + } + hasPropertyAsPrivateName(node) { + if (node.type === "ChainExpression") { + node = node.expression; + } + return super.hasPropertyAsPrivateName(node); + } + isObjectProperty(node) { + return node.type === "Property" && node.kind === "init" && !node.method; + } + isObjectMethod(node) { + return node.type === "Property" && (node.method || node.kind === "get" || node.kind === "set"); + } + castNodeTo(node, type) { + const result = super.castNodeTo(node, type); + this.fillOptionalPropertiesForTSESLint(result); + return result; + } + cloneIdentifier(node) { + const cloned = super.cloneIdentifier(node); + this.fillOptionalPropertiesForTSESLint(cloned); + return cloned; + } + cloneStringLiteral(node) { + if (node.type === "Literal") { + return this.cloneEstreeStringLiteral(node); + } + return super.cloneStringLiteral(node); + } + finishNodeAt(node, type, endLoc) { + return toESTreeLocation(super.finishNodeAt(node, type, endLoc)); + } + finishNode(node, type) { + const result = super.finishNode(node, type); + this.fillOptionalPropertiesForTSESLint(result); + return result; + } + resetStartLocation(node, startLoc) { + super.resetStartLocation(node, startLoc); + toESTreeLocation(node); + } + resetEndLocation(node, endLoc = this.state.lastTokEndLoc) { + super.resetEndLocation(node, endLoc); + toESTreeLocation(node); + } +}; +class TokContext { + constructor(token, preserveSpace) { + this.token = void 0; + this.preserveSpace = void 0; + this.token = token; + this.preserveSpace = !!preserveSpace; + } +} +const types = { + brace: new TokContext("{"), + j_oTag: new TokContext("...", true) +}; +{ + types.template = new TokContext("`", true); +} +const beforeExpr = true; +const startsExpr = true; +const isLoop = true; +const isAssign = true; +const prefix = true; +const postfix = true; +class ExportedTokenType { + constructor(label, conf = {}) { + this.label = void 0; + this.keyword = void 0; + this.beforeExpr = void 0; + this.startsExpr = void 0; + this.rightAssociative = void 0; + this.isLoop = void 0; + this.isAssign = void 0; + this.prefix = void 0; + this.postfix = void 0; + this.binop = void 0; + this.label = label; + this.keyword = conf.keyword; + this.beforeExpr = !!conf.beforeExpr; + this.startsExpr = !!conf.startsExpr; + this.rightAssociative = !!conf.rightAssociative; + this.isLoop = !!conf.isLoop; + this.isAssign = !!conf.isAssign; + this.prefix = !!conf.prefix; + this.postfix = !!conf.postfix; + this.binop = conf.binop != null ? conf.binop : null; + { + this.updateContext = null; + } + } +} +const keywords$1 = new Map(); +function createKeyword(name, options = {}) { + options.keyword = name; + const token = createToken(name, options); + keywords$1.set(name, token); + return token; +} +function createBinop(name, binop) { + return createToken(name, { + beforeExpr, + binop + }); +} +let tokenTypeCounter = -1; +const tokenTypes = []; +const tokenLabels = []; +const tokenBinops = []; +const tokenBeforeExprs = []; +const tokenStartsExprs = []; +const tokenPrefixes = []; +function createToken(name, options = {}) { + var _options$binop, _options$beforeExpr, _options$startsExpr, _options$prefix; + ++tokenTypeCounter; + tokenLabels.push(name); + tokenBinops.push((_options$binop = options.binop) != null ? _options$binop : -1); + tokenBeforeExprs.push((_options$beforeExpr = options.beforeExpr) != null ? _options$beforeExpr : false); + tokenStartsExprs.push((_options$startsExpr = options.startsExpr) != null ? _options$startsExpr : false); + tokenPrefixes.push((_options$prefix = options.prefix) != null ? _options$prefix : false); + tokenTypes.push(new ExportedTokenType(name, options)); + return tokenTypeCounter; +} +function createKeywordLike(name, options = {}) { + var _options$binop2, _options$beforeExpr2, _options$startsExpr2, _options$prefix2; + ++tokenTypeCounter; + keywords$1.set(name, tokenTypeCounter); + tokenLabels.push(name); + tokenBinops.push((_options$binop2 = options.binop) != null ? _options$binop2 : -1); + tokenBeforeExprs.push((_options$beforeExpr2 = options.beforeExpr) != null ? _options$beforeExpr2 : false); + tokenStartsExprs.push((_options$startsExpr2 = options.startsExpr) != null ? _options$startsExpr2 : false); + tokenPrefixes.push((_options$prefix2 = options.prefix) != null ? _options$prefix2 : false); + tokenTypes.push(new ExportedTokenType("name", options)); + return tokenTypeCounter; +} +const tt = { + bracketL: createToken("[", { + beforeExpr, + startsExpr + }), + bracketHashL: createToken("#[", { + beforeExpr, + startsExpr + }), + bracketBarL: createToken("[|", { + beforeExpr, + startsExpr + }), + bracketR: createToken("]"), + bracketBarR: createToken("|]"), + braceL: createToken("{", { + beforeExpr, + startsExpr + }), + braceBarL: createToken("{|", { + beforeExpr, + startsExpr + }), + braceHashL: createToken("#{", { + beforeExpr, + startsExpr + }), + braceR: createToken("}"), + braceBarR: createToken("|}"), + parenL: createToken("(", { + beforeExpr, + startsExpr + }), + parenR: createToken(")"), + comma: createToken(",", { + beforeExpr + }), + semi: createToken(";", { + beforeExpr + }), + colon: createToken(":", { + beforeExpr + }), + doubleColon: createToken("::", { + beforeExpr + }), + dot: createToken("."), + question: createToken("?", { + beforeExpr + }), + questionDot: createToken("?."), + arrow: createToken("=>", { + beforeExpr + }), + template: createToken("template"), + ellipsis: createToken("...", { + beforeExpr + }), + backQuote: createToken("`", { + startsExpr + }), + dollarBraceL: createToken("${", { + beforeExpr, + startsExpr + }), + templateTail: createToken("...`", { + startsExpr + }), + templateNonTail: createToken("...${", { + beforeExpr, + startsExpr + }), + at: createToken("@"), + hash: createToken("#", { + startsExpr + }), + interpreterDirective: createToken("#!..."), + eq: createToken("=", { + beforeExpr, + isAssign + }), + assign: createToken("_=", { + beforeExpr, + isAssign + }), + slashAssign: createToken("_=", { + beforeExpr, + isAssign + }), + xorAssign: createToken("_=", { + beforeExpr, + isAssign + }), + moduloAssign: createToken("_=", { + beforeExpr, + isAssign + }), + incDec: createToken("++/--", { + prefix, + postfix, + startsExpr + }), + bang: createToken("!", { + beforeExpr, + prefix, + startsExpr + }), + tilde: createToken("~", { + beforeExpr, + prefix, + startsExpr + }), + doubleCaret: createToken("^^", { + startsExpr + }), + doubleAt: createToken("@@", { + startsExpr + }), + pipeline: createBinop("|>", 0), + nullishCoalescing: createBinop("??", 1), + logicalOR: createBinop("||", 1), + logicalAND: createBinop("&&", 2), + bitwiseOR: createBinop("|", 3), + bitwiseXOR: createBinop("^", 4), + bitwiseAND: createBinop("&", 5), + equality: createBinop("==/!=/===/!==", 6), + lt: createBinop("/<=/>=", 7), + gt: createBinop("/<=/>=", 7), + relational: createBinop("/<=/>=", 7), + bitShift: createBinop("<>/>>>", 8), + bitShiftL: createBinop("<>/>>>", 8), + bitShiftR: createBinop("<>/>>>", 8), + plusMin: createToken("+/-", { + beforeExpr, + binop: 9, + prefix, + startsExpr + }), + modulo: createToken("%", { + binop: 10, + startsExpr + }), + star: createToken("*", { + binop: 10 + }), + slash: createBinop("/", 10), + exponent: createToken("**", { + beforeExpr, + binop: 11, + rightAssociative: true + }), + _in: createKeyword("in", { + beforeExpr, + binop: 7 + }), + _instanceof: createKeyword("instanceof", { + beforeExpr, + binop: 7 + }), + _break: createKeyword("break"), + _case: createKeyword("case", { + beforeExpr + }), + _catch: createKeyword("catch"), + _continue: createKeyword("continue"), + _debugger: createKeyword("debugger"), + _default: createKeyword("default", { + beforeExpr + }), + _else: createKeyword("else", { + beforeExpr + }), + _finally: createKeyword("finally"), + _function: createKeyword("function", { + startsExpr + }), + _if: createKeyword("if"), + _return: createKeyword("return", { + beforeExpr + }), + _switch: createKeyword("switch"), + _throw: createKeyword("throw", { + beforeExpr, + prefix, + startsExpr + }), + _try: createKeyword("try"), + _var: createKeyword("var"), + _const: createKeyword("const"), + _with: createKeyword("with"), + _new: createKeyword("new", { + beforeExpr, + startsExpr + }), + _this: createKeyword("this", { + startsExpr + }), + _super: createKeyword("super", { + startsExpr + }), + _class: createKeyword("class", { + startsExpr + }), + _extends: createKeyword("extends", { + beforeExpr + }), + _export: createKeyword("export"), + _import: createKeyword("import", { + startsExpr + }), + _null: createKeyword("null", { + startsExpr + }), + _true: createKeyword("true", { + startsExpr + }), + _false: createKeyword("false", { + startsExpr + }), + _typeof: createKeyword("typeof", { + beforeExpr, + prefix, + startsExpr + }), + _void: createKeyword("void", { + beforeExpr, + prefix, + startsExpr + }), + _delete: createKeyword("delete", { + beforeExpr, + prefix, + startsExpr + }), + _do: createKeyword("do", { + isLoop, + beforeExpr + }), + _for: createKeyword("for", { + isLoop + }), + _while: createKeyword("while", { + isLoop + }), + _as: createKeywordLike("as", { + startsExpr + }), + _assert: createKeywordLike("assert", { + startsExpr + }), + _async: createKeywordLike("async", { + startsExpr + }), + _await: createKeywordLike("await", { + startsExpr + }), + _defer: createKeywordLike("defer", { + startsExpr + }), + _from: createKeywordLike("from", { + startsExpr + }), + _get: createKeywordLike("get", { + startsExpr + }), + _let: createKeywordLike("let", { + startsExpr + }), + _meta: createKeywordLike("meta", { + startsExpr + }), + _of: createKeywordLike("of", { + startsExpr + }), + _sent: createKeywordLike("sent", { + startsExpr + }), + _set: createKeywordLike("set", { + startsExpr + }), + _source: createKeywordLike("source", { + startsExpr + }), + _static: createKeywordLike("static", { + startsExpr + }), + _using: createKeywordLike("using", { + startsExpr + }), + _yield: createKeywordLike("yield", { + startsExpr + }), + _asserts: createKeywordLike("asserts", { + startsExpr + }), + _checks: createKeywordLike("checks", { + startsExpr + }), + _exports: createKeywordLike("exports", { + startsExpr + }), + _global: createKeywordLike("global", { + startsExpr + }), + _implements: createKeywordLike("implements", { + startsExpr + }), + _intrinsic: createKeywordLike("intrinsic", { + startsExpr + }), + _infer: createKeywordLike("infer", { + startsExpr + }), + _is: createKeywordLike("is", { + startsExpr + }), + _mixins: createKeywordLike("mixins", { + startsExpr + }), + _proto: createKeywordLike("proto", { + startsExpr + }), + _require: createKeywordLike("require", { + startsExpr + }), + _satisfies: createKeywordLike("satisfies", { + startsExpr + }), + _keyof: createKeywordLike("keyof", { + startsExpr + }), + _readonly: createKeywordLike("readonly", { + startsExpr + }), + _unique: createKeywordLike("unique", { + startsExpr + }), + _abstract: createKeywordLike("abstract", { + startsExpr + }), + _declare: createKeywordLike("declare", { + startsExpr + }), + _enum: createKeywordLike("enum", { + startsExpr + }), + _module: createKeywordLike("module", { + startsExpr + }), + _namespace: createKeywordLike("namespace", { + startsExpr + }), + _interface: createKeywordLike("interface", { + startsExpr + }), + _type: createKeywordLike("type", { + startsExpr + }), + _opaque: createKeywordLike("opaque", { + startsExpr + }), + name: createToken("name", { + startsExpr + }), + placeholder: createToken("%%", { + startsExpr: true + }), + string: createToken("string", { + startsExpr + }), + num: createToken("num", { + startsExpr + }), + bigint: createToken("bigint", { + startsExpr + }), + decimal: createToken("decimal", { + startsExpr + }), + regexp: createToken("regexp", { + startsExpr + }), + privateName: createToken("#name", { + startsExpr + }), + eof: createToken("eof"), + jsxName: createToken("jsxName"), + jsxText: createToken("jsxText", { + beforeExpr: true + }), + jsxTagStart: createToken("jsxTagStart", { + startsExpr: true + }), + jsxTagEnd: createToken("jsxTagEnd") +}; +function tokenIsIdentifier(token) { + return token >= 93 && token <= 133; +} +function tokenKeywordOrIdentifierIsKeyword(token) { + return token <= 92; +} +function tokenIsKeywordOrIdentifier(token) { + return token >= 58 && token <= 133; +} +function tokenIsLiteralPropertyName(token) { + return token >= 58 && token <= 137; +} +function tokenComesBeforeExpression(token) { + return tokenBeforeExprs[token]; +} +function tokenCanStartExpression(token) { + return tokenStartsExprs[token]; +} +function tokenIsAssignment(token) { + return token >= 29 && token <= 33; +} +function tokenIsFlowInterfaceOrTypeOrOpaque(token) { + return token >= 129 && token <= 131; +} +function tokenIsLoop(token) { + return token >= 90 && token <= 92; +} +function tokenIsKeyword(token) { + return token >= 58 && token <= 92; +} +function tokenIsOperator(token) { + return token >= 39 && token <= 59; +} +function tokenIsPostfix(token) { + return token === 34; +} +function tokenIsPrefix(token) { + return tokenPrefixes[token]; +} +function tokenIsTSTypeOperator(token) { + return token >= 121 && token <= 123; +} +function tokenIsTSDeclarationStart(token) { + return token >= 124 && token <= 130; +} +function tokenLabelName(token) { + return tokenLabels[token]; +} +function tokenOperatorPrecedence(token) { + return tokenBinops[token]; +} +function tokenIsRightAssociative(token) { + return token === 57; +} +function tokenIsTemplate(token) { + return token >= 24 && token <= 25; +} +function getExportedToken(token) { + return tokenTypes[token]; +} +{ + tokenTypes[8].updateContext = context => { + context.pop(); + }; + tokenTypes[5].updateContext = tokenTypes[7].updateContext = tokenTypes[23].updateContext = context => { + context.push(types.brace); + }; + tokenTypes[22].updateContext = context => { + if (context[context.length - 1] === types.template) { + context.pop(); + } else { + context.push(types.template); + } + }; + tokenTypes[143].updateContext = context => { + context.push(types.j_expr, types.j_oTag); + }; +} +let nonASCIIidentifierStartChars = "\xaa\xb5\xba\xc0-\xd6\xd8-\xf6\xf8-\u02c1\u02c6-\u02d1\u02e0-\u02e4\u02ec\u02ee\u0370-\u0374\u0376\u0377\u037a-\u037d\u037f\u0386\u0388-\u038a\u038c\u038e-\u03a1\u03a3-\u03f5\u03f7-\u0481\u048a-\u052f\u0531-\u0556\u0559\u0560-\u0588\u05d0-\u05ea\u05ef-\u05f2\u0620-\u064a\u066e\u066f\u0671-\u06d3\u06d5\u06e5\u06e6\u06ee\u06ef\u06fa-\u06fc\u06ff\u0710\u0712-\u072f\u074d-\u07a5\u07b1\u07ca-\u07ea\u07f4\u07f5\u07fa\u0800-\u0815\u081a\u0824\u0828\u0840-\u0858\u0860-\u086a\u0870-\u0887\u0889-\u088e\u08a0-\u08c9\u0904-\u0939\u093d\u0950\u0958-\u0961\u0971-\u0980\u0985-\u098c\u098f\u0990\u0993-\u09a8\u09aa-\u09b0\u09b2\u09b6-\u09b9\u09bd\u09ce\u09dc\u09dd\u09df-\u09e1\u09f0\u09f1\u09fc\u0a05-\u0a0a\u0a0f\u0a10\u0a13-\u0a28\u0a2a-\u0a30\u0a32\u0a33\u0a35\u0a36\u0a38\u0a39\u0a59-\u0a5c\u0a5e\u0a72-\u0a74\u0a85-\u0a8d\u0a8f-\u0a91\u0a93-\u0aa8\u0aaa-\u0ab0\u0ab2\u0ab3\u0ab5-\u0ab9\u0abd\u0ad0\u0ae0\u0ae1\u0af9\u0b05-\u0b0c\u0b0f\u0b10\u0b13-\u0b28\u0b2a-\u0b30\u0b32\u0b33\u0b35-\u0b39\u0b3d\u0b5c\u0b5d\u0b5f-\u0b61\u0b71\u0b83\u0b85-\u0b8a\u0b8e-\u0b90\u0b92-\u0b95\u0b99\u0b9a\u0b9c\u0b9e\u0b9f\u0ba3\u0ba4\u0ba8-\u0baa\u0bae-\u0bb9\u0bd0\u0c05-\u0c0c\u0c0e-\u0c10\u0c12-\u0c28\u0c2a-\u0c39\u0c3d\u0c58-\u0c5a\u0c5d\u0c60\u0c61\u0c80\u0c85-\u0c8c\u0c8e-\u0c90\u0c92-\u0ca8\u0caa-\u0cb3\u0cb5-\u0cb9\u0cbd\u0cdd\u0cde\u0ce0\u0ce1\u0cf1\u0cf2\u0d04-\u0d0c\u0d0e-\u0d10\u0d12-\u0d3a\u0d3d\u0d4e\u0d54-\u0d56\u0d5f-\u0d61\u0d7a-\u0d7f\u0d85-\u0d96\u0d9a-\u0db1\u0db3-\u0dbb\u0dbd\u0dc0-\u0dc6\u0e01-\u0e30\u0e32\u0e33\u0e40-\u0e46\u0e81\u0e82\u0e84\u0e86-\u0e8a\u0e8c-\u0ea3\u0ea5\u0ea7-\u0eb0\u0eb2\u0eb3\u0ebd\u0ec0-\u0ec4\u0ec6\u0edc-\u0edf\u0f00\u0f40-\u0f47\u0f49-\u0f6c\u0f88-\u0f8c\u1000-\u102a\u103f\u1050-\u1055\u105a-\u105d\u1061\u1065\u1066\u106e-\u1070\u1075-\u1081\u108e\u10a0-\u10c5\u10c7\u10cd\u10d0-\u10fa\u10fc-\u1248\u124a-\u124d\u1250-\u1256\u1258\u125a-\u125d\u1260-\u1288\u128a-\u128d\u1290-\u12b0\u12b2-\u12b5\u12b8-\u12be\u12c0\u12c2-\u12c5\u12c8-\u12d6\u12d8-\u1310\u1312-\u1315\u1318-\u135a\u1380-\u138f\u13a0-\u13f5\u13f8-\u13fd\u1401-\u166c\u166f-\u167f\u1681-\u169a\u16a0-\u16ea\u16ee-\u16f8\u1700-\u1711\u171f-\u1731\u1740-\u1751\u1760-\u176c\u176e-\u1770\u1780-\u17b3\u17d7\u17dc\u1820-\u1878\u1880-\u18a8\u18aa\u18b0-\u18f5\u1900-\u191e\u1950-\u196d\u1970-\u1974\u1980-\u19ab\u19b0-\u19c9\u1a00-\u1a16\u1a20-\u1a54\u1aa7\u1b05-\u1b33\u1b45-\u1b4c\u1b83-\u1ba0\u1bae\u1baf\u1bba-\u1be5\u1c00-\u1c23\u1c4d-\u1c4f\u1c5a-\u1c7d\u1c80-\u1c8a\u1c90-\u1cba\u1cbd-\u1cbf\u1ce9-\u1cec\u1cee-\u1cf3\u1cf5\u1cf6\u1cfa\u1d00-\u1dbf\u1e00-\u1f15\u1f18-\u1f1d\u1f20-\u1f45\u1f48-\u1f4d\u1f50-\u1f57\u1f59\u1f5b\u1f5d\u1f5f-\u1f7d\u1f80-\u1fb4\u1fb6-\u1fbc\u1fbe\u1fc2-\u1fc4\u1fc6-\u1fcc\u1fd0-\u1fd3\u1fd6-\u1fdb\u1fe0-\u1fec\u1ff2-\u1ff4\u1ff6-\u1ffc\u2071\u207f\u2090-\u209c\u2102\u2107\u210a-\u2113\u2115\u2118-\u211d\u2124\u2126\u2128\u212a-\u2139\u213c-\u213f\u2145-\u2149\u214e\u2160-\u2188\u2c00-\u2ce4\u2ceb-\u2cee\u2cf2\u2cf3\u2d00-\u2d25\u2d27\u2d2d\u2d30-\u2d67\u2d6f\u2d80-\u2d96\u2da0-\u2da6\u2da8-\u2dae\u2db0-\u2db6\u2db8-\u2dbe\u2dc0-\u2dc6\u2dc8-\u2dce\u2dd0-\u2dd6\u2dd8-\u2dde\u3005-\u3007\u3021-\u3029\u3031-\u3035\u3038-\u303c\u3041-\u3096\u309b-\u309f\u30a1-\u30fa\u30fc-\u30ff\u3105-\u312f\u3131-\u318e\u31a0-\u31bf\u31f0-\u31ff\u3400-\u4dbf\u4e00-\ua48c\ua4d0-\ua4fd\ua500-\ua60c\ua610-\ua61f\ua62a\ua62b\ua640-\ua66e\ua67f-\ua69d\ua6a0-\ua6ef\ua717-\ua71f\ua722-\ua788\ua78b-\ua7cd\ua7d0\ua7d1\ua7d3\ua7d5-\ua7dc\ua7f2-\ua801\ua803-\ua805\ua807-\ua80a\ua80c-\ua822\ua840-\ua873\ua882-\ua8b3\ua8f2-\ua8f7\ua8fb\ua8fd\ua8fe\ua90a-\ua925\ua930-\ua946\ua960-\ua97c\ua984-\ua9b2\ua9cf\ua9e0-\ua9e4\ua9e6-\ua9ef\ua9fa-\ua9fe\uaa00-\uaa28\uaa40-\uaa42\uaa44-\uaa4b\uaa60-\uaa76\uaa7a\uaa7e-\uaaaf\uaab1\uaab5\uaab6\uaab9-\uaabd\uaac0\uaac2\uaadb-\uaadd\uaae0-\uaaea\uaaf2-\uaaf4\uab01-\uab06\uab09-\uab0e\uab11-\uab16\uab20-\uab26\uab28-\uab2e\uab30-\uab5a\uab5c-\uab69\uab70-\uabe2\uac00-\ud7a3\ud7b0-\ud7c6\ud7cb-\ud7fb\uf900-\ufa6d\ufa70-\ufad9\ufb00-\ufb06\ufb13-\ufb17\ufb1d\ufb1f-\ufb28\ufb2a-\ufb36\ufb38-\ufb3c\ufb3e\ufb40\ufb41\ufb43\ufb44\ufb46-\ufbb1\ufbd3-\ufd3d\ufd50-\ufd8f\ufd92-\ufdc7\ufdf0-\ufdfb\ufe70-\ufe74\ufe76-\ufefc\uff21-\uff3a\uff41-\uff5a\uff66-\uffbe\uffc2-\uffc7\uffca-\uffcf\uffd2-\uffd7\uffda-\uffdc"; +let nonASCIIidentifierChars = "\xb7\u0300-\u036f\u0387\u0483-\u0487\u0591-\u05bd\u05bf\u05c1\u05c2\u05c4\u05c5\u05c7\u0610-\u061a\u064b-\u0669\u0670\u06d6-\u06dc\u06df-\u06e4\u06e7\u06e8\u06ea-\u06ed\u06f0-\u06f9\u0711\u0730-\u074a\u07a6-\u07b0\u07c0-\u07c9\u07eb-\u07f3\u07fd\u0816-\u0819\u081b-\u0823\u0825-\u0827\u0829-\u082d\u0859-\u085b\u0897-\u089f\u08ca-\u08e1\u08e3-\u0903\u093a-\u093c\u093e-\u094f\u0951-\u0957\u0962\u0963\u0966-\u096f\u0981-\u0983\u09bc\u09be-\u09c4\u09c7\u09c8\u09cb-\u09cd\u09d7\u09e2\u09e3\u09e6-\u09ef\u09fe\u0a01-\u0a03\u0a3c\u0a3e-\u0a42\u0a47\u0a48\u0a4b-\u0a4d\u0a51\u0a66-\u0a71\u0a75\u0a81-\u0a83\u0abc\u0abe-\u0ac5\u0ac7-\u0ac9\u0acb-\u0acd\u0ae2\u0ae3\u0ae6-\u0aef\u0afa-\u0aff\u0b01-\u0b03\u0b3c\u0b3e-\u0b44\u0b47\u0b48\u0b4b-\u0b4d\u0b55-\u0b57\u0b62\u0b63\u0b66-\u0b6f\u0b82\u0bbe-\u0bc2\u0bc6-\u0bc8\u0bca-\u0bcd\u0bd7\u0be6-\u0bef\u0c00-\u0c04\u0c3c\u0c3e-\u0c44\u0c46-\u0c48\u0c4a-\u0c4d\u0c55\u0c56\u0c62\u0c63\u0c66-\u0c6f\u0c81-\u0c83\u0cbc\u0cbe-\u0cc4\u0cc6-\u0cc8\u0cca-\u0ccd\u0cd5\u0cd6\u0ce2\u0ce3\u0ce6-\u0cef\u0cf3\u0d00-\u0d03\u0d3b\u0d3c\u0d3e-\u0d44\u0d46-\u0d48\u0d4a-\u0d4d\u0d57\u0d62\u0d63\u0d66-\u0d6f\u0d81-\u0d83\u0dca\u0dcf-\u0dd4\u0dd6\u0dd8-\u0ddf\u0de6-\u0def\u0df2\u0df3\u0e31\u0e34-\u0e3a\u0e47-\u0e4e\u0e50-\u0e59\u0eb1\u0eb4-\u0ebc\u0ec8-\u0ece\u0ed0-\u0ed9\u0f18\u0f19\u0f20-\u0f29\u0f35\u0f37\u0f39\u0f3e\u0f3f\u0f71-\u0f84\u0f86\u0f87\u0f8d-\u0f97\u0f99-\u0fbc\u0fc6\u102b-\u103e\u1040-\u1049\u1056-\u1059\u105e-\u1060\u1062-\u1064\u1067-\u106d\u1071-\u1074\u1082-\u108d\u108f-\u109d\u135d-\u135f\u1369-\u1371\u1712-\u1715\u1732-\u1734\u1752\u1753\u1772\u1773\u17b4-\u17d3\u17dd\u17e0-\u17e9\u180b-\u180d\u180f-\u1819\u18a9\u1920-\u192b\u1930-\u193b\u1946-\u194f\u19d0-\u19da\u1a17-\u1a1b\u1a55-\u1a5e\u1a60-\u1a7c\u1a7f-\u1a89\u1a90-\u1a99\u1ab0-\u1abd\u1abf-\u1ace\u1b00-\u1b04\u1b34-\u1b44\u1b50-\u1b59\u1b6b-\u1b73\u1b80-\u1b82\u1ba1-\u1bad\u1bb0-\u1bb9\u1be6-\u1bf3\u1c24-\u1c37\u1c40-\u1c49\u1c50-\u1c59\u1cd0-\u1cd2\u1cd4-\u1ce8\u1ced\u1cf4\u1cf7-\u1cf9\u1dc0-\u1dff\u200c\u200d\u203f\u2040\u2054\u20d0-\u20dc\u20e1\u20e5-\u20f0\u2cef-\u2cf1\u2d7f\u2de0-\u2dff\u302a-\u302f\u3099\u309a\u30fb\ua620-\ua629\ua66f\ua674-\ua67d\ua69e\ua69f\ua6f0\ua6f1\ua802\ua806\ua80b\ua823-\ua827\ua82c\ua880\ua881\ua8b4-\ua8c5\ua8d0-\ua8d9\ua8e0-\ua8f1\ua8ff-\ua909\ua926-\ua92d\ua947-\ua953\ua980-\ua983\ua9b3-\ua9c0\ua9d0-\ua9d9\ua9e5\ua9f0-\ua9f9\uaa29-\uaa36\uaa43\uaa4c\uaa4d\uaa50-\uaa59\uaa7b-\uaa7d\uaab0\uaab2-\uaab4\uaab7\uaab8\uaabe\uaabf\uaac1\uaaeb-\uaaef\uaaf5\uaaf6\uabe3-\uabea\uabec\uabed\uabf0-\uabf9\ufb1e\ufe00-\ufe0f\ufe20-\ufe2f\ufe33\ufe34\ufe4d-\ufe4f\uff10-\uff19\uff3f\uff65"; +const nonASCIIidentifierStart = new RegExp("[" + nonASCIIidentifierStartChars + "]"); +const nonASCIIidentifier = new RegExp("[" + nonASCIIidentifierStartChars + nonASCIIidentifierChars + "]"); +nonASCIIidentifierStartChars = nonASCIIidentifierChars = null; +const astralIdentifierStartCodes = [0, 11, 2, 25, 2, 18, 2, 1, 2, 14, 3, 13, 35, 122, 70, 52, 268, 28, 4, 48, 48, 31, 14, 29, 6, 37, 11, 29, 3, 35, 5, 7, 2, 4, 43, 157, 19, 35, 5, 35, 5, 39, 9, 51, 13, 10, 2, 14, 2, 6, 2, 1, 2, 10, 2, 14, 2, 6, 2, 1, 4, 51, 13, 310, 10, 21, 11, 7, 25, 5, 2, 41, 2, 8, 70, 5, 3, 0, 2, 43, 2, 1, 4, 0, 3, 22, 11, 22, 10, 30, 66, 18, 2, 1, 11, 21, 11, 25, 71, 55, 7, 1, 65, 0, 16, 3, 2, 2, 2, 28, 43, 28, 4, 28, 36, 7, 2, 27, 28, 53, 11, 21, 11, 18, 14, 17, 111, 72, 56, 50, 14, 50, 14, 35, 39, 27, 10, 22, 251, 41, 7, 1, 17, 2, 60, 28, 11, 0, 9, 21, 43, 17, 47, 20, 28, 22, 13, 52, 58, 1, 3, 0, 14, 44, 33, 24, 27, 35, 30, 0, 3, 0, 9, 34, 4, 0, 13, 47, 15, 3, 22, 0, 2, 0, 36, 17, 2, 24, 20, 1, 64, 6, 2, 0, 2, 3, 2, 14, 2, 9, 8, 46, 39, 7, 3, 1, 3, 21, 2, 6, 2, 1, 2, 4, 4, 0, 19, 0, 13, 4, 31, 9, 2, 0, 3, 0, 2, 37, 2, 0, 26, 0, 2, 0, 45, 52, 19, 3, 21, 2, 31, 47, 21, 1, 2, 0, 185, 46, 42, 3, 37, 47, 21, 0, 60, 42, 14, 0, 72, 26, 38, 6, 186, 43, 117, 63, 32, 7, 3, 0, 3, 7, 2, 1, 2, 23, 16, 0, 2, 0, 95, 7, 3, 38, 17, 0, 2, 0, 29, 0, 11, 39, 8, 0, 22, 0, 12, 45, 20, 0, 19, 72, 200, 32, 32, 8, 2, 36, 18, 0, 50, 29, 113, 6, 2, 1, 2, 37, 22, 0, 26, 5, 2, 1, 2, 31, 15, 0, 328, 18, 16, 0, 2, 12, 2, 33, 125, 0, 80, 921, 103, 110, 18, 195, 2637, 96, 16, 1071, 18, 5, 26, 3994, 6, 582, 6842, 29, 1763, 568, 8, 30, 18, 78, 18, 29, 19, 47, 17, 3, 32, 20, 6, 18, 433, 44, 212, 63, 129, 74, 6, 0, 67, 12, 65, 1, 2, 0, 29, 6135, 9, 1237, 42, 9, 8936, 3, 2, 6, 2, 1, 2, 290, 16, 0, 30, 2, 3, 0, 15, 3, 9, 395, 2309, 106, 6, 12, 4, 8, 8, 9, 5991, 84, 2, 70, 2, 1, 3, 0, 3, 1, 3, 3, 2, 11, 2, 0, 2, 6, 2, 64, 2, 3, 3, 7, 2, 6, 2, 27, 2, 3, 2, 4, 2, 0, 4, 6, 2, 339, 3, 24, 2, 24, 2, 30, 2, 24, 2, 30, 2, 24, 2, 30, 2, 24, 2, 30, 2, 24, 2, 7, 1845, 30, 7, 5, 262, 61, 147, 44, 11, 6, 17, 0, 322, 29, 19, 43, 485, 27, 229, 29, 3, 0, 496, 6, 2, 3, 2, 1, 2, 14, 2, 196, 60, 67, 8, 0, 1205, 3, 2, 26, 2, 1, 2, 0, 3, 0, 2, 9, 2, 3, 2, 0, 2, 0, 7, 0, 5, 0, 2, 0, 2, 0, 2, 2, 2, 1, 2, 0, 3, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 1, 2, 0, 3, 3, 2, 6, 2, 3, 2, 3, 2, 0, 2, 9, 2, 16, 6, 2, 2, 4, 2, 16, 4421, 42719, 33, 4153, 7, 221, 3, 5761, 15, 7472, 16, 621, 2467, 541, 1507, 4938, 6, 4191]; +const astralIdentifierCodes = [509, 0, 227, 0, 150, 4, 294, 9, 1368, 2, 2, 1, 6, 3, 41, 2, 5, 0, 166, 1, 574, 3, 9, 9, 7, 9, 32, 4, 318, 1, 80, 3, 71, 10, 50, 3, 123, 2, 54, 14, 32, 10, 3, 1, 11, 3, 46, 10, 8, 0, 46, 9, 7, 2, 37, 13, 2, 9, 6, 1, 45, 0, 13, 2, 49, 13, 9, 3, 2, 11, 83, 11, 7, 0, 3, 0, 158, 11, 6, 9, 7, 3, 56, 1, 2, 6, 3, 1, 3, 2, 10, 0, 11, 1, 3, 6, 4, 4, 68, 8, 2, 0, 3, 0, 2, 3, 2, 4, 2, 0, 15, 1, 83, 17, 10, 9, 5, 0, 82, 19, 13, 9, 214, 6, 3, 8, 28, 1, 83, 16, 16, 9, 82, 12, 9, 9, 7, 19, 58, 14, 5, 9, 243, 14, 166, 9, 71, 5, 2, 1, 3, 3, 2, 0, 2, 1, 13, 9, 120, 6, 3, 6, 4, 0, 29, 9, 41, 6, 2, 3, 9, 0, 10, 10, 47, 15, 343, 9, 54, 7, 2, 7, 17, 9, 57, 21, 2, 13, 123, 5, 4, 0, 2, 1, 2, 6, 2, 0, 9, 9, 49, 4, 2, 1, 2, 4, 9, 9, 330, 3, 10, 1, 2, 0, 49, 6, 4, 4, 14, 10, 5350, 0, 7, 14, 11465, 27, 2343, 9, 87, 9, 39, 4, 60, 6, 26, 9, 535, 9, 470, 0, 2, 54, 8, 3, 82, 0, 12, 1, 19628, 1, 4178, 9, 519, 45, 3, 22, 543, 4, 4, 5, 9, 7, 3, 6, 31, 3, 149, 2, 1418, 49, 513, 54, 5, 49, 9, 0, 15, 0, 23, 4, 2, 14, 1361, 6, 2, 16, 3, 6, 2, 1, 2, 4, 101, 0, 161, 6, 10, 9, 357, 0, 62, 13, 499, 13, 245, 1, 2, 9, 726, 6, 110, 6, 6, 9, 4759, 9, 787719, 239]; +function isInAstralSet(code, set) { + let pos = 0x10000; + for (let i = 0, length = set.length; i < length; i += 2) { + pos += set[i]; + if (pos > code) return false; + pos += set[i + 1]; + if (pos >= code) return true; + } + return false; +} +function isIdentifierStart(code) { + if (code < 65) return code === 36; + if (code <= 90) return true; + if (code < 97) return code === 95; + if (code <= 122) return true; + if (code <= 0xffff) { + return code >= 0xaa && nonASCIIidentifierStart.test(String.fromCharCode(code)); + } + return isInAstralSet(code, astralIdentifierStartCodes); +} +function isIdentifierChar(code) { + if (code < 48) return code === 36; + if (code < 58) return true; + if (code < 65) return false; + if (code <= 90) return true; + if (code < 97) return code === 95; + if (code <= 122) return true; + if (code <= 0xffff) { + return code >= 0xaa && nonASCIIidentifier.test(String.fromCharCode(code)); + } + return isInAstralSet(code, astralIdentifierStartCodes) || isInAstralSet(code, astralIdentifierCodes); +} +const reservedWords = { + keyword: ["break", "case", "catch", "continue", "debugger", "default", "do", "else", "finally", "for", "function", "if", "return", "switch", "throw", "try", "var", "const", "while", "with", "new", "this", "super", "class", "extends", "export", "import", "null", "true", "false", "in", "instanceof", "typeof", "void", "delete"], + strict: ["implements", "interface", "let", "package", "private", "protected", "public", "static", "yield"], + strictBind: ["eval", "arguments"] +}; +const keywords = new Set(reservedWords.keyword); +const reservedWordsStrictSet = new Set(reservedWords.strict); +const reservedWordsStrictBindSet = new Set(reservedWords.strictBind); +function isReservedWord(word, inModule) { + return inModule && word === "await" || word === "enum"; +} +function isStrictReservedWord(word, inModule) { + return isReservedWord(word, inModule) || reservedWordsStrictSet.has(word); +} +function isStrictBindOnlyReservedWord(word) { + return reservedWordsStrictBindSet.has(word); +} +function isStrictBindReservedWord(word, inModule) { + return isStrictReservedWord(word, inModule) || isStrictBindOnlyReservedWord(word); +} +function isKeyword(word) { + return keywords.has(word); +} +function isIteratorStart(current, next, next2) { + return current === 64 && next === 64 && isIdentifierStart(next2); +} +const reservedWordLikeSet = new Set(["break", "case", "catch", "continue", "debugger", "default", "do", "else", "finally", "for", "function", "if", "return", "switch", "throw", "try", "var", "const", "while", "with", "new", "this", "super", "class", "extends", "export", "import", "null", "true", "false", "in", "instanceof", "typeof", "void", "delete", "implements", "interface", "let", "package", "private", "protected", "public", "static", "yield", "eval", "arguments", "enum", "await"]); +function canBeReservedWord(word) { + return reservedWordLikeSet.has(word); +} +class Scope { + constructor(flags) { + this.flags = 0; + this.names = new Map(); + this.firstLexicalName = ""; + this.flags = flags; + } +} +class ScopeHandler { + constructor(parser, inModule) { + this.parser = void 0; + this.scopeStack = []; + this.inModule = void 0; + this.undefinedExports = new Map(); + this.parser = parser; + this.inModule = inModule; + } + get inTopLevel() { + return (this.currentScope().flags & 1) > 0; + } + get inFunction() { + return (this.currentVarScopeFlags() & 2) > 0; + } + get allowSuper() { + return (this.currentThisScopeFlags() & 16) > 0; + } + get allowDirectSuper() { + return (this.currentThisScopeFlags() & 32) > 0; + } + get inClass() { + return (this.currentThisScopeFlags() & 64) > 0; + } + get inClassAndNotInNonArrowFunction() { + const flags = this.currentThisScopeFlags(); + return (flags & 64) > 0 && (flags & 2) === 0; + } + get inStaticBlock() { + for (let i = this.scopeStack.length - 1;; i--) { + const { + flags + } = this.scopeStack[i]; + if (flags & 128) { + return true; + } + if (flags & (387 | 64)) { + return false; + } + } + } + get inNonArrowFunction() { + return (this.currentThisScopeFlags() & 2) > 0; + } + get treatFunctionsAsVar() { + return this.treatFunctionsAsVarInScope(this.currentScope()); + } + createScope(flags) { + return new Scope(flags); + } + enter(flags) { + this.scopeStack.push(this.createScope(flags)); + } + exit() { + const scope = this.scopeStack.pop(); + return scope.flags; + } + treatFunctionsAsVarInScope(scope) { + return !!(scope.flags & (2 | 128) || !this.parser.inModule && scope.flags & 1); + } + declareName(name, bindingType, loc) { + let scope = this.currentScope(); + if (bindingType & 8 || bindingType & 16) { + this.checkRedeclarationInScope(scope, name, bindingType, loc); + let type = scope.names.get(name) || 0; + if (bindingType & 16) { + type = type | 4; + } else { + if (!scope.firstLexicalName) { + scope.firstLexicalName = name; + } + type = type | 2; + } + scope.names.set(name, type); + if (bindingType & 8) { + this.maybeExportDefined(scope, name); + } + } else if (bindingType & 4) { + for (let i = this.scopeStack.length - 1; i >= 0; --i) { + scope = this.scopeStack[i]; + this.checkRedeclarationInScope(scope, name, bindingType, loc); + scope.names.set(name, (scope.names.get(name) || 0) | 1); + this.maybeExportDefined(scope, name); + if (scope.flags & 387) break; + } + } + if (this.parser.inModule && scope.flags & 1) { + this.undefinedExports.delete(name); + } + } + maybeExportDefined(scope, name) { + if (this.parser.inModule && scope.flags & 1) { + this.undefinedExports.delete(name); + } + } + checkRedeclarationInScope(scope, name, bindingType, loc) { + if (this.isRedeclaredInScope(scope, name, bindingType)) { + this.parser.raise(Errors.VarRedeclaration, loc, { + identifierName: name + }); + } + } + isRedeclaredInScope(scope, name, bindingType) { + if (!(bindingType & 1)) return false; + if (bindingType & 8) { + return scope.names.has(name); + } + const type = scope.names.get(name); + if (bindingType & 16) { + return (type & 2) > 0 || !this.treatFunctionsAsVarInScope(scope) && (type & 1) > 0; + } + return (type & 2) > 0 && !(scope.flags & 8 && scope.firstLexicalName === name) || !this.treatFunctionsAsVarInScope(scope) && (type & 4) > 0; + } + checkLocalExport(id) { + const { + name + } = id; + const topLevelScope = this.scopeStack[0]; + if (!topLevelScope.names.has(name)) { + this.undefinedExports.set(name, id.loc.start); + } + } + currentScope() { + return this.scopeStack[this.scopeStack.length - 1]; + } + currentVarScopeFlags() { + for (let i = this.scopeStack.length - 1;; i--) { + const { + flags + } = this.scopeStack[i]; + if (flags & 387) { + return flags; + } + } + } + currentThisScopeFlags() { + for (let i = this.scopeStack.length - 1;; i--) { + const { + flags + } = this.scopeStack[i]; + if (flags & (387 | 64) && !(flags & 4)) { + return flags; + } + } + } +} +class FlowScope extends Scope { + constructor(...args) { + super(...args); + this.declareFunctions = new Set(); + } +} +class FlowScopeHandler extends ScopeHandler { + createScope(flags) { + return new FlowScope(flags); + } + declareName(name, bindingType, loc) { + const scope = this.currentScope(); + if (bindingType & 2048) { + this.checkRedeclarationInScope(scope, name, bindingType, loc); + this.maybeExportDefined(scope, name); + scope.declareFunctions.add(name); + return; + } + super.declareName(name, bindingType, loc); + } + isRedeclaredInScope(scope, name, bindingType) { + if (super.isRedeclaredInScope(scope, name, bindingType)) return true; + if (bindingType & 2048 && !scope.declareFunctions.has(name)) { + const type = scope.names.get(name); + return (type & 4) > 0 || (type & 2) > 0; + } + return false; + } + checkLocalExport(id) { + if (!this.scopeStack[0].declareFunctions.has(id.name)) { + super.checkLocalExport(id); + } + } +} +const reservedTypes = new Set(["_", "any", "bool", "boolean", "empty", "extends", "false", "interface", "mixed", "null", "number", "static", "string", "true", "typeof", "void"]); +const FlowErrors = ParseErrorEnum`flow`({ + AmbiguousConditionalArrow: "Ambiguous expression: wrap the arrow functions in parentheses to disambiguate.", + AmbiguousDeclareModuleKind: "Found both `declare module.exports` and `declare export` in the same module. Modules can only have 1 since they are either an ES module or they are a CommonJS module.", + AssignReservedType: ({ + reservedType + }) => `Cannot overwrite reserved type ${reservedType}.`, + DeclareClassElement: "The `declare` modifier can only appear on class fields.", + DeclareClassFieldInitializer: "Initializers are not allowed in fields with the `declare` modifier.", + DuplicateDeclareModuleExports: "Duplicate `declare module.exports` statement.", + EnumBooleanMemberNotInitialized: ({ + memberName, + enumName + }) => `Boolean enum members need to be initialized. Use either \`${memberName} = true,\` or \`${memberName} = false,\` in enum \`${enumName}\`.`, + EnumDuplicateMemberName: ({ + memberName, + enumName + }) => `Enum member names need to be unique, but the name \`${memberName}\` has already been used before in enum \`${enumName}\`.`, + EnumInconsistentMemberValues: ({ + enumName + }) => `Enum \`${enumName}\` has inconsistent member initializers. Either use no initializers, or consistently use literals (either booleans, numbers, or strings) for all member initializers.`, + EnumInvalidExplicitType: ({ + invalidEnumType, + enumName + }) => `Enum type \`${invalidEnumType}\` is not valid. Use one of \`boolean\`, \`number\`, \`string\`, or \`symbol\` in enum \`${enumName}\`.`, + EnumInvalidExplicitTypeUnknownSupplied: ({ + enumName + }) => `Supplied enum type is not valid. Use one of \`boolean\`, \`number\`, \`string\`, or \`symbol\` in enum \`${enumName}\`.`, + EnumInvalidMemberInitializerPrimaryType: ({ + enumName, + memberName, + explicitType + }) => `Enum \`${enumName}\` has type \`${explicitType}\`, so the initializer of \`${memberName}\` needs to be a ${explicitType} literal.`, + EnumInvalidMemberInitializerSymbolType: ({ + enumName, + memberName + }) => `Symbol enum members cannot be initialized. Use \`${memberName},\` in enum \`${enumName}\`.`, + EnumInvalidMemberInitializerUnknownType: ({ + enumName, + memberName + }) => `The enum member initializer for \`${memberName}\` needs to be a literal (either a boolean, number, or string) in enum \`${enumName}\`.`, + EnumInvalidMemberName: ({ + enumName, + memberName, + suggestion + }) => `Enum member names cannot start with lowercase 'a' through 'z'. Instead of using \`${memberName}\`, consider using \`${suggestion}\`, in enum \`${enumName}\`.`, + EnumNumberMemberNotInitialized: ({ + enumName, + memberName + }) => `Number enum members need to be initialized, e.g. \`${memberName} = 1\` in enum \`${enumName}\`.`, + EnumStringMemberInconsistentlyInitialized: ({ + enumName + }) => `String enum members need to consistently either all use initializers, or use no initializers, in enum \`${enumName}\`.`, + GetterMayNotHaveThisParam: "A getter cannot have a `this` parameter.", + ImportReflectionHasImportType: "An `import module` declaration can not use `type` or `typeof` keyword.", + ImportTypeShorthandOnlyInPureImport: "The `type` and `typeof` keywords on named imports can only be used on regular `import` statements. It cannot be used with `import type` or `import typeof` statements.", + InexactInsideExact: "Explicit inexact syntax cannot appear inside an explicit exact object type.", + InexactInsideNonObject: "Explicit inexact syntax cannot appear in class or interface definitions.", + InexactVariance: "Explicit inexact syntax cannot have variance.", + InvalidNonTypeImportInDeclareModule: "Imports within a `declare module` body must always be `import type` or `import typeof`.", + MissingTypeParamDefault: "Type parameter declaration needs a default, since a preceding type parameter declaration has a default.", + NestedDeclareModule: "`declare module` cannot be used inside another `declare module`.", + NestedFlowComment: "Cannot have a flow comment inside another flow comment.", + PatternIsOptional: Object.assign({ + message: "A binding pattern parameter cannot be optional in an implementation signature." + }, { + reasonCode: "OptionalBindingPattern" + }), + SetterMayNotHaveThisParam: "A setter cannot have a `this` parameter.", + SpreadVariance: "Spread properties cannot have variance.", + ThisParamAnnotationRequired: "A type annotation is required for the `this` parameter.", + ThisParamBannedInConstructor: "Constructors cannot have a `this` parameter; constructors don't bind `this` like other functions.", + ThisParamMayNotBeOptional: "The `this` parameter cannot be optional.", + ThisParamMustBeFirst: "The `this` parameter must be the first function parameter.", + ThisParamNoDefault: "The `this` parameter may not have a default value.", + TypeBeforeInitializer: "Type annotations must come before default assignments, e.g. instead of `age = 25: number` use `age: number = 25`.", + TypeCastInPattern: "The type cast expression is expected to be wrapped with parenthesis.", + UnexpectedExplicitInexactInObject: "Explicit inexact syntax must appear at the end of an inexact object.", + UnexpectedReservedType: ({ + reservedType + }) => `Unexpected reserved type ${reservedType}.`, + UnexpectedReservedUnderscore: "`_` is only allowed as a type argument to call or new.", + UnexpectedSpaceBetweenModuloChecks: "Spaces between `%` and `checks` are not allowed here.", + UnexpectedSpreadType: "Spread operator cannot appear in class or interface definitions.", + UnexpectedSubtractionOperand: 'Unexpected token, expected "number" or "bigint".', + UnexpectedTokenAfterTypeParameter: "Expected an arrow function after this type parameter declaration.", + UnexpectedTypeParameterBeforeAsyncArrowFunction: "Type parameters must come after the async keyword, e.g. instead of ` async () => {}`, use `async () => {}`.", + UnsupportedDeclareExportKind: ({ + unsupportedExportKind, + suggestion + }) => `\`declare export ${unsupportedExportKind}\` is not supported. Use \`${suggestion}\` instead.`, + UnsupportedStatementInDeclareModule: "Only declares and type imports are allowed inside declare module.", + UnterminatedFlowComment: "Unterminated flow-comment." +}); +function isEsModuleType(bodyElement) { + return bodyElement.type === "DeclareExportAllDeclaration" || bodyElement.type === "DeclareExportDeclaration" && (!bodyElement.declaration || bodyElement.declaration.type !== "TypeAlias" && bodyElement.declaration.type !== "InterfaceDeclaration"); +} +function hasTypeImportKind(node) { + return node.importKind === "type" || node.importKind === "typeof"; +} +const exportSuggestions = { + const: "declare export var", + let: "declare export var", + type: "export type", + interface: "export interface" +}; +function partition(list, test) { + const list1 = []; + const list2 = []; + for (let i = 0; i < list.length; i++) { + (test(list[i], i, list) ? list1 : list2).push(list[i]); + } + return [list1, list2]; +} +const FLOW_PRAGMA_REGEX = /\*?\s*@((?:no)?flow)\b/; +var flow = superClass => class FlowParserMixin extends superClass { + constructor(...args) { + super(...args); + this.flowPragma = undefined; + } + getScopeHandler() { + return FlowScopeHandler; + } + shouldParseTypes() { + return this.getPluginOption("flow", "all") || this.flowPragma === "flow"; + } + finishToken(type, val) { + if (type !== 134 && type !== 13 && type !== 28) { + if (this.flowPragma === undefined) { + this.flowPragma = null; + } + } + super.finishToken(type, val); + } + addComment(comment) { + if (this.flowPragma === undefined) { + const matches = FLOW_PRAGMA_REGEX.exec(comment.value); + if (!matches) ;else if (matches[1] === "flow") { + this.flowPragma = "flow"; + } else if (matches[1] === "noflow") { + this.flowPragma = "noflow"; + } else { + throw new Error("Unexpected flow pragma"); + } + } + super.addComment(comment); + } + flowParseTypeInitialiser(tok) { + const oldInType = this.state.inType; + this.state.inType = true; + this.expect(tok || 14); + const type = this.flowParseType(); + this.state.inType = oldInType; + return type; + } + flowParsePredicate() { + const node = this.startNode(); + const moduloLoc = this.state.startLoc; + this.next(); + this.expectContextual(110); + if (this.state.lastTokStartLoc.index > moduloLoc.index + 1) { + this.raise(FlowErrors.UnexpectedSpaceBetweenModuloChecks, moduloLoc); + } + if (this.eat(10)) { + node.value = super.parseExpression(); + this.expect(11); + return this.finishNode(node, "DeclaredPredicate"); + } else { + return this.finishNode(node, "InferredPredicate"); + } + } + flowParseTypeAndPredicateInitialiser() { + const oldInType = this.state.inType; + this.state.inType = true; + this.expect(14); + let type = null; + let predicate = null; + if (this.match(54)) { + this.state.inType = oldInType; + predicate = this.flowParsePredicate(); + } else { + type = this.flowParseType(); + this.state.inType = oldInType; + if (this.match(54)) { + predicate = this.flowParsePredicate(); + } + } + return [type, predicate]; + } + flowParseDeclareClass(node) { + this.next(); + this.flowParseInterfaceish(node, true); + return this.finishNode(node, "DeclareClass"); + } + flowParseDeclareFunction(node) { + this.next(); + const id = node.id = this.parseIdentifier(); + const typeNode = this.startNode(); + const typeContainer = this.startNode(); + if (this.match(47)) { + typeNode.typeParameters = this.flowParseTypeParameterDeclaration(); + } else { + typeNode.typeParameters = null; + } + this.expect(10); + const tmp = this.flowParseFunctionTypeParams(); + typeNode.params = tmp.params; + typeNode.rest = tmp.rest; + typeNode.this = tmp._this; + this.expect(11); + [typeNode.returnType, node.predicate] = this.flowParseTypeAndPredicateInitialiser(); + typeContainer.typeAnnotation = this.finishNode(typeNode, "FunctionTypeAnnotation"); + id.typeAnnotation = this.finishNode(typeContainer, "TypeAnnotation"); + this.resetEndLocation(id); + this.semicolon(); + this.scope.declareName(node.id.name, 2048, node.id.loc.start); + return this.finishNode(node, "DeclareFunction"); + } + flowParseDeclare(node, insideModule) { + if (this.match(80)) { + return this.flowParseDeclareClass(node); + } else if (this.match(68)) { + return this.flowParseDeclareFunction(node); + } else if (this.match(74)) { + return this.flowParseDeclareVariable(node); + } else if (this.eatContextual(127)) { + if (this.match(16)) { + return this.flowParseDeclareModuleExports(node); + } else { + if (insideModule) { + this.raise(FlowErrors.NestedDeclareModule, this.state.lastTokStartLoc); + } + return this.flowParseDeclareModule(node); + } + } else if (this.isContextual(130)) { + return this.flowParseDeclareTypeAlias(node); + } else if (this.isContextual(131)) { + return this.flowParseDeclareOpaqueType(node); + } else if (this.isContextual(129)) { + return this.flowParseDeclareInterface(node); + } else if (this.match(82)) { + return this.flowParseDeclareExportDeclaration(node, insideModule); + } else { + this.unexpected(); + } + } + flowParseDeclareVariable(node) { + this.next(); + node.id = this.flowParseTypeAnnotatableIdentifier(true); + this.scope.declareName(node.id.name, 5, node.id.loc.start); + this.semicolon(); + return this.finishNode(node, "DeclareVariable"); + } + flowParseDeclareModule(node) { + this.scope.enter(0); + if (this.match(134)) { + node.id = super.parseExprAtom(); + } else { + node.id = this.parseIdentifier(); + } + const bodyNode = node.body = this.startNode(); + const body = bodyNode.body = []; + this.expect(5); + while (!this.match(8)) { + let bodyNode = this.startNode(); + if (this.match(83)) { + this.next(); + if (!this.isContextual(130) && !this.match(87)) { + this.raise(FlowErrors.InvalidNonTypeImportInDeclareModule, this.state.lastTokStartLoc); + } + super.parseImport(bodyNode); + } else { + this.expectContextual(125, FlowErrors.UnsupportedStatementInDeclareModule); + bodyNode = this.flowParseDeclare(bodyNode, true); + } + body.push(bodyNode); + } + this.scope.exit(); + this.expect(8); + this.finishNode(bodyNode, "BlockStatement"); + let kind = null; + let hasModuleExport = false; + body.forEach(bodyElement => { + if (isEsModuleType(bodyElement)) { + if (kind === "CommonJS") { + this.raise(FlowErrors.AmbiguousDeclareModuleKind, bodyElement); + } + kind = "ES"; + } else if (bodyElement.type === "DeclareModuleExports") { + if (hasModuleExport) { + this.raise(FlowErrors.DuplicateDeclareModuleExports, bodyElement); + } + if (kind === "ES") { + this.raise(FlowErrors.AmbiguousDeclareModuleKind, bodyElement); + } + kind = "CommonJS"; + hasModuleExport = true; + } + }); + node.kind = kind || "CommonJS"; + return this.finishNode(node, "DeclareModule"); + } + flowParseDeclareExportDeclaration(node, insideModule) { + this.expect(82); + if (this.eat(65)) { + if (this.match(68) || this.match(80)) { + node.declaration = this.flowParseDeclare(this.startNode()); + } else { + node.declaration = this.flowParseType(); + this.semicolon(); + } + node.default = true; + return this.finishNode(node, "DeclareExportDeclaration"); + } else { + if (this.match(75) || this.isLet() || (this.isContextual(130) || this.isContextual(129)) && !insideModule) { + const label = this.state.value; + throw this.raise(FlowErrors.UnsupportedDeclareExportKind, this.state.startLoc, { + unsupportedExportKind: label, + suggestion: exportSuggestions[label] + }); + } + if (this.match(74) || this.match(68) || this.match(80) || this.isContextual(131)) { + node.declaration = this.flowParseDeclare(this.startNode()); + node.default = false; + return this.finishNode(node, "DeclareExportDeclaration"); + } else if (this.match(55) || this.match(5) || this.isContextual(129) || this.isContextual(130) || this.isContextual(131)) { + node = this.parseExport(node, null); + if (node.type === "ExportNamedDeclaration") { + node.default = false; + delete node.exportKind; + return this.castNodeTo(node, "DeclareExportDeclaration"); + } else { + return this.castNodeTo(node, "DeclareExportAllDeclaration"); + } + } + } + this.unexpected(); + } + flowParseDeclareModuleExports(node) { + this.next(); + this.expectContextual(111); + node.typeAnnotation = this.flowParseTypeAnnotation(); + this.semicolon(); + return this.finishNode(node, "DeclareModuleExports"); + } + flowParseDeclareTypeAlias(node) { + this.next(); + const finished = this.flowParseTypeAlias(node); + this.castNodeTo(finished, "DeclareTypeAlias"); + return finished; + } + flowParseDeclareOpaqueType(node) { + this.next(); + const finished = this.flowParseOpaqueType(node, true); + this.castNodeTo(finished, "DeclareOpaqueType"); + return finished; + } + flowParseDeclareInterface(node) { + this.next(); + this.flowParseInterfaceish(node, false); + return this.finishNode(node, "DeclareInterface"); + } + flowParseInterfaceish(node, isClass) { + node.id = this.flowParseRestrictedIdentifier(!isClass, true); + this.scope.declareName(node.id.name, isClass ? 17 : 8201, node.id.loc.start); + if (this.match(47)) { + node.typeParameters = this.flowParseTypeParameterDeclaration(); + } else { + node.typeParameters = null; + } + node.extends = []; + if (this.eat(81)) { + do { + node.extends.push(this.flowParseInterfaceExtends()); + } while (!isClass && this.eat(12)); + } + if (isClass) { + node.implements = []; + node.mixins = []; + if (this.eatContextual(117)) { + do { + node.mixins.push(this.flowParseInterfaceExtends()); + } while (this.eat(12)); + } + if (this.eatContextual(113)) { + do { + node.implements.push(this.flowParseInterfaceExtends()); + } while (this.eat(12)); + } + } + node.body = this.flowParseObjectType({ + allowStatic: isClass, + allowExact: false, + allowSpread: false, + allowProto: isClass, + allowInexact: false + }); + } + flowParseInterfaceExtends() { + const node = this.startNode(); + node.id = this.flowParseQualifiedTypeIdentifier(); + if (this.match(47)) { + node.typeParameters = this.flowParseTypeParameterInstantiation(); + } else { + node.typeParameters = null; + } + return this.finishNode(node, "InterfaceExtends"); + } + flowParseInterface(node) { + this.flowParseInterfaceish(node, false); + return this.finishNode(node, "InterfaceDeclaration"); + } + checkNotUnderscore(word) { + if (word === "_") { + this.raise(FlowErrors.UnexpectedReservedUnderscore, this.state.startLoc); + } + } + checkReservedType(word, startLoc, declaration) { + if (!reservedTypes.has(word)) return; + this.raise(declaration ? FlowErrors.AssignReservedType : FlowErrors.UnexpectedReservedType, startLoc, { + reservedType: word + }); + } + flowParseRestrictedIdentifier(liberal, declaration) { + this.checkReservedType(this.state.value, this.state.startLoc, declaration); + return this.parseIdentifier(liberal); + } + flowParseTypeAlias(node) { + node.id = this.flowParseRestrictedIdentifier(false, true); + this.scope.declareName(node.id.name, 8201, node.id.loc.start); + if (this.match(47)) { + node.typeParameters = this.flowParseTypeParameterDeclaration(); + } else { + node.typeParameters = null; + } + node.right = this.flowParseTypeInitialiser(29); + this.semicolon(); + return this.finishNode(node, "TypeAlias"); + } + flowParseOpaqueType(node, declare) { + this.expectContextual(130); + node.id = this.flowParseRestrictedIdentifier(true, true); + this.scope.declareName(node.id.name, 8201, node.id.loc.start); + if (this.match(47)) { + node.typeParameters = this.flowParseTypeParameterDeclaration(); + } else { + node.typeParameters = null; + } + node.supertype = null; + if (this.match(14)) { + node.supertype = this.flowParseTypeInitialiser(14); + } + node.impltype = null; + if (!declare) { + node.impltype = this.flowParseTypeInitialiser(29); + } + this.semicolon(); + return this.finishNode(node, "OpaqueType"); + } + flowParseTypeParameter(requireDefault = false) { + const nodeStartLoc = this.state.startLoc; + const node = this.startNode(); + const variance = this.flowParseVariance(); + const ident = this.flowParseTypeAnnotatableIdentifier(); + node.name = ident.name; + node.variance = variance; + node.bound = ident.typeAnnotation; + if (this.match(29)) { + this.eat(29); + node.default = this.flowParseType(); + } else { + if (requireDefault) { + this.raise(FlowErrors.MissingTypeParamDefault, nodeStartLoc); + } + } + return this.finishNode(node, "TypeParameter"); + } + flowParseTypeParameterDeclaration() { + const oldInType = this.state.inType; + const node = this.startNode(); + node.params = []; + this.state.inType = true; + if (this.match(47) || this.match(143)) { + this.next(); + } else { + this.unexpected(); + } + let defaultRequired = false; + do { + const typeParameter = this.flowParseTypeParameter(defaultRequired); + node.params.push(typeParameter); + if (typeParameter.default) { + defaultRequired = true; + } + if (!this.match(48)) { + this.expect(12); + } + } while (!this.match(48)); + this.expect(48); + this.state.inType = oldInType; + return this.finishNode(node, "TypeParameterDeclaration"); + } + flowInTopLevelContext(cb) { + if (this.curContext() !== types.brace) { + const oldContext = this.state.context; + this.state.context = [oldContext[0]]; + try { + return cb(); + } finally { + this.state.context = oldContext; + } + } else { + return cb(); + } + } + flowParseTypeParameterInstantiationInExpression() { + if (this.reScan_lt() !== 47) return; + return this.flowParseTypeParameterInstantiation(); + } + flowParseTypeParameterInstantiation() { + const node = this.startNode(); + const oldInType = this.state.inType; + this.state.inType = true; + node.params = []; + this.flowInTopLevelContext(() => { + this.expect(47); + const oldNoAnonFunctionType = this.state.noAnonFunctionType; + this.state.noAnonFunctionType = false; + while (!this.match(48)) { + node.params.push(this.flowParseType()); + if (!this.match(48)) { + this.expect(12); + } + } + this.state.noAnonFunctionType = oldNoAnonFunctionType; + }); + this.state.inType = oldInType; + if (!this.state.inType && this.curContext() === types.brace) { + this.reScan_lt_gt(); + } + this.expect(48); + return this.finishNode(node, "TypeParameterInstantiation"); + } + flowParseTypeParameterInstantiationCallOrNew() { + if (this.reScan_lt() !== 47) return; + const node = this.startNode(); + const oldInType = this.state.inType; + node.params = []; + this.state.inType = true; + this.expect(47); + while (!this.match(48)) { + node.params.push(this.flowParseTypeOrImplicitInstantiation()); + if (!this.match(48)) { + this.expect(12); + } + } + this.expect(48); + this.state.inType = oldInType; + return this.finishNode(node, "TypeParameterInstantiation"); + } + flowParseInterfaceType() { + const node = this.startNode(); + this.expectContextual(129); + node.extends = []; + if (this.eat(81)) { + do { + node.extends.push(this.flowParseInterfaceExtends()); + } while (this.eat(12)); + } + node.body = this.flowParseObjectType({ + allowStatic: false, + allowExact: false, + allowSpread: false, + allowProto: false, + allowInexact: false + }); + return this.finishNode(node, "InterfaceTypeAnnotation"); + } + flowParseObjectPropertyKey() { + return this.match(135) || this.match(134) ? super.parseExprAtom() : this.parseIdentifier(true); + } + flowParseObjectTypeIndexer(node, isStatic, variance) { + node.static = isStatic; + if (this.lookahead().type === 14) { + node.id = this.flowParseObjectPropertyKey(); + node.key = this.flowParseTypeInitialiser(); + } else { + node.id = null; + node.key = this.flowParseType(); + } + this.expect(3); + node.value = this.flowParseTypeInitialiser(); + node.variance = variance; + return this.finishNode(node, "ObjectTypeIndexer"); + } + flowParseObjectTypeInternalSlot(node, isStatic) { + node.static = isStatic; + node.id = this.flowParseObjectPropertyKey(); + this.expect(3); + this.expect(3); + if (this.match(47) || this.match(10)) { + node.method = true; + node.optional = false; + node.value = this.flowParseObjectTypeMethodish(this.startNodeAt(node.loc.start)); + } else { + node.method = false; + if (this.eat(17)) { + node.optional = true; + } + node.value = this.flowParseTypeInitialiser(); + } + return this.finishNode(node, "ObjectTypeInternalSlot"); + } + flowParseObjectTypeMethodish(node) { + node.params = []; + node.rest = null; + node.typeParameters = null; + node.this = null; + if (this.match(47)) { + node.typeParameters = this.flowParseTypeParameterDeclaration(); + } + this.expect(10); + if (this.match(78)) { + node.this = this.flowParseFunctionTypeParam(true); + node.this.name = null; + if (!this.match(11)) { + this.expect(12); + } + } + while (!this.match(11) && !this.match(21)) { + node.params.push(this.flowParseFunctionTypeParam(false)); + if (!this.match(11)) { + this.expect(12); + } + } + if (this.eat(21)) { + node.rest = this.flowParseFunctionTypeParam(false); + } + this.expect(11); + node.returnType = this.flowParseTypeInitialiser(); + return this.finishNode(node, "FunctionTypeAnnotation"); + } + flowParseObjectTypeCallProperty(node, isStatic) { + const valueNode = this.startNode(); + node.static = isStatic; + node.value = this.flowParseObjectTypeMethodish(valueNode); + return this.finishNode(node, "ObjectTypeCallProperty"); + } + flowParseObjectType({ + allowStatic, + allowExact, + allowSpread, + allowProto, + allowInexact + }) { + const oldInType = this.state.inType; + this.state.inType = true; + const nodeStart = this.startNode(); + nodeStart.callProperties = []; + nodeStart.properties = []; + nodeStart.indexers = []; + nodeStart.internalSlots = []; + let endDelim; + let exact; + let inexact = false; + if (allowExact && this.match(6)) { + this.expect(6); + endDelim = 9; + exact = true; + } else { + this.expect(5); + endDelim = 8; + exact = false; + } + nodeStart.exact = exact; + while (!this.match(endDelim)) { + let isStatic = false; + let protoStartLoc = null; + let inexactStartLoc = null; + const node = this.startNode(); + if (allowProto && this.isContextual(118)) { + const lookahead = this.lookahead(); + if (lookahead.type !== 14 && lookahead.type !== 17) { + this.next(); + protoStartLoc = this.state.startLoc; + allowStatic = false; + } + } + if (allowStatic && this.isContextual(106)) { + const lookahead = this.lookahead(); + if (lookahead.type !== 14 && lookahead.type !== 17) { + this.next(); + isStatic = true; + } + } + const variance = this.flowParseVariance(); + if (this.eat(0)) { + if (protoStartLoc != null) { + this.unexpected(protoStartLoc); + } + if (this.eat(0)) { + if (variance) { + this.unexpected(variance.loc.start); + } + nodeStart.internalSlots.push(this.flowParseObjectTypeInternalSlot(node, isStatic)); + } else { + nodeStart.indexers.push(this.flowParseObjectTypeIndexer(node, isStatic, variance)); + } + } else if (this.match(10) || this.match(47)) { + if (protoStartLoc != null) { + this.unexpected(protoStartLoc); + } + if (variance) { + this.unexpected(variance.loc.start); + } + nodeStart.callProperties.push(this.flowParseObjectTypeCallProperty(node, isStatic)); + } else { + let kind = "init"; + if (this.isContextual(99) || this.isContextual(104)) { + const lookahead = this.lookahead(); + if (tokenIsLiteralPropertyName(lookahead.type)) { + kind = this.state.value; + this.next(); + } + } + const propOrInexact = this.flowParseObjectTypeProperty(node, isStatic, protoStartLoc, variance, kind, allowSpread, allowInexact != null ? allowInexact : !exact); + if (propOrInexact === null) { + inexact = true; + inexactStartLoc = this.state.lastTokStartLoc; + } else { + nodeStart.properties.push(propOrInexact); + } + } + this.flowObjectTypeSemicolon(); + if (inexactStartLoc && !this.match(8) && !this.match(9)) { + this.raise(FlowErrors.UnexpectedExplicitInexactInObject, inexactStartLoc); + } + } + this.expect(endDelim); + if (allowSpread) { + nodeStart.inexact = inexact; + } + const out = this.finishNode(nodeStart, "ObjectTypeAnnotation"); + this.state.inType = oldInType; + return out; + } + flowParseObjectTypeProperty(node, isStatic, protoStartLoc, variance, kind, allowSpread, allowInexact) { + if (this.eat(21)) { + const isInexactToken = this.match(12) || this.match(13) || this.match(8) || this.match(9); + if (isInexactToken) { + if (!allowSpread) { + this.raise(FlowErrors.InexactInsideNonObject, this.state.lastTokStartLoc); + } else if (!allowInexact) { + this.raise(FlowErrors.InexactInsideExact, this.state.lastTokStartLoc); + } + if (variance) { + this.raise(FlowErrors.InexactVariance, variance); + } + return null; + } + if (!allowSpread) { + this.raise(FlowErrors.UnexpectedSpreadType, this.state.lastTokStartLoc); + } + if (protoStartLoc != null) { + this.unexpected(protoStartLoc); + } + if (variance) { + this.raise(FlowErrors.SpreadVariance, variance); + } + node.argument = this.flowParseType(); + return this.finishNode(node, "ObjectTypeSpreadProperty"); + } else { + node.key = this.flowParseObjectPropertyKey(); + node.static = isStatic; + node.proto = protoStartLoc != null; + node.kind = kind; + let optional = false; + if (this.match(47) || this.match(10)) { + node.method = true; + if (protoStartLoc != null) { + this.unexpected(protoStartLoc); + } + if (variance) { + this.unexpected(variance.loc.start); + } + node.value = this.flowParseObjectTypeMethodish(this.startNodeAt(node.loc.start)); + if (kind === "get" || kind === "set") { + this.flowCheckGetterSetterParams(node); + } + if (!allowSpread && node.key.name === "constructor" && node.value.this) { + this.raise(FlowErrors.ThisParamBannedInConstructor, node.value.this); + } + } else { + if (kind !== "init") this.unexpected(); + node.method = false; + if (this.eat(17)) { + optional = true; + } + node.value = this.flowParseTypeInitialiser(); + node.variance = variance; + } + node.optional = optional; + return this.finishNode(node, "ObjectTypeProperty"); + } + } + flowCheckGetterSetterParams(property) { + const paramCount = property.kind === "get" ? 0 : 1; + const length = property.value.params.length + (property.value.rest ? 1 : 0); + if (property.value.this) { + this.raise(property.kind === "get" ? FlowErrors.GetterMayNotHaveThisParam : FlowErrors.SetterMayNotHaveThisParam, property.value.this); + } + if (length !== paramCount) { + this.raise(property.kind === "get" ? Errors.BadGetterArity : Errors.BadSetterArity, property); + } + if (property.kind === "set" && property.value.rest) { + this.raise(Errors.BadSetterRestParameter, property); + } + } + flowObjectTypeSemicolon() { + if (!this.eat(13) && !this.eat(12) && !this.match(8) && !this.match(9)) { + this.unexpected(); + } + } + flowParseQualifiedTypeIdentifier(startLoc, id) { + startLoc != null ? startLoc : startLoc = this.state.startLoc; + let node = id || this.flowParseRestrictedIdentifier(true); + while (this.eat(16)) { + const node2 = this.startNodeAt(startLoc); + node2.qualification = node; + node2.id = this.flowParseRestrictedIdentifier(true); + node = this.finishNode(node2, "QualifiedTypeIdentifier"); + } + return node; + } + flowParseGenericType(startLoc, id) { + const node = this.startNodeAt(startLoc); + node.typeParameters = null; + node.id = this.flowParseQualifiedTypeIdentifier(startLoc, id); + if (this.match(47)) { + node.typeParameters = this.flowParseTypeParameterInstantiation(); + } + return this.finishNode(node, "GenericTypeAnnotation"); + } + flowParseTypeofType() { + const node = this.startNode(); + this.expect(87); + node.argument = this.flowParsePrimaryType(); + return this.finishNode(node, "TypeofTypeAnnotation"); + } + flowParseTupleType() { + const node = this.startNode(); + node.types = []; + this.expect(0); + while (this.state.pos < this.length && !this.match(3)) { + node.types.push(this.flowParseType()); + if (this.match(3)) break; + this.expect(12); + } + this.expect(3); + return this.finishNode(node, "TupleTypeAnnotation"); + } + flowParseFunctionTypeParam(first) { + let name = null; + let optional = false; + let typeAnnotation = null; + const node = this.startNode(); + const lh = this.lookahead(); + const isThis = this.state.type === 78; + if (lh.type === 14 || lh.type === 17) { + if (isThis && !first) { + this.raise(FlowErrors.ThisParamMustBeFirst, node); + } + name = this.parseIdentifier(isThis); + if (this.eat(17)) { + optional = true; + if (isThis) { + this.raise(FlowErrors.ThisParamMayNotBeOptional, node); + } + } + typeAnnotation = this.flowParseTypeInitialiser(); + } else { + typeAnnotation = this.flowParseType(); + } + node.name = name; + node.optional = optional; + node.typeAnnotation = typeAnnotation; + return this.finishNode(node, "FunctionTypeParam"); + } + reinterpretTypeAsFunctionTypeParam(type) { + const node = this.startNodeAt(type.loc.start); + node.name = null; + node.optional = false; + node.typeAnnotation = type; + return this.finishNode(node, "FunctionTypeParam"); + } + flowParseFunctionTypeParams(params = []) { + let rest = null; + let _this = null; + if (this.match(78)) { + _this = this.flowParseFunctionTypeParam(true); + _this.name = null; + if (!this.match(11)) { + this.expect(12); + } + } + while (!this.match(11) && !this.match(21)) { + params.push(this.flowParseFunctionTypeParam(false)); + if (!this.match(11)) { + this.expect(12); + } + } + if (this.eat(21)) { + rest = this.flowParseFunctionTypeParam(false); + } + return { + params, + rest, + _this + }; + } + flowIdentToTypeAnnotation(startLoc, node, id) { + switch (id.name) { + case "any": + return this.finishNode(node, "AnyTypeAnnotation"); + case "bool": + case "boolean": + return this.finishNode(node, "BooleanTypeAnnotation"); + case "mixed": + return this.finishNode(node, "MixedTypeAnnotation"); + case "empty": + return this.finishNode(node, "EmptyTypeAnnotation"); + case "number": + return this.finishNode(node, "NumberTypeAnnotation"); + case "string": + return this.finishNode(node, "StringTypeAnnotation"); + case "symbol": + return this.finishNode(node, "SymbolTypeAnnotation"); + default: + this.checkNotUnderscore(id.name); + return this.flowParseGenericType(startLoc, id); + } + } + flowParsePrimaryType() { + const startLoc = this.state.startLoc; + const node = this.startNode(); + let tmp; + let type; + let isGroupedType = false; + const oldNoAnonFunctionType = this.state.noAnonFunctionType; + switch (this.state.type) { + case 5: + return this.flowParseObjectType({ + allowStatic: false, + allowExact: false, + allowSpread: true, + allowProto: false, + allowInexact: true + }); + case 6: + return this.flowParseObjectType({ + allowStatic: false, + allowExact: true, + allowSpread: true, + allowProto: false, + allowInexact: false + }); + case 0: + this.state.noAnonFunctionType = false; + type = this.flowParseTupleType(); + this.state.noAnonFunctionType = oldNoAnonFunctionType; + return type; + case 47: + { + const node = this.startNode(); + node.typeParameters = this.flowParseTypeParameterDeclaration(); + this.expect(10); + tmp = this.flowParseFunctionTypeParams(); + node.params = tmp.params; + node.rest = tmp.rest; + node.this = tmp._this; + this.expect(11); + this.expect(19); + node.returnType = this.flowParseType(); + return this.finishNode(node, "FunctionTypeAnnotation"); + } + case 10: + { + const node = this.startNode(); + this.next(); + if (!this.match(11) && !this.match(21)) { + if (tokenIsIdentifier(this.state.type) || this.match(78)) { + const token = this.lookahead().type; + isGroupedType = token !== 17 && token !== 14; + } else { + isGroupedType = true; + } + } + if (isGroupedType) { + this.state.noAnonFunctionType = false; + type = this.flowParseType(); + this.state.noAnonFunctionType = oldNoAnonFunctionType; + if (this.state.noAnonFunctionType || !(this.match(12) || this.match(11) && this.lookahead().type === 19)) { + this.expect(11); + return type; + } else { + this.eat(12); + } + } + if (type) { + tmp = this.flowParseFunctionTypeParams([this.reinterpretTypeAsFunctionTypeParam(type)]); + } else { + tmp = this.flowParseFunctionTypeParams(); + } + node.params = tmp.params; + node.rest = tmp.rest; + node.this = tmp._this; + this.expect(11); + this.expect(19); + node.returnType = this.flowParseType(); + node.typeParameters = null; + return this.finishNode(node, "FunctionTypeAnnotation"); + } + case 134: + return this.parseLiteral(this.state.value, "StringLiteralTypeAnnotation"); + case 85: + case 86: + node.value = this.match(85); + this.next(); + return this.finishNode(node, "BooleanLiteralTypeAnnotation"); + case 53: + if (this.state.value === "-") { + this.next(); + if (this.match(135)) { + return this.parseLiteralAtNode(-this.state.value, "NumberLiteralTypeAnnotation", node); + } + if (this.match(136)) { + return this.parseLiteralAtNode(-this.state.value, "BigIntLiteralTypeAnnotation", node); + } + throw this.raise(FlowErrors.UnexpectedSubtractionOperand, this.state.startLoc); + } + this.unexpected(); + return; + case 135: + return this.parseLiteral(this.state.value, "NumberLiteralTypeAnnotation"); + case 136: + return this.parseLiteral(this.state.value, "BigIntLiteralTypeAnnotation"); + case 88: + this.next(); + return this.finishNode(node, "VoidTypeAnnotation"); + case 84: + this.next(); + return this.finishNode(node, "NullLiteralTypeAnnotation"); + case 78: + this.next(); + return this.finishNode(node, "ThisTypeAnnotation"); + case 55: + this.next(); + return this.finishNode(node, "ExistsTypeAnnotation"); + case 87: + return this.flowParseTypeofType(); + default: + if (tokenIsKeyword(this.state.type)) { + const label = tokenLabelName(this.state.type); + this.next(); + return super.createIdentifier(node, label); + } else if (tokenIsIdentifier(this.state.type)) { + if (this.isContextual(129)) { + return this.flowParseInterfaceType(); + } + return this.flowIdentToTypeAnnotation(startLoc, node, this.parseIdentifier()); + } + } + this.unexpected(); + } + flowParsePostfixType() { + const startLoc = this.state.startLoc; + let type = this.flowParsePrimaryType(); + let seenOptionalIndexedAccess = false; + while ((this.match(0) || this.match(18)) && !this.canInsertSemicolon()) { + const node = this.startNodeAt(startLoc); + const optional = this.eat(18); + seenOptionalIndexedAccess = seenOptionalIndexedAccess || optional; + this.expect(0); + if (!optional && this.match(3)) { + node.elementType = type; + this.next(); + type = this.finishNode(node, "ArrayTypeAnnotation"); + } else { + node.objectType = type; + node.indexType = this.flowParseType(); + this.expect(3); + if (seenOptionalIndexedAccess) { + node.optional = optional; + type = this.finishNode(node, "OptionalIndexedAccessType"); + } else { + type = this.finishNode(node, "IndexedAccessType"); + } + } + } + return type; + } + flowParsePrefixType() { + const node = this.startNode(); + if (this.eat(17)) { + node.typeAnnotation = this.flowParsePrefixType(); + return this.finishNode(node, "NullableTypeAnnotation"); + } else { + return this.flowParsePostfixType(); + } + } + flowParseAnonFunctionWithoutParens() { + const param = this.flowParsePrefixType(); + if (!this.state.noAnonFunctionType && this.eat(19)) { + const node = this.startNodeAt(param.loc.start); + node.params = [this.reinterpretTypeAsFunctionTypeParam(param)]; + node.rest = null; + node.this = null; + node.returnType = this.flowParseType(); + node.typeParameters = null; + return this.finishNode(node, "FunctionTypeAnnotation"); + } + return param; + } + flowParseIntersectionType() { + const node = this.startNode(); + this.eat(45); + const type = this.flowParseAnonFunctionWithoutParens(); + node.types = [type]; + while (this.eat(45)) { + node.types.push(this.flowParseAnonFunctionWithoutParens()); + } + return node.types.length === 1 ? type : this.finishNode(node, "IntersectionTypeAnnotation"); + } + flowParseUnionType() { + const node = this.startNode(); + this.eat(43); + const type = this.flowParseIntersectionType(); + node.types = [type]; + while (this.eat(43)) { + node.types.push(this.flowParseIntersectionType()); + } + return node.types.length === 1 ? type : this.finishNode(node, "UnionTypeAnnotation"); + } + flowParseType() { + const oldInType = this.state.inType; + this.state.inType = true; + const type = this.flowParseUnionType(); + this.state.inType = oldInType; + return type; + } + flowParseTypeOrImplicitInstantiation() { + if (this.state.type === 132 && this.state.value === "_") { + const startLoc = this.state.startLoc; + const node = this.parseIdentifier(); + return this.flowParseGenericType(startLoc, node); + } else { + return this.flowParseType(); + } + } + flowParseTypeAnnotation() { + const node = this.startNode(); + node.typeAnnotation = this.flowParseTypeInitialiser(); + return this.finishNode(node, "TypeAnnotation"); + } + flowParseTypeAnnotatableIdentifier(allowPrimitiveOverride) { + const ident = allowPrimitiveOverride ? this.parseIdentifier() : this.flowParseRestrictedIdentifier(); + if (this.match(14)) { + ident.typeAnnotation = this.flowParseTypeAnnotation(); + this.resetEndLocation(ident); + } + return ident; + } + typeCastToParameter(node) { + node.expression.typeAnnotation = node.typeAnnotation; + this.resetEndLocation(node.expression, node.typeAnnotation.loc.end); + return node.expression; + } + flowParseVariance() { + let variance = null; + if (this.match(53)) { + variance = this.startNode(); + if (this.state.value === "+") { + variance.kind = "plus"; + } else { + variance.kind = "minus"; + } + this.next(); + return this.finishNode(variance, "Variance"); + } + return variance; + } + parseFunctionBody(node, allowExpressionBody, isMethod = false) { + if (allowExpressionBody) { + this.forwardNoArrowParamsConversionAt(node, () => super.parseFunctionBody(node, true, isMethod)); + return; + } + super.parseFunctionBody(node, false, isMethod); + } + parseFunctionBodyAndFinish(node, type, isMethod = false) { + if (this.match(14)) { + const typeNode = this.startNode(); + [typeNode.typeAnnotation, node.predicate] = this.flowParseTypeAndPredicateInitialiser(); + node.returnType = typeNode.typeAnnotation ? this.finishNode(typeNode, "TypeAnnotation") : null; + } + return super.parseFunctionBodyAndFinish(node, type, isMethod); + } + parseStatementLike(flags) { + if (this.state.strict && this.isContextual(129)) { + const lookahead = this.lookahead(); + if (tokenIsKeywordOrIdentifier(lookahead.type)) { + const node = this.startNode(); + this.next(); + return this.flowParseInterface(node); + } + } else if (this.isContextual(126)) { + const node = this.startNode(); + this.next(); + return this.flowParseEnumDeclaration(node); + } + const stmt = super.parseStatementLike(flags); + if (this.flowPragma === undefined && !this.isValidDirective(stmt)) { + this.flowPragma = null; + } + return stmt; + } + parseExpressionStatement(node, expr, decorators) { + if (expr.type === "Identifier") { + if (expr.name === "declare") { + if (this.match(80) || tokenIsIdentifier(this.state.type) || this.match(68) || this.match(74) || this.match(82)) { + return this.flowParseDeclare(node); + } + } else if (tokenIsIdentifier(this.state.type)) { + if (expr.name === "interface") { + return this.flowParseInterface(node); + } else if (expr.name === "type") { + return this.flowParseTypeAlias(node); + } else if (expr.name === "opaque") { + return this.flowParseOpaqueType(node, false); + } + } + } + return super.parseExpressionStatement(node, expr, decorators); + } + shouldParseExportDeclaration() { + const { + type + } = this.state; + if (type === 126 || tokenIsFlowInterfaceOrTypeOrOpaque(type)) { + return !this.state.containsEsc; + } + return super.shouldParseExportDeclaration(); + } + isExportDefaultSpecifier() { + const { + type + } = this.state; + if (type === 126 || tokenIsFlowInterfaceOrTypeOrOpaque(type)) { + return this.state.containsEsc; + } + return super.isExportDefaultSpecifier(); + } + parseExportDefaultExpression() { + if (this.isContextual(126)) { + const node = this.startNode(); + this.next(); + return this.flowParseEnumDeclaration(node); + } + return super.parseExportDefaultExpression(); + } + parseConditional(expr, startLoc, refExpressionErrors) { + if (!this.match(17)) return expr; + if (this.state.maybeInArrowParameters) { + const nextCh = this.lookaheadCharCode(); + if (nextCh === 44 || nextCh === 61 || nextCh === 58 || nextCh === 41) { + this.setOptionalParametersError(refExpressionErrors); + return expr; + } + } + this.expect(17); + const state = this.state.clone(); + const originalNoArrowAt = this.state.noArrowAt; + const node = this.startNodeAt(startLoc); + let { + consequent, + failed + } = this.tryParseConditionalConsequent(); + let [valid, invalid] = this.getArrowLikeExpressions(consequent); + if (failed || invalid.length > 0) { + const noArrowAt = [...originalNoArrowAt]; + if (invalid.length > 0) { + this.state = state; + this.state.noArrowAt = noArrowAt; + for (let i = 0; i < invalid.length; i++) { + noArrowAt.push(invalid[i].start); + } + ({ + consequent, + failed + } = this.tryParseConditionalConsequent()); + [valid, invalid] = this.getArrowLikeExpressions(consequent); + } + if (failed && valid.length > 1) { + this.raise(FlowErrors.AmbiguousConditionalArrow, state.startLoc); + } + if (failed && valid.length === 1) { + this.state = state; + noArrowAt.push(valid[0].start); + this.state.noArrowAt = noArrowAt; + ({ + consequent, + failed + } = this.tryParseConditionalConsequent()); + } + } + this.getArrowLikeExpressions(consequent, true); + this.state.noArrowAt = originalNoArrowAt; + this.expect(14); + node.test = expr; + node.consequent = consequent; + node.alternate = this.forwardNoArrowParamsConversionAt(node, () => this.parseMaybeAssign(undefined, undefined)); + return this.finishNode(node, "ConditionalExpression"); + } + tryParseConditionalConsequent() { + this.state.noArrowParamsConversionAt.push(this.state.start); + const consequent = this.parseMaybeAssignAllowIn(); + const failed = !this.match(14); + this.state.noArrowParamsConversionAt.pop(); + return { + consequent, + failed + }; + } + getArrowLikeExpressions(node, disallowInvalid) { + const stack = [node]; + const arrows = []; + while (stack.length !== 0) { + const node = stack.pop(); + if (node.type === "ArrowFunctionExpression" && node.body.type !== "BlockStatement") { + if (node.typeParameters || !node.returnType) { + this.finishArrowValidation(node); + } else { + arrows.push(node); + } + stack.push(node.body); + } else if (node.type === "ConditionalExpression") { + stack.push(node.consequent); + stack.push(node.alternate); + } + } + if (disallowInvalid) { + arrows.forEach(node => this.finishArrowValidation(node)); + return [arrows, []]; + } + return partition(arrows, node => node.params.every(param => this.isAssignable(param, true))); + } + finishArrowValidation(node) { + var _node$extra; + this.toAssignableList(node.params, (_node$extra = node.extra) == null ? void 0 : _node$extra.trailingCommaLoc, false); + this.scope.enter(2 | 4); + super.checkParams(node, false, true); + this.scope.exit(); + } + forwardNoArrowParamsConversionAt(node, parse) { + let result; + if (this.state.noArrowParamsConversionAt.includes(this.offsetToSourcePos(node.start))) { + this.state.noArrowParamsConversionAt.push(this.state.start); + result = parse(); + this.state.noArrowParamsConversionAt.pop(); + } else { + result = parse(); + } + return result; + } + parseParenItem(node, startLoc) { + const newNode = super.parseParenItem(node, startLoc); + if (this.eat(17)) { + newNode.optional = true; + this.resetEndLocation(node); + } + if (this.match(14)) { + const typeCastNode = this.startNodeAt(startLoc); + typeCastNode.expression = newNode; + typeCastNode.typeAnnotation = this.flowParseTypeAnnotation(); + return this.finishNode(typeCastNode, "TypeCastExpression"); + } + return newNode; + } + assertModuleNodeAllowed(node) { + if (node.type === "ImportDeclaration" && (node.importKind === "type" || node.importKind === "typeof") || node.type === "ExportNamedDeclaration" && node.exportKind === "type" || node.type === "ExportAllDeclaration" && node.exportKind === "type") { + return; + } + super.assertModuleNodeAllowed(node); + } + parseExportDeclaration(node) { + if (this.isContextual(130)) { + node.exportKind = "type"; + const declarationNode = this.startNode(); + this.next(); + if (this.match(5)) { + node.specifiers = this.parseExportSpecifiers(true); + super.parseExportFrom(node); + return null; + } else { + return this.flowParseTypeAlias(declarationNode); + } + } else if (this.isContextual(131)) { + node.exportKind = "type"; + const declarationNode = this.startNode(); + this.next(); + return this.flowParseOpaqueType(declarationNode, false); + } else if (this.isContextual(129)) { + node.exportKind = "type"; + const declarationNode = this.startNode(); + this.next(); + return this.flowParseInterface(declarationNode); + } else if (this.isContextual(126)) { + node.exportKind = "value"; + const declarationNode = this.startNode(); + this.next(); + return this.flowParseEnumDeclaration(declarationNode); + } else { + return super.parseExportDeclaration(node); + } + } + eatExportStar(node) { + if (super.eatExportStar(node)) return true; + if (this.isContextual(130) && this.lookahead().type === 55) { + node.exportKind = "type"; + this.next(); + this.next(); + return true; + } + return false; + } + maybeParseExportNamespaceSpecifier(node) { + const { + startLoc + } = this.state; + const hasNamespace = super.maybeParseExportNamespaceSpecifier(node); + if (hasNamespace && node.exportKind === "type") { + this.unexpected(startLoc); + } + return hasNamespace; + } + parseClassId(node, isStatement, optionalId) { + super.parseClassId(node, isStatement, optionalId); + if (this.match(47)) { + node.typeParameters = this.flowParseTypeParameterDeclaration(); + } + } + parseClassMember(classBody, member, state) { + const { + startLoc + } = this.state; + if (this.isContextual(125)) { + if (super.parseClassMemberFromModifier(classBody, member)) { + return; + } + member.declare = true; + } + super.parseClassMember(classBody, member, state); + if (member.declare) { + if (member.type !== "ClassProperty" && member.type !== "ClassPrivateProperty" && member.type !== "PropertyDefinition") { + this.raise(FlowErrors.DeclareClassElement, startLoc); + } else if (member.value) { + this.raise(FlowErrors.DeclareClassFieldInitializer, member.value); + } + } + } + isIterator(word) { + return word === "iterator" || word === "asyncIterator"; + } + readIterator() { + const word = super.readWord1(); + const fullWord = "@@" + word; + if (!this.isIterator(word) || !this.state.inType) { + this.raise(Errors.InvalidIdentifier, this.state.curPosition(), { + identifierName: fullWord + }); + } + this.finishToken(132, fullWord); + } + getTokenFromCode(code) { + const next = this.input.charCodeAt(this.state.pos + 1); + if (code === 123 && next === 124) { + this.finishOp(6, 2); + } else if (this.state.inType && (code === 62 || code === 60)) { + this.finishOp(code === 62 ? 48 : 47, 1); + } else if (this.state.inType && code === 63) { + if (next === 46) { + this.finishOp(18, 2); + } else { + this.finishOp(17, 1); + } + } else if (isIteratorStart(code, next, this.input.charCodeAt(this.state.pos + 2))) { + this.state.pos += 2; + this.readIterator(); + } else { + super.getTokenFromCode(code); + } + } + isAssignable(node, isBinding) { + if (node.type === "TypeCastExpression") { + return this.isAssignable(node.expression, isBinding); + } else { + return super.isAssignable(node, isBinding); + } + } + toAssignable(node, isLHS = false) { + if (!isLHS && node.type === "AssignmentExpression" && node.left.type === "TypeCastExpression") { + node.left = this.typeCastToParameter(node.left); + } + super.toAssignable(node, isLHS); + } + toAssignableList(exprList, trailingCommaLoc, isLHS) { + for (let i = 0; i < exprList.length; i++) { + const expr = exprList[i]; + if ((expr == null ? void 0 : expr.type) === "TypeCastExpression") { + exprList[i] = this.typeCastToParameter(expr); + } + } + super.toAssignableList(exprList, trailingCommaLoc, isLHS); + } + toReferencedList(exprList, isParenthesizedExpr) { + for (let i = 0; i < exprList.length; i++) { + var _expr$extra; + const expr = exprList[i]; + if (expr && expr.type === "TypeCastExpression" && !((_expr$extra = expr.extra) != null && _expr$extra.parenthesized) && (exprList.length > 1 || !isParenthesizedExpr)) { + this.raise(FlowErrors.TypeCastInPattern, expr.typeAnnotation); + } + } + return exprList; + } + parseArrayLike(close, canBePattern, isTuple, refExpressionErrors) { + const node = super.parseArrayLike(close, canBePattern, isTuple, refExpressionErrors); + if (canBePattern && !this.state.maybeInArrowParameters) { + this.toReferencedList(node.elements); + } + return node; + } + isValidLVal(type, isParenthesized, binding) { + return type === "TypeCastExpression" || super.isValidLVal(type, isParenthesized, binding); + } + parseClassProperty(node) { + if (this.match(14)) { + node.typeAnnotation = this.flowParseTypeAnnotation(); + } + return super.parseClassProperty(node); + } + parseClassPrivateProperty(node) { + if (this.match(14)) { + node.typeAnnotation = this.flowParseTypeAnnotation(); + } + return super.parseClassPrivateProperty(node); + } + isClassMethod() { + return this.match(47) || super.isClassMethod(); + } + isClassProperty() { + return this.match(14) || super.isClassProperty(); + } + isNonstaticConstructor(method) { + return !this.match(14) && super.isNonstaticConstructor(method); + } + pushClassMethod(classBody, method, isGenerator, isAsync, isConstructor, allowsDirectSuper) { + if (method.variance) { + this.unexpected(method.variance.loc.start); + } + delete method.variance; + if (this.match(47)) { + method.typeParameters = this.flowParseTypeParameterDeclaration(); + } + super.pushClassMethod(classBody, method, isGenerator, isAsync, isConstructor, allowsDirectSuper); + if (method.params && isConstructor) { + const params = method.params; + if (params.length > 0 && this.isThisParam(params[0])) { + this.raise(FlowErrors.ThisParamBannedInConstructor, method); + } + } else if (method.type === "MethodDefinition" && isConstructor && method.value.params) { + const params = method.value.params; + if (params.length > 0 && this.isThisParam(params[0])) { + this.raise(FlowErrors.ThisParamBannedInConstructor, method); + } + } + } + pushClassPrivateMethod(classBody, method, isGenerator, isAsync) { + if (method.variance) { + this.unexpected(method.variance.loc.start); + } + delete method.variance; + if (this.match(47)) { + method.typeParameters = this.flowParseTypeParameterDeclaration(); + } + super.pushClassPrivateMethod(classBody, method, isGenerator, isAsync); + } + parseClassSuper(node) { + super.parseClassSuper(node); + if (node.superClass && (this.match(47) || this.match(51))) { + { + node.superTypeParameters = this.flowParseTypeParameterInstantiationInExpression(); + } + } + if (this.isContextual(113)) { + this.next(); + const implemented = node.implements = []; + do { + const node = this.startNode(); + node.id = this.flowParseRestrictedIdentifier(true); + if (this.match(47)) { + node.typeParameters = this.flowParseTypeParameterInstantiation(); + } else { + node.typeParameters = null; + } + implemented.push(this.finishNode(node, "ClassImplements")); + } while (this.eat(12)); + } + } + checkGetterSetterParams(method) { + super.checkGetterSetterParams(method); + const params = this.getObjectOrClassMethodParams(method); + if (params.length > 0) { + const param = params[0]; + if (this.isThisParam(param) && method.kind === "get") { + this.raise(FlowErrors.GetterMayNotHaveThisParam, param); + } else if (this.isThisParam(param)) { + this.raise(FlowErrors.SetterMayNotHaveThisParam, param); + } + } + } + parsePropertyNamePrefixOperator(node) { + node.variance = this.flowParseVariance(); + } + parseObjPropValue(prop, startLoc, isGenerator, isAsync, isPattern, isAccessor, refExpressionErrors) { + if (prop.variance) { + this.unexpected(prop.variance.loc.start); + } + delete prop.variance; + let typeParameters; + if (this.match(47) && !isAccessor) { + typeParameters = this.flowParseTypeParameterDeclaration(); + if (!this.match(10)) this.unexpected(); + } + const result = super.parseObjPropValue(prop, startLoc, isGenerator, isAsync, isPattern, isAccessor, refExpressionErrors); + if (typeParameters) { + (result.value || result).typeParameters = typeParameters; + } + return result; + } + parseFunctionParamType(param) { + if (this.eat(17)) { + if (param.type !== "Identifier") { + this.raise(FlowErrors.PatternIsOptional, param); + } + if (this.isThisParam(param)) { + this.raise(FlowErrors.ThisParamMayNotBeOptional, param); + } + param.optional = true; + } + if (this.match(14)) { + param.typeAnnotation = this.flowParseTypeAnnotation(); + } else if (this.isThisParam(param)) { + this.raise(FlowErrors.ThisParamAnnotationRequired, param); + } + if (this.match(29) && this.isThisParam(param)) { + this.raise(FlowErrors.ThisParamNoDefault, param); + } + this.resetEndLocation(param); + return param; + } + parseMaybeDefault(startLoc, left) { + const node = super.parseMaybeDefault(startLoc, left); + if (node.type === "AssignmentPattern" && node.typeAnnotation && node.right.start < node.typeAnnotation.start) { + this.raise(FlowErrors.TypeBeforeInitializer, node.typeAnnotation); + } + return node; + } + checkImportReflection(node) { + super.checkImportReflection(node); + if (node.module && node.importKind !== "value") { + this.raise(FlowErrors.ImportReflectionHasImportType, node.specifiers[0].loc.start); + } + } + parseImportSpecifierLocal(node, specifier, type) { + specifier.local = hasTypeImportKind(node) ? this.flowParseRestrictedIdentifier(true, true) : this.parseIdentifier(); + node.specifiers.push(this.finishImportSpecifier(specifier, type)); + } + isPotentialImportPhase(isExport) { + if (super.isPotentialImportPhase(isExport)) return true; + if (this.isContextual(130)) { + if (!isExport) return true; + const ch = this.lookaheadCharCode(); + return ch === 123 || ch === 42; + } + return !isExport && this.isContextual(87); + } + applyImportPhase(node, isExport, phase, loc) { + super.applyImportPhase(node, isExport, phase, loc); + if (isExport) { + if (!phase && this.match(65)) { + return; + } + node.exportKind = phase === "type" ? phase : "value"; + } else { + if (phase === "type" && this.match(55)) this.unexpected(); + node.importKind = phase === "type" || phase === "typeof" ? phase : "value"; + } + } + parseImportSpecifier(specifier, importedIsString, isInTypeOnlyImport, isMaybeTypeOnly, bindingType) { + const firstIdent = specifier.imported; + let specifierTypeKind = null; + if (firstIdent.type === "Identifier") { + if (firstIdent.name === "type") { + specifierTypeKind = "type"; + } else if (firstIdent.name === "typeof") { + specifierTypeKind = "typeof"; + } + } + let isBinding = false; + if (this.isContextual(93) && !this.isLookaheadContextual("as")) { + const as_ident = this.parseIdentifier(true); + if (specifierTypeKind !== null && !tokenIsKeywordOrIdentifier(this.state.type)) { + specifier.imported = as_ident; + specifier.importKind = specifierTypeKind; + specifier.local = this.cloneIdentifier(as_ident); + } else { + specifier.imported = firstIdent; + specifier.importKind = null; + specifier.local = this.parseIdentifier(); + } + } else { + if (specifierTypeKind !== null && tokenIsKeywordOrIdentifier(this.state.type)) { + specifier.imported = this.parseIdentifier(true); + specifier.importKind = specifierTypeKind; + } else { + if (importedIsString) { + throw this.raise(Errors.ImportBindingIsString, specifier, { + importName: firstIdent.value + }); + } + specifier.imported = firstIdent; + specifier.importKind = null; + } + if (this.eatContextual(93)) { + specifier.local = this.parseIdentifier(); + } else { + isBinding = true; + specifier.local = this.cloneIdentifier(specifier.imported); + } + } + const specifierIsTypeImport = hasTypeImportKind(specifier); + if (isInTypeOnlyImport && specifierIsTypeImport) { + this.raise(FlowErrors.ImportTypeShorthandOnlyInPureImport, specifier); + } + if (isInTypeOnlyImport || specifierIsTypeImport) { + this.checkReservedType(specifier.local.name, specifier.local.loc.start, true); + } + if (isBinding && !isInTypeOnlyImport && !specifierIsTypeImport) { + this.checkReservedWord(specifier.local.name, specifier.loc.start, true, true); + } + return this.finishImportSpecifier(specifier, "ImportSpecifier"); + } + parseBindingAtom() { + switch (this.state.type) { + case 78: + return this.parseIdentifier(true); + default: + return super.parseBindingAtom(); + } + } + parseFunctionParams(node, isConstructor) { + const kind = node.kind; + if (kind !== "get" && kind !== "set" && this.match(47)) { + node.typeParameters = this.flowParseTypeParameterDeclaration(); + } + super.parseFunctionParams(node, isConstructor); + } + parseVarId(decl, kind) { + super.parseVarId(decl, kind); + if (this.match(14)) { + decl.id.typeAnnotation = this.flowParseTypeAnnotation(); + this.resetEndLocation(decl.id); + } + } + parseAsyncArrowFromCallExpression(node, call) { + if (this.match(14)) { + const oldNoAnonFunctionType = this.state.noAnonFunctionType; + this.state.noAnonFunctionType = true; + node.returnType = this.flowParseTypeAnnotation(); + this.state.noAnonFunctionType = oldNoAnonFunctionType; + } + return super.parseAsyncArrowFromCallExpression(node, call); + } + shouldParseAsyncArrow() { + return this.match(14) || super.shouldParseAsyncArrow(); + } + parseMaybeAssign(refExpressionErrors, afterLeftParse) { + var _jsx; + let state = null; + let jsx; + if (this.hasPlugin("jsx") && (this.match(143) || this.match(47))) { + state = this.state.clone(); + jsx = this.tryParse(() => super.parseMaybeAssign(refExpressionErrors, afterLeftParse), state); + if (!jsx.error) return jsx.node; + const { + context + } = this.state; + const currentContext = context[context.length - 1]; + if (currentContext === types.j_oTag || currentContext === types.j_expr) { + context.pop(); + } + } + if ((_jsx = jsx) != null && _jsx.error || this.match(47)) { + var _jsx2, _jsx3; + state = state || this.state.clone(); + let typeParameters; + const arrow = this.tryParse(abort => { + var _arrowExpression$extr; + typeParameters = this.flowParseTypeParameterDeclaration(); + const arrowExpression = this.forwardNoArrowParamsConversionAt(typeParameters, () => { + const result = super.parseMaybeAssign(refExpressionErrors, afterLeftParse); + this.resetStartLocationFromNode(result, typeParameters); + return result; + }); + if ((_arrowExpression$extr = arrowExpression.extra) != null && _arrowExpression$extr.parenthesized) abort(); + const expr = this.maybeUnwrapTypeCastExpression(arrowExpression); + if (expr.type !== "ArrowFunctionExpression") abort(); + expr.typeParameters = typeParameters; + this.resetStartLocationFromNode(expr, typeParameters); + return arrowExpression; + }, state); + let arrowExpression = null; + if (arrow.node && this.maybeUnwrapTypeCastExpression(arrow.node).type === "ArrowFunctionExpression") { + if (!arrow.error && !arrow.aborted) { + if (arrow.node.async) { + this.raise(FlowErrors.UnexpectedTypeParameterBeforeAsyncArrowFunction, typeParameters); + } + return arrow.node; + } + arrowExpression = arrow.node; + } + if ((_jsx2 = jsx) != null && _jsx2.node) { + this.state = jsx.failState; + return jsx.node; + } + if (arrowExpression) { + this.state = arrow.failState; + return arrowExpression; + } + if ((_jsx3 = jsx) != null && _jsx3.thrown) throw jsx.error; + if (arrow.thrown) throw arrow.error; + throw this.raise(FlowErrors.UnexpectedTokenAfterTypeParameter, typeParameters); + } + return super.parseMaybeAssign(refExpressionErrors, afterLeftParse); + } + parseArrow(node) { + if (this.match(14)) { + const result = this.tryParse(() => { + const oldNoAnonFunctionType = this.state.noAnonFunctionType; + this.state.noAnonFunctionType = true; + const typeNode = this.startNode(); + [typeNode.typeAnnotation, node.predicate] = this.flowParseTypeAndPredicateInitialiser(); + this.state.noAnonFunctionType = oldNoAnonFunctionType; + if (this.canInsertSemicolon()) this.unexpected(); + if (!this.match(19)) this.unexpected(); + return typeNode; + }); + if (result.thrown) return null; + if (result.error) this.state = result.failState; + node.returnType = result.node.typeAnnotation ? this.finishNode(result.node, "TypeAnnotation") : null; + } + return super.parseArrow(node); + } + shouldParseArrow(params) { + return this.match(14) || super.shouldParseArrow(params); + } + setArrowFunctionParameters(node, params) { + if (this.state.noArrowParamsConversionAt.includes(this.offsetToSourcePos(node.start))) { + node.params = params; + } else { + super.setArrowFunctionParameters(node, params); + } + } + checkParams(node, allowDuplicates, isArrowFunction, strictModeChanged = true) { + if (isArrowFunction && this.state.noArrowParamsConversionAt.includes(this.offsetToSourcePos(node.start))) { + return; + } + for (let i = 0; i < node.params.length; i++) { + if (this.isThisParam(node.params[i]) && i > 0) { + this.raise(FlowErrors.ThisParamMustBeFirst, node.params[i]); + } + } + super.checkParams(node, allowDuplicates, isArrowFunction, strictModeChanged); + } + parseParenAndDistinguishExpression(canBeArrow) { + return super.parseParenAndDistinguishExpression(canBeArrow && !this.state.noArrowAt.includes(this.sourceToOffsetPos(this.state.start))); + } + parseSubscripts(base, startLoc, noCalls) { + if (base.type === "Identifier" && base.name === "async" && this.state.noArrowAt.includes(startLoc.index)) { + this.next(); + const node = this.startNodeAt(startLoc); + node.callee = base; + node.arguments = super.parseCallExpressionArguments(11); + base = this.finishNode(node, "CallExpression"); + } else if (base.type === "Identifier" && base.name === "async" && this.match(47)) { + const state = this.state.clone(); + const arrow = this.tryParse(abort => this.parseAsyncArrowWithTypeParameters(startLoc) || abort(), state); + if (!arrow.error && !arrow.aborted) return arrow.node; + const result = this.tryParse(() => super.parseSubscripts(base, startLoc, noCalls), state); + if (result.node && !result.error) return result.node; + if (arrow.node) { + this.state = arrow.failState; + return arrow.node; + } + if (result.node) { + this.state = result.failState; + return result.node; + } + throw arrow.error || result.error; + } + return super.parseSubscripts(base, startLoc, noCalls); + } + parseSubscript(base, startLoc, noCalls, subscriptState) { + if (this.match(18) && this.isLookaheadToken_lt()) { + subscriptState.optionalChainMember = true; + if (noCalls) { + subscriptState.stop = true; + return base; + } + this.next(); + const node = this.startNodeAt(startLoc); + node.callee = base; + node.typeArguments = this.flowParseTypeParameterInstantiationInExpression(); + this.expect(10); + node.arguments = this.parseCallExpressionArguments(11); + node.optional = true; + return this.finishCallExpression(node, true); + } else if (!noCalls && this.shouldParseTypes() && (this.match(47) || this.match(51))) { + const node = this.startNodeAt(startLoc); + node.callee = base; + const result = this.tryParse(() => { + node.typeArguments = this.flowParseTypeParameterInstantiationCallOrNew(); + this.expect(10); + node.arguments = super.parseCallExpressionArguments(11); + if (subscriptState.optionalChainMember) { + node.optional = false; + } + return this.finishCallExpression(node, subscriptState.optionalChainMember); + }); + if (result.node) { + if (result.error) this.state = result.failState; + return result.node; + } + } + return super.parseSubscript(base, startLoc, noCalls, subscriptState); + } + parseNewCallee(node) { + super.parseNewCallee(node); + let targs = null; + if (this.shouldParseTypes() && this.match(47)) { + targs = this.tryParse(() => this.flowParseTypeParameterInstantiationCallOrNew()).node; + } + node.typeArguments = targs; + } + parseAsyncArrowWithTypeParameters(startLoc) { + const node = this.startNodeAt(startLoc); + this.parseFunctionParams(node, false); + if (!this.parseArrow(node)) return; + return super.parseArrowExpression(node, undefined, true); + } + readToken_mult_modulo(code) { + const next = this.input.charCodeAt(this.state.pos + 1); + if (code === 42 && next === 47 && this.state.hasFlowComment) { + this.state.hasFlowComment = false; + this.state.pos += 2; + this.nextToken(); + return; + } + super.readToken_mult_modulo(code); + } + readToken_pipe_amp(code) { + const next = this.input.charCodeAt(this.state.pos + 1); + if (code === 124 && next === 125) { + this.finishOp(9, 2); + return; + } + super.readToken_pipe_amp(code); + } + parseTopLevel(file, program) { + const fileNode = super.parseTopLevel(file, program); + if (this.state.hasFlowComment) { + this.raise(FlowErrors.UnterminatedFlowComment, this.state.curPosition()); + } + return fileNode; + } + skipBlockComment() { + if (this.hasPlugin("flowComments") && this.skipFlowComment()) { + if (this.state.hasFlowComment) { + throw this.raise(FlowErrors.NestedFlowComment, this.state.startLoc); + } + this.hasFlowCommentCompletion(); + const commentSkip = this.skipFlowComment(); + if (commentSkip) { + this.state.pos += commentSkip; + this.state.hasFlowComment = true; + } + return; + } + return super.skipBlockComment(this.state.hasFlowComment ? "*-/" : "*/"); + } + skipFlowComment() { + const { + pos + } = this.state; + let shiftToFirstNonWhiteSpace = 2; + while ([32, 9].includes(this.input.charCodeAt(pos + shiftToFirstNonWhiteSpace))) { + shiftToFirstNonWhiteSpace++; + } + const ch2 = this.input.charCodeAt(shiftToFirstNonWhiteSpace + pos); + const ch3 = this.input.charCodeAt(shiftToFirstNonWhiteSpace + pos + 1); + if (ch2 === 58 && ch3 === 58) { + return shiftToFirstNonWhiteSpace + 2; + } + if (this.input.slice(shiftToFirstNonWhiteSpace + pos, shiftToFirstNonWhiteSpace + pos + 12) === "flow-include") { + return shiftToFirstNonWhiteSpace + 12; + } + if (ch2 === 58 && ch3 !== 58) { + return shiftToFirstNonWhiteSpace; + } + return false; + } + hasFlowCommentCompletion() { + const end = this.input.indexOf("*/", this.state.pos); + if (end === -1) { + throw this.raise(Errors.UnterminatedComment, this.state.curPosition()); + } + } + flowEnumErrorBooleanMemberNotInitialized(loc, { + enumName, + memberName + }) { + this.raise(FlowErrors.EnumBooleanMemberNotInitialized, loc, { + memberName, + enumName + }); + } + flowEnumErrorInvalidMemberInitializer(loc, enumContext) { + return this.raise(!enumContext.explicitType ? FlowErrors.EnumInvalidMemberInitializerUnknownType : enumContext.explicitType === "symbol" ? FlowErrors.EnumInvalidMemberInitializerSymbolType : FlowErrors.EnumInvalidMemberInitializerPrimaryType, loc, enumContext); + } + flowEnumErrorNumberMemberNotInitialized(loc, details) { + this.raise(FlowErrors.EnumNumberMemberNotInitialized, loc, details); + } + flowEnumErrorStringMemberInconsistentlyInitialized(node, details) { + this.raise(FlowErrors.EnumStringMemberInconsistentlyInitialized, node, details); + } + flowEnumMemberInit() { + const startLoc = this.state.startLoc; + const endOfInit = () => this.match(12) || this.match(8); + switch (this.state.type) { + case 135: + { + const literal = this.parseNumericLiteral(this.state.value); + if (endOfInit()) { + return { + type: "number", + loc: literal.loc.start, + value: literal + }; + } + return { + type: "invalid", + loc: startLoc + }; + } + case 134: + { + const literal = this.parseStringLiteral(this.state.value); + if (endOfInit()) { + return { + type: "string", + loc: literal.loc.start, + value: literal + }; + } + return { + type: "invalid", + loc: startLoc + }; + } + case 85: + case 86: + { + const literal = this.parseBooleanLiteral(this.match(85)); + if (endOfInit()) { + return { + type: "boolean", + loc: literal.loc.start, + value: literal + }; + } + return { + type: "invalid", + loc: startLoc + }; + } + default: + return { + type: "invalid", + loc: startLoc + }; + } + } + flowEnumMemberRaw() { + const loc = this.state.startLoc; + const id = this.parseIdentifier(true); + const init = this.eat(29) ? this.flowEnumMemberInit() : { + type: "none", + loc + }; + return { + id, + init + }; + } + flowEnumCheckExplicitTypeMismatch(loc, context, expectedType) { + const { + explicitType + } = context; + if (explicitType === null) { + return; + } + if (explicitType !== expectedType) { + this.flowEnumErrorInvalidMemberInitializer(loc, context); + } + } + flowEnumMembers({ + enumName, + explicitType + }) { + const seenNames = new Set(); + const members = { + booleanMembers: [], + numberMembers: [], + stringMembers: [], + defaultedMembers: [] + }; + let hasUnknownMembers = false; + while (!this.match(8)) { + if (this.eat(21)) { + hasUnknownMembers = true; + break; + } + const memberNode = this.startNode(); + const { + id, + init + } = this.flowEnumMemberRaw(); + const memberName = id.name; + if (memberName === "") { + continue; + } + if (/^[a-z]/.test(memberName)) { + this.raise(FlowErrors.EnumInvalidMemberName, id, { + memberName, + suggestion: memberName[0].toUpperCase() + memberName.slice(1), + enumName + }); + } + if (seenNames.has(memberName)) { + this.raise(FlowErrors.EnumDuplicateMemberName, id, { + memberName, + enumName + }); + } + seenNames.add(memberName); + const context = { + enumName, + explicitType, + memberName + }; + memberNode.id = id; + switch (init.type) { + case "boolean": + { + this.flowEnumCheckExplicitTypeMismatch(init.loc, context, "boolean"); + memberNode.init = init.value; + members.booleanMembers.push(this.finishNode(memberNode, "EnumBooleanMember")); + break; + } + case "number": + { + this.flowEnumCheckExplicitTypeMismatch(init.loc, context, "number"); + memberNode.init = init.value; + members.numberMembers.push(this.finishNode(memberNode, "EnumNumberMember")); + break; + } + case "string": + { + this.flowEnumCheckExplicitTypeMismatch(init.loc, context, "string"); + memberNode.init = init.value; + members.stringMembers.push(this.finishNode(memberNode, "EnumStringMember")); + break; + } + case "invalid": + { + throw this.flowEnumErrorInvalidMemberInitializer(init.loc, context); + } + case "none": + { + switch (explicitType) { + case "boolean": + this.flowEnumErrorBooleanMemberNotInitialized(init.loc, context); + break; + case "number": + this.flowEnumErrorNumberMemberNotInitialized(init.loc, context); + break; + default: + members.defaultedMembers.push(this.finishNode(memberNode, "EnumDefaultedMember")); + } + } + } + if (!this.match(8)) { + this.expect(12); + } + } + return { + members, + hasUnknownMembers + }; + } + flowEnumStringMembers(initializedMembers, defaultedMembers, { + enumName + }) { + if (initializedMembers.length === 0) { + return defaultedMembers; + } else if (defaultedMembers.length === 0) { + return initializedMembers; + } else if (defaultedMembers.length > initializedMembers.length) { + for (const member of initializedMembers) { + this.flowEnumErrorStringMemberInconsistentlyInitialized(member, { + enumName + }); + } + return defaultedMembers; + } else { + for (const member of defaultedMembers) { + this.flowEnumErrorStringMemberInconsistentlyInitialized(member, { + enumName + }); + } + return initializedMembers; + } + } + flowEnumParseExplicitType({ + enumName + }) { + if (!this.eatContextual(102)) return null; + if (!tokenIsIdentifier(this.state.type)) { + throw this.raise(FlowErrors.EnumInvalidExplicitTypeUnknownSupplied, this.state.startLoc, { + enumName + }); + } + const { + value + } = this.state; + this.next(); + if (value !== "boolean" && value !== "number" && value !== "string" && value !== "symbol") { + this.raise(FlowErrors.EnumInvalidExplicitType, this.state.startLoc, { + enumName, + invalidEnumType: value + }); + } + return value; + } + flowEnumBody(node, id) { + const enumName = id.name; + const nameLoc = id.loc.start; + const explicitType = this.flowEnumParseExplicitType({ + enumName + }); + this.expect(5); + const { + members, + hasUnknownMembers + } = this.flowEnumMembers({ + enumName, + explicitType + }); + node.hasUnknownMembers = hasUnknownMembers; + switch (explicitType) { + case "boolean": + node.explicitType = true; + node.members = members.booleanMembers; + this.expect(8); + return this.finishNode(node, "EnumBooleanBody"); + case "number": + node.explicitType = true; + node.members = members.numberMembers; + this.expect(8); + return this.finishNode(node, "EnumNumberBody"); + case "string": + node.explicitType = true; + node.members = this.flowEnumStringMembers(members.stringMembers, members.defaultedMembers, { + enumName + }); + this.expect(8); + return this.finishNode(node, "EnumStringBody"); + case "symbol": + node.members = members.defaultedMembers; + this.expect(8); + return this.finishNode(node, "EnumSymbolBody"); + default: + { + const empty = () => { + node.members = []; + this.expect(8); + return this.finishNode(node, "EnumStringBody"); + }; + node.explicitType = false; + const boolsLen = members.booleanMembers.length; + const numsLen = members.numberMembers.length; + const strsLen = members.stringMembers.length; + const defaultedLen = members.defaultedMembers.length; + if (!boolsLen && !numsLen && !strsLen && !defaultedLen) { + return empty(); + } else if (!boolsLen && !numsLen) { + node.members = this.flowEnumStringMembers(members.stringMembers, members.defaultedMembers, { + enumName + }); + this.expect(8); + return this.finishNode(node, "EnumStringBody"); + } else if (!numsLen && !strsLen && boolsLen >= defaultedLen) { + for (const member of members.defaultedMembers) { + this.flowEnumErrorBooleanMemberNotInitialized(member.loc.start, { + enumName, + memberName: member.id.name + }); + } + node.members = members.booleanMembers; + this.expect(8); + return this.finishNode(node, "EnumBooleanBody"); + } else if (!boolsLen && !strsLen && numsLen >= defaultedLen) { + for (const member of members.defaultedMembers) { + this.flowEnumErrorNumberMemberNotInitialized(member.loc.start, { + enumName, + memberName: member.id.name + }); + } + node.members = members.numberMembers; + this.expect(8); + return this.finishNode(node, "EnumNumberBody"); + } else { + this.raise(FlowErrors.EnumInconsistentMemberValues, nameLoc, { + enumName + }); + return empty(); + } + } + } + } + flowParseEnumDeclaration(node) { + const id = this.parseIdentifier(); + node.id = id; + node.body = this.flowEnumBody(this.startNode(), id); + return this.finishNode(node, "EnumDeclaration"); + } + jsxParseOpeningElementAfterName(node) { + if (this.shouldParseTypes()) { + if (this.match(47) || this.match(51)) { + node.typeArguments = this.flowParseTypeParameterInstantiationInExpression(); + } + } + return super.jsxParseOpeningElementAfterName(node); + } + isLookaheadToken_lt() { + const next = this.nextTokenStart(); + if (this.input.charCodeAt(next) === 60) { + const afterNext = this.input.charCodeAt(next + 1); + return afterNext !== 60 && afterNext !== 61; + } + return false; + } + reScan_lt_gt() { + const { + type + } = this.state; + if (type === 47) { + this.state.pos -= 1; + this.readToken_lt(); + } else if (type === 48) { + this.state.pos -= 1; + this.readToken_gt(); + } + } + reScan_lt() { + const { + type + } = this.state; + if (type === 51) { + this.state.pos -= 2; + this.finishOp(47, 1); + return 47; + } + return type; + } + maybeUnwrapTypeCastExpression(node) { + return node.type === "TypeCastExpression" ? node.expression : node; + } +}; +const entities = { + __proto__: null, + quot: "\u0022", + amp: "&", + apos: "\u0027", + lt: "<", + gt: ">", + nbsp: "\u00A0", + iexcl: "\u00A1", + cent: "\u00A2", + pound: "\u00A3", + curren: "\u00A4", + yen: "\u00A5", + brvbar: "\u00A6", + sect: "\u00A7", + uml: "\u00A8", + copy: "\u00A9", + ordf: "\u00AA", + laquo: "\u00AB", + not: "\u00AC", + shy: "\u00AD", + reg: "\u00AE", + macr: "\u00AF", + deg: "\u00B0", + plusmn: "\u00B1", + sup2: "\u00B2", + sup3: "\u00B3", + acute: "\u00B4", + micro: "\u00B5", + para: "\u00B6", + middot: "\u00B7", + cedil: "\u00B8", + sup1: "\u00B9", + ordm: "\u00BA", + raquo: "\u00BB", + frac14: "\u00BC", + frac12: "\u00BD", + frac34: "\u00BE", + iquest: "\u00BF", + Agrave: "\u00C0", + Aacute: "\u00C1", + Acirc: "\u00C2", + Atilde: "\u00C3", + Auml: "\u00C4", + Aring: "\u00C5", + AElig: "\u00C6", + Ccedil: "\u00C7", + Egrave: "\u00C8", + Eacute: "\u00C9", + Ecirc: "\u00CA", + Euml: "\u00CB", + Igrave: "\u00CC", + Iacute: "\u00CD", + Icirc: "\u00CE", + Iuml: "\u00CF", + ETH: "\u00D0", + Ntilde: "\u00D1", + Ograve: "\u00D2", + Oacute: "\u00D3", + Ocirc: "\u00D4", + Otilde: "\u00D5", + Ouml: "\u00D6", + times: "\u00D7", + Oslash: "\u00D8", + Ugrave: "\u00D9", + Uacute: "\u00DA", + Ucirc: "\u00DB", + Uuml: "\u00DC", + Yacute: "\u00DD", + THORN: "\u00DE", + szlig: "\u00DF", + agrave: "\u00E0", + aacute: "\u00E1", + acirc: "\u00E2", + atilde: "\u00E3", + auml: "\u00E4", + aring: "\u00E5", + aelig: "\u00E6", + ccedil: "\u00E7", + egrave: "\u00E8", + eacute: "\u00E9", + ecirc: "\u00EA", + euml: "\u00EB", + igrave: "\u00EC", + iacute: "\u00ED", + icirc: "\u00EE", + iuml: "\u00EF", + eth: "\u00F0", + ntilde: "\u00F1", + ograve: "\u00F2", + oacute: "\u00F3", + ocirc: "\u00F4", + otilde: "\u00F5", + ouml: "\u00F6", + divide: "\u00F7", + oslash: "\u00F8", + ugrave: "\u00F9", + uacute: "\u00FA", + ucirc: "\u00FB", + uuml: "\u00FC", + yacute: "\u00FD", + thorn: "\u00FE", + yuml: "\u00FF", + OElig: "\u0152", + oelig: "\u0153", + Scaron: "\u0160", + scaron: "\u0161", + Yuml: "\u0178", + fnof: "\u0192", + circ: "\u02C6", + tilde: "\u02DC", + Alpha: "\u0391", + Beta: "\u0392", + Gamma: "\u0393", + Delta: "\u0394", + Epsilon: "\u0395", + Zeta: "\u0396", + Eta: "\u0397", + Theta: "\u0398", + Iota: "\u0399", + Kappa: "\u039A", + Lambda: "\u039B", + Mu: "\u039C", + Nu: "\u039D", + Xi: "\u039E", + Omicron: "\u039F", + Pi: "\u03A0", + Rho: "\u03A1", + Sigma: "\u03A3", + Tau: "\u03A4", + Upsilon: "\u03A5", + Phi: "\u03A6", + Chi: "\u03A7", + Psi: "\u03A8", + Omega: "\u03A9", + alpha: "\u03B1", + beta: "\u03B2", + gamma: "\u03B3", + delta: "\u03B4", + epsilon: "\u03B5", + zeta: "\u03B6", + eta: "\u03B7", + theta: "\u03B8", + iota: "\u03B9", + kappa: "\u03BA", + lambda: "\u03BB", + mu: "\u03BC", + nu: "\u03BD", + xi: "\u03BE", + omicron: "\u03BF", + pi: "\u03C0", + rho: "\u03C1", + sigmaf: "\u03C2", + sigma: "\u03C3", + tau: "\u03C4", + upsilon: "\u03C5", + phi: "\u03C6", + chi: "\u03C7", + psi: "\u03C8", + omega: "\u03C9", + thetasym: "\u03D1", + upsih: "\u03D2", + piv: "\u03D6", + ensp: "\u2002", + emsp: "\u2003", + thinsp: "\u2009", + zwnj: "\u200C", + zwj: "\u200D", + lrm: "\u200E", + rlm: "\u200F", + ndash: "\u2013", + mdash: "\u2014", + lsquo: "\u2018", + rsquo: "\u2019", + sbquo: "\u201A", + ldquo: "\u201C", + rdquo: "\u201D", + bdquo: "\u201E", + dagger: "\u2020", + Dagger: "\u2021", + bull: "\u2022", + hellip: "\u2026", + permil: "\u2030", + prime: "\u2032", + Prime: "\u2033", + lsaquo: "\u2039", + rsaquo: "\u203A", + oline: "\u203E", + frasl: "\u2044", + euro: "\u20AC", + image: "\u2111", + weierp: "\u2118", + real: "\u211C", + trade: "\u2122", + alefsym: "\u2135", + larr: "\u2190", + uarr: "\u2191", + rarr: "\u2192", + darr: "\u2193", + harr: "\u2194", + crarr: "\u21B5", + lArr: "\u21D0", + uArr: "\u21D1", + rArr: "\u21D2", + dArr: "\u21D3", + hArr: "\u21D4", + forall: "\u2200", + part: "\u2202", + exist: "\u2203", + empty: "\u2205", + nabla: "\u2207", + isin: "\u2208", + notin: "\u2209", + ni: "\u220B", + prod: "\u220F", + sum: "\u2211", + minus: "\u2212", + lowast: "\u2217", + radic: "\u221A", + prop: "\u221D", + infin: "\u221E", + ang: "\u2220", + and: "\u2227", + or: "\u2228", + cap: "\u2229", + cup: "\u222A", + int: "\u222B", + there4: "\u2234", + sim: "\u223C", + cong: "\u2245", + asymp: "\u2248", + ne: "\u2260", + equiv: "\u2261", + le: "\u2264", + ge: "\u2265", + sub: "\u2282", + sup: "\u2283", + nsub: "\u2284", + sube: "\u2286", + supe: "\u2287", + oplus: "\u2295", + otimes: "\u2297", + perp: "\u22A5", + sdot: "\u22C5", + lceil: "\u2308", + rceil: "\u2309", + lfloor: "\u230A", + rfloor: "\u230B", + lang: "\u2329", + rang: "\u232A", + loz: "\u25CA", + spades: "\u2660", + clubs: "\u2663", + hearts: "\u2665", + diams: "\u2666" +}; +const lineBreak = /\r\n|[\r\n\u2028\u2029]/; +const lineBreakG = new RegExp(lineBreak.source, "g"); +function isNewLine(code) { + switch (code) { + case 10: + case 13: + case 8232: + case 8233: + return true; + default: + return false; + } +} +function hasNewLine(input, start, end) { + for (let i = start; i < end; i++) { + if (isNewLine(input.charCodeAt(i))) { + return true; + } + } + return false; +} +const skipWhiteSpace = /(?:\s|\/\/.*|\/\*[^]*?\*\/)*/g; +const skipWhiteSpaceInLine = /(?:[^\S\n\r\u2028\u2029]|\/\/.*|\/\*.*?\*\/)*/g; +function isWhitespace(code) { + switch (code) { + case 0x0009: + case 0x000b: + case 0x000c: + case 32: + case 160: + case 5760: + case 0x2000: + case 0x2001: + case 0x2002: + case 0x2003: + case 0x2004: + case 0x2005: + case 0x2006: + case 0x2007: + case 0x2008: + case 0x2009: + case 0x200a: + case 0x202f: + case 0x205f: + case 0x3000: + case 0xfeff: + return true; + default: + return false; + } +} +const JsxErrors = ParseErrorEnum`jsx`({ + AttributeIsEmpty: "JSX attributes must only be assigned a non-empty expression.", + MissingClosingTagElement: ({ + openingTagName + }) => `Expected corresponding JSX closing tag for <${openingTagName}>.`, + MissingClosingTagFragment: "Expected corresponding JSX closing tag for <>.", + UnexpectedSequenceExpression: "Sequence expressions cannot be directly nested inside JSX. Did you mean to wrap it in parentheses (...)?", + UnexpectedToken: ({ + unexpected, + HTMLEntity + }) => `Unexpected token \`${unexpected}\`. Did you mean \`${HTMLEntity}\` or \`{'${unexpected}'}\`?`, + UnsupportedJsxValue: "JSX value should be either an expression or a quoted JSX text.", + UnterminatedJsxContent: "Unterminated JSX contents.", + UnwrappedAdjacentJSXElements: "Adjacent JSX elements must be wrapped in an enclosing tag. Did you want a JSX fragment <>...?" +}); +function isFragment(object) { + return object ? object.type === "JSXOpeningFragment" || object.type === "JSXClosingFragment" : false; +} +function getQualifiedJSXName(object) { + if (object.type === "JSXIdentifier") { + return object.name; + } + if (object.type === "JSXNamespacedName") { + return object.namespace.name + ":" + object.name.name; + } + if (object.type === "JSXMemberExpression") { + return getQualifiedJSXName(object.object) + "." + getQualifiedJSXName(object.property); + } + throw new Error("Node had unexpected type: " + object.type); +} +var jsx = superClass => class JSXParserMixin extends superClass { + jsxReadToken() { + let out = ""; + let chunkStart = this.state.pos; + for (;;) { + if (this.state.pos >= this.length) { + throw this.raise(JsxErrors.UnterminatedJsxContent, this.state.startLoc); + } + const ch = this.input.charCodeAt(this.state.pos); + switch (ch) { + case 60: + case 123: + if (this.state.pos === this.state.start) { + if (ch === 60 && this.state.canStartJSXElement) { + ++this.state.pos; + this.finishToken(143); + } else { + super.getTokenFromCode(ch); + } + return; + } + out += this.input.slice(chunkStart, this.state.pos); + this.finishToken(142, out); + return; + case 38: + out += this.input.slice(chunkStart, this.state.pos); + out += this.jsxReadEntity(); + chunkStart = this.state.pos; + break; + case 62: + case 125: + default: + if (isNewLine(ch)) { + out += this.input.slice(chunkStart, this.state.pos); + out += this.jsxReadNewLine(true); + chunkStart = this.state.pos; + } else { + ++this.state.pos; + } + } + } + } + jsxReadNewLine(normalizeCRLF) { + const ch = this.input.charCodeAt(this.state.pos); + let out; + ++this.state.pos; + if (ch === 13 && this.input.charCodeAt(this.state.pos) === 10) { + ++this.state.pos; + out = normalizeCRLF ? "\n" : "\r\n"; + } else { + out = String.fromCharCode(ch); + } + ++this.state.curLine; + this.state.lineStart = this.state.pos; + return out; + } + jsxReadString(quote) { + let out = ""; + let chunkStart = ++this.state.pos; + for (;;) { + if (this.state.pos >= this.length) { + throw this.raise(Errors.UnterminatedString, this.state.startLoc); + } + const ch = this.input.charCodeAt(this.state.pos); + if (ch === quote) break; + if (ch === 38) { + out += this.input.slice(chunkStart, this.state.pos); + out += this.jsxReadEntity(); + chunkStart = this.state.pos; + } else if (isNewLine(ch)) { + out += this.input.slice(chunkStart, this.state.pos); + out += this.jsxReadNewLine(false); + chunkStart = this.state.pos; + } else { + ++this.state.pos; + } + } + out += this.input.slice(chunkStart, this.state.pos++); + this.finishToken(134, out); + } + jsxReadEntity() { + const startPos = ++this.state.pos; + if (this.codePointAtPos(this.state.pos) === 35) { + ++this.state.pos; + let radix = 10; + if (this.codePointAtPos(this.state.pos) === 120) { + radix = 16; + ++this.state.pos; + } + const codePoint = this.readInt(radix, undefined, false, "bail"); + if (codePoint !== null && this.codePointAtPos(this.state.pos) === 59) { + ++this.state.pos; + return String.fromCodePoint(codePoint); + } + } else { + let count = 0; + let semi = false; + while (count++ < 10 && this.state.pos < this.length && !(semi = this.codePointAtPos(this.state.pos) === 59)) { + ++this.state.pos; + } + if (semi) { + const desc = this.input.slice(startPos, this.state.pos); + const entity = entities[desc]; + ++this.state.pos; + if (entity) { + return entity; + } + } + } + this.state.pos = startPos; + return "&"; + } + jsxReadWord() { + let ch; + const start = this.state.pos; + do { + ch = this.input.charCodeAt(++this.state.pos); + } while (isIdentifierChar(ch) || ch === 45); + this.finishToken(141, this.input.slice(start, this.state.pos)); + } + jsxParseIdentifier() { + const node = this.startNode(); + if (this.match(141)) { + node.name = this.state.value; + } else if (tokenIsKeyword(this.state.type)) { + node.name = tokenLabelName(this.state.type); + } else { + this.unexpected(); + } + this.next(); + return this.finishNode(node, "JSXIdentifier"); + } + jsxParseNamespacedName() { + const startLoc = this.state.startLoc; + const name = this.jsxParseIdentifier(); + if (!this.eat(14)) return name; + const node = this.startNodeAt(startLoc); + node.namespace = name; + node.name = this.jsxParseIdentifier(); + return this.finishNode(node, "JSXNamespacedName"); + } + jsxParseElementName() { + const startLoc = this.state.startLoc; + let node = this.jsxParseNamespacedName(); + if (node.type === "JSXNamespacedName") { + return node; + } + while (this.eat(16)) { + const newNode = this.startNodeAt(startLoc); + newNode.object = node; + newNode.property = this.jsxParseIdentifier(); + node = this.finishNode(newNode, "JSXMemberExpression"); + } + return node; + } + jsxParseAttributeValue() { + let node; + switch (this.state.type) { + case 5: + node = this.startNode(); + this.setContext(types.brace); + this.next(); + node = this.jsxParseExpressionContainer(node, types.j_oTag); + if (node.expression.type === "JSXEmptyExpression") { + this.raise(JsxErrors.AttributeIsEmpty, node); + } + return node; + case 143: + case 134: + return this.parseExprAtom(); + default: + throw this.raise(JsxErrors.UnsupportedJsxValue, this.state.startLoc); + } + } + jsxParseEmptyExpression() { + const node = this.startNodeAt(this.state.lastTokEndLoc); + return this.finishNodeAt(node, "JSXEmptyExpression", this.state.startLoc); + } + jsxParseSpreadChild(node) { + this.next(); + node.expression = this.parseExpression(); + this.setContext(types.j_expr); + this.state.canStartJSXElement = true; + this.expect(8); + return this.finishNode(node, "JSXSpreadChild"); + } + jsxParseExpressionContainer(node, previousContext) { + if (this.match(8)) { + node.expression = this.jsxParseEmptyExpression(); + } else { + const expression = this.parseExpression(); + node.expression = expression; + } + this.setContext(previousContext); + this.state.canStartJSXElement = true; + this.expect(8); + return this.finishNode(node, "JSXExpressionContainer"); + } + jsxParseAttribute() { + const node = this.startNode(); + if (this.match(5)) { + this.setContext(types.brace); + this.next(); + this.expect(21); + node.argument = this.parseMaybeAssignAllowIn(); + this.setContext(types.j_oTag); + this.state.canStartJSXElement = true; + this.expect(8); + return this.finishNode(node, "JSXSpreadAttribute"); + } + node.name = this.jsxParseNamespacedName(); + node.value = this.eat(29) ? this.jsxParseAttributeValue() : null; + return this.finishNode(node, "JSXAttribute"); + } + jsxParseOpeningElementAt(startLoc) { + const node = this.startNodeAt(startLoc); + if (this.eat(144)) { + return this.finishNode(node, "JSXOpeningFragment"); + } + node.name = this.jsxParseElementName(); + return this.jsxParseOpeningElementAfterName(node); + } + jsxParseOpeningElementAfterName(node) { + const attributes = []; + while (!this.match(56) && !this.match(144)) { + attributes.push(this.jsxParseAttribute()); + } + node.attributes = attributes; + node.selfClosing = this.eat(56); + this.expect(144); + return this.finishNode(node, "JSXOpeningElement"); + } + jsxParseClosingElementAt(startLoc) { + const node = this.startNodeAt(startLoc); + if (this.eat(144)) { + return this.finishNode(node, "JSXClosingFragment"); + } + node.name = this.jsxParseElementName(); + this.expect(144); + return this.finishNode(node, "JSXClosingElement"); + } + jsxParseElementAt(startLoc) { + const node = this.startNodeAt(startLoc); + const children = []; + const openingElement = this.jsxParseOpeningElementAt(startLoc); + let closingElement = null; + if (!openingElement.selfClosing) { + contents: for (;;) { + switch (this.state.type) { + case 143: + startLoc = this.state.startLoc; + this.next(); + if (this.eat(56)) { + closingElement = this.jsxParseClosingElementAt(startLoc); + break contents; + } + children.push(this.jsxParseElementAt(startLoc)); + break; + case 142: + children.push(this.parseLiteral(this.state.value, "JSXText")); + break; + case 5: + { + const node = this.startNode(); + this.setContext(types.brace); + this.next(); + if (this.match(21)) { + children.push(this.jsxParseSpreadChild(node)); + } else { + children.push(this.jsxParseExpressionContainer(node, types.j_expr)); + } + break; + } + default: + this.unexpected(); + } + } + if (isFragment(openingElement) && !isFragment(closingElement) && closingElement !== null) { + this.raise(JsxErrors.MissingClosingTagFragment, closingElement); + } else if (!isFragment(openingElement) && isFragment(closingElement)) { + this.raise(JsxErrors.MissingClosingTagElement, closingElement, { + openingTagName: getQualifiedJSXName(openingElement.name) + }); + } else if (!isFragment(openingElement) && !isFragment(closingElement)) { + if (getQualifiedJSXName(closingElement.name) !== getQualifiedJSXName(openingElement.name)) { + this.raise(JsxErrors.MissingClosingTagElement, closingElement, { + openingTagName: getQualifiedJSXName(openingElement.name) + }); + } + } + } + if (isFragment(openingElement)) { + node.openingFragment = openingElement; + node.closingFragment = closingElement; + } else { + node.openingElement = openingElement; + node.closingElement = closingElement; + } + node.children = children; + if (this.match(47)) { + throw this.raise(JsxErrors.UnwrappedAdjacentJSXElements, this.state.startLoc); + } + return isFragment(openingElement) ? this.finishNode(node, "JSXFragment") : this.finishNode(node, "JSXElement"); + } + jsxParseElement() { + const startLoc = this.state.startLoc; + this.next(); + return this.jsxParseElementAt(startLoc); + } + setContext(newContext) { + const { + context + } = this.state; + context[context.length - 1] = newContext; + } + parseExprAtom(refExpressionErrors) { + if (this.match(143)) { + return this.jsxParseElement(); + } else if (this.match(47) && this.input.charCodeAt(this.state.pos) !== 33) { + this.replaceToken(143); + return this.jsxParseElement(); + } else { + return super.parseExprAtom(refExpressionErrors); + } + } + skipSpace() { + const curContext = this.curContext(); + if (!curContext.preserveSpace) super.skipSpace(); + } + getTokenFromCode(code) { + const context = this.curContext(); + if (context === types.j_expr) { + this.jsxReadToken(); + return; + } + if (context === types.j_oTag || context === types.j_cTag) { + if (isIdentifierStart(code)) { + this.jsxReadWord(); + return; + } + if (code === 62) { + ++this.state.pos; + this.finishToken(144); + return; + } + if ((code === 34 || code === 39) && context === types.j_oTag) { + this.jsxReadString(code); + return; + } + } + if (code === 60 && this.state.canStartJSXElement && this.input.charCodeAt(this.state.pos + 1) !== 33) { + ++this.state.pos; + this.finishToken(143); + return; + } + super.getTokenFromCode(code); + } + updateContext(prevType) { + const { + context, + type + } = this.state; + if (type === 56 && prevType === 143) { + context.splice(-2, 2, types.j_cTag); + this.state.canStartJSXElement = false; + } else if (type === 143) { + context.push(types.j_oTag); + } else if (type === 144) { + const out = context[context.length - 1]; + if (out === types.j_oTag && prevType === 56 || out === types.j_cTag) { + context.pop(); + this.state.canStartJSXElement = context[context.length - 1] === types.j_expr; + } else { + this.setContext(types.j_expr); + this.state.canStartJSXElement = true; + } + } else { + this.state.canStartJSXElement = tokenComesBeforeExpression(type); + } + } +}; +class TypeScriptScope extends Scope { + constructor(...args) { + super(...args); + this.tsNames = new Map(); + } +} +class TypeScriptScopeHandler extends ScopeHandler { + constructor(...args) { + super(...args); + this.importsStack = []; + } + createScope(flags) { + this.importsStack.push(new Set()); + return new TypeScriptScope(flags); + } + enter(flags) { + if (flags === 256) { + this.importsStack.push(new Set()); + } + super.enter(flags); + } + exit() { + const flags = super.exit(); + if (flags === 256) { + this.importsStack.pop(); + } + return flags; + } + hasImport(name, allowShadow) { + const len = this.importsStack.length; + if (this.importsStack[len - 1].has(name)) { + return true; + } + if (!allowShadow && len > 1) { + for (let i = 0; i < len - 1; i++) { + if (this.importsStack[i].has(name)) return true; + } + } + return false; + } + declareName(name, bindingType, loc) { + if (bindingType & 4096) { + if (this.hasImport(name, true)) { + this.parser.raise(Errors.VarRedeclaration, loc, { + identifierName: name + }); + } + this.importsStack[this.importsStack.length - 1].add(name); + return; + } + const scope = this.currentScope(); + let type = scope.tsNames.get(name) || 0; + if (bindingType & 1024) { + this.maybeExportDefined(scope, name); + scope.tsNames.set(name, type | 16); + return; + } + super.declareName(name, bindingType, loc); + if (bindingType & 2) { + if (!(bindingType & 1)) { + this.checkRedeclarationInScope(scope, name, bindingType, loc); + this.maybeExportDefined(scope, name); + } + type = type | 1; + } + if (bindingType & 256) { + type = type | 2; + } + if (bindingType & 512) { + type = type | 4; + } + if (bindingType & 128) { + type = type | 8; + } + if (type) scope.tsNames.set(name, type); + } + isRedeclaredInScope(scope, name, bindingType) { + const type = scope.tsNames.get(name); + if ((type & 2) > 0) { + if (bindingType & 256) { + const isConst = !!(bindingType & 512); + const wasConst = (type & 4) > 0; + return isConst !== wasConst; + } + return true; + } + if (bindingType & 128 && (type & 8) > 0) { + if (scope.names.get(name) & 2) { + return !!(bindingType & 1); + } else { + return false; + } + } + if (bindingType & 2 && (type & 1) > 0) { + return true; + } + return super.isRedeclaredInScope(scope, name, bindingType); + } + checkLocalExport(id) { + const { + name + } = id; + if (this.hasImport(name)) return; + const len = this.scopeStack.length; + for (let i = len - 1; i >= 0; i--) { + const scope = this.scopeStack[i]; + const type = scope.tsNames.get(name); + if ((type & 1) > 0 || (type & 16) > 0) { + return; + } + } + super.checkLocalExport(id); + } +} +class ProductionParameterHandler { + constructor() { + this.stacks = []; + } + enter(flags) { + this.stacks.push(flags); + } + exit() { + this.stacks.pop(); + } + currentFlags() { + return this.stacks[this.stacks.length - 1]; + } + get hasAwait() { + return (this.currentFlags() & 2) > 0; + } + get hasYield() { + return (this.currentFlags() & 1) > 0; + } + get hasReturn() { + return (this.currentFlags() & 4) > 0; + } + get hasIn() { + return (this.currentFlags() & 8) > 0; + } +} +function functionFlags(isAsync, isGenerator) { + return (isAsync ? 2 : 0) | (isGenerator ? 1 : 0); +} +class BaseParser { + constructor() { + this.sawUnambiguousESM = false; + this.ambiguousScriptDifferentAst = false; + } + sourceToOffsetPos(sourcePos) { + return sourcePos + this.startIndex; + } + offsetToSourcePos(offsetPos) { + return offsetPos - this.startIndex; + } + hasPlugin(pluginConfig) { + if (typeof pluginConfig === "string") { + return this.plugins.has(pluginConfig); + } else { + const [pluginName, pluginOptions] = pluginConfig; + if (!this.hasPlugin(pluginName)) { + return false; + } + const actualOptions = this.plugins.get(pluginName); + for (const key of Object.keys(pluginOptions)) { + if ((actualOptions == null ? void 0 : actualOptions[key]) !== pluginOptions[key]) { + return false; + } + } + return true; + } + } + getPluginOption(plugin, name) { + var _this$plugins$get; + return (_this$plugins$get = this.plugins.get(plugin)) == null ? void 0 : _this$plugins$get[name]; + } +} +function setTrailingComments(node, comments) { + if (node.trailingComments === undefined) { + node.trailingComments = comments; + } else { + node.trailingComments.unshift(...comments); + } +} +function setLeadingComments(node, comments) { + if (node.leadingComments === undefined) { + node.leadingComments = comments; + } else { + node.leadingComments.unshift(...comments); + } +} +function setInnerComments(node, comments) { + if (node.innerComments === undefined) { + node.innerComments = comments; + } else { + node.innerComments.unshift(...comments); + } +} +function adjustInnerComments(node, elements, commentWS) { + let lastElement = null; + let i = elements.length; + while (lastElement === null && i > 0) { + lastElement = elements[--i]; + } + if (lastElement === null || lastElement.start > commentWS.start) { + setInnerComments(node, commentWS.comments); + } else { + setTrailingComments(lastElement, commentWS.comments); + } +} +class CommentsParser extends BaseParser { + addComment(comment) { + if (this.filename) comment.loc.filename = this.filename; + const { + commentsLen + } = this.state; + if (this.comments.length !== commentsLen) { + this.comments.length = commentsLen; + } + this.comments.push(comment); + this.state.commentsLen++; + } + processComment(node) { + const { + commentStack + } = this.state; + const commentStackLength = commentStack.length; + if (commentStackLength === 0) return; + let i = commentStackLength - 1; + const lastCommentWS = commentStack[i]; + if (lastCommentWS.start === node.end) { + lastCommentWS.leadingNode = node; + i--; + } + const { + start: nodeStart + } = node; + for (; i >= 0; i--) { + const commentWS = commentStack[i]; + const commentEnd = commentWS.end; + if (commentEnd > nodeStart) { + commentWS.containingNode = node; + this.finalizeComment(commentWS); + commentStack.splice(i, 1); + } else { + if (commentEnd === nodeStart) { + commentWS.trailingNode = node; + } + break; + } + } + } + finalizeComment(commentWS) { + const { + comments + } = commentWS; + if (commentWS.leadingNode !== null || commentWS.trailingNode !== null) { + if (commentWS.leadingNode !== null) { + setTrailingComments(commentWS.leadingNode, comments); + } + if (commentWS.trailingNode !== null) { + setLeadingComments(commentWS.trailingNode, comments); + } + } else { + const { + containingNode: node, + start: commentStart + } = commentWS; + if (this.input.charCodeAt(this.offsetToSourcePos(commentStart) - 1) === 44) { + switch (node.type) { + case "ObjectExpression": + case "ObjectPattern": + case "RecordExpression": + adjustInnerComments(node, node.properties, commentWS); + break; + case "CallExpression": + case "OptionalCallExpression": + adjustInnerComments(node, node.arguments, commentWS); + break; + case "FunctionDeclaration": + case "FunctionExpression": + case "ArrowFunctionExpression": + case "ObjectMethod": + case "ClassMethod": + case "ClassPrivateMethod": + adjustInnerComments(node, node.params, commentWS); + break; + case "ArrayExpression": + case "ArrayPattern": + case "TupleExpression": + adjustInnerComments(node, node.elements, commentWS); + break; + case "ExportNamedDeclaration": + case "ImportDeclaration": + adjustInnerComments(node, node.specifiers, commentWS); + break; + case "TSEnumDeclaration": + { + adjustInnerComments(node, node.members, commentWS); + } + break; + case "TSEnumBody": + adjustInnerComments(node, node.members, commentWS); + break; + default: + { + setInnerComments(node, comments); + } + } + } else { + setInnerComments(node, comments); + } + } + } + finalizeRemainingComments() { + const { + commentStack + } = this.state; + for (let i = commentStack.length - 1; i >= 0; i--) { + this.finalizeComment(commentStack[i]); + } + this.state.commentStack = []; + } + resetPreviousNodeTrailingComments(node) { + const { + commentStack + } = this.state; + const { + length + } = commentStack; + if (length === 0) return; + const commentWS = commentStack[length - 1]; + if (commentWS.leadingNode === node) { + commentWS.leadingNode = null; + } + } + resetPreviousIdentifierLeadingComments(node) { + const { + commentStack + } = this.state; + const { + length + } = commentStack; + if (length === 0) return; + if (commentStack[length - 1].trailingNode === node) { + commentStack[length - 1].trailingNode = null; + } else if (length >= 2 && commentStack[length - 2].trailingNode === node) { + commentStack[length - 2].trailingNode = null; + } + } + takeSurroundingComments(node, start, end) { + const { + commentStack + } = this.state; + const commentStackLength = commentStack.length; + if (commentStackLength === 0) return; + let i = commentStackLength - 1; + for (; i >= 0; i--) { + const commentWS = commentStack[i]; + const commentEnd = commentWS.end; + const commentStart = commentWS.start; + if (commentStart === end) { + commentWS.leadingNode = node; + } else if (commentEnd === start) { + commentWS.trailingNode = node; + } else if (commentEnd < start) { + break; + } + } + } +} +class State { + constructor() { + this.flags = 1024; + this.startIndex = void 0; + this.curLine = void 0; + this.lineStart = void 0; + this.startLoc = void 0; + this.endLoc = void 0; + this.errors = []; + this.potentialArrowAt = -1; + this.noArrowAt = []; + this.noArrowParamsConversionAt = []; + this.topicContext = { + maxNumOfResolvableTopics: 0, + maxTopicIndex: null + }; + this.labels = []; + this.commentsLen = 0; + this.commentStack = []; + this.pos = 0; + this.type = 140; + this.value = null; + this.start = 0; + this.end = 0; + this.lastTokEndLoc = null; + this.lastTokStartLoc = null; + this.context = [types.brace]; + this.firstInvalidTemplateEscapePos = null; + this.strictErrors = new Map(); + this.tokensLength = 0; + } + get strict() { + return (this.flags & 1) > 0; + } + set strict(v) { + if (v) this.flags |= 1;else this.flags &= -2; + } + init({ + strictMode, + sourceType, + startIndex, + startLine, + startColumn + }) { + this.strict = strictMode === false ? false : strictMode === true ? true : sourceType === "module"; + this.startIndex = startIndex; + this.curLine = startLine; + this.lineStart = -startColumn; + this.startLoc = this.endLoc = new Position(startLine, startColumn, startIndex); + } + get maybeInArrowParameters() { + return (this.flags & 2) > 0; + } + set maybeInArrowParameters(v) { + if (v) this.flags |= 2;else this.flags &= -3; + } + get inType() { + return (this.flags & 4) > 0; + } + set inType(v) { + if (v) this.flags |= 4;else this.flags &= -5; + } + get noAnonFunctionType() { + return (this.flags & 8) > 0; + } + set noAnonFunctionType(v) { + if (v) this.flags |= 8;else this.flags &= -9; + } + get hasFlowComment() { + return (this.flags & 16) > 0; + } + set hasFlowComment(v) { + if (v) this.flags |= 16;else this.flags &= -17; + } + get isAmbientContext() { + return (this.flags & 32) > 0; + } + set isAmbientContext(v) { + if (v) this.flags |= 32;else this.flags &= -33; + } + get inAbstractClass() { + return (this.flags & 64) > 0; + } + set inAbstractClass(v) { + if (v) this.flags |= 64;else this.flags &= -65; + } + get inDisallowConditionalTypesContext() { + return (this.flags & 128) > 0; + } + set inDisallowConditionalTypesContext(v) { + if (v) this.flags |= 128;else this.flags &= -129; + } + get soloAwait() { + return (this.flags & 256) > 0; + } + set soloAwait(v) { + if (v) this.flags |= 256;else this.flags &= -257; + } + get inFSharpPipelineDirectBody() { + return (this.flags & 512) > 0; + } + set inFSharpPipelineDirectBody(v) { + if (v) this.flags |= 512;else this.flags &= -513; + } + get canStartJSXElement() { + return (this.flags & 1024) > 0; + } + set canStartJSXElement(v) { + if (v) this.flags |= 1024;else this.flags &= -1025; + } + get containsEsc() { + return (this.flags & 2048) > 0; + } + set containsEsc(v) { + if (v) this.flags |= 2048;else this.flags &= -2049; + } + get hasTopLevelAwait() { + return (this.flags & 4096) > 0; + } + set hasTopLevelAwait(v) { + if (v) this.flags |= 4096;else this.flags &= -4097; + } + curPosition() { + return new Position(this.curLine, this.pos - this.lineStart, this.pos + this.startIndex); + } + clone() { + const state = new State(); + state.flags = this.flags; + state.startIndex = this.startIndex; + state.curLine = this.curLine; + state.lineStart = this.lineStart; + state.startLoc = this.startLoc; + state.endLoc = this.endLoc; + state.errors = this.errors.slice(); + state.potentialArrowAt = this.potentialArrowAt; + state.noArrowAt = this.noArrowAt.slice(); + state.noArrowParamsConversionAt = this.noArrowParamsConversionAt.slice(); + state.topicContext = this.topicContext; + state.labels = this.labels.slice(); + state.commentsLen = this.commentsLen; + state.commentStack = this.commentStack.slice(); + state.pos = this.pos; + state.type = this.type; + state.value = this.value; + state.start = this.start; + state.end = this.end; + state.lastTokEndLoc = this.lastTokEndLoc; + state.lastTokStartLoc = this.lastTokStartLoc; + state.context = this.context.slice(); + state.firstInvalidTemplateEscapePos = this.firstInvalidTemplateEscapePos; + state.strictErrors = this.strictErrors; + state.tokensLength = this.tokensLength; + return state; + } +} +var _isDigit = function isDigit(code) { + return code >= 48 && code <= 57; +}; +const forbiddenNumericSeparatorSiblings = { + decBinOct: new Set([46, 66, 69, 79, 95, 98, 101, 111]), + hex: new Set([46, 88, 95, 120]) +}; +const isAllowedNumericSeparatorSibling = { + bin: ch => ch === 48 || ch === 49, + oct: ch => ch >= 48 && ch <= 55, + dec: ch => ch >= 48 && ch <= 57, + hex: ch => ch >= 48 && ch <= 57 || ch >= 65 && ch <= 70 || ch >= 97 && ch <= 102 +}; +function readStringContents(type, input, pos, lineStart, curLine, errors) { + const initialPos = pos; + const initialLineStart = lineStart; + const initialCurLine = curLine; + let out = ""; + let firstInvalidLoc = null; + let chunkStart = pos; + const { + length + } = input; + for (;;) { + if (pos >= length) { + errors.unterminated(initialPos, initialLineStart, initialCurLine); + out += input.slice(chunkStart, pos); + break; + } + const ch = input.charCodeAt(pos); + if (isStringEnd(type, ch, input, pos)) { + out += input.slice(chunkStart, pos); + break; + } + if (ch === 92) { + out += input.slice(chunkStart, pos); + const res = readEscapedChar(input, pos, lineStart, curLine, type === "template", errors); + if (res.ch === null && !firstInvalidLoc) { + firstInvalidLoc = { + pos, + lineStart, + curLine + }; + } else { + out += res.ch; + } + ({ + pos, + lineStart, + curLine + } = res); + chunkStart = pos; + } else if (ch === 8232 || ch === 8233) { + ++pos; + ++curLine; + lineStart = pos; + } else if (ch === 10 || ch === 13) { + if (type === "template") { + out += input.slice(chunkStart, pos) + "\n"; + ++pos; + if (ch === 13 && input.charCodeAt(pos) === 10) { + ++pos; + } + ++curLine; + chunkStart = lineStart = pos; + } else { + errors.unterminated(initialPos, initialLineStart, initialCurLine); + } + } else { + ++pos; + } + } + return { + pos, + str: out, + firstInvalidLoc, + lineStart, + curLine, + containsInvalid: !!firstInvalidLoc + }; +} +function isStringEnd(type, ch, input, pos) { + if (type === "template") { + return ch === 96 || ch === 36 && input.charCodeAt(pos + 1) === 123; + } + return ch === (type === "double" ? 34 : 39); +} +function readEscapedChar(input, pos, lineStart, curLine, inTemplate, errors) { + const throwOnInvalid = !inTemplate; + pos++; + const res = ch => ({ + pos, + ch, + lineStart, + curLine + }); + const ch = input.charCodeAt(pos++); + switch (ch) { + case 110: + return res("\n"); + case 114: + return res("\r"); + case 120: + { + let code; + ({ + code, + pos + } = readHexChar(input, pos, lineStart, curLine, 2, false, throwOnInvalid, errors)); + return res(code === null ? null : String.fromCharCode(code)); + } + case 117: + { + let code; + ({ + code, + pos + } = readCodePoint(input, pos, lineStart, curLine, throwOnInvalid, errors)); + return res(code === null ? null : String.fromCodePoint(code)); + } + case 116: + return res("\t"); + case 98: + return res("\b"); + case 118: + return res("\u000b"); + case 102: + return res("\f"); + case 13: + if (input.charCodeAt(pos) === 10) { + ++pos; + } + case 10: + lineStart = pos; + ++curLine; + case 8232: + case 8233: + return res(""); + case 56: + case 57: + if (inTemplate) { + return res(null); + } else { + errors.strictNumericEscape(pos - 1, lineStart, curLine); + } + default: + if (ch >= 48 && ch <= 55) { + const startPos = pos - 1; + const match = /^[0-7]+/.exec(input.slice(startPos, pos + 2)); + let octalStr = match[0]; + let octal = parseInt(octalStr, 8); + if (octal > 255) { + octalStr = octalStr.slice(0, -1); + octal = parseInt(octalStr, 8); + } + pos += octalStr.length - 1; + const next = input.charCodeAt(pos); + if (octalStr !== "0" || next === 56 || next === 57) { + if (inTemplate) { + return res(null); + } else { + errors.strictNumericEscape(startPos, lineStart, curLine); + } + } + return res(String.fromCharCode(octal)); + } + return res(String.fromCharCode(ch)); + } +} +function readHexChar(input, pos, lineStart, curLine, len, forceLen, throwOnInvalid, errors) { + const initialPos = pos; + let n; + ({ + n, + pos + } = readInt(input, pos, lineStart, curLine, 16, len, forceLen, false, errors, !throwOnInvalid)); + if (n === null) { + if (throwOnInvalid) { + errors.invalidEscapeSequence(initialPos, lineStart, curLine); + } else { + pos = initialPos - 1; + } + } + return { + code: n, + pos + }; +} +function readInt(input, pos, lineStart, curLine, radix, len, forceLen, allowNumSeparator, errors, bailOnError) { + const start = pos; + const forbiddenSiblings = radix === 16 ? forbiddenNumericSeparatorSiblings.hex : forbiddenNumericSeparatorSiblings.decBinOct; + const isAllowedSibling = radix === 16 ? isAllowedNumericSeparatorSibling.hex : radix === 10 ? isAllowedNumericSeparatorSibling.dec : radix === 8 ? isAllowedNumericSeparatorSibling.oct : isAllowedNumericSeparatorSibling.bin; + let invalid = false; + let total = 0; + for (let i = 0, e = len == null ? Infinity : len; i < e; ++i) { + const code = input.charCodeAt(pos); + let val; + if (code === 95 && allowNumSeparator !== "bail") { + const prev = input.charCodeAt(pos - 1); + const next = input.charCodeAt(pos + 1); + if (!allowNumSeparator) { + if (bailOnError) return { + n: null, + pos + }; + errors.numericSeparatorInEscapeSequence(pos, lineStart, curLine); + } else if (Number.isNaN(next) || !isAllowedSibling(next) || forbiddenSiblings.has(prev) || forbiddenSiblings.has(next)) { + if (bailOnError) return { + n: null, + pos + }; + errors.unexpectedNumericSeparator(pos, lineStart, curLine); + } + ++pos; + continue; + } + if (code >= 97) { + val = code - 97 + 10; + } else if (code >= 65) { + val = code - 65 + 10; + } else if (_isDigit(code)) { + val = code - 48; + } else { + val = Infinity; + } + if (val >= radix) { + if (val <= 9 && bailOnError) { + return { + n: null, + pos + }; + } else if (val <= 9 && errors.invalidDigit(pos, lineStart, curLine, radix)) { + val = 0; + } else if (forceLen) { + val = 0; + invalid = true; + } else { + break; + } + } + ++pos; + total = total * radix + val; + } + if (pos === start || len != null && pos - start !== len || invalid) { + return { + n: null, + pos + }; + } + return { + n: total, + pos + }; +} +function readCodePoint(input, pos, lineStart, curLine, throwOnInvalid, errors) { + const ch = input.charCodeAt(pos); + let code; + if (ch === 123) { + ++pos; + ({ + code, + pos + } = readHexChar(input, pos, lineStart, curLine, input.indexOf("}", pos) - pos, true, throwOnInvalid, errors)); + ++pos; + if (code !== null && code > 0x10ffff) { + if (throwOnInvalid) { + errors.invalidCodePoint(pos, lineStart, curLine); + } else { + return { + code: null, + pos + }; + } + } + } else { + ({ + code, + pos + } = readHexChar(input, pos, lineStart, curLine, 4, false, throwOnInvalid, errors)); + } + return { + code, + pos + }; +} +function buildPosition(pos, lineStart, curLine) { + return new Position(curLine, pos - lineStart, pos); +} +const VALID_REGEX_FLAGS = new Set([103, 109, 115, 105, 121, 117, 100, 118]); +class Token { + constructor(state) { + const startIndex = state.startIndex || 0; + this.type = state.type; + this.value = state.value; + this.start = startIndex + state.start; + this.end = startIndex + state.end; + this.loc = new SourceLocation(state.startLoc, state.endLoc); + } +} +class Tokenizer extends CommentsParser { + constructor(options, input) { + super(); + this.isLookahead = void 0; + this.tokens = []; + this.errorHandlers_readInt = { + invalidDigit: (pos, lineStart, curLine, radix) => { + if (!(this.optionFlags & 2048)) return false; + this.raise(Errors.InvalidDigit, buildPosition(pos, lineStart, curLine), { + radix + }); + return true; + }, + numericSeparatorInEscapeSequence: this.errorBuilder(Errors.NumericSeparatorInEscapeSequence), + unexpectedNumericSeparator: this.errorBuilder(Errors.UnexpectedNumericSeparator) + }; + this.errorHandlers_readCodePoint = Object.assign({}, this.errorHandlers_readInt, { + invalidEscapeSequence: this.errorBuilder(Errors.InvalidEscapeSequence), + invalidCodePoint: this.errorBuilder(Errors.InvalidCodePoint) + }); + this.errorHandlers_readStringContents_string = Object.assign({}, this.errorHandlers_readCodePoint, { + strictNumericEscape: (pos, lineStart, curLine) => { + this.recordStrictModeErrors(Errors.StrictNumericEscape, buildPosition(pos, lineStart, curLine)); + }, + unterminated: (pos, lineStart, curLine) => { + throw this.raise(Errors.UnterminatedString, buildPosition(pos - 1, lineStart, curLine)); + } + }); + this.errorHandlers_readStringContents_template = Object.assign({}, this.errorHandlers_readCodePoint, { + strictNumericEscape: this.errorBuilder(Errors.StrictNumericEscape), + unterminated: (pos, lineStart, curLine) => { + throw this.raise(Errors.UnterminatedTemplate, buildPosition(pos, lineStart, curLine)); + } + }); + this.state = new State(); + this.state.init(options); + this.input = input; + this.length = input.length; + this.comments = []; + this.isLookahead = false; + } + pushToken(token) { + this.tokens.length = this.state.tokensLength; + this.tokens.push(token); + ++this.state.tokensLength; + } + next() { + this.checkKeywordEscapes(); + if (this.optionFlags & 256) { + this.pushToken(new Token(this.state)); + } + this.state.lastTokEndLoc = this.state.endLoc; + this.state.lastTokStartLoc = this.state.startLoc; + this.nextToken(); + } + eat(type) { + if (this.match(type)) { + this.next(); + return true; + } else { + return false; + } + } + match(type) { + return this.state.type === type; + } + createLookaheadState(state) { + return { + pos: state.pos, + value: null, + type: state.type, + start: state.start, + end: state.end, + context: [this.curContext()], + inType: state.inType, + startLoc: state.startLoc, + lastTokEndLoc: state.lastTokEndLoc, + curLine: state.curLine, + lineStart: state.lineStart, + curPosition: state.curPosition + }; + } + lookahead() { + const old = this.state; + this.state = this.createLookaheadState(old); + this.isLookahead = true; + this.nextToken(); + this.isLookahead = false; + const curr = this.state; + this.state = old; + return curr; + } + nextTokenStart() { + return this.nextTokenStartSince(this.state.pos); + } + nextTokenStartSince(pos) { + skipWhiteSpace.lastIndex = pos; + return skipWhiteSpace.test(this.input) ? skipWhiteSpace.lastIndex : pos; + } + lookaheadCharCode() { + return this.lookaheadCharCodeSince(this.state.pos); + } + lookaheadCharCodeSince(pos) { + return this.input.charCodeAt(this.nextTokenStartSince(pos)); + } + nextTokenInLineStart() { + return this.nextTokenInLineStartSince(this.state.pos); + } + nextTokenInLineStartSince(pos) { + skipWhiteSpaceInLine.lastIndex = pos; + return skipWhiteSpaceInLine.test(this.input) ? skipWhiteSpaceInLine.lastIndex : pos; + } + lookaheadInLineCharCode() { + return this.input.charCodeAt(this.nextTokenInLineStart()); + } + codePointAtPos(pos) { + let cp = this.input.charCodeAt(pos); + if ((cp & 0xfc00) === 0xd800 && ++pos < this.input.length) { + const trail = this.input.charCodeAt(pos); + if ((trail & 0xfc00) === 0xdc00) { + cp = 0x10000 + ((cp & 0x3ff) << 10) + (trail & 0x3ff); + } + } + return cp; + } + setStrict(strict) { + this.state.strict = strict; + if (strict) { + this.state.strictErrors.forEach(([toParseError, at]) => this.raise(toParseError, at)); + this.state.strictErrors.clear(); + } + } + curContext() { + return this.state.context[this.state.context.length - 1]; + } + nextToken() { + this.skipSpace(); + this.state.start = this.state.pos; + if (!this.isLookahead) this.state.startLoc = this.state.curPosition(); + if (this.state.pos >= this.length) { + this.finishToken(140); + return; + } + this.getTokenFromCode(this.codePointAtPos(this.state.pos)); + } + skipBlockComment(commentEnd) { + let startLoc; + if (!this.isLookahead) startLoc = this.state.curPosition(); + const start = this.state.pos; + const end = this.input.indexOf(commentEnd, start + 2); + if (end === -1) { + throw this.raise(Errors.UnterminatedComment, this.state.curPosition()); + } + this.state.pos = end + commentEnd.length; + lineBreakG.lastIndex = start + 2; + while (lineBreakG.test(this.input) && lineBreakG.lastIndex <= end) { + ++this.state.curLine; + this.state.lineStart = lineBreakG.lastIndex; + } + if (this.isLookahead) return; + const comment = { + type: "CommentBlock", + value: this.input.slice(start + 2, end), + start: this.sourceToOffsetPos(start), + end: this.sourceToOffsetPos(end + commentEnd.length), + loc: new SourceLocation(startLoc, this.state.curPosition()) + }; + if (this.optionFlags & 256) this.pushToken(comment); + return comment; + } + skipLineComment(startSkip) { + const start = this.state.pos; + let startLoc; + if (!this.isLookahead) startLoc = this.state.curPosition(); + let ch = this.input.charCodeAt(this.state.pos += startSkip); + if (this.state.pos < this.length) { + while (!isNewLine(ch) && ++this.state.pos < this.length) { + ch = this.input.charCodeAt(this.state.pos); + } + } + if (this.isLookahead) return; + const end = this.state.pos; + const value = this.input.slice(start + startSkip, end); + const comment = { + type: "CommentLine", + value, + start: this.sourceToOffsetPos(start), + end: this.sourceToOffsetPos(end), + loc: new SourceLocation(startLoc, this.state.curPosition()) + }; + if (this.optionFlags & 256) this.pushToken(comment); + return comment; + } + skipSpace() { + const spaceStart = this.state.pos; + const comments = this.optionFlags & 4096 ? [] : null; + loop: while (this.state.pos < this.length) { + const ch = this.input.charCodeAt(this.state.pos); + switch (ch) { + case 32: + case 160: + case 9: + ++this.state.pos; + break; + case 13: + if (this.input.charCodeAt(this.state.pos + 1) === 10) { + ++this.state.pos; + } + case 10: + case 8232: + case 8233: + ++this.state.pos; + ++this.state.curLine; + this.state.lineStart = this.state.pos; + break; + case 47: + switch (this.input.charCodeAt(this.state.pos + 1)) { + case 42: + { + const comment = this.skipBlockComment("*/"); + if (comment !== undefined) { + this.addComment(comment); + comments == null || comments.push(comment); + } + break; + } + case 47: + { + const comment = this.skipLineComment(2); + if (comment !== undefined) { + this.addComment(comment); + comments == null || comments.push(comment); + } + break; + } + default: + break loop; + } + break; + default: + if (isWhitespace(ch)) { + ++this.state.pos; + } else if (ch === 45 && !this.inModule && this.optionFlags & 8192) { + const pos = this.state.pos; + if (this.input.charCodeAt(pos + 1) === 45 && this.input.charCodeAt(pos + 2) === 62 && (spaceStart === 0 || this.state.lineStart > spaceStart)) { + const comment = this.skipLineComment(3); + if (comment !== undefined) { + this.addComment(comment); + comments == null || comments.push(comment); + } + } else { + break loop; + } + } else if (ch === 60 && !this.inModule && this.optionFlags & 8192) { + const pos = this.state.pos; + if (this.input.charCodeAt(pos + 1) === 33 && this.input.charCodeAt(pos + 2) === 45 && this.input.charCodeAt(pos + 3) === 45) { + const comment = this.skipLineComment(4); + if (comment !== undefined) { + this.addComment(comment); + comments == null || comments.push(comment); + } + } else { + break loop; + } + } else { + break loop; + } + } + } + if ((comments == null ? void 0 : comments.length) > 0) { + const end = this.state.pos; + const commentWhitespace = { + start: this.sourceToOffsetPos(spaceStart), + end: this.sourceToOffsetPos(end), + comments, + leadingNode: null, + trailingNode: null, + containingNode: null + }; + this.state.commentStack.push(commentWhitespace); + } + } + finishToken(type, val) { + this.state.end = this.state.pos; + this.state.endLoc = this.state.curPosition(); + const prevType = this.state.type; + this.state.type = type; + this.state.value = val; + if (!this.isLookahead) { + this.updateContext(prevType); + } + } + replaceToken(type) { + this.state.type = type; + this.updateContext(); + } + readToken_numberSign() { + if (this.state.pos === 0 && this.readToken_interpreter()) { + return; + } + const nextPos = this.state.pos + 1; + const next = this.codePointAtPos(nextPos); + if (next >= 48 && next <= 57) { + throw this.raise(Errors.UnexpectedDigitAfterHash, this.state.curPosition()); + } + if (next === 123 || next === 91 && this.hasPlugin("recordAndTuple")) { + this.expectPlugin("recordAndTuple"); + if (this.getPluginOption("recordAndTuple", "syntaxType") === "bar") { + throw this.raise(next === 123 ? Errors.RecordExpressionHashIncorrectStartSyntaxType : Errors.TupleExpressionHashIncorrectStartSyntaxType, this.state.curPosition()); + } + this.state.pos += 2; + if (next === 123) { + this.finishToken(7); + } else { + this.finishToken(1); + } + } else if (isIdentifierStart(next)) { + ++this.state.pos; + this.finishToken(139, this.readWord1(next)); + } else if (next === 92) { + ++this.state.pos; + this.finishToken(139, this.readWord1()); + } else { + this.finishOp(27, 1); + } + } + readToken_dot() { + const next = this.input.charCodeAt(this.state.pos + 1); + if (next >= 48 && next <= 57) { + this.readNumber(true); + return; + } + if (next === 46 && this.input.charCodeAt(this.state.pos + 2) === 46) { + this.state.pos += 3; + this.finishToken(21); + } else { + ++this.state.pos; + this.finishToken(16); + } + } + readToken_slash() { + const next = this.input.charCodeAt(this.state.pos + 1); + if (next === 61) { + this.finishOp(31, 2); + } else { + this.finishOp(56, 1); + } + } + readToken_interpreter() { + if (this.state.pos !== 0 || this.length < 2) return false; + let ch = this.input.charCodeAt(this.state.pos + 1); + if (ch !== 33) return false; + const start = this.state.pos; + this.state.pos += 1; + while (!isNewLine(ch) && ++this.state.pos < this.length) { + ch = this.input.charCodeAt(this.state.pos); + } + const value = this.input.slice(start + 2, this.state.pos); + this.finishToken(28, value); + return true; + } + readToken_mult_modulo(code) { + let type = code === 42 ? 55 : 54; + let width = 1; + let next = this.input.charCodeAt(this.state.pos + 1); + if (code === 42 && next === 42) { + width++; + next = this.input.charCodeAt(this.state.pos + 2); + type = 57; + } + if (next === 61 && !this.state.inType) { + width++; + type = code === 37 ? 33 : 30; + } + this.finishOp(type, width); + } + readToken_pipe_amp(code) { + const next = this.input.charCodeAt(this.state.pos + 1); + if (next === code) { + if (this.input.charCodeAt(this.state.pos + 2) === 61) { + this.finishOp(30, 3); + } else { + this.finishOp(code === 124 ? 41 : 42, 2); + } + return; + } + if (code === 124) { + if (next === 62) { + this.finishOp(39, 2); + return; + } + if (this.hasPlugin("recordAndTuple") && next === 125) { + if (this.getPluginOption("recordAndTuple", "syntaxType") !== "bar") { + throw this.raise(Errors.RecordExpressionBarIncorrectEndSyntaxType, this.state.curPosition()); + } + this.state.pos += 2; + this.finishToken(9); + return; + } + if (this.hasPlugin("recordAndTuple") && next === 93) { + if (this.getPluginOption("recordAndTuple", "syntaxType") !== "bar") { + throw this.raise(Errors.TupleExpressionBarIncorrectEndSyntaxType, this.state.curPosition()); + } + this.state.pos += 2; + this.finishToken(4); + return; + } + } + if (next === 61) { + this.finishOp(30, 2); + return; + } + this.finishOp(code === 124 ? 43 : 45, 1); + } + readToken_caret() { + const next = this.input.charCodeAt(this.state.pos + 1); + if (next === 61 && !this.state.inType) { + this.finishOp(32, 2); + } else if (next === 94 && this.hasPlugin(["pipelineOperator", { + proposal: "hack", + topicToken: "^^" + }])) { + this.finishOp(37, 2); + const lookaheadCh = this.input.codePointAt(this.state.pos); + if (lookaheadCh === 94) { + this.unexpected(); + } + } else { + this.finishOp(44, 1); + } + } + readToken_atSign() { + const next = this.input.charCodeAt(this.state.pos + 1); + if (next === 64 && this.hasPlugin(["pipelineOperator", { + proposal: "hack", + topicToken: "@@" + }])) { + this.finishOp(38, 2); + } else { + this.finishOp(26, 1); + } + } + readToken_plus_min(code) { + const next = this.input.charCodeAt(this.state.pos + 1); + if (next === code) { + this.finishOp(34, 2); + return; + } + if (next === 61) { + this.finishOp(30, 2); + } else { + this.finishOp(53, 1); + } + } + readToken_lt() { + const { + pos + } = this.state; + const next = this.input.charCodeAt(pos + 1); + if (next === 60) { + if (this.input.charCodeAt(pos + 2) === 61) { + this.finishOp(30, 3); + return; + } + this.finishOp(51, 2); + return; + } + if (next === 61) { + this.finishOp(49, 2); + return; + } + this.finishOp(47, 1); + } + readToken_gt() { + const { + pos + } = this.state; + const next = this.input.charCodeAt(pos + 1); + if (next === 62) { + const size = this.input.charCodeAt(pos + 2) === 62 ? 3 : 2; + if (this.input.charCodeAt(pos + size) === 61) { + this.finishOp(30, size + 1); + return; + } + this.finishOp(52, size); + return; + } + if (next === 61) { + this.finishOp(49, 2); + return; + } + this.finishOp(48, 1); + } + readToken_eq_excl(code) { + const next = this.input.charCodeAt(this.state.pos + 1); + if (next === 61) { + this.finishOp(46, this.input.charCodeAt(this.state.pos + 2) === 61 ? 3 : 2); + return; + } + if (code === 61 && next === 62) { + this.state.pos += 2; + this.finishToken(19); + return; + } + this.finishOp(code === 61 ? 29 : 35, 1); + } + readToken_question() { + const next = this.input.charCodeAt(this.state.pos + 1); + const next2 = this.input.charCodeAt(this.state.pos + 2); + if (next === 63) { + if (next2 === 61) { + this.finishOp(30, 3); + } else { + this.finishOp(40, 2); + } + } else if (next === 46 && !(next2 >= 48 && next2 <= 57)) { + this.state.pos += 2; + this.finishToken(18); + } else { + ++this.state.pos; + this.finishToken(17); + } + } + getTokenFromCode(code) { + switch (code) { + case 46: + this.readToken_dot(); + return; + case 40: + ++this.state.pos; + this.finishToken(10); + return; + case 41: + ++this.state.pos; + this.finishToken(11); + return; + case 59: + ++this.state.pos; + this.finishToken(13); + return; + case 44: + ++this.state.pos; + this.finishToken(12); + return; + case 91: + if (this.hasPlugin("recordAndTuple") && this.input.charCodeAt(this.state.pos + 1) === 124) { + if (this.getPluginOption("recordAndTuple", "syntaxType") !== "bar") { + throw this.raise(Errors.TupleExpressionBarIncorrectStartSyntaxType, this.state.curPosition()); + } + this.state.pos += 2; + this.finishToken(2); + } else { + ++this.state.pos; + this.finishToken(0); + } + return; + case 93: + ++this.state.pos; + this.finishToken(3); + return; + case 123: + if (this.hasPlugin("recordAndTuple") && this.input.charCodeAt(this.state.pos + 1) === 124) { + if (this.getPluginOption("recordAndTuple", "syntaxType") !== "bar") { + throw this.raise(Errors.RecordExpressionBarIncorrectStartSyntaxType, this.state.curPosition()); + } + this.state.pos += 2; + this.finishToken(6); + } else { + ++this.state.pos; + this.finishToken(5); + } + return; + case 125: + ++this.state.pos; + this.finishToken(8); + return; + case 58: + if (this.hasPlugin("functionBind") && this.input.charCodeAt(this.state.pos + 1) === 58) { + this.finishOp(15, 2); + } else { + ++this.state.pos; + this.finishToken(14); + } + return; + case 63: + this.readToken_question(); + return; + case 96: + this.readTemplateToken(); + return; + case 48: + { + const next = this.input.charCodeAt(this.state.pos + 1); + if (next === 120 || next === 88) { + this.readRadixNumber(16); + return; + } + if (next === 111 || next === 79) { + this.readRadixNumber(8); + return; + } + if (next === 98 || next === 66) { + this.readRadixNumber(2); + return; + } + } + case 49: + case 50: + case 51: + case 52: + case 53: + case 54: + case 55: + case 56: + case 57: + this.readNumber(false); + return; + case 34: + case 39: + this.readString(code); + return; + case 47: + this.readToken_slash(); + return; + case 37: + case 42: + this.readToken_mult_modulo(code); + return; + case 124: + case 38: + this.readToken_pipe_amp(code); + return; + case 94: + this.readToken_caret(); + return; + case 43: + case 45: + this.readToken_plus_min(code); + return; + case 60: + this.readToken_lt(); + return; + case 62: + this.readToken_gt(); + return; + case 61: + case 33: + this.readToken_eq_excl(code); + return; + case 126: + this.finishOp(36, 1); + return; + case 64: + this.readToken_atSign(); + return; + case 35: + this.readToken_numberSign(); + return; + case 92: + this.readWord(); + return; + default: + if (isIdentifierStart(code)) { + this.readWord(code); + return; + } + } + throw this.raise(Errors.InvalidOrUnexpectedToken, this.state.curPosition(), { + unexpected: String.fromCodePoint(code) + }); + } + finishOp(type, size) { + const str = this.input.slice(this.state.pos, this.state.pos + size); + this.state.pos += size; + this.finishToken(type, str); + } + readRegexp() { + const startLoc = this.state.startLoc; + const start = this.state.start + 1; + let escaped, inClass; + let { + pos + } = this.state; + for (;; ++pos) { + if (pos >= this.length) { + throw this.raise(Errors.UnterminatedRegExp, createPositionWithColumnOffset(startLoc, 1)); + } + const ch = this.input.charCodeAt(pos); + if (isNewLine(ch)) { + throw this.raise(Errors.UnterminatedRegExp, createPositionWithColumnOffset(startLoc, 1)); + } + if (escaped) { + escaped = false; + } else { + if (ch === 91) { + inClass = true; + } else if (ch === 93 && inClass) { + inClass = false; + } else if (ch === 47 && !inClass) { + break; + } + escaped = ch === 92; + } + } + const content = this.input.slice(start, pos); + ++pos; + let mods = ""; + const nextPos = () => createPositionWithColumnOffset(startLoc, pos + 2 - start); + while (pos < this.length) { + const cp = this.codePointAtPos(pos); + const char = String.fromCharCode(cp); + if (VALID_REGEX_FLAGS.has(cp)) { + if (cp === 118) { + if (mods.includes("u")) { + this.raise(Errors.IncompatibleRegExpUVFlags, nextPos()); + } + } else if (cp === 117) { + if (mods.includes("v")) { + this.raise(Errors.IncompatibleRegExpUVFlags, nextPos()); + } + } + if (mods.includes(char)) { + this.raise(Errors.DuplicateRegExpFlags, nextPos()); + } + } else if (isIdentifierChar(cp) || cp === 92) { + this.raise(Errors.MalformedRegExpFlags, nextPos()); + } else { + break; + } + ++pos; + mods += char; + } + this.state.pos = pos; + this.finishToken(138, { + pattern: content, + flags: mods + }); + } + readInt(radix, len, forceLen = false, allowNumSeparator = true) { + const { + n, + pos + } = readInt(this.input, this.state.pos, this.state.lineStart, this.state.curLine, radix, len, forceLen, allowNumSeparator, this.errorHandlers_readInt, false); + this.state.pos = pos; + return n; + } + readRadixNumber(radix) { + const start = this.state.pos; + const startLoc = this.state.curPosition(); + let isBigInt = false; + this.state.pos += 2; + const val = this.readInt(radix); + if (val == null) { + this.raise(Errors.InvalidDigit, createPositionWithColumnOffset(startLoc, 2), { + radix + }); + } + const next = this.input.charCodeAt(this.state.pos); + if (next === 110) { + ++this.state.pos; + isBigInt = true; + } else if (next === 109) { + throw this.raise(Errors.InvalidDecimal, startLoc); + } + if (isIdentifierStart(this.codePointAtPos(this.state.pos))) { + throw this.raise(Errors.NumberIdentifier, this.state.curPosition()); + } + if (isBigInt) { + const str = this.input.slice(start, this.state.pos).replace(/[_n]/g, ""); + this.finishToken(136, str); + return; + } + this.finishToken(135, val); + } + readNumber(startsWithDot) { + const start = this.state.pos; + const startLoc = this.state.curPosition(); + let isFloat = false; + let isBigInt = false; + let hasExponent = false; + let isOctal = false; + if (!startsWithDot && this.readInt(10) === null) { + this.raise(Errors.InvalidNumber, this.state.curPosition()); + } + const hasLeadingZero = this.state.pos - start >= 2 && this.input.charCodeAt(start) === 48; + if (hasLeadingZero) { + const integer = this.input.slice(start, this.state.pos); + this.recordStrictModeErrors(Errors.StrictOctalLiteral, startLoc); + if (!this.state.strict) { + const underscorePos = integer.indexOf("_"); + if (underscorePos > 0) { + this.raise(Errors.ZeroDigitNumericSeparator, createPositionWithColumnOffset(startLoc, underscorePos)); + } + } + isOctal = hasLeadingZero && !/[89]/.test(integer); + } + let next = this.input.charCodeAt(this.state.pos); + if (next === 46 && !isOctal) { + ++this.state.pos; + this.readInt(10); + isFloat = true; + next = this.input.charCodeAt(this.state.pos); + } + if ((next === 69 || next === 101) && !isOctal) { + next = this.input.charCodeAt(++this.state.pos); + if (next === 43 || next === 45) { + ++this.state.pos; + } + if (this.readInt(10) === null) { + this.raise(Errors.InvalidOrMissingExponent, startLoc); + } + isFloat = true; + hasExponent = true; + next = this.input.charCodeAt(this.state.pos); + } + if (next === 110) { + if (isFloat || hasLeadingZero) { + this.raise(Errors.InvalidBigIntLiteral, startLoc); + } + ++this.state.pos; + isBigInt = true; + } + if (next === 109) { + this.expectPlugin("decimal", this.state.curPosition()); + if (hasExponent || hasLeadingZero) { + this.raise(Errors.InvalidDecimal, startLoc); + } + ++this.state.pos; + var isDecimal = true; + } + if (isIdentifierStart(this.codePointAtPos(this.state.pos))) { + throw this.raise(Errors.NumberIdentifier, this.state.curPosition()); + } + const str = this.input.slice(start, this.state.pos).replace(/[_mn]/g, ""); + if (isBigInt) { + this.finishToken(136, str); + return; + } + if (isDecimal) { + this.finishToken(137, str); + return; + } + const val = isOctal ? parseInt(str, 8) : parseFloat(str); + this.finishToken(135, val); + } + readCodePoint(throwOnInvalid) { + const { + code, + pos + } = readCodePoint(this.input, this.state.pos, this.state.lineStart, this.state.curLine, throwOnInvalid, this.errorHandlers_readCodePoint); + this.state.pos = pos; + return code; + } + readString(quote) { + const { + str, + pos, + curLine, + lineStart + } = readStringContents(quote === 34 ? "double" : "single", this.input, this.state.pos + 1, this.state.lineStart, this.state.curLine, this.errorHandlers_readStringContents_string); + this.state.pos = pos + 1; + this.state.lineStart = lineStart; + this.state.curLine = curLine; + this.finishToken(134, str); + } + readTemplateContinuation() { + if (!this.match(8)) { + this.unexpected(null, 8); + } + this.state.pos--; + this.readTemplateToken(); + } + readTemplateToken() { + const opening = this.input[this.state.pos]; + const { + str, + firstInvalidLoc, + pos, + curLine, + lineStart + } = readStringContents("template", this.input, this.state.pos + 1, this.state.lineStart, this.state.curLine, this.errorHandlers_readStringContents_template); + this.state.pos = pos + 1; + this.state.lineStart = lineStart; + this.state.curLine = curLine; + if (firstInvalidLoc) { + this.state.firstInvalidTemplateEscapePos = new Position(firstInvalidLoc.curLine, firstInvalidLoc.pos - firstInvalidLoc.lineStart, this.sourceToOffsetPos(firstInvalidLoc.pos)); + } + if (this.input.codePointAt(pos) === 96) { + this.finishToken(24, firstInvalidLoc ? null : opening + str + "`"); + } else { + this.state.pos++; + this.finishToken(25, firstInvalidLoc ? null : opening + str + "${"); + } + } + recordStrictModeErrors(toParseError, at) { + const index = at.index; + if (this.state.strict && !this.state.strictErrors.has(index)) { + this.raise(toParseError, at); + } else { + this.state.strictErrors.set(index, [toParseError, at]); + } + } + readWord1(firstCode) { + this.state.containsEsc = false; + let word = ""; + const start = this.state.pos; + let chunkStart = this.state.pos; + if (firstCode !== undefined) { + this.state.pos += firstCode <= 0xffff ? 1 : 2; + } + while (this.state.pos < this.length) { + const ch = this.codePointAtPos(this.state.pos); + if (isIdentifierChar(ch)) { + this.state.pos += ch <= 0xffff ? 1 : 2; + } else if (ch === 92) { + this.state.containsEsc = true; + word += this.input.slice(chunkStart, this.state.pos); + const escStart = this.state.curPosition(); + const identifierCheck = this.state.pos === start ? isIdentifierStart : isIdentifierChar; + if (this.input.charCodeAt(++this.state.pos) !== 117) { + this.raise(Errors.MissingUnicodeEscape, this.state.curPosition()); + chunkStart = this.state.pos - 1; + continue; + } + ++this.state.pos; + const esc = this.readCodePoint(true); + if (esc !== null) { + if (!identifierCheck(esc)) { + this.raise(Errors.EscapedCharNotAnIdentifier, escStart); + } + word += String.fromCodePoint(esc); + } + chunkStart = this.state.pos; + } else { + break; + } + } + return word + this.input.slice(chunkStart, this.state.pos); + } + readWord(firstCode) { + const word = this.readWord1(firstCode); + const type = keywords$1.get(word); + if (type !== undefined) { + this.finishToken(type, tokenLabelName(type)); + } else { + this.finishToken(132, word); + } + } + checkKeywordEscapes() { + const { + type + } = this.state; + if (tokenIsKeyword(type) && this.state.containsEsc) { + this.raise(Errors.InvalidEscapedReservedWord, this.state.startLoc, { + reservedWord: tokenLabelName(type) + }); + } + } + raise(toParseError, at, details = {}) { + const loc = at instanceof Position ? at : at.loc.start; + const error = toParseError(loc, details); + if (!(this.optionFlags & 2048)) throw error; + if (!this.isLookahead) this.state.errors.push(error); + return error; + } + raiseOverwrite(toParseError, at, details = {}) { + const loc = at instanceof Position ? at : at.loc.start; + const pos = loc.index; + const errors = this.state.errors; + for (let i = errors.length - 1; i >= 0; i--) { + const error = errors[i]; + if (error.loc.index === pos) { + return errors[i] = toParseError(loc, details); + } + if (error.loc.index < pos) break; + } + return this.raise(toParseError, at, details); + } + updateContext(prevType) {} + unexpected(loc, type) { + throw this.raise(Errors.UnexpectedToken, loc != null ? loc : this.state.startLoc, { + expected: type ? tokenLabelName(type) : null + }); + } + expectPlugin(pluginName, loc) { + if (this.hasPlugin(pluginName)) { + return true; + } + throw this.raise(Errors.MissingPlugin, loc != null ? loc : this.state.startLoc, { + missingPlugin: [pluginName] + }); + } + expectOnePlugin(pluginNames) { + if (!pluginNames.some(name => this.hasPlugin(name))) { + throw this.raise(Errors.MissingOneOfPlugins, this.state.startLoc, { + missingPlugin: pluginNames + }); + } + } + errorBuilder(error) { + return (pos, lineStart, curLine) => { + this.raise(error, buildPosition(pos, lineStart, curLine)); + }; + } +} +class ClassScope { + constructor() { + this.privateNames = new Set(); + this.loneAccessors = new Map(); + this.undefinedPrivateNames = new Map(); + } +} +class ClassScopeHandler { + constructor(parser) { + this.parser = void 0; + this.stack = []; + this.undefinedPrivateNames = new Map(); + this.parser = parser; + } + current() { + return this.stack[this.stack.length - 1]; + } + enter() { + this.stack.push(new ClassScope()); + } + exit() { + const oldClassScope = this.stack.pop(); + const current = this.current(); + for (const [name, loc] of Array.from(oldClassScope.undefinedPrivateNames)) { + if (current) { + if (!current.undefinedPrivateNames.has(name)) { + current.undefinedPrivateNames.set(name, loc); + } + } else { + this.parser.raise(Errors.InvalidPrivateFieldResolution, loc, { + identifierName: name + }); + } + } + } + declarePrivateName(name, elementType, loc) { + const { + privateNames, + loneAccessors, + undefinedPrivateNames + } = this.current(); + let redefined = privateNames.has(name); + if (elementType & 3) { + const accessor = redefined && loneAccessors.get(name); + if (accessor) { + const oldStatic = accessor & 4; + const newStatic = elementType & 4; + const oldKind = accessor & 3; + const newKind = elementType & 3; + redefined = oldKind === newKind || oldStatic !== newStatic; + if (!redefined) loneAccessors.delete(name); + } else if (!redefined) { + loneAccessors.set(name, elementType); + } + } + if (redefined) { + this.parser.raise(Errors.PrivateNameRedeclaration, loc, { + identifierName: name + }); + } + privateNames.add(name); + undefinedPrivateNames.delete(name); + } + usePrivateName(name, loc) { + let classScope; + for (classScope of this.stack) { + if (classScope.privateNames.has(name)) return; + } + if (classScope) { + classScope.undefinedPrivateNames.set(name, loc); + } else { + this.parser.raise(Errors.InvalidPrivateFieldResolution, loc, { + identifierName: name + }); + } + } +} +class ExpressionScope { + constructor(type = 0) { + this.type = type; + } + canBeArrowParameterDeclaration() { + return this.type === 2 || this.type === 1; + } + isCertainlyParameterDeclaration() { + return this.type === 3; + } +} +class ArrowHeadParsingScope extends ExpressionScope { + constructor(type) { + super(type); + this.declarationErrors = new Map(); + } + recordDeclarationError(ParsingErrorClass, at) { + const index = at.index; + this.declarationErrors.set(index, [ParsingErrorClass, at]); + } + clearDeclarationError(index) { + this.declarationErrors.delete(index); + } + iterateErrors(iterator) { + this.declarationErrors.forEach(iterator); + } +} +class ExpressionScopeHandler { + constructor(parser) { + this.parser = void 0; + this.stack = [new ExpressionScope()]; + this.parser = parser; + } + enter(scope) { + this.stack.push(scope); + } + exit() { + this.stack.pop(); + } + recordParameterInitializerError(toParseError, node) { + const origin = node.loc.start; + const { + stack + } = this; + let i = stack.length - 1; + let scope = stack[i]; + while (!scope.isCertainlyParameterDeclaration()) { + if (scope.canBeArrowParameterDeclaration()) { + scope.recordDeclarationError(toParseError, origin); + } else { + return; + } + scope = stack[--i]; + } + this.parser.raise(toParseError, origin); + } + recordArrowParameterBindingError(error, node) { + const { + stack + } = this; + const scope = stack[stack.length - 1]; + const origin = node.loc.start; + if (scope.isCertainlyParameterDeclaration()) { + this.parser.raise(error, origin); + } else if (scope.canBeArrowParameterDeclaration()) { + scope.recordDeclarationError(error, origin); + } else { + return; + } + } + recordAsyncArrowParametersError(at) { + const { + stack + } = this; + let i = stack.length - 1; + let scope = stack[i]; + while (scope.canBeArrowParameterDeclaration()) { + if (scope.type === 2) { + scope.recordDeclarationError(Errors.AwaitBindingIdentifier, at); + } + scope = stack[--i]; + } + } + validateAsPattern() { + const { + stack + } = this; + const currentScope = stack[stack.length - 1]; + if (!currentScope.canBeArrowParameterDeclaration()) return; + currentScope.iterateErrors(([toParseError, loc]) => { + this.parser.raise(toParseError, loc); + let i = stack.length - 2; + let scope = stack[i]; + while (scope.canBeArrowParameterDeclaration()) { + scope.clearDeclarationError(loc.index); + scope = stack[--i]; + } + }); + } +} +function newParameterDeclarationScope() { + return new ExpressionScope(3); +} +function newArrowHeadScope() { + return new ArrowHeadParsingScope(1); +} +function newAsyncArrowScope() { + return new ArrowHeadParsingScope(2); +} +function newExpressionScope() { + return new ExpressionScope(); +} +class UtilParser extends Tokenizer { + addExtra(node, key, value, enumerable = true) { + if (!node) return; + let { + extra + } = node; + if (extra == null) { + extra = {}; + node.extra = extra; + } + if (enumerable) { + extra[key] = value; + } else { + Object.defineProperty(extra, key, { + enumerable, + value + }); + } + } + isContextual(token) { + return this.state.type === token && !this.state.containsEsc; + } + isUnparsedContextual(nameStart, name) { + const nameEnd = nameStart + name.length; + if (this.input.slice(nameStart, nameEnd) === name) { + const nextCh = this.input.charCodeAt(nameEnd); + return !(isIdentifierChar(nextCh) || (nextCh & 0xfc00) === 0xd800); + } + return false; + } + isLookaheadContextual(name) { + const next = this.nextTokenStart(); + return this.isUnparsedContextual(next, name); + } + eatContextual(token) { + if (this.isContextual(token)) { + this.next(); + return true; + } + return false; + } + expectContextual(token, toParseError) { + if (!this.eatContextual(token)) { + if (toParseError != null) { + throw this.raise(toParseError, this.state.startLoc); + } + this.unexpected(null, token); + } + } + canInsertSemicolon() { + return this.match(140) || this.match(8) || this.hasPrecedingLineBreak(); + } + hasPrecedingLineBreak() { + return hasNewLine(this.input, this.offsetToSourcePos(this.state.lastTokEndLoc.index), this.state.start); + } + hasFollowingLineBreak() { + return hasNewLine(this.input, this.state.end, this.nextTokenStart()); + } + isLineTerminator() { + return this.eat(13) || this.canInsertSemicolon(); + } + semicolon(allowAsi = true) { + if (allowAsi ? this.isLineTerminator() : this.eat(13)) return; + this.raise(Errors.MissingSemicolon, this.state.lastTokEndLoc); + } + expect(type, loc) { + if (!this.eat(type)) { + this.unexpected(loc, type); + } + } + tryParse(fn, oldState = this.state.clone()) { + const abortSignal = { + node: null + }; + try { + const node = fn((node = null) => { + abortSignal.node = node; + throw abortSignal; + }); + if (this.state.errors.length > oldState.errors.length) { + const failState = this.state; + this.state = oldState; + this.state.tokensLength = failState.tokensLength; + return { + node, + error: failState.errors[oldState.errors.length], + thrown: false, + aborted: false, + failState + }; + } + return { + node, + error: null, + thrown: false, + aborted: false, + failState: null + }; + } catch (error) { + const failState = this.state; + this.state = oldState; + if (error instanceof SyntaxError) { + return { + node: null, + error, + thrown: true, + aborted: false, + failState + }; + } + if (error === abortSignal) { + return { + node: abortSignal.node, + error: null, + thrown: false, + aborted: true, + failState + }; + } + throw error; + } + } + checkExpressionErrors(refExpressionErrors, andThrow) { + if (!refExpressionErrors) return false; + const { + shorthandAssignLoc, + doubleProtoLoc, + privateKeyLoc, + optionalParametersLoc + } = refExpressionErrors; + const hasErrors = !!shorthandAssignLoc || !!doubleProtoLoc || !!optionalParametersLoc || !!privateKeyLoc; + if (!andThrow) { + return hasErrors; + } + if (shorthandAssignLoc != null) { + this.raise(Errors.InvalidCoverInitializedName, shorthandAssignLoc); + } + if (doubleProtoLoc != null) { + this.raise(Errors.DuplicateProto, doubleProtoLoc); + } + if (privateKeyLoc != null) { + this.raise(Errors.UnexpectedPrivateField, privateKeyLoc); + } + if (optionalParametersLoc != null) { + this.unexpected(optionalParametersLoc); + } + } + isLiteralPropertyName() { + return tokenIsLiteralPropertyName(this.state.type); + } + isPrivateName(node) { + return node.type === "PrivateName"; + } + getPrivateNameSV(node) { + return node.id.name; + } + hasPropertyAsPrivateName(node) { + return (node.type === "MemberExpression" || node.type === "OptionalMemberExpression") && this.isPrivateName(node.property); + } + isObjectProperty(node) { + return node.type === "ObjectProperty"; + } + isObjectMethod(node) { + return node.type === "ObjectMethod"; + } + initializeScopes(inModule = this.options.sourceType === "module") { + const oldLabels = this.state.labels; + this.state.labels = []; + const oldExportedIdentifiers = this.exportedIdentifiers; + this.exportedIdentifiers = new Set(); + const oldInModule = this.inModule; + this.inModule = inModule; + const oldScope = this.scope; + const ScopeHandler = this.getScopeHandler(); + this.scope = new ScopeHandler(this, inModule); + const oldProdParam = this.prodParam; + this.prodParam = new ProductionParameterHandler(); + const oldClassScope = this.classScope; + this.classScope = new ClassScopeHandler(this); + const oldExpressionScope = this.expressionScope; + this.expressionScope = new ExpressionScopeHandler(this); + return () => { + this.state.labels = oldLabels; + this.exportedIdentifiers = oldExportedIdentifiers; + this.inModule = oldInModule; + this.scope = oldScope; + this.prodParam = oldProdParam; + this.classScope = oldClassScope; + this.expressionScope = oldExpressionScope; + }; + } + enterInitialScopes() { + let paramFlags = 0; + if (this.inModule) { + paramFlags |= 2; + } + if (this.optionFlags & 32) { + paramFlags |= 1; + } + this.scope.enter(1); + this.prodParam.enter(paramFlags); + } + checkDestructuringPrivate(refExpressionErrors) { + const { + privateKeyLoc + } = refExpressionErrors; + if (privateKeyLoc !== null) { + this.expectPlugin("destructuringPrivate", privateKeyLoc); + } + } +} +class ExpressionErrors { + constructor() { + this.shorthandAssignLoc = null; + this.doubleProtoLoc = null; + this.privateKeyLoc = null; + this.optionalParametersLoc = null; + } +} +class Node { + constructor(parser, pos, loc) { + this.type = ""; + this.start = pos; + this.end = 0; + this.loc = new SourceLocation(loc); + if ((parser == null ? void 0 : parser.optionFlags) & 128) this.range = [pos, 0]; + if (parser != null && parser.filename) this.loc.filename = parser.filename; + } +} +const NodePrototype = Node.prototype; +{ + NodePrototype.__clone = function () { + const newNode = new Node(undefined, this.start, this.loc.start); + const keys = Object.keys(this); + for (let i = 0, length = keys.length; i < length; i++) { + const key = keys[i]; + if (key !== "leadingComments" && key !== "trailingComments" && key !== "innerComments") { + newNode[key] = this[key]; + } + } + return newNode; + }; +} +class NodeUtils extends UtilParser { + startNode() { + const loc = this.state.startLoc; + return new Node(this, loc.index, loc); + } + startNodeAt(loc) { + return new Node(this, loc.index, loc); + } + startNodeAtNode(type) { + return this.startNodeAt(type.loc.start); + } + finishNode(node, type) { + return this.finishNodeAt(node, type, this.state.lastTokEndLoc); + } + finishNodeAt(node, type, endLoc) { + node.type = type; + node.end = endLoc.index; + node.loc.end = endLoc; + if (this.optionFlags & 128) node.range[1] = endLoc.index; + if (this.optionFlags & 4096) { + this.processComment(node); + } + return node; + } + resetStartLocation(node, startLoc) { + node.start = startLoc.index; + node.loc.start = startLoc; + if (this.optionFlags & 128) node.range[0] = startLoc.index; + } + resetEndLocation(node, endLoc = this.state.lastTokEndLoc) { + node.end = endLoc.index; + node.loc.end = endLoc; + if (this.optionFlags & 128) node.range[1] = endLoc.index; + } + resetStartLocationFromNode(node, locationNode) { + this.resetStartLocation(node, locationNode.loc.start); + } + castNodeTo(node, type) { + node.type = type; + return node; + } + cloneIdentifier(node) { + const { + type, + start, + end, + loc, + range, + name + } = node; + const cloned = Object.create(NodePrototype); + cloned.type = type; + cloned.start = start; + cloned.end = end; + cloned.loc = loc; + cloned.range = range; + cloned.name = name; + if (node.extra) cloned.extra = node.extra; + return cloned; + } + cloneStringLiteral(node) { + const { + type, + start, + end, + loc, + range, + extra + } = node; + const cloned = Object.create(NodePrototype); + cloned.type = type; + cloned.start = start; + cloned.end = end; + cloned.loc = loc; + cloned.range = range; + cloned.extra = extra; + cloned.value = node.value; + return cloned; + } +} +const unwrapParenthesizedExpression = node => { + return node.type === "ParenthesizedExpression" ? unwrapParenthesizedExpression(node.expression) : node; +}; +class LValParser extends NodeUtils { + toAssignable(node, isLHS = false) { + var _node$extra, _node$extra3; + let parenthesized = undefined; + if (node.type === "ParenthesizedExpression" || (_node$extra = node.extra) != null && _node$extra.parenthesized) { + parenthesized = unwrapParenthesizedExpression(node); + if (isLHS) { + if (parenthesized.type === "Identifier") { + this.expressionScope.recordArrowParameterBindingError(Errors.InvalidParenthesizedAssignment, node); + } else if (parenthesized.type !== "MemberExpression" && !this.isOptionalMemberExpression(parenthesized)) { + this.raise(Errors.InvalidParenthesizedAssignment, node); + } + } else { + this.raise(Errors.InvalidParenthesizedAssignment, node); + } + } + switch (node.type) { + case "Identifier": + case "ObjectPattern": + case "ArrayPattern": + case "AssignmentPattern": + case "RestElement": + break; + case "ObjectExpression": + this.castNodeTo(node, "ObjectPattern"); + for (let i = 0, length = node.properties.length, last = length - 1; i < length; i++) { + var _node$extra2; + const prop = node.properties[i]; + const isLast = i === last; + this.toAssignableObjectExpressionProp(prop, isLast, isLHS); + if (isLast && prop.type === "RestElement" && (_node$extra2 = node.extra) != null && _node$extra2.trailingCommaLoc) { + this.raise(Errors.RestTrailingComma, node.extra.trailingCommaLoc); + } + } + break; + case "ObjectProperty": + { + const { + key, + value + } = node; + if (this.isPrivateName(key)) { + this.classScope.usePrivateName(this.getPrivateNameSV(key), key.loc.start); + } + this.toAssignable(value, isLHS); + break; + } + case "SpreadElement": + { + throw new Error("Internal @babel/parser error (this is a bug, please report it)." + " SpreadElement should be converted by .toAssignable's caller."); + } + case "ArrayExpression": + this.castNodeTo(node, "ArrayPattern"); + this.toAssignableList(node.elements, (_node$extra3 = node.extra) == null ? void 0 : _node$extra3.trailingCommaLoc, isLHS); + break; + case "AssignmentExpression": + if (node.operator !== "=") { + this.raise(Errors.MissingEqInAssignment, node.left.loc.end); + } + this.castNodeTo(node, "AssignmentPattern"); + delete node.operator; + this.toAssignable(node.left, isLHS); + break; + case "ParenthesizedExpression": + this.toAssignable(parenthesized, isLHS); + break; + } + } + toAssignableObjectExpressionProp(prop, isLast, isLHS) { + if (prop.type === "ObjectMethod") { + this.raise(prop.kind === "get" || prop.kind === "set" ? Errors.PatternHasAccessor : Errors.PatternHasMethod, prop.key); + } else if (prop.type === "SpreadElement") { + this.castNodeTo(prop, "RestElement"); + const arg = prop.argument; + this.checkToRestConversion(arg, false); + this.toAssignable(arg, isLHS); + if (!isLast) { + this.raise(Errors.RestTrailingComma, prop); + } + } else { + this.toAssignable(prop, isLHS); + } + } + toAssignableList(exprList, trailingCommaLoc, isLHS) { + const end = exprList.length - 1; + for (let i = 0; i <= end; i++) { + const elt = exprList[i]; + if (!elt) continue; + this.toAssignableListItem(exprList, i, isLHS); + if (elt.type === "RestElement") { + if (i < end) { + this.raise(Errors.RestTrailingComma, elt); + } else if (trailingCommaLoc) { + this.raise(Errors.RestTrailingComma, trailingCommaLoc); + } + } + } + } + toAssignableListItem(exprList, index, isLHS) { + const node = exprList[index]; + if (node.type === "SpreadElement") { + this.castNodeTo(node, "RestElement"); + const arg = node.argument; + this.checkToRestConversion(arg, true); + this.toAssignable(arg, isLHS); + } else { + this.toAssignable(node, isLHS); + } + } + isAssignable(node, isBinding) { + switch (node.type) { + case "Identifier": + case "ObjectPattern": + case "ArrayPattern": + case "AssignmentPattern": + case "RestElement": + return true; + case "ObjectExpression": + { + const last = node.properties.length - 1; + return node.properties.every((prop, i) => { + return prop.type !== "ObjectMethod" && (i === last || prop.type !== "SpreadElement") && this.isAssignable(prop); + }); + } + case "ObjectProperty": + return this.isAssignable(node.value); + case "SpreadElement": + return this.isAssignable(node.argument); + case "ArrayExpression": + return node.elements.every(element => element === null || this.isAssignable(element)); + case "AssignmentExpression": + return node.operator === "="; + case "ParenthesizedExpression": + return this.isAssignable(node.expression); + case "MemberExpression": + case "OptionalMemberExpression": + return !isBinding; + default: + return false; + } + } + toReferencedList(exprList, isParenthesizedExpr) { + return exprList; + } + toReferencedListDeep(exprList, isParenthesizedExpr) { + this.toReferencedList(exprList, isParenthesizedExpr); + for (const expr of exprList) { + if ((expr == null ? void 0 : expr.type) === "ArrayExpression") { + this.toReferencedListDeep(expr.elements); + } + } + } + parseSpread(refExpressionErrors) { + const node = this.startNode(); + this.next(); + node.argument = this.parseMaybeAssignAllowIn(refExpressionErrors, undefined); + return this.finishNode(node, "SpreadElement"); + } + parseRestBinding() { + const node = this.startNode(); + this.next(); + node.argument = this.parseBindingAtom(); + return this.finishNode(node, "RestElement"); + } + parseBindingAtom() { + switch (this.state.type) { + case 0: + { + const node = this.startNode(); + this.next(); + node.elements = this.parseBindingList(3, 93, 1); + return this.finishNode(node, "ArrayPattern"); + } + case 5: + return this.parseObjectLike(8, true); + } + return this.parseIdentifier(); + } + parseBindingList(close, closeCharCode, flags) { + const allowEmpty = flags & 1; + const elts = []; + let first = true; + while (!this.eat(close)) { + if (first) { + first = false; + } else { + this.expect(12); + } + if (allowEmpty && this.match(12)) { + elts.push(null); + } else if (this.eat(close)) { + break; + } else if (this.match(21)) { + let rest = this.parseRestBinding(); + if (this.hasPlugin("flow") || flags & 2) { + rest = this.parseFunctionParamType(rest); + } + elts.push(rest); + if (!this.checkCommaAfterRest(closeCharCode)) { + this.expect(close); + break; + } + } else { + const decorators = []; + if (flags & 2) { + if (this.match(26) && this.hasPlugin("decorators")) { + this.raise(Errors.UnsupportedParameterDecorator, this.state.startLoc); + } + while (this.match(26)) { + decorators.push(this.parseDecorator()); + } + } + elts.push(this.parseBindingElement(flags, decorators)); + } + } + return elts; + } + parseBindingRestProperty(prop) { + this.next(); + prop.argument = this.parseIdentifier(); + this.checkCommaAfterRest(125); + return this.finishNode(prop, "RestElement"); + } + parseBindingProperty() { + const { + type, + startLoc + } = this.state; + if (type === 21) { + return this.parseBindingRestProperty(this.startNode()); + } + const prop = this.startNode(); + if (type === 139) { + this.expectPlugin("destructuringPrivate", startLoc); + this.classScope.usePrivateName(this.state.value, startLoc); + prop.key = this.parsePrivateName(); + } else { + this.parsePropertyName(prop); + } + prop.method = false; + return this.parseObjPropValue(prop, startLoc, false, false, true, false); + } + parseBindingElement(flags, decorators) { + const left = this.parseMaybeDefault(); + if (this.hasPlugin("flow") || flags & 2) { + this.parseFunctionParamType(left); + } + if (decorators.length) { + left.decorators = decorators; + this.resetStartLocationFromNode(left, decorators[0]); + } + const elt = this.parseMaybeDefault(left.loc.start, left); + return elt; + } + parseFunctionParamType(param) { + return param; + } + parseMaybeDefault(startLoc, left) { + startLoc != null ? startLoc : startLoc = this.state.startLoc; + left = left != null ? left : this.parseBindingAtom(); + if (!this.eat(29)) return left; + const node = this.startNodeAt(startLoc); + node.left = left; + node.right = this.parseMaybeAssignAllowIn(); + return this.finishNode(node, "AssignmentPattern"); + } + isValidLVal(type, isUnparenthesizedInAssign, binding) { + switch (type) { + case "AssignmentPattern": + return "left"; + case "RestElement": + return "argument"; + case "ObjectProperty": + return "value"; + case "ParenthesizedExpression": + return "expression"; + case "ArrayPattern": + return "elements"; + case "ObjectPattern": + return "properties"; + } + return false; + } + isOptionalMemberExpression(expression) { + return expression.type === "OptionalMemberExpression"; + } + checkLVal(expression, ancestor, binding = 64, checkClashes = false, strictModeChanged = false, hasParenthesizedAncestor = false) { + var _expression$extra; + const type = expression.type; + if (this.isObjectMethod(expression)) return; + const isOptionalMemberExpression = this.isOptionalMemberExpression(expression); + if (isOptionalMemberExpression || type === "MemberExpression") { + if (isOptionalMemberExpression) { + this.expectPlugin("optionalChainingAssign", expression.loc.start); + if (ancestor.type !== "AssignmentExpression") { + this.raise(Errors.InvalidLhsOptionalChaining, expression, { + ancestor + }); + } + } + if (binding !== 64) { + this.raise(Errors.InvalidPropertyBindingPattern, expression); + } + return; + } + if (type === "Identifier") { + this.checkIdentifier(expression, binding, strictModeChanged); + const { + name + } = expression; + if (checkClashes) { + if (checkClashes.has(name)) { + this.raise(Errors.ParamDupe, expression); + } else { + checkClashes.add(name); + } + } + return; + } + const validity = this.isValidLVal(type, !(hasParenthesizedAncestor || (_expression$extra = expression.extra) != null && _expression$extra.parenthesized) && ancestor.type === "AssignmentExpression", binding); + if (validity === true) return; + if (validity === false) { + const ParseErrorClass = binding === 64 ? Errors.InvalidLhs : Errors.InvalidLhsBinding; + this.raise(ParseErrorClass, expression, { + ancestor + }); + return; + } + let key, isParenthesizedExpression; + if (typeof validity === "string") { + key = validity; + isParenthesizedExpression = type === "ParenthesizedExpression"; + } else { + [key, isParenthesizedExpression] = validity; + } + const nextAncestor = type === "ArrayPattern" || type === "ObjectPattern" ? { + type + } : ancestor; + const val = expression[key]; + if (Array.isArray(val)) { + for (const child of val) { + if (child) { + this.checkLVal(child, nextAncestor, binding, checkClashes, strictModeChanged, isParenthesizedExpression); + } + } + } else if (val) { + this.checkLVal(val, nextAncestor, binding, checkClashes, strictModeChanged, isParenthesizedExpression); + } + } + checkIdentifier(at, bindingType, strictModeChanged = false) { + if (this.state.strict && (strictModeChanged ? isStrictBindReservedWord(at.name, this.inModule) : isStrictBindOnlyReservedWord(at.name))) { + if (bindingType === 64) { + this.raise(Errors.StrictEvalArguments, at, { + referenceName: at.name + }); + } else { + this.raise(Errors.StrictEvalArgumentsBinding, at, { + bindingName: at.name + }); + } + } + if (bindingType & 8192 && at.name === "let") { + this.raise(Errors.LetInLexicalBinding, at); + } + if (!(bindingType & 64)) { + this.declareNameFromIdentifier(at, bindingType); + } + } + declareNameFromIdentifier(identifier, binding) { + this.scope.declareName(identifier.name, binding, identifier.loc.start); + } + checkToRestConversion(node, allowPattern) { + switch (node.type) { + case "ParenthesizedExpression": + this.checkToRestConversion(node.expression, allowPattern); + break; + case "Identifier": + case "MemberExpression": + break; + case "ArrayExpression": + case "ObjectExpression": + if (allowPattern) break; + default: + this.raise(Errors.InvalidRestAssignmentPattern, node); + } + } + checkCommaAfterRest(close) { + if (!this.match(12)) { + return false; + } + this.raise(this.lookaheadCharCode() === close ? Errors.RestTrailingComma : Errors.ElementAfterRest, this.state.startLoc); + return true; + } +} +function nonNull(x) { + if (x == null) { + throw new Error(`Unexpected ${x} value.`); + } + return x; +} +function assert(x) { + if (!x) { + throw new Error("Assert fail"); + } +} +const TSErrors = ParseErrorEnum`typescript`({ + AbstractMethodHasImplementation: ({ + methodName + }) => `Method '${methodName}' cannot have an implementation because it is marked abstract.`, + AbstractPropertyHasInitializer: ({ + propertyName + }) => `Property '${propertyName}' cannot have an initializer because it is marked abstract.`, + AccessorCannotBeOptional: "An 'accessor' property cannot be declared optional.", + AccessorCannotDeclareThisParameter: "'get' and 'set' accessors cannot declare 'this' parameters.", + AccessorCannotHaveTypeParameters: "An accessor cannot have type parameters.", + ClassMethodHasDeclare: "Class methods cannot have the 'declare' modifier.", + ClassMethodHasReadonly: "Class methods cannot have the 'readonly' modifier.", + ConstInitializerMustBeStringOrNumericLiteralOrLiteralEnumReference: "A 'const' initializer in an ambient context must be a string or numeric literal or literal enum reference.", + ConstructorHasTypeParameters: "Type parameters cannot appear on a constructor declaration.", + DeclareAccessor: ({ + kind + }) => `'declare' is not allowed in ${kind}ters.`, + DeclareClassFieldHasInitializer: "Initializers are not allowed in ambient contexts.", + DeclareFunctionHasImplementation: "An implementation cannot be declared in ambient contexts.", + DuplicateAccessibilityModifier: ({ + modifier + }) => `Accessibility modifier already seen.`, + DuplicateModifier: ({ + modifier + }) => `Duplicate modifier: '${modifier}'.`, + EmptyHeritageClauseType: ({ + token + }) => `'${token}' list cannot be empty.`, + EmptyTypeArguments: "Type argument list cannot be empty.", + EmptyTypeParameters: "Type parameter list cannot be empty.", + ExpectedAmbientAfterExportDeclare: "'export declare' must be followed by an ambient declaration.", + ImportAliasHasImportType: "An import alias can not use 'import type'.", + ImportReflectionHasImportType: "An `import module` declaration can not use `type` modifier", + IncompatibleModifiers: ({ + modifiers + }) => `'${modifiers[0]}' modifier cannot be used with '${modifiers[1]}' modifier.`, + IndexSignatureHasAbstract: "Index signatures cannot have the 'abstract' modifier.", + IndexSignatureHasAccessibility: ({ + modifier + }) => `Index signatures cannot have an accessibility modifier ('${modifier}').`, + IndexSignatureHasDeclare: "Index signatures cannot have the 'declare' modifier.", + IndexSignatureHasOverride: "'override' modifier cannot appear on an index signature.", + IndexSignatureHasStatic: "Index signatures cannot have the 'static' modifier.", + InitializerNotAllowedInAmbientContext: "Initializers are not allowed in ambient contexts.", + InvalidHeritageClauseType: ({ + token + }) => `'${token}' list can only include identifiers or qualified-names with optional type arguments.`, + InvalidModifierOnTypeMember: ({ + modifier + }) => `'${modifier}' modifier cannot appear on a type member.`, + InvalidModifierOnTypeParameter: ({ + modifier + }) => `'${modifier}' modifier cannot appear on a type parameter.`, + InvalidModifierOnTypeParameterPositions: ({ + modifier + }) => `'${modifier}' modifier can only appear on a type parameter of a class, interface or type alias.`, + InvalidModifiersOrder: ({ + orderedModifiers + }) => `'${orderedModifiers[0]}' modifier must precede '${orderedModifiers[1]}' modifier.`, + InvalidPropertyAccessAfterInstantiationExpression: "Invalid property access after an instantiation expression. " + "You can either wrap the instantiation expression in parentheses, or delete the type arguments.", + InvalidTupleMemberLabel: "Tuple members must be labeled with a simple identifier.", + MissingInterfaceName: "'interface' declarations must be followed by an identifier.", + NonAbstractClassHasAbstractMethod: "Abstract methods can only appear within an abstract class.", + NonClassMethodPropertyHasAbstractModifer: "'abstract' modifier can only appear on a class, method, or property declaration.", + OptionalTypeBeforeRequired: "A required element cannot follow an optional element.", + OverrideNotInSubClass: "This member cannot have an 'override' modifier because its containing class does not extend another class.", + PatternIsOptional: "A binding pattern parameter cannot be optional in an implementation signature.", + PrivateElementHasAbstract: "Private elements cannot have the 'abstract' modifier.", + PrivateElementHasAccessibility: ({ + modifier + }) => `Private elements cannot have an accessibility modifier ('${modifier}').`, + ReadonlyForMethodSignature: "'readonly' modifier can only appear on a property declaration or index signature.", + ReservedArrowTypeParam: "This syntax is reserved in files with the .mts or .cts extension. Add a trailing comma, as in `() => ...`.", + ReservedTypeAssertion: "This syntax is reserved in files with the .mts or .cts extension. Use an `as` expression instead.", + SetAccessorCannotHaveOptionalParameter: "A 'set' accessor cannot have an optional parameter.", + SetAccessorCannotHaveRestParameter: "A 'set' accessor cannot have rest parameter.", + SetAccessorCannotHaveReturnType: "A 'set' accessor cannot have a return type annotation.", + SingleTypeParameterWithoutTrailingComma: ({ + typeParameterName + }) => `Single type parameter ${typeParameterName} should have a trailing comma. Example usage: <${typeParameterName},>.`, + StaticBlockCannotHaveModifier: "Static class blocks cannot have any modifier.", + TupleOptionalAfterType: "A labeled tuple optional element must be declared using a question mark after the name and before the colon (`name?: type`), rather than after the type (`name: type?`).", + TypeAnnotationAfterAssign: "Type annotations must come before default assignments, e.g. instead of `age = 25: number` use `age: number = 25`.", + TypeImportCannotSpecifyDefaultAndNamed: "A type-only import can specify a default import or named bindings, but not both.", + TypeModifierIsUsedInTypeExports: "The 'type' modifier cannot be used on a named export when 'export type' is used on its export statement.", + TypeModifierIsUsedInTypeImports: "The 'type' modifier cannot be used on a named import when 'import type' is used on its import statement.", + UnexpectedParameterModifier: "A parameter property is only allowed in a constructor implementation.", + UnexpectedReadonly: "'readonly' type modifier is only permitted on array and tuple literal types.", + UnexpectedTypeAnnotation: "Did not expect a type annotation here.", + UnexpectedTypeCastInParameter: "Unexpected type cast in parameter position.", + UnsupportedImportTypeArgument: "Argument in a type import must be a string literal.", + UnsupportedParameterPropertyKind: "A parameter property may not be declared using a binding pattern.", + UnsupportedSignatureParameterKind: ({ + type + }) => `Name in a signature must be an Identifier, ObjectPattern or ArrayPattern, instead got ${type}.` +}); +function keywordTypeFromName(value) { + switch (value) { + case "any": + return "TSAnyKeyword"; + case "boolean": + return "TSBooleanKeyword"; + case "bigint": + return "TSBigIntKeyword"; + case "never": + return "TSNeverKeyword"; + case "number": + return "TSNumberKeyword"; + case "object": + return "TSObjectKeyword"; + case "string": + return "TSStringKeyword"; + case "symbol": + return "TSSymbolKeyword"; + case "undefined": + return "TSUndefinedKeyword"; + case "unknown": + return "TSUnknownKeyword"; + default: + return undefined; + } +} +function tsIsAccessModifier(modifier) { + return modifier === "private" || modifier === "public" || modifier === "protected"; +} +function tsIsVarianceAnnotations(modifier) { + return modifier === "in" || modifier === "out"; +} +var typescript = superClass => class TypeScriptParserMixin extends superClass { + constructor(...args) { + super(...args); + this.tsParseInOutModifiers = this.tsParseModifiers.bind(this, { + allowedModifiers: ["in", "out"], + disallowedModifiers: ["const", "public", "private", "protected", "readonly", "declare", "abstract", "override"], + errorTemplate: TSErrors.InvalidModifierOnTypeParameter + }); + this.tsParseConstModifier = this.tsParseModifiers.bind(this, { + allowedModifiers: ["const"], + disallowedModifiers: ["in", "out"], + errorTemplate: TSErrors.InvalidModifierOnTypeParameterPositions + }); + this.tsParseInOutConstModifiers = this.tsParseModifiers.bind(this, { + allowedModifiers: ["in", "out", "const"], + disallowedModifiers: ["public", "private", "protected", "readonly", "declare", "abstract", "override"], + errorTemplate: TSErrors.InvalidModifierOnTypeParameter + }); + } + getScopeHandler() { + return TypeScriptScopeHandler; + } + tsIsIdentifier() { + return tokenIsIdentifier(this.state.type); + } + tsTokenCanFollowModifier() { + return this.match(0) || this.match(5) || this.match(55) || this.match(21) || this.match(139) || this.isLiteralPropertyName(); + } + tsNextTokenOnSameLineAndCanFollowModifier() { + this.next(); + if (this.hasPrecedingLineBreak()) { + return false; + } + return this.tsTokenCanFollowModifier(); + } + tsNextTokenCanFollowModifier() { + if (this.match(106)) { + this.next(); + return this.tsTokenCanFollowModifier(); + } + return this.tsNextTokenOnSameLineAndCanFollowModifier(); + } + tsParseModifier(allowedModifiers, stopOnStartOfClassStaticBlock) { + if (!tokenIsIdentifier(this.state.type) && this.state.type !== 58 && this.state.type !== 75) { + return undefined; + } + const modifier = this.state.value; + if (allowedModifiers.includes(modifier)) { + if (stopOnStartOfClassStaticBlock && this.tsIsStartOfStaticBlocks()) { + return undefined; + } + if (this.tsTryParse(this.tsNextTokenCanFollowModifier.bind(this))) { + return modifier; + } + } + return undefined; + } + tsParseModifiers({ + allowedModifiers, + disallowedModifiers, + stopOnStartOfClassStaticBlock, + errorTemplate = TSErrors.InvalidModifierOnTypeMember + }, modified) { + const enforceOrder = (loc, modifier, before, after) => { + if (modifier === before && modified[after]) { + this.raise(TSErrors.InvalidModifiersOrder, loc, { + orderedModifiers: [before, after] + }); + } + }; + const incompatible = (loc, modifier, mod1, mod2) => { + if (modified[mod1] && modifier === mod2 || modified[mod2] && modifier === mod1) { + this.raise(TSErrors.IncompatibleModifiers, loc, { + modifiers: [mod1, mod2] + }); + } + }; + for (;;) { + const { + startLoc + } = this.state; + const modifier = this.tsParseModifier(allowedModifiers.concat(disallowedModifiers != null ? disallowedModifiers : []), stopOnStartOfClassStaticBlock); + if (!modifier) break; + if (tsIsAccessModifier(modifier)) { + if (modified.accessibility) { + this.raise(TSErrors.DuplicateAccessibilityModifier, startLoc, { + modifier + }); + } else { + enforceOrder(startLoc, modifier, modifier, "override"); + enforceOrder(startLoc, modifier, modifier, "static"); + enforceOrder(startLoc, modifier, modifier, "readonly"); + modified.accessibility = modifier; + } + } else if (tsIsVarianceAnnotations(modifier)) { + if (modified[modifier]) { + this.raise(TSErrors.DuplicateModifier, startLoc, { + modifier + }); + } + modified[modifier] = true; + enforceOrder(startLoc, modifier, "in", "out"); + } else { + if (hasOwnProperty.call(modified, modifier)) { + this.raise(TSErrors.DuplicateModifier, startLoc, { + modifier + }); + } else { + enforceOrder(startLoc, modifier, "static", "readonly"); + enforceOrder(startLoc, modifier, "static", "override"); + enforceOrder(startLoc, modifier, "override", "readonly"); + enforceOrder(startLoc, modifier, "abstract", "override"); + incompatible(startLoc, modifier, "declare", "override"); + incompatible(startLoc, modifier, "static", "abstract"); + } + modified[modifier] = true; + } + if (disallowedModifiers != null && disallowedModifiers.includes(modifier)) { + this.raise(errorTemplate, startLoc, { + modifier + }); + } + } + } + tsIsListTerminator(kind) { + switch (kind) { + case "EnumMembers": + case "TypeMembers": + return this.match(8); + case "HeritageClauseElement": + return this.match(5); + case "TupleElementTypes": + return this.match(3); + case "TypeParametersOrArguments": + return this.match(48); + } + } + tsParseList(kind, parseElement) { + const result = []; + while (!this.tsIsListTerminator(kind)) { + result.push(parseElement()); + } + return result; + } + tsParseDelimitedList(kind, parseElement, refTrailingCommaPos) { + return nonNull(this.tsParseDelimitedListWorker(kind, parseElement, true, refTrailingCommaPos)); + } + tsParseDelimitedListWorker(kind, parseElement, expectSuccess, refTrailingCommaPos) { + const result = []; + let trailingCommaPos = -1; + for (;;) { + if (this.tsIsListTerminator(kind)) { + break; + } + trailingCommaPos = -1; + const element = parseElement(); + if (element == null) { + return undefined; + } + result.push(element); + if (this.eat(12)) { + trailingCommaPos = this.state.lastTokStartLoc.index; + continue; + } + if (this.tsIsListTerminator(kind)) { + break; + } + if (expectSuccess) { + this.expect(12); + } + return undefined; + } + if (refTrailingCommaPos) { + refTrailingCommaPos.value = trailingCommaPos; + } + return result; + } + tsParseBracketedList(kind, parseElement, bracket, skipFirstToken, refTrailingCommaPos) { + if (!skipFirstToken) { + if (bracket) { + this.expect(0); + } else { + this.expect(47); + } + } + const result = this.tsParseDelimitedList(kind, parseElement, refTrailingCommaPos); + if (bracket) { + this.expect(3); + } else { + this.expect(48); + } + return result; + } + tsParseImportType() { + const node = this.startNode(); + this.expect(83); + this.expect(10); + if (!this.match(134)) { + this.raise(TSErrors.UnsupportedImportTypeArgument, this.state.startLoc); + { + node.argument = super.parseExprAtom(); + } + } else { + { + node.argument = this.parseStringLiteral(this.state.value); + } + } + if (this.eat(12)) { + node.options = this.tsParseImportTypeOptions(); + } else { + node.options = null; + } + this.expect(11); + if (this.eat(16)) { + node.qualifier = this.tsParseEntityName(1 | 2); + } + if (this.match(47)) { + { + node.typeParameters = this.tsParseTypeArguments(); + } + } + return this.finishNode(node, "TSImportType"); + } + tsParseImportTypeOptions() { + const node = this.startNode(); + this.expect(5); + const withProperty = this.startNode(); + if (this.isContextual(76)) { + withProperty.method = false; + withProperty.key = this.parseIdentifier(true); + withProperty.computed = false; + withProperty.shorthand = false; + } else { + this.unexpected(null, 76); + } + this.expect(14); + withProperty.value = this.tsParseImportTypeWithPropertyValue(); + node.properties = [this.finishObjectProperty(withProperty)]; + this.expect(8); + return this.finishNode(node, "ObjectExpression"); + } + tsParseImportTypeWithPropertyValue() { + const node = this.startNode(); + const properties = []; + this.expect(5); + while (!this.match(8)) { + const type = this.state.type; + if (tokenIsIdentifier(type) || type === 134) { + properties.push(super.parsePropertyDefinition(null)); + } else { + this.unexpected(); + } + this.eat(12); + } + node.properties = properties; + this.next(); + return this.finishNode(node, "ObjectExpression"); + } + tsParseEntityName(flags) { + let entity; + if (flags & 1 && this.match(78)) { + if (flags & 2) { + entity = this.parseIdentifier(true); + } else { + const node = this.startNode(); + this.next(); + entity = this.finishNode(node, "ThisExpression"); + } + } else { + entity = this.parseIdentifier(!!(flags & 1)); + } + while (this.eat(16)) { + const node = this.startNodeAtNode(entity); + node.left = entity; + node.right = this.parseIdentifier(!!(flags & 1)); + entity = this.finishNode(node, "TSQualifiedName"); + } + return entity; + } + tsParseTypeReference() { + const node = this.startNode(); + node.typeName = this.tsParseEntityName(1); + if (!this.hasPrecedingLineBreak() && this.match(47)) { + { + node.typeParameters = this.tsParseTypeArguments(); + } + } + return this.finishNode(node, "TSTypeReference"); + } + tsParseThisTypePredicate(lhs) { + this.next(); + const node = this.startNodeAtNode(lhs); + node.parameterName = lhs; + node.typeAnnotation = this.tsParseTypeAnnotation(false); + node.asserts = false; + return this.finishNode(node, "TSTypePredicate"); + } + tsParseThisTypeNode() { + const node = this.startNode(); + this.next(); + return this.finishNode(node, "TSThisType"); + } + tsParseTypeQuery() { + const node = this.startNode(); + this.expect(87); + if (this.match(83)) { + node.exprName = this.tsParseImportType(); + } else { + { + node.exprName = this.tsParseEntityName(1 | 2); + } + } + if (!this.hasPrecedingLineBreak() && this.match(47)) { + { + node.typeParameters = this.tsParseTypeArguments(); + } + } + return this.finishNode(node, "TSTypeQuery"); + } + tsParseTypeParameter(parseModifiers) { + const node = this.startNode(); + parseModifiers(node); + node.name = this.tsParseTypeParameterName(); + node.constraint = this.tsEatThenParseType(81); + node.default = this.tsEatThenParseType(29); + return this.finishNode(node, "TSTypeParameter"); + } + tsTryParseTypeParameters(parseModifiers) { + if (this.match(47)) { + return this.tsParseTypeParameters(parseModifiers); + } + } + tsParseTypeParameters(parseModifiers) { + const node = this.startNode(); + if (this.match(47) || this.match(143)) { + this.next(); + } else { + this.unexpected(); + } + const refTrailingCommaPos = { + value: -1 + }; + node.params = this.tsParseBracketedList("TypeParametersOrArguments", this.tsParseTypeParameter.bind(this, parseModifiers), false, true, refTrailingCommaPos); + if (node.params.length === 0) { + this.raise(TSErrors.EmptyTypeParameters, node); + } + if (refTrailingCommaPos.value !== -1) { + this.addExtra(node, "trailingComma", refTrailingCommaPos.value); + } + return this.finishNode(node, "TSTypeParameterDeclaration"); + } + tsFillSignature(returnToken, signature) { + const returnTokenRequired = returnToken === 19; + const paramsKey = "parameters"; + const returnTypeKey = "typeAnnotation"; + signature.typeParameters = this.tsTryParseTypeParameters(this.tsParseConstModifier); + this.expect(10); + signature[paramsKey] = this.tsParseBindingListForSignature(); + if (returnTokenRequired) { + signature[returnTypeKey] = this.tsParseTypeOrTypePredicateAnnotation(returnToken); + } else if (this.match(returnToken)) { + signature[returnTypeKey] = this.tsParseTypeOrTypePredicateAnnotation(returnToken); + } + } + tsParseBindingListForSignature() { + const list = super.parseBindingList(11, 41, 2); + for (const pattern of list) { + const { + type + } = pattern; + if (type === "AssignmentPattern" || type === "TSParameterProperty") { + this.raise(TSErrors.UnsupportedSignatureParameterKind, pattern, { + type + }); + } + } + return list; + } + tsParseTypeMemberSemicolon() { + if (!this.eat(12) && !this.isLineTerminator()) { + this.expect(13); + } + } + tsParseSignatureMember(kind, node) { + this.tsFillSignature(14, node); + this.tsParseTypeMemberSemicolon(); + return this.finishNode(node, kind); + } + tsIsUnambiguouslyIndexSignature() { + this.next(); + if (tokenIsIdentifier(this.state.type)) { + this.next(); + return this.match(14); + } + return false; + } + tsTryParseIndexSignature(node) { + if (!(this.match(0) && this.tsLookAhead(this.tsIsUnambiguouslyIndexSignature.bind(this)))) { + return; + } + this.expect(0); + const id = this.parseIdentifier(); + id.typeAnnotation = this.tsParseTypeAnnotation(); + this.resetEndLocation(id); + this.expect(3); + node.parameters = [id]; + const type = this.tsTryParseTypeAnnotation(); + if (type) node.typeAnnotation = type; + this.tsParseTypeMemberSemicolon(); + return this.finishNode(node, "TSIndexSignature"); + } + tsParsePropertyOrMethodSignature(node, readonly) { + if (this.eat(17)) node.optional = true; + if (this.match(10) || this.match(47)) { + if (readonly) { + this.raise(TSErrors.ReadonlyForMethodSignature, node); + } + const method = node; + if (method.kind && this.match(47)) { + this.raise(TSErrors.AccessorCannotHaveTypeParameters, this.state.curPosition()); + } + this.tsFillSignature(14, method); + this.tsParseTypeMemberSemicolon(); + const paramsKey = "parameters"; + const returnTypeKey = "typeAnnotation"; + if (method.kind === "get") { + if (method[paramsKey].length > 0) { + this.raise(Errors.BadGetterArity, this.state.curPosition()); + if (this.isThisParam(method[paramsKey][0])) { + this.raise(TSErrors.AccessorCannotDeclareThisParameter, this.state.curPosition()); + } + } + } else if (method.kind === "set") { + if (method[paramsKey].length !== 1) { + this.raise(Errors.BadSetterArity, this.state.curPosition()); + } else { + const firstParameter = method[paramsKey][0]; + if (this.isThisParam(firstParameter)) { + this.raise(TSErrors.AccessorCannotDeclareThisParameter, this.state.curPosition()); + } + if (firstParameter.type === "Identifier" && firstParameter.optional) { + this.raise(TSErrors.SetAccessorCannotHaveOptionalParameter, this.state.curPosition()); + } + if (firstParameter.type === "RestElement") { + this.raise(TSErrors.SetAccessorCannotHaveRestParameter, this.state.curPosition()); + } + } + if (method[returnTypeKey]) { + this.raise(TSErrors.SetAccessorCannotHaveReturnType, method[returnTypeKey]); + } + } else { + method.kind = "method"; + } + return this.finishNode(method, "TSMethodSignature"); + } else { + const property = node; + if (readonly) property.readonly = true; + const type = this.tsTryParseTypeAnnotation(); + if (type) property.typeAnnotation = type; + this.tsParseTypeMemberSemicolon(); + return this.finishNode(property, "TSPropertySignature"); + } + } + tsParseTypeMember() { + const node = this.startNode(); + if (this.match(10) || this.match(47)) { + return this.tsParseSignatureMember("TSCallSignatureDeclaration", node); + } + if (this.match(77)) { + const id = this.startNode(); + this.next(); + if (this.match(10) || this.match(47)) { + return this.tsParseSignatureMember("TSConstructSignatureDeclaration", node); + } else { + node.key = this.createIdentifier(id, "new"); + return this.tsParsePropertyOrMethodSignature(node, false); + } + } + this.tsParseModifiers({ + allowedModifiers: ["readonly"], + disallowedModifiers: ["declare", "abstract", "private", "protected", "public", "static", "override"] + }, node); + const idx = this.tsTryParseIndexSignature(node); + if (idx) { + return idx; + } + super.parsePropertyName(node); + if (!node.computed && node.key.type === "Identifier" && (node.key.name === "get" || node.key.name === "set") && this.tsTokenCanFollowModifier()) { + node.kind = node.key.name; + super.parsePropertyName(node); + if (!this.match(10) && !this.match(47)) { + this.unexpected(null, 10); + } + } + return this.tsParsePropertyOrMethodSignature(node, !!node.readonly); + } + tsParseTypeLiteral() { + const node = this.startNode(); + node.members = this.tsParseObjectTypeMembers(); + return this.finishNode(node, "TSTypeLiteral"); + } + tsParseObjectTypeMembers() { + this.expect(5); + const members = this.tsParseList("TypeMembers", this.tsParseTypeMember.bind(this)); + this.expect(8); + return members; + } + tsIsStartOfMappedType() { + this.next(); + if (this.eat(53)) { + return this.isContextual(122); + } + if (this.isContextual(122)) { + this.next(); + } + if (!this.match(0)) { + return false; + } + this.next(); + if (!this.tsIsIdentifier()) { + return false; + } + this.next(); + return this.match(58); + } + tsParseMappedType() { + const node = this.startNode(); + this.expect(5); + if (this.match(53)) { + node.readonly = this.state.value; + this.next(); + this.expectContextual(122); + } else if (this.eatContextual(122)) { + node.readonly = true; + } + this.expect(0); + { + const typeParameter = this.startNode(); + typeParameter.name = this.tsParseTypeParameterName(); + typeParameter.constraint = this.tsExpectThenParseType(58); + node.typeParameter = this.finishNode(typeParameter, "TSTypeParameter"); + } + node.nameType = this.eatContextual(93) ? this.tsParseType() : null; + this.expect(3); + if (this.match(53)) { + node.optional = this.state.value; + this.next(); + this.expect(17); + } else if (this.eat(17)) { + node.optional = true; + } + node.typeAnnotation = this.tsTryParseType(); + this.semicolon(); + this.expect(8); + return this.finishNode(node, "TSMappedType"); + } + tsParseTupleType() { + const node = this.startNode(); + node.elementTypes = this.tsParseBracketedList("TupleElementTypes", this.tsParseTupleElementType.bind(this), true, false); + let seenOptionalElement = false; + node.elementTypes.forEach(elementNode => { + const { + type + } = elementNode; + if (seenOptionalElement && type !== "TSRestType" && type !== "TSOptionalType" && !(type === "TSNamedTupleMember" && elementNode.optional)) { + this.raise(TSErrors.OptionalTypeBeforeRequired, elementNode); + } + seenOptionalElement || (seenOptionalElement = type === "TSNamedTupleMember" && elementNode.optional || type === "TSOptionalType"); + }); + return this.finishNode(node, "TSTupleType"); + } + tsParseTupleElementType() { + const restStartLoc = this.state.startLoc; + const rest = this.eat(21); + const { + startLoc + } = this.state; + let labeled; + let label; + let optional; + let type; + const isWord = tokenIsKeywordOrIdentifier(this.state.type); + const chAfterWord = isWord ? this.lookaheadCharCode() : null; + if (chAfterWord === 58) { + labeled = true; + optional = false; + label = this.parseIdentifier(true); + this.expect(14); + type = this.tsParseType(); + } else if (chAfterWord === 63) { + optional = true; + const wordName = this.state.value; + const typeOrLabel = this.tsParseNonArrayType(); + if (this.lookaheadCharCode() === 58) { + labeled = true; + label = this.createIdentifier(this.startNodeAt(startLoc), wordName); + this.expect(17); + this.expect(14); + type = this.tsParseType(); + } else { + labeled = false; + type = typeOrLabel; + this.expect(17); + } + } else { + type = this.tsParseType(); + optional = this.eat(17); + labeled = this.eat(14); + } + if (labeled) { + let labeledNode; + if (label) { + labeledNode = this.startNodeAt(startLoc); + labeledNode.optional = optional; + labeledNode.label = label; + labeledNode.elementType = type; + if (this.eat(17)) { + labeledNode.optional = true; + this.raise(TSErrors.TupleOptionalAfterType, this.state.lastTokStartLoc); + } + } else { + labeledNode = this.startNodeAt(startLoc); + labeledNode.optional = optional; + this.raise(TSErrors.InvalidTupleMemberLabel, type); + labeledNode.label = type; + labeledNode.elementType = this.tsParseType(); + } + type = this.finishNode(labeledNode, "TSNamedTupleMember"); + } else if (optional) { + const optionalTypeNode = this.startNodeAt(startLoc); + optionalTypeNode.typeAnnotation = type; + type = this.finishNode(optionalTypeNode, "TSOptionalType"); + } + if (rest) { + const restNode = this.startNodeAt(restStartLoc); + restNode.typeAnnotation = type; + type = this.finishNode(restNode, "TSRestType"); + } + return type; + } + tsParseParenthesizedType() { + const node = this.startNode(); + this.expect(10); + node.typeAnnotation = this.tsParseType(); + this.expect(11); + return this.finishNode(node, "TSParenthesizedType"); + } + tsParseFunctionOrConstructorType(type, abstract) { + const node = this.startNode(); + if (type === "TSConstructorType") { + node.abstract = !!abstract; + if (abstract) this.next(); + this.next(); + } + this.tsInAllowConditionalTypesContext(() => this.tsFillSignature(19, node)); + return this.finishNode(node, type); + } + tsParseLiteralTypeNode() { + const node = this.startNode(); + switch (this.state.type) { + case 135: + case 136: + case 134: + case 85: + case 86: + node.literal = super.parseExprAtom(); + break; + default: + this.unexpected(); + } + return this.finishNode(node, "TSLiteralType"); + } + tsParseTemplateLiteralType() { + { + const node = this.startNode(); + node.literal = super.parseTemplate(false); + return this.finishNode(node, "TSLiteralType"); + } + } + parseTemplateSubstitution() { + if (this.state.inType) return this.tsParseType(); + return super.parseTemplateSubstitution(); + } + tsParseThisTypeOrThisTypePredicate() { + const thisKeyword = this.tsParseThisTypeNode(); + if (this.isContextual(116) && !this.hasPrecedingLineBreak()) { + return this.tsParseThisTypePredicate(thisKeyword); + } else { + return thisKeyword; + } + } + tsParseNonArrayType() { + switch (this.state.type) { + case 134: + case 135: + case 136: + case 85: + case 86: + return this.tsParseLiteralTypeNode(); + case 53: + if (this.state.value === "-") { + const node = this.startNode(); + const nextToken = this.lookahead(); + if (nextToken.type !== 135 && nextToken.type !== 136) { + this.unexpected(); + } + node.literal = this.parseMaybeUnary(); + return this.finishNode(node, "TSLiteralType"); + } + break; + case 78: + return this.tsParseThisTypeOrThisTypePredicate(); + case 87: + return this.tsParseTypeQuery(); + case 83: + return this.tsParseImportType(); + case 5: + return this.tsLookAhead(this.tsIsStartOfMappedType.bind(this)) ? this.tsParseMappedType() : this.tsParseTypeLiteral(); + case 0: + return this.tsParseTupleType(); + case 10: + return this.tsParseParenthesizedType(); + case 25: + case 24: + return this.tsParseTemplateLiteralType(); + default: + { + const { + type + } = this.state; + if (tokenIsIdentifier(type) || type === 88 || type === 84) { + const nodeType = type === 88 ? "TSVoidKeyword" : type === 84 ? "TSNullKeyword" : keywordTypeFromName(this.state.value); + if (nodeType !== undefined && this.lookaheadCharCode() !== 46) { + const node = this.startNode(); + this.next(); + return this.finishNode(node, nodeType); + } + return this.tsParseTypeReference(); + } + } + } + this.unexpected(); + } + tsParseArrayTypeOrHigher() { + const { + startLoc + } = this.state; + let type = this.tsParseNonArrayType(); + while (!this.hasPrecedingLineBreak() && this.eat(0)) { + if (this.match(3)) { + const node = this.startNodeAt(startLoc); + node.elementType = type; + this.expect(3); + type = this.finishNode(node, "TSArrayType"); + } else { + const node = this.startNodeAt(startLoc); + node.objectType = type; + node.indexType = this.tsParseType(); + this.expect(3); + type = this.finishNode(node, "TSIndexedAccessType"); + } + } + return type; + } + tsParseTypeOperator() { + const node = this.startNode(); + const operator = this.state.value; + this.next(); + node.operator = operator; + node.typeAnnotation = this.tsParseTypeOperatorOrHigher(); + if (operator === "readonly") { + this.tsCheckTypeAnnotationForReadOnly(node); + } + return this.finishNode(node, "TSTypeOperator"); + } + tsCheckTypeAnnotationForReadOnly(node) { + switch (node.typeAnnotation.type) { + case "TSTupleType": + case "TSArrayType": + return; + default: + this.raise(TSErrors.UnexpectedReadonly, node); + } + } + tsParseInferType() { + const node = this.startNode(); + this.expectContextual(115); + const typeParameter = this.startNode(); + typeParameter.name = this.tsParseTypeParameterName(); + typeParameter.constraint = this.tsTryParse(() => this.tsParseConstraintForInferType()); + node.typeParameter = this.finishNode(typeParameter, "TSTypeParameter"); + return this.finishNode(node, "TSInferType"); + } + tsParseConstraintForInferType() { + if (this.eat(81)) { + const constraint = this.tsInDisallowConditionalTypesContext(() => this.tsParseType()); + if (this.state.inDisallowConditionalTypesContext || !this.match(17)) { + return constraint; + } + } + } + tsParseTypeOperatorOrHigher() { + const isTypeOperator = tokenIsTSTypeOperator(this.state.type) && !this.state.containsEsc; + return isTypeOperator ? this.tsParseTypeOperator() : this.isContextual(115) ? this.tsParseInferType() : this.tsInAllowConditionalTypesContext(() => this.tsParseArrayTypeOrHigher()); + } + tsParseUnionOrIntersectionType(kind, parseConstituentType, operator) { + const node = this.startNode(); + const hasLeadingOperator = this.eat(operator); + const types = []; + do { + types.push(parseConstituentType()); + } while (this.eat(operator)); + if (types.length === 1 && !hasLeadingOperator) { + return types[0]; + } + node.types = types; + return this.finishNode(node, kind); + } + tsParseIntersectionTypeOrHigher() { + return this.tsParseUnionOrIntersectionType("TSIntersectionType", this.tsParseTypeOperatorOrHigher.bind(this), 45); + } + tsParseUnionTypeOrHigher() { + return this.tsParseUnionOrIntersectionType("TSUnionType", this.tsParseIntersectionTypeOrHigher.bind(this), 43); + } + tsIsStartOfFunctionType() { + if (this.match(47)) { + return true; + } + return this.match(10) && this.tsLookAhead(this.tsIsUnambiguouslyStartOfFunctionType.bind(this)); + } + tsSkipParameterStart() { + if (tokenIsIdentifier(this.state.type) || this.match(78)) { + this.next(); + return true; + } + if (this.match(5)) { + const { + errors + } = this.state; + const previousErrorCount = errors.length; + try { + this.parseObjectLike(8, true); + return errors.length === previousErrorCount; + } catch (_unused) { + return false; + } + } + if (this.match(0)) { + this.next(); + const { + errors + } = this.state; + const previousErrorCount = errors.length; + try { + super.parseBindingList(3, 93, 1); + return errors.length === previousErrorCount; + } catch (_unused2) { + return false; + } + } + return false; + } + tsIsUnambiguouslyStartOfFunctionType() { + this.next(); + if (this.match(11) || this.match(21)) { + return true; + } + if (this.tsSkipParameterStart()) { + if (this.match(14) || this.match(12) || this.match(17) || this.match(29)) { + return true; + } + if (this.match(11)) { + this.next(); + if (this.match(19)) { + return true; + } + } + } + return false; + } + tsParseTypeOrTypePredicateAnnotation(returnToken) { + return this.tsInType(() => { + const t = this.startNode(); + this.expect(returnToken); + const node = this.startNode(); + const asserts = !!this.tsTryParse(this.tsParseTypePredicateAsserts.bind(this)); + if (asserts && this.match(78)) { + let thisTypePredicate = this.tsParseThisTypeOrThisTypePredicate(); + if (thisTypePredicate.type === "TSThisType") { + node.parameterName = thisTypePredicate; + node.asserts = true; + node.typeAnnotation = null; + thisTypePredicate = this.finishNode(node, "TSTypePredicate"); + } else { + this.resetStartLocationFromNode(thisTypePredicate, node); + thisTypePredicate.asserts = true; + } + t.typeAnnotation = thisTypePredicate; + return this.finishNode(t, "TSTypeAnnotation"); + } + const typePredicateVariable = this.tsIsIdentifier() && this.tsTryParse(this.tsParseTypePredicatePrefix.bind(this)); + if (!typePredicateVariable) { + if (!asserts) { + return this.tsParseTypeAnnotation(false, t); + } + node.parameterName = this.parseIdentifier(); + node.asserts = asserts; + node.typeAnnotation = null; + t.typeAnnotation = this.finishNode(node, "TSTypePredicate"); + return this.finishNode(t, "TSTypeAnnotation"); + } + const type = this.tsParseTypeAnnotation(false); + node.parameterName = typePredicateVariable; + node.typeAnnotation = type; + node.asserts = asserts; + t.typeAnnotation = this.finishNode(node, "TSTypePredicate"); + return this.finishNode(t, "TSTypeAnnotation"); + }); + } + tsTryParseTypeOrTypePredicateAnnotation() { + if (this.match(14)) { + return this.tsParseTypeOrTypePredicateAnnotation(14); + } + } + tsTryParseTypeAnnotation() { + if (this.match(14)) { + return this.tsParseTypeAnnotation(); + } + } + tsTryParseType() { + return this.tsEatThenParseType(14); + } + tsParseTypePredicatePrefix() { + const id = this.parseIdentifier(); + if (this.isContextual(116) && !this.hasPrecedingLineBreak()) { + this.next(); + return id; + } + } + tsParseTypePredicateAsserts() { + if (this.state.type !== 109) { + return false; + } + const containsEsc = this.state.containsEsc; + this.next(); + if (!tokenIsIdentifier(this.state.type) && !this.match(78)) { + return false; + } + if (containsEsc) { + this.raise(Errors.InvalidEscapedReservedWord, this.state.lastTokStartLoc, { + reservedWord: "asserts" + }); + } + return true; + } + tsParseTypeAnnotation(eatColon = true, t = this.startNode()) { + this.tsInType(() => { + if (eatColon) this.expect(14); + t.typeAnnotation = this.tsParseType(); + }); + return this.finishNode(t, "TSTypeAnnotation"); + } + tsParseType() { + assert(this.state.inType); + const type = this.tsParseNonConditionalType(); + if (this.state.inDisallowConditionalTypesContext || this.hasPrecedingLineBreak() || !this.eat(81)) { + return type; + } + const node = this.startNodeAtNode(type); + node.checkType = type; + node.extendsType = this.tsInDisallowConditionalTypesContext(() => this.tsParseNonConditionalType()); + this.expect(17); + node.trueType = this.tsInAllowConditionalTypesContext(() => this.tsParseType()); + this.expect(14); + node.falseType = this.tsInAllowConditionalTypesContext(() => this.tsParseType()); + return this.finishNode(node, "TSConditionalType"); + } + isAbstractConstructorSignature() { + return this.isContextual(124) && this.lookahead().type === 77; + } + tsParseNonConditionalType() { + if (this.tsIsStartOfFunctionType()) { + return this.tsParseFunctionOrConstructorType("TSFunctionType"); + } + if (this.match(77)) { + return this.tsParseFunctionOrConstructorType("TSConstructorType"); + } else if (this.isAbstractConstructorSignature()) { + return this.tsParseFunctionOrConstructorType("TSConstructorType", true); + } + return this.tsParseUnionTypeOrHigher(); + } + tsParseTypeAssertion() { + if (this.getPluginOption("typescript", "disallowAmbiguousJSXLike")) { + this.raise(TSErrors.ReservedTypeAssertion, this.state.startLoc); + } + const node = this.startNode(); + node.typeAnnotation = this.tsInType(() => { + this.next(); + return this.match(75) ? this.tsParseTypeReference() : this.tsParseType(); + }); + this.expect(48); + node.expression = this.parseMaybeUnary(); + return this.finishNode(node, "TSTypeAssertion"); + } + tsParseHeritageClause(token) { + const originalStartLoc = this.state.startLoc; + const delimitedList = this.tsParseDelimitedList("HeritageClauseElement", () => { + { + const node = this.startNode(); + node.expression = this.tsParseEntityName(1 | 2); + if (this.match(47)) { + node.typeParameters = this.tsParseTypeArguments(); + } + return this.finishNode(node, "TSExpressionWithTypeArguments"); + } + }); + if (!delimitedList.length) { + this.raise(TSErrors.EmptyHeritageClauseType, originalStartLoc, { + token + }); + } + return delimitedList; + } + tsParseInterfaceDeclaration(node, properties = {}) { + if (this.hasFollowingLineBreak()) return null; + this.expectContextual(129); + if (properties.declare) node.declare = true; + if (tokenIsIdentifier(this.state.type)) { + node.id = this.parseIdentifier(); + this.checkIdentifier(node.id, 130); + } else { + node.id = null; + this.raise(TSErrors.MissingInterfaceName, this.state.startLoc); + } + node.typeParameters = this.tsTryParseTypeParameters(this.tsParseInOutConstModifiers); + if (this.eat(81)) { + node.extends = this.tsParseHeritageClause("extends"); + } + const body = this.startNode(); + body.body = this.tsInType(this.tsParseObjectTypeMembers.bind(this)); + node.body = this.finishNode(body, "TSInterfaceBody"); + return this.finishNode(node, "TSInterfaceDeclaration"); + } + tsParseTypeAliasDeclaration(node) { + node.id = this.parseIdentifier(); + this.checkIdentifier(node.id, 2); + node.typeAnnotation = this.tsInType(() => { + node.typeParameters = this.tsTryParseTypeParameters(this.tsParseInOutModifiers); + this.expect(29); + if (this.isContextual(114) && this.lookahead().type !== 16) { + const node = this.startNode(); + this.next(); + return this.finishNode(node, "TSIntrinsicKeyword"); + } + return this.tsParseType(); + }); + this.semicolon(); + return this.finishNode(node, "TSTypeAliasDeclaration"); + } + tsInTopLevelContext(cb) { + if (this.curContext() !== types.brace) { + const oldContext = this.state.context; + this.state.context = [oldContext[0]]; + try { + return cb(); + } finally { + this.state.context = oldContext; + } + } else { + return cb(); + } + } + tsInType(cb) { + const oldInType = this.state.inType; + this.state.inType = true; + try { + return cb(); + } finally { + this.state.inType = oldInType; + } + } + tsInDisallowConditionalTypesContext(cb) { + const oldInDisallowConditionalTypesContext = this.state.inDisallowConditionalTypesContext; + this.state.inDisallowConditionalTypesContext = true; + try { + return cb(); + } finally { + this.state.inDisallowConditionalTypesContext = oldInDisallowConditionalTypesContext; + } + } + tsInAllowConditionalTypesContext(cb) { + const oldInDisallowConditionalTypesContext = this.state.inDisallowConditionalTypesContext; + this.state.inDisallowConditionalTypesContext = false; + try { + return cb(); + } finally { + this.state.inDisallowConditionalTypesContext = oldInDisallowConditionalTypesContext; + } + } + tsEatThenParseType(token) { + if (this.match(token)) { + return this.tsNextThenParseType(); + } + } + tsExpectThenParseType(token) { + return this.tsInType(() => { + this.expect(token); + return this.tsParseType(); + }); + } + tsNextThenParseType() { + return this.tsInType(() => { + this.next(); + return this.tsParseType(); + }); + } + tsParseEnumMember() { + const node = this.startNode(); + node.id = this.match(134) ? super.parseStringLiteral(this.state.value) : this.parseIdentifier(true); + if (this.eat(29)) { + node.initializer = super.parseMaybeAssignAllowIn(); + } + return this.finishNode(node, "TSEnumMember"); + } + tsParseEnumDeclaration(node, properties = {}) { + if (properties.const) node.const = true; + if (properties.declare) node.declare = true; + this.expectContextual(126); + node.id = this.parseIdentifier(); + this.checkIdentifier(node.id, node.const ? 8971 : 8459); + { + this.expect(5); + node.members = this.tsParseDelimitedList("EnumMembers", this.tsParseEnumMember.bind(this)); + this.expect(8); + } + return this.finishNode(node, "TSEnumDeclaration"); + } + tsParseEnumBody() { + const node = this.startNode(); + this.expect(5); + node.members = this.tsParseDelimitedList("EnumMembers", this.tsParseEnumMember.bind(this)); + this.expect(8); + return this.finishNode(node, "TSEnumBody"); + } + tsParseModuleBlock() { + const node = this.startNode(); + this.scope.enter(0); + this.expect(5); + super.parseBlockOrModuleBlockBody(node.body = [], undefined, true, 8); + this.scope.exit(); + return this.finishNode(node, "TSModuleBlock"); + } + tsParseModuleOrNamespaceDeclaration(node, nested = false) { + node.id = this.parseIdentifier(); + if (!nested) { + this.checkIdentifier(node.id, 1024); + } + if (this.eat(16)) { + const inner = this.startNode(); + this.tsParseModuleOrNamespaceDeclaration(inner, true); + node.body = inner; + } else { + this.scope.enter(256); + this.prodParam.enter(0); + node.body = this.tsParseModuleBlock(); + this.prodParam.exit(); + this.scope.exit(); + } + return this.finishNode(node, "TSModuleDeclaration"); + } + tsParseAmbientExternalModuleDeclaration(node) { + if (this.isContextual(112)) { + node.kind = "global"; + { + node.global = true; + } + node.id = this.parseIdentifier(); + } else if (this.match(134)) { + node.kind = "module"; + node.id = super.parseStringLiteral(this.state.value); + } else { + this.unexpected(); + } + if (this.match(5)) { + this.scope.enter(256); + this.prodParam.enter(0); + node.body = this.tsParseModuleBlock(); + this.prodParam.exit(); + this.scope.exit(); + } else { + this.semicolon(); + } + return this.finishNode(node, "TSModuleDeclaration"); + } + tsParseImportEqualsDeclaration(node, maybeDefaultIdentifier, isExport) { + { + node.isExport = isExport || false; + } + node.id = maybeDefaultIdentifier || this.parseIdentifier(); + this.checkIdentifier(node.id, 4096); + this.expect(29); + const moduleReference = this.tsParseModuleReference(); + if (node.importKind === "type" && moduleReference.type !== "TSExternalModuleReference") { + this.raise(TSErrors.ImportAliasHasImportType, moduleReference); + } + node.moduleReference = moduleReference; + this.semicolon(); + return this.finishNode(node, "TSImportEqualsDeclaration"); + } + tsIsExternalModuleReference() { + return this.isContextual(119) && this.lookaheadCharCode() === 40; + } + tsParseModuleReference() { + return this.tsIsExternalModuleReference() ? this.tsParseExternalModuleReference() : this.tsParseEntityName(0); + } + tsParseExternalModuleReference() { + const node = this.startNode(); + this.expectContextual(119); + this.expect(10); + if (!this.match(134)) { + this.unexpected(); + } + node.expression = super.parseExprAtom(); + this.expect(11); + this.sawUnambiguousESM = true; + return this.finishNode(node, "TSExternalModuleReference"); + } + tsLookAhead(f) { + const state = this.state.clone(); + const res = f(); + this.state = state; + return res; + } + tsTryParseAndCatch(f) { + const result = this.tryParse(abort => f() || abort()); + if (result.aborted || !result.node) return; + if (result.error) this.state = result.failState; + return result.node; + } + tsTryParse(f) { + const state = this.state.clone(); + const result = f(); + if (result !== undefined && result !== false) { + return result; + } + this.state = state; + } + tsTryParseDeclare(nany) { + if (this.isLineTerminator()) { + return; + } + let startType = this.state.type; + let kind; + if (this.isContextual(100)) { + startType = 74; + kind = "let"; + } + return this.tsInAmbientContext(() => { + switch (startType) { + case 68: + nany.declare = true; + return super.parseFunctionStatement(nany, false, false); + case 80: + nany.declare = true; + return this.parseClass(nany, true, false); + case 126: + return this.tsParseEnumDeclaration(nany, { + declare: true + }); + case 112: + return this.tsParseAmbientExternalModuleDeclaration(nany); + case 75: + case 74: + if (!this.match(75) || !this.isLookaheadContextual("enum")) { + nany.declare = true; + return this.parseVarStatement(nany, kind || this.state.value, true); + } + this.expect(75); + return this.tsParseEnumDeclaration(nany, { + const: true, + declare: true + }); + case 129: + { + const result = this.tsParseInterfaceDeclaration(nany, { + declare: true + }); + if (result) return result; + } + default: + if (tokenIsIdentifier(startType)) { + return this.tsParseDeclaration(nany, this.state.value, true, null); + } + } + }); + } + tsTryParseExportDeclaration() { + return this.tsParseDeclaration(this.startNode(), this.state.value, true, null); + } + tsParseExpressionStatement(node, expr, decorators) { + switch (expr.name) { + case "declare": + { + const declaration = this.tsTryParseDeclare(node); + if (declaration) { + declaration.declare = true; + } + return declaration; + } + case "global": + if (this.match(5)) { + this.scope.enter(256); + this.prodParam.enter(0); + const mod = node; + mod.kind = "global"; + { + node.global = true; + } + mod.id = expr; + mod.body = this.tsParseModuleBlock(); + this.scope.exit(); + this.prodParam.exit(); + return this.finishNode(mod, "TSModuleDeclaration"); + } + break; + default: + return this.tsParseDeclaration(node, expr.name, false, decorators); + } + } + tsParseDeclaration(node, value, next, decorators) { + switch (value) { + case "abstract": + if (this.tsCheckLineTerminator(next) && (this.match(80) || tokenIsIdentifier(this.state.type))) { + return this.tsParseAbstractDeclaration(node, decorators); + } + break; + case "module": + if (this.tsCheckLineTerminator(next)) { + if (this.match(134)) { + return this.tsParseAmbientExternalModuleDeclaration(node); + } else if (tokenIsIdentifier(this.state.type)) { + node.kind = "module"; + return this.tsParseModuleOrNamespaceDeclaration(node); + } + } + break; + case "namespace": + if (this.tsCheckLineTerminator(next) && tokenIsIdentifier(this.state.type)) { + node.kind = "namespace"; + return this.tsParseModuleOrNamespaceDeclaration(node); + } + break; + case "type": + if (this.tsCheckLineTerminator(next) && tokenIsIdentifier(this.state.type)) { + return this.tsParseTypeAliasDeclaration(node); + } + break; + } + } + tsCheckLineTerminator(next) { + if (next) { + if (this.hasFollowingLineBreak()) return false; + this.next(); + return true; + } + return !this.isLineTerminator(); + } + tsTryParseGenericAsyncArrowFunction(startLoc) { + if (!this.match(47)) return; + const oldMaybeInArrowParameters = this.state.maybeInArrowParameters; + this.state.maybeInArrowParameters = true; + const res = this.tsTryParseAndCatch(() => { + const node = this.startNodeAt(startLoc); + node.typeParameters = this.tsParseTypeParameters(this.tsParseConstModifier); + super.parseFunctionParams(node); + node.returnType = this.tsTryParseTypeOrTypePredicateAnnotation(); + this.expect(19); + return node; + }); + this.state.maybeInArrowParameters = oldMaybeInArrowParameters; + if (!res) return; + return super.parseArrowExpression(res, null, true); + } + tsParseTypeArgumentsInExpression() { + if (this.reScan_lt() !== 47) return; + return this.tsParseTypeArguments(); + } + tsParseTypeArguments() { + const node = this.startNode(); + node.params = this.tsInType(() => this.tsInTopLevelContext(() => { + this.expect(47); + return this.tsParseDelimitedList("TypeParametersOrArguments", this.tsParseType.bind(this)); + })); + if (node.params.length === 0) { + this.raise(TSErrors.EmptyTypeArguments, node); + } else if (!this.state.inType && this.curContext() === types.brace) { + this.reScan_lt_gt(); + } + this.expect(48); + return this.finishNode(node, "TSTypeParameterInstantiation"); + } + tsIsDeclarationStart() { + return tokenIsTSDeclarationStart(this.state.type); + } + isExportDefaultSpecifier() { + if (this.tsIsDeclarationStart()) return false; + return super.isExportDefaultSpecifier(); + } + parseBindingElement(flags, decorators) { + const startLoc = decorators.length ? decorators[0].loc.start : this.state.startLoc; + const modified = {}; + this.tsParseModifiers({ + allowedModifiers: ["public", "private", "protected", "override", "readonly"] + }, modified); + const accessibility = modified.accessibility; + const override = modified.override; + const readonly = modified.readonly; + if (!(flags & 4) && (accessibility || readonly || override)) { + this.raise(TSErrors.UnexpectedParameterModifier, startLoc); + } + const left = this.parseMaybeDefault(); + if (flags & 2) { + this.parseFunctionParamType(left); + } + const elt = this.parseMaybeDefault(left.loc.start, left); + if (accessibility || readonly || override) { + const pp = this.startNodeAt(startLoc); + if (decorators.length) { + pp.decorators = decorators; + } + if (accessibility) pp.accessibility = accessibility; + if (readonly) pp.readonly = readonly; + if (override) pp.override = override; + if (elt.type !== "Identifier" && elt.type !== "AssignmentPattern") { + this.raise(TSErrors.UnsupportedParameterPropertyKind, pp); + } + pp.parameter = elt; + return this.finishNode(pp, "TSParameterProperty"); + } + if (decorators.length) { + left.decorators = decorators; + } + return elt; + } + isSimpleParameter(node) { + return node.type === "TSParameterProperty" && super.isSimpleParameter(node.parameter) || super.isSimpleParameter(node); + } + tsDisallowOptionalPattern(node) { + for (const param of node.params) { + if (param.type !== "Identifier" && param.optional && !this.state.isAmbientContext) { + this.raise(TSErrors.PatternIsOptional, param); + } + } + } + setArrowFunctionParameters(node, params, trailingCommaLoc) { + super.setArrowFunctionParameters(node, params, trailingCommaLoc); + this.tsDisallowOptionalPattern(node); + } + parseFunctionBodyAndFinish(node, type, isMethod = false) { + if (this.match(14)) { + node.returnType = this.tsParseTypeOrTypePredicateAnnotation(14); + } + const bodilessType = type === "FunctionDeclaration" ? "TSDeclareFunction" : type === "ClassMethod" || type === "ClassPrivateMethod" ? "TSDeclareMethod" : undefined; + if (bodilessType && !this.match(5) && this.isLineTerminator()) { + return this.finishNode(node, bodilessType); + } + if (bodilessType === "TSDeclareFunction" && this.state.isAmbientContext) { + this.raise(TSErrors.DeclareFunctionHasImplementation, node); + if (node.declare) { + return super.parseFunctionBodyAndFinish(node, bodilessType, isMethod); + } + } + this.tsDisallowOptionalPattern(node); + return super.parseFunctionBodyAndFinish(node, type, isMethod); + } + registerFunctionStatementId(node) { + if (!node.body && node.id) { + this.checkIdentifier(node.id, 1024); + } else { + super.registerFunctionStatementId(node); + } + } + tsCheckForInvalidTypeCasts(items) { + items.forEach(node => { + if ((node == null ? void 0 : node.type) === "TSTypeCastExpression") { + this.raise(TSErrors.UnexpectedTypeAnnotation, node.typeAnnotation); + } + }); + } + toReferencedList(exprList, isInParens) { + this.tsCheckForInvalidTypeCasts(exprList); + return exprList; + } + parseArrayLike(close, canBePattern, isTuple, refExpressionErrors) { + const node = super.parseArrayLike(close, canBePattern, isTuple, refExpressionErrors); + if (node.type === "ArrayExpression") { + this.tsCheckForInvalidTypeCasts(node.elements); + } + return node; + } + parseSubscript(base, startLoc, noCalls, state) { + if (!this.hasPrecedingLineBreak() && this.match(35)) { + this.state.canStartJSXElement = false; + this.next(); + const nonNullExpression = this.startNodeAt(startLoc); + nonNullExpression.expression = base; + return this.finishNode(nonNullExpression, "TSNonNullExpression"); + } + let isOptionalCall = false; + if (this.match(18) && this.lookaheadCharCode() === 60) { + if (noCalls) { + state.stop = true; + return base; + } + state.optionalChainMember = isOptionalCall = true; + this.next(); + } + if (this.match(47) || this.match(51)) { + let missingParenErrorLoc; + const result = this.tsTryParseAndCatch(() => { + if (!noCalls && this.atPossibleAsyncArrow(base)) { + const asyncArrowFn = this.tsTryParseGenericAsyncArrowFunction(startLoc); + if (asyncArrowFn) { + return asyncArrowFn; + } + } + const typeArguments = this.tsParseTypeArgumentsInExpression(); + if (!typeArguments) return; + if (isOptionalCall && !this.match(10)) { + missingParenErrorLoc = this.state.curPosition(); + return; + } + if (tokenIsTemplate(this.state.type)) { + const result = super.parseTaggedTemplateExpression(base, startLoc, state); + { + result.typeParameters = typeArguments; + } + return result; + } + if (!noCalls && this.eat(10)) { + const node = this.startNodeAt(startLoc); + node.callee = base; + node.arguments = this.parseCallExpressionArguments(11); + this.tsCheckForInvalidTypeCasts(node.arguments); + { + node.typeParameters = typeArguments; + } + if (state.optionalChainMember) { + node.optional = isOptionalCall; + } + return this.finishCallExpression(node, state.optionalChainMember); + } + const tokenType = this.state.type; + if (tokenType === 48 || tokenType === 52 || tokenType !== 10 && tokenCanStartExpression(tokenType) && !this.hasPrecedingLineBreak()) { + return; + } + const node = this.startNodeAt(startLoc); + node.expression = base; + { + node.typeParameters = typeArguments; + } + return this.finishNode(node, "TSInstantiationExpression"); + }); + if (missingParenErrorLoc) { + this.unexpected(missingParenErrorLoc, 10); + } + if (result) { + if (result.type === "TSInstantiationExpression") { + if (this.match(16) || this.match(18) && this.lookaheadCharCode() !== 40) { + this.raise(TSErrors.InvalidPropertyAccessAfterInstantiationExpression, this.state.startLoc); + } + if (!this.match(16) && !this.match(18)) { + result.expression = super.stopParseSubscript(base, state); + } + } + return result; + } + } + return super.parseSubscript(base, startLoc, noCalls, state); + } + parseNewCallee(node) { + var _callee$extra; + super.parseNewCallee(node); + const { + callee + } = node; + if (callee.type === "TSInstantiationExpression" && !((_callee$extra = callee.extra) != null && _callee$extra.parenthesized)) { + { + node.typeParameters = callee.typeParameters; + } + node.callee = callee.expression; + } + } + parseExprOp(left, leftStartLoc, minPrec) { + let isSatisfies; + if (tokenOperatorPrecedence(58) > minPrec && !this.hasPrecedingLineBreak() && (this.isContextual(93) || (isSatisfies = this.isContextual(120)))) { + const node = this.startNodeAt(leftStartLoc); + node.expression = left; + node.typeAnnotation = this.tsInType(() => { + this.next(); + if (this.match(75)) { + if (isSatisfies) { + this.raise(Errors.UnexpectedKeyword, this.state.startLoc, { + keyword: "const" + }); + } + return this.tsParseTypeReference(); + } + return this.tsParseType(); + }); + this.finishNode(node, isSatisfies ? "TSSatisfiesExpression" : "TSAsExpression"); + this.reScan_lt_gt(); + return this.parseExprOp(node, leftStartLoc, minPrec); + } + return super.parseExprOp(left, leftStartLoc, minPrec); + } + checkReservedWord(word, startLoc, checkKeywords, isBinding) { + if (!this.state.isAmbientContext) { + super.checkReservedWord(word, startLoc, checkKeywords, isBinding); + } + } + checkImportReflection(node) { + super.checkImportReflection(node); + if (node.module && node.importKind !== "value") { + this.raise(TSErrors.ImportReflectionHasImportType, node.specifiers[0].loc.start); + } + } + checkDuplicateExports() {} + isPotentialImportPhase(isExport) { + if (super.isPotentialImportPhase(isExport)) return true; + if (this.isContextual(130)) { + const ch = this.lookaheadCharCode(); + return isExport ? ch === 123 || ch === 42 : ch !== 61; + } + return !isExport && this.isContextual(87); + } + applyImportPhase(node, isExport, phase, loc) { + super.applyImportPhase(node, isExport, phase, loc); + if (isExport) { + node.exportKind = phase === "type" ? "type" : "value"; + } else { + node.importKind = phase === "type" || phase === "typeof" ? phase : "value"; + } + } + parseImport(node) { + if (this.match(134)) { + node.importKind = "value"; + return super.parseImport(node); + } + let importNode; + if (tokenIsIdentifier(this.state.type) && this.lookaheadCharCode() === 61) { + node.importKind = "value"; + return this.tsParseImportEqualsDeclaration(node); + } else if (this.isContextual(130)) { + const maybeDefaultIdentifier = this.parseMaybeImportPhase(node, false); + if (this.lookaheadCharCode() === 61) { + return this.tsParseImportEqualsDeclaration(node, maybeDefaultIdentifier); + } else { + importNode = super.parseImportSpecifiersAndAfter(node, maybeDefaultIdentifier); + } + } else { + importNode = super.parseImport(node); + } + if (importNode.importKind === "type" && importNode.specifiers.length > 1 && importNode.specifiers[0].type === "ImportDefaultSpecifier") { + this.raise(TSErrors.TypeImportCannotSpecifyDefaultAndNamed, importNode); + } + return importNode; + } + parseExport(node, decorators) { + if (this.match(83)) { + const nodeImportEquals = node; + this.next(); + let maybeDefaultIdentifier = null; + if (this.isContextual(130) && this.isPotentialImportPhase(false)) { + maybeDefaultIdentifier = this.parseMaybeImportPhase(nodeImportEquals, false); + } else { + nodeImportEquals.importKind = "value"; + } + const declaration = this.tsParseImportEqualsDeclaration(nodeImportEquals, maybeDefaultIdentifier, true); + { + return declaration; + } + } else if (this.eat(29)) { + const assign = node; + assign.expression = super.parseExpression(); + this.semicolon(); + this.sawUnambiguousESM = true; + return this.finishNode(assign, "TSExportAssignment"); + } else if (this.eatContextual(93)) { + const decl = node; + this.expectContextual(128); + decl.id = this.parseIdentifier(); + this.semicolon(); + return this.finishNode(decl, "TSNamespaceExportDeclaration"); + } else { + return super.parseExport(node, decorators); + } + } + isAbstractClass() { + return this.isContextual(124) && this.lookahead().type === 80; + } + parseExportDefaultExpression() { + if (this.isAbstractClass()) { + const cls = this.startNode(); + this.next(); + cls.abstract = true; + return this.parseClass(cls, true, true); + } + if (this.match(129)) { + const result = this.tsParseInterfaceDeclaration(this.startNode()); + if (result) return result; + } + return super.parseExportDefaultExpression(); + } + parseVarStatement(node, kind, allowMissingInitializer = false) { + const { + isAmbientContext + } = this.state; + const declaration = super.parseVarStatement(node, kind, allowMissingInitializer || isAmbientContext); + if (!isAmbientContext) return declaration; + for (const { + id, + init + } of declaration.declarations) { + if (!init) continue; + if (kind !== "const" || !!id.typeAnnotation) { + this.raise(TSErrors.InitializerNotAllowedInAmbientContext, init); + } else if (!isValidAmbientConstInitializer(init, this.hasPlugin("estree"))) { + this.raise(TSErrors.ConstInitializerMustBeStringOrNumericLiteralOrLiteralEnumReference, init); + } + } + return declaration; + } + parseStatementContent(flags, decorators) { + if (this.match(75) && this.isLookaheadContextual("enum")) { + const node = this.startNode(); + this.expect(75); + return this.tsParseEnumDeclaration(node, { + const: true + }); + } + if (this.isContextual(126)) { + return this.tsParseEnumDeclaration(this.startNode()); + } + if (this.isContextual(129)) { + const result = this.tsParseInterfaceDeclaration(this.startNode()); + if (result) return result; + } + return super.parseStatementContent(flags, decorators); + } + parseAccessModifier() { + return this.tsParseModifier(["public", "protected", "private"]); + } + tsHasSomeModifiers(member, modifiers) { + return modifiers.some(modifier => { + if (tsIsAccessModifier(modifier)) { + return member.accessibility === modifier; + } + return !!member[modifier]; + }); + } + tsIsStartOfStaticBlocks() { + return this.isContextual(106) && this.lookaheadCharCode() === 123; + } + parseClassMember(classBody, member, state) { + const modifiers = ["declare", "private", "public", "protected", "override", "abstract", "readonly", "static"]; + this.tsParseModifiers({ + allowedModifiers: modifiers, + disallowedModifiers: ["in", "out"], + stopOnStartOfClassStaticBlock: true, + errorTemplate: TSErrors.InvalidModifierOnTypeParameterPositions + }, member); + const callParseClassMemberWithIsStatic = () => { + if (this.tsIsStartOfStaticBlocks()) { + this.next(); + this.next(); + if (this.tsHasSomeModifiers(member, modifiers)) { + this.raise(TSErrors.StaticBlockCannotHaveModifier, this.state.curPosition()); + } + super.parseClassStaticBlock(classBody, member); + } else { + this.parseClassMemberWithIsStatic(classBody, member, state, !!member.static); + } + }; + if (member.declare) { + this.tsInAmbientContext(callParseClassMemberWithIsStatic); + } else { + callParseClassMemberWithIsStatic(); + } + } + parseClassMemberWithIsStatic(classBody, member, state, isStatic) { + const idx = this.tsTryParseIndexSignature(member); + if (idx) { + classBody.body.push(idx); + if (member.abstract) { + this.raise(TSErrors.IndexSignatureHasAbstract, member); + } + if (member.accessibility) { + this.raise(TSErrors.IndexSignatureHasAccessibility, member, { + modifier: member.accessibility + }); + } + if (member.declare) { + this.raise(TSErrors.IndexSignatureHasDeclare, member); + } + if (member.override) { + this.raise(TSErrors.IndexSignatureHasOverride, member); + } + return; + } + if (!this.state.inAbstractClass && member.abstract) { + this.raise(TSErrors.NonAbstractClassHasAbstractMethod, member); + } + if (member.override) { + if (!state.hadSuperClass) { + this.raise(TSErrors.OverrideNotInSubClass, member); + } + } + super.parseClassMemberWithIsStatic(classBody, member, state, isStatic); + } + parsePostMemberNameModifiers(methodOrProp) { + const optional = this.eat(17); + if (optional) methodOrProp.optional = true; + if (methodOrProp.readonly && this.match(10)) { + this.raise(TSErrors.ClassMethodHasReadonly, methodOrProp); + } + if (methodOrProp.declare && this.match(10)) { + this.raise(TSErrors.ClassMethodHasDeclare, methodOrProp); + } + } + parseExpressionStatement(node, expr, decorators) { + const decl = expr.type === "Identifier" ? this.tsParseExpressionStatement(node, expr, decorators) : undefined; + return decl || super.parseExpressionStatement(node, expr, decorators); + } + shouldParseExportDeclaration() { + if (this.tsIsDeclarationStart()) return true; + return super.shouldParseExportDeclaration(); + } + parseConditional(expr, startLoc, refExpressionErrors) { + if (!this.match(17)) return expr; + if (this.state.maybeInArrowParameters) { + const nextCh = this.lookaheadCharCode(); + if (nextCh === 44 || nextCh === 61 || nextCh === 58 || nextCh === 41) { + this.setOptionalParametersError(refExpressionErrors); + return expr; + } + } + return super.parseConditional(expr, startLoc, refExpressionErrors); + } + parseParenItem(node, startLoc) { + const newNode = super.parseParenItem(node, startLoc); + if (this.eat(17)) { + newNode.optional = true; + this.resetEndLocation(node); + } + if (this.match(14)) { + const typeCastNode = this.startNodeAt(startLoc); + typeCastNode.expression = node; + typeCastNode.typeAnnotation = this.tsParseTypeAnnotation(); + return this.finishNode(typeCastNode, "TSTypeCastExpression"); + } + return node; + } + parseExportDeclaration(node) { + if (!this.state.isAmbientContext && this.isContextual(125)) { + return this.tsInAmbientContext(() => this.parseExportDeclaration(node)); + } + const startLoc = this.state.startLoc; + const isDeclare = this.eatContextual(125); + if (isDeclare && (this.isContextual(125) || !this.shouldParseExportDeclaration())) { + throw this.raise(TSErrors.ExpectedAmbientAfterExportDeclare, this.state.startLoc); + } + const isIdentifier = tokenIsIdentifier(this.state.type); + const declaration = isIdentifier && this.tsTryParseExportDeclaration() || super.parseExportDeclaration(node); + if (!declaration) return null; + if (declaration.type === "TSInterfaceDeclaration" || declaration.type === "TSTypeAliasDeclaration" || isDeclare) { + node.exportKind = "type"; + } + if (isDeclare && declaration.type !== "TSImportEqualsDeclaration") { + this.resetStartLocation(declaration, startLoc); + declaration.declare = true; + } + return declaration; + } + parseClassId(node, isStatement, optionalId, bindingType) { + if ((!isStatement || optionalId) && this.isContextual(113)) { + return; + } + super.parseClassId(node, isStatement, optionalId, node.declare ? 1024 : 8331); + const typeParameters = this.tsTryParseTypeParameters(this.tsParseInOutConstModifiers); + if (typeParameters) node.typeParameters = typeParameters; + } + parseClassPropertyAnnotation(node) { + if (!node.optional) { + if (this.eat(35)) { + node.definite = true; + } else if (this.eat(17)) { + node.optional = true; + } + } + const type = this.tsTryParseTypeAnnotation(); + if (type) node.typeAnnotation = type; + } + parseClassProperty(node) { + this.parseClassPropertyAnnotation(node); + if (this.state.isAmbientContext && !(node.readonly && !node.typeAnnotation) && this.match(29)) { + this.raise(TSErrors.DeclareClassFieldHasInitializer, this.state.startLoc); + } + if (node.abstract && this.match(29)) { + const { + key + } = node; + this.raise(TSErrors.AbstractPropertyHasInitializer, this.state.startLoc, { + propertyName: key.type === "Identifier" && !node.computed ? key.name : `[${this.input.slice(this.offsetToSourcePos(key.start), this.offsetToSourcePos(key.end))}]` + }); + } + return super.parseClassProperty(node); + } + parseClassPrivateProperty(node) { + if (node.abstract) { + this.raise(TSErrors.PrivateElementHasAbstract, node); + } + if (node.accessibility) { + this.raise(TSErrors.PrivateElementHasAccessibility, node, { + modifier: node.accessibility + }); + } + this.parseClassPropertyAnnotation(node); + return super.parseClassPrivateProperty(node); + } + parseClassAccessorProperty(node) { + this.parseClassPropertyAnnotation(node); + if (node.optional) { + this.raise(TSErrors.AccessorCannotBeOptional, node); + } + return super.parseClassAccessorProperty(node); + } + pushClassMethod(classBody, method, isGenerator, isAsync, isConstructor, allowsDirectSuper) { + const typeParameters = this.tsTryParseTypeParameters(this.tsParseConstModifier); + if (typeParameters && isConstructor) { + this.raise(TSErrors.ConstructorHasTypeParameters, typeParameters); + } + const { + declare = false, + kind + } = method; + if (declare && (kind === "get" || kind === "set")) { + this.raise(TSErrors.DeclareAccessor, method, { + kind + }); + } + if (typeParameters) method.typeParameters = typeParameters; + super.pushClassMethod(classBody, method, isGenerator, isAsync, isConstructor, allowsDirectSuper); + } + pushClassPrivateMethod(classBody, method, isGenerator, isAsync) { + const typeParameters = this.tsTryParseTypeParameters(this.tsParseConstModifier); + if (typeParameters) method.typeParameters = typeParameters; + super.pushClassPrivateMethod(classBody, method, isGenerator, isAsync); + } + declareClassPrivateMethodInScope(node, kind) { + if (node.type === "TSDeclareMethod") return; + if (node.type === "MethodDefinition" && node.value.body == null) { + return; + } + super.declareClassPrivateMethodInScope(node, kind); + } + parseClassSuper(node) { + super.parseClassSuper(node); + if (node.superClass && (this.match(47) || this.match(51))) { + { + node.superTypeParameters = this.tsParseTypeArgumentsInExpression(); + } + } + if (this.eatContextual(113)) { + node.implements = this.tsParseHeritageClause("implements"); + } + } + parseObjPropValue(prop, startLoc, isGenerator, isAsync, isPattern, isAccessor, refExpressionErrors) { + const typeParameters = this.tsTryParseTypeParameters(this.tsParseConstModifier); + if (typeParameters) prop.typeParameters = typeParameters; + return super.parseObjPropValue(prop, startLoc, isGenerator, isAsync, isPattern, isAccessor, refExpressionErrors); + } + parseFunctionParams(node, isConstructor) { + const typeParameters = this.tsTryParseTypeParameters(this.tsParseConstModifier); + if (typeParameters) node.typeParameters = typeParameters; + super.parseFunctionParams(node, isConstructor); + } + parseVarId(decl, kind) { + super.parseVarId(decl, kind); + if (decl.id.type === "Identifier" && !this.hasPrecedingLineBreak() && this.eat(35)) { + decl.definite = true; + } + const type = this.tsTryParseTypeAnnotation(); + if (type) { + decl.id.typeAnnotation = type; + this.resetEndLocation(decl.id); + } + } + parseAsyncArrowFromCallExpression(node, call) { + if (this.match(14)) { + node.returnType = this.tsParseTypeAnnotation(); + } + return super.parseAsyncArrowFromCallExpression(node, call); + } + parseMaybeAssign(refExpressionErrors, afterLeftParse) { + var _jsx, _jsx2, _typeCast, _jsx3, _typeCast2; + let state; + let jsx; + let typeCast; + if (this.hasPlugin("jsx") && (this.match(143) || this.match(47))) { + state = this.state.clone(); + jsx = this.tryParse(() => super.parseMaybeAssign(refExpressionErrors, afterLeftParse), state); + if (!jsx.error) return jsx.node; + const { + context + } = this.state; + const currentContext = context[context.length - 1]; + if (currentContext === types.j_oTag || currentContext === types.j_expr) { + context.pop(); + } + } + if (!((_jsx = jsx) != null && _jsx.error) && !this.match(47)) { + return super.parseMaybeAssign(refExpressionErrors, afterLeftParse); + } + if (!state || state === this.state) state = this.state.clone(); + let typeParameters; + const arrow = this.tryParse(abort => { + var _expr$extra, _typeParameters; + typeParameters = this.tsParseTypeParameters(this.tsParseConstModifier); + const expr = super.parseMaybeAssign(refExpressionErrors, afterLeftParse); + if (expr.type !== "ArrowFunctionExpression" || (_expr$extra = expr.extra) != null && _expr$extra.parenthesized) { + abort(); + } + if (((_typeParameters = typeParameters) == null ? void 0 : _typeParameters.params.length) !== 0) { + this.resetStartLocationFromNode(expr, typeParameters); + } + expr.typeParameters = typeParameters; + return expr; + }, state); + if (!arrow.error && !arrow.aborted) { + if (typeParameters) this.reportReservedArrowTypeParam(typeParameters); + return arrow.node; + } + if (!jsx) { + assert(!this.hasPlugin("jsx")); + typeCast = this.tryParse(() => super.parseMaybeAssign(refExpressionErrors, afterLeftParse), state); + if (!typeCast.error) return typeCast.node; + } + if ((_jsx2 = jsx) != null && _jsx2.node) { + this.state = jsx.failState; + return jsx.node; + } + if (arrow.node) { + this.state = arrow.failState; + if (typeParameters) this.reportReservedArrowTypeParam(typeParameters); + return arrow.node; + } + if ((_typeCast = typeCast) != null && _typeCast.node) { + this.state = typeCast.failState; + return typeCast.node; + } + throw ((_jsx3 = jsx) == null ? void 0 : _jsx3.error) || arrow.error || ((_typeCast2 = typeCast) == null ? void 0 : _typeCast2.error); + } + reportReservedArrowTypeParam(node) { + var _node$extra2; + if (node.params.length === 1 && !node.params[0].constraint && !((_node$extra2 = node.extra) != null && _node$extra2.trailingComma) && this.getPluginOption("typescript", "disallowAmbiguousJSXLike")) { + this.raise(TSErrors.ReservedArrowTypeParam, node); + } + } + parseMaybeUnary(refExpressionErrors, sawUnary) { + if (!this.hasPlugin("jsx") && this.match(47)) { + return this.tsParseTypeAssertion(); + } + return super.parseMaybeUnary(refExpressionErrors, sawUnary); + } + parseArrow(node) { + if (this.match(14)) { + const result = this.tryParse(abort => { + const returnType = this.tsParseTypeOrTypePredicateAnnotation(14); + if (this.canInsertSemicolon() || !this.match(19)) abort(); + return returnType; + }); + if (result.aborted) return; + if (!result.thrown) { + if (result.error) this.state = result.failState; + node.returnType = result.node; + } + } + return super.parseArrow(node); + } + parseFunctionParamType(param) { + if (this.eat(17)) { + param.optional = true; + } + const type = this.tsTryParseTypeAnnotation(); + if (type) param.typeAnnotation = type; + this.resetEndLocation(param); + return param; + } + isAssignable(node, isBinding) { + switch (node.type) { + case "TSTypeCastExpression": + return this.isAssignable(node.expression, isBinding); + case "TSParameterProperty": + return true; + default: + return super.isAssignable(node, isBinding); + } + } + toAssignable(node, isLHS = false) { + switch (node.type) { + case "ParenthesizedExpression": + this.toAssignableParenthesizedExpression(node, isLHS); + break; + case "TSAsExpression": + case "TSSatisfiesExpression": + case "TSNonNullExpression": + case "TSTypeAssertion": + if (isLHS) { + this.expressionScope.recordArrowParameterBindingError(TSErrors.UnexpectedTypeCastInParameter, node); + } else { + this.raise(TSErrors.UnexpectedTypeCastInParameter, node); + } + this.toAssignable(node.expression, isLHS); + break; + case "AssignmentExpression": + if (!isLHS && node.left.type === "TSTypeCastExpression") { + node.left = this.typeCastToParameter(node.left); + } + default: + super.toAssignable(node, isLHS); + } + } + toAssignableParenthesizedExpression(node, isLHS) { + switch (node.expression.type) { + case "TSAsExpression": + case "TSSatisfiesExpression": + case "TSNonNullExpression": + case "TSTypeAssertion": + case "ParenthesizedExpression": + this.toAssignable(node.expression, isLHS); + break; + default: + super.toAssignable(node, isLHS); + } + } + checkToRestConversion(node, allowPattern) { + switch (node.type) { + case "TSAsExpression": + case "TSSatisfiesExpression": + case "TSTypeAssertion": + case "TSNonNullExpression": + this.checkToRestConversion(node.expression, false); + break; + default: + super.checkToRestConversion(node, allowPattern); + } + } + isValidLVal(type, isUnparenthesizedInAssign, binding) { + switch (type) { + case "TSTypeCastExpression": + return true; + case "TSParameterProperty": + return "parameter"; + case "TSNonNullExpression": + return "expression"; + case "TSAsExpression": + case "TSSatisfiesExpression": + case "TSTypeAssertion": + return (binding !== 64 || !isUnparenthesizedInAssign) && ["expression", true]; + default: + return super.isValidLVal(type, isUnparenthesizedInAssign, binding); + } + } + parseBindingAtom() { + if (this.state.type === 78) { + return this.parseIdentifier(true); + } + return super.parseBindingAtom(); + } + parseMaybeDecoratorArguments(expr, startLoc) { + if (this.match(47) || this.match(51)) { + const typeArguments = this.tsParseTypeArgumentsInExpression(); + if (this.match(10)) { + const call = super.parseMaybeDecoratorArguments(expr, startLoc); + { + call.typeParameters = typeArguments; + } + return call; + } + this.unexpected(null, 10); + } + return super.parseMaybeDecoratorArguments(expr, startLoc); + } + checkCommaAfterRest(close) { + if (this.state.isAmbientContext && this.match(12) && this.lookaheadCharCode() === close) { + this.next(); + return false; + } + return super.checkCommaAfterRest(close); + } + isClassMethod() { + return this.match(47) || super.isClassMethod(); + } + isClassProperty() { + return this.match(35) || this.match(14) || super.isClassProperty(); + } + parseMaybeDefault(startLoc, left) { + const node = super.parseMaybeDefault(startLoc, left); + if (node.type === "AssignmentPattern" && node.typeAnnotation && node.right.start < node.typeAnnotation.start) { + this.raise(TSErrors.TypeAnnotationAfterAssign, node.typeAnnotation); + } + return node; + } + getTokenFromCode(code) { + if (this.state.inType) { + if (code === 62) { + this.finishOp(48, 1); + return; + } + if (code === 60) { + this.finishOp(47, 1); + return; + } + } + super.getTokenFromCode(code); + } + reScan_lt_gt() { + const { + type + } = this.state; + if (type === 47) { + this.state.pos -= 1; + this.readToken_lt(); + } else if (type === 48) { + this.state.pos -= 1; + this.readToken_gt(); + } + } + reScan_lt() { + const { + type + } = this.state; + if (type === 51) { + this.state.pos -= 2; + this.finishOp(47, 1); + return 47; + } + return type; + } + toAssignableListItem(exprList, index, isLHS) { + const node = exprList[index]; + if (node.type === "TSTypeCastExpression") { + exprList[index] = this.typeCastToParameter(node); + } + super.toAssignableListItem(exprList, index, isLHS); + } + typeCastToParameter(node) { + node.expression.typeAnnotation = node.typeAnnotation; + this.resetEndLocation(node.expression, node.typeAnnotation.loc.end); + return node.expression; + } + shouldParseArrow(params) { + if (this.match(14)) { + return params.every(expr => this.isAssignable(expr, true)); + } + return super.shouldParseArrow(params); + } + shouldParseAsyncArrow() { + return this.match(14) || super.shouldParseAsyncArrow(); + } + canHaveLeadingDecorator() { + return super.canHaveLeadingDecorator() || this.isAbstractClass(); + } + jsxParseOpeningElementAfterName(node) { + if (this.match(47) || this.match(51)) { + const typeArguments = this.tsTryParseAndCatch(() => this.tsParseTypeArgumentsInExpression()); + if (typeArguments) { + { + node.typeParameters = typeArguments; + } + } + } + return super.jsxParseOpeningElementAfterName(node); + } + getGetterSetterExpectedParamCount(method) { + const baseCount = super.getGetterSetterExpectedParamCount(method); + const params = this.getObjectOrClassMethodParams(method); + const firstParam = params[0]; + const hasContextParam = firstParam && this.isThisParam(firstParam); + return hasContextParam ? baseCount + 1 : baseCount; + } + parseCatchClauseParam() { + const param = super.parseCatchClauseParam(); + const type = this.tsTryParseTypeAnnotation(); + if (type) { + param.typeAnnotation = type; + this.resetEndLocation(param); + } + return param; + } + tsInAmbientContext(cb) { + const { + isAmbientContext: oldIsAmbientContext, + strict: oldStrict + } = this.state; + this.state.isAmbientContext = true; + this.state.strict = false; + try { + return cb(); + } finally { + this.state.isAmbientContext = oldIsAmbientContext; + this.state.strict = oldStrict; + } + } + parseClass(node, isStatement, optionalId) { + const oldInAbstractClass = this.state.inAbstractClass; + this.state.inAbstractClass = !!node.abstract; + try { + return super.parseClass(node, isStatement, optionalId); + } finally { + this.state.inAbstractClass = oldInAbstractClass; + } + } + tsParseAbstractDeclaration(node, decorators) { + if (this.match(80)) { + node.abstract = true; + return this.maybeTakeDecorators(decorators, this.parseClass(node, true, false)); + } else if (this.isContextual(129)) { + if (!this.hasFollowingLineBreak()) { + node.abstract = true; + this.raise(TSErrors.NonClassMethodPropertyHasAbstractModifer, node); + return this.tsParseInterfaceDeclaration(node); + } + } else { + this.unexpected(null, 80); + } + } + parseMethod(node, isGenerator, isAsync, isConstructor, allowDirectSuper, type, inClassScope) { + const method = super.parseMethod(node, isGenerator, isAsync, isConstructor, allowDirectSuper, type, inClassScope); + if (method.abstract || method.type === "TSAbstractMethodDefinition") { + const hasEstreePlugin = this.hasPlugin("estree"); + const methodFn = hasEstreePlugin ? method.value : method; + if (methodFn.body) { + const { + key + } = method; + this.raise(TSErrors.AbstractMethodHasImplementation, method, { + methodName: key.type === "Identifier" && !method.computed ? key.name : `[${this.input.slice(this.offsetToSourcePos(key.start), this.offsetToSourcePos(key.end))}]` + }); + } + } + return method; + } + tsParseTypeParameterName() { + const typeName = this.parseIdentifier(); + return typeName.name; + } + shouldParseAsAmbientContext() { + return !!this.getPluginOption("typescript", "dts"); + } + parse() { + if (this.shouldParseAsAmbientContext()) { + this.state.isAmbientContext = true; + } + return super.parse(); + } + getExpression() { + if (this.shouldParseAsAmbientContext()) { + this.state.isAmbientContext = true; + } + return super.getExpression(); + } + parseExportSpecifier(node, isString, isInTypeExport, isMaybeTypeOnly) { + if (!isString && isMaybeTypeOnly) { + this.parseTypeOnlyImportExportSpecifier(node, false, isInTypeExport); + return this.finishNode(node, "ExportSpecifier"); + } + node.exportKind = "value"; + return super.parseExportSpecifier(node, isString, isInTypeExport, isMaybeTypeOnly); + } + parseImportSpecifier(specifier, importedIsString, isInTypeOnlyImport, isMaybeTypeOnly, bindingType) { + if (!importedIsString && isMaybeTypeOnly) { + this.parseTypeOnlyImportExportSpecifier(specifier, true, isInTypeOnlyImport); + return this.finishNode(specifier, "ImportSpecifier"); + } + specifier.importKind = "value"; + return super.parseImportSpecifier(specifier, importedIsString, isInTypeOnlyImport, isMaybeTypeOnly, isInTypeOnlyImport ? 4098 : 4096); + } + parseTypeOnlyImportExportSpecifier(node, isImport, isInTypeOnlyImportExport) { + const leftOfAsKey = isImport ? "imported" : "local"; + const rightOfAsKey = isImport ? "local" : "exported"; + let leftOfAs = node[leftOfAsKey]; + let rightOfAs; + let hasTypeSpecifier = false; + let canParseAsKeyword = true; + const loc = leftOfAs.loc.start; + if (this.isContextual(93)) { + const firstAs = this.parseIdentifier(); + if (this.isContextual(93)) { + const secondAs = this.parseIdentifier(); + if (tokenIsKeywordOrIdentifier(this.state.type)) { + hasTypeSpecifier = true; + leftOfAs = firstAs; + rightOfAs = isImport ? this.parseIdentifier() : this.parseModuleExportName(); + canParseAsKeyword = false; + } else { + rightOfAs = secondAs; + canParseAsKeyword = false; + } + } else if (tokenIsKeywordOrIdentifier(this.state.type)) { + canParseAsKeyword = false; + rightOfAs = isImport ? this.parseIdentifier() : this.parseModuleExportName(); + } else { + hasTypeSpecifier = true; + leftOfAs = firstAs; + } + } else if (tokenIsKeywordOrIdentifier(this.state.type)) { + hasTypeSpecifier = true; + if (isImport) { + leftOfAs = this.parseIdentifier(true); + if (!this.isContextual(93)) { + this.checkReservedWord(leftOfAs.name, leftOfAs.loc.start, true, true); + } + } else { + leftOfAs = this.parseModuleExportName(); + } + } + if (hasTypeSpecifier && isInTypeOnlyImportExport) { + this.raise(isImport ? TSErrors.TypeModifierIsUsedInTypeImports : TSErrors.TypeModifierIsUsedInTypeExports, loc); + } + node[leftOfAsKey] = leftOfAs; + node[rightOfAsKey] = rightOfAs; + const kindKey = isImport ? "importKind" : "exportKind"; + node[kindKey] = hasTypeSpecifier ? "type" : "value"; + if (canParseAsKeyword && this.eatContextual(93)) { + node[rightOfAsKey] = isImport ? this.parseIdentifier() : this.parseModuleExportName(); + } + if (!node[rightOfAsKey]) { + node[rightOfAsKey] = this.cloneIdentifier(node[leftOfAsKey]); + } + if (isImport) { + this.checkIdentifier(node[rightOfAsKey], hasTypeSpecifier ? 4098 : 4096); + } + } + fillOptionalPropertiesForTSESLint(node) { + var _node$directive, _node$decorators, _node$optional, _node$typeAnnotation, _node$accessibility, _node$decorators2, _node$override, _node$readonly, _node$static, _node$declare, _node$returnType, _node$typeParameters, _node$optional2, _node$optional3, _node$accessibility2, _node$readonly2, _node$static2, _node$declare2, _node$definite, _node$readonly3, _node$typeAnnotation2, _node$accessibility3, _node$decorators3, _node$override2, _node$optional4, _node$id, _node$abstract, _node$declare3, _node$decorators4, _node$implements, _node$superTypeArgume, _node$typeParameters2, _node$declare4, _node$definite2, _node$const, _node$declare5, _node$computed, _node$qualifier, _node$options, _node$declare6, _node$extends, _node$declare7, _node$global, _node$const2, _node$in, _node$out; + switch (node.type) { + case "ExpressionStatement": + (_node$directive = node.directive) != null ? _node$directive : node.directive = undefined; + return; + case "RestElement": + node.value = undefined; + case "Identifier": + case "ArrayPattern": + case "AssignmentPattern": + case "ObjectPattern": + (_node$decorators = node.decorators) != null ? _node$decorators : node.decorators = []; + (_node$optional = node.optional) != null ? _node$optional : node.optional = false; + (_node$typeAnnotation = node.typeAnnotation) != null ? _node$typeAnnotation : node.typeAnnotation = undefined; + return; + case "TSParameterProperty": + (_node$accessibility = node.accessibility) != null ? _node$accessibility : node.accessibility = undefined; + (_node$decorators2 = node.decorators) != null ? _node$decorators2 : node.decorators = []; + (_node$override = node.override) != null ? _node$override : node.override = false; + (_node$readonly = node.readonly) != null ? _node$readonly : node.readonly = false; + (_node$static = node.static) != null ? _node$static : node.static = false; + return; + case "TSEmptyBodyFunctionExpression": + node.body = null; + case "TSDeclareFunction": + case "FunctionDeclaration": + case "FunctionExpression": + case "ClassMethod": + case "ClassPrivateMethod": + (_node$declare = node.declare) != null ? _node$declare : node.declare = false; + (_node$returnType = node.returnType) != null ? _node$returnType : node.returnType = undefined; + (_node$typeParameters = node.typeParameters) != null ? _node$typeParameters : node.typeParameters = undefined; + return; + case "Property": + (_node$optional2 = node.optional) != null ? _node$optional2 : node.optional = false; + return; + case "TSMethodSignature": + case "TSPropertySignature": + (_node$optional3 = node.optional) != null ? _node$optional3 : node.optional = false; + case "TSIndexSignature": + (_node$accessibility2 = node.accessibility) != null ? _node$accessibility2 : node.accessibility = undefined; + (_node$readonly2 = node.readonly) != null ? _node$readonly2 : node.readonly = false; + (_node$static2 = node.static) != null ? _node$static2 : node.static = false; + return; + case "TSAbstractPropertyDefinition": + case "PropertyDefinition": + case "TSAbstractAccessorProperty": + case "AccessorProperty": + (_node$declare2 = node.declare) != null ? _node$declare2 : node.declare = false; + (_node$definite = node.definite) != null ? _node$definite : node.definite = false; + (_node$readonly3 = node.readonly) != null ? _node$readonly3 : node.readonly = false; + (_node$typeAnnotation2 = node.typeAnnotation) != null ? _node$typeAnnotation2 : node.typeAnnotation = undefined; + case "TSAbstractMethodDefinition": + case "MethodDefinition": + (_node$accessibility3 = node.accessibility) != null ? _node$accessibility3 : node.accessibility = undefined; + (_node$decorators3 = node.decorators) != null ? _node$decorators3 : node.decorators = []; + (_node$override2 = node.override) != null ? _node$override2 : node.override = false; + (_node$optional4 = node.optional) != null ? _node$optional4 : node.optional = false; + return; + case "ClassExpression": + (_node$id = node.id) != null ? _node$id : node.id = null; + case "ClassDeclaration": + (_node$abstract = node.abstract) != null ? _node$abstract : node.abstract = false; + (_node$declare3 = node.declare) != null ? _node$declare3 : node.declare = false; + (_node$decorators4 = node.decorators) != null ? _node$decorators4 : node.decorators = []; + (_node$implements = node.implements) != null ? _node$implements : node.implements = []; + (_node$superTypeArgume = node.superTypeArguments) != null ? _node$superTypeArgume : node.superTypeArguments = undefined; + (_node$typeParameters2 = node.typeParameters) != null ? _node$typeParameters2 : node.typeParameters = undefined; + return; + case "TSTypeAliasDeclaration": + case "VariableDeclaration": + (_node$declare4 = node.declare) != null ? _node$declare4 : node.declare = false; + return; + case "VariableDeclarator": + (_node$definite2 = node.definite) != null ? _node$definite2 : node.definite = false; + return; + case "TSEnumDeclaration": + (_node$const = node.const) != null ? _node$const : node.const = false; + (_node$declare5 = node.declare) != null ? _node$declare5 : node.declare = false; + return; + case "TSEnumMember": + (_node$computed = node.computed) != null ? _node$computed : node.computed = false; + return; + case "TSImportType": + (_node$qualifier = node.qualifier) != null ? _node$qualifier : node.qualifier = null; + (_node$options = node.options) != null ? _node$options : node.options = null; + return; + case "TSInterfaceDeclaration": + (_node$declare6 = node.declare) != null ? _node$declare6 : node.declare = false; + (_node$extends = node.extends) != null ? _node$extends : node.extends = []; + return; + case "TSModuleDeclaration": + (_node$declare7 = node.declare) != null ? _node$declare7 : node.declare = false; + (_node$global = node.global) != null ? _node$global : node.global = node.kind === "global"; + return; + case "TSTypeParameter": + (_node$const2 = node.const) != null ? _node$const2 : node.const = false; + (_node$in = node.in) != null ? _node$in : node.in = false; + (_node$out = node.out) != null ? _node$out : node.out = false; + return; + } + } +}; +function isPossiblyLiteralEnum(expression) { + if (expression.type !== "MemberExpression") return false; + const { + computed, + property + } = expression; + if (computed && property.type !== "StringLiteral" && (property.type !== "TemplateLiteral" || property.expressions.length > 0)) { + return false; + } + return isUncomputedMemberExpressionChain(expression.object); +} +function isValidAmbientConstInitializer(expression, estree) { + var _expression$extra; + const { + type + } = expression; + if ((_expression$extra = expression.extra) != null && _expression$extra.parenthesized) { + return false; + } + if (estree) { + if (type === "Literal") { + const { + value + } = expression; + if (typeof value === "string" || typeof value === "boolean") { + return true; + } + } + } else { + if (type === "StringLiteral" || type === "BooleanLiteral") { + return true; + } + } + if (isNumber(expression, estree) || isNegativeNumber(expression, estree)) { + return true; + } + if (type === "TemplateLiteral" && expression.expressions.length === 0) { + return true; + } + if (isPossiblyLiteralEnum(expression)) { + return true; + } + return false; +} +function isNumber(expression, estree) { + if (estree) { + return expression.type === "Literal" && (typeof expression.value === "number" || "bigint" in expression); + } + return expression.type === "NumericLiteral" || expression.type === "BigIntLiteral"; +} +function isNegativeNumber(expression, estree) { + if (expression.type === "UnaryExpression") { + const { + operator, + argument + } = expression; + if (operator === "-" && isNumber(argument, estree)) { + return true; + } + } + return false; +} +function isUncomputedMemberExpressionChain(expression) { + if (expression.type === "Identifier") return true; + if (expression.type !== "MemberExpression" || expression.computed) { + return false; + } + return isUncomputedMemberExpressionChain(expression.object); +} +const PlaceholderErrors = ParseErrorEnum`placeholders`({ + ClassNameIsRequired: "A class name is required.", + UnexpectedSpace: "Unexpected space in placeholder." +}); +var placeholders = superClass => class PlaceholdersParserMixin extends superClass { + parsePlaceholder(expectedNode) { + if (this.match(133)) { + const node = this.startNode(); + this.next(); + this.assertNoSpace(); + node.name = super.parseIdentifier(true); + this.assertNoSpace(); + this.expect(133); + return this.finishPlaceholder(node, expectedNode); + } + } + finishPlaceholder(node, expectedNode) { + let placeholder = node; + if (!placeholder.expectedNode || !placeholder.type) { + placeholder = this.finishNode(placeholder, "Placeholder"); + } + placeholder.expectedNode = expectedNode; + return placeholder; + } + getTokenFromCode(code) { + if (code === 37 && this.input.charCodeAt(this.state.pos + 1) === 37) { + this.finishOp(133, 2); + } else { + super.getTokenFromCode(code); + } + } + parseExprAtom(refExpressionErrors) { + return this.parsePlaceholder("Expression") || super.parseExprAtom(refExpressionErrors); + } + parseIdentifier(liberal) { + return this.parsePlaceholder("Identifier") || super.parseIdentifier(liberal); + } + checkReservedWord(word, startLoc, checkKeywords, isBinding) { + if (word !== undefined) { + super.checkReservedWord(word, startLoc, checkKeywords, isBinding); + } + } + cloneIdentifier(node) { + const cloned = super.cloneIdentifier(node); + if (cloned.type === "Placeholder") { + cloned.expectedNode = node.expectedNode; + } + return cloned; + } + cloneStringLiteral(node) { + if (node.type === "Placeholder") { + return this.cloneIdentifier(node); + } + return super.cloneStringLiteral(node); + } + parseBindingAtom() { + return this.parsePlaceholder("Pattern") || super.parseBindingAtom(); + } + isValidLVal(type, isParenthesized, binding) { + return type === "Placeholder" || super.isValidLVal(type, isParenthesized, binding); + } + toAssignable(node, isLHS) { + if (node && node.type === "Placeholder" && node.expectedNode === "Expression") { + node.expectedNode = "Pattern"; + } else { + super.toAssignable(node, isLHS); + } + } + chStartsBindingIdentifier(ch, pos) { + if (super.chStartsBindingIdentifier(ch, pos)) { + return true; + } + const nextToken = this.lookahead(); + if (nextToken.type === 133) { + return true; + } + return false; + } + verifyBreakContinue(node, isBreak) { + if (node.label && node.label.type === "Placeholder") return; + super.verifyBreakContinue(node, isBreak); + } + parseExpressionStatement(node, expr) { + var _expr$extra; + if (expr.type !== "Placeholder" || (_expr$extra = expr.extra) != null && _expr$extra.parenthesized) { + return super.parseExpressionStatement(node, expr); + } + if (this.match(14)) { + const stmt = node; + stmt.label = this.finishPlaceholder(expr, "Identifier"); + this.next(); + stmt.body = super.parseStatementOrSloppyAnnexBFunctionDeclaration(); + return this.finishNode(stmt, "LabeledStatement"); + } + this.semicolon(); + const stmtPlaceholder = node; + stmtPlaceholder.name = expr.name; + return this.finishPlaceholder(stmtPlaceholder, "Statement"); + } + parseBlock(allowDirectives, createNewLexicalScope, afterBlockParse) { + return this.parsePlaceholder("BlockStatement") || super.parseBlock(allowDirectives, createNewLexicalScope, afterBlockParse); + } + parseFunctionId(requireId) { + return this.parsePlaceholder("Identifier") || super.parseFunctionId(requireId); + } + parseClass(node, isStatement, optionalId) { + const type = isStatement ? "ClassDeclaration" : "ClassExpression"; + this.next(); + const oldStrict = this.state.strict; + const placeholder = this.parsePlaceholder("Identifier"); + if (placeholder) { + if (this.match(81) || this.match(133) || this.match(5)) { + node.id = placeholder; + } else if (optionalId || !isStatement) { + node.id = null; + node.body = this.finishPlaceholder(placeholder, "ClassBody"); + return this.finishNode(node, type); + } else { + throw this.raise(PlaceholderErrors.ClassNameIsRequired, this.state.startLoc); + } + } else { + this.parseClassId(node, isStatement, optionalId); + } + super.parseClassSuper(node); + node.body = this.parsePlaceholder("ClassBody") || super.parseClassBody(!!node.superClass, oldStrict); + return this.finishNode(node, type); + } + parseExport(node, decorators) { + const placeholder = this.parsePlaceholder("Identifier"); + if (!placeholder) return super.parseExport(node, decorators); + const node2 = node; + if (!this.isContextual(98) && !this.match(12)) { + node2.specifiers = []; + node2.source = null; + node2.declaration = this.finishPlaceholder(placeholder, "Declaration"); + return this.finishNode(node2, "ExportNamedDeclaration"); + } + this.expectPlugin("exportDefaultFrom"); + const specifier = this.startNode(); + specifier.exported = placeholder; + node2.specifiers = [this.finishNode(specifier, "ExportDefaultSpecifier")]; + return super.parseExport(node2, decorators); + } + isExportDefaultSpecifier() { + if (this.match(65)) { + const next = this.nextTokenStart(); + if (this.isUnparsedContextual(next, "from")) { + if (this.input.startsWith(tokenLabelName(133), this.nextTokenStartSince(next + 4))) { + return true; + } + } + } + return super.isExportDefaultSpecifier(); + } + maybeParseExportDefaultSpecifier(node, maybeDefaultIdentifier) { + var _specifiers; + if ((_specifiers = node.specifiers) != null && _specifiers.length) { + return true; + } + return super.maybeParseExportDefaultSpecifier(node, maybeDefaultIdentifier); + } + checkExport(node) { + const { + specifiers + } = node; + if (specifiers != null && specifiers.length) { + node.specifiers = specifiers.filter(node => node.exported.type === "Placeholder"); + } + super.checkExport(node); + node.specifiers = specifiers; + } + parseImport(node) { + const placeholder = this.parsePlaceholder("Identifier"); + if (!placeholder) return super.parseImport(node); + node.specifiers = []; + if (!this.isContextual(98) && !this.match(12)) { + node.source = this.finishPlaceholder(placeholder, "StringLiteral"); + this.semicolon(); + return this.finishNode(node, "ImportDeclaration"); + } + const specifier = this.startNodeAtNode(placeholder); + specifier.local = placeholder; + node.specifiers.push(this.finishNode(specifier, "ImportDefaultSpecifier")); + if (this.eat(12)) { + const hasStarImport = this.maybeParseStarImportSpecifier(node); + if (!hasStarImport) this.parseNamedImportSpecifiers(node); + } + this.expectContextual(98); + node.source = this.parseImportSource(); + this.semicolon(); + return this.finishNode(node, "ImportDeclaration"); + } + parseImportSource() { + return this.parsePlaceholder("StringLiteral") || super.parseImportSource(); + } + assertNoSpace() { + if (this.state.start > this.offsetToSourcePos(this.state.lastTokEndLoc.index)) { + this.raise(PlaceholderErrors.UnexpectedSpace, this.state.lastTokEndLoc); + } + } +}; +var v8intrinsic = superClass => class V8IntrinsicMixin extends superClass { + parseV8Intrinsic() { + if (this.match(54)) { + const v8IntrinsicStartLoc = this.state.startLoc; + const node = this.startNode(); + this.next(); + if (tokenIsIdentifier(this.state.type)) { + const name = this.parseIdentifierName(); + const identifier = this.createIdentifier(node, name); + this.castNodeTo(identifier, "V8IntrinsicIdentifier"); + if (this.match(10)) { + return identifier; + } + } + this.unexpected(v8IntrinsicStartLoc); + } + } + parseExprAtom(refExpressionErrors) { + return this.parseV8Intrinsic() || super.parseExprAtom(refExpressionErrors); + } +}; +const PIPELINE_PROPOSALS = ["minimal", "fsharp", "hack", "smart"]; +const TOPIC_TOKENS = ["^^", "@@", "^", "%", "#"]; +function validatePlugins(pluginsMap) { + if (pluginsMap.has("decorators")) { + if (pluginsMap.has("decorators-legacy")) { + throw new Error("Cannot use the decorators and decorators-legacy plugin together"); + } + const decoratorsBeforeExport = pluginsMap.get("decorators").decoratorsBeforeExport; + if (decoratorsBeforeExport != null && typeof decoratorsBeforeExport !== "boolean") { + throw new Error("'decoratorsBeforeExport' must be a boolean, if specified."); + } + const allowCallParenthesized = pluginsMap.get("decorators").allowCallParenthesized; + if (allowCallParenthesized != null && typeof allowCallParenthesized !== "boolean") { + throw new Error("'allowCallParenthesized' must be a boolean."); + } + } + if (pluginsMap.has("flow") && pluginsMap.has("typescript")) { + throw new Error("Cannot combine flow and typescript plugins."); + } + if (pluginsMap.has("placeholders") && pluginsMap.has("v8intrinsic")) { + throw new Error("Cannot combine placeholders and v8intrinsic plugins."); + } + if (pluginsMap.has("pipelineOperator")) { + var _pluginsMap$get2; + const proposal = pluginsMap.get("pipelineOperator").proposal; + if (!PIPELINE_PROPOSALS.includes(proposal)) { + const proposalList = PIPELINE_PROPOSALS.map(p => `"${p}"`).join(", "); + throw new Error(`"pipelineOperator" requires "proposal" option whose value must be one of: ${proposalList}.`); + } + if (proposal === "hack") { + if (pluginsMap.has("placeholders")) { + throw new Error("Cannot combine placeholders plugin and Hack-style pipes."); + } + if (pluginsMap.has("v8intrinsic")) { + throw new Error("Cannot combine v8intrinsic plugin and Hack-style pipes."); + } + const topicToken = pluginsMap.get("pipelineOperator").topicToken; + if (!TOPIC_TOKENS.includes(topicToken)) { + const tokenList = TOPIC_TOKENS.map(t => `"${t}"`).join(", "); + throw new Error(`"pipelineOperator" in "proposal": "hack" mode also requires a "topicToken" option whose value must be one of: ${tokenList}.`); + } + { + var _pluginsMap$get; + if (topicToken === "#" && ((_pluginsMap$get = pluginsMap.get("recordAndTuple")) == null ? void 0 : _pluginsMap$get.syntaxType) === "hash") { + throw new Error(`Plugin conflict between \`["pipelineOperator", { proposal: "hack", topicToken: "#" }]\` and \`${JSON.stringify(["recordAndTuple", pluginsMap.get("recordAndTuple")])}\`.`); + } + } + } else if (proposal === "smart" && ((_pluginsMap$get2 = pluginsMap.get("recordAndTuple")) == null ? void 0 : _pluginsMap$get2.syntaxType) === "hash") { + throw new Error(`Plugin conflict between \`["pipelineOperator", { proposal: "smart" }]\` and \`${JSON.stringify(["recordAndTuple", pluginsMap.get("recordAndTuple")])}\`.`); + } + } + if (pluginsMap.has("moduleAttributes")) { + { + if (pluginsMap.has("deprecatedImportAssert") || pluginsMap.has("importAssertions")) { + throw new Error("Cannot combine importAssertions, deprecatedImportAssert and moduleAttributes plugins."); + } + const moduleAttributesVersionPluginOption = pluginsMap.get("moduleAttributes").version; + if (moduleAttributesVersionPluginOption !== "may-2020") { + throw new Error("The 'moduleAttributes' plugin requires a 'version' option," + " representing the last proposal update. Currently, the" + " only supported value is 'may-2020'."); + } + } + } + if (pluginsMap.has("importAssertions")) { + if (pluginsMap.has("deprecatedImportAssert")) { + throw new Error("Cannot combine importAssertions and deprecatedImportAssert plugins."); + } + } + if (!pluginsMap.has("deprecatedImportAssert") && pluginsMap.has("importAttributes") && pluginsMap.get("importAttributes").deprecatedAssertSyntax) { + { + pluginsMap.set("deprecatedImportAssert", {}); + } + } + if (pluginsMap.has("recordAndTuple")) { + { + const syntaxType = pluginsMap.get("recordAndTuple").syntaxType; + if (syntaxType != null) { + const RECORD_AND_TUPLE_SYNTAX_TYPES = ["hash", "bar"]; + if (!RECORD_AND_TUPLE_SYNTAX_TYPES.includes(syntaxType)) { + throw new Error("The 'syntaxType' option of the 'recordAndTuple' plugin must be one of: " + RECORD_AND_TUPLE_SYNTAX_TYPES.map(p => `'${p}'`).join(", ")); + } + } + } + } + if (pluginsMap.has("asyncDoExpressions") && !pluginsMap.has("doExpressions")) { + const error = new Error("'asyncDoExpressions' requires 'doExpressions', please add 'doExpressions' to parser plugins."); + error.missingPlugins = "doExpressions"; + throw error; + } + if (pluginsMap.has("optionalChainingAssign") && pluginsMap.get("optionalChainingAssign").version !== "2023-07") { + throw new Error("The 'optionalChainingAssign' plugin requires a 'version' option," + " representing the last proposal update. Currently, the" + " only supported value is '2023-07'."); + } +} +const mixinPlugins = { + estree, + jsx, + flow, + typescript, + v8intrinsic, + placeholders +}; +const mixinPluginNames = Object.keys(mixinPlugins); +class ExpressionParser extends LValParser { + checkProto(prop, isRecord, sawProto, refExpressionErrors) { + if (prop.type === "SpreadElement" || this.isObjectMethod(prop) || prop.computed || prop.shorthand) { + return sawProto; + } + const key = prop.key; + const name = key.type === "Identifier" ? key.name : key.value; + if (name === "__proto__") { + if (isRecord) { + this.raise(Errors.RecordNoProto, key); + return true; + } + if (sawProto) { + if (refExpressionErrors) { + if (refExpressionErrors.doubleProtoLoc === null) { + refExpressionErrors.doubleProtoLoc = key.loc.start; + } + } else { + this.raise(Errors.DuplicateProto, key); + } + } + return true; + } + return sawProto; + } + shouldExitDescending(expr, potentialArrowAt) { + return expr.type === "ArrowFunctionExpression" && this.offsetToSourcePos(expr.start) === potentialArrowAt; + } + getExpression() { + this.enterInitialScopes(); + this.nextToken(); + const expr = this.parseExpression(); + if (!this.match(140)) { + this.unexpected(); + } + this.finalizeRemainingComments(); + expr.comments = this.comments; + expr.errors = this.state.errors; + if (this.optionFlags & 256) { + expr.tokens = this.tokens; + } + return expr; + } + parseExpression(disallowIn, refExpressionErrors) { + if (disallowIn) { + return this.disallowInAnd(() => this.parseExpressionBase(refExpressionErrors)); + } + return this.allowInAnd(() => this.parseExpressionBase(refExpressionErrors)); + } + parseExpressionBase(refExpressionErrors) { + const startLoc = this.state.startLoc; + const expr = this.parseMaybeAssign(refExpressionErrors); + if (this.match(12)) { + const node = this.startNodeAt(startLoc); + node.expressions = [expr]; + while (this.eat(12)) { + node.expressions.push(this.parseMaybeAssign(refExpressionErrors)); + } + this.toReferencedList(node.expressions); + return this.finishNode(node, "SequenceExpression"); + } + return expr; + } + parseMaybeAssignDisallowIn(refExpressionErrors, afterLeftParse) { + return this.disallowInAnd(() => this.parseMaybeAssign(refExpressionErrors, afterLeftParse)); + } + parseMaybeAssignAllowIn(refExpressionErrors, afterLeftParse) { + return this.allowInAnd(() => this.parseMaybeAssign(refExpressionErrors, afterLeftParse)); + } + setOptionalParametersError(refExpressionErrors) { + refExpressionErrors.optionalParametersLoc = this.state.startLoc; + } + parseMaybeAssign(refExpressionErrors, afterLeftParse) { + const startLoc = this.state.startLoc; + const isYield = this.isContextual(108); + if (isYield) { + if (this.prodParam.hasYield) { + this.next(); + let left = this.parseYield(startLoc); + if (afterLeftParse) { + left = afterLeftParse.call(this, left, startLoc); + } + return left; + } + } + let ownExpressionErrors; + if (refExpressionErrors) { + ownExpressionErrors = false; + } else { + refExpressionErrors = new ExpressionErrors(); + ownExpressionErrors = true; + } + const { + type + } = this.state; + if (type === 10 || tokenIsIdentifier(type)) { + this.state.potentialArrowAt = this.state.start; + } + let left = this.parseMaybeConditional(refExpressionErrors); + if (afterLeftParse) { + left = afterLeftParse.call(this, left, startLoc); + } + if (tokenIsAssignment(this.state.type)) { + const node = this.startNodeAt(startLoc); + const operator = this.state.value; + node.operator = operator; + if (this.match(29)) { + this.toAssignable(left, true); + node.left = left; + const startIndex = startLoc.index; + if (refExpressionErrors.doubleProtoLoc != null && refExpressionErrors.doubleProtoLoc.index >= startIndex) { + refExpressionErrors.doubleProtoLoc = null; + } + if (refExpressionErrors.shorthandAssignLoc != null && refExpressionErrors.shorthandAssignLoc.index >= startIndex) { + refExpressionErrors.shorthandAssignLoc = null; + } + if (refExpressionErrors.privateKeyLoc != null && refExpressionErrors.privateKeyLoc.index >= startIndex) { + this.checkDestructuringPrivate(refExpressionErrors); + refExpressionErrors.privateKeyLoc = null; + } + } else { + node.left = left; + } + this.next(); + node.right = this.parseMaybeAssign(); + this.checkLVal(left, this.finishNode(node, "AssignmentExpression")); + return node; + } else if (ownExpressionErrors) { + this.checkExpressionErrors(refExpressionErrors, true); + } + if (isYield) { + const { + type + } = this.state; + const startsExpr = this.hasPlugin("v8intrinsic") ? tokenCanStartExpression(type) : tokenCanStartExpression(type) && !this.match(54); + if (startsExpr && !this.isAmbiguousPrefixOrIdentifier()) { + this.raiseOverwrite(Errors.YieldNotInGeneratorFunction, startLoc); + return this.parseYield(startLoc); + } + } + return left; + } + parseMaybeConditional(refExpressionErrors) { + const startLoc = this.state.startLoc; + const potentialArrowAt = this.state.potentialArrowAt; + const expr = this.parseExprOps(refExpressionErrors); + if (this.shouldExitDescending(expr, potentialArrowAt)) { + return expr; + } + return this.parseConditional(expr, startLoc, refExpressionErrors); + } + parseConditional(expr, startLoc, refExpressionErrors) { + if (this.eat(17)) { + const node = this.startNodeAt(startLoc); + node.test = expr; + node.consequent = this.parseMaybeAssignAllowIn(); + this.expect(14); + node.alternate = this.parseMaybeAssign(); + return this.finishNode(node, "ConditionalExpression"); + } + return expr; + } + parseMaybeUnaryOrPrivate(refExpressionErrors) { + return this.match(139) ? this.parsePrivateName() : this.parseMaybeUnary(refExpressionErrors); + } + parseExprOps(refExpressionErrors) { + const startLoc = this.state.startLoc; + const potentialArrowAt = this.state.potentialArrowAt; + const expr = this.parseMaybeUnaryOrPrivate(refExpressionErrors); + if (this.shouldExitDescending(expr, potentialArrowAt)) { + return expr; + } + return this.parseExprOp(expr, startLoc, -1); + } + parseExprOp(left, leftStartLoc, minPrec) { + if (this.isPrivateName(left)) { + const value = this.getPrivateNameSV(left); + if (minPrec >= tokenOperatorPrecedence(58) || !this.prodParam.hasIn || !this.match(58)) { + this.raise(Errors.PrivateInExpectedIn, left, { + identifierName: value + }); + } + this.classScope.usePrivateName(value, left.loc.start); + } + const op = this.state.type; + if (tokenIsOperator(op) && (this.prodParam.hasIn || !this.match(58))) { + let prec = tokenOperatorPrecedence(op); + if (prec > minPrec) { + if (op === 39) { + this.expectPlugin("pipelineOperator"); + if (this.state.inFSharpPipelineDirectBody) { + return left; + } + this.checkPipelineAtInfixOperator(left, leftStartLoc); + } + const node = this.startNodeAt(leftStartLoc); + node.left = left; + node.operator = this.state.value; + const logical = op === 41 || op === 42; + const coalesce = op === 40; + if (coalesce) { + prec = tokenOperatorPrecedence(42); + } + this.next(); + if (op === 39 && this.hasPlugin(["pipelineOperator", { + proposal: "minimal" + }])) { + if (this.state.type === 96 && this.prodParam.hasAwait) { + throw this.raise(Errors.UnexpectedAwaitAfterPipelineBody, this.state.startLoc); + } + } + node.right = this.parseExprOpRightExpr(op, prec); + const finishedNode = this.finishNode(node, logical || coalesce ? "LogicalExpression" : "BinaryExpression"); + const nextOp = this.state.type; + if (coalesce && (nextOp === 41 || nextOp === 42) || logical && nextOp === 40) { + throw this.raise(Errors.MixingCoalesceWithLogical, this.state.startLoc); + } + return this.parseExprOp(finishedNode, leftStartLoc, minPrec); + } + } + return left; + } + parseExprOpRightExpr(op, prec) { + const startLoc = this.state.startLoc; + switch (op) { + case 39: + switch (this.getPluginOption("pipelineOperator", "proposal")) { + case "hack": + return this.withTopicBindingContext(() => { + return this.parseHackPipeBody(); + }); + case "fsharp": + return this.withSoloAwaitPermittingContext(() => { + return this.parseFSharpPipelineBody(prec); + }); + } + if (this.getPluginOption("pipelineOperator", "proposal") === "smart") { + return this.withTopicBindingContext(() => { + if (this.prodParam.hasYield && this.isContextual(108)) { + throw this.raise(Errors.PipeBodyIsTighter, this.state.startLoc); + } + return this.parseSmartPipelineBodyInStyle(this.parseExprOpBaseRightExpr(op, prec), startLoc); + }); + } + default: + return this.parseExprOpBaseRightExpr(op, prec); + } + } + parseExprOpBaseRightExpr(op, prec) { + const startLoc = this.state.startLoc; + return this.parseExprOp(this.parseMaybeUnaryOrPrivate(), startLoc, tokenIsRightAssociative(op) ? prec - 1 : prec); + } + parseHackPipeBody() { + var _body$extra; + const { + startLoc + } = this.state; + const body = this.parseMaybeAssign(); + const requiredParentheses = UnparenthesizedPipeBodyDescriptions.has(body.type); + if (requiredParentheses && !((_body$extra = body.extra) != null && _body$extra.parenthesized)) { + this.raise(Errors.PipeUnparenthesizedBody, startLoc, { + type: body.type + }); + } + if (!this.topicReferenceWasUsedInCurrentContext()) { + this.raise(Errors.PipeTopicUnused, startLoc); + } + return body; + } + checkExponentialAfterUnary(node) { + if (this.match(57)) { + this.raise(Errors.UnexpectedTokenUnaryExponentiation, node.argument); + } + } + parseMaybeUnary(refExpressionErrors, sawUnary) { + const startLoc = this.state.startLoc; + const isAwait = this.isContextual(96); + if (isAwait && this.recordAwaitIfAllowed()) { + this.next(); + const expr = this.parseAwait(startLoc); + if (!sawUnary) this.checkExponentialAfterUnary(expr); + return expr; + } + const update = this.match(34); + const node = this.startNode(); + if (tokenIsPrefix(this.state.type)) { + node.operator = this.state.value; + node.prefix = true; + if (this.match(72)) { + this.expectPlugin("throwExpressions"); + } + const isDelete = this.match(89); + this.next(); + node.argument = this.parseMaybeUnary(null, true); + this.checkExpressionErrors(refExpressionErrors, true); + if (this.state.strict && isDelete) { + const arg = node.argument; + if (arg.type === "Identifier") { + this.raise(Errors.StrictDelete, node); + } else if (this.hasPropertyAsPrivateName(arg)) { + this.raise(Errors.DeletePrivateField, node); + } + } + if (!update) { + if (!sawUnary) { + this.checkExponentialAfterUnary(node); + } + return this.finishNode(node, "UnaryExpression"); + } + } + const expr = this.parseUpdate(node, update, refExpressionErrors); + if (isAwait) { + const { + type + } = this.state; + const startsExpr = this.hasPlugin("v8intrinsic") ? tokenCanStartExpression(type) : tokenCanStartExpression(type) && !this.match(54); + if (startsExpr && !this.isAmbiguousPrefixOrIdentifier()) { + this.raiseOverwrite(Errors.AwaitNotInAsyncContext, startLoc); + return this.parseAwait(startLoc); + } + } + return expr; + } + parseUpdate(node, update, refExpressionErrors) { + if (update) { + const updateExpressionNode = node; + this.checkLVal(updateExpressionNode.argument, this.finishNode(updateExpressionNode, "UpdateExpression")); + return node; + } + const startLoc = this.state.startLoc; + let expr = this.parseExprSubscripts(refExpressionErrors); + if (this.checkExpressionErrors(refExpressionErrors, false)) return expr; + while (tokenIsPostfix(this.state.type) && !this.canInsertSemicolon()) { + const node = this.startNodeAt(startLoc); + node.operator = this.state.value; + node.prefix = false; + node.argument = expr; + this.next(); + this.checkLVal(expr, expr = this.finishNode(node, "UpdateExpression")); + } + return expr; + } + parseExprSubscripts(refExpressionErrors) { + const startLoc = this.state.startLoc; + const potentialArrowAt = this.state.potentialArrowAt; + const expr = this.parseExprAtom(refExpressionErrors); + if (this.shouldExitDescending(expr, potentialArrowAt)) { + return expr; + } + return this.parseSubscripts(expr, startLoc); + } + parseSubscripts(base, startLoc, noCalls) { + const state = { + optionalChainMember: false, + maybeAsyncArrow: this.atPossibleAsyncArrow(base), + stop: false + }; + do { + base = this.parseSubscript(base, startLoc, noCalls, state); + state.maybeAsyncArrow = false; + } while (!state.stop); + return base; + } + parseSubscript(base, startLoc, noCalls, state) { + const { + type + } = this.state; + if (!noCalls && type === 15) { + return this.parseBind(base, startLoc, noCalls, state); + } else if (tokenIsTemplate(type)) { + return this.parseTaggedTemplateExpression(base, startLoc, state); + } + let optional = false; + if (type === 18) { + if (noCalls) { + this.raise(Errors.OptionalChainingNoNew, this.state.startLoc); + if (this.lookaheadCharCode() === 40) { + return this.stopParseSubscript(base, state); + } + } + state.optionalChainMember = optional = true; + this.next(); + } + if (!noCalls && this.match(10)) { + return this.parseCoverCallAndAsyncArrowHead(base, startLoc, state, optional); + } else { + const computed = this.eat(0); + if (computed || optional || this.eat(16)) { + return this.parseMember(base, startLoc, state, computed, optional); + } else { + return this.stopParseSubscript(base, state); + } + } + } + stopParseSubscript(base, state) { + state.stop = true; + return base; + } + parseMember(base, startLoc, state, computed, optional) { + const node = this.startNodeAt(startLoc); + node.object = base; + node.computed = computed; + if (computed) { + node.property = this.parseExpression(); + this.expect(3); + } else if (this.match(139)) { + if (base.type === "Super") { + this.raise(Errors.SuperPrivateField, startLoc); + } + this.classScope.usePrivateName(this.state.value, this.state.startLoc); + node.property = this.parsePrivateName(); + } else { + node.property = this.parseIdentifier(true); + } + if (state.optionalChainMember) { + node.optional = optional; + return this.finishNode(node, "OptionalMemberExpression"); + } else { + return this.finishNode(node, "MemberExpression"); + } + } + parseBind(base, startLoc, noCalls, state) { + const node = this.startNodeAt(startLoc); + node.object = base; + this.next(); + node.callee = this.parseNoCallExpr(); + state.stop = true; + return this.parseSubscripts(this.finishNode(node, "BindExpression"), startLoc, noCalls); + } + parseCoverCallAndAsyncArrowHead(base, startLoc, state, optional) { + const oldMaybeInArrowParameters = this.state.maybeInArrowParameters; + let refExpressionErrors = null; + this.state.maybeInArrowParameters = true; + this.next(); + const node = this.startNodeAt(startLoc); + node.callee = base; + const { + maybeAsyncArrow, + optionalChainMember + } = state; + if (maybeAsyncArrow) { + this.expressionScope.enter(newAsyncArrowScope()); + refExpressionErrors = new ExpressionErrors(); + } + if (optionalChainMember) { + node.optional = optional; + } + if (optional) { + node.arguments = this.parseCallExpressionArguments(11); + } else { + node.arguments = this.parseCallExpressionArguments(11, base.type !== "Super", node, refExpressionErrors); + } + let finishedNode = this.finishCallExpression(node, optionalChainMember); + if (maybeAsyncArrow && this.shouldParseAsyncArrow() && !optional) { + state.stop = true; + this.checkDestructuringPrivate(refExpressionErrors); + this.expressionScope.validateAsPattern(); + this.expressionScope.exit(); + finishedNode = this.parseAsyncArrowFromCallExpression(this.startNodeAt(startLoc), finishedNode); + } else { + if (maybeAsyncArrow) { + this.checkExpressionErrors(refExpressionErrors, true); + this.expressionScope.exit(); + } + this.toReferencedArguments(finishedNode); + } + this.state.maybeInArrowParameters = oldMaybeInArrowParameters; + return finishedNode; + } + toReferencedArguments(node, isParenthesizedExpr) { + this.toReferencedListDeep(node.arguments, isParenthesizedExpr); + } + parseTaggedTemplateExpression(base, startLoc, state) { + const node = this.startNodeAt(startLoc); + node.tag = base; + node.quasi = this.parseTemplate(true); + if (state.optionalChainMember) { + this.raise(Errors.OptionalChainingNoTemplate, startLoc); + } + return this.finishNode(node, "TaggedTemplateExpression"); + } + atPossibleAsyncArrow(base) { + return base.type === "Identifier" && base.name === "async" && this.state.lastTokEndLoc.index === base.end && !this.canInsertSemicolon() && base.end - base.start === 5 && this.offsetToSourcePos(base.start) === this.state.potentialArrowAt; + } + finishCallExpression(node, optional) { + if (node.callee.type === "Import") { + if (node.arguments.length === 0 || node.arguments.length > 2) { + this.raise(Errors.ImportCallArity, node); + } else { + for (const arg of node.arguments) { + if (arg.type === "SpreadElement") { + this.raise(Errors.ImportCallSpreadArgument, arg); + } + } + } + } + return this.finishNode(node, optional ? "OptionalCallExpression" : "CallExpression"); + } + parseCallExpressionArguments(close, allowPlaceholder, nodeForExtra, refExpressionErrors) { + const elts = []; + let first = true; + const oldInFSharpPipelineDirectBody = this.state.inFSharpPipelineDirectBody; + this.state.inFSharpPipelineDirectBody = false; + while (!this.eat(close)) { + if (first) { + first = false; + } else { + this.expect(12); + if (this.match(close)) { + if (nodeForExtra) { + this.addTrailingCommaExtraToNode(nodeForExtra); + } + this.next(); + break; + } + } + elts.push(this.parseExprListItem(false, refExpressionErrors, allowPlaceholder)); + } + this.state.inFSharpPipelineDirectBody = oldInFSharpPipelineDirectBody; + return elts; + } + shouldParseAsyncArrow() { + return this.match(19) && !this.canInsertSemicolon(); + } + parseAsyncArrowFromCallExpression(node, call) { + var _call$extra; + this.resetPreviousNodeTrailingComments(call); + this.expect(19); + this.parseArrowExpression(node, call.arguments, true, (_call$extra = call.extra) == null ? void 0 : _call$extra.trailingCommaLoc); + if (call.innerComments) { + setInnerComments(node, call.innerComments); + } + if (call.callee.trailingComments) { + setInnerComments(node, call.callee.trailingComments); + } + return node; + } + parseNoCallExpr() { + const startLoc = this.state.startLoc; + return this.parseSubscripts(this.parseExprAtom(), startLoc, true); + } + parseExprAtom(refExpressionErrors) { + let node; + let decorators = null; + const { + type + } = this.state; + switch (type) { + case 79: + return this.parseSuper(); + case 83: + node = this.startNode(); + this.next(); + if (this.match(16)) { + return this.parseImportMetaProperty(node); + } + if (this.match(10)) { + if (this.optionFlags & 512) { + return this.parseImportCall(node); + } else { + return this.finishNode(node, "Import"); + } + } else { + this.raise(Errors.UnsupportedImport, this.state.lastTokStartLoc); + return this.finishNode(node, "Import"); + } + case 78: + node = this.startNode(); + this.next(); + return this.finishNode(node, "ThisExpression"); + case 90: + { + return this.parseDo(this.startNode(), false); + } + case 56: + case 31: + { + this.readRegexp(); + return this.parseRegExpLiteral(this.state.value); + } + case 135: + return this.parseNumericLiteral(this.state.value); + case 136: + return this.parseBigIntLiteral(this.state.value); + case 134: + return this.parseStringLiteral(this.state.value); + case 84: + return this.parseNullLiteral(); + case 85: + return this.parseBooleanLiteral(true); + case 86: + return this.parseBooleanLiteral(false); + case 10: + { + const canBeArrow = this.state.potentialArrowAt === this.state.start; + return this.parseParenAndDistinguishExpression(canBeArrow); + } + case 0: + { + return this.parseArrayLike(3, true, false, refExpressionErrors); + } + case 5: + { + return this.parseObjectLike(8, false, false, refExpressionErrors); + } + case 68: + return this.parseFunctionOrFunctionSent(); + case 26: + decorators = this.parseDecorators(); + case 80: + return this.parseClass(this.maybeTakeDecorators(decorators, this.startNode()), false); + case 77: + return this.parseNewOrNewTarget(); + case 25: + case 24: + return this.parseTemplate(false); + case 15: + { + node = this.startNode(); + this.next(); + node.object = null; + const callee = node.callee = this.parseNoCallExpr(); + if (callee.type === "MemberExpression") { + return this.finishNode(node, "BindExpression"); + } else { + throw this.raise(Errors.UnsupportedBind, callee); + } + } + case 139: + { + this.raise(Errors.PrivateInExpectedIn, this.state.startLoc, { + identifierName: this.state.value + }); + return this.parsePrivateName(); + } + case 33: + { + return this.parseTopicReferenceThenEqualsSign(54, "%"); + } + case 32: + { + return this.parseTopicReferenceThenEqualsSign(44, "^"); + } + case 37: + case 38: + { + return this.parseTopicReference("hack"); + } + case 44: + case 54: + case 27: + { + const pipeProposal = this.getPluginOption("pipelineOperator", "proposal"); + if (pipeProposal) { + return this.parseTopicReference(pipeProposal); + } + this.unexpected(); + break; + } + case 47: + { + const lookaheadCh = this.input.codePointAt(this.nextTokenStart()); + if (isIdentifierStart(lookaheadCh) || lookaheadCh === 62) { + this.expectOnePlugin(["jsx", "flow", "typescript"]); + } else { + this.unexpected(); + } + break; + } + default: + { + if (type === 137) { + return this.parseDecimalLiteral(this.state.value); + } else if (type === 2 || type === 1) { + return this.parseArrayLike(this.state.type === 2 ? 4 : 3, false, true); + } else if (type === 6 || type === 7) { + return this.parseObjectLike(this.state.type === 6 ? 9 : 8, false, true); + } + } + if (tokenIsIdentifier(type)) { + if (this.isContextual(127) && this.lookaheadInLineCharCode() === 123) { + return this.parseModuleExpression(); + } + const canBeArrow = this.state.potentialArrowAt === this.state.start; + const containsEsc = this.state.containsEsc; + const id = this.parseIdentifier(); + if (!containsEsc && id.name === "async" && !this.canInsertSemicolon()) { + const { + type + } = this.state; + if (type === 68) { + this.resetPreviousNodeTrailingComments(id); + this.next(); + return this.parseAsyncFunctionExpression(this.startNodeAtNode(id)); + } else if (tokenIsIdentifier(type)) { + if (this.lookaheadCharCode() === 61) { + return this.parseAsyncArrowUnaryFunction(this.startNodeAtNode(id)); + } else { + return id; + } + } else if (type === 90) { + this.resetPreviousNodeTrailingComments(id); + return this.parseDo(this.startNodeAtNode(id), true); + } + } + if (canBeArrow && this.match(19) && !this.canInsertSemicolon()) { + this.next(); + return this.parseArrowExpression(this.startNodeAtNode(id), [id], false); + } + return id; + } else { + this.unexpected(); + } + } + } + parseTopicReferenceThenEqualsSign(topicTokenType, topicTokenValue) { + const pipeProposal = this.getPluginOption("pipelineOperator", "proposal"); + if (pipeProposal) { + this.state.type = topicTokenType; + this.state.value = topicTokenValue; + this.state.pos--; + this.state.end--; + this.state.endLoc = createPositionWithColumnOffset(this.state.endLoc, -1); + return this.parseTopicReference(pipeProposal); + } else { + this.unexpected(); + } + } + parseTopicReference(pipeProposal) { + const node = this.startNode(); + const startLoc = this.state.startLoc; + const tokenType = this.state.type; + this.next(); + return this.finishTopicReference(node, startLoc, pipeProposal, tokenType); + } + finishTopicReference(node, startLoc, pipeProposal, tokenType) { + if (this.testTopicReferenceConfiguration(pipeProposal, startLoc, tokenType)) { + if (pipeProposal === "hack") { + if (!this.topicReferenceIsAllowedInCurrentContext()) { + this.raise(Errors.PipeTopicUnbound, startLoc); + } + this.registerTopicReference(); + return this.finishNode(node, "TopicReference"); + } else { + if (!this.topicReferenceIsAllowedInCurrentContext()) { + this.raise(Errors.PrimaryTopicNotAllowed, startLoc); + } + this.registerTopicReference(); + return this.finishNode(node, "PipelinePrimaryTopicReference"); + } + } else { + throw this.raise(Errors.PipeTopicUnconfiguredToken, startLoc, { + token: tokenLabelName(tokenType) + }); + } + } + testTopicReferenceConfiguration(pipeProposal, startLoc, tokenType) { + switch (pipeProposal) { + case "hack": + { + return this.hasPlugin(["pipelineOperator", { + topicToken: tokenLabelName(tokenType) + }]); + } + case "smart": + return tokenType === 27; + default: + throw this.raise(Errors.PipeTopicRequiresHackPipes, startLoc); + } + } + parseAsyncArrowUnaryFunction(node) { + this.prodParam.enter(functionFlags(true, this.prodParam.hasYield)); + const params = [this.parseIdentifier()]; + this.prodParam.exit(); + if (this.hasPrecedingLineBreak()) { + this.raise(Errors.LineTerminatorBeforeArrow, this.state.curPosition()); + } + this.expect(19); + return this.parseArrowExpression(node, params, true); + } + parseDo(node, isAsync) { + this.expectPlugin("doExpressions"); + if (isAsync) { + this.expectPlugin("asyncDoExpressions"); + } + node.async = isAsync; + this.next(); + const oldLabels = this.state.labels; + this.state.labels = []; + if (isAsync) { + this.prodParam.enter(2); + node.body = this.parseBlock(); + this.prodParam.exit(); + } else { + node.body = this.parseBlock(); + } + this.state.labels = oldLabels; + return this.finishNode(node, "DoExpression"); + } + parseSuper() { + const node = this.startNode(); + this.next(); + if (this.match(10) && !this.scope.allowDirectSuper && !(this.optionFlags & 16)) { + this.raise(Errors.SuperNotAllowed, node); + } else if (!this.scope.allowSuper && !(this.optionFlags & 16)) { + this.raise(Errors.UnexpectedSuper, node); + } + if (!this.match(10) && !this.match(0) && !this.match(16)) { + this.raise(Errors.UnsupportedSuper, node); + } + return this.finishNode(node, "Super"); + } + parsePrivateName() { + const node = this.startNode(); + const id = this.startNodeAt(createPositionWithColumnOffset(this.state.startLoc, 1)); + const name = this.state.value; + this.next(); + node.id = this.createIdentifier(id, name); + return this.finishNode(node, "PrivateName"); + } + parseFunctionOrFunctionSent() { + const node = this.startNode(); + this.next(); + if (this.prodParam.hasYield && this.match(16)) { + const meta = this.createIdentifier(this.startNodeAtNode(node), "function"); + this.next(); + if (this.match(103)) { + this.expectPlugin("functionSent"); + } else if (!this.hasPlugin("functionSent")) { + this.unexpected(); + } + return this.parseMetaProperty(node, meta, "sent"); + } + return this.parseFunction(node); + } + parseMetaProperty(node, meta, propertyName) { + node.meta = meta; + const containsEsc = this.state.containsEsc; + node.property = this.parseIdentifier(true); + if (node.property.name !== propertyName || containsEsc) { + this.raise(Errors.UnsupportedMetaProperty, node.property, { + target: meta.name, + onlyValidPropertyName: propertyName + }); + } + return this.finishNode(node, "MetaProperty"); + } + parseImportMetaProperty(node) { + const id = this.createIdentifier(this.startNodeAtNode(node), "import"); + this.next(); + if (this.isContextual(101)) { + if (!this.inModule) { + this.raise(Errors.ImportMetaOutsideModule, id); + } + this.sawUnambiguousESM = true; + } else if (this.isContextual(105) || this.isContextual(97)) { + const isSource = this.isContextual(105); + this.expectPlugin(isSource ? "sourcePhaseImports" : "deferredImportEvaluation"); + if (!(this.optionFlags & 512)) { + throw this.raise(Errors.DynamicImportPhaseRequiresImportExpressions, this.state.startLoc, { + phase: this.state.value + }); + } + this.next(); + node.phase = isSource ? "source" : "defer"; + return this.parseImportCall(node); + } + return this.parseMetaProperty(node, id, "meta"); + } + parseLiteralAtNode(value, type, node) { + this.addExtra(node, "rawValue", value); + this.addExtra(node, "raw", this.input.slice(this.offsetToSourcePos(node.start), this.state.end)); + node.value = value; + this.next(); + return this.finishNode(node, type); + } + parseLiteral(value, type) { + const node = this.startNode(); + return this.parseLiteralAtNode(value, type, node); + } + parseStringLiteral(value) { + return this.parseLiteral(value, "StringLiteral"); + } + parseNumericLiteral(value) { + return this.parseLiteral(value, "NumericLiteral"); + } + parseBigIntLiteral(value) { + return this.parseLiteral(value, "BigIntLiteral"); + } + parseDecimalLiteral(value) { + return this.parseLiteral(value, "DecimalLiteral"); + } + parseRegExpLiteral(value) { + const node = this.startNode(); + this.addExtra(node, "raw", this.input.slice(this.offsetToSourcePos(node.start), this.state.end)); + node.pattern = value.pattern; + node.flags = value.flags; + this.next(); + return this.finishNode(node, "RegExpLiteral"); + } + parseBooleanLiteral(value) { + const node = this.startNode(); + node.value = value; + this.next(); + return this.finishNode(node, "BooleanLiteral"); + } + parseNullLiteral() { + const node = this.startNode(); + this.next(); + return this.finishNode(node, "NullLiteral"); + } + parseParenAndDistinguishExpression(canBeArrow) { + const startLoc = this.state.startLoc; + let val; + this.next(); + this.expressionScope.enter(newArrowHeadScope()); + const oldMaybeInArrowParameters = this.state.maybeInArrowParameters; + const oldInFSharpPipelineDirectBody = this.state.inFSharpPipelineDirectBody; + this.state.maybeInArrowParameters = true; + this.state.inFSharpPipelineDirectBody = false; + const innerStartLoc = this.state.startLoc; + const exprList = []; + const refExpressionErrors = new ExpressionErrors(); + let first = true; + let spreadStartLoc; + let optionalCommaStartLoc; + while (!this.match(11)) { + if (first) { + first = false; + } else { + this.expect(12, refExpressionErrors.optionalParametersLoc === null ? null : refExpressionErrors.optionalParametersLoc); + if (this.match(11)) { + optionalCommaStartLoc = this.state.startLoc; + break; + } + } + if (this.match(21)) { + const spreadNodeStartLoc = this.state.startLoc; + spreadStartLoc = this.state.startLoc; + exprList.push(this.parseParenItem(this.parseRestBinding(), spreadNodeStartLoc)); + if (!this.checkCommaAfterRest(41)) { + break; + } + } else { + exprList.push(this.parseMaybeAssignAllowIn(refExpressionErrors, this.parseParenItem)); + } + } + const innerEndLoc = this.state.lastTokEndLoc; + this.expect(11); + this.state.maybeInArrowParameters = oldMaybeInArrowParameters; + this.state.inFSharpPipelineDirectBody = oldInFSharpPipelineDirectBody; + let arrowNode = this.startNodeAt(startLoc); + if (canBeArrow && this.shouldParseArrow(exprList) && (arrowNode = this.parseArrow(arrowNode))) { + this.checkDestructuringPrivate(refExpressionErrors); + this.expressionScope.validateAsPattern(); + this.expressionScope.exit(); + this.parseArrowExpression(arrowNode, exprList, false); + return arrowNode; + } + this.expressionScope.exit(); + if (!exprList.length) { + this.unexpected(this.state.lastTokStartLoc); + } + if (optionalCommaStartLoc) this.unexpected(optionalCommaStartLoc); + if (spreadStartLoc) this.unexpected(spreadStartLoc); + this.checkExpressionErrors(refExpressionErrors, true); + this.toReferencedListDeep(exprList, true); + if (exprList.length > 1) { + val = this.startNodeAt(innerStartLoc); + val.expressions = exprList; + this.finishNode(val, "SequenceExpression"); + this.resetEndLocation(val, innerEndLoc); + } else { + val = exprList[0]; + } + return this.wrapParenthesis(startLoc, val); + } + wrapParenthesis(startLoc, expression) { + if (!(this.optionFlags & 1024)) { + this.addExtra(expression, "parenthesized", true); + this.addExtra(expression, "parenStart", startLoc.index); + this.takeSurroundingComments(expression, startLoc.index, this.state.lastTokEndLoc.index); + return expression; + } + const parenExpression = this.startNodeAt(startLoc); + parenExpression.expression = expression; + return this.finishNode(parenExpression, "ParenthesizedExpression"); + } + shouldParseArrow(params) { + return !this.canInsertSemicolon(); + } + parseArrow(node) { + if (this.eat(19)) { + return node; + } + } + parseParenItem(node, startLoc) { + return node; + } + parseNewOrNewTarget() { + const node = this.startNode(); + this.next(); + if (this.match(16)) { + const meta = this.createIdentifier(this.startNodeAtNode(node), "new"); + this.next(); + const metaProp = this.parseMetaProperty(node, meta, "target"); + if (!this.scope.inNonArrowFunction && !this.scope.inClass && !(this.optionFlags & 4)) { + this.raise(Errors.UnexpectedNewTarget, metaProp); + } + return metaProp; + } + return this.parseNew(node); + } + parseNew(node) { + this.parseNewCallee(node); + if (this.eat(10)) { + const args = this.parseExprList(11); + this.toReferencedList(args); + node.arguments = args; + } else { + node.arguments = []; + } + return this.finishNode(node, "NewExpression"); + } + parseNewCallee(node) { + const isImport = this.match(83); + const callee = this.parseNoCallExpr(); + node.callee = callee; + if (isImport && (callee.type === "Import" || callee.type === "ImportExpression")) { + this.raise(Errors.ImportCallNotNewExpression, callee); + } + } + parseTemplateElement(isTagged) { + const { + start, + startLoc, + end, + value + } = this.state; + const elemStart = start + 1; + const elem = this.startNodeAt(createPositionWithColumnOffset(startLoc, 1)); + if (value === null) { + if (!isTagged) { + this.raise(Errors.InvalidEscapeSequenceTemplate, createPositionWithColumnOffset(this.state.firstInvalidTemplateEscapePos, 1)); + } + } + const isTail = this.match(24); + const endOffset = isTail ? -1 : -2; + const elemEnd = end + endOffset; + elem.value = { + raw: this.input.slice(elemStart, elemEnd).replace(/\r\n?/g, "\n"), + cooked: value === null ? null : value.slice(1, endOffset) + }; + elem.tail = isTail; + this.next(); + const finishedNode = this.finishNode(elem, "TemplateElement"); + this.resetEndLocation(finishedNode, createPositionWithColumnOffset(this.state.lastTokEndLoc, endOffset)); + return finishedNode; + } + parseTemplate(isTagged) { + const node = this.startNode(); + let curElt = this.parseTemplateElement(isTagged); + const quasis = [curElt]; + const substitutions = []; + while (!curElt.tail) { + substitutions.push(this.parseTemplateSubstitution()); + this.readTemplateContinuation(); + quasis.push(curElt = this.parseTemplateElement(isTagged)); + } + node.expressions = substitutions; + node.quasis = quasis; + return this.finishNode(node, "TemplateLiteral"); + } + parseTemplateSubstitution() { + return this.parseExpression(); + } + parseObjectLike(close, isPattern, isRecord, refExpressionErrors) { + if (isRecord) { + this.expectPlugin("recordAndTuple"); + } + const oldInFSharpPipelineDirectBody = this.state.inFSharpPipelineDirectBody; + this.state.inFSharpPipelineDirectBody = false; + let sawProto = false; + let first = true; + const node = this.startNode(); + node.properties = []; + this.next(); + while (!this.match(close)) { + if (first) { + first = false; + } else { + this.expect(12); + if (this.match(close)) { + this.addTrailingCommaExtraToNode(node); + break; + } + } + let prop; + if (isPattern) { + prop = this.parseBindingProperty(); + } else { + prop = this.parsePropertyDefinition(refExpressionErrors); + sawProto = this.checkProto(prop, isRecord, sawProto, refExpressionErrors); + } + if (isRecord && !this.isObjectProperty(prop) && prop.type !== "SpreadElement") { + this.raise(Errors.InvalidRecordProperty, prop); + } + { + if (prop.shorthand) { + this.addExtra(prop, "shorthand", true); + } + } + node.properties.push(prop); + } + this.next(); + this.state.inFSharpPipelineDirectBody = oldInFSharpPipelineDirectBody; + let type = "ObjectExpression"; + if (isPattern) { + type = "ObjectPattern"; + } else if (isRecord) { + type = "RecordExpression"; + } + return this.finishNode(node, type); + } + addTrailingCommaExtraToNode(node) { + this.addExtra(node, "trailingComma", this.state.lastTokStartLoc.index); + this.addExtra(node, "trailingCommaLoc", this.state.lastTokStartLoc, false); + } + maybeAsyncOrAccessorProp(prop) { + return !prop.computed && prop.key.type === "Identifier" && (this.isLiteralPropertyName() || this.match(0) || this.match(55)); + } + parsePropertyDefinition(refExpressionErrors) { + let decorators = []; + if (this.match(26)) { + if (this.hasPlugin("decorators")) { + this.raise(Errors.UnsupportedPropertyDecorator, this.state.startLoc); + } + while (this.match(26)) { + decorators.push(this.parseDecorator()); + } + } + const prop = this.startNode(); + let isAsync = false; + let isAccessor = false; + let startLoc; + if (this.match(21)) { + if (decorators.length) this.unexpected(); + return this.parseSpread(); + } + if (decorators.length) { + prop.decorators = decorators; + decorators = []; + } + prop.method = false; + if (refExpressionErrors) { + startLoc = this.state.startLoc; + } + let isGenerator = this.eat(55); + this.parsePropertyNamePrefixOperator(prop); + const containsEsc = this.state.containsEsc; + this.parsePropertyName(prop, refExpressionErrors); + if (!isGenerator && !containsEsc && this.maybeAsyncOrAccessorProp(prop)) { + const { + key + } = prop; + const keyName = key.name; + if (keyName === "async" && !this.hasPrecedingLineBreak()) { + isAsync = true; + this.resetPreviousNodeTrailingComments(key); + isGenerator = this.eat(55); + this.parsePropertyName(prop); + } + if (keyName === "get" || keyName === "set") { + isAccessor = true; + this.resetPreviousNodeTrailingComments(key); + prop.kind = keyName; + if (this.match(55)) { + isGenerator = true; + this.raise(Errors.AccessorIsGenerator, this.state.curPosition(), { + kind: keyName + }); + this.next(); + } + this.parsePropertyName(prop); + } + } + return this.parseObjPropValue(prop, startLoc, isGenerator, isAsync, false, isAccessor, refExpressionErrors); + } + getGetterSetterExpectedParamCount(method) { + return method.kind === "get" ? 0 : 1; + } + getObjectOrClassMethodParams(method) { + return method.params; + } + checkGetterSetterParams(method) { + var _params; + const paramCount = this.getGetterSetterExpectedParamCount(method); + const params = this.getObjectOrClassMethodParams(method); + if (params.length !== paramCount) { + this.raise(method.kind === "get" ? Errors.BadGetterArity : Errors.BadSetterArity, method); + } + if (method.kind === "set" && ((_params = params[params.length - 1]) == null ? void 0 : _params.type) === "RestElement") { + this.raise(Errors.BadSetterRestParameter, method); + } + } + parseObjectMethod(prop, isGenerator, isAsync, isPattern, isAccessor) { + if (isAccessor) { + const finishedProp = this.parseMethod(prop, isGenerator, false, false, false, "ObjectMethod"); + this.checkGetterSetterParams(finishedProp); + return finishedProp; + } + if (isAsync || isGenerator || this.match(10)) { + if (isPattern) this.unexpected(); + prop.kind = "method"; + prop.method = true; + return this.parseMethod(prop, isGenerator, isAsync, false, false, "ObjectMethod"); + } + } + parseObjectProperty(prop, startLoc, isPattern, refExpressionErrors) { + prop.shorthand = false; + if (this.eat(14)) { + prop.value = isPattern ? this.parseMaybeDefault(this.state.startLoc) : this.parseMaybeAssignAllowIn(refExpressionErrors); + return this.finishObjectProperty(prop); + } + if (!prop.computed && prop.key.type === "Identifier") { + this.checkReservedWord(prop.key.name, prop.key.loc.start, true, false); + if (isPattern) { + prop.value = this.parseMaybeDefault(startLoc, this.cloneIdentifier(prop.key)); + } else if (this.match(29)) { + const shorthandAssignLoc = this.state.startLoc; + if (refExpressionErrors != null) { + if (refExpressionErrors.shorthandAssignLoc === null) { + refExpressionErrors.shorthandAssignLoc = shorthandAssignLoc; + } + } else { + this.raise(Errors.InvalidCoverInitializedName, shorthandAssignLoc); + } + prop.value = this.parseMaybeDefault(startLoc, this.cloneIdentifier(prop.key)); + } else { + prop.value = this.cloneIdentifier(prop.key); + } + prop.shorthand = true; + return this.finishObjectProperty(prop); + } + } + finishObjectProperty(node) { + return this.finishNode(node, "ObjectProperty"); + } + parseObjPropValue(prop, startLoc, isGenerator, isAsync, isPattern, isAccessor, refExpressionErrors) { + const node = this.parseObjectMethod(prop, isGenerator, isAsync, isPattern, isAccessor) || this.parseObjectProperty(prop, startLoc, isPattern, refExpressionErrors); + if (!node) this.unexpected(); + return node; + } + parsePropertyName(prop, refExpressionErrors) { + if (this.eat(0)) { + prop.computed = true; + prop.key = this.parseMaybeAssignAllowIn(); + this.expect(3); + } else { + const { + type, + value + } = this.state; + let key; + if (tokenIsKeywordOrIdentifier(type)) { + key = this.parseIdentifier(true); + } else { + switch (type) { + case 135: + key = this.parseNumericLiteral(value); + break; + case 134: + key = this.parseStringLiteral(value); + break; + case 136: + key = this.parseBigIntLiteral(value); + break; + case 139: + { + const privateKeyLoc = this.state.startLoc; + if (refExpressionErrors != null) { + if (refExpressionErrors.privateKeyLoc === null) { + refExpressionErrors.privateKeyLoc = privateKeyLoc; + } + } else { + this.raise(Errors.UnexpectedPrivateField, privateKeyLoc); + } + key = this.parsePrivateName(); + break; + } + default: + if (type === 137) { + key = this.parseDecimalLiteral(value); + break; + } + this.unexpected(); + } + } + prop.key = key; + if (type !== 139) { + prop.computed = false; + } + } + } + initFunction(node, isAsync) { + node.id = null; + node.generator = false; + node.async = isAsync; + } + parseMethod(node, isGenerator, isAsync, isConstructor, allowDirectSuper, type, inClassScope = false) { + this.initFunction(node, isAsync); + node.generator = isGenerator; + this.scope.enter(2 | 16 | (inClassScope ? 64 : 0) | (allowDirectSuper ? 32 : 0)); + this.prodParam.enter(functionFlags(isAsync, node.generator)); + this.parseFunctionParams(node, isConstructor); + const finishedNode = this.parseFunctionBodyAndFinish(node, type, true); + this.prodParam.exit(); + this.scope.exit(); + return finishedNode; + } + parseArrayLike(close, canBePattern, isTuple, refExpressionErrors) { + if (isTuple) { + this.expectPlugin("recordAndTuple"); + } + const oldInFSharpPipelineDirectBody = this.state.inFSharpPipelineDirectBody; + this.state.inFSharpPipelineDirectBody = false; + const node = this.startNode(); + this.next(); + node.elements = this.parseExprList(close, !isTuple, refExpressionErrors, node); + this.state.inFSharpPipelineDirectBody = oldInFSharpPipelineDirectBody; + return this.finishNode(node, isTuple ? "TupleExpression" : "ArrayExpression"); + } + parseArrowExpression(node, params, isAsync, trailingCommaLoc) { + this.scope.enter(2 | 4); + let flags = functionFlags(isAsync, false); + if (!this.match(5) && this.prodParam.hasIn) { + flags |= 8; + } + this.prodParam.enter(flags); + this.initFunction(node, isAsync); + const oldMaybeInArrowParameters = this.state.maybeInArrowParameters; + if (params) { + this.state.maybeInArrowParameters = true; + this.setArrowFunctionParameters(node, params, trailingCommaLoc); + } + this.state.maybeInArrowParameters = false; + this.parseFunctionBody(node, true); + this.prodParam.exit(); + this.scope.exit(); + this.state.maybeInArrowParameters = oldMaybeInArrowParameters; + return this.finishNode(node, "ArrowFunctionExpression"); + } + setArrowFunctionParameters(node, params, trailingCommaLoc) { + this.toAssignableList(params, trailingCommaLoc, false); + node.params = params; + } + parseFunctionBodyAndFinish(node, type, isMethod = false) { + this.parseFunctionBody(node, false, isMethod); + return this.finishNode(node, type); + } + parseFunctionBody(node, allowExpression, isMethod = false) { + const isExpression = allowExpression && !this.match(5); + this.expressionScope.enter(newExpressionScope()); + if (isExpression) { + node.body = this.parseMaybeAssign(); + this.checkParams(node, false, allowExpression, false); + } else { + const oldStrict = this.state.strict; + const oldLabels = this.state.labels; + this.state.labels = []; + this.prodParam.enter(this.prodParam.currentFlags() | 4); + node.body = this.parseBlock(true, false, hasStrictModeDirective => { + const nonSimple = !this.isSimpleParamList(node.params); + if (hasStrictModeDirective && nonSimple) { + this.raise(Errors.IllegalLanguageModeDirective, (node.kind === "method" || node.kind === "constructor") && !!node.key ? node.key.loc.end : node); + } + const strictModeChanged = !oldStrict && this.state.strict; + this.checkParams(node, !this.state.strict && !allowExpression && !isMethod && !nonSimple, allowExpression, strictModeChanged); + if (this.state.strict && node.id) { + this.checkIdentifier(node.id, 65, strictModeChanged); + } + }); + this.prodParam.exit(); + this.state.labels = oldLabels; + } + this.expressionScope.exit(); + } + isSimpleParameter(node) { + return node.type === "Identifier"; + } + isSimpleParamList(params) { + for (let i = 0, len = params.length; i < len; i++) { + if (!this.isSimpleParameter(params[i])) return false; + } + return true; + } + checkParams(node, allowDuplicates, isArrowFunction, strictModeChanged = true) { + const checkClashes = !allowDuplicates && new Set(); + const formalParameters = { + type: "FormalParameters" + }; + for (const param of node.params) { + this.checkLVal(param, formalParameters, 5, checkClashes, strictModeChanged); + } + } + parseExprList(close, allowEmpty, refExpressionErrors, nodeForExtra) { + const elts = []; + let first = true; + while (!this.eat(close)) { + if (first) { + first = false; + } else { + this.expect(12); + if (this.match(close)) { + if (nodeForExtra) { + this.addTrailingCommaExtraToNode(nodeForExtra); + } + this.next(); + break; + } + } + elts.push(this.parseExprListItem(allowEmpty, refExpressionErrors)); + } + return elts; + } + parseExprListItem(allowEmpty, refExpressionErrors, allowPlaceholder) { + let elt; + if (this.match(12)) { + if (!allowEmpty) { + this.raise(Errors.UnexpectedToken, this.state.curPosition(), { + unexpected: "," + }); + } + elt = null; + } else if (this.match(21)) { + const spreadNodeStartLoc = this.state.startLoc; + elt = this.parseParenItem(this.parseSpread(refExpressionErrors), spreadNodeStartLoc); + } else if (this.match(17)) { + this.expectPlugin("partialApplication"); + if (!allowPlaceholder) { + this.raise(Errors.UnexpectedArgumentPlaceholder, this.state.startLoc); + } + const node = this.startNode(); + this.next(); + elt = this.finishNode(node, "ArgumentPlaceholder"); + } else { + elt = this.parseMaybeAssignAllowIn(refExpressionErrors, this.parseParenItem); + } + return elt; + } + parseIdentifier(liberal) { + const node = this.startNode(); + const name = this.parseIdentifierName(liberal); + return this.createIdentifier(node, name); + } + createIdentifier(node, name) { + node.name = name; + node.loc.identifierName = name; + return this.finishNode(node, "Identifier"); + } + parseIdentifierName(liberal) { + let name; + const { + startLoc, + type + } = this.state; + if (tokenIsKeywordOrIdentifier(type)) { + name = this.state.value; + } else { + this.unexpected(); + } + const tokenIsKeyword = tokenKeywordOrIdentifierIsKeyword(type); + if (liberal) { + if (tokenIsKeyword) { + this.replaceToken(132); + } + } else { + this.checkReservedWord(name, startLoc, tokenIsKeyword, false); + } + this.next(); + return name; + } + checkReservedWord(word, startLoc, checkKeywords, isBinding) { + if (word.length > 10) { + return; + } + if (!canBeReservedWord(word)) { + return; + } + if (checkKeywords && isKeyword(word)) { + this.raise(Errors.UnexpectedKeyword, startLoc, { + keyword: word + }); + return; + } + const reservedTest = !this.state.strict ? isReservedWord : isBinding ? isStrictBindReservedWord : isStrictReservedWord; + if (reservedTest(word, this.inModule)) { + this.raise(Errors.UnexpectedReservedWord, startLoc, { + reservedWord: word + }); + return; + } else if (word === "yield") { + if (this.prodParam.hasYield) { + this.raise(Errors.YieldBindingIdentifier, startLoc); + return; + } + } else if (word === "await") { + if (this.prodParam.hasAwait) { + this.raise(Errors.AwaitBindingIdentifier, startLoc); + return; + } + if (this.scope.inStaticBlock) { + this.raise(Errors.AwaitBindingIdentifierInStaticBlock, startLoc); + return; + } + this.expressionScope.recordAsyncArrowParametersError(startLoc); + } else if (word === "arguments") { + if (this.scope.inClassAndNotInNonArrowFunction) { + this.raise(Errors.ArgumentsInClass, startLoc); + return; + } + } + } + recordAwaitIfAllowed() { + const isAwaitAllowed = this.prodParam.hasAwait || this.optionFlags & 1 && !this.scope.inFunction; + if (isAwaitAllowed && !this.scope.inFunction) { + this.state.hasTopLevelAwait = true; + } + return isAwaitAllowed; + } + parseAwait(startLoc) { + const node = this.startNodeAt(startLoc); + this.expressionScope.recordParameterInitializerError(Errors.AwaitExpressionFormalParameter, node); + if (this.eat(55)) { + this.raise(Errors.ObsoleteAwaitStar, node); + } + if (!this.scope.inFunction && !(this.optionFlags & 1)) { + if (this.isAmbiguousPrefixOrIdentifier()) { + this.ambiguousScriptDifferentAst = true; + } else { + this.sawUnambiguousESM = true; + } + } + if (!this.state.soloAwait) { + node.argument = this.parseMaybeUnary(null, true); + } + return this.finishNode(node, "AwaitExpression"); + } + isAmbiguousPrefixOrIdentifier() { + if (this.hasPrecedingLineBreak()) return true; + const { + type + } = this.state; + return type === 53 || type === 10 || type === 0 || tokenIsTemplate(type) || type === 102 && !this.state.containsEsc || type === 138 || type === 56 || this.hasPlugin("v8intrinsic") && type === 54; + } + parseYield(startLoc) { + const node = this.startNodeAt(startLoc); + this.expressionScope.recordParameterInitializerError(Errors.YieldInParameter, node); + let delegating = false; + let argument = null; + if (!this.hasPrecedingLineBreak()) { + delegating = this.eat(55); + switch (this.state.type) { + case 13: + case 140: + case 8: + case 11: + case 3: + case 9: + case 14: + case 12: + if (!delegating) break; + default: + argument = this.parseMaybeAssign(); + } + } + node.delegate = delegating; + node.argument = argument; + return this.finishNode(node, "YieldExpression"); + } + parseImportCall(node) { + this.next(); + node.source = this.parseMaybeAssignAllowIn(); + node.options = null; + if (this.eat(12)) { + if (!this.match(11)) { + node.options = this.parseMaybeAssignAllowIn(); + if (this.eat(12) && !this.match(11)) { + do { + this.parseMaybeAssignAllowIn(); + } while (this.eat(12) && !this.match(11)); + this.raise(Errors.ImportCallArity, node); + } + } + } + this.expect(11); + return this.finishNode(node, "ImportExpression"); + } + checkPipelineAtInfixOperator(left, leftStartLoc) { + if (this.hasPlugin(["pipelineOperator", { + proposal: "smart" + }])) { + if (left.type === "SequenceExpression") { + this.raise(Errors.PipelineHeadSequenceExpression, leftStartLoc); + } + } + } + parseSmartPipelineBodyInStyle(childExpr, startLoc) { + if (this.isSimpleReference(childExpr)) { + const bodyNode = this.startNodeAt(startLoc); + bodyNode.callee = childExpr; + return this.finishNode(bodyNode, "PipelineBareFunction"); + } else { + const bodyNode = this.startNodeAt(startLoc); + this.checkSmartPipeTopicBodyEarlyErrors(startLoc); + bodyNode.expression = childExpr; + return this.finishNode(bodyNode, "PipelineTopicExpression"); + } + } + isSimpleReference(expression) { + switch (expression.type) { + case "MemberExpression": + return !expression.computed && this.isSimpleReference(expression.object); + case "Identifier": + return true; + default: + return false; + } + } + checkSmartPipeTopicBodyEarlyErrors(startLoc) { + if (this.match(19)) { + throw this.raise(Errors.PipelineBodyNoArrow, this.state.startLoc); + } + if (!this.topicReferenceWasUsedInCurrentContext()) { + this.raise(Errors.PipelineTopicUnused, startLoc); + } + } + withTopicBindingContext(callback) { + const outerContextTopicState = this.state.topicContext; + this.state.topicContext = { + maxNumOfResolvableTopics: 1, + maxTopicIndex: null + }; + try { + return callback(); + } finally { + this.state.topicContext = outerContextTopicState; + } + } + withSmartMixTopicForbiddingContext(callback) { + if (this.hasPlugin(["pipelineOperator", { + proposal: "smart" + }])) { + const outerContextTopicState = this.state.topicContext; + this.state.topicContext = { + maxNumOfResolvableTopics: 0, + maxTopicIndex: null + }; + try { + return callback(); + } finally { + this.state.topicContext = outerContextTopicState; + } + } else { + return callback(); + } + } + withSoloAwaitPermittingContext(callback) { + const outerContextSoloAwaitState = this.state.soloAwait; + this.state.soloAwait = true; + try { + return callback(); + } finally { + this.state.soloAwait = outerContextSoloAwaitState; + } + } + allowInAnd(callback) { + const flags = this.prodParam.currentFlags(); + const prodParamToSet = 8 & ~flags; + if (prodParamToSet) { + this.prodParam.enter(flags | 8); + try { + return callback(); + } finally { + this.prodParam.exit(); + } + } + return callback(); + } + disallowInAnd(callback) { + const flags = this.prodParam.currentFlags(); + const prodParamToClear = 8 & flags; + if (prodParamToClear) { + this.prodParam.enter(flags & ~8); + try { + return callback(); + } finally { + this.prodParam.exit(); + } + } + return callback(); + } + registerTopicReference() { + this.state.topicContext.maxTopicIndex = 0; + } + topicReferenceIsAllowedInCurrentContext() { + return this.state.topicContext.maxNumOfResolvableTopics >= 1; + } + topicReferenceWasUsedInCurrentContext() { + return this.state.topicContext.maxTopicIndex != null && this.state.topicContext.maxTopicIndex >= 0; + } + parseFSharpPipelineBody(prec) { + const startLoc = this.state.startLoc; + this.state.potentialArrowAt = this.state.start; + const oldInFSharpPipelineDirectBody = this.state.inFSharpPipelineDirectBody; + this.state.inFSharpPipelineDirectBody = true; + const ret = this.parseExprOp(this.parseMaybeUnaryOrPrivate(), startLoc, prec); + this.state.inFSharpPipelineDirectBody = oldInFSharpPipelineDirectBody; + return ret; + } + parseModuleExpression() { + this.expectPlugin("moduleBlocks"); + const node = this.startNode(); + this.next(); + if (!this.match(5)) { + this.unexpected(null, 5); + } + const program = this.startNodeAt(this.state.endLoc); + this.next(); + const revertScopes = this.initializeScopes(true); + this.enterInitialScopes(); + try { + node.body = this.parseProgram(program, 8, "module"); + } finally { + revertScopes(); + } + return this.finishNode(node, "ModuleExpression"); + } + parsePropertyNamePrefixOperator(prop) {} +} +const loopLabel = { + kind: 1 + }, + switchLabel = { + kind: 2 + }; +const loneSurrogate = /[\uD800-\uDFFF]/u; +const keywordRelationalOperator = /in(?:stanceof)?/y; +function babel7CompatTokens(tokens, input, startIndex) { + for (let i = 0; i < tokens.length; i++) { + const token = tokens[i]; + const { + type + } = token; + if (typeof type === "number") { + { + if (type === 139) { + const { + loc, + start, + value, + end + } = token; + const hashEndPos = start + 1; + const hashEndLoc = createPositionWithColumnOffset(loc.start, 1); + tokens.splice(i, 1, new Token({ + type: getExportedToken(27), + value: "#", + start: start, + end: hashEndPos, + startLoc: loc.start, + endLoc: hashEndLoc + }), new Token({ + type: getExportedToken(132), + value: value, + start: hashEndPos, + end: end, + startLoc: hashEndLoc, + endLoc: loc.end + })); + i++; + continue; + } + if (tokenIsTemplate(type)) { + const { + loc, + start, + value, + end + } = token; + const backquoteEnd = start + 1; + const backquoteEndLoc = createPositionWithColumnOffset(loc.start, 1); + let startToken; + if (input.charCodeAt(start - startIndex) === 96) { + startToken = new Token({ + type: getExportedToken(22), + value: "`", + start: start, + end: backquoteEnd, + startLoc: loc.start, + endLoc: backquoteEndLoc + }); + } else { + startToken = new Token({ + type: getExportedToken(8), + value: "}", + start: start, + end: backquoteEnd, + startLoc: loc.start, + endLoc: backquoteEndLoc + }); + } + let templateValue, templateElementEnd, templateElementEndLoc, endToken; + if (type === 24) { + templateElementEnd = end - 1; + templateElementEndLoc = createPositionWithColumnOffset(loc.end, -1); + templateValue = value === null ? null : value.slice(1, -1); + endToken = new Token({ + type: getExportedToken(22), + value: "`", + start: templateElementEnd, + end: end, + startLoc: templateElementEndLoc, + endLoc: loc.end + }); + } else { + templateElementEnd = end - 2; + templateElementEndLoc = createPositionWithColumnOffset(loc.end, -2); + templateValue = value === null ? null : value.slice(1, -2); + endToken = new Token({ + type: getExportedToken(23), + value: "${", + start: templateElementEnd, + end: end, + startLoc: templateElementEndLoc, + endLoc: loc.end + }); + } + tokens.splice(i, 1, startToken, new Token({ + type: getExportedToken(20), + value: templateValue, + start: backquoteEnd, + end: templateElementEnd, + startLoc: backquoteEndLoc, + endLoc: templateElementEndLoc + }), endToken); + i += 2; + continue; + } + } + token.type = getExportedToken(type); + } + } + return tokens; +} +class StatementParser extends ExpressionParser { + parseTopLevel(file, program) { + file.program = this.parseProgram(program); + file.comments = this.comments; + if (this.optionFlags & 256) { + file.tokens = babel7CompatTokens(this.tokens, this.input, this.startIndex); + } + return this.finishNode(file, "File"); + } + parseProgram(program, end = 140, sourceType = this.options.sourceType) { + program.sourceType = sourceType; + program.interpreter = this.parseInterpreterDirective(); + this.parseBlockBody(program, true, true, end); + if (this.inModule) { + if (!(this.optionFlags & 64) && this.scope.undefinedExports.size > 0) { + for (const [localName, at] of Array.from(this.scope.undefinedExports)) { + this.raise(Errors.ModuleExportUndefined, at, { + localName + }); + } + } + this.addExtra(program, "topLevelAwait", this.state.hasTopLevelAwait); + } + let finishedProgram; + if (end === 140) { + finishedProgram = this.finishNode(program, "Program"); + } else { + finishedProgram = this.finishNodeAt(program, "Program", createPositionWithColumnOffset(this.state.startLoc, -1)); + } + return finishedProgram; + } + stmtToDirective(stmt) { + const directive = this.castNodeTo(stmt, "Directive"); + const directiveLiteral = this.castNodeTo(stmt.expression, "DirectiveLiteral"); + const expressionValue = directiveLiteral.value; + const raw = this.input.slice(this.offsetToSourcePos(directiveLiteral.start), this.offsetToSourcePos(directiveLiteral.end)); + const val = directiveLiteral.value = raw.slice(1, -1); + this.addExtra(directiveLiteral, "raw", raw); + this.addExtra(directiveLiteral, "rawValue", val); + this.addExtra(directiveLiteral, "expressionValue", expressionValue); + directive.value = directiveLiteral; + delete stmt.expression; + return directive; + } + parseInterpreterDirective() { + if (!this.match(28)) { + return null; + } + const node = this.startNode(); + node.value = this.state.value; + this.next(); + return this.finishNode(node, "InterpreterDirective"); + } + isLet() { + if (!this.isContextual(100)) { + return false; + } + return this.hasFollowingBindingAtom(); + } + chStartsBindingIdentifier(ch, pos) { + if (isIdentifierStart(ch)) { + keywordRelationalOperator.lastIndex = pos; + if (keywordRelationalOperator.test(this.input)) { + const endCh = this.codePointAtPos(keywordRelationalOperator.lastIndex); + if (!isIdentifierChar(endCh) && endCh !== 92) { + return false; + } + } + return true; + } else if (ch === 92) { + return true; + } else { + return false; + } + } + chStartsBindingPattern(ch) { + return ch === 91 || ch === 123; + } + hasFollowingBindingAtom() { + const next = this.nextTokenStart(); + const nextCh = this.codePointAtPos(next); + return this.chStartsBindingPattern(nextCh) || this.chStartsBindingIdentifier(nextCh, next); + } + hasInLineFollowingBindingIdentifierOrBrace() { + const next = this.nextTokenInLineStart(); + const nextCh = this.codePointAtPos(next); + return nextCh === 123 || this.chStartsBindingIdentifier(nextCh, next); + } + allowsForUsing() { + const { + type, + containsEsc, + end + } = this.lookahead(); + if (type === 102 && !containsEsc) { + const nextCharAfterOf = this.lookaheadCharCodeSince(end); + if (nextCharAfterOf !== 61 && nextCharAfterOf !== 58 && nextCharAfterOf !== 59) { + return false; + } + } + if (tokenIsIdentifier(type) && !this.hasFollowingLineBreak()) { + this.expectPlugin("explicitResourceManagement"); + return true; + } + return false; + } + startsAwaitUsing() { + let next = this.nextTokenInLineStart(); + if (this.isUnparsedContextual(next, "using")) { + next = this.nextTokenInLineStartSince(next + 5); + const nextCh = this.codePointAtPos(next); + if (this.chStartsBindingIdentifier(nextCh, next)) { + this.expectPlugin("explicitResourceManagement"); + return true; + } + } + return false; + } + parseModuleItem() { + return this.parseStatementLike(1 | 2 | 4 | 8); + } + parseStatementListItem() { + return this.parseStatementLike(2 | 4 | (!this.options.annexB || this.state.strict ? 0 : 8)); + } + parseStatementOrSloppyAnnexBFunctionDeclaration(allowLabeledFunction = false) { + let flags = 0; + if (this.options.annexB && !this.state.strict) { + flags |= 4; + if (allowLabeledFunction) { + flags |= 8; + } + } + return this.parseStatementLike(flags); + } + parseStatement() { + return this.parseStatementLike(0); + } + parseStatementLike(flags) { + let decorators = null; + if (this.match(26)) { + decorators = this.parseDecorators(true); + } + return this.parseStatementContent(flags, decorators); + } + parseStatementContent(flags, decorators) { + const startType = this.state.type; + const node = this.startNode(); + const allowDeclaration = !!(flags & 2); + const allowFunctionDeclaration = !!(flags & 4); + const topLevel = flags & 1; + switch (startType) { + case 60: + return this.parseBreakContinueStatement(node, true); + case 63: + return this.parseBreakContinueStatement(node, false); + case 64: + return this.parseDebuggerStatement(node); + case 90: + return this.parseDoWhileStatement(node); + case 91: + return this.parseForStatement(node); + case 68: + if (this.lookaheadCharCode() === 46) break; + if (!allowFunctionDeclaration) { + this.raise(this.state.strict ? Errors.StrictFunction : this.options.annexB ? Errors.SloppyFunctionAnnexB : Errors.SloppyFunction, this.state.startLoc); + } + return this.parseFunctionStatement(node, false, !allowDeclaration && allowFunctionDeclaration); + case 80: + if (!allowDeclaration) this.unexpected(); + return this.parseClass(this.maybeTakeDecorators(decorators, node), true); + case 69: + return this.parseIfStatement(node); + case 70: + return this.parseReturnStatement(node); + case 71: + return this.parseSwitchStatement(node); + case 72: + return this.parseThrowStatement(node); + case 73: + return this.parseTryStatement(node); + case 96: + if (!this.state.containsEsc && this.startsAwaitUsing()) { + if (!this.recordAwaitIfAllowed()) { + this.raise(Errors.AwaitUsingNotInAsyncContext, node); + } else if (!allowDeclaration) { + this.raise(Errors.UnexpectedLexicalDeclaration, node); + } + this.next(); + return this.parseVarStatement(node, "await using"); + } + break; + case 107: + if (this.state.containsEsc || !this.hasInLineFollowingBindingIdentifierOrBrace()) { + break; + } + this.expectPlugin("explicitResourceManagement"); + if (!this.scope.inModule && this.scope.inTopLevel) { + this.raise(Errors.UnexpectedUsingDeclaration, this.state.startLoc); + } else if (!allowDeclaration) { + this.raise(Errors.UnexpectedLexicalDeclaration, this.state.startLoc); + } + return this.parseVarStatement(node, "using"); + case 100: + { + if (this.state.containsEsc) { + break; + } + const next = this.nextTokenStart(); + const nextCh = this.codePointAtPos(next); + if (nextCh !== 91) { + if (!allowDeclaration && this.hasFollowingLineBreak()) break; + if (!this.chStartsBindingIdentifier(nextCh, next) && nextCh !== 123) { + break; + } + } + } + case 75: + { + if (!allowDeclaration) { + this.raise(Errors.UnexpectedLexicalDeclaration, this.state.startLoc); + } + } + case 74: + { + const kind = this.state.value; + return this.parseVarStatement(node, kind); + } + case 92: + return this.parseWhileStatement(node); + case 76: + return this.parseWithStatement(node); + case 5: + return this.parseBlock(); + case 13: + return this.parseEmptyStatement(node); + case 83: + { + const nextTokenCharCode = this.lookaheadCharCode(); + if (nextTokenCharCode === 40 || nextTokenCharCode === 46) { + break; + } + } + case 82: + { + if (!(this.optionFlags & 8) && !topLevel) { + this.raise(Errors.UnexpectedImportExport, this.state.startLoc); + } + this.next(); + let result; + if (startType === 83) { + result = this.parseImport(node); + } else { + result = this.parseExport(node, decorators); + } + this.assertModuleNodeAllowed(result); + return result; + } + default: + { + if (this.isAsyncFunction()) { + if (!allowDeclaration) { + this.raise(Errors.AsyncFunctionInSingleStatementContext, this.state.startLoc); + } + this.next(); + return this.parseFunctionStatement(node, true, !allowDeclaration && allowFunctionDeclaration); + } + } + } + const maybeName = this.state.value; + const expr = this.parseExpression(); + if (tokenIsIdentifier(startType) && expr.type === "Identifier" && this.eat(14)) { + return this.parseLabeledStatement(node, maybeName, expr, flags); + } else { + return this.parseExpressionStatement(node, expr, decorators); + } + } + assertModuleNodeAllowed(node) { + if (!(this.optionFlags & 8) && !this.inModule) { + this.raise(Errors.ImportOutsideModule, node); + } + } + decoratorsEnabledBeforeExport() { + if (this.hasPlugin("decorators-legacy")) return true; + return this.hasPlugin("decorators") && this.getPluginOption("decorators", "decoratorsBeforeExport") !== false; + } + maybeTakeDecorators(maybeDecorators, classNode, exportNode) { + if (maybeDecorators) { + var _classNode$decorators; + if ((_classNode$decorators = classNode.decorators) != null && _classNode$decorators.length) { + if (typeof this.getPluginOption("decorators", "decoratorsBeforeExport") !== "boolean") { + this.raise(Errors.DecoratorsBeforeAfterExport, classNode.decorators[0]); + } + classNode.decorators.unshift(...maybeDecorators); + } else { + classNode.decorators = maybeDecorators; + } + this.resetStartLocationFromNode(classNode, maybeDecorators[0]); + if (exportNode) this.resetStartLocationFromNode(exportNode, classNode); + } + return classNode; + } + canHaveLeadingDecorator() { + return this.match(80); + } + parseDecorators(allowExport) { + const decorators = []; + do { + decorators.push(this.parseDecorator()); + } while (this.match(26)); + if (this.match(82)) { + if (!allowExport) { + this.unexpected(); + } + if (!this.decoratorsEnabledBeforeExport()) { + this.raise(Errors.DecoratorExportClass, this.state.startLoc); + } + } else if (!this.canHaveLeadingDecorator()) { + throw this.raise(Errors.UnexpectedLeadingDecorator, this.state.startLoc); + } + return decorators; + } + parseDecorator() { + this.expectOnePlugin(["decorators", "decorators-legacy"]); + const node = this.startNode(); + this.next(); + if (this.hasPlugin("decorators")) { + const startLoc = this.state.startLoc; + let expr; + if (this.match(10)) { + const startLoc = this.state.startLoc; + this.next(); + expr = this.parseExpression(); + this.expect(11); + expr = this.wrapParenthesis(startLoc, expr); + const paramsStartLoc = this.state.startLoc; + node.expression = this.parseMaybeDecoratorArguments(expr, startLoc); + if (this.getPluginOption("decorators", "allowCallParenthesized") === false && node.expression !== expr) { + this.raise(Errors.DecoratorArgumentsOutsideParentheses, paramsStartLoc); + } + } else { + expr = this.parseIdentifier(false); + while (this.eat(16)) { + const node = this.startNodeAt(startLoc); + node.object = expr; + if (this.match(139)) { + this.classScope.usePrivateName(this.state.value, this.state.startLoc); + node.property = this.parsePrivateName(); + } else { + node.property = this.parseIdentifier(true); + } + node.computed = false; + expr = this.finishNode(node, "MemberExpression"); + } + node.expression = this.parseMaybeDecoratorArguments(expr, startLoc); + } + } else { + node.expression = this.parseExprSubscripts(); + } + return this.finishNode(node, "Decorator"); + } + parseMaybeDecoratorArguments(expr, startLoc) { + if (this.eat(10)) { + const node = this.startNodeAt(startLoc); + node.callee = expr; + node.arguments = this.parseCallExpressionArguments(11); + this.toReferencedList(node.arguments); + return this.finishNode(node, "CallExpression"); + } + return expr; + } + parseBreakContinueStatement(node, isBreak) { + this.next(); + if (this.isLineTerminator()) { + node.label = null; + } else { + node.label = this.parseIdentifier(); + this.semicolon(); + } + this.verifyBreakContinue(node, isBreak); + return this.finishNode(node, isBreak ? "BreakStatement" : "ContinueStatement"); + } + verifyBreakContinue(node, isBreak) { + let i; + for (i = 0; i < this.state.labels.length; ++i) { + const lab = this.state.labels[i]; + if (node.label == null || lab.name === node.label.name) { + if (lab.kind != null && (isBreak || lab.kind === 1)) { + break; + } + if (node.label && isBreak) break; + } + } + if (i === this.state.labels.length) { + const type = isBreak ? "BreakStatement" : "ContinueStatement"; + this.raise(Errors.IllegalBreakContinue, node, { + type + }); + } + } + parseDebuggerStatement(node) { + this.next(); + this.semicolon(); + return this.finishNode(node, "DebuggerStatement"); + } + parseHeaderExpression() { + this.expect(10); + const val = this.parseExpression(); + this.expect(11); + return val; + } + parseDoWhileStatement(node) { + this.next(); + this.state.labels.push(loopLabel); + node.body = this.withSmartMixTopicForbiddingContext(() => this.parseStatement()); + this.state.labels.pop(); + this.expect(92); + node.test = this.parseHeaderExpression(); + this.eat(13); + return this.finishNode(node, "DoWhileStatement"); + } + parseForStatement(node) { + this.next(); + this.state.labels.push(loopLabel); + let awaitAt = null; + if (this.isContextual(96) && this.recordAwaitIfAllowed()) { + awaitAt = this.state.startLoc; + this.next(); + } + this.scope.enter(0); + this.expect(10); + if (this.match(13)) { + if (awaitAt !== null) { + this.unexpected(awaitAt); + } + return this.parseFor(node, null); + } + const startsWithLet = this.isContextual(100); + { + const startsWithAwaitUsing = this.isContextual(96) && this.startsAwaitUsing(); + const starsWithUsingDeclaration = startsWithAwaitUsing || this.isContextual(107) && this.allowsForUsing(); + const isLetOrUsing = startsWithLet && this.hasFollowingBindingAtom() || starsWithUsingDeclaration; + if (this.match(74) || this.match(75) || isLetOrUsing) { + const initNode = this.startNode(); + let kind; + if (startsWithAwaitUsing) { + kind = "await using"; + if (!this.recordAwaitIfAllowed()) { + this.raise(Errors.AwaitUsingNotInAsyncContext, this.state.startLoc); + } + this.next(); + } else { + kind = this.state.value; + } + this.next(); + this.parseVar(initNode, true, kind); + const init = this.finishNode(initNode, "VariableDeclaration"); + const isForIn = this.match(58); + if (isForIn && starsWithUsingDeclaration) { + this.raise(Errors.ForInUsing, init); + } + if ((isForIn || this.isContextual(102)) && init.declarations.length === 1) { + return this.parseForIn(node, init, awaitAt); + } + if (awaitAt !== null) { + this.unexpected(awaitAt); + } + return this.parseFor(node, init); + } + } + const startsWithAsync = this.isContextual(95); + const refExpressionErrors = new ExpressionErrors(); + const init = this.parseExpression(true, refExpressionErrors); + const isForOf = this.isContextual(102); + if (isForOf) { + if (startsWithLet) { + this.raise(Errors.ForOfLet, init); + } + if (awaitAt === null && startsWithAsync && init.type === "Identifier") { + this.raise(Errors.ForOfAsync, init); + } + } + if (isForOf || this.match(58)) { + this.checkDestructuringPrivate(refExpressionErrors); + this.toAssignable(init, true); + const type = isForOf ? "ForOfStatement" : "ForInStatement"; + this.checkLVal(init, { + type + }); + return this.parseForIn(node, init, awaitAt); + } else { + this.checkExpressionErrors(refExpressionErrors, true); + } + if (awaitAt !== null) { + this.unexpected(awaitAt); + } + return this.parseFor(node, init); + } + parseFunctionStatement(node, isAsync, isHangingDeclaration) { + this.next(); + return this.parseFunction(node, 1 | (isHangingDeclaration ? 2 : 0) | (isAsync ? 8 : 0)); + } + parseIfStatement(node) { + this.next(); + node.test = this.parseHeaderExpression(); + node.consequent = this.parseStatementOrSloppyAnnexBFunctionDeclaration(); + node.alternate = this.eat(66) ? this.parseStatementOrSloppyAnnexBFunctionDeclaration() : null; + return this.finishNode(node, "IfStatement"); + } + parseReturnStatement(node) { + if (!this.prodParam.hasReturn && !(this.optionFlags & 2)) { + this.raise(Errors.IllegalReturn, this.state.startLoc); + } + this.next(); + if (this.isLineTerminator()) { + node.argument = null; + } else { + node.argument = this.parseExpression(); + this.semicolon(); + } + return this.finishNode(node, "ReturnStatement"); + } + parseSwitchStatement(node) { + this.next(); + node.discriminant = this.parseHeaderExpression(); + const cases = node.cases = []; + this.expect(5); + this.state.labels.push(switchLabel); + this.scope.enter(0); + let cur; + for (let sawDefault; !this.match(8);) { + if (this.match(61) || this.match(65)) { + const isCase = this.match(61); + if (cur) this.finishNode(cur, "SwitchCase"); + cases.push(cur = this.startNode()); + cur.consequent = []; + this.next(); + if (isCase) { + cur.test = this.parseExpression(); + } else { + if (sawDefault) { + this.raise(Errors.MultipleDefaultsInSwitch, this.state.lastTokStartLoc); + } + sawDefault = true; + cur.test = null; + } + this.expect(14); + } else { + if (cur) { + cur.consequent.push(this.parseStatementListItem()); + } else { + this.unexpected(); + } + } + } + this.scope.exit(); + if (cur) this.finishNode(cur, "SwitchCase"); + this.next(); + this.state.labels.pop(); + return this.finishNode(node, "SwitchStatement"); + } + parseThrowStatement(node) { + this.next(); + if (this.hasPrecedingLineBreak()) { + this.raise(Errors.NewlineAfterThrow, this.state.lastTokEndLoc); + } + node.argument = this.parseExpression(); + this.semicolon(); + return this.finishNode(node, "ThrowStatement"); + } + parseCatchClauseParam() { + const param = this.parseBindingAtom(); + this.scope.enter(this.options.annexB && param.type === "Identifier" ? 8 : 0); + this.checkLVal(param, { + type: "CatchClause" + }, 9); + return param; + } + parseTryStatement(node) { + this.next(); + node.block = this.parseBlock(); + node.handler = null; + if (this.match(62)) { + const clause = this.startNode(); + this.next(); + if (this.match(10)) { + this.expect(10); + clause.param = this.parseCatchClauseParam(); + this.expect(11); + } else { + clause.param = null; + this.scope.enter(0); + } + clause.body = this.withSmartMixTopicForbiddingContext(() => this.parseBlock(false, false)); + this.scope.exit(); + node.handler = this.finishNode(clause, "CatchClause"); + } + node.finalizer = this.eat(67) ? this.parseBlock() : null; + if (!node.handler && !node.finalizer) { + this.raise(Errors.NoCatchOrFinally, node); + } + return this.finishNode(node, "TryStatement"); + } + parseVarStatement(node, kind, allowMissingInitializer = false) { + this.next(); + this.parseVar(node, false, kind, allowMissingInitializer); + this.semicolon(); + return this.finishNode(node, "VariableDeclaration"); + } + parseWhileStatement(node) { + this.next(); + node.test = this.parseHeaderExpression(); + this.state.labels.push(loopLabel); + node.body = this.withSmartMixTopicForbiddingContext(() => this.parseStatement()); + this.state.labels.pop(); + return this.finishNode(node, "WhileStatement"); + } + parseWithStatement(node) { + if (this.state.strict) { + this.raise(Errors.StrictWith, this.state.startLoc); + } + this.next(); + node.object = this.parseHeaderExpression(); + node.body = this.withSmartMixTopicForbiddingContext(() => this.parseStatement()); + return this.finishNode(node, "WithStatement"); + } + parseEmptyStatement(node) { + this.next(); + return this.finishNode(node, "EmptyStatement"); + } + parseLabeledStatement(node, maybeName, expr, flags) { + for (const label of this.state.labels) { + if (label.name === maybeName) { + this.raise(Errors.LabelRedeclaration, expr, { + labelName: maybeName + }); + } + } + const kind = tokenIsLoop(this.state.type) ? 1 : this.match(71) ? 2 : null; + for (let i = this.state.labels.length - 1; i >= 0; i--) { + const label = this.state.labels[i]; + if (label.statementStart === node.start) { + label.statementStart = this.sourceToOffsetPos(this.state.start); + label.kind = kind; + } else { + break; + } + } + this.state.labels.push({ + name: maybeName, + kind: kind, + statementStart: this.sourceToOffsetPos(this.state.start) + }); + node.body = flags & 8 ? this.parseStatementOrSloppyAnnexBFunctionDeclaration(true) : this.parseStatement(); + this.state.labels.pop(); + node.label = expr; + return this.finishNode(node, "LabeledStatement"); + } + parseExpressionStatement(node, expr, decorators) { + node.expression = expr; + this.semicolon(); + return this.finishNode(node, "ExpressionStatement"); + } + parseBlock(allowDirectives = false, createNewLexicalScope = true, afterBlockParse) { + const node = this.startNode(); + if (allowDirectives) { + this.state.strictErrors.clear(); + } + this.expect(5); + if (createNewLexicalScope) { + this.scope.enter(0); + } + this.parseBlockBody(node, allowDirectives, false, 8, afterBlockParse); + if (createNewLexicalScope) { + this.scope.exit(); + } + return this.finishNode(node, "BlockStatement"); + } + isValidDirective(stmt) { + return stmt.type === "ExpressionStatement" && stmt.expression.type === "StringLiteral" && !stmt.expression.extra.parenthesized; + } + parseBlockBody(node, allowDirectives, topLevel, end, afterBlockParse) { + const body = node.body = []; + const directives = node.directives = []; + this.parseBlockOrModuleBlockBody(body, allowDirectives ? directives : undefined, topLevel, end, afterBlockParse); + } + parseBlockOrModuleBlockBody(body, directives, topLevel, end, afterBlockParse) { + const oldStrict = this.state.strict; + let hasStrictModeDirective = false; + let parsedNonDirective = false; + while (!this.match(end)) { + const stmt = topLevel ? this.parseModuleItem() : this.parseStatementListItem(); + if (directives && !parsedNonDirective) { + if (this.isValidDirective(stmt)) { + const directive = this.stmtToDirective(stmt); + directives.push(directive); + if (!hasStrictModeDirective && directive.value.value === "use strict") { + hasStrictModeDirective = true; + this.setStrict(true); + } + continue; + } + parsedNonDirective = true; + this.state.strictErrors.clear(); + } + body.push(stmt); + } + afterBlockParse == null || afterBlockParse.call(this, hasStrictModeDirective); + if (!oldStrict) { + this.setStrict(false); + } + this.next(); + } + parseFor(node, init) { + node.init = init; + this.semicolon(false); + node.test = this.match(13) ? null : this.parseExpression(); + this.semicolon(false); + node.update = this.match(11) ? null : this.parseExpression(); + this.expect(11); + node.body = this.withSmartMixTopicForbiddingContext(() => this.parseStatement()); + this.scope.exit(); + this.state.labels.pop(); + return this.finishNode(node, "ForStatement"); + } + parseForIn(node, init, awaitAt) { + const isForIn = this.match(58); + this.next(); + if (isForIn) { + if (awaitAt !== null) this.unexpected(awaitAt); + } else { + node.await = awaitAt !== null; + } + if (init.type === "VariableDeclaration" && init.declarations[0].init != null && (!isForIn || !this.options.annexB || this.state.strict || init.kind !== "var" || init.declarations[0].id.type !== "Identifier")) { + this.raise(Errors.ForInOfLoopInitializer, init, { + type: isForIn ? "ForInStatement" : "ForOfStatement" + }); + } + if (init.type === "AssignmentPattern") { + this.raise(Errors.InvalidLhs, init, { + ancestor: { + type: "ForStatement" + } + }); + } + node.left = init; + node.right = isForIn ? this.parseExpression() : this.parseMaybeAssignAllowIn(); + this.expect(11); + node.body = this.withSmartMixTopicForbiddingContext(() => this.parseStatement()); + this.scope.exit(); + this.state.labels.pop(); + return this.finishNode(node, isForIn ? "ForInStatement" : "ForOfStatement"); + } + parseVar(node, isFor, kind, allowMissingInitializer = false) { + const declarations = node.declarations = []; + node.kind = kind; + for (;;) { + const decl = this.startNode(); + this.parseVarId(decl, kind); + decl.init = !this.eat(29) ? null : isFor ? this.parseMaybeAssignDisallowIn() : this.parseMaybeAssignAllowIn(); + if (decl.init === null && !allowMissingInitializer) { + if (decl.id.type !== "Identifier" && !(isFor && (this.match(58) || this.isContextual(102)))) { + this.raise(Errors.DeclarationMissingInitializer, this.state.lastTokEndLoc, { + kind: "destructuring" + }); + } else if ((kind === "const" || kind === "using" || kind === "await using") && !(this.match(58) || this.isContextual(102))) { + this.raise(Errors.DeclarationMissingInitializer, this.state.lastTokEndLoc, { + kind + }); + } + } + declarations.push(this.finishNode(decl, "VariableDeclarator")); + if (!this.eat(12)) break; + } + return node; + } + parseVarId(decl, kind) { + const id = this.parseBindingAtom(); + if (kind === "using" || kind === "await using") { + if (id.type === "ArrayPattern" || id.type === "ObjectPattern") { + this.raise(Errors.UsingDeclarationHasBindingPattern, id.loc.start); + } + } + this.checkLVal(id, { + type: "VariableDeclarator" + }, kind === "var" ? 5 : 8201); + decl.id = id; + } + parseAsyncFunctionExpression(node) { + return this.parseFunction(node, 8); + } + parseFunction(node, flags = 0) { + const hangingDeclaration = flags & 2; + const isDeclaration = !!(flags & 1); + const requireId = isDeclaration && !(flags & 4); + const isAsync = !!(flags & 8); + this.initFunction(node, isAsync); + if (this.match(55)) { + if (hangingDeclaration) { + this.raise(Errors.GeneratorInSingleStatementContext, this.state.startLoc); + } + this.next(); + node.generator = true; + } + if (isDeclaration) { + node.id = this.parseFunctionId(requireId); + } + const oldMaybeInArrowParameters = this.state.maybeInArrowParameters; + this.state.maybeInArrowParameters = false; + this.scope.enter(2); + this.prodParam.enter(functionFlags(isAsync, node.generator)); + if (!isDeclaration) { + node.id = this.parseFunctionId(); + } + this.parseFunctionParams(node, false); + this.withSmartMixTopicForbiddingContext(() => { + this.parseFunctionBodyAndFinish(node, isDeclaration ? "FunctionDeclaration" : "FunctionExpression"); + }); + this.prodParam.exit(); + this.scope.exit(); + if (isDeclaration && !hangingDeclaration) { + this.registerFunctionStatementId(node); + } + this.state.maybeInArrowParameters = oldMaybeInArrowParameters; + return node; + } + parseFunctionId(requireId) { + return requireId || tokenIsIdentifier(this.state.type) ? this.parseIdentifier() : null; + } + parseFunctionParams(node, isConstructor) { + this.expect(10); + this.expressionScope.enter(newParameterDeclarationScope()); + node.params = this.parseBindingList(11, 41, 2 | (isConstructor ? 4 : 0)); + this.expressionScope.exit(); + } + registerFunctionStatementId(node) { + if (!node.id) return; + this.scope.declareName(node.id.name, !this.options.annexB || this.state.strict || node.generator || node.async ? this.scope.treatFunctionsAsVar ? 5 : 8201 : 17, node.id.loc.start); + } + parseClass(node, isStatement, optionalId) { + this.next(); + const oldStrict = this.state.strict; + this.state.strict = true; + this.parseClassId(node, isStatement, optionalId); + this.parseClassSuper(node); + node.body = this.parseClassBody(!!node.superClass, oldStrict); + return this.finishNode(node, isStatement ? "ClassDeclaration" : "ClassExpression"); + } + isClassProperty() { + return this.match(29) || this.match(13) || this.match(8); + } + isClassMethod() { + return this.match(10); + } + nameIsConstructor(key) { + return key.type === "Identifier" && key.name === "constructor" || key.type === "StringLiteral" && key.value === "constructor"; + } + isNonstaticConstructor(method) { + return !method.computed && !method.static && this.nameIsConstructor(method.key); + } + parseClassBody(hadSuperClass, oldStrict) { + this.classScope.enter(); + const state = { + hadConstructor: false, + hadSuperClass + }; + let decorators = []; + const classBody = this.startNode(); + classBody.body = []; + this.expect(5); + this.withSmartMixTopicForbiddingContext(() => { + while (!this.match(8)) { + if (this.eat(13)) { + if (decorators.length > 0) { + throw this.raise(Errors.DecoratorSemicolon, this.state.lastTokEndLoc); + } + continue; + } + if (this.match(26)) { + decorators.push(this.parseDecorator()); + continue; + } + const member = this.startNode(); + if (decorators.length) { + member.decorators = decorators; + this.resetStartLocationFromNode(member, decorators[0]); + decorators = []; + } + this.parseClassMember(classBody, member, state); + if (member.kind === "constructor" && member.decorators && member.decorators.length > 0) { + this.raise(Errors.DecoratorConstructor, member); + } + } + }); + this.state.strict = oldStrict; + this.next(); + if (decorators.length) { + throw this.raise(Errors.TrailingDecorator, this.state.startLoc); + } + this.classScope.exit(); + return this.finishNode(classBody, "ClassBody"); + } + parseClassMemberFromModifier(classBody, member) { + const key = this.parseIdentifier(true); + if (this.isClassMethod()) { + const method = member; + method.kind = "method"; + method.computed = false; + method.key = key; + method.static = false; + this.pushClassMethod(classBody, method, false, false, false, false); + return true; + } else if (this.isClassProperty()) { + const prop = member; + prop.computed = false; + prop.key = key; + prop.static = false; + classBody.body.push(this.parseClassProperty(prop)); + return true; + } + this.resetPreviousNodeTrailingComments(key); + return false; + } + parseClassMember(classBody, member, state) { + const isStatic = this.isContextual(106); + if (isStatic) { + if (this.parseClassMemberFromModifier(classBody, member)) { + return; + } + if (this.eat(5)) { + this.parseClassStaticBlock(classBody, member); + return; + } + } + this.parseClassMemberWithIsStatic(classBody, member, state, isStatic); + } + parseClassMemberWithIsStatic(classBody, member, state, isStatic) { + const publicMethod = member; + const privateMethod = member; + const publicProp = member; + const privateProp = member; + const accessorProp = member; + const method = publicMethod; + const publicMember = publicMethod; + member.static = isStatic; + this.parsePropertyNamePrefixOperator(member); + if (this.eat(55)) { + method.kind = "method"; + const isPrivateName = this.match(139); + this.parseClassElementName(method); + if (isPrivateName) { + this.pushClassPrivateMethod(classBody, privateMethod, true, false); + return; + } + if (this.isNonstaticConstructor(publicMethod)) { + this.raise(Errors.ConstructorIsGenerator, publicMethod.key); + } + this.pushClassMethod(classBody, publicMethod, true, false, false, false); + return; + } + const isContextual = !this.state.containsEsc && tokenIsIdentifier(this.state.type); + const key = this.parseClassElementName(member); + const maybeContextualKw = isContextual ? key.name : null; + const isPrivate = this.isPrivateName(key); + const maybeQuestionTokenStartLoc = this.state.startLoc; + this.parsePostMemberNameModifiers(publicMember); + if (this.isClassMethod()) { + method.kind = "method"; + if (isPrivate) { + this.pushClassPrivateMethod(classBody, privateMethod, false, false); + return; + } + const isConstructor = this.isNonstaticConstructor(publicMethod); + let allowsDirectSuper = false; + if (isConstructor) { + publicMethod.kind = "constructor"; + if (state.hadConstructor && !this.hasPlugin("typescript")) { + this.raise(Errors.DuplicateConstructor, key); + } + if (isConstructor && this.hasPlugin("typescript") && member.override) { + this.raise(Errors.OverrideOnConstructor, key); + } + state.hadConstructor = true; + allowsDirectSuper = state.hadSuperClass; + } + this.pushClassMethod(classBody, publicMethod, false, false, isConstructor, allowsDirectSuper); + } else if (this.isClassProperty()) { + if (isPrivate) { + this.pushClassPrivateProperty(classBody, privateProp); + } else { + this.pushClassProperty(classBody, publicProp); + } + } else if (maybeContextualKw === "async" && !this.isLineTerminator()) { + this.resetPreviousNodeTrailingComments(key); + const isGenerator = this.eat(55); + if (publicMember.optional) { + this.unexpected(maybeQuestionTokenStartLoc); + } + method.kind = "method"; + const isPrivate = this.match(139); + this.parseClassElementName(method); + this.parsePostMemberNameModifiers(publicMember); + if (isPrivate) { + this.pushClassPrivateMethod(classBody, privateMethod, isGenerator, true); + } else { + if (this.isNonstaticConstructor(publicMethod)) { + this.raise(Errors.ConstructorIsAsync, publicMethod.key); + } + this.pushClassMethod(classBody, publicMethod, isGenerator, true, false, false); + } + } else if ((maybeContextualKw === "get" || maybeContextualKw === "set") && !(this.match(55) && this.isLineTerminator())) { + this.resetPreviousNodeTrailingComments(key); + method.kind = maybeContextualKw; + const isPrivate = this.match(139); + this.parseClassElementName(publicMethod); + if (isPrivate) { + this.pushClassPrivateMethod(classBody, privateMethod, false, false); + } else { + if (this.isNonstaticConstructor(publicMethod)) { + this.raise(Errors.ConstructorIsAccessor, publicMethod.key); + } + this.pushClassMethod(classBody, publicMethod, false, false, false, false); + } + this.checkGetterSetterParams(publicMethod); + } else if (maybeContextualKw === "accessor" && !this.isLineTerminator()) { + this.expectPlugin("decoratorAutoAccessors"); + this.resetPreviousNodeTrailingComments(key); + const isPrivate = this.match(139); + this.parseClassElementName(publicProp); + this.pushClassAccessorProperty(classBody, accessorProp, isPrivate); + } else if (this.isLineTerminator()) { + if (isPrivate) { + this.pushClassPrivateProperty(classBody, privateProp); + } else { + this.pushClassProperty(classBody, publicProp); + } + } else { + this.unexpected(); + } + } + parseClassElementName(member) { + const { + type, + value + } = this.state; + if ((type === 132 || type === 134) && member.static && value === "prototype") { + this.raise(Errors.StaticPrototype, this.state.startLoc); + } + if (type === 139) { + if (value === "constructor") { + this.raise(Errors.ConstructorClassPrivateField, this.state.startLoc); + } + const key = this.parsePrivateName(); + member.key = key; + return key; + } + this.parsePropertyName(member); + return member.key; + } + parseClassStaticBlock(classBody, member) { + var _member$decorators; + this.scope.enter(64 | 128 | 16); + const oldLabels = this.state.labels; + this.state.labels = []; + this.prodParam.enter(0); + const body = member.body = []; + this.parseBlockOrModuleBlockBody(body, undefined, false, 8); + this.prodParam.exit(); + this.scope.exit(); + this.state.labels = oldLabels; + classBody.body.push(this.finishNode(member, "StaticBlock")); + if ((_member$decorators = member.decorators) != null && _member$decorators.length) { + this.raise(Errors.DecoratorStaticBlock, member); + } + } + pushClassProperty(classBody, prop) { + if (!prop.computed && this.nameIsConstructor(prop.key)) { + this.raise(Errors.ConstructorClassField, prop.key); + } + classBody.body.push(this.parseClassProperty(prop)); + } + pushClassPrivateProperty(classBody, prop) { + const node = this.parseClassPrivateProperty(prop); + classBody.body.push(node); + this.classScope.declarePrivateName(this.getPrivateNameSV(node.key), 0, node.key.loc.start); + } + pushClassAccessorProperty(classBody, prop, isPrivate) { + if (!isPrivate && !prop.computed && this.nameIsConstructor(prop.key)) { + this.raise(Errors.ConstructorClassField, prop.key); + } + const node = this.parseClassAccessorProperty(prop); + classBody.body.push(node); + if (isPrivate) { + this.classScope.declarePrivateName(this.getPrivateNameSV(node.key), 0, node.key.loc.start); + } + } + pushClassMethod(classBody, method, isGenerator, isAsync, isConstructor, allowsDirectSuper) { + classBody.body.push(this.parseMethod(method, isGenerator, isAsync, isConstructor, allowsDirectSuper, "ClassMethod", true)); + } + pushClassPrivateMethod(classBody, method, isGenerator, isAsync) { + const node = this.parseMethod(method, isGenerator, isAsync, false, false, "ClassPrivateMethod", true); + classBody.body.push(node); + const kind = node.kind === "get" ? node.static ? 6 : 2 : node.kind === "set" ? node.static ? 5 : 1 : 0; + this.declareClassPrivateMethodInScope(node, kind); + } + declareClassPrivateMethodInScope(node, kind) { + this.classScope.declarePrivateName(this.getPrivateNameSV(node.key), kind, node.key.loc.start); + } + parsePostMemberNameModifiers(methodOrProp) {} + parseClassPrivateProperty(node) { + this.parseInitializer(node); + this.semicolon(); + return this.finishNode(node, "ClassPrivateProperty"); + } + parseClassProperty(node) { + this.parseInitializer(node); + this.semicolon(); + return this.finishNode(node, "ClassProperty"); + } + parseClassAccessorProperty(node) { + this.parseInitializer(node); + this.semicolon(); + return this.finishNode(node, "ClassAccessorProperty"); + } + parseInitializer(node) { + this.scope.enter(64 | 16); + this.expressionScope.enter(newExpressionScope()); + this.prodParam.enter(0); + node.value = this.eat(29) ? this.parseMaybeAssignAllowIn() : null; + this.expressionScope.exit(); + this.prodParam.exit(); + this.scope.exit(); + } + parseClassId(node, isStatement, optionalId, bindingType = 8331) { + if (tokenIsIdentifier(this.state.type)) { + node.id = this.parseIdentifier(); + if (isStatement) { + this.declareNameFromIdentifier(node.id, bindingType); + } + } else { + if (optionalId || !isStatement) { + node.id = null; + } else { + throw this.raise(Errors.MissingClassName, this.state.startLoc); + } + } + } + parseClassSuper(node) { + node.superClass = this.eat(81) ? this.parseExprSubscripts() : null; + } + parseExport(node, decorators) { + const maybeDefaultIdentifier = this.parseMaybeImportPhase(node, true); + const hasDefault = this.maybeParseExportDefaultSpecifier(node, maybeDefaultIdentifier); + const parseAfterDefault = !hasDefault || this.eat(12); + const hasStar = parseAfterDefault && this.eatExportStar(node); + const hasNamespace = hasStar && this.maybeParseExportNamespaceSpecifier(node); + const parseAfterNamespace = parseAfterDefault && (!hasNamespace || this.eat(12)); + const isFromRequired = hasDefault || hasStar; + if (hasStar && !hasNamespace) { + if (hasDefault) this.unexpected(); + if (decorators) { + throw this.raise(Errors.UnsupportedDecoratorExport, node); + } + this.parseExportFrom(node, true); + this.sawUnambiguousESM = true; + return this.finishNode(node, "ExportAllDeclaration"); + } + const hasSpecifiers = this.maybeParseExportNamedSpecifiers(node); + if (hasDefault && parseAfterDefault && !hasStar && !hasSpecifiers) { + this.unexpected(null, 5); + } + if (hasNamespace && parseAfterNamespace) { + this.unexpected(null, 98); + } + let hasDeclaration; + if (isFromRequired || hasSpecifiers) { + hasDeclaration = false; + if (decorators) { + throw this.raise(Errors.UnsupportedDecoratorExport, node); + } + this.parseExportFrom(node, isFromRequired); + } else { + hasDeclaration = this.maybeParseExportDeclaration(node); + } + if (isFromRequired || hasSpecifiers || hasDeclaration) { + var _node2$declaration; + const node2 = node; + this.checkExport(node2, true, false, !!node2.source); + if (((_node2$declaration = node2.declaration) == null ? void 0 : _node2$declaration.type) === "ClassDeclaration") { + this.maybeTakeDecorators(decorators, node2.declaration, node2); + } else if (decorators) { + throw this.raise(Errors.UnsupportedDecoratorExport, node); + } + this.sawUnambiguousESM = true; + return this.finishNode(node2, "ExportNamedDeclaration"); + } + if (this.eat(65)) { + const node2 = node; + const decl = this.parseExportDefaultExpression(); + node2.declaration = decl; + if (decl.type === "ClassDeclaration") { + this.maybeTakeDecorators(decorators, decl, node2); + } else if (decorators) { + throw this.raise(Errors.UnsupportedDecoratorExport, node); + } + this.checkExport(node2, true, true); + this.sawUnambiguousESM = true; + return this.finishNode(node2, "ExportDefaultDeclaration"); + } + this.unexpected(null, 5); + } + eatExportStar(node) { + return this.eat(55); + } + maybeParseExportDefaultSpecifier(node, maybeDefaultIdentifier) { + if (maybeDefaultIdentifier || this.isExportDefaultSpecifier()) { + this.expectPlugin("exportDefaultFrom", maybeDefaultIdentifier == null ? void 0 : maybeDefaultIdentifier.loc.start); + const id = maybeDefaultIdentifier || this.parseIdentifier(true); + const specifier = this.startNodeAtNode(id); + specifier.exported = id; + node.specifiers = [this.finishNode(specifier, "ExportDefaultSpecifier")]; + return true; + } + return false; + } + maybeParseExportNamespaceSpecifier(node) { + if (this.isContextual(93)) { + var _ref, _ref$specifiers; + (_ref$specifiers = (_ref = node).specifiers) != null ? _ref$specifiers : _ref.specifiers = []; + const specifier = this.startNodeAt(this.state.lastTokStartLoc); + this.next(); + specifier.exported = this.parseModuleExportName(); + node.specifiers.push(this.finishNode(specifier, "ExportNamespaceSpecifier")); + return true; + } + return false; + } + maybeParseExportNamedSpecifiers(node) { + if (this.match(5)) { + const node2 = node; + if (!node2.specifiers) node2.specifiers = []; + const isTypeExport = node2.exportKind === "type"; + node2.specifiers.push(...this.parseExportSpecifiers(isTypeExport)); + node2.source = null; + if (this.hasPlugin("importAssertions")) { + node2.assertions = []; + } else { + node2.attributes = []; + } + node2.declaration = null; + return true; + } + return false; + } + maybeParseExportDeclaration(node) { + if (this.shouldParseExportDeclaration()) { + node.specifiers = []; + node.source = null; + if (this.hasPlugin("importAssertions")) { + node.assertions = []; + } else { + node.attributes = []; + } + node.declaration = this.parseExportDeclaration(node); + return true; + } + return false; + } + isAsyncFunction() { + if (!this.isContextual(95)) return false; + const next = this.nextTokenInLineStart(); + return this.isUnparsedContextual(next, "function"); + } + parseExportDefaultExpression() { + const expr = this.startNode(); + if (this.match(68)) { + this.next(); + return this.parseFunction(expr, 1 | 4); + } else if (this.isAsyncFunction()) { + this.next(); + this.next(); + return this.parseFunction(expr, 1 | 4 | 8); + } + if (this.match(80)) { + return this.parseClass(expr, true, true); + } + if (this.match(26)) { + if (this.hasPlugin("decorators") && this.getPluginOption("decorators", "decoratorsBeforeExport") === true) { + this.raise(Errors.DecoratorBeforeExport, this.state.startLoc); + } + return this.parseClass(this.maybeTakeDecorators(this.parseDecorators(false), this.startNode()), true, true); + } + if (this.match(75) || this.match(74) || this.isLet()) { + throw this.raise(Errors.UnsupportedDefaultExport, this.state.startLoc); + } + const res = this.parseMaybeAssignAllowIn(); + this.semicolon(); + return res; + } + parseExportDeclaration(node) { + if (this.match(80)) { + const node = this.parseClass(this.startNode(), true, false); + return node; + } + return this.parseStatementListItem(); + } + isExportDefaultSpecifier() { + const { + type + } = this.state; + if (tokenIsIdentifier(type)) { + if (type === 95 && !this.state.containsEsc || type === 100) { + return false; + } + if ((type === 130 || type === 129) && !this.state.containsEsc) { + const { + type: nextType + } = this.lookahead(); + if (tokenIsIdentifier(nextType) && nextType !== 98 || nextType === 5) { + this.expectOnePlugin(["flow", "typescript"]); + return false; + } + } + } else if (!this.match(65)) { + return false; + } + const next = this.nextTokenStart(); + const hasFrom = this.isUnparsedContextual(next, "from"); + if (this.input.charCodeAt(next) === 44 || tokenIsIdentifier(this.state.type) && hasFrom) { + return true; + } + if (this.match(65) && hasFrom) { + const nextAfterFrom = this.input.charCodeAt(this.nextTokenStartSince(next + 4)); + return nextAfterFrom === 34 || nextAfterFrom === 39; + } + return false; + } + parseExportFrom(node, expect) { + if (this.eatContextual(98)) { + node.source = this.parseImportSource(); + this.checkExport(node); + this.maybeParseImportAttributes(node); + this.checkJSONModuleImport(node); + } else if (expect) { + this.unexpected(); + } + this.semicolon(); + } + shouldParseExportDeclaration() { + const { + type + } = this.state; + if (type === 26) { + this.expectOnePlugin(["decorators", "decorators-legacy"]); + if (this.hasPlugin("decorators")) { + if (this.getPluginOption("decorators", "decoratorsBeforeExport") === true) { + this.raise(Errors.DecoratorBeforeExport, this.state.startLoc); + } + return true; + } + } + if (this.isContextual(107)) { + this.raise(Errors.UsingDeclarationExport, this.state.startLoc); + return true; + } + if (this.isContextual(96) && this.startsAwaitUsing()) { + this.raise(Errors.UsingDeclarationExport, this.state.startLoc); + return true; + } + return type === 74 || type === 75 || type === 68 || type === 80 || this.isLet() || this.isAsyncFunction(); + } + checkExport(node, checkNames, isDefault, isFrom) { + if (checkNames) { + var _node$specifiers; + if (isDefault) { + this.checkDuplicateExports(node, "default"); + if (this.hasPlugin("exportDefaultFrom")) { + var _declaration$extra; + const declaration = node.declaration; + if (declaration.type === "Identifier" && declaration.name === "from" && declaration.end - declaration.start === 4 && !((_declaration$extra = declaration.extra) != null && _declaration$extra.parenthesized)) { + this.raise(Errors.ExportDefaultFromAsIdentifier, declaration); + } + } + } else if ((_node$specifiers = node.specifiers) != null && _node$specifiers.length) { + for (const specifier of node.specifiers) { + const { + exported + } = specifier; + const exportName = exported.type === "Identifier" ? exported.name : exported.value; + this.checkDuplicateExports(specifier, exportName); + if (!isFrom && specifier.local) { + const { + local + } = specifier; + if (local.type !== "Identifier") { + this.raise(Errors.ExportBindingIsString, specifier, { + localName: local.value, + exportName + }); + } else { + this.checkReservedWord(local.name, local.loc.start, true, false); + this.scope.checkLocalExport(local); + } + } + } + } else if (node.declaration) { + const decl = node.declaration; + if (decl.type === "FunctionDeclaration" || decl.type === "ClassDeclaration") { + const { + id + } = decl; + if (!id) throw new Error("Assertion failure"); + this.checkDuplicateExports(node, id.name); + } else if (decl.type === "VariableDeclaration") { + for (const declaration of decl.declarations) { + this.checkDeclaration(declaration.id); + } + } + } + } + } + checkDeclaration(node) { + if (node.type === "Identifier") { + this.checkDuplicateExports(node, node.name); + } else if (node.type === "ObjectPattern") { + for (const prop of node.properties) { + this.checkDeclaration(prop); + } + } else if (node.type === "ArrayPattern") { + for (const elem of node.elements) { + if (elem) { + this.checkDeclaration(elem); + } + } + } else if (node.type === "ObjectProperty") { + this.checkDeclaration(node.value); + } else if (node.type === "RestElement") { + this.checkDeclaration(node.argument); + } else if (node.type === "AssignmentPattern") { + this.checkDeclaration(node.left); + } + } + checkDuplicateExports(node, exportName) { + if (this.exportedIdentifiers.has(exportName)) { + if (exportName === "default") { + this.raise(Errors.DuplicateDefaultExport, node); + } else { + this.raise(Errors.DuplicateExport, node, { + exportName + }); + } + } + this.exportedIdentifiers.add(exportName); + } + parseExportSpecifiers(isInTypeExport) { + const nodes = []; + let first = true; + this.expect(5); + while (!this.eat(8)) { + if (first) { + first = false; + } else { + this.expect(12); + if (this.eat(8)) break; + } + const isMaybeTypeOnly = this.isContextual(130); + const isString = this.match(134); + const node = this.startNode(); + node.local = this.parseModuleExportName(); + nodes.push(this.parseExportSpecifier(node, isString, isInTypeExport, isMaybeTypeOnly)); + } + return nodes; + } + parseExportSpecifier(node, isString, isInTypeExport, isMaybeTypeOnly) { + if (this.eatContextual(93)) { + node.exported = this.parseModuleExportName(); + } else if (isString) { + node.exported = this.cloneStringLiteral(node.local); + } else if (!node.exported) { + node.exported = this.cloneIdentifier(node.local); + } + return this.finishNode(node, "ExportSpecifier"); + } + parseModuleExportName() { + if (this.match(134)) { + const result = this.parseStringLiteral(this.state.value); + const surrogate = loneSurrogate.exec(result.value); + if (surrogate) { + this.raise(Errors.ModuleExportNameHasLoneSurrogate, result, { + surrogateCharCode: surrogate[0].charCodeAt(0) + }); + } + return result; + } + return this.parseIdentifier(true); + } + isJSONModuleImport(node) { + if (node.assertions != null) { + return node.assertions.some(({ + key, + value + }) => { + return value.value === "json" && (key.type === "Identifier" ? key.name === "type" : key.value === "type"); + }); + } + return false; + } + checkImportReflection(node) { + const { + specifiers + } = node; + const singleBindingType = specifiers.length === 1 ? specifiers[0].type : null; + if (node.phase === "source") { + if (singleBindingType !== "ImportDefaultSpecifier") { + this.raise(Errors.SourcePhaseImportRequiresDefault, specifiers[0].loc.start); + } + } else if (node.phase === "defer") { + if (singleBindingType !== "ImportNamespaceSpecifier") { + this.raise(Errors.DeferImportRequiresNamespace, specifiers[0].loc.start); + } + } else if (node.module) { + var _node$assertions; + if (singleBindingType !== "ImportDefaultSpecifier") { + this.raise(Errors.ImportReflectionNotBinding, specifiers[0].loc.start); + } + if (((_node$assertions = node.assertions) == null ? void 0 : _node$assertions.length) > 0) { + this.raise(Errors.ImportReflectionHasAssertion, specifiers[0].loc.start); + } + } + } + checkJSONModuleImport(node) { + if (this.isJSONModuleImport(node) && node.type !== "ExportAllDeclaration") { + const { + specifiers + } = node; + if (specifiers != null) { + const nonDefaultNamedSpecifier = specifiers.find(specifier => { + let imported; + if (specifier.type === "ExportSpecifier") { + imported = specifier.local; + } else if (specifier.type === "ImportSpecifier") { + imported = specifier.imported; + } + if (imported !== undefined) { + return imported.type === "Identifier" ? imported.name !== "default" : imported.value !== "default"; + } + }); + if (nonDefaultNamedSpecifier !== undefined) { + this.raise(Errors.ImportJSONBindingNotDefault, nonDefaultNamedSpecifier.loc.start); + } + } + } + } + isPotentialImportPhase(isExport) { + if (isExport) return false; + return this.isContextual(105) || this.isContextual(97) || this.isContextual(127); + } + applyImportPhase(node, isExport, phase, loc) { + if (isExport) { + return; + } + if (phase === "module") { + this.expectPlugin("importReflection", loc); + node.module = true; + } else if (this.hasPlugin("importReflection")) { + node.module = false; + } + if (phase === "source") { + this.expectPlugin("sourcePhaseImports", loc); + node.phase = "source"; + } else if (phase === "defer") { + this.expectPlugin("deferredImportEvaluation", loc); + node.phase = "defer"; + } else if (this.hasPlugin("sourcePhaseImports")) { + node.phase = null; + } + } + parseMaybeImportPhase(node, isExport) { + if (!this.isPotentialImportPhase(isExport)) { + this.applyImportPhase(node, isExport, null); + return null; + } + const phaseIdentifier = this.parseIdentifier(true); + const { + type + } = this.state; + const isImportPhase = tokenIsKeywordOrIdentifier(type) ? type !== 98 || this.lookaheadCharCode() === 102 : type !== 12; + if (isImportPhase) { + this.resetPreviousIdentifierLeadingComments(phaseIdentifier); + this.applyImportPhase(node, isExport, phaseIdentifier.name, phaseIdentifier.loc.start); + return null; + } else { + this.applyImportPhase(node, isExport, null); + return phaseIdentifier; + } + } + isPrecedingIdImportPhase(phase) { + const { + type + } = this.state; + return tokenIsIdentifier(type) ? type !== 98 || this.lookaheadCharCode() === 102 : type !== 12; + } + parseImport(node) { + if (this.match(134)) { + return this.parseImportSourceAndAttributes(node); + } + return this.parseImportSpecifiersAndAfter(node, this.parseMaybeImportPhase(node, false)); + } + parseImportSpecifiersAndAfter(node, maybeDefaultIdentifier) { + node.specifiers = []; + const hasDefault = this.maybeParseDefaultImportSpecifier(node, maybeDefaultIdentifier); + const parseNext = !hasDefault || this.eat(12); + const hasStar = parseNext && this.maybeParseStarImportSpecifier(node); + if (parseNext && !hasStar) this.parseNamedImportSpecifiers(node); + this.expectContextual(98); + return this.parseImportSourceAndAttributes(node); + } + parseImportSourceAndAttributes(node) { + var _node$specifiers2; + (_node$specifiers2 = node.specifiers) != null ? _node$specifiers2 : node.specifiers = []; + node.source = this.parseImportSource(); + this.maybeParseImportAttributes(node); + this.checkImportReflection(node); + this.checkJSONModuleImport(node); + this.semicolon(); + this.sawUnambiguousESM = true; + return this.finishNode(node, "ImportDeclaration"); + } + parseImportSource() { + if (!this.match(134)) this.unexpected(); + return this.parseExprAtom(); + } + parseImportSpecifierLocal(node, specifier, type) { + specifier.local = this.parseIdentifier(); + node.specifiers.push(this.finishImportSpecifier(specifier, type)); + } + finishImportSpecifier(specifier, type, bindingType = 8201) { + this.checkLVal(specifier.local, { + type + }, bindingType); + return this.finishNode(specifier, type); + } + parseImportAttributes() { + this.expect(5); + const attrs = []; + const attrNames = new Set(); + do { + if (this.match(8)) { + break; + } + const node = this.startNode(); + const keyName = this.state.value; + if (attrNames.has(keyName)) { + this.raise(Errors.ModuleAttributesWithDuplicateKeys, this.state.startLoc, { + key: keyName + }); + } + attrNames.add(keyName); + if (this.match(134)) { + node.key = this.parseStringLiteral(keyName); + } else { + node.key = this.parseIdentifier(true); + } + this.expect(14); + if (!this.match(134)) { + throw this.raise(Errors.ModuleAttributeInvalidValue, this.state.startLoc); + } + node.value = this.parseStringLiteral(this.state.value); + attrs.push(this.finishNode(node, "ImportAttribute")); + } while (this.eat(12)); + this.expect(8); + return attrs; + } + parseModuleAttributes() { + const attrs = []; + const attributes = new Set(); + do { + const node = this.startNode(); + node.key = this.parseIdentifier(true); + if (node.key.name !== "type") { + this.raise(Errors.ModuleAttributeDifferentFromType, node.key); + } + if (attributes.has(node.key.name)) { + this.raise(Errors.ModuleAttributesWithDuplicateKeys, node.key, { + key: node.key.name + }); + } + attributes.add(node.key.name); + this.expect(14); + if (!this.match(134)) { + throw this.raise(Errors.ModuleAttributeInvalidValue, this.state.startLoc); + } + node.value = this.parseStringLiteral(this.state.value); + attrs.push(this.finishNode(node, "ImportAttribute")); + } while (this.eat(12)); + return attrs; + } + maybeParseImportAttributes(node) { + let attributes; + { + var useWith = false; + } + if (this.match(76)) { + if (this.hasPrecedingLineBreak() && this.lookaheadCharCode() === 40) { + return; + } + this.next(); + if (this.hasPlugin("moduleAttributes")) { + attributes = this.parseModuleAttributes(); + this.addExtra(node, "deprecatedWithLegacySyntax", true); + } else { + attributes = this.parseImportAttributes(); + } + { + useWith = true; + } + } else if (this.isContextual(94) && !this.hasPrecedingLineBreak()) { + if (!this.hasPlugin("deprecatedImportAssert") && !this.hasPlugin("importAssertions")) { + this.raise(Errors.ImportAttributesUseAssert, this.state.startLoc); + } + if (!this.hasPlugin("importAssertions")) { + this.addExtra(node, "deprecatedAssertSyntax", true); + } + this.next(); + attributes = this.parseImportAttributes(); + } else { + attributes = []; + } + if (!useWith && this.hasPlugin("importAssertions")) { + node.assertions = attributes; + } else { + node.attributes = attributes; + } + } + maybeParseDefaultImportSpecifier(node, maybeDefaultIdentifier) { + if (maybeDefaultIdentifier) { + const specifier = this.startNodeAtNode(maybeDefaultIdentifier); + specifier.local = maybeDefaultIdentifier; + node.specifiers.push(this.finishImportSpecifier(specifier, "ImportDefaultSpecifier")); + return true; + } else if (tokenIsKeywordOrIdentifier(this.state.type)) { + this.parseImportSpecifierLocal(node, this.startNode(), "ImportDefaultSpecifier"); + return true; + } + return false; + } + maybeParseStarImportSpecifier(node) { + if (this.match(55)) { + const specifier = this.startNode(); + this.next(); + this.expectContextual(93); + this.parseImportSpecifierLocal(node, specifier, "ImportNamespaceSpecifier"); + return true; + } + return false; + } + parseNamedImportSpecifiers(node) { + let first = true; + this.expect(5); + while (!this.eat(8)) { + if (first) { + first = false; + } else { + if (this.eat(14)) { + throw this.raise(Errors.DestructureNamedImport, this.state.startLoc); + } + this.expect(12); + if (this.eat(8)) break; + } + const specifier = this.startNode(); + const importedIsString = this.match(134); + const isMaybeTypeOnly = this.isContextual(130); + specifier.imported = this.parseModuleExportName(); + const importSpecifier = this.parseImportSpecifier(specifier, importedIsString, node.importKind === "type" || node.importKind === "typeof", isMaybeTypeOnly, undefined); + node.specifiers.push(importSpecifier); + } + } + parseImportSpecifier(specifier, importedIsString, isInTypeOnlyImport, isMaybeTypeOnly, bindingType) { + if (this.eatContextual(93)) { + specifier.local = this.parseIdentifier(); + } else { + const { + imported + } = specifier; + if (importedIsString) { + throw this.raise(Errors.ImportBindingIsString, specifier, { + importName: imported.value + }); + } + this.checkReservedWord(imported.name, specifier.loc.start, true, true); + if (!specifier.local) { + specifier.local = this.cloneIdentifier(imported); + } + } + return this.finishImportSpecifier(specifier, "ImportSpecifier", bindingType); + } + isThisParam(param) { + return param.type === "Identifier" && param.name === "this"; + } +} +class Parser extends StatementParser { + constructor(options, input, pluginsMap) { + options = getOptions(options); + super(options, input); + this.options = options; + this.initializeScopes(); + this.plugins = pluginsMap; + this.filename = options.sourceFilename; + this.startIndex = options.startIndex; + let optionFlags = 0; + if (options.allowAwaitOutsideFunction) { + optionFlags |= 1; + } + if (options.allowReturnOutsideFunction) { + optionFlags |= 2; + } + if (options.allowImportExportEverywhere) { + optionFlags |= 8; + } + if (options.allowSuperOutsideMethod) { + optionFlags |= 16; + } + if (options.allowUndeclaredExports) { + optionFlags |= 64; + } + if (options.allowNewTargetOutsideFunction) { + optionFlags |= 4; + } + if (options.allowYieldOutsideFunction) { + optionFlags |= 32; + } + if (options.ranges) { + optionFlags |= 128; + } + if (options.tokens) { + optionFlags |= 256; + } + if (options.createImportExpressions) { + optionFlags |= 512; + } + if (options.createParenthesizedExpressions) { + optionFlags |= 1024; + } + if (options.errorRecovery) { + optionFlags |= 2048; + } + if (options.attachComment) { + optionFlags |= 4096; + } + if (options.annexB) { + optionFlags |= 8192; + } + this.optionFlags = optionFlags; + } + getScopeHandler() { + return ScopeHandler; + } + parse() { + this.enterInitialScopes(); + const file = this.startNode(); + const program = this.startNode(); + this.nextToken(); + file.errors = null; + this.parseTopLevel(file, program); + file.errors = this.state.errors; + file.comments.length = this.state.commentsLen; + return file; + } +} +function parse(input, options) { + var _options; + if (((_options = options) == null ? void 0 : _options.sourceType) === "unambiguous") { + options = Object.assign({}, options); + try { + options.sourceType = "module"; + const parser = getParser(options, input); + const ast = parser.parse(); + if (parser.sawUnambiguousESM) { + return ast; + } + if (parser.ambiguousScriptDifferentAst) { + try { + options.sourceType = "script"; + return getParser(options, input).parse(); + } catch (_unused) {} + } else { + ast.program.sourceType = "script"; + } + return ast; + } catch (moduleError) { + try { + options.sourceType = "script"; + return getParser(options, input).parse(); + } catch (_unused2) {} + throw moduleError; + } + } else { + return getParser(options, input).parse(); + } +} +function parseExpression(input, options) { + const parser = getParser(options, input); + if (parser.options.strictMode) { + parser.state.strict = true; + } + return parser.getExpression(); +} +function generateExportedTokenTypes(internalTokenTypes) { + const tokenTypes = {}; + for (const typeName of Object.keys(internalTokenTypes)) { + tokenTypes[typeName] = getExportedToken(internalTokenTypes[typeName]); + } + return tokenTypes; +} +const tokTypes = generateExportedTokenTypes(tt); +function getParser(options, input) { + let cls = Parser; + const pluginsMap = new Map(); + if (options != null && options.plugins) { + for (const plugin of options.plugins) { + let name, opts; + if (typeof plugin === "string") { + name = plugin; + } else { + [name, opts] = plugin; + } + if (!pluginsMap.has(name)) { + pluginsMap.set(name, opts || {}); + } + } + validatePlugins(pluginsMap); + cls = getParserClass(pluginsMap); + } + return new cls(options, input, pluginsMap); +} +const parserClassCache = new Map(); +function getParserClass(pluginsMap) { + const pluginList = []; + for (const name of mixinPluginNames) { + if (pluginsMap.has(name)) { + pluginList.push(name); + } + } + const key = pluginList.join("|"); + let cls = parserClassCache.get(key); + if (!cls) { + cls = Parser; + for (const plugin of pluginList) { + cls = mixinPlugins[plugin](cls); + } + parserClassCache.set(key, cls); + } + return cls; +} +exports.parse = parse; +exports.parseExpression = parseExpression; +exports.tokTypes = tokTypes; +//# sourceMappingURL=index.js.map + + +/***/ }), + +/***/ 32519: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +Object.defineProperty(exports, "convertFunctionParams", ({ + enumerable: true, + get: function () { + return _params.default; + } +})); +exports["default"] = void 0; +var _helperPluginUtils = __nccwpck_require__(30484); +var _params = __nccwpck_require__(61893); +var _rest = __nccwpck_require__(36061); +var _default = exports["default"] = (0, _helperPluginUtils.declare)((api, options) => { + var _api$assumption, _api$assumption2; + api.assertVersion(7); + const ignoreFunctionLength = (_api$assumption = api.assumption("ignoreFunctionLength")) != null ? _api$assumption : options.loose; + const noNewArrows = (_api$assumption2 = api.assumption("noNewArrows")) != null ? _api$assumption2 : true; + return { + name: "transform-parameters", + visitor: { + Function(path) { + if (path.isArrowFunctionExpression() && path.get("params").some(param => param.isRestElement() || param.isAssignmentPattern())) { + path.arrowFunctionToExpression({ + allowInsertArrowWithRest: false, + noNewArrows + }); + if (!path.isFunctionExpression()) return; + } + const convertedRest = (0, _rest.default)(path); + const convertedParams = (0, _params.default)(path, ignoreFunctionLength); + if (convertedRest || convertedParams) { + path.scope.crawl(); + } + } + } + }; +}); + +//# sourceMappingURL=index.js.map + + +/***/ }), + +/***/ 61893: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports["default"] = convertFunctionParams; +var _core = __nccwpck_require__(85414); +var _shadowUtils = __nccwpck_require__(92399); +const buildDefaultParam = _core.template.statement(` + let VARIABLE_NAME = + arguments.length > ARGUMENT_KEY && arguments[ARGUMENT_KEY] !== undefined ? + arguments[ARGUMENT_KEY] + : + DEFAULT_VALUE; +`); +const buildLooseDefaultParam = _core.template.statement(` + if (ASSIGNMENT_IDENTIFIER === UNDEFINED) { + ASSIGNMENT_IDENTIFIER = DEFAULT_VALUE; + } +`); +const buildLooseDestructuredDefaultParam = _core.template.statement(` + let ASSIGNMENT_IDENTIFIER = PARAMETER_NAME === UNDEFINED ? DEFAULT_VALUE : PARAMETER_NAME ; +`); +const buildSafeArgumentsAccess = _core.template.statement(` + let $0 = arguments.length > $1 ? arguments[$1] : undefined; +`); +function convertFunctionParams(path, ignoreFunctionLength, shouldTransformParam, replaceRestElement) { + const params = path.get("params"); + const isSimpleParameterList = params.every(param => param.isIdentifier()); + if (isSimpleParameterList) return false; + const { + node, + scope + } = path; + const body = []; + const shadowedParams = new Set(); + for (const param of params) { + (0, _shadowUtils.collectShadowedParamsNames)(param, scope, shadowedParams); + } + const state = { + needsOuterBinding: false, + scope + }; + if (shadowedParams.size === 0) { + for (const param of params) { + if (!param.isIdentifier()) param.traverse(_shadowUtils.iifeVisitor, state); + if (state.needsOuterBinding) break; + } + } + let firstOptionalIndex = null; + for (let i = 0; i < params.length; i++) { + const param = params[i]; + if (shouldTransformParam && !shouldTransformParam(i)) { + continue; + } + const transformedRestNodes = []; + if (replaceRestElement) { + replaceRestElement(path, param, transformedRestNodes); + } + const paramIsAssignmentPattern = param.isAssignmentPattern(); + if (paramIsAssignmentPattern && (ignoreFunctionLength || _core.types.isMethod(node, { + kind: "set" + }))) { + const left = param.get("left"); + const right = param.get("right"); + const undefinedNode = scope.buildUndefinedNode(); + if (left.isIdentifier()) { + body.push(buildLooseDefaultParam({ + ASSIGNMENT_IDENTIFIER: _core.types.cloneNode(left.node), + DEFAULT_VALUE: right.node, + UNDEFINED: undefinedNode + })); + param.replaceWith(left.node); + } else if (left.isObjectPattern() || left.isArrayPattern()) { + const paramName = scope.generateUidIdentifier(); + body.push(buildLooseDestructuredDefaultParam({ + ASSIGNMENT_IDENTIFIER: left.node, + DEFAULT_VALUE: right.node, + PARAMETER_NAME: _core.types.cloneNode(paramName), + UNDEFINED: undefinedNode + })); + param.replaceWith(paramName); + } + } else if (paramIsAssignmentPattern) { + if (firstOptionalIndex === null) firstOptionalIndex = i; + const left = param.get("left"); + const right = param.get("right"); + const defNode = buildDefaultParam({ + VARIABLE_NAME: left.node, + DEFAULT_VALUE: right.node, + ARGUMENT_KEY: _core.types.numericLiteral(i) + }); + body.push(defNode); + } else if (firstOptionalIndex !== null) { + const defNode = buildSafeArgumentsAccess([param.node, _core.types.numericLiteral(i)]); + body.push(defNode); + } else if (param.isObjectPattern() || param.isArrayPattern()) { + const uid = path.scope.generateUidIdentifier("ref"); + uid.typeAnnotation = param.node.typeAnnotation; + const defNode = _core.types.variableDeclaration("let", [_core.types.variableDeclarator(param.node, uid)]); + body.push(defNode); + param.replaceWith(_core.types.cloneNode(uid)); + } + if (transformedRestNodes) { + for (const transformedNode of transformedRestNodes) { + body.push(transformedNode); + } + } + } + if (firstOptionalIndex !== null) { + node.params = node.params.slice(0, firstOptionalIndex); + } + path.ensureBlock(); + const path2 = path; + const { + async, + generator + } = node; + if (generator || state.needsOuterBinding || shadowedParams.size > 0) { + body.push((0, _shadowUtils.buildScopeIIFE)(shadowedParams, path2.node.body)); + path.set("body", _core.types.blockStatement(body)); + const bodyPath = path2.get("body.body"); + const arrowPath = bodyPath[bodyPath.length - 1].get("argument.callee"); + arrowPath.arrowFunctionToExpression(); + arrowPath.node.generator = generator; + arrowPath.node.async = async; + node.generator = false; + node.async = false; + if (async) { + path2.node.body = _core.template.statement.ast`{ + try { + ${path2.node.body.body} + } catch (e) { + return Promise.reject(e); + } + }`; + } + } else { + path2.get("body").unshiftContainer("body", body); + } + return true; +} + +//# sourceMappingURL=params.js.map + + +/***/ }), + +/***/ 36061: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports["default"] = convertFunctionRest; +var _core = __nccwpck_require__(85414); +var _shadowUtils = __nccwpck_require__(92399); +const buildRest = _core.template.statement(` + for (var LEN = ARGUMENTS.length, + ARRAY = new Array(ARRAY_LEN), + KEY = START; + KEY < LEN; + KEY++) { + ARRAY[ARRAY_KEY] = ARGUMENTS[KEY]; + } +`); +const restIndex = _core.template.expression(` + (INDEX < OFFSET || ARGUMENTS.length <= INDEX) ? undefined : ARGUMENTS[INDEX] +`); +const restIndexImpure = _core.template.expression(` + REF = INDEX, (REF < OFFSET || ARGUMENTS.length <= REF) ? undefined : ARGUMENTS[REF] +`); +const restLength = _core.template.expression(` + ARGUMENTS.length <= OFFSET ? 0 : ARGUMENTS.length - OFFSET +`); +function referencesRest(path, state) { + if (path.node.name === state.name) { + return path.scope.bindingIdentifierEquals(state.name, state.outerBinding); + } + return false; +} +const memberExpressionOptimisationVisitor = { + Scope(path, state) { + if (!path.scope.bindingIdentifierEquals(state.name, state.outerBinding)) { + path.skip(); + } + }, + Flow(path) { + if (path.isTypeCastExpression()) return; + path.skip(); + }, + Function(path, state) { + const oldNoOptimise = state.noOptimise; + state.noOptimise = true; + path.traverse(memberExpressionOptimisationVisitor, state); + state.noOptimise = oldNoOptimise; + path.skip(); + }, + ReferencedIdentifier(path, state) { + const { + node + } = path; + if (node.name === "arguments") { + state.deopted = true; + } + if (!referencesRest(path, state)) return; + if (state.noOptimise) { + state.deopted = true; + } else { + const { + parentPath + } = path; + if (parentPath.listKey === "params" && parentPath.key < state.offset) { + return; + } + if (parentPath.isMemberExpression({ + object: node + })) { + const grandparentPath = parentPath.parentPath; + const argsOptEligible = !state.deopted && !(grandparentPath.isAssignmentExpression() && parentPath.node === grandparentPath.node.left || grandparentPath.isLVal() || grandparentPath.isForXStatement() || grandparentPath.isUpdateExpression() || grandparentPath.isUnaryExpression({ + operator: "delete" + }) || (grandparentPath.isCallExpression() || grandparentPath.isNewExpression()) && parentPath.node === grandparentPath.node.callee); + if (argsOptEligible) { + if (parentPath.node.computed) { + if (parentPath.get("property").isBaseType("number")) { + state.candidates.push({ + cause: "indexGetter", + path + }); + return; + } + } else if (parentPath.node.property.name === "length") { + state.candidates.push({ + cause: "lengthGetter", + path + }); + return; + } + } + } + if (state.offset === 0 && parentPath.isSpreadElement()) { + const call = parentPath.parentPath; + if (call.isCallExpression() && call.node.arguments.length === 1) { + state.candidates.push({ + cause: "argSpread", + path + }); + return; + } + } + state.references.push(path); + } + }, + BindingIdentifier(path, state) { + if (referencesRest(path, state)) { + state.deopted = true; + } + } +}; +function getParamsCount(node) { + let count = node.params.length; + if (count > 0 && _core.types.isIdentifier(node.params[0], { + name: "this" + })) { + count -= 1; + } + return count; +} +function hasRest(node) { + const length = node.params.length; + return length > 0 && _core.types.isRestElement(node.params[length - 1]); +} +function optimiseIndexGetter(path, argsId, offset) { + const offsetLiteral = _core.types.numericLiteral(offset); + let index; + const parent = path.parent; + if (_core.types.isNumericLiteral(parent.property)) { + index = _core.types.numericLiteral(parent.property.value + offset); + } else if (offset === 0) { + index = parent.property; + } else { + index = _core.types.binaryExpression("+", parent.property, _core.types.cloneNode(offsetLiteral)); + } + const { + scope, + parentPath + } = path; + if (!scope.isPure(index)) { + const temp = scope.generateUidIdentifierBasedOnNode(index); + scope.push({ + id: temp, + kind: "var" + }); + parentPath.replaceWith(restIndexImpure({ + ARGUMENTS: argsId, + OFFSET: offsetLiteral, + INDEX: index, + REF: _core.types.cloneNode(temp) + })); + } else { + parentPath.replaceWith(restIndex({ + ARGUMENTS: argsId, + OFFSET: offsetLiteral, + INDEX: index + })); + const replacedParentPath = parentPath; + const offsetTestPath = replacedParentPath.get("test"); + const valRes = offsetTestPath.get("left").evaluate(); + if (valRes.confident) { + if (valRes.value === true) { + replacedParentPath.replaceWith(scope.buildUndefinedNode()); + } else { + offsetTestPath.replaceWith(offsetTestPath.get("right")); + } + } + } +} +function optimiseLengthGetter(path, argsId, offset) { + if (offset) { + path.parentPath.replaceWith(restLength({ + ARGUMENTS: argsId, + OFFSET: _core.types.numericLiteral(offset) + })); + } else { + path.replaceWith(argsId); + } +} +function convertFunctionRest(path) { + const { + node, + scope + } = path; + if (!hasRest(node)) return false; + const restPath = path.get(`params.${node.params.length - 1}.argument`); + if (!restPath.isIdentifier()) { + const shadowedParams = new Set(); + (0, _shadowUtils.collectShadowedParamsNames)(restPath, path.scope, shadowedParams); + let needsIIFE = shadowedParams.size > 0; + if (!needsIIFE) { + const state = { + needsOuterBinding: false, + scope + }; + restPath.traverse(_shadowUtils.iifeVisitor, state); + needsIIFE = state.needsOuterBinding; + } + if (needsIIFE) { + path.ensureBlock(); + path.set("body", _core.types.blockStatement([(0, _shadowUtils.buildScopeIIFE)(shadowedParams, path.node.body)])); + } + } + let rest = restPath.node; + node.params.pop(); + if (_core.types.isPattern(rest)) { + const pattern = rest; + rest = scope.generateUidIdentifier("ref"); + const declar = _core.types.variableDeclaration("let", [_core.types.variableDeclarator(pattern, rest)]); + path.ensureBlock(); + node.body.body.unshift(declar); + } else if (rest.name === "arguments") { + scope.rename(rest.name); + } + const argsId = _core.types.identifier("arguments"); + const paramsCount = getParamsCount(node); + const state = { + references: [], + offset: paramsCount, + argumentsNode: argsId, + outerBinding: scope.getBindingIdentifier(rest.name), + candidates: [], + name: rest.name, + deopted: false + }; + path.traverse(memberExpressionOptimisationVisitor, state); + if (!state.deopted && !state.references.length) { + for (const { + path, + cause + } of state.candidates) { + const clonedArgsId = _core.types.cloneNode(argsId); + switch (cause) { + case "indexGetter": + optimiseIndexGetter(path, clonedArgsId, state.offset); + break; + case "lengthGetter": + optimiseLengthGetter(path, clonedArgsId, state.offset); + break; + default: + path.replaceWith(clonedArgsId); + } + } + return true; + } + state.references.push(...state.candidates.map(({ + path + }) => path)); + const start = _core.types.numericLiteral(paramsCount); + const key = scope.generateUidIdentifier("key"); + const len = scope.generateUidIdentifier("len"); + let arrKey, arrLen; + if (paramsCount) { + arrKey = _core.types.binaryExpression("-", _core.types.cloneNode(key), _core.types.cloneNode(start)); + arrLen = _core.types.conditionalExpression(_core.types.binaryExpression(">", _core.types.cloneNode(len), _core.types.cloneNode(start)), _core.types.binaryExpression("-", _core.types.cloneNode(len), _core.types.cloneNode(start)), _core.types.numericLiteral(0)); + } else { + arrKey = _core.types.identifier(key.name); + arrLen = _core.types.identifier(len.name); + } + const loop = buildRest({ + ARGUMENTS: argsId, + ARRAY_KEY: arrKey, + ARRAY_LEN: arrLen, + START: start, + ARRAY: rest, + KEY: key, + LEN: len + }); + if (state.deopted) { + node.body.body.unshift(loop); + } else { + let target = path.getEarliestCommonAncestorFrom(state.references).getStatementParent(); + target.findParent(path => { + if (path.isLoop()) { + target = path; + } else { + return path.isFunction(); + } + }); + target.insertBefore(loop); + } + return true; +} + +//# sourceMappingURL=rest.js.map + + +/***/ }), + +/***/ 92399: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports.buildScopeIIFE = buildScopeIIFE; +exports.collectShadowedParamsNames = collectShadowedParamsNames; +exports.iifeVisitor = void 0; +var _core = __nccwpck_require__(85414); +const iifeVisitor = exports.iifeVisitor = { + "ReferencedIdentifier|BindingIdentifier"(path, state) { + const { + scope, + node + } = path; + const { + name + } = node; + if (name === "eval" || scope.getBinding(name) === state.scope.parent.getBinding(name) && state.scope.hasOwnBinding(name)) { + state.needsOuterBinding = true; + path.stop(); + } + }, + "TypeAnnotation|TSTypeAnnotation|TypeParameterDeclaration|TSTypeParameterDeclaration": path => path.skip() +}; +function collectShadowedParamsNames(param, functionScope, shadowedParams) { + for (const name of Object.keys(param.getBindingIdentifiers())) { + var _functionScope$bindin; + const constantViolations = (_functionScope$bindin = functionScope.bindings[name]) == null ? void 0 : _functionScope$bindin.constantViolations; + if (constantViolations) { + for (const redeclarator of constantViolations) { + const node = redeclarator.node; + switch (node.type) { + case "VariableDeclarator": + { + if (node.init === null) { + const declaration = redeclarator.parentPath; + if (!declaration.parentPath.isFor() || declaration.parentPath.get("body") === declaration) { + redeclarator.remove(); + break; + } + } + shadowedParams.add(name); + break; + } + case "FunctionDeclaration": + shadowedParams.add(name); + break; + } + } + } + } +} +function buildScopeIIFE(shadowedParams, body) { + const args = []; + const params = []; + for (const name of shadowedParams) { + args.push(_core.types.identifier(name)); + params.push(_core.types.identifier(name)); + } + return _core.types.returnStatement(_core.types.callExpression(_core.types.arrowFunctionExpression(params, body), args)); +} + +//# sourceMappingURL=shadow-utils.js.map + + +/***/ }), + +/***/ 30484: +/***/ ((__unused_webpack_module, exports) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports.declare = declare; +exports.declarePreset = void 0; +const apiPolyfills = { + assertVersion: api => range => { + throwVersionError(range, api.version); + } +}; +{ + Object.assign(apiPolyfills, { + targets: () => () => { + return {}; + }, + assumption: () => () => { + return undefined; + }, + addExternalDependency: () => () => {} + }); +} +function declare(builder) { + return (api, options, dirname) => { + let clonedApi; + for (const name of Object.keys(apiPolyfills)) { + if (api[name]) continue; + clonedApi != null ? clonedApi : clonedApi = copyApiObject(api); + clonedApi[name] = apiPolyfills[name](clonedApi); + } + return builder(clonedApi != null ? clonedApi : api, options || {}, dirname); + }; +} +const declarePreset = exports.declarePreset = declare; +function copyApiObject(api) { + let proto = null; + if (typeof api.version === "string" && /^7\./.test(api.version)) { + proto = Object.getPrototypeOf(api); + if (proto && (!hasOwnProperty.call(proto, "version") || !hasOwnProperty.call(proto, "transform") || !hasOwnProperty.call(proto, "template") || !hasOwnProperty.call(proto, "types"))) { + proto = null; + } + } + return Object.assign({}, proto, api); +} +function throwVersionError(range, version) { + if (typeof range === "number") { + if (!Number.isInteger(range)) { + throw new Error("Expected string or integer value."); + } + range = `^${range}.0.0-0`; + } + if (typeof range !== "string") { + throw new Error("Expected string or integer value."); + } + const limit = Error.stackTraceLimit; + if (typeof limit === "number" && limit < 25) { + Error.stackTraceLimit = 25; + } + let err; + if (version.slice(0, 2) === "7.") { + err = new Error(`Requires Babel "^7.0.0-beta.41", but was loaded with "${version}". ` + `You'll need to update your @babel/core version.`); + } else { + err = new Error(`Requires Babel "${range}", but was loaded with "${version}". ` + `If you are sure you have a compatible version of @babel/core, ` + `it is likely that something in your build process is loading the ` + `wrong version. Inspect the stack trace of this error to look for ` + `the first entry that doesn't mention "@babel/core" or "babel-core" ` + `to see what is calling Babel.`); + } + if (typeof limit === "number") { + Error.stackTraceLimit = limit; + } + throw Object.assign(err, { + code: "BABEL_VERSION_UNSUPPORTED", + version, + range + }); +} + +//# sourceMappingURL=index.js.map + + +/***/ }), + +/***/ 66631: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports["default"] = createTemplateBuilder; +var _options = __nccwpck_require__(97886); +var _string = __nccwpck_require__(1231); +var _literal = __nccwpck_require__(98083); +const NO_PLACEHOLDER = (0, _options.validate)({ + placeholderPattern: false +}); +function createTemplateBuilder(formatter, defaultOpts) { + const templateFnCache = new WeakMap(); + const templateAstCache = new WeakMap(); + const cachedOpts = defaultOpts || (0, _options.validate)(null); + return Object.assign((tpl, ...args) => { + if (typeof tpl === "string") { + if (args.length > 1) throw new Error("Unexpected extra params."); + return extendedTrace((0, _string.default)(formatter, tpl, (0, _options.merge)(cachedOpts, (0, _options.validate)(args[0])))); + } else if (Array.isArray(tpl)) { + let builder = templateFnCache.get(tpl); + if (!builder) { + builder = (0, _literal.default)(formatter, tpl, cachedOpts); + templateFnCache.set(tpl, builder); + } + return extendedTrace(builder(args)); + } else if (typeof tpl === "object" && tpl) { + if (args.length > 0) throw new Error("Unexpected extra params."); + return createTemplateBuilder(formatter, (0, _options.merge)(cachedOpts, (0, _options.validate)(tpl))); + } + throw new Error(`Unexpected template param ${typeof tpl}`); + }, { + ast: (tpl, ...args) => { + if (typeof tpl === "string") { + if (args.length > 1) throw new Error("Unexpected extra params."); + return (0, _string.default)(formatter, tpl, (0, _options.merge)((0, _options.merge)(cachedOpts, (0, _options.validate)(args[0])), NO_PLACEHOLDER))(); + } else if (Array.isArray(tpl)) { + let builder = templateAstCache.get(tpl); + if (!builder) { + builder = (0, _literal.default)(formatter, tpl, (0, _options.merge)(cachedOpts, NO_PLACEHOLDER)); + templateAstCache.set(tpl, builder); + } + return builder(args)(); + } + throw new Error(`Unexpected template param ${typeof tpl}`); + } + }); +} +function extendedTrace(fn) { + let rootStack = ""; + try { + throw new Error(); + } catch (error) { + if (error.stack) { + rootStack = error.stack.split("\n").slice(3).join("\n"); + } + } + return arg => { + try { + return fn(arg); + } catch (err) { + err.stack += `\n =============\n${rootStack}`; + throw err; + } + }; +} + +//# sourceMappingURL=builder.js.map + + +/***/ }), + +/***/ 53637: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports.statements = exports.statement = exports.smart = exports.program = exports.expression = void 0; +var _t = __nccwpck_require__(16535); +const { + assertExpressionStatement +} = _t; +function makeStatementFormatter(fn) { + return { + code: str => `/* @babel/template */;\n${str}`, + validate: () => {}, + unwrap: ast => { + return fn(ast.program.body.slice(1)); + } + }; +} +const smart = exports.smart = makeStatementFormatter(body => { + if (body.length > 1) { + return body; + } else { + return body[0]; + } +}); +const statements = exports.statements = makeStatementFormatter(body => body); +const statement = exports.statement = makeStatementFormatter(body => { + if (body.length === 0) { + throw new Error("Found nothing to return."); + } + if (body.length > 1) { + throw new Error("Found multiple statements but wanted one"); + } + return body[0]; +}); +const expression = exports.expression = { + code: str => `(\n${str}\n)`, + validate: ast => { + if (ast.program.body.length > 1) { + throw new Error("Found multiple statements but wanted one"); + } + if (expression.unwrap(ast).start === 0) { + throw new Error("Parse result included parens."); + } + }, + unwrap: ({ + program + }) => { + const [stmt] = program.body; + assertExpressionStatement(stmt); + return stmt.expression; + } +}; +const program = exports.program = { + code: str => str, + validate: () => {}, + unwrap: ast => ast.program +}; + +//# sourceMappingURL=formatters.js.map + + +/***/ }), + +/***/ 19648: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports.statements = exports.statement = exports.smart = exports.program = exports.expression = exports["default"] = void 0; +var formatters = __nccwpck_require__(53637); +var _builder = __nccwpck_require__(66631); +const smart = exports.smart = (0, _builder.default)(formatters.smart); +const statement = exports.statement = (0, _builder.default)(formatters.statement); +const statements = exports.statements = (0, _builder.default)(formatters.statements); +const expression = exports.expression = (0, _builder.default)(formatters.expression); +const program = exports.program = (0, _builder.default)(formatters.program); +var _default = exports["default"] = Object.assign(smart.bind(undefined), { + smart, + statement, + statements, + expression, + program, + ast: smart.ast +}); + +//# sourceMappingURL=index.js.map + + +/***/ }), + +/***/ 98083: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports["default"] = literalTemplate; +var _options = __nccwpck_require__(97886); +var _parse = __nccwpck_require__(73539); +var _populate = __nccwpck_require__(18624); +function literalTemplate(formatter, tpl, opts) { + const { + metadata, + names + } = buildLiteralData(formatter, tpl, opts); + return arg => { + const defaultReplacements = {}; + arg.forEach((replacement, i) => { + defaultReplacements[names[i]] = replacement; + }); + return arg => { + const replacements = (0, _options.normalizeReplacements)(arg); + if (replacements) { + Object.keys(replacements).forEach(key => { + if (hasOwnProperty.call(defaultReplacements, key)) { + throw new Error("Unexpected replacement overlap."); + } + }); + } + return formatter.unwrap((0, _populate.default)(metadata, replacements ? Object.assign(replacements, defaultReplacements) : defaultReplacements)); + }; + }; +} +function buildLiteralData(formatter, tpl, opts) { + let prefix = "BABEL_TPL$"; + const raw = tpl.join(""); + do { + prefix = "$$" + prefix; + } while (raw.includes(prefix)); + const { + names, + code + } = buildTemplateCode(tpl, prefix); + const metadata = (0, _parse.default)(formatter, formatter.code(code), { + parser: opts.parser, + placeholderWhitelist: new Set(names.concat(opts.placeholderWhitelist ? Array.from(opts.placeholderWhitelist) : [])), + placeholderPattern: opts.placeholderPattern, + preserveComments: opts.preserveComments, + syntacticPlaceholders: opts.syntacticPlaceholders + }); + return { + metadata, + names + }; +} +function buildTemplateCode(tpl, prefix) { + const names = []; + let code = tpl[0]; + for (let i = 1; i < tpl.length; i++) { + const value = `${prefix}${i - 1}`; + names.push(value); + code += value + tpl[i]; + } + return { + names, + code + }; +} + +//# sourceMappingURL=literal.js.map + + +/***/ }), + +/***/ 97886: +/***/ ((__unused_webpack_module, exports) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports.merge = merge; +exports.normalizeReplacements = normalizeReplacements; +exports.validate = validate; +const _excluded = ["placeholderWhitelist", "placeholderPattern", "preserveComments", "syntacticPlaceholders"]; +function _objectWithoutPropertiesLoose(r, e) { if (null == r) return {}; var t = {}; for (var n in r) if ({}.hasOwnProperty.call(r, n)) { if (-1 !== e.indexOf(n)) continue; t[n] = r[n]; } return t; } +function merge(a, b) { + const { + placeholderWhitelist = a.placeholderWhitelist, + placeholderPattern = a.placeholderPattern, + preserveComments = a.preserveComments, + syntacticPlaceholders = a.syntacticPlaceholders + } = b; + return { + parser: Object.assign({}, a.parser, b.parser), + placeholderWhitelist, + placeholderPattern, + preserveComments, + syntacticPlaceholders + }; +} +function validate(opts) { + if (opts != null && typeof opts !== "object") { + throw new Error("Unknown template options."); + } + const _ref = opts || {}, + { + placeholderWhitelist, + placeholderPattern, + preserveComments, + syntacticPlaceholders + } = _ref, + parser = _objectWithoutPropertiesLoose(_ref, _excluded); + if (placeholderWhitelist != null && !(placeholderWhitelist instanceof Set)) { + throw new Error("'.placeholderWhitelist' must be a Set, null, or undefined"); + } + if (placeholderPattern != null && !(placeholderPattern instanceof RegExp) && placeholderPattern !== false) { + throw new Error("'.placeholderPattern' must be a RegExp, false, null, or undefined"); + } + if (preserveComments != null && typeof preserveComments !== "boolean") { + throw new Error("'.preserveComments' must be a boolean, null, or undefined"); + } + if (syntacticPlaceholders != null && typeof syntacticPlaceholders !== "boolean") { + throw new Error("'.syntacticPlaceholders' must be a boolean, null, or undefined"); + } + if (syntacticPlaceholders === true && (placeholderWhitelist != null || placeholderPattern != null)) { + throw new Error("'.placeholderWhitelist' and '.placeholderPattern' aren't compatible" + " with '.syntacticPlaceholders: true'"); + } + return { + parser, + placeholderWhitelist: placeholderWhitelist || undefined, + placeholderPattern: placeholderPattern == null ? undefined : placeholderPattern, + preserveComments: preserveComments == null ? undefined : preserveComments, + syntacticPlaceholders: syntacticPlaceholders == null ? undefined : syntacticPlaceholders + }; +} +function normalizeReplacements(replacements) { + if (Array.isArray(replacements)) { + return replacements.reduce((acc, replacement, i) => { + acc["$" + i] = replacement; + return acc; + }, {}); + } else if (typeof replacements === "object" || replacements == null) { + return replacements || undefined; + } + throw new Error("Template replacements must be an array, object, null, or undefined"); +} + +//# sourceMappingURL=options.js.map + + +/***/ }), + +/***/ 73539: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports["default"] = parseAndBuildMetadata; +var _t = __nccwpck_require__(16535); +var _parser = __nccwpck_require__(5429); +var _codeFrame = __nccwpck_require__(90147); +const { + isCallExpression, + isExpressionStatement, + isFunction, + isIdentifier, + isJSXIdentifier, + isNewExpression, + isPlaceholder, + isStatement, + isStringLiteral, + removePropertiesDeep, + traverse +} = _t; +const PATTERN = /^[_$A-Z0-9]+$/; +function parseAndBuildMetadata(formatter, code, opts) { + const { + placeholderWhitelist, + placeholderPattern, + preserveComments, + syntacticPlaceholders + } = opts; + const ast = parseWithCodeFrame(code, opts.parser, syntacticPlaceholders); + removePropertiesDeep(ast, { + preserveComments + }); + formatter.validate(ast); + const state = { + syntactic: { + placeholders: [], + placeholderNames: new Set() + }, + legacy: { + placeholders: [], + placeholderNames: new Set() + }, + placeholderWhitelist, + placeholderPattern, + syntacticPlaceholders + }; + traverse(ast, placeholderVisitorHandler, state); + return Object.assign({ + ast + }, state.syntactic.placeholders.length ? state.syntactic : state.legacy); +} +function placeholderVisitorHandler(node, ancestors, state) { + var _state$placeholderWhi; + let name; + let hasSyntacticPlaceholders = state.syntactic.placeholders.length > 0; + if (isPlaceholder(node)) { + if (state.syntacticPlaceholders === false) { + throw new Error("%%foo%%-style placeholders can't be used when " + "'.syntacticPlaceholders' is false."); + } + name = node.name.name; + hasSyntacticPlaceholders = true; + } else if (hasSyntacticPlaceholders || state.syntacticPlaceholders) { + return; + } else if (isIdentifier(node) || isJSXIdentifier(node)) { + name = node.name; + } else if (isStringLiteral(node)) { + name = node.value; + } else { + return; + } + if (hasSyntacticPlaceholders && (state.placeholderPattern != null || state.placeholderWhitelist != null)) { + throw new Error("'.placeholderWhitelist' and '.placeholderPattern' aren't compatible" + " with '.syntacticPlaceholders: true'"); + } + if (!hasSyntacticPlaceholders && (state.placeholderPattern === false || !(state.placeholderPattern || PATTERN).test(name)) && !((_state$placeholderWhi = state.placeholderWhitelist) != null && _state$placeholderWhi.has(name))) { + return; + } + ancestors = ancestors.slice(); + const { + node: parent, + key + } = ancestors[ancestors.length - 1]; + let type; + if (isStringLiteral(node) || isPlaceholder(node, { + expectedNode: "StringLiteral" + })) { + type = "string"; + } else if (isNewExpression(parent) && key === "arguments" || isCallExpression(parent) && key === "arguments" || isFunction(parent) && key === "params") { + type = "param"; + } else if (isExpressionStatement(parent) && !isPlaceholder(node)) { + type = "statement"; + ancestors = ancestors.slice(0, -1); + } else if (isStatement(node) && isPlaceholder(node)) { + type = "statement"; + } else { + type = "other"; + } + const { + placeholders, + placeholderNames + } = !hasSyntacticPlaceholders ? state.legacy : state.syntactic; + placeholders.push({ + name, + type, + resolve: ast => resolveAncestors(ast, ancestors), + isDuplicate: placeholderNames.has(name) + }); + placeholderNames.add(name); +} +function resolveAncestors(ast, ancestors) { + let parent = ast; + for (let i = 0; i < ancestors.length - 1; i++) { + const { + key, + index + } = ancestors[i]; + if (index === undefined) { + parent = parent[key]; + } else { + parent = parent[key][index]; + } + } + const { + key, + index + } = ancestors[ancestors.length - 1]; + return { + parent, + key, + index + }; +} +function parseWithCodeFrame(code, parserOpts, syntacticPlaceholders) { + const plugins = (parserOpts.plugins || []).slice(); + if (syntacticPlaceholders !== false) { + plugins.push("placeholders"); + } + parserOpts = Object.assign({ + allowAwaitOutsideFunction: true, + allowReturnOutsideFunction: true, + allowNewTargetOutsideFunction: true, + allowSuperOutsideMethod: true, + allowYieldOutsideFunction: true, + sourceType: "module" + }, parserOpts, { + plugins + }); + try { + return (0, _parser.parse)(code, parserOpts); + } catch (err) { + const loc = err.loc; + if (loc) { + err.message += "\n" + (0, _codeFrame.codeFrameColumns)(code, { + start: loc + }); + err.code = "BABEL_TEMPLATE_PARSE_ERROR"; + } + throw err; + } +} + +//# sourceMappingURL=parse.js.map + + +/***/ }), + +/***/ 18624: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports["default"] = populatePlaceholders; +var _t = __nccwpck_require__(16535); +const { + blockStatement, + cloneNode, + emptyStatement, + expressionStatement, + identifier, + isStatement, + isStringLiteral, + stringLiteral, + validate +} = _t; +function populatePlaceholders(metadata, replacements) { + const ast = cloneNode(metadata.ast); + if (replacements) { + metadata.placeholders.forEach(placeholder => { + if (!hasOwnProperty.call(replacements, placeholder.name)) { + const placeholderName = placeholder.name; + throw new Error(`Error: No substitution given for "${placeholderName}". If this is not meant to be a + placeholder you may want to consider passing one of the following options to @babel/template: + - { placeholderPattern: false, placeholderWhitelist: new Set(['${placeholderName}'])} + - { placeholderPattern: /^${placeholderName}$/ }`); + } + }); + Object.keys(replacements).forEach(key => { + if (!metadata.placeholderNames.has(key)) { + throw new Error(`Unknown substitution "${key}" given`); + } + }); + } + metadata.placeholders.slice().reverse().forEach(placeholder => { + try { + var _ref; + applyReplacement(placeholder, ast, (_ref = replacements && replacements[placeholder.name]) != null ? _ref : null); + } catch (e) { + e.message = `@babel/template placeholder "${placeholder.name}": ${e.message}`; + throw e; + } + }); + return ast; +} +function applyReplacement(placeholder, ast, replacement) { + if (placeholder.isDuplicate) { + if (Array.isArray(replacement)) { + replacement = replacement.map(node => cloneNode(node)); + } else if (typeof replacement === "object") { + replacement = cloneNode(replacement); + } + } + const { + parent, + key, + index + } = placeholder.resolve(ast); + if (placeholder.type === "string") { + if (typeof replacement === "string") { + replacement = stringLiteral(replacement); + } + if (!replacement || !isStringLiteral(replacement)) { + throw new Error("Expected string substitution"); + } + } else if (placeholder.type === "statement") { + if (index === undefined) { + if (!replacement) { + replacement = emptyStatement(); + } else if (Array.isArray(replacement)) { + replacement = blockStatement(replacement); + } else if (typeof replacement === "string") { + replacement = expressionStatement(identifier(replacement)); + } else if (!isStatement(replacement)) { + replacement = expressionStatement(replacement); + } + } else { + if (replacement && !Array.isArray(replacement)) { + if (typeof replacement === "string") { + replacement = identifier(replacement); + } + if (!isStatement(replacement)) { + replacement = expressionStatement(replacement); + } + } + } + } else if (placeholder.type === "param") { + if (typeof replacement === "string") { + replacement = identifier(replacement); + } + if (index === undefined) throw new Error("Assertion failure."); + } else { + if (typeof replacement === "string") { + replacement = identifier(replacement); + } + if (Array.isArray(replacement)) { + throw new Error("Cannot replace single expression with an array."); + } + } + function set(parent, key, value) { + const node = parent[key]; + parent[key] = value; + if (node.type === "Identifier" || node.type === "Placeholder") { + if (node.typeAnnotation) { + value.typeAnnotation = node.typeAnnotation; + } + if (node.optional) { + value.optional = node.optional; + } + if (node.decorators) { + value.decorators = node.decorators; + } + } + } + if (index === undefined) { + validate(parent, key, replacement); + set(parent, key, replacement); + } else { + const items = parent[key].slice(); + if (placeholder.type === "statement" || placeholder.type === "param") { + if (replacement == null) { + items.splice(index, 1); + } else if (Array.isArray(replacement)) { + items.splice(index, 1, ...replacement); + } else { + set(items, index, replacement); + } + } else { + set(items, index, replacement); + } + validate(parent, key, items); + parent[key] = items; + } +} + +//# sourceMappingURL=populate.js.map + + +/***/ }), + +/***/ 1231: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports["default"] = stringTemplate; +var _options = __nccwpck_require__(97886); +var _parse = __nccwpck_require__(73539); +var _populate = __nccwpck_require__(18624); +function stringTemplate(formatter, code, opts) { + code = formatter.code(code); + let metadata; + return arg => { + const replacements = (0, _options.normalizeReplacements)(arg); + if (!metadata) metadata = (0, _parse.default)(formatter, code, opts); + return formatter.unwrap((0, _populate.default)(metadata, replacements)); + }; +} + +//# sourceMappingURL=string.js.map + + +/***/ }), + +/***/ 6730: +/***/ ((__unused_webpack_module, exports) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports.clear = clear; +exports.clearPath = clearPath; +exports.clearScope = clearScope; +exports.getCachedPaths = getCachedPaths; +exports.getOrCreateCachedPaths = getOrCreateCachedPaths; +exports.scope = exports.path = void 0; +let pathsCache = exports.path = new WeakMap(); +let scope = exports.scope = new WeakMap(); +function clear() { + clearPath(); + clearScope(); +} +function clearPath() { + exports.path = pathsCache = new WeakMap(); +} +function clearScope() { + exports.scope = scope = new WeakMap(); +} +function getCachedPaths(path) { + const { + parent, + parentPath + } = path; + return pathsCache.get(parent); +} +function getOrCreateCachedPaths(node, parentPath) { + ; + let paths = pathsCache.get(node); + if (!paths) pathsCache.set(node, paths = new Map()); + return paths; +} + +//# sourceMappingURL=cache.js.map + + +/***/ }), + +/***/ 49767: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports["default"] = void 0; +var _index = __nccwpck_require__(91806); +var _t = __nccwpck_require__(16535); +var _context = __nccwpck_require__(74105); +const { + VISITOR_KEYS +} = _t; +class TraversalContext { + constructor(scope, opts, state, parentPath) { + this.queue = null; + this.priorityQueue = null; + this.parentPath = parentPath; + this.scope = scope; + this.state = state; + this.opts = opts; + } + shouldVisit(node) { + const opts = this.opts; + if (opts.enter || opts.exit) return true; + if (opts[node.type]) return true; + const keys = VISITOR_KEYS[node.type]; + if (!(keys != null && keys.length)) return false; + for (const key of keys) { + if (node[key]) { + return true; + } + } + return false; + } + create(node, container, key, listKey) { + return _index.default.get({ + parentPath: this.parentPath, + parent: node, + container, + key: key, + listKey + }); + } + maybeQueue(path, notPriority) { + if (this.queue) { + if (notPriority) { + this.queue.push(path); + } else { + this.priorityQueue.push(path); + } + } + } + visitMultiple(container, parent, listKey) { + if (container.length === 0) return false; + const queue = []; + for (let key = 0; key < container.length; key++) { + const node = container[key]; + if (node && this.shouldVisit(node)) { + queue.push(this.create(parent, container, key, listKey)); + } + } + return this.visitQueue(queue); + } + visitSingle(node, key) { + if (this.shouldVisit(node[key])) { + return this.visitQueue([this.create(node, node, key)]); + } else { + return false; + } + } + visitQueue(queue) { + this.queue = queue; + this.priorityQueue = []; + const visited = new WeakSet(); + let stop = false; + let visitIndex = 0; + for (; visitIndex < queue.length;) { + const path = queue[visitIndex]; + visitIndex++; + _context.resync.call(path); + if (path.contexts.length === 0 || path.contexts[path.contexts.length - 1] !== this) { + _context.pushContext.call(path, this); + } + if (path.key === null) continue; + const { + node + } = path; + if (visited.has(node)) continue; + if (node) visited.add(node); + if (path.visit()) { + stop = true; + break; + } + if (this.priorityQueue.length) { + stop = this.visitQueue(this.priorityQueue); + this.priorityQueue = []; + this.queue = queue; + if (stop) break; + } + } + for (let i = 0; i < visitIndex; i++) { + _context.popContext.call(queue[i]); + } + this.queue = null; + return stop; + } + visit(node, key) { + const nodes = node[key]; + if (!nodes) return false; + if (Array.isArray(nodes)) { + return this.visitMultiple(nodes, node, key); + } else { + return this.visitSingle(node, key); + } + } +} +exports["default"] = TraversalContext; + +//# sourceMappingURL=context.js.map + + +/***/ }), + +/***/ 18327: +/***/ ((__unused_webpack_module, exports) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports["default"] = void 0; +class Hub { + getCode() {} + getScope() {} + addHelper() { + throw new Error("Helpers are not supported by the default hub."); + } + buildError(node, msg, Error = TypeError) { + return new Error(msg); + } +} +exports["default"] = Hub; + +//# sourceMappingURL=hub.js.map + + +/***/ }), + +/***/ 50148: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +Object.defineProperty(exports, "Hub", ({ + enumerable: true, + get: function () { + return _hub.default; + } +})); +Object.defineProperty(exports, "NodePath", ({ + enumerable: true, + get: function () { + return _index.default; + } +})); +Object.defineProperty(exports, "Scope", ({ + enumerable: true, + get: function () { + return _index2.default; + } +})); +exports.visitors = exports["default"] = void 0; +__nccwpck_require__(74105); +var visitors = __nccwpck_require__(38133); +exports.visitors = visitors; +var _t = __nccwpck_require__(16535); +var cache = __nccwpck_require__(6730); +var _traverseNode = __nccwpck_require__(95469); +var _index = __nccwpck_require__(91806); +var _index2 = __nccwpck_require__(18171); +var _hub = __nccwpck_require__(18327); +const { + VISITOR_KEYS, + removeProperties, + traverseFast +} = _t; +function traverse(parent, opts = {}, scope, state, parentPath, visitSelf) { + if (!parent) return; + if (!opts.noScope && !scope) { + if (parent.type !== "Program" && parent.type !== "File") { + throw new Error("You must pass a scope and parentPath unless traversing a Program/File. " + `Instead of that you tried to traverse a ${parent.type} node without ` + "passing scope and parentPath."); + } + } + if (!parentPath && visitSelf) { + throw new Error("visitSelf can only be used when providing a NodePath."); + } + if (!VISITOR_KEYS[parent.type]) { + return; + } + visitors.explode(opts); + (0, _traverseNode.traverseNode)(parent, opts, scope, state, parentPath, null, visitSelf); +} +var _default = exports["default"] = traverse; +traverse.visitors = visitors; +traverse.verify = visitors.verify; +traverse.explode = visitors.explode; +traverse.cheap = function (node, enter) { + traverseFast(node, enter); + return; +}; +traverse.node = function (node, opts, scope, state, path, skipKeys) { + (0, _traverseNode.traverseNode)(node, opts, scope, state, path, skipKeys); +}; +traverse.clearNode = function (node, opts) { + removeProperties(node, opts); +}; +traverse.removeProperties = function (tree, opts) { + traverseFast(tree, traverse.clearNode, opts); + return tree; +}; +traverse.hasType = function (tree, type, denylistTypes) { + if (denylistTypes != null && denylistTypes.includes(tree.type)) return false; + if (tree.type === type) return true; + return traverseFast(tree, function (node) { + if (denylistTypes != null && denylistTypes.includes(node.type)) { + return traverseFast.skip; + } + if (node.type === type) { + return traverseFast.stop; + } + }); +}; +traverse.cache = cache; + +//# sourceMappingURL=index.js.map + + +/***/ }), + +/***/ 63405: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports.find = find; +exports.findParent = findParent; +exports.getAncestry = getAncestry; +exports.getDeepestCommonAncestorFrom = getDeepestCommonAncestorFrom; +exports.getEarliestCommonAncestorFrom = getEarliestCommonAncestorFrom; +exports.getFunctionParent = getFunctionParent; +exports.getStatementParent = getStatementParent; +exports.inType = inType; +exports.isAncestor = isAncestor; +exports.isDescendant = isDescendant; +var _t = __nccwpck_require__(16535); +const { + VISITOR_KEYS +} = _t; +function findParent(callback) { + let path = this; + while (path = path.parentPath) { + if (callback(path)) return path; + } + return null; +} +function find(callback) { + let path = this; + do { + if (callback(path)) return path; + } while (path = path.parentPath); + return null; +} +function getFunctionParent() { + return this.findParent(p => p.isFunction()); +} +function getStatementParent() { + let path = this; + do { + if (!path.parentPath || Array.isArray(path.container) && path.isStatement()) { + break; + } else { + path = path.parentPath; + } + } while (path); + if (path && (path.isProgram() || path.isFile())) { + throw new Error("File/Program node, we can't possibly find a statement parent to this"); + } + return path; +} +function getEarliestCommonAncestorFrom(paths) { + return this.getDeepestCommonAncestorFrom(paths, function (deepest, i, ancestries) { + let earliest; + const keys = VISITOR_KEYS[deepest.type]; + for (const ancestry of ancestries) { + const path = ancestry[i + 1]; + if (!earliest) { + earliest = path; + continue; + } + if (path.listKey && earliest.listKey === path.listKey) { + if (path.key < earliest.key) { + earliest = path; + continue; + } + } + const earliestKeyIndex = keys.indexOf(earliest.parentKey); + const currentKeyIndex = keys.indexOf(path.parentKey); + if (earliestKeyIndex > currentKeyIndex) { + earliest = path; + } + } + return earliest; + }); +} +function getDeepestCommonAncestorFrom(paths, filter) { + if (!paths.length) { + return this; + } + if (paths.length === 1) { + return paths[0]; + } + let minDepth = Infinity; + let lastCommonIndex, lastCommon; + const ancestries = paths.map(path => { + const ancestry = []; + do { + ancestry.unshift(path); + } while ((path = path.parentPath) && path !== this); + if (ancestry.length < minDepth) { + minDepth = ancestry.length; + } + return ancestry; + }); + const first = ancestries[0]; + depthLoop: for (let i = 0; i < minDepth; i++) { + const shouldMatch = first[i]; + for (const ancestry of ancestries) { + if (ancestry[i] !== shouldMatch) { + break depthLoop; + } + } + lastCommonIndex = i; + lastCommon = shouldMatch; + } + if (lastCommon) { + if (filter) { + return filter(lastCommon, lastCommonIndex, ancestries); + } else { + return lastCommon; + } + } else { + throw new Error("Couldn't find intersection"); + } +} +function getAncestry() { + let path = this; + const paths = []; + do { + paths.push(path); + } while (path = path.parentPath); + return paths; +} +function isAncestor(maybeDescendant) { + return maybeDescendant.isDescendant(this); +} +function isDescendant(maybeAncestor) { + return !!this.findParent(parent => parent === maybeAncestor); +} +function inType(...candidateTypes) { + let path = this; + while (path) { + if (candidateTypes.includes(path.node.type)) return true; + path = path.parentPath; + } + return false; +} + +//# sourceMappingURL=ancestry.js.map + + +/***/ }), + +/***/ 17794: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports.addComment = addComment; +exports.addComments = addComments; +exports.shareCommentsWithSiblings = shareCommentsWithSiblings; +var _t = __nccwpck_require__(16535); +const { + addComment: _addComment, + addComments: _addComments +} = _t; +function shareCommentsWithSiblings() { + if (typeof this.key === "string") return; + const node = this.node; + if (!node) return; + const trailing = node.trailingComments; + const leading = node.leadingComments; + if (!trailing && !leading) return; + const prev = this.getSibling(this.key - 1); + const next = this.getSibling(this.key + 1); + const hasPrev = Boolean(prev.node); + const hasNext = Boolean(next.node); + if (hasPrev) { + if (leading) { + prev.addComments("trailing", removeIfExisting(leading, prev.node.trailingComments)); + } + if (trailing && !hasNext) prev.addComments("trailing", trailing); + } + if (hasNext) { + if (trailing) { + next.addComments("leading", removeIfExisting(trailing, next.node.leadingComments)); + } + if (leading && !hasPrev) next.addComments("leading", leading); + } +} +function removeIfExisting(list, toRemove) { + if (!(toRemove != null && toRemove.length)) return list; + const set = new Set(toRemove); + return list.filter(el => { + return !set.has(el); + }); +} +function addComment(type, content, line) { + _addComment(this.node, type, content, line); +} +function addComments(type, comments) { + _addComments(this.node, type, comments); +} + +//# sourceMappingURL=comments.js.map + + +/***/ }), + +/***/ 74105: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports._call = _call; +exports._getQueueContexts = _getQueueContexts; +exports._resyncKey = _resyncKey; +exports._resyncList = _resyncList; +exports._resyncParent = _resyncParent; +exports._resyncRemoved = _resyncRemoved; +exports.call = call; +exports.isDenylisted = isDenylisted; +exports.popContext = popContext; +exports.pushContext = pushContext; +exports.requeue = requeue; +exports.requeueComputedKeyAndDecorators = requeueComputedKeyAndDecorators; +exports.resync = resync; +exports.setContext = setContext; +exports.setKey = setKey; +exports.setScope = setScope; +exports.setup = setup; +exports.skip = skip; +exports.skipKey = skipKey; +exports.stop = stop; +exports.visit = visit; +var _traverseNode = __nccwpck_require__(95469); +var _index = __nccwpck_require__(91806); +var _removal = __nccwpck_require__(23562); +var t = __nccwpck_require__(16535); +function call(key) { + const opts = this.opts; + this.debug(key); + if (this.node) { + if (_call.call(this, opts[key])) return true; + } + if (this.node) { + var _opts$this$node$type; + return _call.call(this, (_opts$this$node$type = opts[this.node.type]) == null ? void 0 : _opts$this$node$type[key]); + } + return false; +} +function _call(fns) { + if (!fns) return false; + for (const fn of fns) { + if (!fn) continue; + const node = this.node; + if (!node) return true; + const ret = fn.call(this.state, this, this.state); + if (ret && typeof ret === "object" && typeof ret.then === "function") { + throw new Error(`You appear to be using a plugin with an async traversal visitor, ` + `which your current version of Babel does not support. ` + `If you're using a published plugin, you may need to upgrade ` + `your @babel/core version.`); + } + if (ret) { + throw new Error(`Unexpected return value from visitor method ${fn}`); + } + if (this.node !== node) return true; + if (this._traverseFlags > 0) return true; + } + return false; +} +function isDenylisted() { + var _this$opts$denylist; + const denylist = (_this$opts$denylist = this.opts.denylist) != null ? _this$opts$denylist : this.opts.blacklist; + return denylist == null ? void 0 : denylist.includes(this.node.type); +} +{ + exports.isBlacklisted = isDenylisted; +} +function restoreContext(path, context) { + if (path.context !== context) { + path.context = context; + path.state = context.state; + path.opts = context.opts; + } +} +function visit() { + var _this$opts$shouldSkip, _this$opts; + if (!this.node) { + return false; + } + if (this.isDenylisted()) { + return false; + } + if ((_this$opts$shouldSkip = (_this$opts = this.opts).shouldSkip) != null && _this$opts$shouldSkip.call(_this$opts, this)) { + return false; + } + const currentContext = this.context; + if (this.shouldSkip || call.call(this, "enter")) { + this.debug("Skip..."); + return this.shouldStop; + } + restoreContext(this, currentContext); + this.debug("Recursing into..."); + this.shouldStop = (0, _traverseNode.traverseNode)(this.node, this.opts, this.scope, this.state, this, this.skipKeys); + restoreContext(this, currentContext); + call.call(this, "exit"); + return this.shouldStop; +} +function skip() { + this.shouldSkip = true; +} +function skipKey(key) { + if (this.skipKeys == null) { + this.skipKeys = {}; + } + this.skipKeys[key] = true; +} +function stop() { + this._traverseFlags |= _index.SHOULD_SKIP | _index.SHOULD_STOP; +} +function setScope() { + var _this$opts2, _this$scope; + if ((_this$opts2 = this.opts) != null && _this$opts2.noScope) return; + let path = this.parentPath; + if ((this.key === "key" || this.listKey === "decorators") && path.isMethod() || this.key === "discriminant" && path.isSwitchStatement()) { + path = path.parentPath; + } + let target; + while (path && !target) { + var _path$opts; + if ((_path$opts = path.opts) != null && _path$opts.noScope) return; + target = path.scope; + path = path.parentPath; + } + this.scope = this.getScope(target); + (_this$scope = this.scope) == null || _this$scope.init(); +} +function setContext(context) { + if (this.skipKeys != null) { + this.skipKeys = {}; + } + this._traverseFlags = 0; + if (context) { + this.context = context; + this.state = context.state; + this.opts = context.opts; + } + setScope.call(this); + return this; +} +function resync() { + if (this.removed) return; + _resyncParent.call(this); + _resyncList.call(this); + _resyncKey.call(this); +} +function _resyncParent() { + if (this.parentPath) { + this.parent = this.parentPath.node; + } +} +function _resyncKey() { + if (!this.container) return; + if (this.node === this.container[this.key]) { + return; + } + if (Array.isArray(this.container)) { + for (let i = 0; i < this.container.length; i++) { + if (this.container[i] === this.node) { + setKey.call(this, i); + return; + } + } + } else { + for (const key of Object.keys(this.container)) { + if (this.container[key] === this.node) { + setKey.call(this, key); + return; + } + } + } + this.key = null; +} +function _resyncList() { + if (!this.parent || !this.inList) return; + const newContainer = this.parent[this.listKey]; + if (this.container === newContainer) return; + this.container = newContainer || null; +} +function _resyncRemoved() { + if (this.key == null || !this.container || this.container[this.key] !== this.node) { + _removal._markRemoved.call(this); + } +} +function popContext() { + this.contexts.pop(); + if (this.contexts.length > 0) { + this.setContext(this.contexts[this.contexts.length - 1]); + } else { + this.setContext(undefined); + } +} +function pushContext(context) { + this.contexts.push(context); + this.setContext(context); +} +function setup(parentPath, container, listKey, key) { + this.listKey = listKey; + this.container = container; + this.parentPath = parentPath || this.parentPath; + setKey.call(this, key); +} +function setKey(key) { + var _this$node; + this.key = key; + this.node = this.container[this.key]; + this.type = (_this$node = this.node) == null ? void 0 : _this$node.type; +} +function requeue(pathToQueue = this) { + if (pathToQueue.removed) return; + ; + const contexts = this.contexts; + for (const context of contexts) { + context.maybeQueue(pathToQueue); + } +} +function requeueComputedKeyAndDecorators() { + const { + context, + node + } = this; + if (!t.isPrivate(node) && node.computed) { + context.maybeQueue(this.get("key")); + } + if (node.decorators) { + for (const decorator of this.get("decorators")) { + context.maybeQueue(decorator); + } + } +} +function _getQueueContexts() { + let path = this; + let contexts = this.contexts; + while (!contexts.length) { + path = path.parentPath; + if (!path) break; + contexts = path.contexts; + } + return contexts; +} + +//# sourceMappingURL=context.js.map + + +/***/ }), + +/***/ 26900: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports.arrowFunctionToExpression = arrowFunctionToExpression; +exports.ensureBlock = ensureBlock; +exports.ensureFunctionName = ensureFunctionName; +exports.splitExportDeclaration = splitExportDeclaration; +exports.toComputedKey = toComputedKey; +exports.unwrapFunctionEnvironment = unwrapFunctionEnvironment; +var _t = __nccwpck_require__(16535); +var _template = __nccwpck_require__(19648); +var _visitors = __nccwpck_require__(38133); +var _context = __nccwpck_require__(74105); +const { + arrowFunctionExpression, + assignmentExpression, + binaryExpression, + blockStatement, + callExpression, + conditionalExpression, + expressionStatement, + identifier, + isIdentifier, + jsxIdentifier, + logicalExpression, + LOGICAL_OPERATORS, + memberExpression, + metaProperty, + numericLiteral, + objectExpression, + restElement, + returnStatement, + sequenceExpression, + spreadElement, + stringLiteral, + super: _super, + thisExpression, + toExpression, + unaryExpression, + toBindingIdentifierName, + isFunction, + isAssignmentPattern, + isRestElement, + getFunctionName, + cloneNode, + variableDeclaration, + variableDeclarator, + exportNamedDeclaration, + exportSpecifier, + inherits +} = _t; +function toComputedKey() { + let key; + if (this.isMemberExpression()) { + key = this.node.property; + } else if (this.isProperty() || this.isMethod()) { + key = this.node.key; + } else { + throw new ReferenceError("todo"); + } + if (!this.node.computed) { + if (isIdentifier(key)) key = stringLiteral(key.name); + } + return key; +} +function ensureBlock() { + const body = this.get("body"); + const bodyNode = body.node; + if (Array.isArray(body)) { + throw new Error("Can't convert array path to a block statement"); + } + if (!bodyNode) { + throw new Error("Can't convert node without a body"); + } + if (body.isBlockStatement()) { + return bodyNode; + } + const statements = []; + let stringPath = "body"; + let key; + let listKey; + if (body.isStatement()) { + listKey = "body"; + key = 0; + statements.push(body.node); + } else { + stringPath += ".body.0"; + if (this.isFunction()) { + key = "argument"; + statements.push(returnStatement(body.node)); + } else { + key = "expression"; + statements.push(expressionStatement(body.node)); + } + } + this.node.body = blockStatement(statements); + const parentPath = this.get(stringPath); + _context.setup.call(body, parentPath, listKey ? parentPath.node[listKey] : parentPath.node, listKey, key); + return this.node; +} +{ + exports.arrowFunctionToShadowed = function () { + if (!this.isArrowFunctionExpression()) return; + this.arrowFunctionToExpression(); + }; +} +function unwrapFunctionEnvironment() { + if (!this.isArrowFunctionExpression() && !this.isFunctionExpression() && !this.isFunctionDeclaration()) { + throw this.buildCodeFrameError("Can only unwrap the environment of a function."); + } + hoistFunctionEnvironment(this); +} +function setType(path, type) { + path.node.type = type; +} +function arrowFunctionToExpression({ + allowInsertArrow = true, + allowInsertArrowWithRest = allowInsertArrow, + noNewArrows = !(_arguments$ => (_arguments$ = arguments[0]) == null ? void 0 : _arguments$.specCompliant)() +} = {}) { + if (!this.isArrowFunctionExpression()) { + throw this.buildCodeFrameError("Cannot convert non-arrow function to a function expression."); + } + let self = this; + if (!noNewArrows) { + var _self$ensureFunctionN; + self = (_self$ensureFunctionN = self.ensureFunctionName(false)) != null ? _self$ensureFunctionN : self; + } + const { + thisBinding, + fnPath: fn + } = hoistFunctionEnvironment(self, noNewArrows, allowInsertArrow, allowInsertArrowWithRest); + fn.ensureBlock(); + setType(fn, "FunctionExpression"); + if (!noNewArrows) { + const checkBinding = thisBinding ? null : fn.scope.generateUidIdentifier("arrowCheckId"); + if (checkBinding) { + fn.parentPath.scope.push({ + id: checkBinding, + init: objectExpression([]) + }); + } + fn.get("body").unshiftContainer("body", expressionStatement(callExpression(this.hub.addHelper("newArrowCheck"), [thisExpression(), checkBinding ? identifier(checkBinding.name) : identifier(thisBinding)]))); + fn.replaceWith(callExpression(memberExpression(fn.node, identifier("bind")), [checkBinding ? identifier(checkBinding.name) : thisExpression()])); + return fn.get("callee.object"); + } + return fn; +} +const getSuperCallsVisitor = (0, _visitors.environmentVisitor)({ + CallExpression(child, { + allSuperCalls + }) { + if (!child.get("callee").isSuper()) return; + allSuperCalls.push(child); + } +}); +function hoistFunctionEnvironment(fnPath, noNewArrows = true, allowInsertArrow = true, allowInsertArrowWithRest = true) { + let arrowParent; + let thisEnvFn = fnPath.findParent(p => { + if (p.isArrowFunctionExpression()) { + arrowParent != null ? arrowParent : arrowParent = p; + return false; + } + return p.isFunction() || p.isProgram() || p.isClassProperty({ + static: false + }) || p.isClassPrivateProperty({ + static: false + }); + }); + const inConstructor = thisEnvFn.isClassMethod({ + kind: "constructor" + }); + if (thisEnvFn.isClassProperty() || thisEnvFn.isClassPrivateProperty()) { + if (arrowParent) { + thisEnvFn = arrowParent; + } else if (allowInsertArrow) { + fnPath.replaceWith(callExpression(arrowFunctionExpression([], toExpression(fnPath.node)), [])); + thisEnvFn = fnPath.get("callee"); + fnPath = thisEnvFn.get("body"); + } else { + throw fnPath.buildCodeFrameError("Unable to transform arrow inside class property"); + } + } + const { + thisPaths, + argumentsPaths, + newTargetPaths, + superProps, + superCalls + } = getScopeInformation(fnPath); + if (inConstructor && superCalls.length > 0) { + if (!allowInsertArrow) { + throw superCalls[0].buildCodeFrameError("When using '@babel/plugin-transform-arrow-functions', " + "it's not possible to compile `super()` in an arrow function without compiling classes.\n" + "Please add '@babel/plugin-transform-classes' to your Babel configuration."); + } + if (!allowInsertArrowWithRest) { + throw superCalls[0].buildCodeFrameError("When using '@babel/plugin-transform-parameters', " + "it's not possible to compile `super()` in an arrow function with default or rest parameters without compiling classes.\n" + "Please add '@babel/plugin-transform-classes' to your Babel configuration."); + } + const allSuperCalls = []; + thisEnvFn.traverse(getSuperCallsVisitor, { + allSuperCalls + }); + const superBinding = getSuperBinding(thisEnvFn); + allSuperCalls.forEach(superCall => { + const callee = identifier(superBinding); + callee.loc = superCall.node.callee.loc; + superCall.get("callee").replaceWith(callee); + }); + } + if (argumentsPaths.length > 0) { + const argumentsBinding = getBinding(thisEnvFn, "arguments", () => { + const args = () => identifier("arguments"); + if (thisEnvFn.scope.path.isProgram()) { + return conditionalExpression(binaryExpression("===", unaryExpression("typeof", args()), stringLiteral("undefined")), thisEnvFn.scope.buildUndefinedNode(), args()); + } else { + return args(); + } + }); + argumentsPaths.forEach(argumentsChild => { + const argsRef = identifier(argumentsBinding); + argsRef.loc = argumentsChild.node.loc; + argumentsChild.replaceWith(argsRef); + }); + } + if (newTargetPaths.length > 0) { + const newTargetBinding = getBinding(thisEnvFn, "newtarget", () => metaProperty(identifier("new"), identifier("target"))); + newTargetPaths.forEach(targetChild => { + const targetRef = identifier(newTargetBinding); + targetRef.loc = targetChild.node.loc; + targetChild.replaceWith(targetRef); + }); + } + if (superProps.length > 0) { + if (!allowInsertArrow) { + throw superProps[0].buildCodeFrameError("When using '@babel/plugin-transform-arrow-functions', " + "it's not possible to compile `super.prop` in an arrow function without compiling classes.\n" + "Please add '@babel/plugin-transform-classes' to your Babel configuration."); + } + const flatSuperProps = superProps.reduce((acc, superProp) => acc.concat(standardizeSuperProperty(superProp)), []); + flatSuperProps.forEach(superProp => { + const key = superProp.node.computed ? "" : superProp.get("property").node.name; + const superParentPath = superProp.parentPath; + const isAssignment = superParentPath.isAssignmentExpression({ + left: superProp.node + }); + const isCall = superParentPath.isCallExpression({ + callee: superProp.node + }); + const isTaggedTemplate = superParentPath.isTaggedTemplateExpression({ + tag: superProp.node + }); + const superBinding = getSuperPropBinding(thisEnvFn, isAssignment, key); + const args = []; + if (superProp.node.computed) { + args.push(superProp.get("property").node); + } + if (isAssignment) { + const value = superParentPath.node.right; + args.push(value); + } + const call = callExpression(identifier(superBinding), args); + if (isCall) { + superParentPath.unshiftContainer("arguments", thisExpression()); + superProp.replaceWith(memberExpression(call, identifier("call"))); + thisPaths.push(superParentPath.get("arguments.0")); + } else if (isAssignment) { + superParentPath.replaceWith(call); + } else if (isTaggedTemplate) { + superProp.replaceWith(callExpression(memberExpression(call, identifier("bind"), false), [thisExpression()])); + thisPaths.push(superProp.get("arguments.0")); + } else { + superProp.replaceWith(call); + } + }); + } + let thisBinding; + if (thisPaths.length > 0 || !noNewArrows) { + thisBinding = getThisBinding(thisEnvFn, inConstructor); + if (noNewArrows || inConstructor && hasSuperClass(thisEnvFn)) { + thisPaths.forEach(thisChild => { + const thisRef = thisChild.isJSX() ? jsxIdentifier(thisBinding) : identifier(thisBinding); + thisRef.loc = thisChild.node.loc; + thisChild.replaceWith(thisRef); + }); + if (!noNewArrows) thisBinding = null; + } + } + return { + thisBinding, + fnPath + }; +} +function isLogicalOp(op) { + return LOGICAL_OPERATORS.includes(op); +} +function standardizeSuperProperty(superProp) { + if (superProp.parentPath.isAssignmentExpression() && superProp.parentPath.node.operator !== "=") { + const assignmentPath = superProp.parentPath; + const op = assignmentPath.node.operator.slice(0, -1); + const value = assignmentPath.node.right; + const isLogicalAssignment = isLogicalOp(op); + if (superProp.node.computed) { + const tmp = superProp.scope.generateDeclaredUidIdentifier("tmp"); + const object = superProp.node.object; + const property = superProp.node.property; + assignmentPath.get("left").replaceWith(memberExpression(object, assignmentExpression("=", tmp, property), true)); + assignmentPath.get("right").replaceWith(rightExpression(isLogicalAssignment ? "=" : op, memberExpression(object, identifier(tmp.name), true), value)); + } else { + const object = superProp.node.object; + const property = superProp.node.property; + assignmentPath.get("left").replaceWith(memberExpression(object, property)); + assignmentPath.get("right").replaceWith(rightExpression(isLogicalAssignment ? "=" : op, memberExpression(object, identifier(property.name)), value)); + } + if (isLogicalAssignment) { + assignmentPath.replaceWith(logicalExpression(op, assignmentPath.node.left, assignmentPath.node.right)); + } else { + assignmentPath.node.operator = "="; + } + return [assignmentPath.get("left"), assignmentPath.get("right").get("left")]; + } else if (superProp.parentPath.isUpdateExpression()) { + const updateExpr = superProp.parentPath; + const tmp = superProp.scope.generateDeclaredUidIdentifier("tmp"); + const computedKey = superProp.node.computed ? superProp.scope.generateDeclaredUidIdentifier("prop") : null; + const parts = [assignmentExpression("=", tmp, memberExpression(superProp.node.object, computedKey ? assignmentExpression("=", computedKey, superProp.node.property) : superProp.node.property, superProp.node.computed)), assignmentExpression("=", memberExpression(superProp.node.object, computedKey ? identifier(computedKey.name) : superProp.node.property, superProp.node.computed), binaryExpression(superProp.parentPath.node.operator[0], identifier(tmp.name), numericLiteral(1)))]; + if (!superProp.parentPath.node.prefix) { + parts.push(identifier(tmp.name)); + } + updateExpr.replaceWith(sequenceExpression(parts)); + const left = updateExpr.get("expressions.0.right"); + const right = updateExpr.get("expressions.1.left"); + return [left, right]; + } + return [superProp]; + function rightExpression(op, left, right) { + if (op === "=") { + return assignmentExpression("=", left, right); + } else { + return binaryExpression(op, left, right); + } + } +} +function hasSuperClass(thisEnvFn) { + return thisEnvFn.isClassMethod() && !!thisEnvFn.parentPath.parentPath.node.superClass; +} +const assignSuperThisVisitor = (0, _visitors.environmentVisitor)({ + CallExpression(child, { + supers, + thisBinding + }) { + if (!child.get("callee").isSuper()) return; + if (supers.has(child.node)) return; + supers.add(child.node); + child.replaceWithMultiple([child.node, assignmentExpression("=", identifier(thisBinding), identifier("this"))]); + } +}); +function getThisBinding(thisEnvFn, inConstructor) { + return getBinding(thisEnvFn, "this", thisBinding => { + if (!inConstructor || !hasSuperClass(thisEnvFn)) return thisExpression(); + thisEnvFn.traverse(assignSuperThisVisitor, { + supers: new WeakSet(), + thisBinding + }); + }); +} +function getSuperBinding(thisEnvFn) { + return getBinding(thisEnvFn, "supercall", () => { + const argsBinding = thisEnvFn.scope.generateUidIdentifier("args"); + return arrowFunctionExpression([restElement(argsBinding)], callExpression(_super(), [spreadElement(identifier(argsBinding.name))])); + }); +} +function getSuperPropBinding(thisEnvFn, isAssignment, propName) { + const op = isAssignment ? "set" : "get"; + return getBinding(thisEnvFn, `superprop_${op}:${propName || ""}`, () => { + const argsList = []; + let fnBody; + if (propName) { + fnBody = memberExpression(_super(), identifier(propName)); + } else { + const method = thisEnvFn.scope.generateUidIdentifier("prop"); + argsList.unshift(method); + fnBody = memberExpression(_super(), identifier(method.name), true); + } + if (isAssignment) { + const valueIdent = thisEnvFn.scope.generateUidIdentifier("value"); + argsList.push(valueIdent); + fnBody = assignmentExpression("=", fnBody, identifier(valueIdent.name)); + } + return arrowFunctionExpression(argsList, fnBody); + }); +} +function getBinding(thisEnvFn, key, init) { + const cacheKey = "binding:" + key; + let data = thisEnvFn.getData(cacheKey); + if (!data) { + const id = thisEnvFn.scope.generateUidIdentifier(key); + data = id.name; + thisEnvFn.setData(cacheKey, data); + thisEnvFn.scope.push({ + id: id, + init: init(data) + }); + } + return data; +} +const getScopeInformationVisitor = (0, _visitors.environmentVisitor)({ + ThisExpression(child, { + thisPaths + }) { + thisPaths.push(child); + }, + JSXIdentifier(child, { + thisPaths + }) { + if (child.node.name !== "this") return; + if (!child.parentPath.isJSXMemberExpression({ + object: child.node + }) && !child.parentPath.isJSXOpeningElement({ + name: child.node + })) { + return; + } + thisPaths.push(child); + }, + CallExpression(child, { + superCalls + }) { + if (child.get("callee").isSuper()) superCalls.push(child); + }, + MemberExpression(child, { + superProps + }) { + if (child.get("object").isSuper()) superProps.push(child); + }, + Identifier(child, { + argumentsPaths + }) { + if (!child.isReferencedIdentifier({ + name: "arguments" + })) return; + let curr = child.scope; + do { + if (curr.hasOwnBinding("arguments")) { + curr.rename("arguments"); + return; + } + if (curr.path.isFunction() && !curr.path.isArrowFunctionExpression()) { + break; + } + } while (curr = curr.parent); + argumentsPaths.push(child); + }, + MetaProperty(child, { + newTargetPaths + }) { + if (!child.get("meta").isIdentifier({ + name: "new" + })) return; + if (!child.get("property").isIdentifier({ + name: "target" + })) return; + newTargetPaths.push(child); + } +}); +function getScopeInformation(fnPath) { + const thisPaths = []; + const argumentsPaths = []; + const newTargetPaths = []; + const superProps = []; + const superCalls = []; + fnPath.traverse(getScopeInformationVisitor, { + thisPaths, + argumentsPaths, + newTargetPaths, + superProps, + superCalls + }); + return { + thisPaths, + argumentsPaths, + newTargetPaths, + superProps, + superCalls + }; +} +function splitExportDeclaration() { + if (!this.isExportDeclaration() || this.isExportAllDeclaration()) { + throw new Error("Only default and named export declarations can be split."); + } + if (this.isExportNamedDeclaration() && this.get("specifiers").length > 0) { + throw new Error("It doesn't make sense to split exported specifiers."); + } + const declaration = this.get("declaration"); + if (this.isExportDefaultDeclaration()) { + const standaloneDeclaration = declaration.isFunctionDeclaration() || declaration.isClassDeclaration(); + const exportExpr = declaration.isFunctionExpression() || declaration.isClassExpression(); + const scope = declaration.isScope() ? declaration.scope.parent : declaration.scope; + let id = declaration.node.id; + let needBindingRegistration = false; + if (!id) { + needBindingRegistration = true; + id = scope.generateUidIdentifier("default"); + if (standaloneDeclaration || exportExpr) { + declaration.node.id = cloneNode(id); + } + } else if (exportExpr && scope.hasBinding(id.name)) { + needBindingRegistration = true; + id = scope.generateUidIdentifier(id.name); + } + const updatedDeclaration = standaloneDeclaration ? declaration.node : variableDeclaration("var", [variableDeclarator(cloneNode(id), declaration.node)]); + const updatedExportDeclaration = exportNamedDeclaration(null, [exportSpecifier(cloneNode(id), identifier("default"))]); + this.insertAfter(updatedExportDeclaration); + this.replaceWith(updatedDeclaration); + if (needBindingRegistration) { + scope.registerDeclaration(this); + } + return this; + } else if (this.get("specifiers").length > 0) { + throw new Error("It doesn't make sense to split exported specifiers."); + } + const bindingIdentifiers = declaration.getOuterBindingIdentifiers(); + const specifiers = Object.keys(bindingIdentifiers).map(name => { + return exportSpecifier(identifier(name), identifier(name)); + }); + const aliasDeclar = exportNamedDeclaration(null, specifiers); + this.insertAfter(aliasDeclar); + this.replaceWith(declaration.node); + return this; +} +const refersOuterBindingVisitor = { + "ReferencedIdentifier|BindingIdentifier"(path, state) { + if (path.node.name !== state.name) return; + state.needsRename = true; + path.stop(); + }, + Scope(path, state) { + if (path.scope.hasOwnBinding(state.name)) { + path.skip(); + } + } +}; +function ensureFunctionName(supportUnicodeId) { + if (this.node.id) return this; + const res = getFunctionName(this.node, this.parent); + if (res == null) return this; + let { + name + } = res; + if (!supportUnicodeId && /[\uD800-\uDFFF]/.test(name)) { + return null; + } + if (name.startsWith("get ") || name.startsWith("set ")) { + return null; + } + name = toBindingIdentifierName(name.replace(/[/ ]/g, "_")); + const id = identifier(name); + inherits(id, res.originalNode); + const state = { + needsRename: false, + name + }; + const { + scope + } = this; + const binding = scope.getOwnBinding(name); + if (binding) { + if (binding.kind === "param") { + state.needsRename = true; + } else {} + } else if (scope.parent.hasBinding(name) || scope.hasGlobal(name)) { + this.traverse(refersOuterBindingVisitor, state); + } + if (!state.needsRename) { + this.node.id = id; + scope.getProgramParent().references[id.name] = true; + return this; + } + if (scope.hasBinding(id.name) && !scope.hasGlobal(id.name)) { + scope.rename(id.name); + this.node.id = id; + scope.getProgramParent().references[id.name] = true; + return this; + } + if (!isFunction(this.node)) return null; + const key = scope.generateUidIdentifier(id.name); + const params = []; + for (let i = 0, len = getFunctionArity(this.node); i < len; i++) { + params.push(scope.generateUidIdentifier("x")); + } + const call = _template.default.expression.ast` + (function (${key}) { + function ${id}(${params}) { + return ${cloneNode(key)}.apply(this, arguments); + } + + ${cloneNode(id)}.toString = function () { + return ${cloneNode(key)}.toString(); + } + + return ${cloneNode(id)}; + })(${toExpression(this.node)}) + `; + return this.replaceWith(call)[0].get("arguments.0"); +} +function getFunctionArity(node) { + const count = node.params.findIndex(param => isAssignmentPattern(param) || isRestElement(param)); + return count === -1 ? node.params.length : count; +} + +//# sourceMappingURL=conversion.js.map + + +/***/ }), + +/***/ 20398: +/***/ ((__unused_webpack_module, exports) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports.evaluate = evaluate; +exports.evaluateTruthy = evaluateTruthy; +const VALID_OBJECT_CALLEES = ["Number", "String", "Math"]; +const VALID_IDENTIFIER_CALLEES = ["isFinite", "isNaN", "parseFloat", "parseInt", "decodeURI", "decodeURIComponent", "encodeURI", "encodeURIComponent", null, null]; +const INVALID_METHODS = ["random"]; +function isValidObjectCallee(val) { + return VALID_OBJECT_CALLEES.includes(val); +} +function isValidIdentifierCallee(val) { + return VALID_IDENTIFIER_CALLEES.includes(val); +} +function isInvalidMethod(val) { + return INVALID_METHODS.includes(val); +} +function evaluateTruthy() { + const res = this.evaluate(); + if (res.confident) return !!res.value; +} +function deopt(path, state) { + if (!state.confident) return; + state.deoptPath = path; + state.confident = false; +} +const Globals = new Map([["undefined", undefined], ["Infinity", Infinity], ["NaN", NaN]]); +function evaluateCached(path, state) { + const { + node + } = path; + const { + seen + } = state; + if (seen.has(node)) { + const existing = seen.get(node); + if (existing.resolved) { + return existing.value; + } else { + deopt(path, state); + return; + } + } else { + const item = { + resolved: false + }; + seen.set(node, item); + const val = _evaluate(path, state); + if (state.confident) { + item.resolved = true; + item.value = val; + } + return val; + } +} +function _evaluate(path, state) { + if (!state.confident) return; + if (path.isSequenceExpression()) { + const exprs = path.get("expressions"); + return evaluateCached(exprs[exprs.length - 1], state); + } + if (path.isStringLiteral() || path.isNumericLiteral() || path.isBooleanLiteral()) { + return path.node.value; + } + if (path.isNullLiteral()) { + return null; + } + if (path.isTemplateLiteral()) { + return evaluateQuasis(path, path.node.quasis, state); + } + if (path.isTaggedTemplateExpression() && path.get("tag").isMemberExpression()) { + const object = path.get("tag.object"); + const { + node: { + name + } + } = object; + const property = path.get("tag.property"); + if (object.isIdentifier() && name === "String" && !path.scope.getBinding(name) && property.isIdentifier() && property.node.name === "raw") { + return evaluateQuasis(path, path.node.quasi.quasis, state, true); + } + } + if (path.isConditionalExpression()) { + const testResult = evaluateCached(path.get("test"), state); + if (!state.confident) return; + if (testResult) { + return evaluateCached(path.get("consequent"), state); + } else { + return evaluateCached(path.get("alternate"), state); + } + } + if (path.isExpressionWrapper()) { + return evaluateCached(path.get("expression"), state); + } + if (path.isMemberExpression() && !path.parentPath.isCallExpression({ + callee: path.node + })) { + const property = path.get("property"); + const object = path.get("object"); + if (object.isLiteral()) { + const value = object.node.value; + const type = typeof value; + let key = null; + if (path.node.computed) { + key = evaluateCached(property, state); + if (!state.confident) return; + } else if (property.isIdentifier()) { + key = property.node.name; + } + if ((type === "number" || type === "string") && key != null && (typeof key === "number" || typeof key === "string")) { + return value[key]; + } + } + } + if (path.isReferencedIdentifier()) { + const binding = path.scope.getBinding(path.node.name); + if (binding) { + if (binding.constantViolations.length > 0 || path.node.start < binding.path.node.end) { + deopt(binding.path, state); + return; + } + const bindingPathScope = binding.path.scope; + if (binding.kind === "var" && bindingPathScope !== binding.scope) { + let hasUnsafeBlock = !bindingPathScope.path.parentPath.isBlockStatement(); + for (let scope = bindingPathScope.parent; scope; scope = scope.parent) { + var _scope$path$parentPat; + if (scope === path.scope) { + if (hasUnsafeBlock) { + deopt(binding.path, state); + return; + } + break; + } + if ((_scope$path$parentPat = scope.path.parentPath) != null && _scope$path$parentPat.isBlockStatement()) { + hasUnsafeBlock = true; + } + } + } + if (binding.hasValue) { + return binding.value; + } + } + const name = path.node.name; + if (Globals.has(name)) { + if (!binding) { + return Globals.get(name); + } + deopt(binding.path, state); + return; + } + const resolved = path.resolve(); + if (resolved === path) { + deopt(path, state); + return; + } + const value = evaluateCached(resolved, state); + if (typeof value === "object" && value !== null && binding.references > 1) { + deopt(resolved, state); + return; + } + return value; + } + if (path.isUnaryExpression({ + prefix: true + })) { + if (path.node.operator === "void") { + return undefined; + } + const argument = path.get("argument"); + if (path.node.operator === "typeof" && (argument.isFunction() || argument.isClass())) { + return "function"; + } + const arg = evaluateCached(argument, state); + if (!state.confident) return; + switch (path.node.operator) { + case "!": + return !arg; + case "+": + return +arg; + case "-": + return -arg; + case "~": + return ~arg; + case "typeof": + return typeof arg; + } + } + if (path.isArrayExpression()) { + const arr = []; + const elems = path.get("elements"); + for (const elem of elems) { + const elemValue = elem.evaluate(); + if (elemValue.confident) { + arr.push(elemValue.value); + } else { + deopt(elemValue.deopt, state); + return; + } + } + return arr; + } + if (path.isObjectExpression()) { + const obj = {}; + const props = path.get("properties"); + for (const prop of props) { + if (prop.isObjectMethod() || prop.isSpreadElement()) { + deopt(prop, state); + return; + } + const keyPath = prop.get("key"); + let key; + if (prop.node.computed) { + key = keyPath.evaluate(); + if (!key.confident) { + deopt(key.deopt, state); + return; + } + key = key.value; + } else if (keyPath.isIdentifier()) { + key = keyPath.node.name; + } else { + key = keyPath.node.value; + } + const valuePath = prop.get("value"); + let value = valuePath.evaluate(); + if (!value.confident) { + deopt(value.deopt, state); + return; + } + value = value.value; + obj[key] = value; + } + return obj; + } + if (path.isLogicalExpression()) { + const wasConfident = state.confident; + const left = evaluateCached(path.get("left"), state); + const leftConfident = state.confident; + state.confident = wasConfident; + const right = evaluateCached(path.get("right"), state); + const rightConfident = state.confident; + switch (path.node.operator) { + case "||": + state.confident = leftConfident && (!!left || rightConfident); + if (!state.confident) return; + return left || right; + case "&&": + state.confident = leftConfident && (!left || rightConfident); + if (!state.confident) return; + return left && right; + case "??": + state.confident = leftConfident && (left != null || rightConfident); + if (!state.confident) return; + return left != null ? left : right; + } + } + if (path.isBinaryExpression()) { + const left = evaluateCached(path.get("left"), state); + if (!state.confident) return; + const right = evaluateCached(path.get("right"), state); + if (!state.confident) return; + switch (path.node.operator) { + case "-": + return left - right; + case "+": + return left + right; + case "/": + return left / right; + case "*": + return left * right; + case "%": + return left % right; + case "**": + return Math.pow(left, right); + case "<": + return left < right; + case ">": + return left > right; + case "<=": + return left <= right; + case ">=": + return left >= right; + case "==": + return left == right; + case "!=": + return left != right; + case "===": + return left === right; + case "!==": + return left !== right; + case "|": + return left | right; + case "&": + return left & right; + case "^": + return left ^ right; + case "<<": + return left << right; + case ">>": + return left >> right; + case ">>>": + return left >>> right; + } + } + if (path.isCallExpression()) { + const callee = path.get("callee"); + let context; + let func; + if (callee.isIdentifier() && !path.scope.getBinding(callee.node.name) && (isValidObjectCallee(callee.node.name) || isValidIdentifierCallee(callee.node.name))) { + func = global[callee.node.name]; + } + if (callee.isMemberExpression()) { + const object = callee.get("object"); + const property = callee.get("property"); + if (object.isIdentifier() && property.isIdentifier() && isValidObjectCallee(object.node.name) && !isInvalidMethod(property.node.name)) { + context = global[object.node.name]; + const key = property.node.name; + if (hasOwnProperty.call(context, key)) { + func = context[key]; + } + } + if (object.isLiteral() && property.isIdentifier()) { + const type = typeof object.node.value; + if (type === "string" || type === "number") { + context = object.node.value; + func = context[property.node.name]; + } + } + } + if (func) { + const args = path.get("arguments").map(arg => evaluateCached(arg, state)); + if (!state.confident) return; + return func.apply(context, args); + } + } + deopt(path, state); +} +function evaluateQuasis(path, quasis, state, raw = false) { + let str = ""; + let i = 0; + const exprs = path.isTemplateLiteral() ? path.get("expressions") : path.get("quasi.expressions"); + for (const elem of quasis) { + if (!state.confident) break; + str += raw ? elem.value.raw : elem.value.cooked; + const expr = exprs[i++]; + if (expr) str += String(evaluateCached(expr, state)); + } + if (!state.confident) return; + return str; +} +function evaluate() { + const state = { + confident: true, + deoptPath: null, + seen: new Map() + }; + let value = evaluateCached(this, state); + if (!state.confident) value = undefined; + return { + confident: state.confident, + deopt: state.deoptPath, + value: value + }; +} + +//# sourceMappingURL=evaluation.js.map + + +/***/ }), + +/***/ 47588: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports._getKey = _getKey; +exports._getPattern = _getPattern; +exports.get = get; +exports.getAllNextSiblings = getAllNextSiblings; +exports.getAllPrevSiblings = getAllPrevSiblings; +exports.getAssignmentIdentifiers = getAssignmentIdentifiers; +exports.getBindingIdentifierPaths = getBindingIdentifierPaths; +exports.getBindingIdentifiers = getBindingIdentifiers; +exports.getCompletionRecords = getCompletionRecords; +exports.getNextSibling = getNextSibling; +exports.getOpposite = getOpposite; +exports.getOuterBindingIdentifierPaths = getOuterBindingIdentifierPaths; +exports.getOuterBindingIdentifiers = getOuterBindingIdentifiers; +exports.getPrevSibling = getPrevSibling; +exports.getSibling = getSibling; +var _index = __nccwpck_require__(91806); +var _t = __nccwpck_require__(16535); +const { + getAssignmentIdentifiers: _getAssignmentIdentifiers, + getBindingIdentifiers: _getBindingIdentifiers, + getOuterBindingIdentifiers: _getOuterBindingIdentifiers, + numericLiteral, + unaryExpression +} = _t; +const NORMAL_COMPLETION = 0; +const BREAK_COMPLETION = 1; +function NormalCompletion(path) { + return { + type: NORMAL_COMPLETION, + path + }; +} +function BreakCompletion(path) { + return { + type: BREAK_COMPLETION, + path + }; +} +function getOpposite() { + if (this.key === "left") { + return this.getSibling("right"); + } else if (this.key === "right") { + return this.getSibling("left"); + } + return null; +} +function addCompletionRecords(path, records, context) { + if (path) { + records.push(..._getCompletionRecords(path, context)); + } + return records; +} +function completionRecordForSwitch(cases, records, context) { + let lastNormalCompletions = []; + for (let i = 0; i < cases.length; i++) { + const casePath = cases[i]; + const caseCompletions = _getCompletionRecords(casePath, context); + const normalCompletions = []; + const breakCompletions = []; + for (const c of caseCompletions) { + if (c.type === NORMAL_COMPLETION) { + normalCompletions.push(c); + } + if (c.type === BREAK_COMPLETION) { + breakCompletions.push(c); + } + } + if (normalCompletions.length) { + lastNormalCompletions = normalCompletions; + } + records.push(...breakCompletions); + } + records.push(...lastNormalCompletions); + return records; +} +function normalCompletionToBreak(completions) { + completions.forEach(c => { + c.type = BREAK_COMPLETION; + }); +} +function replaceBreakStatementInBreakCompletion(completions, reachable) { + completions.forEach(c => { + if (c.path.isBreakStatement({ + label: null + })) { + if (reachable) { + c.path.replaceWith(unaryExpression("void", numericLiteral(0))); + } else { + c.path.remove(); + } + } + }); +} +function getStatementListCompletion(paths, context) { + const completions = []; + if (context.canHaveBreak) { + let lastNormalCompletions = []; + for (let i = 0; i < paths.length; i++) { + const path = paths[i]; + const newContext = Object.assign({}, context, { + inCaseClause: false + }); + if (path.isBlockStatement() && (context.inCaseClause || context.shouldPopulateBreak)) { + newContext.shouldPopulateBreak = true; + } else { + newContext.shouldPopulateBreak = false; + } + const statementCompletions = _getCompletionRecords(path, newContext); + if (statementCompletions.length > 0 && statementCompletions.every(c => c.type === BREAK_COMPLETION)) { + if (lastNormalCompletions.length > 0 && statementCompletions.every(c => c.path.isBreakStatement({ + label: null + }))) { + normalCompletionToBreak(lastNormalCompletions); + completions.push(...lastNormalCompletions); + if (lastNormalCompletions.some(c => c.path.isDeclaration())) { + completions.push(...statementCompletions); + if (!context.shouldPreserveBreak) { + replaceBreakStatementInBreakCompletion(statementCompletions, true); + } + } + if (!context.shouldPreserveBreak) { + replaceBreakStatementInBreakCompletion(statementCompletions, false); + } + } else { + completions.push(...statementCompletions); + if (!context.shouldPopulateBreak && !context.shouldPreserveBreak) { + replaceBreakStatementInBreakCompletion(statementCompletions, true); + } + } + break; + } + if (i === paths.length - 1) { + completions.push(...statementCompletions); + } else { + lastNormalCompletions = []; + for (let i = 0; i < statementCompletions.length; i++) { + const c = statementCompletions[i]; + if (c.type === BREAK_COMPLETION) { + completions.push(c); + } + if (c.type === NORMAL_COMPLETION) { + lastNormalCompletions.push(c); + } + } + } + } + } else if (paths.length) { + for (let i = paths.length - 1; i >= 0; i--) { + const pathCompletions = _getCompletionRecords(paths[i], context); + if (pathCompletions.length > 1 || pathCompletions.length === 1 && !pathCompletions[0].path.isVariableDeclaration() && !pathCompletions[0].path.isEmptyStatement()) { + completions.push(...pathCompletions); + break; + } + } + } + return completions; +} +function _getCompletionRecords(path, context) { + let records = []; + if (path.isIfStatement()) { + records = addCompletionRecords(path.get("consequent"), records, context); + records = addCompletionRecords(path.get("alternate"), records, context); + } else if (path.isDoExpression() || path.isFor() || path.isWhile() || path.isLabeledStatement()) { + return addCompletionRecords(path.get("body"), records, context); + } else if (path.isProgram() || path.isBlockStatement()) { + return getStatementListCompletion(path.get("body"), context); + } else if (path.isFunction()) { + return _getCompletionRecords(path.get("body"), context); + } else if (path.isTryStatement()) { + records = addCompletionRecords(path.get("block"), records, context); + records = addCompletionRecords(path.get("handler"), records, context); + } else if (path.isCatchClause()) { + return addCompletionRecords(path.get("body"), records, context); + } else if (path.isSwitchStatement()) { + return completionRecordForSwitch(path.get("cases"), records, context); + } else if (path.isSwitchCase()) { + return getStatementListCompletion(path.get("consequent"), { + canHaveBreak: true, + shouldPopulateBreak: false, + inCaseClause: true, + shouldPreserveBreak: context.shouldPreserveBreak + }); + } else if (path.isBreakStatement()) { + records.push(BreakCompletion(path)); + } else { + records.push(NormalCompletion(path)); + } + return records; +} +function getCompletionRecords(shouldPreserveBreak = false) { + const records = _getCompletionRecords(this, { + canHaveBreak: false, + shouldPopulateBreak: false, + inCaseClause: false, + shouldPreserveBreak + }); + return records.map(r => r.path); +} +function getSibling(key) { + return _index.default.get({ + parentPath: this.parentPath, + parent: this.parent, + container: this.container, + listKey: this.listKey, + key: key + }).setContext(this.context); +} +function getPrevSibling() { + return this.getSibling(this.key - 1); +} +function getNextSibling() { + return this.getSibling(this.key + 1); +} +function getAllNextSiblings() { + let _key = this.key; + let sibling = this.getSibling(++_key); + const siblings = []; + while (sibling.node) { + siblings.push(sibling); + sibling = this.getSibling(++_key); + } + return siblings; +} +function getAllPrevSiblings() { + let _key = this.key; + let sibling = this.getSibling(--_key); + const siblings = []; + while (sibling.node) { + siblings.push(sibling); + sibling = this.getSibling(--_key); + } + return siblings; +} +function get(key, context = true) { + if (context === true) context = this.context; + const parts = key.split("."); + if (parts.length === 1) { + return _getKey.call(this, key, context); + } else { + return _getPattern.call(this, parts, context); + } +} +function _getKey(key, context) { + const node = this.node; + const container = node[key]; + if (Array.isArray(container)) { + return container.map((_, i) => { + return _index.default.get({ + listKey: key, + parentPath: this, + parent: node, + container: container, + key: i + }).setContext(context); + }); + } else { + return _index.default.get({ + parentPath: this, + parent: node, + container: node, + key: key + }).setContext(context); + } +} +function _getPattern(parts, context) { + let path = this; + for (const part of parts) { + if (part === ".") { + path = path.parentPath; + } else { + if (Array.isArray(path)) { + path = path[part]; + } else { + path = path.get(part, context); + } + } + } + return path; +} +function getAssignmentIdentifiers() { + return _getAssignmentIdentifiers(this.node); +} +function getBindingIdentifiers(duplicates) { + return _getBindingIdentifiers(this.node, duplicates); +} +function getOuterBindingIdentifiers(duplicates) { + return _getOuterBindingIdentifiers(this.node, duplicates); +} +function getBindingIdentifierPaths(duplicates = false, outerOnly = false) { + const path = this; + const search = [path]; + const ids = Object.create(null); + while (search.length) { + const id = search.shift(); + if (!id) continue; + if (!id.node) continue; + const keys = _getBindingIdentifiers.keys[id.node.type]; + if (id.isIdentifier()) { + if (duplicates) { + const _ids = ids[id.node.name] = ids[id.node.name] || []; + _ids.push(id); + } else { + ids[id.node.name] = id; + } + continue; + } + if (id.isExportDeclaration()) { + const declaration = id.get("declaration"); + if (declaration.isDeclaration()) { + search.push(declaration); + } + continue; + } + if (outerOnly) { + if (id.isFunctionDeclaration()) { + search.push(id.get("id")); + continue; + } + if (id.isFunctionExpression()) { + continue; + } + } + if (keys) { + for (let i = 0; i < keys.length; i++) { + const key = keys[i]; + const child = id.get(key); + if (Array.isArray(child)) { + search.push(...child); + } else if (child.node) { + search.push(child); + } + } + } + } + return ids; +} +function getOuterBindingIdentifierPaths(duplicates = false) { + return this.getBindingIdentifierPaths(duplicates, true); +} + +//# sourceMappingURL=family.js.map + + +/***/ }), + +/***/ 91806: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports["default"] = exports.SHOULD_STOP = exports.SHOULD_SKIP = exports.REMOVED = void 0; +var virtualTypes = __nccwpck_require__(74425); +var _debug = __nccwpck_require__(2830); +var _index = __nccwpck_require__(50148); +var _index2 = __nccwpck_require__(18171); +var _t = __nccwpck_require__(16535); +var t = _t; +var cache = __nccwpck_require__(6730); +var _generator = __nccwpck_require__(12123); +var NodePath_ancestry = __nccwpck_require__(63405); +var NodePath_inference = __nccwpck_require__(98882); +var NodePath_replacement = __nccwpck_require__(76178); +var NodePath_evaluation = __nccwpck_require__(20398); +var NodePath_conversion = __nccwpck_require__(26900); +var NodePath_introspection = __nccwpck_require__(7009); +var _context = __nccwpck_require__(74105); +var NodePath_context = _context; +var NodePath_removal = __nccwpck_require__(23562); +var NodePath_modification = __nccwpck_require__(20184); +var NodePath_family = __nccwpck_require__(47588); +var NodePath_comments = __nccwpck_require__(17794); +var NodePath_virtual_types_validator = __nccwpck_require__(66582); +const { + validate +} = _t; +const debug = _debug("babel"); +const REMOVED = exports.REMOVED = 1 << 0; +const SHOULD_STOP = exports.SHOULD_STOP = 1 << 1; +const SHOULD_SKIP = exports.SHOULD_SKIP = 1 << 2; +const NodePath_Final = exports["default"] = class NodePath { + constructor(hub, parent) { + this.contexts = []; + this.state = null; + this.opts = null; + this._traverseFlags = 0; + this.skipKeys = null; + this.parentPath = null; + this.container = null; + this.listKey = null; + this.key = null; + this.node = null; + this.type = null; + this._store = null; + this.parent = parent; + this.hub = hub; + this.data = null; + this.context = null; + this.scope = null; + } + get removed() { + return (this._traverseFlags & 1) > 0; + } + set removed(v) { + if (v) this._traverseFlags |= 1;else this._traverseFlags &= -2; + } + get shouldStop() { + return (this._traverseFlags & 2) > 0; + } + set shouldStop(v) { + if (v) this._traverseFlags |= 2;else this._traverseFlags &= -3; + } + get shouldSkip() { + return (this._traverseFlags & 4) > 0; + } + set shouldSkip(v) { + if (v) this._traverseFlags |= 4;else this._traverseFlags &= -5; + } + static get({ + hub, + parentPath, + parent, + container, + listKey, + key + }) { + if (!hub && parentPath) { + hub = parentPath.hub; + } + if (!parent) { + throw new Error("To get a node path the parent needs to exist"); + } + const targetNode = container[key]; + const paths = cache.getOrCreateCachedPaths(parent, parentPath); + let path = paths.get(targetNode); + if (!path) { + path = new NodePath(hub, parent); + if (targetNode) paths.set(targetNode, path); + } + _context.setup.call(path, parentPath, container, listKey, key); + return path; + } + getScope(scope) { + return this.isScope() ? new _index2.default(this) : scope; + } + setData(key, val) { + if (this.data == null) { + this.data = Object.create(null); + } + return this.data[key] = val; + } + getData(key, def) { + if (this.data == null) { + this.data = Object.create(null); + } + let val = this.data[key]; + if (val === undefined && def !== undefined) val = this.data[key] = def; + return val; + } + hasNode() { + return this.node != null; + } + buildCodeFrameError(msg, Error = SyntaxError) { + return this.hub.buildError(this.node, msg, Error); + } + traverse(visitor, state) { + (0, _index.default)(this.node, visitor, this.scope, state, this); + } + set(key, node) { + validate(this.node, key, node); + this.node[key] = node; + } + getPathLocation() { + const parts = []; + let path = this; + do { + let key = path.key; + if (path.inList) key = `${path.listKey}[${key}]`; + parts.unshift(key); + } while (path = path.parentPath); + return parts.join("."); + } + debug(message) { + if (!debug.enabled) return; + debug(`${this.getPathLocation()} ${this.type}: ${message}`); + } + toString() { + return (0, _generator.default)(this.node).code; + } + get inList() { + return !!this.listKey; + } + set inList(inList) { + if (!inList) { + this.listKey = null; + } + } + get parentKey() { + return this.listKey || this.key; + } +}; +const methods = { + findParent: NodePath_ancestry.findParent, + find: NodePath_ancestry.find, + getFunctionParent: NodePath_ancestry.getFunctionParent, + getStatementParent: NodePath_ancestry.getStatementParent, + getEarliestCommonAncestorFrom: NodePath_ancestry.getEarliestCommonAncestorFrom, + getDeepestCommonAncestorFrom: NodePath_ancestry.getDeepestCommonAncestorFrom, + getAncestry: NodePath_ancestry.getAncestry, + isAncestor: NodePath_ancestry.isAncestor, + isDescendant: NodePath_ancestry.isDescendant, + inType: NodePath_ancestry.inType, + getTypeAnnotation: NodePath_inference.getTypeAnnotation, + isBaseType: NodePath_inference.isBaseType, + couldBeBaseType: NodePath_inference.couldBeBaseType, + baseTypeStrictlyMatches: NodePath_inference.baseTypeStrictlyMatches, + isGenericType: NodePath_inference.isGenericType, + replaceWithMultiple: NodePath_replacement.replaceWithMultiple, + replaceWithSourceString: NodePath_replacement.replaceWithSourceString, + replaceWith: NodePath_replacement.replaceWith, + replaceExpressionWithStatements: NodePath_replacement.replaceExpressionWithStatements, + replaceInline: NodePath_replacement.replaceInline, + evaluateTruthy: NodePath_evaluation.evaluateTruthy, + evaluate: NodePath_evaluation.evaluate, + toComputedKey: NodePath_conversion.toComputedKey, + ensureBlock: NodePath_conversion.ensureBlock, + unwrapFunctionEnvironment: NodePath_conversion.unwrapFunctionEnvironment, + arrowFunctionToExpression: NodePath_conversion.arrowFunctionToExpression, + splitExportDeclaration: NodePath_conversion.splitExportDeclaration, + ensureFunctionName: NodePath_conversion.ensureFunctionName, + matchesPattern: NodePath_introspection.matchesPattern, + isStatic: NodePath_introspection.isStatic, + isNodeType: NodePath_introspection.isNodeType, + canHaveVariableDeclarationOrExpression: NodePath_introspection.canHaveVariableDeclarationOrExpression, + canSwapBetweenExpressionAndStatement: NodePath_introspection.canSwapBetweenExpressionAndStatement, + isCompletionRecord: NodePath_introspection.isCompletionRecord, + isStatementOrBlock: NodePath_introspection.isStatementOrBlock, + referencesImport: NodePath_introspection.referencesImport, + getSource: NodePath_introspection.getSource, + willIMaybeExecuteBefore: NodePath_introspection.willIMaybeExecuteBefore, + _guessExecutionStatusRelativeTo: NodePath_introspection._guessExecutionStatusRelativeTo, + resolve: NodePath_introspection.resolve, + isConstantExpression: NodePath_introspection.isConstantExpression, + isInStrictMode: NodePath_introspection.isInStrictMode, + isDenylisted: NodePath_context.isDenylisted, + visit: NodePath_context.visit, + skip: NodePath_context.skip, + skipKey: NodePath_context.skipKey, + stop: NodePath_context.stop, + setContext: NodePath_context.setContext, + requeue: NodePath_context.requeue, + requeueComputedKeyAndDecorators: NodePath_context.requeueComputedKeyAndDecorators, + remove: NodePath_removal.remove, + insertBefore: NodePath_modification.insertBefore, + insertAfter: NodePath_modification.insertAfter, + unshiftContainer: NodePath_modification.unshiftContainer, + pushContainer: NodePath_modification.pushContainer, + getOpposite: NodePath_family.getOpposite, + getCompletionRecords: NodePath_family.getCompletionRecords, + getSibling: NodePath_family.getSibling, + getPrevSibling: NodePath_family.getPrevSibling, + getNextSibling: NodePath_family.getNextSibling, + getAllNextSiblings: NodePath_family.getAllNextSiblings, + getAllPrevSiblings: NodePath_family.getAllPrevSiblings, + get: NodePath_family.get, + getAssignmentIdentifiers: NodePath_family.getAssignmentIdentifiers, + getBindingIdentifiers: NodePath_family.getBindingIdentifiers, + getOuterBindingIdentifiers: NodePath_family.getOuterBindingIdentifiers, + getBindingIdentifierPaths: NodePath_family.getBindingIdentifierPaths, + getOuterBindingIdentifierPaths: NodePath_family.getOuterBindingIdentifierPaths, + shareCommentsWithSiblings: NodePath_comments.shareCommentsWithSiblings, + addComment: NodePath_comments.addComment, + addComments: NodePath_comments.addComments +}; +Object.assign(NodePath_Final.prototype, methods); +{ + NodePath_Final.prototype.arrowFunctionToShadowed = NodePath_conversion[String("arrowFunctionToShadowed")]; + Object.assign(NodePath_Final.prototype, { + has: NodePath_introspection[String("has")], + is: NodePath_introspection[String("is")], + isnt: NodePath_introspection[String("isnt")], + equals: NodePath_introspection[String("equals")], + hoist: NodePath_modification[String("hoist")], + updateSiblingKeys: NodePath_modification.updateSiblingKeys, + call: NodePath_context.call, + isBlacklisted: NodePath_context[String("isBlacklisted")], + setScope: NodePath_context.setScope, + resync: NodePath_context.resync, + popContext: NodePath_context.popContext, + pushContext: NodePath_context.pushContext, + setup: NodePath_context.setup, + setKey: NodePath_context.setKey + }); +} +{ + NodePath_Final.prototype._guessExecutionStatusRelativeToDifferentFunctions = NodePath_introspection._guessExecutionStatusRelativeTo; + NodePath_Final.prototype._guessExecutionStatusRelativeToDifferentFunctions = NodePath_introspection._guessExecutionStatusRelativeTo; + Object.assign(NodePath_Final.prototype, { + _getTypeAnnotation: NodePath_inference._getTypeAnnotation, + _replaceWith: NodePath_replacement._replaceWith, + _resolve: NodePath_introspection._resolve, + _call: NodePath_context._call, + _resyncParent: NodePath_context._resyncParent, + _resyncKey: NodePath_context._resyncKey, + _resyncList: NodePath_context._resyncList, + _resyncRemoved: NodePath_context._resyncRemoved, + _getQueueContexts: NodePath_context._getQueueContexts, + _removeFromScope: NodePath_removal._removeFromScope, + _callRemovalHooks: NodePath_removal._callRemovalHooks, + _remove: NodePath_removal._remove, + _markRemoved: NodePath_removal._markRemoved, + _assertUnremoved: NodePath_removal._assertUnremoved, + _containerInsert: NodePath_modification._containerInsert, + _containerInsertBefore: NodePath_modification._containerInsertBefore, + _containerInsertAfter: NodePath_modification._containerInsertAfter, + _verifyNodeList: NodePath_modification._verifyNodeList, + _getKey: NodePath_family._getKey, + _getPattern: NodePath_family._getPattern + }); +} +for (const type of t.TYPES) { + const typeKey = `is${type}`; + const fn = t[typeKey]; + NodePath_Final.prototype[typeKey] = function (opts) { + return fn(this.node, opts); + }; + NodePath_Final.prototype[`assert${type}`] = function (opts) { + if (!fn(this.node, opts)) { + throw new TypeError(`Expected node path of type ${type}`); + } + }; +} +Object.assign(NodePath_Final.prototype, NodePath_virtual_types_validator); +for (const type of Object.keys(virtualTypes)) { + if (type[0] === "_") continue; + if (!t.TYPES.includes(type)) t.TYPES.push(type); +} + +//# sourceMappingURL=index.js.map + + +/***/ }), + +/***/ 98882: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports._getTypeAnnotation = _getTypeAnnotation; +exports.baseTypeStrictlyMatches = baseTypeStrictlyMatches; +exports.couldBeBaseType = couldBeBaseType; +exports.getTypeAnnotation = getTypeAnnotation; +exports.isBaseType = isBaseType; +exports.isGenericType = isGenericType; +var inferers = __nccwpck_require__(87116); +var _t = __nccwpck_require__(16535); +const { + anyTypeAnnotation, + isAnyTypeAnnotation, + isArrayTypeAnnotation, + isBooleanTypeAnnotation, + isEmptyTypeAnnotation, + isFlowBaseAnnotation, + isGenericTypeAnnotation, + isIdentifier, + isMixedTypeAnnotation, + isNumberTypeAnnotation, + isStringTypeAnnotation, + isTSArrayType, + isTSTypeAnnotation, + isTSTypeReference, + isTupleTypeAnnotation, + isTypeAnnotation, + isUnionTypeAnnotation, + isVoidTypeAnnotation, + stringTypeAnnotation, + voidTypeAnnotation +} = _t; +function getTypeAnnotation() { + let type = this.getData("typeAnnotation"); + if (type != null) { + return type; + } + type = _getTypeAnnotation.call(this) || anyTypeAnnotation(); + if (isTypeAnnotation(type) || isTSTypeAnnotation(type)) { + type = type.typeAnnotation; + } + this.setData("typeAnnotation", type); + return type; +} +const typeAnnotationInferringNodes = new WeakSet(); +function _getTypeAnnotation() { + const node = this.node; + if (!node) { + if (this.key === "init" && this.parentPath.isVariableDeclarator()) { + const declar = this.parentPath.parentPath; + const declarParent = declar.parentPath; + if (declar.key === "left" && declarParent.isForInStatement()) { + return stringTypeAnnotation(); + } + if (declar.key === "left" && declarParent.isForOfStatement()) { + return anyTypeAnnotation(); + } + return voidTypeAnnotation(); + } else { + return; + } + } + if (node.typeAnnotation) { + return node.typeAnnotation; + } + if (typeAnnotationInferringNodes.has(node)) { + return; + } + typeAnnotationInferringNodes.add(node); + try { + var _inferer; + let inferer = inferers[node.type]; + if (inferer) { + return inferer.call(this, node); + } + inferer = inferers[this.parentPath.type]; + if ((_inferer = inferer) != null && _inferer.validParent) { + return this.parentPath.getTypeAnnotation(); + } + } finally { + typeAnnotationInferringNodes.delete(node); + } +} +function isBaseType(baseName, soft) { + return _isBaseType(baseName, this.getTypeAnnotation(), soft); +} +function _isBaseType(baseName, type, soft) { + if (baseName === "string") { + return isStringTypeAnnotation(type); + } else if (baseName === "number") { + return isNumberTypeAnnotation(type); + } else if (baseName === "boolean") { + return isBooleanTypeAnnotation(type); + } else if (baseName === "any") { + return isAnyTypeAnnotation(type); + } else if (baseName === "mixed") { + return isMixedTypeAnnotation(type); + } else if (baseName === "empty") { + return isEmptyTypeAnnotation(type); + } else if (baseName === "void") { + return isVoidTypeAnnotation(type); + } else { + if (soft) { + return false; + } else { + throw new Error(`Unknown base type ${baseName}`); + } + } +} +function couldBeBaseType(name) { + const type = this.getTypeAnnotation(); + if (isAnyTypeAnnotation(type)) return true; + if (isUnionTypeAnnotation(type)) { + for (const type2 of type.types) { + if (isAnyTypeAnnotation(type2) || _isBaseType(name, type2, true)) { + return true; + } + } + return false; + } else { + return _isBaseType(name, type, true); + } +} +function baseTypeStrictlyMatches(rightArg) { + const left = this.getTypeAnnotation(); + const right = rightArg.getTypeAnnotation(); + if (!isAnyTypeAnnotation(left) && isFlowBaseAnnotation(left)) { + return right.type === left.type; + } + return false; +} +function isGenericType(genericName) { + const type = this.getTypeAnnotation(); + if (genericName === "Array") { + if (isTSArrayType(type) || isArrayTypeAnnotation(type) || isTupleTypeAnnotation(type)) { + return true; + } + } + return isGenericTypeAnnotation(type) && isIdentifier(type.id, { + name: genericName + }) || isTSTypeReference(type) && isIdentifier(type.typeName, { + name: genericName + }); +} + +//# sourceMappingURL=index.js.map + + +/***/ }), + +/***/ 77081: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports["default"] = _default; +var _t = __nccwpck_require__(16535); +var _util = __nccwpck_require__(97964); +const { + BOOLEAN_NUMBER_BINARY_OPERATORS, + createTypeAnnotationBasedOnTypeof, + numberTypeAnnotation, + voidTypeAnnotation +} = _t; +function _default(node) { + if (!this.isReferenced()) return; + const binding = this.scope.getBinding(node.name); + if (binding) { + if (binding.identifier.typeAnnotation) { + return binding.identifier.typeAnnotation; + } else { + return getTypeAnnotationBindingConstantViolations(binding, this, node.name); + } + } + if (node.name === "undefined") { + return voidTypeAnnotation(); + } else if (node.name === "NaN" || node.name === "Infinity") { + return numberTypeAnnotation(); + } else if (node.name === "arguments") {} +} +function getTypeAnnotationBindingConstantViolations(binding, path, name) { + const types = []; + const functionConstantViolations = []; + let constantViolations = getConstantViolationsBefore(binding, path, functionConstantViolations); + const testType = getConditionalAnnotation(binding, path, name); + if (testType) { + const testConstantViolations = getConstantViolationsBefore(binding, testType.ifStatement); + constantViolations = constantViolations.filter(path => !testConstantViolations.includes(path)); + types.push(testType.typeAnnotation); + } + if (constantViolations.length) { + constantViolations.push(...functionConstantViolations); + for (const violation of constantViolations) { + types.push(violation.getTypeAnnotation()); + } + } + if (!types.length) { + return; + } + return (0, _util.createUnionType)(types); +} +function getConstantViolationsBefore(binding, path, functions) { + const violations = binding.constantViolations.slice(); + violations.unshift(binding.path); + return violations.filter(violation => { + violation = violation.resolve(); + const status = violation._guessExecutionStatusRelativeTo(path); + if (functions && status === "unknown") functions.push(violation); + return status === "before"; + }); +} +function inferAnnotationFromBinaryExpression(name, path) { + const operator = path.node.operator; + const right = path.get("right").resolve(); + const left = path.get("left").resolve(); + let target; + if (left.isIdentifier({ + name + })) { + target = right; + } else if (right.isIdentifier({ + name + })) { + target = left; + } + if (target) { + if (operator === "===") { + return target.getTypeAnnotation(); + } + if (BOOLEAN_NUMBER_BINARY_OPERATORS.includes(operator)) { + return numberTypeAnnotation(); + } + return; + } + if (operator !== "===" && operator !== "==") return; + let typeofPath; + let typePath; + if (left.isUnaryExpression({ + operator: "typeof" + })) { + typeofPath = left; + typePath = right; + } else if (right.isUnaryExpression({ + operator: "typeof" + })) { + typeofPath = right; + typePath = left; + } + if (!typeofPath) return; + if (!typeofPath.get("argument").isIdentifier({ + name + })) return; + typePath = typePath.resolve(); + if (!typePath.isLiteral()) return; + const typeValue = typePath.node.value; + if (typeof typeValue !== "string") return; + return createTypeAnnotationBasedOnTypeof(typeValue); +} +function getParentConditionalPath(binding, path, name) { + let parentPath; + while (parentPath = path.parentPath) { + if (parentPath.isIfStatement() || parentPath.isConditionalExpression()) { + if (path.key === "test") { + return; + } + return parentPath; + } + if (parentPath.isFunction()) { + if (parentPath.parentPath.scope.getBinding(name) !== binding) return; + } + path = parentPath; + } +} +function getConditionalAnnotation(binding, path, name) { + const ifStatement = getParentConditionalPath(binding, path, name); + if (!ifStatement) return; + const test = ifStatement.get("test"); + const paths = [test]; + const types = []; + for (let i = 0; i < paths.length; i++) { + const path = paths[i]; + if (path.isLogicalExpression()) { + if (path.node.operator === "&&") { + paths.push(path.get("left")); + paths.push(path.get("right")); + } + } else if (path.isBinaryExpression()) { + const type = inferAnnotationFromBinaryExpression(name, path); + if (type) types.push(type); + } + } + if (types.length) { + return { + typeAnnotation: (0, _util.createUnionType)(types), + ifStatement + }; + } + return getConditionalAnnotation(binding, ifStatement, name); +} + +//# sourceMappingURL=inferer-reference.js.map + + +/***/ }), + +/***/ 87116: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports.ArrayExpression = ArrayExpression; +exports.AssignmentExpression = AssignmentExpression; +exports.BinaryExpression = BinaryExpression; +exports.BooleanLiteral = BooleanLiteral; +exports.CallExpression = CallExpression; +exports.ConditionalExpression = ConditionalExpression; +exports.ClassDeclaration = exports.ClassExpression = exports.FunctionDeclaration = exports.ArrowFunctionExpression = exports.FunctionExpression = Func; +Object.defineProperty(exports, "Identifier", ({ + enumerable: true, + get: function () { + return _infererReference.default; + } +})); +exports.LogicalExpression = LogicalExpression; +exports.NewExpression = NewExpression; +exports.NullLiteral = NullLiteral; +exports.NumericLiteral = NumericLiteral; +exports.ObjectExpression = ObjectExpression; +exports.ParenthesizedExpression = ParenthesizedExpression; +exports.RegExpLiteral = RegExpLiteral; +exports.RestElement = RestElement; +exports.SequenceExpression = SequenceExpression; +exports.StringLiteral = StringLiteral; +exports.TSAsExpression = TSAsExpression; +exports.TSNonNullExpression = TSNonNullExpression; +exports.TaggedTemplateExpression = TaggedTemplateExpression; +exports.TemplateLiteral = TemplateLiteral; +exports.TypeCastExpression = TypeCastExpression; +exports.UnaryExpression = UnaryExpression; +exports.UpdateExpression = UpdateExpression; +exports.VariableDeclarator = VariableDeclarator; +var _t = __nccwpck_require__(16535); +var _infererReference = __nccwpck_require__(77081); +var _util = __nccwpck_require__(97964); +const { + BOOLEAN_BINARY_OPERATORS, + BOOLEAN_UNARY_OPERATORS, + NUMBER_BINARY_OPERATORS, + NUMBER_UNARY_OPERATORS, + STRING_UNARY_OPERATORS, + anyTypeAnnotation, + arrayTypeAnnotation, + booleanTypeAnnotation, + buildMatchMemberExpression, + genericTypeAnnotation, + identifier, + nullLiteralTypeAnnotation, + numberTypeAnnotation, + stringTypeAnnotation, + tupleTypeAnnotation, + unionTypeAnnotation, + voidTypeAnnotation, + isIdentifier +} = _t; +function VariableDeclarator() { + if (!this.get("id").isIdentifier()) return; + return this.get("init").getTypeAnnotation(); +} +function TypeCastExpression(node) { + return node.typeAnnotation; +} +TypeCastExpression.validParent = true; +function TSAsExpression(node) { + return node.typeAnnotation; +} +TSAsExpression.validParent = true; +function TSNonNullExpression() { + return this.get("expression").getTypeAnnotation(); +} +function NewExpression(node) { + if (node.callee.type === "Identifier") { + return genericTypeAnnotation(node.callee); + } +} +function TemplateLiteral() { + return stringTypeAnnotation(); +} +function UnaryExpression(node) { + const operator = node.operator; + if (operator === "void") { + return voidTypeAnnotation(); + } else if (NUMBER_UNARY_OPERATORS.includes(operator)) { + return numberTypeAnnotation(); + } else if (STRING_UNARY_OPERATORS.includes(operator)) { + return stringTypeAnnotation(); + } else if (BOOLEAN_UNARY_OPERATORS.includes(operator)) { + return booleanTypeAnnotation(); + } +} +function BinaryExpression(node) { + const operator = node.operator; + if (NUMBER_BINARY_OPERATORS.includes(operator)) { + return numberTypeAnnotation(); + } else if (BOOLEAN_BINARY_OPERATORS.includes(operator)) { + return booleanTypeAnnotation(); + } else if (operator === "+") { + const right = this.get("right"); + const left = this.get("left"); + if (left.isBaseType("number") && right.isBaseType("number")) { + return numberTypeAnnotation(); + } else if (left.isBaseType("string") || right.isBaseType("string")) { + return stringTypeAnnotation(); + } + return unionTypeAnnotation([stringTypeAnnotation(), numberTypeAnnotation()]); + } +} +function LogicalExpression() { + const argumentTypes = [this.get("left").getTypeAnnotation(), this.get("right").getTypeAnnotation()]; + return (0, _util.createUnionType)(argumentTypes); +} +function ConditionalExpression() { + const argumentTypes = [this.get("consequent").getTypeAnnotation(), this.get("alternate").getTypeAnnotation()]; + return (0, _util.createUnionType)(argumentTypes); +} +function SequenceExpression() { + return this.get("expressions").pop().getTypeAnnotation(); +} +function ParenthesizedExpression() { + return this.get("expression").getTypeAnnotation(); +} +function AssignmentExpression() { + return this.get("right").getTypeAnnotation(); +} +function UpdateExpression(node) { + const operator = node.operator; + if (operator === "++" || operator === "--") { + return numberTypeAnnotation(); + } +} +function StringLiteral() { + return stringTypeAnnotation(); +} +function NumericLiteral() { + return numberTypeAnnotation(); +} +function BooleanLiteral() { + return booleanTypeAnnotation(); +} +function NullLiteral() { + return nullLiteralTypeAnnotation(); +} +function RegExpLiteral() { + return genericTypeAnnotation(identifier("RegExp")); +} +function ObjectExpression() { + return genericTypeAnnotation(identifier("Object")); +} +function ArrayExpression() { + return genericTypeAnnotation(identifier("Array")); +} +function RestElement() { + return ArrayExpression(); +} +RestElement.validParent = true; +function Func() { + return genericTypeAnnotation(identifier("Function")); +} +const isArrayFrom = buildMatchMemberExpression("Array.from"); +const isObjectKeys = buildMatchMemberExpression("Object.keys"); +const isObjectValues = buildMatchMemberExpression("Object.values"); +const isObjectEntries = buildMatchMemberExpression("Object.entries"); +function CallExpression() { + const { + callee + } = this.node; + if (isObjectKeys(callee)) { + return arrayTypeAnnotation(stringTypeAnnotation()); + } else if (isArrayFrom(callee) || isObjectValues(callee) || isIdentifier(callee, { + name: "Array" + })) { + return arrayTypeAnnotation(anyTypeAnnotation()); + } else if (isObjectEntries(callee)) { + return arrayTypeAnnotation(tupleTypeAnnotation([stringTypeAnnotation(), anyTypeAnnotation()])); + } + return resolveCall(this.get("callee")); +} +function TaggedTemplateExpression() { + return resolveCall(this.get("tag")); +} +function resolveCall(callee) { + callee = callee.resolve(); + if (callee.isFunction()) { + const { + node + } = callee; + if (node.async) { + if (node.generator) { + return genericTypeAnnotation(identifier("AsyncIterator")); + } else { + return genericTypeAnnotation(identifier("Promise")); + } + } else { + if (node.generator) { + return genericTypeAnnotation(identifier("Iterator")); + } else if (callee.node.returnType) { + return callee.node.returnType; + } else {} + } + } +} + +//# sourceMappingURL=inferers.js.map + + +/***/ }), + +/***/ 97964: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports.createUnionType = createUnionType; +var _t = __nccwpck_require__(16535); +const { + createFlowUnionType, + createTSUnionType, + createUnionTypeAnnotation, + isFlowType, + isTSType +} = _t; +function createUnionType(types) { + { + if (types.every(v => isFlowType(v))) { + if (createFlowUnionType) { + return createFlowUnionType(types); + } + return createUnionTypeAnnotation(types); + } else if (types.every(v => isTSType(v))) { + if (createTSUnionType) { + return createTSUnionType(types); + } + } + } +} + +//# sourceMappingURL=util.js.map + + +/***/ }), + +/***/ 7009: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports._guessExecutionStatusRelativeTo = _guessExecutionStatusRelativeTo; +exports._resolve = _resolve; +exports.canHaveVariableDeclarationOrExpression = canHaveVariableDeclarationOrExpression; +exports.canSwapBetweenExpressionAndStatement = canSwapBetweenExpressionAndStatement; +exports.getSource = getSource; +exports.isCompletionRecord = isCompletionRecord; +exports.isConstantExpression = isConstantExpression; +exports.isInStrictMode = isInStrictMode; +exports.isNodeType = isNodeType; +exports.isStatementOrBlock = isStatementOrBlock; +exports.isStatic = isStatic; +exports.matchesPattern = matchesPattern; +exports.referencesImport = referencesImport; +exports.resolve = resolve; +exports.willIMaybeExecuteBefore = willIMaybeExecuteBefore; +var _t = __nccwpck_require__(16535); +const { + STATEMENT_OR_BLOCK_KEYS, + VISITOR_KEYS, + isBlockStatement, + isExpression, + isIdentifier, + isLiteral, + isStringLiteral, + isType, + matchesPattern: _matchesPattern +} = _t; +function matchesPattern(pattern, allowPartial) { + return _matchesPattern(this.node, pattern, allowPartial); +} +{ + exports.has = function has(key) { + var _this$node; + const val = (_this$node = this.node) == null ? void 0 : _this$node[key]; + if (val && Array.isArray(val)) { + return !!val.length; + } else { + return !!val; + } + }; +} +function isStatic() { + return this.scope.isStatic(this.node); +} +{ + exports.is = exports.has; + exports.isnt = function isnt(key) { + return !this.has(key); + }; + exports.equals = function equals(key, value) { + return this.node[key] === value; + }; +} +function isNodeType(type) { + return isType(this.type, type); +} +function canHaveVariableDeclarationOrExpression() { + return (this.key === "init" || this.key === "left") && this.parentPath.isFor(); +} +function canSwapBetweenExpressionAndStatement(replacement) { + if (this.key !== "body" || !this.parentPath.isArrowFunctionExpression()) { + return false; + } + if (this.isExpression()) { + return isBlockStatement(replacement); + } else if (this.isBlockStatement()) { + return isExpression(replacement); + } + return false; +} +function isCompletionRecord(allowInsideFunction) { + let path = this; + let first = true; + do { + const { + type, + container + } = path; + if (!first && (path.isFunction() || type === "StaticBlock")) { + return !!allowInsideFunction; + } + first = false; + if (Array.isArray(container) && path.key !== container.length - 1) { + return false; + } + } while ((path = path.parentPath) && !path.isProgram() && !path.isDoExpression()); + return true; +} +function isStatementOrBlock() { + if (this.parentPath.isLabeledStatement() || isBlockStatement(this.container)) { + return false; + } else { + return STATEMENT_OR_BLOCK_KEYS.includes(this.key); + } +} +function referencesImport(moduleSource, importName) { + if (!this.isReferencedIdentifier()) { + if (this.isJSXMemberExpression() && this.node.property.name === importName || (this.isMemberExpression() || this.isOptionalMemberExpression()) && (this.node.computed ? isStringLiteral(this.node.property, { + value: importName + }) : this.node.property.name === importName)) { + const object = this.get("object"); + return object.isReferencedIdentifier() && object.referencesImport(moduleSource, "*"); + } + return false; + } + const binding = this.scope.getBinding(this.node.name); + if (!binding || binding.kind !== "module") return false; + const path = binding.path; + const parent = path.parentPath; + if (!parent.isImportDeclaration()) return false; + if (parent.node.source.value === moduleSource) { + if (!importName) return true; + } else { + return false; + } + if (path.isImportDefaultSpecifier() && importName === "default") { + return true; + } + if (path.isImportNamespaceSpecifier() && importName === "*") { + return true; + } + if (path.isImportSpecifier() && isIdentifier(path.node.imported, { + name: importName + })) { + return true; + } + return false; +} +function getSource() { + const node = this.node; + if (node.end) { + const code = this.hub.getCode(); + if (code) return code.slice(node.start, node.end); + } + return ""; +} +function willIMaybeExecuteBefore(target) { + return this._guessExecutionStatusRelativeTo(target) !== "after"; +} +function getOuterFunction(path) { + return path.isProgram() ? path : (path.parentPath.scope.getFunctionParent() || path.parentPath.scope.getProgramParent()).path; +} +function isExecutionUncertain(type, key) { + switch (type) { + case "LogicalExpression": + return key === "right"; + case "ConditionalExpression": + case "IfStatement": + return key === "consequent" || key === "alternate"; + case "WhileStatement": + case "DoWhileStatement": + case "ForInStatement": + case "ForOfStatement": + return key === "body"; + case "ForStatement": + return key === "body" || key === "update"; + case "SwitchStatement": + return key === "cases"; + case "TryStatement": + return key === "handler"; + case "AssignmentPattern": + return key === "right"; + case "OptionalMemberExpression": + return key === "property"; + case "OptionalCallExpression": + return key === "arguments"; + default: + return false; + } +} +function isExecutionUncertainInList(paths, maxIndex) { + for (let i = 0; i < maxIndex; i++) { + const path = paths[i]; + if (isExecutionUncertain(path.parent.type, path.parentKey)) { + return true; + } + } + return false; +} +const SYMBOL_CHECKING = Symbol(); +function _guessExecutionStatusRelativeTo(target) { + return _guessExecutionStatusRelativeToCached(this, target, new Map()); +} +function _guessExecutionStatusRelativeToCached(base, target, cache) { + const funcParent = { + this: getOuterFunction(base), + target: getOuterFunction(target) + }; + if (funcParent.target.node !== funcParent.this.node) { + return _guessExecutionStatusRelativeToDifferentFunctionsCached(base, funcParent.target, cache); + } + const paths = { + target: target.getAncestry(), + this: base.getAncestry() + }; + if (paths.target.includes(base)) return "after"; + if (paths.this.includes(target)) return "before"; + let commonPath; + const commonIndex = { + target: 0, + this: 0 + }; + while (!commonPath && commonIndex.this < paths.this.length) { + const path = paths.this[commonIndex.this]; + commonIndex.target = paths.target.indexOf(path); + if (commonIndex.target >= 0) { + commonPath = path; + } else { + commonIndex.this++; + } + } + if (!commonPath) { + throw new Error("Internal Babel error - The two compared nodes" + " don't appear to belong to the same program."); + } + if (isExecutionUncertainInList(paths.this, commonIndex.this - 1) || isExecutionUncertainInList(paths.target, commonIndex.target - 1)) { + return "unknown"; + } + const divergence = { + this: paths.this[commonIndex.this - 1], + target: paths.target[commonIndex.target - 1] + }; + if (divergence.target.listKey && divergence.this.listKey && divergence.target.container === divergence.this.container) { + return divergence.target.key > divergence.this.key ? "before" : "after"; + } + const keys = VISITOR_KEYS[commonPath.type]; + const keyPosition = { + this: keys.indexOf(divergence.this.parentKey), + target: keys.indexOf(divergence.target.parentKey) + }; + return keyPosition.target > keyPosition.this ? "before" : "after"; +} +function _guessExecutionStatusRelativeToDifferentFunctionsInternal(base, target, cache) { + if (!target.isFunctionDeclaration()) { + if (_guessExecutionStatusRelativeToCached(base, target, cache) === "before") { + return "before"; + } + return "unknown"; + } else if (target.parentPath.isExportDeclaration()) { + return "unknown"; + } + const binding = target.scope.getBinding(target.node.id.name); + if (!binding.references) return "before"; + const referencePaths = binding.referencePaths; + let allStatus; + for (const path of referencePaths) { + const childOfFunction = !!path.find(path => path.node === target.node); + if (childOfFunction) continue; + if (path.key !== "callee" || !path.parentPath.isCallExpression()) { + return "unknown"; + } + const status = _guessExecutionStatusRelativeToCached(base, path, cache); + if (allStatus && allStatus !== status) { + return "unknown"; + } else { + allStatus = status; + } + } + return allStatus; +} +function _guessExecutionStatusRelativeToDifferentFunctionsCached(base, target, cache) { + let nodeMap = cache.get(base.node); + let cached; + if (!nodeMap) { + cache.set(base.node, nodeMap = new Map()); + } else if (cached = nodeMap.get(target.node)) { + if (cached === SYMBOL_CHECKING) { + return "unknown"; + } + return cached; + } + nodeMap.set(target.node, SYMBOL_CHECKING); + const result = _guessExecutionStatusRelativeToDifferentFunctionsInternal(base, target, cache); + nodeMap.set(target.node, result); + return result; +} +function resolve(dangerous, resolved) { + return _resolve.call(this, dangerous, resolved) || this; +} +function _resolve(dangerous, resolved) { + var _resolved; + if ((_resolved = resolved) != null && _resolved.includes(this)) return; + resolved = resolved || []; + resolved.push(this); + if (this.isVariableDeclarator()) { + if (this.get("id").isIdentifier()) { + return this.get("init").resolve(dangerous, resolved); + } else {} + } else if (this.isReferencedIdentifier()) { + const binding = this.scope.getBinding(this.node.name); + if (!binding) return; + if (!binding.constant) return; + if (binding.kind === "module") return; + if (binding.path !== this) { + const ret = binding.path.resolve(dangerous, resolved); + if (this.find(parent => parent.node === ret.node)) return; + return ret; + } + } else if (this.isTypeCastExpression()) { + return this.get("expression").resolve(dangerous, resolved); + } else if (dangerous && this.isMemberExpression()) { + const targetKey = this.toComputedKey(); + if (!isLiteral(targetKey)) return; + const targetName = targetKey.value; + const target = this.get("object").resolve(dangerous, resolved); + if (target.isObjectExpression()) { + const props = target.get("properties"); + for (const prop of props) { + if (!prop.isProperty()) continue; + const key = prop.get("key"); + let match = prop.isnt("computed") && key.isIdentifier({ + name: targetName + }); + match = match || key.isLiteral({ + value: targetName + }); + if (match) return prop.get("value").resolve(dangerous, resolved); + } + } else if (target.isArrayExpression() && !isNaN(+targetName)) { + const elems = target.get("elements"); + const elem = elems[targetName]; + if (elem) return elem.resolve(dangerous, resolved); + } + } +} +function isConstantExpression() { + if (this.isIdentifier()) { + const binding = this.scope.getBinding(this.node.name); + if (!binding) return false; + return binding.constant; + } + if (this.isLiteral()) { + if (this.isRegExpLiteral()) { + return false; + } + if (this.isTemplateLiteral()) { + return this.get("expressions").every(expression => expression.isConstantExpression()); + } + return true; + } + if (this.isUnaryExpression()) { + if (this.node.operator !== "void") { + return false; + } + return this.get("argument").isConstantExpression(); + } + if (this.isBinaryExpression()) { + const { + operator + } = this.node; + return operator !== "in" && operator !== "instanceof" && this.get("left").isConstantExpression() && this.get("right").isConstantExpression(); + } + if (this.isMemberExpression()) { + return !this.node.computed && this.get("object").isIdentifier({ + name: "Symbol" + }) && !this.scope.hasBinding("Symbol", { + noGlobals: true + }); + } + if (this.isCallExpression()) { + return this.node.arguments.length === 1 && this.get("callee").matchesPattern("Symbol.for") && !this.scope.hasBinding("Symbol", { + noGlobals: true + }) && this.get("arguments")[0].isStringLiteral(); + } + return false; +} +function isInStrictMode() { + const start = this.isProgram() ? this : this.parentPath; + const strictParent = start.find(path => { + if (path.isProgram({ + sourceType: "module" + })) return true; + if (path.isClass()) return true; + if (path.isArrowFunctionExpression() && !path.get("body").isBlockStatement()) { + return false; + } + let body; + if (path.isFunction()) { + body = path.node.body; + } else if (path.isProgram()) { + body = path.node; + } else { + return false; + } + for (const directive of body.directives) { + if (directive.value.value === "use strict") { + return true; + } + } + }); + return !!strictParent; +} + +//# sourceMappingURL=introspection.js.map + + +/***/ }), + +/***/ 95250: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports["default"] = void 0; +var _t = __nccwpck_require__(16535); +var _t2 = _t; +const { + react +} = _t; +const { + cloneNode, + jsxExpressionContainer, + variableDeclaration, + variableDeclarator +} = _t2; +const referenceVisitor = { + ReferencedIdentifier(path, state) { + if (path.isJSXIdentifier() && react.isCompatTag(path.node.name) && !path.parentPath.isJSXMemberExpression()) { + return; + } + if (path.node.name === "this") { + let scope = path.scope; + do { + if (scope.path.isFunction() && !scope.path.isArrowFunctionExpression()) { + break; + } + } while (scope = scope.parent); + if (scope) state.breakOnScopePaths.push(scope.path); + } + const binding = path.scope.getBinding(path.node.name); + if (!binding) return; + for (const violation of binding.constantViolations) { + if (violation.scope !== binding.path.scope) { + state.mutableBinding = true; + path.stop(); + return; + } + } + if (binding !== state.scope.getBinding(path.node.name)) return; + state.bindings[path.node.name] = binding; + } +}; +class PathHoister { + constructor(path, scope) { + this.breakOnScopePaths = void 0; + this.bindings = void 0; + this.mutableBinding = void 0; + this.scopes = void 0; + this.scope = void 0; + this.path = void 0; + this.attachAfter = void 0; + this.breakOnScopePaths = []; + this.bindings = {}; + this.mutableBinding = false; + this.scopes = []; + this.scope = scope; + this.path = path; + this.attachAfter = false; + } + isCompatibleScope(scope) { + for (const key of Object.keys(this.bindings)) { + const binding = this.bindings[key]; + if (!scope.bindingIdentifierEquals(key, binding.identifier)) { + return false; + } + } + return true; + } + getCompatibleScopes() { + let scope = this.path.scope; + do { + if (this.isCompatibleScope(scope)) { + this.scopes.push(scope); + } else { + break; + } + if (this.breakOnScopePaths.includes(scope.path)) { + break; + } + } while (scope = scope.parent); + } + getAttachmentPath() { + let path = this._getAttachmentPath(); + if (!path) return; + let targetScope = path.scope; + if (targetScope.path === path) { + targetScope = path.scope.parent; + } + if (targetScope.path.isProgram() || targetScope.path.isFunction()) { + for (const name of Object.keys(this.bindings)) { + if (!targetScope.hasOwnBinding(name)) continue; + const binding = this.bindings[name]; + if (binding.kind === "param" || binding.path.parentKey === "params") { + continue; + } + const bindingParentPath = this.getAttachmentParentForPath(binding.path); + if (bindingParentPath.key >= path.key) { + this.attachAfter = true; + path = binding.path; + for (const violationPath of binding.constantViolations) { + if (this.getAttachmentParentForPath(violationPath).key > path.key) { + path = violationPath; + } + } + } + } + } + return path; + } + _getAttachmentPath() { + const scopes = this.scopes; + const scope = scopes.pop(); + if (!scope) return; + if (scope.path.isFunction()) { + if (this.hasOwnParamBindings(scope)) { + if (this.scope === scope) return; + const bodies = scope.path.get("body").get("body"); + for (let i = 0; i < bodies.length; i++) { + if (bodies[i].node._blockHoist) continue; + return bodies[i]; + } + } else { + return this.getNextScopeAttachmentParent(); + } + } else if (scope.path.isProgram()) { + return this.getNextScopeAttachmentParent(); + } + } + getNextScopeAttachmentParent() { + const scope = this.scopes.pop(); + if (scope) return this.getAttachmentParentForPath(scope.path); + } + getAttachmentParentForPath(path) { + do { + if (!path.parentPath || Array.isArray(path.container) && path.isStatement()) { + return path; + } + } while (path = path.parentPath); + } + hasOwnParamBindings(scope) { + for (const name of Object.keys(this.bindings)) { + if (!scope.hasOwnBinding(name)) continue; + const binding = this.bindings[name]; + if (binding.kind === "param" && binding.constant) return true; + } + return false; + } + run() { + this.path.traverse(referenceVisitor, this); + if (this.mutableBinding) return; + this.getCompatibleScopes(); + const attachTo = this.getAttachmentPath(); + if (!attachTo) return; + if (attachTo.getFunctionParent() === this.path.getFunctionParent()) return; + let uid = attachTo.scope.generateUidIdentifier("ref"); + const declarator = variableDeclarator(uid, this.path.node); + const insertFn = this.attachAfter ? "insertAfter" : "insertBefore"; + const [attached] = attachTo[insertFn]([attachTo.isVariableDeclarator() ? declarator : variableDeclaration("var", [declarator])]); + const parent = this.path.parentPath; + if (parent.isJSXElement() && this.path.container === parent.node.children) { + uid = jsxExpressionContainer(uid); + } + this.path.replaceWith(cloneNode(uid)); + return attachTo.isVariableDeclarator() ? attached.get("init") : attached.get("declarations.0.init"); + } +} +exports["default"] = PathHoister; + +//# sourceMappingURL=hoister.js.map + + +/***/ }), + +/***/ 60547: +/***/ ((__unused_webpack_module, exports) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports.hooks = void 0; +const hooks = exports.hooks = [function (self, parent) { + const removeParent = self.key === "test" && (parent.isWhile() || parent.isSwitchCase()) || self.key === "declaration" && parent.isExportDeclaration() || self.key === "body" && parent.isLabeledStatement() || self.listKey === "declarations" && parent.isVariableDeclaration() && parent.node.declarations.length === 1 || self.key === "expression" && parent.isExpressionStatement(); + if (removeParent) { + parent.remove(); + return true; + } +}, function (self, parent) { + if (parent.isSequenceExpression() && parent.node.expressions.length === 1) { + parent.replaceWith(parent.node.expressions[0]); + return true; + } +}, function (self, parent) { + if (parent.isBinary()) { + if (self.key === "left") { + parent.replaceWith(parent.node.right); + } else { + parent.replaceWith(parent.node.left); + } + return true; + } +}, function (self, parent) { + if (parent.isIfStatement() && self.key === "consequent" || self.key === "body" && (parent.isLoop() || parent.isArrowFunctionExpression())) { + self.replaceWith({ + type: "BlockStatement", + body: [] + }); + return true; + } +}]; + +//# sourceMappingURL=removal-hooks.js.map + + +/***/ }), + +/***/ 66582: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports.isBindingIdentifier = isBindingIdentifier; +exports.isBlockScoped = isBlockScoped; +exports.isExpression = isExpression; +exports.isFlow = isFlow; +exports.isForAwaitStatement = isForAwaitStatement; +exports.isGenerated = isGenerated; +exports.isPure = isPure; +exports.isReferenced = isReferenced; +exports.isReferencedIdentifier = isReferencedIdentifier; +exports.isReferencedMemberExpression = isReferencedMemberExpression; +exports.isRestProperty = isRestProperty; +exports.isScope = isScope; +exports.isSpreadProperty = isSpreadProperty; +exports.isStatement = isStatement; +exports.isUser = isUser; +exports.isVar = isVar; +var _t = __nccwpck_require__(16535); +const { + isBinding, + isBlockScoped: nodeIsBlockScoped, + isExportDeclaration, + isExpression: nodeIsExpression, + isFlow: nodeIsFlow, + isForStatement, + isForXStatement, + isIdentifier, + isImportDeclaration, + isImportSpecifier, + isJSXIdentifier, + isJSXMemberExpression, + isMemberExpression, + isRestElement: nodeIsRestElement, + isReferenced: nodeIsReferenced, + isScope: nodeIsScope, + isStatement: nodeIsStatement, + isVar: nodeIsVar, + isVariableDeclaration, + react, + isForOfStatement +} = _t; +const { + isCompatTag +} = react; +function isReferencedIdentifier(opts) { + const { + node, + parent + } = this; + if (!isIdentifier(node, opts) && !isJSXMemberExpression(parent, opts)) { + if (isJSXIdentifier(node, opts)) { + if (isCompatTag(node.name)) return false; + } else { + return false; + } + } + return nodeIsReferenced(node, parent, this.parentPath.parent); +} +function isReferencedMemberExpression() { + const { + node, + parent + } = this; + return isMemberExpression(node) && nodeIsReferenced(node, parent); +} +function isBindingIdentifier() { + const { + node, + parent + } = this; + const grandparent = this.parentPath.parent; + return isIdentifier(node) && isBinding(node, parent, grandparent); +} +function isStatement() { + const { + node, + parent + } = this; + if (nodeIsStatement(node)) { + if (isVariableDeclaration(node)) { + if (isForXStatement(parent, { + left: node + })) return false; + if (isForStatement(parent, { + init: node + })) return false; + } + return true; + } else { + return false; + } +} +function isExpression() { + if (this.isIdentifier()) { + return this.isReferencedIdentifier(); + } else { + return nodeIsExpression(this.node); + } +} +function isScope() { + return nodeIsScope(this.node, this.parent); +} +function isReferenced() { + return nodeIsReferenced(this.node, this.parent); +} +function isBlockScoped() { + return nodeIsBlockScoped(this.node); +} +function isVar() { + return nodeIsVar(this.node); +} +function isUser() { + return this.node && !!this.node.loc; +} +function isGenerated() { + return !this.isUser(); +} +function isPure(constantsOnly) { + return this.scope.isPure(this.node, constantsOnly); +} +function isFlow() { + const { + node + } = this; + if (nodeIsFlow(node)) { + return true; + } else if (isImportDeclaration(node)) { + return node.importKind === "type" || node.importKind === "typeof"; + } else if (isExportDeclaration(node)) { + return node.exportKind === "type"; + } else if (isImportSpecifier(node)) { + return node.importKind === "type" || node.importKind === "typeof"; + } else { + return false; + } +} +function isRestProperty() { + var _this$parentPath; + return nodeIsRestElement(this.node) && ((_this$parentPath = this.parentPath) == null ? void 0 : _this$parentPath.isObjectPattern()); +} +function isSpreadProperty() { + var _this$parentPath2; + return nodeIsRestElement(this.node) && ((_this$parentPath2 = this.parentPath) == null ? void 0 : _this$parentPath2.isObjectExpression()); +} +function isForAwaitStatement() { + return isForOfStatement(this.node, { + await: true + }); +} +{ + exports.isExistentialTypeParam = function isExistentialTypeParam() { + throw new Error("`path.isExistentialTypeParam` has been renamed to `path.isExistsTypeAnnotation()` in Babel 7."); + }; + exports.isNumericLiteralTypeAnnotation = function isNumericLiteralTypeAnnotation() { + throw new Error("`path.isNumericLiteralTypeAnnotation()` has been renamed to `path.isNumberLiteralTypeAnnotation()` in Babel 7."); + }; +} + +//# sourceMappingURL=virtual-types-validator.js.map + + +/***/ }), + +/***/ 74425: +/***/ ((__unused_webpack_module, exports) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports.Var = exports.User = exports.Statement = exports.SpreadProperty = exports.Scope = exports.RestProperty = exports.ReferencedMemberExpression = exports.ReferencedIdentifier = exports.Referenced = exports.Pure = exports.NumericLiteralTypeAnnotation = exports.Generated = exports.ForAwaitStatement = exports.Flow = exports.Expression = exports.ExistentialTypeParam = exports.BlockScoped = exports.BindingIdentifier = void 0; +const ReferencedIdentifier = exports.ReferencedIdentifier = ["Identifier", "JSXIdentifier"]; +const ReferencedMemberExpression = exports.ReferencedMemberExpression = ["MemberExpression"]; +const BindingIdentifier = exports.BindingIdentifier = ["Identifier"]; +const Statement = exports.Statement = ["Statement"]; +const Expression = exports.Expression = ["Expression"]; +const Scope = exports.Scope = ["Scopable", "Pattern"]; +const Referenced = exports.Referenced = null; +const BlockScoped = exports.BlockScoped = null; +const Var = exports.Var = ["VariableDeclaration"]; +const User = exports.User = null; +const Generated = exports.Generated = null; +const Pure = exports.Pure = null; +const Flow = exports.Flow = ["Flow", "ImportDeclaration", "ExportDeclaration", "ImportSpecifier"]; +const RestProperty = exports.RestProperty = ["RestElement"]; +const SpreadProperty = exports.SpreadProperty = ["RestElement"]; +const ExistentialTypeParam = exports.ExistentialTypeParam = ["ExistsTypeAnnotation"]; +const NumericLiteralTypeAnnotation = exports.NumericLiteralTypeAnnotation = ["NumberLiteralTypeAnnotation"]; +const ForAwaitStatement = exports.ForAwaitStatement = ["ForOfStatement"]; + +//# sourceMappingURL=virtual-types.js.map + + +/***/ }), + +/***/ 20184: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports._containerInsert = _containerInsert; +exports._containerInsertAfter = _containerInsertAfter; +exports._containerInsertBefore = _containerInsertBefore; +exports._verifyNodeList = _verifyNodeList; +exports.insertAfter = insertAfter; +exports.insertBefore = insertBefore; +exports.pushContainer = pushContainer; +exports.unshiftContainer = unshiftContainer; +exports.updateSiblingKeys = updateSiblingKeys; +var _cache = __nccwpck_require__(6730); +var _index = __nccwpck_require__(91806); +var _context = __nccwpck_require__(74105); +var _removal = __nccwpck_require__(23562); +var _t = __nccwpck_require__(16535); +var _hoister = __nccwpck_require__(95250); +const { + arrowFunctionExpression, + assertExpression, + assignmentExpression, + blockStatement, + callExpression, + cloneNode, + expressionStatement, + isAssignmentExpression, + isCallExpression, + isExportNamedDeclaration, + isExpression, + isIdentifier, + isSequenceExpression, + isSuper, + thisExpression +} = _t; +function insertBefore(nodes_) { + _removal._assertUnremoved.call(this); + const nodes = _verifyNodeList.call(this, nodes_); + const { + parentPath, + parent + } = this; + if (parentPath.isExpressionStatement() || parentPath.isLabeledStatement() || isExportNamedDeclaration(parent) || parentPath.isExportDefaultDeclaration() && this.isDeclaration()) { + return parentPath.insertBefore(nodes); + } else if (this.isNodeType("Expression") && !this.isJSXElement() || parentPath.isForStatement() && this.key === "init") { + if (this.node) nodes.push(this.node); + return this.replaceExpressionWithStatements(nodes); + } else if (Array.isArray(this.container)) { + return _containerInsertBefore.call(this, nodes); + } else if (this.isStatementOrBlock()) { + const node = this.node; + const shouldInsertCurrentNode = node && (!this.isExpressionStatement() || node.expression != null); + this.replaceWith(blockStatement(shouldInsertCurrentNode ? [node] : [])); + return this.unshiftContainer("body", nodes); + } else { + throw new Error("We don't know what to do with this node type. " + "We were previously a Statement but we can't fit in here?"); + } +} +function _containerInsert(from, nodes) { + updateSiblingKeys.call(this, from, nodes.length); + const paths = []; + this.container.splice(from, 0, ...nodes); + for (let i = 0; i < nodes.length; i++) { + var _this$context; + const to = from + i; + const path = this.getSibling(to); + paths.push(path); + if ((_this$context = this.context) != null && _this$context.queue) { + _context.pushContext.call(path, this.context); + } + } + const contexts = _context._getQueueContexts.call(this); + for (const path of paths) { + _context.setScope.call(path); + path.debug("Inserted."); + for (const context of contexts) { + context.maybeQueue(path, true); + } + } + return paths; +} +function _containerInsertBefore(nodes) { + return _containerInsert.call(this, this.key, nodes); +} +function _containerInsertAfter(nodes) { + return _containerInsert.call(this, this.key + 1, nodes); +} +const last = arr => arr[arr.length - 1]; +function isHiddenInSequenceExpression(path) { + return isSequenceExpression(path.parent) && (last(path.parent.expressions) !== path.node || isHiddenInSequenceExpression(path.parentPath)); +} +function isAlmostConstantAssignment(node, scope) { + if (!isAssignmentExpression(node) || !isIdentifier(node.left)) { + return false; + } + const blockScope = scope.getBlockParent(); + return blockScope.hasOwnBinding(node.left.name) && blockScope.getOwnBinding(node.left.name).constantViolations.length <= 1; +} +function insertAfter(nodes_) { + _removal._assertUnremoved.call(this); + if (this.isSequenceExpression()) { + return last(this.get("expressions")).insertAfter(nodes_); + } + const nodes = _verifyNodeList.call(this, nodes_); + const { + parentPath, + parent + } = this; + if (parentPath.isExpressionStatement() || parentPath.isLabeledStatement() || isExportNamedDeclaration(parent) || parentPath.isExportDefaultDeclaration() && this.isDeclaration()) { + return parentPath.insertAfter(nodes.map(node => { + return isExpression(node) ? expressionStatement(node) : node; + })); + } else if (this.isNodeType("Expression") && !this.isJSXElement() && !parentPath.isJSXElement() || parentPath.isForStatement() && this.key === "init") { + const self = this; + if (self.node) { + const node = self.node; + let { + scope + } = this; + if (scope.path.isPattern()) { + assertExpression(node); + self.replaceWith(callExpression(arrowFunctionExpression([], node), [])); + self.get("callee.body").insertAfter(nodes); + return [self]; + } + if (isHiddenInSequenceExpression(self)) { + nodes.unshift(node); + } else if (isCallExpression(node) && isSuper(node.callee)) { + nodes.unshift(node); + nodes.push(thisExpression()); + } else if (isAlmostConstantAssignment(node, scope)) { + nodes.unshift(node); + nodes.push(cloneNode(node.left)); + } else if (scope.isPure(node, true)) { + nodes.push(node); + } else { + if (parentPath.isMethod({ + computed: true, + key: node + })) { + scope = scope.parent; + } + const temp = scope.generateDeclaredUidIdentifier(); + nodes.unshift(expressionStatement(assignmentExpression("=", cloneNode(temp), node))); + nodes.push(expressionStatement(cloneNode(temp))); + } + } + return this.replaceExpressionWithStatements(nodes); + } else if (Array.isArray(this.container)) { + return _containerInsertAfter.call(this, nodes); + } else if (this.isStatementOrBlock()) { + const node = this.node; + const shouldInsertCurrentNode = node && (!this.isExpressionStatement() || node.expression != null); + this.replaceWith(blockStatement(shouldInsertCurrentNode ? [node] : [])); + return this.pushContainer("body", nodes); + } else { + throw new Error("We don't know what to do with this node type. " + "We were previously a Statement but we can't fit in here?"); + } +} +function updateSiblingKeys(fromIndex, incrementBy) { + if (!this.parent) return; + const paths = (0, _cache.getCachedPaths)(this); + if (!paths) return; + for (const [, path] of paths) { + if (typeof path.key === "number" && path.container === this.container && path.key >= fromIndex) { + path.key += incrementBy; + } + } +} +function _verifyNodeList(nodes) { + if (!nodes) { + return []; + } + if (!Array.isArray(nodes)) { + nodes = [nodes]; + } + for (let i = 0; i < nodes.length; i++) { + const node = nodes[i]; + let msg; + if (!node) { + msg = "has falsy node"; + } else if (typeof node !== "object") { + msg = "contains a non-object node"; + } else if (!node.type) { + msg = "without a type"; + } else if (node instanceof _index.default) { + msg = "has a NodePath when it expected a raw object"; + } + if (msg) { + const type = Array.isArray(node) ? "array" : typeof node; + throw new Error(`Node list ${msg} with the index of ${i} and type of ${type}`); + } + } + return nodes; +} +function unshiftContainer(listKey, nodes) { + _removal._assertUnremoved.call(this); + nodes = _verifyNodeList.call(this, nodes); + const path = _index.default.get({ + parentPath: this, + parent: this.node, + container: this.node[listKey], + listKey, + key: 0 + }).setContext(this.context); + return _containerInsertBefore.call(path, nodes); +} +function pushContainer(listKey, nodes) { + _removal._assertUnremoved.call(this); + const verifiedNodes = _verifyNodeList.call(this, nodes); + const container = this.node[listKey]; + const path = _index.default.get({ + parentPath: this, + parent: this.node, + container: container, + listKey, + key: container.length + }).setContext(this.context); + return path.replaceWithMultiple(verifiedNodes); +} +{ + exports.hoist = function hoist(scope = this.scope) { + const hoister = new _hoister.default(this, scope); + return hoister.run(); + }; +} + +//# sourceMappingURL=modification.js.map + + +/***/ }), + +/***/ 23562: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports._assertUnremoved = _assertUnremoved; +exports._callRemovalHooks = _callRemovalHooks; +exports._markRemoved = _markRemoved; +exports._remove = _remove; +exports._removeFromScope = _removeFromScope; +exports.remove = remove; +var _removalHooks = __nccwpck_require__(60547); +var _cache = __nccwpck_require__(6730); +var _replacement = __nccwpck_require__(76178); +var _index = __nccwpck_require__(91806); +var _t = __nccwpck_require__(16535); +var _modification = __nccwpck_require__(20184); +var _context = __nccwpck_require__(74105); +const { + getBindingIdentifiers +} = _t; +function remove() { + var _this$opts; + _assertUnremoved.call(this); + _context.resync.call(this); + if (_callRemovalHooks.call(this)) { + _markRemoved.call(this); + return; + } + if (!((_this$opts = this.opts) != null && _this$opts.noScope)) { + _removeFromScope.call(this); + } + this.shareCommentsWithSiblings(); + _remove.call(this); + _markRemoved.call(this); +} +function _removeFromScope() { + const bindings = getBindingIdentifiers(this.node, false, false, true); + Object.keys(bindings).forEach(name => this.scope.removeBinding(name)); +} +function _callRemovalHooks() { + if (this.parentPath) { + for (const fn of _removalHooks.hooks) { + if (fn(this, this.parentPath)) return true; + } + } +} +function _remove() { + if (Array.isArray(this.container)) { + this.container.splice(this.key, 1); + _modification.updateSiblingKeys.call(this, this.key, -1); + } else { + _replacement._replaceWith.call(this, null); + } +} +function _markRemoved() { + this._traverseFlags |= _index.SHOULD_SKIP | _index.REMOVED; + if (this.parent) { + var _getCachedPaths; + (_getCachedPaths = (0, _cache.getCachedPaths)(this)) == null || _getCachedPaths.delete(this.node); + } + this.node = null; +} +function _assertUnremoved() { + if (this.removed) { + throw this.buildCodeFrameError("NodePath has been removed so is read-only."); + } +} + +//# sourceMappingURL=removal.js.map + + +/***/ }), + +/***/ 76178: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports._replaceWith = _replaceWith; +exports.replaceExpressionWithStatements = replaceExpressionWithStatements; +exports.replaceInline = replaceInline; +exports.replaceWith = replaceWith; +exports.replaceWithMultiple = replaceWithMultiple; +exports.replaceWithSourceString = replaceWithSourceString; +var _codeFrame = __nccwpck_require__(90147); +var _index = __nccwpck_require__(50148); +var _index2 = __nccwpck_require__(91806); +var _cache = __nccwpck_require__(6730); +var _modification = __nccwpck_require__(20184); +var _parser = __nccwpck_require__(5429); +var _t = __nccwpck_require__(16535); +var _context = __nccwpck_require__(74105); +const { + FUNCTION_TYPES, + arrowFunctionExpression, + assignmentExpression, + awaitExpression, + blockStatement, + buildUndefinedNode, + callExpression, + cloneNode, + conditionalExpression, + expressionStatement, + getBindingIdentifiers, + identifier, + inheritLeadingComments, + inheritTrailingComments, + inheritsComments, + isBlockStatement, + isEmptyStatement, + isExpression, + isExpressionStatement, + isIfStatement, + isProgram, + isStatement, + isVariableDeclaration, + removeComments, + returnStatement, + sequenceExpression, + validate, + yieldExpression +} = _t; +function replaceWithMultiple(nodes) { + var _getCachedPaths; + _context.resync.call(this); + nodes = _modification._verifyNodeList.call(this, nodes); + inheritLeadingComments(nodes[0], this.node); + inheritTrailingComments(nodes[nodes.length - 1], this.node); + (_getCachedPaths = (0, _cache.getCachedPaths)(this)) == null || _getCachedPaths.delete(this.node); + this.node = this.container[this.key] = null; + const paths = this.insertAfter(nodes); + if (this.node) { + this.requeue(); + } else { + this.remove(); + } + return paths; +} +function replaceWithSourceString(replacement) { + _context.resync.call(this); + let ast; + try { + replacement = `(${replacement})`; + ast = (0, _parser.parse)(replacement); + } catch (err) { + const loc = err.loc; + if (loc) { + err.message += " - make sure this is an expression.\n" + (0, _codeFrame.codeFrameColumns)(replacement, { + start: { + line: loc.line, + column: loc.column + 1 + } + }); + err.code = "BABEL_REPLACE_SOURCE_ERROR"; + } + throw err; + } + const expressionAST = ast.program.body[0].expression; + _index.default.removeProperties(expressionAST); + return this.replaceWith(expressionAST); +} +function replaceWith(replacementPath) { + _context.resync.call(this); + if (this.removed) { + throw new Error("You can't replace this node, we've already removed it"); + } + let replacement = replacementPath instanceof _index2.default ? replacementPath.node : replacementPath; + if (!replacement) { + throw new Error("You passed `path.replaceWith()` a falsy node, use `path.remove()` instead"); + } + if (this.node === replacement) { + return [this]; + } + if (this.isProgram() && !isProgram(replacement)) { + throw new Error("You can only replace a Program root node with another Program node"); + } + if (Array.isArray(replacement)) { + throw new Error("Don't use `path.replaceWith()` with an array of nodes, use `path.replaceWithMultiple()`"); + } + if (typeof replacement === "string") { + throw new Error("Don't use `path.replaceWith()` with a source string, use `path.replaceWithSourceString()`"); + } + let nodePath = ""; + if (this.isNodeType("Statement") && isExpression(replacement)) { + if (!this.canHaveVariableDeclarationOrExpression() && !this.canSwapBetweenExpressionAndStatement(replacement) && !this.parentPath.isExportDefaultDeclaration()) { + replacement = expressionStatement(replacement); + nodePath = "expression"; + } + } + if (this.isNodeType("Expression") && isStatement(replacement)) { + if (!this.canHaveVariableDeclarationOrExpression() && !this.canSwapBetweenExpressionAndStatement(replacement)) { + return this.replaceExpressionWithStatements([replacement]); + } + } + const oldNode = this.node; + if (oldNode) { + inheritsComments(replacement, oldNode); + removeComments(oldNode); + } + _replaceWith.call(this, replacement); + this.type = replacement.type; + _context.setScope.call(this); + this.requeue(); + return [nodePath ? this.get(nodePath) : this]; +} +function _replaceWith(node) { + var _getCachedPaths2; + if (!this.container) { + throw new ReferenceError("Container is falsy"); + } + if (this.inList) { + validate(this.parent, this.key, [node]); + } else { + validate(this.parent, this.key, node); + } + this.debug(`Replace with ${node == null ? void 0 : node.type}`); + (_getCachedPaths2 = (0, _cache.getCachedPaths)(this)) == null || _getCachedPaths2.set(node, this).delete(this.node); + this.node = this.container[this.key] = node; +} +function replaceExpressionWithStatements(nodes) { + _context.resync.call(this); + const declars = []; + const nodesAsSingleExpression = gatherSequenceExpressions(nodes, declars); + if (nodesAsSingleExpression) { + for (const id of declars) this.scope.push({ + id + }); + return this.replaceWith(nodesAsSingleExpression)[0].get("expressions"); + } + const functionParent = this.getFunctionParent(); + const isParentAsync = functionParent == null ? void 0 : functionParent.node.async; + const isParentGenerator = functionParent == null ? void 0 : functionParent.node.generator; + const container = arrowFunctionExpression([], blockStatement(nodes)); + this.replaceWith(callExpression(container, [])); + const callee = this.get("callee"); + callee.get("body").scope.hoistVariables(id => this.scope.push({ + id + })); + const completionRecords = callee.getCompletionRecords(); + for (const path of completionRecords) { + if (!path.isExpressionStatement()) continue; + const loop = path.findParent(path => path.isLoop()); + if (loop) { + let uid = loop.getData("expressionReplacementReturnUid"); + if (!uid) { + uid = callee.scope.generateDeclaredUidIdentifier("ret"); + callee.get("body").pushContainer("body", returnStatement(cloneNode(uid))); + loop.setData("expressionReplacementReturnUid", uid); + } else { + uid = identifier(uid.name); + } + path.get("expression").replaceWith(assignmentExpression("=", cloneNode(uid), path.node.expression)); + } else { + path.replaceWith(returnStatement(path.node.expression)); + } + } + callee.arrowFunctionToExpression(); + const newCallee = callee; + const needToAwaitFunction = isParentAsync && _index.default.hasType(this.get("callee.body").node, "AwaitExpression", FUNCTION_TYPES); + const needToYieldFunction = isParentGenerator && _index.default.hasType(this.get("callee.body").node, "YieldExpression", FUNCTION_TYPES); + if (needToAwaitFunction) { + newCallee.set("async", true); + if (!needToYieldFunction) { + this.replaceWith(awaitExpression(this.node)); + } + } + if (needToYieldFunction) { + newCallee.set("generator", true); + this.replaceWith(yieldExpression(this.node, true)); + } + return newCallee.get("body.body"); +} +function gatherSequenceExpressions(nodes, declars) { + const exprs = []; + let ensureLastUndefined = true; + for (const node of nodes) { + if (!isEmptyStatement(node)) { + ensureLastUndefined = false; + } + if (isExpression(node)) { + exprs.push(node); + } else if (isExpressionStatement(node)) { + exprs.push(node.expression); + } else if (isVariableDeclaration(node)) { + if (node.kind !== "var") return; + for (const declar of node.declarations) { + const bindings = getBindingIdentifiers(declar); + for (const key of Object.keys(bindings)) { + declars.push(cloneNode(bindings[key])); + } + if (declar.init) { + exprs.push(assignmentExpression("=", declar.id, declar.init)); + } + } + ensureLastUndefined = true; + } else if (isIfStatement(node)) { + const consequent = node.consequent ? gatherSequenceExpressions([node.consequent], declars) : buildUndefinedNode(); + const alternate = node.alternate ? gatherSequenceExpressions([node.alternate], declars) : buildUndefinedNode(); + if (!consequent || !alternate) return; + exprs.push(conditionalExpression(node.test, consequent, alternate)); + } else if (isBlockStatement(node)) { + const body = gatherSequenceExpressions(node.body, declars); + if (!body) return; + exprs.push(body); + } else if (isEmptyStatement(node)) { + if (nodes.indexOf(node) === 0) { + ensureLastUndefined = true; + } + } else { + return; + } + } + if (ensureLastUndefined) exprs.push(buildUndefinedNode()); + if (exprs.length === 1) { + return exprs[0]; + } else { + return sequenceExpression(exprs); + } +} +function replaceInline(nodes) { + _context.resync.call(this); + if (Array.isArray(nodes)) { + if (Array.isArray(this.container)) { + nodes = _modification._verifyNodeList.call(this, nodes); + const paths = _modification._containerInsertAfter.call(this, nodes); + this.remove(); + return paths; + } else { + return this.replaceWithMultiple(nodes); + } + } else { + return this.replaceWith(nodes); + } +} + +//# sourceMappingURL=replacement.js.map + + +/***/ }), + +/***/ 42058: +/***/ ((__unused_webpack_module, exports) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports["default"] = void 0; +class Binding { + constructor({ + identifier, + scope, + path, + kind + }) { + this.identifier = void 0; + this.scope = void 0; + this.path = void 0; + this.kind = void 0; + this.constantViolations = []; + this.constant = true; + this.referencePaths = []; + this.referenced = false; + this.references = 0; + this.identifier = identifier; + this.scope = scope; + this.path = path; + this.kind = kind; + if ((kind === "var" || kind === "hoisted") && isInitInLoop(path)) { + this.reassign(path); + } + this.clearValue(); + } + deoptValue() { + this.clearValue(); + this.hasDeoptedValue = true; + } + setValue(value) { + if (this.hasDeoptedValue) return; + this.hasValue = true; + this.value = value; + } + clearValue() { + this.hasDeoptedValue = false; + this.hasValue = false; + this.value = null; + } + reassign(path) { + this.constant = false; + if (this.constantViolations.includes(path)) { + return; + } + this.constantViolations.push(path); + } + reference(path) { + if (this.referencePaths.includes(path)) { + return; + } + this.referenced = true; + this.references++; + this.referencePaths.push(path); + } + dereference() { + this.references--; + this.referenced = !!this.references; + } +} +exports["default"] = Binding; +function isInitInLoop(path) { + const isFunctionDeclarationOrHasInit = !path.isVariableDeclarator() || path.node.init; + for (let { + parentPath, + key + } = path; parentPath; { + parentPath, + key + } = parentPath) { + if (parentPath.isFunctionParent()) return false; + if (key === "left" && parentPath.isForXStatement() || isFunctionDeclarationOrHasInit && key === "body" && parentPath.isLoop()) { + return true; + } + } + return false; +} + +//# sourceMappingURL=binding.js.map + + +/***/ }), + +/***/ 18171: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports["default"] = void 0; +var _renamer = __nccwpck_require__(99391); +var _index = __nccwpck_require__(50148); +var _binding = __nccwpck_require__(42058); +var _globals = __nccwpck_require__(21384); +var _t = __nccwpck_require__(16535); +var t = _t; +var _cache = __nccwpck_require__(6730); +const { + assignmentExpression, + callExpression, + cloneNode, + getBindingIdentifiers, + identifier, + isArrayExpression, + isBinary, + isCallExpression, + isClass, + isClassBody, + isClassDeclaration, + isExportAllDeclaration, + isExportDefaultDeclaration, + isExportNamedDeclaration, + isFunctionDeclaration, + isIdentifier, + isImportDeclaration, + isLiteral, + isMemberExpression, + isMethod, + isModuleSpecifier, + isNullLiteral, + isObjectExpression, + isProperty, + isPureish, + isRegExpLiteral, + isSuper, + isTaggedTemplateExpression, + isTemplateLiteral, + isThisExpression, + isUnaryExpression, + isVariableDeclaration, + expressionStatement, + matchesPattern, + memberExpression, + numericLiteral, + toIdentifier, + variableDeclaration, + variableDeclarator, + isRecordExpression, + isTupleExpression, + isObjectProperty, + isTopicReference, + isMetaProperty, + isPrivateName, + isExportDeclaration, + buildUndefinedNode, + sequenceExpression +} = _t; +function gatherNodeParts(node, parts) { + switch (node == null ? void 0 : node.type) { + default: + if (isImportDeclaration(node) || isExportDeclaration(node)) { + var _node$specifiers; + if ((isExportAllDeclaration(node) || isExportNamedDeclaration(node) || isImportDeclaration(node)) && node.source) { + gatherNodeParts(node.source, parts); + } else if ((isExportNamedDeclaration(node) || isImportDeclaration(node)) && (_node$specifiers = node.specifiers) != null && _node$specifiers.length) { + for (const e of node.specifiers) gatherNodeParts(e, parts); + } else if ((isExportDefaultDeclaration(node) || isExportNamedDeclaration(node)) && node.declaration) { + gatherNodeParts(node.declaration, parts); + } + } else if (isModuleSpecifier(node)) { + gatherNodeParts(node.local, parts); + } else if (isLiteral(node) && !isNullLiteral(node) && !isRegExpLiteral(node) && !isTemplateLiteral(node)) { + parts.push(node.value); + } + break; + case "MemberExpression": + case "OptionalMemberExpression": + case "JSXMemberExpression": + gatherNodeParts(node.object, parts); + gatherNodeParts(node.property, parts); + break; + case "Identifier": + case "JSXIdentifier": + parts.push(node.name); + break; + case "CallExpression": + case "OptionalCallExpression": + case "NewExpression": + gatherNodeParts(node.callee, parts); + break; + case "ObjectExpression": + case "ObjectPattern": + for (const e of node.properties) { + gatherNodeParts(e, parts); + } + break; + case "SpreadElement": + case "RestElement": + gatherNodeParts(node.argument, parts); + break; + case "ObjectProperty": + case "ObjectMethod": + case "ClassProperty": + case "ClassMethod": + case "ClassPrivateProperty": + case "ClassPrivateMethod": + gatherNodeParts(node.key, parts); + break; + case "ThisExpression": + parts.push("this"); + break; + case "Super": + parts.push("super"); + break; + case "Import": + parts.push("import"); + break; + case "DoExpression": + parts.push("do"); + break; + case "YieldExpression": + parts.push("yield"); + gatherNodeParts(node.argument, parts); + break; + case "AwaitExpression": + parts.push("await"); + gatherNodeParts(node.argument, parts); + break; + case "AssignmentExpression": + gatherNodeParts(node.left, parts); + break; + case "VariableDeclarator": + gatherNodeParts(node.id, parts); + break; + case "FunctionExpression": + case "FunctionDeclaration": + case "ClassExpression": + case "ClassDeclaration": + gatherNodeParts(node.id, parts); + break; + case "PrivateName": + gatherNodeParts(node.id, parts); + break; + case "ParenthesizedExpression": + gatherNodeParts(node.expression, parts); + break; + case "UnaryExpression": + case "UpdateExpression": + gatherNodeParts(node.argument, parts); + break; + case "MetaProperty": + gatherNodeParts(node.meta, parts); + gatherNodeParts(node.property, parts); + break; + case "JSXElement": + gatherNodeParts(node.openingElement, parts); + break; + case "JSXOpeningElement": + gatherNodeParts(node.name, parts); + break; + case "JSXFragment": + gatherNodeParts(node.openingFragment, parts); + break; + case "JSXOpeningFragment": + parts.push("Fragment"); + break; + case "JSXNamespacedName": + gatherNodeParts(node.namespace, parts); + gatherNodeParts(node.name, parts); + break; + } +} +function resetScope(scope) { + scope.references = Object.create(null); + scope.bindings = Object.create(null); + scope.globals = Object.create(null); + scope.uids = Object.create(null); +} +{ + var NOT_LOCAL_BINDING = Symbol.for("should not be considered a local binding"); +} +const collectorVisitor = { + ForStatement(path) { + const declar = path.get("init"); + if (declar.isVar()) { + const { + scope + } = path; + const parentScope = scope.getFunctionParent() || scope.getProgramParent(); + parentScope.registerBinding("var", declar); + } + }, + Declaration(path) { + if (path.isBlockScoped()) return; + if (path.isImportDeclaration()) return; + if (path.isExportDeclaration()) return; + const parent = path.scope.getFunctionParent() || path.scope.getProgramParent(); + parent.registerDeclaration(path); + }, + ImportDeclaration(path) { + const parent = path.scope.getBlockParent(); + parent.registerDeclaration(path); + }, + TSImportEqualsDeclaration(path) { + const parent = path.scope.getBlockParent(); + parent.registerDeclaration(path); + }, + ReferencedIdentifier(path, state) { + if (t.isTSQualifiedName(path.parent) && path.parent.right === path.node) { + return; + } + if (path.parentPath.isTSImportEqualsDeclaration()) return; + state.references.push(path); + }, + ForXStatement(path, state) { + const left = path.get("left"); + if (left.isPattern() || left.isIdentifier()) { + state.constantViolations.push(path); + } else if (left.isVar()) { + const { + scope + } = path; + const parentScope = scope.getFunctionParent() || scope.getProgramParent(); + parentScope.registerBinding("var", left); + } + }, + ExportDeclaration: { + exit(path) { + const { + node, + scope + } = path; + if (isExportAllDeclaration(node)) return; + const declar = node.declaration; + if (isClassDeclaration(declar) || isFunctionDeclaration(declar)) { + const id = declar.id; + if (!id) return; + const binding = scope.getBinding(id.name); + binding == null || binding.reference(path); + } else if (isVariableDeclaration(declar)) { + for (const decl of declar.declarations) { + for (const name of Object.keys(getBindingIdentifiers(decl))) { + const binding = scope.getBinding(name); + binding == null || binding.reference(path); + } + } + } + } + }, + LabeledStatement(path) { + path.scope.getBlockParent().registerDeclaration(path); + }, + AssignmentExpression(path, state) { + state.assignments.push(path); + }, + UpdateExpression(path, state) { + state.constantViolations.push(path); + }, + UnaryExpression(path, state) { + if (path.node.operator === "delete") { + state.constantViolations.push(path); + } + }, + BlockScoped(path) { + let scope = path.scope; + if (scope.path === path) scope = scope.parent; + const parent = scope.getBlockParent(); + parent.registerDeclaration(path); + if (path.isClassDeclaration() && path.node.id) { + const id = path.node.id; + const name = id.name; + path.scope.bindings[name] = path.scope.parent.getBinding(name); + } + }, + CatchClause(path) { + path.scope.registerBinding("let", path); + }, + Function(path) { + const params = path.get("params"); + for (const param of params) { + path.scope.registerBinding("param", param); + } + if (path.isFunctionExpression() && path.node.id && !path.node.id[NOT_LOCAL_BINDING]) { + path.scope.registerBinding("local", path.get("id"), path); + } + }, + ClassExpression(path) { + if (path.node.id && !path.node.id[NOT_LOCAL_BINDING]) { + path.scope.registerBinding("local", path.get("id"), path); + } + }, + TSTypeAnnotation(path) { + path.skip(); + } +}; +let scopeVisitor; +let uid = 0; +class Scope { + constructor(path) { + this.uid = void 0; + this.path = void 0; + this.block = void 0; + this.inited = void 0; + this.labels = void 0; + this.bindings = void 0; + this.references = void 0; + this.globals = void 0; + this.uids = void 0; + this.data = void 0; + this.crawling = void 0; + const { + node + } = path; + const cached = _cache.scope.get(node); + if ((cached == null ? void 0 : cached.path) === path) { + return cached; + } + _cache.scope.set(node, this); + this.uid = uid++; + this.block = node; + this.path = path; + this.labels = new Map(); + this.inited = false; + } + get parent() { + var _parent; + let parent, + path = this.path; + do { + var _path; + const shouldSkip = path.key === "key" || path.listKey === "decorators"; + path = path.parentPath; + if (shouldSkip && path.isMethod()) path = path.parentPath; + if ((_path = path) != null && _path.isScope()) parent = path; + } while (path && !parent); + return (_parent = parent) == null ? void 0 : _parent.scope; + } + generateDeclaredUidIdentifier(name) { + const id = this.generateUidIdentifier(name); + this.push({ + id + }); + return cloneNode(id); + } + generateUidIdentifier(name) { + return identifier(this.generateUid(name)); + } + generateUid(name = "temp") { + name = toIdentifier(name).replace(/^_+/, "").replace(/\d+$/g, ""); + let uid; + let i = 0; + do { + uid = `_${name}`; + if (i >= 11) uid += i - 1;else if (i >= 9) uid += i - 9;else if (i >= 1) uid += i + 1; + i++; + } while (this.hasLabel(uid) || this.hasBinding(uid) || this.hasGlobal(uid) || this.hasReference(uid)); + const program = this.getProgramParent(); + program.references[uid] = true; + program.uids[uid] = true; + return uid; + } + generateUidBasedOnNode(node, defaultName) { + const parts = []; + gatherNodeParts(node, parts); + let id = parts.join("$"); + id = id.replace(/^_/, "") || defaultName || "ref"; + return this.generateUid(id.slice(0, 20)); + } + generateUidIdentifierBasedOnNode(node, defaultName) { + return identifier(this.generateUidBasedOnNode(node, defaultName)); + } + isStatic(node) { + if (isThisExpression(node) || isSuper(node) || isTopicReference(node)) { + return true; + } + if (isIdentifier(node)) { + const binding = this.getBinding(node.name); + if (binding) { + return binding.constant; + } else { + return this.hasBinding(node.name); + } + } + return false; + } + maybeGenerateMemoised(node, dontPush) { + if (this.isStatic(node)) { + return null; + } else { + const id = this.generateUidIdentifierBasedOnNode(node); + if (!dontPush) { + this.push({ + id + }); + return cloneNode(id); + } + return id; + } + } + checkBlockScopedCollisions(local, kind, name, id) { + if (kind === "param") return; + if (local.kind === "local") return; + const duplicate = kind === "let" || local.kind === "let" || local.kind === "const" || local.kind === "module" || local.kind === "param" && kind === "const"; + if (duplicate) { + throw this.path.hub.buildError(id, `Duplicate declaration "${name}"`, TypeError); + } + } + rename(oldName, newName) { + const binding = this.getBinding(oldName); + if (binding) { + newName || (newName = this.generateUidIdentifier(oldName).name); + const renamer = new _renamer.default(binding, oldName, newName); + { + renamer.rename(arguments[2]); + } + } + } + dump() { + const sep = "-".repeat(60); + console.log(sep); + let scope = this; + do { + console.log("#", scope.block.type); + for (const name of Object.keys(scope.bindings)) { + const binding = scope.bindings[name]; + console.log(" -", name, { + constant: binding.constant, + references: binding.references, + violations: binding.constantViolations.length, + kind: binding.kind + }); + } + } while (scope = scope.parent); + console.log(sep); + } + hasLabel(name) { + return !!this.getLabel(name); + } + getLabel(name) { + return this.labels.get(name); + } + registerLabel(path) { + this.labels.set(path.node.label.name, path); + } + registerDeclaration(path) { + if (path.isLabeledStatement()) { + this.registerLabel(path); + } else if (path.isFunctionDeclaration()) { + this.registerBinding("hoisted", path.get("id"), path); + } else if (path.isVariableDeclaration()) { + const declarations = path.get("declarations"); + const { + kind + } = path.node; + for (const declar of declarations) { + this.registerBinding(kind === "using" || kind === "await using" ? "const" : kind, declar); + } + } else if (path.isClassDeclaration()) { + if (path.node.declare) return; + this.registerBinding("let", path); + } else if (path.isImportDeclaration()) { + const isTypeDeclaration = path.node.importKind === "type" || path.node.importKind === "typeof"; + const specifiers = path.get("specifiers"); + for (const specifier of specifiers) { + const isTypeSpecifier = isTypeDeclaration || specifier.isImportSpecifier() && (specifier.node.importKind === "type" || specifier.node.importKind === "typeof"); + this.registerBinding(isTypeSpecifier ? "unknown" : "module", specifier); + } + } else if (path.isExportDeclaration()) { + const declar = path.get("declaration"); + if (declar.isClassDeclaration() || declar.isFunctionDeclaration() || declar.isVariableDeclaration()) { + this.registerDeclaration(declar); + } + } else { + this.registerBinding("unknown", path); + } + } + buildUndefinedNode() { + return buildUndefinedNode(); + } + registerConstantViolation(path) { + const ids = path.getAssignmentIdentifiers(); + for (const name of Object.keys(ids)) { + var _this$getBinding; + (_this$getBinding = this.getBinding(name)) == null || _this$getBinding.reassign(path); + } + } + registerBinding(kind, path, bindingPath = path) { + if (!kind) throw new ReferenceError("no `kind`"); + if (path.isVariableDeclaration()) { + const declarators = path.get("declarations"); + for (const declar of declarators) { + this.registerBinding(kind, declar); + } + return; + } + const parent = this.getProgramParent(); + const ids = path.getOuterBindingIdentifiers(true); + for (const name of Object.keys(ids)) { + parent.references[name] = true; + for (const id of ids[name]) { + const local = this.getOwnBinding(name); + if (local) { + if (local.identifier === id) continue; + this.checkBlockScopedCollisions(local, kind, name, id); + } + if (local) { + local.reassign(bindingPath); + } else { + this.bindings[name] = new _binding.default({ + identifier: id, + scope: this, + path: bindingPath, + kind: kind + }); + } + } + } + } + addGlobal(node) { + this.globals[node.name] = node; + } + hasUid(name) { + let scope = this; + do { + if (scope.uids[name]) return true; + } while (scope = scope.parent); + return false; + } + hasGlobal(name) { + let scope = this; + do { + if (scope.globals[name]) return true; + } while (scope = scope.parent); + return false; + } + hasReference(name) { + return !!this.getProgramParent().references[name]; + } + isPure(node, constantsOnly) { + if (isIdentifier(node)) { + const binding = this.getBinding(node.name); + if (!binding) return false; + if (constantsOnly) return binding.constant; + return true; + } else if (isThisExpression(node) || isMetaProperty(node) || isTopicReference(node) || isPrivateName(node)) { + return true; + } else if (isClass(node)) { + var _node$decorators; + if (node.superClass && !this.isPure(node.superClass, constantsOnly)) { + return false; + } + if (((_node$decorators = node.decorators) == null ? void 0 : _node$decorators.length) > 0) { + return false; + } + return this.isPure(node.body, constantsOnly); + } else if (isClassBody(node)) { + for (const method of node.body) { + if (!this.isPure(method, constantsOnly)) return false; + } + return true; + } else if (isBinary(node)) { + return this.isPure(node.left, constantsOnly) && this.isPure(node.right, constantsOnly); + } else if (isArrayExpression(node) || isTupleExpression(node)) { + for (const elem of node.elements) { + if (elem !== null && !this.isPure(elem, constantsOnly)) return false; + } + return true; + } else if (isObjectExpression(node) || isRecordExpression(node)) { + for (const prop of node.properties) { + if (!this.isPure(prop, constantsOnly)) return false; + } + return true; + } else if (isMethod(node)) { + var _node$decorators2; + if (node.computed && !this.isPure(node.key, constantsOnly)) return false; + if (((_node$decorators2 = node.decorators) == null ? void 0 : _node$decorators2.length) > 0) { + return false; + } + return true; + } else if (isProperty(node)) { + var _node$decorators3; + if (node.computed && !this.isPure(node.key, constantsOnly)) return false; + if (((_node$decorators3 = node.decorators) == null ? void 0 : _node$decorators3.length) > 0) { + return false; + } + if (isObjectProperty(node) || node.static) { + if (node.value !== null && !this.isPure(node.value, constantsOnly)) { + return false; + } + } + return true; + } else if (isUnaryExpression(node)) { + return this.isPure(node.argument, constantsOnly); + } else if (isTemplateLiteral(node)) { + for (const expression of node.expressions) { + if (!this.isPure(expression, constantsOnly)) return false; + } + return true; + } else if (isTaggedTemplateExpression(node)) { + return matchesPattern(node.tag, "String.raw") && !this.hasBinding("String", { + noGlobals: true + }) && this.isPure(node.quasi, constantsOnly); + } else if (isMemberExpression(node)) { + return !node.computed && isIdentifier(node.object) && node.object.name === "Symbol" && isIdentifier(node.property) && node.property.name !== "for" && !this.hasBinding("Symbol", { + noGlobals: true + }); + } else if (isCallExpression(node)) { + return matchesPattern(node.callee, "Symbol.for") && !this.hasBinding("Symbol", { + noGlobals: true + }) && node.arguments.length === 1 && t.isStringLiteral(node.arguments[0]); + } else { + return isPureish(node); + } + } + setData(key, val) { + return this.data[key] = val; + } + getData(key) { + let scope = this; + do { + const data = scope.data[key]; + if (data != null) return data; + } while (scope = scope.parent); + } + removeData(key) { + let scope = this; + do { + const data = scope.data[key]; + if (data != null) scope.data[key] = null; + } while (scope = scope.parent); + } + init() { + if (!this.inited) { + this.inited = true; + this.crawl(); + } + } + crawl() { + const path = this.path; + resetScope(this); + this.data = Object.create(null); + let scope = this; + do { + if (scope.crawling) return; + if (scope.path.isProgram()) { + break; + } + } while (scope = scope.parent); + const programParent = scope; + const state = { + references: [], + constantViolations: [], + assignments: [] + }; + this.crawling = true; + scopeVisitor || (scopeVisitor = _index.default.visitors.merge([{ + Scope(path) { + resetScope(path.scope); + } + }, collectorVisitor])); + if (path.type !== "Program") { + for (const visit of scopeVisitor.enter) { + visit.call(state, path, state); + } + const typeVisitors = scopeVisitor[path.type]; + if (typeVisitors) { + for (const visit of typeVisitors.enter) { + visit.call(state, path, state); + } + } + } + path.traverse(scopeVisitor, state); + this.crawling = false; + for (const path of state.assignments) { + const ids = path.getAssignmentIdentifiers(); + for (const name of Object.keys(ids)) { + if (path.scope.getBinding(name)) continue; + programParent.addGlobal(ids[name]); + } + path.scope.registerConstantViolation(path); + } + for (const ref of state.references) { + const binding = ref.scope.getBinding(ref.node.name); + if (binding) { + binding.reference(ref); + } else { + programParent.addGlobal(ref.node); + } + } + for (const path of state.constantViolations) { + path.scope.registerConstantViolation(path); + } + } + push(opts) { + let path = this.path; + if (path.isPattern()) { + path = this.getPatternParent().path; + } else if (!path.isBlockStatement() && !path.isProgram()) { + path = this.getBlockParent().path; + } + if (path.isSwitchStatement()) { + path = (this.getFunctionParent() || this.getProgramParent()).path; + } + const { + init, + unique, + kind = "var", + id + } = opts; + if (!init && !unique && (kind === "var" || kind === "let") && path.isFunction() && !path.node.name && isCallExpression(path.parent, { + callee: path.node + }) && path.parent.arguments.length <= path.node.params.length && isIdentifier(id)) { + path.pushContainer("params", id); + path.scope.registerBinding("param", path.get("params")[path.node.params.length - 1]); + return; + } + if (path.isLoop() || path.isCatchClause() || path.isFunction()) { + path.ensureBlock(); + path = path.get("body"); + } + const blockHoist = opts._blockHoist == null ? 2 : opts._blockHoist; + const dataKey = `declaration:${kind}:${blockHoist}`; + let declarPath = !unique && path.getData(dataKey); + if (!declarPath) { + const declar = variableDeclaration(kind, []); + declar._blockHoist = blockHoist; + [declarPath] = path.unshiftContainer("body", [declar]); + if (!unique) path.setData(dataKey, declarPath); + } + const declarator = variableDeclarator(id, init); + const len = declarPath.node.declarations.push(declarator); + path.scope.registerBinding(kind, declarPath.get("declarations")[len - 1]); + } + getProgramParent() { + let scope = this; + do { + if (scope.path.isProgram()) { + return scope; + } + } while (scope = scope.parent); + throw new Error("Couldn't find a Program"); + } + getFunctionParent() { + let scope = this; + do { + if (scope.path.isFunctionParent()) { + return scope; + } + } while (scope = scope.parent); + return null; + } + getBlockParent() { + let scope = this; + do { + if (scope.path.isBlockParent()) { + return scope; + } + } while (scope = scope.parent); + throw new Error("We couldn't find a BlockStatement, For, Switch, Function, Loop or Program..."); + } + getPatternParent() { + let scope = this; + do { + if (!scope.path.isPattern()) { + return scope.getBlockParent(); + } + } while (scope = scope.parent.parent); + throw new Error("We couldn't find a BlockStatement, For, Switch, Function, Loop or Program..."); + } + getAllBindings() { + const ids = Object.create(null); + let scope = this; + do { + for (const key of Object.keys(scope.bindings)) { + if (key in ids === false) { + ids[key] = scope.bindings[key]; + } + } + scope = scope.parent; + } while (scope); + return ids; + } + bindingIdentifierEquals(name, node) { + return this.getBindingIdentifier(name) === node; + } + getBinding(name) { + let scope = this; + let previousPath; + do { + const binding = scope.getOwnBinding(name); + if (binding) { + var _previousPath; + if ((_previousPath = previousPath) != null && _previousPath.isPattern() && binding.kind !== "param" && binding.kind !== "local") {} else { + return binding; + } + } else if (!binding && name === "arguments" && scope.path.isFunction() && !scope.path.isArrowFunctionExpression()) { + break; + } + previousPath = scope.path; + } while (scope = scope.parent); + } + getOwnBinding(name) { + return this.bindings[name]; + } + getBindingIdentifier(name) { + var _this$getBinding2; + return (_this$getBinding2 = this.getBinding(name)) == null ? void 0 : _this$getBinding2.identifier; + } + getOwnBindingIdentifier(name) { + const binding = this.bindings[name]; + return binding == null ? void 0 : binding.identifier; + } + hasOwnBinding(name) { + return !!this.getOwnBinding(name); + } + hasBinding(name, opts) { + if (!name) return false; + let noGlobals; + let noUids; + let upToScope; + if (typeof opts === "object") { + noGlobals = opts.noGlobals; + noUids = opts.noUids; + upToScope = opts.upToScope; + } else if (typeof opts === "boolean") { + noGlobals = opts; + } + let scope = this; + do { + if (upToScope === scope) { + break; + } + if (scope.hasOwnBinding(name)) { + return true; + } + } while (scope = scope.parent); + if (!noUids && this.hasUid(name)) return true; + if (!noGlobals && Scope.globals.includes(name)) return true; + if (!noGlobals && Scope.contextVariables.includes(name)) return true; + return false; + } + parentHasBinding(name, opts) { + var _this$parent; + return (_this$parent = this.parent) == null ? void 0 : _this$parent.hasBinding(name, opts); + } + moveBindingTo(name, scope) { + const info = this.getBinding(name); + if (info) { + info.scope.removeOwnBinding(name); + info.scope = scope; + scope.bindings[name] = info; + } + } + removeOwnBinding(name) { + delete this.bindings[name]; + } + removeBinding(name) { + var _this$getBinding3; + (_this$getBinding3 = this.getBinding(name)) == null || _this$getBinding3.scope.removeOwnBinding(name); + let scope = this; + do { + if (scope.uids[name]) { + scope.uids[name] = false; + } + } while (scope = scope.parent); + } + hoistVariables(emit = id => this.push({ + id + })) { + this.crawl(); + const seen = new Set(); + for (const name of Object.keys(this.bindings)) { + const binding = this.bindings[name]; + if (!binding) continue; + const { + path + } = binding; + if (!path.isVariableDeclarator()) continue; + const { + parent, + parentPath + } = path; + if (parent.kind !== "var" || seen.has(parent)) continue; + seen.add(path.parent); + let firstId; + const init = []; + for (const decl of parent.declarations) { + firstId != null ? firstId : firstId = decl.id; + if (decl.init) { + init.push(assignmentExpression("=", decl.id, decl.init)); + } + const ids = Object.keys(getBindingIdentifiers(decl, false, true, true)); + for (const name of ids) { + emit(identifier(name), decl.init != null); + } + } + if (parentPath.parentPath.isFor({ + left: parent + })) { + parentPath.replaceWith(firstId); + } else if (init.length === 0) { + parentPath.remove(); + } else { + const expr = init.length === 1 ? init[0] : sequenceExpression(init); + if (parentPath.parentPath.isForStatement({ + init: parent + })) { + parentPath.replaceWith(expr); + } else { + parentPath.replaceWith(expressionStatement(expr)); + } + } + } + } +} +exports["default"] = Scope; +Scope.globals = Object.keys(_globals.builtin); +Scope.contextVariables = ["arguments", "undefined", "Infinity", "NaN"]; +{ + Scope.prototype._renameFromMap = function _renameFromMap(map, oldName, newName, value) { + if (map[oldName]) { + map[newName] = value; + map[oldName] = null; + } + }; + Scope.prototype.traverse = function (node, opts, state) { + (0, _index.default)(node, opts, this, state, this.path); + }; + Scope.prototype._generateUid = function _generateUid(name, i) { + let id = name; + if (i > 1) id += i; + return `_${id}`; + }; + Scope.prototype.toArray = function toArray(node, i, arrayLikeIsIterable) { + if (isIdentifier(node)) { + const binding = this.getBinding(node.name); + if (binding != null && binding.constant && binding.path.isGenericType("Array")) { + return node; + } + } + if (isArrayExpression(node)) { + return node; + } + if (isIdentifier(node, { + name: "arguments" + })) { + return callExpression(memberExpression(memberExpression(memberExpression(identifier("Array"), identifier("prototype")), identifier("slice")), identifier("call")), [node]); + } + let helperName; + const args = [node]; + if (i === true) { + helperName = "toConsumableArray"; + } else if (typeof i === "number") { + args.push(numericLiteral(i)); + helperName = "slicedToArray"; + } else { + helperName = "toArray"; + } + if (arrayLikeIsIterable) { + args.unshift(this.path.hub.addHelper(helperName)); + helperName = "maybeArrayLike"; + } + return callExpression(this.path.hub.addHelper(helperName), args); + }; + Scope.prototype.getAllBindingsOfKind = function getAllBindingsOfKind(...kinds) { + const ids = Object.create(null); + for (const kind of kinds) { + let scope = this; + do { + for (const name of Object.keys(scope.bindings)) { + const binding = scope.bindings[name]; + if (binding.kind === kind) ids[name] = binding; + } + scope = scope.parent; + } while (scope); + } + return ids; + }; + Object.defineProperties(Scope.prototype, { + parentBlock: { + configurable: true, + enumerable: true, + get() { + return this.path.parent; + } + }, + hub: { + configurable: true, + enumerable: true, + get() { + return this.path.hub; + } + } + }); +} + +//# sourceMappingURL=index.js.map + + +/***/ }), + +/***/ 99391: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports["default"] = void 0; +var t = __nccwpck_require__(16535); +var _t = t; +var _traverseNode = __nccwpck_require__(95469); +var _visitors = __nccwpck_require__(38133); +var _context = __nccwpck_require__(74105); +const { + getAssignmentIdentifiers +} = _t; +const renameVisitor = { + ReferencedIdentifier({ + node + }, state) { + if (node.name === state.oldName) { + node.name = state.newName; + } + }, + Scope(path, state) { + if (!path.scope.bindingIdentifierEquals(state.oldName, state.binding.identifier)) { + path.skip(); + if (path.isMethod()) { + if (!path.requeueComputedKeyAndDecorators) { + _context.requeueComputedKeyAndDecorators.call(path); + } else { + path.requeueComputedKeyAndDecorators(); + } + } + } + }, + ObjectProperty({ + node, + scope + }, state) { + const { + name + } = node.key; + if (node.shorthand && (name === state.oldName || name === state.newName) && scope.getBindingIdentifier(name) === state.binding.identifier) { + node.shorthand = false; + { + var _node$extra; + if ((_node$extra = node.extra) != null && _node$extra.shorthand) node.extra.shorthand = false; + } + } + }, + "AssignmentExpression|Declaration|VariableDeclarator"(path, state) { + if (path.isVariableDeclaration()) return; + const ids = path.isAssignmentExpression() ? getAssignmentIdentifiers(path.node) : path.getOuterBindingIdentifiers(); + for (const name in ids) { + if (name === state.oldName) ids[name].name = state.newName; + } + } +}; +class Renamer { + constructor(binding, oldName, newName) { + this.newName = newName; + this.oldName = oldName; + this.binding = binding; + } + maybeConvertFromExportDeclaration(parentDeclar) { + const maybeExportDeclar = parentDeclar.parentPath; + if (!maybeExportDeclar.isExportDeclaration()) { + return; + } + if (maybeExportDeclar.isExportDefaultDeclaration()) { + const { + declaration + } = maybeExportDeclar.node; + if (t.isDeclaration(declaration) && !declaration.id) { + return; + } + } + if (maybeExportDeclar.isExportAllDeclaration()) { + return; + } + maybeExportDeclar.splitExportDeclaration(); + } + maybeConvertFromClassFunctionDeclaration(path) { + return path; + } + maybeConvertFromClassFunctionExpression(path) { + return path; + } + rename() { + const { + binding, + oldName, + newName + } = this; + const { + scope, + path + } = binding; + const parentDeclar = path.find(path => path.isDeclaration() || path.isFunctionExpression() || path.isClassExpression()); + if (parentDeclar) { + const bindingIds = parentDeclar.getOuterBindingIdentifiers(); + if (bindingIds[oldName] === binding.identifier) { + this.maybeConvertFromExportDeclaration(parentDeclar); + } + } + const blockToTraverse = arguments[0] || scope.block; + const skipKeys = { + discriminant: true + }; + if (t.isMethod(blockToTraverse)) { + if (blockToTraverse.computed) { + skipKeys.key = true; + } + if (!t.isObjectMethod(blockToTraverse)) { + skipKeys.decorators = true; + } + } + (0, _traverseNode.traverseNode)(blockToTraverse, (0, _visitors.explode)(renameVisitor), scope, this, scope.path, skipKeys); + if (!arguments[0]) { + scope.removeOwnBinding(oldName); + scope.bindings[newName] = binding; + this.binding.identifier.name = newName; + } + if (parentDeclar) { + this.maybeConvertFromClassFunctionDeclaration(path); + this.maybeConvertFromClassFunctionExpression(path); + } + } +} +exports["default"] = Renamer; + +//# sourceMappingURL=renamer.js.map + + +/***/ }), + +/***/ 95469: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports.traverseNode = traverseNode; +var _context = __nccwpck_require__(49767); +var _index = __nccwpck_require__(91806); +var _t = __nccwpck_require__(16535); +var _context2 = __nccwpck_require__(74105); +const { + VISITOR_KEYS +} = _t; +function _visitPaths(ctx, paths) { + ctx.queue = paths; + ctx.priorityQueue = []; + const visited = new Set(); + let stop = false; + let visitIndex = 0; + for (; visitIndex < paths.length;) { + const path = paths[visitIndex]; + visitIndex++; + _context2.resync.call(path); + if (path.contexts.length === 0 || path.contexts[path.contexts.length - 1] !== ctx) { + _context2.pushContext.call(path, ctx); + } + if (path.key === null) continue; + const { + node + } = path; + if (visited.has(node)) continue; + if (node) visited.add(node); + if (_visit(ctx, path)) { + stop = true; + break; + } + if (ctx.priorityQueue.length) { + stop = _visitPaths(ctx, ctx.priorityQueue); + ctx.priorityQueue = []; + ctx.queue = paths; + if (stop) break; + } + } + for (let i = 0; i < visitIndex; i++) { + _context2.popContext.call(paths[i]); + } + ctx.queue = null; + return stop; +} +function _visit(ctx, path) { + var _opts$denylist; + const node = path.node; + if (!node) { + return false; + } + const opts = ctx.opts; + const denylist = (_opts$denylist = opts.denylist) != null ? _opts$denylist : opts.blacklist; + if (denylist != null && denylist.includes(node.type)) { + return false; + } + if (opts.shouldSkip != null && opts.shouldSkip(path)) { + return false; + } + if (path.shouldSkip) return path.shouldStop; + if (_context2._call.call(path, opts.enter)) return path.shouldStop; + if (path.node) { + var _opts$node$type; + if (_context2._call.call(path, (_opts$node$type = opts[node.type]) == null ? void 0 : _opts$node$type.enter)) return path.shouldStop; + } + path.shouldStop = _traverse(path.node, opts, path.scope, ctx.state, path, path.skipKeys); + if (path.node) { + if (_context2._call.call(path, opts.exit)) return true; + } + if (path.node) { + var _opts$node$type2; + _context2._call.call(path, (_opts$node$type2 = opts[node.type]) == null ? void 0 : _opts$node$type2.exit); + } + return path.shouldStop; +} +function _traverse(node, opts, scope, state, path, skipKeys, visitSelf) { + const keys = VISITOR_KEYS[node.type]; + if (!(keys != null && keys.length)) return false; + const ctx = new _context.default(scope, opts, state, path); + if (visitSelf) { + if (skipKeys != null && skipKeys[path.parentKey]) return false; + return _visitPaths(ctx, [path]); + } + for (const key of keys) { + if (skipKeys != null && skipKeys[key]) continue; + const prop = node[key]; + if (!prop) continue; + if (Array.isArray(prop)) { + if (!prop.length) continue; + const paths = []; + for (let i = 0; i < prop.length; i++) { + const childPath = _index.default.get({ + parentPath: path, + parent: node, + container: prop, + key: i, + listKey: key + }); + paths.push(childPath); + } + if (_visitPaths(ctx, paths)) return true; + } else { + if (_visitPaths(ctx, [_index.default.get({ + parentPath: path, + parent: node, + container: node, + key, + listKey: null + })])) { + return true; + } + } + } + return false; +} +function traverseNode(node, opts, scope, state, path, skipKeys, visitSelf) { + ; + const keys = VISITOR_KEYS[node.type]; + if (!keys) return false; + const context = new _context.default(scope, opts, state, path); + if (visitSelf) { + if (skipKeys != null && skipKeys[path.parentKey]) return false; + return context.visitQueue([path]); + } + for (const key of keys) { + if (skipKeys != null && skipKeys[key]) continue; + if (context.visit(node, key)) { + return true; + } + } + return false; +} + +//# sourceMappingURL=traverse-node.js.map + + +/***/ }), + +/***/ 38133: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports.environmentVisitor = environmentVisitor; +exports.explode = explode$1; +exports.isExplodedVisitor = isExplodedVisitor; +exports.merge = merge; +exports.verify = verify$1; +var virtualTypes = __nccwpck_require__(74425); +var virtualTypesValidators = __nccwpck_require__(66582); +var _t = __nccwpck_require__(16535); +var _context = __nccwpck_require__(74105); +const { + DEPRECATED_KEYS, + DEPRECATED_ALIASES, + FLIPPED_ALIAS_KEYS, + TYPES, + __internal__deprecationWarning: deprecationWarning +} = _t; +function isVirtualType(type) { + return type in virtualTypes; +} +function isExplodedVisitor(visitor) { + return visitor == null ? void 0 : visitor._exploded; +} +function explode$1(visitor) { + if (isExplodedVisitor(visitor)) return visitor; + visitor._exploded = true; + for (const nodeType of Object.keys(visitor)) { + if (shouldIgnoreKey(nodeType)) continue; + const parts = nodeType.split("|"); + if (parts.length === 1) continue; + const fns = visitor[nodeType]; + delete visitor[nodeType]; + for (const part of parts) { + visitor[part] = fns; + } + } + verify$1(visitor); + delete visitor.__esModule; + ensureEntranceObjects(visitor); + ensureCallbackArrays(visitor); + for (const nodeType of Object.keys(visitor)) { + if (shouldIgnoreKey(nodeType)) continue; + if (!isVirtualType(nodeType)) continue; + const fns = visitor[nodeType]; + for (const type of Object.keys(fns)) { + fns[type] = wrapCheck(nodeType, fns[type]); + } + delete visitor[nodeType]; + const types = virtualTypes[nodeType]; + if (types !== null) { + for (const type of types) { + if (visitor[type]) { + mergePair(visitor[type], fns); + } else { + visitor[type] = fns; + } + } + } else { + mergePair(visitor, fns); + } + } + for (const nodeType of Object.keys(visitor)) { + if (shouldIgnoreKey(nodeType)) continue; + let aliases = FLIPPED_ALIAS_KEYS[nodeType]; + if (nodeType in DEPRECATED_KEYS) { + const deprecatedKey = DEPRECATED_KEYS[nodeType]; + deprecationWarning(nodeType, deprecatedKey, "Visitor "); + aliases = [deprecatedKey]; + } else if (nodeType in DEPRECATED_ALIASES) { + const deprecatedAlias = DEPRECATED_ALIASES[nodeType]; + deprecationWarning(nodeType, deprecatedAlias, "Visitor "); + aliases = FLIPPED_ALIAS_KEYS[deprecatedAlias]; + } + if (!aliases) continue; + const fns = visitor[nodeType]; + delete visitor[nodeType]; + for (const alias of aliases) { + const existing = visitor[alias]; + if (existing) { + mergePair(existing, fns); + } else { + visitor[alias] = Object.assign({}, fns); + } + } + } + for (const nodeType of Object.keys(visitor)) { + if (shouldIgnoreKey(nodeType)) continue; + ensureCallbackArrays(visitor[nodeType]); + } + return visitor; +} +function verify$1(visitor) { + if (visitor._verified) return; + if (typeof visitor === "function") { + throw new Error("You passed `traverse()` a function when it expected a visitor object, " + "are you sure you didn't mean `{ enter: Function }`?"); + } + for (const nodeType of Object.keys(visitor)) { + if (nodeType === "enter" || nodeType === "exit") { + validateVisitorMethods(nodeType, visitor[nodeType]); + } + if (shouldIgnoreKey(nodeType)) continue; + if (!TYPES.includes(nodeType)) { + throw new Error(`You gave us a visitor for the node type ${nodeType} but it's not a valid type in @babel/traverse ${"7.27.1"}`); + } + const visitors = visitor[nodeType]; + if (typeof visitors === "object") { + for (const visitorKey of Object.keys(visitors)) { + if (visitorKey === "enter" || visitorKey === "exit") { + validateVisitorMethods(`${nodeType}.${visitorKey}`, visitors[visitorKey]); + } else { + throw new Error("You passed `traverse()` a visitor object with the property " + `${nodeType} that has the invalid property ${visitorKey}`); + } + } + } + } + visitor._verified = true; +} +function validateVisitorMethods(path, val) { + const fns = [].concat(val); + for (const fn of fns) { + if (typeof fn !== "function") { + throw new TypeError(`Non-function found defined in ${path} with type ${typeof fn}`); + } + } +} +function merge(visitors, states = [], wrapper) { + const mergedVisitor = { + _verified: true, + _exploded: true + }; + { + Object.defineProperty(mergedVisitor, "_exploded", { + enumerable: false + }); + Object.defineProperty(mergedVisitor, "_verified", { + enumerable: false + }); + } + for (let i = 0; i < visitors.length; i++) { + const visitor = explode$1(visitors[i]); + const state = states[i]; + let topVisitor = visitor; + if (state || wrapper) { + topVisitor = wrapWithStateOrWrapper(topVisitor, state, wrapper); + } + mergePair(mergedVisitor, topVisitor); + for (const key of Object.keys(visitor)) { + if (shouldIgnoreKey(key)) continue; + let typeVisitor = visitor[key]; + if (state || wrapper) { + typeVisitor = wrapWithStateOrWrapper(typeVisitor, state, wrapper); + } + const nodeVisitor = mergedVisitor[key] || (mergedVisitor[key] = {}); + mergePair(nodeVisitor, typeVisitor); + } + } + return mergedVisitor; +} +function wrapWithStateOrWrapper(oldVisitor, state, wrapper) { + const newVisitor = {}; + for (const phase of ["enter", "exit"]) { + let fns = oldVisitor[phase]; + if (!Array.isArray(fns)) continue; + fns = fns.map(function (fn) { + let newFn = fn; + if (state) { + newFn = function (path) { + fn.call(state, path, state); + }; + } + if (wrapper) { + newFn = wrapper(state == null ? void 0 : state.key, phase, newFn); + } + if (newFn !== fn) { + newFn.toString = () => fn.toString(); + } + return newFn; + }); + newVisitor[phase] = fns; + } + return newVisitor; +} +function ensureEntranceObjects(obj) { + for (const key of Object.keys(obj)) { + if (shouldIgnoreKey(key)) continue; + const fns = obj[key]; + if (typeof fns === "function") { + obj[key] = { + enter: fns + }; + } + } +} +function ensureCallbackArrays(obj) { + if (obj.enter && !Array.isArray(obj.enter)) obj.enter = [obj.enter]; + if (obj.exit && !Array.isArray(obj.exit)) obj.exit = [obj.exit]; +} +function wrapCheck(nodeType, fn) { + const fnKey = `is${nodeType}`; + const validator = virtualTypesValidators[fnKey]; + const newFn = function (path) { + if (validator.call(path)) { + return fn.apply(this, arguments); + } + }; + newFn.toString = () => fn.toString(); + return newFn; +} +function shouldIgnoreKey(key) { + if (key[0] === "_") return true; + if (key === "enter" || key === "exit" || key === "shouldSkip") return true; + if (key === "denylist" || key === "noScope" || key === "skipKeys") { + return true; + } + { + if (key === "blacklist") { + return true; + } + } + return false; +} +function mergePair(dest, src) { + for (const phase of ["enter", "exit"]) { + if (!src[phase]) continue; + dest[phase] = [].concat(dest[phase] || [], src[phase]); + } +} +const _environmentVisitor = { + FunctionParent(path) { + if (path.isArrowFunctionExpression()) return; + path.skip(); + if (path.isMethod()) { + if (!path.requeueComputedKeyAndDecorators) { + _context.requeueComputedKeyAndDecorators.call(path); + } else { + path.requeueComputedKeyAndDecorators(); + } + } + }, + Property(path) { + if (path.isObjectProperty()) return; + path.skip(); + if (!path.requeueComputedKeyAndDecorators) { + _context.requeueComputedKeyAndDecorators.call(path); + } else { + path.requeueComputedKeyAndDecorators(); + } + } +}; +function environmentVisitor(visitor) { + return merge([_environmentVisitor, visitor]); +} + +//# sourceMappingURL=visitors.js.map + + +/***/ }), + +/***/ 41737: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports["default"] = assertNode; +var _isNode = __nccwpck_require__(84503); +function assertNode(node) { + if (!(0, _isNode.default)(node)) { + var _node$type; + const type = (_node$type = node == null ? void 0 : node.type) != null ? _node$type : JSON.stringify(node); + throw new TypeError(`Not a valid node of type "${type}"`); + } +} + +//# sourceMappingURL=assertNode.js.map + + +/***/ }), + +/***/ 98345: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports.assertAccessor = assertAccessor; +exports.assertAnyTypeAnnotation = assertAnyTypeAnnotation; +exports.assertArgumentPlaceholder = assertArgumentPlaceholder; +exports.assertArrayExpression = assertArrayExpression; +exports.assertArrayPattern = assertArrayPattern; +exports.assertArrayTypeAnnotation = assertArrayTypeAnnotation; +exports.assertArrowFunctionExpression = assertArrowFunctionExpression; +exports.assertAssignmentExpression = assertAssignmentExpression; +exports.assertAssignmentPattern = assertAssignmentPattern; +exports.assertAwaitExpression = assertAwaitExpression; +exports.assertBigIntLiteral = assertBigIntLiteral; +exports.assertBinary = assertBinary; +exports.assertBinaryExpression = assertBinaryExpression; +exports.assertBindExpression = assertBindExpression; +exports.assertBlock = assertBlock; +exports.assertBlockParent = assertBlockParent; +exports.assertBlockStatement = assertBlockStatement; +exports.assertBooleanLiteral = assertBooleanLiteral; +exports.assertBooleanLiteralTypeAnnotation = assertBooleanLiteralTypeAnnotation; +exports.assertBooleanTypeAnnotation = assertBooleanTypeAnnotation; +exports.assertBreakStatement = assertBreakStatement; +exports.assertCallExpression = assertCallExpression; +exports.assertCatchClause = assertCatchClause; +exports.assertClass = assertClass; +exports.assertClassAccessorProperty = assertClassAccessorProperty; +exports.assertClassBody = assertClassBody; +exports.assertClassDeclaration = assertClassDeclaration; +exports.assertClassExpression = assertClassExpression; +exports.assertClassImplements = assertClassImplements; +exports.assertClassMethod = assertClassMethod; +exports.assertClassPrivateMethod = assertClassPrivateMethod; +exports.assertClassPrivateProperty = assertClassPrivateProperty; +exports.assertClassProperty = assertClassProperty; +exports.assertCompletionStatement = assertCompletionStatement; +exports.assertConditional = assertConditional; +exports.assertConditionalExpression = assertConditionalExpression; +exports.assertContinueStatement = assertContinueStatement; +exports.assertDebuggerStatement = assertDebuggerStatement; +exports.assertDecimalLiteral = assertDecimalLiteral; +exports.assertDeclaration = assertDeclaration; +exports.assertDeclareClass = assertDeclareClass; +exports.assertDeclareExportAllDeclaration = assertDeclareExportAllDeclaration; +exports.assertDeclareExportDeclaration = assertDeclareExportDeclaration; +exports.assertDeclareFunction = assertDeclareFunction; +exports.assertDeclareInterface = assertDeclareInterface; +exports.assertDeclareModule = assertDeclareModule; +exports.assertDeclareModuleExports = assertDeclareModuleExports; +exports.assertDeclareOpaqueType = assertDeclareOpaqueType; +exports.assertDeclareTypeAlias = assertDeclareTypeAlias; +exports.assertDeclareVariable = assertDeclareVariable; +exports.assertDeclaredPredicate = assertDeclaredPredicate; +exports.assertDecorator = assertDecorator; +exports.assertDirective = assertDirective; +exports.assertDirectiveLiteral = assertDirectiveLiteral; +exports.assertDoExpression = assertDoExpression; +exports.assertDoWhileStatement = assertDoWhileStatement; +exports.assertEmptyStatement = assertEmptyStatement; +exports.assertEmptyTypeAnnotation = assertEmptyTypeAnnotation; +exports.assertEnumBody = assertEnumBody; +exports.assertEnumBooleanBody = assertEnumBooleanBody; +exports.assertEnumBooleanMember = assertEnumBooleanMember; +exports.assertEnumDeclaration = assertEnumDeclaration; +exports.assertEnumDefaultedMember = assertEnumDefaultedMember; +exports.assertEnumMember = assertEnumMember; +exports.assertEnumNumberBody = assertEnumNumberBody; +exports.assertEnumNumberMember = assertEnumNumberMember; +exports.assertEnumStringBody = assertEnumStringBody; +exports.assertEnumStringMember = assertEnumStringMember; +exports.assertEnumSymbolBody = assertEnumSymbolBody; +exports.assertExistsTypeAnnotation = assertExistsTypeAnnotation; +exports.assertExportAllDeclaration = assertExportAllDeclaration; +exports.assertExportDeclaration = assertExportDeclaration; +exports.assertExportDefaultDeclaration = assertExportDefaultDeclaration; +exports.assertExportDefaultSpecifier = assertExportDefaultSpecifier; +exports.assertExportNamedDeclaration = assertExportNamedDeclaration; +exports.assertExportNamespaceSpecifier = assertExportNamespaceSpecifier; +exports.assertExportSpecifier = assertExportSpecifier; +exports.assertExpression = assertExpression; +exports.assertExpressionStatement = assertExpressionStatement; +exports.assertExpressionWrapper = assertExpressionWrapper; +exports.assertFile = assertFile; +exports.assertFlow = assertFlow; +exports.assertFlowBaseAnnotation = assertFlowBaseAnnotation; +exports.assertFlowDeclaration = assertFlowDeclaration; +exports.assertFlowPredicate = assertFlowPredicate; +exports.assertFlowType = assertFlowType; +exports.assertFor = assertFor; +exports.assertForInStatement = assertForInStatement; +exports.assertForOfStatement = assertForOfStatement; +exports.assertForStatement = assertForStatement; +exports.assertForXStatement = assertForXStatement; +exports.assertFunction = assertFunction; +exports.assertFunctionDeclaration = assertFunctionDeclaration; +exports.assertFunctionExpression = assertFunctionExpression; +exports.assertFunctionParent = assertFunctionParent; +exports.assertFunctionTypeAnnotation = assertFunctionTypeAnnotation; +exports.assertFunctionTypeParam = assertFunctionTypeParam; +exports.assertGenericTypeAnnotation = assertGenericTypeAnnotation; +exports.assertIdentifier = assertIdentifier; +exports.assertIfStatement = assertIfStatement; +exports.assertImmutable = assertImmutable; +exports.assertImport = assertImport; +exports.assertImportAttribute = assertImportAttribute; +exports.assertImportDeclaration = assertImportDeclaration; +exports.assertImportDefaultSpecifier = assertImportDefaultSpecifier; +exports.assertImportExpression = assertImportExpression; +exports.assertImportNamespaceSpecifier = assertImportNamespaceSpecifier; +exports.assertImportOrExportDeclaration = assertImportOrExportDeclaration; +exports.assertImportSpecifier = assertImportSpecifier; +exports.assertIndexedAccessType = assertIndexedAccessType; +exports.assertInferredPredicate = assertInferredPredicate; +exports.assertInterfaceDeclaration = assertInterfaceDeclaration; +exports.assertInterfaceExtends = assertInterfaceExtends; +exports.assertInterfaceTypeAnnotation = assertInterfaceTypeAnnotation; +exports.assertInterpreterDirective = assertInterpreterDirective; +exports.assertIntersectionTypeAnnotation = assertIntersectionTypeAnnotation; +exports.assertJSX = assertJSX; +exports.assertJSXAttribute = assertJSXAttribute; +exports.assertJSXClosingElement = assertJSXClosingElement; +exports.assertJSXClosingFragment = assertJSXClosingFragment; +exports.assertJSXElement = assertJSXElement; +exports.assertJSXEmptyExpression = assertJSXEmptyExpression; +exports.assertJSXExpressionContainer = assertJSXExpressionContainer; +exports.assertJSXFragment = assertJSXFragment; +exports.assertJSXIdentifier = assertJSXIdentifier; +exports.assertJSXMemberExpression = assertJSXMemberExpression; +exports.assertJSXNamespacedName = assertJSXNamespacedName; +exports.assertJSXOpeningElement = assertJSXOpeningElement; +exports.assertJSXOpeningFragment = assertJSXOpeningFragment; +exports.assertJSXSpreadAttribute = assertJSXSpreadAttribute; +exports.assertJSXSpreadChild = assertJSXSpreadChild; +exports.assertJSXText = assertJSXText; +exports.assertLVal = assertLVal; +exports.assertLabeledStatement = assertLabeledStatement; +exports.assertLiteral = assertLiteral; +exports.assertLogicalExpression = assertLogicalExpression; +exports.assertLoop = assertLoop; +exports.assertMemberExpression = assertMemberExpression; +exports.assertMetaProperty = assertMetaProperty; +exports.assertMethod = assertMethod; +exports.assertMiscellaneous = assertMiscellaneous; +exports.assertMixedTypeAnnotation = assertMixedTypeAnnotation; +exports.assertModuleDeclaration = assertModuleDeclaration; +exports.assertModuleExpression = assertModuleExpression; +exports.assertModuleSpecifier = assertModuleSpecifier; +exports.assertNewExpression = assertNewExpression; +exports.assertNoop = assertNoop; +exports.assertNullLiteral = assertNullLiteral; +exports.assertNullLiteralTypeAnnotation = assertNullLiteralTypeAnnotation; +exports.assertNullableTypeAnnotation = assertNullableTypeAnnotation; +exports.assertNumberLiteral = assertNumberLiteral; +exports.assertNumberLiteralTypeAnnotation = assertNumberLiteralTypeAnnotation; +exports.assertNumberTypeAnnotation = assertNumberTypeAnnotation; +exports.assertNumericLiteral = assertNumericLiteral; +exports.assertObjectExpression = assertObjectExpression; +exports.assertObjectMember = assertObjectMember; +exports.assertObjectMethod = assertObjectMethod; +exports.assertObjectPattern = assertObjectPattern; +exports.assertObjectProperty = assertObjectProperty; +exports.assertObjectTypeAnnotation = assertObjectTypeAnnotation; +exports.assertObjectTypeCallProperty = assertObjectTypeCallProperty; +exports.assertObjectTypeIndexer = assertObjectTypeIndexer; +exports.assertObjectTypeInternalSlot = assertObjectTypeInternalSlot; +exports.assertObjectTypeProperty = assertObjectTypeProperty; +exports.assertObjectTypeSpreadProperty = assertObjectTypeSpreadProperty; +exports.assertOpaqueType = assertOpaqueType; +exports.assertOptionalCallExpression = assertOptionalCallExpression; +exports.assertOptionalIndexedAccessType = assertOptionalIndexedAccessType; +exports.assertOptionalMemberExpression = assertOptionalMemberExpression; +exports.assertParenthesizedExpression = assertParenthesizedExpression; +exports.assertPattern = assertPattern; +exports.assertPatternLike = assertPatternLike; +exports.assertPipelineBareFunction = assertPipelineBareFunction; +exports.assertPipelinePrimaryTopicReference = assertPipelinePrimaryTopicReference; +exports.assertPipelineTopicExpression = assertPipelineTopicExpression; +exports.assertPlaceholder = assertPlaceholder; +exports.assertPrivate = assertPrivate; +exports.assertPrivateName = assertPrivateName; +exports.assertProgram = assertProgram; +exports.assertProperty = assertProperty; +exports.assertPureish = assertPureish; +exports.assertQualifiedTypeIdentifier = assertQualifiedTypeIdentifier; +exports.assertRecordExpression = assertRecordExpression; +exports.assertRegExpLiteral = assertRegExpLiteral; +exports.assertRegexLiteral = assertRegexLiteral; +exports.assertRestElement = assertRestElement; +exports.assertRestProperty = assertRestProperty; +exports.assertReturnStatement = assertReturnStatement; +exports.assertScopable = assertScopable; +exports.assertSequenceExpression = assertSequenceExpression; +exports.assertSpreadElement = assertSpreadElement; +exports.assertSpreadProperty = assertSpreadProperty; +exports.assertStandardized = assertStandardized; +exports.assertStatement = assertStatement; +exports.assertStaticBlock = assertStaticBlock; +exports.assertStringLiteral = assertStringLiteral; +exports.assertStringLiteralTypeAnnotation = assertStringLiteralTypeAnnotation; +exports.assertStringTypeAnnotation = assertStringTypeAnnotation; +exports.assertSuper = assertSuper; +exports.assertSwitchCase = assertSwitchCase; +exports.assertSwitchStatement = assertSwitchStatement; +exports.assertSymbolTypeAnnotation = assertSymbolTypeAnnotation; +exports.assertTSAnyKeyword = assertTSAnyKeyword; +exports.assertTSArrayType = assertTSArrayType; +exports.assertTSAsExpression = assertTSAsExpression; +exports.assertTSBaseType = assertTSBaseType; +exports.assertTSBigIntKeyword = assertTSBigIntKeyword; +exports.assertTSBooleanKeyword = assertTSBooleanKeyword; +exports.assertTSCallSignatureDeclaration = assertTSCallSignatureDeclaration; +exports.assertTSConditionalType = assertTSConditionalType; +exports.assertTSConstructSignatureDeclaration = assertTSConstructSignatureDeclaration; +exports.assertTSConstructorType = assertTSConstructorType; +exports.assertTSDeclareFunction = assertTSDeclareFunction; +exports.assertTSDeclareMethod = assertTSDeclareMethod; +exports.assertTSEntityName = assertTSEntityName; +exports.assertTSEnumBody = assertTSEnumBody; +exports.assertTSEnumDeclaration = assertTSEnumDeclaration; +exports.assertTSEnumMember = assertTSEnumMember; +exports.assertTSExportAssignment = assertTSExportAssignment; +exports.assertTSExpressionWithTypeArguments = assertTSExpressionWithTypeArguments; +exports.assertTSExternalModuleReference = assertTSExternalModuleReference; +exports.assertTSFunctionType = assertTSFunctionType; +exports.assertTSImportEqualsDeclaration = assertTSImportEqualsDeclaration; +exports.assertTSImportType = assertTSImportType; +exports.assertTSIndexSignature = assertTSIndexSignature; +exports.assertTSIndexedAccessType = assertTSIndexedAccessType; +exports.assertTSInferType = assertTSInferType; +exports.assertTSInstantiationExpression = assertTSInstantiationExpression; +exports.assertTSInterfaceBody = assertTSInterfaceBody; +exports.assertTSInterfaceDeclaration = assertTSInterfaceDeclaration; +exports.assertTSIntersectionType = assertTSIntersectionType; +exports.assertTSIntrinsicKeyword = assertTSIntrinsicKeyword; +exports.assertTSLiteralType = assertTSLiteralType; +exports.assertTSMappedType = assertTSMappedType; +exports.assertTSMethodSignature = assertTSMethodSignature; +exports.assertTSModuleBlock = assertTSModuleBlock; +exports.assertTSModuleDeclaration = assertTSModuleDeclaration; +exports.assertTSNamedTupleMember = assertTSNamedTupleMember; +exports.assertTSNamespaceExportDeclaration = assertTSNamespaceExportDeclaration; +exports.assertTSNeverKeyword = assertTSNeverKeyword; +exports.assertTSNonNullExpression = assertTSNonNullExpression; +exports.assertTSNullKeyword = assertTSNullKeyword; +exports.assertTSNumberKeyword = assertTSNumberKeyword; +exports.assertTSObjectKeyword = assertTSObjectKeyword; +exports.assertTSOptionalType = assertTSOptionalType; +exports.assertTSParameterProperty = assertTSParameterProperty; +exports.assertTSParenthesizedType = assertTSParenthesizedType; +exports.assertTSPropertySignature = assertTSPropertySignature; +exports.assertTSQualifiedName = assertTSQualifiedName; +exports.assertTSRestType = assertTSRestType; +exports.assertTSSatisfiesExpression = assertTSSatisfiesExpression; +exports.assertTSStringKeyword = assertTSStringKeyword; +exports.assertTSSymbolKeyword = assertTSSymbolKeyword; +exports.assertTSTemplateLiteralType = assertTSTemplateLiteralType; +exports.assertTSThisType = assertTSThisType; +exports.assertTSTupleType = assertTSTupleType; +exports.assertTSType = assertTSType; +exports.assertTSTypeAliasDeclaration = assertTSTypeAliasDeclaration; +exports.assertTSTypeAnnotation = assertTSTypeAnnotation; +exports.assertTSTypeAssertion = assertTSTypeAssertion; +exports.assertTSTypeElement = assertTSTypeElement; +exports.assertTSTypeLiteral = assertTSTypeLiteral; +exports.assertTSTypeOperator = assertTSTypeOperator; +exports.assertTSTypeParameter = assertTSTypeParameter; +exports.assertTSTypeParameterDeclaration = assertTSTypeParameterDeclaration; +exports.assertTSTypeParameterInstantiation = assertTSTypeParameterInstantiation; +exports.assertTSTypePredicate = assertTSTypePredicate; +exports.assertTSTypeQuery = assertTSTypeQuery; +exports.assertTSTypeReference = assertTSTypeReference; +exports.assertTSUndefinedKeyword = assertTSUndefinedKeyword; +exports.assertTSUnionType = assertTSUnionType; +exports.assertTSUnknownKeyword = assertTSUnknownKeyword; +exports.assertTSVoidKeyword = assertTSVoidKeyword; +exports.assertTaggedTemplateExpression = assertTaggedTemplateExpression; +exports.assertTemplateElement = assertTemplateElement; +exports.assertTemplateLiteral = assertTemplateLiteral; +exports.assertTerminatorless = assertTerminatorless; +exports.assertThisExpression = assertThisExpression; +exports.assertThisTypeAnnotation = assertThisTypeAnnotation; +exports.assertThrowStatement = assertThrowStatement; +exports.assertTopicReference = assertTopicReference; +exports.assertTryStatement = assertTryStatement; +exports.assertTupleExpression = assertTupleExpression; +exports.assertTupleTypeAnnotation = assertTupleTypeAnnotation; +exports.assertTypeAlias = assertTypeAlias; +exports.assertTypeAnnotation = assertTypeAnnotation; +exports.assertTypeCastExpression = assertTypeCastExpression; +exports.assertTypeParameter = assertTypeParameter; +exports.assertTypeParameterDeclaration = assertTypeParameterDeclaration; +exports.assertTypeParameterInstantiation = assertTypeParameterInstantiation; +exports.assertTypeScript = assertTypeScript; +exports.assertTypeofTypeAnnotation = assertTypeofTypeAnnotation; +exports.assertUnaryExpression = assertUnaryExpression; +exports.assertUnaryLike = assertUnaryLike; +exports.assertUnionTypeAnnotation = assertUnionTypeAnnotation; +exports.assertUpdateExpression = assertUpdateExpression; +exports.assertUserWhitespacable = assertUserWhitespacable; +exports.assertV8IntrinsicIdentifier = assertV8IntrinsicIdentifier; +exports.assertVariableDeclaration = assertVariableDeclaration; +exports.assertVariableDeclarator = assertVariableDeclarator; +exports.assertVariance = assertVariance; +exports.assertVoidTypeAnnotation = assertVoidTypeAnnotation; +exports.assertWhile = assertWhile; +exports.assertWhileStatement = assertWhileStatement; +exports.assertWithStatement = assertWithStatement; +exports.assertYieldExpression = assertYieldExpression; +var _is = __nccwpck_require__(20051); +var _deprecationWarning = __nccwpck_require__(14711); +function assert(type, node, opts) { + if (!(0, _is.default)(type, node, opts)) { + throw new Error(`Expected type "${type}" with option ${JSON.stringify(opts)}, ` + `but instead got "${node.type}".`); + } +} +function assertArrayExpression(node, opts) { + assert("ArrayExpression", node, opts); +} +function assertAssignmentExpression(node, opts) { + assert("AssignmentExpression", node, opts); +} +function assertBinaryExpression(node, opts) { + assert("BinaryExpression", node, opts); +} +function assertInterpreterDirective(node, opts) { + assert("InterpreterDirective", node, opts); +} +function assertDirective(node, opts) { + assert("Directive", node, opts); +} +function assertDirectiveLiteral(node, opts) { + assert("DirectiveLiteral", node, opts); +} +function assertBlockStatement(node, opts) { + assert("BlockStatement", node, opts); +} +function assertBreakStatement(node, opts) { + assert("BreakStatement", node, opts); +} +function assertCallExpression(node, opts) { + assert("CallExpression", node, opts); +} +function assertCatchClause(node, opts) { + assert("CatchClause", node, opts); +} +function assertConditionalExpression(node, opts) { + assert("ConditionalExpression", node, opts); +} +function assertContinueStatement(node, opts) { + assert("ContinueStatement", node, opts); +} +function assertDebuggerStatement(node, opts) { + assert("DebuggerStatement", node, opts); +} +function assertDoWhileStatement(node, opts) { + assert("DoWhileStatement", node, opts); +} +function assertEmptyStatement(node, opts) { + assert("EmptyStatement", node, opts); +} +function assertExpressionStatement(node, opts) { + assert("ExpressionStatement", node, opts); +} +function assertFile(node, opts) { + assert("File", node, opts); +} +function assertForInStatement(node, opts) { + assert("ForInStatement", node, opts); +} +function assertForStatement(node, opts) { + assert("ForStatement", node, opts); +} +function assertFunctionDeclaration(node, opts) { + assert("FunctionDeclaration", node, opts); +} +function assertFunctionExpression(node, opts) { + assert("FunctionExpression", node, opts); +} +function assertIdentifier(node, opts) { + assert("Identifier", node, opts); +} +function assertIfStatement(node, opts) { + assert("IfStatement", node, opts); +} +function assertLabeledStatement(node, opts) { + assert("LabeledStatement", node, opts); +} +function assertStringLiteral(node, opts) { + assert("StringLiteral", node, opts); +} +function assertNumericLiteral(node, opts) { + assert("NumericLiteral", node, opts); +} +function assertNullLiteral(node, opts) { + assert("NullLiteral", node, opts); +} +function assertBooleanLiteral(node, opts) { + assert("BooleanLiteral", node, opts); +} +function assertRegExpLiteral(node, opts) { + assert("RegExpLiteral", node, opts); +} +function assertLogicalExpression(node, opts) { + assert("LogicalExpression", node, opts); +} +function assertMemberExpression(node, opts) { + assert("MemberExpression", node, opts); +} +function assertNewExpression(node, opts) { + assert("NewExpression", node, opts); +} +function assertProgram(node, opts) { + assert("Program", node, opts); +} +function assertObjectExpression(node, opts) { + assert("ObjectExpression", node, opts); +} +function assertObjectMethod(node, opts) { + assert("ObjectMethod", node, opts); +} +function assertObjectProperty(node, opts) { + assert("ObjectProperty", node, opts); +} +function assertRestElement(node, opts) { + assert("RestElement", node, opts); +} +function assertReturnStatement(node, opts) { + assert("ReturnStatement", node, opts); +} +function assertSequenceExpression(node, opts) { + assert("SequenceExpression", node, opts); +} +function assertParenthesizedExpression(node, opts) { + assert("ParenthesizedExpression", node, opts); +} +function assertSwitchCase(node, opts) { + assert("SwitchCase", node, opts); +} +function assertSwitchStatement(node, opts) { + assert("SwitchStatement", node, opts); +} +function assertThisExpression(node, opts) { + assert("ThisExpression", node, opts); +} +function assertThrowStatement(node, opts) { + assert("ThrowStatement", node, opts); +} +function assertTryStatement(node, opts) { + assert("TryStatement", node, opts); +} +function assertUnaryExpression(node, opts) { + assert("UnaryExpression", node, opts); +} +function assertUpdateExpression(node, opts) { + assert("UpdateExpression", node, opts); +} +function assertVariableDeclaration(node, opts) { + assert("VariableDeclaration", node, opts); +} +function assertVariableDeclarator(node, opts) { + assert("VariableDeclarator", node, opts); +} +function assertWhileStatement(node, opts) { + assert("WhileStatement", node, opts); +} +function assertWithStatement(node, opts) { + assert("WithStatement", node, opts); +} +function assertAssignmentPattern(node, opts) { + assert("AssignmentPattern", node, opts); +} +function assertArrayPattern(node, opts) { + assert("ArrayPattern", node, opts); +} +function assertArrowFunctionExpression(node, opts) { + assert("ArrowFunctionExpression", node, opts); +} +function assertClassBody(node, opts) { + assert("ClassBody", node, opts); +} +function assertClassExpression(node, opts) { + assert("ClassExpression", node, opts); +} +function assertClassDeclaration(node, opts) { + assert("ClassDeclaration", node, opts); +} +function assertExportAllDeclaration(node, opts) { + assert("ExportAllDeclaration", node, opts); +} +function assertExportDefaultDeclaration(node, opts) { + assert("ExportDefaultDeclaration", node, opts); +} +function assertExportNamedDeclaration(node, opts) { + assert("ExportNamedDeclaration", node, opts); +} +function assertExportSpecifier(node, opts) { + assert("ExportSpecifier", node, opts); +} +function assertForOfStatement(node, opts) { + assert("ForOfStatement", node, opts); +} +function assertImportDeclaration(node, opts) { + assert("ImportDeclaration", node, opts); +} +function assertImportDefaultSpecifier(node, opts) { + assert("ImportDefaultSpecifier", node, opts); +} +function assertImportNamespaceSpecifier(node, opts) { + assert("ImportNamespaceSpecifier", node, opts); +} +function assertImportSpecifier(node, opts) { + assert("ImportSpecifier", node, opts); +} +function assertImportExpression(node, opts) { + assert("ImportExpression", node, opts); +} +function assertMetaProperty(node, opts) { + assert("MetaProperty", node, opts); +} +function assertClassMethod(node, opts) { + assert("ClassMethod", node, opts); +} +function assertObjectPattern(node, opts) { + assert("ObjectPattern", node, opts); +} +function assertSpreadElement(node, opts) { + assert("SpreadElement", node, opts); +} +function assertSuper(node, opts) { + assert("Super", node, opts); +} +function assertTaggedTemplateExpression(node, opts) { + assert("TaggedTemplateExpression", node, opts); +} +function assertTemplateElement(node, opts) { + assert("TemplateElement", node, opts); +} +function assertTemplateLiteral(node, opts) { + assert("TemplateLiteral", node, opts); +} +function assertYieldExpression(node, opts) { + assert("YieldExpression", node, opts); +} +function assertAwaitExpression(node, opts) { + assert("AwaitExpression", node, opts); +} +function assertImport(node, opts) { + assert("Import", node, opts); +} +function assertBigIntLiteral(node, opts) { + assert("BigIntLiteral", node, opts); +} +function assertExportNamespaceSpecifier(node, opts) { + assert("ExportNamespaceSpecifier", node, opts); +} +function assertOptionalMemberExpression(node, opts) { + assert("OptionalMemberExpression", node, opts); +} +function assertOptionalCallExpression(node, opts) { + assert("OptionalCallExpression", node, opts); +} +function assertClassProperty(node, opts) { + assert("ClassProperty", node, opts); +} +function assertClassAccessorProperty(node, opts) { + assert("ClassAccessorProperty", node, opts); +} +function assertClassPrivateProperty(node, opts) { + assert("ClassPrivateProperty", node, opts); +} +function assertClassPrivateMethod(node, opts) { + assert("ClassPrivateMethod", node, opts); +} +function assertPrivateName(node, opts) { + assert("PrivateName", node, opts); +} +function assertStaticBlock(node, opts) { + assert("StaticBlock", node, opts); +} +function assertImportAttribute(node, opts) { + assert("ImportAttribute", node, opts); +} +function assertAnyTypeAnnotation(node, opts) { + assert("AnyTypeAnnotation", node, opts); +} +function assertArrayTypeAnnotation(node, opts) { + assert("ArrayTypeAnnotation", node, opts); +} +function assertBooleanTypeAnnotation(node, opts) { + assert("BooleanTypeAnnotation", node, opts); +} +function assertBooleanLiteralTypeAnnotation(node, opts) { + assert("BooleanLiteralTypeAnnotation", node, opts); +} +function assertNullLiteralTypeAnnotation(node, opts) { + assert("NullLiteralTypeAnnotation", node, opts); +} +function assertClassImplements(node, opts) { + assert("ClassImplements", node, opts); +} +function assertDeclareClass(node, opts) { + assert("DeclareClass", node, opts); +} +function assertDeclareFunction(node, opts) { + assert("DeclareFunction", node, opts); +} +function assertDeclareInterface(node, opts) { + assert("DeclareInterface", node, opts); +} +function assertDeclareModule(node, opts) { + assert("DeclareModule", node, opts); +} +function assertDeclareModuleExports(node, opts) { + assert("DeclareModuleExports", node, opts); +} +function assertDeclareTypeAlias(node, opts) { + assert("DeclareTypeAlias", node, opts); +} +function assertDeclareOpaqueType(node, opts) { + assert("DeclareOpaqueType", node, opts); +} +function assertDeclareVariable(node, opts) { + assert("DeclareVariable", node, opts); +} +function assertDeclareExportDeclaration(node, opts) { + assert("DeclareExportDeclaration", node, opts); +} +function assertDeclareExportAllDeclaration(node, opts) { + assert("DeclareExportAllDeclaration", node, opts); +} +function assertDeclaredPredicate(node, opts) { + assert("DeclaredPredicate", node, opts); +} +function assertExistsTypeAnnotation(node, opts) { + assert("ExistsTypeAnnotation", node, opts); +} +function assertFunctionTypeAnnotation(node, opts) { + assert("FunctionTypeAnnotation", node, opts); +} +function assertFunctionTypeParam(node, opts) { + assert("FunctionTypeParam", node, opts); +} +function assertGenericTypeAnnotation(node, opts) { + assert("GenericTypeAnnotation", node, opts); +} +function assertInferredPredicate(node, opts) { + assert("InferredPredicate", node, opts); +} +function assertInterfaceExtends(node, opts) { + assert("InterfaceExtends", node, opts); +} +function assertInterfaceDeclaration(node, opts) { + assert("InterfaceDeclaration", node, opts); +} +function assertInterfaceTypeAnnotation(node, opts) { + assert("InterfaceTypeAnnotation", node, opts); +} +function assertIntersectionTypeAnnotation(node, opts) { + assert("IntersectionTypeAnnotation", node, opts); +} +function assertMixedTypeAnnotation(node, opts) { + assert("MixedTypeAnnotation", node, opts); +} +function assertEmptyTypeAnnotation(node, opts) { + assert("EmptyTypeAnnotation", node, opts); +} +function assertNullableTypeAnnotation(node, opts) { + assert("NullableTypeAnnotation", node, opts); +} +function assertNumberLiteralTypeAnnotation(node, opts) { + assert("NumberLiteralTypeAnnotation", node, opts); +} +function assertNumberTypeAnnotation(node, opts) { + assert("NumberTypeAnnotation", node, opts); +} +function assertObjectTypeAnnotation(node, opts) { + assert("ObjectTypeAnnotation", node, opts); +} +function assertObjectTypeInternalSlot(node, opts) { + assert("ObjectTypeInternalSlot", node, opts); +} +function assertObjectTypeCallProperty(node, opts) { + assert("ObjectTypeCallProperty", node, opts); +} +function assertObjectTypeIndexer(node, opts) { + assert("ObjectTypeIndexer", node, opts); +} +function assertObjectTypeProperty(node, opts) { + assert("ObjectTypeProperty", node, opts); +} +function assertObjectTypeSpreadProperty(node, opts) { + assert("ObjectTypeSpreadProperty", node, opts); +} +function assertOpaqueType(node, opts) { + assert("OpaqueType", node, opts); +} +function assertQualifiedTypeIdentifier(node, opts) { + assert("QualifiedTypeIdentifier", node, opts); +} +function assertStringLiteralTypeAnnotation(node, opts) { + assert("StringLiteralTypeAnnotation", node, opts); +} +function assertStringTypeAnnotation(node, opts) { + assert("StringTypeAnnotation", node, opts); +} +function assertSymbolTypeAnnotation(node, opts) { + assert("SymbolTypeAnnotation", node, opts); +} +function assertThisTypeAnnotation(node, opts) { + assert("ThisTypeAnnotation", node, opts); +} +function assertTupleTypeAnnotation(node, opts) { + assert("TupleTypeAnnotation", node, opts); +} +function assertTypeofTypeAnnotation(node, opts) { + assert("TypeofTypeAnnotation", node, opts); +} +function assertTypeAlias(node, opts) { + assert("TypeAlias", node, opts); +} +function assertTypeAnnotation(node, opts) { + assert("TypeAnnotation", node, opts); +} +function assertTypeCastExpression(node, opts) { + assert("TypeCastExpression", node, opts); +} +function assertTypeParameter(node, opts) { + assert("TypeParameter", node, opts); +} +function assertTypeParameterDeclaration(node, opts) { + assert("TypeParameterDeclaration", node, opts); +} +function assertTypeParameterInstantiation(node, opts) { + assert("TypeParameterInstantiation", node, opts); +} +function assertUnionTypeAnnotation(node, opts) { + assert("UnionTypeAnnotation", node, opts); +} +function assertVariance(node, opts) { + assert("Variance", node, opts); +} +function assertVoidTypeAnnotation(node, opts) { + assert("VoidTypeAnnotation", node, opts); +} +function assertEnumDeclaration(node, opts) { + assert("EnumDeclaration", node, opts); +} +function assertEnumBooleanBody(node, opts) { + assert("EnumBooleanBody", node, opts); +} +function assertEnumNumberBody(node, opts) { + assert("EnumNumberBody", node, opts); +} +function assertEnumStringBody(node, opts) { + assert("EnumStringBody", node, opts); +} +function assertEnumSymbolBody(node, opts) { + assert("EnumSymbolBody", node, opts); +} +function assertEnumBooleanMember(node, opts) { + assert("EnumBooleanMember", node, opts); +} +function assertEnumNumberMember(node, opts) { + assert("EnumNumberMember", node, opts); +} +function assertEnumStringMember(node, opts) { + assert("EnumStringMember", node, opts); +} +function assertEnumDefaultedMember(node, opts) { + assert("EnumDefaultedMember", node, opts); +} +function assertIndexedAccessType(node, opts) { + assert("IndexedAccessType", node, opts); +} +function assertOptionalIndexedAccessType(node, opts) { + assert("OptionalIndexedAccessType", node, opts); +} +function assertJSXAttribute(node, opts) { + assert("JSXAttribute", node, opts); +} +function assertJSXClosingElement(node, opts) { + assert("JSXClosingElement", node, opts); +} +function assertJSXElement(node, opts) { + assert("JSXElement", node, opts); +} +function assertJSXEmptyExpression(node, opts) { + assert("JSXEmptyExpression", node, opts); +} +function assertJSXExpressionContainer(node, opts) { + assert("JSXExpressionContainer", node, opts); +} +function assertJSXSpreadChild(node, opts) { + assert("JSXSpreadChild", node, opts); +} +function assertJSXIdentifier(node, opts) { + assert("JSXIdentifier", node, opts); +} +function assertJSXMemberExpression(node, opts) { + assert("JSXMemberExpression", node, opts); +} +function assertJSXNamespacedName(node, opts) { + assert("JSXNamespacedName", node, opts); +} +function assertJSXOpeningElement(node, opts) { + assert("JSXOpeningElement", node, opts); +} +function assertJSXSpreadAttribute(node, opts) { + assert("JSXSpreadAttribute", node, opts); +} +function assertJSXText(node, opts) { + assert("JSXText", node, opts); +} +function assertJSXFragment(node, opts) { + assert("JSXFragment", node, opts); +} +function assertJSXOpeningFragment(node, opts) { + assert("JSXOpeningFragment", node, opts); +} +function assertJSXClosingFragment(node, opts) { + assert("JSXClosingFragment", node, opts); +} +function assertNoop(node, opts) { + assert("Noop", node, opts); +} +function assertPlaceholder(node, opts) { + assert("Placeholder", node, opts); +} +function assertV8IntrinsicIdentifier(node, opts) { + assert("V8IntrinsicIdentifier", node, opts); +} +function assertArgumentPlaceholder(node, opts) { + assert("ArgumentPlaceholder", node, opts); +} +function assertBindExpression(node, opts) { + assert("BindExpression", node, opts); +} +function assertDecorator(node, opts) { + assert("Decorator", node, opts); +} +function assertDoExpression(node, opts) { + assert("DoExpression", node, opts); +} +function assertExportDefaultSpecifier(node, opts) { + assert("ExportDefaultSpecifier", node, opts); +} +function assertRecordExpression(node, opts) { + assert("RecordExpression", node, opts); +} +function assertTupleExpression(node, opts) { + assert("TupleExpression", node, opts); +} +function assertDecimalLiteral(node, opts) { + assert("DecimalLiteral", node, opts); +} +function assertModuleExpression(node, opts) { + assert("ModuleExpression", node, opts); +} +function assertTopicReference(node, opts) { + assert("TopicReference", node, opts); +} +function assertPipelineTopicExpression(node, opts) { + assert("PipelineTopicExpression", node, opts); +} +function assertPipelineBareFunction(node, opts) { + assert("PipelineBareFunction", node, opts); +} +function assertPipelinePrimaryTopicReference(node, opts) { + assert("PipelinePrimaryTopicReference", node, opts); +} +function assertTSParameterProperty(node, opts) { + assert("TSParameterProperty", node, opts); +} +function assertTSDeclareFunction(node, opts) { + assert("TSDeclareFunction", node, opts); +} +function assertTSDeclareMethod(node, opts) { + assert("TSDeclareMethod", node, opts); +} +function assertTSQualifiedName(node, opts) { + assert("TSQualifiedName", node, opts); +} +function assertTSCallSignatureDeclaration(node, opts) { + assert("TSCallSignatureDeclaration", node, opts); +} +function assertTSConstructSignatureDeclaration(node, opts) { + assert("TSConstructSignatureDeclaration", node, opts); +} +function assertTSPropertySignature(node, opts) { + assert("TSPropertySignature", node, opts); +} +function assertTSMethodSignature(node, opts) { + assert("TSMethodSignature", node, opts); +} +function assertTSIndexSignature(node, opts) { + assert("TSIndexSignature", node, opts); +} +function assertTSAnyKeyword(node, opts) { + assert("TSAnyKeyword", node, opts); +} +function assertTSBooleanKeyword(node, opts) { + assert("TSBooleanKeyword", node, opts); +} +function assertTSBigIntKeyword(node, opts) { + assert("TSBigIntKeyword", node, opts); +} +function assertTSIntrinsicKeyword(node, opts) { + assert("TSIntrinsicKeyword", node, opts); +} +function assertTSNeverKeyword(node, opts) { + assert("TSNeverKeyword", node, opts); +} +function assertTSNullKeyword(node, opts) { + assert("TSNullKeyword", node, opts); +} +function assertTSNumberKeyword(node, opts) { + assert("TSNumberKeyword", node, opts); +} +function assertTSObjectKeyword(node, opts) { + assert("TSObjectKeyword", node, opts); +} +function assertTSStringKeyword(node, opts) { + assert("TSStringKeyword", node, opts); +} +function assertTSSymbolKeyword(node, opts) { + assert("TSSymbolKeyword", node, opts); +} +function assertTSUndefinedKeyword(node, opts) { + assert("TSUndefinedKeyword", node, opts); +} +function assertTSUnknownKeyword(node, opts) { + assert("TSUnknownKeyword", node, opts); +} +function assertTSVoidKeyword(node, opts) { + assert("TSVoidKeyword", node, opts); +} +function assertTSThisType(node, opts) { + assert("TSThisType", node, opts); +} +function assertTSFunctionType(node, opts) { + assert("TSFunctionType", node, opts); +} +function assertTSConstructorType(node, opts) { + assert("TSConstructorType", node, opts); +} +function assertTSTypeReference(node, opts) { + assert("TSTypeReference", node, opts); +} +function assertTSTypePredicate(node, opts) { + assert("TSTypePredicate", node, opts); +} +function assertTSTypeQuery(node, opts) { + assert("TSTypeQuery", node, opts); +} +function assertTSTypeLiteral(node, opts) { + assert("TSTypeLiteral", node, opts); +} +function assertTSArrayType(node, opts) { + assert("TSArrayType", node, opts); +} +function assertTSTupleType(node, opts) { + assert("TSTupleType", node, opts); +} +function assertTSOptionalType(node, opts) { + assert("TSOptionalType", node, opts); +} +function assertTSRestType(node, opts) { + assert("TSRestType", node, opts); +} +function assertTSNamedTupleMember(node, opts) { + assert("TSNamedTupleMember", node, opts); +} +function assertTSUnionType(node, opts) { + assert("TSUnionType", node, opts); +} +function assertTSIntersectionType(node, opts) { + assert("TSIntersectionType", node, opts); +} +function assertTSConditionalType(node, opts) { + assert("TSConditionalType", node, opts); +} +function assertTSInferType(node, opts) { + assert("TSInferType", node, opts); +} +function assertTSParenthesizedType(node, opts) { + assert("TSParenthesizedType", node, opts); +} +function assertTSTypeOperator(node, opts) { + assert("TSTypeOperator", node, opts); +} +function assertTSIndexedAccessType(node, opts) { + assert("TSIndexedAccessType", node, opts); +} +function assertTSMappedType(node, opts) { + assert("TSMappedType", node, opts); +} +function assertTSTemplateLiteralType(node, opts) { + assert("TSTemplateLiteralType", node, opts); +} +function assertTSLiteralType(node, opts) { + assert("TSLiteralType", node, opts); +} +function assertTSExpressionWithTypeArguments(node, opts) { + assert("TSExpressionWithTypeArguments", node, opts); +} +function assertTSInterfaceDeclaration(node, opts) { + assert("TSInterfaceDeclaration", node, opts); +} +function assertTSInterfaceBody(node, opts) { + assert("TSInterfaceBody", node, opts); +} +function assertTSTypeAliasDeclaration(node, opts) { + assert("TSTypeAliasDeclaration", node, opts); +} +function assertTSInstantiationExpression(node, opts) { + assert("TSInstantiationExpression", node, opts); +} +function assertTSAsExpression(node, opts) { + assert("TSAsExpression", node, opts); +} +function assertTSSatisfiesExpression(node, opts) { + assert("TSSatisfiesExpression", node, opts); +} +function assertTSTypeAssertion(node, opts) { + assert("TSTypeAssertion", node, opts); +} +function assertTSEnumBody(node, opts) { + assert("TSEnumBody", node, opts); +} +function assertTSEnumDeclaration(node, opts) { + assert("TSEnumDeclaration", node, opts); +} +function assertTSEnumMember(node, opts) { + assert("TSEnumMember", node, opts); +} +function assertTSModuleDeclaration(node, opts) { + assert("TSModuleDeclaration", node, opts); +} +function assertTSModuleBlock(node, opts) { + assert("TSModuleBlock", node, opts); +} +function assertTSImportType(node, opts) { + assert("TSImportType", node, opts); +} +function assertTSImportEqualsDeclaration(node, opts) { + assert("TSImportEqualsDeclaration", node, opts); +} +function assertTSExternalModuleReference(node, opts) { + assert("TSExternalModuleReference", node, opts); +} +function assertTSNonNullExpression(node, opts) { + assert("TSNonNullExpression", node, opts); +} +function assertTSExportAssignment(node, opts) { + assert("TSExportAssignment", node, opts); +} +function assertTSNamespaceExportDeclaration(node, opts) { + assert("TSNamespaceExportDeclaration", node, opts); +} +function assertTSTypeAnnotation(node, opts) { + assert("TSTypeAnnotation", node, opts); +} +function assertTSTypeParameterInstantiation(node, opts) { + assert("TSTypeParameterInstantiation", node, opts); +} +function assertTSTypeParameterDeclaration(node, opts) { + assert("TSTypeParameterDeclaration", node, opts); +} +function assertTSTypeParameter(node, opts) { + assert("TSTypeParameter", node, opts); +} +function assertStandardized(node, opts) { + assert("Standardized", node, opts); +} +function assertExpression(node, opts) { + assert("Expression", node, opts); +} +function assertBinary(node, opts) { + assert("Binary", node, opts); +} +function assertScopable(node, opts) { + assert("Scopable", node, opts); +} +function assertBlockParent(node, opts) { + assert("BlockParent", node, opts); +} +function assertBlock(node, opts) { + assert("Block", node, opts); +} +function assertStatement(node, opts) { + assert("Statement", node, opts); +} +function assertTerminatorless(node, opts) { + assert("Terminatorless", node, opts); +} +function assertCompletionStatement(node, opts) { + assert("CompletionStatement", node, opts); +} +function assertConditional(node, opts) { + assert("Conditional", node, opts); +} +function assertLoop(node, opts) { + assert("Loop", node, opts); +} +function assertWhile(node, opts) { + assert("While", node, opts); +} +function assertExpressionWrapper(node, opts) { + assert("ExpressionWrapper", node, opts); +} +function assertFor(node, opts) { + assert("For", node, opts); +} +function assertForXStatement(node, opts) { + assert("ForXStatement", node, opts); +} +function assertFunction(node, opts) { + assert("Function", node, opts); +} +function assertFunctionParent(node, opts) { + assert("FunctionParent", node, opts); +} +function assertPureish(node, opts) { + assert("Pureish", node, opts); +} +function assertDeclaration(node, opts) { + assert("Declaration", node, opts); +} +function assertPatternLike(node, opts) { + assert("PatternLike", node, opts); +} +function assertLVal(node, opts) { + assert("LVal", node, opts); +} +function assertTSEntityName(node, opts) { + assert("TSEntityName", node, opts); +} +function assertLiteral(node, opts) { + assert("Literal", node, opts); +} +function assertImmutable(node, opts) { + assert("Immutable", node, opts); +} +function assertUserWhitespacable(node, opts) { + assert("UserWhitespacable", node, opts); +} +function assertMethod(node, opts) { + assert("Method", node, opts); +} +function assertObjectMember(node, opts) { + assert("ObjectMember", node, opts); +} +function assertProperty(node, opts) { + assert("Property", node, opts); +} +function assertUnaryLike(node, opts) { + assert("UnaryLike", node, opts); +} +function assertPattern(node, opts) { + assert("Pattern", node, opts); +} +function assertClass(node, opts) { + assert("Class", node, opts); +} +function assertImportOrExportDeclaration(node, opts) { + assert("ImportOrExportDeclaration", node, opts); +} +function assertExportDeclaration(node, opts) { + assert("ExportDeclaration", node, opts); +} +function assertModuleSpecifier(node, opts) { + assert("ModuleSpecifier", node, opts); +} +function assertAccessor(node, opts) { + assert("Accessor", node, opts); +} +function assertPrivate(node, opts) { + assert("Private", node, opts); +} +function assertFlow(node, opts) { + assert("Flow", node, opts); +} +function assertFlowType(node, opts) { + assert("FlowType", node, opts); +} +function assertFlowBaseAnnotation(node, opts) { + assert("FlowBaseAnnotation", node, opts); +} +function assertFlowDeclaration(node, opts) { + assert("FlowDeclaration", node, opts); +} +function assertFlowPredicate(node, opts) { + assert("FlowPredicate", node, opts); +} +function assertEnumBody(node, opts) { + assert("EnumBody", node, opts); +} +function assertEnumMember(node, opts) { + assert("EnumMember", node, opts); +} +function assertJSX(node, opts) { + assert("JSX", node, opts); +} +function assertMiscellaneous(node, opts) { + assert("Miscellaneous", node, opts); +} +function assertTypeScript(node, opts) { + assert("TypeScript", node, opts); +} +function assertTSTypeElement(node, opts) { + assert("TSTypeElement", node, opts); +} +function assertTSType(node, opts) { + assert("TSType", node, opts); +} +function assertTSBaseType(node, opts) { + assert("TSBaseType", node, opts); +} +function assertNumberLiteral(node, opts) { + (0, _deprecationWarning.default)("assertNumberLiteral", "assertNumericLiteral"); + assert("NumberLiteral", node, opts); +} +function assertRegexLiteral(node, opts) { + (0, _deprecationWarning.default)("assertRegexLiteral", "assertRegExpLiteral"); + assert("RegexLiteral", node, opts); +} +function assertRestProperty(node, opts) { + (0, _deprecationWarning.default)("assertRestProperty", "assertRestElement"); + assert("RestProperty", node, opts); +} +function assertSpreadProperty(node, opts) { + (0, _deprecationWarning.default)("assertSpreadProperty", "assertSpreadElement"); + assert("SpreadProperty", node, opts); +} +function assertModuleDeclaration(node, opts) { + (0, _deprecationWarning.default)("assertModuleDeclaration", "assertImportOrExportDeclaration"); + assert("ModuleDeclaration", node, opts); +} + +//# sourceMappingURL=index.js.map + + +/***/ }), + +/***/ 58806: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports["default"] = createFlowUnionType; +var _index = __nccwpck_require__(90670); +var _removeTypeDuplicates = __nccwpck_require__(41034); +function createFlowUnionType(types) { + const flattened = (0, _removeTypeDuplicates.default)(types); + if (flattened.length === 1) { + return flattened[0]; + } else { + return (0, _index.unionTypeAnnotation)(flattened); + } +} + +//# sourceMappingURL=createFlowUnionType.js.map + + +/***/ }), + +/***/ 9197: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports["default"] = void 0; +var _index = __nccwpck_require__(90670); +var _default = exports["default"] = createTypeAnnotationBasedOnTypeof; +function createTypeAnnotationBasedOnTypeof(type) { + switch (type) { + case "string": + return (0, _index.stringTypeAnnotation)(); + case "number": + return (0, _index.numberTypeAnnotation)(); + case "undefined": + return (0, _index.voidTypeAnnotation)(); + case "boolean": + return (0, _index.booleanTypeAnnotation)(); + case "function": + return (0, _index.genericTypeAnnotation)((0, _index.identifier)("Function")); + case "object": + return (0, _index.genericTypeAnnotation)((0, _index.identifier)("Object")); + case "symbol": + return (0, _index.genericTypeAnnotation)((0, _index.identifier)("Symbol")); + case "bigint": + return (0, _index.anyTypeAnnotation)(); + } + throw new Error("Invalid typeof value: " + type); +} + +//# sourceMappingURL=createTypeAnnotationBasedOnTypeof.js.map + + +/***/ }), + +/***/ 90670: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +var _lowercase = __nccwpck_require__(11999); +Object.keys(_lowercase).forEach(function (key) { + if (key === "default" || key === "__esModule") return; + if (key in exports && exports[key] === _lowercase[key]) return; + Object.defineProperty(exports, key, { + enumerable: true, + get: function () { + return _lowercase[key]; + } + }); +}); +var _uppercase = __nccwpck_require__(1862); +Object.keys(_uppercase).forEach(function (key) { + if (key === "default" || key === "__esModule") return; + if (key in exports && exports[key] === _uppercase[key]) return; + Object.defineProperty(exports, key, { + enumerable: true, + get: function () { + return _uppercase[key]; + } + }); +}); + +//# sourceMappingURL=index.js.map + + +/***/ }), + +/***/ 11999: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports.anyTypeAnnotation = anyTypeAnnotation; +exports.argumentPlaceholder = argumentPlaceholder; +exports.arrayExpression = arrayExpression; +exports.arrayPattern = arrayPattern; +exports.arrayTypeAnnotation = arrayTypeAnnotation; +exports.arrowFunctionExpression = arrowFunctionExpression; +exports.assignmentExpression = assignmentExpression; +exports.assignmentPattern = assignmentPattern; +exports.awaitExpression = awaitExpression; +exports.bigIntLiteral = bigIntLiteral; +exports.binaryExpression = binaryExpression; +exports.bindExpression = bindExpression; +exports.blockStatement = blockStatement; +exports.booleanLiteral = booleanLiteral; +exports.booleanLiteralTypeAnnotation = booleanLiteralTypeAnnotation; +exports.booleanTypeAnnotation = booleanTypeAnnotation; +exports.breakStatement = breakStatement; +exports.callExpression = callExpression; +exports.catchClause = catchClause; +exports.classAccessorProperty = classAccessorProperty; +exports.classBody = classBody; +exports.classDeclaration = classDeclaration; +exports.classExpression = classExpression; +exports.classImplements = classImplements; +exports.classMethod = classMethod; +exports.classPrivateMethod = classPrivateMethod; +exports.classPrivateProperty = classPrivateProperty; +exports.classProperty = classProperty; +exports.conditionalExpression = conditionalExpression; +exports.continueStatement = continueStatement; +exports.debuggerStatement = debuggerStatement; +exports.decimalLiteral = decimalLiteral; +exports.declareClass = declareClass; +exports.declareExportAllDeclaration = declareExportAllDeclaration; +exports.declareExportDeclaration = declareExportDeclaration; +exports.declareFunction = declareFunction; +exports.declareInterface = declareInterface; +exports.declareModule = declareModule; +exports.declareModuleExports = declareModuleExports; +exports.declareOpaqueType = declareOpaqueType; +exports.declareTypeAlias = declareTypeAlias; +exports.declareVariable = declareVariable; +exports.declaredPredicate = declaredPredicate; +exports.decorator = decorator; +exports.directive = directive; +exports.directiveLiteral = directiveLiteral; +exports.doExpression = doExpression; +exports.doWhileStatement = doWhileStatement; +exports.emptyStatement = emptyStatement; +exports.emptyTypeAnnotation = emptyTypeAnnotation; +exports.enumBooleanBody = enumBooleanBody; +exports.enumBooleanMember = enumBooleanMember; +exports.enumDeclaration = enumDeclaration; +exports.enumDefaultedMember = enumDefaultedMember; +exports.enumNumberBody = enumNumberBody; +exports.enumNumberMember = enumNumberMember; +exports.enumStringBody = enumStringBody; +exports.enumStringMember = enumStringMember; +exports.enumSymbolBody = enumSymbolBody; +exports.existsTypeAnnotation = existsTypeAnnotation; +exports.exportAllDeclaration = exportAllDeclaration; +exports.exportDefaultDeclaration = exportDefaultDeclaration; +exports.exportDefaultSpecifier = exportDefaultSpecifier; +exports.exportNamedDeclaration = exportNamedDeclaration; +exports.exportNamespaceSpecifier = exportNamespaceSpecifier; +exports.exportSpecifier = exportSpecifier; +exports.expressionStatement = expressionStatement; +exports.file = file; +exports.forInStatement = forInStatement; +exports.forOfStatement = forOfStatement; +exports.forStatement = forStatement; +exports.functionDeclaration = functionDeclaration; +exports.functionExpression = functionExpression; +exports.functionTypeAnnotation = functionTypeAnnotation; +exports.functionTypeParam = functionTypeParam; +exports.genericTypeAnnotation = genericTypeAnnotation; +exports.identifier = identifier; +exports.ifStatement = ifStatement; +exports["import"] = _import; +exports.importAttribute = importAttribute; +exports.importDeclaration = importDeclaration; +exports.importDefaultSpecifier = importDefaultSpecifier; +exports.importExpression = importExpression; +exports.importNamespaceSpecifier = importNamespaceSpecifier; +exports.importSpecifier = importSpecifier; +exports.indexedAccessType = indexedAccessType; +exports.inferredPredicate = inferredPredicate; +exports.interfaceDeclaration = interfaceDeclaration; +exports.interfaceExtends = interfaceExtends; +exports.interfaceTypeAnnotation = interfaceTypeAnnotation; +exports.interpreterDirective = interpreterDirective; +exports.intersectionTypeAnnotation = intersectionTypeAnnotation; +exports.jSXAttribute = exports.jsxAttribute = jsxAttribute; +exports.jSXClosingElement = exports.jsxClosingElement = jsxClosingElement; +exports.jSXClosingFragment = exports.jsxClosingFragment = jsxClosingFragment; +exports.jSXElement = exports.jsxElement = jsxElement; +exports.jSXEmptyExpression = exports.jsxEmptyExpression = jsxEmptyExpression; +exports.jSXExpressionContainer = exports.jsxExpressionContainer = jsxExpressionContainer; +exports.jSXFragment = exports.jsxFragment = jsxFragment; +exports.jSXIdentifier = exports.jsxIdentifier = jsxIdentifier; +exports.jSXMemberExpression = exports.jsxMemberExpression = jsxMemberExpression; +exports.jSXNamespacedName = exports.jsxNamespacedName = jsxNamespacedName; +exports.jSXOpeningElement = exports.jsxOpeningElement = jsxOpeningElement; +exports.jSXOpeningFragment = exports.jsxOpeningFragment = jsxOpeningFragment; +exports.jSXSpreadAttribute = exports.jsxSpreadAttribute = jsxSpreadAttribute; +exports.jSXSpreadChild = exports.jsxSpreadChild = jsxSpreadChild; +exports.jSXText = exports.jsxText = jsxText; +exports.labeledStatement = labeledStatement; +exports.logicalExpression = logicalExpression; +exports.memberExpression = memberExpression; +exports.metaProperty = metaProperty; +exports.mixedTypeAnnotation = mixedTypeAnnotation; +exports.moduleExpression = moduleExpression; +exports.newExpression = newExpression; +exports.noop = noop; +exports.nullLiteral = nullLiteral; +exports.nullLiteralTypeAnnotation = nullLiteralTypeAnnotation; +exports.nullableTypeAnnotation = nullableTypeAnnotation; +exports.numberLiteral = NumberLiteral; +exports.numberLiteralTypeAnnotation = numberLiteralTypeAnnotation; +exports.numberTypeAnnotation = numberTypeAnnotation; +exports.numericLiteral = numericLiteral; +exports.objectExpression = objectExpression; +exports.objectMethod = objectMethod; +exports.objectPattern = objectPattern; +exports.objectProperty = objectProperty; +exports.objectTypeAnnotation = objectTypeAnnotation; +exports.objectTypeCallProperty = objectTypeCallProperty; +exports.objectTypeIndexer = objectTypeIndexer; +exports.objectTypeInternalSlot = objectTypeInternalSlot; +exports.objectTypeProperty = objectTypeProperty; +exports.objectTypeSpreadProperty = objectTypeSpreadProperty; +exports.opaqueType = opaqueType; +exports.optionalCallExpression = optionalCallExpression; +exports.optionalIndexedAccessType = optionalIndexedAccessType; +exports.optionalMemberExpression = optionalMemberExpression; +exports.parenthesizedExpression = parenthesizedExpression; +exports.pipelineBareFunction = pipelineBareFunction; +exports.pipelinePrimaryTopicReference = pipelinePrimaryTopicReference; +exports.pipelineTopicExpression = pipelineTopicExpression; +exports.placeholder = placeholder; +exports.privateName = privateName; +exports.program = program; +exports.qualifiedTypeIdentifier = qualifiedTypeIdentifier; +exports.recordExpression = recordExpression; +exports.regExpLiteral = regExpLiteral; +exports.regexLiteral = RegexLiteral; +exports.restElement = restElement; +exports.restProperty = RestProperty; +exports.returnStatement = returnStatement; +exports.sequenceExpression = sequenceExpression; +exports.spreadElement = spreadElement; +exports.spreadProperty = SpreadProperty; +exports.staticBlock = staticBlock; +exports.stringLiteral = stringLiteral; +exports.stringLiteralTypeAnnotation = stringLiteralTypeAnnotation; +exports.stringTypeAnnotation = stringTypeAnnotation; +exports["super"] = _super; +exports.switchCase = switchCase; +exports.switchStatement = switchStatement; +exports.symbolTypeAnnotation = symbolTypeAnnotation; +exports.taggedTemplateExpression = taggedTemplateExpression; +exports.templateElement = templateElement; +exports.templateLiteral = templateLiteral; +exports.thisExpression = thisExpression; +exports.thisTypeAnnotation = thisTypeAnnotation; +exports.throwStatement = throwStatement; +exports.topicReference = topicReference; +exports.tryStatement = tryStatement; +exports.tSAnyKeyword = exports.tsAnyKeyword = tsAnyKeyword; +exports.tSArrayType = exports.tsArrayType = tsArrayType; +exports.tSAsExpression = exports.tsAsExpression = tsAsExpression; +exports.tSBigIntKeyword = exports.tsBigIntKeyword = tsBigIntKeyword; +exports.tSBooleanKeyword = exports.tsBooleanKeyword = tsBooleanKeyword; +exports.tSCallSignatureDeclaration = exports.tsCallSignatureDeclaration = tsCallSignatureDeclaration; +exports.tSConditionalType = exports.tsConditionalType = tsConditionalType; +exports.tSConstructSignatureDeclaration = exports.tsConstructSignatureDeclaration = tsConstructSignatureDeclaration; +exports.tSConstructorType = exports.tsConstructorType = tsConstructorType; +exports.tSDeclareFunction = exports.tsDeclareFunction = tsDeclareFunction; +exports.tSDeclareMethod = exports.tsDeclareMethod = tsDeclareMethod; +exports.tSEnumBody = exports.tsEnumBody = tsEnumBody; +exports.tSEnumDeclaration = exports.tsEnumDeclaration = tsEnumDeclaration; +exports.tSEnumMember = exports.tsEnumMember = tsEnumMember; +exports.tSExportAssignment = exports.tsExportAssignment = tsExportAssignment; +exports.tSExpressionWithTypeArguments = exports.tsExpressionWithTypeArguments = tsExpressionWithTypeArguments; +exports.tSExternalModuleReference = exports.tsExternalModuleReference = tsExternalModuleReference; +exports.tSFunctionType = exports.tsFunctionType = tsFunctionType; +exports.tSImportEqualsDeclaration = exports.tsImportEqualsDeclaration = tsImportEqualsDeclaration; +exports.tSImportType = exports.tsImportType = tsImportType; +exports.tSIndexSignature = exports.tsIndexSignature = tsIndexSignature; +exports.tSIndexedAccessType = exports.tsIndexedAccessType = tsIndexedAccessType; +exports.tSInferType = exports.tsInferType = tsInferType; +exports.tSInstantiationExpression = exports.tsInstantiationExpression = tsInstantiationExpression; +exports.tSInterfaceBody = exports.tsInterfaceBody = tsInterfaceBody; +exports.tSInterfaceDeclaration = exports.tsInterfaceDeclaration = tsInterfaceDeclaration; +exports.tSIntersectionType = exports.tsIntersectionType = tsIntersectionType; +exports.tSIntrinsicKeyword = exports.tsIntrinsicKeyword = tsIntrinsicKeyword; +exports.tSLiteralType = exports.tsLiteralType = tsLiteralType; +exports.tSMappedType = exports.tsMappedType = tsMappedType; +exports.tSMethodSignature = exports.tsMethodSignature = tsMethodSignature; +exports.tSModuleBlock = exports.tsModuleBlock = tsModuleBlock; +exports.tSModuleDeclaration = exports.tsModuleDeclaration = tsModuleDeclaration; +exports.tSNamedTupleMember = exports.tsNamedTupleMember = tsNamedTupleMember; +exports.tSNamespaceExportDeclaration = exports.tsNamespaceExportDeclaration = tsNamespaceExportDeclaration; +exports.tSNeverKeyword = exports.tsNeverKeyword = tsNeverKeyword; +exports.tSNonNullExpression = exports.tsNonNullExpression = tsNonNullExpression; +exports.tSNullKeyword = exports.tsNullKeyword = tsNullKeyword; +exports.tSNumberKeyword = exports.tsNumberKeyword = tsNumberKeyword; +exports.tSObjectKeyword = exports.tsObjectKeyword = tsObjectKeyword; +exports.tSOptionalType = exports.tsOptionalType = tsOptionalType; +exports.tSParameterProperty = exports.tsParameterProperty = tsParameterProperty; +exports.tSParenthesizedType = exports.tsParenthesizedType = tsParenthesizedType; +exports.tSPropertySignature = exports.tsPropertySignature = tsPropertySignature; +exports.tSQualifiedName = exports.tsQualifiedName = tsQualifiedName; +exports.tSRestType = exports.tsRestType = tsRestType; +exports.tSSatisfiesExpression = exports.tsSatisfiesExpression = tsSatisfiesExpression; +exports.tSStringKeyword = exports.tsStringKeyword = tsStringKeyword; +exports.tSSymbolKeyword = exports.tsSymbolKeyword = tsSymbolKeyword; +exports.tSTemplateLiteralType = exports.tsTemplateLiteralType = tsTemplateLiteralType; +exports.tSThisType = exports.tsThisType = tsThisType; +exports.tSTupleType = exports.tsTupleType = tsTupleType; +exports.tSTypeAliasDeclaration = exports.tsTypeAliasDeclaration = tsTypeAliasDeclaration; +exports.tSTypeAnnotation = exports.tsTypeAnnotation = tsTypeAnnotation; +exports.tSTypeAssertion = exports.tsTypeAssertion = tsTypeAssertion; +exports.tSTypeLiteral = exports.tsTypeLiteral = tsTypeLiteral; +exports.tSTypeOperator = exports.tsTypeOperator = tsTypeOperator; +exports.tSTypeParameter = exports.tsTypeParameter = tsTypeParameter; +exports.tSTypeParameterDeclaration = exports.tsTypeParameterDeclaration = tsTypeParameterDeclaration; +exports.tSTypeParameterInstantiation = exports.tsTypeParameterInstantiation = tsTypeParameterInstantiation; +exports.tSTypePredicate = exports.tsTypePredicate = tsTypePredicate; +exports.tSTypeQuery = exports.tsTypeQuery = tsTypeQuery; +exports.tSTypeReference = exports.tsTypeReference = tsTypeReference; +exports.tSUndefinedKeyword = exports.tsUndefinedKeyword = tsUndefinedKeyword; +exports.tSUnionType = exports.tsUnionType = tsUnionType; +exports.tSUnknownKeyword = exports.tsUnknownKeyword = tsUnknownKeyword; +exports.tSVoidKeyword = exports.tsVoidKeyword = tsVoidKeyword; +exports.tupleExpression = tupleExpression; +exports.tupleTypeAnnotation = tupleTypeAnnotation; +exports.typeAlias = typeAlias; +exports.typeAnnotation = typeAnnotation; +exports.typeCastExpression = typeCastExpression; +exports.typeParameter = typeParameter; +exports.typeParameterDeclaration = typeParameterDeclaration; +exports.typeParameterInstantiation = typeParameterInstantiation; +exports.typeofTypeAnnotation = typeofTypeAnnotation; +exports.unaryExpression = unaryExpression; +exports.unionTypeAnnotation = unionTypeAnnotation; +exports.updateExpression = updateExpression; +exports.v8IntrinsicIdentifier = v8IntrinsicIdentifier; +exports.variableDeclaration = variableDeclaration; +exports.variableDeclarator = variableDeclarator; +exports.variance = variance; +exports.voidTypeAnnotation = voidTypeAnnotation; +exports.whileStatement = whileStatement; +exports.withStatement = withStatement; +exports.yieldExpression = yieldExpression; +var _validate = __nccwpck_require__(71581); +var _deprecationWarning = __nccwpck_require__(14711); +var utils = __nccwpck_require__(34559); +const { + validateInternal: validate +} = _validate; +const { + NODE_FIELDS +} = utils; +function arrayExpression(elements = []) { + const node = { + type: "ArrayExpression", + elements + }; + const defs = NODE_FIELDS.ArrayExpression; + validate(defs.elements, node, "elements", elements, 1); + return node; +} +function assignmentExpression(operator, left, right) { + const node = { + type: "AssignmentExpression", + operator, + left, + right + }; + const defs = NODE_FIELDS.AssignmentExpression; + validate(defs.operator, node, "operator", operator); + validate(defs.left, node, "left", left, 1); + validate(defs.right, node, "right", right, 1); + return node; +} +function binaryExpression(operator, left, right) { + const node = { + type: "BinaryExpression", + operator, + left, + right + }; + const defs = NODE_FIELDS.BinaryExpression; + validate(defs.operator, node, "operator", operator); + validate(defs.left, node, "left", left, 1); + validate(defs.right, node, "right", right, 1); + return node; +} +function interpreterDirective(value) { + const node = { + type: "InterpreterDirective", + value + }; + const defs = NODE_FIELDS.InterpreterDirective; + validate(defs.value, node, "value", value); + return node; +} +function directive(value) { + const node = { + type: "Directive", + value + }; + const defs = NODE_FIELDS.Directive; + validate(defs.value, node, "value", value, 1); + return node; +} +function directiveLiteral(value) { + const node = { + type: "DirectiveLiteral", + value + }; + const defs = NODE_FIELDS.DirectiveLiteral; + validate(defs.value, node, "value", value); + return node; +} +function blockStatement(body, directives = []) { + const node = { + type: "BlockStatement", + body, + directives + }; + const defs = NODE_FIELDS.BlockStatement; + validate(defs.body, node, "body", body, 1); + validate(defs.directives, node, "directives", directives, 1); + return node; +} +function breakStatement(label = null) { + const node = { + type: "BreakStatement", + label + }; + const defs = NODE_FIELDS.BreakStatement; + validate(defs.label, node, "label", label, 1); + return node; +} +function callExpression(callee, _arguments) { + const node = { + type: "CallExpression", + callee, + arguments: _arguments + }; + const defs = NODE_FIELDS.CallExpression; + validate(defs.callee, node, "callee", callee, 1); + validate(defs.arguments, node, "arguments", _arguments, 1); + return node; +} +function catchClause(param = null, body) { + const node = { + type: "CatchClause", + param, + body + }; + const defs = NODE_FIELDS.CatchClause; + validate(defs.param, node, "param", param, 1); + validate(defs.body, node, "body", body, 1); + return node; +} +function conditionalExpression(test, consequent, alternate) { + const node = { + type: "ConditionalExpression", + test, + consequent, + alternate + }; + const defs = NODE_FIELDS.ConditionalExpression; + validate(defs.test, node, "test", test, 1); + validate(defs.consequent, node, "consequent", consequent, 1); + validate(defs.alternate, node, "alternate", alternate, 1); + return node; +} +function continueStatement(label = null) { + const node = { + type: "ContinueStatement", + label + }; + const defs = NODE_FIELDS.ContinueStatement; + validate(defs.label, node, "label", label, 1); + return node; +} +function debuggerStatement() { + return { + type: "DebuggerStatement" + }; +} +function doWhileStatement(test, body) { + const node = { + type: "DoWhileStatement", + test, + body + }; + const defs = NODE_FIELDS.DoWhileStatement; + validate(defs.test, node, "test", test, 1); + validate(defs.body, node, "body", body, 1); + return node; +} +function emptyStatement() { + return { + type: "EmptyStatement" + }; +} +function expressionStatement(expression) { + const node = { + type: "ExpressionStatement", + expression + }; + const defs = NODE_FIELDS.ExpressionStatement; + validate(defs.expression, node, "expression", expression, 1); + return node; +} +function file(program, comments = null, tokens = null) { + const node = { + type: "File", + program, + comments, + tokens + }; + const defs = NODE_FIELDS.File; + validate(defs.program, node, "program", program, 1); + validate(defs.comments, node, "comments", comments, 1); + validate(defs.tokens, node, "tokens", tokens); + return node; +} +function forInStatement(left, right, body) { + const node = { + type: "ForInStatement", + left, + right, + body + }; + const defs = NODE_FIELDS.ForInStatement; + validate(defs.left, node, "left", left, 1); + validate(defs.right, node, "right", right, 1); + validate(defs.body, node, "body", body, 1); + return node; +} +function forStatement(init = null, test = null, update = null, body) { + const node = { + type: "ForStatement", + init, + test, + update, + body + }; + const defs = NODE_FIELDS.ForStatement; + validate(defs.init, node, "init", init, 1); + validate(defs.test, node, "test", test, 1); + validate(defs.update, node, "update", update, 1); + validate(defs.body, node, "body", body, 1); + return node; +} +function functionDeclaration(id = null, params, body, generator = false, async = false) { + const node = { + type: "FunctionDeclaration", + id, + params, + body, + generator, + async + }; + const defs = NODE_FIELDS.FunctionDeclaration; + validate(defs.id, node, "id", id, 1); + validate(defs.params, node, "params", params, 1); + validate(defs.body, node, "body", body, 1); + validate(defs.generator, node, "generator", generator); + validate(defs.async, node, "async", async); + return node; +} +function functionExpression(id = null, params, body, generator = false, async = false) { + const node = { + type: "FunctionExpression", + id, + params, + body, + generator, + async + }; + const defs = NODE_FIELDS.FunctionExpression; + validate(defs.id, node, "id", id, 1); + validate(defs.params, node, "params", params, 1); + validate(defs.body, node, "body", body, 1); + validate(defs.generator, node, "generator", generator); + validate(defs.async, node, "async", async); + return node; +} +function identifier(name) { + const node = { + type: "Identifier", + name + }; + const defs = NODE_FIELDS.Identifier; + validate(defs.name, node, "name", name); + return node; +} +function ifStatement(test, consequent, alternate = null) { + const node = { + type: "IfStatement", + test, + consequent, + alternate + }; + const defs = NODE_FIELDS.IfStatement; + validate(defs.test, node, "test", test, 1); + validate(defs.consequent, node, "consequent", consequent, 1); + validate(defs.alternate, node, "alternate", alternate, 1); + return node; +} +function labeledStatement(label, body) { + const node = { + type: "LabeledStatement", + label, + body + }; + const defs = NODE_FIELDS.LabeledStatement; + validate(defs.label, node, "label", label, 1); + validate(defs.body, node, "body", body, 1); + return node; +} +function stringLiteral(value) { + const node = { + type: "StringLiteral", + value + }; + const defs = NODE_FIELDS.StringLiteral; + validate(defs.value, node, "value", value); + return node; +} +function numericLiteral(value) { + const node = { + type: "NumericLiteral", + value + }; + const defs = NODE_FIELDS.NumericLiteral; + validate(defs.value, node, "value", value); + return node; +} +function nullLiteral() { + return { + type: "NullLiteral" + }; +} +function booleanLiteral(value) { + const node = { + type: "BooleanLiteral", + value + }; + const defs = NODE_FIELDS.BooleanLiteral; + validate(defs.value, node, "value", value); + return node; +} +function regExpLiteral(pattern, flags = "") { + const node = { + type: "RegExpLiteral", + pattern, + flags + }; + const defs = NODE_FIELDS.RegExpLiteral; + validate(defs.pattern, node, "pattern", pattern); + validate(defs.flags, node, "flags", flags); + return node; +} +function logicalExpression(operator, left, right) { + const node = { + type: "LogicalExpression", + operator, + left, + right + }; + const defs = NODE_FIELDS.LogicalExpression; + validate(defs.operator, node, "operator", operator); + validate(defs.left, node, "left", left, 1); + validate(defs.right, node, "right", right, 1); + return node; +} +function memberExpression(object, property, computed = false, optional = null) { + const node = { + type: "MemberExpression", + object, + property, + computed, + optional + }; + const defs = NODE_FIELDS.MemberExpression; + validate(defs.object, node, "object", object, 1); + validate(defs.property, node, "property", property, 1); + validate(defs.computed, node, "computed", computed); + validate(defs.optional, node, "optional", optional); + return node; +} +function newExpression(callee, _arguments) { + const node = { + type: "NewExpression", + callee, + arguments: _arguments + }; + const defs = NODE_FIELDS.NewExpression; + validate(defs.callee, node, "callee", callee, 1); + validate(defs.arguments, node, "arguments", _arguments, 1); + return node; +} +function program(body, directives = [], sourceType = "script", interpreter = null) { + const node = { + type: "Program", + body, + directives, + sourceType, + interpreter + }; + const defs = NODE_FIELDS.Program; + validate(defs.body, node, "body", body, 1); + validate(defs.directives, node, "directives", directives, 1); + validate(defs.sourceType, node, "sourceType", sourceType); + validate(defs.interpreter, node, "interpreter", interpreter, 1); + return node; +} +function objectExpression(properties) { + const node = { + type: "ObjectExpression", + properties + }; + const defs = NODE_FIELDS.ObjectExpression; + validate(defs.properties, node, "properties", properties, 1); + return node; +} +function objectMethod(kind = "method", key, params, body, computed = false, generator = false, async = false) { + const node = { + type: "ObjectMethod", + kind, + key, + params, + body, + computed, + generator, + async + }; + const defs = NODE_FIELDS.ObjectMethod; + validate(defs.kind, node, "kind", kind); + validate(defs.key, node, "key", key, 1); + validate(defs.params, node, "params", params, 1); + validate(defs.body, node, "body", body, 1); + validate(defs.computed, node, "computed", computed); + validate(defs.generator, node, "generator", generator); + validate(defs.async, node, "async", async); + return node; +} +function objectProperty(key, value, computed = false, shorthand = false, decorators = null) { + const node = { + type: "ObjectProperty", + key, + value, + computed, + shorthand, + decorators + }; + const defs = NODE_FIELDS.ObjectProperty; + validate(defs.key, node, "key", key, 1); + validate(defs.value, node, "value", value, 1); + validate(defs.computed, node, "computed", computed); + validate(defs.shorthand, node, "shorthand", shorthand); + validate(defs.decorators, node, "decorators", decorators, 1); + return node; +} +function restElement(argument) { + const node = { + type: "RestElement", + argument + }; + const defs = NODE_FIELDS.RestElement; + validate(defs.argument, node, "argument", argument, 1); + return node; +} +function returnStatement(argument = null) { + const node = { + type: "ReturnStatement", + argument + }; + const defs = NODE_FIELDS.ReturnStatement; + validate(defs.argument, node, "argument", argument, 1); + return node; +} +function sequenceExpression(expressions) { + const node = { + type: "SequenceExpression", + expressions + }; + const defs = NODE_FIELDS.SequenceExpression; + validate(defs.expressions, node, "expressions", expressions, 1); + return node; +} +function parenthesizedExpression(expression) { + const node = { + type: "ParenthesizedExpression", + expression + }; + const defs = NODE_FIELDS.ParenthesizedExpression; + validate(defs.expression, node, "expression", expression, 1); + return node; +} +function switchCase(test = null, consequent) { + const node = { + type: "SwitchCase", + test, + consequent + }; + const defs = NODE_FIELDS.SwitchCase; + validate(defs.test, node, "test", test, 1); + validate(defs.consequent, node, "consequent", consequent, 1); + return node; +} +function switchStatement(discriminant, cases) { + const node = { + type: "SwitchStatement", + discriminant, + cases + }; + const defs = NODE_FIELDS.SwitchStatement; + validate(defs.discriminant, node, "discriminant", discriminant, 1); + validate(defs.cases, node, "cases", cases, 1); + return node; +} +function thisExpression() { + return { + type: "ThisExpression" + }; +} +function throwStatement(argument) { + const node = { + type: "ThrowStatement", + argument + }; + const defs = NODE_FIELDS.ThrowStatement; + validate(defs.argument, node, "argument", argument, 1); + return node; +} +function tryStatement(block, handler = null, finalizer = null) { + const node = { + type: "TryStatement", + block, + handler, + finalizer + }; + const defs = NODE_FIELDS.TryStatement; + validate(defs.block, node, "block", block, 1); + validate(defs.handler, node, "handler", handler, 1); + validate(defs.finalizer, node, "finalizer", finalizer, 1); + return node; +} +function unaryExpression(operator, argument, prefix = true) { + const node = { + type: "UnaryExpression", + operator, + argument, + prefix + }; + const defs = NODE_FIELDS.UnaryExpression; + validate(defs.operator, node, "operator", operator); + validate(defs.argument, node, "argument", argument, 1); + validate(defs.prefix, node, "prefix", prefix); + return node; +} +function updateExpression(operator, argument, prefix = false) { + const node = { + type: "UpdateExpression", + operator, + argument, + prefix + }; + const defs = NODE_FIELDS.UpdateExpression; + validate(defs.operator, node, "operator", operator); + validate(defs.argument, node, "argument", argument, 1); + validate(defs.prefix, node, "prefix", prefix); + return node; +} +function variableDeclaration(kind, declarations) { + const node = { + type: "VariableDeclaration", + kind, + declarations + }; + const defs = NODE_FIELDS.VariableDeclaration; + validate(defs.kind, node, "kind", kind); + validate(defs.declarations, node, "declarations", declarations, 1); + return node; +} +function variableDeclarator(id, init = null) { + const node = { + type: "VariableDeclarator", + id, + init + }; + const defs = NODE_FIELDS.VariableDeclarator; + validate(defs.id, node, "id", id, 1); + validate(defs.init, node, "init", init, 1); + return node; +} +function whileStatement(test, body) { + const node = { + type: "WhileStatement", + test, + body + }; + const defs = NODE_FIELDS.WhileStatement; + validate(defs.test, node, "test", test, 1); + validate(defs.body, node, "body", body, 1); + return node; +} +function withStatement(object, body) { + const node = { + type: "WithStatement", + object, + body + }; + const defs = NODE_FIELDS.WithStatement; + validate(defs.object, node, "object", object, 1); + validate(defs.body, node, "body", body, 1); + return node; +} +function assignmentPattern(left, right) { + const node = { + type: "AssignmentPattern", + left, + right + }; + const defs = NODE_FIELDS.AssignmentPattern; + validate(defs.left, node, "left", left, 1); + validate(defs.right, node, "right", right, 1); + return node; +} +function arrayPattern(elements) { + const node = { + type: "ArrayPattern", + elements + }; + const defs = NODE_FIELDS.ArrayPattern; + validate(defs.elements, node, "elements", elements, 1); + return node; +} +function arrowFunctionExpression(params, body, async = false) { + const node = { + type: "ArrowFunctionExpression", + params, + body, + async, + expression: null + }; + const defs = NODE_FIELDS.ArrowFunctionExpression; + validate(defs.params, node, "params", params, 1); + validate(defs.body, node, "body", body, 1); + validate(defs.async, node, "async", async); + return node; +} +function classBody(body) { + const node = { + type: "ClassBody", + body + }; + const defs = NODE_FIELDS.ClassBody; + validate(defs.body, node, "body", body, 1); + return node; +} +function classExpression(id = null, superClass = null, body, decorators = null) { + const node = { + type: "ClassExpression", + id, + superClass, + body, + decorators + }; + const defs = NODE_FIELDS.ClassExpression; + validate(defs.id, node, "id", id, 1); + validate(defs.superClass, node, "superClass", superClass, 1); + validate(defs.body, node, "body", body, 1); + validate(defs.decorators, node, "decorators", decorators, 1); + return node; +} +function classDeclaration(id = null, superClass = null, body, decorators = null) { + const node = { + type: "ClassDeclaration", + id, + superClass, + body, + decorators + }; + const defs = NODE_FIELDS.ClassDeclaration; + validate(defs.id, node, "id", id, 1); + validate(defs.superClass, node, "superClass", superClass, 1); + validate(defs.body, node, "body", body, 1); + validate(defs.decorators, node, "decorators", decorators, 1); + return node; +} +function exportAllDeclaration(source) { + const node = { + type: "ExportAllDeclaration", + source + }; + const defs = NODE_FIELDS.ExportAllDeclaration; + validate(defs.source, node, "source", source, 1); + return node; +} +function exportDefaultDeclaration(declaration) { + const node = { + type: "ExportDefaultDeclaration", + declaration + }; + const defs = NODE_FIELDS.ExportDefaultDeclaration; + validate(defs.declaration, node, "declaration", declaration, 1); + return node; +} +function exportNamedDeclaration(declaration = null, specifiers = [], source = null) { + const node = { + type: "ExportNamedDeclaration", + declaration, + specifiers, + source + }; + const defs = NODE_FIELDS.ExportNamedDeclaration; + validate(defs.declaration, node, "declaration", declaration, 1); + validate(defs.specifiers, node, "specifiers", specifiers, 1); + validate(defs.source, node, "source", source, 1); + return node; +} +function exportSpecifier(local, exported) { + const node = { + type: "ExportSpecifier", + local, + exported + }; + const defs = NODE_FIELDS.ExportSpecifier; + validate(defs.local, node, "local", local, 1); + validate(defs.exported, node, "exported", exported, 1); + return node; +} +function forOfStatement(left, right, body, _await = false) { + const node = { + type: "ForOfStatement", + left, + right, + body, + await: _await + }; + const defs = NODE_FIELDS.ForOfStatement; + validate(defs.left, node, "left", left, 1); + validate(defs.right, node, "right", right, 1); + validate(defs.body, node, "body", body, 1); + validate(defs.await, node, "await", _await); + return node; +} +function importDeclaration(specifiers, source) { + const node = { + type: "ImportDeclaration", + specifiers, + source + }; + const defs = NODE_FIELDS.ImportDeclaration; + validate(defs.specifiers, node, "specifiers", specifiers, 1); + validate(defs.source, node, "source", source, 1); + return node; +} +function importDefaultSpecifier(local) { + const node = { + type: "ImportDefaultSpecifier", + local + }; + const defs = NODE_FIELDS.ImportDefaultSpecifier; + validate(defs.local, node, "local", local, 1); + return node; +} +function importNamespaceSpecifier(local) { + const node = { + type: "ImportNamespaceSpecifier", + local + }; + const defs = NODE_FIELDS.ImportNamespaceSpecifier; + validate(defs.local, node, "local", local, 1); + return node; +} +function importSpecifier(local, imported) { + const node = { + type: "ImportSpecifier", + local, + imported + }; + const defs = NODE_FIELDS.ImportSpecifier; + validate(defs.local, node, "local", local, 1); + validate(defs.imported, node, "imported", imported, 1); + return node; +} +function importExpression(source, options = null) { + const node = { + type: "ImportExpression", + source, + options + }; + const defs = NODE_FIELDS.ImportExpression; + validate(defs.source, node, "source", source, 1); + validate(defs.options, node, "options", options, 1); + return node; +} +function metaProperty(meta, property) { + const node = { + type: "MetaProperty", + meta, + property + }; + const defs = NODE_FIELDS.MetaProperty; + validate(defs.meta, node, "meta", meta, 1); + validate(defs.property, node, "property", property, 1); + return node; +} +function classMethod(kind = "method", key, params, body, computed = false, _static = false, generator = false, async = false) { + const node = { + type: "ClassMethod", + kind, + key, + params, + body, + computed, + static: _static, + generator, + async + }; + const defs = NODE_FIELDS.ClassMethod; + validate(defs.kind, node, "kind", kind); + validate(defs.key, node, "key", key, 1); + validate(defs.params, node, "params", params, 1); + validate(defs.body, node, "body", body, 1); + validate(defs.computed, node, "computed", computed); + validate(defs.static, node, "static", _static); + validate(defs.generator, node, "generator", generator); + validate(defs.async, node, "async", async); + return node; +} +function objectPattern(properties) { + const node = { + type: "ObjectPattern", + properties + }; + const defs = NODE_FIELDS.ObjectPattern; + validate(defs.properties, node, "properties", properties, 1); + return node; +} +function spreadElement(argument) { + const node = { + type: "SpreadElement", + argument + }; + const defs = NODE_FIELDS.SpreadElement; + validate(defs.argument, node, "argument", argument, 1); + return node; +} +function _super() { + return { + type: "Super" + }; +} +function taggedTemplateExpression(tag, quasi) { + const node = { + type: "TaggedTemplateExpression", + tag, + quasi + }; + const defs = NODE_FIELDS.TaggedTemplateExpression; + validate(defs.tag, node, "tag", tag, 1); + validate(defs.quasi, node, "quasi", quasi, 1); + return node; +} +function templateElement(value, tail = false) { + const node = { + type: "TemplateElement", + value, + tail + }; + const defs = NODE_FIELDS.TemplateElement; + validate(defs.value, node, "value", value); + validate(defs.tail, node, "tail", tail); + return node; +} +function templateLiteral(quasis, expressions) { + const node = { + type: "TemplateLiteral", + quasis, + expressions + }; + const defs = NODE_FIELDS.TemplateLiteral; + validate(defs.quasis, node, "quasis", quasis, 1); + validate(defs.expressions, node, "expressions", expressions, 1); + return node; +} +function yieldExpression(argument = null, delegate = false) { + const node = { + type: "YieldExpression", + argument, + delegate + }; + const defs = NODE_FIELDS.YieldExpression; + validate(defs.argument, node, "argument", argument, 1); + validate(defs.delegate, node, "delegate", delegate); + return node; +} +function awaitExpression(argument) { + const node = { + type: "AwaitExpression", + argument + }; + const defs = NODE_FIELDS.AwaitExpression; + validate(defs.argument, node, "argument", argument, 1); + return node; +} +function _import() { + return { + type: "Import" + }; +} +function bigIntLiteral(value) { + const node = { + type: "BigIntLiteral", + value + }; + const defs = NODE_FIELDS.BigIntLiteral; + validate(defs.value, node, "value", value); + return node; +} +function exportNamespaceSpecifier(exported) { + const node = { + type: "ExportNamespaceSpecifier", + exported + }; + const defs = NODE_FIELDS.ExportNamespaceSpecifier; + validate(defs.exported, node, "exported", exported, 1); + return node; +} +function optionalMemberExpression(object, property, computed = false, optional) { + const node = { + type: "OptionalMemberExpression", + object, + property, + computed, + optional + }; + const defs = NODE_FIELDS.OptionalMemberExpression; + validate(defs.object, node, "object", object, 1); + validate(defs.property, node, "property", property, 1); + validate(defs.computed, node, "computed", computed); + validate(defs.optional, node, "optional", optional); + return node; +} +function optionalCallExpression(callee, _arguments, optional) { + const node = { + type: "OptionalCallExpression", + callee, + arguments: _arguments, + optional + }; + const defs = NODE_FIELDS.OptionalCallExpression; + validate(defs.callee, node, "callee", callee, 1); + validate(defs.arguments, node, "arguments", _arguments, 1); + validate(defs.optional, node, "optional", optional); + return node; +} +function classProperty(key, value = null, typeAnnotation = null, decorators = null, computed = false, _static = false) { + const node = { + type: "ClassProperty", + key, + value, + typeAnnotation, + decorators, + computed, + static: _static + }; + const defs = NODE_FIELDS.ClassProperty; + validate(defs.key, node, "key", key, 1); + validate(defs.value, node, "value", value, 1); + validate(defs.typeAnnotation, node, "typeAnnotation", typeAnnotation, 1); + validate(defs.decorators, node, "decorators", decorators, 1); + validate(defs.computed, node, "computed", computed); + validate(defs.static, node, "static", _static); + return node; +} +function classAccessorProperty(key, value = null, typeAnnotation = null, decorators = null, computed = false, _static = false) { + const node = { + type: "ClassAccessorProperty", + key, + value, + typeAnnotation, + decorators, + computed, + static: _static + }; + const defs = NODE_FIELDS.ClassAccessorProperty; + validate(defs.key, node, "key", key, 1); + validate(defs.value, node, "value", value, 1); + validate(defs.typeAnnotation, node, "typeAnnotation", typeAnnotation, 1); + validate(defs.decorators, node, "decorators", decorators, 1); + validate(defs.computed, node, "computed", computed); + validate(defs.static, node, "static", _static); + return node; +} +function classPrivateProperty(key, value = null, decorators = null, _static = false) { + const node = { + type: "ClassPrivateProperty", + key, + value, + decorators, + static: _static + }; + const defs = NODE_FIELDS.ClassPrivateProperty; + validate(defs.key, node, "key", key, 1); + validate(defs.value, node, "value", value, 1); + validate(defs.decorators, node, "decorators", decorators, 1); + validate(defs.static, node, "static", _static); + return node; +} +function classPrivateMethod(kind = "method", key, params, body, _static = false) { + const node = { + type: "ClassPrivateMethod", + kind, + key, + params, + body, + static: _static + }; + const defs = NODE_FIELDS.ClassPrivateMethod; + validate(defs.kind, node, "kind", kind); + validate(defs.key, node, "key", key, 1); + validate(defs.params, node, "params", params, 1); + validate(defs.body, node, "body", body, 1); + validate(defs.static, node, "static", _static); + return node; +} +function privateName(id) { + const node = { + type: "PrivateName", + id + }; + const defs = NODE_FIELDS.PrivateName; + validate(defs.id, node, "id", id, 1); + return node; +} +function staticBlock(body) { + const node = { + type: "StaticBlock", + body + }; + const defs = NODE_FIELDS.StaticBlock; + validate(defs.body, node, "body", body, 1); + return node; +} +function importAttribute(key, value) { + const node = { + type: "ImportAttribute", + key, + value + }; + const defs = NODE_FIELDS.ImportAttribute; + validate(defs.key, node, "key", key, 1); + validate(defs.value, node, "value", value, 1); + return node; +} +function anyTypeAnnotation() { + return { + type: "AnyTypeAnnotation" + }; +} +function arrayTypeAnnotation(elementType) { + const node = { + type: "ArrayTypeAnnotation", + elementType + }; + const defs = NODE_FIELDS.ArrayTypeAnnotation; + validate(defs.elementType, node, "elementType", elementType, 1); + return node; +} +function booleanTypeAnnotation() { + return { + type: "BooleanTypeAnnotation" + }; +} +function booleanLiteralTypeAnnotation(value) { + const node = { + type: "BooleanLiteralTypeAnnotation", + value + }; + const defs = NODE_FIELDS.BooleanLiteralTypeAnnotation; + validate(defs.value, node, "value", value); + return node; +} +function nullLiteralTypeAnnotation() { + return { + type: "NullLiteralTypeAnnotation" + }; +} +function classImplements(id, typeParameters = null) { + const node = { + type: "ClassImplements", + id, + typeParameters + }; + const defs = NODE_FIELDS.ClassImplements; + validate(defs.id, node, "id", id, 1); + validate(defs.typeParameters, node, "typeParameters", typeParameters, 1); + return node; +} +function declareClass(id, typeParameters = null, _extends = null, body) { + const node = { + type: "DeclareClass", + id, + typeParameters, + extends: _extends, + body + }; + const defs = NODE_FIELDS.DeclareClass; + validate(defs.id, node, "id", id, 1); + validate(defs.typeParameters, node, "typeParameters", typeParameters, 1); + validate(defs.extends, node, "extends", _extends, 1); + validate(defs.body, node, "body", body, 1); + return node; +} +function declareFunction(id) { + const node = { + type: "DeclareFunction", + id + }; + const defs = NODE_FIELDS.DeclareFunction; + validate(defs.id, node, "id", id, 1); + return node; +} +function declareInterface(id, typeParameters = null, _extends = null, body) { + const node = { + type: "DeclareInterface", + id, + typeParameters, + extends: _extends, + body + }; + const defs = NODE_FIELDS.DeclareInterface; + validate(defs.id, node, "id", id, 1); + validate(defs.typeParameters, node, "typeParameters", typeParameters, 1); + validate(defs.extends, node, "extends", _extends, 1); + validate(defs.body, node, "body", body, 1); + return node; +} +function declareModule(id, body, kind = null) { + const node = { + type: "DeclareModule", + id, + body, + kind + }; + const defs = NODE_FIELDS.DeclareModule; + validate(defs.id, node, "id", id, 1); + validate(defs.body, node, "body", body, 1); + validate(defs.kind, node, "kind", kind); + return node; +} +function declareModuleExports(typeAnnotation) { + const node = { + type: "DeclareModuleExports", + typeAnnotation + }; + const defs = NODE_FIELDS.DeclareModuleExports; + validate(defs.typeAnnotation, node, "typeAnnotation", typeAnnotation, 1); + return node; +} +function declareTypeAlias(id, typeParameters = null, right) { + const node = { + type: "DeclareTypeAlias", + id, + typeParameters, + right + }; + const defs = NODE_FIELDS.DeclareTypeAlias; + validate(defs.id, node, "id", id, 1); + validate(defs.typeParameters, node, "typeParameters", typeParameters, 1); + validate(defs.right, node, "right", right, 1); + return node; +} +function declareOpaqueType(id, typeParameters = null, supertype = null) { + const node = { + type: "DeclareOpaqueType", + id, + typeParameters, + supertype + }; + const defs = NODE_FIELDS.DeclareOpaqueType; + validate(defs.id, node, "id", id, 1); + validate(defs.typeParameters, node, "typeParameters", typeParameters, 1); + validate(defs.supertype, node, "supertype", supertype, 1); + return node; +} +function declareVariable(id) { + const node = { + type: "DeclareVariable", + id + }; + const defs = NODE_FIELDS.DeclareVariable; + validate(defs.id, node, "id", id, 1); + return node; +} +function declareExportDeclaration(declaration = null, specifiers = null, source = null, attributes = null) { + const node = { + type: "DeclareExportDeclaration", + declaration, + specifiers, + source, + attributes + }; + const defs = NODE_FIELDS.DeclareExportDeclaration; + validate(defs.declaration, node, "declaration", declaration, 1); + validate(defs.specifiers, node, "specifiers", specifiers, 1); + validate(defs.source, node, "source", source, 1); + validate(defs.attributes, node, "attributes", attributes, 1); + return node; +} +function declareExportAllDeclaration(source, attributes = null) { + const node = { + type: "DeclareExportAllDeclaration", + source, + attributes + }; + const defs = NODE_FIELDS.DeclareExportAllDeclaration; + validate(defs.source, node, "source", source, 1); + validate(defs.attributes, node, "attributes", attributes, 1); + return node; +} +function declaredPredicate(value) { + const node = { + type: "DeclaredPredicate", + value + }; + const defs = NODE_FIELDS.DeclaredPredicate; + validate(defs.value, node, "value", value, 1); + return node; +} +function existsTypeAnnotation() { + return { + type: "ExistsTypeAnnotation" + }; +} +function functionTypeAnnotation(typeParameters = null, params, rest = null, returnType) { + const node = { + type: "FunctionTypeAnnotation", + typeParameters, + params, + rest, + returnType + }; + const defs = NODE_FIELDS.FunctionTypeAnnotation; + validate(defs.typeParameters, node, "typeParameters", typeParameters, 1); + validate(defs.params, node, "params", params, 1); + validate(defs.rest, node, "rest", rest, 1); + validate(defs.returnType, node, "returnType", returnType, 1); + return node; +} +function functionTypeParam(name = null, typeAnnotation) { + const node = { + type: "FunctionTypeParam", + name, + typeAnnotation + }; + const defs = NODE_FIELDS.FunctionTypeParam; + validate(defs.name, node, "name", name, 1); + validate(defs.typeAnnotation, node, "typeAnnotation", typeAnnotation, 1); + return node; +} +function genericTypeAnnotation(id, typeParameters = null) { + const node = { + type: "GenericTypeAnnotation", + id, + typeParameters + }; + const defs = NODE_FIELDS.GenericTypeAnnotation; + validate(defs.id, node, "id", id, 1); + validate(defs.typeParameters, node, "typeParameters", typeParameters, 1); + return node; +} +function inferredPredicate() { + return { + type: "InferredPredicate" + }; +} +function interfaceExtends(id, typeParameters = null) { + const node = { + type: "InterfaceExtends", + id, + typeParameters + }; + const defs = NODE_FIELDS.InterfaceExtends; + validate(defs.id, node, "id", id, 1); + validate(defs.typeParameters, node, "typeParameters", typeParameters, 1); + return node; +} +function interfaceDeclaration(id, typeParameters = null, _extends = null, body) { + const node = { + type: "InterfaceDeclaration", + id, + typeParameters, + extends: _extends, + body + }; + const defs = NODE_FIELDS.InterfaceDeclaration; + validate(defs.id, node, "id", id, 1); + validate(defs.typeParameters, node, "typeParameters", typeParameters, 1); + validate(defs.extends, node, "extends", _extends, 1); + validate(defs.body, node, "body", body, 1); + return node; +} +function interfaceTypeAnnotation(_extends = null, body) { + const node = { + type: "InterfaceTypeAnnotation", + extends: _extends, + body + }; + const defs = NODE_FIELDS.InterfaceTypeAnnotation; + validate(defs.extends, node, "extends", _extends, 1); + validate(defs.body, node, "body", body, 1); + return node; +} +function intersectionTypeAnnotation(types) { + const node = { + type: "IntersectionTypeAnnotation", + types + }; + const defs = NODE_FIELDS.IntersectionTypeAnnotation; + validate(defs.types, node, "types", types, 1); + return node; +} +function mixedTypeAnnotation() { + return { + type: "MixedTypeAnnotation" + }; +} +function emptyTypeAnnotation() { + return { + type: "EmptyTypeAnnotation" + }; +} +function nullableTypeAnnotation(typeAnnotation) { + const node = { + type: "NullableTypeAnnotation", + typeAnnotation + }; + const defs = NODE_FIELDS.NullableTypeAnnotation; + validate(defs.typeAnnotation, node, "typeAnnotation", typeAnnotation, 1); + return node; +} +function numberLiteralTypeAnnotation(value) { + const node = { + type: "NumberLiteralTypeAnnotation", + value + }; + const defs = NODE_FIELDS.NumberLiteralTypeAnnotation; + validate(defs.value, node, "value", value); + return node; +} +function numberTypeAnnotation() { + return { + type: "NumberTypeAnnotation" + }; +} +function objectTypeAnnotation(properties, indexers = [], callProperties = [], internalSlots = [], exact = false) { + const node = { + type: "ObjectTypeAnnotation", + properties, + indexers, + callProperties, + internalSlots, + exact + }; + const defs = NODE_FIELDS.ObjectTypeAnnotation; + validate(defs.properties, node, "properties", properties, 1); + validate(defs.indexers, node, "indexers", indexers, 1); + validate(defs.callProperties, node, "callProperties", callProperties, 1); + validate(defs.internalSlots, node, "internalSlots", internalSlots, 1); + validate(defs.exact, node, "exact", exact); + return node; +} +function objectTypeInternalSlot(id, value, optional, _static, method) { + const node = { + type: "ObjectTypeInternalSlot", + id, + value, + optional, + static: _static, + method + }; + const defs = NODE_FIELDS.ObjectTypeInternalSlot; + validate(defs.id, node, "id", id, 1); + validate(defs.value, node, "value", value, 1); + validate(defs.optional, node, "optional", optional); + validate(defs.static, node, "static", _static); + validate(defs.method, node, "method", method); + return node; +} +function objectTypeCallProperty(value) { + const node = { + type: "ObjectTypeCallProperty", + value, + static: null + }; + const defs = NODE_FIELDS.ObjectTypeCallProperty; + validate(defs.value, node, "value", value, 1); + return node; +} +function objectTypeIndexer(id = null, key, value, variance = null) { + const node = { + type: "ObjectTypeIndexer", + id, + key, + value, + variance, + static: null + }; + const defs = NODE_FIELDS.ObjectTypeIndexer; + validate(defs.id, node, "id", id, 1); + validate(defs.key, node, "key", key, 1); + validate(defs.value, node, "value", value, 1); + validate(defs.variance, node, "variance", variance, 1); + return node; +} +function objectTypeProperty(key, value, variance = null) { + const node = { + type: "ObjectTypeProperty", + key, + value, + variance, + kind: null, + method: null, + optional: null, + proto: null, + static: null + }; + const defs = NODE_FIELDS.ObjectTypeProperty; + validate(defs.key, node, "key", key, 1); + validate(defs.value, node, "value", value, 1); + validate(defs.variance, node, "variance", variance, 1); + return node; +} +function objectTypeSpreadProperty(argument) { + const node = { + type: "ObjectTypeSpreadProperty", + argument + }; + const defs = NODE_FIELDS.ObjectTypeSpreadProperty; + validate(defs.argument, node, "argument", argument, 1); + return node; +} +function opaqueType(id, typeParameters = null, supertype = null, impltype) { + const node = { + type: "OpaqueType", + id, + typeParameters, + supertype, + impltype + }; + const defs = NODE_FIELDS.OpaqueType; + validate(defs.id, node, "id", id, 1); + validate(defs.typeParameters, node, "typeParameters", typeParameters, 1); + validate(defs.supertype, node, "supertype", supertype, 1); + validate(defs.impltype, node, "impltype", impltype, 1); + return node; +} +function qualifiedTypeIdentifier(id, qualification) { + const node = { + type: "QualifiedTypeIdentifier", + id, + qualification + }; + const defs = NODE_FIELDS.QualifiedTypeIdentifier; + validate(defs.id, node, "id", id, 1); + validate(defs.qualification, node, "qualification", qualification, 1); + return node; +} +function stringLiteralTypeAnnotation(value) { + const node = { + type: "StringLiteralTypeAnnotation", + value + }; + const defs = NODE_FIELDS.StringLiteralTypeAnnotation; + validate(defs.value, node, "value", value); + return node; +} +function stringTypeAnnotation() { + return { + type: "StringTypeAnnotation" + }; +} +function symbolTypeAnnotation() { + return { + type: "SymbolTypeAnnotation" + }; +} +function thisTypeAnnotation() { + return { + type: "ThisTypeAnnotation" + }; +} +function tupleTypeAnnotation(types) { + const node = { + type: "TupleTypeAnnotation", + types + }; + const defs = NODE_FIELDS.TupleTypeAnnotation; + validate(defs.types, node, "types", types, 1); + return node; +} +function typeofTypeAnnotation(argument) { + const node = { + type: "TypeofTypeAnnotation", + argument + }; + const defs = NODE_FIELDS.TypeofTypeAnnotation; + validate(defs.argument, node, "argument", argument, 1); + return node; +} +function typeAlias(id, typeParameters = null, right) { + const node = { + type: "TypeAlias", + id, + typeParameters, + right + }; + const defs = NODE_FIELDS.TypeAlias; + validate(defs.id, node, "id", id, 1); + validate(defs.typeParameters, node, "typeParameters", typeParameters, 1); + validate(defs.right, node, "right", right, 1); + return node; +} +function typeAnnotation(typeAnnotation) { + const node = { + type: "TypeAnnotation", + typeAnnotation + }; + const defs = NODE_FIELDS.TypeAnnotation; + validate(defs.typeAnnotation, node, "typeAnnotation", typeAnnotation, 1); + return node; +} +function typeCastExpression(expression, typeAnnotation) { + const node = { + type: "TypeCastExpression", + expression, + typeAnnotation + }; + const defs = NODE_FIELDS.TypeCastExpression; + validate(defs.expression, node, "expression", expression, 1); + validate(defs.typeAnnotation, node, "typeAnnotation", typeAnnotation, 1); + return node; +} +function typeParameter(bound = null, _default = null, variance = null) { + const node = { + type: "TypeParameter", + bound, + default: _default, + variance, + name: null + }; + const defs = NODE_FIELDS.TypeParameter; + validate(defs.bound, node, "bound", bound, 1); + validate(defs.default, node, "default", _default, 1); + validate(defs.variance, node, "variance", variance, 1); + return node; +} +function typeParameterDeclaration(params) { + const node = { + type: "TypeParameterDeclaration", + params + }; + const defs = NODE_FIELDS.TypeParameterDeclaration; + validate(defs.params, node, "params", params, 1); + return node; +} +function typeParameterInstantiation(params) { + const node = { + type: "TypeParameterInstantiation", + params + }; + const defs = NODE_FIELDS.TypeParameterInstantiation; + validate(defs.params, node, "params", params, 1); + return node; +} +function unionTypeAnnotation(types) { + const node = { + type: "UnionTypeAnnotation", + types + }; + const defs = NODE_FIELDS.UnionTypeAnnotation; + validate(defs.types, node, "types", types, 1); + return node; +} +function variance(kind) { + const node = { + type: "Variance", + kind + }; + const defs = NODE_FIELDS.Variance; + validate(defs.kind, node, "kind", kind); + return node; +} +function voidTypeAnnotation() { + return { + type: "VoidTypeAnnotation" + }; +} +function enumDeclaration(id, body) { + const node = { + type: "EnumDeclaration", + id, + body + }; + const defs = NODE_FIELDS.EnumDeclaration; + validate(defs.id, node, "id", id, 1); + validate(defs.body, node, "body", body, 1); + return node; +} +function enumBooleanBody(members) { + const node = { + type: "EnumBooleanBody", + members, + explicitType: null, + hasUnknownMembers: null + }; + const defs = NODE_FIELDS.EnumBooleanBody; + validate(defs.members, node, "members", members, 1); + return node; +} +function enumNumberBody(members) { + const node = { + type: "EnumNumberBody", + members, + explicitType: null, + hasUnknownMembers: null + }; + const defs = NODE_FIELDS.EnumNumberBody; + validate(defs.members, node, "members", members, 1); + return node; +} +function enumStringBody(members) { + const node = { + type: "EnumStringBody", + members, + explicitType: null, + hasUnknownMembers: null + }; + const defs = NODE_FIELDS.EnumStringBody; + validate(defs.members, node, "members", members, 1); + return node; +} +function enumSymbolBody(members) { + const node = { + type: "EnumSymbolBody", + members, + hasUnknownMembers: null + }; + const defs = NODE_FIELDS.EnumSymbolBody; + validate(defs.members, node, "members", members, 1); + return node; +} +function enumBooleanMember(id) { + const node = { + type: "EnumBooleanMember", + id, + init: null + }; + const defs = NODE_FIELDS.EnumBooleanMember; + validate(defs.id, node, "id", id, 1); + return node; +} +function enumNumberMember(id, init) { + const node = { + type: "EnumNumberMember", + id, + init + }; + const defs = NODE_FIELDS.EnumNumberMember; + validate(defs.id, node, "id", id, 1); + validate(defs.init, node, "init", init, 1); + return node; +} +function enumStringMember(id, init) { + const node = { + type: "EnumStringMember", + id, + init + }; + const defs = NODE_FIELDS.EnumStringMember; + validate(defs.id, node, "id", id, 1); + validate(defs.init, node, "init", init, 1); + return node; +} +function enumDefaultedMember(id) { + const node = { + type: "EnumDefaultedMember", + id + }; + const defs = NODE_FIELDS.EnumDefaultedMember; + validate(defs.id, node, "id", id, 1); + return node; +} +function indexedAccessType(objectType, indexType) { + const node = { + type: "IndexedAccessType", + objectType, + indexType + }; + const defs = NODE_FIELDS.IndexedAccessType; + validate(defs.objectType, node, "objectType", objectType, 1); + validate(defs.indexType, node, "indexType", indexType, 1); + return node; +} +function optionalIndexedAccessType(objectType, indexType) { + const node = { + type: "OptionalIndexedAccessType", + objectType, + indexType, + optional: null + }; + const defs = NODE_FIELDS.OptionalIndexedAccessType; + validate(defs.objectType, node, "objectType", objectType, 1); + validate(defs.indexType, node, "indexType", indexType, 1); + return node; +} +function jsxAttribute(name, value = null) { + const node = { + type: "JSXAttribute", + name, + value + }; + const defs = NODE_FIELDS.JSXAttribute; + validate(defs.name, node, "name", name, 1); + validate(defs.value, node, "value", value, 1); + return node; +} +function jsxClosingElement(name) { + const node = { + type: "JSXClosingElement", + name + }; + const defs = NODE_FIELDS.JSXClosingElement; + validate(defs.name, node, "name", name, 1); + return node; +} +function jsxElement(openingElement, closingElement = null, children, selfClosing = null) { + const node = { + type: "JSXElement", + openingElement, + closingElement, + children, + selfClosing + }; + const defs = NODE_FIELDS.JSXElement; + validate(defs.openingElement, node, "openingElement", openingElement, 1); + validate(defs.closingElement, node, "closingElement", closingElement, 1); + validate(defs.children, node, "children", children, 1); + validate(defs.selfClosing, node, "selfClosing", selfClosing); + return node; +} +function jsxEmptyExpression() { + return { + type: "JSXEmptyExpression" + }; +} +function jsxExpressionContainer(expression) { + const node = { + type: "JSXExpressionContainer", + expression + }; + const defs = NODE_FIELDS.JSXExpressionContainer; + validate(defs.expression, node, "expression", expression, 1); + return node; +} +function jsxSpreadChild(expression) { + const node = { + type: "JSXSpreadChild", + expression + }; + const defs = NODE_FIELDS.JSXSpreadChild; + validate(defs.expression, node, "expression", expression, 1); + return node; +} +function jsxIdentifier(name) { + const node = { + type: "JSXIdentifier", + name + }; + const defs = NODE_FIELDS.JSXIdentifier; + validate(defs.name, node, "name", name); + return node; +} +function jsxMemberExpression(object, property) { + const node = { + type: "JSXMemberExpression", + object, + property + }; + const defs = NODE_FIELDS.JSXMemberExpression; + validate(defs.object, node, "object", object, 1); + validate(defs.property, node, "property", property, 1); + return node; +} +function jsxNamespacedName(namespace, name) { + const node = { + type: "JSXNamespacedName", + namespace, + name + }; + const defs = NODE_FIELDS.JSXNamespacedName; + validate(defs.namespace, node, "namespace", namespace, 1); + validate(defs.name, node, "name", name, 1); + return node; +} +function jsxOpeningElement(name, attributes, selfClosing = false) { + const node = { + type: "JSXOpeningElement", + name, + attributes, + selfClosing + }; + const defs = NODE_FIELDS.JSXOpeningElement; + validate(defs.name, node, "name", name, 1); + validate(defs.attributes, node, "attributes", attributes, 1); + validate(defs.selfClosing, node, "selfClosing", selfClosing); + return node; +} +function jsxSpreadAttribute(argument) { + const node = { + type: "JSXSpreadAttribute", + argument + }; + const defs = NODE_FIELDS.JSXSpreadAttribute; + validate(defs.argument, node, "argument", argument, 1); + return node; +} +function jsxText(value) { + const node = { + type: "JSXText", + value + }; + const defs = NODE_FIELDS.JSXText; + validate(defs.value, node, "value", value); + return node; +} +function jsxFragment(openingFragment, closingFragment, children) { + const node = { + type: "JSXFragment", + openingFragment, + closingFragment, + children + }; + const defs = NODE_FIELDS.JSXFragment; + validate(defs.openingFragment, node, "openingFragment", openingFragment, 1); + validate(defs.closingFragment, node, "closingFragment", closingFragment, 1); + validate(defs.children, node, "children", children, 1); + return node; +} +function jsxOpeningFragment() { + return { + type: "JSXOpeningFragment" + }; +} +function jsxClosingFragment() { + return { + type: "JSXClosingFragment" + }; +} +function noop() { + return { + type: "Noop" + }; +} +function placeholder(expectedNode, name) { + const node = { + type: "Placeholder", + expectedNode, + name + }; + const defs = NODE_FIELDS.Placeholder; + validate(defs.expectedNode, node, "expectedNode", expectedNode); + validate(defs.name, node, "name", name, 1); + return node; +} +function v8IntrinsicIdentifier(name) { + const node = { + type: "V8IntrinsicIdentifier", + name + }; + const defs = NODE_FIELDS.V8IntrinsicIdentifier; + validate(defs.name, node, "name", name); + return node; +} +function argumentPlaceholder() { + return { + type: "ArgumentPlaceholder" + }; +} +function bindExpression(object, callee) { + const node = { + type: "BindExpression", + object, + callee + }; + const defs = NODE_FIELDS.BindExpression; + validate(defs.object, node, "object", object, 1); + validate(defs.callee, node, "callee", callee, 1); + return node; +} +function decorator(expression) { + const node = { + type: "Decorator", + expression + }; + const defs = NODE_FIELDS.Decorator; + validate(defs.expression, node, "expression", expression, 1); + return node; +} +function doExpression(body, async = false) { + const node = { + type: "DoExpression", + body, + async + }; + const defs = NODE_FIELDS.DoExpression; + validate(defs.body, node, "body", body, 1); + validate(defs.async, node, "async", async); + return node; +} +function exportDefaultSpecifier(exported) { + const node = { + type: "ExportDefaultSpecifier", + exported + }; + const defs = NODE_FIELDS.ExportDefaultSpecifier; + validate(defs.exported, node, "exported", exported, 1); + return node; +} +function recordExpression(properties) { + const node = { + type: "RecordExpression", + properties + }; + const defs = NODE_FIELDS.RecordExpression; + validate(defs.properties, node, "properties", properties, 1); + return node; +} +function tupleExpression(elements = []) { + const node = { + type: "TupleExpression", + elements + }; + const defs = NODE_FIELDS.TupleExpression; + validate(defs.elements, node, "elements", elements, 1); + return node; +} +function decimalLiteral(value) { + const node = { + type: "DecimalLiteral", + value + }; + const defs = NODE_FIELDS.DecimalLiteral; + validate(defs.value, node, "value", value); + return node; +} +function moduleExpression(body) { + const node = { + type: "ModuleExpression", + body + }; + const defs = NODE_FIELDS.ModuleExpression; + validate(defs.body, node, "body", body, 1); + return node; +} +function topicReference() { + return { + type: "TopicReference" + }; +} +function pipelineTopicExpression(expression) { + const node = { + type: "PipelineTopicExpression", + expression + }; + const defs = NODE_FIELDS.PipelineTopicExpression; + validate(defs.expression, node, "expression", expression, 1); + return node; +} +function pipelineBareFunction(callee) { + const node = { + type: "PipelineBareFunction", + callee + }; + const defs = NODE_FIELDS.PipelineBareFunction; + validate(defs.callee, node, "callee", callee, 1); + return node; +} +function pipelinePrimaryTopicReference() { + return { + type: "PipelinePrimaryTopicReference" + }; +} +function tsParameterProperty(parameter) { + const node = { + type: "TSParameterProperty", + parameter + }; + const defs = NODE_FIELDS.TSParameterProperty; + validate(defs.parameter, node, "parameter", parameter, 1); + return node; +} +function tsDeclareFunction(id = null, typeParameters = null, params, returnType = null) { + const node = { + type: "TSDeclareFunction", + id, + typeParameters, + params, + returnType + }; + const defs = NODE_FIELDS.TSDeclareFunction; + validate(defs.id, node, "id", id, 1); + validate(defs.typeParameters, node, "typeParameters", typeParameters, 1); + validate(defs.params, node, "params", params, 1); + validate(defs.returnType, node, "returnType", returnType, 1); + return node; +} +function tsDeclareMethod(decorators = null, key, typeParameters = null, params, returnType = null) { + const node = { + type: "TSDeclareMethod", + decorators, + key, + typeParameters, + params, + returnType + }; + const defs = NODE_FIELDS.TSDeclareMethod; + validate(defs.decorators, node, "decorators", decorators, 1); + validate(defs.key, node, "key", key, 1); + validate(defs.typeParameters, node, "typeParameters", typeParameters, 1); + validate(defs.params, node, "params", params, 1); + validate(defs.returnType, node, "returnType", returnType, 1); + return node; +} +function tsQualifiedName(left, right) { + const node = { + type: "TSQualifiedName", + left, + right + }; + const defs = NODE_FIELDS.TSQualifiedName; + validate(defs.left, node, "left", left, 1); + validate(defs.right, node, "right", right, 1); + return node; +} +function tsCallSignatureDeclaration(typeParameters = null, parameters, typeAnnotation = null) { + const node = { + type: "TSCallSignatureDeclaration", + typeParameters, + parameters, + typeAnnotation + }; + const defs = NODE_FIELDS.TSCallSignatureDeclaration; + validate(defs.typeParameters, node, "typeParameters", typeParameters, 1); + validate(defs.parameters, node, "parameters", parameters, 1); + validate(defs.typeAnnotation, node, "typeAnnotation", typeAnnotation, 1); + return node; +} +function tsConstructSignatureDeclaration(typeParameters = null, parameters, typeAnnotation = null) { + const node = { + type: "TSConstructSignatureDeclaration", + typeParameters, + parameters, + typeAnnotation + }; + const defs = NODE_FIELDS.TSConstructSignatureDeclaration; + validate(defs.typeParameters, node, "typeParameters", typeParameters, 1); + validate(defs.parameters, node, "parameters", parameters, 1); + validate(defs.typeAnnotation, node, "typeAnnotation", typeAnnotation, 1); + return node; +} +function tsPropertySignature(key, typeAnnotation = null) { + const node = { + type: "TSPropertySignature", + key, + typeAnnotation + }; + const defs = NODE_FIELDS.TSPropertySignature; + validate(defs.key, node, "key", key, 1); + validate(defs.typeAnnotation, node, "typeAnnotation", typeAnnotation, 1); + return node; +} +function tsMethodSignature(key, typeParameters = null, parameters, typeAnnotation = null) { + const node = { + type: "TSMethodSignature", + key, + typeParameters, + parameters, + typeAnnotation, + kind: null + }; + const defs = NODE_FIELDS.TSMethodSignature; + validate(defs.key, node, "key", key, 1); + validate(defs.typeParameters, node, "typeParameters", typeParameters, 1); + validate(defs.parameters, node, "parameters", parameters, 1); + validate(defs.typeAnnotation, node, "typeAnnotation", typeAnnotation, 1); + return node; +} +function tsIndexSignature(parameters, typeAnnotation = null) { + const node = { + type: "TSIndexSignature", + parameters, + typeAnnotation + }; + const defs = NODE_FIELDS.TSIndexSignature; + validate(defs.parameters, node, "parameters", parameters, 1); + validate(defs.typeAnnotation, node, "typeAnnotation", typeAnnotation, 1); + return node; +} +function tsAnyKeyword() { + return { + type: "TSAnyKeyword" + }; +} +function tsBooleanKeyword() { + return { + type: "TSBooleanKeyword" + }; +} +function tsBigIntKeyword() { + return { + type: "TSBigIntKeyword" + }; +} +function tsIntrinsicKeyword() { + return { + type: "TSIntrinsicKeyword" + }; +} +function tsNeverKeyword() { + return { + type: "TSNeverKeyword" + }; +} +function tsNullKeyword() { + return { + type: "TSNullKeyword" + }; +} +function tsNumberKeyword() { + return { + type: "TSNumberKeyword" + }; +} +function tsObjectKeyword() { + return { + type: "TSObjectKeyword" + }; +} +function tsStringKeyword() { + return { + type: "TSStringKeyword" + }; +} +function tsSymbolKeyword() { + return { + type: "TSSymbolKeyword" + }; +} +function tsUndefinedKeyword() { + return { + type: "TSUndefinedKeyword" + }; +} +function tsUnknownKeyword() { + return { + type: "TSUnknownKeyword" + }; +} +function tsVoidKeyword() { + return { + type: "TSVoidKeyword" + }; +} +function tsThisType() { + return { + type: "TSThisType" + }; +} +function tsFunctionType(typeParameters = null, parameters, typeAnnotation = null) { + const node = { + type: "TSFunctionType", + typeParameters, + parameters, + typeAnnotation + }; + const defs = NODE_FIELDS.TSFunctionType; + validate(defs.typeParameters, node, "typeParameters", typeParameters, 1); + validate(defs.parameters, node, "parameters", parameters, 1); + validate(defs.typeAnnotation, node, "typeAnnotation", typeAnnotation, 1); + return node; +} +function tsConstructorType(typeParameters = null, parameters, typeAnnotation = null) { + const node = { + type: "TSConstructorType", + typeParameters, + parameters, + typeAnnotation + }; + const defs = NODE_FIELDS.TSConstructorType; + validate(defs.typeParameters, node, "typeParameters", typeParameters, 1); + validate(defs.parameters, node, "parameters", parameters, 1); + validate(defs.typeAnnotation, node, "typeAnnotation", typeAnnotation, 1); + return node; +} +function tsTypeReference(typeName, typeParameters = null) { + const node = { + type: "TSTypeReference", + typeName, + typeParameters + }; + const defs = NODE_FIELDS.TSTypeReference; + validate(defs.typeName, node, "typeName", typeName, 1); + validate(defs.typeParameters, node, "typeParameters", typeParameters, 1); + return node; +} +function tsTypePredicate(parameterName, typeAnnotation = null, asserts = null) { + const node = { + type: "TSTypePredicate", + parameterName, + typeAnnotation, + asserts + }; + const defs = NODE_FIELDS.TSTypePredicate; + validate(defs.parameterName, node, "parameterName", parameterName, 1); + validate(defs.typeAnnotation, node, "typeAnnotation", typeAnnotation, 1); + validate(defs.asserts, node, "asserts", asserts); + return node; +} +function tsTypeQuery(exprName, typeParameters = null) { + const node = { + type: "TSTypeQuery", + exprName, + typeParameters + }; + const defs = NODE_FIELDS.TSTypeQuery; + validate(defs.exprName, node, "exprName", exprName, 1); + validate(defs.typeParameters, node, "typeParameters", typeParameters, 1); + return node; +} +function tsTypeLiteral(members) { + const node = { + type: "TSTypeLiteral", + members + }; + const defs = NODE_FIELDS.TSTypeLiteral; + validate(defs.members, node, "members", members, 1); + return node; +} +function tsArrayType(elementType) { + const node = { + type: "TSArrayType", + elementType + }; + const defs = NODE_FIELDS.TSArrayType; + validate(defs.elementType, node, "elementType", elementType, 1); + return node; +} +function tsTupleType(elementTypes) { + const node = { + type: "TSTupleType", + elementTypes + }; + const defs = NODE_FIELDS.TSTupleType; + validate(defs.elementTypes, node, "elementTypes", elementTypes, 1); + return node; +} +function tsOptionalType(typeAnnotation) { + const node = { + type: "TSOptionalType", + typeAnnotation + }; + const defs = NODE_FIELDS.TSOptionalType; + validate(defs.typeAnnotation, node, "typeAnnotation", typeAnnotation, 1); + return node; +} +function tsRestType(typeAnnotation) { + const node = { + type: "TSRestType", + typeAnnotation + }; + const defs = NODE_FIELDS.TSRestType; + validate(defs.typeAnnotation, node, "typeAnnotation", typeAnnotation, 1); + return node; +} +function tsNamedTupleMember(label, elementType, optional = false) { + const node = { + type: "TSNamedTupleMember", + label, + elementType, + optional + }; + const defs = NODE_FIELDS.TSNamedTupleMember; + validate(defs.label, node, "label", label, 1); + validate(defs.elementType, node, "elementType", elementType, 1); + validate(defs.optional, node, "optional", optional); + return node; +} +function tsUnionType(types) { + const node = { + type: "TSUnionType", + types + }; + const defs = NODE_FIELDS.TSUnionType; + validate(defs.types, node, "types", types, 1); + return node; +} +function tsIntersectionType(types) { + const node = { + type: "TSIntersectionType", + types + }; + const defs = NODE_FIELDS.TSIntersectionType; + validate(defs.types, node, "types", types, 1); + return node; +} +function tsConditionalType(checkType, extendsType, trueType, falseType) { + const node = { + type: "TSConditionalType", + checkType, + extendsType, + trueType, + falseType + }; + const defs = NODE_FIELDS.TSConditionalType; + validate(defs.checkType, node, "checkType", checkType, 1); + validate(defs.extendsType, node, "extendsType", extendsType, 1); + validate(defs.trueType, node, "trueType", trueType, 1); + validate(defs.falseType, node, "falseType", falseType, 1); + return node; +} +function tsInferType(typeParameter) { + const node = { + type: "TSInferType", + typeParameter + }; + const defs = NODE_FIELDS.TSInferType; + validate(defs.typeParameter, node, "typeParameter", typeParameter, 1); + return node; +} +function tsParenthesizedType(typeAnnotation) { + const node = { + type: "TSParenthesizedType", + typeAnnotation + }; + const defs = NODE_FIELDS.TSParenthesizedType; + validate(defs.typeAnnotation, node, "typeAnnotation", typeAnnotation, 1); + return node; +} +function tsTypeOperator(typeAnnotation) { + const node = { + type: "TSTypeOperator", + typeAnnotation, + operator: null + }; + const defs = NODE_FIELDS.TSTypeOperator; + validate(defs.typeAnnotation, node, "typeAnnotation", typeAnnotation, 1); + return node; +} +function tsIndexedAccessType(objectType, indexType) { + const node = { + type: "TSIndexedAccessType", + objectType, + indexType + }; + const defs = NODE_FIELDS.TSIndexedAccessType; + validate(defs.objectType, node, "objectType", objectType, 1); + validate(defs.indexType, node, "indexType", indexType, 1); + return node; +} +function tsMappedType(typeParameter, typeAnnotation = null, nameType = null) { + const node = { + type: "TSMappedType", + typeParameter, + typeAnnotation, + nameType + }; + const defs = NODE_FIELDS.TSMappedType; + validate(defs.typeParameter, node, "typeParameter", typeParameter, 1); + validate(defs.typeAnnotation, node, "typeAnnotation", typeAnnotation, 1); + validate(defs.nameType, node, "nameType", nameType, 1); + return node; +} +function tsTemplateLiteralType(quasis, types) { + const node = { + type: "TSTemplateLiteralType", + quasis, + types + }; + const defs = NODE_FIELDS.TSTemplateLiteralType; + validate(defs.quasis, node, "quasis", quasis, 1); + validate(defs.types, node, "types", types, 1); + return node; +} +function tsLiteralType(literal) { + const node = { + type: "TSLiteralType", + literal + }; + const defs = NODE_FIELDS.TSLiteralType; + validate(defs.literal, node, "literal", literal, 1); + return node; +} +function tsExpressionWithTypeArguments(expression, typeParameters = null) { + const node = { + type: "TSExpressionWithTypeArguments", + expression, + typeParameters + }; + const defs = NODE_FIELDS.TSExpressionWithTypeArguments; + validate(defs.expression, node, "expression", expression, 1); + validate(defs.typeParameters, node, "typeParameters", typeParameters, 1); + return node; +} +function tsInterfaceDeclaration(id, typeParameters = null, _extends = null, body) { + const node = { + type: "TSInterfaceDeclaration", + id, + typeParameters, + extends: _extends, + body + }; + const defs = NODE_FIELDS.TSInterfaceDeclaration; + validate(defs.id, node, "id", id, 1); + validate(defs.typeParameters, node, "typeParameters", typeParameters, 1); + validate(defs.extends, node, "extends", _extends, 1); + validate(defs.body, node, "body", body, 1); + return node; +} +function tsInterfaceBody(body) { + const node = { + type: "TSInterfaceBody", + body + }; + const defs = NODE_FIELDS.TSInterfaceBody; + validate(defs.body, node, "body", body, 1); + return node; +} +function tsTypeAliasDeclaration(id, typeParameters = null, typeAnnotation) { + const node = { + type: "TSTypeAliasDeclaration", + id, + typeParameters, + typeAnnotation + }; + const defs = NODE_FIELDS.TSTypeAliasDeclaration; + validate(defs.id, node, "id", id, 1); + validate(defs.typeParameters, node, "typeParameters", typeParameters, 1); + validate(defs.typeAnnotation, node, "typeAnnotation", typeAnnotation, 1); + return node; +} +function tsInstantiationExpression(expression, typeParameters = null) { + const node = { + type: "TSInstantiationExpression", + expression, + typeParameters + }; + const defs = NODE_FIELDS.TSInstantiationExpression; + validate(defs.expression, node, "expression", expression, 1); + validate(defs.typeParameters, node, "typeParameters", typeParameters, 1); + return node; +} +function tsAsExpression(expression, typeAnnotation) { + const node = { + type: "TSAsExpression", + expression, + typeAnnotation + }; + const defs = NODE_FIELDS.TSAsExpression; + validate(defs.expression, node, "expression", expression, 1); + validate(defs.typeAnnotation, node, "typeAnnotation", typeAnnotation, 1); + return node; +} +function tsSatisfiesExpression(expression, typeAnnotation) { + const node = { + type: "TSSatisfiesExpression", + expression, + typeAnnotation + }; + const defs = NODE_FIELDS.TSSatisfiesExpression; + validate(defs.expression, node, "expression", expression, 1); + validate(defs.typeAnnotation, node, "typeAnnotation", typeAnnotation, 1); + return node; +} +function tsTypeAssertion(typeAnnotation, expression) { + const node = { + type: "TSTypeAssertion", + typeAnnotation, + expression + }; + const defs = NODE_FIELDS.TSTypeAssertion; + validate(defs.typeAnnotation, node, "typeAnnotation", typeAnnotation, 1); + validate(defs.expression, node, "expression", expression, 1); + return node; +} +function tsEnumBody(members) { + const node = { + type: "TSEnumBody", + members + }; + const defs = NODE_FIELDS.TSEnumBody; + validate(defs.members, node, "members", members, 1); + return node; +} +function tsEnumDeclaration(id, members) { + const node = { + type: "TSEnumDeclaration", + id, + members + }; + const defs = NODE_FIELDS.TSEnumDeclaration; + validate(defs.id, node, "id", id, 1); + validate(defs.members, node, "members", members, 1); + return node; +} +function tsEnumMember(id, initializer = null) { + const node = { + type: "TSEnumMember", + id, + initializer + }; + const defs = NODE_FIELDS.TSEnumMember; + validate(defs.id, node, "id", id, 1); + validate(defs.initializer, node, "initializer", initializer, 1); + return node; +} +function tsModuleDeclaration(id, body) { + const node = { + type: "TSModuleDeclaration", + id, + body, + kind: null + }; + const defs = NODE_FIELDS.TSModuleDeclaration; + validate(defs.id, node, "id", id, 1); + validate(defs.body, node, "body", body, 1); + return node; +} +function tsModuleBlock(body) { + const node = { + type: "TSModuleBlock", + body + }; + const defs = NODE_FIELDS.TSModuleBlock; + validate(defs.body, node, "body", body, 1); + return node; +} +function tsImportType(argument, qualifier = null, typeParameters = null) { + const node = { + type: "TSImportType", + argument, + qualifier, + typeParameters + }; + const defs = NODE_FIELDS.TSImportType; + validate(defs.argument, node, "argument", argument, 1); + validate(defs.qualifier, node, "qualifier", qualifier, 1); + validate(defs.typeParameters, node, "typeParameters", typeParameters, 1); + return node; +} +function tsImportEqualsDeclaration(id, moduleReference) { + const node = { + type: "TSImportEqualsDeclaration", + id, + moduleReference, + isExport: null + }; + const defs = NODE_FIELDS.TSImportEqualsDeclaration; + validate(defs.id, node, "id", id, 1); + validate(defs.moduleReference, node, "moduleReference", moduleReference, 1); + return node; +} +function tsExternalModuleReference(expression) { + const node = { + type: "TSExternalModuleReference", + expression + }; + const defs = NODE_FIELDS.TSExternalModuleReference; + validate(defs.expression, node, "expression", expression, 1); + return node; +} +function tsNonNullExpression(expression) { + const node = { + type: "TSNonNullExpression", + expression + }; + const defs = NODE_FIELDS.TSNonNullExpression; + validate(defs.expression, node, "expression", expression, 1); + return node; +} +function tsExportAssignment(expression) { + const node = { + type: "TSExportAssignment", + expression + }; + const defs = NODE_FIELDS.TSExportAssignment; + validate(defs.expression, node, "expression", expression, 1); + return node; +} +function tsNamespaceExportDeclaration(id) { + const node = { + type: "TSNamespaceExportDeclaration", + id + }; + const defs = NODE_FIELDS.TSNamespaceExportDeclaration; + validate(defs.id, node, "id", id, 1); + return node; +} +function tsTypeAnnotation(typeAnnotation) { + const node = { + type: "TSTypeAnnotation", + typeAnnotation + }; + const defs = NODE_FIELDS.TSTypeAnnotation; + validate(defs.typeAnnotation, node, "typeAnnotation", typeAnnotation, 1); + return node; +} +function tsTypeParameterInstantiation(params) { + const node = { + type: "TSTypeParameterInstantiation", + params + }; + const defs = NODE_FIELDS.TSTypeParameterInstantiation; + validate(defs.params, node, "params", params, 1); + return node; +} +function tsTypeParameterDeclaration(params) { + const node = { + type: "TSTypeParameterDeclaration", + params + }; + const defs = NODE_FIELDS.TSTypeParameterDeclaration; + validate(defs.params, node, "params", params, 1); + return node; +} +function tsTypeParameter(constraint = null, _default = null, name) { + const node = { + type: "TSTypeParameter", + constraint, + default: _default, + name + }; + const defs = NODE_FIELDS.TSTypeParameter; + validate(defs.constraint, node, "constraint", constraint, 1); + validate(defs.default, node, "default", _default, 1); + validate(defs.name, node, "name", name); + return node; +} +function NumberLiteral(value) { + (0, _deprecationWarning.default)("NumberLiteral", "NumericLiteral", "The node type "); + return numericLiteral(value); +} +function RegexLiteral(pattern, flags = "") { + (0, _deprecationWarning.default)("RegexLiteral", "RegExpLiteral", "The node type "); + return regExpLiteral(pattern, flags); +} +function RestProperty(argument) { + (0, _deprecationWarning.default)("RestProperty", "RestElement", "The node type "); + return restElement(argument); +} +function SpreadProperty(argument) { + (0, _deprecationWarning.default)("SpreadProperty", "SpreadElement", "The node type "); + return spreadElement(argument); +} + +//# sourceMappingURL=lowercase.js.map + + +/***/ }), + +/***/ 1862: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports.JSXIdentifier = exports.JSXFragment = exports.JSXExpressionContainer = exports.JSXEmptyExpression = exports.JSXElement = exports.JSXClosingFragment = exports.JSXClosingElement = exports.JSXAttribute = exports.IntersectionTypeAnnotation = exports.InterpreterDirective = exports.InterfaceTypeAnnotation = exports.InterfaceExtends = exports.InterfaceDeclaration = exports.InferredPredicate = exports.IndexedAccessType = exports.ImportSpecifier = exports.ImportNamespaceSpecifier = exports.ImportExpression = exports.ImportDefaultSpecifier = exports.ImportDeclaration = exports.ImportAttribute = exports.Import = exports.IfStatement = exports.Identifier = exports.GenericTypeAnnotation = exports.FunctionTypeParam = exports.FunctionTypeAnnotation = exports.FunctionExpression = exports.FunctionDeclaration = exports.ForStatement = exports.ForOfStatement = exports.ForInStatement = exports.File = exports.ExpressionStatement = exports.ExportSpecifier = exports.ExportNamespaceSpecifier = exports.ExportNamedDeclaration = exports.ExportDefaultSpecifier = exports.ExportDefaultDeclaration = exports.ExportAllDeclaration = exports.ExistsTypeAnnotation = exports.EnumSymbolBody = exports.EnumStringMember = exports.EnumStringBody = exports.EnumNumberMember = exports.EnumNumberBody = exports.EnumDefaultedMember = exports.EnumDeclaration = exports.EnumBooleanMember = exports.EnumBooleanBody = exports.EmptyTypeAnnotation = exports.EmptyStatement = exports.DoWhileStatement = exports.DoExpression = exports.DirectiveLiteral = exports.Directive = exports.Decorator = exports.DeclaredPredicate = exports.DeclareVariable = exports.DeclareTypeAlias = exports.DeclareOpaqueType = exports.DeclareModuleExports = exports.DeclareModule = exports.DeclareInterface = exports.DeclareFunction = exports.DeclareExportDeclaration = exports.DeclareExportAllDeclaration = exports.DeclareClass = exports.DecimalLiteral = exports.DebuggerStatement = exports.ContinueStatement = exports.ConditionalExpression = exports.ClassProperty = exports.ClassPrivateProperty = exports.ClassPrivateMethod = exports.ClassMethod = exports.ClassImplements = exports.ClassExpression = exports.ClassDeclaration = exports.ClassBody = exports.ClassAccessorProperty = exports.CatchClause = exports.CallExpression = exports.BreakStatement = exports.BooleanTypeAnnotation = exports.BooleanLiteralTypeAnnotation = exports.BooleanLiteral = exports.BlockStatement = exports.BindExpression = exports.BinaryExpression = exports.BigIntLiteral = exports.AwaitExpression = exports.AssignmentPattern = exports.AssignmentExpression = exports.ArrowFunctionExpression = exports.ArrayTypeAnnotation = exports.ArrayPattern = exports.ArrayExpression = exports.ArgumentPlaceholder = exports.AnyTypeAnnotation = void 0; +exports.TSNumberKeyword = exports.TSNullKeyword = exports.TSNonNullExpression = exports.TSNeverKeyword = exports.TSNamespaceExportDeclaration = exports.TSNamedTupleMember = exports.TSModuleDeclaration = exports.TSModuleBlock = exports.TSMethodSignature = exports.TSMappedType = exports.TSLiteralType = exports.TSIntrinsicKeyword = exports.TSIntersectionType = exports.TSInterfaceDeclaration = exports.TSInterfaceBody = exports.TSInstantiationExpression = exports.TSInferType = exports.TSIndexedAccessType = exports.TSIndexSignature = exports.TSImportType = exports.TSImportEqualsDeclaration = exports.TSFunctionType = exports.TSExternalModuleReference = exports.TSExpressionWithTypeArguments = exports.TSExportAssignment = exports.TSEnumMember = exports.TSEnumDeclaration = exports.TSEnumBody = exports.TSDeclareMethod = exports.TSDeclareFunction = exports.TSConstructorType = exports.TSConstructSignatureDeclaration = exports.TSConditionalType = exports.TSCallSignatureDeclaration = exports.TSBooleanKeyword = exports.TSBigIntKeyword = exports.TSAsExpression = exports.TSArrayType = exports.TSAnyKeyword = exports.SymbolTypeAnnotation = exports.SwitchStatement = exports.SwitchCase = exports.Super = exports.StringTypeAnnotation = exports.StringLiteralTypeAnnotation = exports.StringLiteral = exports.StaticBlock = exports.SpreadProperty = exports.SpreadElement = exports.SequenceExpression = exports.ReturnStatement = exports.RestProperty = exports.RestElement = exports.RegexLiteral = exports.RegExpLiteral = exports.RecordExpression = exports.QualifiedTypeIdentifier = exports.Program = exports.PrivateName = exports.Placeholder = exports.PipelineTopicExpression = exports.PipelinePrimaryTopicReference = exports.PipelineBareFunction = exports.ParenthesizedExpression = exports.OptionalMemberExpression = exports.OptionalIndexedAccessType = exports.OptionalCallExpression = exports.OpaqueType = exports.ObjectTypeSpreadProperty = exports.ObjectTypeProperty = exports.ObjectTypeInternalSlot = exports.ObjectTypeIndexer = exports.ObjectTypeCallProperty = exports.ObjectTypeAnnotation = exports.ObjectProperty = exports.ObjectPattern = exports.ObjectMethod = exports.ObjectExpression = exports.NumericLiteral = exports.NumberTypeAnnotation = exports.NumberLiteralTypeAnnotation = exports.NumberLiteral = exports.NullableTypeAnnotation = exports.NullLiteralTypeAnnotation = exports.NullLiteral = exports.Noop = exports.NewExpression = exports.ModuleExpression = exports.MixedTypeAnnotation = exports.MetaProperty = exports.MemberExpression = exports.LogicalExpression = exports.LabeledStatement = exports.JSXText = exports.JSXSpreadChild = exports.JSXSpreadAttribute = exports.JSXOpeningFragment = exports.JSXOpeningElement = exports.JSXNamespacedName = exports.JSXMemberExpression = void 0; +exports.YieldExpression = exports.WithStatement = exports.WhileStatement = exports.VoidTypeAnnotation = exports.Variance = exports.VariableDeclarator = exports.VariableDeclaration = exports.V8IntrinsicIdentifier = exports.UpdateExpression = exports.UnionTypeAnnotation = exports.UnaryExpression = exports.TypeofTypeAnnotation = exports.TypeParameterInstantiation = exports.TypeParameterDeclaration = exports.TypeParameter = exports.TypeCastExpression = exports.TypeAnnotation = exports.TypeAlias = exports.TupleTypeAnnotation = exports.TupleExpression = exports.TryStatement = exports.TopicReference = exports.ThrowStatement = exports.ThisTypeAnnotation = exports.ThisExpression = exports.TemplateLiteral = exports.TemplateElement = exports.TaggedTemplateExpression = exports.TSVoidKeyword = exports.TSUnknownKeyword = exports.TSUnionType = exports.TSUndefinedKeyword = exports.TSTypeReference = exports.TSTypeQuery = exports.TSTypePredicate = exports.TSTypeParameterInstantiation = exports.TSTypeParameterDeclaration = exports.TSTypeParameter = exports.TSTypeOperator = exports.TSTypeLiteral = exports.TSTypeAssertion = exports.TSTypeAnnotation = exports.TSTypeAliasDeclaration = exports.TSTupleType = exports.TSThisType = exports.TSTemplateLiteralType = exports.TSSymbolKeyword = exports.TSStringKeyword = exports.TSSatisfiesExpression = exports.TSRestType = exports.TSQualifiedName = exports.TSPropertySignature = exports.TSParenthesizedType = exports.TSParameterProperty = exports.TSOptionalType = exports.TSObjectKeyword = void 0; +var b = __nccwpck_require__(11999); +var _deprecationWarning = __nccwpck_require__(14711); +function alias(lowercase) { + { + return b[lowercase]; + } +} +const ArrayExpression = exports.ArrayExpression = alias("arrayExpression"), + AssignmentExpression = exports.AssignmentExpression = alias("assignmentExpression"), + BinaryExpression = exports.BinaryExpression = alias("binaryExpression"), + InterpreterDirective = exports.InterpreterDirective = alias("interpreterDirective"), + Directive = exports.Directive = alias("directive"), + DirectiveLiteral = exports.DirectiveLiteral = alias("directiveLiteral"), + BlockStatement = exports.BlockStatement = alias("blockStatement"), + BreakStatement = exports.BreakStatement = alias("breakStatement"), + CallExpression = exports.CallExpression = alias("callExpression"), + CatchClause = exports.CatchClause = alias("catchClause"), + ConditionalExpression = exports.ConditionalExpression = alias("conditionalExpression"), + ContinueStatement = exports.ContinueStatement = alias("continueStatement"), + DebuggerStatement = exports.DebuggerStatement = alias("debuggerStatement"), + DoWhileStatement = exports.DoWhileStatement = alias("doWhileStatement"), + EmptyStatement = exports.EmptyStatement = alias("emptyStatement"), + ExpressionStatement = exports.ExpressionStatement = alias("expressionStatement"), + File = exports.File = alias("file"), + ForInStatement = exports.ForInStatement = alias("forInStatement"), + ForStatement = exports.ForStatement = alias("forStatement"), + FunctionDeclaration = exports.FunctionDeclaration = alias("functionDeclaration"), + FunctionExpression = exports.FunctionExpression = alias("functionExpression"), + Identifier = exports.Identifier = alias("identifier"), + IfStatement = exports.IfStatement = alias("ifStatement"), + LabeledStatement = exports.LabeledStatement = alias("labeledStatement"), + StringLiteral = exports.StringLiteral = alias("stringLiteral"), + NumericLiteral = exports.NumericLiteral = alias("numericLiteral"), + NullLiteral = exports.NullLiteral = alias("nullLiteral"), + BooleanLiteral = exports.BooleanLiteral = alias("booleanLiteral"), + RegExpLiteral = exports.RegExpLiteral = alias("regExpLiteral"), + LogicalExpression = exports.LogicalExpression = alias("logicalExpression"), + MemberExpression = exports.MemberExpression = alias("memberExpression"), + NewExpression = exports.NewExpression = alias("newExpression"), + Program = exports.Program = alias("program"), + ObjectExpression = exports.ObjectExpression = alias("objectExpression"), + ObjectMethod = exports.ObjectMethod = alias("objectMethod"), + ObjectProperty = exports.ObjectProperty = alias("objectProperty"), + RestElement = exports.RestElement = alias("restElement"), + ReturnStatement = exports.ReturnStatement = alias("returnStatement"), + SequenceExpression = exports.SequenceExpression = alias("sequenceExpression"), + ParenthesizedExpression = exports.ParenthesizedExpression = alias("parenthesizedExpression"), + SwitchCase = exports.SwitchCase = alias("switchCase"), + SwitchStatement = exports.SwitchStatement = alias("switchStatement"), + ThisExpression = exports.ThisExpression = alias("thisExpression"), + ThrowStatement = exports.ThrowStatement = alias("throwStatement"), + TryStatement = exports.TryStatement = alias("tryStatement"), + UnaryExpression = exports.UnaryExpression = alias("unaryExpression"), + UpdateExpression = exports.UpdateExpression = alias("updateExpression"), + VariableDeclaration = exports.VariableDeclaration = alias("variableDeclaration"), + VariableDeclarator = exports.VariableDeclarator = alias("variableDeclarator"), + WhileStatement = exports.WhileStatement = alias("whileStatement"), + WithStatement = exports.WithStatement = alias("withStatement"), + AssignmentPattern = exports.AssignmentPattern = alias("assignmentPattern"), + ArrayPattern = exports.ArrayPattern = alias("arrayPattern"), + ArrowFunctionExpression = exports.ArrowFunctionExpression = alias("arrowFunctionExpression"), + ClassBody = exports.ClassBody = alias("classBody"), + ClassExpression = exports.ClassExpression = alias("classExpression"), + ClassDeclaration = exports.ClassDeclaration = alias("classDeclaration"), + ExportAllDeclaration = exports.ExportAllDeclaration = alias("exportAllDeclaration"), + ExportDefaultDeclaration = exports.ExportDefaultDeclaration = alias("exportDefaultDeclaration"), + ExportNamedDeclaration = exports.ExportNamedDeclaration = alias("exportNamedDeclaration"), + ExportSpecifier = exports.ExportSpecifier = alias("exportSpecifier"), + ForOfStatement = exports.ForOfStatement = alias("forOfStatement"), + ImportDeclaration = exports.ImportDeclaration = alias("importDeclaration"), + ImportDefaultSpecifier = exports.ImportDefaultSpecifier = alias("importDefaultSpecifier"), + ImportNamespaceSpecifier = exports.ImportNamespaceSpecifier = alias("importNamespaceSpecifier"), + ImportSpecifier = exports.ImportSpecifier = alias("importSpecifier"), + ImportExpression = exports.ImportExpression = alias("importExpression"), + MetaProperty = exports.MetaProperty = alias("metaProperty"), + ClassMethod = exports.ClassMethod = alias("classMethod"), + ObjectPattern = exports.ObjectPattern = alias("objectPattern"), + SpreadElement = exports.SpreadElement = alias("spreadElement"), + Super = exports.Super = alias("super"), + TaggedTemplateExpression = exports.TaggedTemplateExpression = alias("taggedTemplateExpression"), + TemplateElement = exports.TemplateElement = alias("templateElement"), + TemplateLiteral = exports.TemplateLiteral = alias("templateLiteral"), + YieldExpression = exports.YieldExpression = alias("yieldExpression"), + AwaitExpression = exports.AwaitExpression = alias("awaitExpression"), + Import = exports.Import = alias("import"), + BigIntLiteral = exports.BigIntLiteral = alias("bigIntLiteral"), + ExportNamespaceSpecifier = exports.ExportNamespaceSpecifier = alias("exportNamespaceSpecifier"), + OptionalMemberExpression = exports.OptionalMemberExpression = alias("optionalMemberExpression"), + OptionalCallExpression = exports.OptionalCallExpression = alias("optionalCallExpression"), + ClassProperty = exports.ClassProperty = alias("classProperty"), + ClassAccessorProperty = exports.ClassAccessorProperty = alias("classAccessorProperty"), + ClassPrivateProperty = exports.ClassPrivateProperty = alias("classPrivateProperty"), + ClassPrivateMethod = exports.ClassPrivateMethod = alias("classPrivateMethod"), + PrivateName = exports.PrivateName = alias("privateName"), + StaticBlock = exports.StaticBlock = alias("staticBlock"), + ImportAttribute = exports.ImportAttribute = alias("importAttribute"), + AnyTypeAnnotation = exports.AnyTypeAnnotation = alias("anyTypeAnnotation"), + ArrayTypeAnnotation = exports.ArrayTypeAnnotation = alias("arrayTypeAnnotation"), + BooleanTypeAnnotation = exports.BooleanTypeAnnotation = alias("booleanTypeAnnotation"), + BooleanLiteralTypeAnnotation = exports.BooleanLiteralTypeAnnotation = alias("booleanLiteralTypeAnnotation"), + NullLiteralTypeAnnotation = exports.NullLiteralTypeAnnotation = alias("nullLiteralTypeAnnotation"), + ClassImplements = exports.ClassImplements = alias("classImplements"), + DeclareClass = exports.DeclareClass = alias("declareClass"), + DeclareFunction = exports.DeclareFunction = alias("declareFunction"), + DeclareInterface = exports.DeclareInterface = alias("declareInterface"), + DeclareModule = exports.DeclareModule = alias("declareModule"), + DeclareModuleExports = exports.DeclareModuleExports = alias("declareModuleExports"), + DeclareTypeAlias = exports.DeclareTypeAlias = alias("declareTypeAlias"), + DeclareOpaqueType = exports.DeclareOpaqueType = alias("declareOpaqueType"), + DeclareVariable = exports.DeclareVariable = alias("declareVariable"), + DeclareExportDeclaration = exports.DeclareExportDeclaration = alias("declareExportDeclaration"), + DeclareExportAllDeclaration = exports.DeclareExportAllDeclaration = alias("declareExportAllDeclaration"), + DeclaredPredicate = exports.DeclaredPredicate = alias("declaredPredicate"), + ExistsTypeAnnotation = exports.ExistsTypeAnnotation = alias("existsTypeAnnotation"), + FunctionTypeAnnotation = exports.FunctionTypeAnnotation = alias("functionTypeAnnotation"), + FunctionTypeParam = exports.FunctionTypeParam = alias("functionTypeParam"), + GenericTypeAnnotation = exports.GenericTypeAnnotation = alias("genericTypeAnnotation"), + InferredPredicate = exports.InferredPredicate = alias("inferredPredicate"), + InterfaceExtends = exports.InterfaceExtends = alias("interfaceExtends"), + InterfaceDeclaration = exports.InterfaceDeclaration = alias("interfaceDeclaration"), + InterfaceTypeAnnotation = exports.InterfaceTypeAnnotation = alias("interfaceTypeAnnotation"), + IntersectionTypeAnnotation = exports.IntersectionTypeAnnotation = alias("intersectionTypeAnnotation"), + MixedTypeAnnotation = exports.MixedTypeAnnotation = alias("mixedTypeAnnotation"), + EmptyTypeAnnotation = exports.EmptyTypeAnnotation = alias("emptyTypeAnnotation"), + NullableTypeAnnotation = exports.NullableTypeAnnotation = alias("nullableTypeAnnotation"), + NumberLiteralTypeAnnotation = exports.NumberLiteralTypeAnnotation = alias("numberLiteralTypeAnnotation"), + NumberTypeAnnotation = exports.NumberTypeAnnotation = alias("numberTypeAnnotation"), + ObjectTypeAnnotation = exports.ObjectTypeAnnotation = alias("objectTypeAnnotation"), + ObjectTypeInternalSlot = exports.ObjectTypeInternalSlot = alias("objectTypeInternalSlot"), + ObjectTypeCallProperty = exports.ObjectTypeCallProperty = alias("objectTypeCallProperty"), + ObjectTypeIndexer = exports.ObjectTypeIndexer = alias("objectTypeIndexer"), + ObjectTypeProperty = exports.ObjectTypeProperty = alias("objectTypeProperty"), + ObjectTypeSpreadProperty = exports.ObjectTypeSpreadProperty = alias("objectTypeSpreadProperty"), + OpaqueType = exports.OpaqueType = alias("opaqueType"), + QualifiedTypeIdentifier = exports.QualifiedTypeIdentifier = alias("qualifiedTypeIdentifier"), + StringLiteralTypeAnnotation = exports.StringLiteralTypeAnnotation = alias("stringLiteralTypeAnnotation"), + StringTypeAnnotation = exports.StringTypeAnnotation = alias("stringTypeAnnotation"), + SymbolTypeAnnotation = exports.SymbolTypeAnnotation = alias("symbolTypeAnnotation"), + ThisTypeAnnotation = exports.ThisTypeAnnotation = alias("thisTypeAnnotation"), + TupleTypeAnnotation = exports.TupleTypeAnnotation = alias("tupleTypeAnnotation"), + TypeofTypeAnnotation = exports.TypeofTypeAnnotation = alias("typeofTypeAnnotation"), + TypeAlias = exports.TypeAlias = alias("typeAlias"), + TypeAnnotation = exports.TypeAnnotation = alias("typeAnnotation"), + TypeCastExpression = exports.TypeCastExpression = alias("typeCastExpression"), + TypeParameter = exports.TypeParameter = alias("typeParameter"), + TypeParameterDeclaration = exports.TypeParameterDeclaration = alias("typeParameterDeclaration"), + TypeParameterInstantiation = exports.TypeParameterInstantiation = alias("typeParameterInstantiation"), + UnionTypeAnnotation = exports.UnionTypeAnnotation = alias("unionTypeAnnotation"), + Variance = exports.Variance = alias("variance"), + VoidTypeAnnotation = exports.VoidTypeAnnotation = alias("voidTypeAnnotation"), + EnumDeclaration = exports.EnumDeclaration = alias("enumDeclaration"), + EnumBooleanBody = exports.EnumBooleanBody = alias("enumBooleanBody"), + EnumNumberBody = exports.EnumNumberBody = alias("enumNumberBody"), + EnumStringBody = exports.EnumStringBody = alias("enumStringBody"), + EnumSymbolBody = exports.EnumSymbolBody = alias("enumSymbolBody"), + EnumBooleanMember = exports.EnumBooleanMember = alias("enumBooleanMember"), + EnumNumberMember = exports.EnumNumberMember = alias("enumNumberMember"), + EnumStringMember = exports.EnumStringMember = alias("enumStringMember"), + EnumDefaultedMember = exports.EnumDefaultedMember = alias("enumDefaultedMember"), + IndexedAccessType = exports.IndexedAccessType = alias("indexedAccessType"), + OptionalIndexedAccessType = exports.OptionalIndexedAccessType = alias("optionalIndexedAccessType"), + JSXAttribute = exports.JSXAttribute = alias("jsxAttribute"), + JSXClosingElement = exports.JSXClosingElement = alias("jsxClosingElement"), + JSXElement = exports.JSXElement = alias("jsxElement"), + JSXEmptyExpression = exports.JSXEmptyExpression = alias("jsxEmptyExpression"), + JSXExpressionContainer = exports.JSXExpressionContainer = alias("jsxExpressionContainer"), + JSXSpreadChild = exports.JSXSpreadChild = alias("jsxSpreadChild"), + JSXIdentifier = exports.JSXIdentifier = alias("jsxIdentifier"), + JSXMemberExpression = exports.JSXMemberExpression = alias("jsxMemberExpression"), + JSXNamespacedName = exports.JSXNamespacedName = alias("jsxNamespacedName"), + JSXOpeningElement = exports.JSXOpeningElement = alias("jsxOpeningElement"), + JSXSpreadAttribute = exports.JSXSpreadAttribute = alias("jsxSpreadAttribute"), + JSXText = exports.JSXText = alias("jsxText"), + JSXFragment = exports.JSXFragment = alias("jsxFragment"), + JSXOpeningFragment = exports.JSXOpeningFragment = alias("jsxOpeningFragment"), + JSXClosingFragment = exports.JSXClosingFragment = alias("jsxClosingFragment"), + Noop = exports.Noop = alias("noop"), + Placeholder = exports.Placeholder = alias("placeholder"), + V8IntrinsicIdentifier = exports.V8IntrinsicIdentifier = alias("v8IntrinsicIdentifier"), + ArgumentPlaceholder = exports.ArgumentPlaceholder = alias("argumentPlaceholder"), + BindExpression = exports.BindExpression = alias("bindExpression"), + Decorator = exports.Decorator = alias("decorator"), + DoExpression = exports.DoExpression = alias("doExpression"), + ExportDefaultSpecifier = exports.ExportDefaultSpecifier = alias("exportDefaultSpecifier"), + RecordExpression = exports.RecordExpression = alias("recordExpression"), + TupleExpression = exports.TupleExpression = alias("tupleExpression"), + DecimalLiteral = exports.DecimalLiteral = alias("decimalLiteral"), + ModuleExpression = exports.ModuleExpression = alias("moduleExpression"), + TopicReference = exports.TopicReference = alias("topicReference"), + PipelineTopicExpression = exports.PipelineTopicExpression = alias("pipelineTopicExpression"), + PipelineBareFunction = exports.PipelineBareFunction = alias("pipelineBareFunction"), + PipelinePrimaryTopicReference = exports.PipelinePrimaryTopicReference = alias("pipelinePrimaryTopicReference"), + TSParameterProperty = exports.TSParameterProperty = alias("tsParameterProperty"), + TSDeclareFunction = exports.TSDeclareFunction = alias("tsDeclareFunction"), + TSDeclareMethod = exports.TSDeclareMethod = alias("tsDeclareMethod"), + TSQualifiedName = exports.TSQualifiedName = alias("tsQualifiedName"), + TSCallSignatureDeclaration = exports.TSCallSignatureDeclaration = alias("tsCallSignatureDeclaration"), + TSConstructSignatureDeclaration = exports.TSConstructSignatureDeclaration = alias("tsConstructSignatureDeclaration"), + TSPropertySignature = exports.TSPropertySignature = alias("tsPropertySignature"), + TSMethodSignature = exports.TSMethodSignature = alias("tsMethodSignature"), + TSIndexSignature = exports.TSIndexSignature = alias("tsIndexSignature"), + TSAnyKeyword = exports.TSAnyKeyword = alias("tsAnyKeyword"), + TSBooleanKeyword = exports.TSBooleanKeyword = alias("tsBooleanKeyword"), + TSBigIntKeyword = exports.TSBigIntKeyword = alias("tsBigIntKeyword"), + TSIntrinsicKeyword = exports.TSIntrinsicKeyword = alias("tsIntrinsicKeyword"), + TSNeverKeyword = exports.TSNeverKeyword = alias("tsNeverKeyword"), + TSNullKeyword = exports.TSNullKeyword = alias("tsNullKeyword"), + TSNumberKeyword = exports.TSNumberKeyword = alias("tsNumberKeyword"), + TSObjectKeyword = exports.TSObjectKeyword = alias("tsObjectKeyword"), + TSStringKeyword = exports.TSStringKeyword = alias("tsStringKeyword"), + TSSymbolKeyword = exports.TSSymbolKeyword = alias("tsSymbolKeyword"), + TSUndefinedKeyword = exports.TSUndefinedKeyword = alias("tsUndefinedKeyword"), + TSUnknownKeyword = exports.TSUnknownKeyword = alias("tsUnknownKeyword"), + TSVoidKeyword = exports.TSVoidKeyword = alias("tsVoidKeyword"), + TSThisType = exports.TSThisType = alias("tsThisType"), + TSFunctionType = exports.TSFunctionType = alias("tsFunctionType"), + TSConstructorType = exports.TSConstructorType = alias("tsConstructorType"), + TSTypeReference = exports.TSTypeReference = alias("tsTypeReference"), + TSTypePredicate = exports.TSTypePredicate = alias("tsTypePredicate"), + TSTypeQuery = exports.TSTypeQuery = alias("tsTypeQuery"), + TSTypeLiteral = exports.TSTypeLiteral = alias("tsTypeLiteral"), + TSArrayType = exports.TSArrayType = alias("tsArrayType"), + TSTupleType = exports.TSTupleType = alias("tsTupleType"), + TSOptionalType = exports.TSOptionalType = alias("tsOptionalType"), + TSRestType = exports.TSRestType = alias("tsRestType"), + TSNamedTupleMember = exports.TSNamedTupleMember = alias("tsNamedTupleMember"), + TSUnionType = exports.TSUnionType = alias("tsUnionType"), + TSIntersectionType = exports.TSIntersectionType = alias("tsIntersectionType"), + TSConditionalType = exports.TSConditionalType = alias("tsConditionalType"), + TSInferType = exports.TSInferType = alias("tsInferType"), + TSParenthesizedType = exports.TSParenthesizedType = alias("tsParenthesizedType"), + TSTypeOperator = exports.TSTypeOperator = alias("tsTypeOperator"), + TSIndexedAccessType = exports.TSIndexedAccessType = alias("tsIndexedAccessType"), + TSMappedType = exports.TSMappedType = alias("tsMappedType"), + TSTemplateLiteralType = exports.TSTemplateLiteralType = alias("tsTemplateLiteralType"), + TSLiteralType = exports.TSLiteralType = alias("tsLiteralType"), + TSExpressionWithTypeArguments = exports.TSExpressionWithTypeArguments = alias("tsExpressionWithTypeArguments"), + TSInterfaceDeclaration = exports.TSInterfaceDeclaration = alias("tsInterfaceDeclaration"), + TSInterfaceBody = exports.TSInterfaceBody = alias("tsInterfaceBody"), + TSTypeAliasDeclaration = exports.TSTypeAliasDeclaration = alias("tsTypeAliasDeclaration"), + TSInstantiationExpression = exports.TSInstantiationExpression = alias("tsInstantiationExpression"), + TSAsExpression = exports.TSAsExpression = alias("tsAsExpression"), + TSSatisfiesExpression = exports.TSSatisfiesExpression = alias("tsSatisfiesExpression"), + TSTypeAssertion = exports.TSTypeAssertion = alias("tsTypeAssertion"), + TSEnumBody = exports.TSEnumBody = alias("tsEnumBody"), + TSEnumDeclaration = exports.TSEnumDeclaration = alias("tsEnumDeclaration"), + TSEnumMember = exports.TSEnumMember = alias("tsEnumMember"), + TSModuleDeclaration = exports.TSModuleDeclaration = alias("tsModuleDeclaration"), + TSModuleBlock = exports.TSModuleBlock = alias("tsModuleBlock"), + TSImportType = exports.TSImportType = alias("tsImportType"), + TSImportEqualsDeclaration = exports.TSImportEqualsDeclaration = alias("tsImportEqualsDeclaration"), + TSExternalModuleReference = exports.TSExternalModuleReference = alias("tsExternalModuleReference"), + TSNonNullExpression = exports.TSNonNullExpression = alias("tsNonNullExpression"), + TSExportAssignment = exports.TSExportAssignment = alias("tsExportAssignment"), + TSNamespaceExportDeclaration = exports.TSNamespaceExportDeclaration = alias("tsNamespaceExportDeclaration"), + TSTypeAnnotation = exports.TSTypeAnnotation = alias("tsTypeAnnotation"), + TSTypeParameterInstantiation = exports.TSTypeParameterInstantiation = alias("tsTypeParameterInstantiation"), + TSTypeParameterDeclaration = exports.TSTypeParameterDeclaration = alias("tsTypeParameterDeclaration"), + TSTypeParameter = exports.TSTypeParameter = alias("tsTypeParameter"); +const NumberLiteral = exports.NumberLiteral = b.numberLiteral, + RegexLiteral = exports.RegexLiteral = b.regexLiteral, + RestProperty = exports.RestProperty = b.restProperty, + SpreadProperty = exports.SpreadProperty = b.spreadProperty; + +//# sourceMappingURL=uppercase.js.map + + +/***/ }), + +/***/ 38504: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports.buildUndefinedNode = buildUndefinedNode; +var _index = __nccwpck_require__(90670); +function buildUndefinedNode() { + return (0, _index.unaryExpression)("void", (0, _index.numericLiteral)(0), true); +} + +//# sourceMappingURL=productions.js.map + + +/***/ }), + +/***/ 93415: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports["default"] = buildChildren; +var _index = __nccwpck_require__(40741); +var _cleanJSXElementLiteralChild = __nccwpck_require__(43508); +function buildChildren(node) { + const elements = []; + for (let i = 0; i < node.children.length; i++) { + let child = node.children[i]; + if ((0, _index.isJSXText)(child)) { + (0, _cleanJSXElementLiteralChild.default)(child, elements); + continue; + } + if ((0, _index.isJSXExpressionContainer)(child)) child = child.expression; + if ((0, _index.isJSXEmptyExpression)(child)) continue; + elements.push(child); + } + return elements; +} + +//# sourceMappingURL=buildChildren.js.map + + +/***/ }), + +/***/ 15766: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports["default"] = createTSUnionType; +var _index = __nccwpck_require__(90670); +var _removeTypeDuplicates = __nccwpck_require__(76123); +var _index2 = __nccwpck_require__(40741); +function createTSUnionType(typeAnnotations) { + const types = typeAnnotations.map(type => { + return (0, _index2.isTSTypeAnnotation)(type) ? type.typeAnnotation : type; + }); + const flattened = (0, _removeTypeDuplicates.default)(types); + if (flattened.length === 1) { + return flattened[0]; + } else { + return (0, _index.tsUnionType)(flattened); + } +} + +//# sourceMappingURL=createTSUnionType.js.map + + +/***/ }), + +/***/ 59260: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports["default"] = clone; +var _cloneNode = __nccwpck_require__(89260); +function clone(node) { + return (0, _cloneNode.default)(node, false); +} + +//# sourceMappingURL=clone.js.map + + +/***/ }), + +/***/ 45922: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports["default"] = cloneDeep; +var _cloneNode = __nccwpck_require__(89260); +function cloneDeep(node) { + return (0, _cloneNode.default)(node); +} + +//# sourceMappingURL=cloneDeep.js.map + + +/***/ }), + +/***/ 37992: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports["default"] = cloneDeepWithoutLoc; +var _cloneNode = __nccwpck_require__(89260); +function cloneDeepWithoutLoc(node) { + return (0, _cloneNode.default)(node, true, true); +} + +//# sourceMappingURL=cloneDeepWithoutLoc.js.map + + +/***/ }), + +/***/ 89260: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports["default"] = cloneNode; +var _index = __nccwpck_require__(40910); +var _index2 = __nccwpck_require__(40741); +const { + hasOwn +} = { + hasOwn: Function.call.bind(Object.prototype.hasOwnProperty) +}; +function cloneIfNode(obj, deep, withoutLoc, commentsCache) { + if (obj && typeof obj.type === "string") { + return cloneNodeInternal(obj, deep, withoutLoc, commentsCache); + } + return obj; +} +function cloneIfNodeOrArray(obj, deep, withoutLoc, commentsCache) { + if (Array.isArray(obj)) { + return obj.map(node => cloneIfNode(node, deep, withoutLoc, commentsCache)); + } + return cloneIfNode(obj, deep, withoutLoc, commentsCache); +} +function cloneNode(node, deep = true, withoutLoc = false) { + return cloneNodeInternal(node, deep, withoutLoc, new Map()); +} +function cloneNodeInternal(node, deep = true, withoutLoc = false, commentsCache) { + if (!node) return node; + const { + type + } = node; + const newNode = { + type: node.type + }; + if ((0, _index2.isIdentifier)(node)) { + newNode.name = node.name; + if (hasOwn(node, "optional") && typeof node.optional === "boolean") { + newNode.optional = node.optional; + } + if (hasOwn(node, "typeAnnotation")) { + newNode.typeAnnotation = deep ? cloneIfNodeOrArray(node.typeAnnotation, true, withoutLoc, commentsCache) : node.typeAnnotation; + } + if (hasOwn(node, "decorators")) { + newNode.decorators = deep ? cloneIfNodeOrArray(node.decorators, true, withoutLoc, commentsCache) : node.decorators; + } + } else if (!hasOwn(_index.NODE_FIELDS, type)) { + throw new Error(`Unknown node type: "${type}"`); + } else { + for (const field of Object.keys(_index.NODE_FIELDS[type])) { + if (hasOwn(node, field)) { + if (deep) { + newNode[field] = (0, _index2.isFile)(node) && field === "comments" ? maybeCloneComments(node.comments, deep, withoutLoc, commentsCache) : cloneIfNodeOrArray(node[field], true, withoutLoc, commentsCache); + } else { + newNode[field] = node[field]; + } + } + } + } + if (hasOwn(node, "loc")) { + if (withoutLoc) { + newNode.loc = null; + } else { + newNode.loc = node.loc; + } + } + if (hasOwn(node, "leadingComments")) { + newNode.leadingComments = maybeCloneComments(node.leadingComments, deep, withoutLoc, commentsCache); + } + if (hasOwn(node, "innerComments")) { + newNode.innerComments = maybeCloneComments(node.innerComments, deep, withoutLoc, commentsCache); + } + if (hasOwn(node, "trailingComments")) { + newNode.trailingComments = maybeCloneComments(node.trailingComments, deep, withoutLoc, commentsCache); + } + if (hasOwn(node, "extra")) { + newNode.extra = Object.assign({}, node.extra); + } + return newNode; +} +function maybeCloneComments(comments, deep, withoutLoc, commentsCache) { + if (!comments || !deep) { + return comments; + } + return comments.map(comment => { + const cache = commentsCache.get(comment); + if (cache) return cache; + const { + type, + value, + loc + } = comment; + const ret = { + type, + value, + loc + }; + if (withoutLoc) { + ret.loc = null; + } + commentsCache.set(comment, ret); + return ret; + }); +} + +//# sourceMappingURL=cloneNode.js.map + + +/***/ }), + +/***/ 19258: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports["default"] = cloneWithoutLoc; +var _cloneNode = __nccwpck_require__(89260); +function cloneWithoutLoc(node) { + return (0, _cloneNode.default)(node, false, true); +} + +//# sourceMappingURL=cloneWithoutLoc.js.map + + +/***/ }), + +/***/ 70704: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports["default"] = addComment; +var _addComments = __nccwpck_require__(21071); +function addComment(node, type, content, line) { + return (0, _addComments.default)(node, type, [{ + type: line ? "CommentLine" : "CommentBlock", + value: content + }]); +} + +//# sourceMappingURL=addComment.js.map + + +/***/ }), + +/***/ 21071: +/***/ ((__unused_webpack_module, exports) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports["default"] = addComments; +function addComments(node, type, comments) { + if (!comments || !node) return node; + const key = `${type}Comments`; + if (node[key]) { + if (type === "leading") { + node[key] = comments.concat(node[key]); + } else { + node[key].push(...comments); + } + } else { + node[key] = comments; + } + return node; +} + +//# sourceMappingURL=addComments.js.map + + +/***/ }), + +/***/ 40731: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports["default"] = inheritInnerComments; +var _inherit = __nccwpck_require__(40066); +function inheritInnerComments(child, parent) { + (0, _inherit.default)("innerComments", child, parent); +} + +//# sourceMappingURL=inheritInnerComments.js.map + + +/***/ }), + +/***/ 7725: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports["default"] = inheritLeadingComments; +var _inherit = __nccwpck_require__(40066); +function inheritLeadingComments(child, parent) { + (0, _inherit.default)("leadingComments", child, parent); +} + +//# sourceMappingURL=inheritLeadingComments.js.map + + +/***/ }), + +/***/ 23491: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports["default"] = inheritTrailingComments; +var _inherit = __nccwpck_require__(40066); +function inheritTrailingComments(child, parent) { + (0, _inherit.default)("trailingComments", child, parent); +} + +//# sourceMappingURL=inheritTrailingComments.js.map + + +/***/ }), + +/***/ 32078: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports["default"] = inheritsComments; +var _inheritTrailingComments = __nccwpck_require__(23491); +var _inheritLeadingComments = __nccwpck_require__(7725); +var _inheritInnerComments = __nccwpck_require__(40731); +function inheritsComments(child, parent) { + (0, _inheritTrailingComments.default)(child, parent); + (0, _inheritLeadingComments.default)(child, parent); + (0, _inheritInnerComments.default)(child, parent); + return child; +} + +//# sourceMappingURL=inheritsComments.js.map + + +/***/ }), + +/***/ 84066: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports["default"] = removeComments; +var _index = __nccwpck_require__(17945); +function removeComments(node) { + _index.COMMENT_KEYS.forEach(key => { + node[key] = null; + }); + return node; +} + +//# sourceMappingURL=removeComments.js.map + + +/***/ }), + +/***/ 12359: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports.WHILE_TYPES = exports.USERWHITESPACABLE_TYPES = exports.UNARYLIKE_TYPES = exports.TYPESCRIPT_TYPES = exports.TSTYPE_TYPES = exports.TSTYPEELEMENT_TYPES = exports.TSENTITYNAME_TYPES = exports.TSBASETYPE_TYPES = exports.TERMINATORLESS_TYPES = exports.STATEMENT_TYPES = exports.STANDARDIZED_TYPES = exports.SCOPABLE_TYPES = exports.PUREISH_TYPES = exports.PROPERTY_TYPES = exports.PRIVATE_TYPES = exports.PATTERN_TYPES = exports.PATTERNLIKE_TYPES = exports.OBJECTMEMBER_TYPES = exports.MODULESPECIFIER_TYPES = exports.MODULEDECLARATION_TYPES = exports.MISCELLANEOUS_TYPES = exports.METHOD_TYPES = exports.LVAL_TYPES = exports.LOOP_TYPES = exports.LITERAL_TYPES = exports.JSX_TYPES = exports.IMPORTOREXPORTDECLARATION_TYPES = exports.IMMUTABLE_TYPES = exports.FUNCTION_TYPES = exports.FUNCTIONPARENT_TYPES = exports.FOR_TYPES = exports.FORXSTATEMENT_TYPES = exports.FLOW_TYPES = exports.FLOWTYPE_TYPES = exports.FLOWPREDICATE_TYPES = exports.FLOWDECLARATION_TYPES = exports.FLOWBASEANNOTATION_TYPES = exports.EXPRESSION_TYPES = exports.EXPRESSIONWRAPPER_TYPES = exports.EXPORTDECLARATION_TYPES = exports.ENUMMEMBER_TYPES = exports.ENUMBODY_TYPES = exports.DECLARATION_TYPES = exports.CONDITIONAL_TYPES = exports.COMPLETIONSTATEMENT_TYPES = exports.CLASS_TYPES = exports.BLOCK_TYPES = exports.BLOCKPARENT_TYPES = exports.BINARY_TYPES = exports.ACCESSOR_TYPES = void 0; +var _index = __nccwpck_require__(40910); +const STANDARDIZED_TYPES = exports.STANDARDIZED_TYPES = _index.FLIPPED_ALIAS_KEYS["Standardized"]; +const EXPRESSION_TYPES = exports.EXPRESSION_TYPES = _index.FLIPPED_ALIAS_KEYS["Expression"]; +const BINARY_TYPES = exports.BINARY_TYPES = _index.FLIPPED_ALIAS_KEYS["Binary"]; +const SCOPABLE_TYPES = exports.SCOPABLE_TYPES = _index.FLIPPED_ALIAS_KEYS["Scopable"]; +const BLOCKPARENT_TYPES = exports.BLOCKPARENT_TYPES = _index.FLIPPED_ALIAS_KEYS["BlockParent"]; +const BLOCK_TYPES = exports.BLOCK_TYPES = _index.FLIPPED_ALIAS_KEYS["Block"]; +const STATEMENT_TYPES = exports.STATEMENT_TYPES = _index.FLIPPED_ALIAS_KEYS["Statement"]; +const TERMINATORLESS_TYPES = exports.TERMINATORLESS_TYPES = _index.FLIPPED_ALIAS_KEYS["Terminatorless"]; +const COMPLETIONSTATEMENT_TYPES = exports.COMPLETIONSTATEMENT_TYPES = _index.FLIPPED_ALIAS_KEYS["CompletionStatement"]; +const CONDITIONAL_TYPES = exports.CONDITIONAL_TYPES = _index.FLIPPED_ALIAS_KEYS["Conditional"]; +const LOOP_TYPES = exports.LOOP_TYPES = _index.FLIPPED_ALIAS_KEYS["Loop"]; +const WHILE_TYPES = exports.WHILE_TYPES = _index.FLIPPED_ALIAS_KEYS["While"]; +const EXPRESSIONWRAPPER_TYPES = exports.EXPRESSIONWRAPPER_TYPES = _index.FLIPPED_ALIAS_KEYS["ExpressionWrapper"]; +const FOR_TYPES = exports.FOR_TYPES = _index.FLIPPED_ALIAS_KEYS["For"]; +const FORXSTATEMENT_TYPES = exports.FORXSTATEMENT_TYPES = _index.FLIPPED_ALIAS_KEYS["ForXStatement"]; +const FUNCTION_TYPES = exports.FUNCTION_TYPES = _index.FLIPPED_ALIAS_KEYS["Function"]; +const FUNCTIONPARENT_TYPES = exports.FUNCTIONPARENT_TYPES = _index.FLIPPED_ALIAS_KEYS["FunctionParent"]; +const PUREISH_TYPES = exports.PUREISH_TYPES = _index.FLIPPED_ALIAS_KEYS["Pureish"]; +const DECLARATION_TYPES = exports.DECLARATION_TYPES = _index.FLIPPED_ALIAS_KEYS["Declaration"]; +const PATTERNLIKE_TYPES = exports.PATTERNLIKE_TYPES = _index.FLIPPED_ALIAS_KEYS["PatternLike"]; +const LVAL_TYPES = exports.LVAL_TYPES = _index.FLIPPED_ALIAS_KEYS["LVal"]; +const TSENTITYNAME_TYPES = exports.TSENTITYNAME_TYPES = _index.FLIPPED_ALIAS_KEYS["TSEntityName"]; +const LITERAL_TYPES = exports.LITERAL_TYPES = _index.FLIPPED_ALIAS_KEYS["Literal"]; +const IMMUTABLE_TYPES = exports.IMMUTABLE_TYPES = _index.FLIPPED_ALIAS_KEYS["Immutable"]; +const USERWHITESPACABLE_TYPES = exports.USERWHITESPACABLE_TYPES = _index.FLIPPED_ALIAS_KEYS["UserWhitespacable"]; +const METHOD_TYPES = exports.METHOD_TYPES = _index.FLIPPED_ALIAS_KEYS["Method"]; +const OBJECTMEMBER_TYPES = exports.OBJECTMEMBER_TYPES = _index.FLIPPED_ALIAS_KEYS["ObjectMember"]; +const PROPERTY_TYPES = exports.PROPERTY_TYPES = _index.FLIPPED_ALIAS_KEYS["Property"]; +const UNARYLIKE_TYPES = exports.UNARYLIKE_TYPES = _index.FLIPPED_ALIAS_KEYS["UnaryLike"]; +const PATTERN_TYPES = exports.PATTERN_TYPES = _index.FLIPPED_ALIAS_KEYS["Pattern"]; +const CLASS_TYPES = exports.CLASS_TYPES = _index.FLIPPED_ALIAS_KEYS["Class"]; +const IMPORTOREXPORTDECLARATION_TYPES = exports.IMPORTOREXPORTDECLARATION_TYPES = _index.FLIPPED_ALIAS_KEYS["ImportOrExportDeclaration"]; +const EXPORTDECLARATION_TYPES = exports.EXPORTDECLARATION_TYPES = _index.FLIPPED_ALIAS_KEYS["ExportDeclaration"]; +const MODULESPECIFIER_TYPES = exports.MODULESPECIFIER_TYPES = _index.FLIPPED_ALIAS_KEYS["ModuleSpecifier"]; +const ACCESSOR_TYPES = exports.ACCESSOR_TYPES = _index.FLIPPED_ALIAS_KEYS["Accessor"]; +const PRIVATE_TYPES = exports.PRIVATE_TYPES = _index.FLIPPED_ALIAS_KEYS["Private"]; +const FLOW_TYPES = exports.FLOW_TYPES = _index.FLIPPED_ALIAS_KEYS["Flow"]; +const FLOWTYPE_TYPES = exports.FLOWTYPE_TYPES = _index.FLIPPED_ALIAS_KEYS["FlowType"]; +const FLOWBASEANNOTATION_TYPES = exports.FLOWBASEANNOTATION_TYPES = _index.FLIPPED_ALIAS_KEYS["FlowBaseAnnotation"]; +const FLOWDECLARATION_TYPES = exports.FLOWDECLARATION_TYPES = _index.FLIPPED_ALIAS_KEYS["FlowDeclaration"]; +const FLOWPREDICATE_TYPES = exports.FLOWPREDICATE_TYPES = _index.FLIPPED_ALIAS_KEYS["FlowPredicate"]; +const ENUMBODY_TYPES = exports.ENUMBODY_TYPES = _index.FLIPPED_ALIAS_KEYS["EnumBody"]; +const ENUMMEMBER_TYPES = exports.ENUMMEMBER_TYPES = _index.FLIPPED_ALIAS_KEYS["EnumMember"]; +const JSX_TYPES = exports.JSX_TYPES = _index.FLIPPED_ALIAS_KEYS["JSX"]; +const MISCELLANEOUS_TYPES = exports.MISCELLANEOUS_TYPES = _index.FLIPPED_ALIAS_KEYS["Miscellaneous"]; +const TYPESCRIPT_TYPES = exports.TYPESCRIPT_TYPES = _index.FLIPPED_ALIAS_KEYS["TypeScript"]; +const TSTYPEELEMENT_TYPES = exports.TSTYPEELEMENT_TYPES = _index.FLIPPED_ALIAS_KEYS["TSTypeElement"]; +const TSTYPE_TYPES = exports.TSTYPE_TYPES = _index.FLIPPED_ALIAS_KEYS["TSType"]; +const TSBASETYPE_TYPES = exports.TSBASETYPE_TYPES = _index.FLIPPED_ALIAS_KEYS["TSBaseType"]; +const MODULEDECLARATION_TYPES = exports.MODULEDECLARATION_TYPES = IMPORTOREXPORTDECLARATION_TYPES; + +//# sourceMappingURL=index.js.map + + +/***/ }), + +/***/ 17945: +/***/ ((__unused_webpack_module, exports) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports.UPDATE_OPERATORS = exports.UNARY_OPERATORS = exports.STRING_UNARY_OPERATORS = exports.STATEMENT_OR_BLOCK_KEYS = exports.NUMBER_UNARY_OPERATORS = exports.NUMBER_BINARY_OPERATORS = exports.LOGICAL_OPERATORS = exports.INHERIT_KEYS = exports.FOR_INIT_KEYS = exports.FLATTENABLE_KEYS = exports.EQUALITY_BINARY_OPERATORS = exports.COMPARISON_BINARY_OPERATORS = exports.COMMENT_KEYS = exports.BOOLEAN_UNARY_OPERATORS = exports.BOOLEAN_NUMBER_BINARY_OPERATORS = exports.BOOLEAN_BINARY_OPERATORS = exports.BINARY_OPERATORS = exports.ASSIGNMENT_OPERATORS = void 0; +const STATEMENT_OR_BLOCK_KEYS = exports.STATEMENT_OR_BLOCK_KEYS = ["consequent", "body", "alternate"]; +const FLATTENABLE_KEYS = exports.FLATTENABLE_KEYS = ["body", "expressions"]; +const FOR_INIT_KEYS = exports.FOR_INIT_KEYS = ["left", "init"]; +const COMMENT_KEYS = exports.COMMENT_KEYS = ["leadingComments", "trailingComments", "innerComments"]; +const LOGICAL_OPERATORS = exports.LOGICAL_OPERATORS = ["||", "&&", "??"]; +const UPDATE_OPERATORS = exports.UPDATE_OPERATORS = ["++", "--"]; +const BOOLEAN_NUMBER_BINARY_OPERATORS = exports.BOOLEAN_NUMBER_BINARY_OPERATORS = [">", "<", ">=", "<="]; +const EQUALITY_BINARY_OPERATORS = exports.EQUALITY_BINARY_OPERATORS = ["==", "===", "!=", "!=="]; +const COMPARISON_BINARY_OPERATORS = exports.COMPARISON_BINARY_OPERATORS = [...EQUALITY_BINARY_OPERATORS, "in", "instanceof"]; +const BOOLEAN_BINARY_OPERATORS = exports.BOOLEAN_BINARY_OPERATORS = [...COMPARISON_BINARY_OPERATORS, ...BOOLEAN_NUMBER_BINARY_OPERATORS]; +const NUMBER_BINARY_OPERATORS = exports.NUMBER_BINARY_OPERATORS = ["-", "/", "%", "*", "**", "&", "|", ">>", ">>>", "<<", "^"]; +const BINARY_OPERATORS = exports.BINARY_OPERATORS = ["+", ...NUMBER_BINARY_OPERATORS, ...BOOLEAN_BINARY_OPERATORS, "|>"]; +const ASSIGNMENT_OPERATORS = exports.ASSIGNMENT_OPERATORS = ["=", "+=", ...NUMBER_BINARY_OPERATORS.map(op => op + "="), ...LOGICAL_OPERATORS.map(op => op + "=")]; +const BOOLEAN_UNARY_OPERATORS = exports.BOOLEAN_UNARY_OPERATORS = ["delete", "!"]; +const NUMBER_UNARY_OPERATORS = exports.NUMBER_UNARY_OPERATORS = ["+", "-", "~"]; +const STRING_UNARY_OPERATORS = exports.STRING_UNARY_OPERATORS = ["typeof"]; +const UNARY_OPERATORS = exports.UNARY_OPERATORS = ["void", "throw", ...BOOLEAN_UNARY_OPERATORS, ...NUMBER_UNARY_OPERATORS, ...STRING_UNARY_OPERATORS]; +const INHERIT_KEYS = exports.INHERIT_KEYS = { + optional: ["typeAnnotation", "typeParameters", "returnType"], + force: ["start", "loc", "end"] +}; +{ + exports.BLOCK_SCOPED_SYMBOL = Symbol.for("var used to be block scoped"); + exports.NOT_LOCAL_BINDING = Symbol.for("should not be considered a local binding"); +} + +//# sourceMappingURL=index.js.map + + +/***/ }), + +/***/ 77046: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports["default"] = ensureBlock; +var _toBlock = __nccwpck_require__(59735); +function ensureBlock(node, key = "body") { + const result = (0, _toBlock.default)(node[key], node); + node[key] = result; + return result; +} + +//# sourceMappingURL=ensureBlock.js.map + + +/***/ }), + +/***/ 77060: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports["default"] = gatherSequenceExpressions; +var _getBindingIdentifiers = __nccwpck_require__(45300); +var _index = __nccwpck_require__(40741); +var _index2 = __nccwpck_require__(90670); +var _productions = __nccwpck_require__(38504); +var _cloneNode = __nccwpck_require__(89260); +; +function gatherSequenceExpressions(nodes, declars) { + const exprs = []; + let ensureLastUndefined = true; + for (const node of nodes) { + if (!(0, _index.isEmptyStatement)(node)) { + ensureLastUndefined = false; + } + if ((0, _index.isExpression)(node)) { + exprs.push(node); + } else if ((0, _index.isExpressionStatement)(node)) { + exprs.push(node.expression); + } else if ((0, _index.isVariableDeclaration)(node)) { + if (node.kind !== "var") return; + for (const declar of node.declarations) { + const bindings = (0, _getBindingIdentifiers.default)(declar); + for (const key of Object.keys(bindings)) { + declars.push({ + kind: node.kind, + id: (0, _cloneNode.default)(bindings[key]) + }); + } + if (declar.init) { + exprs.push((0, _index2.assignmentExpression)("=", declar.id, declar.init)); + } + } + ensureLastUndefined = true; + } else if ((0, _index.isIfStatement)(node)) { + const consequent = node.consequent ? gatherSequenceExpressions([node.consequent], declars) : (0, _productions.buildUndefinedNode)(); + const alternate = node.alternate ? gatherSequenceExpressions([node.alternate], declars) : (0, _productions.buildUndefinedNode)(); + if (!consequent || !alternate) return; + exprs.push((0, _index2.conditionalExpression)(node.test, consequent, alternate)); + } else if ((0, _index.isBlockStatement)(node)) { + const body = gatherSequenceExpressions(node.body, declars); + if (!body) return; + exprs.push(body); + } else if ((0, _index.isEmptyStatement)(node)) { + if (nodes.indexOf(node) === 0) { + ensureLastUndefined = true; + } + } else { + return; + } + } + if (ensureLastUndefined) { + exprs.push((0, _productions.buildUndefinedNode)()); + } + if (exprs.length === 1) { + return exprs[0]; + } else { + return (0, _index2.sequenceExpression)(exprs); + } +} + +//# sourceMappingURL=gatherSequenceExpressions.js.map + + +/***/ }), + +/***/ 19687: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports["default"] = toBindingIdentifierName; +var _toIdentifier = __nccwpck_require__(59451); +function toBindingIdentifierName(name) { + name = (0, _toIdentifier.default)(name); + if (name === "eval" || name === "arguments") name = "_" + name; + return name; +} + +//# sourceMappingURL=toBindingIdentifierName.js.map + + +/***/ }), + +/***/ 59735: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports["default"] = toBlock; +var _index = __nccwpck_require__(40741); +var _index2 = __nccwpck_require__(90670); +function toBlock(node, parent) { + if ((0, _index.isBlockStatement)(node)) { + return node; + } + let blockNodes = []; + if ((0, _index.isEmptyStatement)(node)) { + blockNodes = []; + } else { + if (!(0, _index.isStatement)(node)) { + if ((0, _index.isFunction)(parent)) { + node = (0, _index2.returnStatement)(node); + } else { + node = (0, _index2.expressionStatement)(node); + } + } + blockNodes = [node]; + } + return (0, _index2.blockStatement)(blockNodes); +} + +//# sourceMappingURL=toBlock.js.map + + +/***/ }), + +/***/ 49480: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports["default"] = toComputedKey; +var _index = __nccwpck_require__(40741); +var _index2 = __nccwpck_require__(90670); +function toComputedKey(node, key = node.key || node.property) { + if (!node.computed && (0, _index.isIdentifier)(key)) key = (0, _index2.stringLiteral)(key.name); + return key; +} + +//# sourceMappingURL=toComputedKey.js.map + + +/***/ }), + +/***/ 36490: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports["default"] = void 0; +var _index = __nccwpck_require__(40741); +var _default = exports["default"] = toExpression; +function toExpression(node) { + if ((0, _index.isExpressionStatement)(node)) { + node = node.expression; + } + if ((0, _index.isExpression)(node)) { + return node; + } + if ((0, _index.isClass)(node)) { + node.type = "ClassExpression"; + } else if ((0, _index.isFunction)(node)) { + node.type = "FunctionExpression"; + } + if (!(0, _index.isExpression)(node)) { + throw new Error(`cannot turn ${node.type} to an expression`); + } + return node; +} + +//# sourceMappingURL=toExpression.js.map + + +/***/ }), + +/***/ 59451: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports["default"] = toIdentifier; +var _isValidIdentifier = __nccwpck_require__(66030); +var _helperValidatorIdentifier = __nccwpck_require__(76599); +function toIdentifier(input) { + input = input + ""; + let name = ""; + for (const c of input) { + name += (0, _helperValidatorIdentifier.isIdentifierChar)(c.codePointAt(0)) ? c : "-"; + } + name = name.replace(/^[-0-9]+/, ""); + name = name.replace(/[-\s]+(.)?/g, function (match, c) { + return c ? c.toUpperCase() : ""; + }); + if (!(0, _isValidIdentifier.default)(name)) { + name = `_${name}`; + } + return name || "_"; +} + +//# sourceMappingURL=toIdentifier.js.map + + +/***/ }), + +/***/ 23381: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports["default"] = toKeyAlias; +var _index = __nccwpck_require__(40741); +var _cloneNode = __nccwpck_require__(89260); +var _removePropertiesDeep = __nccwpck_require__(55902); +function toKeyAlias(node, key = node.key) { + let alias; + if (node.kind === "method") { + return toKeyAlias.increment() + ""; + } else if ((0, _index.isIdentifier)(key)) { + alias = key.name; + } else if ((0, _index.isStringLiteral)(key)) { + alias = JSON.stringify(key.value); + } else { + alias = JSON.stringify((0, _removePropertiesDeep.default)((0, _cloneNode.default)(key))); + } + if (node.computed) { + alias = `[${alias}]`; + } + if (node.static) { + alias = `static:${alias}`; + } + return alias; +} +toKeyAlias.uid = 0; +toKeyAlias.increment = function () { + if (toKeyAlias.uid >= Number.MAX_SAFE_INTEGER) { + return toKeyAlias.uid = 0; + } else { + return toKeyAlias.uid++; + } +}; + +//# sourceMappingURL=toKeyAlias.js.map + + +/***/ }), + +/***/ 403: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports["default"] = toSequenceExpression; +var _gatherSequenceExpressions = __nccwpck_require__(77060); +; +function toSequenceExpression(nodes, scope) { + if (!(nodes != null && nodes.length)) return; + const declars = []; + const result = (0, _gatherSequenceExpressions.default)(nodes, declars); + if (!result) return; + for (const declar of declars) { + scope.push(declar); + } + return result; +} + +//# sourceMappingURL=toSequenceExpression.js.map + + +/***/ }), + +/***/ 82635: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports["default"] = void 0; +var _index = __nccwpck_require__(40741); +var _index2 = __nccwpck_require__(90670); +var _default = exports["default"] = toStatement; +function toStatement(node, ignore) { + if ((0, _index.isStatement)(node)) { + return node; + } + let mustHaveId = false; + let newType; + if ((0, _index.isClass)(node)) { + mustHaveId = true; + newType = "ClassDeclaration"; + } else if ((0, _index.isFunction)(node)) { + mustHaveId = true; + newType = "FunctionDeclaration"; + } else if ((0, _index.isAssignmentExpression)(node)) { + return (0, _index2.expressionStatement)(node); + } + if (mustHaveId && !node.id) { + newType = false; + } + if (!newType) { + if (ignore) { + return false; + } else { + throw new Error(`cannot turn ${node.type} to a statement`); + } + } + node.type = newType; + return node; +} + +//# sourceMappingURL=toStatement.js.map + + +/***/ }), + +/***/ 20021: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports["default"] = void 0; +var _isValidIdentifier = __nccwpck_require__(66030); +var _index = __nccwpck_require__(90670); +var _default = exports["default"] = valueToNode; +const objectToString = Function.call.bind(Object.prototype.toString); +function isRegExp(value) { + return objectToString(value) === "[object RegExp]"; +} +function isPlainObject(value) { + if (typeof value !== "object" || value === null || Object.prototype.toString.call(value) !== "[object Object]") { + return false; + } + const proto = Object.getPrototypeOf(value); + return proto === null || Object.getPrototypeOf(proto) === null; +} +function valueToNode(value) { + if (value === undefined) { + return (0, _index.identifier)("undefined"); + } + if (value === true || value === false) { + return (0, _index.booleanLiteral)(value); + } + if (value === null) { + return (0, _index.nullLiteral)(); + } + if (typeof value === "string") { + return (0, _index.stringLiteral)(value); + } + if (typeof value === "number") { + let result; + if (Number.isFinite(value)) { + result = (0, _index.numericLiteral)(Math.abs(value)); + } else { + let numerator; + if (Number.isNaN(value)) { + numerator = (0, _index.numericLiteral)(0); + } else { + numerator = (0, _index.numericLiteral)(1); + } + result = (0, _index.binaryExpression)("/", numerator, (0, _index.numericLiteral)(0)); + } + if (value < 0 || Object.is(value, -0)) { + result = (0, _index.unaryExpression)("-", result); + } + return result; + } + if (typeof value === "bigint") { + return (0, _index.bigIntLiteral)(value.toString()); + } + if (isRegExp(value)) { + const pattern = value.source; + const flags = /\/([a-z]*)$/.exec(value.toString())[1]; + return (0, _index.regExpLiteral)(pattern, flags); + } + if (Array.isArray(value)) { + return (0, _index.arrayExpression)(value.map(valueToNode)); + } + if (isPlainObject(value)) { + const props = []; + for (const key of Object.keys(value)) { + let nodeKey, + computed = false; + if ((0, _isValidIdentifier.default)(key)) { + if (key === "__proto__") { + computed = true; + nodeKey = (0, _index.stringLiteral)(key); + } else { + nodeKey = (0, _index.identifier)(key); + } + } else { + nodeKey = (0, _index.stringLiteral)(key); + } + props.push((0, _index.objectProperty)(nodeKey, valueToNode(value[key]), computed)); + } + return (0, _index.objectExpression)(props); + } + throw new Error("don't know how to turn this value into a node"); +} + +//# sourceMappingURL=valueToNode.js.map + + +/***/ }), + +/***/ 62803: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports.patternLikeCommon = exports.importAttributes = exports.functionTypeAnnotationCommon = exports.functionDeclarationCommon = exports.functionCommon = exports.classMethodOrPropertyCommon = exports.classMethodOrDeclareMethodCommon = void 0; +var _is = __nccwpck_require__(20051); +var _isValidIdentifier = __nccwpck_require__(66030); +var _helperValidatorIdentifier = __nccwpck_require__(76599); +var _helperStringParser = __nccwpck_require__(73728); +var _index = __nccwpck_require__(17945); +var _utils = __nccwpck_require__(34559); +const defineType = (0, _utils.defineAliasedType)("Standardized"); +defineType("ArrayExpression", { + fields: { + elements: { + validate: (0, _utils.arrayOf)((0, _utils.assertNodeOrValueType)("null", "Expression", "SpreadElement")), + default: !process.env.BABEL_TYPES_8_BREAKING ? [] : undefined + } + }, + visitor: ["elements"], + aliases: ["Expression"] +}); +defineType("AssignmentExpression", { + fields: { + operator: { + validate: !process.env.BABEL_TYPES_8_BREAKING ? (0, _utils.assertValueType)("string") : Object.assign(function () { + const identifier = (0, _utils.assertOneOf)(..._index.ASSIGNMENT_OPERATORS); + const pattern = (0, _utils.assertOneOf)("="); + return function (node, key, val) { + const validator = (0, _is.default)("Pattern", node.left) ? pattern : identifier; + validator(node, key, val); + }; + }(), { + type: "string" + }) + }, + left: { + validate: !process.env.BABEL_TYPES_8_BREAKING ? (0, _utils.assertNodeType)("LVal", "OptionalMemberExpression") : (0, _utils.assertNodeType)("Identifier", "MemberExpression", "OptionalMemberExpression", "ArrayPattern", "ObjectPattern", "TSAsExpression", "TSSatisfiesExpression", "TSTypeAssertion", "TSNonNullExpression") + }, + right: { + validate: (0, _utils.assertNodeType)("Expression") + } + }, + builder: ["operator", "left", "right"], + visitor: ["left", "right"], + aliases: ["Expression"] +}); +defineType("BinaryExpression", { + builder: ["operator", "left", "right"], + fields: { + operator: { + validate: (0, _utils.assertOneOf)(..._index.BINARY_OPERATORS) + }, + left: { + validate: function () { + const expression = (0, _utils.assertNodeType)("Expression"); + const inOp = (0, _utils.assertNodeType)("Expression", "PrivateName"); + const validator = Object.assign(function (node, key, val) { + const validator = node.operator === "in" ? inOp : expression; + validator(node, key, val); + }, { + oneOfNodeTypes: ["Expression", "PrivateName"] + }); + return validator; + }() + }, + right: { + validate: (0, _utils.assertNodeType)("Expression") + } + }, + visitor: ["left", "right"], + aliases: ["Binary", "Expression"] +}); +defineType("InterpreterDirective", { + builder: ["value"], + fields: { + value: { + validate: (0, _utils.assertValueType)("string") + } + } +}); +defineType("Directive", { + visitor: ["value"], + fields: { + value: { + validate: (0, _utils.assertNodeType)("DirectiveLiteral") + } + } +}); +defineType("DirectiveLiteral", { + builder: ["value"], + fields: { + value: { + validate: (0, _utils.assertValueType)("string") + } + } +}); +defineType("BlockStatement", { + builder: ["body", "directives"], + visitor: ["directives", "body"], + fields: { + directives: { + validate: (0, _utils.arrayOfType)("Directive"), + default: [] + }, + body: (0, _utils.validateArrayOfType)("Statement") + }, + aliases: ["Scopable", "BlockParent", "Block", "Statement"] +}); +defineType("BreakStatement", { + visitor: ["label"], + fields: { + label: { + validate: (0, _utils.assertNodeType)("Identifier"), + optional: true + } + }, + aliases: ["Statement", "Terminatorless", "CompletionStatement"] +}); +defineType("CallExpression", { + visitor: ["callee", "arguments", "typeParameters", "typeArguments"], + builder: ["callee", "arguments"], + aliases: ["Expression"], + fields: Object.assign({ + callee: { + validate: (0, _utils.assertNodeType)("Expression", "Super", "V8IntrinsicIdentifier") + }, + arguments: (0, _utils.validateArrayOfType)("Expression", "SpreadElement", "ArgumentPlaceholder"), + typeArguments: { + validate: (0, _utils.assertNodeType)("TypeParameterInstantiation"), + optional: true + } + }, { + optional: { + validate: (0, _utils.assertValueType)("boolean"), + optional: true + }, + typeParameters: { + validate: (0, _utils.assertNodeType)("TSTypeParameterInstantiation"), + optional: true + } + }, process.env.BABEL_TYPES_8_BREAKING ? {} : { + optional: { + validate: (0, _utils.assertValueType)("boolean"), + optional: true + } + }) +}); +defineType("CatchClause", { + visitor: ["param", "body"], + fields: { + param: { + validate: (0, _utils.assertNodeType)("Identifier", "ArrayPattern", "ObjectPattern"), + optional: true + }, + body: { + validate: (0, _utils.assertNodeType)("BlockStatement") + } + }, + aliases: ["Scopable", "BlockParent"] +}); +defineType("ConditionalExpression", { + visitor: ["test", "consequent", "alternate"], + fields: { + test: { + validate: (0, _utils.assertNodeType)("Expression") + }, + consequent: { + validate: (0, _utils.assertNodeType)("Expression") + }, + alternate: { + validate: (0, _utils.assertNodeType)("Expression") + } + }, + aliases: ["Expression", "Conditional"] +}); +defineType("ContinueStatement", { + visitor: ["label"], + fields: { + label: { + validate: (0, _utils.assertNodeType)("Identifier"), + optional: true + } + }, + aliases: ["Statement", "Terminatorless", "CompletionStatement"] +}); +defineType("DebuggerStatement", { + aliases: ["Statement"] +}); +defineType("DoWhileStatement", { + builder: ["test", "body"], + visitor: ["body", "test"], + fields: { + test: { + validate: (0, _utils.assertNodeType)("Expression") + }, + body: { + validate: (0, _utils.assertNodeType)("Statement") + } + }, + aliases: ["Statement", "BlockParent", "Loop", "While", "Scopable"] +}); +defineType("EmptyStatement", { + aliases: ["Statement"] +}); +defineType("ExpressionStatement", { + visitor: ["expression"], + fields: { + expression: { + validate: (0, _utils.assertNodeType)("Expression") + } + }, + aliases: ["Statement", "ExpressionWrapper"] +}); +defineType("File", { + builder: ["program", "comments", "tokens"], + visitor: ["program"], + fields: { + program: { + validate: (0, _utils.assertNodeType)("Program") + }, + comments: { + validate: !process.env.BABEL_TYPES_8_BREAKING ? Object.assign(() => {}, { + each: { + oneOfNodeTypes: ["CommentBlock", "CommentLine"] + } + }) : (0, _utils.assertEach)((0, _utils.assertNodeType)("CommentBlock", "CommentLine")), + optional: true + }, + tokens: { + validate: (0, _utils.assertEach)(Object.assign(() => {}, { + type: "any" + })), + optional: true + } + } +}); +defineType("ForInStatement", { + visitor: ["left", "right", "body"], + aliases: ["Scopable", "Statement", "For", "BlockParent", "Loop", "ForXStatement"], + fields: { + left: { + validate: !process.env.BABEL_TYPES_8_BREAKING ? (0, _utils.assertNodeType)("VariableDeclaration", "LVal") : (0, _utils.assertNodeType)("VariableDeclaration", "Identifier", "MemberExpression", "ArrayPattern", "ObjectPattern", "TSAsExpression", "TSSatisfiesExpression", "TSTypeAssertion", "TSNonNullExpression") + }, + right: { + validate: (0, _utils.assertNodeType)("Expression") + }, + body: { + validate: (0, _utils.assertNodeType)("Statement") + } + } +}); +defineType("ForStatement", { + visitor: ["init", "test", "update", "body"], + aliases: ["Scopable", "Statement", "For", "BlockParent", "Loop"], + fields: { + init: { + validate: (0, _utils.assertNodeType)("VariableDeclaration", "Expression"), + optional: true + }, + test: { + validate: (0, _utils.assertNodeType)("Expression"), + optional: true + }, + update: { + validate: (0, _utils.assertNodeType)("Expression"), + optional: true + }, + body: { + validate: (0, _utils.assertNodeType)("Statement") + } + } +}); +const functionCommon = () => ({ + params: (0, _utils.validateArrayOfType)("Identifier", "Pattern", "RestElement"), + generator: { + default: false + }, + async: { + default: false + } +}); +exports.functionCommon = functionCommon; +const functionTypeAnnotationCommon = () => ({ + returnType: { + validate: (0, _utils.assertNodeType)("TypeAnnotation", "TSTypeAnnotation", "Noop"), + optional: true + }, + typeParameters: { + validate: (0, _utils.assertNodeType)("TypeParameterDeclaration", "TSTypeParameterDeclaration", "Noop"), + optional: true + } +}); +exports.functionTypeAnnotationCommon = functionTypeAnnotationCommon; +const functionDeclarationCommon = () => Object.assign({}, functionCommon(), { + declare: { + validate: (0, _utils.assertValueType)("boolean"), + optional: true + }, + id: { + validate: (0, _utils.assertNodeType)("Identifier"), + optional: true + } +}); +exports.functionDeclarationCommon = functionDeclarationCommon; +defineType("FunctionDeclaration", { + builder: ["id", "params", "body", "generator", "async"], + visitor: ["id", "typeParameters", "params", "predicate", "returnType", "body"], + fields: Object.assign({}, functionDeclarationCommon(), functionTypeAnnotationCommon(), { + body: { + validate: (0, _utils.assertNodeType)("BlockStatement") + }, + predicate: { + validate: (0, _utils.assertNodeType)("DeclaredPredicate", "InferredPredicate"), + optional: true + } + }), + aliases: ["Scopable", "Function", "BlockParent", "FunctionParent", "Statement", "Pureish", "Declaration"], + validate: !process.env.BABEL_TYPES_8_BREAKING ? undefined : function () { + const identifier = (0, _utils.assertNodeType)("Identifier"); + return function (parent, key, node) { + if (!(0, _is.default)("ExportDefaultDeclaration", parent)) { + identifier(node, "id", node.id); + } + }; + }() +}); +defineType("FunctionExpression", { + inherits: "FunctionDeclaration", + aliases: ["Scopable", "Function", "BlockParent", "FunctionParent", "Expression", "Pureish"], + fields: Object.assign({}, functionCommon(), functionTypeAnnotationCommon(), { + id: { + validate: (0, _utils.assertNodeType)("Identifier"), + optional: true + }, + body: { + validate: (0, _utils.assertNodeType)("BlockStatement") + }, + predicate: { + validate: (0, _utils.assertNodeType)("DeclaredPredicate", "InferredPredicate"), + optional: true + } + }) +}); +const patternLikeCommon = () => ({ + typeAnnotation: { + validate: (0, _utils.assertNodeType)("TypeAnnotation", "TSTypeAnnotation", "Noop"), + optional: true + }, + optional: { + validate: (0, _utils.assertValueType)("boolean"), + optional: true + }, + decorators: { + validate: (0, _utils.arrayOfType)("Decorator"), + optional: true + } +}); +exports.patternLikeCommon = patternLikeCommon; +defineType("Identifier", { + builder: ["name"], + visitor: ["typeAnnotation", "decorators"], + aliases: ["Expression", "PatternLike", "LVal", "TSEntityName"], + fields: Object.assign({}, patternLikeCommon(), { + name: { + validate: process.env.BABEL_TYPES_8_BREAKING ? (0, _utils.chain)((0, _utils.assertValueType)("string"), Object.assign(function (node, key, val) { + if (!(0, _isValidIdentifier.default)(val, false)) { + throw new TypeError(`"${val}" is not a valid identifier name`); + } + }, { + type: "string" + })) : (0, _utils.assertValueType)("string") + } + }), + validate: process.env.BABEL_TYPES_8_BREAKING ? function (parent, key, node) { + const match = /\.(\w+)$/.exec(key.toString()); + if (!match) return; + const [, parentKey] = match; + const nonComp = { + computed: false + }; + if (parentKey === "property") { + if ((0, _is.default)("MemberExpression", parent, nonComp)) return; + if ((0, _is.default)("OptionalMemberExpression", parent, nonComp)) return; + } else if (parentKey === "key") { + if ((0, _is.default)("Property", parent, nonComp)) return; + if ((0, _is.default)("Method", parent, nonComp)) return; + } else if (parentKey === "exported") { + if ((0, _is.default)("ExportSpecifier", parent)) return; + } else if (parentKey === "imported") { + if ((0, _is.default)("ImportSpecifier", parent, { + imported: node + })) return; + } else if (parentKey === "meta") { + if ((0, _is.default)("MetaProperty", parent, { + meta: node + })) return; + } + if (((0, _helperValidatorIdentifier.isKeyword)(node.name) || (0, _helperValidatorIdentifier.isReservedWord)(node.name, false)) && node.name !== "this") { + throw new TypeError(`"${node.name}" is not a valid identifier`); + } + } : undefined +}); +defineType("IfStatement", { + visitor: ["test", "consequent", "alternate"], + aliases: ["Statement", "Conditional"], + fields: { + test: { + validate: (0, _utils.assertNodeType)("Expression") + }, + consequent: { + validate: (0, _utils.assertNodeType)("Statement") + }, + alternate: { + optional: true, + validate: (0, _utils.assertNodeType)("Statement") + } + } +}); +defineType("LabeledStatement", { + visitor: ["label", "body"], + aliases: ["Statement"], + fields: { + label: { + validate: (0, _utils.assertNodeType)("Identifier") + }, + body: { + validate: (0, _utils.assertNodeType)("Statement") + } + } +}); +defineType("StringLiteral", { + builder: ["value"], + fields: { + value: { + validate: (0, _utils.assertValueType)("string") + } + }, + aliases: ["Expression", "Pureish", "Literal", "Immutable"] +}); +defineType("NumericLiteral", { + builder: ["value"], + deprecatedAlias: "NumberLiteral", + fields: { + value: { + validate: (0, _utils.chain)((0, _utils.assertValueType)("number"), Object.assign(function (node, key, val) { + if (1 / val < 0 || !Number.isFinite(val)) { + const error = new Error("NumericLiterals must be non-negative finite numbers. " + `You can use t.valueToNode(${val}) instead.`); + {} + } + }, { + type: "number" + })) + } + }, + aliases: ["Expression", "Pureish", "Literal", "Immutable"] +}); +defineType("NullLiteral", { + aliases: ["Expression", "Pureish", "Literal", "Immutable"] +}); +defineType("BooleanLiteral", { + builder: ["value"], + fields: { + value: { + validate: (0, _utils.assertValueType)("boolean") + } + }, + aliases: ["Expression", "Pureish", "Literal", "Immutable"] +}); +defineType("RegExpLiteral", { + builder: ["pattern", "flags"], + deprecatedAlias: "RegexLiteral", + aliases: ["Expression", "Pureish", "Literal"], + fields: { + pattern: { + validate: (0, _utils.assertValueType)("string") + }, + flags: { + validate: process.env.BABEL_TYPES_8_BREAKING ? (0, _utils.chain)((0, _utils.assertValueType)("string"), Object.assign(function (node, key, val) { + const invalid = /[^gimsuy]/.exec(val); + if (invalid) { + throw new TypeError(`"${invalid[0]}" is not a valid RegExp flag`); + } + }, { + type: "string" + })) : (0, _utils.assertValueType)("string"), + default: "" + } + } +}); +defineType("LogicalExpression", { + builder: ["operator", "left", "right"], + visitor: ["left", "right"], + aliases: ["Binary", "Expression"], + fields: { + operator: { + validate: (0, _utils.assertOneOf)(..._index.LOGICAL_OPERATORS) + }, + left: { + validate: (0, _utils.assertNodeType)("Expression") + }, + right: { + validate: (0, _utils.assertNodeType)("Expression") + } + } +}); +defineType("MemberExpression", { + builder: ["object", "property", "computed", ...(!process.env.BABEL_TYPES_8_BREAKING ? ["optional"] : [])], + visitor: ["object", "property"], + aliases: ["Expression", "LVal"], + fields: Object.assign({ + object: { + validate: (0, _utils.assertNodeType)("Expression", "Super") + }, + property: { + validate: function () { + const normal = (0, _utils.assertNodeType)("Identifier", "PrivateName"); + const computed = (0, _utils.assertNodeType)("Expression"); + const validator = function (node, key, val) { + const validator = node.computed ? computed : normal; + validator(node, key, val); + }; + validator.oneOfNodeTypes = ["Expression", "Identifier", "PrivateName"]; + return validator; + }() + }, + computed: { + default: false + } + }, !process.env.BABEL_TYPES_8_BREAKING ? { + optional: { + validate: (0, _utils.assertValueType)("boolean"), + optional: true + } + } : {}) +}); +defineType("NewExpression", { + inherits: "CallExpression" +}); +defineType("Program", { + visitor: ["directives", "body"], + builder: ["body", "directives", "sourceType", "interpreter"], + fields: { + sourceType: { + validate: (0, _utils.assertOneOf)("script", "module"), + default: "script" + }, + interpreter: { + validate: (0, _utils.assertNodeType)("InterpreterDirective"), + default: null, + optional: true + }, + directives: { + validate: (0, _utils.arrayOfType)("Directive"), + default: [] + }, + body: (0, _utils.validateArrayOfType)("Statement") + }, + aliases: ["Scopable", "BlockParent", "Block"] +}); +defineType("ObjectExpression", { + visitor: ["properties"], + aliases: ["Expression"], + fields: { + properties: (0, _utils.validateArrayOfType)("ObjectMethod", "ObjectProperty", "SpreadElement") + } +}); +defineType("ObjectMethod", { + builder: ["kind", "key", "params", "body", "computed", "generator", "async"], + visitor: ["decorators", "key", "typeParameters", "params", "returnType", "body"], + fields: Object.assign({}, functionCommon(), functionTypeAnnotationCommon(), { + kind: Object.assign({ + validate: (0, _utils.assertOneOf)("method", "get", "set") + }, !process.env.BABEL_TYPES_8_BREAKING ? { + default: "method" + } : {}), + computed: { + default: false + }, + key: { + validate: function () { + const normal = (0, _utils.assertNodeType)("Identifier", "StringLiteral", "NumericLiteral", "BigIntLiteral"); + const computed = (0, _utils.assertNodeType)("Expression"); + const validator = function (node, key, val) { + const validator = node.computed ? computed : normal; + validator(node, key, val); + }; + validator.oneOfNodeTypes = ["Expression", "Identifier", "StringLiteral", "NumericLiteral", "BigIntLiteral"]; + return validator; + }() + }, + decorators: { + validate: (0, _utils.arrayOfType)("Decorator"), + optional: true + }, + body: { + validate: (0, _utils.assertNodeType)("BlockStatement") + } + }), + aliases: ["UserWhitespacable", "Function", "Scopable", "BlockParent", "FunctionParent", "Method", "ObjectMember"] +}); +defineType("ObjectProperty", { + builder: ["key", "value", "computed", "shorthand", ...(!process.env.BABEL_TYPES_8_BREAKING ? ["decorators"] : [])], + fields: { + computed: { + default: false + }, + key: { + validate: function () { + const normal = (0, _utils.assertNodeType)("Identifier", "StringLiteral", "NumericLiteral", "BigIntLiteral", "DecimalLiteral", "PrivateName"); + const computed = (0, _utils.assertNodeType)("Expression"); + const validator = Object.assign(function (node, key, val) { + const validator = node.computed ? computed : normal; + validator(node, key, val); + }, { + oneOfNodeTypes: ["Expression", "Identifier", "StringLiteral", "NumericLiteral", "BigIntLiteral", "DecimalLiteral", "PrivateName"] + }); + return validator; + }() + }, + value: { + validate: (0, _utils.assertNodeType)("Expression", "PatternLike") + }, + shorthand: { + validate: process.env.BABEL_TYPES_8_BREAKING ? (0, _utils.chain)((0, _utils.assertValueType)("boolean"), Object.assign(function (node, key, shorthand) { + if (!shorthand) return; + if (node.computed) { + throw new TypeError("Property shorthand of ObjectProperty cannot be true if computed is true"); + } + if (!(0, _is.default)("Identifier", node.key)) { + throw new TypeError("Property shorthand of ObjectProperty cannot be true if key is not an Identifier"); + } + }, { + type: "boolean" + })) : (0, _utils.assertValueType)("boolean"), + default: false + }, + decorators: { + validate: (0, _utils.arrayOfType)("Decorator"), + optional: true + } + }, + visitor: ["key", "value", "decorators"], + aliases: ["UserWhitespacable", "Property", "ObjectMember"], + validate: !process.env.BABEL_TYPES_8_BREAKING ? undefined : function () { + const pattern = (0, _utils.assertNodeType)("Identifier", "Pattern", "TSAsExpression", "TSSatisfiesExpression", "TSNonNullExpression", "TSTypeAssertion"); + const expression = (0, _utils.assertNodeType)("Expression"); + return function (parent, key, node) { + const validator = (0, _is.default)("ObjectPattern", parent) ? pattern : expression; + validator(node, "value", node.value); + }; + }() +}); +defineType("RestElement", { + visitor: ["argument", "typeAnnotation"], + builder: ["argument"], + aliases: ["LVal", "PatternLike"], + deprecatedAlias: "RestProperty", + fields: Object.assign({}, patternLikeCommon(), { + argument: { + validate: !process.env.BABEL_TYPES_8_BREAKING ? (0, _utils.assertNodeType)("LVal") : (0, _utils.assertNodeType)("Identifier", "ArrayPattern", "ObjectPattern", "MemberExpression", "TSAsExpression", "TSSatisfiesExpression", "TSTypeAssertion", "TSNonNullExpression") + } + }), + validate: process.env.BABEL_TYPES_8_BREAKING ? function (parent, key) { + const match = /(\w+)\[(\d+)\]/.exec(key.toString()); + if (!match) throw new Error("Internal Babel error: malformed key."); + const [, listKey, index] = match; + if (parent[listKey].length > +index + 1) { + throw new TypeError(`RestElement must be last element of ${listKey}`); + } + } : undefined +}); +defineType("ReturnStatement", { + visitor: ["argument"], + aliases: ["Statement", "Terminatorless", "CompletionStatement"], + fields: { + argument: { + validate: (0, _utils.assertNodeType)("Expression"), + optional: true + } + } +}); +defineType("SequenceExpression", { + visitor: ["expressions"], + fields: { + expressions: (0, _utils.validateArrayOfType)("Expression") + }, + aliases: ["Expression"] +}); +defineType("ParenthesizedExpression", { + visitor: ["expression"], + aliases: ["Expression", "ExpressionWrapper"], + fields: { + expression: { + validate: (0, _utils.assertNodeType)("Expression") + } + } +}); +defineType("SwitchCase", { + visitor: ["test", "consequent"], + fields: { + test: { + validate: (0, _utils.assertNodeType)("Expression"), + optional: true + }, + consequent: (0, _utils.validateArrayOfType)("Statement") + } +}); +defineType("SwitchStatement", { + visitor: ["discriminant", "cases"], + aliases: ["Statement", "BlockParent", "Scopable"], + fields: { + discriminant: { + validate: (0, _utils.assertNodeType)("Expression") + }, + cases: (0, _utils.validateArrayOfType)("SwitchCase") + } +}); +defineType("ThisExpression", { + aliases: ["Expression"] +}); +defineType("ThrowStatement", { + visitor: ["argument"], + aliases: ["Statement", "Terminatorless", "CompletionStatement"], + fields: { + argument: { + validate: (0, _utils.assertNodeType)("Expression") + } + } +}); +defineType("TryStatement", { + visitor: ["block", "handler", "finalizer"], + aliases: ["Statement"], + fields: { + block: { + validate: process.env.BABEL_TYPES_8_BREAKING ? (0, _utils.chain)((0, _utils.assertNodeType)("BlockStatement"), Object.assign(function (node) { + if (!node.handler && !node.finalizer) { + throw new TypeError("TryStatement expects either a handler or finalizer, or both"); + } + }, { + oneOfNodeTypes: ["BlockStatement"] + })) : (0, _utils.assertNodeType)("BlockStatement") + }, + handler: { + optional: true, + validate: (0, _utils.assertNodeType)("CatchClause") + }, + finalizer: { + optional: true, + validate: (0, _utils.assertNodeType)("BlockStatement") + } + } +}); +defineType("UnaryExpression", { + builder: ["operator", "argument", "prefix"], + fields: { + prefix: { + default: true + }, + argument: { + validate: (0, _utils.assertNodeType)("Expression") + }, + operator: { + validate: (0, _utils.assertOneOf)(..._index.UNARY_OPERATORS) + } + }, + visitor: ["argument"], + aliases: ["UnaryLike", "Expression"] +}); +defineType("UpdateExpression", { + builder: ["operator", "argument", "prefix"], + fields: { + prefix: { + default: false + }, + argument: { + validate: !process.env.BABEL_TYPES_8_BREAKING ? (0, _utils.assertNodeType)("Expression") : (0, _utils.assertNodeType)("Identifier", "MemberExpression") + }, + operator: { + validate: (0, _utils.assertOneOf)(..._index.UPDATE_OPERATORS) + } + }, + visitor: ["argument"], + aliases: ["Expression"] +}); +defineType("VariableDeclaration", { + builder: ["kind", "declarations"], + visitor: ["declarations"], + aliases: ["Statement", "Declaration"], + fields: { + declare: { + validate: (0, _utils.assertValueType)("boolean"), + optional: true + }, + kind: { + validate: (0, _utils.assertOneOf)("var", "let", "const", "using", "await using") + }, + declarations: (0, _utils.validateArrayOfType)("VariableDeclarator") + }, + validate: process.env.BABEL_TYPES_8_BREAKING ? (() => { + const withoutInit = (0, _utils.assertNodeType)("Identifier", "Placeholder"); + const constOrLetOrVar = (0, _utils.assertNodeType)("Identifier", "ArrayPattern", "ObjectPattern", "Placeholder"); + const usingOrAwaitUsing = withoutInit; + return function (parent, key, node) { + const { + kind, + declarations + } = node; + const parentIsForX = (0, _is.default)("ForXStatement", parent, { + left: node + }); + if (parentIsForX) { + if (declarations.length !== 1) { + throw new TypeError(`Exactly one VariableDeclarator is required in the VariableDeclaration of a ${parent.type}`); + } + } + for (const decl of declarations) { + if (kind === "const" || kind === "let" || kind === "var") { + if (!parentIsForX && !decl.init) { + withoutInit(decl, "id", decl.id); + } else { + constOrLetOrVar(decl, "id", decl.id); + } + } else { + usingOrAwaitUsing(decl, "id", decl.id); + } + } + }; + })() : undefined +}); +defineType("VariableDeclarator", { + visitor: ["id", "init"], + fields: { + id: { + validate: !process.env.BABEL_TYPES_8_BREAKING ? (0, _utils.assertNodeType)("LVal") : (0, _utils.assertNodeType)("Identifier", "ArrayPattern", "ObjectPattern") + }, + definite: { + optional: true, + validate: (0, _utils.assertValueType)("boolean") + }, + init: { + optional: true, + validate: (0, _utils.assertNodeType)("Expression") + } + } +}); +defineType("WhileStatement", { + visitor: ["test", "body"], + aliases: ["Statement", "BlockParent", "Loop", "While", "Scopable"], + fields: { + test: { + validate: (0, _utils.assertNodeType)("Expression") + }, + body: { + validate: (0, _utils.assertNodeType)("Statement") + } + } +}); +defineType("WithStatement", { + visitor: ["object", "body"], + aliases: ["Statement"], + fields: { + object: { + validate: (0, _utils.assertNodeType)("Expression") + }, + body: { + validate: (0, _utils.assertNodeType)("Statement") + } + } +}); +defineType("AssignmentPattern", { + visitor: ["left", "right", "decorators"], + builder: ["left", "right"], + aliases: ["Pattern", "PatternLike", "LVal"], + fields: Object.assign({}, patternLikeCommon(), { + left: { + validate: (0, _utils.assertNodeType)("Identifier", "ObjectPattern", "ArrayPattern", "MemberExpression", "TSAsExpression", "TSSatisfiesExpression", "TSTypeAssertion", "TSNonNullExpression") + }, + right: { + validate: (0, _utils.assertNodeType)("Expression") + }, + decorators: { + validate: (0, _utils.arrayOfType)("Decorator"), + optional: true + } + }) +}); +defineType("ArrayPattern", { + visitor: ["elements", "typeAnnotation"], + builder: ["elements"], + aliases: ["Pattern", "PatternLike", "LVal"], + fields: Object.assign({}, patternLikeCommon(), { + elements: { + validate: (0, _utils.chain)((0, _utils.assertValueType)("array"), (0, _utils.assertEach)((0, _utils.assertNodeOrValueType)("null", "PatternLike", "LVal"))) + } + }) +}); +defineType("ArrowFunctionExpression", { + builder: ["params", "body", "async"], + visitor: ["typeParameters", "params", "predicate", "returnType", "body"], + aliases: ["Scopable", "Function", "BlockParent", "FunctionParent", "Expression", "Pureish"], + fields: Object.assign({}, functionCommon(), functionTypeAnnotationCommon(), { + expression: { + validate: (0, _utils.assertValueType)("boolean") + }, + body: { + validate: (0, _utils.assertNodeType)("BlockStatement", "Expression") + }, + predicate: { + validate: (0, _utils.assertNodeType)("DeclaredPredicate", "InferredPredicate"), + optional: true + } + }) +}); +defineType("ClassBody", { + visitor: ["body"], + fields: { + body: (0, _utils.validateArrayOfType)("ClassMethod", "ClassPrivateMethod", "ClassProperty", "ClassPrivateProperty", "ClassAccessorProperty", "TSDeclareMethod", "TSIndexSignature", "StaticBlock") + } +}); +defineType("ClassExpression", { + builder: ["id", "superClass", "body", "decorators"], + visitor: ["decorators", "id", "typeParameters", "superClass", "superTypeParameters", "mixins", "implements", "body"], + aliases: ["Scopable", "Class", "Expression"], + fields: { + id: { + validate: (0, _utils.assertNodeType)("Identifier"), + optional: true + }, + typeParameters: { + validate: (0, _utils.assertNodeType)("TypeParameterDeclaration", "TSTypeParameterDeclaration", "Noop"), + optional: true + }, + body: { + validate: (0, _utils.assertNodeType)("ClassBody") + }, + superClass: { + optional: true, + validate: (0, _utils.assertNodeType)("Expression") + }, + ["superTypeParameters"]: { + validate: (0, _utils.assertNodeType)("TypeParameterInstantiation", "TSTypeParameterInstantiation"), + optional: true + }, + implements: { + validate: (0, _utils.arrayOfType)("TSExpressionWithTypeArguments", "ClassImplements"), + optional: true + }, + decorators: { + validate: (0, _utils.arrayOfType)("Decorator"), + optional: true + }, + mixins: { + validate: (0, _utils.assertNodeType)("InterfaceExtends"), + optional: true + } + } +}); +defineType("ClassDeclaration", { + inherits: "ClassExpression", + aliases: ["Scopable", "Class", "Statement", "Declaration"], + fields: { + id: { + validate: (0, _utils.assertNodeType)("Identifier"), + optional: true + }, + typeParameters: { + validate: (0, _utils.assertNodeType)("TypeParameterDeclaration", "TSTypeParameterDeclaration", "Noop"), + optional: true + }, + body: { + validate: (0, _utils.assertNodeType)("ClassBody") + }, + superClass: { + optional: true, + validate: (0, _utils.assertNodeType)("Expression") + }, + ["superTypeParameters"]: { + validate: (0, _utils.assertNodeType)("TypeParameterInstantiation", "TSTypeParameterInstantiation"), + optional: true + }, + implements: { + validate: (0, _utils.arrayOfType)("TSExpressionWithTypeArguments", "ClassImplements"), + optional: true + }, + decorators: { + validate: (0, _utils.arrayOfType)("Decorator"), + optional: true + }, + mixins: { + validate: (0, _utils.assertNodeType)("InterfaceExtends"), + optional: true + }, + declare: { + validate: (0, _utils.assertValueType)("boolean"), + optional: true + }, + abstract: { + validate: (0, _utils.assertValueType)("boolean"), + optional: true + } + }, + validate: !process.env.BABEL_TYPES_8_BREAKING ? undefined : function () { + const identifier = (0, _utils.assertNodeType)("Identifier"); + return function (parent, key, node) { + if (!(0, _is.default)("ExportDefaultDeclaration", parent)) { + identifier(node, "id", node.id); + } + }; + }() +}); +const importAttributes = exports.importAttributes = { + attributes: { + optional: true, + validate: (0, _utils.arrayOfType)("ImportAttribute") + }, + assertions: { + deprecated: true, + optional: true, + validate: (0, _utils.arrayOfType)("ImportAttribute") + } +}; +defineType("ExportAllDeclaration", { + builder: ["source"], + visitor: ["source", "attributes", "assertions"], + aliases: ["Statement", "Declaration", "ImportOrExportDeclaration", "ExportDeclaration"], + fields: Object.assign({ + source: { + validate: (0, _utils.assertNodeType)("StringLiteral") + }, + exportKind: (0, _utils.validateOptional)((0, _utils.assertOneOf)("type", "value")) + }, importAttributes) +}); +defineType("ExportDefaultDeclaration", { + visitor: ["declaration"], + aliases: ["Statement", "Declaration", "ImportOrExportDeclaration", "ExportDeclaration"], + fields: { + declaration: (0, _utils.validateType)("TSDeclareFunction", "FunctionDeclaration", "ClassDeclaration", "Expression"), + exportKind: (0, _utils.validateOptional)((0, _utils.assertOneOf)("value")) + } +}); +defineType("ExportNamedDeclaration", { + builder: ["declaration", "specifiers", "source"], + visitor: process.env ? ["declaration", "specifiers", "source", "attributes"] : ["declaration", "specifiers", "source", "attributes", "assertions"], + aliases: ["Statement", "Declaration", "ImportOrExportDeclaration", "ExportDeclaration"], + fields: Object.assign({ + declaration: { + optional: true, + validate: process.env.BABEL_TYPES_8_BREAKING ? (0, _utils.chain)((0, _utils.assertNodeType)("Declaration"), Object.assign(function (node, key, val) { + if (val && node.specifiers.length) { + throw new TypeError("Only declaration or specifiers is allowed on ExportNamedDeclaration"); + } + if (val && node.source) { + throw new TypeError("Cannot export a declaration from a source"); + } + }, { + oneOfNodeTypes: ["Declaration"] + })) : (0, _utils.assertNodeType)("Declaration") + } + }, importAttributes, { + specifiers: { + default: [], + validate: (0, _utils.arrayOf)(function () { + const sourced = (0, _utils.assertNodeType)("ExportSpecifier", "ExportDefaultSpecifier", "ExportNamespaceSpecifier"); + const sourceless = (0, _utils.assertNodeType)("ExportSpecifier"); + if (!process.env.BABEL_TYPES_8_BREAKING) return sourced; + return Object.assign(function (node, key, val) { + const validator = node.source ? sourced : sourceless; + validator(node, key, val); + }, { + oneOfNodeTypes: ["ExportSpecifier", "ExportDefaultSpecifier", "ExportNamespaceSpecifier"] + }); + }()) + }, + source: { + validate: (0, _utils.assertNodeType)("StringLiteral"), + optional: true + }, + exportKind: (0, _utils.validateOptional)((0, _utils.assertOneOf)("type", "value")) + }) +}); +defineType("ExportSpecifier", { + visitor: ["local", "exported"], + aliases: ["ModuleSpecifier"], + fields: { + local: { + validate: (0, _utils.assertNodeType)("Identifier") + }, + exported: { + validate: (0, _utils.assertNodeType)("Identifier", "StringLiteral") + }, + exportKind: { + validate: (0, _utils.assertOneOf)("type", "value"), + optional: true + } + } +}); +defineType("ForOfStatement", { + visitor: ["left", "right", "body"], + builder: ["left", "right", "body", "await"], + aliases: ["Scopable", "Statement", "For", "BlockParent", "Loop", "ForXStatement"], + fields: { + left: { + validate: function () { + if (!process.env.BABEL_TYPES_8_BREAKING) { + return (0, _utils.assertNodeType)("VariableDeclaration", "LVal"); + } + const declaration = (0, _utils.assertNodeType)("VariableDeclaration"); + const lval = (0, _utils.assertNodeType)("Identifier", "MemberExpression", "ArrayPattern", "ObjectPattern", "TSAsExpression", "TSSatisfiesExpression", "TSTypeAssertion", "TSNonNullExpression"); + return Object.assign(function (node, key, val) { + if ((0, _is.default)("VariableDeclaration", val)) { + declaration(node, key, val); + } else { + lval(node, key, val); + } + }, { + oneOfNodeTypes: ["VariableDeclaration", "Identifier", "MemberExpression", "ArrayPattern", "ObjectPattern", "TSAsExpression", "TSSatisfiesExpression", "TSTypeAssertion", "TSNonNullExpression"] + }); + }() + }, + right: { + validate: (0, _utils.assertNodeType)("Expression") + }, + body: { + validate: (0, _utils.assertNodeType)("Statement") + }, + await: { + default: false + } + } +}); +defineType("ImportDeclaration", { + builder: ["specifiers", "source"], + visitor: ["specifiers", "source", "attributes", "assertions"], + aliases: ["Statement", "Declaration", "ImportOrExportDeclaration"], + fields: Object.assign({}, importAttributes, { + module: { + optional: true, + validate: (0, _utils.assertValueType)("boolean") + }, + phase: { + default: null, + validate: (0, _utils.assertOneOf)("source", "defer") + }, + specifiers: (0, _utils.validateArrayOfType)("ImportSpecifier", "ImportDefaultSpecifier", "ImportNamespaceSpecifier"), + source: { + validate: (0, _utils.assertNodeType)("StringLiteral") + }, + importKind: { + validate: (0, _utils.assertOneOf)("type", "typeof", "value"), + optional: true + } + }) +}); +defineType("ImportDefaultSpecifier", { + visitor: ["local"], + aliases: ["ModuleSpecifier"], + fields: { + local: { + validate: (0, _utils.assertNodeType)("Identifier") + } + } +}); +defineType("ImportNamespaceSpecifier", { + visitor: ["local"], + aliases: ["ModuleSpecifier"], + fields: { + local: { + validate: (0, _utils.assertNodeType)("Identifier") + } + } +}); +defineType("ImportSpecifier", { + visitor: ["imported", "local"], + builder: ["local", "imported"], + aliases: ["ModuleSpecifier"], + fields: { + local: { + validate: (0, _utils.assertNodeType)("Identifier") + }, + imported: { + validate: (0, _utils.assertNodeType)("Identifier", "StringLiteral") + }, + importKind: { + validate: (0, _utils.assertOneOf)("type", "typeof", "value"), + optional: true + } + } +}); +defineType("ImportExpression", { + visitor: ["source", "options"], + aliases: ["Expression"], + fields: { + phase: { + default: null, + validate: (0, _utils.assertOneOf)("source", "defer") + }, + source: { + validate: (0, _utils.assertNodeType)("Expression") + }, + options: { + validate: (0, _utils.assertNodeType)("Expression"), + optional: true + } + } +}); +defineType("MetaProperty", { + visitor: ["meta", "property"], + aliases: ["Expression"], + fields: { + meta: { + validate: process.env.BABEL_TYPES_8_BREAKING ? (0, _utils.chain)((0, _utils.assertNodeType)("Identifier"), Object.assign(function (node, key, val) { + let property; + switch (val.name) { + case "function": + property = "sent"; + break; + case "new": + property = "target"; + break; + case "import": + property = "meta"; + break; + } + if (!(0, _is.default)("Identifier", node.property, { + name: property + })) { + throw new TypeError("Unrecognised MetaProperty"); + } + }, { + oneOfNodeTypes: ["Identifier"] + })) : (0, _utils.assertNodeType)("Identifier") + }, + property: { + validate: (0, _utils.assertNodeType)("Identifier") + } + } +}); +const classMethodOrPropertyCommon = () => ({ + abstract: { + validate: (0, _utils.assertValueType)("boolean"), + optional: true + }, + accessibility: { + validate: (0, _utils.assertOneOf)("public", "private", "protected"), + optional: true + }, + static: { + default: false + }, + override: { + default: false + }, + computed: { + default: false + }, + optional: { + validate: (0, _utils.assertValueType)("boolean"), + optional: true + }, + key: { + validate: (0, _utils.chain)(function () { + const normal = (0, _utils.assertNodeType)("Identifier", "StringLiteral", "NumericLiteral", "BigIntLiteral"); + const computed = (0, _utils.assertNodeType)("Expression"); + return function (node, key, val) { + const validator = node.computed ? computed : normal; + validator(node, key, val); + }; + }(), (0, _utils.assertNodeType)("Identifier", "StringLiteral", "NumericLiteral", "BigIntLiteral", "Expression")) + } +}); +exports.classMethodOrPropertyCommon = classMethodOrPropertyCommon; +const classMethodOrDeclareMethodCommon = () => Object.assign({}, functionCommon(), classMethodOrPropertyCommon(), { + params: (0, _utils.validateArrayOfType)("Identifier", "Pattern", "RestElement", "TSParameterProperty"), + kind: { + validate: (0, _utils.assertOneOf)("get", "set", "method", "constructor"), + default: "method" + }, + access: { + validate: (0, _utils.chain)((0, _utils.assertValueType)("string"), (0, _utils.assertOneOf)("public", "private", "protected")), + optional: true + }, + decorators: { + validate: (0, _utils.arrayOfType)("Decorator"), + optional: true + } +}); +exports.classMethodOrDeclareMethodCommon = classMethodOrDeclareMethodCommon; +defineType("ClassMethod", { + aliases: ["Function", "Scopable", "BlockParent", "FunctionParent", "Method"], + builder: ["kind", "key", "params", "body", "computed", "static", "generator", "async"], + visitor: ["decorators", "key", "typeParameters", "params", "returnType", "body"], + fields: Object.assign({}, classMethodOrDeclareMethodCommon(), functionTypeAnnotationCommon(), { + body: { + validate: (0, _utils.assertNodeType)("BlockStatement") + } + }) +}); +defineType("ObjectPattern", { + visitor: ["properties", "typeAnnotation", "decorators"], + builder: ["properties"], + aliases: ["Pattern", "PatternLike", "LVal"], + fields: Object.assign({}, patternLikeCommon(), { + properties: (0, _utils.validateArrayOfType)("RestElement", "ObjectProperty") + }) +}); +defineType("SpreadElement", { + visitor: ["argument"], + aliases: ["UnaryLike"], + deprecatedAlias: "SpreadProperty", + fields: { + argument: { + validate: (0, _utils.assertNodeType)("Expression") + } + } +}); +defineType("Super", { + aliases: ["Expression"] +}); +defineType("TaggedTemplateExpression", { + visitor: ["tag", "typeParameters", "quasi"], + builder: ["tag", "quasi"], + aliases: ["Expression"], + fields: { + tag: { + validate: (0, _utils.assertNodeType)("Expression") + }, + quasi: { + validate: (0, _utils.assertNodeType)("TemplateLiteral") + }, + ["typeParameters"]: { + validate: (0, _utils.assertNodeType)("TypeParameterInstantiation", "TSTypeParameterInstantiation"), + optional: true + } + } +}); +defineType("TemplateElement", { + builder: ["value", "tail"], + fields: { + value: { + validate: (0, _utils.chain)((0, _utils.assertShape)({ + raw: { + validate: (0, _utils.assertValueType)("string") + }, + cooked: { + validate: (0, _utils.assertValueType)("string"), + optional: true + } + }), function templateElementCookedValidator(node) { + const raw = node.value.raw; + let unterminatedCalled = false; + const error = () => { + throw new Error("Internal @babel/types error."); + }; + const { + str, + firstInvalidLoc + } = (0, _helperStringParser.readStringContents)("template", raw, 0, 0, 0, { + unterminated() { + unterminatedCalled = true; + }, + strictNumericEscape: error, + invalidEscapeSequence: error, + numericSeparatorInEscapeSequence: error, + unexpectedNumericSeparator: error, + invalidDigit: error, + invalidCodePoint: error + }); + if (!unterminatedCalled) throw new Error("Invalid raw"); + node.value.cooked = firstInvalidLoc ? null : str; + }) + }, + tail: { + default: false + } + } +}); +defineType("TemplateLiteral", { + visitor: ["quasis", "expressions"], + aliases: ["Expression", "Literal"], + fields: { + quasis: (0, _utils.validateArrayOfType)("TemplateElement"), + expressions: { + validate: (0, _utils.chain)((0, _utils.assertValueType)("array"), (0, _utils.assertEach)((0, _utils.assertNodeType)("Expression", "TSType")), function (node, key, val) { + if (node.quasis.length !== val.length + 1) { + throw new TypeError(`Number of ${node.type} quasis should be exactly one more than the number of expressions.\nExpected ${val.length + 1} quasis but got ${node.quasis.length}`); + } + }) + } + } +}); +defineType("YieldExpression", { + builder: ["argument", "delegate"], + visitor: ["argument"], + aliases: ["Expression", "Terminatorless"], + fields: { + delegate: { + validate: process.env.BABEL_TYPES_8_BREAKING ? (0, _utils.chain)((0, _utils.assertValueType)("boolean"), Object.assign(function (node, key, val) { + if (val && !node.argument) { + throw new TypeError("Property delegate of YieldExpression cannot be true if there is no argument"); + } + }, { + type: "boolean" + })) : (0, _utils.assertValueType)("boolean"), + default: false + }, + argument: { + optional: true, + validate: (0, _utils.assertNodeType)("Expression") + } + } +}); +defineType("AwaitExpression", { + builder: ["argument"], + visitor: ["argument"], + aliases: ["Expression", "Terminatorless"], + fields: { + argument: { + validate: (0, _utils.assertNodeType)("Expression") + } + } +}); +defineType("Import", { + aliases: ["Expression"] +}); +defineType("BigIntLiteral", { + builder: ["value"], + fields: { + value: { + validate: (0, _utils.assertValueType)("string") + } + }, + aliases: ["Expression", "Pureish", "Literal", "Immutable"] +}); +defineType("ExportNamespaceSpecifier", { + visitor: ["exported"], + aliases: ["ModuleSpecifier"], + fields: { + exported: { + validate: (0, _utils.assertNodeType)("Identifier") + } + } +}); +defineType("OptionalMemberExpression", { + builder: ["object", "property", "computed", "optional"], + visitor: ["object", "property"], + aliases: ["Expression"], + fields: { + object: { + validate: (0, _utils.assertNodeType)("Expression") + }, + property: { + validate: function () { + const normal = (0, _utils.assertNodeType)("Identifier"); + const computed = (0, _utils.assertNodeType)("Expression"); + const validator = Object.assign(function (node, key, val) { + const validator = node.computed ? computed : normal; + validator(node, key, val); + }, { + oneOfNodeTypes: ["Expression", "Identifier"] + }); + return validator; + }() + }, + computed: { + default: false + }, + optional: { + validate: !process.env.BABEL_TYPES_8_BREAKING ? (0, _utils.assertValueType)("boolean") : (0, _utils.chain)((0, _utils.assertValueType)("boolean"), (0, _utils.assertOptionalChainStart)()) + } + } +}); +defineType("OptionalCallExpression", { + visitor: ["callee", "arguments", "typeParameters", "typeArguments"], + builder: ["callee", "arguments", "optional"], + aliases: ["Expression"], + fields: Object.assign({ + callee: { + validate: (0, _utils.assertNodeType)("Expression") + }, + arguments: (0, _utils.validateArrayOfType)("Expression", "SpreadElement", "ArgumentPlaceholder"), + optional: { + validate: !process.env.BABEL_TYPES_8_BREAKING ? (0, _utils.assertValueType)("boolean") : (0, _utils.chain)((0, _utils.assertValueType)("boolean"), (0, _utils.assertOptionalChainStart)()) + }, + typeArguments: { + validate: (0, _utils.assertNodeType)("TypeParameterInstantiation"), + optional: true + } + }, { + typeParameters: { + validate: (0, _utils.assertNodeType)("TSTypeParameterInstantiation"), + optional: true + } + }) +}); +defineType("ClassProperty", { + visitor: ["decorators", "variance", "key", "typeAnnotation", "value"], + builder: ["key", "value", "typeAnnotation", "decorators", "computed", "static"], + aliases: ["Property"], + fields: Object.assign({}, classMethodOrPropertyCommon(), { + value: { + validate: (0, _utils.assertNodeType)("Expression"), + optional: true + }, + definite: { + validate: (0, _utils.assertValueType)("boolean"), + optional: true + }, + typeAnnotation: { + validate: (0, _utils.assertNodeType)("TypeAnnotation", "TSTypeAnnotation", "Noop"), + optional: true + }, + decorators: { + validate: (0, _utils.arrayOfType)("Decorator"), + optional: true + }, + readonly: { + validate: (0, _utils.assertValueType)("boolean"), + optional: true + }, + declare: { + validate: (0, _utils.assertValueType)("boolean"), + optional: true + }, + variance: { + validate: (0, _utils.assertNodeType)("Variance"), + optional: true + } + }) +}); +defineType("ClassAccessorProperty", { + visitor: ["decorators", "key", "typeAnnotation", "value"], + builder: ["key", "value", "typeAnnotation", "decorators", "computed", "static"], + aliases: ["Property", "Accessor"], + fields: Object.assign({}, classMethodOrPropertyCommon(), { + key: { + validate: (0, _utils.chain)(function () { + const normal = (0, _utils.assertNodeType)("Identifier", "StringLiteral", "NumericLiteral", "BigIntLiteral", "PrivateName"); + const computed = (0, _utils.assertNodeType)("Expression"); + return function (node, key, val) { + const validator = node.computed ? computed : normal; + validator(node, key, val); + }; + }(), (0, _utils.assertNodeType)("Identifier", "StringLiteral", "NumericLiteral", "BigIntLiteral", "Expression", "PrivateName")) + }, + value: { + validate: (0, _utils.assertNodeType)("Expression"), + optional: true + }, + definite: { + validate: (0, _utils.assertValueType)("boolean"), + optional: true + }, + typeAnnotation: { + validate: (0, _utils.assertNodeType)("TypeAnnotation", "TSTypeAnnotation", "Noop"), + optional: true + }, + decorators: { + validate: (0, _utils.arrayOfType)("Decorator"), + optional: true + }, + readonly: { + validate: (0, _utils.assertValueType)("boolean"), + optional: true + }, + declare: { + validate: (0, _utils.assertValueType)("boolean"), + optional: true + }, + variance: { + validate: (0, _utils.assertNodeType)("Variance"), + optional: true + } + }) +}); +defineType("ClassPrivateProperty", { + visitor: ["decorators", "variance", "key", "typeAnnotation", "value"], + builder: ["key", "value", "decorators", "static"], + aliases: ["Property", "Private"], + fields: { + key: { + validate: (0, _utils.assertNodeType)("PrivateName") + }, + value: { + validate: (0, _utils.assertNodeType)("Expression"), + optional: true + }, + typeAnnotation: { + validate: (0, _utils.assertNodeType)("TypeAnnotation", "TSTypeAnnotation", "Noop"), + optional: true + }, + decorators: { + validate: (0, _utils.arrayOfType)("Decorator"), + optional: true + }, + static: { + validate: (0, _utils.assertValueType)("boolean"), + default: false + }, + readonly: { + validate: (0, _utils.assertValueType)("boolean"), + optional: true + }, + optional: { + validate: (0, _utils.assertValueType)("boolean"), + optional: true + }, + definite: { + validate: (0, _utils.assertValueType)("boolean"), + optional: true + }, + variance: { + validate: (0, _utils.assertNodeType)("Variance"), + optional: true + } + } +}); +defineType("ClassPrivateMethod", { + builder: ["kind", "key", "params", "body", "static"], + visitor: ["decorators", "key", "typeParameters", "params", "returnType", "body"], + aliases: ["Function", "Scopable", "BlockParent", "FunctionParent", "Method", "Private"], + fields: Object.assign({}, classMethodOrDeclareMethodCommon(), functionTypeAnnotationCommon(), { + kind: { + validate: (0, _utils.assertOneOf)("get", "set", "method"), + default: "method" + }, + key: { + validate: (0, _utils.assertNodeType)("PrivateName") + }, + body: { + validate: (0, _utils.assertNodeType)("BlockStatement") + } + }) +}); +defineType("PrivateName", { + visitor: ["id"], + aliases: ["Private"], + fields: { + id: { + validate: (0, _utils.assertNodeType)("Identifier") + } + } +}); +defineType("StaticBlock", { + visitor: ["body"], + fields: { + body: (0, _utils.validateArrayOfType)("Statement") + }, + aliases: ["Scopable", "BlockParent", "FunctionParent"] +}); +defineType("ImportAttribute", { + visitor: ["key", "value"], + fields: { + key: { + validate: (0, _utils.assertNodeType)("Identifier", "StringLiteral") + }, + value: { + validate: (0, _utils.assertNodeType)("StringLiteral") + } + } +}); + +//# sourceMappingURL=core.js.map + + +/***/ }), + +/***/ 44856: +/***/ ((__unused_webpack_module, exports) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports.DEPRECATED_ALIASES = void 0; +const DEPRECATED_ALIASES = exports.DEPRECATED_ALIASES = { + ModuleDeclaration: "ImportOrExportDeclaration" +}; + +//# sourceMappingURL=deprecated-aliases.js.map + + +/***/ }), + +/***/ 78794: +/***/ ((__unused_webpack_module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +var _utils = __nccwpck_require__(34559); +(0, _utils.default)("ArgumentPlaceholder", {}); +(0, _utils.default)("BindExpression", { + visitor: ["object", "callee"], + aliases: ["Expression"], + fields: !process.env.BABEL_TYPES_8_BREAKING ? { + object: { + validate: Object.assign(() => {}, { + oneOfNodeTypes: ["Expression"] + }) + }, + callee: { + validate: Object.assign(() => {}, { + oneOfNodeTypes: ["Expression"] + }) + } + } : { + object: { + validate: (0, _utils.assertNodeType)("Expression") + }, + callee: { + validate: (0, _utils.assertNodeType)("Expression") + } + } +}); +(0, _utils.default)("Decorator", { + visitor: ["expression"], + fields: { + expression: { + validate: (0, _utils.assertNodeType)("Expression") + } + } +}); +(0, _utils.default)("DoExpression", { + visitor: ["body"], + builder: ["body", "async"], + aliases: ["Expression"], + fields: { + body: { + validate: (0, _utils.assertNodeType)("BlockStatement") + }, + async: { + validate: (0, _utils.assertValueType)("boolean"), + default: false + } + } +}); +(0, _utils.default)("ExportDefaultSpecifier", { + visitor: ["exported"], + aliases: ["ModuleSpecifier"], + fields: { + exported: { + validate: (0, _utils.assertNodeType)("Identifier") + } + } +}); +(0, _utils.default)("RecordExpression", { + visitor: ["properties"], + aliases: ["Expression"], + fields: { + properties: (0, _utils.validateArrayOfType)("ObjectProperty", "SpreadElement") + } +}); +(0, _utils.default)("TupleExpression", { + fields: { + elements: { + validate: (0, _utils.arrayOfType)("Expression", "SpreadElement"), + default: [] + } + }, + visitor: ["elements"], + aliases: ["Expression"] +}); +{ + (0, _utils.default)("DecimalLiteral", { + builder: ["value"], + fields: { + value: { + validate: (0, _utils.assertValueType)("string") + } + }, + aliases: ["Expression", "Pureish", "Literal", "Immutable"] + }); +} +(0, _utils.default)("ModuleExpression", { + visitor: ["body"], + fields: { + body: { + validate: (0, _utils.assertNodeType)("Program") + } + }, + aliases: ["Expression"] +}); +(0, _utils.default)("TopicReference", { + aliases: ["Expression"] +}); +(0, _utils.default)("PipelineTopicExpression", { + builder: ["expression"], + visitor: ["expression"], + fields: { + expression: { + validate: (0, _utils.assertNodeType)("Expression") + } + }, + aliases: ["Expression"] +}); +(0, _utils.default)("PipelineBareFunction", { + builder: ["callee"], + visitor: ["callee"], + fields: { + callee: { + validate: (0, _utils.assertNodeType)("Expression") + } + }, + aliases: ["Expression"] +}); +(0, _utils.default)("PipelinePrimaryTopicReference", { + aliases: ["Expression"] +}); + +//# sourceMappingURL=experimental.js.map + + +/***/ }), + +/***/ 67256: +/***/ ((__unused_webpack_module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +var _core = __nccwpck_require__(62803); +var _utils = __nccwpck_require__(34559); +const defineType = (0, _utils.defineAliasedType)("Flow"); +const defineInterfaceishType = name => { + const isDeclareClass = name === "DeclareClass"; + defineType(name, { + builder: ["id", "typeParameters", "extends", "body"], + visitor: ["id", "typeParameters", "extends", ...(isDeclareClass ? ["mixins", "implements"] : []), "body"], + aliases: ["FlowDeclaration", "Statement", "Declaration"], + fields: Object.assign({ + id: (0, _utils.validateType)("Identifier"), + typeParameters: (0, _utils.validateOptionalType)("TypeParameterDeclaration"), + extends: (0, _utils.validateOptional)((0, _utils.arrayOfType)("InterfaceExtends")) + }, isDeclareClass ? { + mixins: (0, _utils.validateOptional)((0, _utils.arrayOfType)("InterfaceExtends")), + implements: (0, _utils.validateOptional)((0, _utils.arrayOfType)("ClassImplements")) + } : {}, { + body: (0, _utils.validateType)("ObjectTypeAnnotation") + }) + }); +}; +defineType("AnyTypeAnnotation", { + aliases: ["FlowType", "FlowBaseAnnotation"] +}); +defineType("ArrayTypeAnnotation", { + visitor: ["elementType"], + aliases: ["FlowType"], + fields: { + elementType: (0, _utils.validateType)("FlowType") + } +}); +defineType("BooleanTypeAnnotation", { + aliases: ["FlowType", "FlowBaseAnnotation"] +}); +defineType("BooleanLiteralTypeAnnotation", { + builder: ["value"], + aliases: ["FlowType"], + fields: { + value: (0, _utils.validate)((0, _utils.assertValueType)("boolean")) + } +}); +defineType("NullLiteralTypeAnnotation", { + aliases: ["FlowType", "FlowBaseAnnotation"] +}); +defineType("ClassImplements", { + visitor: ["id", "typeParameters"], + fields: { + id: (0, _utils.validateType)("Identifier"), + typeParameters: (0, _utils.validateOptionalType)("TypeParameterInstantiation") + } +}); +defineInterfaceishType("DeclareClass"); +defineType("DeclareFunction", { + builder: ["id"], + visitor: ["id", "predicate"], + aliases: ["FlowDeclaration", "Statement", "Declaration"], + fields: { + id: (0, _utils.validateType)("Identifier"), + predicate: (0, _utils.validateOptionalType)("DeclaredPredicate") + } +}); +defineInterfaceishType("DeclareInterface"); +defineType("DeclareModule", { + builder: ["id", "body", "kind"], + visitor: ["id", "body"], + aliases: ["FlowDeclaration", "Statement", "Declaration"], + fields: { + id: (0, _utils.validateType)("Identifier", "StringLiteral"), + body: (0, _utils.validateType)("BlockStatement"), + kind: (0, _utils.validateOptional)((0, _utils.assertOneOf)("CommonJS", "ES")) + } +}); +defineType("DeclareModuleExports", { + visitor: ["typeAnnotation"], + aliases: ["FlowDeclaration", "Statement", "Declaration"], + fields: { + typeAnnotation: (0, _utils.validateType)("TypeAnnotation") + } +}); +defineType("DeclareTypeAlias", { + visitor: ["id", "typeParameters", "right"], + aliases: ["FlowDeclaration", "Statement", "Declaration"], + fields: { + id: (0, _utils.validateType)("Identifier"), + typeParameters: (0, _utils.validateOptionalType)("TypeParameterDeclaration"), + right: (0, _utils.validateType)("FlowType") + } +}); +defineType("DeclareOpaqueType", { + visitor: ["id", "typeParameters", "supertype"], + aliases: ["FlowDeclaration", "Statement", "Declaration"], + fields: { + id: (0, _utils.validateType)("Identifier"), + typeParameters: (0, _utils.validateOptionalType)("TypeParameterDeclaration"), + supertype: (0, _utils.validateOptionalType)("FlowType"), + impltype: (0, _utils.validateOptionalType)("FlowType") + } +}); +defineType("DeclareVariable", { + visitor: ["id"], + aliases: ["FlowDeclaration", "Statement", "Declaration"], + fields: { + id: (0, _utils.validateType)("Identifier") + } +}); +defineType("DeclareExportDeclaration", { + visitor: ["declaration", "specifiers", "source", "attributes"], + aliases: ["FlowDeclaration", "Statement", "Declaration"], + fields: Object.assign({ + declaration: (0, _utils.validateOptionalType)("Flow"), + specifiers: (0, _utils.validateOptional)((0, _utils.arrayOfType)("ExportSpecifier", "ExportNamespaceSpecifier")), + source: (0, _utils.validateOptionalType)("StringLiteral"), + default: (0, _utils.validateOptional)((0, _utils.assertValueType)("boolean")) + }, _core.importAttributes) +}); +defineType("DeclareExportAllDeclaration", { + visitor: ["source", "attributes"], + aliases: ["FlowDeclaration", "Statement", "Declaration"], + fields: Object.assign({ + source: (0, _utils.validateType)("StringLiteral"), + exportKind: (0, _utils.validateOptional)((0, _utils.assertOneOf)("type", "value")) + }, _core.importAttributes) +}); +defineType("DeclaredPredicate", { + visitor: ["value"], + aliases: ["FlowPredicate"], + fields: { + value: (0, _utils.validateType)("Flow") + } +}); +defineType("ExistsTypeAnnotation", { + aliases: ["FlowType"] +}); +defineType("FunctionTypeAnnotation", { + builder: ["typeParameters", "params", "rest", "returnType"], + visitor: ["typeParameters", "this", "params", "rest", "returnType"], + aliases: ["FlowType"], + fields: { + typeParameters: (0, _utils.validateOptionalType)("TypeParameterDeclaration"), + params: (0, _utils.validateArrayOfType)("FunctionTypeParam"), + rest: (0, _utils.validateOptionalType)("FunctionTypeParam"), + this: (0, _utils.validateOptionalType)("FunctionTypeParam"), + returnType: (0, _utils.validateType)("FlowType") + } +}); +defineType("FunctionTypeParam", { + visitor: ["name", "typeAnnotation"], + fields: { + name: (0, _utils.validateOptionalType)("Identifier"), + typeAnnotation: (0, _utils.validateType)("FlowType"), + optional: (0, _utils.validateOptional)((0, _utils.assertValueType)("boolean")) + } +}); +defineType("GenericTypeAnnotation", { + visitor: ["id", "typeParameters"], + aliases: ["FlowType"], + fields: { + id: (0, _utils.validateType)("Identifier", "QualifiedTypeIdentifier"), + typeParameters: (0, _utils.validateOptionalType)("TypeParameterInstantiation") + } +}); +defineType("InferredPredicate", { + aliases: ["FlowPredicate"] +}); +defineType("InterfaceExtends", { + visitor: ["id", "typeParameters"], + fields: { + id: (0, _utils.validateType)("Identifier", "QualifiedTypeIdentifier"), + typeParameters: (0, _utils.validateOptionalType)("TypeParameterInstantiation") + } +}); +defineInterfaceishType("InterfaceDeclaration"); +defineType("InterfaceTypeAnnotation", { + visitor: ["extends", "body"], + aliases: ["FlowType"], + fields: { + extends: (0, _utils.validateOptional)((0, _utils.arrayOfType)("InterfaceExtends")), + body: (0, _utils.validateType)("ObjectTypeAnnotation") + } +}); +defineType("IntersectionTypeAnnotation", { + visitor: ["types"], + aliases: ["FlowType"], + fields: { + types: (0, _utils.validate)((0, _utils.arrayOfType)("FlowType")) + } +}); +defineType("MixedTypeAnnotation", { + aliases: ["FlowType", "FlowBaseAnnotation"] +}); +defineType("EmptyTypeAnnotation", { + aliases: ["FlowType", "FlowBaseAnnotation"] +}); +defineType("NullableTypeAnnotation", { + visitor: ["typeAnnotation"], + aliases: ["FlowType"], + fields: { + typeAnnotation: (0, _utils.validateType)("FlowType") + } +}); +defineType("NumberLiteralTypeAnnotation", { + builder: ["value"], + aliases: ["FlowType"], + fields: { + value: (0, _utils.validate)((0, _utils.assertValueType)("number")) + } +}); +defineType("NumberTypeAnnotation", { + aliases: ["FlowType", "FlowBaseAnnotation"] +}); +defineType("ObjectTypeAnnotation", { + visitor: ["properties", "indexers", "callProperties", "internalSlots"], + aliases: ["FlowType"], + builder: ["properties", "indexers", "callProperties", "internalSlots", "exact"], + fields: { + properties: (0, _utils.validate)((0, _utils.arrayOfType)("ObjectTypeProperty", "ObjectTypeSpreadProperty")), + indexers: { + validate: (0, _utils.arrayOfType)("ObjectTypeIndexer"), + optional: true, + default: [] + }, + callProperties: { + validate: (0, _utils.arrayOfType)("ObjectTypeCallProperty"), + optional: true, + default: [] + }, + internalSlots: { + validate: (0, _utils.arrayOfType)("ObjectTypeInternalSlot"), + optional: true, + default: [] + }, + exact: { + validate: (0, _utils.assertValueType)("boolean"), + default: false + }, + inexact: (0, _utils.validateOptional)((0, _utils.assertValueType)("boolean")) + } +}); +defineType("ObjectTypeInternalSlot", { + visitor: ["id", "value"], + builder: ["id", "value", "optional", "static", "method"], + aliases: ["UserWhitespacable"], + fields: { + id: (0, _utils.validateType)("Identifier"), + value: (0, _utils.validateType)("FlowType"), + optional: (0, _utils.validate)((0, _utils.assertValueType)("boolean")), + static: (0, _utils.validate)((0, _utils.assertValueType)("boolean")), + method: (0, _utils.validate)((0, _utils.assertValueType)("boolean")) + } +}); +defineType("ObjectTypeCallProperty", { + visitor: ["value"], + aliases: ["UserWhitespacable"], + fields: { + value: (0, _utils.validateType)("FlowType"), + static: (0, _utils.validate)((0, _utils.assertValueType)("boolean")) + } +}); +defineType("ObjectTypeIndexer", { + visitor: ["variance", "id", "key", "value"], + builder: ["id", "key", "value", "variance"], + aliases: ["UserWhitespacable"], + fields: { + id: (0, _utils.validateOptionalType)("Identifier"), + key: (0, _utils.validateType)("FlowType"), + value: (0, _utils.validateType)("FlowType"), + static: (0, _utils.validate)((0, _utils.assertValueType)("boolean")), + variance: (0, _utils.validateOptionalType)("Variance") + } +}); +defineType("ObjectTypeProperty", { + visitor: ["key", "value", "variance"], + aliases: ["UserWhitespacable"], + fields: { + key: (0, _utils.validateType)("Identifier", "StringLiteral"), + value: (0, _utils.validateType)("FlowType"), + kind: (0, _utils.validate)((0, _utils.assertOneOf)("init", "get", "set")), + static: (0, _utils.validate)((0, _utils.assertValueType)("boolean")), + proto: (0, _utils.validate)((0, _utils.assertValueType)("boolean")), + optional: (0, _utils.validate)((0, _utils.assertValueType)("boolean")), + variance: (0, _utils.validateOptionalType)("Variance"), + method: (0, _utils.validate)((0, _utils.assertValueType)("boolean")) + } +}); +defineType("ObjectTypeSpreadProperty", { + visitor: ["argument"], + aliases: ["UserWhitespacable"], + fields: { + argument: (0, _utils.validateType)("FlowType") + } +}); +defineType("OpaqueType", { + visitor: ["id", "typeParameters", "supertype", "impltype"], + aliases: ["FlowDeclaration", "Statement", "Declaration"], + fields: { + id: (0, _utils.validateType)("Identifier"), + typeParameters: (0, _utils.validateOptionalType)("TypeParameterDeclaration"), + supertype: (0, _utils.validateOptionalType)("FlowType"), + impltype: (0, _utils.validateType)("FlowType") + } +}); +defineType("QualifiedTypeIdentifier", { + visitor: ["qualification", "id"], + builder: ["id", "qualification"], + fields: { + id: (0, _utils.validateType)("Identifier"), + qualification: (0, _utils.validateType)("Identifier", "QualifiedTypeIdentifier") + } +}); +defineType("StringLiteralTypeAnnotation", { + builder: ["value"], + aliases: ["FlowType"], + fields: { + value: (0, _utils.validate)((0, _utils.assertValueType)("string")) + } +}); +defineType("StringTypeAnnotation", { + aliases: ["FlowType", "FlowBaseAnnotation"] +}); +defineType("SymbolTypeAnnotation", { + aliases: ["FlowType", "FlowBaseAnnotation"] +}); +defineType("ThisTypeAnnotation", { + aliases: ["FlowType", "FlowBaseAnnotation"] +}); +defineType("TupleTypeAnnotation", { + visitor: ["types"], + aliases: ["FlowType"], + fields: { + types: (0, _utils.validate)((0, _utils.arrayOfType)("FlowType")) + } +}); +defineType("TypeofTypeAnnotation", { + visitor: ["argument"], + aliases: ["FlowType"], + fields: { + argument: (0, _utils.validateType)("FlowType") + } +}); +defineType("TypeAlias", { + visitor: ["id", "typeParameters", "right"], + aliases: ["FlowDeclaration", "Statement", "Declaration"], + fields: { + id: (0, _utils.validateType)("Identifier"), + typeParameters: (0, _utils.validateOptionalType)("TypeParameterDeclaration"), + right: (0, _utils.validateType)("FlowType") + } +}); +defineType("TypeAnnotation", { + visitor: ["typeAnnotation"], + fields: { + typeAnnotation: (0, _utils.validateType)("FlowType") + } +}); +defineType("TypeCastExpression", { + visitor: ["expression", "typeAnnotation"], + aliases: ["ExpressionWrapper", "Expression"], + fields: { + expression: (0, _utils.validateType)("Expression"), + typeAnnotation: (0, _utils.validateType)("TypeAnnotation") + } +}); +defineType("TypeParameter", { + visitor: ["bound", "default", "variance"], + fields: { + name: (0, _utils.validate)((0, _utils.assertValueType)("string")), + bound: (0, _utils.validateOptionalType)("TypeAnnotation"), + default: (0, _utils.validateOptionalType)("FlowType"), + variance: (0, _utils.validateOptionalType)("Variance") + } +}); +defineType("TypeParameterDeclaration", { + visitor: ["params"], + fields: { + params: (0, _utils.validate)((0, _utils.arrayOfType)("TypeParameter")) + } +}); +defineType("TypeParameterInstantiation", { + visitor: ["params"], + fields: { + params: (0, _utils.validate)((0, _utils.arrayOfType)("FlowType")) + } +}); +defineType("UnionTypeAnnotation", { + visitor: ["types"], + aliases: ["FlowType"], + fields: { + types: (0, _utils.validate)((0, _utils.arrayOfType)("FlowType")) + } +}); +defineType("Variance", { + builder: ["kind"], + fields: { + kind: (0, _utils.validate)((0, _utils.assertOneOf)("minus", "plus")) + } +}); +defineType("VoidTypeAnnotation", { + aliases: ["FlowType", "FlowBaseAnnotation"] +}); +defineType("EnumDeclaration", { + aliases: ["Statement", "Declaration"], + visitor: ["id", "body"], + fields: { + id: (0, _utils.validateType)("Identifier"), + body: (0, _utils.validateType)("EnumBooleanBody", "EnumNumberBody", "EnumStringBody", "EnumSymbolBody") + } +}); +defineType("EnumBooleanBody", { + aliases: ["EnumBody"], + visitor: ["members"], + fields: { + explicitType: (0, _utils.validate)((0, _utils.assertValueType)("boolean")), + members: (0, _utils.validateArrayOfType)("EnumBooleanMember"), + hasUnknownMembers: (0, _utils.validate)((0, _utils.assertValueType)("boolean")) + } +}); +defineType("EnumNumberBody", { + aliases: ["EnumBody"], + visitor: ["members"], + fields: { + explicitType: (0, _utils.validate)((0, _utils.assertValueType)("boolean")), + members: (0, _utils.validateArrayOfType)("EnumNumberMember"), + hasUnknownMembers: (0, _utils.validate)((0, _utils.assertValueType)("boolean")) + } +}); +defineType("EnumStringBody", { + aliases: ["EnumBody"], + visitor: ["members"], + fields: { + explicitType: (0, _utils.validate)((0, _utils.assertValueType)("boolean")), + members: (0, _utils.validateArrayOfType)("EnumStringMember", "EnumDefaultedMember"), + hasUnknownMembers: (0, _utils.validate)((0, _utils.assertValueType)("boolean")) + } +}); +defineType("EnumSymbolBody", { + aliases: ["EnumBody"], + visitor: ["members"], + fields: { + members: (0, _utils.validateArrayOfType)("EnumDefaultedMember"), + hasUnknownMembers: (0, _utils.validate)((0, _utils.assertValueType)("boolean")) + } +}); +defineType("EnumBooleanMember", { + aliases: ["EnumMember"], + builder: ["id"], + visitor: ["id", "init"], + fields: { + id: (0, _utils.validateType)("Identifier"), + init: (0, _utils.validateType)("BooleanLiteral") + } +}); +defineType("EnumNumberMember", { + aliases: ["EnumMember"], + visitor: ["id", "init"], + fields: { + id: (0, _utils.validateType)("Identifier"), + init: (0, _utils.validateType)("NumericLiteral") + } +}); +defineType("EnumStringMember", { + aliases: ["EnumMember"], + visitor: ["id", "init"], + fields: { + id: (0, _utils.validateType)("Identifier"), + init: (0, _utils.validateType)("StringLiteral") + } +}); +defineType("EnumDefaultedMember", { + aliases: ["EnumMember"], + visitor: ["id"], + fields: { + id: (0, _utils.validateType)("Identifier") + } +}); +defineType("IndexedAccessType", { + visitor: ["objectType", "indexType"], + aliases: ["FlowType"], + fields: { + objectType: (0, _utils.validateType)("FlowType"), + indexType: (0, _utils.validateType)("FlowType") + } +}); +defineType("OptionalIndexedAccessType", { + visitor: ["objectType", "indexType"], + aliases: ["FlowType"], + fields: { + objectType: (0, _utils.validateType)("FlowType"), + indexType: (0, _utils.validateType)("FlowType"), + optional: (0, _utils.validate)((0, _utils.assertValueType)("boolean")) + } +}); + +//# sourceMappingURL=flow.js.map + + +/***/ }), + +/***/ 40910: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +Object.defineProperty(exports, "ALIAS_KEYS", ({ + enumerable: true, + get: function () { + return _utils.ALIAS_KEYS; + } +})); +Object.defineProperty(exports, "BUILDER_KEYS", ({ + enumerable: true, + get: function () { + return _utils.BUILDER_KEYS; + } +})); +Object.defineProperty(exports, "DEPRECATED_ALIASES", ({ + enumerable: true, + get: function () { + return _deprecatedAliases.DEPRECATED_ALIASES; + } +})); +Object.defineProperty(exports, "DEPRECATED_KEYS", ({ + enumerable: true, + get: function () { + return _utils.DEPRECATED_KEYS; + } +})); +Object.defineProperty(exports, "FLIPPED_ALIAS_KEYS", ({ + enumerable: true, + get: function () { + return _utils.FLIPPED_ALIAS_KEYS; + } +})); +Object.defineProperty(exports, "NODE_FIELDS", ({ + enumerable: true, + get: function () { + return _utils.NODE_FIELDS; + } +})); +Object.defineProperty(exports, "NODE_PARENT_VALIDATIONS", ({ + enumerable: true, + get: function () { + return _utils.NODE_PARENT_VALIDATIONS; + } +})); +Object.defineProperty(exports, "PLACEHOLDERS", ({ + enumerable: true, + get: function () { + return _placeholders.PLACEHOLDERS; + } +})); +Object.defineProperty(exports, "PLACEHOLDERS_ALIAS", ({ + enumerable: true, + get: function () { + return _placeholders.PLACEHOLDERS_ALIAS; + } +})); +Object.defineProperty(exports, "PLACEHOLDERS_FLIPPED_ALIAS", ({ + enumerable: true, + get: function () { + return _placeholders.PLACEHOLDERS_FLIPPED_ALIAS; + } +})); +exports.TYPES = void 0; +Object.defineProperty(exports, "VISITOR_KEYS", ({ + enumerable: true, + get: function () { + return _utils.VISITOR_KEYS; + } +})); +__nccwpck_require__(62803); +__nccwpck_require__(67256); +__nccwpck_require__(24379); +__nccwpck_require__(34528); +__nccwpck_require__(78794); +__nccwpck_require__(60463); +var _utils = __nccwpck_require__(34559); +var _placeholders = __nccwpck_require__(73182); +var _deprecatedAliases = __nccwpck_require__(44856); +Object.keys(_deprecatedAliases.DEPRECATED_ALIASES).forEach(deprecatedAlias => { + _utils.FLIPPED_ALIAS_KEYS[deprecatedAlias] = _utils.FLIPPED_ALIAS_KEYS[_deprecatedAliases.DEPRECATED_ALIASES[deprecatedAlias]]; +}); +for (const { + types, + set +} of _utils.allExpandedTypes) { + for (const type of types) { + const aliases = _utils.FLIPPED_ALIAS_KEYS[type]; + if (aliases) { + aliases.forEach(set.add, set); + } else { + set.add(type); + } + } +} +const TYPES = exports.TYPES = [].concat(Object.keys(_utils.VISITOR_KEYS), Object.keys(_utils.FLIPPED_ALIAS_KEYS), Object.keys(_utils.DEPRECATED_KEYS)); + +//# sourceMappingURL=index.js.map + + +/***/ }), + +/***/ 24379: +/***/ ((__unused_webpack_module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +var _utils = __nccwpck_require__(34559); +const defineType = (0, _utils.defineAliasedType)("JSX"); +defineType("JSXAttribute", { + visitor: ["name", "value"], + aliases: ["Immutable"], + fields: { + name: { + validate: (0, _utils.assertNodeType)("JSXIdentifier", "JSXNamespacedName") + }, + value: { + optional: true, + validate: (0, _utils.assertNodeType)("JSXElement", "JSXFragment", "StringLiteral", "JSXExpressionContainer") + } + } +}); +defineType("JSXClosingElement", { + visitor: ["name"], + aliases: ["Immutable"], + fields: { + name: { + validate: (0, _utils.assertNodeType)("JSXIdentifier", "JSXMemberExpression", "JSXNamespacedName") + } + } +}); +defineType("JSXElement", { + builder: ["openingElement", "closingElement", "children", "selfClosing"], + visitor: ["openingElement", "children", "closingElement"], + aliases: ["Immutable", "Expression"], + fields: Object.assign({ + openingElement: { + validate: (0, _utils.assertNodeType)("JSXOpeningElement") + }, + closingElement: { + optional: true, + validate: (0, _utils.assertNodeType)("JSXClosingElement") + }, + children: (0, _utils.validateArrayOfType)("JSXText", "JSXExpressionContainer", "JSXSpreadChild", "JSXElement", "JSXFragment") + }, { + selfClosing: { + validate: (0, _utils.assertValueType)("boolean"), + optional: true + } + }) +}); +defineType("JSXEmptyExpression", {}); +defineType("JSXExpressionContainer", { + visitor: ["expression"], + aliases: ["Immutable"], + fields: { + expression: { + validate: (0, _utils.assertNodeType)("Expression", "JSXEmptyExpression") + } + } +}); +defineType("JSXSpreadChild", { + visitor: ["expression"], + aliases: ["Immutable"], + fields: { + expression: { + validate: (0, _utils.assertNodeType)("Expression") + } + } +}); +defineType("JSXIdentifier", { + builder: ["name"], + fields: { + name: { + validate: (0, _utils.assertValueType)("string") + } + } +}); +defineType("JSXMemberExpression", { + visitor: ["object", "property"], + fields: { + object: { + validate: (0, _utils.assertNodeType)("JSXMemberExpression", "JSXIdentifier") + }, + property: { + validate: (0, _utils.assertNodeType)("JSXIdentifier") + } + } +}); +defineType("JSXNamespacedName", { + visitor: ["namespace", "name"], + fields: { + namespace: { + validate: (0, _utils.assertNodeType)("JSXIdentifier") + }, + name: { + validate: (0, _utils.assertNodeType)("JSXIdentifier") + } + } +}); +defineType("JSXOpeningElement", { + builder: ["name", "attributes", "selfClosing"], + visitor: ["name", "typeParameters", "typeArguments", "attributes"], + aliases: ["Immutable"], + fields: Object.assign({ + name: { + validate: (0, _utils.assertNodeType)("JSXIdentifier", "JSXMemberExpression", "JSXNamespacedName") + }, + selfClosing: { + default: false + }, + attributes: (0, _utils.validateArrayOfType)("JSXAttribute", "JSXSpreadAttribute"), + typeArguments: { + validate: (0, _utils.assertNodeType)("TypeParameterInstantiation"), + optional: true + } + }, { + typeParameters: { + validate: (0, _utils.assertNodeType)("TSTypeParameterInstantiation"), + optional: true + } + }) +}); +defineType("JSXSpreadAttribute", { + visitor: ["argument"], + fields: { + argument: { + validate: (0, _utils.assertNodeType)("Expression") + } + } +}); +defineType("JSXText", { + aliases: ["Immutable"], + builder: ["value"], + fields: { + value: { + validate: (0, _utils.assertValueType)("string") + } + } +}); +defineType("JSXFragment", { + builder: ["openingFragment", "closingFragment", "children"], + visitor: ["openingFragment", "children", "closingFragment"], + aliases: ["Immutable", "Expression"], + fields: { + openingFragment: { + validate: (0, _utils.assertNodeType)("JSXOpeningFragment") + }, + closingFragment: { + validate: (0, _utils.assertNodeType)("JSXClosingFragment") + }, + children: (0, _utils.validateArrayOfType)("JSXText", "JSXExpressionContainer", "JSXSpreadChild", "JSXElement", "JSXFragment") + } +}); +defineType("JSXOpeningFragment", { + aliases: ["Immutable"] +}); +defineType("JSXClosingFragment", { + aliases: ["Immutable"] +}); + +//# sourceMappingURL=jsx.js.map + + +/***/ }), + +/***/ 34528: +/***/ ((__unused_webpack_module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +var _utils = __nccwpck_require__(34559); +var _placeholders = __nccwpck_require__(73182); +var _core = __nccwpck_require__(62803); +const defineType = (0, _utils.defineAliasedType)("Miscellaneous"); +{ + defineType("Noop", { + visitor: [] + }); +} +defineType("Placeholder", { + visitor: [], + builder: ["expectedNode", "name"], + fields: Object.assign({ + name: { + validate: (0, _utils.assertNodeType)("Identifier") + }, + expectedNode: { + validate: (0, _utils.assertOneOf)(..._placeholders.PLACEHOLDERS) + } + }, (0, _core.patternLikeCommon)()) +}); +defineType("V8IntrinsicIdentifier", { + builder: ["name"], + fields: { + name: { + validate: (0, _utils.assertValueType)("string") + } + } +}); + +//# sourceMappingURL=misc.js.map + + +/***/ }), + +/***/ 73182: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports.PLACEHOLDERS_FLIPPED_ALIAS = exports.PLACEHOLDERS_ALIAS = exports.PLACEHOLDERS = void 0; +var _utils = __nccwpck_require__(34559); +const PLACEHOLDERS = exports.PLACEHOLDERS = ["Identifier", "StringLiteral", "Expression", "Statement", "Declaration", "BlockStatement", "ClassBody", "Pattern"]; +const PLACEHOLDERS_ALIAS = exports.PLACEHOLDERS_ALIAS = { + Declaration: ["Statement"], + Pattern: ["PatternLike", "LVal"] +}; +for (const type of PLACEHOLDERS) { + const alias = _utils.ALIAS_KEYS[type]; + if (alias != null && alias.length) PLACEHOLDERS_ALIAS[type] = alias; +} +const PLACEHOLDERS_FLIPPED_ALIAS = exports.PLACEHOLDERS_FLIPPED_ALIAS = {}; +Object.keys(PLACEHOLDERS_ALIAS).forEach(type => { + PLACEHOLDERS_ALIAS[type].forEach(alias => { + if (!hasOwnProperty.call(PLACEHOLDERS_FLIPPED_ALIAS, alias)) { + PLACEHOLDERS_FLIPPED_ALIAS[alias] = []; + } + PLACEHOLDERS_FLIPPED_ALIAS[alias].push(type); + }); +}); + +//# sourceMappingURL=placeholders.js.map + + +/***/ }), + +/***/ 60463: +/***/ ((__unused_webpack_module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +var _utils = __nccwpck_require__(34559); +var _core = __nccwpck_require__(62803); +var _is = __nccwpck_require__(20051); +const defineType = (0, _utils.defineAliasedType)("TypeScript"); +const bool = (0, _utils.assertValueType)("boolean"); +const tSFunctionTypeAnnotationCommon = () => ({ + returnType: { + validate: (0, _utils.assertNodeType)("TSTypeAnnotation", "Noop"), + optional: true + }, + typeParameters: { + validate: (0, _utils.assertNodeType)("TSTypeParameterDeclaration", "Noop"), + optional: true + } +}); +defineType("TSParameterProperty", { + aliases: ["LVal"], + visitor: ["parameter"], + fields: { + accessibility: { + validate: (0, _utils.assertOneOf)("public", "private", "protected"), + optional: true + }, + readonly: { + validate: (0, _utils.assertValueType)("boolean"), + optional: true + }, + parameter: { + validate: (0, _utils.assertNodeType)("Identifier", "AssignmentPattern") + }, + override: { + validate: (0, _utils.assertValueType)("boolean"), + optional: true + }, + decorators: { + validate: (0, _utils.arrayOfType)("Decorator"), + optional: true + } + } +}); +defineType("TSDeclareFunction", { + aliases: ["Statement", "Declaration"], + visitor: ["id", "typeParameters", "params", "returnType"], + fields: Object.assign({}, (0, _core.functionDeclarationCommon)(), tSFunctionTypeAnnotationCommon()) +}); +defineType("TSDeclareMethod", { + visitor: ["decorators", "key", "typeParameters", "params", "returnType"], + fields: Object.assign({}, (0, _core.classMethodOrDeclareMethodCommon)(), tSFunctionTypeAnnotationCommon()) +}); +defineType("TSQualifiedName", { + aliases: ["TSEntityName"], + visitor: ["left", "right"], + fields: { + left: (0, _utils.validateType)("TSEntityName"), + right: (0, _utils.validateType)("Identifier") + } +}); +const signatureDeclarationCommon = () => ({ + typeParameters: (0, _utils.validateOptionalType)("TSTypeParameterDeclaration"), + ["parameters"]: (0, _utils.validateArrayOfType)("ArrayPattern", "Identifier", "ObjectPattern", "RestElement"), + ["typeAnnotation"]: (0, _utils.validateOptionalType)("TSTypeAnnotation") +}); +const callConstructSignatureDeclaration = { + aliases: ["TSTypeElement"], + visitor: ["typeParameters", "parameters", "typeAnnotation"], + fields: signatureDeclarationCommon() +}; +defineType("TSCallSignatureDeclaration", callConstructSignatureDeclaration); +defineType("TSConstructSignatureDeclaration", callConstructSignatureDeclaration); +const namedTypeElementCommon = () => ({ + key: (0, _utils.validateType)("Expression"), + computed: { + default: false + }, + optional: (0, _utils.validateOptional)(bool) +}); +defineType("TSPropertySignature", { + aliases: ["TSTypeElement"], + visitor: ["key", "typeAnnotation"], + fields: Object.assign({}, namedTypeElementCommon(), { + readonly: (0, _utils.validateOptional)(bool), + typeAnnotation: (0, _utils.validateOptionalType)("TSTypeAnnotation"), + kind: { + optional: true, + validate: (0, _utils.assertOneOf)("get", "set") + } + }) +}); +defineType("TSMethodSignature", { + aliases: ["TSTypeElement"], + visitor: ["key", "typeParameters", "parameters", "typeAnnotation"], + fields: Object.assign({}, signatureDeclarationCommon(), namedTypeElementCommon(), { + kind: { + validate: (0, _utils.assertOneOf)("method", "get", "set") + } + }) +}); +defineType("TSIndexSignature", { + aliases: ["TSTypeElement"], + visitor: ["parameters", "typeAnnotation"], + fields: { + readonly: (0, _utils.validateOptional)(bool), + static: (0, _utils.validateOptional)(bool), + parameters: (0, _utils.validateArrayOfType)("Identifier"), + typeAnnotation: (0, _utils.validateOptionalType)("TSTypeAnnotation") + } +}); +const tsKeywordTypes = ["TSAnyKeyword", "TSBooleanKeyword", "TSBigIntKeyword", "TSIntrinsicKeyword", "TSNeverKeyword", "TSNullKeyword", "TSNumberKeyword", "TSObjectKeyword", "TSStringKeyword", "TSSymbolKeyword", "TSUndefinedKeyword", "TSUnknownKeyword", "TSVoidKeyword"]; +for (const type of tsKeywordTypes) { + defineType(type, { + aliases: ["TSType", "TSBaseType"], + visitor: [], + fields: {} + }); +} +defineType("TSThisType", { + aliases: ["TSType", "TSBaseType"], + visitor: [], + fields: {} +}); +const fnOrCtrBase = { + aliases: ["TSType"], + visitor: ["typeParameters", "parameters", "typeAnnotation"] +}; +defineType("TSFunctionType", Object.assign({}, fnOrCtrBase, { + fields: signatureDeclarationCommon() +})); +defineType("TSConstructorType", Object.assign({}, fnOrCtrBase, { + fields: Object.assign({}, signatureDeclarationCommon(), { + abstract: (0, _utils.validateOptional)(bool) + }) +})); +defineType("TSTypeReference", { + aliases: ["TSType"], + visitor: ["typeName", "typeParameters"], + fields: { + typeName: (0, _utils.validateType)("TSEntityName"), + ["typeParameters"]: (0, _utils.validateOptionalType)("TSTypeParameterInstantiation") + } +}); +defineType("TSTypePredicate", { + aliases: ["TSType"], + visitor: ["parameterName", "typeAnnotation"], + builder: ["parameterName", "typeAnnotation", "asserts"], + fields: { + parameterName: (0, _utils.validateType)("Identifier", "TSThisType"), + typeAnnotation: (0, _utils.validateOptionalType)("TSTypeAnnotation"), + asserts: (0, _utils.validateOptional)(bool) + } +}); +defineType("TSTypeQuery", { + aliases: ["TSType"], + visitor: ["exprName", "typeParameters"], + fields: { + exprName: (0, _utils.validateType)("TSEntityName", "TSImportType"), + ["typeParameters"]: (0, _utils.validateOptionalType)("TSTypeParameterInstantiation") + } +}); +defineType("TSTypeLiteral", { + aliases: ["TSType"], + visitor: ["members"], + fields: { + members: (0, _utils.validateArrayOfType)("TSTypeElement") + } +}); +defineType("TSArrayType", { + aliases: ["TSType"], + visitor: ["elementType"], + fields: { + elementType: (0, _utils.validateType)("TSType") + } +}); +defineType("TSTupleType", { + aliases: ["TSType"], + visitor: ["elementTypes"], + fields: { + elementTypes: (0, _utils.validateArrayOfType)("TSType", "TSNamedTupleMember") + } +}); +defineType("TSOptionalType", { + aliases: ["TSType"], + visitor: ["typeAnnotation"], + fields: { + typeAnnotation: (0, _utils.validateType)("TSType") + } +}); +defineType("TSRestType", { + aliases: ["TSType"], + visitor: ["typeAnnotation"], + fields: { + typeAnnotation: (0, _utils.validateType)("TSType") + } +}); +defineType("TSNamedTupleMember", { + visitor: ["label", "elementType"], + builder: ["label", "elementType", "optional"], + fields: { + label: (0, _utils.validateType)("Identifier"), + optional: { + validate: bool, + default: false + }, + elementType: (0, _utils.validateType)("TSType") + } +}); +const unionOrIntersection = { + aliases: ["TSType"], + visitor: ["types"], + fields: { + types: (0, _utils.validateArrayOfType)("TSType") + } +}; +defineType("TSUnionType", unionOrIntersection); +defineType("TSIntersectionType", unionOrIntersection); +defineType("TSConditionalType", { + aliases: ["TSType"], + visitor: ["checkType", "extendsType", "trueType", "falseType"], + fields: { + checkType: (0, _utils.validateType)("TSType"), + extendsType: (0, _utils.validateType)("TSType"), + trueType: (0, _utils.validateType)("TSType"), + falseType: (0, _utils.validateType)("TSType") + } +}); +defineType("TSInferType", { + aliases: ["TSType"], + visitor: ["typeParameter"], + fields: { + typeParameter: (0, _utils.validateType)("TSTypeParameter") + } +}); +defineType("TSParenthesizedType", { + aliases: ["TSType"], + visitor: ["typeAnnotation"], + fields: { + typeAnnotation: (0, _utils.validateType)("TSType") + } +}); +defineType("TSTypeOperator", { + aliases: ["TSType"], + visitor: ["typeAnnotation"], + fields: { + operator: (0, _utils.validate)((0, _utils.assertValueType)("string")), + typeAnnotation: (0, _utils.validateType)("TSType") + } +}); +defineType("TSIndexedAccessType", { + aliases: ["TSType"], + visitor: ["objectType", "indexType"], + fields: { + objectType: (0, _utils.validateType)("TSType"), + indexType: (0, _utils.validateType)("TSType") + } +}); +defineType("TSMappedType", { + aliases: ["TSType"], + visitor: ["typeParameter", "nameType", "typeAnnotation"], + builder: ["typeParameter", "typeAnnotation", "nameType"], + fields: Object.assign({}, { + typeParameter: (0, _utils.validateType)("TSTypeParameter") + }, { + readonly: (0, _utils.validateOptional)((0, _utils.assertOneOf)(true, false, "+", "-")), + optional: (0, _utils.validateOptional)((0, _utils.assertOneOf)(true, false, "+", "-")), + typeAnnotation: (0, _utils.validateOptionalType)("TSType"), + nameType: (0, _utils.validateOptionalType)("TSType") + }) +}); +defineType("TSTemplateLiteralType", { + aliases: ["TSType", "TSBaseType"], + visitor: ["quasis", "types"], + fields: { + quasis: (0, _utils.validateArrayOfType)("TemplateElement"), + types: { + validate: (0, _utils.chain)((0, _utils.assertValueType)("array"), (0, _utils.assertEach)((0, _utils.assertNodeType)("TSType")), function (node, key, val) { + if (node.quasis.length !== val.length + 1) { + throw new TypeError(`Number of ${node.type} quasis should be exactly one more than the number of types.\nExpected ${val.length + 1} quasis but got ${node.quasis.length}`); + } + }) + } + } +}); +defineType("TSLiteralType", { + aliases: ["TSType", "TSBaseType"], + visitor: ["literal"], + fields: { + literal: { + validate: function () { + const unaryExpression = (0, _utils.assertNodeType)("NumericLiteral", "BigIntLiteral"); + const unaryOperator = (0, _utils.assertOneOf)("-"); + const literal = (0, _utils.assertNodeType)("NumericLiteral", "StringLiteral", "BooleanLiteral", "BigIntLiteral", "TemplateLiteral"); + function validator(parent, key, node) { + if ((0, _is.default)("UnaryExpression", node)) { + unaryOperator(node, "operator", node.operator); + unaryExpression(node, "argument", node.argument); + } else { + literal(parent, key, node); + } + } + validator.oneOfNodeTypes = ["NumericLiteral", "StringLiteral", "BooleanLiteral", "BigIntLiteral", "TemplateLiteral", "UnaryExpression"]; + return validator; + }() + } + } +}); +{ + defineType("TSExpressionWithTypeArguments", { + aliases: ["TSType"], + visitor: ["expression", "typeParameters"], + fields: { + expression: (0, _utils.validateType)("TSEntityName"), + typeParameters: (0, _utils.validateOptionalType)("TSTypeParameterInstantiation") + } + }); +} +defineType("TSInterfaceDeclaration", { + aliases: ["Statement", "Declaration"], + visitor: ["id", "typeParameters", "extends", "body"], + fields: { + declare: (0, _utils.validateOptional)(bool), + id: (0, _utils.validateType)("Identifier"), + typeParameters: (0, _utils.validateOptionalType)("TSTypeParameterDeclaration"), + extends: (0, _utils.validateOptional)((0, _utils.arrayOfType)("TSExpressionWithTypeArguments")), + body: (0, _utils.validateType)("TSInterfaceBody") + } +}); +defineType("TSInterfaceBody", { + visitor: ["body"], + fields: { + body: (0, _utils.validateArrayOfType)("TSTypeElement") + } +}); +defineType("TSTypeAliasDeclaration", { + aliases: ["Statement", "Declaration"], + visitor: ["id", "typeParameters", "typeAnnotation"], + fields: { + declare: (0, _utils.validateOptional)(bool), + id: (0, _utils.validateType)("Identifier"), + typeParameters: (0, _utils.validateOptionalType)("TSTypeParameterDeclaration"), + typeAnnotation: (0, _utils.validateType)("TSType") + } +}); +defineType("TSInstantiationExpression", { + aliases: ["Expression"], + visitor: ["expression", "typeParameters"], + fields: { + expression: (0, _utils.validateType)("Expression"), + ["typeParameters"]: (0, _utils.validateOptionalType)("TSTypeParameterInstantiation") + } +}); +const TSTypeExpression = { + aliases: ["Expression", "LVal", "PatternLike"], + visitor: ["expression", "typeAnnotation"], + fields: { + expression: (0, _utils.validateType)("Expression"), + typeAnnotation: (0, _utils.validateType)("TSType") + } +}; +defineType("TSAsExpression", TSTypeExpression); +defineType("TSSatisfiesExpression", TSTypeExpression); +defineType("TSTypeAssertion", { + aliases: ["Expression", "LVal", "PatternLike"], + visitor: ["typeAnnotation", "expression"], + fields: { + typeAnnotation: (0, _utils.validateType)("TSType"), + expression: (0, _utils.validateType)("Expression") + } +}); +defineType("TSEnumBody", { + visitor: ["members"], + fields: { + members: (0, _utils.validateArrayOfType)("TSEnumMember") + } +}); +{ + defineType("TSEnumDeclaration", { + aliases: ["Statement", "Declaration"], + visitor: ["id", "members"], + fields: { + declare: (0, _utils.validateOptional)(bool), + const: (0, _utils.validateOptional)(bool), + id: (0, _utils.validateType)("Identifier"), + members: (0, _utils.validateArrayOfType)("TSEnumMember"), + initializer: (0, _utils.validateOptionalType)("Expression"), + body: (0, _utils.validateOptionalType)("TSEnumBody") + } + }); +} +defineType("TSEnumMember", { + visitor: ["id", "initializer"], + fields: { + id: (0, _utils.validateType)("Identifier", "StringLiteral"), + initializer: (0, _utils.validateOptionalType)("Expression") + } +}); +defineType("TSModuleDeclaration", { + aliases: ["Statement", "Declaration"], + visitor: ["id", "body"], + fields: Object.assign({ + kind: { + validate: (0, _utils.assertOneOf)("global", "module", "namespace") + }, + declare: (0, _utils.validateOptional)(bool) + }, { + global: (0, _utils.validateOptional)(bool) + }, { + id: (0, _utils.validateType)("Identifier", "StringLiteral"), + body: (0, _utils.validateType)("TSModuleBlock", "TSModuleDeclaration") + }) +}); +defineType("TSModuleBlock", { + aliases: ["Scopable", "Block", "BlockParent", "FunctionParent"], + visitor: ["body"], + fields: { + body: (0, _utils.validateArrayOfType)("Statement") + } +}); +defineType("TSImportType", { + aliases: ["TSType"], + builder: ["argument", "qualifier", "typeParameters"], + visitor: ["argument", "options", "qualifier", "typeParameters"], + fields: { + argument: (0, _utils.validateType)("StringLiteral"), + qualifier: (0, _utils.validateOptionalType)("TSEntityName"), + ["typeParameters"]: (0, _utils.validateOptionalType)("TSTypeParameterInstantiation"), + options: { + validate: (0, _utils.assertNodeType)("ObjectExpression"), + optional: true + } + } +}); +defineType("TSImportEqualsDeclaration", { + aliases: ["Statement", "Declaration"], + visitor: ["id", "moduleReference"], + fields: Object.assign({}, { + isExport: (0, _utils.validate)(bool) + }, { + id: (0, _utils.validateType)("Identifier"), + moduleReference: (0, _utils.validateType)("TSEntityName", "TSExternalModuleReference"), + importKind: { + validate: (0, _utils.assertOneOf)("type", "value"), + optional: true + } + }) +}); +defineType("TSExternalModuleReference", { + visitor: ["expression"], + fields: { + expression: (0, _utils.validateType)("StringLiteral") + } +}); +defineType("TSNonNullExpression", { + aliases: ["Expression", "LVal", "PatternLike"], + visitor: ["expression"], + fields: { + expression: (0, _utils.validateType)("Expression") + } +}); +defineType("TSExportAssignment", { + aliases: ["Statement"], + visitor: ["expression"], + fields: { + expression: (0, _utils.validateType)("Expression") + } +}); +defineType("TSNamespaceExportDeclaration", { + aliases: ["Statement"], + visitor: ["id"], + fields: { + id: (0, _utils.validateType)("Identifier") + } +}); +defineType("TSTypeAnnotation", { + visitor: ["typeAnnotation"], + fields: { + typeAnnotation: { + validate: (0, _utils.assertNodeType)("TSType") + } + } +}); +defineType("TSTypeParameterInstantiation", { + visitor: ["params"], + fields: { + params: (0, _utils.validateArrayOfType)("TSType") + } +}); +defineType("TSTypeParameterDeclaration", { + visitor: ["params"], + fields: { + params: (0, _utils.validateArrayOfType)("TSTypeParameter") + } +}); +defineType("TSTypeParameter", { + builder: ["constraint", "default", "name"], + visitor: ["constraint", "default"], + fields: { + name: { + validate: (0, _utils.assertValueType)("string") + }, + in: { + validate: (0, _utils.assertValueType)("boolean"), + optional: true + }, + out: { + validate: (0, _utils.assertValueType)("boolean"), + optional: true + }, + const: { + validate: (0, _utils.assertValueType)("boolean"), + optional: true + }, + constraint: { + validate: (0, _utils.assertNodeType)("TSType"), + optional: true + }, + default: { + validate: (0, _utils.assertNodeType)("TSType"), + optional: true + } + } +}); + +//# sourceMappingURL=typescript.js.map + + +/***/ }), + +/***/ 34559: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports.allExpandedTypes = exports.VISITOR_KEYS = exports.NODE_PARENT_VALIDATIONS = exports.NODE_FIELDS = exports.FLIPPED_ALIAS_KEYS = exports.DEPRECATED_KEYS = exports.BUILDER_KEYS = exports.ALIAS_KEYS = void 0; +exports.arrayOf = arrayOf; +exports.arrayOfType = arrayOfType; +exports.assertEach = assertEach; +exports.assertNodeOrValueType = assertNodeOrValueType; +exports.assertNodeType = assertNodeType; +exports.assertOneOf = assertOneOf; +exports.assertOptionalChainStart = assertOptionalChainStart; +exports.assertShape = assertShape; +exports.assertValueType = assertValueType; +exports.chain = chain; +exports["default"] = defineType; +exports.defineAliasedType = defineAliasedType; +exports.validate = validate; +exports.validateArrayOfType = validateArrayOfType; +exports.validateOptional = validateOptional; +exports.validateOptionalType = validateOptionalType; +exports.validateType = validateType; +var _is = __nccwpck_require__(20051); +var _validate = __nccwpck_require__(71581); +const VISITOR_KEYS = exports.VISITOR_KEYS = {}; +const ALIAS_KEYS = exports.ALIAS_KEYS = {}; +const FLIPPED_ALIAS_KEYS = exports.FLIPPED_ALIAS_KEYS = {}; +const NODE_FIELDS = exports.NODE_FIELDS = {}; +const BUILDER_KEYS = exports.BUILDER_KEYS = {}; +const DEPRECATED_KEYS = exports.DEPRECATED_KEYS = {}; +const NODE_PARENT_VALIDATIONS = exports.NODE_PARENT_VALIDATIONS = {}; +function getType(val) { + if (Array.isArray(val)) { + return "array"; + } else if (val === null) { + return "null"; + } else { + return typeof val; + } +} +function validate(validate) { + return { + validate + }; +} +function validateType(...typeNames) { + return validate(assertNodeType(...typeNames)); +} +function validateOptional(validate) { + return { + validate, + optional: true + }; +} +function validateOptionalType(...typeNames) { + return { + validate: assertNodeType(...typeNames), + optional: true + }; +} +function arrayOf(elementType) { + return chain(assertValueType("array"), assertEach(elementType)); +} +function arrayOfType(...typeNames) { + return arrayOf(assertNodeType(...typeNames)); +} +function validateArrayOfType(...typeNames) { + return validate(arrayOfType(...typeNames)); +} +function assertEach(callback) { + const childValidator = process.env.BABEL_TYPES_8_BREAKING ? _validate.validateChild : () => {}; + function validator(node, key, val) { + if (!Array.isArray(val)) return; + let i = 0; + const subKey = { + toString() { + return `${key}[${i}]`; + } + }; + for (; i < val.length; i++) { + const v = val[i]; + callback(node, subKey, v); + childValidator(node, subKey, v); + } + } + validator.each = callback; + return validator; +} +function assertOneOf(...values) { + function validate(node, key, val) { + if (!values.includes(val)) { + throw new TypeError(`Property ${key} expected value to be one of ${JSON.stringify(values)} but got ${JSON.stringify(val)}`); + } + } + validate.oneOf = values; + return validate; +} +const allExpandedTypes = exports.allExpandedTypes = []; +function assertNodeType(...types) { + const expandedTypes = new Set(); + allExpandedTypes.push({ + types, + set: expandedTypes + }); + function validate(node, key, val) { + const valType = val == null ? void 0 : val.type; + if (valType != null) { + if (expandedTypes.has(valType)) { + (0, _validate.validateChild)(node, key, val); + return; + } + if (valType === "Placeholder") { + for (const type of types) { + if ((0, _is.default)(type, val)) { + (0, _validate.validateChild)(node, key, val); + return; + } + } + } + } + throw new TypeError(`Property ${key} of ${node.type} expected node to be of a type ${JSON.stringify(types)} but instead got ${JSON.stringify(valType)}`); + } + validate.oneOfNodeTypes = types; + return validate; +} +function assertNodeOrValueType(...types) { + function validate(node, key, val) { + const primitiveType = getType(val); + for (const type of types) { + if (primitiveType === type || (0, _is.default)(type, val)) { + (0, _validate.validateChild)(node, key, val); + return; + } + } + throw new TypeError(`Property ${key} of ${node.type} expected node to be of a type ${JSON.stringify(types)} but instead got ${JSON.stringify(val == null ? void 0 : val.type)}`); + } + validate.oneOfNodeOrValueTypes = types; + return validate; +} +function assertValueType(type) { + function validate(node, key, val) { + if (getType(val) === type) { + return; + } + throw new TypeError(`Property ${key} expected type of ${type} but got ${getType(val)}`); + } + validate.type = type; + return validate; +} +function assertShape(shape) { + const keys = Object.keys(shape); + function validate(node, key, val) { + const errors = []; + for (const property of keys) { + try { + (0, _validate.validateField)(node, property, val[property], shape[property]); + } catch (error) { + if (error instanceof TypeError) { + errors.push(error.message); + continue; + } + throw error; + } + } + if (errors.length) { + throw new TypeError(`Property ${key} of ${node.type} expected to have the following:\n${errors.join("\n")}`); + } + } + validate.shapeOf = shape; + return validate; +} +function assertOptionalChainStart() { + function validate(node) { + var _current; + let current = node; + while (node) { + const { + type + } = current; + if (type === "OptionalCallExpression") { + if (current.optional) return; + current = current.callee; + continue; + } + if (type === "OptionalMemberExpression") { + if (current.optional) return; + current = current.object; + continue; + } + break; + } + throw new TypeError(`Non-optional ${node.type} must chain from an optional OptionalMemberExpression or OptionalCallExpression. Found chain from ${(_current = current) == null ? void 0 : _current.type}`); + } + return validate; +} +function chain(...fns) { + function validate(...args) { + for (const fn of fns) { + fn(...args); + } + } + validate.chainOf = fns; + if (fns.length >= 2 && "type" in fns[0] && fns[0].type === "array" && !("each" in fns[1])) { + throw new Error(`An assertValueType("array") validator can only be followed by an assertEach(...) validator.`); + } + return validate; +} +const validTypeOpts = new Set(["aliases", "builder", "deprecatedAlias", "fields", "inherits", "visitor", "validate"]); +const validFieldKeys = new Set(["default", "optional", "deprecated", "validate"]); +const store = {}; +function defineAliasedType(...aliases) { + return (type, opts = {}) => { + let defined = opts.aliases; + if (!defined) { + var _store$opts$inherits$; + if (opts.inherits) defined = (_store$opts$inherits$ = store[opts.inherits].aliases) == null ? void 0 : _store$opts$inherits$.slice(); + defined != null ? defined : defined = []; + opts.aliases = defined; + } + const additional = aliases.filter(a => !defined.includes(a)); + defined.unshift(...additional); + defineType(type, opts); + }; +} +function defineType(type, opts = {}) { + const inherits = opts.inherits && store[opts.inherits] || {}; + let fields = opts.fields; + if (!fields) { + fields = {}; + if (inherits.fields) { + const keys = Object.getOwnPropertyNames(inherits.fields); + for (const key of keys) { + const field = inherits.fields[key]; + const def = field.default; + if (Array.isArray(def) ? def.length > 0 : def && typeof def === "object") { + throw new Error("field defaults can only be primitives or empty arrays currently"); + } + fields[key] = { + default: Array.isArray(def) ? [] : def, + optional: field.optional, + deprecated: field.deprecated, + validate: field.validate + }; + } + } + } + const visitor = opts.visitor || inherits.visitor || []; + const aliases = opts.aliases || inherits.aliases || []; + const builder = opts.builder || inherits.builder || opts.visitor || []; + for (const k of Object.keys(opts)) { + if (!validTypeOpts.has(k)) { + throw new Error(`Unknown type option "${k}" on ${type}`); + } + } + if (opts.deprecatedAlias) { + DEPRECATED_KEYS[opts.deprecatedAlias] = type; + } + for (const key of visitor.concat(builder)) { + fields[key] = fields[key] || {}; + } + for (const key of Object.keys(fields)) { + const field = fields[key]; + if (field.default !== undefined && !builder.includes(key)) { + field.optional = true; + } + if (field.default === undefined) { + field.default = null; + } else if (!field.validate && field.default != null) { + field.validate = assertValueType(getType(field.default)); + } + for (const k of Object.keys(field)) { + if (!validFieldKeys.has(k)) { + throw new Error(`Unknown field key "${k}" on ${type}.${key}`); + } + } + } + VISITOR_KEYS[type] = opts.visitor = visitor; + BUILDER_KEYS[type] = opts.builder = builder; + NODE_FIELDS[type] = opts.fields = fields; + ALIAS_KEYS[type] = opts.aliases = aliases; + aliases.forEach(alias => { + FLIPPED_ALIAS_KEYS[alias] = FLIPPED_ALIAS_KEYS[alias] || []; + FLIPPED_ALIAS_KEYS[alias].push(type); + }); + if (opts.validate) { + NODE_PARENT_VALIDATIONS[type] = opts.validate; + } + store[type] = opts; +} + +//# sourceMappingURL=utils.js.map + + +/***/ }), + +/***/ 16535: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +var _exportNames = { + react: true, + assertNode: true, + createTypeAnnotationBasedOnTypeof: true, + createUnionTypeAnnotation: true, + createFlowUnionType: true, + createTSUnionType: true, + cloneNode: true, + clone: true, + cloneDeep: true, + cloneDeepWithoutLoc: true, + cloneWithoutLoc: true, + addComment: true, + addComments: true, + inheritInnerComments: true, + inheritLeadingComments: true, + inheritsComments: true, + inheritTrailingComments: true, + removeComments: true, + ensureBlock: true, + toBindingIdentifierName: true, + toBlock: true, + toComputedKey: true, + toExpression: true, + toIdentifier: true, + toKeyAlias: true, + toStatement: true, + valueToNode: true, + appendToMemberExpression: true, + inherits: true, + prependToMemberExpression: true, + removeProperties: true, + removePropertiesDeep: true, + removeTypeDuplicates: true, + getAssignmentIdentifiers: true, + getBindingIdentifiers: true, + getOuterBindingIdentifiers: true, + getFunctionName: true, + traverse: true, + traverseFast: true, + shallowEqual: true, + is: true, + isBinding: true, + isBlockScoped: true, + isImmutable: true, + isLet: true, + isNode: true, + isNodesEquivalent: true, + isPlaceholderType: true, + isReferenced: true, + isScope: true, + isSpecifierDefault: true, + isType: true, + isValidES3Identifier: true, + isValidIdentifier: true, + isVar: true, + matchesPattern: true, + validate: true, + buildMatchMemberExpression: true, + __internal__deprecationWarning: true +}; +Object.defineProperty(exports, "__internal__deprecationWarning", ({ + enumerable: true, + get: function () { + return _deprecationWarning.default; + } +})); +Object.defineProperty(exports, "addComment", ({ + enumerable: true, + get: function () { + return _addComment.default; + } +})); +Object.defineProperty(exports, "addComments", ({ + enumerable: true, + get: function () { + return _addComments.default; + } +})); +Object.defineProperty(exports, "appendToMemberExpression", ({ + enumerable: true, + get: function () { + return _appendToMemberExpression.default; + } +})); +Object.defineProperty(exports, "assertNode", ({ + enumerable: true, + get: function () { + return _assertNode.default; + } +})); +Object.defineProperty(exports, "buildMatchMemberExpression", ({ + enumerable: true, + get: function () { + return _buildMatchMemberExpression.default; + } +})); +Object.defineProperty(exports, "clone", ({ + enumerable: true, + get: function () { + return _clone.default; + } +})); +Object.defineProperty(exports, "cloneDeep", ({ + enumerable: true, + get: function () { + return _cloneDeep.default; + } +})); +Object.defineProperty(exports, "cloneDeepWithoutLoc", ({ + enumerable: true, + get: function () { + return _cloneDeepWithoutLoc.default; + } +})); +Object.defineProperty(exports, "cloneNode", ({ + enumerable: true, + get: function () { + return _cloneNode.default; + } +})); +Object.defineProperty(exports, "cloneWithoutLoc", ({ + enumerable: true, + get: function () { + return _cloneWithoutLoc.default; + } +})); +Object.defineProperty(exports, "createFlowUnionType", ({ + enumerable: true, + get: function () { + return _createFlowUnionType.default; + } +})); +Object.defineProperty(exports, "createTSUnionType", ({ + enumerable: true, + get: function () { + return _createTSUnionType.default; + } +})); +Object.defineProperty(exports, "createTypeAnnotationBasedOnTypeof", ({ + enumerable: true, + get: function () { + return _createTypeAnnotationBasedOnTypeof.default; + } +})); +Object.defineProperty(exports, "createUnionTypeAnnotation", ({ + enumerable: true, + get: function () { + return _createFlowUnionType.default; + } +})); +Object.defineProperty(exports, "ensureBlock", ({ + enumerable: true, + get: function () { + return _ensureBlock.default; + } +})); +Object.defineProperty(exports, "getAssignmentIdentifiers", ({ + enumerable: true, + get: function () { + return _getAssignmentIdentifiers.default; + } +})); +Object.defineProperty(exports, "getBindingIdentifiers", ({ + enumerable: true, + get: function () { + return _getBindingIdentifiers.default; + } +})); +Object.defineProperty(exports, "getFunctionName", ({ + enumerable: true, + get: function () { + return _getFunctionName.default; + } +})); +Object.defineProperty(exports, "getOuterBindingIdentifiers", ({ + enumerable: true, + get: function () { + return _getOuterBindingIdentifiers.default; + } +})); +Object.defineProperty(exports, "inheritInnerComments", ({ + enumerable: true, + get: function () { + return _inheritInnerComments.default; + } +})); +Object.defineProperty(exports, "inheritLeadingComments", ({ + enumerable: true, + get: function () { + return _inheritLeadingComments.default; + } +})); +Object.defineProperty(exports, "inheritTrailingComments", ({ + enumerable: true, + get: function () { + return _inheritTrailingComments.default; + } +})); +Object.defineProperty(exports, "inherits", ({ + enumerable: true, + get: function () { + return _inherits.default; + } +})); +Object.defineProperty(exports, "inheritsComments", ({ + enumerable: true, + get: function () { + return _inheritsComments.default; + } +})); +Object.defineProperty(exports, "is", ({ + enumerable: true, + get: function () { + return _is.default; + } +})); +Object.defineProperty(exports, "isBinding", ({ + enumerable: true, + get: function () { + return _isBinding.default; + } +})); +Object.defineProperty(exports, "isBlockScoped", ({ + enumerable: true, + get: function () { + return _isBlockScoped.default; + } +})); +Object.defineProperty(exports, "isImmutable", ({ + enumerable: true, + get: function () { + return _isImmutable.default; + } +})); +Object.defineProperty(exports, "isLet", ({ + enumerable: true, + get: function () { + return _isLet.default; + } +})); +Object.defineProperty(exports, "isNode", ({ + enumerable: true, + get: function () { + return _isNode.default; + } +})); +Object.defineProperty(exports, "isNodesEquivalent", ({ + enumerable: true, + get: function () { + return _isNodesEquivalent.default; + } +})); +Object.defineProperty(exports, "isPlaceholderType", ({ + enumerable: true, + get: function () { + return _isPlaceholderType.default; + } +})); +Object.defineProperty(exports, "isReferenced", ({ + enumerable: true, + get: function () { + return _isReferenced.default; + } +})); +Object.defineProperty(exports, "isScope", ({ + enumerable: true, + get: function () { + return _isScope.default; + } +})); +Object.defineProperty(exports, "isSpecifierDefault", ({ + enumerable: true, + get: function () { + return _isSpecifierDefault.default; + } +})); +Object.defineProperty(exports, "isType", ({ + enumerable: true, + get: function () { + return _isType.default; + } +})); +Object.defineProperty(exports, "isValidES3Identifier", ({ + enumerable: true, + get: function () { + return _isValidES3Identifier.default; + } +})); +Object.defineProperty(exports, "isValidIdentifier", ({ + enumerable: true, + get: function () { + return _isValidIdentifier.default; + } +})); +Object.defineProperty(exports, "isVar", ({ + enumerable: true, + get: function () { + return _isVar.default; + } +})); +Object.defineProperty(exports, "matchesPattern", ({ + enumerable: true, + get: function () { + return _matchesPattern.default; + } +})); +Object.defineProperty(exports, "prependToMemberExpression", ({ + enumerable: true, + get: function () { + return _prependToMemberExpression.default; + } +})); +exports.react = void 0; +Object.defineProperty(exports, "removeComments", ({ + enumerable: true, + get: function () { + return _removeComments.default; + } +})); +Object.defineProperty(exports, "removeProperties", ({ + enumerable: true, + get: function () { + return _removeProperties.default; + } +})); +Object.defineProperty(exports, "removePropertiesDeep", ({ + enumerable: true, + get: function () { + return _removePropertiesDeep.default; + } +})); +Object.defineProperty(exports, "removeTypeDuplicates", ({ + enumerable: true, + get: function () { + return _removeTypeDuplicates.default; + } +})); +Object.defineProperty(exports, "shallowEqual", ({ + enumerable: true, + get: function () { + return _shallowEqual.default; + } +})); +Object.defineProperty(exports, "toBindingIdentifierName", ({ + enumerable: true, + get: function () { + return _toBindingIdentifierName.default; + } +})); +Object.defineProperty(exports, "toBlock", ({ + enumerable: true, + get: function () { + return _toBlock.default; + } +})); +Object.defineProperty(exports, "toComputedKey", ({ + enumerable: true, + get: function () { + return _toComputedKey.default; + } +})); +Object.defineProperty(exports, "toExpression", ({ + enumerable: true, + get: function () { + return _toExpression.default; + } +})); +Object.defineProperty(exports, "toIdentifier", ({ + enumerable: true, + get: function () { + return _toIdentifier.default; + } +})); +Object.defineProperty(exports, "toKeyAlias", ({ + enumerable: true, + get: function () { + return _toKeyAlias.default; + } +})); +Object.defineProperty(exports, "toStatement", ({ + enumerable: true, + get: function () { + return _toStatement.default; + } +})); +Object.defineProperty(exports, "traverse", ({ + enumerable: true, + get: function () { + return _traverse.default; + } +})); +Object.defineProperty(exports, "traverseFast", ({ + enumerable: true, + get: function () { + return _traverseFast.default; + } +})); +Object.defineProperty(exports, "validate", ({ + enumerable: true, + get: function () { + return _validate.default; + } +})); +Object.defineProperty(exports, "valueToNode", ({ + enumerable: true, + get: function () { + return _valueToNode.default; + } +})); +var _isReactComponent = __nccwpck_require__(24513); +var _isCompatTag = __nccwpck_require__(20817); +var _buildChildren = __nccwpck_require__(93415); +var _assertNode = __nccwpck_require__(41737); +var _index = __nccwpck_require__(98345); +Object.keys(_index).forEach(function (key) { + if (key === "default" || key === "__esModule") return; + if (Object.prototype.hasOwnProperty.call(_exportNames, key)) return; + if (key in exports && exports[key] === _index[key]) return; + Object.defineProperty(exports, key, { + enumerable: true, + get: function () { + return _index[key]; + } + }); +}); +var _createTypeAnnotationBasedOnTypeof = __nccwpck_require__(9197); +var _createFlowUnionType = __nccwpck_require__(58806); +var _createTSUnionType = __nccwpck_require__(15766); +var _productions = __nccwpck_require__(38504); +Object.keys(_productions).forEach(function (key) { + if (key === "default" || key === "__esModule") return; + if (Object.prototype.hasOwnProperty.call(_exportNames, key)) return; + if (key in exports && exports[key] === _productions[key]) return; + Object.defineProperty(exports, key, { + enumerable: true, + get: function () { + return _productions[key]; + } + }); +}); +var _index2 = __nccwpck_require__(90670); +Object.keys(_index2).forEach(function (key) { + if (key === "default" || key === "__esModule") return; + if (Object.prototype.hasOwnProperty.call(_exportNames, key)) return; + if (key in exports && exports[key] === _index2[key]) return; + Object.defineProperty(exports, key, { + enumerable: true, + get: function () { + return _index2[key]; + } + }); +}); +var _cloneNode = __nccwpck_require__(89260); +var _clone = __nccwpck_require__(59260); +var _cloneDeep = __nccwpck_require__(45922); +var _cloneDeepWithoutLoc = __nccwpck_require__(37992); +var _cloneWithoutLoc = __nccwpck_require__(19258); +var _addComment = __nccwpck_require__(70704); +var _addComments = __nccwpck_require__(21071); +var _inheritInnerComments = __nccwpck_require__(40731); +var _inheritLeadingComments = __nccwpck_require__(7725); +var _inheritsComments = __nccwpck_require__(32078); +var _inheritTrailingComments = __nccwpck_require__(23491); +var _removeComments = __nccwpck_require__(84066); +var _index3 = __nccwpck_require__(12359); +Object.keys(_index3).forEach(function (key) { + if (key === "default" || key === "__esModule") return; + if (Object.prototype.hasOwnProperty.call(_exportNames, key)) return; + if (key in exports && exports[key] === _index3[key]) return; + Object.defineProperty(exports, key, { + enumerable: true, + get: function () { + return _index3[key]; + } + }); +}); +var _index4 = __nccwpck_require__(17945); +Object.keys(_index4).forEach(function (key) { + if (key === "default" || key === "__esModule") return; + if (Object.prototype.hasOwnProperty.call(_exportNames, key)) return; + if (key in exports && exports[key] === _index4[key]) return; + Object.defineProperty(exports, key, { + enumerable: true, + get: function () { + return _index4[key]; + } + }); +}); +var _ensureBlock = __nccwpck_require__(77046); +var _toBindingIdentifierName = __nccwpck_require__(19687); +var _toBlock = __nccwpck_require__(59735); +var _toComputedKey = __nccwpck_require__(49480); +var _toExpression = __nccwpck_require__(36490); +var _toIdentifier = __nccwpck_require__(59451); +var _toKeyAlias = __nccwpck_require__(23381); +var _toStatement = __nccwpck_require__(82635); +var _valueToNode = __nccwpck_require__(20021); +var _index5 = __nccwpck_require__(40910); +Object.keys(_index5).forEach(function (key) { + if (key === "default" || key === "__esModule") return; + if (Object.prototype.hasOwnProperty.call(_exportNames, key)) return; + if (key in exports && exports[key] === _index5[key]) return; + Object.defineProperty(exports, key, { + enumerable: true, + get: function () { + return _index5[key]; + } + }); +}); +var _appendToMemberExpression = __nccwpck_require__(4948); +var _inherits = __nccwpck_require__(40257); +var _prependToMemberExpression = __nccwpck_require__(78564); +var _removeProperties = __nccwpck_require__(6656); +var _removePropertiesDeep = __nccwpck_require__(55902); +var _removeTypeDuplicates = __nccwpck_require__(41034); +var _getAssignmentIdentifiers = __nccwpck_require__(21228); +var _getBindingIdentifiers = __nccwpck_require__(45300); +var _getOuterBindingIdentifiers = __nccwpck_require__(46815); +var _getFunctionName = __nccwpck_require__(48836); +var _traverse = __nccwpck_require__(6878); +Object.keys(_traverse).forEach(function (key) { + if (key === "default" || key === "__esModule") return; + if (Object.prototype.hasOwnProperty.call(_exportNames, key)) return; + if (key in exports && exports[key] === _traverse[key]) return; + Object.defineProperty(exports, key, { + enumerable: true, + get: function () { + return _traverse[key]; + } + }); +}); +var _traverseFast = __nccwpck_require__(47060); +var _shallowEqual = __nccwpck_require__(56085); +var _is = __nccwpck_require__(20051); +var _isBinding = __nccwpck_require__(73998); +var _isBlockScoped = __nccwpck_require__(942); +var _isImmutable = __nccwpck_require__(42343); +var _isLet = __nccwpck_require__(57674); +var _isNode = __nccwpck_require__(84503); +var _isNodesEquivalent = __nccwpck_require__(8338); +var _isPlaceholderType = __nccwpck_require__(49410); +var _isReferenced = __nccwpck_require__(89508); +var _isScope = __nccwpck_require__(33583); +var _isSpecifierDefault = __nccwpck_require__(64394); +var _isType = __nccwpck_require__(53547); +var _isValidES3Identifier = __nccwpck_require__(40267); +var _isValidIdentifier = __nccwpck_require__(66030); +var _isVar = __nccwpck_require__(21566); +var _matchesPattern = __nccwpck_require__(47814); +var _validate = __nccwpck_require__(71581); +var _buildMatchMemberExpression = __nccwpck_require__(77334); +var _index6 = __nccwpck_require__(40741); +Object.keys(_index6).forEach(function (key) { + if (key === "default" || key === "__esModule") return; + if (Object.prototype.hasOwnProperty.call(_exportNames, key)) return; + if (key in exports && exports[key] === _index6[key]) return; + Object.defineProperty(exports, key, { + enumerable: true, + get: function () { + return _index6[key]; + } + }); +}); +var _deprecationWarning = __nccwpck_require__(14711); +var _toSequenceExpression = __nccwpck_require__(403); +const react = exports.react = { + isReactComponent: _isReactComponent.default, + isCompatTag: _isCompatTag.default, + buildChildren: _buildChildren.default +}; +{ + exports.toSequenceExpression = _toSequenceExpression.default; +} +if (process.env.BABEL_TYPES_8_BREAKING) { + console.warn("BABEL_TYPES_8_BREAKING is not supported anymore. Use the latest Babel 8.0.0 pre-release instead!"); +} + +//# sourceMappingURL=index.js.map + + +/***/ }), + +/***/ 4948: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports["default"] = appendToMemberExpression; +var _index = __nccwpck_require__(90670); +function appendToMemberExpression(member, append, computed = false) { + member.object = (0, _index.memberExpression)(member.object, member.property, member.computed); + member.property = append; + member.computed = !!computed; + return member; +} + +//# sourceMappingURL=appendToMemberExpression.js.map + + +/***/ }), + +/***/ 41034: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports["default"] = removeTypeDuplicates; +var _index = __nccwpck_require__(40741); +function getQualifiedName(node) { + return (0, _index.isIdentifier)(node) ? node.name : `${node.id.name}.${getQualifiedName(node.qualification)}`; +} +function removeTypeDuplicates(nodesIn) { + const nodes = Array.from(nodesIn); + const generics = new Map(); + const bases = new Map(); + const typeGroups = new Set(); + const types = []; + for (let i = 0; i < nodes.length; i++) { + const node = nodes[i]; + if (!node) continue; + if (types.includes(node)) { + continue; + } + if ((0, _index.isAnyTypeAnnotation)(node)) { + return [node]; + } + if ((0, _index.isFlowBaseAnnotation)(node)) { + bases.set(node.type, node); + continue; + } + if ((0, _index.isUnionTypeAnnotation)(node)) { + if (!typeGroups.has(node.types)) { + nodes.push(...node.types); + typeGroups.add(node.types); + } + continue; + } + if ((0, _index.isGenericTypeAnnotation)(node)) { + const name = getQualifiedName(node.id); + if (generics.has(name)) { + let existing = generics.get(name); + if (existing.typeParameters) { + if (node.typeParameters) { + existing.typeParameters.params.push(...node.typeParameters.params); + existing.typeParameters.params = removeTypeDuplicates(existing.typeParameters.params); + } + } else { + existing = node.typeParameters; + } + } else { + generics.set(name, node); + } + continue; + } + types.push(node); + } + for (const [, baseType] of bases) { + types.push(baseType); + } + for (const [, genericName] of generics) { + types.push(genericName); + } + return types; +} + +//# sourceMappingURL=removeTypeDuplicates.js.map + + +/***/ }), + +/***/ 40257: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports["default"] = inherits; +var _index = __nccwpck_require__(17945); +var _inheritsComments = __nccwpck_require__(32078); +function inherits(child, parent) { + if (!child || !parent) return child; + for (const key of _index.INHERIT_KEYS.optional) { + if (child[key] == null) { + child[key] = parent[key]; + } + } + for (const key of Object.keys(parent)) { + if (key[0] === "_" && key !== "__clone") { + child[key] = parent[key]; + } + } + for (const key of _index.INHERIT_KEYS.force) { + child[key] = parent[key]; + } + (0, _inheritsComments.default)(child, parent); + return child; +} + +//# sourceMappingURL=inherits.js.map + + +/***/ }), + +/***/ 78564: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports["default"] = prependToMemberExpression; +var _index = __nccwpck_require__(90670); +var _index2 = __nccwpck_require__(16535); +function prependToMemberExpression(member, prepend) { + if ((0, _index2.isSuper)(member.object)) { + throw new Error("Cannot prepend node to super property access (`super.foo`)."); + } + member.object = (0, _index.memberExpression)(prepend, member.object); + return member; +} + +//# sourceMappingURL=prependToMemberExpression.js.map + + +/***/ }), + +/***/ 6656: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports["default"] = removeProperties; +var _index = __nccwpck_require__(17945); +const CLEAR_KEYS = ["tokens", "start", "end", "loc", "raw", "rawValue"]; +const CLEAR_KEYS_PLUS_COMMENTS = [..._index.COMMENT_KEYS, "comments", ...CLEAR_KEYS]; +function removeProperties(node, opts = {}) { + const map = opts.preserveComments ? CLEAR_KEYS : CLEAR_KEYS_PLUS_COMMENTS; + for (const key of map) { + if (node[key] != null) node[key] = undefined; + } + for (const key of Object.keys(node)) { + if (key[0] === "_" && node[key] != null) node[key] = undefined; + } + const symbols = Object.getOwnPropertySymbols(node); + for (const sym of symbols) { + node[sym] = null; + } +} + +//# sourceMappingURL=removeProperties.js.map + + +/***/ }), + +/***/ 55902: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports["default"] = removePropertiesDeep; +var _traverseFast = __nccwpck_require__(47060); +var _removeProperties = __nccwpck_require__(6656); +function removePropertiesDeep(tree, opts) { + (0, _traverseFast.default)(tree, _removeProperties.default, opts); + return tree; +} + +//# sourceMappingURL=removePropertiesDeep.js.map + + +/***/ }), + +/***/ 76123: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports["default"] = removeTypeDuplicates; +var _index = __nccwpck_require__(40741); +function getQualifiedName(node) { + return (0, _index.isIdentifier)(node) ? node.name : (0, _index.isThisExpression)(node) ? "this" : `${node.right.name}.${getQualifiedName(node.left)}`; +} +function removeTypeDuplicates(nodesIn) { + const nodes = Array.from(nodesIn); + const generics = new Map(); + const bases = new Map(); + const typeGroups = new Set(); + const types = []; + for (let i = 0; i < nodes.length; i++) { + const node = nodes[i]; + if (!node) continue; + if (types.includes(node)) { + continue; + } + if ((0, _index.isTSAnyKeyword)(node)) { + return [node]; + } + if ((0, _index.isTSBaseType)(node)) { + bases.set(node.type, node); + continue; + } + if ((0, _index.isTSUnionType)(node)) { + if (!typeGroups.has(node.types)) { + nodes.push(...node.types); + typeGroups.add(node.types); + } + continue; + } + const typeArgumentsKey = "typeParameters"; + if ((0, _index.isTSTypeReference)(node) && node[typeArgumentsKey]) { + const typeArguments = node[typeArgumentsKey]; + const name = getQualifiedName(node.typeName); + if (generics.has(name)) { + let existing = generics.get(name); + const existingTypeArguments = existing[typeArgumentsKey]; + if (existingTypeArguments) { + existingTypeArguments.params.push(...typeArguments.params); + existingTypeArguments.params = removeTypeDuplicates(existingTypeArguments.params); + } else { + existing = typeArguments; + } + } else { + generics.set(name, node); + } + continue; + } + types.push(node); + } + for (const [, baseType] of bases) { + types.push(baseType); + } + for (const [, genericName] of generics) { + types.push(genericName); + } + return types; +} + +//# sourceMappingURL=removeTypeDuplicates.js.map + + +/***/ }), + +/***/ 21228: +/***/ ((__unused_webpack_module, exports) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports["default"] = getAssignmentIdentifiers; +function getAssignmentIdentifiers(node) { + const search = [].concat(node); + const ids = Object.create(null); + while (search.length) { + const id = search.pop(); + if (!id) continue; + switch (id.type) { + case "ArrayPattern": + search.push(...id.elements); + break; + case "AssignmentExpression": + case "AssignmentPattern": + case "ForInStatement": + case "ForOfStatement": + search.push(id.left); + break; + case "ObjectPattern": + search.push(...id.properties); + break; + case "ObjectProperty": + search.push(id.value); + break; + case "RestElement": + case "UpdateExpression": + search.push(id.argument); + break; + case "UnaryExpression": + if (id.operator === "delete") { + search.push(id.argument); + } + break; + case "Identifier": + ids[id.name] = id; + break; + default: + break; + } + } + return ids; +} + +//# sourceMappingURL=getAssignmentIdentifiers.js.map + + +/***/ }), + +/***/ 45300: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports["default"] = getBindingIdentifiers; +var _index = __nccwpck_require__(40741); +function getBindingIdentifiers(node, duplicates, outerOnly, newBindingsOnly) { + const search = [].concat(node); + const ids = Object.create(null); + while (search.length) { + const id = search.shift(); + if (!id) continue; + if (newBindingsOnly && ((0, _index.isAssignmentExpression)(id) || (0, _index.isUnaryExpression)(id) || (0, _index.isUpdateExpression)(id))) { + continue; + } + if ((0, _index.isIdentifier)(id)) { + if (duplicates) { + const _ids = ids[id.name] = ids[id.name] || []; + _ids.push(id); + } else { + ids[id.name] = id; + } + continue; + } + if ((0, _index.isExportDeclaration)(id) && !(0, _index.isExportAllDeclaration)(id)) { + if ((0, _index.isDeclaration)(id.declaration)) { + search.push(id.declaration); + } + continue; + } + if (outerOnly) { + if ((0, _index.isFunctionDeclaration)(id)) { + search.push(id.id); + continue; + } + if ((0, _index.isFunctionExpression)(id)) { + continue; + } + } + const keys = getBindingIdentifiers.keys[id.type]; + if (keys) { + for (let i = 0; i < keys.length; i++) { + const key = keys[i]; + const nodes = id[key]; + if (nodes) { + if (Array.isArray(nodes)) { + search.push(...nodes); + } else { + search.push(nodes); + } + } + } + } + } + return ids; +} +const keys = { + DeclareClass: ["id"], + DeclareFunction: ["id"], + DeclareModule: ["id"], + DeclareVariable: ["id"], + DeclareInterface: ["id"], + DeclareTypeAlias: ["id"], + DeclareOpaqueType: ["id"], + InterfaceDeclaration: ["id"], + TypeAlias: ["id"], + OpaqueType: ["id"], + CatchClause: ["param"], + LabeledStatement: ["label"], + UnaryExpression: ["argument"], + AssignmentExpression: ["left"], + ImportSpecifier: ["local"], + ImportNamespaceSpecifier: ["local"], + ImportDefaultSpecifier: ["local"], + ImportDeclaration: ["specifiers"], + TSImportEqualsDeclaration: ["id"], + ExportSpecifier: ["exported"], + ExportNamespaceSpecifier: ["exported"], + ExportDefaultSpecifier: ["exported"], + FunctionDeclaration: ["id", "params"], + FunctionExpression: ["id", "params"], + ArrowFunctionExpression: ["params"], + ObjectMethod: ["params"], + ClassMethod: ["params"], + ClassPrivateMethod: ["params"], + ForInStatement: ["left"], + ForOfStatement: ["left"], + ClassDeclaration: ["id"], + ClassExpression: ["id"], + RestElement: ["argument"], + UpdateExpression: ["argument"], + ObjectProperty: ["value"], + AssignmentPattern: ["left"], + ArrayPattern: ["elements"], + ObjectPattern: ["properties"], + VariableDeclaration: ["declarations"], + VariableDeclarator: ["id"] +}; +getBindingIdentifiers.keys = keys; + +//# sourceMappingURL=getBindingIdentifiers.js.map + + +/***/ }), + +/***/ 48836: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports["default"] = getFunctionName; +var _index = __nccwpck_require__(40741); +function getNameFromLiteralId(id) { + if ((0, _index.isNullLiteral)(id)) { + return "null"; + } + if ((0, _index.isRegExpLiteral)(id)) { + return `/${id.pattern}/${id.flags}`; + } + if ((0, _index.isTemplateLiteral)(id)) { + return id.quasis.map(quasi => quasi.value.raw).join(""); + } + if (id.value !== undefined) { + return String(id.value); + } + return null; +} +function getObjectMemberKey(node) { + if (!node.computed || (0, _index.isLiteral)(node.key)) { + return node.key; + } +} +function getFunctionName(node, parent) { + if ("id" in node && node.id) { + return { + name: node.id.name, + originalNode: node.id + }; + } + let prefix = ""; + let id; + if ((0, _index.isObjectProperty)(parent, { + value: node + })) { + id = getObjectMemberKey(parent); + } else if ((0, _index.isObjectMethod)(node) || (0, _index.isClassMethod)(node)) { + id = getObjectMemberKey(node); + if (node.kind === "get") prefix = "get ";else if (node.kind === "set") prefix = "set "; + } else if ((0, _index.isVariableDeclarator)(parent, { + init: node + })) { + id = parent.id; + } else if ((0, _index.isAssignmentExpression)(parent, { + operator: "=", + right: node + })) { + id = parent.left; + } + if (!id) return null; + const name = (0, _index.isLiteral)(id) ? getNameFromLiteralId(id) : (0, _index.isIdentifier)(id) ? id.name : (0, _index.isPrivateName)(id) ? id.id.name : null; + if (name == null) return null; + return { + name: prefix + name, + originalNode: id + }; +} + +//# sourceMappingURL=getFunctionName.js.map + + +/***/ }), + +/***/ 46815: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports["default"] = void 0; +var _getBindingIdentifiers = __nccwpck_require__(45300); +var _default = exports["default"] = getOuterBindingIdentifiers; +function getOuterBindingIdentifiers(node, duplicates) { + return (0, _getBindingIdentifiers.default)(node, duplicates, true); +} + +//# sourceMappingURL=getOuterBindingIdentifiers.js.map + + +/***/ }), + +/***/ 6878: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports["default"] = traverse; +var _index = __nccwpck_require__(40910); +function traverse(node, handlers, state) { + if (typeof handlers === "function") { + handlers = { + enter: handlers + }; + } + const { + enter, + exit + } = handlers; + traverseSimpleImpl(node, enter, exit, state, []); +} +function traverseSimpleImpl(node, enter, exit, state, ancestors) { + const keys = _index.VISITOR_KEYS[node.type]; + if (!keys) return; + if (enter) enter(node, ancestors, state); + for (const key of keys) { + const subNode = node[key]; + if (Array.isArray(subNode)) { + for (let i = 0; i < subNode.length; i++) { + const child = subNode[i]; + if (!child) continue; + ancestors.push({ + node, + key, + index: i + }); + traverseSimpleImpl(child, enter, exit, state, ancestors); + ancestors.pop(); + } + } else if (subNode) { + ancestors.push({ + node, + key + }); + traverseSimpleImpl(subNode, enter, exit, state, ancestors); + ancestors.pop(); + } + } + if (exit) exit(node, ancestors, state); +} + +//# sourceMappingURL=traverse.js.map + + +/***/ }), + +/***/ 47060: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports["default"] = traverseFast; +var _index = __nccwpck_require__(40910); +const _skip = Symbol(); +const _stop = Symbol(); +function traverseFast(node, enter, opts) { + if (!node) return false; + const keys = _index.VISITOR_KEYS[node.type]; + if (!keys) return false; + opts = opts || {}; + const ret = enter(node, opts); + if (ret !== undefined) { + switch (ret) { + case _skip: + return false; + case _stop: + return true; + } + } + for (const key of keys) { + const subNode = node[key]; + if (!subNode) continue; + if (Array.isArray(subNode)) { + for (const node of subNode) { + if (traverseFast(node, enter, opts)) return true; + } + } else { + if (traverseFast(subNode, enter, opts)) return true; + } + } + return false; +} +traverseFast.skip = _skip; +traverseFast.stop = _stop; + +//# sourceMappingURL=traverseFast.js.map + + +/***/ }), + +/***/ 14711: +/***/ ((__unused_webpack_module, exports) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports["default"] = deprecationWarning; +const warnings = new Set(); +function deprecationWarning(oldName, newName, prefix = "", cacheKey = oldName) { + if (warnings.has(cacheKey)) return; + warnings.add(cacheKey); + const { + internal, + trace + } = captureShortStackTrace(1, 2); + if (internal) { + return; + } + console.warn(`${prefix}\`${oldName}\` has been deprecated, please migrate to \`${newName}\`\n${trace}`); +} +function captureShortStackTrace(skip, length) { + const { + stackTraceLimit, + prepareStackTrace + } = Error; + let stackTrace; + Error.stackTraceLimit = 1 + skip + length; + Error.prepareStackTrace = function (err, stack) { + stackTrace = stack; + }; + new Error().stack; + Error.stackTraceLimit = stackTraceLimit; + Error.prepareStackTrace = prepareStackTrace; + if (!stackTrace) return { + internal: false, + trace: "" + }; + const shortStackTrace = stackTrace.slice(1 + skip, 1 + skip + length); + return { + internal: /[\\/]@babel[\\/]/.test(shortStackTrace[1].getFileName()), + trace: shortStackTrace.map(frame => ` at ${frame}`).join("\n") + }; +} + +//# sourceMappingURL=deprecationWarning.js.map + + +/***/ }), + +/***/ 40066: +/***/ ((__unused_webpack_module, exports) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports["default"] = inherit; +function inherit(key, child, parent) { + if (child && parent) { + child[key] = Array.from(new Set([].concat(child[key], parent[key]).filter(Boolean))); + } +} + +//# sourceMappingURL=inherit.js.map + + +/***/ }), + +/***/ 43508: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports["default"] = cleanJSXElementLiteralChild; +var _index = __nccwpck_require__(90670); +var _index2 = __nccwpck_require__(16535); +function cleanJSXElementLiteralChild(child, args) { + const lines = child.value.split(/\r\n|\n|\r/); + let lastNonEmptyLine = 0; + for (let i = 0; i < lines.length; i++) { + if (/[^ \t]/.exec(lines[i])) { + lastNonEmptyLine = i; + } + } + let str = ""; + for (let i = 0; i < lines.length; i++) { + const line = lines[i]; + const isFirstLine = i === 0; + const isLastLine = i === lines.length - 1; + const isLastNonEmptyLine = i === lastNonEmptyLine; + let trimmedLine = line.replace(/\t/g, " "); + if (!isFirstLine) { + trimmedLine = trimmedLine.replace(/^ +/, ""); + } + if (!isLastLine) { + trimmedLine = trimmedLine.replace(/ +$/, ""); + } + if (trimmedLine) { + if (!isLastNonEmptyLine) { + trimmedLine += " "; + } + str += trimmedLine; + } + } + if (str) args.push((0, _index2.inherits)((0, _index.stringLiteral)(str), child)); +} + +//# sourceMappingURL=cleanJSXElementLiteralChild.js.map + + +/***/ }), + +/***/ 56085: +/***/ ((__unused_webpack_module, exports) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports["default"] = shallowEqual; +function shallowEqual(actual, expected) { + const keys = Object.keys(expected); + for (const key of keys) { + if (actual[key] !== expected[key]) { + return false; + } + } + return true; +} + +//# sourceMappingURL=shallowEqual.js.map + + +/***/ }), + +/***/ 77334: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports["default"] = buildMatchMemberExpression; +var _matchesPattern = __nccwpck_require__(47814); +function buildMatchMemberExpression(match, allowPartial) { + const parts = match.split("."); + return member => (0, _matchesPattern.default)(member, parts, allowPartial); +} + +//# sourceMappingURL=buildMatchMemberExpression.js.map + + +/***/ }), + +/***/ 40741: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports.isAccessor = isAccessor; +exports.isAnyTypeAnnotation = isAnyTypeAnnotation; +exports.isArgumentPlaceholder = isArgumentPlaceholder; +exports.isArrayExpression = isArrayExpression; +exports.isArrayPattern = isArrayPattern; +exports.isArrayTypeAnnotation = isArrayTypeAnnotation; +exports.isArrowFunctionExpression = isArrowFunctionExpression; +exports.isAssignmentExpression = isAssignmentExpression; +exports.isAssignmentPattern = isAssignmentPattern; +exports.isAwaitExpression = isAwaitExpression; +exports.isBigIntLiteral = isBigIntLiteral; +exports.isBinary = isBinary; +exports.isBinaryExpression = isBinaryExpression; +exports.isBindExpression = isBindExpression; +exports.isBlock = isBlock; +exports.isBlockParent = isBlockParent; +exports.isBlockStatement = isBlockStatement; +exports.isBooleanLiteral = isBooleanLiteral; +exports.isBooleanLiteralTypeAnnotation = isBooleanLiteralTypeAnnotation; +exports.isBooleanTypeAnnotation = isBooleanTypeAnnotation; +exports.isBreakStatement = isBreakStatement; +exports.isCallExpression = isCallExpression; +exports.isCatchClause = isCatchClause; +exports.isClass = isClass; +exports.isClassAccessorProperty = isClassAccessorProperty; +exports.isClassBody = isClassBody; +exports.isClassDeclaration = isClassDeclaration; +exports.isClassExpression = isClassExpression; +exports.isClassImplements = isClassImplements; +exports.isClassMethod = isClassMethod; +exports.isClassPrivateMethod = isClassPrivateMethod; +exports.isClassPrivateProperty = isClassPrivateProperty; +exports.isClassProperty = isClassProperty; +exports.isCompletionStatement = isCompletionStatement; +exports.isConditional = isConditional; +exports.isConditionalExpression = isConditionalExpression; +exports.isContinueStatement = isContinueStatement; +exports.isDebuggerStatement = isDebuggerStatement; +exports.isDecimalLiteral = isDecimalLiteral; +exports.isDeclaration = isDeclaration; +exports.isDeclareClass = isDeclareClass; +exports.isDeclareExportAllDeclaration = isDeclareExportAllDeclaration; +exports.isDeclareExportDeclaration = isDeclareExportDeclaration; +exports.isDeclareFunction = isDeclareFunction; +exports.isDeclareInterface = isDeclareInterface; +exports.isDeclareModule = isDeclareModule; +exports.isDeclareModuleExports = isDeclareModuleExports; +exports.isDeclareOpaqueType = isDeclareOpaqueType; +exports.isDeclareTypeAlias = isDeclareTypeAlias; +exports.isDeclareVariable = isDeclareVariable; +exports.isDeclaredPredicate = isDeclaredPredicate; +exports.isDecorator = isDecorator; +exports.isDirective = isDirective; +exports.isDirectiveLiteral = isDirectiveLiteral; +exports.isDoExpression = isDoExpression; +exports.isDoWhileStatement = isDoWhileStatement; +exports.isEmptyStatement = isEmptyStatement; +exports.isEmptyTypeAnnotation = isEmptyTypeAnnotation; +exports.isEnumBody = isEnumBody; +exports.isEnumBooleanBody = isEnumBooleanBody; +exports.isEnumBooleanMember = isEnumBooleanMember; +exports.isEnumDeclaration = isEnumDeclaration; +exports.isEnumDefaultedMember = isEnumDefaultedMember; +exports.isEnumMember = isEnumMember; +exports.isEnumNumberBody = isEnumNumberBody; +exports.isEnumNumberMember = isEnumNumberMember; +exports.isEnumStringBody = isEnumStringBody; +exports.isEnumStringMember = isEnumStringMember; +exports.isEnumSymbolBody = isEnumSymbolBody; +exports.isExistsTypeAnnotation = isExistsTypeAnnotation; +exports.isExportAllDeclaration = isExportAllDeclaration; +exports.isExportDeclaration = isExportDeclaration; +exports.isExportDefaultDeclaration = isExportDefaultDeclaration; +exports.isExportDefaultSpecifier = isExportDefaultSpecifier; +exports.isExportNamedDeclaration = isExportNamedDeclaration; +exports.isExportNamespaceSpecifier = isExportNamespaceSpecifier; +exports.isExportSpecifier = isExportSpecifier; +exports.isExpression = isExpression; +exports.isExpressionStatement = isExpressionStatement; +exports.isExpressionWrapper = isExpressionWrapper; +exports.isFile = isFile; +exports.isFlow = isFlow; +exports.isFlowBaseAnnotation = isFlowBaseAnnotation; +exports.isFlowDeclaration = isFlowDeclaration; +exports.isFlowPredicate = isFlowPredicate; +exports.isFlowType = isFlowType; +exports.isFor = isFor; +exports.isForInStatement = isForInStatement; +exports.isForOfStatement = isForOfStatement; +exports.isForStatement = isForStatement; +exports.isForXStatement = isForXStatement; +exports.isFunction = isFunction; +exports.isFunctionDeclaration = isFunctionDeclaration; +exports.isFunctionExpression = isFunctionExpression; +exports.isFunctionParent = isFunctionParent; +exports.isFunctionTypeAnnotation = isFunctionTypeAnnotation; +exports.isFunctionTypeParam = isFunctionTypeParam; +exports.isGenericTypeAnnotation = isGenericTypeAnnotation; +exports.isIdentifier = isIdentifier; +exports.isIfStatement = isIfStatement; +exports.isImmutable = isImmutable; +exports.isImport = isImport; +exports.isImportAttribute = isImportAttribute; +exports.isImportDeclaration = isImportDeclaration; +exports.isImportDefaultSpecifier = isImportDefaultSpecifier; +exports.isImportExpression = isImportExpression; +exports.isImportNamespaceSpecifier = isImportNamespaceSpecifier; +exports.isImportOrExportDeclaration = isImportOrExportDeclaration; +exports.isImportSpecifier = isImportSpecifier; +exports.isIndexedAccessType = isIndexedAccessType; +exports.isInferredPredicate = isInferredPredicate; +exports.isInterfaceDeclaration = isInterfaceDeclaration; +exports.isInterfaceExtends = isInterfaceExtends; +exports.isInterfaceTypeAnnotation = isInterfaceTypeAnnotation; +exports.isInterpreterDirective = isInterpreterDirective; +exports.isIntersectionTypeAnnotation = isIntersectionTypeAnnotation; +exports.isJSX = isJSX; +exports.isJSXAttribute = isJSXAttribute; +exports.isJSXClosingElement = isJSXClosingElement; +exports.isJSXClosingFragment = isJSXClosingFragment; +exports.isJSXElement = isJSXElement; +exports.isJSXEmptyExpression = isJSXEmptyExpression; +exports.isJSXExpressionContainer = isJSXExpressionContainer; +exports.isJSXFragment = isJSXFragment; +exports.isJSXIdentifier = isJSXIdentifier; +exports.isJSXMemberExpression = isJSXMemberExpression; +exports.isJSXNamespacedName = isJSXNamespacedName; +exports.isJSXOpeningElement = isJSXOpeningElement; +exports.isJSXOpeningFragment = isJSXOpeningFragment; +exports.isJSXSpreadAttribute = isJSXSpreadAttribute; +exports.isJSXSpreadChild = isJSXSpreadChild; +exports.isJSXText = isJSXText; +exports.isLVal = isLVal; +exports.isLabeledStatement = isLabeledStatement; +exports.isLiteral = isLiteral; +exports.isLogicalExpression = isLogicalExpression; +exports.isLoop = isLoop; +exports.isMemberExpression = isMemberExpression; +exports.isMetaProperty = isMetaProperty; +exports.isMethod = isMethod; +exports.isMiscellaneous = isMiscellaneous; +exports.isMixedTypeAnnotation = isMixedTypeAnnotation; +exports.isModuleDeclaration = isModuleDeclaration; +exports.isModuleExpression = isModuleExpression; +exports.isModuleSpecifier = isModuleSpecifier; +exports.isNewExpression = isNewExpression; +exports.isNoop = isNoop; +exports.isNullLiteral = isNullLiteral; +exports.isNullLiteralTypeAnnotation = isNullLiteralTypeAnnotation; +exports.isNullableTypeAnnotation = isNullableTypeAnnotation; +exports.isNumberLiteral = isNumberLiteral; +exports.isNumberLiteralTypeAnnotation = isNumberLiteralTypeAnnotation; +exports.isNumberTypeAnnotation = isNumberTypeAnnotation; +exports.isNumericLiteral = isNumericLiteral; +exports.isObjectExpression = isObjectExpression; +exports.isObjectMember = isObjectMember; +exports.isObjectMethod = isObjectMethod; +exports.isObjectPattern = isObjectPattern; +exports.isObjectProperty = isObjectProperty; +exports.isObjectTypeAnnotation = isObjectTypeAnnotation; +exports.isObjectTypeCallProperty = isObjectTypeCallProperty; +exports.isObjectTypeIndexer = isObjectTypeIndexer; +exports.isObjectTypeInternalSlot = isObjectTypeInternalSlot; +exports.isObjectTypeProperty = isObjectTypeProperty; +exports.isObjectTypeSpreadProperty = isObjectTypeSpreadProperty; +exports.isOpaqueType = isOpaqueType; +exports.isOptionalCallExpression = isOptionalCallExpression; +exports.isOptionalIndexedAccessType = isOptionalIndexedAccessType; +exports.isOptionalMemberExpression = isOptionalMemberExpression; +exports.isParenthesizedExpression = isParenthesizedExpression; +exports.isPattern = isPattern; +exports.isPatternLike = isPatternLike; +exports.isPipelineBareFunction = isPipelineBareFunction; +exports.isPipelinePrimaryTopicReference = isPipelinePrimaryTopicReference; +exports.isPipelineTopicExpression = isPipelineTopicExpression; +exports.isPlaceholder = isPlaceholder; +exports.isPrivate = isPrivate; +exports.isPrivateName = isPrivateName; +exports.isProgram = isProgram; +exports.isProperty = isProperty; +exports.isPureish = isPureish; +exports.isQualifiedTypeIdentifier = isQualifiedTypeIdentifier; +exports.isRecordExpression = isRecordExpression; +exports.isRegExpLiteral = isRegExpLiteral; +exports.isRegexLiteral = isRegexLiteral; +exports.isRestElement = isRestElement; +exports.isRestProperty = isRestProperty; +exports.isReturnStatement = isReturnStatement; +exports.isScopable = isScopable; +exports.isSequenceExpression = isSequenceExpression; +exports.isSpreadElement = isSpreadElement; +exports.isSpreadProperty = isSpreadProperty; +exports.isStandardized = isStandardized; +exports.isStatement = isStatement; +exports.isStaticBlock = isStaticBlock; +exports.isStringLiteral = isStringLiteral; +exports.isStringLiteralTypeAnnotation = isStringLiteralTypeAnnotation; +exports.isStringTypeAnnotation = isStringTypeAnnotation; +exports.isSuper = isSuper; +exports.isSwitchCase = isSwitchCase; +exports.isSwitchStatement = isSwitchStatement; +exports.isSymbolTypeAnnotation = isSymbolTypeAnnotation; +exports.isTSAnyKeyword = isTSAnyKeyword; +exports.isTSArrayType = isTSArrayType; +exports.isTSAsExpression = isTSAsExpression; +exports.isTSBaseType = isTSBaseType; +exports.isTSBigIntKeyword = isTSBigIntKeyword; +exports.isTSBooleanKeyword = isTSBooleanKeyword; +exports.isTSCallSignatureDeclaration = isTSCallSignatureDeclaration; +exports.isTSConditionalType = isTSConditionalType; +exports.isTSConstructSignatureDeclaration = isTSConstructSignatureDeclaration; +exports.isTSConstructorType = isTSConstructorType; +exports.isTSDeclareFunction = isTSDeclareFunction; +exports.isTSDeclareMethod = isTSDeclareMethod; +exports.isTSEntityName = isTSEntityName; +exports.isTSEnumBody = isTSEnumBody; +exports.isTSEnumDeclaration = isTSEnumDeclaration; +exports.isTSEnumMember = isTSEnumMember; +exports.isTSExportAssignment = isTSExportAssignment; +exports.isTSExpressionWithTypeArguments = isTSExpressionWithTypeArguments; +exports.isTSExternalModuleReference = isTSExternalModuleReference; +exports.isTSFunctionType = isTSFunctionType; +exports.isTSImportEqualsDeclaration = isTSImportEqualsDeclaration; +exports.isTSImportType = isTSImportType; +exports.isTSIndexSignature = isTSIndexSignature; +exports.isTSIndexedAccessType = isTSIndexedAccessType; +exports.isTSInferType = isTSInferType; +exports.isTSInstantiationExpression = isTSInstantiationExpression; +exports.isTSInterfaceBody = isTSInterfaceBody; +exports.isTSInterfaceDeclaration = isTSInterfaceDeclaration; +exports.isTSIntersectionType = isTSIntersectionType; +exports.isTSIntrinsicKeyword = isTSIntrinsicKeyword; +exports.isTSLiteralType = isTSLiteralType; +exports.isTSMappedType = isTSMappedType; +exports.isTSMethodSignature = isTSMethodSignature; +exports.isTSModuleBlock = isTSModuleBlock; +exports.isTSModuleDeclaration = isTSModuleDeclaration; +exports.isTSNamedTupleMember = isTSNamedTupleMember; +exports.isTSNamespaceExportDeclaration = isTSNamespaceExportDeclaration; +exports.isTSNeverKeyword = isTSNeverKeyword; +exports.isTSNonNullExpression = isTSNonNullExpression; +exports.isTSNullKeyword = isTSNullKeyword; +exports.isTSNumberKeyword = isTSNumberKeyword; +exports.isTSObjectKeyword = isTSObjectKeyword; +exports.isTSOptionalType = isTSOptionalType; +exports.isTSParameterProperty = isTSParameterProperty; +exports.isTSParenthesizedType = isTSParenthesizedType; +exports.isTSPropertySignature = isTSPropertySignature; +exports.isTSQualifiedName = isTSQualifiedName; +exports.isTSRestType = isTSRestType; +exports.isTSSatisfiesExpression = isTSSatisfiesExpression; +exports.isTSStringKeyword = isTSStringKeyword; +exports.isTSSymbolKeyword = isTSSymbolKeyword; +exports.isTSTemplateLiteralType = isTSTemplateLiteralType; +exports.isTSThisType = isTSThisType; +exports.isTSTupleType = isTSTupleType; +exports.isTSType = isTSType; +exports.isTSTypeAliasDeclaration = isTSTypeAliasDeclaration; +exports.isTSTypeAnnotation = isTSTypeAnnotation; +exports.isTSTypeAssertion = isTSTypeAssertion; +exports.isTSTypeElement = isTSTypeElement; +exports.isTSTypeLiteral = isTSTypeLiteral; +exports.isTSTypeOperator = isTSTypeOperator; +exports.isTSTypeParameter = isTSTypeParameter; +exports.isTSTypeParameterDeclaration = isTSTypeParameterDeclaration; +exports.isTSTypeParameterInstantiation = isTSTypeParameterInstantiation; +exports.isTSTypePredicate = isTSTypePredicate; +exports.isTSTypeQuery = isTSTypeQuery; +exports.isTSTypeReference = isTSTypeReference; +exports.isTSUndefinedKeyword = isTSUndefinedKeyword; +exports.isTSUnionType = isTSUnionType; +exports.isTSUnknownKeyword = isTSUnknownKeyword; +exports.isTSVoidKeyword = isTSVoidKeyword; +exports.isTaggedTemplateExpression = isTaggedTemplateExpression; +exports.isTemplateElement = isTemplateElement; +exports.isTemplateLiteral = isTemplateLiteral; +exports.isTerminatorless = isTerminatorless; +exports.isThisExpression = isThisExpression; +exports.isThisTypeAnnotation = isThisTypeAnnotation; +exports.isThrowStatement = isThrowStatement; +exports.isTopicReference = isTopicReference; +exports.isTryStatement = isTryStatement; +exports.isTupleExpression = isTupleExpression; +exports.isTupleTypeAnnotation = isTupleTypeAnnotation; +exports.isTypeAlias = isTypeAlias; +exports.isTypeAnnotation = isTypeAnnotation; +exports.isTypeCastExpression = isTypeCastExpression; +exports.isTypeParameter = isTypeParameter; +exports.isTypeParameterDeclaration = isTypeParameterDeclaration; +exports.isTypeParameterInstantiation = isTypeParameterInstantiation; +exports.isTypeScript = isTypeScript; +exports.isTypeofTypeAnnotation = isTypeofTypeAnnotation; +exports.isUnaryExpression = isUnaryExpression; +exports.isUnaryLike = isUnaryLike; +exports.isUnionTypeAnnotation = isUnionTypeAnnotation; +exports.isUpdateExpression = isUpdateExpression; +exports.isUserWhitespacable = isUserWhitespacable; +exports.isV8IntrinsicIdentifier = isV8IntrinsicIdentifier; +exports.isVariableDeclaration = isVariableDeclaration; +exports.isVariableDeclarator = isVariableDeclarator; +exports.isVariance = isVariance; +exports.isVoidTypeAnnotation = isVoidTypeAnnotation; +exports.isWhile = isWhile; +exports.isWhileStatement = isWhileStatement; +exports.isWithStatement = isWithStatement; +exports.isYieldExpression = isYieldExpression; +var _shallowEqual = __nccwpck_require__(56085); +var _deprecationWarning = __nccwpck_require__(14711); +function isArrayExpression(node, opts) { + if (!node) return false; + if (node.type !== "ArrayExpression") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isAssignmentExpression(node, opts) { + if (!node) return false; + if (node.type !== "AssignmentExpression") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isBinaryExpression(node, opts) { + if (!node) return false; + if (node.type !== "BinaryExpression") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isInterpreterDirective(node, opts) { + if (!node) return false; + if (node.type !== "InterpreterDirective") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isDirective(node, opts) { + if (!node) return false; + if (node.type !== "Directive") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isDirectiveLiteral(node, opts) { + if (!node) return false; + if (node.type !== "DirectiveLiteral") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isBlockStatement(node, opts) { + if (!node) return false; + if (node.type !== "BlockStatement") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isBreakStatement(node, opts) { + if (!node) return false; + if (node.type !== "BreakStatement") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isCallExpression(node, opts) { + if (!node) return false; + if (node.type !== "CallExpression") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isCatchClause(node, opts) { + if (!node) return false; + if (node.type !== "CatchClause") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isConditionalExpression(node, opts) { + if (!node) return false; + if (node.type !== "ConditionalExpression") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isContinueStatement(node, opts) { + if (!node) return false; + if (node.type !== "ContinueStatement") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isDebuggerStatement(node, opts) { + if (!node) return false; + if (node.type !== "DebuggerStatement") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isDoWhileStatement(node, opts) { + if (!node) return false; + if (node.type !== "DoWhileStatement") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isEmptyStatement(node, opts) { + if (!node) return false; + if (node.type !== "EmptyStatement") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isExpressionStatement(node, opts) { + if (!node) return false; + if (node.type !== "ExpressionStatement") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isFile(node, opts) { + if (!node) return false; + if (node.type !== "File") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isForInStatement(node, opts) { + if (!node) return false; + if (node.type !== "ForInStatement") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isForStatement(node, opts) { + if (!node) return false; + if (node.type !== "ForStatement") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isFunctionDeclaration(node, opts) { + if (!node) return false; + if (node.type !== "FunctionDeclaration") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isFunctionExpression(node, opts) { + if (!node) return false; + if (node.type !== "FunctionExpression") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isIdentifier(node, opts) { + if (!node) return false; + if (node.type !== "Identifier") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isIfStatement(node, opts) { + if (!node) return false; + if (node.type !== "IfStatement") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isLabeledStatement(node, opts) { + if (!node) return false; + if (node.type !== "LabeledStatement") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isStringLiteral(node, opts) { + if (!node) return false; + if (node.type !== "StringLiteral") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isNumericLiteral(node, opts) { + if (!node) return false; + if (node.type !== "NumericLiteral") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isNullLiteral(node, opts) { + if (!node) return false; + if (node.type !== "NullLiteral") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isBooleanLiteral(node, opts) { + if (!node) return false; + if (node.type !== "BooleanLiteral") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isRegExpLiteral(node, opts) { + if (!node) return false; + if (node.type !== "RegExpLiteral") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isLogicalExpression(node, opts) { + if (!node) return false; + if (node.type !== "LogicalExpression") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isMemberExpression(node, opts) { + if (!node) return false; + if (node.type !== "MemberExpression") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isNewExpression(node, opts) { + if (!node) return false; + if (node.type !== "NewExpression") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isProgram(node, opts) { + if (!node) return false; + if (node.type !== "Program") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isObjectExpression(node, opts) { + if (!node) return false; + if (node.type !== "ObjectExpression") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isObjectMethod(node, opts) { + if (!node) return false; + if (node.type !== "ObjectMethod") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isObjectProperty(node, opts) { + if (!node) return false; + if (node.type !== "ObjectProperty") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isRestElement(node, opts) { + if (!node) return false; + if (node.type !== "RestElement") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isReturnStatement(node, opts) { + if (!node) return false; + if (node.type !== "ReturnStatement") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isSequenceExpression(node, opts) { + if (!node) return false; + if (node.type !== "SequenceExpression") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isParenthesizedExpression(node, opts) { + if (!node) return false; + if (node.type !== "ParenthesizedExpression") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isSwitchCase(node, opts) { + if (!node) return false; + if (node.type !== "SwitchCase") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isSwitchStatement(node, opts) { + if (!node) return false; + if (node.type !== "SwitchStatement") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isThisExpression(node, opts) { + if (!node) return false; + if (node.type !== "ThisExpression") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isThrowStatement(node, opts) { + if (!node) return false; + if (node.type !== "ThrowStatement") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isTryStatement(node, opts) { + if (!node) return false; + if (node.type !== "TryStatement") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isUnaryExpression(node, opts) { + if (!node) return false; + if (node.type !== "UnaryExpression") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isUpdateExpression(node, opts) { + if (!node) return false; + if (node.type !== "UpdateExpression") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isVariableDeclaration(node, opts) { + if (!node) return false; + if (node.type !== "VariableDeclaration") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isVariableDeclarator(node, opts) { + if (!node) return false; + if (node.type !== "VariableDeclarator") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isWhileStatement(node, opts) { + if (!node) return false; + if (node.type !== "WhileStatement") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isWithStatement(node, opts) { + if (!node) return false; + if (node.type !== "WithStatement") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isAssignmentPattern(node, opts) { + if (!node) return false; + if (node.type !== "AssignmentPattern") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isArrayPattern(node, opts) { + if (!node) return false; + if (node.type !== "ArrayPattern") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isArrowFunctionExpression(node, opts) { + if (!node) return false; + if (node.type !== "ArrowFunctionExpression") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isClassBody(node, opts) { + if (!node) return false; + if (node.type !== "ClassBody") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isClassExpression(node, opts) { + if (!node) return false; + if (node.type !== "ClassExpression") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isClassDeclaration(node, opts) { + if (!node) return false; + if (node.type !== "ClassDeclaration") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isExportAllDeclaration(node, opts) { + if (!node) return false; + if (node.type !== "ExportAllDeclaration") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isExportDefaultDeclaration(node, opts) { + if (!node) return false; + if (node.type !== "ExportDefaultDeclaration") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isExportNamedDeclaration(node, opts) { + if (!node) return false; + if (node.type !== "ExportNamedDeclaration") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isExportSpecifier(node, opts) { + if (!node) return false; + if (node.type !== "ExportSpecifier") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isForOfStatement(node, opts) { + if (!node) return false; + if (node.type !== "ForOfStatement") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isImportDeclaration(node, opts) { + if (!node) return false; + if (node.type !== "ImportDeclaration") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isImportDefaultSpecifier(node, opts) { + if (!node) return false; + if (node.type !== "ImportDefaultSpecifier") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isImportNamespaceSpecifier(node, opts) { + if (!node) return false; + if (node.type !== "ImportNamespaceSpecifier") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isImportSpecifier(node, opts) { + if (!node) return false; + if (node.type !== "ImportSpecifier") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isImportExpression(node, opts) { + if (!node) return false; + if (node.type !== "ImportExpression") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isMetaProperty(node, opts) { + if (!node) return false; + if (node.type !== "MetaProperty") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isClassMethod(node, opts) { + if (!node) return false; + if (node.type !== "ClassMethod") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isObjectPattern(node, opts) { + if (!node) return false; + if (node.type !== "ObjectPattern") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isSpreadElement(node, opts) { + if (!node) return false; + if (node.type !== "SpreadElement") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isSuper(node, opts) { + if (!node) return false; + if (node.type !== "Super") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isTaggedTemplateExpression(node, opts) { + if (!node) return false; + if (node.type !== "TaggedTemplateExpression") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isTemplateElement(node, opts) { + if (!node) return false; + if (node.type !== "TemplateElement") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isTemplateLiteral(node, opts) { + if (!node) return false; + if (node.type !== "TemplateLiteral") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isYieldExpression(node, opts) { + if (!node) return false; + if (node.type !== "YieldExpression") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isAwaitExpression(node, opts) { + if (!node) return false; + if (node.type !== "AwaitExpression") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isImport(node, opts) { + if (!node) return false; + if (node.type !== "Import") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isBigIntLiteral(node, opts) { + if (!node) return false; + if (node.type !== "BigIntLiteral") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isExportNamespaceSpecifier(node, opts) { + if (!node) return false; + if (node.type !== "ExportNamespaceSpecifier") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isOptionalMemberExpression(node, opts) { + if (!node) return false; + if (node.type !== "OptionalMemberExpression") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isOptionalCallExpression(node, opts) { + if (!node) return false; + if (node.type !== "OptionalCallExpression") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isClassProperty(node, opts) { + if (!node) return false; + if (node.type !== "ClassProperty") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isClassAccessorProperty(node, opts) { + if (!node) return false; + if (node.type !== "ClassAccessorProperty") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isClassPrivateProperty(node, opts) { + if (!node) return false; + if (node.type !== "ClassPrivateProperty") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isClassPrivateMethod(node, opts) { + if (!node) return false; + if (node.type !== "ClassPrivateMethod") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isPrivateName(node, opts) { + if (!node) return false; + if (node.type !== "PrivateName") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isStaticBlock(node, opts) { + if (!node) return false; + if (node.type !== "StaticBlock") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isImportAttribute(node, opts) { + if (!node) return false; + if (node.type !== "ImportAttribute") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isAnyTypeAnnotation(node, opts) { + if (!node) return false; + if (node.type !== "AnyTypeAnnotation") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isArrayTypeAnnotation(node, opts) { + if (!node) return false; + if (node.type !== "ArrayTypeAnnotation") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isBooleanTypeAnnotation(node, opts) { + if (!node) return false; + if (node.type !== "BooleanTypeAnnotation") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isBooleanLiteralTypeAnnotation(node, opts) { + if (!node) return false; + if (node.type !== "BooleanLiteralTypeAnnotation") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isNullLiteralTypeAnnotation(node, opts) { + if (!node) return false; + if (node.type !== "NullLiteralTypeAnnotation") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isClassImplements(node, opts) { + if (!node) return false; + if (node.type !== "ClassImplements") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isDeclareClass(node, opts) { + if (!node) return false; + if (node.type !== "DeclareClass") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isDeclareFunction(node, opts) { + if (!node) return false; + if (node.type !== "DeclareFunction") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isDeclareInterface(node, opts) { + if (!node) return false; + if (node.type !== "DeclareInterface") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isDeclareModule(node, opts) { + if (!node) return false; + if (node.type !== "DeclareModule") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isDeclareModuleExports(node, opts) { + if (!node) return false; + if (node.type !== "DeclareModuleExports") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isDeclareTypeAlias(node, opts) { + if (!node) return false; + if (node.type !== "DeclareTypeAlias") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isDeclareOpaqueType(node, opts) { + if (!node) return false; + if (node.type !== "DeclareOpaqueType") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isDeclareVariable(node, opts) { + if (!node) return false; + if (node.type !== "DeclareVariable") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isDeclareExportDeclaration(node, opts) { + if (!node) return false; + if (node.type !== "DeclareExportDeclaration") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isDeclareExportAllDeclaration(node, opts) { + if (!node) return false; + if (node.type !== "DeclareExportAllDeclaration") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isDeclaredPredicate(node, opts) { + if (!node) return false; + if (node.type !== "DeclaredPredicate") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isExistsTypeAnnotation(node, opts) { + if (!node) return false; + if (node.type !== "ExistsTypeAnnotation") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isFunctionTypeAnnotation(node, opts) { + if (!node) return false; + if (node.type !== "FunctionTypeAnnotation") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isFunctionTypeParam(node, opts) { + if (!node) return false; + if (node.type !== "FunctionTypeParam") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isGenericTypeAnnotation(node, opts) { + if (!node) return false; + if (node.type !== "GenericTypeAnnotation") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isInferredPredicate(node, opts) { + if (!node) return false; + if (node.type !== "InferredPredicate") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isInterfaceExtends(node, opts) { + if (!node) return false; + if (node.type !== "InterfaceExtends") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isInterfaceDeclaration(node, opts) { + if (!node) return false; + if (node.type !== "InterfaceDeclaration") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isInterfaceTypeAnnotation(node, opts) { + if (!node) return false; + if (node.type !== "InterfaceTypeAnnotation") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isIntersectionTypeAnnotation(node, opts) { + if (!node) return false; + if (node.type !== "IntersectionTypeAnnotation") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isMixedTypeAnnotation(node, opts) { + if (!node) return false; + if (node.type !== "MixedTypeAnnotation") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isEmptyTypeAnnotation(node, opts) { + if (!node) return false; + if (node.type !== "EmptyTypeAnnotation") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isNullableTypeAnnotation(node, opts) { + if (!node) return false; + if (node.type !== "NullableTypeAnnotation") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isNumberLiteralTypeAnnotation(node, opts) { + if (!node) return false; + if (node.type !== "NumberLiteralTypeAnnotation") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isNumberTypeAnnotation(node, opts) { + if (!node) return false; + if (node.type !== "NumberTypeAnnotation") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isObjectTypeAnnotation(node, opts) { + if (!node) return false; + if (node.type !== "ObjectTypeAnnotation") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isObjectTypeInternalSlot(node, opts) { + if (!node) return false; + if (node.type !== "ObjectTypeInternalSlot") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isObjectTypeCallProperty(node, opts) { + if (!node) return false; + if (node.type !== "ObjectTypeCallProperty") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isObjectTypeIndexer(node, opts) { + if (!node) return false; + if (node.type !== "ObjectTypeIndexer") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isObjectTypeProperty(node, opts) { + if (!node) return false; + if (node.type !== "ObjectTypeProperty") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isObjectTypeSpreadProperty(node, opts) { + if (!node) return false; + if (node.type !== "ObjectTypeSpreadProperty") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isOpaqueType(node, opts) { + if (!node) return false; + if (node.type !== "OpaqueType") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isQualifiedTypeIdentifier(node, opts) { + if (!node) return false; + if (node.type !== "QualifiedTypeIdentifier") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isStringLiteralTypeAnnotation(node, opts) { + if (!node) return false; + if (node.type !== "StringLiteralTypeAnnotation") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isStringTypeAnnotation(node, opts) { + if (!node) return false; + if (node.type !== "StringTypeAnnotation") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isSymbolTypeAnnotation(node, opts) { + if (!node) return false; + if (node.type !== "SymbolTypeAnnotation") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isThisTypeAnnotation(node, opts) { + if (!node) return false; + if (node.type !== "ThisTypeAnnotation") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isTupleTypeAnnotation(node, opts) { + if (!node) return false; + if (node.type !== "TupleTypeAnnotation") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isTypeofTypeAnnotation(node, opts) { + if (!node) return false; + if (node.type !== "TypeofTypeAnnotation") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isTypeAlias(node, opts) { + if (!node) return false; + if (node.type !== "TypeAlias") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isTypeAnnotation(node, opts) { + if (!node) return false; + if (node.type !== "TypeAnnotation") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isTypeCastExpression(node, opts) { + if (!node) return false; + if (node.type !== "TypeCastExpression") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isTypeParameter(node, opts) { + if (!node) return false; + if (node.type !== "TypeParameter") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isTypeParameterDeclaration(node, opts) { + if (!node) return false; + if (node.type !== "TypeParameterDeclaration") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isTypeParameterInstantiation(node, opts) { + if (!node) return false; + if (node.type !== "TypeParameterInstantiation") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isUnionTypeAnnotation(node, opts) { + if (!node) return false; + if (node.type !== "UnionTypeAnnotation") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isVariance(node, opts) { + if (!node) return false; + if (node.type !== "Variance") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isVoidTypeAnnotation(node, opts) { + if (!node) return false; + if (node.type !== "VoidTypeAnnotation") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isEnumDeclaration(node, opts) { + if (!node) return false; + if (node.type !== "EnumDeclaration") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isEnumBooleanBody(node, opts) { + if (!node) return false; + if (node.type !== "EnumBooleanBody") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isEnumNumberBody(node, opts) { + if (!node) return false; + if (node.type !== "EnumNumberBody") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isEnumStringBody(node, opts) { + if (!node) return false; + if (node.type !== "EnumStringBody") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isEnumSymbolBody(node, opts) { + if (!node) return false; + if (node.type !== "EnumSymbolBody") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isEnumBooleanMember(node, opts) { + if (!node) return false; + if (node.type !== "EnumBooleanMember") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isEnumNumberMember(node, opts) { + if (!node) return false; + if (node.type !== "EnumNumberMember") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isEnumStringMember(node, opts) { + if (!node) return false; + if (node.type !== "EnumStringMember") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isEnumDefaultedMember(node, opts) { + if (!node) return false; + if (node.type !== "EnumDefaultedMember") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isIndexedAccessType(node, opts) { + if (!node) return false; + if (node.type !== "IndexedAccessType") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isOptionalIndexedAccessType(node, opts) { + if (!node) return false; + if (node.type !== "OptionalIndexedAccessType") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isJSXAttribute(node, opts) { + if (!node) return false; + if (node.type !== "JSXAttribute") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isJSXClosingElement(node, opts) { + if (!node) return false; + if (node.type !== "JSXClosingElement") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isJSXElement(node, opts) { + if (!node) return false; + if (node.type !== "JSXElement") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isJSXEmptyExpression(node, opts) { + if (!node) return false; + if (node.type !== "JSXEmptyExpression") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isJSXExpressionContainer(node, opts) { + if (!node) return false; + if (node.type !== "JSXExpressionContainer") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isJSXSpreadChild(node, opts) { + if (!node) return false; + if (node.type !== "JSXSpreadChild") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isJSXIdentifier(node, opts) { + if (!node) return false; + if (node.type !== "JSXIdentifier") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isJSXMemberExpression(node, opts) { + if (!node) return false; + if (node.type !== "JSXMemberExpression") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isJSXNamespacedName(node, opts) { + if (!node) return false; + if (node.type !== "JSXNamespacedName") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isJSXOpeningElement(node, opts) { + if (!node) return false; + if (node.type !== "JSXOpeningElement") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isJSXSpreadAttribute(node, opts) { + if (!node) return false; + if (node.type !== "JSXSpreadAttribute") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isJSXText(node, opts) { + if (!node) return false; + if (node.type !== "JSXText") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isJSXFragment(node, opts) { + if (!node) return false; + if (node.type !== "JSXFragment") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isJSXOpeningFragment(node, opts) { + if (!node) return false; + if (node.type !== "JSXOpeningFragment") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isJSXClosingFragment(node, opts) { + if (!node) return false; + if (node.type !== "JSXClosingFragment") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isNoop(node, opts) { + if (!node) return false; + if (node.type !== "Noop") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isPlaceholder(node, opts) { + if (!node) return false; + if (node.type !== "Placeholder") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isV8IntrinsicIdentifier(node, opts) { + if (!node) return false; + if (node.type !== "V8IntrinsicIdentifier") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isArgumentPlaceholder(node, opts) { + if (!node) return false; + if (node.type !== "ArgumentPlaceholder") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isBindExpression(node, opts) { + if (!node) return false; + if (node.type !== "BindExpression") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isDecorator(node, opts) { + if (!node) return false; + if (node.type !== "Decorator") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isDoExpression(node, opts) { + if (!node) return false; + if (node.type !== "DoExpression") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isExportDefaultSpecifier(node, opts) { + if (!node) return false; + if (node.type !== "ExportDefaultSpecifier") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isRecordExpression(node, opts) { + if (!node) return false; + if (node.type !== "RecordExpression") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isTupleExpression(node, opts) { + if (!node) return false; + if (node.type !== "TupleExpression") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isDecimalLiteral(node, opts) { + if (!node) return false; + if (node.type !== "DecimalLiteral") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isModuleExpression(node, opts) { + if (!node) return false; + if (node.type !== "ModuleExpression") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isTopicReference(node, opts) { + if (!node) return false; + if (node.type !== "TopicReference") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isPipelineTopicExpression(node, opts) { + if (!node) return false; + if (node.type !== "PipelineTopicExpression") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isPipelineBareFunction(node, opts) { + if (!node) return false; + if (node.type !== "PipelineBareFunction") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isPipelinePrimaryTopicReference(node, opts) { + if (!node) return false; + if (node.type !== "PipelinePrimaryTopicReference") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isTSParameterProperty(node, opts) { + if (!node) return false; + if (node.type !== "TSParameterProperty") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isTSDeclareFunction(node, opts) { + if (!node) return false; + if (node.type !== "TSDeclareFunction") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isTSDeclareMethod(node, opts) { + if (!node) return false; + if (node.type !== "TSDeclareMethod") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isTSQualifiedName(node, opts) { + if (!node) return false; + if (node.type !== "TSQualifiedName") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isTSCallSignatureDeclaration(node, opts) { + if (!node) return false; + if (node.type !== "TSCallSignatureDeclaration") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isTSConstructSignatureDeclaration(node, opts) { + if (!node) return false; + if (node.type !== "TSConstructSignatureDeclaration") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isTSPropertySignature(node, opts) { + if (!node) return false; + if (node.type !== "TSPropertySignature") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isTSMethodSignature(node, opts) { + if (!node) return false; + if (node.type !== "TSMethodSignature") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isTSIndexSignature(node, opts) { + if (!node) return false; + if (node.type !== "TSIndexSignature") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isTSAnyKeyword(node, opts) { + if (!node) return false; + if (node.type !== "TSAnyKeyword") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isTSBooleanKeyword(node, opts) { + if (!node) return false; + if (node.type !== "TSBooleanKeyword") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isTSBigIntKeyword(node, opts) { + if (!node) return false; + if (node.type !== "TSBigIntKeyword") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isTSIntrinsicKeyword(node, opts) { + if (!node) return false; + if (node.type !== "TSIntrinsicKeyword") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isTSNeverKeyword(node, opts) { + if (!node) return false; + if (node.type !== "TSNeverKeyword") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isTSNullKeyword(node, opts) { + if (!node) return false; + if (node.type !== "TSNullKeyword") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isTSNumberKeyword(node, opts) { + if (!node) return false; + if (node.type !== "TSNumberKeyword") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isTSObjectKeyword(node, opts) { + if (!node) return false; + if (node.type !== "TSObjectKeyword") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isTSStringKeyword(node, opts) { + if (!node) return false; + if (node.type !== "TSStringKeyword") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isTSSymbolKeyword(node, opts) { + if (!node) return false; + if (node.type !== "TSSymbolKeyword") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isTSUndefinedKeyword(node, opts) { + if (!node) return false; + if (node.type !== "TSUndefinedKeyword") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isTSUnknownKeyword(node, opts) { + if (!node) return false; + if (node.type !== "TSUnknownKeyword") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isTSVoidKeyword(node, opts) { + if (!node) return false; + if (node.type !== "TSVoidKeyword") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isTSThisType(node, opts) { + if (!node) return false; + if (node.type !== "TSThisType") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isTSFunctionType(node, opts) { + if (!node) return false; + if (node.type !== "TSFunctionType") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isTSConstructorType(node, opts) { + if (!node) return false; + if (node.type !== "TSConstructorType") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isTSTypeReference(node, opts) { + if (!node) return false; + if (node.type !== "TSTypeReference") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isTSTypePredicate(node, opts) { + if (!node) return false; + if (node.type !== "TSTypePredicate") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isTSTypeQuery(node, opts) { + if (!node) return false; + if (node.type !== "TSTypeQuery") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isTSTypeLiteral(node, opts) { + if (!node) return false; + if (node.type !== "TSTypeLiteral") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isTSArrayType(node, opts) { + if (!node) return false; + if (node.type !== "TSArrayType") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isTSTupleType(node, opts) { + if (!node) return false; + if (node.type !== "TSTupleType") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isTSOptionalType(node, opts) { + if (!node) return false; + if (node.type !== "TSOptionalType") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isTSRestType(node, opts) { + if (!node) return false; + if (node.type !== "TSRestType") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isTSNamedTupleMember(node, opts) { + if (!node) return false; + if (node.type !== "TSNamedTupleMember") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isTSUnionType(node, opts) { + if (!node) return false; + if (node.type !== "TSUnionType") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isTSIntersectionType(node, opts) { + if (!node) return false; + if (node.type !== "TSIntersectionType") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isTSConditionalType(node, opts) { + if (!node) return false; + if (node.type !== "TSConditionalType") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isTSInferType(node, opts) { + if (!node) return false; + if (node.type !== "TSInferType") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isTSParenthesizedType(node, opts) { + if (!node) return false; + if (node.type !== "TSParenthesizedType") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isTSTypeOperator(node, opts) { + if (!node) return false; + if (node.type !== "TSTypeOperator") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isTSIndexedAccessType(node, opts) { + if (!node) return false; + if (node.type !== "TSIndexedAccessType") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isTSMappedType(node, opts) { + if (!node) return false; + if (node.type !== "TSMappedType") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isTSTemplateLiteralType(node, opts) { + if (!node) return false; + if (node.type !== "TSTemplateLiteralType") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isTSLiteralType(node, opts) { + if (!node) return false; + if (node.type !== "TSLiteralType") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isTSExpressionWithTypeArguments(node, opts) { + if (!node) return false; + if (node.type !== "TSExpressionWithTypeArguments") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isTSInterfaceDeclaration(node, opts) { + if (!node) return false; + if (node.type !== "TSInterfaceDeclaration") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isTSInterfaceBody(node, opts) { + if (!node) return false; + if (node.type !== "TSInterfaceBody") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isTSTypeAliasDeclaration(node, opts) { + if (!node) return false; + if (node.type !== "TSTypeAliasDeclaration") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isTSInstantiationExpression(node, opts) { + if (!node) return false; + if (node.type !== "TSInstantiationExpression") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isTSAsExpression(node, opts) { + if (!node) return false; + if (node.type !== "TSAsExpression") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isTSSatisfiesExpression(node, opts) { + if (!node) return false; + if (node.type !== "TSSatisfiesExpression") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isTSTypeAssertion(node, opts) { + if (!node) return false; + if (node.type !== "TSTypeAssertion") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isTSEnumBody(node, opts) { + if (!node) return false; + if (node.type !== "TSEnumBody") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isTSEnumDeclaration(node, opts) { + if (!node) return false; + if (node.type !== "TSEnumDeclaration") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isTSEnumMember(node, opts) { + if (!node) return false; + if (node.type !== "TSEnumMember") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isTSModuleDeclaration(node, opts) { + if (!node) return false; + if (node.type !== "TSModuleDeclaration") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isTSModuleBlock(node, opts) { + if (!node) return false; + if (node.type !== "TSModuleBlock") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isTSImportType(node, opts) { + if (!node) return false; + if (node.type !== "TSImportType") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isTSImportEqualsDeclaration(node, opts) { + if (!node) return false; + if (node.type !== "TSImportEqualsDeclaration") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isTSExternalModuleReference(node, opts) { + if (!node) return false; + if (node.type !== "TSExternalModuleReference") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isTSNonNullExpression(node, opts) { + if (!node) return false; + if (node.type !== "TSNonNullExpression") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isTSExportAssignment(node, opts) { + if (!node) return false; + if (node.type !== "TSExportAssignment") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isTSNamespaceExportDeclaration(node, opts) { + if (!node) return false; + if (node.type !== "TSNamespaceExportDeclaration") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isTSTypeAnnotation(node, opts) { + if (!node) return false; + if (node.type !== "TSTypeAnnotation") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isTSTypeParameterInstantiation(node, opts) { + if (!node) return false; + if (node.type !== "TSTypeParameterInstantiation") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isTSTypeParameterDeclaration(node, opts) { + if (!node) return false; + if (node.type !== "TSTypeParameterDeclaration") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isTSTypeParameter(node, opts) { + if (!node) return false; + if (node.type !== "TSTypeParameter") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isStandardized(node, opts) { + if (!node) return false; + switch (node.type) { + case "ArrayExpression": + case "AssignmentExpression": + case "BinaryExpression": + case "InterpreterDirective": + case "Directive": + case "DirectiveLiteral": + case "BlockStatement": + case "BreakStatement": + case "CallExpression": + case "CatchClause": + case "ConditionalExpression": + case "ContinueStatement": + case "DebuggerStatement": + case "DoWhileStatement": + case "EmptyStatement": + case "ExpressionStatement": + case "File": + case "ForInStatement": + case "ForStatement": + case "FunctionDeclaration": + case "FunctionExpression": + case "Identifier": + case "IfStatement": + case "LabeledStatement": + case "StringLiteral": + case "NumericLiteral": + case "NullLiteral": + case "BooleanLiteral": + case "RegExpLiteral": + case "LogicalExpression": + case "MemberExpression": + case "NewExpression": + case "Program": + case "ObjectExpression": + case "ObjectMethod": + case "ObjectProperty": + case "RestElement": + case "ReturnStatement": + case "SequenceExpression": + case "ParenthesizedExpression": + case "SwitchCase": + case "SwitchStatement": + case "ThisExpression": + case "ThrowStatement": + case "TryStatement": + case "UnaryExpression": + case "UpdateExpression": + case "VariableDeclaration": + case "VariableDeclarator": + case "WhileStatement": + case "WithStatement": + case "AssignmentPattern": + case "ArrayPattern": + case "ArrowFunctionExpression": + case "ClassBody": + case "ClassExpression": + case "ClassDeclaration": + case "ExportAllDeclaration": + case "ExportDefaultDeclaration": + case "ExportNamedDeclaration": + case "ExportSpecifier": + case "ForOfStatement": + case "ImportDeclaration": + case "ImportDefaultSpecifier": + case "ImportNamespaceSpecifier": + case "ImportSpecifier": + case "ImportExpression": + case "MetaProperty": + case "ClassMethod": + case "ObjectPattern": + case "SpreadElement": + case "Super": + case "TaggedTemplateExpression": + case "TemplateElement": + case "TemplateLiteral": + case "YieldExpression": + case "AwaitExpression": + case "Import": + case "BigIntLiteral": + case "ExportNamespaceSpecifier": + case "OptionalMemberExpression": + case "OptionalCallExpression": + case "ClassProperty": + case "ClassAccessorProperty": + case "ClassPrivateProperty": + case "ClassPrivateMethod": + case "PrivateName": + case "StaticBlock": + case "ImportAttribute": + break; + case "Placeholder": + switch (node.expectedNode) { + case "Identifier": + case "StringLiteral": + case "BlockStatement": + case "ClassBody": + break; + default: + return false; + } + break; + default: + return false; + } + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isExpression(node, opts) { + if (!node) return false; + switch (node.type) { + case "ArrayExpression": + case "AssignmentExpression": + case "BinaryExpression": + case "CallExpression": + case "ConditionalExpression": + case "FunctionExpression": + case "Identifier": + case "StringLiteral": + case "NumericLiteral": + case "NullLiteral": + case "BooleanLiteral": + case "RegExpLiteral": + case "LogicalExpression": + case "MemberExpression": + case "NewExpression": + case "ObjectExpression": + case "SequenceExpression": + case "ParenthesizedExpression": + case "ThisExpression": + case "UnaryExpression": + case "UpdateExpression": + case "ArrowFunctionExpression": + case "ClassExpression": + case "ImportExpression": + case "MetaProperty": + case "Super": + case "TaggedTemplateExpression": + case "TemplateLiteral": + case "YieldExpression": + case "AwaitExpression": + case "Import": + case "BigIntLiteral": + case "OptionalMemberExpression": + case "OptionalCallExpression": + case "TypeCastExpression": + case "JSXElement": + case "JSXFragment": + case "BindExpression": + case "DoExpression": + case "RecordExpression": + case "TupleExpression": + case "DecimalLiteral": + case "ModuleExpression": + case "TopicReference": + case "PipelineTopicExpression": + case "PipelineBareFunction": + case "PipelinePrimaryTopicReference": + case "TSInstantiationExpression": + case "TSAsExpression": + case "TSSatisfiesExpression": + case "TSTypeAssertion": + case "TSNonNullExpression": + break; + case "Placeholder": + switch (node.expectedNode) { + case "Expression": + case "Identifier": + case "StringLiteral": + break; + default: + return false; + } + break; + default: + return false; + } + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isBinary(node, opts) { + if (!node) return false; + switch (node.type) { + case "BinaryExpression": + case "LogicalExpression": + break; + default: + return false; + } + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isScopable(node, opts) { + if (!node) return false; + switch (node.type) { + case "BlockStatement": + case "CatchClause": + case "DoWhileStatement": + case "ForInStatement": + case "ForStatement": + case "FunctionDeclaration": + case "FunctionExpression": + case "Program": + case "ObjectMethod": + case "SwitchStatement": + case "WhileStatement": + case "ArrowFunctionExpression": + case "ClassExpression": + case "ClassDeclaration": + case "ForOfStatement": + case "ClassMethod": + case "ClassPrivateMethod": + case "StaticBlock": + case "TSModuleBlock": + break; + case "Placeholder": + if (node.expectedNode === "BlockStatement") break; + default: + return false; + } + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isBlockParent(node, opts) { + if (!node) return false; + switch (node.type) { + case "BlockStatement": + case "CatchClause": + case "DoWhileStatement": + case "ForInStatement": + case "ForStatement": + case "FunctionDeclaration": + case "FunctionExpression": + case "Program": + case "ObjectMethod": + case "SwitchStatement": + case "WhileStatement": + case "ArrowFunctionExpression": + case "ForOfStatement": + case "ClassMethod": + case "ClassPrivateMethod": + case "StaticBlock": + case "TSModuleBlock": + break; + case "Placeholder": + if (node.expectedNode === "BlockStatement") break; + default: + return false; + } + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isBlock(node, opts) { + if (!node) return false; + switch (node.type) { + case "BlockStatement": + case "Program": + case "TSModuleBlock": + break; + case "Placeholder": + if (node.expectedNode === "BlockStatement") break; + default: + return false; + } + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isStatement(node, opts) { + if (!node) return false; + switch (node.type) { + case "BlockStatement": + case "BreakStatement": + case "ContinueStatement": + case "DebuggerStatement": + case "DoWhileStatement": + case "EmptyStatement": + case "ExpressionStatement": + case "ForInStatement": + case "ForStatement": + case "FunctionDeclaration": + case "IfStatement": + case "LabeledStatement": + case "ReturnStatement": + case "SwitchStatement": + case "ThrowStatement": + case "TryStatement": + case "VariableDeclaration": + case "WhileStatement": + case "WithStatement": + case "ClassDeclaration": + case "ExportAllDeclaration": + case "ExportDefaultDeclaration": + case "ExportNamedDeclaration": + case "ForOfStatement": + case "ImportDeclaration": + case "DeclareClass": + case "DeclareFunction": + case "DeclareInterface": + case "DeclareModule": + case "DeclareModuleExports": + case "DeclareTypeAlias": + case "DeclareOpaqueType": + case "DeclareVariable": + case "DeclareExportDeclaration": + case "DeclareExportAllDeclaration": + case "InterfaceDeclaration": + case "OpaqueType": + case "TypeAlias": + case "EnumDeclaration": + case "TSDeclareFunction": + case "TSInterfaceDeclaration": + case "TSTypeAliasDeclaration": + case "TSEnumDeclaration": + case "TSModuleDeclaration": + case "TSImportEqualsDeclaration": + case "TSExportAssignment": + case "TSNamespaceExportDeclaration": + break; + case "Placeholder": + switch (node.expectedNode) { + case "Statement": + case "Declaration": + case "BlockStatement": + break; + default: + return false; + } + break; + default: + return false; + } + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isTerminatorless(node, opts) { + if (!node) return false; + switch (node.type) { + case "BreakStatement": + case "ContinueStatement": + case "ReturnStatement": + case "ThrowStatement": + case "YieldExpression": + case "AwaitExpression": + break; + default: + return false; + } + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isCompletionStatement(node, opts) { + if (!node) return false; + switch (node.type) { + case "BreakStatement": + case "ContinueStatement": + case "ReturnStatement": + case "ThrowStatement": + break; + default: + return false; + } + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isConditional(node, opts) { + if (!node) return false; + switch (node.type) { + case "ConditionalExpression": + case "IfStatement": + break; + default: + return false; + } + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isLoop(node, opts) { + if (!node) return false; + switch (node.type) { + case "DoWhileStatement": + case "ForInStatement": + case "ForStatement": + case "WhileStatement": + case "ForOfStatement": + break; + default: + return false; + } + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isWhile(node, opts) { + if (!node) return false; + switch (node.type) { + case "DoWhileStatement": + case "WhileStatement": + break; + default: + return false; + } + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isExpressionWrapper(node, opts) { + if (!node) return false; + switch (node.type) { + case "ExpressionStatement": + case "ParenthesizedExpression": + case "TypeCastExpression": + break; + default: + return false; + } + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isFor(node, opts) { + if (!node) return false; + switch (node.type) { + case "ForInStatement": + case "ForStatement": + case "ForOfStatement": + break; + default: + return false; + } + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isForXStatement(node, opts) { + if (!node) return false; + switch (node.type) { + case "ForInStatement": + case "ForOfStatement": + break; + default: + return false; + } + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isFunction(node, opts) { + if (!node) return false; + switch (node.type) { + case "FunctionDeclaration": + case "FunctionExpression": + case "ObjectMethod": + case "ArrowFunctionExpression": + case "ClassMethod": + case "ClassPrivateMethod": + break; + default: + return false; + } + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isFunctionParent(node, opts) { + if (!node) return false; + switch (node.type) { + case "FunctionDeclaration": + case "FunctionExpression": + case "ObjectMethod": + case "ArrowFunctionExpression": + case "ClassMethod": + case "ClassPrivateMethod": + case "StaticBlock": + case "TSModuleBlock": + break; + default: + return false; + } + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isPureish(node, opts) { + if (!node) return false; + switch (node.type) { + case "FunctionDeclaration": + case "FunctionExpression": + case "StringLiteral": + case "NumericLiteral": + case "NullLiteral": + case "BooleanLiteral": + case "RegExpLiteral": + case "ArrowFunctionExpression": + case "BigIntLiteral": + case "DecimalLiteral": + break; + case "Placeholder": + if (node.expectedNode === "StringLiteral") break; + default: + return false; + } + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isDeclaration(node, opts) { + if (!node) return false; + switch (node.type) { + case "FunctionDeclaration": + case "VariableDeclaration": + case "ClassDeclaration": + case "ExportAllDeclaration": + case "ExportDefaultDeclaration": + case "ExportNamedDeclaration": + case "ImportDeclaration": + case "DeclareClass": + case "DeclareFunction": + case "DeclareInterface": + case "DeclareModule": + case "DeclareModuleExports": + case "DeclareTypeAlias": + case "DeclareOpaqueType": + case "DeclareVariable": + case "DeclareExportDeclaration": + case "DeclareExportAllDeclaration": + case "InterfaceDeclaration": + case "OpaqueType": + case "TypeAlias": + case "EnumDeclaration": + case "TSDeclareFunction": + case "TSInterfaceDeclaration": + case "TSTypeAliasDeclaration": + case "TSEnumDeclaration": + case "TSModuleDeclaration": + case "TSImportEqualsDeclaration": + break; + case "Placeholder": + if (node.expectedNode === "Declaration") break; + default: + return false; + } + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isPatternLike(node, opts) { + if (!node) return false; + switch (node.type) { + case "Identifier": + case "RestElement": + case "AssignmentPattern": + case "ArrayPattern": + case "ObjectPattern": + case "TSAsExpression": + case "TSSatisfiesExpression": + case "TSTypeAssertion": + case "TSNonNullExpression": + break; + case "Placeholder": + switch (node.expectedNode) { + case "Pattern": + case "Identifier": + break; + default: + return false; + } + break; + default: + return false; + } + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isLVal(node, opts) { + if (!node) return false; + switch (node.type) { + case "Identifier": + case "MemberExpression": + case "RestElement": + case "AssignmentPattern": + case "ArrayPattern": + case "ObjectPattern": + case "TSParameterProperty": + case "TSAsExpression": + case "TSSatisfiesExpression": + case "TSTypeAssertion": + case "TSNonNullExpression": + break; + case "Placeholder": + switch (node.expectedNode) { + case "Pattern": + case "Identifier": + break; + default: + return false; + } + break; + default: + return false; + } + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isTSEntityName(node, opts) { + if (!node) return false; + switch (node.type) { + case "Identifier": + case "TSQualifiedName": + break; + case "Placeholder": + if (node.expectedNode === "Identifier") break; + default: + return false; + } + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isLiteral(node, opts) { + if (!node) return false; + switch (node.type) { + case "StringLiteral": + case "NumericLiteral": + case "NullLiteral": + case "BooleanLiteral": + case "RegExpLiteral": + case "TemplateLiteral": + case "BigIntLiteral": + case "DecimalLiteral": + break; + case "Placeholder": + if (node.expectedNode === "StringLiteral") break; + default: + return false; + } + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isImmutable(node, opts) { + if (!node) return false; + switch (node.type) { + case "StringLiteral": + case "NumericLiteral": + case "NullLiteral": + case "BooleanLiteral": + case "BigIntLiteral": + case "JSXAttribute": + case "JSXClosingElement": + case "JSXElement": + case "JSXExpressionContainer": + case "JSXSpreadChild": + case "JSXOpeningElement": + case "JSXText": + case "JSXFragment": + case "JSXOpeningFragment": + case "JSXClosingFragment": + case "DecimalLiteral": + break; + case "Placeholder": + if (node.expectedNode === "StringLiteral") break; + default: + return false; + } + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isUserWhitespacable(node, opts) { + if (!node) return false; + switch (node.type) { + case "ObjectMethod": + case "ObjectProperty": + case "ObjectTypeInternalSlot": + case "ObjectTypeCallProperty": + case "ObjectTypeIndexer": + case "ObjectTypeProperty": + case "ObjectTypeSpreadProperty": + break; + default: + return false; + } + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isMethod(node, opts) { + if (!node) return false; + switch (node.type) { + case "ObjectMethod": + case "ClassMethod": + case "ClassPrivateMethod": + break; + default: + return false; + } + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isObjectMember(node, opts) { + if (!node) return false; + switch (node.type) { + case "ObjectMethod": + case "ObjectProperty": + break; + default: + return false; + } + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isProperty(node, opts) { + if (!node) return false; + switch (node.type) { + case "ObjectProperty": + case "ClassProperty": + case "ClassAccessorProperty": + case "ClassPrivateProperty": + break; + default: + return false; + } + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isUnaryLike(node, opts) { + if (!node) return false; + switch (node.type) { + case "UnaryExpression": + case "SpreadElement": + break; + default: + return false; + } + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isPattern(node, opts) { + if (!node) return false; + switch (node.type) { + case "AssignmentPattern": + case "ArrayPattern": + case "ObjectPattern": + break; + case "Placeholder": + if (node.expectedNode === "Pattern") break; + default: + return false; + } + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isClass(node, opts) { + if (!node) return false; + switch (node.type) { + case "ClassExpression": + case "ClassDeclaration": + break; + default: + return false; + } + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isImportOrExportDeclaration(node, opts) { + if (!node) return false; + switch (node.type) { + case "ExportAllDeclaration": + case "ExportDefaultDeclaration": + case "ExportNamedDeclaration": + case "ImportDeclaration": + break; + default: + return false; + } + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isExportDeclaration(node, opts) { + if (!node) return false; + switch (node.type) { + case "ExportAllDeclaration": + case "ExportDefaultDeclaration": + case "ExportNamedDeclaration": + break; + default: + return false; + } + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isModuleSpecifier(node, opts) { + if (!node) return false; + switch (node.type) { + case "ExportSpecifier": + case "ImportDefaultSpecifier": + case "ImportNamespaceSpecifier": + case "ImportSpecifier": + case "ExportNamespaceSpecifier": + case "ExportDefaultSpecifier": + break; + default: + return false; + } + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isAccessor(node, opts) { + if (!node) return false; + switch (node.type) { + case "ClassAccessorProperty": + break; + default: + return false; + } + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isPrivate(node, opts) { + if (!node) return false; + switch (node.type) { + case "ClassPrivateProperty": + case "ClassPrivateMethod": + case "PrivateName": + break; + default: + return false; + } + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isFlow(node, opts) { + if (!node) return false; + switch (node.type) { + case "AnyTypeAnnotation": + case "ArrayTypeAnnotation": + case "BooleanTypeAnnotation": + case "BooleanLiteralTypeAnnotation": + case "NullLiteralTypeAnnotation": + case "ClassImplements": + case "DeclareClass": + case "DeclareFunction": + case "DeclareInterface": + case "DeclareModule": + case "DeclareModuleExports": + case "DeclareTypeAlias": + case "DeclareOpaqueType": + case "DeclareVariable": + case "DeclareExportDeclaration": + case "DeclareExportAllDeclaration": + case "DeclaredPredicate": + case "ExistsTypeAnnotation": + case "FunctionTypeAnnotation": + case "FunctionTypeParam": + case "GenericTypeAnnotation": + case "InferredPredicate": + case "InterfaceExtends": + case "InterfaceDeclaration": + case "InterfaceTypeAnnotation": + case "IntersectionTypeAnnotation": + case "MixedTypeAnnotation": + case "EmptyTypeAnnotation": + case "NullableTypeAnnotation": + case "NumberLiteralTypeAnnotation": + case "NumberTypeAnnotation": + case "ObjectTypeAnnotation": + case "ObjectTypeInternalSlot": + case "ObjectTypeCallProperty": + case "ObjectTypeIndexer": + case "ObjectTypeProperty": + case "ObjectTypeSpreadProperty": + case "OpaqueType": + case "QualifiedTypeIdentifier": + case "StringLiteralTypeAnnotation": + case "StringTypeAnnotation": + case "SymbolTypeAnnotation": + case "ThisTypeAnnotation": + case "TupleTypeAnnotation": + case "TypeofTypeAnnotation": + case "TypeAlias": + case "TypeAnnotation": + case "TypeCastExpression": + case "TypeParameter": + case "TypeParameterDeclaration": + case "TypeParameterInstantiation": + case "UnionTypeAnnotation": + case "Variance": + case "VoidTypeAnnotation": + case "EnumDeclaration": + case "EnumBooleanBody": + case "EnumNumberBody": + case "EnumStringBody": + case "EnumSymbolBody": + case "EnumBooleanMember": + case "EnumNumberMember": + case "EnumStringMember": + case "EnumDefaultedMember": + case "IndexedAccessType": + case "OptionalIndexedAccessType": + break; + default: + return false; + } + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isFlowType(node, opts) { + if (!node) return false; + switch (node.type) { + case "AnyTypeAnnotation": + case "ArrayTypeAnnotation": + case "BooleanTypeAnnotation": + case "BooleanLiteralTypeAnnotation": + case "NullLiteralTypeAnnotation": + case "ExistsTypeAnnotation": + case "FunctionTypeAnnotation": + case "GenericTypeAnnotation": + case "InterfaceTypeAnnotation": + case "IntersectionTypeAnnotation": + case "MixedTypeAnnotation": + case "EmptyTypeAnnotation": + case "NullableTypeAnnotation": + case "NumberLiteralTypeAnnotation": + case "NumberTypeAnnotation": + case "ObjectTypeAnnotation": + case "StringLiteralTypeAnnotation": + case "StringTypeAnnotation": + case "SymbolTypeAnnotation": + case "ThisTypeAnnotation": + case "TupleTypeAnnotation": + case "TypeofTypeAnnotation": + case "UnionTypeAnnotation": + case "VoidTypeAnnotation": + case "IndexedAccessType": + case "OptionalIndexedAccessType": + break; + default: + return false; + } + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isFlowBaseAnnotation(node, opts) { + if (!node) return false; + switch (node.type) { + case "AnyTypeAnnotation": + case "BooleanTypeAnnotation": + case "NullLiteralTypeAnnotation": + case "MixedTypeAnnotation": + case "EmptyTypeAnnotation": + case "NumberTypeAnnotation": + case "StringTypeAnnotation": + case "SymbolTypeAnnotation": + case "ThisTypeAnnotation": + case "VoidTypeAnnotation": + break; + default: + return false; + } + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isFlowDeclaration(node, opts) { + if (!node) return false; + switch (node.type) { + case "DeclareClass": + case "DeclareFunction": + case "DeclareInterface": + case "DeclareModule": + case "DeclareModuleExports": + case "DeclareTypeAlias": + case "DeclareOpaqueType": + case "DeclareVariable": + case "DeclareExportDeclaration": + case "DeclareExportAllDeclaration": + case "InterfaceDeclaration": + case "OpaqueType": + case "TypeAlias": + break; + default: + return false; + } + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isFlowPredicate(node, opts) { + if (!node) return false; + switch (node.type) { + case "DeclaredPredicate": + case "InferredPredicate": + break; + default: + return false; + } + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isEnumBody(node, opts) { + if (!node) return false; + switch (node.type) { + case "EnumBooleanBody": + case "EnumNumberBody": + case "EnumStringBody": + case "EnumSymbolBody": + break; + default: + return false; + } + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isEnumMember(node, opts) { + if (!node) return false; + switch (node.type) { + case "EnumBooleanMember": + case "EnumNumberMember": + case "EnumStringMember": + case "EnumDefaultedMember": + break; + default: + return false; + } + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isJSX(node, opts) { + if (!node) return false; + switch (node.type) { + case "JSXAttribute": + case "JSXClosingElement": + case "JSXElement": + case "JSXEmptyExpression": + case "JSXExpressionContainer": + case "JSXSpreadChild": + case "JSXIdentifier": + case "JSXMemberExpression": + case "JSXNamespacedName": + case "JSXOpeningElement": + case "JSXSpreadAttribute": + case "JSXText": + case "JSXFragment": + case "JSXOpeningFragment": + case "JSXClosingFragment": + break; + default: + return false; + } + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isMiscellaneous(node, opts) { + if (!node) return false; + switch (node.type) { + case "Noop": + case "Placeholder": + case "V8IntrinsicIdentifier": + break; + default: + return false; + } + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isTypeScript(node, opts) { + if (!node) return false; + switch (node.type) { + case "TSParameterProperty": + case "TSDeclareFunction": + case "TSDeclareMethod": + case "TSQualifiedName": + case "TSCallSignatureDeclaration": + case "TSConstructSignatureDeclaration": + case "TSPropertySignature": + case "TSMethodSignature": + case "TSIndexSignature": + case "TSAnyKeyword": + case "TSBooleanKeyword": + case "TSBigIntKeyword": + case "TSIntrinsicKeyword": + case "TSNeverKeyword": + case "TSNullKeyword": + case "TSNumberKeyword": + case "TSObjectKeyword": + case "TSStringKeyword": + case "TSSymbolKeyword": + case "TSUndefinedKeyword": + case "TSUnknownKeyword": + case "TSVoidKeyword": + case "TSThisType": + case "TSFunctionType": + case "TSConstructorType": + case "TSTypeReference": + case "TSTypePredicate": + case "TSTypeQuery": + case "TSTypeLiteral": + case "TSArrayType": + case "TSTupleType": + case "TSOptionalType": + case "TSRestType": + case "TSNamedTupleMember": + case "TSUnionType": + case "TSIntersectionType": + case "TSConditionalType": + case "TSInferType": + case "TSParenthesizedType": + case "TSTypeOperator": + case "TSIndexedAccessType": + case "TSMappedType": + case "TSTemplateLiteralType": + case "TSLiteralType": + case "TSExpressionWithTypeArguments": + case "TSInterfaceDeclaration": + case "TSInterfaceBody": + case "TSTypeAliasDeclaration": + case "TSInstantiationExpression": + case "TSAsExpression": + case "TSSatisfiesExpression": + case "TSTypeAssertion": + case "TSEnumBody": + case "TSEnumDeclaration": + case "TSEnumMember": + case "TSModuleDeclaration": + case "TSModuleBlock": + case "TSImportType": + case "TSImportEqualsDeclaration": + case "TSExternalModuleReference": + case "TSNonNullExpression": + case "TSExportAssignment": + case "TSNamespaceExportDeclaration": + case "TSTypeAnnotation": + case "TSTypeParameterInstantiation": + case "TSTypeParameterDeclaration": + case "TSTypeParameter": + break; + default: + return false; + } + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isTSTypeElement(node, opts) { + if (!node) return false; + switch (node.type) { + case "TSCallSignatureDeclaration": + case "TSConstructSignatureDeclaration": + case "TSPropertySignature": + case "TSMethodSignature": + case "TSIndexSignature": + break; + default: + return false; + } + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isTSType(node, opts) { + if (!node) return false; + switch (node.type) { + case "TSAnyKeyword": + case "TSBooleanKeyword": + case "TSBigIntKeyword": + case "TSIntrinsicKeyword": + case "TSNeverKeyword": + case "TSNullKeyword": + case "TSNumberKeyword": + case "TSObjectKeyword": + case "TSStringKeyword": + case "TSSymbolKeyword": + case "TSUndefinedKeyword": + case "TSUnknownKeyword": + case "TSVoidKeyword": + case "TSThisType": + case "TSFunctionType": + case "TSConstructorType": + case "TSTypeReference": + case "TSTypePredicate": + case "TSTypeQuery": + case "TSTypeLiteral": + case "TSArrayType": + case "TSTupleType": + case "TSOptionalType": + case "TSRestType": + case "TSUnionType": + case "TSIntersectionType": + case "TSConditionalType": + case "TSInferType": + case "TSParenthesizedType": + case "TSTypeOperator": + case "TSIndexedAccessType": + case "TSMappedType": + case "TSTemplateLiteralType": + case "TSLiteralType": + case "TSExpressionWithTypeArguments": + case "TSImportType": + break; + default: + return false; + } + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isTSBaseType(node, opts) { + if (!node) return false; + switch (node.type) { + case "TSAnyKeyword": + case "TSBooleanKeyword": + case "TSBigIntKeyword": + case "TSIntrinsicKeyword": + case "TSNeverKeyword": + case "TSNullKeyword": + case "TSNumberKeyword": + case "TSObjectKeyword": + case "TSStringKeyword": + case "TSSymbolKeyword": + case "TSUndefinedKeyword": + case "TSUnknownKeyword": + case "TSVoidKeyword": + case "TSThisType": + case "TSTemplateLiteralType": + case "TSLiteralType": + break; + default: + return false; + } + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isNumberLiteral(node, opts) { + (0, _deprecationWarning.default)("isNumberLiteral", "isNumericLiteral"); + if (!node) return false; + if (node.type !== "NumberLiteral") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isRegexLiteral(node, opts) { + (0, _deprecationWarning.default)("isRegexLiteral", "isRegExpLiteral"); + if (!node) return false; + if (node.type !== "RegexLiteral") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isRestProperty(node, opts) { + (0, _deprecationWarning.default)("isRestProperty", "isRestElement"); + if (!node) return false; + if (node.type !== "RestProperty") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isSpreadProperty(node, opts) { + (0, _deprecationWarning.default)("isSpreadProperty", "isSpreadElement"); + if (!node) return false; + if (node.type !== "SpreadProperty") return false; + return opts == null || (0, _shallowEqual.default)(node, opts); +} +function isModuleDeclaration(node, opts) { + (0, _deprecationWarning.default)("isModuleDeclaration", "isImportOrExportDeclaration"); + return isImportOrExportDeclaration(node, opts); +} + +//# sourceMappingURL=index.js.map + + +/***/ }), + +/***/ 20051: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports["default"] = is; +var _shallowEqual = __nccwpck_require__(56085); +var _isType = __nccwpck_require__(53547); +var _isPlaceholderType = __nccwpck_require__(49410); +var _index = __nccwpck_require__(40910); +function is(type, node, opts) { + if (!node) return false; + const matches = (0, _isType.default)(node.type, type); + if (!matches) { + if (!opts && node.type === "Placeholder" && type in _index.FLIPPED_ALIAS_KEYS) { + return (0, _isPlaceholderType.default)(node.expectedNode, type); + } + return false; + } + if (opts === undefined) { + return true; + } else { + return (0, _shallowEqual.default)(node, opts); + } +} + +//# sourceMappingURL=is.js.map + + +/***/ }), + +/***/ 73998: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports["default"] = isBinding; +var _getBindingIdentifiers = __nccwpck_require__(45300); +function isBinding(node, parent, grandparent) { + if (grandparent && node.type === "Identifier" && parent.type === "ObjectProperty" && grandparent.type === "ObjectExpression") { + return false; + } + const keys = _getBindingIdentifiers.default.keys[parent.type]; + if (keys) { + for (let i = 0; i < keys.length; i++) { + const key = keys[i]; + const val = parent[key]; + if (Array.isArray(val)) { + if (val.includes(node)) return true; + } else { + if (val === node) return true; + } + } + } + return false; +} + +//# sourceMappingURL=isBinding.js.map + + +/***/ }), + +/***/ 942: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports["default"] = isBlockScoped; +var _index = __nccwpck_require__(40741); +var _isLet = __nccwpck_require__(57674); +function isBlockScoped(node) { + return (0, _index.isFunctionDeclaration)(node) || (0, _index.isClassDeclaration)(node) || (0, _isLet.default)(node); +} + +//# sourceMappingURL=isBlockScoped.js.map + + +/***/ }), + +/***/ 42343: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports["default"] = isImmutable; +var _isType = __nccwpck_require__(53547); +var _index = __nccwpck_require__(40741); +function isImmutable(node) { + if ((0, _isType.default)(node.type, "Immutable")) return true; + if ((0, _index.isIdentifier)(node)) { + if (node.name === "undefined") { + return true; + } else { + return false; + } + } + return false; +} + +//# sourceMappingURL=isImmutable.js.map + + +/***/ }), + +/***/ 57674: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports["default"] = isLet; +var _index = __nccwpck_require__(40741); +{ + var BLOCK_SCOPED_SYMBOL = Symbol.for("var used to be block scoped"); +} +function isLet(node) { + { + return (0, _index.isVariableDeclaration)(node) && (node.kind !== "var" || node[BLOCK_SCOPED_SYMBOL]); + } +} + +//# sourceMappingURL=isLet.js.map + + +/***/ }), + +/***/ 84503: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports["default"] = isNode; +var _index = __nccwpck_require__(40910); +function isNode(node) { + return !!(node && _index.VISITOR_KEYS[node.type]); +} + +//# sourceMappingURL=isNode.js.map + + +/***/ }), + +/***/ 8338: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports["default"] = isNodesEquivalent; +var _index = __nccwpck_require__(40910); +function isNodesEquivalent(a, b) { + if (typeof a !== "object" || typeof b !== "object" || a == null || b == null) { + return a === b; + } + if (a.type !== b.type) { + return false; + } + const fields = Object.keys(_index.NODE_FIELDS[a.type] || a.type); + const visitorKeys = _index.VISITOR_KEYS[a.type]; + for (const field of fields) { + const val_a = a[field]; + const val_b = b[field]; + if (typeof val_a !== typeof val_b) { + return false; + } + if (val_a == null && val_b == null) { + continue; + } else if (val_a == null || val_b == null) { + return false; + } + if (Array.isArray(val_a)) { + if (!Array.isArray(val_b)) { + return false; + } + if (val_a.length !== val_b.length) { + return false; + } + for (let i = 0; i < val_a.length; i++) { + if (!isNodesEquivalent(val_a[i], val_b[i])) { + return false; + } + } + continue; + } + if (typeof val_a === "object" && !(visitorKeys != null && visitorKeys.includes(field))) { + for (const key of Object.keys(val_a)) { + if (val_a[key] !== val_b[key]) { + return false; + } + } + continue; + } + if (!isNodesEquivalent(val_a, val_b)) { + return false; + } + } + return true; +} + +//# sourceMappingURL=isNodesEquivalent.js.map + + +/***/ }), + +/***/ 49410: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports["default"] = isPlaceholderType; +var _index = __nccwpck_require__(40910); +function isPlaceholderType(placeholderType, targetType) { + if (placeholderType === targetType) return true; + const aliases = _index.PLACEHOLDERS_ALIAS[placeholderType]; + if (aliases != null && aliases.includes(targetType)) return true; + return false; +} + +//# sourceMappingURL=isPlaceholderType.js.map + + +/***/ }), + +/***/ 89508: +/***/ ((__unused_webpack_module, exports) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports["default"] = isReferenced; +function isReferenced(node, parent, grandparent) { + switch (parent.type) { + case "MemberExpression": + case "OptionalMemberExpression": + if (parent.property === node) { + return !!parent.computed; + } + return parent.object === node; + case "JSXMemberExpression": + return parent.object === node; + case "VariableDeclarator": + return parent.init === node; + case "ArrowFunctionExpression": + return parent.body === node; + case "PrivateName": + return false; + case "ClassMethod": + case "ClassPrivateMethod": + case "ObjectMethod": + if (parent.key === node) { + return !!parent.computed; + } + return false; + case "ObjectProperty": + if (parent.key === node) { + return !!parent.computed; + } + return !grandparent || grandparent.type !== "ObjectPattern"; + case "ClassProperty": + case "ClassAccessorProperty": + if (parent.key === node) { + return !!parent.computed; + } + return true; + case "ClassPrivateProperty": + return parent.key !== node; + case "ClassDeclaration": + case "ClassExpression": + return parent.superClass === node; + case "AssignmentExpression": + return parent.right === node; + case "AssignmentPattern": + return parent.right === node; + case "LabeledStatement": + return false; + case "CatchClause": + return false; + case "RestElement": + return false; + case "BreakStatement": + case "ContinueStatement": + return false; + case "FunctionDeclaration": + case "FunctionExpression": + return false; + case "ExportNamespaceSpecifier": + case "ExportDefaultSpecifier": + return false; + case "ExportSpecifier": + if (grandparent != null && grandparent.source) { + return false; + } + return parent.local === node; + case "ImportDefaultSpecifier": + case "ImportNamespaceSpecifier": + case "ImportSpecifier": + return false; + case "ImportAttribute": + return false; + case "JSXAttribute": + return false; + case "ObjectPattern": + case "ArrayPattern": + return false; + case "MetaProperty": + return false; + case "ObjectTypeProperty": + return parent.key !== node; + case "TSEnumMember": + return parent.id !== node; + case "TSPropertySignature": + if (parent.key === node) { + return !!parent.computed; + } + return true; + } + return true; +} + +//# sourceMappingURL=isReferenced.js.map + + +/***/ }), + +/***/ 33583: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports["default"] = isScope; +var _index = __nccwpck_require__(40741); +function isScope(node, parent) { + if ((0, _index.isBlockStatement)(node) && ((0, _index.isFunction)(parent) || (0, _index.isCatchClause)(parent))) { + return false; + } + if ((0, _index.isPattern)(node) && ((0, _index.isFunction)(parent) || (0, _index.isCatchClause)(parent))) { + return true; + } + return (0, _index.isScopable)(node); +} + +//# sourceMappingURL=isScope.js.map + + +/***/ }), + +/***/ 64394: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports["default"] = isSpecifierDefault; +var _index = __nccwpck_require__(40741); +function isSpecifierDefault(specifier) { + return (0, _index.isImportDefaultSpecifier)(specifier) || (0, _index.isIdentifier)(specifier.imported || specifier.exported, { + name: "default" + }); +} + +//# sourceMappingURL=isSpecifierDefault.js.map + + +/***/ }), + +/***/ 53547: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports["default"] = isType; +var _index = __nccwpck_require__(40910); +function isType(nodeType, targetType) { + if (nodeType === targetType) return true; + if (nodeType == null) return false; + if (_index.ALIAS_KEYS[targetType]) return false; + const aliases = _index.FLIPPED_ALIAS_KEYS[targetType]; + if (aliases != null && aliases.includes(nodeType)) return true; + return false; +} + +//# sourceMappingURL=isType.js.map + + +/***/ }), + +/***/ 40267: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports["default"] = isValidES3Identifier; +var _isValidIdentifier = __nccwpck_require__(66030); +const RESERVED_WORDS_ES3_ONLY = new Set(["abstract", "boolean", "byte", "char", "double", "enum", "final", "float", "goto", "implements", "int", "interface", "long", "native", "package", "private", "protected", "public", "short", "static", "synchronized", "throws", "transient", "volatile"]); +function isValidES3Identifier(name) { + return (0, _isValidIdentifier.default)(name) && !RESERVED_WORDS_ES3_ONLY.has(name); +} + +//# sourceMappingURL=isValidES3Identifier.js.map + + +/***/ }), + +/***/ 66030: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports["default"] = isValidIdentifier; +var _helperValidatorIdentifier = __nccwpck_require__(76599); +function isValidIdentifier(name, reserved = true) { + if (typeof name !== "string") return false; + if (reserved) { + if ((0, _helperValidatorIdentifier.isKeyword)(name) || (0, _helperValidatorIdentifier.isStrictReservedWord)(name, true)) { + return false; + } + } + return (0, _helperValidatorIdentifier.isIdentifierName)(name); +} + +//# sourceMappingURL=isValidIdentifier.js.map + + +/***/ }), + +/***/ 21566: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports["default"] = isVar; +var _index = __nccwpck_require__(40741); +{ + var BLOCK_SCOPED_SYMBOL = Symbol.for("var used to be block scoped"); +} +function isVar(node) { + { + return (0, _index.isVariableDeclaration)(node, { + kind: "var" + }) && !node[BLOCK_SCOPED_SYMBOL]; + } +} + +//# sourceMappingURL=isVar.js.map + + +/***/ }), + +/***/ 47814: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports["default"] = matchesPattern; +var _index = __nccwpck_require__(40741); +function matchesPattern(member, match, allowPartial) { + if (!(0, _index.isMemberExpression)(member)) return false; + const parts = Array.isArray(match) ? match : match.split("."); + const nodes = []; + let node; + for (node = member; (0, _index.isMemberExpression)(node); node = node.object) { + nodes.push(node.property); + } + nodes.push(node); + if (nodes.length < parts.length) return false; + if (!allowPartial && nodes.length > parts.length) return false; + for (let i = 0, j = nodes.length - 1; i < parts.length; i++, j--) { + const node = nodes[j]; + let value; + if ((0, _index.isIdentifier)(node)) { + value = node.name; + } else if ((0, _index.isStringLiteral)(node)) { + value = node.value; + } else if ((0, _index.isThisExpression)(node)) { + value = "this"; + } else { + return false; + } + if (parts[i] !== value) return false; + } + return true; +} + +//# sourceMappingURL=matchesPattern.js.map + + +/***/ }), + +/***/ 20817: +/***/ ((__unused_webpack_module, exports) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports["default"] = isCompatTag; +function isCompatTag(tagName) { + return !!tagName && /^[a-z]/.test(tagName); +} + +//# sourceMappingURL=isCompatTag.js.map + + +/***/ }), + +/***/ 24513: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports["default"] = void 0; +var _buildMatchMemberExpression = __nccwpck_require__(77334); +const isReactComponent = (0, _buildMatchMemberExpression.default)("React.Component"); +var _default = exports["default"] = isReactComponent; + +//# sourceMappingURL=isReactComponent.js.map + + +/***/ }), + +/***/ 71581: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports["default"] = validate; +exports.validateChild = validateChild; +exports.validateField = validateField; +exports.validateInternal = validateInternal; +var _index = __nccwpck_require__(40910); +function validate(node, key, val) { + if (!node) return; + const fields = _index.NODE_FIELDS[node.type]; + if (!fields) return; + const field = fields[key]; + validateField(node, key, val, field); + validateChild(node, key, val); +} +function validateInternal(field, node, key, val, maybeNode) { + if (!(field != null && field.validate)) return; + if (field.optional && val == null) return; + field.validate(node, key, val); + if (maybeNode) { + var _NODE_PARENT_VALIDATI; + const type = val.type; + if (type == null) return; + (_NODE_PARENT_VALIDATI = _index.NODE_PARENT_VALIDATIONS[type]) == null || _NODE_PARENT_VALIDATI.call(_index.NODE_PARENT_VALIDATIONS, node, key, val); + } +} +function validateField(node, key, val, field) { + if (!(field != null && field.validate)) return; + if (field.optional && val == null) return; + field.validate(node, key, val); +} +function validateChild(node, key, val) { + var _NODE_PARENT_VALIDATI2; + const type = val == null ? void 0 : val.type; + if (type == null) return; + (_NODE_PARENT_VALIDATI2 = _index.NODE_PARENT_VALIDATIONS[type]) == null || _NODE_PARENT_VALIDATI2.call(_index.NODE_PARENT_VALIDATIONS, node, key, val); +} + +//# sourceMappingURL=validate.js.map + + +/***/ }), + +/***/ 27182: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const WritableStream = (__nccwpck_require__(57075).Writable) +const inherits = (__nccwpck_require__(57975).inherits) + +const StreamSearch = __nccwpck_require__(84136) + +const PartStream = __nccwpck_require__(50612) +const HeaderParser = __nccwpck_require__(62271) + +const DASH = 45 +const B_ONEDASH = Buffer.from('-') +const B_CRLF = Buffer.from('\r\n') +const EMPTY_FN = function () {} + +function Dicer (cfg) { + if (!(this instanceof Dicer)) { return new Dicer(cfg) } + WritableStream.call(this, cfg) + + if (!cfg || (!cfg.headerFirst && typeof cfg.boundary !== 'string')) { throw new TypeError('Boundary required') } + + if (typeof cfg.boundary === 'string') { this.setBoundary(cfg.boundary) } else { this._bparser = undefined } + + this._headerFirst = cfg.headerFirst + + this._dashes = 0 + this._parts = 0 + this._finished = false + this._realFinish = false + this._isPreamble = true + this._justMatched = false + this._firstWrite = true + this._inHeader = true + this._part = undefined + this._cb = undefined + this._ignoreData = false + this._partOpts = { highWaterMark: cfg.partHwm } + this._pause = false + + const self = this + this._hparser = new HeaderParser(cfg) + this._hparser.on('header', function (header) { + self._inHeader = false + self._part.emit('header', header) + }) +} +inherits(Dicer, WritableStream) + +Dicer.prototype.emit = function (ev) { + if (ev === 'finish' && !this._realFinish) { + if (!this._finished) { + const self = this + process.nextTick(function () { + self.emit('error', new Error('Unexpected end of multipart data')) + if (self._part && !self._ignoreData) { + const type = (self._isPreamble ? 'Preamble' : 'Part') + self._part.emit('error', new Error(type + ' terminated early due to unexpected end of multipart data')) + self._part.push(null) + process.nextTick(function () { + self._realFinish = true + self.emit('finish') + self._realFinish = false + }) + return + } + self._realFinish = true + self.emit('finish') + self._realFinish = false + }) + } + } else { WritableStream.prototype.emit.apply(this, arguments) } +} + +Dicer.prototype._write = function (data, encoding, cb) { + // ignore unexpected data (e.g. extra trailer data after finished) + if (!this._hparser && !this._bparser) { return cb() } + + if (this._headerFirst && this._isPreamble) { + if (!this._part) { + this._part = new PartStream(this._partOpts) + if (this.listenerCount('preamble') !== 0) { this.emit('preamble', this._part) } else { this._ignore() } + } + const r = this._hparser.push(data) + if (!this._inHeader && r !== undefined && r < data.length) { data = data.slice(r) } else { return cb() } + } + + // allows for "easier" testing + if (this._firstWrite) { + this._bparser.push(B_CRLF) + this._firstWrite = false + } + + this._bparser.push(data) + + if (this._pause) { this._cb = cb } else { cb() } +} + +Dicer.prototype.reset = function () { + this._part = undefined + this._bparser = undefined + this._hparser = undefined +} + +Dicer.prototype.setBoundary = function (boundary) { + const self = this + this._bparser = new StreamSearch('\r\n--' + boundary) + this._bparser.on('info', function (isMatch, data, start, end) { + self._oninfo(isMatch, data, start, end) + }) +} + +Dicer.prototype._ignore = function () { + if (this._part && !this._ignoreData) { + this._ignoreData = true + this._part.on('error', EMPTY_FN) + // we must perform some kind of read on the stream even though we are + // ignoring the data, otherwise node's Readable stream will not emit 'end' + // after pushing null to the stream + this._part.resume() + } +} + +Dicer.prototype._oninfo = function (isMatch, data, start, end) { + let buf; const self = this; let i = 0; let r; let shouldWriteMore = true + + if (!this._part && this._justMatched && data) { + while (this._dashes < 2 && (start + i) < end) { + if (data[start + i] === DASH) { + ++i + ++this._dashes + } else { + if (this._dashes) { buf = B_ONEDASH } + this._dashes = 0 + break + } + } + if (this._dashes === 2) { + if ((start + i) < end && this.listenerCount('trailer') !== 0) { this.emit('trailer', data.slice(start + i, end)) } + this.reset() + this._finished = true + // no more parts will be added + if (self._parts === 0) { + self._realFinish = true + self.emit('finish') + self._realFinish = false + } + } + if (this._dashes) { return } + } + if (this._justMatched) { this._justMatched = false } + if (!this._part) { + this._part = new PartStream(this._partOpts) + this._part._read = function (n) { + self._unpause() + } + if (this._isPreamble && this.listenerCount('preamble') !== 0) { + this.emit('preamble', this._part) + } else if (this._isPreamble !== true && this.listenerCount('part') !== 0) { + this.emit('part', this._part) + } else { + this._ignore() + } + if (!this._isPreamble) { this._inHeader = true } + } + if (data && start < end && !this._ignoreData) { + if (this._isPreamble || !this._inHeader) { + if (buf) { shouldWriteMore = this._part.push(buf) } + shouldWriteMore = this._part.push(data.slice(start, end)) + if (!shouldWriteMore) { this._pause = true } + } else if (!this._isPreamble && this._inHeader) { + if (buf) { this._hparser.push(buf) } + r = this._hparser.push(data.slice(start, end)) + if (!this._inHeader && r !== undefined && r < end) { this._oninfo(false, data, start + r, end) } + } + } + if (isMatch) { + this._hparser.reset() + if (this._isPreamble) { this._isPreamble = false } else { + if (start !== end) { + ++this._parts + this._part.on('end', function () { + if (--self._parts === 0) { + if (self._finished) { + self._realFinish = true + self.emit('finish') + self._realFinish = false + } else { + self._unpause() + } + } + }) + } + } + this._part.push(null) + this._part = undefined + this._ignoreData = false + this._justMatched = true + this._dashes = 0 + } +} + +Dicer.prototype._unpause = function () { + if (!this._pause) { return } + + this._pause = false + if (this._cb) { + const cb = this._cb + this._cb = undefined + cb() + } +} + +module.exports = Dicer + + +/***/ }), + +/***/ 62271: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const EventEmitter = (__nccwpck_require__(78474).EventEmitter) +const inherits = (__nccwpck_require__(57975).inherits) +const getLimit = __nccwpck_require__(22393) + +const StreamSearch = __nccwpck_require__(84136) + +const B_DCRLF = Buffer.from('\r\n\r\n') +const RE_CRLF = /\r\n/g +const RE_HDR = /^([^:]+):[ \t]?([\x00-\xFF]+)?$/ // eslint-disable-line no-control-regex + +function HeaderParser (cfg) { + EventEmitter.call(this) + + cfg = cfg || {} + const self = this + this.nread = 0 + this.maxed = false + this.npairs = 0 + this.maxHeaderPairs = getLimit(cfg, 'maxHeaderPairs', 2000) + this.maxHeaderSize = getLimit(cfg, 'maxHeaderSize', 80 * 1024) + this.buffer = '' + this.header = {} + this.finished = false + this.ss = new StreamSearch(B_DCRLF) + this.ss.on('info', function (isMatch, data, start, end) { + if (data && !self.maxed) { + if (self.nread + end - start >= self.maxHeaderSize) { + end = self.maxHeaderSize - self.nread + start + self.nread = self.maxHeaderSize + self.maxed = true + } else { self.nread += (end - start) } + + self.buffer += data.toString('binary', start, end) + } + if (isMatch) { self._finish() } + }) +} +inherits(HeaderParser, EventEmitter) + +HeaderParser.prototype.push = function (data) { + const r = this.ss.push(data) + if (this.finished) { return r } +} + +HeaderParser.prototype.reset = function () { + this.finished = false + this.buffer = '' + this.header = {} + this.ss.reset() +} + +HeaderParser.prototype._finish = function () { + if (this.buffer) { this._parseHeader() } + this.ss.matches = this.ss.maxMatches + const header = this.header + this.header = {} + this.buffer = '' + this.finished = true + this.nread = this.npairs = 0 + this.maxed = false + this.emit('header', header) +} + +HeaderParser.prototype._parseHeader = function () { + if (this.npairs === this.maxHeaderPairs) { return } + + const lines = this.buffer.split(RE_CRLF) + const len = lines.length + let m, h + + for (var i = 0; i < len; ++i) { // eslint-disable-line no-var + if (lines[i].length === 0) { continue } + if (lines[i][0] === '\t' || lines[i][0] === ' ') { + // folded header content + // RFC2822 says to just remove the CRLF and not the whitespace following + // it, so we follow the RFC and include the leading whitespace ... + if (h) { + this.header[h][this.header[h].length - 1] += lines[i] + continue + } + } + + const posColon = lines[i].indexOf(':') + if ( + posColon === -1 || + posColon === 0 + ) { + return + } + m = RE_HDR.exec(lines[i]) + h = m[1].toLowerCase() + this.header[h] = this.header[h] || [] + this.header[h].push((m[2] || '')) + if (++this.npairs === this.maxHeaderPairs) { break } + } +} + +module.exports = HeaderParser + + +/***/ }), + +/***/ 50612: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const inherits = (__nccwpck_require__(57975).inherits) +const ReadableStream = (__nccwpck_require__(57075).Readable) + +function PartStream (opts) { + ReadableStream.call(this, opts) +} +inherits(PartStream, ReadableStream) + +PartStream.prototype._read = function (n) {} + +module.exports = PartStream + + +/***/ }), + +/***/ 84136: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +/** + * Copyright Brian White. All rights reserved. + * + * @see https://github.com/mscdex/streamsearch + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + * + * Based heavily on the Streaming Boyer-Moore-Horspool C++ implementation + * by Hongli Lai at: https://github.com/FooBarWidget/boyer-moore-horspool + */ +const EventEmitter = (__nccwpck_require__(78474).EventEmitter) +const inherits = (__nccwpck_require__(57975).inherits) + +function SBMH (needle) { + if (typeof needle === 'string') { + needle = Buffer.from(needle) + } + + if (!Buffer.isBuffer(needle)) { + throw new TypeError('The needle has to be a String or a Buffer.') + } + + const needleLength = needle.length + + if (needleLength === 0) { + throw new Error('The needle cannot be an empty String/Buffer.') + } + + if (needleLength > 256) { + throw new Error('The needle cannot have a length bigger than 256.') + } + + this.maxMatches = Infinity + this.matches = 0 + + this._occ = new Array(256) + .fill(needleLength) // Initialize occurrence table. + this._lookbehind_size = 0 + this._needle = needle + this._bufpos = 0 + + this._lookbehind = Buffer.alloc(needleLength) + + // Populate occurrence table with analysis of the needle, + // ignoring last letter. + for (var i = 0; i < needleLength - 1; ++i) { // eslint-disable-line no-var + this._occ[needle[i]] = needleLength - 1 - i + } +} +inherits(SBMH, EventEmitter) + +SBMH.prototype.reset = function () { + this._lookbehind_size = 0 + this.matches = 0 + this._bufpos = 0 +} + +SBMH.prototype.push = function (chunk, pos) { + if (!Buffer.isBuffer(chunk)) { + chunk = Buffer.from(chunk, 'binary') + } + const chlen = chunk.length + this._bufpos = pos || 0 + let r + while (r !== chlen && this.matches < this.maxMatches) { r = this._sbmh_feed(chunk) } + return r +} + +SBMH.prototype._sbmh_feed = function (data) { + const len = data.length + const needle = this._needle + const needleLength = needle.length + const lastNeedleChar = needle[needleLength - 1] + + // Positive: points to a position in `data` + // pos == 3 points to data[3] + // Negative: points to a position in the lookbehind buffer + // pos == -2 points to lookbehind[lookbehind_size - 2] + let pos = -this._lookbehind_size + let ch + + if (pos < 0) { + // Lookbehind buffer is not empty. Perform Boyer-Moore-Horspool + // search with character lookup code that considers both the + // lookbehind buffer and the current round's haystack data. + // + // Loop until + // there is a match. + // or until + // we've moved past the position that requires the + // lookbehind buffer. In this case we switch to the + // optimized loop. + // or until + // the character to look at lies outside the haystack. + while (pos < 0 && pos <= len - needleLength) { + ch = this._sbmh_lookup_char(data, pos + needleLength - 1) + + if ( + ch === lastNeedleChar && + this._sbmh_memcmp(data, pos, needleLength - 1) + ) { + this._lookbehind_size = 0 + ++this.matches + this.emit('info', true) + + return (this._bufpos = pos + needleLength) + } + pos += this._occ[ch] + } + + // No match. + + if (pos < 0) { + // There's too few data for Boyer-Moore-Horspool to run, + // so let's use a different algorithm to skip as much as + // we can. + // Forward pos until + // the trailing part of lookbehind + data + // looks like the beginning of the needle + // or until + // pos == 0 + while (pos < 0 && !this._sbmh_memcmp(data, pos, len - pos)) { ++pos } + } + + if (pos >= 0) { + // Discard lookbehind buffer. + this.emit('info', false, this._lookbehind, 0, this._lookbehind_size) + this._lookbehind_size = 0 + } else { + // Cut off part of the lookbehind buffer that has + // been processed and append the entire haystack + // into it. + const bytesToCutOff = this._lookbehind_size + pos + if (bytesToCutOff > 0) { + // The cut off data is guaranteed not to contain the needle. + this.emit('info', false, this._lookbehind, 0, bytesToCutOff) + } + + this._lookbehind.copy(this._lookbehind, 0, bytesToCutOff, + this._lookbehind_size - bytesToCutOff) + this._lookbehind_size -= bytesToCutOff + + data.copy(this._lookbehind, this._lookbehind_size) + this._lookbehind_size += len + + this._bufpos = len + return len + } + } + + pos += (pos >= 0) * this._bufpos + + // Lookbehind buffer is now empty. We only need to check if the + // needle is in the haystack. + if (data.indexOf(needle, pos) !== -1) { + pos = data.indexOf(needle, pos) + ++this.matches + if (pos > 0) { this.emit('info', true, data, this._bufpos, pos) } else { this.emit('info', true) } + + return (this._bufpos = pos + needleLength) + } else { + pos = len - needleLength + } + + // There was no match. If there's trailing haystack data that we cannot + // match yet using the Boyer-Moore-Horspool algorithm (because the trailing + // data is less than the needle size) then match using a modified + // algorithm that starts matching from the beginning instead of the end. + // Whatever trailing data is left after running this algorithm is added to + // the lookbehind buffer. + while ( + pos < len && + ( + data[pos] !== needle[0] || + ( + (Buffer.compare( + data.subarray(pos, pos + len - pos), + needle.subarray(0, len - pos) + ) !== 0) + ) + ) + ) { + ++pos + } + if (pos < len) { + data.copy(this._lookbehind, 0, pos, pos + (len - pos)) + this._lookbehind_size = len - pos + } + + // Everything until pos is guaranteed not to contain needle data. + if (pos > 0) { this.emit('info', false, data, this._bufpos, pos < len ? pos : len) } + + this._bufpos = len + return len +} + +SBMH.prototype._sbmh_lookup_char = function (data, pos) { + return (pos < 0) + ? this._lookbehind[this._lookbehind_size + pos] + : data[pos] +} + +SBMH.prototype._sbmh_memcmp = function (data, pos, len) { + for (var i = 0; i < len; ++i) { // eslint-disable-line no-var + if (this._sbmh_lookup_char(data, pos + i) !== this._needle[i]) { return false } + } + return true +} + +module.exports = SBMH + + +/***/ }), + +/***/ 89581: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const WritableStream = (__nccwpck_require__(57075).Writable) +const { inherits } = __nccwpck_require__(57975) +const Dicer = __nccwpck_require__(27182) + +const MultipartParser = __nccwpck_require__(41192) +const UrlencodedParser = __nccwpck_require__(80855) +const parseParams = __nccwpck_require__(8929) + +function Busboy (opts) { + if (!(this instanceof Busboy)) { return new Busboy(opts) } + + if (typeof opts !== 'object') { + throw new TypeError('Busboy expected an options-Object.') + } + if (typeof opts.headers !== 'object') { + throw new TypeError('Busboy expected an options-Object with headers-attribute.') + } + if (typeof opts.headers['content-type'] !== 'string') { + throw new TypeError('Missing Content-Type-header.') + } + + const { + headers, + ...streamOptions + } = opts + + this.opts = { + autoDestroy: false, + ...streamOptions + } + WritableStream.call(this, this.opts) + + this._done = false + this._parser = this.getParserByHeaders(headers) + this._finished = false +} +inherits(Busboy, WritableStream) + +Busboy.prototype.emit = function (ev) { + if (ev === 'finish') { + if (!this._done) { + this._parser?.end() + return + } else if (this._finished) { + return + } + this._finished = true + } + WritableStream.prototype.emit.apply(this, arguments) +} + +Busboy.prototype.getParserByHeaders = function (headers) { + const parsed = parseParams(headers['content-type']) + + const cfg = { + defCharset: this.opts.defCharset, + fileHwm: this.opts.fileHwm, + headers, + highWaterMark: this.opts.highWaterMark, + isPartAFile: this.opts.isPartAFile, + limits: this.opts.limits, + parsedConType: parsed, + preservePath: this.opts.preservePath + } + + if (MultipartParser.detect.test(parsed[0])) { + return new MultipartParser(this, cfg) + } + if (UrlencodedParser.detect.test(parsed[0])) { + return new UrlencodedParser(this, cfg) + } + throw new Error('Unsupported Content-Type.') +} + +Busboy.prototype._write = function (chunk, encoding, cb) { + this._parser.write(chunk, cb) +} + +module.exports = Busboy +module.exports["default"] = Busboy +module.exports.Busboy = Busboy + +module.exports.Dicer = Dicer + + +/***/ }), + +/***/ 41192: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +// TODO: +// * support 1 nested multipart level +// (see second multipart example here: +// http://www.w3.org/TR/html401/interact/forms.html#didx-multipartform-data) +// * support limits.fieldNameSize +// -- this will require modifications to utils.parseParams + +const { Readable } = __nccwpck_require__(57075) +const { inherits } = __nccwpck_require__(57975) + +const Dicer = __nccwpck_require__(27182) + +const parseParams = __nccwpck_require__(8929) +const decodeText = __nccwpck_require__(72747) +const basename = __nccwpck_require__(20692) +const getLimit = __nccwpck_require__(22393) + +const RE_BOUNDARY = /^boundary$/i +const RE_FIELD = /^form-data$/i +const RE_CHARSET = /^charset$/i +const RE_FILENAME = /^filename$/i +const RE_NAME = /^name$/i + +Multipart.detect = /^multipart\/form-data/i +function Multipart (boy, cfg) { + let i + let len + const self = this + let boundary + const limits = cfg.limits + const isPartAFile = cfg.isPartAFile || ((fieldName, contentType, fileName) => (contentType === 'application/octet-stream' || fileName !== undefined)) + const parsedConType = cfg.parsedConType || [] + const defCharset = cfg.defCharset || 'utf8' + const preservePath = cfg.preservePath + const fileOpts = { highWaterMark: cfg.fileHwm } + + for (i = 0, len = parsedConType.length; i < len; ++i) { + if (Array.isArray(parsedConType[i]) && + RE_BOUNDARY.test(parsedConType[i][0])) { + boundary = parsedConType[i][1] + break + } + } + + function checkFinished () { + if (nends === 0 && finished && !boy._done) { + finished = false + self.end() + } + } + + if (typeof boundary !== 'string') { throw new Error('Multipart: Boundary not found') } + + const fieldSizeLimit = getLimit(limits, 'fieldSize', 1 * 1024 * 1024) + const fileSizeLimit = getLimit(limits, 'fileSize', Infinity) + const filesLimit = getLimit(limits, 'files', Infinity) + const fieldsLimit = getLimit(limits, 'fields', Infinity) + const partsLimit = getLimit(limits, 'parts', Infinity) + const headerPairsLimit = getLimit(limits, 'headerPairs', 2000) + const headerSizeLimit = getLimit(limits, 'headerSize', 80 * 1024) + + let nfiles = 0 + let nfields = 0 + let nends = 0 + let curFile + let curField + let finished = false + + this._needDrain = false + this._pause = false + this._cb = undefined + this._nparts = 0 + this._boy = boy + + const parserCfg = { + boundary, + maxHeaderPairs: headerPairsLimit, + maxHeaderSize: headerSizeLimit, + partHwm: fileOpts.highWaterMark, + highWaterMark: cfg.highWaterMark + } + + this.parser = new Dicer(parserCfg) + this.parser.on('drain', function () { + self._needDrain = false + if (self._cb && !self._pause) { + const cb = self._cb + self._cb = undefined + cb() + } + }).on('part', function onPart (part) { + if (++self._nparts > partsLimit) { + self.parser.removeListener('part', onPart) + self.parser.on('part', skipPart) + boy.hitPartsLimit = true + boy.emit('partsLimit') + return skipPart(part) + } + + // hack because streams2 _always_ doesn't emit 'end' until nextTick, so let + // us emit 'end' early since we know the part has ended if we are already + // seeing the next part + if (curField) { + const field = curField + field.emit('end') + field.removeAllListeners('end') + } + + part.on('header', function (header) { + let contype + let fieldname + let parsed + let charset + let encoding + let filename + let nsize = 0 + + if (header['content-type']) { + parsed = parseParams(header['content-type'][0]) + if (parsed[0]) { + contype = parsed[0].toLowerCase() + for (i = 0, len = parsed.length; i < len; ++i) { + if (RE_CHARSET.test(parsed[i][0])) { + charset = parsed[i][1].toLowerCase() + break + } + } + } + } + + if (contype === undefined) { contype = 'text/plain' } + if (charset === undefined) { charset = defCharset } + + if (header['content-disposition']) { + parsed = parseParams(header['content-disposition'][0]) + if (!RE_FIELD.test(parsed[0])) { return skipPart(part) } + for (i = 0, len = parsed.length; i < len; ++i) { + if (RE_NAME.test(parsed[i][0])) { + fieldname = parsed[i][1] + } else if (RE_FILENAME.test(parsed[i][0])) { + filename = parsed[i][1] + if (!preservePath) { filename = basename(filename) } + } + } + } else { return skipPart(part) } + + if (header['content-transfer-encoding']) { encoding = header['content-transfer-encoding'][0].toLowerCase() } else { encoding = '7bit' } + + let onData, + onEnd + + if (isPartAFile(fieldname, contype, filename)) { + // file/binary field + if (nfiles === filesLimit) { + if (!boy.hitFilesLimit) { + boy.hitFilesLimit = true + boy.emit('filesLimit') + } + return skipPart(part) + } + + ++nfiles + + if (boy.listenerCount('file') === 0) { + self.parser._ignore() + return + } + + ++nends + const file = new FileStream(fileOpts) + curFile = file + file.on('end', function () { + --nends + self._pause = false + checkFinished() + if (self._cb && !self._needDrain) { + const cb = self._cb + self._cb = undefined + cb() + } + }) + file._read = function (n) { + if (!self._pause) { return } + self._pause = false + if (self._cb && !self._needDrain) { + const cb = self._cb + self._cb = undefined + cb() + } + } + boy.emit('file', fieldname, file, filename, encoding, contype) + + onData = function (data) { + if ((nsize += data.length) > fileSizeLimit) { + const extralen = fileSizeLimit - nsize + data.length + if (extralen > 0) { file.push(data.slice(0, extralen)) } + file.truncated = true + file.bytesRead = fileSizeLimit + part.removeAllListeners('data') + file.emit('limit') + return + } else if (!file.push(data)) { self._pause = true } + + file.bytesRead = nsize + } + + onEnd = function () { + curFile = undefined + file.push(null) + } + } else { + // non-file field + if (nfields === fieldsLimit) { + if (!boy.hitFieldsLimit) { + boy.hitFieldsLimit = true + boy.emit('fieldsLimit') + } + return skipPart(part) + } + + ++nfields + ++nends + let buffer = '' + let truncated = false + curField = part + + onData = function (data) { + if ((nsize += data.length) > fieldSizeLimit) { + const extralen = (fieldSizeLimit - (nsize - data.length)) + buffer += data.toString('binary', 0, extralen) + truncated = true + part.removeAllListeners('data') + } else { buffer += data.toString('binary') } + } + + onEnd = function () { + curField = undefined + if (buffer.length) { buffer = decodeText(buffer, 'binary', charset) } + boy.emit('field', fieldname, buffer, false, truncated, encoding, contype) + --nends + checkFinished() + } + } + + /* As of node@2efe4ab761666 (v0.10.29+/v0.11.14+), busboy had become + broken. Streams2/streams3 is a huge black box of confusion, but + somehow overriding the sync state seems to fix things again (and still + seems to work for previous node versions). + */ + part._readableState.sync = false + + part.on('data', onData) + part.on('end', onEnd) + }).on('error', function (err) { + if (curFile) { curFile.emit('error', err) } + }) + }).on('error', function (err) { + boy.emit('error', err) + }).on('finish', function () { + finished = true + checkFinished() + }) +} + +Multipart.prototype.write = function (chunk, cb) { + const r = this.parser.write(chunk) + if (r && !this._pause) { + cb() + } else { + this._needDrain = !r + this._cb = cb + } +} + +Multipart.prototype.end = function () { + const self = this + + if (self.parser.writable) { + self.parser.end() + } else if (!self._boy._done) { + process.nextTick(function () { + self._boy._done = true + self._boy.emit('finish') + }) + } +} + +function skipPart (part) { + part.resume() +} + +function FileStream (opts) { + Readable.call(this, opts) + + this.bytesRead = 0 + + this.truncated = false +} + +inherits(FileStream, Readable) + +FileStream.prototype._read = function (n) {} + +module.exports = Multipart + + +/***/ }), + +/***/ 80855: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const Decoder = __nccwpck_require__(11496) +const decodeText = __nccwpck_require__(72747) +const getLimit = __nccwpck_require__(22393) + +const RE_CHARSET = /^charset$/i + +UrlEncoded.detect = /^application\/x-www-form-urlencoded/i +function UrlEncoded (boy, cfg) { + const limits = cfg.limits + const parsedConType = cfg.parsedConType + this.boy = boy + + this.fieldSizeLimit = getLimit(limits, 'fieldSize', 1 * 1024 * 1024) + this.fieldNameSizeLimit = getLimit(limits, 'fieldNameSize', 100) + this.fieldsLimit = getLimit(limits, 'fields', Infinity) + + let charset + for (var i = 0, len = parsedConType.length; i < len; ++i) { // eslint-disable-line no-var + if (Array.isArray(parsedConType[i]) && + RE_CHARSET.test(parsedConType[i][0])) { + charset = parsedConType[i][1].toLowerCase() + break + } + } + + if (charset === undefined) { charset = cfg.defCharset || 'utf8' } + + this.decoder = new Decoder() + this.charset = charset + this._fields = 0 + this._state = 'key' + this._checkingBytes = true + this._bytesKey = 0 + this._bytesVal = 0 + this._key = '' + this._val = '' + this._keyTrunc = false + this._valTrunc = false + this._hitLimit = false +} + +UrlEncoded.prototype.write = function (data, cb) { + if (this._fields === this.fieldsLimit) { + if (!this.boy.hitFieldsLimit) { + this.boy.hitFieldsLimit = true + this.boy.emit('fieldsLimit') + } + return cb() + } + + let idxeq; let idxamp; let i; let p = 0; const len = data.length + + while (p < len) { + if (this._state === 'key') { + idxeq = idxamp = undefined + for (i = p; i < len; ++i) { + if (!this._checkingBytes) { ++p } + if (data[i] === 0x3D/* = */) { + idxeq = i + break + } else if (data[i] === 0x26/* & */) { + idxamp = i + break + } + if (this._checkingBytes && this._bytesKey === this.fieldNameSizeLimit) { + this._hitLimit = true + break + } else if (this._checkingBytes) { ++this._bytesKey } + } + + if (idxeq !== undefined) { + // key with assignment + if (idxeq > p) { this._key += this.decoder.write(data.toString('binary', p, idxeq)) } + this._state = 'val' + + this._hitLimit = false + this._checkingBytes = true + this._val = '' + this._bytesVal = 0 + this._valTrunc = false + this.decoder.reset() + + p = idxeq + 1 + } else if (idxamp !== undefined) { + // key with no assignment + ++this._fields + let key; const keyTrunc = this._keyTrunc + if (idxamp > p) { key = (this._key += this.decoder.write(data.toString('binary', p, idxamp))) } else { key = this._key } + + this._hitLimit = false + this._checkingBytes = true + this._key = '' + this._bytesKey = 0 + this._keyTrunc = false + this.decoder.reset() + + if (key.length) { + this.boy.emit('field', decodeText(key, 'binary', this.charset), + '', + keyTrunc, + false) + } + + p = idxamp + 1 + if (this._fields === this.fieldsLimit) { return cb() } + } else if (this._hitLimit) { + // we may not have hit the actual limit if there are encoded bytes... + if (i > p) { this._key += this.decoder.write(data.toString('binary', p, i)) } + p = i + if ((this._bytesKey = this._key.length) === this.fieldNameSizeLimit) { + // yep, we actually did hit the limit + this._checkingBytes = false + this._keyTrunc = true + } + } else { + if (p < len) { this._key += this.decoder.write(data.toString('binary', p)) } + p = len + } + } else { + idxamp = undefined + for (i = p; i < len; ++i) { + if (!this._checkingBytes) { ++p } + if (data[i] === 0x26/* & */) { + idxamp = i + break + } + if (this._checkingBytes && this._bytesVal === this.fieldSizeLimit) { + this._hitLimit = true + break + } else if (this._checkingBytes) { ++this._bytesVal } + } + + if (idxamp !== undefined) { + ++this._fields + if (idxamp > p) { this._val += this.decoder.write(data.toString('binary', p, idxamp)) } + this.boy.emit('field', decodeText(this._key, 'binary', this.charset), + decodeText(this._val, 'binary', this.charset), + this._keyTrunc, + this._valTrunc) + this._state = 'key' + + this._hitLimit = false + this._checkingBytes = true + this._key = '' + this._bytesKey = 0 + this._keyTrunc = false + this.decoder.reset() + + p = idxamp + 1 + if (this._fields === this.fieldsLimit) { return cb() } + } else if (this._hitLimit) { + // we may not have hit the actual limit if there are encoded bytes... + if (i > p) { this._val += this.decoder.write(data.toString('binary', p, i)) } + p = i + if ((this._val === '' && this.fieldSizeLimit === 0) || + (this._bytesVal = this._val.length) === this.fieldSizeLimit) { + // yep, we actually did hit the limit + this._checkingBytes = false + this._valTrunc = true + } + } else { + if (p < len) { this._val += this.decoder.write(data.toString('binary', p)) } + p = len + } + } + } + cb() +} + +UrlEncoded.prototype.end = function () { + if (this.boy._done) { return } + + if (this._state === 'key' && this._key.length > 0) { + this.boy.emit('field', decodeText(this._key, 'binary', this.charset), + '', + this._keyTrunc, + false) + } else if (this._state === 'val') { + this.boy.emit('field', decodeText(this._key, 'binary', this.charset), + decodeText(this._val, 'binary', this.charset), + this._keyTrunc, + this._valTrunc) + } + this.boy._done = true + this.boy.emit('finish') +} + +module.exports = UrlEncoded + + +/***/ }), + +/***/ 11496: +/***/ ((module) => { + +"use strict"; + + +const RE_PLUS = /\+/g + +const HEX = [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, + 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +] + +function Decoder () { + this.buffer = undefined +} +Decoder.prototype.write = function (str) { + // Replace '+' with ' ' before decoding + str = str.replace(RE_PLUS, ' ') + let res = '' + let i = 0; let p = 0; const len = str.length + for (; i < len; ++i) { + if (this.buffer !== undefined) { + if (!HEX[str.charCodeAt(i)]) { + res += '%' + this.buffer + this.buffer = undefined + --i // retry character + } else { + this.buffer += str[i] + ++p + if (this.buffer.length === 2) { + res += String.fromCharCode(parseInt(this.buffer, 16)) + this.buffer = undefined + } + } + } else if (str[i] === '%') { + if (i > p) { + res += str.substring(p, i) + p = i + } + this.buffer = '' + ++p + } + } + if (p < len && this.buffer === undefined) { res += str.substring(p) } + return res +} +Decoder.prototype.reset = function () { + this.buffer = undefined +} + +module.exports = Decoder + + +/***/ }), + +/***/ 20692: +/***/ ((module) => { + +"use strict"; + + +module.exports = function basename (path) { + if (typeof path !== 'string') { return '' } + for (var i = path.length - 1; i >= 0; --i) { // eslint-disable-line no-var + switch (path.charCodeAt(i)) { + case 0x2F: // '/' + case 0x5C: // '\' + path = path.slice(i + 1) + return (path === '..' || path === '.' ? '' : path) + } + } + return (path === '..' || path === '.' ? '' : path) +} + + +/***/ }), + +/***/ 72747: +/***/ (function(module) { + +"use strict"; + + +// Node has always utf-8 +const utf8Decoder = new TextDecoder('utf-8') +const textDecoders = new Map([ + ['utf-8', utf8Decoder], + ['utf8', utf8Decoder] +]) + +function getDecoder (charset) { + let lc + while (true) { + switch (charset) { + case 'utf-8': + case 'utf8': + return decoders.utf8 + case 'latin1': + case 'ascii': // TODO: Make these a separate, strict decoder? + case 'us-ascii': + case 'iso-8859-1': + case 'iso8859-1': + case 'iso88591': + case 'iso_8859-1': + case 'windows-1252': + case 'iso_8859-1:1987': + case 'cp1252': + case 'x-cp1252': + return decoders.latin1 + case 'utf16le': + case 'utf-16le': + case 'ucs2': + case 'ucs-2': + return decoders.utf16le + case 'base64': + return decoders.base64 + default: + if (lc === undefined) { + lc = true + charset = charset.toLowerCase() + continue + } + return decoders.other.bind(charset) + } + } +} + +const decoders = { + utf8: (data, sourceEncoding) => { + if (data.length === 0) { + return '' + } + if (typeof data === 'string') { + data = Buffer.from(data, sourceEncoding) + } + return data.utf8Slice(0, data.length) + }, + + latin1: (data, sourceEncoding) => { + if (data.length === 0) { + return '' + } + if (typeof data === 'string') { + return data + } + return data.latin1Slice(0, data.length) + }, + + utf16le: (data, sourceEncoding) => { + if (data.length === 0) { + return '' + } + if (typeof data === 'string') { + data = Buffer.from(data, sourceEncoding) + } + return data.ucs2Slice(0, data.length) + }, + + base64: (data, sourceEncoding) => { + if (data.length === 0) { + return '' + } + if (typeof data === 'string') { + data = Buffer.from(data, sourceEncoding) + } + return data.base64Slice(0, data.length) + }, + + other: (data, sourceEncoding) => { + if (data.length === 0) { + return '' + } + if (typeof data === 'string') { + data = Buffer.from(data, sourceEncoding) + } + + if (textDecoders.has(this.toString())) { + try { + return textDecoders.get(this).decode(data) + } catch {} + } + return typeof data === 'string' + ? data + : data.toString() + } +} + +function decodeText (text, sourceEncoding, destEncoding) { + if (text) { + return getDecoder(destEncoding)(text, sourceEncoding) + } + return text +} + +module.exports = decodeText + + +/***/ }), + +/***/ 22393: +/***/ ((module) => { + +"use strict"; + + +module.exports = function getLimit (limits, name, defaultLimit) { + if ( + !limits || + limits[name] === undefined || + limits[name] === null + ) { return defaultLimit } + + if ( + typeof limits[name] !== 'number' || + isNaN(limits[name]) + ) { throw new TypeError('Limit ' + name + ' is not a valid number') } + + return limits[name] +} + + +/***/ }), + +/***/ 8929: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; +/* eslint-disable object-property-newline */ + + +const decodeText = __nccwpck_require__(72747) + +const RE_ENCODED = /%[a-fA-F0-9][a-fA-F0-9]/g + +const EncodedLookup = { + '%00': '\x00', '%01': '\x01', '%02': '\x02', '%03': '\x03', '%04': '\x04', + '%05': '\x05', '%06': '\x06', '%07': '\x07', '%08': '\x08', '%09': '\x09', + '%0a': '\x0a', '%0A': '\x0a', '%0b': '\x0b', '%0B': '\x0b', '%0c': '\x0c', + '%0C': '\x0c', '%0d': '\x0d', '%0D': '\x0d', '%0e': '\x0e', '%0E': '\x0e', + '%0f': '\x0f', '%0F': '\x0f', '%10': '\x10', '%11': '\x11', '%12': '\x12', + '%13': '\x13', '%14': '\x14', '%15': '\x15', '%16': '\x16', '%17': '\x17', + '%18': '\x18', '%19': '\x19', '%1a': '\x1a', '%1A': '\x1a', '%1b': '\x1b', + '%1B': '\x1b', '%1c': '\x1c', '%1C': '\x1c', '%1d': '\x1d', '%1D': '\x1d', + '%1e': '\x1e', '%1E': '\x1e', '%1f': '\x1f', '%1F': '\x1f', '%20': '\x20', + '%21': '\x21', '%22': '\x22', '%23': '\x23', '%24': '\x24', '%25': '\x25', + '%26': '\x26', '%27': '\x27', '%28': '\x28', '%29': '\x29', '%2a': '\x2a', + '%2A': '\x2a', '%2b': '\x2b', '%2B': '\x2b', '%2c': '\x2c', '%2C': '\x2c', + '%2d': '\x2d', '%2D': '\x2d', '%2e': '\x2e', '%2E': '\x2e', '%2f': '\x2f', + '%2F': '\x2f', '%30': '\x30', '%31': '\x31', '%32': '\x32', '%33': '\x33', + '%34': '\x34', '%35': '\x35', '%36': '\x36', '%37': '\x37', '%38': '\x38', + '%39': '\x39', '%3a': '\x3a', '%3A': '\x3a', '%3b': '\x3b', '%3B': '\x3b', + '%3c': '\x3c', '%3C': '\x3c', '%3d': '\x3d', '%3D': '\x3d', '%3e': '\x3e', + '%3E': '\x3e', '%3f': '\x3f', '%3F': '\x3f', '%40': '\x40', '%41': '\x41', + '%42': '\x42', '%43': '\x43', '%44': '\x44', '%45': '\x45', '%46': '\x46', + '%47': '\x47', '%48': '\x48', '%49': '\x49', '%4a': '\x4a', '%4A': '\x4a', + '%4b': '\x4b', '%4B': '\x4b', '%4c': '\x4c', '%4C': '\x4c', '%4d': '\x4d', + '%4D': '\x4d', '%4e': '\x4e', '%4E': '\x4e', '%4f': '\x4f', '%4F': '\x4f', + '%50': '\x50', '%51': '\x51', '%52': '\x52', '%53': '\x53', '%54': '\x54', + '%55': '\x55', '%56': '\x56', '%57': '\x57', '%58': '\x58', '%59': '\x59', + '%5a': '\x5a', '%5A': '\x5a', '%5b': '\x5b', '%5B': '\x5b', '%5c': '\x5c', + '%5C': '\x5c', '%5d': '\x5d', '%5D': '\x5d', '%5e': '\x5e', '%5E': '\x5e', + '%5f': '\x5f', '%5F': '\x5f', '%60': '\x60', '%61': '\x61', '%62': '\x62', + '%63': '\x63', '%64': '\x64', '%65': '\x65', '%66': '\x66', '%67': '\x67', + '%68': '\x68', '%69': '\x69', '%6a': '\x6a', '%6A': '\x6a', '%6b': '\x6b', + '%6B': '\x6b', '%6c': '\x6c', '%6C': '\x6c', '%6d': '\x6d', '%6D': '\x6d', + '%6e': '\x6e', '%6E': '\x6e', '%6f': '\x6f', '%6F': '\x6f', '%70': '\x70', + '%71': '\x71', '%72': '\x72', '%73': '\x73', '%74': '\x74', '%75': '\x75', + '%76': '\x76', '%77': '\x77', '%78': '\x78', '%79': '\x79', '%7a': '\x7a', + '%7A': '\x7a', '%7b': '\x7b', '%7B': '\x7b', '%7c': '\x7c', '%7C': '\x7c', + '%7d': '\x7d', '%7D': '\x7d', '%7e': '\x7e', '%7E': '\x7e', '%7f': '\x7f', + '%7F': '\x7f', '%80': '\x80', '%81': '\x81', '%82': '\x82', '%83': '\x83', + '%84': '\x84', '%85': '\x85', '%86': '\x86', '%87': '\x87', '%88': '\x88', + '%89': '\x89', '%8a': '\x8a', '%8A': '\x8a', '%8b': '\x8b', '%8B': '\x8b', + '%8c': '\x8c', '%8C': '\x8c', '%8d': '\x8d', '%8D': '\x8d', '%8e': '\x8e', + '%8E': '\x8e', '%8f': '\x8f', '%8F': '\x8f', '%90': '\x90', '%91': '\x91', + '%92': '\x92', '%93': '\x93', '%94': '\x94', '%95': '\x95', '%96': '\x96', + '%97': '\x97', '%98': '\x98', '%99': '\x99', '%9a': '\x9a', '%9A': '\x9a', + '%9b': '\x9b', '%9B': '\x9b', '%9c': '\x9c', '%9C': '\x9c', '%9d': '\x9d', + '%9D': '\x9d', '%9e': '\x9e', '%9E': '\x9e', '%9f': '\x9f', '%9F': '\x9f', + '%a0': '\xa0', '%A0': '\xa0', '%a1': '\xa1', '%A1': '\xa1', '%a2': '\xa2', + '%A2': '\xa2', '%a3': '\xa3', '%A3': '\xa3', '%a4': '\xa4', '%A4': '\xa4', + '%a5': '\xa5', '%A5': '\xa5', '%a6': '\xa6', '%A6': '\xa6', '%a7': '\xa7', + '%A7': '\xa7', '%a8': '\xa8', '%A8': '\xa8', '%a9': '\xa9', '%A9': '\xa9', + '%aa': '\xaa', '%Aa': '\xaa', '%aA': '\xaa', '%AA': '\xaa', '%ab': '\xab', + '%Ab': '\xab', '%aB': '\xab', '%AB': '\xab', '%ac': '\xac', '%Ac': '\xac', + '%aC': '\xac', '%AC': '\xac', '%ad': '\xad', '%Ad': '\xad', '%aD': '\xad', + '%AD': '\xad', '%ae': '\xae', '%Ae': '\xae', '%aE': '\xae', '%AE': '\xae', + '%af': '\xaf', '%Af': '\xaf', '%aF': '\xaf', '%AF': '\xaf', '%b0': '\xb0', + '%B0': '\xb0', '%b1': '\xb1', '%B1': '\xb1', '%b2': '\xb2', '%B2': '\xb2', + '%b3': '\xb3', '%B3': '\xb3', '%b4': '\xb4', '%B4': '\xb4', '%b5': '\xb5', + '%B5': '\xb5', '%b6': '\xb6', '%B6': '\xb6', '%b7': '\xb7', '%B7': '\xb7', + '%b8': '\xb8', '%B8': '\xb8', '%b9': '\xb9', '%B9': '\xb9', '%ba': '\xba', + '%Ba': '\xba', '%bA': '\xba', '%BA': '\xba', '%bb': '\xbb', '%Bb': '\xbb', + '%bB': '\xbb', '%BB': '\xbb', '%bc': '\xbc', '%Bc': '\xbc', '%bC': '\xbc', + '%BC': '\xbc', '%bd': '\xbd', '%Bd': '\xbd', '%bD': '\xbd', '%BD': '\xbd', + '%be': '\xbe', '%Be': '\xbe', '%bE': '\xbe', '%BE': '\xbe', '%bf': '\xbf', + '%Bf': '\xbf', '%bF': '\xbf', '%BF': '\xbf', '%c0': '\xc0', '%C0': '\xc0', + '%c1': '\xc1', '%C1': '\xc1', '%c2': '\xc2', '%C2': '\xc2', '%c3': '\xc3', + '%C3': '\xc3', '%c4': '\xc4', '%C4': '\xc4', '%c5': '\xc5', '%C5': '\xc5', + '%c6': '\xc6', '%C6': '\xc6', '%c7': '\xc7', '%C7': '\xc7', '%c8': '\xc8', + '%C8': '\xc8', '%c9': '\xc9', '%C9': '\xc9', '%ca': '\xca', '%Ca': '\xca', + '%cA': '\xca', '%CA': '\xca', '%cb': '\xcb', '%Cb': '\xcb', '%cB': '\xcb', + '%CB': '\xcb', '%cc': '\xcc', '%Cc': '\xcc', '%cC': '\xcc', '%CC': '\xcc', + '%cd': '\xcd', '%Cd': '\xcd', '%cD': '\xcd', '%CD': '\xcd', '%ce': '\xce', + '%Ce': '\xce', '%cE': '\xce', '%CE': '\xce', '%cf': '\xcf', '%Cf': '\xcf', + '%cF': '\xcf', '%CF': '\xcf', '%d0': '\xd0', '%D0': '\xd0', '%d1': '\xd1', + '%D1': '\xd1', '%d2': '\xd2', '%D2': '\xd2', '%d3': '\xd3', '%D3': '\xd3', + '%d4': '\xd4', '%D4': '\xd4', '%d5': '\xd5', '%D5': '\xd5', '%d6': '\xd6', + '%D6': '\xd6', '%d7': '\xd7', '%D7': '\xd7', '%d8': '\xd8', '%D8': '\xd8', + '%d9': '\xd9', '%D9': '\xd9', '%da': '\xda', '%Da': '\xda', '%dA': '\xda', + '%DA': '\xda', '%db': '\xdb', '%Db': '\xdb', '%dB': '\xdb', '%DB': '\xdb', + '%dc': '\xdc', '%Dc': '\xdc', '%dC': '\xdc', '%DC': '\xdc', '%dd': '\xdd', + '%Dd': '\xdd', '%dD': '\xdd', '%DD': '\xdd', '%de': '\xde', '%De': '\xde', + '%dE': '\xde', '%DE': '\xde', '%df': '\xdf', '%Df': '\xdf', '%dF': '\xdf', + '%DF': '\xdf', '%e0': '\xe0', '%E0': '\xe0', '%e1': '\xe1', '%E1': '\xe1', + '%e2': '\xe2', '%E2': '\xe2', '%e3': '\xe3', '%E3': '\xe3', '%e4': '\xe4', + '%E4': '\xe4', '%e5': '\xe5', '%E5': '\xe5', '%e6': '\xe6', '%E6': '\xe6', + '%e7': '\xe7', '%E7': '\xe7', '%e8': '\xe8', '%E8': '\xe8', '%e9': '\xe9', + '%E9': '\xe9', '%ea': '\xea', '%Ea': '\xea', '%eA': '\xea', '%EA': '\xea', + '%eb': '\xeb', '%Eb': '\xeb', '%eB': '\xeb', '%EB': '\xeb', '%ec': '\xec', + '%Ec': '\xec', '%eC': '\xec', '%EC': '\xec', '%ed': '\xed', '%Ed': '\xed', + '%eD': '\xed', '%ED': '\xed', '%ee': '\xee', '%Ee': '\xee', '%eE': '\xee', + '%EE': '\xee', '%ef': '\xef', '%Ef': '\xef', '%eF': '\xef', '%EF': '\xef', + '%f0': '\xf0', '%F0': '\xf0', '%f1': '\xf1', '%F1': '\xf1', '%f2': '\xf2', + '%F2': '\xf2', '%f3': '\xf3', '%F3': '\xf3', '%f4': '\xf4', '%F4': '\xf4', + '%f5': '\xf5', '%F5': '\xf5', '%f6': '\xf6', '%F6': '\xf6', '%f7': '\xf7', + '%F7': '\xf7', '%f8': '\xf8', '%F8': '\xf8', '%f9': '\xf9', '%F9': '\xf9', + '%fa': '\xfa', '%Fa': '\xfa', '%fA': '\xfa', '%FA': '\xfa', '%fb': '\xfb', + '%Fb': '\xfb', '%fB': '\xfb', '%FB': '\xfb', '%fc': '\xfc', '%Fc': '\xfc', + '%fC': '\xfc', '%FC': '\xfc', '%fd': '\xfd', '%Fd': '\xfd', '%fD': '\xfd', + '%FD': '\xfd', '%fe': '\xfe', '%Fe': '\xfe', '%fE': '\xfe', '%FE': '\xfe', + '%ff': '\xff', '%Ff': '\xff', '%fF': '\xff', '%FF': '\xff' +} + +function encodedReplacer (match) { + return EncodedLookup[match] +} + +const STATE_KEY = 0 +const STATE_VALUE = 1 +const STATE_CHARSET = 2 +const STATE_LANG = 3 + +function parseParams (str) { + const res = [] + let state = STATE_KEY + let charset = '' + let inquote = false + let escaping = false + let p = 0 + let tmp = '' + const len = str.length + + for (var i = 0; i < len; ++i) { // eslint-disable-line no-var + const char = str[i] + if (char === '\\' && inquote) { + if (escaping) { escaping = false } else { + escaping = true + continue + } + } else if (char === '"') { + if (!escaping) { + if (inquote) { + inquote = false + state = STATE_KEY + } else { inquote = true } + continue + } else { escaping = false } + } else { + if (escaping && inquote) { tmp += '\\' } + escaping = false + if ((state === STATE_CHARSET || state === STATE_LANG) && char === "'") { + if (state === STATE_CHARSET) { + state = STATE_LANG + charset = tmp.substring(1) + } else { state = STATE_VALUE } + tmp = '' + continue + } else if (state === STATE_KEY && + (char === '*' || char === '=') && + res.length) { + state = char === '*' + ? STATE_CHARSET + : STATE_VALUE + res[p] = [tmp, undefined] + tmp = '' + continue + } else if (!inquote && char === ';') { + state = STATE_KEY + if (charset) { + if (tmp.length) { + tmp = decodeText(tmp.replace(RE_ENCODED, encodedReplacer), + 'binary', + charset) + } + charset = '' + } else if (tmp.length) { + tmp = decodeText(tmp, 'binary', 'utf8') + } + if (res[p] === undefined) { res[p] = tmp } else { res[p][1] = tmp } + tmp = '' + ++p + continue + } else if (!inquote && (char === ' ' || char === '\t')) { continue } + } + tmp += char + } + if (charset && tmp.length) { + tmp = decodeText(tmp.replace(RE_ENCODED, encodedReplacer), + 'binary', + charset) + } else if (tmp) { + tmp = decodeText(tmp, 'binary', 'utf8') + } + + if (res[p] === undefined) { + if (tmp) { res[p] = tmp } + } else { res[p][1] = tmp } + + return res +} + +module.exports = parseParams + + +/***/ }), + +/***/ 29602: +/***/ ((module) => { + +"use strict"; +module.exports = /*#__PURE__*/JSON.parse('{"name":"@babel/core","version":"7.12.9","description":"Babel compiler core.","main":"lib/index.js","author":"Sebastian McKenzie ","homepage":"https://babeljs.io/","license":"MIT","publishConfig":{"access":"public"},"repository":{"type":"git","url":"https://github.com/babel/babel.git","directory":"packages/babel-core"},"keywords":["6to5","babel","classes","const","es6","harmony","let","modules","transpile","transpiler","var","babel-core","compiler"],"engines":{"node":">=6.9.0"},"funding":{"type":"opencollective","url":"https://opencollective.com/babel"},"browser":{"./lib/config/files/index.js":"./lib/config/files/index-browser.js","./lib/transform-file.js":"./lib/transform-file-browser.js","./src/config/files/index.js":"./src/config/files/index-browser.js","./src/transform-file.js":"./src/transform-file-browser.js"},"dependencies":{"@babel/code-frame":"^7.10.4","@babel/generator":"^7.12.5","@babel/helper-module-transforms":"^7.12.1","@babel/helpers":"^7.12.5","@babel/parser":"^7.12.7","@babel/template":"^7.12.7","@babel/traverse":"^7.12.9","@babel/types":"^7.12.7","convert-source-map":"^1.7.0","debug":"^4.1.0","gensync":"^1.0.0-beta.1","json5":"^2.1.2","lodash":"^4.17.19","resolve":"^1.3.2","semver":"^5.4.1","source-map":"^0.5.0"},"devDependencies":{"@babel/helper-transform-fixture-test-runner":"7.12.1"}}'); + +/***/ }), + +/***/ 33757: +/***/ ((module) => { + +"use strict"; +module.exports = /*#__PURE__*/JSON.parse('{"nbsp":" ","iexcl":"¡","cent":"¢","pound":"£","curren":"¤","yen":"¥","brvbar":"¦","sect":"§","uml":"¨","copy":"©","ordf":"ª","laquo":"«","not":"¬","shy":"­","reg":"®","macr":"¯","deg":"°","plusmn":"±","sup2":"²","sup3":"³","acute":"´","micro":"µ","para":"¶","middot":"·","cedil":"¸","sup1":"¹","ordm":"º","raquo":"»","frac14":"¼","frac12":"½","frac34":"¾","iquest":"¿","Agrave":"À","Aacute":"Á","Acirc":"Â","Atilde":"Ã","Auml":"Ä","Aring":"Å","AElig":"Æ","Ccedil":"Ç","Egrave":"È","Eacute":"É","Ecirc":"Ê","Euml":"Ë","Igrave":"Ì","Iacute":"Í","Icirc":"Î","Iuml":"Ï","ETH":"Ð","Ntilde":"Ñ","Ograve":"Ò","Oacute":"Ó","Ocirc":"Ô","Otilde":"Õ","Ouml":"Ö","times":"×","Oslash":"Ø","Ugrave":"Ù","Uacute":"Ú","Ucirc":"Û","Uuml":"Ü","Yacute":"Ý","THORN":"Þ","szlig":"ß","agrave":"à","aacute":"á","acirc":"â","atilde":"ã","auml":"ä","aring":"å","aelig":"æ","ccedil":"ç","egrave":"è","eacute":"é","ecirc":"ê","euml":"ë","igrave":"ì","iacute":"í","icirc":"î","iuml":"ï","eth":"ð","ntilde":"ñ","ograve":"ò","oacute":"ó","ocirc":"ô","otilde":"õ","ouml":"ö","divide":"÷","oslash":"ø","ugrave":"ù","uacute":"ú","ucirc":"û","uuml":"ü","yacute":"ý","thorn":"þ","yuml":"ÿ","fnof":"ƒ","Alpha":"Α","Beta":"Β","Gamma":"Γ","Delta":"Δ","Epsilon":"Ε","Zeta":"Ζ","Eta":"Η","Theta":"Θ","Iota":"Ι","Kappa":"Κ","Lambda":"Λ","Mu":"Μ","Nu":"Ν","Xi":"Ξ","Omicron":"Ο","Pi":"Π","Rho":"Ρ","Sigma":"Σ","Tau":"Τ","Upsilon":"Υ","Phi":"Φ","Chi":"Χ","Psi":"Ψ","Omega":"Ω","alpha":"α","beta":"β","gamma":"γ","delta":"δ","epsilon":"ε","zeta":"ζ","eta":"η","theta":"θ","iota":"ι","kappa":"κ","lambda":"λ","mu":"μ","nu":"ν","xi":"ξ","omicron":"ο","pi":"π","rho":"ρ","sigmaf":"ς","sigma":"σ","tau":"τ","upsilon":"υ","phi":"φ","chi":"χ","psi":"ψ","omega":"ω","thetasym":"ϑ","upsih":"ϒ","piv":"ϖ","bull":"•","hellip":"…","prime":"′","Prime":"″","oline":"‾","frasl":"⁄","weierp":"℘","image":"ℑ","real":"ℜ","trade":"™","alefsym":"ℵ","larr":"←","uarr":"↑","rarr":"→","darr":"↓","harr":"↔","crarr":"↵","lArr":"⇐","uArr":"⇑","rArr":"⇒","dArr":"⇓","hArr":"⇔","forall":"∀","part":"∂","exist":"∃","empty":"∅","nabla":"∇","isin":"∈","notin":"∉","ni":"∋","prod":"∏","sum":"∑","minus":"−","lowast":"∗","radic":"√","prop":"∝","infin":"∞","ang":"∠","and":"∧","or":"∨","cap":"∩","cup":"∪","int":"∫","there4":"∴","sim":"∼","cong":"≅","asymp":"≈","ne":"≠","equiv":"≡","le":"≤","ge":"≥","sub":"⊂","sup":"⊃","nsub":"⊄","sube":"⊆","supe":"⊇","oplus":"⊕","otimes":"⊗","perp":"⊥","sdot":"⋅","lceil":"⌈","rceil":"⌉","lfloor":"⌊","rfloor":"⌋","lang":"〈","rang":"〉","loz":"◊","spades":"♠","clubs":"♣","hearts":"♥","diams":"♦","quot":"\\"","amp":"&","lt":"<","gt":">","OElig":"Œ","oelig":"œ","Scaron":"Š","scaron":"š","Yuml":"Ÿ","circ":"ˆ","tilde":"˜","ensp":" ","emsp":" ","thinsp":" ","zwnj":"‌","zwj":"‍","lrm":"‎","rlm":"‏","ndash":"–","mdash":"—","lsquo":"‘","rsquo":"’","sbquo":"‚","ldquo":"“","rdquo":"”","bdquo":"„","dagger":"†","Dagger":"‡","permil":"‰","lsaquo":"‹","rsaquo":"›","euro":"€"}'); + +/***/ }), + +/***/ 82719: +/***/ ((module) => { + +"use strict"; +module.exports = /*#__PURE__*/JSON.parse('{"AElig":"Æ","AMP":"&","Aacute":"Á","Acirc":"Â","Agrave":"À","Aring":"Å","Atilde":"Ã","Auml":"Ä","COPY":"©","Ccedil":"Ç","ETH":"Ð","Eacute":"É","Ecirc":"Ê","Egrave":"È","Euml":"Ë","GT":">","Iacute":"Í","Icirc":"Î","Igrave":"Ì","Iuml":"Ï","LT":"<","Ntilde":"Ñ","Oacute":"Ó","Ocirc":"Ô","Ograve":"Ò","Oslash":"Ø","Otilde":"Õ","Ouml":"Ö","QUOT":"\\"","REG":"®","THORN":"Þ","Uacute":"Ú","Ucirc":"Û","Ugrave":"Ù","Uuml":"Ü","Yacute":"Ý","aacute":"á","acirc":"â","acute":"´","aelig":"æ","agrave":"à","amp":"&","aring":"å","atilde":"ã","auml":"ä","brvbar":"¦","ccedil":"ç","cedil":"¸","cent":"¢","copy":"©","curren":"¤","deg":"°","divide":"÷","eacute":"é","ecirc":"ê","egrave":"è","eth":"ð","euml":"ë","frac12":"½","frac14":"¼","frac34":"¾","gt":">","iacute":"í","icirc":"î","iexcl":"¡","igrave":"ì","iquest":"¿","iuml":"ï","laquo":"«","lt":"<","macr":"¯","micro":"µ","middot":"·","nbsp":" ","not":"¬","ntilde":"ñ","oacute":"ó","ocirc":"ô","ograve":"ò","ordf":"ª","ordm":"º","oslash":"ø","otilde":"õ","ouml":"ö","para":"¶","plusmn":"±","pound":"£","quot":"\\"","raquo":"»","reg":"®","sect":"§","shy":"­","sup1":"¹","sup2":"²","sup3":"³","szlig":"ß","thorn":"þ","times":"×","uacute":"ú","ucirc":"û","ugrave":"ù","uml":"¨","uuml":"ü","yacute":"ý","yen":"¥","yuml":"ÿ"}'); + +/***/ }), + +/***/ 37839: +/***/ ((module) => { + +"use strict"; +module.exports = /*#__PURE__*/JSON.parse('{"AEli":"Æ","AElig":"Æ","AM":"&","AMP":"&","Aacut":"Á","Aacute":"Á","Abreve":"Ă","Acir":"Â","Acirc":"Â","Acy":"А","Afr":"𝔄","Agrav":"À","Agrave":"À","Alpha":"Α","Amacr":"Ā","And":"⩓","Aogon":"Ą","Aopf":"𝔸","ApplyFunction":"⁡","Arin":"Å","Aring":"Å","Ascr":"𝒜","Assign":"≔","Atild":"Ã","Atilde":"Ã","Aum":"Ä","Auml":"Ä","Backslash":"∖","Barv":"⫧","Barwed":"⌆","Bcy":"Б","Because":"∵","Bernoullis":"ℬ","Beta":"Β","Bfr":"𝔅","Bopf":"𝔹","Breve":"˘","Bscr":"ℬ","Bumpeq":"≎","CHcy":"Ч","COP":"©","COPY":"©","Cacute":"Ć","Cap":"⋒","CapitalDifferentialD":"ⅅ","Cayleys":"ℭ","Ccaron":"Č","Ccedi":"Ç","Ccedil":"Ç","Ccirc":"Ĉ","Cconint":"∰","Cdot":"Ċ","Cedilla":"¸","CenterDot":"·","Cfr":"ℭ","Chi":"Χ","CircleDot":"⊙","CircleMinus":"⊖","CirclePlus":"⊕","CircleTimes":"⊗","ClockwiseContourIntegral":"∲","CloseCurlyDoubleQuote":"”","CloseCurlyQuote":"’","Colon":"∷","Colone":"⩴","Congruent":"≡","Conint":"∯","ContourIntegral":"∮","Copf":"ℂ","Coproduct":"∐","CounterClockwiseContourIntegral":"∳","Cross":"⨯","Cscr":"𝒞","Cup":"⋓","CupCap":"≍","DD":"ⅅ","DDotrahd":"⤑","DJcy":"Ђ","DScy":"Ѕ","DZcy":"Џ","Dagger":"‡","Darr":"↡","Dashv":"⫤","Dcaron":"Ď","Dcy":"Д","Del":"∇","Delta":"Δ","Dfr":"𝔇","DiacriticalAcute":"´","DiacriticalDot":"˙","DiacriticalDoubleAcute":"˝","DiacriticalGrave":"`","DiacriticalTilde":"˜","Diamond":"⋄","DifferentialD":"ⅆ","Dopf":"𝔻","Dot":"¨","DotDot":"⃜","DotEqual":"≐","DoubleContourIntegral":"∯","DoubleDot":"¨","DoubleDownArrow":"⇓","DoubleLeftArrow":"⇐","DoubleLeftRightArrow":"⇔","DoubleLeftTee":"⫤","DoubleLongLeftArrow":"⟸","DoubleLongLeftRightArrow":"⟺","DoubleLongRightArrow":"⟹","DoubleRightArrow":"⇒","DoubleRightTee":"⊨","DoubleUpArrow":"⇑","DoubleUpDownArrow":"⇕","DoubleVerticalBar":"∥","DownArrow":"↓","DownArrowBar":"⤓","DownArrowUpArrow":"⇵","DownBreve":"̑","DownLeftRightVector":"⥐","DownLeftTeeVector":"⥞","DownLeftVector":"↽","DownLeftVectorBar":"⥖","DownRightTeeVector":"⥟","DownRightVector":"⇁","DownRightVectorBar":"⥗","DownTee":"⊤","DownTeeArrow":"↧","Downarrow":"⇓","Dscr":"𝒟","Dstrok":"Đ","ENG":"Ŋ","ET":"Ð","ETH":"Ð","Eacut":"É","Eacute":"É","Ecaron":"Ě","Ecir":"Ê","Ecirc":"Ê","Ecy":"Э","Edot":"Ė","Efr":"𝔈","Egrav":"È","Egrave":"È","Element":"∈","Emacr":"Ē","EmptySmallSquare":"◻","EmptyVerySmallSquare":"▫","Eogon":"Ę","Eopf":"𝔼","Epsilon":"Ε","Equal":"⩵","EqualTilde":"≂","Equilibrium":"⇌","Escr":"ℰ","Esim":"⩳","Eta":"Η","Eum":"Ë","Euml":"Ë","Exists":"∃","ExponentialE":"ⅇ","Fcy":"Ф","Ffr":"𝔉","FilledSmallSquare":"◼","FilledVerySmallSquare":"▪","Fopf":"𝔽","ForAll":"∀","Fouriertrf":"ℱ","Fscr":"ℱ","GJcy":"Ѓ","G":">","GT":">","Gamma":"Γ","Gammad":"Ϝ","Gbreve":"Ğ","Gcedil":"Ģ","Gcirc":"Ĝ","Gcy":"Г","Gdot":"Ġ","Gfr":"𝔊","Gg":"⋙","Gopf":"𝔾","GreaterEqual":"≥","GreaterEqualLess":"⋛","GreaterFullEqual":"≧","GreaterGreater":"⪢","GreaterLess":"≷","GreaterSlantEqual":"⩾","GreaterTilde":"≳","Gscr":"𝒢","Gt":"≫","HARDcy":"Ъ","Hacek":"ˇ","Hat":"^","Hcirc":"Ĥ","Hfr":"ℌ","HilbertSpace":"ℋ","Hopf":"ℍ","HorizontalLine":"─","Hscr":"ℋ","Hstrok":"Ħ","HumpDownHump":"≎","HumpEqual":"≏","IEcy":"Е","IJlig":"IJ","IOcy":"Ё","Iacut":"Í","Iacute":"Í","Icir":"Î","Icirc":"Î","Icy":"И","Idot":"İ","Ifr":"ℑ","Igrav":"Ì","Igrave":"Ì","Im":"ℑ","Imacr":"Ī","ImaginaryI":"ⅈ","Implies":"⇒","Int":"∬","Integral":"∫","Intersection":"⋂","InvisibleComma":"⁣","InvisibleTimes":"⁢","Iogon":"Į","Iopf":"𝕀","Iota":"Ι","Iscr":"ℐ","Itilde":"Ĩ","Iukcy":"І","Ium":"Ï","Iuml":"Ï","Jcirc":"Ĵ","Jcy":"Й","Jfr":"𝔍","Jopf":"𝕁","Jscr":"𝒥","Jsercy":"Ј","Jukcy":"Є","KHcy":"Х","KJcy":"Ќ","Kappa":"Κ","Kcedil":"Ķ","Kcy":"К","Kfr":"𝔎","Kopf":"𝕂","Kscr":"𝒦","LJcy":"Љ","L":"<","LT":"<","Lacute":"Ĺ","Lambda":"Λ","Lang":"⟪","Laplacetrf":"ℒ","Larr":"↞","Lcaron":"Ľ","Lcedil":"Ļ","Lcy":"Л","LeftAngleBracket":"⟨","LeftArrow":"←","LeftArrowBar":"⇤","LeftArrowRightArrow":"⇆","LeftCeiling":"⌈","LeftDoubleBracket":"⟦","LeftDownTeeVector":"⥡","LeftDownVector":"⇃","LeftDownVectorBar":"⥙","LeftFloor":"⌊","LeftRightArrow":"↔","LeftRightVector":"⥎","LeftTee":"⊣","LeftTeeArrow":"↤","LeftTeeVector":"⥚","LeftTriangle":"⊲","LeftTriangleBar":"⧏","LeftTriangleEqual":"⊴","LeftUpDownVector":"⥑","LeftUpTeeVector":"⥠","LeftUpVector":"↿","LeftUpVectorBar":"⥘","LeftVector":"↼","LeftVectorBar":"⥒","Leftarrow":"⇐","Leftrightarrow":"⇔","LessEqualGreater":"⋚","LessFullEqual":"≦","LessGreater":"≶","LessLess":"⪡","LessSlantEqual":"⩽","LessTilde":"≲","Lfr":"𝔏","Ll":"⋘","Lleftarrow":"⇚","Lmidot":"Ŀ","LongLeftArrow":"⟵","LongLeftRightArrow":"⟷","LongRightArrow":"⟶","Longleftarrow":"⟸","Longleftrightarrow":"⟺","Longrightarrow":"⟹","Lopf":"𝕃","LowerLeftArrow":"↙","LowerRightArrow":"↘","Lscr":"ℒ","Lsh":"↰","Lstrok":"Ł","Lt":"≪","Map":"⤅","Mcy":"М","MediumSpace":" ","Mellintrf":"ℳ","Mfr":"𝔐","MinusPlus":"∓","Mopf":"𝕄","Mscr":"ℳ","Mu":"Μ","NJcy":"Њ","Nacute":"Ń","Ncaron":"Ň","Ncedil":"Ņ","Ncy":"Н","NegativeMediumSpace":"​","NegativeThickSpace":"​","NegativeThinSpace":"​","NegativeVeryThinSpace":"​","NestedGreaterGreater":"≫","NestedLessLess":"≪","NewLine":"\\n","Nfr":"𝔑","NoBreak":"⁠","NonBreakingSpace":" ","Nopf":"ℕ","Not":"⫬","NotCongruent":"≢","NotCupCap":"≭","NotDoubleVerticalBar":"∦","NotElement":"∉","NotEqual":"≠","NotEqualTilde":"≂̸","NotExists":"∄","NotGreater":"≯","NotGreaterEqual":"≱","NotGreaterFullEqual":"≧̸","NotGreaterGreater":"≫̸","NotGreaterLess":"≹","NotGreaterSlantEqual":"⩾̸","NotGreaterTilde":"≵","NotHumpDownHump":"≎̸","NotHumpEqual":"≏̸","NotLeftTriangle":"⋪","NotLeftTriangleBar":"⧏̸","NotLeftTriangleEqual":"⋬","NotLess":"≮","NotLessEqual":"≰","NotLessGreater":"≸","NotLessLess":"≪̸","NotLessSlantEqual":"⩽̸","NotLessTilde":"≴","NotNestedGreaterGreater":"⪢̸","NotNestedLessLess":"⪡̸","NotPrecedes":"⊀","NotPrecedesEqual":"⪯̸","NotPrecedesSlantEqual":"⋠","NotReverseElement":"∌","NotRightTriangle":"⋫","NotRightTriangleBar":"⧐̸","NotRightTriangleEqual":"⋭","NotSquareSubset":"⊏̸","NotSquareSubsetEqual":"⋢","NotSquareSuperset":"⊐̸","NotSquareSupersetEqual":"⋣","NotSubset":"⊂⃒","NotSubsetEqual":"⊈","NotSucceeds":"⊁","NotSucceedsEqual":"⪰̸","NotSucceedsSlantEqual":"⋡","NotSucceedsTilde":"≿̸","NotSuperset":"⊃⃒","NotSupersetEqual":"⊉","NotTilde":"≁","NotTildeEqual":"≄","NotTildeFullEqual":"≇","NotTildeTilde":"≉","NotVerticalBar":"∤","Nscr":"𝒩","Ntild":"Ñ","Ntilde":"Ñ","Nu":"Ν","OElig":"Œ","Oacut":"Ó","Oacute":"Ó","Ocir":"Ô","Ocirc":"Ô","Ocy":"О","Odblac":"Ő","Ofr":"𝔒","Ograv":"Ò","Ograve":"Ò","Omacr":"Ō","Omega":"Ω","Omicron":"Ο","Oopf":"𝕆","OpenCurlyDoubleQuote":"“","OpenCurlyQuote":"‘","Or":"⩔","Oscr":"𝒪","Oslas":"Ø","Oslash":"Ø","Otild":"Õ","Otilde":"Õ","Otimes":"⨷","Oum":"Ö","Ouml":"Ö","OverBar":"‾","OverBrace":"⏞","OverBracket":"⎴","OverParenthesis":"⏜","PartialD":"∂","Pcy":"П","Pfr":"𝔓","Phi":"Φ","Pi":"Π","PlusMinus":"±","Poincareplane":"ℌ","Popf":"ℙ","Pr":"⪻","Precedes":"≺","PrecedesEqual":"⪯","PrecedesSlantEqual":"≼","PrecedesTilde":"≾","Prime":"″","Product":"∏","Proportion":"∷","Proportional":"∝","Pscr":"𝒫","Psi":"Ψ","QUO":"\\"","QUOT":"\\"","Qfr":"𝔔","Qopf":"ℚ","Qscr":"𝒬","RBarr":"⤐","RE":"®","REG":"®","Racute":"Ŕ","Rang":"⟫","Rarr":"↠","Rarrtl":"⤖","Rcaron":"Ř","Rcedil":"Ŗ","Rcy":"Р","Re":"ℜ","ReverseElement":"∋","ReverseEquilibrium":"⇋","ReverseUpEquilibrium":"⥯","Rfr":"ℜ","Rho":"Ρ","RightAngleBracket":"⟩","RightArrow":"→","RightArrowBar":"⇥","RightArrowLeftArrow":"⇄","RightCeiling":"⌉","RightDoubleBracket":"⟧","RightDownTeeVector":"⥝","RightDownVector":"⇂","RightDownVectorBar":"⥕","RightFloor":"⌋","RightTee":"⊢","RightTeeArrow":"↦","RightTeeVector":"⥛","RightTriangle":"⊳","RightTriangleBar":"⧐","RightTriangleEqual":"⊵","RightUpDownVector":"⥏","RightUpTeeVector":"⥜","RightUpVector":"↾","RightUpVectorBar":"⥔","RightVector":"⇀","RightVectorBar":"⥓","Rightarrow":"⇒","Ropf":"ℝ","RoundImplies":"⥰","Rrightarrow":"⇛","Rscr":"ℛ","Rsh":"↱","RuleDelayed":"⧴","SHCHcy":"Щ","SHcy":"Ш","SOFTcy":"Ь","Sacute":"Ś","Sc":"⪼","Scaron":"Š","Scedil":"Ş","Scirc":"Ŝ","Scy":"С","Sfr":"𝔖","ShortDownArrow":"↓","ShortLeftArrow":"←","ShortRightArrow":"→","ShortUpArrow":"↑","Sigma":"Σ","SmallCircle":"∘","Sopf":"𝕊","Sqrt":"√","Square":"□","SquareIntersection":"⊓","SquareSubset":"⊏","SquareSubsetEqual":"⊑","SquareSuperset":"⊐","SquareSupersetEqual":"⊒","SquareUnion":"⊔","Sscr":"𝒮","Star":"⋆","Sub":"⋐","Subset":"⋐","SubsetEqual":"⊆","Succeeds":"≻","SucceedsEqual":"⪰","SucceedsSlantEqual":"≽","SucceedsTilde":"≿","SuchThat":"∋","Sum":"∑","Sup":"⋑","Superset":"⊃","SupersetEqual":"⊇","Supset":"⋑","THOR":"Þ","THORN":"Þ","TRADE":"™","TSHcy":"Ћ","TScy":"Ц","Tab":"\\t","Tau":"Τ","Tcaron":"Ť","Tcedil":"Ţ","Tcy":"Т","Tfr":"𝔗","Therefore":"∴","Theta":"Θ","ThickSpace":"  ","ThinSpace":" ","Tilde":"∼","TildeEqual":"≃","TildeFullEqual":"≅","TildeTilde":"≈","Topf":"𝕋","TripleDot":"⃛","Tscr":"𝒯","Tstrok":"Ŧ","Uacut":"Ú","Uacute":"Ú","Uarr":"↟","Uarrocir":"⥉","Ubrcy":"Ў","Ubreve":"Ŭ","Ucir":"Û","Ucirc":"Û","Ucy":"У","Udblac":"Ű","Ufr":"𝔘","Ugrav":"Ù","Ugrave":"Ù","Umacr":"Ū","UnderBar":"_","UnderBrace":"⏟","UnderBracket":"⎵","UnderParenthesis":"⏝","Union":"⋃","UnionPlus":"⊎","Uogon":"Ų","Uopf":"𝕌","UpArrow":"↑","UpArrowBar":"⤒","UpArrowDownArrow":"⇅","UpDownArrow":"↕","UpEquilibrium":"⥮","UpTee":"⊥","UpTeeArrow":"↥","Uparrow":"⇑","Updownarrow":"⇕","UpperLeftArrow":"↖","UpperRightArrow":"↗","Upsi":"ϒ","Upsilon":"Υ","Uring":"Ů","Uscr":"𝒰","Utilde":"Ũ","Uum":"Ü","Uuml":"Ü","VDash":"⊫","Vbar":"⫫","Vcy":"В","Vdash":"⊩","Vdashl":"⫦","Vee":"⋁","Verbar":"‖","Vert":"‖","VerticalBar":"∣","VerticalLine":"|","VerticalSeparator":"❘","VerticalTilde":"≀","VeryThinSpace":" ","Vfr":"𝔙","Vopf":"𝕍","Vscr":"𝒱","Vvdash":"⊪","Wcirc":"Ŵ","Wedge":"⋀","Wfr":"𝔚","Wopf":"𝕎","Wscr":"𝒲","Xfr":"𝔛","Xi":"Ξ","Xopf":"𝕏","Xscr":"𝒳","YAcy":"Я","YIcy":"Ї","YUcy":"Ю","Yacut":"Ý","Yacute":"Ý","Ycirc":"Ŷ","Ycy":"Ы","Yfr":"𝔜","Yopf":"𝕐","Yscr":"𝒴","Yuml":"Ÿ","ZHcy":"Ж","Zacute":"Ź","Zcaron":"Ž","Zcy":"З","Zdot":"Ż","ZeroWidthSpace":"​","Zeta":"Ζ","Zfr":"ℨ","Zopf":"ℤ","Zscr":"𝒵","aacut":"á","aacute":"á","abreve":"ă","ac":"∾","acE":"∾̳","acd":"∿","acir":"â","acirc":"â","acut":"´","acute":"´","acy":"а","aeli":"æ","aelig":"æ","af":"⁡","afr":"𝔞","agrav":"à","agrave":"à","alefsym":"ℵ","aleph":"ℵ","alpha":"α","amacr":"ā","amalg":"⨿","am":"&","amp":"&","and":"∧","andand":"⩕","andd":"⩜","andslope":"⩘","andv":"⩚","ang":"∠","ange":"⦤","angle":"∠","angmsd":"∡","angmsdaa":"⦨","angmsdab":"⦩","angmsdac":"⦪","angmsdad":"⦫","angmsdae":"⦬","angmsdaf":"⦭","angmsdag":"⦮","angmsdah":"⦯","angrt":"∟","angrtvb":"⊾","angrtvbd":"⦝","angsph":"∢","angst":"Å","angzarr":"⍼","aogon":"ą","aopf":"𝕒","ap":"≈","apE":"⩰","apacir":"⩯","ape":"≊","apid":"≋","apos":"\'","approx":"≈","approxeq":"≊","arin":"å","aring":"å","ascr":"𝒶","ast":"*","asymp":"≈","asympeq":"≍","atild":"ã","atilde":"ã","aum":"ä","auml":"ä","awconint":"∳","awint":"⨑","bNot":"⫭","backcong":"≌","backepsilon":"϶","backprime":"‵","backsim":"∽","backsimeq":"⋍","barvee":"⊽","barwed":"⌅","barwedge":"⌅","bbrk":"⎵","bbrktbrk":"⎶","bcong":"≌","bcy":"б","bdquo":"„","becaus":"∵","because":"∵","bemptyv":"⦰","bepsi":"϶","bernou":"ℬ","beta":"β","beth":"ℶ","between":"≬","bfr":"𝔟","bigcap":"⋂","bigcirc":"◯","bigcup":"⋃","bigodot":"⨀","bigoplus":"⨁","bigotimes":"⨂","bigsqcup":"⨆","bigstar":"★","bigtriangledown":"▽","bigtriangleup":"△","biguplus":"⨄","bigvee":"⋁","bigwedge":"⋀","bkarow":"⤍","blacklozenge":"⧫","blacksquare":"▪","blacktriangle":"▴","blacktriangledown":"▾","blacktriangleleft":"◂","blacktriangleright":"▸","blank":"␣","blk12":"▒","blk14":"░","blk34":"▓","block":"█","bne":"=⃥","bnequiv":"≡⃥","bnot":"⌐","bopf":"𝕓","bot":"⊥","bottom":"⊥","bowtie":"⋈","boxDL":"╗","boxDR":"╔","boxDl":"╖","boxDr":"╓","boxH":"═","boxHD":"╦","boxHU":"╩","boxHd":"╤","boxHu":"╧","boxUL":"╝","boxUR":"╚","boxUl":"╜","boxUr":"╙","boxV":"║","boxVH":"╬","boxVL":"╣","boxVR":"╠","boxVh":"╫","boxVl":"╢","boxVr":"╟","boxbox":"⧉","boxdL":"╕","boxdR":"╒","boxdl":"┐","boxdr":"┌","boxh":"─","boxhD":"╥","boxhU":"╨","boxhd":"┬","boxhu":"┴","boxminus":"⊟","boxplus":"⊞","boxtimes":"⊠","boxuL":"╛","boxuR":"╘","boxul":"┘","boxur":"└","boxv":"│","boxvH":"╪","boxvL":"╡","boxvR":"╞","boxvh":"┼","boxvl":"┤","boxvr":"├","bprime":"‵","breve":"˘","brvba":"¦","brvbar":"¦","bscr":"𝒷","bsemi":"⁏","bsim":"∽","bsime":"⋍","bsol":"\\\\","bsolb":"⧅","bsolhsub":"⟈","bull":"•","bullet":"•","bump":"≎","bumpE":"⪮","bumpe":"≏","bumpeq":"≏","cacute":"ć","cap":"∩","capand":"⩄","capbrcup":"⩉","capcap":"⩋","capcup":"⩇","capdot":"⩀","caps":"∩︀","caret":"⁁","caron":"ˇ","ccaps":"⩍","ccaron":"č","ccedi":"ç","ccedil":"ç","ccirc":"ĉ","ccups":"⩌","ccupssm":"⩐","cdot":"ċ","cedi":"¸","cedil":"¸","cemptyv":"⦲","cen":"¢","cent":"¢","centerdot":"·","cfr":"𝔠","chcy":"ч","check":"✓","checkmark":"✓","chi":"χ","cir":"○","cirE":"⧃","circ":"ˆ","circeq":"≗","circlearrowleft":"↺","circlearrowright":"↻","circledR":"®","circledS":"Ⓢ","circledast":"⊛","circledcirc":"⊚","circleddash":"⊝","cire":"≗","cirfnint":"⨐","cirmid":"⫯","cirscir":"⧂","clubs":"♣","clubsuit":"♣","colon":":","colone":"≔","coloneq":"≔","comma":",","commat":"@","comp":"∁","compfn":"∘","complement":"∁","complexes":"ℂ","cong":"≅","congdot":"⩭","conint":"∮","copf":"𝕔","coprod":"∐","cop":"©","copy":"©","copysr":"℗","crarr":"↵","cross":"✗","cscr":"𝒸","csub":"⫏","csube":"⫑","csup":"⫐","csupe":"⫒","ctdot":"⋯","cudarrl":"⤸","cudarrr":"⤵","cuepr":"⋞","cuesc":"⋟","cularr":"↶","cularrp":"⤽","cup":"∪","cupbrcap":"⩈","cupcap":"⩆","cupcup":"⩊","cupdot":"⊍","cupor":"⩅","cups":"∪︀","curarr":"↷","curarrm":"⤼","curlyeqprec":"⋞","curlyeqsucc":"⋟","curlyvee":"⋎","curlywedge":"⋏","curre":"¤","curren":"¤","curvearrowleft":"↶","curvearrowright":"↷","cuvee":"⋎","cuwed":"⋏","cwconint":"∲","cwint":"∱","cylcty":"⌭","dArr":"⇓","dHar":"⥥","dagger":"†","daleth":"ℸ","darr":"↓","dash":"‐","dashv":"⊣","dbkarow":"⤏","dblac":"˝","dcaron":"ď","dcy":"д","dd":"ⅆ","ddagger":"‡","ddarr":"⇊","ddotseq":"⩷","de":"°","deg":"°","delta":"δ","demptyv":"⦱","dfisht":"⥿","dfr":"𝔡","dharl":"⇃","dharr":"⇂","diam":"⋄","diamond":"⋄","diamondsuit":"♦","diams":"♦","die":"¨","digamma":"ϝ","disin":"⋲","div":"÷","divid":"÷","divide":"÷","divideontimes":"⋇","divonx":"⋇","djcy":"ђ","dlcorn":"⌞","dlcrop":"⌍","dollar":"$","dopf":"𝕕","dot":"˙","doteq":"≐","doteqdot":"≑","dotminus":"∸","dotplus":"∔","dotsquare":"⊡","doublebarwedge":"⌆","downarrow":"↓","downdownarrows":"⇊","downharpoonleft":"⇃","downharpoonright":"⇂","drbkarow":"⤐","drcorn":"⌟","drcrop":"⌌","dscr":"𝒹","dscy":"ѕ","dsol":"⧶","dstrok":"đ","dtdot":"⋱","dtri":"▿","dtrif":"▾","duarr":"⇵","duhar":"⥯","dwangle":"⦦","dzcy":"џ","dzigrarr":"⟿","eDDot":"⩷","eDot":"≑","eacut":"é","eacute":"é","easter":"⩮","ecaron":"ě","ecir":"ê","ecirc":"ê","ecolon":"≕","ecy":"э","edot":"ė","ee":"ⅇ","efDot":"≒","efr":"𝔢","eg":"⪚","egrav":"è","egrave":"è","egs":"⪖","egsdot":"⪘","el":"⪙","elinters":"⏧","ell":"ℓ","els":"⪕","elsdot":"⪗","emacr":"ē","empty":"∅","emptyset":"∅","emptyv":"∅","emsp13":" ","emsp14":" ","emsp":" ","eng":"ŋ","ensp":" ","eogon":"ę","eopf":"𝕖","epar":"⋕","eparsl":"⧣","eplus":"⩱","epsi":"ε","epsilon":"ε","epsiv":"ϵ","eqcirc":"≖","eqcolon":"≕","eqsim":"≂","eqslantgtr":"⪖","eqslantless":"⪕","equals":"=","equest":"≟","equiv":"≡","equivDD":"⩸","eqvparsl":"⧥","erDot":"≓","erarr":"⥱","escr":"ℯ","esdot":"≐","esim":"≂","eta":"η","et":"ð","eth":"ð","eum":"ë","euml":"ë","euro":"€","excl":"!","exist":"∃","expectation":"ℰ","exponentiale":"ⅇ","fallingdotseq":"≒","fcy":"ф","female":"♀","ffilig":"ffi","fflig":"ff","ffllig":"ffl","ffr":"𝔣","filig":"fi","fjlig":"fj","flat":"♭","fllig":"fl","fltns":"▱","fnof":"ƒ","fopf":"𝕗","forall":"∀","fork":"⋔","forkv":"⫙","fpartint":"⨍","frac1":"¼","frac12":"½","frac13":"⅓","frac14":"¼","frac15":"⅕","frac16":"⅙","frac18":"⅛","frac23":"⅔","frac25":"⅖","frac3":"¾","frac34":"¾","frac35":"⅗","frac38":"⅜","frac45":"⅘","frac56":"⅚","frac58":"⅝","frac78":"⅞","frasl":"⁄","frown":"⌢","fscr":"𝒻","gE":"≧","gEl":"⪌","gacute":"ǵ","gamma":"γ","gammad":"ϝ","gap":"⪆","gbreve":"ğ","gcirc":"ĝ","gcy":"г","gdot":"ġ","ge":"≥","gel":"⋛","geq":"≥","geqq":"≧","geqslant":"⩾","ges":"⩾","gescc":"⪩","gesdot":"⪀","gesdoto":"⪂","gesdotol":"⪄","gesl":"⋛︀","gesles":"⪔","gfr":"𝔤","gg":"≫","ggg":"⋙","gimel":"ℷ","gjcy":"ѓ","gl":"≷","glE":"⪒","gla":"⪥","glj":"⪤","gnE":"≩","gnap":"⪊","gnapprox":"⪊","gne":"⪈","gneq":"⪈","gneqq":"≩","gnsim":"⋧","gopf":"𝕘","grave":"`","gscr":"ℊ","gsim":"≳","gsime":"⪎","gsiml":"⪐","g":">","gt":">","gtcc":"⪧","gtcir":"⩺","gtdot":"⋗","gtlPar":"⦕","gtquest":"⩼","gtrapprox":"⪆","gtrarr":"⥸","gtrdot":"⋗","gtreqless":"⋛","gtreqqless":"⪌","gtrless":"≷","gtrsim":"≳","gvertneqq":"≩︀","gvnE":"≩︀","hArr":"⇔","hairsp":" ","half":"½","hamilt":"ℋ","hardcy":"ъ","harr":"↔","harrcir":"⥈","harrw":"↭","hbar":"ℏ","hcirc":"ĥ","hearts":"♥","heartsuit":"♥","hellip":"…","hercon":"⊹","hfr":"𝔥","hksearow":"⤥","hkswarow":"⤦","hoarr":"⇿","homtht":"∻","hookleftarrow":"↩","hookrightarrow":"↪","hopf":"𝕙","horbar":"―","hscr":"𝒽","hslash":"ℏ","hstrok":"ħ","hybull":"⁃","hyphen":"‐","iacut":"í","iacute":"í","ic":"⁣","icir":"î","icirc":"î","icy":"и","iecy":"е","iexc":"¡","iexcl":"¡","iff":"⇔","ifr":"𝔦","igrav":"ì","igrave":"ì","ii":"ⅈ","iiiint":"⨌","iiint":"∭","iinfin":"⧜","iiota":"℩","ijlig":"ij","imacr":"ī","image":"ℑ","imagline":"ℐ","imagpart":"ℑ","imath":"ı","imof":"⊷","imped":"Ƶ","in":"∈","incare":"℅","infin":"∞","infintie":"⧝","inodot":"ı","int":"∫","intcal":"⊺","integers":"ℤ","intercal":"⊺","intlarhk":"⨗","intprod":"⨼","iocy":"ё","iogon":"į","iopf":"𝕚","iota":"ι","iprod":"⨼","iques":"¿","iquest":"¿","iscr":"𝒾","isin":"∈","isinE":"⋹","isindot":"⋵","isins":"⋴","isinsv":"⋳","isinv":"∈","it":"⁢","itilde":"ĩ","iukcy":"і","ium":"ï","iuml":"ï","jcirc":"ĵ","jcy":"й","jfr":"𝔧","jmath":"ȷ","jopf":"𝕛","jscr":"𝒿","jsercy":"ј","jukcy":"є","kappa":"κ","kappav":"ϰ","kcedil":"ķ","kcy":"к","kfr":"𝔨","kgreen":"ĸ","khcy":"х","kjcy":"ќ","kopf":"𝕜","kscr":"𝓀","lAarr":"⇚","lArr":"⇐","lAtail":"⤛","lBarr":"⤎","lE":"≦","lEg":"⪋","lHar":"⥢","lacute":"ĺ","laemptyv":"⦴","lagran":"ℒ","lambda":"λ","lang":"⟨","langd":"⦑","langle":"⟨","lap":"⪅","laqu":"«","laquo":"«","larr":"←","larrb":"⇤","larrbfs":"⤟","larrfs":"⤝","larrhk":"↩","larrlp":"↫","larrpl":"⤹","larrsim":"⥳","larrtl":"↢","lat":"⪫","latail":"⤙","late":"⪭","lates":"⪭︀","lbarr":"⤌","lbbrk":"❲","lbrace":"{","lbrack":"[","lbrke":"⦋","lbrksld":"⦏","lbrkslu":"⦍","lcaron":"ľ","lcedil":"ļ","lceil":"⌈","lcub":"{","lcy":"л","ldca":"⤶","ldquo":"“","ldquor":"„","ldrdhar":"⥧","ldrushar":"⥋","ldsh":"↲","le":"≤","leftarrow":"←","leftarrowtail":"↢","leftharpoondown":"↽","leftharpoonup":"↼","leftleftarrows":"⇇","leftrightarrow":"↔","leftrightarrows":"⇆","leftrightharpoons":"⇋","leftrightsquigarrow":"↭","leftthreetimes":"⋋","leg":"⋚","leq":"≤","leqq":"≦","leqslant":"⩽","les":"⩽","lescc":"⪨","lesdot":"⩿","lesdoto":"⪁","lesdotor":"⪃","lesg":"⋚︀","lesges":"⪓","lessapprox":"⪅","lessdot":"⋖","lesseqgtr":"⋚","lesseqqgtr":"⪋","lessgtr":"≶","lesssim":"≲","lfisht":"⥼","lfloor":"⌊","lfr":"𝔩","lg":"≶","lgE":"⪑","lhard":"↽","lharu":"↼","lharul":"⥪","lhblk":"▄","ljcy":"љ","ll":"≪","llarr":"⇇","llcorner":"⌞","llhard":"⥫","lltri":"◺","lmidot":"ŀ","lmoust":"⎰","lmoustache":"⎰","lnE":"≨","lnap":"⪉","lnapprox":"⪉","lne":"⪇","lneq":"⪇","lneqq":"≨","lnsim":"⋦","loang":"⟬","loarr":"⇽","lobrk":"⟦","longleftarrow":"⟵","longleftrightarrow":"⟷","longmapsto":"⟼","longrightarrow":"⟶","looparrowleft":"↫","looparrowright":"↬","lopar":"⦅","lopf":"𝕝","loplus":"⨭","lotimes":"⨴","lowast":"∗","lowbar":"_","loz":"◊","lozenge":"◊","lozf":"⧫","lpar":"(","lparlt":"⦓","lrarr":"⇆","lrcorner":"⌟","lrhar":"⇋","lrhard":"⥭","lrm":"‎","lrtri":"⊿","lsaquo":"‹","lscr":"𝓁","lsh":"↰","lsim":"≲","lsime":"⪍","lsimg":"⪏","lsqb":"[","lsquo":"‘","lsquor":"‚","lstrok":"ł","l":"<","lt":"<","ltcc":"⪦","ltcir":"⩹","ltdot":"⋖","lthree":"⋋","ltimes":"⋉","ltlarr":"⥶","ltquest":"⩻","ltrPar":"⦖","ltri":"◃","ltrie":"⊴","ltrif":"◂","lurdshar":"⥊","luruhar":"⥦","lvertneqq":"≨︀","lvnE":"≨︀","mDDot":"∺","mac":"¯","macr":"¯","male":"♂","malt":"✠","maltese":"✠","map":"↦","mapsto":"↦","mapstodown":"↧","mapstoleft":"↤","mapstoup":"↥","marker":"▮","mcomma":"⨩","mcy":"м","mdash":"—","measuredangle":"∡","mfr":"𝔪","mho":"℧","micr":"µ","micro":"µ","mid":"∣","midast":"*","midcir":"⫰","middo":"·","middot":"·","minus":"−","minusb":"⊟","minusd":"∸","minusdu":"⨪","mlcp":"⫛","mldr":"…","mnplus":"∓","models":"⊧","mopf":"𝕞","mp":"∓","mscr":"𝓂","mstpos":"∾","mu":"μ","multimap":"⊸","mumap":"⊸","nGg":"⋙̸","nGt":"≫⃒","nGtv":"≫̸","nLeftarrow":"⇍","nLeftrightarrow":"⇎","nLl":"⋘̸","nLt":"≪⃒","nLtv":"≪̸","nRightarrow":"⇏","nVDash":"⊯","nVdash":"⊮","nabla":"∇","nacute":"ń","nang":"∠⃒","nap":"≉","napE":"⩰̸","napid":"≋̸","napos":"ʼn","napprox":"≉","natur":"♮","natural":"♮","naturals":"ℕ","nbs":" ","nbsp":" ","nbump":"≎̸","nbumpe":"≏̸","ncap":"⩃","ncaron":"ň","ncedil":"ņ","ncong":"≇","ncongdot":"⩭̸","ncup":"⩂","ncy":"н","ndash":"–","ne":"≠","neArr":"⇗","nearhk":"⤤","nearr":"↗","nearrow":"↗","nedot":"≐̸","nequiv":"≢","nesear":"⤨","nesim":"≂̸","nexist":"∄","nexists":"∄","nfr":"𝔫","ngE":"≧̸","nge":"≱","ngeq":"≱","ngeqq":"≧̸","ngeqslant":"⩾̸","nges":"⩾̸","ngsim":"≵","ngt":"≯","ngtr":"≯","nhArr":"⇎","nharr":"↮","nhpar":"⫲","ni":"∋","nis":"⋼","nisd":"⋺","niv":"∋","njcy":"њ","nlArr":"⇍","nlE":"≦̸","nlarr":"↚","nldr":"‥","nle":"≰","nleftarrow":"↚","nleftrightarrow":"↮","nleq":"≰","nleqq":"≦̸","nleqslant":"⩽̸","nles":"⩽̸","nless":"≮","nlsim":"≴","nlt":"≮","nltri":"⋪","nltrie":"⋬","nmid":"∤","nopf":"𝕟","no":"¬","not":"¬","notin":"∉","notinE":"⋹̸","notindot":"⋵̸","notinva":"∉","notinvb":"⋷","notinvc":"⋶","notni":"∌","notniva":"∌","notnivb":"⋾","notnivc":"⋽","npar":"∦","nparallel":"∦","nparsl":"⫽⃥","npart":"∂̸","npolint":"⨔","npr":"⊀","nprcue":"⋠","npre":"⪯̸","nprec":"⊀","npreceq":"⪯̸","nrArr":"⇏","nrarr":"↛","nrarrc":"⤳̸","nrarrw":"↝̸","nrightarrow":"↛","nrtri":"⋫","nrtrie":"⋭","nsc":"⊁","nsccue":"⋡","nsce":"⪰̸","nscr":"𝓃","nshortmid":"∤","nshortparallel":"∦","nsim":"≁","nsime":"≄","nsimeq":"≄","nsmid":"∤","nspar":"∦","nsqsube":"⋢","nsqsupe":"⋣","nsub":"⊄","nsubE":"⫅̸","nsube":"⊈","nsubset":"⊂⃒","nsubseteq":"⊈","nsubseteqq":"⫅̸","nsucc":"⊁","nsucceq":"⪰̸","nsup":"⊅","nsupE":"⫆̸","nsupe":"⊉","nsupset":"⊃⃒","nsupseteq":"⊉","nsupseteqq":"⫆̸","ntgl":"≹","ntild":"ñ","ntilde":"ñ","ntlg":"≸","ntriangleleft":"⋪","ntrianglelefteq":"⋬","ntriangleright":"⋫","ntrianglerighteq":"⋭","nu":"ν","num":"#","numero":"№","numsp":" ","nvDash":"⊭","nvHarr":"⤄","nvap":"≍⃒","nvdash":"⊬","nvge":"≥⃒","nvgt":">⃒","nvinfin":"⧞","nvlArr":"⤂","nvle":"≤⃒","nvlt":"<⃒","nvltrie":"⊴⃒","nvrArr":"⤃","nvrtrie":"⊵⃒","nvsim":"∼⃒","nwArr":"⇖","nwarhk":"⤣","nwarr":"↖","nwarrow":"↖","nwnear":"⤧","oS":"Ⓢ","oacut":"ó","oacute":"ó","oast":"⊛","ocir":"ô","ocirc":"ô","ocy":"о","odash":"⊝","odblac":"ő","odiv":"⨸","odot":"⊙","odsold":"⦼","oelig":"œ","ofcir":"⦿","ofr":"𝔬","ogon":"˛","ograv":"ò","ograve":"ò","ogt":"⧁","ohbar":"⦵","ohm":"Ω","oint":"∮","olarr":"↺","olcir":"⦾","olcross":"⦻","oline":"‾","olt":"⧀","omacr":"ō","omega":"ω","omicron":"ο","omid":"⦶","ominus":"⊖","oopf":"𝕠","opar":"⦷","operp":"⦹","oplus":"⊕","or":"∨","orarr":"↻","ord":"º","order":"ℴ","orderof":"ℴ","ordf":"ª","ordm":"º","origof":"⊶","oror":"⩖","orslope":"⩗","orv":"⩛","oscr":"ℴ","oslas":"ø","oslash":"ø","osol":"⊘","otild":"õ","otilde":"õ","otimes":"⊗","otimesas":"⨶","oum":"ö","ouml":"ö","ovbar":"⌽","par":"¶","para":"¶","parallel":"∥","parsim":"⫳","parsl":"⫽","part":"∂","pcy":"п","percnt":"%","period":".","permil":"‰","perp":"⊥","pertenk":"‱","pfr":"𝔭","phi":"φ","phiv":"ϕ","phmmat":"ℳ","phone":"☎","pi":"π","pitchfork":"⋔","piv":"ϖ","planck":"ℏ","planckh":"ℎ","plankv":"ℏ","plus":"+","plusacir":"⨣","plusb":"⊞","pluscir":"⨢","plusdo":"∔","plusdu":"⨥","pluse":"⩲","plusm":"±","plusmn":"±","plussim":"⨦","plustwo":"⨧","pm":"±","pointint":"⨕","popf":"𝕡","poun":"£","pound":"£","pr":"≺","prE":"⪳","prap":"⪷","prcue":"≼","pre":"⪯","prec":"≺","precapprox":"⪷","preccurlyeq":"≼","preceq":"⪯","precnapprox":"⪹","precneqq":"⪵","precnsim":"⋨","precsim":"≾","prime":"′","primes":"ℙ","prnE":"⪵","prnap":"⪹","prnsim":"⋨","prod":"∏","profalar":"⌮","profline":"⌒","profsurf":"⌓","prop":"∝","propto":"∝","prsim":"≾","prurel":"⊰","pscr":"𝓅","psi":"ψ","puncsp":" ","qfr":"𝔮","qint":"⨌","qopf":"𝕢","qprime":"⁗","qscr":"𝓆","quaternions":"ℍ","quatint":"⨖","quest":"?","questeq":"≟","quo":"\\"","quot":"\\"","rAarr":"⇛","rArr":"⇒","rAtail":"⤜","rBarr":"⤏","rHar":"⥤","race":"∽̱","racute":"ŕ","radic":"√","raemptyv":"⦳","rang":"⟩","rangd":"⦒","range":"⦥","rangle":"⟩","raqu":"»","raquo":"»","rarr":"→","rarrap":"⥵","rarrb":"⇥","rarrbfs":"⤠","rarrc":"⤳","rarrfs":"⤞","rarrhk":"↪","rarrlp":"↬","rarrpl":"⥅","rarrsim":"⥴","rarrtl":"↣","rarrw":"↝","ratail":"⤚","ratio":"∶","rationals":"ℚ","rbarr":"⤍","rbbrk":"❳","rbrace":"}","rbrack":"]","rbrke":"⦌","rbrksld":"⦎","rbrkslu":"⦐","rcaron":"ř","rcedil":"ŗ","rceil":"⌉","rcub":"}","rcy":"р","rdca":"⤷","rdldhar":"⥩","rdquo":"”","rdquor":"”","rdsh":"↳","real":"ℜ","realine":"ℛ","realpart":"ℜ","reals":"ℝ","rect":"▭","re":"®","reg":"®","rfisht":"⥽","rfloor":"⌋","rfr":"𝔯","rhard":"⇁","rharu":"⇀","rharul":"⥬","rho":"ρ","rhov":"ϱ","rightarrow":"→","rightarrowtail":"↣","rightharpoondown":"⇁","rightharpoonup":"⇀","rightleftarrows":"⇄","rightleftharpoons":"⇌","rightrightarrows":"⇉","rightsquigarrow":"↝","rightthreetimes":"⋌","ring":"˚","risingdotseq":"≓","rlarr":"⇄","rlhar":"⇌","rlm":"‏","rmoust":"⎱","rmoustache":"⎱","rnmid":"⫮","roang":"⟭","roarr":"⇾","robrk":"⟧","ropar":"⦆","ropf":"𝕣","roplus":"⨮","rotimes":"⨵","rpar":")","rpargt":"⦔","rppolint":"⨒","rrarr":"⇉","rsaquo":"›","rscr":"𝓇","rsh":"↱","rsqb":"]","rsquo":"’","rsquor":"’","rthree":"⋌","rtimes":"⋊","rtri":"▹","rtrie":"⊵","rtrif":"▸","rtriltri":"⧎","ruluhar":"⥨","rx":"℞","sacute":"ś","sbquo":"‚","sc":"≻","scE":"⪴","scap":"⪸","scaron":"š","sccue":"≽","sce":"⪰","scedil":"ş","scirc":"ŝ","scnE":"⪶","scnap":"⪺","scnsim":"⋩","scpolint":"⨓","scsim":"≿","scy":"с","sdot":"⋅","sdotb":"⊡","sdote":"⩦","seArr":"⇘","searhk":"⤥","searr":"↘","searrow":"↘","sec":"§","sect":"§","semi":";","seswar":"⤩","setminus":"∖","setmn":"∖","sext":"✶","sfr":"𝔰","sfrown":"⌢","sharp":"♯","shchcy":"щ","shcy":"ш","shortmid":"∣","shortparallel":"∥","sh":"­","shy":"­","sigma":"σ","sigmaf":"ς","sigmav":"ς","sim":"∼","simdot":"⩪","sime":"≃","simeq":"≃","simg":"⪞","simgE":"⪠","siml":"⪝","simlE":"⪟","simne":"≆","simplus":"⨤","simrarr":"⥲","slarr":"←","smallsetminus":"∖","smashp":"⨳","smeparsl":"⧤","smid":"∣","smile":"⌣","smt":"⪪","smte":"⪬","smtes":"⪬︀","softcy":"ь","sol":"/","solb":"⧄","solbar":"⌿","sopf":"𝕤","spades":"♠","spadesuit":"♠","spar":"∥","sqcap":"⊓","sqcaps":"⊓︀","sqcup":"⊔","sqcups":"⊔︀","sqsub":"⊏","sqsube":"⊑","sqsubset":"⊏","sqsubseteq":"⊑","sqsup":"⊐","sqsupe":"⊒","sqsupset":"⊐","sqsupseteq":"⊒","squ":"□","square":"□","squarf":"▪","squf":"▪","srarr":"→","sscr":"𝓈","ssetmn":"∖","ssmile":"⌣","sstarf":"⋆","star":"☆","starf":"★","straightepsilon":"ϵ","straightphi":"ϕ","strns":"¯","sub":"⊂","subE":"⫅","subdot":"⪽","sube":"⊆","subedot":"⫃","submult":"⫁","subnE":"⫋","subne":"⊊","subplus":"⪿","subrarr":"⥹","subset":"⊂","subseteq":"⊆","subseteqq":"⫅","subsetneq":"⊊","subsetneqq":"⫋","subsim":"⫇","subsub":"⫕","subsup":"⫓","succ":"≻","succapprox":"⪸","succcurlyeq":"≽","succeq":"⪰","succnapprox":"⪺","succneqq":"⪶","succnsim":"⋩","succsim":"≿","sum":"∑","sung":"♪","sup":"⊃","sup1":"¹","sup2":"²","sup3":"³","supE":"⫆","supdot":"⪾","supdsub":"⫘","supe":"⊇","supedot":"⫄","suphsol":"⟉","suphsub":"⫗","suplarr":"⥻","supmult":"⫂","supnE":"⫌","supne":"⊋","supplus":"⫀","supset":"⊃","supseteq":"⊇","supseteqq":"⫆","supsetneq":"⊋","supsetneqq":"⫌","supsim":"⫈","supsub":"⫔","supsup":"⫖","swArr":"⇙","swarhk":"⤦","swarr":"↙","swarrow":"↙","swnwar":"⤪","szli":"ß","szlig":"ß","target":"⌖","tau":"τ","tbrk":"⎴","tcaron":"ť","tcedil":"ţ","tcy":"т","tdot":"⃛","telrec":"⌕","tfr":"𝔱","there4":"∴","therefore":"∴","theta":"θ","thetasym":"ϑ","thetav":"ϑ","thickapprox":"≈","thicksim":"∼","thinsp":" ","thkap":"≈","thksim":"∼","thor":"þ","thorn":"þ","tilde":"˜","time":"×","times":"×","timesb":"⊠","timesbar":"⨱","timesd":"⨰","tint":"∭","toea":"⤨","top":"⊤","topbot":"⌶","topcir":"⫱","topf":"𝕥","topfork":"⫚","tosa":"⤩","tprime":"‴","trade":"™","triangle":"▵","triangledown":"▿","triangleleft":"◃","trianglelefteq":"⊴","triangleq":"≜","triangleright":"▹","trianglerighteq":"⊵","tridot":"◬","trie":"≜","triminus":"⨺","triplus":"⨹","trisb":"⧍","tritime":"⨻","trpezium":"⏢","tscr":"𝓉","tscy":"ц","tshcy":"ћ","tstrok":"ŧ","twixt":"≬","twoheadleftarrow":"↞","twoheadrightarrow":"↠","uArr":"⇑","uHar":"⥣","uacut":"ú","uacute":"ú","uarr":"↑","ubrcy":"ў","ubreve":"ŭ","ucir":"û","ucirc":"û","ucy":"у","udarr":"⇅","udblac":"ű","udhar":"⥮","ufisht":"⥾","ufr":"𝔲","ugrav":"ù","ugrave":"ù","uharl":"↿","uharr":"↾","uhblk":"▀","ulcorn":"⌜","ulcorner":"⌜","ulcrop":"⌏","ultri":"◸","umacr":"ū","um":"¨","uml":"¨","uogon":"ų","uopf":"𝕦","uparrow":"↑","updownarrow":"↕","upharpoonleft":"↿","upharpoonright":"↾","uplus":"⊎","upsi":"υ","upsih":"ϒ","upsilon":"υ","upuparrows":"⇈","urcorn":"⌝","urcorner":"⌝","urcrop":"⌎","uring":"ů","urtri":"◹","uscr":"𝓊","utdot":"⋰","utilde":"ũ","utri":"▵","utrif":"▴","uuarr":"⇈","uum":"ü","uuml":"ü","uwangle":"⦧","vArr":"⇕","vBar":"⫨","vBarv":"⫩","vDash":"⊨","vangrt":"⦜","varepsilon":"ϵ","varkappa":"ϰ","varnothing":"∅","varphi":"ϕ","varpi":"ϖ","varpropto":"∝","varr":"↕","varrho":"ϱ","varsigma":"ς","varsubsetneq":"⊊︀","varsubsetneqq":"⫋︀","varsupsetneq":"⊋︀","varsupsetneqq":"⫌︀","vartheta":"ϑ","vartriangleleft":"⊲","vartriangleright":"⊳","vcy":"в","vdash":"⊢","vee":"∨","veebar":"⊻","veeeq":"≚","vellip":"⋮","verbar":"|","vert":"|","vfr":"𝔳","vltri":"⊲","vnsub":"⊂⃒","vnsup":"⊃⃒","vopf":"𝕧","vprop":"∝","vrtri":"⊳","vscr":"𝓋","vsubnE":"⫋︀","vsubne":"⊊︀","vsupnE":"⫌︀","vsupne":"⊋︀","vzigzag":"⦚","wcirc":"ŵ","wedbar":"⩟","wedge":"∧","wedgeq":"≙","weierp":"℘","wfr":"𝔴","wopf":"𝕨","wp":"℘","wr":"≀","wreath":"≀","wscr":"𝓌","xcap":"⋂","xcirc":"◯","xcup":"⋃","xdtri":"▽","xfr":"𝔵","xhArr":"⟺","xharr":"⟷","xi":"ξ","xlArr":"⟸","xlarr":"⟵","xmap":"⟼","xnis":"⋻","xodot":"⨀","xopf":"𝕩","xoplus":"⨁","xotime":"⨂","xrArr":"⟹","xrarr":"⟶","xscr":"𝓍","xsqcup":"⨆","xuplus":"⨄","xutri":"△","xvee":"⋁","xwedge":"⋀","yacut":"ý","yacute":"ý","yacy":"я","ycirc":"ŷ","ycy":"ы","ye":"¥","yen":"¥","yfr":"𝔶","yicy":"ї","yopf":"𝕪","yscr":"𝓎","yucy":"ю","yum":"ÿ","yuml":"ÿ","zacute":"ź","zcaron":"ž","zcy":"з","zdot":"ż","zeetrf":"ℨ","zeta":"ζ","zfr":"𝔷","zhcy":"ж","zigrarr":"⇝","zopf":"𝕫","zscr":"𝓏","zwj":"‍","zwnj":"‌"}'); + +/***/ }), + +/***/ 21257: +/***/ ((module) => { + +"use strict"; +module.exports = /*#__PURE__*/JSON.parse('{"0":"�","128":"€","130":"‚","131":"ƒ","132":"„","133":"…","134":"†","135":"‡","136":"ˆ","137":"‰","138":"Š","139":"‹","140":"Œ","142":"Ž","145":"‘","146":"’","147":"“","148":"”","149":"•","150":"–","151":"—","152":"˜","153":"™","154":"š","155":"›","156":"œ","158":"ž","159":"Ÿ"}'); + +/***/ }), + +/***/ 14652: +/***/ ((module) => { + +"use strict"; +module.exports = /*#__PURE__*/JSON.parse('{"builtin":{"Array":false,"ArrayBuffer":false,"Atomics":false,"BigInt":false,"BigInt64Array":false,"BigUint64Array":false,"Boolean":false,"constructor":false,"DataView":false,"Date":false,"decodeURI":false,"decodeURIComponent":false,"encodeURI":false,"encodeURIComponent":false,"Error":false,"escape":false,"eval":false,"EvalError":false,"Float32Array":false,"Float64Array":false,"Function":false,"globalThis":false,"hasOwnProperty":false,"Infinity":false,"Int16Array":false,"Int32Array":false,"Int8Array":false,"isFinite":false,"isNaN":false,"isPrototypeOf":false,"JSON":false,"Map":false,"Math":false,"NaN":false,"Number":false,"Object":false,"parseFloat":false,"parseInt":false,"Promise":false,"propertyIsEnumerable":false,"Proxy":false,"RangeError":false,"ReferenceError":false,"Reflect":false,"RegExp":false,"Set":false,"SharedArrayBuffer":false,"String":false,"Symbol":false,"SyntaxError":false,"toLocaleString":false,"toString":false,"TypeError":false,"Uint16Array":false,"Uint32Array":false,"Uint8Array":false,"Uint8ClampedArray":false,"undefined":false,"unescape":false,"URIError":false,"valueOf":false,"WeakMap":false,"WeakSet":false},"es5":{"Array":false,"Boolean":false,"constructor":false,"Date":false,"decodeURI":false,"decodeURIComponent":false,"encodeURI":false,"encodeURIComponent":false,"Error":false,"escape":false,"eval":false,"EvalError":false,"Function":false,"hasOwnProperty":false,"Infinity":false,"isFinite":false,"isNaN":false,"isPrototypeOf":false,"JSON":false,"Math":false,"NaN":false,"Number":false,"Object":false,"parseFloat":false,"parseInt":false,"propertyIsEnumerable":false,"RangeError":false,"ReferenceError":false,"RegExp":false,"String":false,"SyntaxError":false,"toLocaleString":false,"toString":false,"TypeError":false,"undefined":false,"unescape":false,"URIError":false,"valueOf":false},"es2015":{"Array":false,"ArrayBuffer":false,"Boolean":false,"constructor":false,"DataView":false,"Date":false,"decodeURI":false,"decodeURIComponent":false,"encodeURI":false,"encodeURIComponent":false,"Error":false,"escape":false,"eval":false,"EvalError":false,"Float32Array":false,"Float64Array":false,"Function":false,"hasOwnProperty":false,"Infinity":false,"Int16Array":false,"Int32Array":false,"Int8Array":false,"isFinite":false,"isNaN":false,"isPrototypeOf":false,"JSON":false,"Map":false,"Math":false,"NaN":false,"Number":false,"Object":false,"parseFloat":false,"parseInt":false,"Promise":false,"propertyIsEnumerable":false,"Proxy":false,"RangeError":false,"ReferenceError":false,"Reflect":false,"RegExp":false,"Set":false,"String":false,"Symbol":false,"SyntaxError":false,"toLocaleString":false,"toString":false,"TypeError":false,"Uint16Array":false,"Uint32Array":false,"Uint8Array":false,"Uint8ClampedArray":false,"undefined":false,"unescape":false,"URIError":false,"valueOf":false,"WeakMap":false,"WeakSet":false},"es2017":{"Array":false,"ArrayBuffer":false,"Atomics":false,"Boolean":false,"constructor":false,"DataView":false,"Date":false,"decodeURI":false,"decodeURIComponent":false,"encodeURI":false,"encodeURIComponent":false,"Error":false,"escape":false,"eval":false,"EvalError":false,"Float32Array":false,"Float64Array":false,"Function":false,"hasOwnProperty":false,"Infinity":false,"Int16Array":false,"Int32Array":false,"Int8Array":false,"isFinite":false,"isNaN":false,"isPrototypeOf":false,"JSON":false,"Map":false,"Math":false,"NaN":false,"Number":false,"Object":false,"parseFloat":false,"parseInt":false,"Promise":false,"propertyIsEnumerable":false,"Proxy":false,"RangeError":false,"ReferenceError":false,"Reflect":false,"RegExp":false,"Set":false,"SharedArrayBuffer":false,"String":false,"Symbol":false,"SyntaxError":false,"toLocaleString":false,"toString":false,"TypeError":false,"Uint16Array":false,"Uint32Array":false,"Uint8Array":false,"Uint8ClampedArray":false,"undefined":false,"unescape":false,"URIError":false,"valueOf":false,"WeakMap":false,"WeakSet":false},"browser":{"AbortController":false,"AbortSignal":false,"addEventListener":false,"alert":false,"AnalyserNode":false,"Animation":false,"AnimationEffectReadOnly":false,"AnimationEffectTiming":false,"AnimationEffectTimingReadOnly":false,"AnimationEvent":false,"AnimationPlaybackEvent":false,"AnimationTimeline":false,"applicationCache":false,"ApplicationCache":false,"ApplicationCacheErrorEvent":false,"atob":false,"Attr":false,"Audio":false,"AudioBuffer":false,"AudioBufferSourceNode":false,"AudioContext":false,"AudioDestinationNode":false,"AudioListener":false,"AudioNode":false,"AudioParam":false,"AudioProcessingEvent":false,"AudioScheduledSourceNode":false,"AudioWorkletGlobalScope ":false,"AudioWorkletNode":false,"AudioWorkletProcessor":false,"BarProp":false,"BaseAudioContext":false,"BatteryManager":false,"BeforeUnloadEvent":false,"BiquadFilterNode":false,"Blob":false,"BlobEvent":false,"blur":false,"BroadcastChannel":false,"btoa":false,"BudgetService":false,"ByteLengthQueuingStrategy":false,"Cache":false,"caches":false,"CacheStorage":false,"cancelAnimationFrame":false,"cancelIdleCallback":false,"CanvasCaptureMediaStreamTrack":false,"CanvasGradient":false,"CanvasPattern":false,"CanvasRenderingContext2D":false,"ChannelMergerNode":false,"ChannelSplitterNode":false,"CharacterData":false,"clearInterval":false,"clearTimeout":false,"clientInformation":false,"ClipboardEvent":false,"close":false,"closed":false,"CloseEvent":false,"Comment":false,"CompositionEvent":false,"confirm":false,"console":false,"ConstantSourceNode":false,"ConvolverNode":false,"CountQueuingStrategy":false,"createImageBitmap":false,"Credential":false,"CredentialsContainer":false,"crypto":false,"Crypto":false,"CryptoKey":false,"CSS":false,"CSSConditionRule":false,"CSSFontFaceRule":false,"CSSGroupingRule":false,"CSSImportRule":false,"CSSKeyframeRule":false,"CSSKeyframesRule":false,"CSSMediaRule":false,"CSSNamespaceRule":false,"CSSPageRule":false,"CSSRule":false,"CSSRuleList":false,"CSSStyleDeclaration":false,"CSSStyleRule":false,"CSSStyleSheet":false,"CSSSupportsRule":false,"CustomElementRegistry":false,"customElements":false,"CustomEvent":false,"DataTransfer":false,"DataTransferItem":false,"DataTransferItemList":false,"defaultstatus":false,"defaultStatus":false,"DelayNode":false,"DeviceMotionEvent":false,"DeviceOrientationEvent":false,"devicePixelRatio":false,"dispatchEvent":false,"document":false,"Document":false,"DocumentFragment":false,"DocumentType":false,"DOMError":false,"DOMException":false,"DOMImplementation":false,"DOMMatrix":false,"DOMMatrixReadOnly":false,"DOMParser":false,"DOMPoint":false,"DOMPointReadOnly":false,"DOMQuad":false,"DOMRect":false,"DOMRectReadOnly":false,"DOMStringList":false,"DOMStringMap":false,"DOMTokenList":false,"DragEvent":false,"DynamicsCompressorNode":false,"Element":false,"ErrorEvent":false,"event":false,"Event":false,"EventSource":false,"EventTarget":false,"external":false,"fetch":false,"File":false,"FileList":false,"FileReader":false,"find":false,"focus":false,"FocusEvent":false,"FontFace":false,"FontFaceSetLoadEvent":false,"FormData":false,"frameElement":false,"frames":false,"GainNode":false,"Gamepad":false,"GamepadButton":false,"GamepadEvent":false,"getComputedStyle":false,"getSelection":false,"HashChangeEvent":false,"Headers":false,"history":false,"History":false,"HTMLAllCollection":false,"HTMLAnchorElement":false,"HTMLAreaElement":false,"HTMLAudioElement":false,"HTMLBaseElement":false,"HTMLBodyElement":false,"HTMLBRElement":false,"HTMLButtonElement":false,"HTMLCanvasElement":false,"HTMLCollection":false,"HTMLContentElement":false,"HTMLDataElement":false,"HTMLDataListElement":false,"HTMLDetailsElement":false,"HTMLDialogElement":false,"HTMLDirectoryElement":false,"HTMLDivElement":false,"HTMLDListElement":false,"HTMLDocument":false,"HTMLElement":false,"HTMLEmbedElement":false,"HTMLFieldSetElement":false,"HTMLFontElement":false,"HTMLFormControlsCollection":false,"HTMLFormElement":false,"HTMLFrameElement":false,"HTMLFrameSetElement":false,"HTMLHeadElement":false,"HTMLHeadingElement":false,"HTMLHRElement":false,"HTMLHtmlElement":false,"HTMLIFrameElement":false,"HTMLImageElement":false,"HTMLInputElement":false,"HTMLLabelElement":false,"HTMLLegendElement":false,"HTMLLIElement":false,"HTMLLinkElement":false,"HTMLMapElement":false,"HTMLMarqueeElement":false,"HTMLMediaElement":false,"HTMLMenuElement":false,"HTMLMetaElement":false,"HTMLMeterElement":false,"HTMLModElement":false,"HTMLObjectElement":false,"HTMLOListElement":false,"HTMLOptGroupElement":false,"HTMLOptionElement":false,"HTMLOptionsCollection":false,"HTMLOutputElement":false,"HTMLParagraphElement":false,"HTMLParamElement":false,"HTMLPictureElement":false,"HTMLPreElement":false,"HTMLProgressElement":false,"HTMLQuoteElement":false,"HTMLScriptElement":false,"HTMLSelectElement":false,"HTMLShadowElement":false,"HTMLSlotElement":false,"HTMLSourceElement":false,"HTMLSpanElement":false,"HTMLStyleElement":false,"HTMLTableCaptionElement":false,"HTMLTableCellElement":false,"HTMLTableColElement":false,"HTMLTableElement":false,"HTMLTableRowElement":false,"HTMLTableSectionElement":false,"HTMLTemplateElement":false,"HTMLTextAreaElement":false,"HTMLTimeElement":false,"HTMLTitleElement":false,"HTMLTrackElement":false,"HTMLUListElement":false,"HTMLUnknownElement":false,"HTMLVideoElement":false,"IDBCursor":false,"IDBCursorWithValue":false,"IDBDatabase":false,"IDBFactory":false,"IDBIndex":false,"IDBKeyRange":false,"IDBObjectStore":false,"IDBOpenDBRequest":false,"IDBRequest":false,"IDBTransaction":false,"IDBVersionChangeEvent":false,"IdleDeadline":false,"IIRFilterNode":false,"Image":false,"ImageBitmap":false,"ImageBitmapRenderingContext":false,"ImageCapture":false,"ImageData":false,"indexedDB":false,"innerHeight":false,"innerWidth":false,"InputEvent":false,"IntersectionObserver":false,"IntersectionObserverEntry":false,"Intl":false,"isSecureContext":false,"KeyboardEvent":false,"KeyframeEffect":false,"KeyframeEffectReadOnly":false,"length":false,"localStorage":false,"location":true,"Location":false,"locationbar":false,"matchMedia":false,"MediaDeviceInfo":false,"MediaDevices":false,"MediaElementAudioSourceNode":false,"MediaEncryptedEvent":false,"MediaError":false,"MediaKeyMessageEvent":false,"MediaKeySession":false,"MediaKeyStatusMap":false,"MediaKeySystemAccess":false,"MediaList":false,"MediaQueryList":false,"MediaQueryListEvent":false,"MediaRecorder":false,"MediaSettingsRange":false,"MediaSource":false,"MediaStream":false,"MediaStreamAudioDestinationNode":false,"MediaStreamAudioSourceNode":false,"MediaStreamEvent":false,"MediaStreamTrack":false,"MediaStreamTrackEvent":false,"menubar":false,"MessageChannel":false,"MessageEvent":false,"MessagePort":false,"MIDIAccess":false,"MIDIConnectionEvent":false,"MIDIInput":false,"MIDIInputMap":false,"MIDIMessageEvent":false,"MIDIOutput":false,"MIDIOutputMap":false,"MIDIPort":false,"MimeType":false,"MimeTypeArray":false,"MouseEvent":false,"moveBy":false,"moveTo":false,"MutationEvent":false,"MutationObserver":false,"MutationRecord":false,"name":false,"NamedNodeMap":false,"NavigationPreloadManager":false,"navigator":false,"Navigator":false,"NetworkInformation":false,"Node":false,"NodeFilter":false,"NodeIterator":false,"NodeList":false,"Notification":false,"OfflineAudioCompletionEvent":false,"OfflineAudioContext":false,"offscreenBuffering":false,"OffscreenCanvas":true,"onabort":true,"onafterprint":true,"onanimationend":true,"onanimationiteration":true,"onanimationstart":true,"onappinstalled":true,"onauxclick":true,"onbeforeinstallprompt":true,"onbeforeprint":true,"onbeforeunload":true,"onblur":true,"oncancel":true,"oncanplay":true,"oncanplaythrough":true,"onchange":true,"onclick":true,"onclose":true,"oncontextmenu":true,"oncuechange":true,"ondblclick":true,"ondevicemotion":true,"ondeviceorientation":true,"ondeviceorientationabsolute":true,"ondrag":true,"ondragend":true,"ondragenter":true,"ondragleave":true,"ondragover":true,"ondragstart":true,"ondrop":true,"ondurationchange":true,"onemptied":true,"onended":true,"onerror":true,"onfocus":true,"ongotpointercapture":true,"onhashchange":true,"oninput":true,"oninvalid":true,"onkeydown":true,"onkeypress":true,"onkeyup":true,"onlanguagechange":true,"onload":true,"onloadeddata":true,"onloadedmetadata":true,"onloadstart":true,"onlostpointercapture":true,"onmessage":true,"onmessageerror":true,"onmousedown":true,"onmouseenter":true,"onmouseleave":true,"onmousemove":true,"onmouseout":true,"onmouseover":true,"onmouseup":true,"onmousewheel":true,"onoffline":true,"ononline":true,"onpagehide":true,"onpageshow":true,"onpause":true,"onplay":true,"onplaying":true,"onpointercancel":true,"onpointerdown":true,"onpointerenter":true,"onpointerleave":true,"onpointermove":true,"onpointerout":true,"onpointerover":true,"onpointerup":true,"onpopstate":true,"onprogress":true,"onratechange":true,"onrejectionhandled":true,"onreset":true,"onresize":true,"onscroll":true,"onsearch":true,"onseeked":true,"onseeking":true,"onselect":true,"onstalled":true,"onstorage":true,"onsubmit":true,"onsuspend":true,"ontimeupdate":true,"ontoggle":true,"ontransitionend":true,"onunhandledrejection":true,"onunload":true,"onvolumechange":true,"onwaiting":true,"onwheel":true,"open":false,"openDatabase":false,"opener":false,"Option":false,"origin":false,"OscillatorNode":false,"outerHeight":false,"outerWidth":false,"PageTransitionEvent":false,"pageXOffset":false,"pageYOffset":false,"PannerNode":false,"parent":false,"Path2D":false,"PaymentAddress":false,"PaymentRequest":false,"PaymentRequestUpdateEvent":false,"PaymentResponse":false,"performance":false,"Performance":false,"PerformanceEntry":false,"PerformanceLongTaskTiming":false,"PerformanceMark":false,"PerformanceMeasure":false,"PerformanceNavigation":false,"PerformanceNavigationTiming":false,"PerformanceObserver":false,"PerformanceObserverEntryList":false,"PerformancePaintTiming":false,"PerformanceResourceTiming":false,"PerformanceTiming":false,"PeriodicWave":false,"Permissions":false,"PermissionStatus":false,"personalbar":false,"PhotoCapabilities":false,"Plugin":false,"PluginArray":false,"PointerEvent":false,"PopStateEvent":false,"postMessage":false,"Presentation":false,"PresentationAvailability":false,"PresentationConnection":false,"PresentationConnectionAvailableEvent":false,"PresentationConnectionCloseEvent":false,"PresentationConnectionList":false,"PresentationReceiver":false,"PresentationRequest":false,"print":false,"ProcessingInstruction":false,"ProgressEvent":false,"PromiseRejectionEvent":false,"prompt":false,"PushManager":false,"PushSubscription":false,"PushSubscriptionOptions":false,"queueMicrotask":false,"RadioNodeList":false,"Range":false,"ReadableStream":false,"registerProcessor":false,"RemotePlayback":false,"removeEventListener":false,"Request":false,"requestAnimationFrame":false,"requestIdleCallback":false,"resizeBy":false,"ResizeObserver":false,"ResizeObserverEntry":false,"resizeTo":false,"Response":false,"RTCCertificate":false,"RTCDataChannel":false,"RTCDataChannelEvent":false,"RTCDtlsTransport":false,"RTCIceCandidate":false,"RTCIceGatherer":false,"RTCIceTransport":false,"RTCPeerConnection":false,"RTCPeerConnectionIceEvent":false,"RTCRtpContributingSource":false,"RTCRtpReceiver":false,"RTCRtpSender":false,"RTCSctpTransport":false,"RTCSessionDescription":false,"RTCStatsReport":false,"RTCTrackEvent":false,"screen":false,"Screen":false,"screenLeft":false,"ScreenOrientation":false,"screenTop":false,"screenX":false,"screenY":false,"ScriptProcessorNode":false,"scroll":false,"scrollbars":false,"scrollBy":false,"scrollTo":false,"scrollX":false,"scrollY":false,"SecurityPolicyViolationEvent":false,"Selection":false,"self":false,"ServiceWorker":false,"ServiceWorkerContainer":false,"ServiceWorkerRegistration":false,"sessionStorage":false,"setInterval":false,"setTimeout":false,"ShadowRoot":false,"SharedWorker":false,"SourceBuffer":false,"SourceBufferList":false,"speechSynthesis":false,"SpeechSynthesisEvent":false,"SpeechSynthesisUtterance":false,"StaticRange":false,"status":false,"statusbar":false,"StereoPannerNode":false,"stop":false,"Storage":false,"StorageEvent":false,"StorageManager":false,"styleMedia":false,"StyleSheet":false,"StyleSheetList":false,"SubtleCrypto":false,"SVGAElement":false,"SVGAngle":false,"SVGAnimatedAngle":false,"SVGAnimatedBoolean":false,"SVGAnimatedEnumeration":false,"SVGAnimatedInteger":false,"SVGAnimatedLength":false,"SVGAnimatedLengthList":false,"SVGAnimatedNumber":false,"SVGAnimatedNumberList":false,"SVGAnimatedPreserveAspectRatio":false,"SVGAnimatedRect":false,"SVGAnimatedString":false,"SVGAnimatedTransformList":false,"SVGAnimateElement":false,"SVGAnimateMotionElement":false,"SVGAnimateTransformElement":false,"SVGAnimationElement":false,"SVGCircleElement":false,"SVGClipPathElement":false,"SVGComponentTransferFunctionElement":false,"SVGDefsElement":false,"SVGDescElement":false,"SVGDiscardElement":false,"SVGElement":false,"SVGEllipseElement":false,"SVGFEBlendElement":false,"SVGFEColorMatrixElement":false,"SVGFEComponentTransferElement":false,"SVGFECompositeElement":false,"SVGFEConvolveMatrixElement":false,"SVGFEDiffuseLightingElement":false,"SVGFEDisplacementMapElement":false,"SVGFEDistantLightElement":false,"SVGFEDropShadowElement":false,"SVGFEFloodElement":false,"SVGFEFuncAElement":false,"SVGFEFuncBElement":false,"SVGFEFuncGElement":false,"SVGFEFuncRElement":false,"SVGFEGaussianBlurElement":false,"SVGFEImageElement":false,"SVGFEMergeElement":false,"SVGFEMergeNodeElement":false,"SVGFEMorphologyElement":false,"SVGFEOffsetElement":false,"SVGFEPointLightElement":false,"SVGFESpecularLightingElement":false,"SVGFESpotLightElement":false,"SVGFETileElement":false,"SVGFETurbulenceElement":false,"SVGFilterElement":false,"SVGForeignObjectElement":false,"SVGGElement":false,"SVGGeometryElement":false,"SVGGradientElement":false,"SVGGraphicsElement":false,"SVGImageElement":false,"SVGLength":false,"SVGLengthList":false,"SVGLinearGradientElement":false,"SVGLineElement":false,"SVGMarkerElement":false,"SVGMaskElement":false,"SVGMatrix":false,"SVGMetadataElement":false,"SVGMPathElement":false,"SVGNumber":false,"SVGNumberList":false,"SVGPathElement":false,"SVGPatternElement":false,"SVGPoint":false,"SVGPointList":false,"SVGPolygonElement":false,"SVGPolylineElement":false,"SVGPreserveAspectRatio":false,"SVGRadialGradientElement":false,"SVGRect":false,"SVGRectElement":false,"SVGScriptElement":false,"SVGSetElement":false,"SVGStopElement":false,"SVGStringList":false,"SVGStyleElement":false,"SVGSVGElement":false,"SVGSwitchElement":false,"SVGSymbolElement":false,"SVGTextContentElement":false,"SVGTextElement":false,"SVGTextPathElement":false,"SVGTextPositioningElement":false,"SVGTitleElement":false,"SVGTransform":false,"SVGTransformList":false,"SVGTSpanElement":false,"SVGUnitTypes":false,"SVGUseElement":false,"SVGViewElement":false,"TaskAttributionTiming":false,"Text":false,"TextDecoder":false,"TextEncoder":false,"TextEvent":false,"TextMetrics":false,"TextTrack":false,"TextTrackCue":false,"TextTrackCueList":false,"TextTrackList":false,"TimeRanges":false,"toolbar":false,"top":false,"Touch":false,"TouchEvent":false,"TouchList":false,"TrackEvent":false,"TransitionEvent":false,"TreeWalker":false,"UIEvent":false,"URL":false,"URLSearchParams":false,"ValidityState":false,"visualViewport":false,"VisualViewport":false,"VTTCue":false,"WaveShaperNode":false,"WebAssembly":false,"WebGL2RenderingContext":false,"WebGLActiveInfo":false,"WebGLBuffer":false,"WebGLContextEvent":false,"WebGLFramebuffer":false,"WebGLProgram":false,"WebGLQuery":false,"WebGLRenderbuffer":false,"WebGLRenderingContext":false,"WebGLSampler":false,"WebGLShader":false,"WebGLShaderPrecisionFormat":false,"WebGLSync":false,"WebGLTexture":false,"WebGLTransformFeedback":false,"WebGLUniformLocation":false,"WebGLVertexArrayObject":false,"WebSocket":false,"WheelEvent":false,"window":false,"Window":false,"Worker":false,"WritableStream":false,"XMLDocument":false,"XMLHttpRequest":false,"XMLHttpRequestEventTarget":false,"XMLHttpRequestUpload":false,"XMLSerializer":false,"XPathEvaluator":false,"XPathExpression":false,"XPathResult":false,"XSLTProcessor":false},"worker":{"addEventListener":false,"applicationCache":false,"atob":false,"Blob":false,"BroadcastChannel":false,"btoa":false,"Cache":false,"caches":false,"clearInterval":false,"clearTimeout":false,"close":true,"console":false,"fetch":false,"FileReaderSync":false,"FormData":false,"Headers":false,"IDBCursor":false,"IDBCursorWithValue":false,"IDBDatabase":false,"IDBFactory":false,"IDBIndex":false,"IDBKeyRange":false,"IDBObjectStore":false,"IDBOpenDBRequest":false,"IDBRequest":false,"IDBTransaction":false,"IDBVersionChangeEvent":false,"ImageData":false,"importScripts":true,"indexedDB":false,"location":false,"MessageChannel":false,"MessagePort":false,"name":false,"navigator":false,"Notification":false,"onclose":true,"onconnect":true,"onerror":true,"onlanguagechange":true,"onmessage":true,"onoffline":true,"ononline":true,"onrejectionhandled":true,"onunhandledrejection":true,"performance":false,"Performance":false,"PerformanceEntry":false,"PerformanceMark":false,"PerformanceMeasure":false,"PerformanceNavigation":false,"PerformanceResourceTiming":false,"PerformanceTiming":false,"postMessage":true,"Promise":false,"queueMicrotask":false,"removeEventListener":false,"Request":false,"Response":false,"self":true,"ServiceWorkerRegistration":false,"setInterval":false,"setTimeout":false,"TextDecoder":false,"TextEncoder":false,"URL":false,"URLSearchParams":false,"WebSocket":false,"Worker":false,"WorkerGlobalScope":false,"XMLHttpRequest":false},"node":{"__dirname":false,"__filename":false,"Buffer":false,"clearImmediate":false,"clearInterval":false,"clearTimeout":false,"console":false,"exports":true,"global":false,"Intl":false,"module":false,"process":false,"queueMicrotask":false,"require":false,"setImmediate":false,"setInterval":false,"setTimeout":false,"TextDecoder":false,"TextEncoder":false,"URL":false,"URLSearchParams":false},"commonjs":{"exports":true,"global":false,"module":false,"require":false},"amd":{"define":false,"require":false},"mocha":{"after":false,"afterEach":false,"before":false,"beforeEach":false,"context":false,"describe":false,"it":false,"mocha":false,"run":false,"setup":false,"specify":false,"suite":false,"suiteSetup":false,"suiteTeardown":false,"teardown":false,"test":false,"xcontext":false,"xdescribe":false,"xit":false,"xspecify":false},"jasmine":{"afterAll":false,"afterEach":false,"beforeAll":false,"beforeEach":false,"describe":false,"expect":false,"fail":false,"fdescribe":false,"fit":false,"it":false,"jasmine":false,"pending":false,"runs":false,"spyOn":false,"spyOnProperty":false,"waits":false,"waitsFor":false,"xdescribe":false,"xit":false},"jest":{"afterAll":false,"afterEach":false,"beforeAll":false,"beforeEach":false,"describe":false,"expect":false,"fdescribe":false,"fit":false,"it":false,"jest":false,"pit":false,"require":false,"test":false,"xdescribe":false,"xit":false,"xtest":false},"qunit":{"asyncTest":false,"deepEqual":false,"equal":false,"expect":false,"module":false,"notDeepEqual":false,"notEqual":false,"notOk":false,"notPropEqual":false,"notStrictEqual":false,"ok":false,"propEqual":false,"QUnit":false,"raises":false,"start":false,"stop":false,"strictEqual":false,"test":false,"throws":false},"phantomjs":{"console":true,"exports":true,"phantom":true,"require":true,"WebPage":true},"couch":{"emit":false,"exports":false,"getRow":false,"log":false,"module":false,"provides":false,"require":false,"respond":false,"send":false,"start":false,"sum":false},"rhino":{"defineClass":false,"deserialize":false,"gc":false,"help":false,"importClass":false,"importPackage":false,"java":false,"load":false,"loadClass":false,"Packages":false,"print":false,"quit":false,"readFile":false,"readUrl":false,"runCommand":false,"seal":false,"serialize":false,"spawn":false,"sync":false,"toint32":false,"version":false},"nashorn":{"__DIR__":false,"__FILE__":false,"__LINE__":false,"com":false,"edu":false,"exit":false,"java":false,"Java":false,"javafx":false,"JavaImporter":false,"javax":false,"JSAdapter":false,"load":false,"loadWithNewGlobal":false,"org":false,"Packages":false,"print":false,"quit":false},"wsh":{"ActiveXObject":true,"Enumerator":true,"GetObject":true,"ScriptEngine":true,"ScriptEngineBuildVersion":true,"ScriptEngineMajorVersion":true,"ScriptEngineMinorVersion":true,"VBArray":true,"WScript":true,"WSH":true,"XDomainRequest":true},"jquery":{"$":false,"jQuery":false},"yui":{"YAHOO":false,"YAHOO_config":false,"YUI":false,"YUI_config":false},"shelljs":{"cat":false,"cd":false,"chmod":false,"config":false,"cp":false,"dirs":false,"echo":false,"env":false,"error":false,"exec":false,"exit":false,"find":false,"grep":false,"ln":false,"ls":false,"mkdir":false,"mv":false,"popd":false,"pushd":false,"pwd":false,"rm":false,"sed":false,"set":false,"target":false,"tempdir":false,"test":false,"touch":false,"which":false},"prototypejs":{"$":false,"$$":false,"$A":false,"$break":false,"$continue":false,"$F":false,"$H":false,"$R":false,"$w":false,"Abstract":false,"Ajax":false,"Autocompleter":false,"Builder":false,"Class":false,"Control":false,"Draggable":false,"Draggables":false,"Droppables":false,"Effect":false,"Element":false,"Enumerable":false,"Event":false,"Field":false,"Form":false,"Hash":false,"Insertion":false,"ObjectRange":false,"PeriodicalExecuter":false,"Position":false,"Prototype":false,"Scriptaculous":false,"Selector":false,"Sortable":false,"SortableObserver":false,"Sound":false,"Template":false,"Toggle":false,"Try":false},"meteor":{"_":false,"$":false,"Accounts":false,"AccountsClient":false,"AccountsCommon":false,"AccountsServer":false,"App":false,"Assets":false,"Blaze":false,"check":false,"Cordova":false,"DDP":false,"DDPRateLimiter":false,"DDPServer":false,"Deps":false,"EJSON":false,"Email":false,"HTTP":false,"Log":false,"Match":false,"Meteor":false,"Mongo":false,"MongoInternals":false,"Npm":false,"Package":false,"Plugin":false,"process":false,"Random":false,"ReactiveDict":false,"ReactiveVar":false,"Router":false,"ServiceConfiguration":false,"Session":false,"share":false,"Spacebars":false,"Template":false,"Tinytest":false,"Tracker":false,"UI":false,"Utils":false,"WebApp":false,"WebAppInternals":false},"mongo":{"_isWindows":false,"_rand":false,"BulkWriteResult":false,"cat":false,"cd":false,"connect":false,"db":false,"getHostName":false,"getMemInfo":false,"hostname":false,"ISODate":false,"listFiles":false,"load":false,"ls":false,"md5sumFile":false,"mkdir":false,"Mongo":false,"NumberInt":false,"NumberLong":false,"ObjectId":false,"PlanCache":false,"print":false,"printjson":false,"pwd":false,"quit":false,"removeFile":false,"rs":false,"sh":false,"UUID":false,"version":false,"WriteResult":false},"applescript":{"$":false,"Application":false,"Automation":false,"console":false,"delay":false,"Library":false,"ObjC":false,"ObjectSpecifier":false,"Path":false,"Progress":false,"Ref":false},"serviceworker":{"addEventListener":false,"applicationCache":false,"atob":false,"Blob":false,"BroadcastChannel":false,"btoa":false,"Cache":false,"caches":false,"CacheStorage":false,"clearInterval":false,"clearTimeout":false,"Client":false,"clients":false,"Clients":false,"close":true,"console":false,"ExtendableEvent":false,"ExtendableMessageEvent":false,"fetch":false,"FetchEvent":false,"FileReaderSync":false,"FormData":false,"Headers":false,"IDBCursor":false,"IDBCursorWithValue":false,"IDBDatabase":false,"IDBFactory":false,"IDBIndex":false,"IDBKeyRange":false,"IDBObjectStore":false,"IDBOpenDBRequest":false,"IDBRequest":false,"IDBTransaction":false,"IDBVersionChangeEvent":false,"ImageData":false,"importScripts":false,"indexedDB":false,"location":false,"MessageChannel":false,"MessagePort":false,"name":false,"navigator":false,"Notification":false,"onclose":true,"onconnect":true,"onerror":true,"onfetch":true,"oninstall":true,"onlanguagechange":true,"onmessage":true,"onmessageerror":true,"onnotificationclick":true,"onnotificationclose":true,"onoffline":true,"ononline":true,"onpush":true,"onpushsubscriptionchange":true,"onrejectionhandled":true,"onsync":true,"onunhandledrejection":true,"performance":false,"Performance":false,"PerformanceEntry":false,"PerformanceMark":false,"PerformanceMeasure":false,"PerformanceNavigation":false,"PerformanceResourceTiming":false,"PerformanceTiming":false,"postMessage":true,"Promise":false,"queueMicrotask":false,"registration":false,"removeEventListener":false,"Request":false,"Response":false,"self":false,"ServiceWorker":false,"ServiceWorkerContainer":false,"ServiceWorkerGlobalScope":false,"ServiceWorkerMessageEvent":false,"ServiceWorkerRegistration":false,"setInterval":false,"setTimeout":false,"skipWaiting":false,"TextDecoder":false,"TextEncoder":false,"URL":false,"URLSearchParams":false,"WebSocket":false,"WindowClient":false,"Worker":false,"WorkerGlobalScope":false,"XMLHttpRequest":false},"atomtest":{"advanceClock":false,"fakeClearInterval":false,"fakeClearTimeout":false,"fakeSetInterval":false,"fakeSetTimeout":false,"resetTimeouts":false,"waitsForPromise":false},"embertest":{"andThen":false,"click":false,"currentPath":false,"currentRouteName":false,"currentURL":false,"fillIn":false,"find":false,"findAll":false,"findWithAssert":false,"keyEvent":false,"pauseTest":false,"resumeTest":false,"triggerEvent":false,"visit":false,"wait":false},"protractor":{"$":false,"$$":false,"browser":false,"by":false,"By":false,"DartObject":false,"element":false,"protractor":false},"shared-node-browser":{"clearInterval":false,"clearTimeout":false,"console":false,"setInterval":false,"setTimeout":false,"URL":false,"URLSearchParams":false},"webextensions":{"browser":false,"chrome":false,"opr":false},"greasemonkey":{"cloneInto":false,"createObjectIn":false,"exportFunction":false,"GM":false,"GM_addStyle":false,"GM_deleteValue":false,"GM_getResourceText":false,"GM_getResourceURL":false,"GM_getValue":false,"GM_info":false,"GM_listValues":false,"GM_log":false,"GM_openInTab":false,"GM_registerMenuCommand":false,"GM_setClipboard":false,"GM_setValue":false,"GM_xmlhttpRequest":false,"unsafeWindow":false},"devtools":{"$":false,"$_":false,"$$":false,"$0":false,"$1":false,"$2":false,"$3":false,"$4":false,"$x":false,"chrome":false,"clear":false,"copy":false,"debug":false,"dir":false,"dirxml":false,"getEventListeners":false,"inspect":false,"keys":false,"monitor":false,"monitorEvents":false,"profile":false,"profileEnd":false,"queryObjects":false,"table":false,"undebug":false,"unmonitor":false,"unmonitorEvents":false,"values":false}}'); + +/***/ }), + +/***/ 17324: +/***/ ((module) => { + +"use strict"; +module.exports = /*#__PURE__*/JSON.parse('{"assert":true,"node:assert":[">= 14.18 && < 15",">= 16"],"assert/strict":">= 15","node:assert/strict":">= 16","async_hooks":">= 8","node:async_hooks":[">= 14.18 && < 15",">= 16"],"buffer_ieee754":">= 0.5 && < 0.9.7","buffer":true,"node:buffer":[">= 14.18 && < 15",">= 16"],"child_process":true,"node:child_process":[">= 14.18 && < 15",">= 16"],"cluster":">= 0.5","node:cluster":[">= 14.18 && < 15",">= 16"],"console":true,"node:console":[">= 14.18 && < 15",">= 16"],"constants":true,"node:constants":[">= 14.18 && < 15",">= 16"],"crypto":true,"node:crypto":[">= 14.18 && < 15",">= 16"],"_debug_agent":">= 1 && < 8","_debugger":"< 8","dgram":true,"node:dgram":[">= 14.18 && < 15",">= 16"],"diagnostics_channel":[">= 14.17 && < 15",">= 15.1"],"node:diagnostics_channel":[">= 14.18 && < 15",">= 16"],"dns":true,"node:dns":[">= 14.18 && < 15",">= 16"],"dns/promises":">= 15","node:dns/promises":">= 16","domain":">= 0.7.12","node:domain":[">= 14.18 && < 15",">= 16"],"events":true,"node:events":[">= 14.18 && < 15",">= 16"],"freelist":"< 6","fs":true,"node:fs":[">= 14.18 && < 15",">= 16"],"fs/promises":[">= 10 && < 10.1",">= 14"],"node:fs/promises":[">= 14.18 && < 15",">= 16"],"_http_agent":">= 0.11.1","node:_http_agent":[">= 14.18 && < 15",">= 16"],"_http_client":">= 0.11.1","node:_http_client":[">= 14.18 && < 15",">= 16"],"_http_common":">= 0.11.1","node:_http_common":[">= 14.18 && < 15",">= 16"],"_http_incoming":">= 0.11.1","node:_http_incoming":[">= 14.18 && < 15",">= 16"],"_http_outgoing":">= 0.11.1","node:_http_outgoing":[">= 14.18 && < 15",">= 16"],"_http_server":">= 0.11.1","node:_http_server":[">= 14.18 && < 15",">= 16"],"http":true,"node:http":[">= 14.18 && < 15",">= 16"],"http2":">= 8.8","node:http2":[">= 14.18 && < 15",">= 16"],"https":true,"node:https":[">= 14.18 && < 15",">= 16"],"inspector":">= 8","node:inspector":[">= 14.18 && < 15",">= 16"],"inspector/promises":[">= 19"],"node:inspector/promises":[">= 19"],"_linklist":"< 8","module":true,"node:module":[">= 14.18 && < 15",">= 16"],"net":true,"node:net":[">= 14.18 && < 15",">= 16"],"node-inspect/lib/_inspect":">= 7.6 && < 12","node-inspect/lib/internal/inspect_client":">= 7.6 && < 12","node-inspect/lib/internal/inspect_repl":">= 7.6 && < 12","os":true,"node:os":[">= 14.18 && < 15",">= 16"],"path":true,"node:path":[">= 14.18 && < 15",">= 16"],"path/posix":">= 15.3","node:path/posix":">= 16","path/win32":">= 15.3","node:path/win32":">= 16","perf_hooks":">= 8.5","node:perf_hooks":[">= 14.18 && < 15",">= 16"],"process":">= 1","node:process":[">= 14.18 && < 15",">= 16"],"punycode":">= 0.5","node:punycode":[">= 14.18 && < 15",">= 16"],"querystring":true,"node:querystring":[">= 14.18 && < 15",">= 16"],"readline":true,"node:readline":[">= 14.18 && < 15",">= 16"],"readline/promises":">= 17","node:readline/promises":">= 17","repl":true,"node:repl":[">= 14.18 && < 15",">= 16"],"node:sea":[">= 20.12 && < 21",">= 21.7"],"smalloc":">= 0.11.5 && < 3","node:sqlite":[">= 22.13 && < 23",">= 23.4"],"_stream_duplex":">= 0.9.4","node:_stream_duplex":[">= 14.18 && < 15",">= 16"],"_stream_transform":">= 0.9.4","node:_stream_transform":[">= 14.18 && < 15",">= 16"],"_stream_wrap":">= 1.4.1","node:_stream_wrap":[">= 14.18 && < 15",">= 16"],"_stream_passthrough":">= 0.9.4","node:_stream_passthrough":[">= 14.18 && < 15",">= 16"],"_stream_readable":">= 0.9.4","node:_stream_readable":[">= 14.18 && < 15",">= 16"],"_stream_writable":">= 0.9.4","node:_stream_writable":[">= 14.18 && < 15",">= 16"],"stream":true,"node:stream":[">= 14.18 && < 15",">= 16"],"stream/consumers":">= 16.7","node:stream/consumers":">= 16.7","stream/promises":">= 15","node:stream/promises":">= 16","stream/web":">= 16.5","node:stream/web":">= 16.5","string_decoder":true,"node:string_decoder":[">= 14.18 && < 15",">= 16"],"sys":[">= 0.4 && < 0.7",">= 0.8"],"node:sys":[">= 14.18 && < 15",">= 16"],"test/reporters":">= 19.9 && < 20.2","node:test/reporters":[">= 18.17 && < 19",">= 19.9",">= 20"],"test/mock_loader":">= 22.3 && < 22.7","node:test/mock_loader":">= 22.3 && < 22.7","node:test":[">= 16.17 && < 17",">= 18"],"timers":true,"node:timers":[">= 14.18 && < 15",">= 16"],"timers/promises":">= 15","node:timers/promises":">= 16","_tls_common":">= 0.11.13","node:_tls_common":[">= 14.18 && < 15",">= 16"],"_tls_legacy":">= 0.11.3 && < 10","_tls_wrap":">= 0.11.3","node:_tls_wrap":[">= 14.18 && < 15",">= 16"],"tls":true,"node:tls":[">= 14.18 && < 15",">= 16"],"trace_events":">= 10","node:trace_events":[">= 14.18 && < 15",">= 16"],"tty":true,"node:tty":[">= 14.18 && < 15",">= 16"],"url":true,"node:url":[">= 14.18 && < 15",">= 16"],"util":true,"node:util":[">= 14.18 && < 15",">= 16"],"util/types":">= 15.3","node:util/types":">= 16","v8/tools/arguments":">= 10 && < 12","v8/tools/codemap":[">= 4.4 && < 5",">= 5.2 && < 12"],"v8/tools/consarray":[">= 4.4 && < 5",">= 5.2 && < 12"],"v8/tools/csvparser":[">= 4.4 && < 5",">= 5.2 && < 12"],"v8/tools/logreader":[">= 4.4 && < 5",">= 5.2 && < 12"],"v8/tools/profile_view":[">= 4.4 && < 5",">= 5.2 && < 12"],"v8/tools/splaytree":[">= 4.4 && < 5",">= 5.2 && < 12"],"v8":">= 1","node:v8":[">= 14.18 && < 15",">= 16"],"vm":true,"node:vm":[">= 14.18 && < 15",">= 16"],"wasi":[">= 13.4 && < 13.5",">= 18.17 && < 19",">= 20"],"node:wasi":[">= 18.17 && < 19",">= 20"],"worker_threads":">= 11.7","node:worker_threads":[">= 14.18 && < 15",">= 16"],"zlib":">= 0.5","node:zlib":[">= 14.18 && < 15",">= 16"]}'); + +/***/ }), + +/***/ 62035: +/***/ ((module) => { + +"use strict"; +module.exports = /*#__PURE__*/JSON.parse('{"assert":true,"node:assert":[">= 14.18 && < 15",">= 16"],"assert/strict":">= 15","node:assert/strict":">= 16","async_hooks":">= 8","node:async_hooks":[">= 14.18 && < 15",">= 16"],"buffer_ieee754":">= 0.5 && < 0.9.7","buffer":true,"node:buffer":[">= 14.18 && < 15",">= 16"],"child_process":true,"node:child_process":[">= 14.18 && < 15",">= 16"],"cluster":">= 0.5","node:cluster":[">= 14.18 && < 15",">= 16"],"console":true,"node:console":[">= 14.18 && < 15",">= 16"],"constants":true,"node:constants":[">= 14.18 && < 15",">= 16"],"crypto":true,"node:crypto":[">= 14.18 && < 15",">= 16"],"_debug_agent":">= 1 && < 8","_debugger":"< 8","dgram":true,"node:dgram":[">= 14.18 && < 15",">= 16"],"diagnostics_channel":[">= 14.17 && < 15",">= 15.1"],"node:diagnostics_channel":[">= 14.18 && < 15",">= 16"],"dns":true,"node:dns":[">= 14.18 && < 15",">= 16"],"dns/promises":">= 15","node:dns/promises":">= 16","domain":">= 0.7.12","node:domain":[">= 14.18 && < 15",">= 16"],"events":true,"node:events":[">= 14.18 && < 15",">= 16"],"freelist":"< 6","fs":true,"node:fs":[">= 14.18 && < 15",">= 16"],"fs/promises":[">= 10 && < 10.1",">= 14"],"node:fs/promises":[">= 14.18 && < 15",">= 16"],"_http_agent":">= 0.11.1","node:_http_agent":[">= 14.18 && < 15",">= 16"],"_http_client":">= 0.11.1","node:_http_client":[">= 14.18 && < 15",">= 16"],"_http_common":">= 0.11.1","node:_http_common":[">= 14.18 && < 15",">= 16"],"_http_incoming":">= 0.11.1","node:_http_incoming":[">= 14.18 && < 15",">= 16"],"_http_outgoing":">= 0.11.1","node:_http_outgoing":[">= 14.18 && < 15",">= 16"],"_http_server":">= 0.11.1","node:_http_server":[">= 14.18 && < 15",">= 16"],"http":true,"node:http":[">= 14.18 && < 15",">= 16"],"http2":">= 8.8","node:http2":[">= 14.18 && < 15",">= 16"],"https":true,"node:https":[">= 14.18 && < 15",">= 16"],"inspector":">= 8","node:inspector":[">= 14.18 && < 15",">= 16"],"inspector/promises":[">= 19"],"node:inspector/promises":[">= 19"],"_linklist":"< 8","module":true,"node:module":[">= 14.18 && < 15",">= 16"],"net":true,"node:net":[">= 14.18 && < 15",">= 16"],"node-inspect/lib/_inspect":">= 7.6 && < 12","node-inspect/lib/internal/inspect_client":">= 7.6 && < 12","node-inspect/lib/internal/inspect_repl":">= 7.6 && < 12","os":true,"node:os":[">= 14.18 && < 15",">= 16"],"path":true,"node:path":[">= 14.18 && < 15",">= 16"],"path/posix":">= 15.3","node:path/posix":">= 16","path/win32":">= 15.3","node:path/win32":">= 16","perf_hooks":">= 8.5","node:perf_hooks":[">= 14.18 && < 15",">= 16"],"process":">= 1","node:process":[">= 14.18 && < 15",">= 16"],"punycode":">= 0.5","node:punycode":[">= 14.18 && < 15",">= 16"],"querystring":true,"node:querystring":[">= 14.18 && < 15",">= 16"],"readline":true,"node:readline":[">= 14.18 && < 15",">= 16"],"readline/promises":">= 17","node:readline/promises":">= 17","repl":true,"node:repl":[">= 14.18 && < 15",">= 16"],"node:sea":[">= 20.12 && < 21",">= 21.7"],"smalloc":">= 0.11.5 && < 3","node:sqlite":">= 23.4","_stream_duplex":">= 0.9.4","node:_stream_duplex":[">= 14.18 && < 15",">= 16"],"_stream_transform":">= 0.9.4","node:_stream_transform":[">= 14.18 && < 15",">= 16"],"_stream_wrap":">= 1.4.1","node:_stream_wrap":[">= 14.18 && < 15",">= 16"],"_stream_passthrough":">= 0.9.4","node:_stream_passthrough":[">= 14.18 && < 15",">= 16"],"_stream_readable":">= 0.9.4","node:_stream_readable":[">= 14.18 && < 15",">= 16"],"_stream_writable":">= 0.9.4","node:_stream_writable":[">= 14.18 && < 15",">= 16"],"stream":true,"node:stream":[">= 14.18 && < 15",">= 16"],"stream/consumers":">= 16.7","node:stream/consumers":">= 16.7","stream/promises":">= 15","node:stream/promises":">= 16","stream/web":">= 16.5","node:stream/web":">= 16.5","string_decoder":true,"node:string_decoder":[">= 14.18 && < 15",">= 16"],"sys":[">= 0.4 && < 0.7",">= 0.8"],"node:sys":[">= 14.18 && < 15",">= 16"],"test/reporters":">= 19.9 && < 20.2","node:test/reporters":[">= 18.17 && < 19",">= 19.9",">= 20"],"test/mock_loader":">= 22.3 && < 22.7","node:test/mock_loader":">= 22.3 && < 22.7","node:test":[">= 16.17 && < 17",">= 18"],"timers":true,"node:timers":[">= 14.18 && < 15",">= 16"],"timers/promises":">= 15","node:timers/promises":">= 16","_tls_common":">= 0.11.13","node:_tls_common":[">= 14.18 && < 15",">= 16"],"_tls_legacy":">= 0.11.3 && < 10","_tls_wrap":">= 0.11.3","node:_tls_wrap":[">= 14.18 && < 15",">= 16"],"tls":true,"node:tls":[">= 14.18 && < 15",">= 16"],"trace_events":">= 10","node:trace_events":[">= 14.18 && < 15",">= 16"],"tty":true,"node:tty":[">= 14.18 && < 15",">= 16"],"url":true,"node:url":[">= 14.18 && < 15",">= 16"],"util":true,"node:util":[">= 14.18 && < 15",">= 16"],"util/types":">= 15.3","node:util/types":">= 16","v8/tools/arguments":">= 10 && < 12","v8/tools/codemap":[">= 4.4 && < 5",">= 5.2 && < 12"],"v8/tools/consarray":[">= 4.4 && < 5",">= 5.2 && < 12"],"v8/tools/csvparser":[">= 4.4 && < 5",">= 5.2 && < 12"],"v8/tools/logreader":[">= 4.4 && < 5",">= 5.2 && < 12"],"v8/tools/profile_view":[">= 4.4 && < 5",">= 5.2 && < 12"],"v8/tools/splaytree":[">= 4.4 && < 5",">= 5.2 && < 12"],"v8":">= 1","node:v8":[">= 14.18 && < 15",">= 16"],"vm":true,"node:vm":[">= 14.18 && < 15",">= 16"],"wasi":[">= 13.4 && < 13.5",">= 18.17 && < 19",">= 20"],"node:wasi":[">= 18.17 && < 19",">= 20"],"worker_threads":">= 11.7","node:worker_threads":[">= 14.18 && < 15",">= 16"],"zlib":">= 0.5","node:zlib":[">= 14.18 && < 15",">= 16"]}'); + +/***/ }), + +/***/ 50408: +/***/ ((module) => { + +"use strict"; +module.exports = /*#__PURE__*/JSON.parse('["cent","copy","divide","gt","lt","not","para","times"]'); + +/***/ }) + +/******/ }); +/************************************************************************/ +/******/ // The module cache +/******/ var __webpack_module_cache__ = {}; +/******/ +/******/ // The require function +/******/ function __nccwpck_require__(moduleId) { +/******/ // Check if module is in cache +/******/ var cachedModule = __webpack_module_cache__[moduleId]; +/******/ if (cachedModule !== undefined) { +/******/ return cachedModule.exports; +/******/ } +/******/ // Create a new module (and put it into the cache) +/******/ var module = __webpack_module_cache__[moduleId] = { +/******/ id: moduleId, +/******/ loaded: false, +/******/ exports: {} +/******/ }; +/******/ +/******/ // Execute the module function +/******/ var threw = true; +/******/ try { +/******/ __webpack_modules__[moduleId].call(module.exports, module, module.exports, __nccwpck_require__); +/******/ threw = false; +/******/ } finally { +/******/ if(threw) delete __webpack_module_cache__[moduleId]; +/******/ } +/******/ +/******/ // Flag the module as loaded +/******/ module.loaded = true; +/******/ +/******/ // Return the exports of the module +/******/ return module.exports; +/******/ } +/******/ +/************************************************************************/ +/******/ /* webpack/runtime/compat get default export */ +/******/ (() => { +/******/ // getDefaultExport function for compatibility with non-harmony modules +/******/ __nccwpck_require__.n = (module) => { +/******/ var getter = module && module.__esModule ? +/******/ () => (module['default']) : +/******/ () => (module); +/******/ __nccwpck_require__.d(getter, { a: getter }); +/******/ return getter; +/******/ }; +/******/ })(); +/******/ +/******/ /* webpack/runtime/define property getters */ +/******/ (() => { +/******/ // define getter functions for harmony exports +/******/ __nccwpck_require__.d = (exports, definition) => { +/******/ for(var key in definition) { +/******/ if(__nccwpck_require__.o(definition, key) && !__nccwpck_require__.o(exports, key)) { +/******/ Object.defineProperty(exports, key, { enumerable: true, get: definition[key] }); +/******/ } +/******/ } +/******/ }; +/******/ })(); +/******/ +/******/ /* webpack/runtime/hasOwnProperty shorthand */ +/******/ (() => { +/******/ __nccwpck_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop)) +/******/ })(); +/******/ +/******/ /* webpack/runtime/node module decorator */ +/******/ (() => { +/******/ __nccwpck_require__.nmd = (module) => { +/******/ module.paths = []; +/******/ if (!module.children) module.children = []; +/******/ return module; +/******/ }; +/******/ })(); +/******/ +/******/ /* webpack/runtime/compat */ +/******/ +/******/ if (typeof __nccwpck_require__ !== 'undefined') __nccwpck_require__.ab = __dirname + "/"; +/******/ +/************************************************************************/ +var __webpack_exports__ = {}; +// This entry need to be wrapped in an IIFE because it need to be in strict mode. +(() => { +"use strict"; + +// EXTERNAL MODULE: ./node_modules/@actions/core/lib/core.js +var core = __nccwpck_require__(37484); +// EXTERNAL MODULE: external "fs" +var external_fs_ = __nccwpck_require__(79896); +// EXTERNAL MODULE: external "path" +var external_path_ = __nccwpck_require__(16928); +// EXTERNAL MODULE: ./node_modules/klaw-sync/klaw-sync.js +var klaw_sync = __nccwpck_require__(71628); +var klaw_sync_default = /*#__PURE__*/__nccwpck_require__.n(klaw_sync); +// EXTERNAL MODULE: ./node_modules/gray-matter/index.js +var gray_matter = __nccwpck_require__(19599); +var gray_matter_default = /*#__PURE__*/__nccwpck_require__.n(gray_matter); +// EXTERNAL MODULE: ./node_modules/remark/index.js +var remark = __nccwpck_require__(11112); +var remark_default = /*#__PURE__*/__nccwpck_require__.n(remark); +// EXTERNAL MODULE: ./node_modules/remark-mdx/index.js +var remark_mdx = __nccwpck_require__(47926); +var remark_mdx_default = /*#__PURE__*/__nccwpck_require__.n(remark_mdx); +// EXTERNAL MODULE: external "assert" +var external_assert_ = __nccwpck_require__(42613); +;// CONCATENATED MODULE: ./node_modules/unist-util-is/lib/index.js +/** + * @typedef {import('unist').Node} Node + * @typedef {import('unist').Parent} Parent + */ + +/** + * @template Fn + * @template Fallback + * @typedef {Fn extends (value: any) => value is infer Thing ? Thing : Fallback} Predicate + */ + +/** + * @callback Check + * Check that an arbitrary value is a node. + * @param {unknown} this + * The given context. + * @param {unknown} [node] + * Anything (typically a node). + * @param {number | null | undefined} [index] + * The node’s position in its parent. + * @param {Parent | null | undefined} [parent] + * The node’s parent. + * @returns {boolean} + * Whether this is a node and passes a test. + * + * @typedef {Record | Node} Props + * Object to check for equivalence. + * + * Note: `Node` is included as it is common but is not indexable. + * + * @typedef {Array | Props | TestFunction | string | null | undefined} Test + * Check for an arbitrary node. + * + * @callback TestFunction + * Check if a node passes a test. + * @param {unknown} this + * The given context. + * @param {Node} node + * A node. + * @param {number | undefined} [index] + * The node’s position in its parent. + * @param {Parent | undefined} [parent] + * The node’s parent. + * @returns {boolean | undefined | void} + * Whether this node passes the test. + * + * Note: `void` is included until TS sees no return as `undefined`. + */ + +/** + * Check if `node` is a `Node` and whether it passes the given test. + * + * @param {unknown} node + * Thing to check, typically `Node`. + * @param {Test} test + * A check for a specific node. + * @param {number | null | undefined} index + * The node’s position in its parent. + * @param {Parent | null | undefined} parent + * The node’s parent. + * @param {unknown} context + * Context object (`this`) to pass to `test` functions. + * @returns {boolean} + * Whether `node` is a node and passes a test. + */ +const is = + // Note: overloads in JSDoc can’t yet use different `@template`s. + /** + * @type {( + * ((node: unknown, test: Condition, index?: number | null | undefined, parent?: Parent | null | undefined, context?: unknown) => node is Node & {type: Condition}) & + * ((node: unknown, test: Condition, index?: number | null | undefined, parent?: Parent | null | undefined, context?: unknown) => node is Node & Condition) & + * ((node: unknown, test: Condition, index?: number | null | undefined, parent?: Parent | null | undefined, context?: unknown) => node is Node & Predicate) & + * ((node?: null | undefined) => false) & + * ((node: unknown, test?: null | undefined, index?: number | null | undefined, parent?: Parent | null | undefined, context?: unknown) => node is Node) & + * ((node: unknown, test?: Test, index?: number | null | undefined, parent?: Parent | null | undefined, context?: unknown) => boolean) + * )} + */ + ( + /** + * @param {unknown} [node] + * @param {Test} [test] + * @param {number | null | undefined} [index] + * @param {Parent | null | undefined} [parent] + * @param {unknown} [context] + * @returns {boolean} + */ + // eslint-disable-next-line max-params + function (node, test, index, parent, context) { + const check = convert(test) + + if ( + index !== undefined && + index !== null && + (typeof index !== 'number' || + index < 0 || + index === Number.POSITIVE_INFINITY) + ) { + throw new Error('Expected positive finite index') + } + + if ( + parent !== undefined && + parent !== null && + (!is(parent) || !parent.children) + ) { + throw new Error('Expected parent node') + } + + if ( + (parent === undefined || parent === null) !== + (index === undefined || index === null) + ) { + throw new Error('Expected both parent and index') + } + + return looksLikeANode(node) + ? check.call(context, node, index, parent) + : false + } + ) + +/** + * Generate an assertion from a test. + * + * Useful if you’re going to test many nodes, for example when creating a + * utility where something else passes a compatible test. + * + * The created function is a bit faster because it expects valid input only: + * a `node`, `index`, and `parent`. + * + * @param {Test} test + * * when nullish, checks if `node` is a `Node`. + * * when `string`, works like passing `(node) => node.type === test`. + * * when `function` checks if function passed the node is true. + * * when `object`, checks that all keys in test are in node, and that they have (strictly) equal values. + * * when `array`, checks if any one of the subtests pass. + * @returns {Check} + * An assertion. + */ +const convert = + // Note: overloads in JSDoc can’t yet use different `@template`s. + /** + * @type {( + * ((test: Condition) => (node: unknown, index?: number | null | undefined, parent?: Parent | null | undefined, context?: unknown) => node is Node & {type: Condition}) & + * ((test: Condition) => (node: unknown, index?: number | null | undefined, parent?: Parent | null | undefined, context?: unknown) => node is Node & Condition) & + * ((test: Condition) => (node: unknown, index?: number | null | undefined, parent?: Parent | null | undefined, context?: unknown) => node is Node & Predicate) & + * ((test?: null | undefined) => (node?: unknown, index?: number | null | undefined, parent?: Parent | null | undefined, context?: unknown) => node is Node) & + * ((test?: Test) => Check) + * )} + */ + ( + /** + * @param {Test} [test] + * @returns {Check} + */ + function (test) { + if (test === null || test === undefined) { + return ok + } + + if (typeof test === 'function') { + return castFactory(test) + } + + if (typeof test === 'object') { + return Array.isArray(test) ? anyFactory(test) : propsFactory(test) + } + + if (typeof test === 'string') { + return typeFactory(test) + } + + throw new Error('Expected function, string, or object as test') + } + ) + +/** + * @param {Array} tests + * @returns {Check} + */ +function anyFactory(tests) { + /** @type {Array} */ + const checks = [] + let index = -1 + + while (++index < tests.length) { + checks[index] = convert(tests[index]) + } + + return castFactory(any) + + /** + * @this {unknown} + * @type {TestFunction} + */ + function any(...parameters) { + let index = -1 + + while (++index < checks.length) { + if (checks[index].apply(this, parameters)) return true + } + + return false + } +} + +/** + * Turn an object into a test for a node with a certain fields. + * + * @param {Props} check + * @returns {Check} + */ +function propsFactory(check) { + const checkAsRecord = /** @type {Record} */ (check) + + return castFactory(all) + + /** + * @param {Node} node + * @returns {boolean} + */ + function all(node) { + const nodeAsRecord = /** @type {Record} */ ( + /** @type {unknown} */ (node) + ) + + /** @type {string} */ + let key + + for (key in check) { + if (nodeAsRecord[key] !== checkAsRecord[key]) return false + } + + return true + } +} + +/** + * Turn a string into a test for a node with a certain type. + * + * @param {string} check + * @returns {Check} + */ +function typeFactory(check) { + return castFactory(type) + + /** + * @param {Node} node + */ + function type(node) { + return node && node.type === check + } +} + +/** + * Turn a custom test into a test for a node that passes that test. + * + * @param {TestFunction} testFunction + * @returns {Check} + */ +function castFactory(testFunction) { + return check + + /** + * @this {unknown} + * @type {Check} + */ + function check(value, index, parent) { + return Boolean( + looksLikeANode(value) && + testFunction.call( + this, + value, + typeof index === 'number' ? index : undefined, + parent || undefined + ) + ) + } +} + +function ok() { + return true +} + +/** + * @param {unknown} value + * @returns {value is Node} + */ +function looksLikeANode(value) { + return value !== null && typeof value === 'object' && 'type' in value +} + +;// CONCATENATED MODULE: ./node_modules/unist-util-visit-parents/lib/color.node.js +/** + * @param {string} d + * @returns {string} + */ +function color(d) { + return '\u001B[33m' + d + '\u001B[39m' +} + +;// CONCATENATED MODULE: ./node_modules/unist-util-visit-parents/lib/index.js +/** + * @typedef {import('unist').Node} UnistNode + * @typedef {import('unist').Parent} UnistParent + */ + +/** + * @typedef {Exclude | undefined} Test + * Test from `unist-util-is`. + * + * Note: we have remove and add `undefined`, because otherwise when generating + * automatic `.d.ts` files, TS tries to flatten paths from a local perspective, + * which doesn’t work when publishing on npm. + */ + +/** + * @typedef {( + * Fn extends (value: any) => value is infer Thing + * ? Thing + * : Fallback + * )} Predicate + * Get the value of a type guard `Fn`. + * @template Fn + * Value; typically function that is a type guard (such as `(x): x is Y`). + * @template Fallback + * Value to yield if `Fn` is not a type guard. + */ + +/** + * @typedef {( + * Check extends null | undefined // No test. + * ? Value + * : Value extends {type: Check} // String (type) test. + * ? Value + * : Value extends Check // Partial test. + * ? Value + * : Check extends Function // Function test. + * ? Predicate extends Value + * ? Predicate + * : never + * : never // Some other test? + * )} MatchesOne + * Check whether a node matches a primitive check in the type system. + * @template Value + * Value; typically unist `Node`. + * @template Check + * Value; typically `unist-util-is`-compatible test, but not arrays. + */ + +/** + * @typedef {( + * Check extends Array + * ? MatchesOne + * : MatchesOne + * )} Matches + * Check whether a node matches a check in the type system. + * @template Value + * Value; typically unist `Node`. + * @template Check + * Value; typically `unist-util-is`-compatible test. + */ + +/** + * @typedef {0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10} Uint + * Number; capped reasonably. + */ + +/** + * @typedef {I extends 0 ? 1 : I extends 1 ? 2 : I extends 2 ? 3 : I extends 3 ? 4 : I extends 4 ? 5 : I extends 5 ? 6 : I extends 6 ? 7 : I extends 7 ? 8 : I extends 8 ? 9 : 10} Increment + * Increment a number in the type system. + * @template {Uint} [I=0] + * Index. + */ + +/** + * @typedef {( + * Node extends UnistParent + * ? Node extends {children: Array} + * ? Child extends Children ? Node : never + * : never + * : never + * )} InternalParent + * Collect nodes that can be parents of `Child`. + * @template {UnistNode} Node + * All node types in a tree. + * @template {UnistNode} Child + * Node to search for. + */ + +/** + * @typedef {InternalParent, Child>} Parent + * Collect nodes in `Tree` that can be parents of `Child`. + * @template {UnistNode} Tree + * All node types in a tree. + * @template {UnistNode} Child + * Node to search for. + */ + +/** + * @typedef {( + * Depth extends Max + * ? never + * : + * | InternalParent + * | InternalAncestor, Max, Increment> + * )} InternalAncestor + * Collect nodes in `Tree` that can be ancestors of `Child`. + * @template {UnistNode} Node + * All node types in a tree. + * @template {UnistNode} Child + * Node to search for. + * @template {Uint} [Max=10] + * Max; searches up to this depth. + * @template {Uint} [Depth=0] + * Current depth. + */ + +/** + * @typedef {InternalAncestor, Child>} Ancestor + * Collect nodes in `Tree` that can be ancestors of `Child`. + * @template {UnistNode} Tree + * All node types in a tree. + * @template {UnistNode} Child + * Node to search for. + */ + +/** + * @typedef {( + * Tree extends UnistParent + * ? Depth extends Max + * ? Tree + * : Tree | InclusiveDescendant> + * : Tree + * )} InclusiveDescendant + * Collect all (inclusive) descendants of `Tree`. + * + * > 👉 **Note**: for performance reasons, this seems to be the fastest way to + * > recurse without actually running into an infinite loop, which the + * > previous version did. + * > + * > Practically, a max of `2` is typically enough assuming a `Root` is + * > passed, but it doesn’t improve performance. + * > It gets higher with `List > ListItem > Table > TableRow > TableCell`. + * > Using up to `10` doesn’t hurt or help either. + * @template {UnistNode} Tree + * Tree type. + * @template {Uint} [Max=10] + * Max; searches up to this depth. + * @template {Uint} [Depth=0] + * Current depth. + */ + +/** + * @typedef {'skip' | boolean} Action + * Union of the action types. + * + * @typedef {number} Index + * Move to the sibling at `index` next (after node itself is completely + * traversed). + * + * Useful if mutating the tree, such as removing the node the visitor is + * currently on, or any of its previous siblings. + * Results less than 0 or greater than or equal to `children.length` stop + * traversing the parent. + * + * @typedef {[(Action | null | undefined | void)?, (Index | null | undefined)?]} ActionTuple + * List with one or two values, the first an action, the second an index. + * + * @typedef {Action | ActionTuple | Index | null | undefined | void} VisitorResult + * Any value that can be returned from a visitor. + */ + +/** + * @callback Visitor + * Handle a node (matching `test`, if given). + * + * Visitors are free to transform `node`. + * They can also transform the parent of node (the last of `ancestors`). + * + * Replacing `node` itself, if `SKIP` is not returned, still causes its + * descendants to be walked (which is a bug). + * + * When adding or removing previous siblings of `node` (or next siblings, in + * case of reverse), the `Visitor` should return a new `Index` to specify the + * sibling to traverse after `node` is traversed. + * Adding or removing next siblings of `node` (or previous siblings, in case + * of reverse) is handled as expected without needing to return a new `Index`. + * + * Removing the children property of an ancestor still results in them being + * traversed. + * @param {Visited} node + * Found node. + * @param {Array} ancestors + * Ancestors of `node`. + * @returns {VisitorResult} + * What to do next. + * + * An `Index` is treated as a tuple of `[CONTINUE, Index]`. + * An `Action` is treated as a tuple of `[Action]`. + * + * Passing a tuple back only makes sense if the `Action` is `SKIP`. + * When the `Action` is `EXIT`, that action can be returned. + * When the `Action` is `CONTINUE`, `Index` can be returned. + * @template {UnistNode} [Visited=UnistNode] + * Visited node type. + * @template {UnistParent} [VisitedParents=UnistParent] + * Ancestor type. + */ + +/** + * @typedef {Visitor, Check>, Ancestor, Check>>>} BuildVisitor + * Build a typed `Visitor` function from a tree and a test. + * + * It will infer which values are passed as `node` and which as `parents`. + * @template {UnistNode} [Tree=UnistNode] + * Tree type. + * @template {Test} [Check=Test] + * Test type. + */ + + + + +/** @type {Readonly} */ +const empty = [] + +/** + * Continue traversing as normal. + */ +const CONTINUE = true + +/** + * Stop traversing immediately. + */ +const EXIT = false + +/** + * Do not traverse this node’s children. + */ +const SKIP = 'skip' + +/** + * Visit nodes, with ancestral information. + * + * This algorithm performs *depth-first* *tree traversal* in *preorder* + * (**NLR**) or if `reverse` is given, in *reverse preorder* (**NRL**). + * + * You can choose for which nodes `visitor` is called by passing a `test`. + * For complex tests, you should test yourself in `visitor`, as it will be + * faster and will have improved type information. + * + * Walking the tree is an intensive task. + * Make use of the return values of the visitor when possible. + * Instead of walking a tree multiple times, walk it once, use `unist-util-is` + * to check if a node matches, and then perform different operations. + * + * You can change the tree. + * See `Visitor` for more info. + * + * @overload + * @param {Tree} tree + * @param {Check} check + * @param {BuildVisitor} visitor + * @param {boolean | null | undefined} [reverse] + * @returns {undefined} + * + * @overload + * @param {Tree} tree + * @param {BuildVisitor} visitor + * @param {boolean | null | undefined} [reverse] + * @returns {undefined} + * + * @param {UnistNode} tree + * Tree to traverse. + * @param {Visitor | Test} test + * `unist-util-is`-compatible test + * @param {Visitor | boolean | null | undefined} [visitor] + * Handle each node. + * @param {boolean | null | undefined} [reverse] + * Traverse in reverse preorder (NRL) instead of the default preorder (NLR). + * @returns {undefined} + * Nothing. + * + * @template {UnistNode} Tree + * Node type. + * @template {Test} Check + * `unist-util-is`-compatible test. + */ +function visitParents(tree, test, visitor, reverse) { + /** @type {Test} */ + let check + + if (typeof test === 'function' && typeof visitor !== 'function') { + reverse = visitor + // @ts-expect-error no visitor given, so `visitor` is test. + visitor = test + } else { + // @ts-expect-error visitor given, so `test` isn’t a visitor. + check = test + } + + const is = convert(check) + const step = reverse ? -1 : 1 + + factory(tree, undefined, [])() + + /** + * @param {UnistNode} node + * @param {number | undefined} index + * @param {Array} parents + */ + function factory(node, index, parents) { + const value = /** @type {Record} */ ( + node && typeof node === 'object' ? node : {} + ) + + if (typeof value.type === 'string') { + const name = + // `hast` + typeof value.tagName === 'string' + ? value.tagName + : // `xast` + typeof value.name === 'string' + ? value.name + : undefined + + Object.defineProperty(visit, 'name', { + value: + 'node (' + color(node.type + (name ? '<' + name + '>' : '')) + ')' + }) + } + + return visit + + function visit() { + /** @type {Readonly} */ + let result = empty + /** @type {Readonly} */ + let subresult + /** @type {number} */ + let offset + /** @type {Array} */ + let grandparents + + if (!test || is(node, index, parents[parents.length - 1] || undefined)) { + // @ts-expect-error: `visitor` is now a visitor. + result = toResult(visitor(node, parents)) + + if (result[0] === EXIT) { + return result + } + } + + if ('children' in node && node.children) { + const nodeAsParent = /** @type {UnistParent} */ (node) + + if (nodeAsParent.children && result[0] !== SKIP) { + offset = (reverse ? nodeAsParent.children.length : -1) + step + grandparents = parents.concat(nodeAsParent) + + while (offset > -1 && offset < nodeAsParent.children.length) { + const child = nodeAsParent.children[offset] + + subresult = factory(child, offset, grandparents)() + + if (subresult[0] === EXIT) { + return subresult + } + + offset = + typeof subresult[1] === 'number' ? subresult[1] : offset + step + } + } + } + + return result + } + } +} + +/** + * Turn a return value into a clean result. + * + * @param {VisitorResult} value + * Valid return values from visitors. + * @returns {Readonly} + * Clean result. + */ +function toResult(value) { + if (Array.isArray(value)) { + return value + } + + if (typeof value === 'number') { + return [CONTINUE, value] + } + + return value === null || value === undefined ? empty : [value] +} + +;// CONCATENATED MODULE: ./node_modules/unist-util-visit/lib/index.js +/** + * @typedef {import('unist').Node} UnistNode + * @typedef {import('unist').Parent} UnistParent + * @typedef {import('unist-util-visit-parents').VisitorResult} VisitorResult + */ + +/** + * @typedef {Exclude | undefined} Test + * Test from `unist-util-is`. + * + * Note: we have remove and add `undefined`, because otherwise when generating + * automatic `.d.ts` files, TS tries to flatten paths from a local perspective, + * which doesn’t work when publishing on npm. + */ + +// To do: use types from `unist-util-visit-parents` when it’s released. + +/** + * @typedef {( + * Fn extends (value: any) => value is infer Thing + * ? Thing + * : Fallback + * )} Predicate + * Get the value of a type guard `Fn`. + * @template Fn + * Value; typically function that is a type guard (such as `(x): x is Y`). + * @template Fallback + * Value to yield if `Fn` is not a type guard. + */ + +/** + * @typedef {( + * Check extends null | undefined // No test. + * ? Value + * : Value extends {type: Check} // String (type) test. + * ? Value + * : Value extends Check // Partial test. + * ? Value + * : Check extends Function // Function test. + * ? Predicate extends Value + * ? Predicate + * : never + * : never // Some other test? + * )} MatchesOne + * Check whether a node matches a primitive check in the type system. + * @template Value + * Value; typically unist `Node`. + * @template Check + * Value; typically `unist-util-is`-compatible test, but not arrays. + */ + +/** + * @typedef {( + * Check extends Array + * ? MatchesOne + * : MatchesOne + * )} Matches + * Check whether a node matches a check in the type system. + * @template Value + * Value; typically unist `Node`. + * @template Check + * Value; typically `unist-util-is`-compatible test. + */ + +/** + * @typedef {0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10} Uint + * Number; capped reasonably. + */ + +/** + * @typedef {I extends 0 ? 1 : I extends 1 ? 2 : I extends 2 ? 3 : I extends 3 ? 4 : I extends 4 ? 5 : I extends 5 ? 6 : I extends 6 ? 7 : I extends 7 ? 8 : I extends 8 ? 9 : 10} Increment + * Increment a number in the type system. + * @template {Uint} [I=0] + * Index. + */ + +/** + * @typedef {( + * Node extends UnistParent + * ? Node extends {children: Array} + * ? Child extends Children ? Node : never + * : never + * : never + * )} InternalParent + * Collect nodes that can be parents of `Child`. + * @template {UnistNode} Node + * All node types in a tree. + * @template {UnistNode} Child + * Node to search for. + */ + +/** + * @typedef {InternalParent, Child>} Parent + * Collect nodes in `Tree` that can be parents of `Child`. + * @template {UnistNode} Tree + * All node types in a tree. + * @template {UnistNode} Child + * Node to search for. + */ + +/** + * @typedef {( + * Depth extends Max + * ? never + * : + * | InternalParent + * | InternalAncestor, Max, Increment> + * )} InternalAncestor + * Collect nodes in `Tree` that can be ancestors of `Child`. + * @template {UnistNode} Node + * All node types in a tree. + * @template {UnistNode} Child + * Node to search for. + * @template {Uint} [Max=10] + * Max; searches up to this depth. + * @template {Uint} [Depth=0] + * Current depth. + */ + +/** + * @typedef {( + * Tree extends UnistParent + * ? Depth extends Max + * ? Tree + * : Tree | InclusiveDescendant> + * : Tree + * )} InclusiveDescendant + * Collect all (inclusive) descendants of `Tree`. + * + * > 👉 **Note**: for performance reasons, this seems to be the fastest way to + * > recurse without actually running into an infinite loop, which the + * > previous version did. + * > + * > Practically, a max of `2` is typically enough assuming a `Root` is + * > passed, but it doesn’t improve performance. + * > It gets higher with `List > ListItem > Table > TableRow > TableCell`. + * > Using up to `10` doesn’t hurt or help either. + * @template {UnistNode} Tree + * Tree type. + * @template {Uint} [Max=10] + * Max; searches up to this depth. + * @template {Uint} [Depth=0] + * Current depth. + */ + +/** + * @callback Visitor + * Handle a node (matching `test`, if given). + * + * Visitors are free to transform `node`. + * They can also transform `parent`. + * + * Replacing `node` itself, if `SKIP` is not returned, still causes its + * descendants to be walked (which is a bug). + * + * When adding or removing previous siblings of `node` (or next siblings, in + * case of reverse), the `Visitor` should return a new `Index` to specify the + * sibling to traverse after `node` is traversed. + * Adding or removing next siblings of `node` (or previous siblings, in case + * of reverse) is handled as expected without needing to return a new `Index`. + * + * Removing the children property of `parent` still results in them being + * traversed. + * @param {Visited} node + * Found node. + * @param {Visited extends UnistNode ? number | undefined : never} index + * Index of `node` in `parent`. + * @param {Ancestor extends UnistParent ? Ancestor | undefined : never} parent + * Parent of `node`. + * @returns {VisitorResult} + * What to do next. + * + * An `Index` is treated as a tuple of `[CONTINUE, Index]`. + * An `Action` is treated as a tuple of `[Action]`. + * + * Passing a tuple back only makes sense if the `Action` is `SKIP`. + * When the `Action` is `EXIT`, that action can be returned. + * When the `Action` is `CONTINUE`, `Index` can be returned. + * @template {UnistNode} [Visited=UnistNode] + * Visited node type. + * @template {UnistParent} [Ancestor=UnistParent] + * Ancestor type. + */ + +/** + * @typedef {Visitor>} BuildVisitorFromMatch + * Build a typed `Visitor` function from a node and all possible parents. + * + * It will infer which values are passed as `node` and which as `parent`. + * @template {UnistNode} Visited + * Node type. + * @template {UnistParent} Ancestor + * Parent type. + */ + +/** + * @typedef {( + * BuildVisitorFromMatch< + * Matches, + * Extract + * > + * )} BuildVisitorFromDescendants + * Build a typed `Visitor` function from a list of descendants and a test. + * + * It will infer which values are passed as `node` and which as `parent`. + * @template {UnistNode} Descendant + * Node type. + * @template {Test} Check + * Test type. + */ + +/** + * @typedef {( + * BuildVisitorFromDescendants< + * InclusiveDescendant, + * Check + * > + * )} BuildVisitor + * Build a typed `Visitor` function from a tree and a test. + * + * It will infer which values are passed as `node` and which as `parent`. + * @template {UnistNode} [Tree=UnistNode] + * Node type. + * @template {Test} [Check=Test] + * Test type. + */ + + + + + +/** + * Visit nodes. + * + * This algorithm performs *depth-first* *tree traversal* in *preorder* + * (**NLR**) or if `reverse` is given, in *reverse preorder* (**NRL**). + * + * You can choose for which nodes `visitor` is called by passing a `test`. + * For complex tests, you should test yourself in `visitor`, as it will be + * faster and will have improved type information. + * + * Walking the tree is an intensive task. + * Make use of the return values of the visitor when possible. + * Instead of walking a tree multiple times, walk it once, use `unist-util-is` + * to check if a node matches, and then perform different operations. + * + * You can change the tree. + * See `Visitor` for more info. + * + * @overload + * @param {Tree} tree + * @param {Check} check + * @param {BuildVisitor} visitor + * @param {boolean | null | undefined} [reverse] + * @returns {undefined} + * + * @overload + * @param {Tree} tree + * @param {BuildVisitor} visitor + * @param {boolean | null | undefined} [reverse] + * @returns {undefined} + * + * @param {UnistNode} tree + * Tree to traverse. + * @param {Visitor | Test} testOrVisitor + * `unist-util-is`-compatible test (optional, omit to pass a visitor). + * @param {Visitor | boolean | null | undefined} [visitorOrReverse] + * Handle each node (when test is omitted, pass `reverse`). + * @param {boolean | null | undefined} [maybeReverse=false] + * Traverse in reverse preorder (NRL) instead of the default preorder (NLR). + * @returns {undefined} + * Nothing. + * + * @template {UnistNode} Tree + * Node type. + * @template {Test} Check + * `unist-util-is`-compatible test. + */ +function visit(tree, testOrVisitor, visitorOrReverse, maybeReverse) { + /** @type {boolean | null | undefined} */ + let reverse + /** @type {Test} */ + let test + /** @type {Visitor} */ + let visitor + + if ( + typeof testOrVisitor === 'function' && + typeof visitorOrReverse !== 'function' + ) { + test = undefined + visitor = testOrVisitor + reverse = visitorOrReverse + } else { + // @ts-expect-error: assume the overload with test was given. + test = testOrVisitor + // @ts-expect-error: assume the overload with test was given. + visitor = visitorOrReverse + reverse = maybeReverse + } + + visitParents(tree, test, overload, reverse) + + /** + * @param {UnistNode} node + * @param {Array} parents + */ + function overload(node, parents) { + const parent = parents[parents.length - 1] + const index = parent ? parent.children.indexOf(node) : undefined + return visitor(node, index, parent) + } +} + +;// CONCATENATED MODULE: ./remark-get-images-plugin.ts +/* eslint-disable */ +// @ts-nocheck +// TODO: fix types +// stdlib + + + +// for creating custom remark plugin + // Use 4.1.0; 5+ is ESM + +const remarkGetImages = (HCPsourceDir, imageSrcSet) => { + const test = (node) => { + return is(node, 'image'); + }; + return function (tree) { + visit(tree, test, (node) => { + const src = external_path_.join(HCPsourceDir, node.url); + external_assert_.ok(external_fs_.existsSync(src), '[getImagesPlugin] image not found: ' + src); + imageSrcSet.add(src); + }); + }; +}; + +;// CONCATENATED MODULE: ./remark-transfrom-cloud-docs-links.ts +/* eslint-disable */ +// @ts-nocheck +// TODO: fix types +// for creating custom remark plugin + // Use 4.1.0; 5+ is ESM + + +const remarkTransformCloudDocsLinks = () => { + const test = (node) => { + return is(node, 'link') || is(node, 'definition'); + }; + return function (tree) { + visit(tree, test, (node) => { + // early exit if any urls match any ignored patterns + if (IGNORE_PATTERNS.some((e) => e.test(node.url))) { + return; + } + // Match urls beginning with `/cloud-docs` or `/terraform/cloud-docs` + if (/^(\/terraform)?\/cloud-docs/i.test(node.url)) { + node.url = node.url.replace('cloud-docs', 'enterprise'); + } + }); + }; +}; + +;// CONCATENATED MODULE: ./main.ts +// stdlib + + +// fs traversal + +// initial processing + +// mdx processing + + +// plugins + + +const imageSrcSet = new Set(); +// List of MDX files to exclude from being copied +const IGNORE_LIST = ['cloud-docs/index.mdx']; +const IGNORE_PATTERNS = [/cloud-docs\/agents/i]; +const SUB_PATH_MAPPINGS = [ + { + source: 'cloud-docs', + target: 'enterprise', + }, +]; +/** + * This is a helper to be passed to `walk` dry up repeated logic + * for ignore certain files. + */ +const filterFunc = (item) => { + // if the item matches a IGNORE_PATTERNS expression, exclude it + if (IGNORE_PATTERNS.some((pattern) => { + return pattern.test(item.path); + })) { + return false; + } + // Check files for `tfc_only` frontmatter property; Ignore them if true + if (item.stats.isFile()) { + const fullContent = external_fs_.readFileSync(item.path, 'utf8'); + const { data } = gray_matter_default()(fullContent); + if (data.tfc_only == true) { + return false; + } + } + return true; +}; +/** + * A helper that accepts a data object and an array of functions that + * receive the object as an arg and transform it. + */ +const transformObject = (data, plugins) => { + let result = data; + plugins.forEach((fn) => { + result = fn(result); + }); + return result; +}; +/** + * This function will copy 3 things + * - MDX files + * - these can be at varying levels of nesting + * - used images + * - these are expected to all be at the same level + * - nav-data JSON files + * + * This function will also prune the target directory + * of any files that are not in the source directory. + * + * @param newTFEVersion An absolute path to a GitHub repository on disk + */ +async function main( +// sourceDir: string, +// targetDir: string, +newTFEVersion) { + const newTFEVersionDir = external_path_.join('./content/ptfe-releases', newTFEVersion); + // Create a new folder for the new TFE version + // if (fs.existsSync(targetDir)) { + // throw new Error(`Directory already exists: ${targetDir}`) + // } + // fs.mkdirSync(targetDir, { recursive: true }) + const HCPsourceDir = './testing/content/terraform-docs-common'; + // const HCPsourceDir = './content/terraform-docs-common' + const HCPContentDir = external_path_.join(HCPsourceDir, 'docs'); + const newTFEVersionContentDir = external_path_.join(newTFEVersionDir, 'docs'); + const newTFEVersionImageDir = external_path_.join(newTFEVersionDir, 'img/docs'); + // Read version metadata and get the latest version of ptfe-releases + // const versionMetadataPath = path.resolve('app/api/versionMetadata.json') + // const versionMetadata = JSON.parse(fs.readFileSync(versionMetadataPath, 'utf8')) + // const ptfeReleases = versionMetadata['ptfe-releases'] + // if (!ptfeReleases || ptfeReleases.length === 0) { + // throw new Error('No ptfe-releases found in versionMetadata.json') + // } + // const latestPtfeRelease = ptfeReleases[0] + // Actually don't think I need this + // traverse source docs and accumulate mdx files for a given set of "subPaths" + let items = []; + for (const { source: subPath } of SUB_PATH_MAPPINGS) { + const src = external_path_.join(HCPContentDir, subPath); + const docItems = klaw_sync_default()(src, { + nodir: true, + filter: filterFunc, + }); + items = items.concat(docItems); + } + // process each mdx file + for (const item of items) { + // ignore some files + if (IGNORE_LIST.some((ignore) => { + return item.path.endsWith(ignore); + })) { + continue; + } + // extract mdx content; ignore frontmatter + const fullContent = external_fs_.readFileSync(item.path, 'utf8'); + // eslint-disable-next-line prefer-const + let { content, data } = gray_matter_default()(fullContent); + data = transformObject(data, [ + // inject `source` frontmatter property + function injectSource(d) { + d.source = external_path_.basename(HCPsourceDir); + return d; + }, + // replace cloud instances with enterprise + function replaceCloudWithEnterprise(d) { + // Some docs do not have all frontmatter properties. Make sure + // we do not assign `undefined` (which is invalid) in YAML + if (d.page_title) { + d.page_title = d.page_title.replace('Terraform Cloud', 'Terraform Enterprise'); + d.page_title = d.page_title.replace('HCP Terraform', 'Terraform Enterprise'); + } + if (d.description) { + d.description = d.description.replace('Terraform Cloud', 'Terraform Enterprise'); + d.description = d.description.replace('HCP Terraform', 'Terraform Enterprise'); + } + return d; + }, + ]); + const vfile = await remark_default()() + .use((remark_mdx_default())) + // @ts-expect-error remark is being passed in through the pipeline + .use(remarkGetImages, HCPsourceDir, imageSrcSet) + // @ts-expect-error remark is being passed in through the pipeline + .use(remarkTransformCloudDocsLinks) + .process(content); + // replace \-> with -> + const stringOutput = vfile.toString().replaceAll('\\->', '->'); + // overwrite original file with transformed content + const contents = gray_matter_default().stringify('\n' + stringOutput, data); + external_fs_.writeFileSync(item.path, contents); + } + // keep track of the files that were copied in the target repo + const copiedTargetRepoRelativePaths = []; + // Copy an entire directory + // --------------------------------------------- + // /{source}/cloud-docs/dir/some-doc.mdx + // ↓ ↓ ↓ ↓ + // /{target}/enterprise/dir/some-docs.mdx + // --------------------------------------------- + for (const { source, target } of SUB_PATH_MAPPINGS) { + const src = external_path_.join(HCPContentDir, source); + const dest = external_path_.join(newTFEVersionContentDir, target); + const items = klaw_sync_default()(src, { + nodir: true, + filter: filterFunc, + }); + for (const item of items) { + // ignore some files + if (IGNORE_LIST.some((ignore) => { + return item.path.endsWith(ignore); + })) { + continue; + } + const destAbsolutePath = item.path.replace(src, dest); + external_fs_.mkdirSync(external_path_.dirname(destAbsolutePath), { recursive: true }); + external_fs_.copyFileSync(item.path, destAbsolutePath); + // accumulate copied files + const relativePath = external_path_.relative(newTFEVersionDir, destAbsolutePath); + copiedTargetRepoRelativePaths.push(relativePath); + } + } + // Copy images + for (const src of Array.from(imageSrcSet)) { + const basename = external_path_.basename(src); + const target = external_path_.join(newTFEVersionImageDir, basename); + external_fs_.mkdirSync(newTFEVersionImageDir, { recursive: true }); + external_fs_.copyFileSync(src, target); + // accumulate copied files + const relativePath = external_path_.relative(newTFEVersionDir, target); + copiedTargetRepoRelativePaths.push(relativePath); + } +} + +;// CONCATENATED MODULE: ./index.ts + + +async function action() { + const newTFEVersion = core.getInput('new_TFE_version'); + // const newTFEVersion = 'v000011-1' + await main(newTFEVersion); +} +action(); + +})(); + +module.exports = __webpack_exports__; +/******/ })() +; \ No newline at end of file diff --git a/.github/actions/copy-cloud-docs-for-tfe/out/worker-pipeline.js b/.github/actions/copy-cloud-docs-for-tfe/out/worker-pipeline.js new file mode 100644 index 0000000000..32e7c03760 --- /dev/null +++ b/.github/actions/copy-cloud-docs-for-tfe/out/worker-pipeline.js @@ -0,0 +1,51 @@ +'use strict' + +const EE = require('events') +const { realImport, realRequire } = require('real-require') +const { pipeline, PassThrough } = require('stream') + +// This file is not checked by the code coverage tool, +// as it is not reliable. + +/* istanbul ignore file */ + +module.exports = async function ({ targets }) { + const streams = await Promise.all(targets.map(async (t) => { + let fn + try { + const toLoad = 'file://' + t.target + fn = (await realImport(toLoad)).default + } catch (error) { + // See this PR for details: https://github.com/pinojs/thread-stream/pull/34 + if ((error.code === 'ENOTDIR' || error.code === 'ERR_MODULE_NOT_FOUND')) { + fn = realRequire(t.target) + } else { + throw error + } + } + const stream = await fn(t.options) + return stream + })) + const ee = new EE() + + const stream = new PassThrough({ + autoDestroy: true, + destroy (err, cb) { + // destroying one stream is enough + streams[0].destroy(err) + ee.on('error', cb) + ee.on('closed', cb) + } + }) + + pipeline(stream, ...streams, function (err) { + if (err && err.code !== 'ERR_STREAM_PREMATURE_CLOSE') { + ee.emit('error', err) + return + } + + ee.emit('closed') + }) + + return stream +} diff --git a/.github/actions/copy-cloud-docs-for-tfe/out/worker.js b/.github/actions/copy-cloud-docs-for-tfe/out/worker.js new file mode 100644 index 0000000000..9bfabcf5cd --- /dev/null +++ b/.github/actions/copy-cloud-docs-for-tfe/out/worker.js @@ -0,0 +1,65 @@ +'use strict' + +const pino = require('../pino.js') +const build = require('pino-abstract-transport') +const { realImport, realRequire } = require('real-require') + +// This file is not checked by the code coverage tool, +// as it is not reliable. + +/* istanbul ignore file */ + +module.exports = async function ({ targets }) { + targets = await Promise.all(targets.map(async (t) => { + let fn + try { + const toLoad = 'file://' + t.target + fn = (await realImport(toLoad)).default + } catch (error) { + // See this PR for details: https://github.com/pinojs/thread-stream/pull/34 + if ((error.code === 'ENOTDIR' || error.code === 'ERR_MODULE_NOT_FOUND')) { + fn = realRequire(t.target) + } else { + throw error + } + } + const stream = await fn(t.options) + return { + level: t.level, + stream + } + })) + return build(process, { + parse: 'lines', + metadata: true, + close (err, cb) { + let expected = 0 + for (const transport of targets) { + expected++ + transport.stream.on('close', closeCb) + transport.stream.end() + } + + function closeCb () { + if (--expected === 0) { + cb(err) + } + } + } + }) + + function process (stream) { + const multi = pino.multistream(targets) + // TODO manage backpressure + stream.on('data', function (chunk) { + const { lastTime, lastMsg, lastObj, lastLevel } = this + multi.lastLevel = lastLevel + multi.lastTime = lastTime + multi.lastMsg = lastMsg + multi.lastObj = lastObj + + // TODO handle backpressure + multi.write(chunk + '\n') + }) + } +} diff --git a/.github/actions/copy-cloud-docs-for-tfe/out/worker1.js b/.github/actions/copy-cloud-docs-for-tfe/out/worker1.js new file mode 100644 index 0000000000..08e81ccf14 --- /dev/null +++ b/.github/actions/copy-cloud-docs-for-tfe/out/worker1.js @@ -0,0 +1,149 @@ +'use strict' + +const { realImport, realRequire } = require('real-require') +const { workerData, parentPort } = require('worker_threads') +const { WRITE_INDEX, READ_INDEX } = require('./indexes') +const { waitDiff } = require('./wait') + +const { + dataBuf, + filename, + stateBuf +} = workerData + +let destination + +const state = new Int32Array(stateBuf) +const data = Buffer.from(dataBuf) + +async function start () { + let fn + try { + // TODO: fix loading .ts files in Windows. See: ../test/ts.test.ts + if (filename.endsWith('.ts') || filename.endsWith('.cts')) { + // TODO: add support for the TSM modules loader ( https://github.com/lukeed/tsm ). + if (process[Symbol.for('ts-node.register.instance')]) { + realRequire('ts-node/register') + } else if (process.env.TS_NODE_DEV) { + realRequire('ts-node-dev') + } + // TODO: Support ES imports once tsc, tap & ts-node provide better compatibility guarantees. + fn = realRequire(decodeURIComponent(filename.replace('file://', ''))) + } else { + fn = (await realImport(filename)) + } + + // Depending on how the default export is performed, and on how the code is + // transpiled, we may find cases of two nested "default" objects. + // See https://github.com/pinojs/pino/issues/1243#issuecomment-982774762 + if (typeof fn === 'object') fn = fn.default + if (typeof fn === 'object') fn = fn.default + } catch (error) { + // A yarn user that tries to start a ThreadStream for an external module + // provides a filename pointing to a zip file. + // eg. require.resolve('pino-elasticsearch') // returns /foo/pino-elasticsearch-npm-6.1.0-0c03079478-6915435172.zip/bar.js + // The `import` will fail to try to load it. + // This catch block executes the `require` fallback to load the module correctly. + // In fact, yarn modifies the `require` function to manage the zipped path. + // More details at https://github.com/pinojs/pino/pull/1113 + // The error codes may change based on the node.js version (ENOTDIR > 12, ERR_MODULE_NOT_FOUND <= 12 ) + if ((error.code === 'ENOTDIR' || error.code === 'ERR_MODULE_NOT_FOUND') && + filename.startsWith('file://')) { + fn = realRequire(decodeURIComponent(filename.replace('file://', ''))) + } else { + throw error + } + } + destination = await fn(workerData.workerData) + + destination.on('error', function (err) { + Atomics.store(state, WRITE_INDEX, -2) + Atomics.notify(state, WRITE_INDEX) + + Atomics.store(state, READ_INDEX, -2) + Atomics.notify(state, READ_INDEX) + + parentPort.postMessage({ + code: 'ERROR', + err + }) + }) + + destination.on('close', function () { + // process._rawDebug('worker close emitted') + const end = Atomics.load(state, WRITE_INDEX) + Atomics.store(state, READ_INDEX, end) + Atomics.notify(state, READ_INDEX) + setImmediate(() => { + process.exit(0) + }) + }) +} + +// No .catch() handler, +// in case there is an error it goes +// to unhandledRejection +start().then(function () { + parentPort.postMessage({ + code: 'READY' + }) + + process.nextTick(run) +}) + +function run () { + const current = Atomics.load(state, READ_INDEX) + const end = Atomics.load(state, WRITE_INDEX) + + // process._rawDebug(`pre state ${current} ${end}`) + + if (end === current) { + if (end === data.length) { + waitDiff(state, READ_INDEX, end, Infinity, run) + } else { + waitDiff(state, WRITE_INDEX, end, Infinity, run) + } + return + } + + // process._rawDebug(`post state ${current} ${end}`) + + if (end === -1) { + // process._rawDebug('end') + destination.end() + return + } + + const toWrite = data.toString('utf8', current, end) + // process._rawDebug('worker writing: ' + toWrite) + + const res = destination.write(toWrite) + + if (res) { + Atomics.store(state, READ_INDEX, end) + Atomics.notify(state, READ_INDEX) + setImmediate(run) + } else { + destination.once('drain', function () { + Atomics.store(state, READ_INDEX, end) + Atomics.notify(state, READ_INDEX) + run() + }) + } +} + +process.on('unhandledRejection', function (err) { + parentPort.postMessage({ + code: 'ERROR', + err + }) + process.exit(1) +}) + +process.on('uncaughtException', function (err) { + parentPort.postMessage({ + code: 'ERROR', + err + }) + process.exit(1) +}) diff --git a/.github/actions/copy-cloud-docs-for-tfe/package-lock.json b/.github/actions/copy-cloud-docs-for-tfe/package-lock.json new file mode 100644 index 0000000000..c3d2ab5d03 --- /dev/null +++ b/.github/actions/copy-cloud-docs-for-tfe/package-lock.json @@ -0,0 +1,4866 @@ +{ + "name": "copy-cloud-docs-for-tfe", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "copy-cloud-docs-for-tfe", + "devDependencies": { + "@actions/core": "^1.11.1", + "@vercel/ncc": "^0.38.3", + "gray-matter": "^4.0.3", + "klaw-sync": "^7.0.0", + "mdast": "^2.3.2", + "ncc": "^0.3.6", + "remark": "^12.0.0", + "remark-mdx": "^1.6.22", + "unified": "^11.0.5", + "unist": "^0.0.1", + "unist-util-is": "^6.0.0", + "unist-util-visit": "^5.0.0" + } + }, + "node_modules/@actions/core": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@actions/core/-/core-1.11.1.tgz", + "integrity": "sha512-hXJCSrkwfA46Vd9Z3q4cpEpHB1rL5NG04+/rbqW9d3+CSvtB1tYe8UTpAlixa1vj0m/ULglfEK2UKxMGxCxv5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@actions/exec": "^1.1.1", + "@actions/http-client": "^2.0.1" + } + }, + "node_modules/@actions/exec": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@actions/exec/-/exec-1.1.1.tgz", + "integrity": "sha512-+sCcHHbVdk93a0XT19ECtO/gIXoxvdsgQLzb2fE2/5sIZmWQuluYyjPQtrtTHdU1YzTZ7bAPN4sITq2xi1679w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@actions/io": "^1.0.1" + } + }, + "node_modules/@actions/http-client": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-2.2.3.tgz", + "integrity": "sha512-mx8hyJi/hjFvbPokCg4uRd4ZX78t+YyRPtnKWwIl+RzNaVuFpQHfmlGVfsKEJN8LwTCvL+DfVgAM04XaHkm6bA==", + "dev": true, + "license": "MIT", + "dependencies": { + "tunnel": "^0.0.6", + "undici": "^5.25.4" + } + }, + "node_modules/@actions/io": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@actions/io/-/io-1.1.3.tgz", + "integrity": "sha512-wi9JjgKLYS7U/z8PPbco+PvTb/nRWjeoFlJ1Qer83k/3C5PHQi28hiVdeE2kHXmIL99mQFawx8qt/JPjZilJ8Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@babel/code-frame": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.12.9", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.12.9.tgz", + "integrity": "sha512-gTXYh3M5wb7FRXQy+FErKFAv90BnlOuNn1QkCK2lREoPAjrQCO49+HVSrFoe5uakFAF5eenS75KbO2vQiLrTMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.10.4", + "@babel/generator": "^7.12.5", + "@babel/helper-module-transforms": "^7.12.1", + "@babel/helpers": "^7.12.5", + "@babel/parser": "^7.12.7", + "@babel/template": "^7.12.7", + "@babel/traverse": "^7.12.9", + "@babel/types": "^7.12.7", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.1", + "json5": "^2.1.2", + "lodash": "^4.17.19", + "resolve": "^1.3.2", + "semver": "^5.4.1", + "source-map": "^0.5.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.1.tgz", + "integrity": "sha512-UnJfnIpc/+JO0/+KRVQNGU+y5taA5vCbwN8+azkX6beii/ZF+enZJSOKo11ZSzGJjlNfJHfQtmQT8H+9TXPG2w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.27.1", + "@babel/types": "^7.27.1", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", + "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.27.1.tgz", + "integrity": "sha512-9yHn519/8KvTU5BjTVEEeIM3w9/2yXNKoD82JifINImhpKkARMJKPP59kLo+BafpdN5zgNeIcS4jsGDmd3l58g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1", + "@babel/traverse": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.4.tgz", + "integrity": "sha512-O4KCvQA6lLiMU9l2eawBPMf1xPP8xPfB3iEQw150hOVTqj/rfXz0ThTb4HEzqQfs2Bmo5Ay8BzxfzVtBrr9dVg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", + "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.1.tgz", + "integrity": "sha512-FCvFTm0sWV8Fxhpp2McP5/W53GPllQ9QeQ7SiqGWjMf/LVG07lFa5+pgK05IRhVwtvafT22KF+ZSnM9I545CvQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.2.tgz", + "integrity": "sha512-QYLs8299NA7WM/bZAdp+CviYYkVoYXlDW2rzliy3chxd1PQjej7JORuMJDJXJUb9g0TT+B99EwaVLKmX+sPXWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.27.1" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-proposal-object-rest-spread": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.12.1.tgz", + "integrity": "sha512-s6SowJIjzlhx8o7lsFx5zmY4At6CTtDvgNQDdPzkBQucle58A6b/TTeEBYtyDgmcXjUTM+vE8YOGHZzzbc/ioA==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-object-rest-spread instead.", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.0", + "@babel/plugin-transform-parameters": "^7.12.1" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.12.1.tgz", + "integrity": "sha512-1yRi7yAtB0ETgxdY9ti/p2TivUxJkTdhu/ZbF9MshVGqOx1TdB3b7xCXs49Fupgg50N45KcAsRP/ZqWjs9SRjg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-parameters": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.27.1.tgz", + "integrity": "sha512-018KRk76HWKeZ5l4oTj2zPpSh+NbGdt0st5S6x0pga6HgrjBOJb24mMDHorFopOOd6YHkLgOZ+zaCjZGPO4aKg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-parameters/node_modules/@babel/helper-plugin-utils": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", + "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", + "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.1.tgz", + "integrity": "sha512-ZCYtZciz1IWJB4U61UPu4KEaqyfj+r5T1Q5mqPo+IBpcG9kHv30Z0aD8LXPgC1trYa6rK0orRyAhqUgk4MjmEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.27.1", + "@babel/parser": "^7.27.1", + "@babel/template": "^7.27.1", + "@babel/types": "^7.27.1", + "debug": "^4.3.1", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.1.tgz", + "integrity": "sha512-+EzkxvLNfiUeKMgy/3luqfsCWFRXLb7U6wNQTk60tovuckwB15B191tJWvpp4HjiQWdJkCxO3Wbvc6jlk3Xb2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@fastify/busboy": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz", + "integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", + "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@jsonjoy.com/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-q6XAnWQDIMA3+FTiOYajoYqySkO+JSat0ytXGSuRdq9uXE7o92gzuQwQM14xaCRlBLGq3v5miDGC4vkVTn54xA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/json-pack": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pack/-/json-pack-1.2.0.tgz", + "integrity": "sha512-io1zEbbYcElht3tdlqEOFxZ0dMTYrHz9iMf0gqn1pPjZFTCgM5R4R5IMA20Chb2UPYYsxjzs8CgZ7Nb5n2K2rA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/base64": "^1.1.1", + "@jsonjoy.com/util": "^1.1.2", + "hyperdyperid": "^1.2.0", + "thingies": "^1.20.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/util": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/util/-/util-1.6.0.tgz", + "integrity": "sha512-sw/RMbehRhN68WRtcKCpQOPfnH6lLP4GJfqzi3iYej8tnzpZUDr6UkZYJjcjjC0FWEJOJbyM3PTIwxucUmDG2A==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@mdx-js/util": { + "version": "1.6.22", + "resolved": "https://registry.npmjs.org/@mdx-js/util/-/util-1.6.22.tgz", + "integrity": "sha512-H1rQc1ZOHANWBvPcW+JpGwr+juXSxM8Q8YCkm3GhZd8REu1fHR3z99CErO1p9pkcfcxZnMdIZdIsXkOHY0NilA==", + "dev": true, + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@vercel/ncc": { + "version": "0.38.3", + "resolved": "https://registry.npmjs.org/@vercel/ncc/-/ncc-0.38.3.tgz", + "integrity": "sha512-rnK6hJBS6mwc+Bkab+PGPs9OiS0i/3kdTO+CkI8V0/VrW3vmz7O2Pxjw/owOlmo6PKEIxRSeZKv/kuL9itnpYA==", + "dev": true, + "license": "MIT", + "bin": { + "ncc": "dist/ncc/cli.js" + } + }, + "node_modules/ansi-escapes": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-1.4.0.tgz", + "integrity": "sha512-wiXutNjDUlNEDWHcYH3jtZUhd3c4/VojassD8zHdHCY13xbZy2XbW+NKQwA0tWGBVzDA9qEzYwfoSsWmviidhw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/anymatch": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-1.3.2.tgz", + "integrity": "sha512-0XNayC8lTHQ2OI8aljNCN3sSx6hsr/1+rlcDAotXJR7C1oZZHCNsfpbKwMjRA3Uqb5tF1Rae2oloTr4xpq+WjA==", + "dev": true, + "license": "ISC", + "dependencies": { + "micromatch": "^2.1.5", + "normalize-path": "^2.0.0" + } + }, + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/arr-diff": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz", + "integrity": "sha512-dtXTVMkh6VkEEA7OhXnN1Ecb8aAGFdZ1LFxtOCoqj4qkyOJMt7+qs6Ahdy6p/NQCPYsRSXXivhSB/J5E9jmYKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "arr-flatten": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/arr-flatten": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", + "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/arr-union": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", + "integrity": "sha512-sKpyeERZ02v1FeCZT8lrfJq5u6goHCtpTAzPwJYe7c8SPFOboNjNg1vz2L4VTn9T4PQxEx13TbXLmYUcS6Ug7Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/array-union": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", + "integrity": "sha512-Dxr6QJj/RdU/hCaBjOfxW+q6lyuVE6JFWIrAUpuOOhoJJoQ99cUn3igRaHVB5P9WrgFVN0FfArM3x0cueOU8ng==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-uniq": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/array-uniq": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", + "integrity": "sha512-MNha4BWQ6JbwhFhj03YK552f7cb3AzoE8SzeljgChvL1dl3IcvggXVz1DilzySZkCja+CXuZbdW7yATchWn8/Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/array-unique": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz", + "integrity": "sha512-G2n5bG5fSUCpnsXz4+8FUkYsGPkNfLn9YvS66U5qbTIXI2Ynnlo4Bi42bWv+omKUCqz+ejzfClwne0alJWJPhg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/arrify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", + "integrity": "sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/assign-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", + "integrity": "sha512-Q+JC7Whu8HhmTdBph/Tq59IoRtoy6KAm5zzPv00WdujX82lbAL8K7WVjne7vdCsAmbF4AYaDOPyO3k0kl8qIrw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/async-each": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.6.tgz", + "integrity": "sha512-c646jH1avxr+aVpndVMeAfYw7wAa6idufrlN3LPA4PmKS0QEGp6PIC9nwz0WQkkvBGAMEki3pFdtxaF39J9vvg==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "license": "MIT" + }, + "node_modules/atob": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", + "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", + "dev": true, + "license": "(MIT OR Apache-2.0)", + "bin": { + "atob": "bin/atob.js" + }, + "engines": { + "node": ">= 4.5.0" + } + }, + "node_modules/attach-ware": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/attach-ware/-/attach-ware-1.1.1.tgz", + "integrity": "sha512-OpavlXWZkyE7m28fpCWF/RmxCukC1edukJp9IKjEpZs/O11H3896DkLpK7lMiL8ZDx2yxo9FrZQaeHkyJGcIuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "unherit": "^1.0.0" + } + }, + "node_modules/bail": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", + "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/base": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", + "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cache-base": "^1.0.1", + "class-utils": "^0.3.5", + "component-emitter": "^1.2.1", + "define-property": "^1.0.0", + "isobject": "^3.0.1", + "mixin-deep": "^1.2.0", + "pascalcase": "^0.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/base/node_modules/define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-descriptor": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/base/node_modules/isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/binary-extensions": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz", + "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "file-uri-to-path": "1.0.0" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz", + "integrity": "sha512-xU7bpz2ytJl1bH9cgIurjpg/n8Gohy9GTw81heDYLJQ4RU60dlyJsa+atVF2pI0yMMvKxI9HkKwjePCj5XI1hw==", + "dev": true, + "license": "MIT", + "dependencies": { + "expand-range": "^1.8.1", + "preserve": "^0.2.0", + "repeat-element": "^1.1.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/cache-base": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", + "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "collection-visit": "^1.0.0", + "component-emitter": "^1.2.1", + "get-value": "^2.0.6", + "has-value": "^1.0.0", + "isobject": "^3.0.1", + "set-value": "^2.0.0", + "to-object-path": "^0.3.0", + "union-value": "^1.0.0", + "unset-value": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/cache-base/node_modules/isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/camelcase": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz", + "integrity": "sha512-DLIsRzJVBQu72meAKPkWQOLcujdXT32hwdfnkI1frSiSRMK1MofjKHf+MEx0SB6fjEFXL8fBDv1dKymBlOp4Qw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ccount": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-1.1.0.tgz", + "integrity": "sha512-vlNK021QdI7PNeiUh/lKkC/mNHHfV0m/Ad5JoI0TYtlBnJAslM/JIkm/tGC88bkLIwO6OQ5uV6ztS6kVAtCDlg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/character-entities": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-1.2.4.tgz", + "integrity": "sha512-iBMyeEHxfVnIakwOuDXpVkc54HijNgCyQB2w0VfGQThle6NXn50zU6V/u+LDhxHcDUPojn6Kpga3PTAD8W1bQw==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-html4": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-1.1.4.tgz", + "integrity": "sha512-HRcDxZuZqMx3/a+qrzxdBKBPUpxWEq9xw2OPZ3a/174ihfrQKVsFhqtthBInFy1zZ9GgZyFXOatNujm8M+El3g==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-legacy": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-1.1.4.tgz", + "integrity": "sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-reference-invalid": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-1.1.4.tgz", + "integrity": "sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/chokidar": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-1.7.0.tgz", + "integrity": "sha512-mk8fAWcRUOxY7btlLtitj3A45jOwSAxH4tOFOoEGbVsl6cL6pPMWUy7dwZ/canfj3QEdP6FHSnf/l1c6/WkzVg==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "^1.3.0", + "async-each": "^1.0.0", + "glob-parent": "^2.0.0", + "inherits": "^2.0.1", + "is-binary-path": "^1.0.0", + "is-glob": "^2.0.0", + "path-is-absolute": "^1.0.0", + "readdirp": "^2.0.0" + }, + "optionalDependencies": { + "fsevents": "^1.0.0" + } + }, + "node_modules/class-utils": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", + "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "arr-union": "^3.1.0", + "define-property": "^0.2.5", + "isobject": "^3.0.0", + "static-extend": "^0.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/class-utils/node_modules/define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-descriptor": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/class-utils/node_modules/is-descriptor": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.7.tgz", + "integrity": "sha512-C3grZTvObeN1xud4cRWl366OMXZTj0+HGyk4hvfpx4ZHt1Pb60ANSXqCK7pdOTeUQpRzECBSTphqvD7U+l22Eg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-accessor-descriptor": "^1.0.1", + "is-data-descriptor": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/class-utils/node_modules/isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/cli-cursor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-1.0.2.tgz", + "integrity": "sha512-25tABq090YNKkF6JH7lcwO0zFJTRke4Jcq9iX2nr/Sz0Cjjv4gckmwlW6Ty/aoyFd6z3ysR2hMGC2GFugmBo6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "restore-cursor": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/co": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/co/-/co-3.1.0.tgz", + "integrity": "sha512-CQsjCRiNObI8AtTsNIBDRMQ4oMR83CzEswHYahClvul7gKk+lDQiOKv+5qh7LQWf5sh6jkZNispz/QlsZxyNgA==", + "dev": true, + "license": "MIT" + }, + "node_modules/code-point-at": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha512-RpAVKQA5T63xEj6/giIbUEtZwJ4UFIc3ZtvEkiaUERylqe8xb5IvqcgOurZLahv93CLKfxcw5YI+DZcUBRyLXA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/collapse-white-space": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/collapse-white-space/-/collapse-white-space-1.0.6.tgz", + "integrity": "sha512-jEovNnrhMuqyCcjfEJA56v0Xq8SkIoPKDyaHahwo3POf4qcSXqMYuwNcOTzp74vTsR9Tn08z4MxWqAhcekogkQ==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/collection-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", + "integrity": "sha512-lNkKvzEeMBBjUGHZ+q6z9pSJla0KWAQPvtzhEV9+iGyQYG+pBpl7xKDhxoNSOZH2hhv0v5k0y2yAM4o4SjoSkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "map-visit": "^1.0.0", + "object-visit": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/colors": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.2.3.tgz", + "integrity": "sha512-qTfM2pNFeMZcLvf/RbrVAzDEVttZjFhaApfx9dplNjvHSX88Ui66zBRb/4YGob/xUWxDceirgoC1lT676asfCQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/component-emitter": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.1.tgz", + "integrity": "sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "dev": true, + "engines": [ + "node >= 0.8" + ], + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } + }, + "node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "dev": true, + "license": "MIT" + }, + "node_modules/copy-descriptor": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", + "integrity": "sha512-XgZ0pFcakEUlbwQEVNg3+QAis1FyTL3Qel9FYy8pSkQqoG3PNoT0bOCQtOXcOkur21r2Eq2kI+IE+gsmAEVlYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/dateformat": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-3.0.3.tgz", + "integrity": "sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decode-uri-component": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz", + "integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/define-property": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", + "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-descriptor": "^1.0.2", + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/define-property/node_modules/isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/devlop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", + "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", + "dev": true, + "license": "MIT", + "dependencies": { + "dequal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/elegant-spinner": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/elegant-spinner/-/elegant-spinner-1.0.1.tgz", + "integrity": "sha512-B+ZM+RXvRqQaAmkMlO/oSe5nMUOaUnyfGYCEHoR8wrXsZR2mA0XVibsxV1bvTwxdRWah1PkQqso2EzhILGHtEQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/exit-hook": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/exit-hook/-/exit-hook-1.1.1.tgz", + "integrity": "sha512-MsG3prOVw1WtLXAZbM3KiYtooKR1LvxHh3VHsVtIy0uiUu8usxgB/94DP2HxtD/661lLdB6yzQ09lGJSQr6nkg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-brackets": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz", + "integrity": "sha512-hxx03P2dJxss6ceIeri9cmYOT4SRs3Zk3afZwWpOsRqLqprhTR8u++SlC+sFGsQr7WGFPdMF7Gjc1njDLDK6UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-posix-bracket": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-range": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/expand-range/-/expand-range-1.8.2.tgz", + "integrity": "sha512-AFASGfIlnIbkKPQwX1yHaDjFvh/1gyKJODme52V6IORh69uEYgZp0o9C+qsIGNVEiuuhQU0CSSl++Rlegg1qvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^2.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true, + "license": "MIT" + }, + "node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/extend.js": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/extend.js/-/extend.js-0.0.2.tgz", + "integrity": "sha512-kSK5oO9X2i9qUptwhkilKqBfLG322xXY2ZO6/dlPY/ozt0fc+Ac9Qo6hZE/RiRTau5XUvVv2y6z1G6lNZ8f1WA==", + "dev": true + }, + "node_modules/extglob": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz", + "integrity": "sha512-1FOj1LOwn42TMrruOHGt18HemVnbwAmAak7krWk+wa93KXxGbK+2jpezm+ytJYDaBX0/SPLZFHKM7m+tKobWGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/filename-regex": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/filename-regex/-/filename-regex-2.0.1.tgz", + "integrity": "sha512-BTCqyBaWBTsauvnHiE8i562+EdJj+oUpkqWp2R1iCoR8f6oo8STRu3of7WJJ0TqWtxN50a5YFpzYK4Jj9esYfQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fill-range": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.4.tgz", + "integrity": "sha512-cnrcCbj01+j2gTG921VZPnHbjmdAf8oQV/iGeV2kZxGSyfYjjTyY79ErsK1WJWMpw6DaApEX72binqJE+/d+5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^2.1.0", + "isobject": "^2.0.0", + "randomatic": "^3.0.0", + "repeat-element": "^1.1.2", + "repeat-string": "^1.5.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/for-in": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", + "integrity": "sha512-7EwmXrOjyL+ChxMhmG5lnW9MPt1aIeZEwKhQzoBUdTV0N3zuwWDZYVJatDvZ2OyzPUvdIAZDsCetk3coyMfcnQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/for-own": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/for-own/-/for-own-0.1.5.tgz", + "integrity": "sha512-SKmowqGTJoPzLO1T0BBJpkfp3EMacCMOuH40hOUbrbzElVktk4DioXVM99QkLCyKoiuOmyjgcWMpVz2xjE7LZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "for-in": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fragment-cache": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", + "integrity": "sha512-GMBAbW9antB8iZRHLoGw0b3HANt57diZYFO/HL1JGIC1MjKrdmhxvrJbupnVvpys0zsz7yBApXdQyfepKly2kA==", + "dev": true, + "license": "MIT", + "dependencies": { + "map-cache": "^0.2.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", + "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", + "deprecated": "Upgrade to fsevents v2 to mitigate potential security issues", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "dependencies": { + "bindings": "^1.5.0", + "nan": "^2.12.1" + }, + "engines": { + "node": ">= 4.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-value": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", + "integrity": "sha512-Ln0UQDlxH1BapMu3GPtf7CuYNwRZf2gwCuPqbyG6pB8WfmFpzqcy4xtAaAMUhnNqjMKTiCPZG2oMT3YSx8U2NA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-base": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/glob-base/-/glob-base-0.3.0.tgz", + "integrity": "sha512-ab1S1g1EbO7YzauaJLkgLp7DZVAqj9M/dvKlTt8DkXA2tiOIcSMrlVI2J1RZyB5iJVccEscjGn+kpOG9788MHA==", + "dev": true, + "license": "MIT", + "dependencies": { + "glob-parent": "^2.0.0", + "is-glob": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/glob-parent": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz", + "integrity": "sha512-JDYOvfxio/t42HKdxkAYaCiBN7oYiuxykOxKxdaUW5Qn0zaYN3gRQWolrwdnf0shM9/EP0ebuuTmyoXNr1cC5w==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^2.0.0" + } + }, + "node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/globby": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-4.1.0.tgz", + "integrity": "sha512-JPDtMSr0bt25W64q792rvlrSwIaZwqUAhqdYKSr57Wh/xBcQ5JDWLM85ndn+Q1WdBQXLb9YGCl0QN/T0HpqU0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-union": "^1.0.1", + "arrify": "^1.0.0", + "glob": "^6.0.1", + "object-assign": "^4.0.1", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/globby/node_modules/glob": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/glob/-/glob-6.0.4.tgz", + "integrity": "sha512-MKZeRNyYZAVVVG1oZeLaWie1uweH40m9AZwIwxyPbTSX4hHrVYSzLg0Ro5Z5R7XKkIX+Cc6oD1rqeDJnwsB8/A==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "2 || 3", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/gray-matter": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/gray-matter/-/gray-matter-4.0.3.tgz", + "integrity": "sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "js-yaml": "^3.13.1", + "kind-of": "^6.0.2", + "section-matter": "^1.0.0", + "strip-bom-string": "^1.0.0" + }, + "engines": { + "node": ">=6.0" + } + }, + "node_modules/has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/has-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", + "integrity": "sha512-IBXk4GTsLYdQ7Rvt+GRBrFSVEkmuOUy4re0Xjd9kJSUQpnTrWR4/y9RpfexN9vkAPMFuQoeWKwqzPozRTlasGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-value": "^2.0.6", + "has-values": "^1.0.0", + "isobject": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/has-value/node_modules/isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/has-values": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", + "integrity": "sha512-ODYZC64uqzmtfGMEAX/FvZiRyWLpAC3vYnNunURUnkGVTS+mI0smVsWaPydRBsE3g+ok7h960jChO8mFcWlHaQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^3.0.0", + "kind-of": "^4.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/has-values/node_modules/is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/has-values/node_modules/is-number/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/has-values/node_modules/kind-of": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", + "integrity": "sha512-24XsCxmEbRwEDbz/qz3stgin8TTzZ1ESR56OMCN0ujYg+vRutNSiOj9bHH9u85DKgXguraugV5sFuvbD4FW/hw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/he": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/he/-/he-0.5.0.tgz", + "integrity": "sha512-DoufbNNOFzwRPy8uecq+j+VCPQ+JyDelHTmSgygrA5TsR8Cbw4Qcir5sGtWiusB4BdT89nmlaVDhSJOqC/33vw==", + "dev": true, + "license": "MIT", + "bin": { + "he": "bin/he" + } + }, + "node_modules/hyperdyperid": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/hyperdyperid/-/hyperdyperid-1.2.0.tgz", + "integrity": "sha512-Y93lCzHYgGWdrJ66yIktxiaGULYc6oGiABxhcO5AufBeOyoIdZF7bIfLaOrbM0iGIOXQQgxxRrFEnb+Y6w1n4A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.18" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true, + "license": "ISC" + }, + "node_modules/irregular-plurals": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/irregular-plurals/-/irregular-plurals-1.4.0.tgz", + "integrity": "sha512-kniTIJmaZYiwa17eTtWIfm0K342seyugl6vuC8DiiyiRAJWAVlLkqGCI0Im0neo0TkXw+pRcKaBPRdcKHnQJ6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-accessor-descriptor": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.1.tgz", + "integrity": "sha512-YBUanLI8Yoihw923YeFUS5fs0fF2f5TSFTNiYAAzhhDscDa3lEqYuz1pDOEP5KvX94I9ey3vsqjJcLVFVU+3QA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-alphabetical": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-1.0.4.tgz", + "integrity": "sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-alphanumeric": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-alphanumeric/-/is-alphanumeric-1.0.0.tgz", + "integrity": "sha512-ZmRL7++ZkcMOfDuWZuMJyIVLr2keE1o/DeNWh1EmgqGhUcV+9BIVsx0BcSBOHTZqzjs4+dISzr2KAeBEWGgXeA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-alphanumerical": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-1.0.4.tgz", + "integrity": "sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-alphabetical": "^1.0.0", + "is-decimal": "^1.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-binary-path": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", + "integrity": "sha512-9fRVlXc0uCxEDj1nQzaWONSpbTfx0FmJfzHF7pwlI8DkWGoHBBea4Pg5Ky0ojwwxQmnSifgbKkI06Qv0Ljgj+Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-data-descriptor": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.1.tgz", + "integrity": "sha512-bc4NlCDiCr28U4aEsQ3Qs2491gVq4V8G7MQyws968ImqjKuYtTJXrl7Vq7jsN7Ly/C3xj5KWFrY7sHNeDkAzXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-decimal": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-1.0.4.tgz", + "integrity": "sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-descriptor": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.3.tgz", + "integrity": "sha512-JCNNGbwWZEVaSPtS45mdtrneRWJFp07LLmykxeFV5F6oBvNF8vHSfJuJgoT472pSfk+Mf8VnlrspaFBHWM8JAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-accessor-descriptor": "^1.0.1", + "is-data-descriptor": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-dotfile": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-dotfile/-/is-dotfile-1.0.3.tgz", + "integrity": "sha512-9YclgOGtN/f8zx0Pr4FQYMdibBiTaH3sn52vjYip4ZSf6C4/6RfTEZ+MR4GvKhCxdPh21Bg42/WL55f6KSnKpg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-equal-shallow": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz", + "integrity": "sha512-0EygVC5qPvIyb+gSz7zdD5/AAoS6Qrx1e//6N4yv4oNm30kqvdmG66oZFWVlQHUWe5OjP08FuTw2IdT0EOTcYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-primitive": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-extglob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", + "integrity": "sha512-7Q+VbVafe6x2T+Tu6NcOf6sRklazEPmBoB3IWk3WdGZM2iGUwU/Oe3Wtq5lSEkDTTlpp8yx+5t4pzO/i9Ty1ww==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha512-1pqUqRjkhPJ9miNq9SwMfdvi6lBJcd6eFxvfaivQhaH3SgisfiuudvFntdKOmxuee/77l+FPjKrQjWvmPjWrRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "number-is-nan": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", + "integrity": "sha512-a1dBeB19NXsf/E0+FHqkagizel/LQw2DjSQpvQrj3zT+jYPpaUCryPnrQajXKFLCMuf4I6FhRpaGtw4lPrG6Eg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-hexadecimal": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-1.0.4.tgz", + "integrity": "sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-number": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-2.1.0.tgz", + "integrity": "sha512-QUzH43Gfb9+5yckcrSA0VBDwEtDUchrk4F6tfJZQuNzDJbEDB9cZNzSfXGQ1jqmdDY/kl41lUOWM9syA8z8jlg==", + "dev": true, + "license": "MIT", + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "license": "MIT", + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-plain-object/node_modules/isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-posix-bracket": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz", + "integrity": "sha512-Yu68oeXJ7LeWNmZ3Zov/xg/oDBnBK2RNxwYY1ilNJX+tKKZqgPK+qOn/Gs9jEu66KDY9Netf5XLKNGzas/vPfQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-primitive": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-primitive/-/is-primitive-2.0.0.tgz", + "integrity": "sha512-N3w1tFaRfk3UrPfqeRyD+GYDASU3W5VinKhlORy8EWVf/sIdDL9GAcew85XmktCfH+ngG7SRXEVDoO18WMdB/Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-whitespace-character": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-whitespace-character/-/is-whitespace-character-1.0.4.tgz", + "integrity": "sha512-SDweEzfIZM0SJV0EUga669UTKlmL0Pq8Lno0QDQsPnvECB3IM2aP0gdx5TrU0A01MAPfViaZiI2V1QMZLaKK5w==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-word-character": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-word-character/-/is-word-character-1.0.4.tgz", + "integrity": "sha512-5SMO8RVennx3nZrqtKwCGyyetPE9VDba5ugvKLaD4KopPG5kR4mQ7tNt/r7feL5yt5h3lpuBbIUmCOG2eSzXHA==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/isobject": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha512-+OUdGJlgjOBZDfxnDjYYG6zp487z0JGNQq3cYQYg5f5hKR+syHMsaztzGeml/4kGG55CSpKSpWTY+jYGgsHLgA==", + "dev": true, + "license": "MIT", + "dependencies": { + "isarray": "1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/klaw-sync": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/klaw-sync/-/klaw-sync-7.0.0.tgz", + "integrity": "sha512-UQVrq/XIu/nqf/t89IAhc0sLy0x7VlE+Gv2XCouN4rp0RKrv/S4rx80O61V6LCHqoidBr3Sz28gWvobiPAH9UQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.1.11", + "memfs": "^4.17.0" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true, + "license": "MIT" + }, + "node_modules/log-symbols": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-1.0.2.tgz", + "integrity": "sha512-mmPrW0Fh2fxOzdBbFv4g1m6pR72haFLPJ2G5SJEELf1y+iaQrDG6cWCPjy54RHYbZAt7X+ls690Kw62AdWXBzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/log-update": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/log-update/-/log-update-1.0.2.tgz", + "integrity": "sha512-4vSow8gbiGnwdDNrpy1dyNaXWKSCIPop0EHdE8GrnngHoJujM3QhvHUN/igsYCgPoHo7pFOezlJ61Hlln0KHyA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-escapes": "^1.0.0", + "cli-cursor": "^1.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/longest-streak": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-2.0.4.tgz", + "integrity": "sha512-vM6rUVCVUJJt33bnmHiZEvr7wPT78ztX7rojL+LW51bHtLh6HTjx84LA5W4+oa6aKEJA7jJu5LR6vQRBpA5DVg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/map-cache": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", + "integrity": "sha512-8y/eV9QQZCiyn1SprXSrCmqJN0yNRATe+PO8ztwqrvrbdRLA3eYJF0yaR0YayLWkMbsQSKWS9N2gPcGEc4UsZg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/map-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", + "integrity": "sha512-4y7uGv8bd2WdM9vpQsiQNo41Ln1NvhvDRuVt0k2JZQ+ezN2uaQes7lZeZ+QQUHOLQAtDaBJ+7wCbi+ab/KFs+w==", + "dev": true, + "license": "MIT", + "dependencies": { + "object-visit": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/markdown-escapes": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/markdown-escapes/-/markdown-escapes-1.0.4.tgz", + "integrity": "sha512-8z4efJYk43E0upd0NbVXwgSTQs6cT3T06etieCMEg7dRbzCbxUCK/GHlX8mhHRDcp+OLlHkPKsvqQTCvsRl2cg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/markdown-table": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-0.4.0.tgz", + "integrity": "sha512-9i/E3ZtVAoaDulRQjoPseX2X5pBNdeR8MInQb57JFvCAq4glz/w2q31eL0NHMKOntzy2D6X3plZDH4+OGuz5Fw==", + "dev": true, + "license": "MIT" + }, + "node_modules/math-random": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/math-random/-/math-random-1.0.4.tgz", + "integrity": "sha512-rUxjysqif/BZQH2yhd5Aaq7vXMSx9NdEsQcyA07uEzIvxgI7zIr33gGsh+RU0/XjmQpCW7RsVof1vlkvQVCK5A==", + "dev": true, + "license": "MIT" + }, + "node_modules/mdast": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/mdast/-/mdast-2.3.2.tgz", + "integrity": "sha512-GyTRTczR3uAbXpxy56FOBXQueq3WST8aBG0T/xoV8jGBBr2YBLDhsCHyEuXg/yILWHLvqG4F54BKsLTyIKLX/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "camelcase": "^2.0.0", + "ccount": "^1.0.0", + "chalk": "^1.0.0", + "chokidar": "^1.0.5", + "collapse-white-space": "^1.0.0", + "commander": "^2.0.0", + "concat-stream": "^1.0.0", + "debug": "^2.0.0", + "elegant-spinner": "^1.0.0", + "extend.js": "0.0.2", + "glob": "^6.0.1", + "globby": "^4.0.0", + "he": "^0.5.0", + "log-update": "^1.0.1", + "longest-streak": "^1.0.0", + "markdown-table": "^0.4.0", + "minimatch": "^3.0.0", + "npm-prefix": "^1.0.1", + "repeat-string": "^1.5.0", + "text-table": "^0.2.0", + "to-vfile": "^1.0.0", + "trim": "^0.0.1", + "trim-trailing-lines": "^1.0.0", + "unified": "^2.0.0", + "user-home": "^2.0.0", + "vfile": "^1.1.0", + "vfile-find-down": "^1.0.0", + "vfile-find-up": "^1.0.0", + "vfile-reporter": "^1.5.0", + "ware": "^1.3.0" + }, + "bin": { + "mdast": "bin/mdast" + } + }, + "node_modules/mdast-util-compact": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-compact/-/mdast-util-compact-2.0.1.tgz", + "integrity": "sha512-7GlnT24gEwDrdAwEHrU4Vv5lLWrEer4KOkAiKT9nYstsTad7Oc1TwqT2zIMKRdZF7cTuaf+GA1E4Kv7jJh8mPA==", + "dev": true, + "license": "MIT", + "dependencies": { + "unist-util-visit": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-compact/node_modules/@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", + "dev": true, + "license": "MIT" + }, + "node_modules/mdast-util-compact/node_modules/unist-util-is": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-4.1.0.tgz", + "integrity": "sha512-ZOQSsnce92GrxSqlnEEseX0gi7GH9zTJZ0p9dtu87WRb/37mMPO2Ilx1s/t9vBHrFhbgweUwb+t7cIn5dxPhZg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-compact/node_modules/unist-util-visit": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-2.0.3.tgz", + "integrity": "sha512-iJ4/RczbJMkD0712mGktuGpm/U4By4FfDonL7N/9tATGIF4imikjOuagyMY53tnZq3NP6BcmlrHhEKAfGWjh7Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^2.0.0", + "unist-util-is": "^4.0.0", + "unist-util-visit-parents": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-compact/node_modules/unist-util-visit-parents": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-3.1.1.tgz", + "integrity": "sha512-1KROIZWo6bcMrZEwiH2UrXDyalAa0uqzWCxCJj6lPOvTve2WkfgCytoDTPaMnodXh1WrXOq0haVYHj99ynJlsg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^2.0.0", + "unist-util-is": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast/node_modules/bail": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/bail/-/bail-1.0.5.tgz", + "integrity": "sha512-xFbRxM1tahm08yHBP16MMjVUAvDaBMD38zsM9EMAUN61omwLmKlOpB/Zku5QkjZ8TZ4vn53pj+t518cH0S03RQ==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/mdast/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/mdast/node_modules/glob": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/glob/-/glob-6.0.4.tgz", + "integrity": "sha512-MKZeRNyYZAVVVG1oZeLaWie1uweH40m9AZwIwxyPbTSX4hHrVYSzLg0Ro5Z5R7XKkIX+Cc6oD1rqeDJnwsB8/A==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "2 || 3", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + } + }, + "node_modules/mdast/node_modules/longest-streak": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-1.0.0.tgz", + "integrity": "sha512-84jGpz/1j02Xm/L4y4uEXGxFFPHFabKjMHQ+rEPi0gPQbD5p0J3aZomvk0ZpUPpTtcVqhtSEq+4WNQbJjWiZ1Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/mdast/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, + "node_modules/mdast/node_modules/unified": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/unified/-/unified-2.1.4.tgz", + "integrity": "sha512-qa4nA26ms49OczPueTt7G46r89TOlwAJ4pEk2U4mwkV1wNhjttItF03SE/YnfkgWg14tzmAHXGhJp2GhDYwn1A==", + "dev": true, + "license": "MIT", + "dependencies": { + "attach-ware": "^1.0.0", + "bail": "^1.0.0", + "extend": "^3.0.0", + "unherit": "^1.0.4", + "vfile": "^1.0.0", + "ware": "^1.3.0" + } + }, + "node_modules/mdast/node_modules/vfile": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-1.4.0.tgz", + "integrity": "sha512-7Fz639rwERslMqQCuf1/0H4Tqe2q484Xl6X/jsKqrP7IjFcDODFURhv0GekMnImpbj9pTOojtqL7r39LJJkjGA==", + "dev": true, + "license": "MIT" + }, + "node_modules/memfs": { + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/memfs/-/memfs-4.17.2.tgz", + "integrity": "sha512-NgYhCOWgovOXSzvYgUW0LQ7Qy72rWQMGGFJDoWg4G30RHd3z77VbYdtJ4fembJXBy8pMIUA31XNAupobOQlwdg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/json-pack": "^1.0.3", + "@jsonjoy.com/util": "^1.3.0", + "tree-dump": "^1.0.1", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">= 4.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + } + }, + "node_modules/micromatch": { + "version": "2.3.11", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz", + "integrity": "sha512-LnU2XFEk9xxSJ6rfgAry/ty5qwUTyHYOBU0g4R6tIw5ljwgGIBmiKhRWLw5NpMOnrgUNcDJ4WMp8rl3sYVHLNA==", + "dev": true, + "license": "MIT", + "dependencies": { + "arr-diff": "^2.0.0", + "array-unique": "^0.2.1", + "braces": "^1.8.2", + "expand-brackets": "^0.1.4", + "extglob": "^0.3.1", + "filename-regex": "^2.0.0", + "is-extglob": "^1.0.0", + "is-glob": "^2.0.1", + "kind-of": "^3.0.2", + "normalize-path": "^2.0.1", + "object.omit": "^2.0.0", + "parse-glob": "^3.0.4", + "regex-cache": "^0.4.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/micromatch/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/mixin-deep": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", + "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", + "dev": true, + "license": "MIT", + "dependencies": { + "for-in": "^1.0.2", + "is-extendable": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/mixin-deep/node_modules/is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-plain-object": "^2.0.4" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nan": { + "version": "2.22.2", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.22.2.tgz", + "integrity": "sha512-DANghxFkS1plDdRsX0X9pm0Z6SJNN6gBdtXfanwoZ8hooC5gosGFSBGRYHUVPz1asKA/kMRqDRdHrluZ61SpBQ==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/nanomatch": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", + "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "fragment-cache": "^0.2.1", + "is-windows": "^1.0.2", + "kind-of": "^6.0.2", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/nanomatch/node_modules/arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha512-YVIQ82gZPGBebQV/a8dar4AitzCQs0jjXwMPZllpXMaGjXPYVUawSxQrRsjhjupyVxEvbHgUmIhKVlND+j02kA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/nanomatch/node_modules/array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha512-SleRWjh9JUud2wH1hPs9rZBZ33H6T9HOiL0uwGnGx9FpE6wKGyfWugmbkEOIs6qWrZhg0LWeLziLrEwQJhs5mQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/nanomatch/node_modules/extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/nanomatch/node_modules/is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-plain-object": "^2.0.4" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ncc": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/ncc/-/ncc-0.3.6.tgz", + "integrity": "sha512-OXudTB2Ebt/FnOuDoPQbaa17+tdVqSOWA+gLfPxccWwsNED1uA2zEhpoB1hwdFC9yYbio/mdV5cvOtQI3Zrx1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "mkdirp": "^0.5.1", + "rimraf": "^2.6.1", + "tracer": "^0.8.7", + "ws": "^2.3.1" + } + }, + "node_modules/normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha512-3pKJwH184Xo/lnH6oyP1q2pMd7HcypqqmRs91/6/i2CGtWwIKGCkOOMTm/zXbgTEWHw1uNpNi/igc3ePOYHb6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "remove-trailing-separator": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-prefix": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/npm-prefix/-/npm-prefix-1.2.0.tgz", + "integrity": "sha512-EkGZ7jtA2onsULFpnZ/P5S0DPy8w9qH1TVytPhY54s+dmtLXBmp1evt8W9nfg5JEay24K3bX9WWTIHR8WQcOJA==", + "dev": true, + "license": "MIT", + "dependencies": { + "rc": "^1.1.0", + "shellsubstitute": "^1.1.0", + "untildify": "^2.1.0" + } + }, + "node_modules/number-is-nan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha512-4jbtZXNAsfZbAHiiqjLPBiCl16dES1zI4Hpzzxw61Tk+loF+sBDBKx1ICKKKwIqQ7M0mFn1TmkN7euSncWgHiQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-copy": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", + "integrity": "sha512-79LYn6VAb63zgtmAteVOWo9Vdj71ZVBy3Pbse+VqxDpEP83XuujMrGqHIwAXJ5I/aM0zU7dIyIAhifVTPrNItQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "copy-descriptor": "^0.1.0", + "define-property": "^0.2.5", + "kind-of": "^3.0.3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-copy/node_modules/define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-descriptor": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-copy/node_modules/is-descriptor": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.7.tgz", + "integrity": "sha512-C3grZTvObeN1xud4cRWl366OMXZTj0+HGyk4hvfpx4ZHt1Pb60ANSXqCK7pdOTeUQpRzECBSTphqvD7U+l22Eg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-accessor-descriptor": "^1.0.1", + "is-data-descriptor": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object-copy/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-visit": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", + "integrity": "sha512-GBaMwwAVK9qbQN3Scdo0OyvgPW7l3lnaVMj84uTOZlswkX0KpF6fyDBJhtTthf7pymztoN36/KEr1DyhF96zEA==", + "dev": true, + "license": "MIT", + "dependencies": { + "isobject": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-visit/node_modules/isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object.omit": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/object.omit/-/object.omit-2.0.1.tgz", + "integrity": "sha512-UiAM5mhmIuKLsOvrL+B0U2d1hXHF3bFYWIuH1LMpuV2EJEHG1Ntz06PgLEHjm6VFd87NpH8rastvPoyv6UW2fA==", + "dev": true, + "license": "MIT", + "dependencies": { + "for-own": "^0.1.4", + "is-extendable": "^0.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object.pick": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", + "integrity": "sha512-tqa/UMy/CCoYmj+H5qc07qvSL9dqcs/WZENZ1JbtWBlATP+iVOe778gE6MSijnyCnORzDuX6hU+LA4SZ09YjFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object.pick/node_modules/isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz", + "integrity": "sha512-GZ+g4jayMqzCRMgB2sol7GiCLjKfS1PINkjmx8spcKce1LiVqcbQreXwqs2YAFXC6R03VIG28ZS31t8M866v6A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/os-homedir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha512-B5JU3cabzk8c67mRRd3ECmROafjYMXbuzlwtqdM8IbS8ktlTix8aFGb2bAGKrSRIlnfKwovGUUr72JUPyOb6kQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/parse-entities": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-2.0.0.tgz", + "integrity": "sha512-kkywGpCcRYhqQIchaWqZ875wzpS/bMKhz5HnN3p7wveJTkTtyAB/AlnS0f8DFSqYW1T82t6yEAkEcB+A1I3MbQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "character-entities": "^1.0.0", + "character-entities-legacy": "^1.0.0", + "character-reference-invalid": "^1.0.0", + "is-alphanumerical": "^1.0.0", + "is-decimal": "^1.0.0", + "is-hexadecimal": "^1.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/parse-glob": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/parse-glob/-/parse-glob-3.0.4.tgz", + "integrity": "sha512-FC5TeK0AwXzq3tUBFtH74naWkPQCEWs4K+xMxWZBlKDWu0bVHXGZa+KKqxKidd7xwhdZ19ZNuF2uO1M/r196HA==", + "dev": true, + "license": "MIT", + "dependencies": { + "glob-base": "^0.3.0", + "is-dotfile": "^1.0.0", + "is-extglob": "^1.0.0", + "is-glob": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pascalcase": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", + "integrity": "sha512-XHXfu/yOQRy9vYOtUDVMN60OEJjW013GoObG1o+xwQTpB9eYJX/BjXMsdW13ZDPruFhYYn0AG22w0xgQMwl3Nw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha512-MnUuEycAemtSaeFSjXKW/aroV7akBbY+Sv+RkyqFjgAe73F+MR0TBWKBRDkmfWq/HiFmdavfZ1G7h4SPZXaCSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha512-0Gni6D4UcLTbv9c57DfxDGdr41XfgUjqWZu492f0cIGr16zDU06BWP/RAEvOuo7CQ0CNjHaLlM59YJJFm3NWlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "pinkie": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/plur": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/plur/-/plur-2.1.2.tgz", + "integrity": "sha512-WhcHk576xg9y/iv6RWOuroZgsqvCbJN+XGvAypCJwLAYs2iWDp5LUmvaCdV6JR2O0SMBf8l6p7A94AyLCFVMlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "irregular-plurals": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/posix-character-classes": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", + "integrity": "sha512-xTgYBc3fuo7Yt7JbiuFxSYGToMoz8fLoE6TC9Wx1P/u+LfeThMOAqmuyECnlBaaJb+u1m9hHiXUEtwW4OzfUJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/preserve": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/preserve/-/preserve-0.2.0.tgz", + "integrity": "sha512-s/46sYeylUfHNjI+sA/78FAHlmIuKqI9wNnzEOGehAlUUYeObv5C2mOinXBjyUyWmJ2SfcS2/ydApH4hTF4WXQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true, + "license": "MIT" + }, + "node_modules/randomatic": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-3.1.1.tgz", + "integrity": "sha512-TuDE5KxZ0J461RVjrJZCJc+J+zCkTb1MbH9AQUq68sMhOMcy9jLcb3BrZKgp9q9Ncltdg4QVqWrH02W2EFFVYw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^4.0.0", + "kind-of": "^6.0.0", + "math-random": "^1.0.1" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/randomatic/node_modules/is-number": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", + "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "dev": true, + "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/readable-stream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/readdirp": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", + "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.1.11", + "micromatch": "^3.1.10", + "readable-stream": "^2.0.2" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/readdirp/node_modules/arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha512-YVIQ82gZPGBebQV/a8dar4AitzCQs0jjXwMPZllpXMaGjXPYVUawSxQrRsjhjupyVxEvbHgUmIhKVlND+j02kA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/readdirp/node_modules/array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha512-SleRWjh9JUud2wH1hPs9rZBZ33H6T9HOiL0uwGnGx9FpE6wKGyfWugmbkEOIs6qWrZhg0LWeLziLrEwQJhs5mQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/readdirp/node_modules/braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/readdirp/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/readdirp/node_modules/expand-brackets": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", + "integrity": "sha512-w/ozOKR9Obk3qoWeY/WDi6MFta9AoMR+zud60mdnbniMcBxRuFJyDt2LdX/14A1UABeqk+Uk+LDfUpvoGKppZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^2.3.3", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "posix-character-classes": "^0.1.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/readdirp/node_modules/expand-brackets/node_modules/define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-descriptor": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/readdirp/node_modules/expand-brackets/node_modules/is-descriptor": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.7.tgz", + "integrity": "sha512-C3grZTvObeN1xud4cRWl366OMXZTj0+HGyk4hvfpx4ZHt1Pb60ANSXqCK7pdOTeUQpRzECBSTphqvD7U+l22Eg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-accessor-descriptor": "^1.0.1", + "is-data-descriptor": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/readdirp/node_modules/extglob": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", + "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-unique": "^0.3.2", + "define-property": "^1.0.0", + "expand-brackets": "^2.1.4", + "extend-shallow": "^2.0.1", + "fragment-cache": "^0.2.1", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/readdirp/node_modules/extglob/node_modules/define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-descriptor": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/readdirp/node_modules/fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha512-VcpLTWqWDiTerugjj8e3+esbg+skS3M9e54UuR3iCeIDMXCLTsAH8hTSzDQU/X6/6t3eYkOKoZSef2PlU6U1XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/readdirp/node_modules/is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-plain-object": "^2.0.4" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/readdirp/node_modules/is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/readdirp/node_modules/is-number/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/readdirp/node_modules/isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/readdirp/node_modules/micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "license": "MIT", + "dependencies": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/readdirp/node_modules/micromatch/node_modules/extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/readdirp/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, + "node_modules/regex-cache": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/regex-cache/-/regex-cache-0.4.4.tgz", + "integrity": "sha512-nVIZwtCjkC9YgvWkpM55B5rBhBYRZhAaJbgcFYXXsHnbZ9UZI9nnVWYZpBlCqv9ho2eZryPnWrZGsOdPwVWXWQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-equal-shallow": "^0.1.3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/regex-not": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", + "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", + "dev": true, + "license": "MIT", + "dependencies": { + "extend-shallow": "^3.0.2", + "safe-regex": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/regex-not/node_modules/extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/regex-not/node_modules/is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-plain-object": "^2.0.4" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/remark": { + "version": "12.0.1", + "resolved": "https://registry.npmjs.org/remark/-/remark-12.0.1.tgz", + "integrity": "sha512-gS7HDonkdIaHmmP/+shCPejCEEW+liMp/t/QwmF0Xt47Rpuhl32lLtDV1uKWvGoq+kxr5jSgg5oAIpGuyULjUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "remark-parse": "^8.0.0", + "remark-stringify": "^8.0.0", + "unified": "^9.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-mdx": { + "version": "1.6.22", + "resolved": "https://registry.npmjs.org/remark-mdx/-/remark-mdx-1.6.22.tgz", + "integrity": "sha512-phMHBJgeV76uyFkH4rvzCftLfKCr2RZuF+/gmVcaKrpsihyzmhXjA0BEMDaPTXG5y8qZOKPVo83NAOX01LPnOQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "7.12.9", + "@babel/helper-plugin-utils": "7.10.4", + "@babel/plugin-proposal-object-rest-spread": "7.12.1", + "@babel/plugin-syntax-jsx": "7.12.1", + "@mdx-js/util": "1.6.22", + "is-alphabetical": "1.0.4", + "remark-parse": "8.0.3", + "unified": "9.2.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-mdx/node_modules/@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", + "dev": true, + "license": "MIT" + }, + "node_modules/remark-mdx/node_modules/bail": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/bail/-/bail-1.0.5.tgz", + "integrity": "sha512-xFbRxM1tahm08yHBP16MMjVUAvDaBMD38zsM9EMAUN61omwLmKlOpB/Zku5QkjZ8TZ4vn53pj+t518cH0S03RQ==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/remark-mdx/node_modules/is-buffer": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz", + "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/remark-mdx/node_modules/is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/remark-mdx/node_modules/trough": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/trough/-/trough-1.0.5.tgz", + "integrity": "sha512-rvuRbTarPXmMb79SmzEp8aqXNKcK+y0XaB298IXueQ8I2PsrATcPBCSPyK/dDNa2iWOhKlfNnOjdAOTBU/nkFA==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/remark-mdx/node_modules/unified": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/unified/-/unified-9.2.0.tgz", + "integrity": "sha512-vx2Z0vY+a3YoTj8+pttM3tiJHCwY5UFbYdiWrwBEbHmK8pvsPj2rtAX2BFfgXen8T39CJWblWRDT4L5WGXtDdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "bail": "^1.0.0", + "extend": "^3.0.0", + "is-buffer": "^2.0.0", + "is-plain-obj": "^2.0.0", + "trough": "^1.0.0", + "vfile": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-mdx/node_modules/unist-util-stringify-position": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-2.0.3.tgz", + "integrity": "sha512-3faScn5I+hy9VleOq/qNbAd6pAx7iH5jYBMS9I1HgQVijz/4mv5Bvw5iw1sC/90CODiKo81G/ps8AJrISn687g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^2.0.2" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-mdx/node_modules/vfile": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-4.2.1.tgz", + "integrity": "sha512-O6AE4OskCG5S1emQ/4gl8zK586RqA3srz3nfK/Viy0UPToBc5Trp9BVFb1u0CjsKrAWwnpr4ifM/KBXPWwJbCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^2.0.0", + "is-buffer": "^2.0.0", + "unist-util-stringify-position": "^2.0.0", + "vfile-message": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-mdx/node_modules/vfile-message": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-2.0.4.tgz", + "integrity": "sha512-DjssxRGkMvifUOJre00juHoP9DPWuzjxKuMDrhNbk2TdaYYBNMStsNhEOt3idrtI12VQYM/1+iM0KOzXi4pxwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^2.0.0", + "unist-util-stringify-position": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-parse": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-8.0.3.tgz", + "integrity": "sha512-E1K9+QLGgggHxCQtLt++uXltxEprmWzNfg+MxpfHsZlrddKzZ/hZyWHDbK3/Ap8HJQqYJRXP+jHczdL6q6i85Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ccount": "^1.0.0", + "collapse-white-space": "^1.0.2", + "is-alphabetical": "^1.0.0", + "is-decimal": "^1.0.0", + "is-whitespace-character": "^1.0.0", + "is-word-character": "^1.0.0", + "markdown-escapes": "^1.0.0", + "parse-entities": "^2.0.0", + "repeat-string": "^1.5.4", + "state-toggle": "^1.0.0", + "trim": "0.0.1", + "trim-trailing-lines": "^1.0.0", + "unherit": "^1.0.4", + "unist-util-remove-position": "^2.0.0", + "vfile-location": "^3.0.0", + "xtend": "^4.0.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-stringify": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-8.1.1.tgz", + "integrity": "sha512-q4EyPZT3PcA3Eq7vPpT6bIdokXzFGp9i85igjmhRyXWmPs0Y6/d2FYwUNotKAWyLch7g0ASZJn/KHHcHZQ163A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ccount": "^1.0.0", + "is-alphanumeric": "^1.0.0", + "is-decimal": "^1.0.0", + "is-whitespace-character": "^1.0.0", + "longest-streak": "^2.0.1", + "markdown-escapes": "^1.0.0", + "markdown-table": "^2.0.0", + "mdast-util-compact": "^2.0.0", + "parse-entities": "^2.0.0", + "repeat-string": "^1.5.4", + "state-toggle": "^1.0.0", + "stringify-entities": "^3.0.0", + "unherit": "^1.0.4", + "xtend": "^4.0.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-stringify/node_modules/markdown-table": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-2.0.0.tgz", + "integrity": "sha512-Ezda85ToJUBhM6WGaG6veasyym+Tbs3cMAw/ZhOPqXiYsr0jgocBV3j3nx+4lk47plLlIqjwuTm/ywVI+zjJ/A==", + "dev": true, + "license": "MIT", + "dependencies": { + "repeat-string": "^1.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/remark/node_modules/@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", + "dev": true, + "license": "MIT" + }, + "node_modules/remark/node_modules/bail": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/bail/-/bail-1.0.5.tgz", + "integrity": "sha512-xFbRxM1tahm08yHBP16MMjVUAvDaBMD38zsM9EMAUN61omwLmKlOpB/Zku5QkjZ8TZ4vn53pj+t518cH0S03RQ==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/remark/node_modules/is-buffer": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz", + "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/remark/node_modules/is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/remark/node_modules/trough": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/trough/-/trough-1.0.5.tgz", + "integrity": "sha512-rvuRbTarPXmMb79SmzEp8aqXNKcK+y0XaB298IXueQ8I2PsrATcPBCSPyK/dDNa2iWOhKlfNnOjdAOTBU/nkFA==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/remark/node_modules/unified": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/unified/-/unified-9.2.2.tgz", + "integrity": "sha512-Sg7j110mtefBD+qunSLO1lqOEKdrwBFBrR6Qd8f4uwkhWNlbkaqwHse6e7QvD3AP/MNoJdEDLaf8OxYyoWgorQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "bail": "^1.0.0", + "extend": "^3.0.0", + "is-buffer": "^2.0.0", + "is-plain-obj": "^2.0.0", + "trough": "^1.0.0", + "vfile": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark/node_modules/unist-util-stringify-position": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-2.0.3.tgz", + "integrity": "sha512-3faScn5I+hy9VleOq/qNbAd6pAx7iH5jYBMS9I1HgQVijz/4mv5Bvw5iw1sC/90CODiKo81G/ps8AJrISn687g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^2.0.2" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark/node_modules/vfile": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-4.2.1.tgz", + "integrity": "sha512-O6AE4OskCG5S1emQ/4gl8zK586RqA3srz3nfK/Viy0UPToBc5Trp9BVFb1u0CjsKrAWwnpr4ifM/KBXPWwJbCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^2.0.0", + "is-buffer": "^2.0.0", + "unist-util-stringify-position": "^2.0.0", + "vfile-message": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark/node_modules/vfile-message": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-2.0.4.tgz", + "integrity": "sha512-DjssxRGkMvifUOJre00juHoP9DPWuzjxKuMDrhNbk2TdaYYBNMStsNhEOt3idrtI12VQYM/1+iM0KOzXi4pxwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^2.0.0", + "unist-util-stringify-position": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remove-trailing-separator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", + "integrity": "sha512-/hS+Y0u3aOfIETiaiirUFwDBDzmXPvO+jAfKTitUngIPzdKc6Z0LoFjM/CK5PL4C+eKwHohlHAb6H0VFfmmUsw==", + "dev": true, + "license": "ISC" + }, + "node_modules/repeat-element": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.4.tgz", + "integrity": "sha512-LFiNfRcSu7KK3evMyYOuCzv3L10TW7yC1G2/+StMjK8Y6Vqd2MG7r/Qjw4ghtuCOjFvlnms/iMmLqpvW/ES/WQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/resolve": { + "version": "1.22.10", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", + "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-url": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", + "integrity": "sha512-ZuF55hVUQaaczgOIwqWzkEcEidmlD/xl44x1UZnhOXcYuFN2S6+rcxpG+C1N3So0wvNI3DmJICUFfu2SxhBmvg==", + "deprecated": "https://github.com/lydell/resolve-url#deprecated", + "dev": true, + "license": "MIT" + }, + "node_modules/restore-cursor": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-1.0.1.tgz", + "integrity": "sha512-reSjH4HuiFlxlaBaFCiS6O76ZGG2ygKoSlCsipKdaZuKSPx/+bt9mULkn4l0asVzbEfQQmXRg6Wp6gv6m0wElw==", + "dev": true, + "license": "MIT", + "dependencies": { + "exit-hook": "^1.0.0", + "onetime": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ret": { + "version": "0.1.15", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", + "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12" + } + }, + "node_modules/rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, + "node_modules/safe-buffer": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.0.1.tgz", + "integrity": "sha512-cr7dZWLwOeaFBLTIuZeYdkfO7UzGIKhjYENJFAxUOMKWGaWDm2nJM2rzxNRm5Owu0DH3ApwNo6kx5idXZfb/Iw==", + "dev": true, + "license": "MIT" + }, + "node_modules/safe-regex": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", + "integrity": "sha512-aJXcif4xnaNUzvUuC5gcb46oTS7zvg4jpMTnuqtrEPlR3vFr4pxtdTwaF1Qs3Enjn9HK+ZlwQui+a7z0SywIzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ret": "~0.1.10" + } + }, + "node_modules/section-matter": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/section-matter/-/section-matter-1.0.0.tgz", + "integrity": "sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "extend-shallow": "^2.0.1", + "kind-of": "^6.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/set-value": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", + "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "extend-shallow": "^2.0.1", + "is-extendable": "^0.1.1", + "is-plain-object": "^2.0.3", + "split-string": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/shellsubstitute": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shellsubstitute/-/shellsubstitute-1.2.0.tgz", + "integrity": "sha512-CI1ViFC5a3ub86aaBmBVQ7kqg8eFypZLgBh+Bmq+ehHy9g7vu9kqCj5hS82cPzLwfdJRgiPB2hNHnd6oetiakQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/snapdragon": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", + "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", + "dev": true, + "license": "MIT", + "dependencies": { + "base": "^0.11.1", + "debug": "^2.2.0", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "map-cache": "^0.2.2", + "source-map": "^0.5.6", + "source-map-resolve": "^0.5.0", + "use": "^3.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon-node": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", + "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-property": "^1.0.0", + "isobject": "^3.0.0", + "snapdragon-util": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon-node/node_modules/define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-descriptor": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon-node/node_modules/isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon-util": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", + "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "kind-of": "^3.2.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon-util/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/snapdragon/node_modules/define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-descriptor": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon/node_modules/is-descriptor": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.7.tgz", + "integrity": "sha512-C3grZTvObeN1xud4cRWl366OMXZTj0+HGyk4hvfpx4ZHt1Pb60ANSXqCK7pdOTeUQpRzECBSTphqvD7U+l22Eg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-accessor-descriptor": "^1.0.1", + "is-data-descriptor": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/snapdragon/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, + "node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-resolve": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz", + "integrity": "sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==", + "deprecated": "See https://github.com/lydell/source-map-resolve#deprecated", + "dev": true, + "license": "MIT", + "dependencies": { + "atob": "^2.1.2", + "decode-uri-component": "^0.2.0", + "resolve-url": "^0.2.1", + "source-map-url": "^0.4.0", + "urix": "^0.1.0" + } + }, + "node_modules/source-map-url": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.1.tgz", + "integrity": "sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw==", + "deprecated": "See https://github.com/lydell/source-map-url#deprecated", + "dev": true, + "license": "MIT" + }, + "node_modules/split-string": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", + "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "extend-shallow": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/split-string/node_modules/extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/split-string/node_modules/is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-plain-object": "^2.0.4" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/state-toggle": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/state-toggle/-/state-toggle-1.0.3.tgz", + "integrity": "sha512-d/5Z4/2iiCnHw6Xzghyhb+GcmF89bxwgXG60wjIiZaxnymbyOmI8Hk4VqHXiVVp6u2ysaskFfXg3ekCj4WNftQ==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/static-extend": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", + "integrity": "sha512-72E9+uLc27Mt718pMHt9VMNiAL4LMsmDbBva8mxWUCkT07fSzEGMYUCk0XWY6lp0j6RBAG4cJ3mWuZv2OE3s0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-property": "^0.2.5", + "object-copy": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/static-extend/node_modules/define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-descriptor": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/static-extend/node_modules/is-descriptor": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.7.tgz", + "integrity": "sha512-C3grZTvObeN1xud4cRWl366OMXZTj0+HGyk4hvfpx4ZHt1Pb60ANSXqCK7pdOTeUQpRzECBSTphqvD7U+l22Eg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-accessor-descriptor": "^1.0.1", + "is-data-descriptor": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/string_decoder/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha512-0XsVpQLnVCXHJfyEs8tC0zpTVIr5PKKsQtkT29IwupnPTjtPmQ3xT/4yCREF9hYkV/3M3kzcUTSAZT6a6h81tw==", + "dev": true, + "license": "MIT", + "dependencies": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stringify-entities": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-3.1.0.tgz", + "integrity": "sha512-3FP+jGMmMV/ffZs86MoghGqAoqXAdxLrJP4GUdrDN1aIScYih5tuIO3eF4To5AJZ79KDZ8Fpdy7QJnK8SsL1Vg==", + "dev": true, + "license": "MIT", + "dependencies": { + "character-entities-html4": "^1.0.0", + "character-entities-legacy": "^1.0.0", + "xtend": "^4.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strip-bom-string": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-bom-string/-/strip-bom-string-1.0.0.tgz", + "integrity": "sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true, + "license": "MIT" + }, + "node_modules/thingies": { + "version": "1.21.0", + "resolved": "https://registry.npmjs.org/thingies/-/thingies-1.21.0.tgz", + "integrity": "sha512-hsqsJsFMsV+aD4s3CWKk85ep/3I9XzYV/IXaSouJMYIoDlgyi11cBhsqYe9/geRfB0YIikBQg6raRaM+nIMP9g==", + "dev": true, + "license": "Unlicense", + "engines": { + "node": ">=10.18" + }, + "peerDependencies": { + "tslib": "^2" + } + }, + "node_modules/tinytim": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/tinytim/-/tinytim-0.1.1.tgz", + "integrity": "sha512-NIpsp9lBIxPNzB++HnMmUd4byzJSVbbO4F+As1Gb1IG/YQT5QvmBDjpx8SpDS8fhGC+t+Qw8ldQgbcAIaU+2cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.2.0" + } + }, + "node_modules/to-object-path": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", + "integrity": "sha512-9mWHdnGRuh3onocaHzukyvCZhzvr6tiflAy/JRFXcJX0TjgfWA9pk9t8CMbzmBE4Jfw58pXbkngtBtqYxzNEyg==", + "dev": true, + "license": "MIT", + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/to-object-path/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/to-regex": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", + "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "regex-not": "^1.0.2", + "safe-regex": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha512-ZZWNfCjUokXXDGXFpZehJIkZqq91BcULFq/Pi7M5i4JnxXdhMKAK682z8bCW3o8Hj1wuuzoKcW3DfVzaP6VuNg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/to-regex-range/node_modules/is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/to-regex-range/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/to-regex/node_modules/extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/to-regex/node_modules/is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-plain-object": "^2.0.4" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/to-vfile": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/to-vfile/-/to-vfile-1.0.0.tgz", + "integrity": "sha512-BHc+hdHwULe8x6xmQhSuTsiiPHyTCCf7dtH7l6WkBoYBR2rDfYtoJufKLDDAYGMfA+1XoRq44HfyjoB9vMBr1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "vfile": "^1.0.0" + } + }, + "node_modules/to-vfile/node_modules/vfile": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-1.4.0.tgz", + "integrity": "sha512-7Fz639rwERslMqQCuf1/0H4Tqe2q484Xl6X/jsKqrP7IjFcDODFURhv0GekMnImpbj9pTOojtqL7r39LJJkjGA==", + "dev": true, + "license": "MIT" + }, + "node_modules/tracer": { + "version": "0.8.15", + "resolved": "https://registry.npmjs.org/tracer/-/tracer-0.8.15.tgz", + "integrity": "sha512-ZQzlhd6zZFIpAhACiZkxLjl65XqVwi8t8UEBVGRIHAQN6nj55ftJWiFell+WSqWCP/vEycrIbUSuiyMwul+TFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "colors": "1.2.3", + "dateformat": "3.0.3", + "mkdirp": "^0.5.1", + "tinytim": "0.1.1" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/tree-dump": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/tree-dump/-/tree-dump-1.0.2.tgz", + "integrity": "sha512-dpev9ABuLWdEubk+cIaI9cHwRNNDjkBBLXTwI4UCUFdQ5xXKqNXoK4FEciw/vxf+NQ7Cb7sGUyeUtORvHIdRXQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/trim": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/trim/-/trim-0.0.1.tgz", + "integrity": "sha512-YzQV+TZg4AxpKxaTHK3c3D+kRDCGVEE7LemdlQZoQXn0iennk10RsIoY6ikzAqJTc9Xjl9C1/waHom/J86ziAQ==", + "deprecated": "Use String.prototype.trim() instead", + "dev": true + }, + "node_modules/trim-trailing-lines": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/trim-trailing-lines/-/trim-trailing-lines-1.1.4.tgz", + "integrity": "sha512-rjUWSqnfTNrjbB9NQWfPMH/xRK1deHeGsHoVfpxJ++XeYXE0d6B1En37AHfw3jtfTU7dzMzZL2jjpe8Qb5gLIQ==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/trough": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz", + "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, + "node_modules/tunnel": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz", + "integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.6.11 <=0.7.0 || >=0.7.3" + } + }, + "node_modules/typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/ultron": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ultron/-/ultron-1.1.1.tgz", + "integrity": "sha512-UIEXBNeYmKptWH6z8ZnqTeS8fV74zG0/eRU9VGkpzz+LIJNs8W/zM/L+7ctCkRrgbNnnR0xxw4bKOr0cW0N0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/undici": { + "version": "5.29.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-5.29.0.tgz", + "integrity": "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@fastify/busboy": "^2.0.0" + }, + "engines": { + "node": ">=14.0" + } + }, + "node_modules/unherit": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/unherit/-/unherit-1.1.3.tgz", + "integrity": "sha512-Ft16BJcnapDKp0+J/rqFC3Rrk6Y/Ng4nzsC028k2jdDII/rdZ7Wd3pPT/6+vIIxRagwRc9K0IUX0Ra4fKvw+WQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.0", + "xtend": "^4.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/unified": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", + "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "bail": "^2.0.0", + "devlop": "^1.0.0", + "extend": "^3.0.0", + "is-plain-obj": "^4.0.0", + "trough": "^2.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/union-value": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", + "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", + "dev": true, + "license": "MIT", + "dependencies": { + "arr-union": "^3.1.0", + "get-value": "^2.0.6", + "is-extendable": "^0.1.1", + "set-value": "^2.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/unist": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/unist/-/unist-0.0.1.tgz", + "integrity": "sha512-bnzuF8b6d47WubA4a5yLqFbuZz/v/NS6eRwUIdOaDmsqzwTlyv8yS1g3M7ISdtBQrigPD3qKK87Cu7zhEfCF3A==", + "deprecated": "Use @types/unist instead", + "dev": true + }, + "node_modules/unist-util-is": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.0.tgz", + "integrity": "sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-remove-position": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/unist-util-remove-position/-/unist-util-remove-position-2.0.1.tgz", + "integrity": "sha512-fDZsLYIe2uT+oGFnuZmy73K6ZxOPG/Qcm+w7jbEjaFcJgbQ6cqjs/eSPzXhsmGpAsWPkqZM9pYjww5QTn3LHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "unist-util-visit": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-remove-position/node_modules/@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", + "dev": true, + "license": "MIT" + }, + "node_modules/unist-util-remove-position/node_modules/unist-util-is": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-4.1.0.tgz", + "integrity": "sha512-ZOQSsnce92GrxSqlnEEseX0gi7GH9zTJZ0p9dtu87WRb/37mMPO2Ilx1s/t9vBHrFhbgweUwb+t7cIn5dxPhZg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-remove-position/node_modules/unist-util-visit": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-2.0.3.tgz", + "integrity": "sha512-iJ4/RczbJMkD0712mGktuGpm/U4By4FfDonL7N/9tATGIF4imikjOuagyMY53tnZq3NP6BcmlrHhEKAfGWjh7Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^2.0.0", + "unist-util-is": "^4.0.0", + "unist-util-visit-parents": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-remove-position/node_modules/unist-util-visit-parents": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-3.1.1.tgz", + "integrity": "sha512-1KROIZWo6bcMrZEwiH2UrXDyalAa0uqzWCxCJj6lPOvTve2WkfgCytoDTPaMnodXh1WrXOq0haVYHj99ynJlsg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^2.0.0", + "unist-util-is": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.0.0.tgz", + "integrity": "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit-parents": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.1.tgz", + "integrity": "sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unset-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", + "integrity": "sha512-PcA2tsuGSF9cnySLHTLSh2qrQiJ70mn+r+Glzxv2TWZblxsxCC52BDlZoPCsz7STd9pN7EZetkWZBAvk4cgZdQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-value": "^0.3.1", + "isobject": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/unset-value/node_modules/has-value": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", + "integrity": "sha512-gpG936j8/MzaeID5Yif+577c17TxaDmhuyVgSwtnL/q8UUTySg8Mecb+8Cf1otgLoD7DDH75axp86ER7LFsf3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-value": "^2.0.3", + "has-values": "^0.1.4", + "isobject": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/unset-value/node_modules/has-value/node_modules/isobject": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha512-+OUdGJlgjOBZDfxnDjYYG6zp487z0JGNQq3cYQYg5f5hKR+syHMsaztzGeml/4kGG55CSpKSpWTY+jYGgsHLgA==", + "dev": true, + "license": "MIT", + "dependencies": { + "isarray": "1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/unset-value/node_modules/has-values": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", + "integrity": "sha512-J8S0cEdWuQbqD9//tlZxiMuMNmxB8PlEwvYwuxsTmR1G5RXUePEX/SJn7aD0GMLieuZYSwNH0cQuJGwnYunXRQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/unset-value/node_modules/isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/untildify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/untildify/-/untildify-2.1.0.tgz", + "integrity": "sha512-sJjbDp2GodvkB0FZZcn7k6afVisqX5BZD7Yq3xp4nN2O15BBK0cLm3Vwn2vQaF7UDS0UUsrQMkkplmDI5fskig==", + "dev": true, + "license": "MIT", + "dependencies": { + "os-homedir": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/urix": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", + "integrity": "sha512-Am1ousAhSLBeB9cG/7k7r2R0zj50uDRlZHPGbazid5s9rlF1F/QKYObEKSIunSjIOkJZqwRRLpvewjEkM7pSqg==", + "deprecated": "Please see https://github.com/lydell/urix#deprecated", + "dev": true, + "license": "MIT" + }, + "node_modules/use": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", + "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/user-home": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/user-home/-/user-home-2.0.0.tgz", + "integrity": "sha512-KMWqdlOcjCYdtIJpicDSFBQ8nFwS2i9sslAd6f4+CBGcU4gist2REnr2fxj2YocvJFxSF3ZOHLYLVZnUxv4BZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "os-homedir": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/vfile": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", + "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-find-down": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/vfile-find-down/-/vfile-find-down-1.0.0.tgz", + "integrity": "sha512-AOXiJrVKizToYfRosXd1p9Fq8b0u0qchvSwVF1/ue3JE7o7KuQ/UH24bNAPLDUG/RIM1DZ6zWtDsiLShcma4WA==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-vfile": "^1.0.0" + } + }, + "node_modules/vfile-find-up": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/vfile-find-up/-/vfile-find-up-1.0.0.tgz", + "integrity": "sha512-t97P/jQswvX0n//+RB74Wj43VOg3tel2InzaJYryaBewd4uN4pNXuoH/F00PkI3U1fBp+w/SIyrTjzIzPwDODg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-vfile": "^1.0.0" + } + }, + "node_modules/vfile-location": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-3.2.0.tgz", + "integrity": "sha512-aLEIZKv/oxuCDZ8lkJGhuhztf/BW4M+iHdCwglA/eWc+vtuRFJj8EtgceYFX4LRjOhCAAiNHsKGssC6onJ+jbA==", + "dev": true, + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-message": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.2.tgz", + "integrity": "sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-reporter": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/vfile-reporter/-/vfile-reporter-1.5.0.tgz", + "integrity": "sha512-VFF1LK0O8/nLmrPcc+5VMEnyP21BTzdVoq1rbxTaVt6cmSVk5MQs1POhkfY/cctndmZheNgirTcAMoiKj3aJYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^1.1.0", + "log-symbols": "^1.0.2", + "plur": "^2.0.0", + "repeat-string": "^1.5.0", + "string-width": "^1.0.0", + "text-table": "^0.2.0", + "vfile-sort": "^1.0.0" + } + }, + "node_modules/vfile-sort": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/vfile-sort/-/vfile-sort-1.0.0.tgz", + "integrity": "sha512-6qIalNEKUt2YyVFzyJptdEo9sm/pMrSKvOJ35lH4us9YeW08zRs3E9VbdJ0O0n2Thxc1TWINP5QVhucy/YiGPA==", + "dev": true, + "license": "MIT" + }, + "node_modules/ware": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/ware/-/ware-1.3.0.tgz", + "integrity": "sha512-Y2HUDMktriUm+SR2gZWxlrszcgtXExlhQYZ8QJNYbl22jum00KIUcHJ/h/sdAXhWTJcbSkiMYN9Z2tWbWYSrrw==", + "dev": true, + "license": "MIT", + "dependencies": { + "wrap-fn": "^0.1.0" + } + }, + "node_modules/wrap-fn": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/wrap-fn/-/wrap-fn-0.1.5.tgz", + "integrity": "sha512-xDLdGx0M8JQw9QDAC9s5NUxtg9MI09F6Vbxa2LYoSoCvzJnx2n81YMIfykmXEGsUvuLaxnblJTzhSOjUOX37ag==", + "dev": true, + "license": "MIT", + "dependencies": { + "co": "3.1.0" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/ws": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-2.3.1.tgz", + "integrity": "sha512-61a+9LgtYZxTq1hAonhX8Xwpo2riK4IOR/BIVxioFbCfc3QFKmpE4x9dLExfLHKtUfVZigYa36tThVhO57erEw==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.0.1", + "ultron": "~1.1.0" + } + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4" + } + } + } +} diff --git a/.github/actions/copy-cloud-docs-for-tfe/package.json b/.github/actions/copy-cloud-docs-for-tfe/package.json new file mode 100644 index 0000000000..d690608765 --- /dev/null +++ b/.github/actions/copy-cloud-docs-for-tfe/package.json @@ -0,0 +1,20 @@ +{ + "name": "copy-cloud-docs-for-tfe", + "scripts": { + "build": "ncc build index.ts -o out" + }, + "devDependencies": { + "@actions/core": "^1.11.1", + "@vercel/ncc": "^0.38.3", + "gray-matter": "^4.0.3", + "klaw-sync": "^7.0.0", + "mdast": "^2.3.2", + "ncc": "^0.3.6", + "remark": "^12.0.0", + "remark-mdx": "^1.6.22", + "unified": "^11.0.5", + "unist": "^0.0.1", + "unist-util-is": "^6.0.0", + "unist-util-visit": "^5.0.0" + } +} diff --git a/.github/actions/copy-cloud-docs-for-tfe/remark-get-images-plugin.ts b/.github/actions/copy-cloud-docs-for-tfe/remark-get-images-plugin.ts new file mode 100644 index 0000000000..c06096370a --- /dev/null +++ b/.github/actions/copy-cloud-docs-for-tfe/remark-get-images-plugin.ts @@ -0,0 +1,32 @@ +/* eslint-disable */ +// @ts-nocheck +// TODO: fix types + +// stdlib +import * as fs from 'fs' +import * as path from 'path' +import * as assert from 'assert' + +// for creating custom remark plugin +import { is } from 'unist-util-is' // Use 4.1.0; 5+ is ESM +import { visit } from 'unist-util-visit' +import type { Node } from 'unist' +import type { Image } from 'mdast' +import type { Plugin } from 'unified' + +export const remarkGetImages: Plugin<[string, Set]> = ( + HCPsourceDir, + imageSrcSet, +) => { + const test = (node: Node): node is Image => { + return is(node, 'image') + } + + return function (tree) { + visit(tree, test, (node) => { + const src = path.join(HCPsourceDir, node.url) + assert.ok(fs.existsSync(src), '[getImagesPlugin] image not found: ' + src) + imageSrcSet.add(src) + }) + } +} diff --git a/.github/actions/copy-cloud-docs-for-tfe/remark-transfrom-cloud-docs-links.ts b/.github/actions/copy-cloud-docs-for-tfe/remark-transfrom-cloud-docs-links.ts new file mode 100644 index 0000000000..88c3d497af --- /dev/null +++ b/.github/actions/copy-cloud-docs-for-tfe/remark-transfrom-cloud-docs-links.ts @@ -0,0 +1,32 @@ +/* eslint-disable */ +// @ts-nocheck +// TODO: fix types + +// for creating custom remark plugin +import { is } from 'unist-util-is' // Use 4.1.0; 5+ is ESM +import { visit } from 'unist-util-visit' +import type { Node } from 'unist' +import type { Link, Definition } from 'mdast' +import type { Plugin } from 'unified' + +import { IGNORE_PATTERNS } from './main' + +export const remarkTransformCloudDocsLinks: Plugin = () => { + const test = (node: Node): node is Link | Definition => { + return is(node, 'link') || is(node, 'definition') + } + + return function (tree) { + visit(tree, test, (node) => { + // early exit if any urls match any ignored patterns + if (IGNORE_PATTERNS.some((e) => e.test(node.url))) { + return + } + + // Match urls beginning with `/cloud-docs` or `/terraform/cloud-docs` + if (/^(\/terraform)?\/cloud-docs/i.test(node.url)) { + node.url = node.url.replace('cloud-docs', 'enterprise') + } + }) + } +} diff --git a/.github/actions/copy-cloud-docs-for-tfe/tsconfig.json b/.github/actions/copy-cloud-docs-for-tfe/tsconfig.json new file mode 100644 index 0000000000..df7d1e2c7d --- /dev/null +++ b/.github/actions/copy-cloud-docs-for-tfe/tsconfig.json @@ -0,0 +1,8 @@ +{ + "compilerOptions": { + "target": "ES2021", + "moduleResolution": "node", + "allowSyntheticDefaultImports": true + }, + "files": ["index.ts"] +} diff --git a/.github/workflows/broken-link-check-full.yml b/.github/workflows/broken-link-check-full.yml index 1e4a13aaad..1fd0e6aad3 100644 --- a/.github/workflows/broken-link-check-full.yml +++ b/.github/workflows/broken-link-check-full.yml @@ -16,7 +16,17 @@ jobs: id: lychee uses: lycheeverse/lychee-action@f613c4a64e50d792e0b31ec34bbcbba12263c6a6 # v2.3.0 with: - args: ./content/ -b https://developer.hashicorp.com/ --exclude-all-private --exclude '\.(svg|gif|jpg|png)' --accept 200,408,429 --timeout=60 --max-concurrency 24 --no-progress --verbose + args: >- + ./content/ + -b https://developer.hashicorp.com/ + --exclude-all-private + --exclude '\.(svg|gif|jpg|png)' + --exclude 'gnu\.org' + --accept 200,408,429 + --timeout=60 + --max-concurrency 24 + --no-progress + --verbose fail: false env: GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} diff --git a/.github/workflows/build-pr-preview.yml b/.github/workflows/build-pr-preview.yml index 0bac2f3b43..d12f66131d 100644 --- a/.github/workflows/build-pr-preview.yml +++ b/.github/workflows/build-pr-preview.yml @@ -300,6 +300,7 @@ jobs: -b ${{ needs.deploy-dev-portal-preview.outputs.preview_url }} --exclude-all-private --exclude '\.(svg|gif|jpg|png)' + --exclude 'gnu\.org' --accept 200,408,429 --timeout=60 --max-concurrency 24 diff --git a/.github/workflows/copy-cloud-docs-for-tfe.yml b/.github/workflows/copy-cloud-docs-for-tfe.yml new file mode 100644 index 0000000000..ff96f7eaba --- /dev/null +++ b/.github/workflows/copy-cloud-docs-for-tfe.yml @@ -0,0 +1,155 @@ +name: Copy Cloud Docs For TFE +on: + workflow_dispatch: + inputs: + branch: + description: 'Ex: docs-tfe-releases/v123456-1; ptfe-releases/v123456-1' + required: true + + # #only required for testing + # push: + # branches: + # - 'rn/add-copy-docs-workflow' + +env: + release_branch: ${{ inputs.branch }} + +jobs: + copy-docs: + name: Copy Docs + runs-on: ubuntu-latest + steps: + # TODO: `exit 1` here short circuits the whole workflow, which we want, but results in + # an unnecessary `failure` status, which we don't want. + - name: Validate branch name + # ✅ docs-tfe-releases/v000011-1 + # ✅ ptfe-releases/v000011-1 + # ❌ ptfe-releases/v000011-2 + # ❌ ptfe-releases/v000011- + # ❌ ptfe-releases/v000011 + run: | + if [[ ${{ env.release_branch }} =~ ^(docs-tfe-releases|ptfe-releases)/v[0-9]{6}-1$ ]] + then + echo "::notice::Branch is valid — ${{ env.release_branch }}" + else + echo "::warning::Branch is invalid — ${{ env.release_branch }}" + exit 1 + fi + + - name: Set workspace & ref_name on env + run: | + echo "workspace=$GITHUB_WORKSPACE" >> $GITHUB_ENV + echo "ref_name=$GITHUB_REF_NAME" >> $GITHUB_ENV + + - name: Get series (YYYYMM) number from branch name + run: | + series=$(echo ${{ env.release_branch }} \ + | awk -F '/' '{print $2}' \ + | awk -F '-' '{print $1} {print $2}' \ + | head -n 1) + echo "SERIES=$series" >> $GITHUB_ENV + - name: Get release (X) number from branch name + run: | + release=$(echo ${{ env.release_branch }} \ + | awk -F '/' '{print $2}' \ + | awk -F '-' '{print $1} {print $2}' \ + | tail -n 1) + echo "RELEASE=$release" >> $GITHUB_ENV + + - name: Series/Release Summary + run: | + echo "# Summary" >> $GITHUB_STEP_SUMMARY + echo "**Workflow ref**: ${{env.ref_name}}" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "Triggered by branch creation (or manual workflow):" >> $GITHUB_STEP_SUMMARY + echo "- ${{github.server_url}}/hashicorp/${{env.target_repo}}/tree/${{env.release_branch}}" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "**Series**: ${{env.SERIES}}" >> $GITHUB_STEP_SUMMARY + echo "**Release**: ${{env.RELEASE}}" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "---" >> $GITHUB_STEP_SUMMARY + + - # This repository holds workflow logic + name: Checkout repository + uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + with: + ref: ${{env.release_branch}} + # TODO: remove this after testing + path: "${{github.workspace}}/testing" + + - # This repository holds workflow logic + name: Checkout repository + uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + with: + ref: "rn/add-copy-docs-workflow" + path: "${{github.workspace}}/workflow" + + - name: List files in ./testing + run: | + echo "Listing files in ./testing:" + mv ${{github.workspace}}/workflow/.* ${{github.workspace}}/workflow/* ${{github.workspace}}/ + echo "Listing files in GHA" + ls -al "${{github.workspace}}/testing/content/terraform-docs-common/docs/cloud-docs" + + - # Run our composite action + name: Copy files + uses: ./.github/actions/copy-cloud-docs-for-tfe + with: + new_TFE_version: ${{env.SERIES}}-${{env.RELEASE}} + + - name: Open PR in Target Repo + env: + branch_name: docs/${{env.SERIES}}-${{env.RELEASE}} + pr_body: | + # Automated Docs PR + + **TFE Series**: ${{ env.SERIES }} + **TFE Release**: ${{ env.RELEASE }} + + This copies over `cloud-docs` from: + - ${{github.server_url}}/${{github.repository}}/tree/${{env.release_branch}} + + This PR was created via: + - ${{github.server_url}}/${{github.repository}}/actions/runs/${{github.run_id}} + + Triggered by creation of branch: + - ${{github.server_url}}/${{github.repository}}/tree/${{env.release_branch}} + + ### Reviewers + + > **Note**: The `digital-content-events` GH App currently does not have permissions to request PR reviews from teams. + + - [ ] @hashicorp/ptfe-review + - [ ] @hashicorp/web-platform + + # secrets.WORKFLOW_TESTING_TOKEN requires permissions read:org, repo, workflow + + + + run: | + echo ${{ secrets.WORKFLOW_TESTING_TOKEN }} | gh auth login --with-token + git config --global user.email "team-rel-eng@hashicorp.com" + git config --global user.name "tfe-release-bot" + + # Check if branch already exists on origin + if git ls-remote --exit-code --heads origin "${{env.branch_name}}"; then + echo "❌ branch name ${{env.branch_name}} already exists, please delete it from remote/origin and try again. (i.e. git push origin --delete ${{env.branch_name}})" >> $GITHUB_STEP_SUMMARY + exit 1 + fi + + git checkout -b ${{env.branch_name}} + + git add ./content/ptfe-releases/${{env.SERIES}}-${{env.RELEASE}} + + git commit -m "Automated Docs PR" --no-verify + git push origin HEAD + + gh pr create \ + --body="${{env.pr_body}}" \ + --title="Automated Docs PR" \ + --draft \ + --head ${{env.branch_name}} \ + --base develop + url=$(gh pr view --json url --jq '.url') + + echo "**Automated PR URL**: ${url}" >> $GITHUB_STEP_SUMMARY diff --git a/.gitignore b/.gitignore index e079bed6ed..56f405d097 100644 --- a/.gitignore +++ b/.gitignore @@ -54,3 +54,6 @@ scripts/test-output/** # tfe-releases release notes workflow tfe-releases-repos.json + +# copy-cloud-docs-for-tfe action +.github/actions/copy-cloud-docs-for-tfe/node_modules diff --git a/CODEOWNERS b/CODEOWNERS index 60100df076..a81c73960a 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -9,5 +9,5 @@ /content/terraform-plugin-mux @hashicorp/terraform-devex @hashicorp/terraform-education /content/terraform-plugin-sdk @hashicorp/terraform-devex @hashicorp/terraform-education /content/terraform-plugin-testing @hashicorp/terraform-devex @hashicorp/terraform-education -/content/terraform-docs-agents @hashicorp/tfc-agent-core +/content/terraform-docs-agents @hashicorp/team-hcpt-agent-engineering /content/terraform-cdk @hashicorp/cdktf diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/_template.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/_template.mdx new file mode 100644 index 0000000000..94c74253f0 --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/_template.mdx @@ -0,0 +1,222 @@ +--- +page_title: /example-endpoint API reference for Terraform Enterprise +description: >- + Use the Terraform Enterprise API's `/endpoint-name` to {A meaningful + description of this endpoint}. Aim for 130-160 characters total. +source: terraform-docs-common +--- + +Follow this template to format each API method. There are usually multiple sections like this on a given API endpoint page. + + + +[200]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/200 + +[201]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/201 + +[202]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/202 + +[204]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/204 + +[400]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/400 + +[401]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/401 + +[403]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/403 + +[404]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404 + +[409]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/409 + +[412]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/412 + +[422]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/422 + +[429]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/429 + +[500]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500 + +[504]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/504 + +[JSON API document]: /terraform/enterprise/api-docs#json-api-documents + +[JSON API error object]: http://jsonapi.org/format/#error-objects + +# Example name API reference + +A explanatory sentence about what this thing in HCP Terraform does. + +## Create a Something + +Add at least one sentence of description about what this endpoint does. + + + +`POST /organizations/:organization_name/somethings` + + + +| Parameter | Description | +| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `:organization_name` | The name of the organization to create the something in. The organization must already exist in the system, and the user must have permissions to create new somethings. | + + + +-> **Note:** This endpoint cannot be accessed with [organization tokens](/terraform/enterprise/users-teams-organizations/api-tokens#organization-api-tokens). You must access it with a [user token](/terraform/enterprise/users-teams-organizations/users#api-tokens) or [team token](/terraform/enterprise/users-teams-organizations/api-tokens#team-api-tokens). + + + +| Status | Response | Reason | +| ------- | -------------------------------------------- | -------------------------------------------------------------- | +| [200][] | [JSON API document][] (`type: "somethings"`) | Successfully created a team | +| [400][] | [JSON API error object][] | Invalid `include` parameter | +| [404][] | [JSON API error object][] | Organization not found, or user unauthorized to perform action | +| [422][] | [JSON API error object][] | Malformed request body (missing attributes, wrong types, etc.) | +| [500][] | [JSON API error object][] | Failure during team creation | + + + +### Query Parameters + +[These are standard URL query parameters](/terraform/enterprise/api-docs#query-parameters); remember to percent-encode `[` as `%5B` and `]` as `%5D` if your tooling doesn't automatically encode URLs. + + + +| Parameter | Description | +| ----------------------- | ------------------------------------------------------------- | +| `filter[workspace][id]` | **Required.** The workspace ID where this action will happen. | + + + +### Request Body + +This POST endpoint requires a JSON object with the following properties as a request payload. + +Properties without a default value are required. + + + +| Key path | Type | Default | Description | +| --------------------------- | ------ | ------- | ------------------------------------------------------------------------------------------------------ | +| `data.type` | string | | Must be `"somethings"`. | +| `data[].type` | string | | ... | +| `data.attributes.category` | string | | Whether this is a blue or red something. Valid values are `"blue"` or `"red"`. | +| `data.attributes.sensitive` | bool | `false` | Whether the value is sensitive. If true then the something is written once and not visible thereafter. | +| `filter.workspace.name` | string | | The name of the workspace that owns the something. | +| `filter.organization.name` | string | | The name of the organization that owns the workspace. | + + + +### Available Related Resources + + + +This GET endpoint can optionally return related resources, if requested with [the `include` query parameter](/terraform/enterprise/api-docs#inclusion-of-related-resources). The following resource types are available: + +| Resource Name | Description | +| ------------------ | --------------------------------------------- | +| `organization` | The full organization record. | +| `current_run` | Additional information about the current run. | +| `current_run.plan` | The plan used in the current run. | + +### Sample Payload + +```json +{ + "data": { + "type":"somethings", + "attributes": { + "category":"red", + "sensitive":true + } + }, + "filter": { + "organization": { + "name":"my-organization" + }, + "workspace": { + "name":"my-workspace" + } + } +} +``` + +### Sample Request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request POST \ + --data @payload.json \ + https://app.terraform.io/api/v2/somethings +``` + + + +### Sample Response + +```json +{ + "data": { + "id":"som-EavQ1LztoRTQHSNT", + "type":"somethings", + "attributes": { + "sensitive":true, + "category":"red", + }, + "relationships": { + "configurable": { + "data": { + "id":"ws-4j8p6jX1w33MiDC7", + "type":"workspaces" + }, + "links": { + "related":"/api/v2/organizations/my-organization/workspaces/my-workspace" + } + } + }, + "links": { + "self":"/api/v2/somethings/som-EavQ1LztoRTQHSNT" + } + } +} +``` + + diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/account.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/account.mdx new file mode 100644 index 0000000000..570770ef0d --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/account.mdx @@ -0,0 +1,278 @@ +--- +page_title: /account API reference for Terraform Enterprise +description: >- + Use the Terraform Enterprise API's `/account` endpoint to manage the current + user. Learn how to read and update your account's details and change your + password. +source: terraform-docs-common +--- + +[200]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/200 + +[201]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/201 + +[202]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/202 + +[204]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/204 + +[400]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/400 + +[401]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/401 + +[403]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/403 + +[404]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404 + +[409]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/409 + +[412]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/412 + +[422]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/422 + +[429]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/429 + +[500]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500 + +[504]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/504 + +[JSON API document]: /terraform/enterprise/api-docs#json-api-documents + +[JSON API error object]: https://jsonapi.org/format/#error-objects + +# Account API reference + +Account represents the current user interacting with Terraform. It returns the same type of object as the [Users](/terraform/enterprise/api-docs/users) API, but also includes an email address, which is hidden when viewing info about other users. + +For internal reasons, HCP Terraform associates team and organization tokens with a synthetic user account called _service user_. HCP Terraform returns the associated service user for account requests authenticated by a team or organization token. Use the `authenticated-resource` relationship to access the underlying team or organization associated with a token. For user tokens, you can use the user, itself. + +## Get your account details + +`GET /account/details` + +| Status | Response | Reason | +| ------- | --------------------------------------- | -------------------------- | +| [200][] | [JSON API document][] (`type: "users"`) | The request was successful | + +### Sample Request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request GET \ + https://app.terraform.io/api/v2/account/details +``` + +### Sample Response + +```json +{ + "data": { + "id": "user-V3R563qtJNcExAkN", + "type": "users", + "attributes": { + "username": "admin", + "is-service-account": false, + "auth-method": "tfc", + "avatar-url": "https://www.gravatar.com/avatar/9babb00091b97b9ce9538c45807fd35f?s=100&d=mm", + "v2-only": false, + "is-site-admin": true, + "is-sso-login": false, + "email": "admin@hashicorp.com", + "unconfirmed-email": null, + "permissions": { + "can-create-organizations": true, + "can-change-email": true, + "can-change-username": true + } + }, + "relationships": { + "authentication-tokens": { + "links": { + "related": "/api/v2/users/user-V3R563qtJNcExAkN/authentication-tokens" + } + }, + "authenticated-resource": { + "data": { + "id": "user-V3R563qtJNcExAkN", + "type": "users" + }, + "links": { + "related": "/api/v2/users/user-V3R563qtJNcExAkN" + } + } + }, + "links": { + "self": "/api/v2/users/user-V3R563qtJNcExAkN" + } + } +} +``` + +## Update your account info + +Your username and email address can be updated with this endpoint. + +`PATCH /account/update` + +| Status | Response | Reason | +| ------- | --------------------------------------- | -------------------------------------------------------------- | +| [200][] | [JSON API document][] (`type: "users"`) | Your info was successfully updated | +| [401][] | [JSON API error object][] | Unauthorized | +| [422][] | [JSON API error object][] | Malformed request body (missing attributes, wrong types, etc.) | + +### Request Body + +This PATCH endpoint requires a JSON object with the following properties as a request payload. + +| Key path | Type | Default | Description | +| -------------------------- | ------ | ------- | --------------------------------------------------------------- | +| `data.type` | string | | Must be `"users"` | +| `data.attributes.username` | string | | New username | +| `data.attributes.email` | string | | New email address (must be confirmed afterwards to take effect) | + +### Sample Payload + +```json +{ + "data": { + "type": "users", + "attributes": { + "email": "admin@example.com", + "username": "admin" + } + } +} +``` + +### Sample Request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request PATCH \ + --data @payload.json \ + https://app.terraform.io/api/v2/account/update +``` + +### Sample Response + +```json +{ + "data": { + "id": "user-V3R563qtJNcExAkN", + "type": "users", + "attributes": { + "username": "admin", + "is-service-account": false, + "auth-method": "hcp_username_password", + "avatar-url": "https://www.gravatar.com/avatar/9babb00091b97b9ce9538c45807fd35f?s=100&d=mm", + "v2-only": false, + "is-site-admin": true, + "is-sso-login": false, + "email": "admin@hashicorp.com", + "unconfirmed-email": null, + "permissions": { + "can-create-organizations": true, + "can-change-email": true, + "can-change-username": true + } + }, + "relationships": { + "authentication-tokens": { + "links": { + "related": "/api/v2/users/user-V3R563qtJNcExAkN/authentication-tokens" + } + } + }, + "links": { + "self": "/api/v2/users/user-V3R563qtJNcExAkN" + } + } +} +``` + +## Change your password + +`PATCH /account/password` + +| Status | Response | Reason | +| ------- | --------------------------------------- | -------------------------------------------------------------- | +| [200][] | [JSON API document][] (`type: "users"`) | Your password was successfully changed | +| [401][] | [JSON API error object][] | Unauthorized | +| [422][] | [JSON API error object][] | Malformed request body (missing attributes, wrong types, etc.) | + +### Request Body + +This PATCH endpoint requires a JSON object with the following properties as a request payload. + +| Key path | Type | Default | Description | +| --------------------------------------- | ------ | ------- | ------------------------------------------------------- | +| `data.type` | string | | Must be `"users"` | +| `data.attributes.current_password` | string | | Current password | +| `data.attributes.password` | string | | New password (must be at least 10 characters in length) | +| `data.attributes.password_confirmation` | string | | New password (confirmation) | + +### Sample Payload + +```json +{ + "data": { + "type": "users", + "attributes": { + "current_password": "current password e.g. 2:C)e'G4{D\n06:[d1~y", + "password": "new password e.g. 34rk492+jgLL0@xhfyisj", + "password_confirmation": "new password e.g. 34rk492+jLL0@xhfyisj" + } + } +} +``` + +### Sample Request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request PATCH \ + --data @payload.json \ + https://app.terraform.io/api/v2/account/password +``` + +### Sample Response + +```json +{ + "data": { + "id": "user-V3R563qtJNcExAkN", + "type": "users", + "attributes": { + "username": "admin", + "is-service-account": false, + "auth-method": "hcp_github", + "avatar-url": "https://www.gravatar.com/avatar/9babb00091b97b9ce9538c45807fd35f?s=100&d=mm", + "v2-only": false, + "is-site-admin": true, + "is-sso-login": false, + "email": "admin@hashicorp.com", + "unconfirmed-email": null, + "permissions": { + "can-create-organizations": true, + "can-change-email": true, + "can-change-username": true + } + }, + "relationships": { + "authentication-tokens": { + "links": { + "related": "/api/v2/users/user-V3R563qtJNcExAkN/authentication-tokens" + } + } + }, + "links": { + "self": "/api/v2/users/user-V3R563qtJNcExAkN" + } + } +} +``` diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/agent-tokens.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/agent-tokens.mdx new file mode 100644 index 0000000000..e1d27c1395 --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/agent-tokens.mdx @@ -0,0 +1,272 @@ +--- +page_title: /authentication-tokens API reference for Terraform Enterprise +description: >- + Use the Terraform Enterprise API's `/authentication-tokens` endpoint to manage + agent tokens. Learn how to read, create, and destroy agent tokens. +source: terraform-docs-common +--- + +[200]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/200 + +[201]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/201 + +[202]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/202 + +[204]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/204 + +[400]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/400 + +[401]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/401 + +[403]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/403 + +[404]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404 + +[409]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/409 + +[412]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/412 + +[422]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/422 + +[429]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/429 + +[500]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500 + +[504]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/504 + +[JSON API document]: /terraform/enterprise/api-docs#json-api-documents + +[JSON API error object]: https://jsonapi.org/format/#error-objects + +# Agent token API reference + + + +@include 'tfc-package-callouts/agents.mdx' + + + +## List Agent Tokens + +`GET /agent-pools/:agent_pool_id/authentication-tokens` + +| Parameter | Description | +| ---------------- | ------------------------- | +| `:agent_pool_id` | The ID of the Agent Pool. | + +The objects returned by this endpoint only contain metadata, and do not include the secret text of any authentication tokens. A token is only shown upon creation, and cannot be recovered later. + +| Status | Response | Reason | +| ------- | ------------------------------------------------------- | ------------------------------------------------------------ | +| [200][] | [JSON API document][] (`type: "authentication-tokens"`) | Success | +| [404][] | [JSON API error object][] | Agent Pool not found, or user unauthorized to perform action | + +### Query Parameters + +This endpoint supports pagination [with standard URL query parameters](/terraform/enterprise/api-docs#query-parameters). Remember to percent-encode `[` as `%5B` and `]` as `%5D` if your tooling doesn't automatically encode URLs. + +| Parameter | Description | +| -------------- | ---------------------------------------------------------------------- | +| `page[number]` | **Optional.** If omitted, the endpoint will return the first page. | +| `page[size]` | **Optional.** If omitted, the endpoint will return 20 tokens per page. | + +### Sample Request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request GET \ + https://app.terraform.io/api/v2/agent-pools/apool-MCf6kkxu5FyHbqhd/authentication-tokens +``` + +### Sample Response + +```json +{ + "data": [ + { + "id": "at-bonpPzYqv2bGD7vr", + "type": "authentication-tokens", + "attributes": { + "created-at": "2020-08-07T19:38:20.868Z", + "last-used-at": "2020-08-07T19:40:55.139Z", + "description": "asdfsdf", + "token": null + }, + "relationships": { + "created-by": { + "data": { + "id": "user-Nxv6svuhVrTW7eb1", + "type": "users" + } + } + } + } + ], + "links": { + "self": "https://app.terraform.io/api/v2/agent-pools/apool-MCf6kkxu5FyHbqhd/authentication-tokens?page%5Bnumber%5D=1&page%5Bsize%5D=20", + "first": "https://app.terraform.io/api/v2/agent-pools/apool-MCf6kkxu5FyHbqhd/authentication-tokens?page%5Bnumber%5D=1&page%5Bsize%5D=20", + "prev": null, + "next": null, + "last": "https://app.terraform.io/api/v2/agent-pools/apool-MCf6kkxu5FyHbqhd/authentication-tokens?page%5Bnumber%5D=1&page%5Bsize%5D=20" + }, + "meta": { + "pagination": { + "current-page": 1, + "prev-page": null, + "next-page": null, + "total-pages": 1, + "total-count": 1 + } + } +} +``` + +## Show an Agent Token + +`GET /authentication-tokens/:id` + +| Parameter | Description | +| --------- | --------------------------------- | +| `:id` | The ID of the Agent Token to show | + +| Status | Response | Reason | +| ------- | ------------------------------------------------------- | ------------------------------------------------------------- | +| [200][] | [JSON API document][] (`type: "authentication-tokens"`) | Success | +| [404][] | [JSON API error object][] | Agent Token not found, or user unauthorized to perform action | + +### Sample Request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request GET \ + https://app.terraform.io/api/v2/authentication-tokens/at-bonpPzYqv2bGD7vr +``` + +### Sample Response + +```json +{ + "data": { + "id": "at-bonpPzYqv2bGD7vr", + "type": "authentication-tokens", + "attributes": { + "created-at": "2020-08-07T19:38:20.868Z", + "last-used-at": "2020-08-07T19:40:55.139Z", + "description": "test token", + "token": null + }, + "relationships": { + "created-by": { + "data": { + "id": "user-Nxv6svuhVrTW7eb1", + "type": "users" + } + } + } + } +} +``` + +## Create an Agent Token + +`POST /agent-pools/:agent_pool_id/authentication-tokens` + +| Parameter | Description | +| ---------------- | ------------------------ | +| `:agent_pool_id` | The ID of the Agent Pool | + +This endpoint returns the secret text of the created authentication token. A token is only shown upon creation, and cannot be recovered later. + +| Status | Response | Reason | +| ------- | ------------------------------------------------------- | -------------------------------------------------------------- | +| [201][] | [JSON API document][] (`type: "authentication-tokens"`) | The request was successful | +| [404][] | [JSON API error object][] | Agent Pool not found or user unauthorized to perform action | +| [422][] | [JSON API error object][] | Malformed request body (missing attributes, wrong types, etc.) | +| [500][] | [JSON API error object][] | Failure during Agent Token creation | + +### Request Body + +This POST endpoint requires a JSON object with the following properties as a request payload. + +Properties without a default value are required. + +| Key path | Type | Default | Description | +| ----------------------------- | ------ | ------- | ------------------------------------ | +| `data.type` | string | | Must be `"authentication-tokens"`. | +| `data.attributes.description` | string | | The description for the Agent Token. | + +### Sample Payload + +```json +{ + "data": { + "type": "authentication-tokens", + "attributes": { + "description":"api" + } + } +} +``` + +### Sample Request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request POST \ + --data @payload.json \ + https://app.terraform.io/api/v2/agent-pools/apool-xkuMi7x4LsEnBUdY/authentication-tokens +``` + +### Sample Response + +```json +{ + "data": { + "id": "at-2rG2oYU9JEvfaqji", + "type": "authentication-tokens", + "attributes": { + "created-at": "2020-08-10T22:29:21.907Z", + "last-used-at": null, + "description": "api", + "token": "eHub7TsW7fz7LQ.atlasv1.cHGFcvf2VxVfUH4PZ7UNdaGB6SjyKWs5phdZ371zkI2KniZs2qKgrAcazhlsiy02awk" + }, + "relationships": { + "created-by": { + "data": { + "id": "user-Nxv6svuhVrTW7eb1", + "type": "users" + } + } + } + } +} +``` + +## Destroy an Agent Token + +`DELETE /api/v2/authentication-tokens/:id` + +| Parameter | Description | +| --------- | ------------------------------------- | +| `:id` | The ID of the Agent Token to destroy. | + +| Status | Response | Reason | +| ------- | ------------------------- | ------------------------------------------------------------- | +| [204][] | Empty response | The Agent Token was successfully destroyed | +| [404][] | [JSON API error object][] | Agent Token not found, or user unauthorized to perform action | + +### Sample Request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request DELETE \ + https://app.terraform.io/api/v2/authentication-tokens/at-6yEmxNAhaoQLH1Da +``` diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/agents.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/agents.mdx new file mode 100644 index 0000000000..58acfa2aad --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/agents.mdx @@ -0,0 +1,637 @@ +--- +page_title: /agents and /agent-pools API reference for Terraform Enterprise +description: >- + Use the Terraform Enterprise API's `/agents` endpoint to read and delete + agents. Use the `/agent-pools` endpoint to read, create, update, and delete + agent pools. +source: terraform-docs-common +--- + +[200]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/200 + +[201]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/201 + +[202]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/202 + +[204]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/204 + +[400]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/400 + +[401]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/401 + +[403]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/403 + +[404]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404 + +[409]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/409 + +[412]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/412 + +[422]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/422 + +[429]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/429 + +[500]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500 + +[504]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/504 + +[JSON API document]: /terraform/enterprise/api-docs#json-api-documents + +[JSON API error object]: https://jsonapi.org/format/#error-objects + +# Agents and agent pools API reference + +An Agent Pool represents a group of Agents, often related to one another by sharing a common network segment or purpose. +A workspace may be configured to use one of the organization's agent pools to run remote operations with isolated, +private, or on-premises infrastructure. + + + +@include 'tfc-package-callouts/agents.mdx' + + + +## List Agent Pools + +`GET /organizations/:organization_name/agent-pools` + +| Parameter | Description | +| -------------------- | ----------------------------- | +| `:organization_name` | The name of the organization. | + +This endpoint allows you to list agent pools, their agents, and their tokens for an organization. + +| Status | Response | Reason | +| ------- | --------------------------------------------- | ---------------------- | +| [200][] | [JSON API document][] (`type: "agent-pools"`) | Success | +| [404][] | [JSON API error object][] | Organization not found | + +### Query Parameters + +This endpoint supports pagination [with standard URL query parameters](/terraform/enterprise/api-docs#query-parameters). Remember to percent-encode `[` as `%5B` and `]` as `%5D` if your tooling doesn't automatically encode URLs. + +| Parameter | Description | | +| ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | - | +| `q` | **Optional.** A search query string. Agent pools are searchable by name. | | +| `sort` | **Optional.** Allows sorting the returned agents pools. Valid values are `"name"` and `"created-at"`. Prepending a hyphen to the sort parameter will reverse the order (e.g. `"-name"`). | | +| `page[number]` | **Optional.** If omitted, the endpoint will return the first page. | | +| `page[size]` | **Optional.** If omitted, the endpoint will return 20 agent pools per page. | | +| `filter[allowed_workspaces][name]` | **Optional.** Filters agent pools to those associated with the given workspace. The workspace must have permission to use the agent pool. Refer to [Scoping Agent Pools to Specific Workspaces](/terraform/cloud-docs/agents#scope-an-agent-pool-to-specific-workspaces). | | + +### Sample Request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request GET \ + https://app.terraform.io/api/v2/organizations/my-organization/agent-pools +``` + +### Sample Response + +```json +{ + "data": [ + { + "id": "apool-yoGUFz5zcRMMz53i", + "type": "agent-pools", + "attributes": { + "name": "example-pool", + "created-at": "2020-08-05T18:10:26.964Z", + "organization-scoped": false, + "agent-count": 3 + }, + "relationships": { + "agents": { + "links": { + "related": "/api/v2/agent-pools/apool-yoGUFz5zcRMMz53i/agents" + } + }, + "authentication-tokens": { + "links": { + "related": "/api/v2/agent-pools/apool-yoGUFz5zcRMMz53i/authentication-tokens" + } + }, + "workspaces": { + "data": [ + { + "id": "ws-9EEkcEQSA3XgWyGe", + "type": "workspaces" + } + ] + }, + "allowed-workspaces": { + "data": [ + { + "id": "ws-x9taqV23mxrGcDrn", + "type": "workspaces" + } + ] + } + }, + "links": { + "self": "/api/v2/agent-pools/apool-yoGUFz5zcRMMz53i" + } + } + ], + "links": { + "self": "https://app.terraform.io/api/v2/organizations/my-organization/agent-pools?page%5Bnumber%5D=1&page%5Bsize%5D=20", + "first": "https://app.terraform.io/api/v2/organizations/my-organization/agent-pools?page%5Bnumber%5D=1&page%5Bsize%5D=20", + "prev": null, + "next": null, + "last": "https://app.terraform.io/api/v2/organizations/my-organization/agent-pools?page%5Bnumber%5D=1&page%5Bsize%5D=20" + }, + "meta": { + "pagination": { + "current-page": 1, + "prev-page": null, + "next-page": null, + "total-pages": 1, + "total-count": 1 + }, + "status-counts": { + "total": 1, + "matching": 1 + } + } +} +``` + +## List Agents + +`GET /agent-pools/:agent_pool_id/agents` + +| Parameter | Description | +| ---------------- | --------------------------------- | +| `:agent_pool_id` | The ID of the Agent Pool to list. | + +| Status | Response | Reason | +| ------- | ---------------------------------------- | ------------------------------------------------------------ | +| [200][] | [JSON API document][] (`type: "agents"`) | Success | +| [404][] | [JSON API error object][] | Agent Pool not found, or user unauthorized to perform action | + +### Query Parameters + +This endpoint supports pagination [with standard URL query parameters](/terraform/enterprise/api-docs#query-parameters). Remember to percent-encode `[` as `%5B` and `]` as `%5D` if your tooling doesn't automatically encode URLs. + +| Parameter | Description | +| ------------------------- | ---------------------------------------------------------------------------- | +| `filter[last-ping-since]` | **Optional.** Accepts a date in ISO8601 format (ex. `2020-08-11T10:41:23Z`). | +| `page[number]` | **Optional.** If omitted, the endpoint will return the first page. | +| `page[size]` | **Optional.** If omitted, the endpoint will return 20 agents per page. | + +### Sample Request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request GET \ + https://app.terraform.io/api/v2/agent-pools/apool-xkuMi7x4LsEnBUdY/agents +``` + +### Sample Response + +```json +{ + "data": [ + { + "id": "agent-A726QeosTCpCumAs", + "type": "agents", + "attributes": { + "name": "my-cool-agent", + "status": "idle", + "ip-address": "123.123.123.123", + "last-ping-at": "2020-10-09T18:52:25.246Z" + }, + "links": { + "self": "/api/v2/agents/agent-A726QeosTCpCumAs" + } + }, + { + "id": "agent-4cQzjbr1cnM6Pcxr", + "type": "agents", + "attributes": { + "name": "my-other-cool-agent", + "status": "exited", + "ip-address": "123.123.123.123", + "last-ping-at": "2020-08-12T15:25:09.726Z" + }, + "links": { + "self": "/api/v2/agents/agent-4cQzjbr1cnM6Pcxr" + } + }, + { + "id": "agent-yEJjXQCucpNxtxd2", + "type": "agents", + "attributes": { + "name": null, + "status": "errored", + "ip-address": "123.123.123.123", + "last-ping-at": "2020-08-11T06:22:20.300Z" + }, + "links": { + "self": "/api/v2/agents/agent-yEJjXQCucpNxtxd2" + } + } + ], + "links": { + "self": "https://app.terraform.io/api/v2/agent-pools/apool-yoGUFz5zcRMMz53i/agents?page%5Bnumber%5D=1&page%5Bsize%5D=20", + "first": "https://app.terraform.io/api/v2/agent-pools/apool-yoGUFz5zcRMMz53i/agents?page%5Bnumber%5D=1&page%5Bsize%5D=20", + "prev": null, + "next": null, + "last": "https://app.terraform.io/api/v2/agent-pools/apool-yoGUFz5zcRMMz53i/agents?page%5Bnumber%5D=1&page%5Bsize%5D=20" + }, + "meta": { + "pagination": { + "current-page": 1, + "prev-page": null, + "next-page": null, + "total-pages": 1, + "total-count": 3 + } + } +} +``` + +## Show an Agent Pool + +`GET /agent-pools/:id` + +| Parameter | Description | +| --------- | -------------------------------- | +| `:id` | The ID of the Agent Pool to show | + +| Status | Response | Reason | +| ------- | --------------------------------------------- | ------------------------------------------------------------ | +| [200][] | [JSON API document][] (`type: "agent-pools"`) | Success | +| [404][] | [JSON API error object][] | Agent Pool not found, or user unauthorized to perform action | + +### Sample Request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request GET \ + https://app.terraform.io/api/v2/agent-pools/apool-MCf6kkxu5FyHbqhd +``` + +### Sample Response + +```json +{ + "data": { + "id": "apool-yoGUFz5zcRMMz53i", + "type": "agent-pools", + "attributes": { + "name": "example-pool", + "created-at": "2020-08-05T18:10:26.964Z", + "organization-scoped": false + }, + "relationships": { + "agents": { + "links": { + "related": "/api/v2/agent-pools/apool-yoGUFz5zcRMMz53i/agents" + } + }, + "authentication-tokens": { + "links": { + "related": "/api/v2/agent-pools/apool-yoGUFz5zcRMMz53i/authentication-tokens" + } + }, + "workspaces": { + "data": [ + { + "id": "ws-9EEkcEQSA3XgWyGe", + "type": "workspaces" + } + ] + }, + "allowed-workspaces": { + "data": [ + { + "id": "ws-x9taqV23mxrGcDrn", + "type": "workspaces" + } + ] + } + }, + "links": { + "self": "/api/v2/agent-pools/apool-yoGUFz5zcRMMz53i" + } + } +} +``` + +## Show an Agent + +`GET /agents/:id` + +| Parameter | Description | +| --------- | --------------------------- | +| `:id` | The ID of the agent to show | + +| Status | Response | Reason | +| ------- | ---------------------------------------- | ------------------------------------------------------- | +| [200][] | [JSON API document][] (`type: "agents"`) | Success | +| [404][] | [JSON API error object][] | Agent not found, or user unauthorized to perform action | + +### Sample Request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request GET \ + https://app.terraform.io/api/v2/agents/agent-73PJNzbZB5idR7AQ +``` + +### Sample Response + +```json +{ + "data": { + "id": "agent-Zz9PTEcUgBtYzht8", + "type": "agents", + "attributes": { + "name": "my-agent", + "status": "busy", + "ip-address": "123.123.123.123", + "last-ping-at": "2020-09-08T18:47:35.361Z" + }, + "links": { + "self": "/api/v2/agents/agent-Zz9PTEcUgBtYzht8" + } + } +} +``` + +This endpoint lists details about an agent along with that agent's status. [Learn more about agents statuses](/terraform/cloud-docs/agents/agent-pools#view-agent-statuses). + +## Delete an Agent + +`DELETE /agents/:id` + +| Parameter | Description | +| --------- | ----------------------------- | +| `:id` | The ID of the agent to delete | + +| Status | Response | Reason | +| ------- | ------------------------- | ------------------------------------------------------------------------------------------------------------ | +| [204][] | No Content | Success | +| [412][] | [JSON API error object][] | Agent is not deletable. Agents must have a status of `unknown`, `errored`, or `exited` before being deleted. | +| [404][] | [JSON API error object][] | Agent not found, or user unauthorized to perform action | + +### Sample Request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --request DELETE \ + https://app.terraform.io/api/v2/agents/agent-73PJNzbZB5idR7AQ +``` + +## Create an Agent Pool + +`POST /organizations/:organization_name/agent-pools` + +| Parameter | Description | +| -------------------- | ----------------------------- | +| `:organization_name` | The name of the organization. | + +This endpoint allows you to create an Agent Pool for an organization. Only one Agent Pool may exist for an organization. + +| Status | Response | Reason | +| ------- | --------------------------------------------- | -------------------------------------------------------------- | +| [201][] | [JSON API document][] (`type: "agent-pools"`) | Agent Pool successfully created | +| [404][] | [JSON API error object][] | Organization not found or user unauthorized to perform action | +| [422][] | [JSON API error object][] | Malformed request body (missing attributes, wrong types, etc.) | + +### Request Body + +This POST endpoint requires a JSON object with the following properties as a request payload. + +Properties without a default value are required. + +| Key path | Type | Default | Description | +| ------------------------------------------------- | ------ | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `data.type` | string | | Must be `"agent-pools"`. | +| `data.attributes.name` | string | | The name of the agent pool, which can only include letters, numbers, `-`, and `_`. This will be used as an identifier and must be unique in the organization. | +| `data.attributes.organization-scoped` | bool | true | The scope of the agent pool. If true, all workspaces in the organization can use the agent pool. | +| `data.relationships.allowed-workspaces.data.type` | string | | Must be `"workspaces"`. | +| `data.relationships.allowed-workspaces.data.id` | string | | The ID of the workspace that has permission to use the agent pool. Refer to [Scoping Agent Pools to Specific Workspaces](/terraform/cloud-docs/agents#scope-an-agent-pool-to-specific-workspaces). | + +### Sample Payload + +```json +{ + "data": { + "type": "agent-pools", + "attributes": { + "name": "my-pool", + "organization-scoped": false + }, + "relationships": { + "allowed-workspaces": { + "data": [ + { + "id": "ws-x9taqV23mxrGcDrn", + "type": "workspaces" + } + ] + } + } + } +} +``` + +### Sample Request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request POST \ + --data @payload.json \ + https://app.terraform.io/api/v2/organizations/my-organization/agent-pools +``` + +### Sample Response + +```json +{ + "data": { + "id": "apool-55jZekR57npjHHYQ", + "type": "agent-pools", + "attributes": { + "name": "my-pool", + "created-at": "2020-10-13T16:32:45.165Z", + "organization-scoped": false, + + }, + "relationships": { + "agents": { + "links": { + "related": "/api/v2/agent-pools/apool-55jZekR57npjHHYQ/agents" + } + }, + "authentication-tokens": { + "links": { + "related": "/api/v2/agent-pools/apool-55jZekR57npjHHYQ/authentication-tokens" + } + }, + "workspaces": { + "data": [] + }, + "allowed-workspaces": { + "data": [ + { + "id": "ws-x9taqV23mxrGcDrn", + "type": "workspaces" + } + ] + } + }, + "links": { + "self": "/api/v2/agent-pools/apool-55jZekR57npjHHYQ" + } + } +} +``` + +## Update an Agent Pool + +`PATCH /agent-pools/:id` + +| Parameter | Description | +| --------- | ---------------------------------- | +| `:id` | The ID of the Agent Pool to update | + +| Status | Response | Reason | +| ------- | --------------------------------------------- | -------------------------------------------------------------- | +| [200][] | [JSON API document][] (`type: "agent-pools"`) | Success | +| [404][] | [JSON API error object][] | Agent Pool not found, or user unauthorized to perform action | +| [422][] | JSON error document | Malformed request body (missing attributes, wrong types, etc.) | + +### Request Body + +This PATCH endpoint requires a JSON object with the following properties as a request payload. + +Properties without a default value are required. + +| Key path | Type | Default | Description | +| ------------------------------------------------- | ------ | ---------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `data.type` | string | | Must be `"agent-pools"`. | +| `data.attributes.name` | string | (previous value) | The name of the agent pool, which can only include letters, numbers, `-`, and `_`. This will be used as an identifier and must be unique in the organization. | +| `data.attributes.organization-scoped` | bool | true | The scope of the agent pool. If true, all workspaces in the organization can use the agent pool. | +| `data.relationships.allowed-workspaces.data.type` | string | | Must be `"workspaces"`. | +| `data.relationships.allowed-workspaces.data.id` | string | | The ID of the workspace that has permission to use the agent pool. Refer to [Scoping Agent Pools to Specific Workspaces](/terraform/cloud-docs/agents#scope-an-agent-pool-to-specific-workspaces). | + +### Sample Payload + +```json +{ + "data": { + "type": "agent-pools", + "attributes": { + "name": "example-pool", + "organization-scoped": false + }, + "relationships": { + "allowed-workspaces": { + "data": [ + { + "id": "ws-x9taqV23mxrGcDrn", + "type": "workspaces" + } + ] + } + } + } +} +``` + +### Sample Request + +```shell +$ curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request PATCH \ + --data @payload.json \ + https://app.terraform.io/api/v2/agent-pools/apool-MCf6kkxu5FyHbqhd +``` + +### Sample Response + +```json +{ + "data": { + "id": "apool-yoGUFz5zcRMMz53i", + "type": "agent-pools", + "attributes": { + "name": "example-pool", + "created-at": "2020-08-05T18:10:26.964Z" + }, + "relationships": { + "agents": { + "links": { + "related": "/api/v2/agent-pools/apool-yoGUFz5zcRMMz53i/agents" + } + }, + "authentication-tokens": { + "links": { + "related": "/api/v2/agent-pools/apool-yoGUFz5zcRMMz53i/authentication-tokens" + } + }, + "workspaces": { + "data": [ + { + "id": "ws-9EEkcEQSA3XgWyGe", + "type": "workspaces" + } + ] + }, + "allowed-workspaces": { + "data": [ + { + "id": "ws-x9taqV23mxrGcDrn", + "type": "workspaces" + } + ] + } + }, + "links": { + "self": "/api/v2/agent-pools/apool-yoGUFz5zcRMMz53i" + } + } +} +``` + +## Delete an Agent Pool + +`DELETE /agent-pools/:agent_pool_id` + +| Parameter | Description | +| ---------------- | ------------------------------------- | +| `:agent_pool_id` | The ID of the agent pool ID to delete | + +### Sample Request + +```shell +$ curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request DELETE \ + https://app.terraform.io/api/v2/agent-pools/apool-MCf6kkxu5FyHbqhd +``` + +### Available Related Resources + +The GET endpoints above can optionally return related resources, if requested with [the `include` query parameter](/terraform/enterprise/api-docs#inclusion-of-related-resources). The following resource types are available: + +| Resource Name | Description | +| ------------- | ------------------------------------------- | +| `workspaces` | The workspaces attached to this agent pool. | diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/applies.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/applies.mdx new file mode 100644 index 0000000000..a124bf588c --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/applies.mdx @@ -0,0 +1,201 @@ +--- +page_title: /applies API reference for Terraform Enterprise +description: >- + Use the Terraform Enterprise API's `/applies` endpoint to read the results of + a Terraform apply and to recover any failed state uploads after applying. +source: terraform-docs-common +--- + +[200]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/200 + +[307]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/307 + +[404]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404 + +[JSON API document]: /terraform/enterprise/api-docs#json-api-documents + +[JSON API error object]: https://jsonapi.org/format/#error-objects + +# Applies API reference + +An apply represents the results of applying a Terraform Run's execution plan. + +## Attributes + +### Apply States + +You'll find the apply state in `data.attributes.status`, as one of the following values. + +| State | Description | +| ------------------------- | ------------------------------------------------------------------------------ | +| `pending` | The initial status of a apply once it has been created. | +| `managed_queued`/`queued` | The apply has been queued, awaiting backend service capacity to run terraform. | +| `running` | The apply is running. | +| `errored` | The apply has errored. This is a final state. | +| `canceled` | The apply has been canceled. This is a final state. | +| `finished` | The apply has completed successfully. This is a final state. | +| `unreachable` | The apply will not run. This is a final state. | + +## Show an apply + +`GET /applies/:id` + +| Parameter | Description | +| --------- | ---------------------------- | +| `id` | The ID of the apply to show. | + +There is no endpoint to list applies. You can find the ID for an apply in the +`relationships.apply` property of a run object. + +| Status | Response | Reason | +| ------- | ----------------------------------------- | ------------------------------------------------------- | +| [200][] | [JSON API document][] (`type: "applies"`) | The request was successful | +| [404][] | [JSON API error object][] | Apply not found, or user unauthorized to perform action | + +### Sample Request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + https://app.terraform.io/api/v2/applies/apply-47MBvjwzBG8YKc2v +``` + +### Sample Response + +```json +{ + "data": { + "id": "apply-47MBvjwzBG8YKc2v", + "type": "applies", + "attributes": { + "execution-details": { + "mode": "remote", + }, + "status": "finished", + "status-timestamps": { + "queued-at": "2018-10-17T18:58:27+00:00", + "started-at": "2018-10-17T18:58:29+00:00", + "finished-at": "2018-10-17T18:58:37+00:00" + }, + "log-read-url": "https://archivist.terraform.io/v1/object/dmF1bHQ6djE6OFA1eEdlSFVHRSs4YUcwaW83a1dRRDA0U2E3T3FiWk1HM2NyQlNtcS9JS1hHN3dmTXJmaFhEYTlHdTF1ZlgxZ2wzVC9kVTlNcjRPOEJkK050VFI3U3dvS2ZuaUhFSGpVenJVUFYzSFVZQ1VZYno3T3UyYjdDRVRPRE5pbWJDVTIrNllQTENyTndYd1Y0ak1DL1dPVlN1VlNxKzYzbWlIcnJPa2dRRkJZZGtFeTNiaU84YlZ4QWs2QzlLY3VJb3lmWlIrajF4a1hYZTlsWnFYemRkL2pNOG9Zc0ZDakdVMCtURUE3dDNMODRsRnY4cWl1dUN5dUVuUzdnZzFwL3BNeHlwbXNXZWRrUDhXdzhGNnF4c3dqaXlZS29oL3FKakI5dm9uYU5ZKzAybnloREdnQ3J2Rk5WMlBJemZQTg", + "resource-additions": 1, + "resource-changes": 0, + "resource-destructions": 0, + "resource-imports": 0 + }, + "relationships": { + "state-versions": { + "data": [ + { + "id": "sv-TpnsuD3iewwsfeRD", + "type": "state-versions" + }, + { + "id": "sv-Fu1n6a3TgJ1Typq9", + "type": "state-versions" + } + ] + } + }, + "links": { + "self": "/api/v2/applies/apply-47MBvjwzBG8YKc2v" + } + } +} +``` + +_Using HCP Terraform agents_ + +[HCP Terraform agents](/terraform/enterprise/api-docs/agents) allow HCP Terraform to communicate with isolated, private, or on-premises infrastructure. When a workspace is set to use the agent execution mode, the apply response will include additional details about the agent pool and agent used. + +```json +{ + "data": { + "id": "apply-47MBvjwzBG8YKc2v", + "type": "applies", + "attributes": { + "execution-details": { + "agent-id": "agent-S1Y7tcKxXPJDQAvq", + "agent-name": "agent_01", + "agent-pool-id": "apool-Zigq2VGreKq7nwph", + "agent-pool-name": "first-pool", + "mode": "agent", + }, + "status": "finished", + "status-timestamps": { + "queued-at": "2018-10-17T18:58:27+00:00", + "started-at": "2018-10-17T18:58:29+00:00", + "finished-at": "2018-10-17T18:58:37+00:00" + }, + "log-read-url": "https://archivist.terraform.io/v1/object/dmF1bHQ6djE6OFA1eEdlSFVHRSs4YUcwaW83a1dRRDA0U2E3T3FiWk1HM2NyQlNtcS9JS1hHN3dmTXJmaFhEYTlHdTF1ZlgxZ2wzVC9kVTlNcjRPOEJkK050VFI3U3dvS2ZuaUhFSGpVenJVUFYzSFVZQ1VZYno3T3UyYjdDRVRPRE5pbWJDVTIrNllQTENyTndYd1Y0ak1DL1dPVlN1VlNxKzYzbWlIcnJPa2dRRkJZZGtFeTNiaU84YlZ4QWs2QzlLY3VJb3lmWlIrajF4a1hYZTlsWnFYemRkL2pNOG9Zc0ZDakdVMCtURUE3dDNMODRsRnY4cWl1dUN5dUVuUzdnZzFwL3BNeHlwbXNXZWRrUDhXdzhGNnF4c3dqaXlZS29oL3FKakI5dm9uYU5ZKzAybnloREdnQ3J2Rk5WMlBJemZQTg", + "resource-additions": 1, + "resource-changes": 0, + "resource-destructions": 0, + "resource-imports": 0 + }, + "relationships": { + "state-versions": { + "data": [ + { + "id": "sv-TpnsuD3iewwsfeRD", + "type": "state-versions" + }, + { + "id": "sv-Fu1n6a3TgJ1Typq9", + "type": "state-versions" + } + ] + } + }, + "links": { + "self": "/api/v2/applies/apply-47MBvjwzBG8YKc2v" + } + } +} +``` + +## Recover a failed state upload after applying + +`GET /applies/:id/errored-state` + +| Parameter | Description | +| --------- | ----------------------------------------- | +| `id` | The ID of the apply to recover state for. | + +It is possible that during the course of a Run, Terraform may fail to upload a +state file. This can happen for a variety of reasons, but should be an +exceptionally rare occurrence. HCP Terraform agent versions greater than 1.12.0 +include a fallback mechanism which attempts to upload the state directly to +HCP Terraform's backend storage system in these cases. This endpoint then +makes the raw data from these failed uploads available to users who are +authorized to read the state contents. + +| Status | Response | Reason | +| ------- | -------------------------------------------- | ----------------------------------------------------------------------------------- | +| [307][] | HTTP temporary redirect to state storage URL | Errored state available and user is authorized to read it | +| [404][] | [JSON API error object][] | Apply not found, errored state not uploaded, or user unauthorized to perform action | + +When a 307 redirect is returned, the storage URL to the raw state file will be +present in the `Location` header of the response. The URL in the `Location` +header will expire after one minute. It is recommended for the API client to +follow the redirect immediately. Each successful request to the errored-state +endpoint will generate a new, unique storage URL with the same one minute +expiration window. + +### Sample Request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + https://app.terraform.io/api/v2/applies/apply-47MBvjwzBG8YKc2v/errored-state +``` + +### Sample Response + + HTTP/1.1 307 Temporary Redirect + Content-Length: 22 + Content-Type: text/plain + Location: https://archivist.terraform.io/v1/object/dmF1bHQ6djE6OFA1eEdlSFVHRSs4YUcwaW83a1dRRDA0U2E3T3FiWk1HM2NyQlNtcS9JS1hHN3dmTXJmaFhEYTlHdTF1ZlgxZ2wzVC9kVTlNcjRPOEJkK050VFI3U3dvS2ZuaUhFSGpVenJVUFYzSFVZQ1VZYno3T3UyYjdDRVRPRE5pbWJDVTIrNllQTENyTndYd1Y0ak1DL1dPVlN1VlNxKzYzbWlIcnJPa2dRRkJZZGtFeTNiaU84YlZ4QWs2QzlLY3VJb3lmWlIrajF4a1hYZTlsWnFYemRkL2pNOG9Zc0ZDakdVMCtURUE3dDNMODRsRnY4cWl1dUN5dUVuUzdnZzFwL3BNeHlwbXNXZWRrUDhXdzhGNnF4c3dqaXlZS29oL3FKakI5dm9uYU5ZKzAybnloREdnQ3J2Rk5WMlBJemZQTg + + 307 Temporary Redirect diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/assessment-results.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/assessment-results.mdx new file mode 100644 index 0000000000..6ce07a2eda --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/assessment-results.mdx @@ -0,0 +1,129 @@ +--- +page_title: /assessment-results API reference for Terraform Enterprise +description: >- + Use the Terraform Enterprise API's `/assessment-results` endpoint to read a + health assessment's results, including details on continuous validation and + drift detection. +source: terraform-docs-common +--- + +[200]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/200 + +[400]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/400 + +[401]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/401 + +[403]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/403 + +[404]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404 + +[422]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/422 + +[500]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500 + +[504]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/504 + +# Health assessment results API reference + +An Assessment Result is the summary record of an instance of health assessment. HCP Terraform can perform automatic health assessments in a workspace to assess whether its real infrastructure matches the requirements defined in its Terraform configuration. Refer to [Health](/terraform/enterprise/workspaces/health) for more details. + + + +@include 'tfc-package-callouts/health-assessments.mdx' + + + +## Show Assessment Result + +Any user with read access to a workspace can retrieve assessment results for the workspace. + +`GET api/v2/assessment-results/:assessment_result_id` + +| Parameter | Description | +| ----------------------- | ------------------------ | +| `:assessment_result_id` | The assessment result ID | + +### Sample Request + +```shell +$ curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + https://app.terraform.io/api/v2/assessment-results/asmtres-cHh5777xm +``` + +### Sample Response + +```json +{ + "id": "asmtres-UG5rE9L1373hMYMA", + "type": "assessment-results", + "data": { + "attributes": { + "drifted": true, + "succeeded": true, + "error-msg": null, + "created-at": "2022-07-02T22:29:58+00:00", + }, + "links": { + "self": "/api/v2/assessment-results/asmtres-UG5rE9L1373hMYMA/" + "json-output": "/api/v2/assessment-results/asmtres-UG5rE9L1373hMYMA/json-output" + "json-schema": "/api/v2/assessment-results/asmtres-UG5rE9L1373hMYMA/json-schema" + "log-output": "/api/v2/assessment-results/asmtres-UG5rE9L1373hMYMA/log-output" + } + } +} +``` + +## Retrieve the JSON output from the assessment execution + +The following endpoints retrieve files documenting the plan, schema, and logged runtime associated with the specified assessment result. They provide complete context for an assessment result. The responses do not adhere to JSON API spec. + +You cannot access these endpoints with [organization tokens](/terraform/enterprise/users-teams-organizations/api-tokens#organization-api-tokens). You must access them with a [user token](/terraform/enterprise/users-teams-organizations/users#api-tokens) or [team token](/terraform/enterprise/users-teams-organizations/api-tokens#team-api-tokens) that has admin level access to the workspace. Refer to [Permissions](/terraform/enterprise/users-teams-organizations/permissions) for details. + +[permissions-citation]: #intentionally-unused---keep-for-maintainers + +### JSON Plan + +The following endpoint returns the JSON plan output associated with the assessment result. + +`GET api/v2/assessment-results/:assessment_result_id/json-output` + +#### Sample Request + +```shell +$ curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + https://app.terraform.io/api/v2/assessment-results/asmtres-cHh5777xm/json-output +``` + +### JSON Schema file + +The following endpoint returns the JSON [provider schema](/terraform/cli/commands/providers/schema) associated with the assessment result. + +`GET api/v2/assessment-results/:assessment_result_id/json-schema` + +#### Sample Request + +```shell +$ curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + https://app.terraform.io/api/v2/assessment-results/asmtres-cHh5777xm/json-schema +``` + +### JSON Log Output + +The following endpoint returns Terraform JSON log output. + +`GET api/v2/assessment-results/assessment_result_id/log-output` + +#### Sample Request + +```shell +$ curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + https://app.terraform.io/api/v2/assessment-results/asmtres-cHh5777xm/log-output +``` diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/changelog.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/changelog.mdx new file mode 100644 index 0000000000..7cda177f5a --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/changelog.mdx @@ -0,0 +1,578 @@ +--- +page_title: API Changelog for Terraform Enterprise +page_id: api-changelog +description: >- + Use this log of Terraform Enterprise API changes to track new features and + evolving functionality over time. +source: terraform-docs-common +--- + +# API Changelog + +Keep track of changes to the API for HCP Terraform and Terraform Enterprise. + +## 2025-05-1 + +- Add `agent-pool` relationship to the [run task API](/terraform/enterprise/api-docs/run-tasks/run-tasks), which you can use to assign a run task to an agent pool. +- Add `private-run-tasks` to [feature entitlements](/terraform/enterprise/api-docs#feature-entitlements). + +- You can now revoke, and revert the revocation of, module versions. Learn more about [Managing module versions](/terraform/enterprise/api-docs/private-registry/manage-module-versions). + + +## 2025-03-20 + +- Add API documentation for multiple [team tokens](/terraform/enterprise/api-docs/api-tokens), and update documentation around [legacy team tokens](/terraform/enterprise/api-docs/team-tokens##legacy-team-tokens-api-reference). +- Update existing API documentation for [team tokens](/terraform/enterprise/api-docs/team-tokens) to distinguish multiple team tokens from [legacy team tokens](/terraform/enterprise/api-docs/team-tokens##legacy-team-tokens-api-reference). + +## 2025-3-10 + +- Document unique pagination metadata given in the response of [Organization Runs Index API](/terraform/enterprise/api-docs/run##list-runs-in-an-organization). + +## 2025-03-10 + +- Add new field `current_rum_count` to the [explorer API](/terraform/enterprise/api-docs/explorer) in the `workspaces` view type that lists a workspace's current resources under management. + +## 2024-11-19 + +- Clarify `tag-bindings` and `effective-tag-bindings` on [workspaces](/terraform/enterprise/api-docs/workspaces) and [projects](/terraform/enterprise/api-docs/projects) +- Adds new documentation for `PATCH`ing tag bindings on [projects](/terraform/enterprise/api-docs/projects) and [workspaces](/terraform/enterprise/api-docs/workspaces) + +## 2024-10-15 + +- Add new documentation for the ability to deprecate, and revert the deprecation of, module versions. Learn more about [Managing module versions](/terraform/enterprise/api-docs/private-registry/manage-module-versions). + +## 2024-10-14 + +- Update the [Organizations API](/terraform/enterprise/api-docs/organizations) to support the `speculative-plan-management-enabled` attribute, which controls [automatic cancellation of plan-only runs triggered by outdated commits](/terraform/enterprise/users-teams-organizations/organizations/vcs-speculative-plan-management). + +## 2024-10-11 + +- Add documentation for new timeframe filter on List endpoints for [Runs](/terraform/enterprise/api-docs/run) API + +## 2024-09-02 + +- Add warning about the deprecation and future removal of the [Policy Checks](/terraform/enterprise/api-docs/policy-checks) API. + +## 2024-08-16 + +- Fixes Workspace API responses to be consistent and contain all attributes and relationships. + +## 2024-08-14 + +- Add documentation for a new API endpoint that lists an [organization's team tokens](/terraform/enterprise/api-docs/team-tokens). + +## 2024-08-01 + + +This endpoint is exclusive to Terraform Enterprise, and not available in HCP Terraform. + + +- Update the [admin settings API](/terraform/enterprise/api-docs/admin/settings##update-general-settings) and [admin organizations API](/terraform/enterprise/api-docs/admin/organizations#update-an-organization) to indicate that the `terraform-build-worker-plan-timeout` and `terraform-build-worker-apply-timeout` attributes are deprecated and will be replaced by `plan-timeout` and `apply-timeout`, respectively. + + + +## 2024-07-24 + +- Remove beta tags from documentation for audit trail tokens. + + +## 2024-07-15 + +- Update the [Team API](/terraform/enterprise/api-docs/teams) to include `allow-member-token-management`. + + + +## 2024-07-12 + +- Add beta tags to documentation for audit trail tokens. + + +## 2024-06-25 + +- Add API documentation for new [team token management setting](/terraform/enterprise/users-teams-organizations/api-tokens). +- Update API documentation for the [manage teams permission](/terraform/enterprise/users-teams-organizations/permissions#team-management-permissions). + +## 2024-05-29 + +- Add API documentation for the new [audit trails token](/terraform/enterprise/api-docs/audit-trails-tokens). + +## 2024-05-23 + +- Update the [registry modules API](/terraform/enterprise/api-docs/private-registry/modules#create-a-module-version) for publishing new versions of branch based modules. + +## 2024-05-10 + +- Add API documentation for new [manage agent pools permission](/terraform/enterprise/users-teams-organizations/permissions#manage-agent-pools). + +## 2024-04-25 + +- Project names can now be up to 40 characters. + +## 2024-04-08 + +- Add API documentation for new [team management permissions](/terraform/enterprise/users-teams-organizations/permissions#team-management-permissions). + +## 2024-04-04 + +- Add a `sort` parameter to the [Projects list API](/terraform/enterprise/api-docs/projects#query-parameters) to allow sorting projects by name. +- Add a `description` attribute to the [Projects API](/terraform/enterprise/api-docs/projects). +- Add `project-count` and `workspace-count` attributes to sample [Projects API](/terraform/enterprise/api-docs/projects) responses. + +## 2024-3-27 + +- Add `private-vcs` to [Feature Entitlements](/terraform/enterprise/api-docs#feature-entitlements). + +## 2024-3-26 + +- Add API documentation for searching [variable sets](/terraform/enterprise/api-docs/variable-sets#list-variable-sets) by name. + +## 2024-3-14 + +- Add documentation for creating runs with debugging mode enabled. + +## 2024-3-12 + +- Update OAuth Client API endpoints to create, update, and return projects associated with an oauth client. +- Add API endpoints to [Attach an OAuth Client](/terraform/enterprise/api-docs/oauth-clients#attach-an-oauth-client-to-projects) and [Detach an OAuth Client](/terraform/enterprise/api-docs/oauth-clients#detach-an-oauth-client-from-projects) from a project. +- Add `organization-scoped` attribute to the [OAuth Clients API](/terraform/enterprise/api-docs/oauth-clients). + +## 2024-2-29 + +- Update [run task stages](/terraform/enterprise/api-docs/run-tasks/run-task-stages-and-results) with new multi-stage payload format. +- Update [run tasks](/terraform/enterprise/api-docs/run-tasks/run-tasks) with global run tasks request/response payloads. + +## 2024-2-27 + +- Add `private-policy-agents` to [Feature Entitlements](/terraform/enterprise/api-docs#feature-entitlements). + +## 2024-2-20 + +- Add documentation for configuring organization and workspace data retention policies through the API and on the different [types of data retention policies](/terraform/enterprise/api-docs/data-retention-policies). + + +## 2024-2-8 + +- Add [Explorer API documentation](/terraform/enterprise/api-docs/explorer) + + +## 2024-1-30 + +- Update the [Audit trails](/terraform/enterprise/api-docs/audit-trails) documentation to expand on the payloads for each event. + +## 2024-1-24 + +- Introduce configurable data retention policies at the site-wide level and extend data retention policies at the organization and workspace levels. +- Added and/or updated data retention policy documentation to the following topics: + - [Admin Settings Documentation](/terraform/enterprise/application-administration/general#data-retention-policies) + - [Admin API Documentation](/terraform/enterprise/api-docs/admin/settings#data-retention-policies) + - [Organization Documentation](/terraform/enterprise/users-teams-organizations/organizations#data-retention-policies) + - [Workspace Documentation](/terraform/enterprise/workspaces/settings/deletion#data-retention-policies) + +## 2024-1-4 + +- Update the [Organizations API](/terraform/enterprise/api-docs/organizations) to support the `aggregated-commit-status-enabled` attribute, which controls whether [Aggregated Status Checks](/terraform/enterprise/users-teams-organizations/organizations/vcs-status-checks) are enabled. + +## 2023-11-17 + +- Added the [`opa-versions` endpoint](/terraform/enterprise/api-docs/admin/opa-versions) to allow administrators to manage available Open Policy Agent (OPA) versions. +- Added the [`sentinel-versions` endpoint](/terraform/enterprise/api-docs/admin/sentinel-versions) to allow administrators to manage available Sentinel versions. +- Add `authenticated-resource` relationship to the [`account` API](/terraform/enterprise/api-docs/account). + +## 2023-11-15 + +- Introduce configurable data retention policies at the [organization](/terraform/enterprise/users-teams-organizations/organizations#data-retention-policies) and [workspace](/terraform/enterprise/workspaces/settings/deletion#data-retention-policies) levels. +- Added data retention policy documentation to the following topics: + - [`state-versions` API documentation](/terraform/enterprise/api-docs/state-versions) + - [`configuration-versions` API documentation](/terraform/enterprise/api-docs/configuration-versions) + - [Organizations documentation](/terraform/enterprise/users-teams-organizations/organizations#destruction-and-deletion) + - [Workspaces documentation](/terraform/enterprise/workspaces/settings/deletion#data-retention-policies) + +## 2023-11-07 + +- Add `auto_destroy_activity_duration` to the [Workspaces API](/terraform/enterprise/api-docs/workspaces), which allows Terraform Cloud to schedule auto-destroy runs [based on workspace inactivity](/terraform/enterprise/workspaces/settings/deletion#automatically-destroy). + +## 2023-10-31 + +- Update the [Workspaces API](/terraform/enterprise/api-docs/workspaces) to support the `auto-apply-run-trigger` attribute, which controls if run trigger runs are auto-applied. + +## 2023-10-30 + +- Add `priority` attribute to the [Variable Sets API](/terraform/enterprise/api-docs/variable-sets). + +## 2023-10-04 + +- Updates to [run task integration API](/terraform/enterprise/api-docs/run-tasks/run-tasks-integration) + - Fix invalid JSON in the example payload. + - Clarify the expected JSON:API payload fields. +- Add `policy-tool-version` attribute to [Policy Set Outcomes](/terraform/enterprise/api-docs/policy-evaluations#list-policy-outcomes). + +## 2023-10-03 + +- Update [Policy Sets API](/terraform/enterprise/api-docs/policy-sets) to include `agent-enabled` and `policy-tool-version`. +- Update [Policy Evaluations API](/terraform/enterprise/api-docs/policy-evaluations) to include `policy-tool-version`. + +## 2023-09-29 + +- Add support for [streamlined run task reviews](/terraform/enterprise/integrations/run-tasks), enabling run task integrations to return high fidelity results. + - Update the [Terraform cloud run task API](/terraform/enterprise/api-docs/run-tasks/run-tasks) to enable streamlined run task reviews. + - The [run task integration API](/terraform/enterprise/api-docs/run-tasks/run-tasks-integration) now guides integrations through sending rich results. + - Updated the run task payload [JSON Schema](https://github.com/hashicorp/terraform-docs-common/blob/main/website/public/schema/run-tasks/runtask-result.json). + +## 2023-09-25 + +- Add `intermediate` boolean attribute to the [State Versions API](/terraform/enterprise/api-docs/state-versions). + +## 2023-09-19 + +- Add [failed state upload recovery](/terraform/enterprise/api-docs/applies#recover-a-failed-state-upload-after-applying) endpoint. + +## 2023-09-15 + +- Add `auto-destroy-at` attribute to the [Workspaces API](/terraform/enterprise/api-docs/workspaces). +- Update the [Notification Configurations API](/terraform/enterprise/api-docs/notification-configurations) to include [automatic destroy run](/terraform/enterprise/api-docs/notification-configurations#automatic-destroy-runs) details. + +## 2023-09-08 + +- Update the [Organizations API](/terraform/enterprise/api-docs/organizations) to include `default-execution-mode` and `default-agent-pool`. +- Update the [Workspaces API](/terraform/enterprise/api-docs/workspaces) to add a `setting-overwrites` object to allow you to overwrite `default-execution-mode` and `default-agent-pool`. + +## 2023-09-06 + +- Update Policy Sets API endpoints to create, update, and return excluded workspaces associated with a policy set. +- Add API endpoints to [Attach a Policy Set](/terraform/enterprise/api-docs/policy-sets#attach-a-policy-set-to-exclusions) and [Detach a Policy Set](/terraform/enterprise/api-docs/policy-sets#detach-a-policy-set-to-exclusions) from excluded workspaces. + +## 2023-08-21 + +- Add `save-plan` attribute, `planned_and_saved` status, and `save_plan` operation type to [Runs endpoints](/terraform/enterprise/api-docs/run). + +## 2023-08-10 + +- Add `provisional` to `configuration-versions` endpoint. + +## 2023-07-26 + +- Add support for a `custom` option to the `team_project` access level along with various customizable permissions. The `project-access` permissions apply to the project itself, and `workspace-access` permissions apply to all workspaces within the project. For more information, see [Project Team Access](/terraform/enterprise/api-docs/project-team-access). + +## 2023-06-09 + +- Introduce support for [`import` blocks](/terraform/language/import/generating-configuration). + - [Runs](/terraform/enterprise/api-docs/run#create-a-run) have a new `allow-config-generation` option. + - [Plans](/terraform/enterprise/api-docs/plans#show-a-plan) have new `resource-imports` and `generated-configuration` properties. + - [Applies](/terraform/enterprise/api-docs/applies#show-an-apply) have a new `resource-imports` property. +- The workspaces associated with a policy set can now be updated using the [Policy Sets PATCH endpoint](/terraform/enterprise/api-docs/policy-sets#update-a-policy-set) +- Update the [Workspaces](/terraform/enterprise/api-docs/workspaces) API endpoints to include the associated [project](/terraform/enterprise/api-docs/projects). + +## 2023-05-25 + +- Terraform Cloud sets the `configuration_version_download_url`, `configuration_version_id`, and `workspace_working_directory` properties for all stages of the [Run Task Request](/terraform/enterprise/api-docs/run-tasks/run-tasks-integration#request-body). +- Add the new `enforcement-level` property in the request and response of [Policies endpoints](/terraform/enterprise/api-docs/policies). +- Deprecate the old `enforce` property in the request and response of [Policies endpoints](/terraform/enterprise/api-docs/policies). +- Add new properties to limit run tasks and policies for the Terraform Cloud free tier. We updated the [entitlement set](/terraform/enterprise/api-docs/organizations#show-the-entitlement-set), [feature set](/terraform/enterprise/api-docs/feature-sets#sample-response), and [subscription](/terraform/enterprise/api-docs/subscriptions#sample-response) endpoints with the following properties: + - `run-task-limit` + - `run-task-workspace-limit` + - `run-task-mandatory-enforcement-limit` + - `policy-set-limit` + - `policy-limit` + - `policy-mandatory-enforcement-limit` + - `versioned-policy-set-limit` + +## 2023-04-25 + +- Add the `version-id` property to the response for the Create, List, and Update [Workspace Variables endpoints](/terraform/enterprise/api-docs/workspaces-variables). + +## 2023-03-30 + +- Add the `sort` query parameter to the Workspaces API's [list workspaces endpoint](/terraform/enterprise/api-docs/workspaces#list-workspaces). + +## 2023-03-24 + +- Update the [Variable Sets](/terraform/enterprise/api-docs/variable-sets) API endpoints to include assigning variable sets to projects. + +## 2023-03-20 + +- Add a names filter to the [Projects list API](/terraform/enterprise/api-docs/projects#query-parameters) to allow fetching a list of projects by name. + +## 2023-03-13 + +- Update [Project Team Access API](/terraform/enterprise/api-docs/project-team-access) to include additional Project roles. +- Update [Permissions](/terraform/enterprise/users-teams-organizations/permissions) to reflect the decoupling of projects and workspaces in the Organization Permissions UI. + +## 2023-03-08 + +- Introduced the [GitHub App Installation APIs](/terraform/enterprise/api-docs/github-app-installations). +- Updated [Workspaces API](/terraform/enterprise/api-docs/workspaces) to accept `vcs-repo.github-app-installation-id` to connect a workspace to a GitHub App Installation. +- Updated [Registry Module API](/terraform/enterprise/api-docs/private-registry/modules) to accept `vcs-repo.github-app-installation-id` to connect to a GitHub App Installation. +- Updated [Policy Sets API](/terraform/enterprise/api-docs/policy-sets) to accept `vcs-repo.github-app-installation-id` to connect to a GitHub App Installation. + +## 2023-02-16 + +- Add `manage-membership` to the organization access settings of the [Team API](/terraform/enterprise/api-docs/teams). + +## 2023-02-03 + +- Updated the [List Runs API](/terraform/enterprise/api-docs/run#list-runs-in-a-workspace) to note that the filter query parameters accept comma-separated lists. + +## 2023-01-18 + +- Updated the [Team API](/terraform/enterprise/api-docs/teams) to include the `read-workspaces` and `read-projects` permissions which grants teams view access to workspaces and projects. + +## 2023-01-17 + +- Add [Projects API](/terraform/enterprise/api-docs/projects) for creating, updating and deleting projects. +- Add [Project Team Access API](/terraform/enterprise/api-docs/project-team-access) for managing access for teams to individual projects. +- Update [Workspaces API](/terraform/enterprise/api-docs/workspaces) to include examples of creating a workspace in a project and moving a workspace between projects. +- Update [List Teams API](/terraform/enterprise/api-docs/teams#query-parameters) to accept a search parameter `q`, so that teams can be searched by name. + +## 2023-01-12 + +- Added new rollback to previous state endpoint to [State Versions API](/terraform/enterprise/api-docs/state-versions) + +## 2022-12-22 + +- Updated [Safe Delete a workspace](/terraform/enterprise/api-docs/workspaces#safe-delete-a-workspace) to fix HTTP verb as `POST`. + +## 2022-11-18 + +- Update [Policies API](/terraform/enterprise/api-docs/policies) to fix policy enforcement level defaults. Enforcement level is a required field, so no defaults are available. + +## 2022-11-03 + +- Update [Policy Checks](/terraform/enterprise/api-docs/policy-checks) to fix policy set outcome return data type. + +### 2022-10-17 + +- Updated the [Organizations API](/terraform/enterprise/api-docs/organizations) with the `allow-force-delete-workspaces`, which controls whether workspace administrators can delete workspaces with resources under management. +- Updated the [Workspaces API](/terraform/enterprise/api-docs/workspaces) with a safe delete endpoint that guards against deleting workspaces that are managing resources. + +### 2022-10-12 + +- Update [Policy Checks](/terraform/enterprise/api-docs/policy-checks) with result counts and support for filtering policy set outcomes. +- Update [Team Membership API](/terraform/enterprise/api-docs/team-members) to include adding and removing users from teams using organization membership ID. + + + +### 2022-10-06 + +- Updated the [Policies API](/terraform/enterprise/api-docs/policies) with support for Open Policy Agent (OPA) policies. +- Update [Policy Sets](/terraform/enterprise/api-docs/policy-sets) with support for OPA policy sets. +- Updated [Policy Checks](/terraform/enterprise/api-docs/policy-checks) to add support for listing policy evaluations and policy set outcomes. +- Update [Run Tasks Stage](/terraform/enterprise/api-docs/run-tasks/run-task-stages-and-results) to include the new `policy_evaluations` attribute in the output. + + +### 2022-09-21 + +- Update [State Versions](/terraform/enterprise/api-docs/state-versions#create) with optional `json-state-outputs` and `json-state` attributes, which are base-64 encoded external JSON representations of the terraform state. The read-only `hosted-json-state-download-url` attribute links to this version of the state file when available. +- Update [State Version Outputs](/terraform/enterprise/api-docs/state-version-outputs) with a `detailed-type` attribute, which refines the output with the precise Terraform type. + +### 2022-07-26 + +- Updated the [Run status list](/terraform/enterprise/api-docs/run#run-states) with `fetching`, `queuing`, `pre_plan_running` and `pre_plan_completed` +- Update [Run Tasks](/terraform/enterprise/api-docs/run-tasks.mdx) to include the new `stages` attribute when attaching or updating workspace tasks. +- Updated [Run Tasks Integration](/terraform/enterprise/api-docs/run-tasks/run-tasks-integration) to specify the different request payloads for different stages. + +### 2022-06-23 + + + +- Added the [Assessments](/terraform/enterprise/api-docs/assessments). + +- Updated [Workspace](/terraform/enterprise/api-docs/workspaces#create-a-workspace) and + [Notification Configurations](/terraform/enterprise/api-docs/notification-configurations#notification-triggers) to account for assessments. + + +- Added new query parameter(s) to [List Runs endpoint](/terraform/enterprise/api-docs/run#list-runs-in-a-workspace). + +### 2022-06-21 + +- Updated [Admin Organizations](/terraform/enterprise/api-docs/admin/organizations) endpoints with new `workspace-limit` setting. This is available in Terraform Enterprise v202207-1 and later. +- Updated [List Agent Pools](/terraform/enterprise/api-docs/agents#list-agent-pools) to accept a filter parameter `filter[allowed_workspaces][name]` so that agent pools can be filtered by name of an associated workspace. The given workspace must be allowed to use the agent pool. Refer to [Scoping Agent Pools to Specific Workspaces](/terraform/cloud-docs/agents#scope-an-agent-pool-to-specific-workspaces). +- Added new `organization-scoped` attribute and `allowed-workspaces` relationship to the request/response body of the below endpoints. This is available in Terraform Enterprise v202207-1 and later. + - [Show an Agent Pool](/terraform/enterprise/api-docs/agents#show-an-agent-pool) + - [Create an Agent Pool](/terraform/enterprise/api-docs/agents#create-an-agent-pool) + - [Update an Agent Pool](/terraform/enterprise/api-docs/agents#update-an-agent-pool) + +### 2022-06-17 + +- Updated [Creating a Run Task](/terraform/enterprise/workspaces/settings/run-tasks#creating-a-run-task) section to include the new description information for the run task to be configured. +- Update [Run Tasks](/terraform/enterprise/api-docs/run-tasks.mdx) to include the new description attribute. + +### 2022-06-09 + +- Updated [List Agent Pools](/terraform/enterprise/api-docs/agents#list-agent-pools) to accept a search parameter `q`, so that agent pools can be searched by `name`. This is available in Terraform Enterprise v202207-1 and later. +- Fixed [List Workspaces](/terraform/enterprise/api-docs/workspaces#list-workspaces) to add missing `search[tags]` query parameter. +- Updated [List Workspaces](/terraform/enterprise/api-docs/workspaces#list-workspaces) to add new `search[exclude_tags]` query parameter. This is available in Terraform Enterprise v202207-1 and later. + +### 2022-05-11 + +- Updated Run Tasks permission to the following endpoints: + - [Organizations](/terraform/enterprise/api-docs/organizations#list-organizations). + - [Team Access](/terraform/enterprise/api-docs/team-access#list-team-access-to-a-workspace). + - [Teams](/terraform/enterprise/api-docs/teams#list-teams). + +### 2022-05-04 + +- Updated [Feature Sets](/terraform/enterprise/api-docs/feature-sets#list-feature-sets) to add new `run-tasks` attribute. + +### 2022-05-03 + +- Added Run Tasks permission to the following endpoints: + - [Organizations](/terraform/enterprise/api-docs/organizations#list-organizations) + - [Workspaces](/terraform/enterprise/api-docs/workspaces#show-workspace) + +### 2022-04-29 + +- Updated [Run Tasks Integration](/terraform/enterprise/api-docs/run-tasks/run-tasks-integration) to specify the allowed `status` attribute values. +- Updated [Organization Memberships](/terraform/enterprise/api-docs/organization-memberships#query-parameters) to add new `filter[email]` query parameter. +- Updated [Teams](/terraform/enterprise/api-docs/teams#query-parameters) to add new `filter[names]` query parameter. + +### 2022-04-04 + +- Added the [Run Tasks Integration](/terraform/enterprise/api-docs/run-tasks/run-tasks-integration) endpoint. + +### 2022-03-14 + +- Added the [Download Configuration Files](/terraform/enterprise/api-docs/configuration-versions#download-configuration-files) endpoints. + +### 2022-03-11 + +- Introduced [Archiving Configuration Versions](/terraform/enterprise/workspaces/configurations#archiving-configuration-versions). + - Updated [Configuration Versions](/terraform/enterprise/api-docs/configuration-versions#attributes) to add new `fetching` and `archived` states. + - Updated [Runs](/terraform/enterprise/api-docs/run#attributes) to add new `fetching` state. + - Added the [Archive a Configuration Version](/terraform/enterprise/api-docs/configuration-versions#archive-a-configuration-version) endpoint. + +### 2022-02-25 + +- Updated [Workspace Run Tasks](/terraform/enterprise/api-docs/run-tasks/run-tasks#show-a-run-task) to add new `enabled`attribute. + +### 2022-02-28 + +- Introduced the [Registry Providers](/terraform/enterprise/api-docs/private-registry/providers) endpoints to manage private providers for a private registry. + +### 2021-12-09 + +- Added `variables` field for POST /runs and the run resource, enabling run-specific variable values. + +### 2021-12-03 + +- OAuth API updated to handle `secret` and `rsa_public_key` fields for POST/PUT. + +### 2021-11-17 + +- Added pagination support to the following endpoints: + - [Feature Sets](/terraform/enterprise/api-docs/feature-sets#list-feature-sets) - `GET /feature-sets` + - [Notification Configurations](/terraform/enterprise/api-docs/notification-configurations#list-notification-configurations) - `GET /workspaces/:workspace_id/notification-configurations` + - [Oauth Clients](/terraform/enterprise/api-docs/oauth-clients#list-oauth-clients) - `GET /organizations/:organization_name/oauth-clients` + - [Oauth Tokens](/terraform/enterprise/api-docs/oauth-tokens#list-oauth-tokens) - `GET /oauth-clients/:oauth_client_id/oauth-tokens` + - [Organization Feature Sets](/terraform/enterprise/api-docs/feature-sets#list-feature-sets-for-organization) - `GET /organizations/:organization_name/feature-sets` + - [Organizations](/terraform/enterprise/api-docs/organizations#list-organizations) - `GET /organizations` + - [Policy Checks](/terraform/enterprise/api-docs/policy-checks#list-policy-checks) - `GET /runs/:run_id/policy-checks` + - [Policy Set Parameters](/terraform/enterprise/api-docs/policy-set-params#list-parameters) - `GET /policy-sets/:policy_set_id/parameters` + - [SSH Keys](/terraform/enterprise/api-docs/ssh-keys#list-ssh-keys) - `GET /organizations/:organization_name/ssh-keys` + - [User Tokens](/terraform/enterprise/api-docs/user-tokens#list-user-tokens) - `GET /users/:user_id/authentication-tokens` + +### 2021-11-11 + +- Introduced the [Variable Sets](/terraform/enterprise/api-docs/variable-sets) endpoints for viewing and administering Variable Sets + +### 2021-11-18 + +- Introduced the [Registry Providers](/terraform/enterprise/api-docs/private-registry/providers) endpoint to manage public providers for a + private registry. These endpoints will be available in the following Terraform Enterprise Release: `v202112-1` + +### 2021-09-12 + +- Added [Run Tasks Stages and Results](/terraform/enterprise/api-docs/run-tasks/run-task-stages-and-results) endpoint. + +### 2021-08-18 + +- Introduced the [State Version Outputs](/terraform/enterprise/api-docs/state-versions) endpoint to retrieve the Outputs for a + given State Version + +### 2021-08-11 + +- **BREAKING CHANGE:** Security fix to [Configuration versions](/terraform/enterprise/api-docs/configuration-versions): upload-url attribute for [uploading configuration files](/terraform/enterprise/api-docs/configuration-versions#upload-configuration-files) is now only available on the create response. + +### 2021-07-30 + +- Introduced Workspace Tagging + - Updated [Workspaces](/terraform/enterprise/api-docs/workspaces): + - added `tag-names` attribute. + - added `POST /workspaces/:workspace_id/relationships/tags` + - added `DELETE /workspaces/:workspace_id/relationships/tags` + - Added [Organization Tags](/terraform/enterprise/api-docs/organization-tags). + - Added `tags` attribute to [`tfrun`](/terraform/enterprise/policy-enforcement/sentinel/import/tfrun) + +### 2021-07-19 + +- [Notification configurations](/terraform/enterprise/api-docs/notification-configurations): Gave organization tokens permission to create and manage notification configurations. + +### 2021-07-09 + +- [State versions](/terraform/enterprise/api-docs/state-versions): Fixed the ID format for the workspace relationship of a state version. Previously, the reported ID was unusable due to a bug. +- [Workspaces](/terraform/enterprise/api-docs/workspaces): Added `locked_by` as an includable related resource. +- Added [Run Tasks](/terraform/enterprise/api-docs/run-tasks/run-tasks) API endpoint. + +### 2021-06-8 + +- Updated [Registry Module APIs](/terraform/enterprise/api-docs/private-registry/modules). + - added `registry_name` scoped APIs. + - added `organization_name` scoped APIs. + - added [Module List API](/terraform/enterprise/api-docs/private-registry/modules#list-registry-modules-for-an-organization). + - updated [Module Delete APIs](/terraform/enterprise/api-docs/private-registry/modules#delete-a-module) (see deprecation note below). + - **CLOUD**: added public registry module related APIs. +- **DEPRECATION**: The following [Registry Module APIs](/terraform/enterprise/api-docs/private-registry/modules) have been replaced with newer apis and will be removed in the future. + - The following endpoints to delete modules are replaced with [Module Delete APIs](/terraform/enterprise/api-docs/private-registry/modules#delete-a-module) + - `POST /registry-modules/actions/delete/:organization_name/:name/:provider/:version` replaced with `DELETE /organizations/:organization_name/registry-modules/:registry_name/:namespace/:name/:provider/:version` + - `POST /registry-modules/actions/delete/:organization_name/:name/:provider` replaced with `DELETE /organizations/:organization_name/registry-modules/:registry_name/:namespace/:name/:provider` + - `POST /registry-modules/actions/delete/:organization_name/:name` replaced with `DELETE /organizations/:organization_name/registry-modules/:registry_name/:namespace/:name` + - `POST /registry-modules` replaced with [`POST /organizations/:organization_name/registry-modules/vcs`](/terraform/enterprise/api-docs/private-registry/modules#publish-a-private-module-from-a-vcs) + - `POST /registry-modules/:organization_name/:name/:provider/versions` replaced with [`POST /organizations/:organization_name/registry-modules/:registry_name/:namespace/:name/:provider/versions`](/terraform/enterprise/api-docs/private-registry/modules#create-a-module-version) + - `GET /registry-modules/show/:organization_name/:name/:provider` replaced with [`GET /organizations/:organization_name/registry-modules/:registry_name/:namespace/:name/:provider`](/terraform/enterprise/api-docs/private-registry/modules#get-a-module) + +### 2021-05-27 + +- **CLOUD**: [Agents](/terraform/enterprise/api-docs/agents): added [delete endpoint](/terraform/enterprise/api-docs/agents#delete-an-agent). + +### 2021-05-19 + +- [Runs](/terraform/enterprise/api-docs/run): added `refresh`, `refresh-only`, and `replace-addrs` attributes. + +### 2021-04-16 + +- Introduced [Controlled Remote State Access](https://www.hashicorp.com/blog/announcing-controlled-remote-state-access-for-terraform-cloud-and-enterprise). + - [Admin Settings](/terraform/enterprise/api-docs/admin/settings): added `default-remote-state-access` attribute. + - [Workspaces](/terraform/enterprise/api-docs/workspaces): + - added `global-remote-state` attribute. + - added [Remote State Consumers](/terraform/enterprise/api-docs/workspaces#get-remote-state-consumers) relationship. + +### 2021-04-13 + +- [Teams](/terraform/enterprise/api-docs/teams): added `manage-policy-overrides` property to the `organization-access` attribute. + +### 2021-03-23 + +- **ENTERPRISE**: `v202103-1` Introduced [Share Modules Across Organizations with Terraform Enterprise](https://www.hashicorp.com/blog/share-modules-across-organizations-terraform-enterprise). + - [Admin Organizations](/terraform/enterprise/api-docs/admin/organizations): + - added new query parameters to [List all Organizations endpoint](/terraform/enterprise/api-docs/admin/organizations#query-parameters) + - added module-consumers link in `relationships` response + - added [update module consumers endpoint](/terraform/enterprise/api-docs/admin/organizations#update-an-organization-39-s-module-consumers) + - added [list module consumers endpoint](/terraform/enterprise/api-docs/admin/organizations#list-module-consumers-for-an-organization) + - [Organizations](/terraform/enterprise/api-docs/organizations): added [Module Producers](/terraform/enterprise/api-docs/organizations#show-module-producers) + - **DEPRECATION**: [Admin Module Sharing](/terraform/enterprise/api-docs/admin/module-sharing): is replaced with a new JSON::Api compliant [endpoint](/terraform/enterprise/api-docs/admin/organizations#update-an-organization-39-s-module-consumers) + +### 2021-03-18 + +- **CLOUD**: Introduced [New Workspace Overview for Terraform Cloud](https://www.hashicorp.com/blog/new-workspace-overview-for-terraform-cloud). + - [Workspaces](/terraform/enterprise/api-docs/workspaces): + - added `resource-count` and `updated-at` attributes. + - added [performance attributes](/terraform/enterprise/api-docs/workspaces#workspace-performance-attributes) (`apply-duration-average`, `plan-duration-average`, `policy-check-failures`, `run-failures`, `workspaces-kpis-run-count`). + - added `readme` and `outputs` [related resources](/terraform/enterprise/api-docs/workspaces#available-related-resources). + - [Team Access](/terraform/enterprise/api-docs/team-access): updated to support pagination. + +### 2021-03-11 + +- Added [VCS Events](/terraform/enterprise/api-docs/vcs-events), limited to GitLab.com connections. + +### 2021-03-08 + +- [Workspaces](/terraform/enterprise/api-docs/workspaces): added `current_configuration_version` and `current_configuration_version.ingress_attributes` as includable related resources. diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/comments.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/comments.mdx new file mode 100644 index 0000000000..2e96414328 --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/comments.mdx @@ -0,0 +1,227 @@ +--- +page_title: /comments API reference for Terraform Enterprise +description: >- + Use the Terraform Enterprise API's `/comments` endpoint to create and read + comments on Terraform runs. +source: terraform-docs-common +--- + +[200]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/200 + +[201]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/201 + +[202]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/202 + +[204]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/204 + +[307]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/307 + +[400]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/400 + +[401]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/401 + +[403]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/403 + +[404]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404 + +[409]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/409 + +[412]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/412 + +[422]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/422 + +[429]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/429 + +[500]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500 + +[504]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/504 + +[JSON API document]: /terraform/enterprise/api-docs#json-api-documents + +[JSON API error object]: https://jsonapi.org/format/#error-objects + +# Comments API reference + +Comments allow users to leave feedback or record decisions about a run. + +## List Comments for a Run + +`GET /runs/:id/comments` + +| Parameter | Description | +| --------- | ------------------ | +| `id` | The ID of the run. | + +### Sample Request + +```shell +$ curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + https://app.terraform.io/api/v2/runs/run-KTuq99JSzgmDSvYj/comments +``` + +### Sample Response + +```json +{ + "data": [ + { + "id": "wsc-JdFX3u8o114F4CWf", + "type": "comments", + "attributes": { + "body": "A comment body" + }, + "relationships": { + "run-event": { + "data": { + "id": "re-fo1YXZ8W5bp5GBKM", + "type": "run-events" + }, + "links": { + "related": "/api/v2/run-events/re-fo1YXZ8W5bp5GBKM" + } + } + }, + "links": { + "self": "/api/v2/comments/wsc-JdFX3u8o114F4CWf" + } + }, + { + "id": "wsc-QdhSPFTNoCTpfafp", + "type": "comments", + "attributes": { + "body": "Another comment body" + }, + "relationships": { + "run-event": { + "data": { + "id": "re-fo1YXZ8W5bp5GBKM", + "type": "run-events" + }, + "links": { + "related": "/api/v2/run-events/re-fo1YXZ8W5bp5GBKM" + } + } + }, + "links": { + "self": "/api/v2/comments/wsc-QdhSPFTNoCTpfafp" + } + } + ] +} +``` + +## Show a Comment + +`GET /comments/:id` + +| Parameter | Description | +| --------- | ---------------------- | +| `id` | The ID of the comment. | + +### Sample Request + +```shell +$ curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + https://app.terraform.io/api/v2/comments/wsc-gTFq83JSzjmAvYj +``` + +### Sample Response + +```json +{ + "data": { + "id": "wsc-gTFq83JSzjmAvYj", + "type": "comments", + "attributes": { + "body": "Another comment" + }, + "relationships": { + "run-event": { + "data": { + "id": "re-8RB5ZaFrDanG2hGY", + "type": "run-events" + }, + "links": { + "related": "/api/v2/run-events/re-8RB5ZaFrDanG2hGY" + } + } + }, + "links": { + "self": "/api/v2/comments/wsc-gTFq83JSzjmAvYj" + } + } +} +``` + +## Create Comment + +`POST /runs/:id/comments` + +| Parameter | Description | +| --------- | ------------------ | +| `id` | The ID of the run. | + +### Request Body + +This POST endpoint requires a JSON object with the following properties as the request payload. + +| Key Path | Type | Default | Description | +| ---------------------- | ------ | ------- | ------------------------ | +| `data.type` | string | | Must be `"comments"`. | +| `data.attributes.body` | string | | The body of the comment. | + +### Sample Payload + +```json +{ + "data": { + "attributes": { + "body": "A comment about the run", + }, + "type": "comments" + } +} +``` + +### Sample Request + +```shell +$ curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request POST \ + --data @payload.json \ + https://app.terraform.io/api/v2/runs/run-KTuq99JSzgmDSvYj/comments +``` + +### Sample Response + +```json +{ + "data": { + "id": "wsc-oRiShushpgLU4JD2", + "type": "comments", + "attributes": { + "body": "A comment about the run" + }, + "relationships": { + "run-event": { + "data": { + "id": "re-E3xsBX11F1fbm2zV", + "type": "run-events" + }, + "links": { + "related": "/api/v2/run-events/re-E3xsBX11F1fbm2zV" + } + } + }, + "links": { + "self": "/api/v2/comments/wsc-oRiShushpgLU4JD2" + } + } +} +``` diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/configuration-versions.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/configuration-versions.mdx new file mode 100644 index 0000000000..8b5f077525 --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/configuration-versions.mdx @@ -0,0 +1,561 @@ +--- +page_title: /configuration-versions API reference for Terraform Enterprise +description: >- + Use the Terraform Enterprise API's `/configuration-versions` endpoint to list, + show, and create a configuration version and its files within a workspace. +source: terraform-docs-common +--- + +[200]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/200 + +[201]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/201 + +[202]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/202 + +[204]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/204 + +[302]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/302 + +[400]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/400 + +[401]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/401 + +[403]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/403 + +[404]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404 + +[409]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/409 + +[412]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/412 + +[422]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/422 + +[429]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/429 + +[500]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500 + +[504]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/504 + +[JSON API document]: /terraform/enterprise/api-docs#json-api-documents + +[JSON API error object]: https://jsonapi.org/format/#error-objects + +# Configuration versions API reference + +-> **Note:** Before working with the runs or configuration versions APIs, read the [API-driven run workflow](/terraform/enterprise/run/api) page, which includes both a full overview of this workflow and a walkthrough of a simple implementation of it. + +A configuration version (`configuration-version`) is a resource used to reference the uploaded configuration files. It is associated with the run to use the uploaded configuration files for performing the plan and apply. + +You need read runs permission to list and view configuration versions for a workspace, and you need queue plans permission to create new configuration versions. Refer to the [permissions](/terraform/enterprise/users-teams-organizations/permissions#general-workspace-permissions) documentation for more details. + +[permissions-citation]: #intentionally-unused---keep-for-maintainers + +## Attributes + +### Configuration Version States + +The configuration version state is found in `data.attributes.status`, and you can reference the following list of possible states. + +A configuration version created through the API or CLI can only be used in runs if it is in an `uploaded` state. A configuration version created through a linked VCS repository may also be used in runs if it is in an `archived` state. + +| State | Description | | +| ---------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | - | +| `pending` | The initial status of a configuration version after it has been created. Pending configuration versions cannot be used to create new runs. | | +| `fetching` | For configuration versions created from a commit to a connected VCS repository, HCP Terraform is currently fetching the associated files from VCS. | | +| `uploaded` | The configuration version is fully processed and can be used in runs. | | +| `archived` | All immediate runs are complete and HCP Terraform has discarded the files associated with this configuration version. If the configuration version was created through a connected VCS repository, it can still be used in new runs. In those cases, HCP Terraform will re-fetch the files from VCS. | | +| `errored` | HCP Terraform could not process this configuration version, and it cannot be used to create new runs. You can try again by pushing a new commit to your linked VCS repository or creating a new configuration version with the API or CLI. | | +| `backing_data_soft_deleted` | Indicates that the configuration version's backing data is marked for garbage collection. If no action is taken, the backing data associated with this configuration version is permanently deleted after a set number of days. You can restore the backing data associated with the configuration version before it is permanently deleted. | | +| `backing_data_permanently_deleted` | The configuration version's backing data has been permanently deleted and can no longer be restored. | | + +## List Configuration Versions + +`GET /workspaces/:workspace_id/configuration-versions` + +| Parameter | Description | +| --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `:workspace_id` | The ID of the workspace to list configurations from. Obtain this from the [workspace settings](/terraform/enterprise/workspaces/settings) or the [Show Workspace](/terraform/enterprise/api-docs/workspaces#show-workspace) endpoint. | + +### Query Parameters + +This endpoint supports pagination [with standard URL query parameters](/terraform/enterprise/api-docs#query-parameters). Remember to percent-encode `[` as `%5B` and `]` as `%5D` if your tooling doesn't automatically encode URLs. + +| Parameter | Description | +| -------------- | -------------------------------------------------------------------------------------- | +| `page[number]` | **Optional.** If omitted, the endpoint will return the first page. | +| `page[size]` | **Optional.** If omitted, the endpoint will return 20 configuration versions per page. | + +### Sample Request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request GET \ + https://app.terraform.io/api/v2/workspaces/ws-2Qhk7LHgbMrm3grF/configuration-versions +``` + +### Sample Response + +```json +{ + "data": [ + { + "id": "cv-ntv3HbhJqvFzamy7", + "type": "configuration-versions", + "attributes": { + "error": null, + "error-message": null, + "source": "gitlab", + "speculative":false, + "status": "uploaded", + "status-timestamps": {}, + "provisional": false + }, + "relationships": { + "ingress-attributes": { + "data": { + "id": "ia-i4MrTxmQXYxH2nYD", + "type": "ingress-attributes" + }, + "links": { + "related": + "/api/v2/configuration-versions/cv-ntv3HbhJqvFzamy7/ingress-attributes" + } + } + }, + "links": { + "self": "/api/v2/configuration-versions/cv-ntv3HbhJqvFzamy7", + "download": "/api/v2/configuration-versions/cv-ntv3HbhJqvFzamy7/download" + } + } + ] +} +``` + +## Show a Configuration Version + +`GET /configuration-versions/:configuration-id` + +| Parameter | Description | +| ------------------- | ------------------------------------ | +| `:configuration-id` | The id of the configuration to show. | + +### Sample Request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request GET \ + https://app.terraform.io/api/v2/configuration-versions/cv-ntv3HbhJqvFzamy7 +``` + +### Sample Response + +```json +{ + "data": { + "id": "cv-ntv3HbhJqvFzamy7", + "type": "configuration-versions", + "attributes": { + "error": null, + "error-message": null, + "source": "gitlab", + "speculative":false, + "status": "uploaded", + "status-timestamps": {}, + "provisional": false + }, + "relationships": { + "ingress-attributes": { + "data": { + "id": "ia-i4MrTxmQXYxH2nYD", + "type": "ingress-attributes" + }, + "links": { + "related": + "/api/v2/configuration-versions/cv-ntv3HbhJqvFzamy7/ingress-attributes" + } + } + }, + "links": { + "self": "/api/v2/configuration-versions/cv-ntv3HbhJqvFzamy7", + "download": "/api/v2/configuration-versions/cv-ntv3HbhJqvFzamy7/download" + } + } +} +``` + +## Show a Configuration Version's Commit Information + +An ingress attributes resource (`ingress-attributes`) is used to reference commit information for configuration versions created in a workspace with a VCS repository. + +`GET /configuration-versions/:configuration-id/ingress-attributes` + +| Parameter | Description | +| ------------------- | ------------------------------------ | +| `:configuration-id` | The id of the configuration to show. | + +Ingress attributes can also be fetched as part of a query to a particular configuration version via `include`: + +`GET /configuration-versions/:configuration-id?include=ingress-attributes` + +| Parameter | Description | +| ------------------- | ------------------------------------ | +| `:configuration-id` | The id of the configuration to show. | + + + +### Sample Request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request GET \ + https://app.terraform.io/api/v2/configuration-versions/cv-TrHjxIzad9Ae9i8x/ingress-attributes +``` + +### Sample Response + +```json +{ + "data": { + "id": "ia-zqHjxJzaB9Ae6i9t", + "type": "ingress-attributes", + "attributes": { + "branch": "add-cool-stuff", + "clone-url": "https://github.com/hashicorp/foobar.git", + "commit-message": "Adding really cool infrastructure", + "commit-sha": "1e1c1018d1bbc0b8517d072718e0d87c1a0eda95", + "commit-url": "https://github.com/hashicorp/foobar/commit/1e1c1018d1bbc0b8517d072718e0d87c1a0eda95", + "compare-url": "https://github.com/hashicorp/foobar/pull/163", + "identifier": "hashicorp/foobar", + "is-pull-request": true, + "on-default-branch": false, + "pull-request-number": 163, + "pull-request-url": "https://github.com/hashicorp/foobar/pull/163", + "pull-request-title": "Adding really cool infrastructure", + "pull-request-body": "These are changes to add really cool stuff. We should absolutely merge this.", + "tag": null, + "sender-username": "chrisarcand", + "sender-avatar-url": "https://avatars.githubusercontent.com/u/2430490?v=4", + "sender-html-url": "https://github.com/chrisarcand" + }, + "relationships": { + "created-by": { + "data": { + "id": "user-PQk2Z3dfXAax18P6s", + "type": "users" + }, + "links": { + "related": "/api/v2/ingress-attributes/ia-zqHjxJzaB9Ae6i9t/created-by" + } + } + }, + "links": { + "self": "/api/v2/ingress-attributes/ia-zqHjxJzaB9Ae6i9t" + } + } +} +``` + +## Create a Configuration Version + +`POST /workspaces/:workspace_id/configuration-versions` + +| Parameter | Description | +| --------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `:workspace_id` | The ID of the workspace to create the new configuration version in. Obtain this from the [workspace settings](/terraform/enterprise/workspaces/settings) or the [Show Workspace](/terraform/enterprise/api-docs/workspaces#show-workspace) endpoint. | + +-> **Note:** This endpoint cannot be accessed with [organization tokens](/terraform/enterprise/users-teams-organizations/api-tokens#organization-api-tokens). You must access it with a [user token](/terraform/enterprise/users-teams-organizations/users#api-tokens) or [team token](/terraform/enterprise/users-teams-organizations/api-tokens#team-api-tokens). + +### Request Body + +This POST endpoint requires a JSON object with the following properties as a request payload. + +Properties without a default value are required. + +| Key path | Type | Default | Description | +| --------------------------------- | ------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `data.attributes.auto-queue-runs` | boolean | true | When true, runs are queued automatically when the configuration version is uploaded. | +| `data.attributes.speculative` | boolean | false | When true, this configuration version may only be used to create runs which are speculative, that is, can neither be confirmed nor applied. | +| `data.attributes.provisional` | boolean | false | When true, this configuration version does not immediately become the workspace current configuration version. If the associated run is applied, it then becomes the current configuration version unless a newer one exists. | + +### Sample Payload + +```json +{ + "data": { + "type": "configuration-versions", + "attributes": { + "auto-queue-runs": true + } + } +} +``` + +### Sample Request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request POST \ + --data @payload.json \ + https://app.terraform.io/api/v2/workspaces/ws-2Qhk7LHgbMrm3grF/configuration-versions +``` + +### Sample Response + +```json +{ + "data": { + "id": "cv-UYwHEakurukz85nW", + "type": "configuration-versions", + "attributes": { + "auto-queue-runs": true, + "error": null, + "error-message": null, + "source": "tfe-api", + "speculative":false, + "status": "pending", + "status-timestamps": {}, + "upload-url": + "https://archivist.terraform.io/v1/object/9224c6b3-2e14-4cd7-adff-ed484d7294c2", + "provisional": false + }, + "relationships": { + "ingress-attributes": { + "data": null, + "links": { + "related": + "/api/v2/configuration-versions/cv-UYwHEakurukz85nW/ingress-attributes" + } + } + }, + "links": { "self": "/api/v2/configuration-versions/cv-UYwHEakurukz85nW" } + } +} +``` + +### Configuration Files Upload URL + +Once a configuration version is created, use the `upload-url` attribute to [upload configuration files](#upload-configuration-files) associated with that version. The `upload-url` attribute is only provided in the response when creating configuration versions. + +## Upload Configuration Files + +-> **Note**: If `auto-queue-runs` was either not provided or set to `true` during creation of the configuration version, a run using this configuration version will be automatically queued on the workspace. If `auto-queue-runs` was set to `false` explicitly, then it is necessary to [create a run on the workspace](/terraform/enterprise/api-docs/run#create-a-run) manually after the configuration version is uploaded. + +`PUT https://archivist.terraform.io/v1/object/` + +**The URL is provided in the `upload-url` attribute when creating a `configuration-versions` resource. After creation, the URL is hidden on the resource and no longer available.** + +### Sample Request + +**@filename is the name of configuration file you wish to upload.** + +```shell +curl \ + --header "Content-Type: application/octet-stream" \ + --request PUT \ + --data-binary @filename \ + https://archivist.terraform.io/v1/object/4c44d964-eba7-4dd5-ad29-1ece7b99e8da +``` + +## Archive a Configuration Version + +`POST /configuration-versions/:configuration_version_id/actions/archive` + +| Parameter | Description | +| -------------------------- | ----------------------------------------------- | +| `configuration_version_id` | The ID of the configuration version to archive. | + +This endpoint notifies HCP Terraform to discard the uploaded `.tar.gz` file associated with this configuration version. This endpoint can only archive configuration versions that were created with the API or CLI, are in an `uploaded` [state](#configuration-version-states), have no runs in progress, and are not the current configuration version for any workspace. Otherwise, calling this endpoint will result in an error. + +HCP Terraform automatically archives configuration versions created through VCS when associated runs are complete and then re-fetches the files for subsequent runs. + +| Status | Response | Reason(s) | +| ------- | ------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- | +| [202][] | none | Successfully initiated the archive process. | +| [409][] | [JSON API error object][] | Configuration version was in a non-archivable state or the configuration version was created with VCS and cannot be archived through the API. | +| [404][] | [JSON API error object][] | Configuration version was not found or user not authorized. | + +### Request Body + +This POST endpoint does not take a request body. + +### Sample Request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request POST \ + https://app.terraform.io/api/v2/configuration-versions/cv-ntv3HbhJqvFzamy7/actions/archive +``` + +## Download Configuration Files + +`GET /configuration-versions/:configuration_version_id/download` + +| Parameter | Description | +| -------------------------- | ------------------------------------------------ | +| `configuration_version_id` | The ID of the configuration version to download. | + +`GET /runs/:run_id/configuration-version/download` + +| Parameter | Description | +| --------- | ------------------------------------------------------------------- | +| `run_id` | The ID of the run whose configuration version should be downloaded. | + +These endpoints generate a temporary URL to the location of the configuration version in a `.tar.gz` archive, and then redirect to that link. If using a client that can follow redirects, you can use these endpoints to save the `.tar.gz` archive locally without needing to save the temporary URL. These endpoints will return an error if attempting to download a configuration version that is not in an `uploaded` [state](#configuration-version-states). + +| Status | Response | Reason | +| ------- | ------------------------- | --------------------------------------------------------------------------------------------------------------------------- | +| [302][] | HTTP Redirect | Configuration version found and temporary download URL generated | +| [404][] | [JSON API error object][] | Configuration version not found, or specified configuration version is not uploaded, or user unauthorized to perform action | + +### Sample Request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --location \ + https://app.terraform.io/api/v2/configuration-versions/cv-C6Py6WQ1cUXQX2x2/download \ + > export.tar.gz +``` + +## Mark a Configuration Version for Garbage Collection + + +This endpoint is exclusive to Terraform Enterprise, and not available in HCP Terraform. Learn more about Terraform Enterprise. + + +`POST /api/v2/configuration-versions/:configuration-id/actions/soft_delete_backing_data` + +| Parameter | Description | +| ------------------- | --------------------------------------------------- | +| `:configuration-id` | The ID of the configuration version to soft delete. | + +This endpoint directs Terraform Enterprise to _soft delete_ the backing files associated with the configuration version. Soft deletion refers to marking the configuration version for garbage collection. Terraform permanently deletes configuration versions marked for soft deletion after a set number of days unless the configuration version is restored. Once a configuration version is soft deleted, any attempts to read the configuration version will fail. Refer to [Configuration Version States](#configuration-version-states) for information about all data states. + +This endpoint can only soft delete configuration versions that meet the following criteria: + +- Were created using the API or CLI, +- are in an [`uploaded` state](#configuration-version-states), +- and are not the current configuration version. + +Otherwise, the endpoint returns an error. + +| Status | Response | Reason(s) | +| ------- | ------------------------- | ------------------------------------------------------------------------------------------------------------------------- | +| [200][] | none | Terraform successfully marked the data for garbage collection. | +| [400][] | [JSON API error object][] | Terraform failed to transition the state to `backing_data_soft_deleted`. | +| [404][] | [JSON API error object][] | Terraform did not find the configuration version or the user is not authorized to modify the configuration version state. | + +### Request Body + +This POST endpoint does not take a request body. + +### Sample Request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request POST \ + https://app.terraform.io/api/v2/configuration-versions/cv-ntv3HbhJqvFzamy7/actions/soft_delete_backing_data + --data {"data": {"attributes": {"delete-older-than-n-days": 23}}} +``` + +## Restore Configuration Versions Marked for Garbage Collection + + +This endpoint is exclusive to Terraform Enterprise, and not available in HCP Terraform. Learn more about Terraform Enterprise. + + +`POST /api/v2/configuration-versions/:configuration-id/actions/restore_backing_data` + +| Parameter | Description | +| ------------------- | ---------------------------------------------------------------------------------------- | +| `:configuration-id` | The ID of the configuration version to restore back to its uploaded state if applicable. | + +This endpoint directs Terraform Enterprise to restore backing files associated with this configuration version. This endpoint can only restore delete configuration versions that meet the following criteria: + +- are not in a [`backing_data_permanently_deleted` state](#configuration-version-states). + +Otherwise, the endpoint returns an error. Terraform restores applicable configuration versions back to their `uploaded` state. + +| Status | Response | Reason(s) | +| ------- | ------------------------- | ------------------------------------------------------------------------------------------------------------------------- | +| [200][] | none | Terraform successfully initiated the restore process. | +| [400][] | [JSON API error object][] | Terraform failed to transition the state to `uploaded`. | +| [404][] | [JSON API error object][] | Terraform did not find the configuration version or the user is not authorized to modify the configuration version state. | + +### Request Body + +This POST endpoint does not take a request body. + +### Sample Request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request POST \ + https://app.terraform.io/api/v2/configuration-versions/cv-ntv3HbhJqvFzamy7/actions/restore_backing_data +``` + +## Permanently Delete a Configuration Version + + +This endpoint is exclusive to Terraform Enterprise, and not available in HCP Terraform. Learn more about Terraform Enterprise. + + +`POST /api/v2/configuration-versions/:configuration-id/actions/permanently_delete_backing_data` + +| Parameter | Description | +| ------------------- | ---------------------------------------------------------- | +| `:configuration-id` | The ID of the configuration version to permanently delete. | + +This endpoint directs Terraform Enterprise to permanently delete backing files associated with this configuration version. This endpoint can only permanently delete configuration versions that meet the following criteria: + +- Were created using the API or CLI, +- are in a [`backing_data_soft_deleted` state](#configuration-version-states), +- and are not the current configuration version. + +Otherwise, the endpoint returns an error. + +| Status | Response | Reason(s) | +| ------- | ------------------------- | ------------------------------------------------------------------------------------------------------------------------- | +| [200][] | none | Terraform successfully deleted the data permanently. | +| [400][] | [JSON API error object][] | Terraform failed to transition the state to `backing_data_permanently_deleted`. | +| [404][] | [JSON API error object][] | Terraform did not find the configuration version or the user is not authorized to modify the configuration version state. | + +### Request Body + +This POST endpoint does not take a request body. + +### Sample Request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request POST \ + https://app.terraform.io/api/v2/configuration-versions/cv-ntv3HbhJqvFzamy7/actions/permanently_delete_backing_data +``` + +## Available Related Resources + +The GET endpoints above can optionally return related resources, if requested with [the `include` query parameter](/terraform/enterprise/api-docs#inclusion-of-related-resources). The following resource types are available: + +| Resource Name | Description | +| -------------------- | ------------------------------------------------- | +| `ingress_attributes` | The commit information used in the configuration. | +| `run` | The run created by the configuration. | diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/cost-estimates.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/cost-estimates.mdx new file mode 100644 index 0000000000..a58a2b8481 --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/cost-estimates.mdx @@ -0,0 +1,98 @@ +--- +page_title: /cost-estimates API reference for Terraform Enterprise +description: >- + Use the Terraform Enterprise API's `/cost-estimates` endpoint to read a cost + estimate using its ID. +source: terraform-docs-common +--- + +[200]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/200 + +[201]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/201 + +[202]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/202 + +[204]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/204 + +[400]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/400 + +[401]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/401 + +[403]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/403 + +[404]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404 + +[409]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/409 + +[412]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/412 + +[422]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/422 + +[429]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/429 + +[500]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500 + +[504]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/504 + +[JSON API document]: /terraform/enterprise/api-docs#json-api-documents + +[JSON API error object]: https://jsonapi.org/format/#error-objects + +# Cost estimates API reference + +## Show a cost estimate + +-> **Note**: The hash in the `resources` attribute structure represents low-level Cost Estimation details. The keys or structure may change over time. Use the data in this hash at your own risk. + +`GET /cost-estimates/:id` + +| Parameter | Description | +| --------- | ------------------------------------ | +| `id` | The ID of the cost estimate to show. | + +There is no endpoint to list cost estimates. You can find the ID for a cost estimate in the +`relationships.cost-estimate` property of a run object. + +| Status | Response | Reason | +| ------- | ------------------------------------------------ | --------------------------------------------------------------- | +| [200][] | [JSON API document][] (`type: "cost-estimates"`) | The request was successful | +| [404][] | [JSON API error object][] | Cost estimate not found, or user unauthorized to perform action | + +### Sample Request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + https://app.terraform.io/api/v2/cost-estimates/ce-BPvFFrYCqRV6qVBK +``` + +### Sample Response + +```json +{ + "data": [ + { + "id": "ce-BPvFFrYCqRV6qVBK", + "type": "cost-estimates", + "attributes": { + "error-message": null, + "status": "finished", + "status-timestamps": { + "queued-at": "2017-11-29T20:02:17+00:00", + "finished-at": "2017-11-29T20:02:20+00:00" + }, + "resources": {...}, + "resources-count": 4, + "matched-resources-count": 3, + "unmatched-resources-count": 1, + "prior-monthly-cost": "0.0", + "proposed-monthly-cost": "25.488", + "delta-monthly-cost": "25.488", + }, + "links": { + "self": "/api/v2/cost-estimate/ce-9VYRc9bpfJEsnwum" + } + } + ] +} +``` diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/github-app-installations.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/github-app-installations.mdx new file mode 100644 index 0000000000..8573467c08 --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/github-app-installations.mdx @@ -0,0 +1,127 @@ +--- +page_title: /github-app/installations API reference for Terraform Enterprise +description: >- + Use the Terraform Enterprise API's `/github-app/installations` endpoint to + view details about where you have installed the Terraform Enterprise GitHub + App. +source: terraform-docs-common +--- + +[200]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/200 + +[201]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/201 + +[202]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/202 + +[204]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/204 + +[400]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/400 + +[401]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/401 + +[403]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/403 + +[404]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404 + +[409]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/409 + +[412]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/412 + +[422]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/422 + +[429]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/429 + +[500]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500 + +[504]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/504 + +[JSON API document]: /terraform/enterprise/api-docs#json-api-documents + +[JSON API error object]: https://jsonapi.org/format/#error-objects + +# GitHub App installations API reference + +You can create a GitHub App installation using the HCP Terraform UI. Learn how to [create a GitHub App installation](/terraform/enterprise/vcs/github-app). + +~> **Note:** To use this resource in Terraform Enterprise installations, you must configure the GitHub App in the site admin area. + +~> **Note:** You can only use this API if you have already authorized the Terraform Cloud GitHub App. Manage your [GitHub App token](/terraform/enterprise/users-teams-organizations/users#github-app-oauth-token) from **Account Settings** > **Tokens**. + +## List Installations + +This endpoint lists GitHub App installations available to the current user. + +`GET /github-app/installations` + +### Query Parameters + +Queries only return GitHub App Installations that the current user has access to within GitHub. + +| Parameter | Description | +| ------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | +| `filter[name]` | **Optional.** If present, returns a list of available GitHub App installations that match the GitHub organization or login. | +| `filter[installation_id]` | **Optional.** If present, returns a list of available GitHub App installations that match the installation ID within GitHub. (**Not HCP Terraform**) | + +### Sample Request + +```shell +$ curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + https://app.terraform.io/api/v2/github-app/installations +``` + +### Sample Response + +```json +{ + "data": [ + { + "id": "ghain-BYrbNeGQ8nAzKouu", + "type": "github-app-installations", + "attributes": { + "name": "octouser", + "installation-id": 54810170, + "icon-url": "https://avatars.githubusercontent.com/u/29916665?v=4", + "installation-type": "User", + "installation-url": "https://github.com/settings/installations/54810170" + } + } + ] +} +``` + +## Show Installation + +`GET /github-app/installation/:gh_app_installation_id` + +| Parameter | Description | +| ------------------------- | ------------------------------ | +| `:gh_app_installation_id` | The Github App Installation ID | + +### Sample Request + +```shell +$ curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + https://app.terraform.io/api/v2/github-app/installation/ghain-R4xmKTaxnhLFioUq +``` + +### Sample Response + +```json +{ + "data": { + "id": "ghain-R4xmKTaxnhLFioUq", + "type": "github-app-installations", + "attributes": { + "name": "octouser", + "installation-id": 54810170, + "icon-url": "https://avatars.githubusercontent.com/u/29916665?v=4", + "installation-type": "User", + "installation-url": "https://github.com/settings/installations/54810170" + } + } +} +``` diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/index.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/index.mdx new file mode 100644 index 0000000000..94b8db2657 --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/index.mdx @@ -0,0 +1,396 @@ +--- +page_title: API documentation for Terraform Enterprise +description: >- + Learn about API authentication, response codes, versioning, formatting, rate + limiting, and clients. +source: terraform-docs-common +--- + +[200]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/200 + +[201]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/201 + +[202]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/202 + +[204]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/204 + +[400]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/400 + +[401]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/401 + +[403]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/403 + +[404]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404 + +[409]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/409 + +[412]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/412 + +[422]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/422 + +[429]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/429 + +[500]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500 + +[504]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/504 + +[JSON API document]: /terraform/enterprise/api-docs#json-api-documents + +[JSON API error object]: https://jsonapi.org/format/#error-objects + +# HCP Terraform API documentation + +HCP Terraform provides an API for a subset of its features. If you need assistance or want to submit a feature request, visit the [HashiCorp support center](https://support.hashicorp.com/hc/en-us) and open a ticket. + +-> **Note:** Before planning an API integration, consider whether [the `tfe` Terraform provider](https://registry.terraform.io/providers/hashicorp/tfe/latest/docs) meets your needs. It can't create or approve runs in response to arbitrary events, but it's a useful tool for managing your organizations, teams, and workspaces as code. + +HashiCorp provides a [stability policy](/terraform/enterprise/api-docs/stability-policy) for the HCP Terraform API, ensuring backwards compatibility for stable endpoints. The [changelog](/terraform/enterprise/api-docs/changelog) tracks changes to the API for HCP Terraform and Terraform Enterprise. + +## Authentication + +All requests must be authenticated with a bearer token. Use the HTTP header `Authorization` with the value `Bearer `. If the token is absent or invalid, HCP Terraform responds with [HTTP status 401][401] and a [JSON API error object][]. The 401 status code is reserved for problems with the authentication token; forbidden requests with a valid token result in a 404. + +You can use the following types of tokens to authenticate: + +- [User tokens](/terraform/enterprise/users-teams-organizations/users#api-tokens) — each HCP Terraform user can have any number of API tokens, which can make requests on their behalf. +- [Team tokens](/terraform/enterprise/users-teams-organizations/api-tokens#team-api-tokens) — each team can have one API token at a time. This is intended for performing plans and applies via a CI/CD pipeline. +- [Organization tokens](/terraform/enterprise/users-teams-organizations/api-tokens#organization-api-tokens) — each organization can have one API token at a time. This is intended for automating the management of teams, team membership, and workspaces. The organization token cannot perform plans and applies. + +- [Audit trails token](/terraform/enterprise/users-teams-organizations/api-tokens#audit-trails-tokens) - each organization can have a single token that can read that organization's audit trails. Use this token type to authenticate integrations pulling audit trail data, for example, using the [HCP Terraform for Splunk](/terraform/enterprise/integrations/splunk) app. + + +### Blob Storage Authentication + +HCP Terraform relies on a HashiCorp-developed blob storage service for storing statefiles and multiple other pieces of customer data, all of which are documented on our [data security page](/terraform/enterprise/architectural-details/data-security). + +Unlike the HCP Terraform API, this service does not require that a bearer token be submitted with each request. Instead, each URL includes a securely generated secret and is only valid for 25 hours. + +For example, the [state versions api](/terraform/enterprise/api-docs/state-versions) returns a field named `hosted-state-download`, which is a URL of this form: +`https://archivist.terraform.io/v1/object/` + +This is a broadly accepted pattern for secure access. It is important to treat these URLs themselves as secrets. They should not be logged, nor shared with untrusted parties. + +## Feature Entitlements + +HCP Terraform is available at multiple pricing tiers (including free), which offer different feature sets. + +Each organization has a set of _entitlements_ that corresponds to its pricing tier. These entitlements determine which HCP Terraform features the organization can use. + +If an organization doesn't have the necessary entitlement to use a given feature, HCP Terraform returns a 404 error for API requests to any endpoints devoted to that feature. + +The [show entitlement set](/terraform/enterprise/api-docs/organizations#show-the-entitlement-set) endpoint can return information about an organization's current entitlements, which is useful if your client needs to change its interface when a given feature isn't available. + +The following entitlements are available: + +- `agents` — Allows isolated, private or on-premises infrastructure to communicate with an organization in HCP Terraform. Affects the [agent pools][], [agents][], and [agent tokens][] endpoints. + +- `audit-logging` — Allows an organization to access [audit trails][]. + +- `configuration-designer` — Allows an organization to use the [Configuration Designer][]. +- `cost-estimation` — Allows an organization to access [cost estimation][]. +- `global-run-tasks` — Allows an organization to apply [run tasks](/terraform/enterprise/workspaces/settings/run-tasks) to every workspace. Affects the [run tasks][] endpoints. This feature is currently in beta. +- `module-tests-generation` - Allows an organization to generate tests for private registry modules. This feature is currently in beta. +- `module-deprecations` - Allows an organization to mark a module version from the Private Registry as deprecated. +- `module-revocations` - Allows an organization to mark a deprecated module version from the Private Registry as revoked. +- `operations` — Allows an organization to perform runs within HCP Terraform. Affects the [runs][], [plans][], and [applies][] endpoints. +- `policy-enforcement` — Allows an organization to use [Sentinel][]. Affects the [policies][], [policy sets][], and [policy checks][] endpoints. +- `private-module-registry` — Allows an organization to publish and use modules with the [private module registry][]. Affects the [registry modules][] endpoints. +- `private-policy-agents` - Allows an organization to ensure that HTTP enabled [Sentinel][] and OPA [policies][] can communicate with isolated, private, or on-premises infrastructure. +- `private-run-tasks` - Allows an organization to ensure that [run tasks](/terraform/enterprise/workspaces/settings/run-tasks) can communicate with isolated, private, or on-premises infrastructure. +- `private-vcs` - Allows a self-hosted HCP Terraform agent to [connect to a private VCS provider](/terraform/enterprise/vcs/private) without having to expose that provider to the public internet. +- `run-tasks` — Allows an organization to use [run tasks](/terraform/enterprise/workspaces/settings/run-tasks). Affects the [run tasks][] endpoints. +- `self-serve-billing` — Allows an organization to pay via credit card using the in-app billing UI. +- `sentinel` - **DEPRECATED** Use `policy-enforcement` instead. +- `state-storage` — Allows an organization to store state versions in its workspaces, which enables local Terraform runs with HCP Terraform. Affects the [state versions][] endpoints. +- `sso` — Allows an organization to manage and authenticate users with single sign on. +- `teams` — Allows an organization to manage access to its workspaces with [teams](/terraform/enterprise/users-teams-organizations/teams). Without this entitlement, an organization only has an owners team. Affects the [teams][], [team members][], [team access][], and [team tokens][] endpoints. +- `user-limit` — An integer value representing the maximum number of users allowed for the organization. If blank, there is no limit. +- `vcs-integrations` — Allows an organization to [connect with a VCS provider][vcs integrations] and link VCS repositories to workspaces. Affects the [OAuth Clients][o-clients], and [OAuth Tokens][o-tokens] endpoints, and determines whether the `data.attributes.vcs-repo` property can be set for [workspaces][]. + +[agents]: /terraform/enterprise/api-docs/agents + +[agent pools]: /terraform/enterprise/api-docs/agents + +[agent tokens]: /terraform/enterprise/api-docs/agent-tokens + +[applies]: /terraform/enterprise/api-docs/applies + + + +[audit trails]: /terraform/enterprise/api-docs/audit-trails + + + +[Configuration Designer]: /terraform/enterprise/registry/design + +[cost estimation]: /terraform/enterprise/cost-estimation + +[o-clients]: /terraform/enterprise/api-docs/oauth-clients + +[o-tokens]: /terraform/enterprise/api-docs/oauth-tokens + +[plans]: /terraform/enterprise/api-docs/plans + +[policies]: /terraform/enterprise/api-docs/policies + +[policy checks]: /terraform/enterprise/api-docs/policy-checks + +[policy sets]: /terraform/enterprise/api-docs/policy-sets + +[private module registry]: /terraform/enterprise/registry + +[registry modules]: /terraform/enterprise/api-docs/private-registry/modules + +[registry providers]: /terraform/enterprise/api-docs/private-registry/providers + +[runs]: /terraform/enterprise/api-docs/run + +[run tasks]: /terraform/enterprise/api-docs/run-tasks/run-tasks + +[Sentinel]: /terraform/enterprise/policy-enforcement/sentinel + +[single sign on]: /terraform/enterprise/users-teams-organizations/single-sign-on + +[state versions]: /terraform/enterprise/api-docs/state-versions + +[teams]: /terraform/enterprise/api-docs/teams + +[team access]: /terraform/enterprise/api-docs/team-access + +[team members]: /terraform/enterprise/api-docs/team-members + +[team tokens]: /terraform/enterprise/api-docs/team-tokens + +[vcs integrations]: /terraform/enterprise/vcs + +[workspaces]: /terraform/enterprise/api-docs/workspaces + +## Response Codes + +This API returns standard HTTP response codes. + +We return 404 Not Found codes for resources that a user doesn't have access to, as well as for resources that don't exist. This is to avoid telling a potential attacker that a given resource exists. + +## Versioning + +The API documented in these pages is the second version of HCP Terraform's API, and resides under the `/v2` prefix. + +Future APIs will increment this version, leaving the `/v1` API intact, though in the future we might deprecate certain features. In that case, we'll provide ample notice to migrate to the new API. + +## Paths + +All V2 API endpoints use `/api/v2` as a prefix unless otherwise specified. + +For example, if the API endpoint documentation defines the path `/runs` then the full path is `/api/v2/runs`. + +## JSON API Formatting + +The HCP Terraform endpoints use the [JSON API specification](https://jsonapi.org/), which specifies key aspects of the API. Most notably: + +- [HTTP error codes](https://jsonapi.org/examples/#error-objects-error-codes) +- [Error objects](https://jsonapi.org/examples/#error-objects-basics) +- [Document structure][document] +- [HTTP request/response headers](https://jsonapi.org/format/#content-negotiation) + +[document]: https://jsonapi.org/format/#document-structure + +### JSON API Documents + +Since our API endpoints use the JSON API spec, most of them return [JSON API documents][document]. + +Endpoints that use the POST method also require a JSON API document as the request payload. A request object usually looks something like this: + +```json +{ + "data": { + "type":"vars", + "attributes": { + "key":"some_key", + "value":"some_value", + "category":"terraform", + "hcl":false, + "sensitive":false + }, + "relationships": { + "workspace": { + "data": { + "id":"ws-4j8p6jX1w33MiDC7", + "type":"workspaces" + } + } + } + } +} +``` + +These objects always include a top-level `data` property, which: + +- Must have a `type` property to indicate what type of API object you're interacting with. +- Often has an `attributes` property to specify attributes of the object you're creating or modifying. +- Sometimes has a `relationships` property to specify other objects that are linked to what you're working with. + +In the documentation for each API method, we use dot notation to explain the structure of nested objects in the request. For example, the properties of the request object above are listed as follows: + +| Key path | Type | Default | Description | +| ---------------------------------------- | ------ | ------- | --------------------------------------------------------------------------------------------------------------- | +| `data.type` | string | | Must be `"vars"`. | +| `data.attributes.key` | string | | The name of the variable. | +| `data.attributes.value` | string | | The value of the variable. | +| `data.attributes.category` | string | | Whether this is a Terraform or environment variable. Valid values are `"terraform"` or `"env"`. | +| `data.attributes.hcl` | bool | `false` | Whether to evaluate the value of the variable as a string of HCL code. Has no effect for environment variables. | +| `data.attributes.sensitive` | bool | `false` | Whether the value is sensitive. If true then the variable is written once and not visible thereafter. | +| `data.relationships.workspace.data.type` | string | | Must be `"workspaces"`. | +| `data.relationships.workspace.data.id` | string | | The ID of the workspace that owns the variable. | + +We also always include a sample payload object, to show the document structure more visually. + +### Query Parameters + +Although most of our API endpoints use the POST method and receive their parameters as a JSON object in the request payload, some of them use the GET method. These GET endpoints sometimes require URL query parameters, in the standard `...path?key1=value1&key2=value2` format. + +Since these parameters were originally designed as part of a JSON object, they sometimes have characters that must be [percent-encoded](https://en.wikipedia.org/wiki/Percent-encoding) in a query parameter. For example, `[` becomes `%5B` and `]` becomes `%5D`. + +For more about URI structure and query strings, see [the specification (RFC 3986)](https://tools.ietf.org/html/rfc3986) or [the Wikipedia page on URIs](https://en.wikipedia.org/wiki/Uniform_Resource_Identifier). + +### Pagination + +Most of the endpoints that return lists of objects support pagination. A client may pass the following query parameters to control pagination on supported endpoints: + +| Parameter | Description | +| -------------- | --------------------------------------------------------------------------------------------------- | +| `page[number]` | **Optional.** If omitted, the endpoint will return the first page. | +| `page[size]` | **Optional.** If omitted, the endpoint will return 20 items per page. The maximum page size is 100. | + +Additional data is returned in the `links` and `meta` top level attributes of the response. + +```json +{ + "data": [...], + "links": { + "self": "https://app.terraform.io/api/v2/organizations/hashicorp/workspaces?page%5Bnumber%5D=1&page%5Bsize%5D=20", + "first": "https://app.terraform.io/api/v2/organizations/hashicorp/workspaces?page%5Bnumber%5D=1&page%5Bsize%5D=20", + "prev": null, + "next": "https://app.terraform.io/api/v2/organizations/hashicorp/workspaces?page%5Bnumber%5D=2&page%5Bsize%5D=20", + "last": "https://app.terraform.io/api/v2/organizations/hashicorp/workspaces?page%5Bnumber%5D=2&page%5Bsize%5D=20" + }, + "meta": { + "pagination": { + "current-page": 1, + "prev-page": null, + "next-page": 2, + "total-pages": 2, + "total-count": 21 + } + } +} +``` + +### Inclusion of Related Resources + +Some of the API's GET endpoints can return additional information about nested resources by adding an `include` query parameter, whose value is a comma-separated list of resource types. + +The related resource options are listed in each endpoint's documentation where available. + +The related resources will appear in an `included` section of the response. + +Example: + +```shell +$ curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request GET \ + https://app.terraform.io/api/v2/teams/team-n8UQ6wfhyym25sMe?include=users +``` + +```json +{ + "data": { + "id": "team-n8UQ6wfhyym25sMe", + "type": "teams", + "attributes": { + "name": "owners", + "users-count": 1 + ... + }, + "relationships": { + "users": { + "data": [ + { + "id": "user-62goNpx1ThQf689e", + "type": "users" + } + ] + } ... + } + ... + }, + "included": [ + { + "id": "user-62goNpx1ThQf689e", + "type": "users", + "attributes": { + "username": "hashibot" + ... + } ... + } + ] +} +``` + +## Rate Limiting + +You can make up to 30 requests per second to most API endpoints as an authenticated or unauthenticated request. If you reach the rate limit then your access will be throttled and an error response will be returned. + +Requests are per user, not per token. As a result, you cannot use multiple tokens to make more than 30 requests per second. + +Unauthenticated requests are associated with the requesting IP address. + +| Status | Response | Reason | +| ------- | ------------------------- | ---------------------------- | +| [429][] | [JSON API error object][] | Rate limit has been reached. | + +```json +{ + "errors": [ + { + "detail": "You have exceeded the API's rate limit.", + "status": 429, + "title": "Too many requests" + } + ] +} +``` + +### Lower rate limits for some endoints + +To prevent abuse, some endpoints have lower rate limits. The lower limits are unnoticeable under normal use. If you trigger a rate-limited response, you can see that limit in the `x-ratelimit-limit` header. + +The following endpoints have lower rate limits: + +| Method and endpoint | Purpose | Limit | +| --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------- | ----------------------------------- | +|

`POST /session/two-factor-send-sms`

`POST /api/v2/account/actions/two-factor-enable`

`POST /api/v2/account/actions/two-factor-resend-verification-code`

| Send SMS message | 5 requests per minute per user | +|

`POST /api/v2/account/actions/two-factor-enable`

`POST /api/v2/account/actions/two-factor-resend-verification-code`

| Send SMS message | 10 requests per hour per user | +|

`POST /api/v2/account/actions/two-factor-enable`

`POST /api/v2/account/actions/two-factor-resend-verification-code`

| Send SMS message | 100 requests per day per IP address | +|

`POST /session/two-factor`

`POST /session/two-factor-recovery`

| Submit 2FA code | 5 requests per minute per user | +|

`POST` and `PATCH /api/v2/account/create`

`POST` and `PATCH /api/v2/account/update`

`POST` and `PATCH /api/v2/account/password`

`POST` and `PATCH /api/v2/account/reconfirm`

`POST /session`

| Send emails | 100 per minute | +|

`POST` and `GET /sso/link-new-account`

`POST` and `GET /sso/link-account`

`POST` and `GET /sso/link-existing-account`

`POST /sso/saml/{SAML_CONFIGURATION_EXTERNAL_ID}/acs`

| Send emails | 20 per minute | +|

`POST /api/v2/notification-configurations/{EXTERNAL_ID}/actions/verify`

`DELETE /api/v2/oauth-tokens`

| Send emails | 10 per minute | +|

`POST /account/reconfirm`

| Send emails | 40 per hour | +|

`POST /auth`

| Send emails | 40 per hour per email address | + +## Client libraries and tools + +HashiCorp maintains [go-tfe](https://github.com/hashicorp/go-tfe), a Go client for HCP Terraform's API. + +Additionally, the community of HCP Terraform users and vendors have built client libraries in other languages. These client libraries and tools are not tested nor officially maintained by HashiCorp, but are listed below in order to help users find them easily. + +If you have built a client library and would like to add it to this community list, please [contribute](https://github.com/hashicorp/terraform-website#contributions-welcome) to [this page](https://github.com/hashicorp/terraform-docs-common/blob/main/website/docs/cloud-docs/api-docs/index.mdx#client-libraries-and-tools). + +- [tfh](https://github.com/hashicorp-community/tf-helper): UNIX shell console app +- [tf_api_gateway](https://github.com/PlethoraOfHate/tf_api_gateway): Python API library and console app +- [terrasnek](https://github.com/dahlke/terrasnek): Python API library +- [terraform-enterprise-client](https://github.com/skierkowski/terraform-enterprise-client): Ruby API library and console app +- [pyterprise](https://github.com/JFryy/terraform-enterprise-api-python-client): A simple Python API client library. +- [Tfe.NetClient](https://github.com/everis-technology/Tfe.NetClient): .NET Client Library diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/no-code-provisioning.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/no-code-provisioning.mdx new file mode 100644 index 0000000000..c8c0bfd9f9 --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/no-code-provisioning.mdx @@ -0,0 +1,924 @@ +--- +page_title: /no-code-modules API reference for Terraform Enterprise +description: >- + Use the Terraform Enterprise API's `/no-code-modules` endpoint to designate + and configure no-code modules. You can also use this endpoint to deploy and + upgrade no-code workspaces. +source: terraform-docs-common +--- + +[200]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/200 + +[201]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/201 + +[202]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/202 + +[204]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/204 + +[400]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/400 + +[401]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/401 + +[403]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/403 + +[404]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404 + +[409]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/409 + +[412]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/412 + +[422]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/422 + +[429]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/429 + +[500]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500 + +[504]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/504 + +[JSON API document]: /terraform/enterprise/api-docs#json-api-documents + +[JSON API error object]: http://jsonapi.org/format/#error-objects + +# No-code provisioning API reference + +The no-code provisioning API allows you to create, configure, and deploy Terraform modules in the no-code provisioning workflows within HCP Terraform. For more information on no-code modules, refer to [Designing No-Code Ready Modules](/terraform/enterprise/no-code-provisioning/module-design). + +## Enable no-code provisioning for a module + +`POST /organizations/:organization_name/no-code-modules` + +| Parameter | Description | +| -------------------- | --------------------------------------------------- | +| `:organization_name` | The name of the organization the module belongs to. | + +To deploy a module using the no-code provisioning workflow, the module must meet the following requirement: + +1. The module must exist in your organization's private registry. +2. The module must meet the [design requirements](/terraform/enterprise/no-code-provisioning/module-design#requirements) for a no-code module. +3. You must enable no-code provisioning for the module. + +You can send a `POST` request to the `/no-code-modules` endpoint to enable no-code provisioning for a specific module. You can also call this endpoint to set options for the allowed values of a variable for a no-code module in your organization. + +-> **Note**: This endpoint can not be accessed with [organization tokens](/terraform/enterprise/users-teams-organizations/api-tokens#organization-api-tokens). You must access it with a [user token](/terraform/enterprise/users-teams-organizations/users#api-tokens) or [team token](/terraform/enterprise/users-teams-organizations/api-tokens#team-api-tokens). + +| Status | Response | Reason | +| ------- | ------------------------------------------------- | --------------------------------------------------------------------- | +| [200][] | [JSON API document][] (`type: "no-code-modules"`) | Successfully enabled a module for no-code provisioning. | +| [404][] | [JSON API error object][] | Not found, or the user is unauthorized to perform this action. | +| [422][] | [JSON API error object][] | Malformed request body (e.g., missing attributes, wrong types, etc.). | +| [500][] | [JSON API error object][] | Internal system failure. | + +### Request Body + +This POST endpoint requires a JSON object with the following properties as a request payload. + +Properties without a default value are required. + +| Key path | Type | Default | Description | +| --------------------------------------------------------------------- | ------- | ---------------------------- | ---------------------------------------------------------------------------------------------------------------------- | +| `data.type` | string | | Must be `"no-code-modules"`. | +| `data.attributes.version-pin` | string | Latest version of the module | The module version to use in no-code provisioning workflows. | +| `data.attributes.enabled` | boolean | `false` | Set to `true` to enable no-code provisioning workflows. | +| `data.relationships.registry-module.data.id` | string | | The ID of a module in the organization's private registry. | +| `data.relationships.registry-module.data.type` | string | | Must be `"registry-module"`. | +| `data.relationships.variable-options.data[].type` | string | | Must be `"variable-options"`. | +| `data.relationships.variable-options.data[].attributes.variable-name` | string | | The name of a variable within the module. | +| `data.relationships.variable-options.data[].attributes.variable-type` | string | | The data type for the variable. Can be [any type supported by Terraform](/terraform/language/expressions/types#types). | +| `data.relationships.variable-options.data[].attributes.options` | Any\[] | | A list of allowed values for the variable. | + +### Sample Payload + +```json +{ + "data": { + "type": "no-code-modules", + "attributes": { + "version-pin": "1.0.1", + "enabled": true + }, + "relationships": { + "registry-module": { + "data": { + "id": "mod-2aaFrmRPZs2N9epr", + "type": "registry-module" + } + }, + "variable-options": { + "data": [ + { + "type": "variable-options", + "attributes": { + "variable-name": "amis", + "variable-type": "string", + "options": [ + "ami-1", + "ami-2", + "ami-3" + ] + } + }, + { + "type": "variable-options", + "attributes": { + "variable-name": "region", + "variable-type": "string", + "options": [ + "eu-north-1", + "us-east-2", + "us-west-1" + ] + } + } + ] + } + } + } +} + +``` + +### Sample Request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request POST \ + --data @payload.json \ + https://app.terraform.io/api/v2/organizations/my-organization/no-code-modules +``` + +### Sample Response + +```json +{ + "data": { + "id": "nocode-9HE91XDNY3faePn2", + "type": "no-code-modules", + "attributes": { + "enabled": true, + "version-pin": "1.0.1" + }, + "relationships": { + "organization": { + "data": { + "id": "my-organization", + "type": "organizations" + }, + "links": { + "related": "/api/v2/organizations/my-organization" + } + }, + "registry-module": { + "data": { + "id": "mod-2aaFrmRPZs2N9epr", + "type": "registry-modules" + }, + "links": { + "related": "/api/v2/registry-modules/mod-2aaFrmRPZs2N9epr" + } + }, + "variable-options": { + "data": [ + { + "id": "ncvaropt-fcHDfnZ1EGdRzFNC", + "type": "variable-options" + }, + { + "id": "ncvaropt-dZMfdh9KBcwFjyv2", + "type": "variable-options" + } + ] + } + }, + "links": { + "self": "/api/v2/no-code-modules/nocode-9HE91XDNY3faePn2" + } + } +} +``` + +## Update no-code provisioning settings + +`PATCH /no-code-modules/:id` + +| Parameter | Description | +| --------- | -------------------------------------------- | +| `:id` | The unique identifier of the no-code module. | + +Send a `PATCH` request to the `/no-code-modules/:id` endpoint to update the settings for the no-code provisioning of a module. You can update the following settings: + +- Enable or disable no-code provisioning. +- Adjust the set of options for allowed variable values. +- Change the module version being provisioned. +- Change the module being provisioned. + +The [API call that enables no-code provisioning for a module](#allow-no-code-provisioning-of-a-module-within-an-organization) returns that module's unique identifier. + +-> **Note:** This endpoint cannot be accessed with [organization tokens](/terraform/enterprise/users-teams-organizations/api-tokens#organization-api-tokens). You must access it with a [user token](/terraform/enterprise/users-teams-organizations/users#api-tokens) or [team token](/terraform/enterprise/users-teams-organizations/api-tokens#team-api-tokens). + +| Status | Response | Reason | +| ------- | ------------------------------------------------- | --------------------------------------------------------------------- | +| [200][] | [JSON API document][] (`type: "no-code-modules"`) | Successfully updated a no-code module. | +| [404][] | [JSON API error object][] | Not found, or the user is unauthorized to perform this action. | +| [422][] | [JSON API error object][] | Malformed request body (e.g., missing attributes, wrong types, etc.). | +| [500][] | [JSON API error object][] | Internal system failure. | + +### Request Body + +This PATCH endpoint requires a JSON object with the following properties as a request payload. + +Properties without a default value are required. + +| Key path | Type | Default | Description | +| --------------------------------------------------------------------- | ------- | -------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `data.type` | string | | Must be `"no-code-modules"`. | +| `data.attributes.version-pin` | string | Existing value | The module version to use in no-code provisioning workflows. | +| `data.attributes.enabled` | boolean | Existing value | Set to `true` to enable no-code provisioning workflows, or `false` to disable them. | +| `data.relationships.registry-module.data.id` | string | Existing value | The ID of a module in the organization's Private Registry. | +| `data.relationships.registry-module.data.type` | string | Existing value | Must be `"registry-module"`. | +| `data.relationships.variable-options.data[].id` | string | | The ID of an existing variable-options set. If provided, a new variable-options set replaces the set with this ID. If not provided, this creates a new variable-option set. | +| `data.relationships.variable-options.data[].type` | string | | Must be `"variable-options"`. | +| `data.relationships.variable-options.data[].attributes.variable-name` | string | | The name of a variable within the module. | +| `data.relationships.variable-options.data[].attributes.variable-type` | string | | The data type for the variable. Can be [any type supported by Terraform](/terraform/language/expressions/types#types). | +| `data.relationships.variable-options.data[].attributes.options` | Any\[] | | A list of allowed values for the variable. | + +### Sample Payload + +```json +{ + "data": { + "type": "no-code-modules", + "attributes": { + "enabled": false + }, + "relationships": { + "registry-module": { + "data": { + "id": "mod-zyai9dwH4VPPaVuC", + "type": "registry-module" + } + }, + "variable-options": { + "data": [ + { + "id": "ncvaropt-fcHDfnZ1EGdRzFNC", + "type": "variable-options", + "attributes": { + "variable-name": "Linux AMIs", + "variable-type": "array", + "options": [ + "Xenial Xerus", + "Trusty Tahr" + ] + } + }, + { + "id": "ncvaropt-dZMfdh9KBcwFjyv2", + "type": "variable-options", + "attributes": { + "variable-name": "region", + "variable-type": "array", + "options": [ + "eu-north-1", + "us-east-2", + "us-west-1" + ] + } + } + ] + } + } + } +} +``` + +### Sample Request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request PATCH \ + --data @payload.json \ + https://app.terraform.io/api/v2/no-code-modules/nocode-9HE91XDNY3faePn2 +``` + +### Sample Response + +```json +{ + "data": { + "id": "nocode-9HE91XDNY3faePn2", + "type": "no-code-modules", + "attributes": { + "enabled": true, + "version-pin": "1.0.1" + }, + "relationships": { + "organization": { + "data": { + "id": "my-organization", + "type": "organizations" + }, + "links": { + "related": "/api/v2/organizations/my-organization" + } + }, + "registry-module": { + "data": { + "id": "mod-2aaFrmRPZs2N9epr", + "type": "registry-modules" + }, + "links": { + "related": "/api/v2/registry-modules/mod-2aaFrmRPZs2N9epr" + } + }, + "variable-options": { + "data": [ + { + "id": "ncvaropt-fcHDfnZ1EGdRzFNC", + "type": "variable-options" + }, + { + "id": "ncvaropt-dZMfdh9KBcwFjyv2", + "type": "variable-options" + } + ] + } + }, + "links": { + "self": "/api/v2/no-code-modules/nocode-9HE91XDNY3faePn2" + } + } +} +``` + +## Read a no-code module's properties + +`GET /no-code-modules/:id` + +| Parameter | Description | +| --------- | -------------------------------------------- | +| `:id` | The unique identifier of the no-code module. | + +Use this API to read the details of an existing no-code module. + +The [API call that enables no-code provisioning for a module](#allow-no-code-provisioning-of-a-module-within-an-organization) returns that module's unique identifier. + +| Status | Response | Reason | +| ------- | ------------------------------------------------- | --------------------------------------------------------------------- | +| [200][] | [JSON API document][] (`type: "no-code-modules"`) | Successfully read the no-code module. | +| [400][] | [JSON API error object][] | Invalid `include` parameter. | +| [404][] | [JSON API error object][] | Not found, or the user is unauthorized to perform this action. | +| [422][] | [JSON API error object][] | Malformed request body (e.g., missing attributes, wrong types, etc.). | +| [500][] | [JSON API error object][] | Internal system failure. | + +### Query Parameters + +This endpoint uses our [standard URL query parameters](/terraform/enterprise/api-docs#query-parameters). Use HTML URL encoding syntax, such as `%5B` to represent `[` and `%5D` to represent `]`, if your tooling does not automatically encode URLs. + +Terraform returns related resources when you add the [`include` query parameter](/terraform/enterprise/api-docs#inclusion-of-related-resources) to the request. + +| Parameter | Description | +| --------- | ------------------------------------------------- | +| `include` | List related resource to include in the response. | + +The following resource types are available: + +| Resource Name | Description | +| ------------------ | --------------------------------------------------------- | +| `variable_options` | Module variables with a configured set of allowed values. | + +### Sample Request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + https://app.terraform.io/api/v2/no-code-modules/nocode-9HE91XDNY3faePn2?include=variable_options +``` + +### Sample Response + +```json +{ + "data": { + "id": "nocode-9HE91XDNY3faePn2", + "type": "no-code-modules", + "attributes": { + "enabled": true, + "version-pin": "1.0.1" + }, + "relationships": { + "organization": { + "data": { + "id": "my-organization", + "type": "organizations" + }, + "links": { + "related": "/api/v2/organizations/my-organization" + } + }, + "registry-module": { + "data": { + "id": "mod-2aaFrmRPZs2N9epr", + "type": "registry-modules" + }, + "links": { + "related": "/api/v2/registry-modules/mod-2aaFrmRPZs2N9epr" + } + }, + "variable-options": { + "data": [ + { + "id": "ncvaropt-fcHDfnZ1EGdRzFNC", + "type": "variable-options" + }, + { + "id": "ncvaropt-dZMfdh9KBcwFjyv2", + "type": "variable-options" + } + ] + } + }, + "links": { + "self": "/api/v2/no-code-modules/nocode-9HE91XDNY3faePn2" + } + }, + "included": [ + { + "id": "ncvaropt-fcHDfnZ1EGdRzFNC", + "type": "variable-options", + "attributes": { + "variable-name": "Linux AMIs", + "variable-type": "array", + "options": [ + "Xenial Xerus", + "Trusty Tahr" + ] + }, + "relationships": { + "no-code-allowed-module": { + "data": { + "id": "nocode-9HE91XDNY3faePn2", + "type": "no-code-allowed-modules" + } + } + } + }, + { + "id": "ncvaropt-dZMfdh9KBcwFjyv2", + "type": "variable-options", + "attributes": { + "variable-name": "region", + "variable-type": "array", + "options": [ + "eu-north-1", + "us-east-2", + "us-west-1" + ] + }, + "relationships": { + "no-code-allowed-module": { + "data": { + "id": "nocode-9HE91XDNY3faePn2", + "type": "no-code-allowed-modules" + } + } + } + } + ] +} +``` + +## Create a no-code module workspace + +This endpoint creates a workspace from a no-code module. + +`POST /no-code-modules/:id/workspaces` + +| Parameter | Description | +| --------- | ------------------------------------------ | +| `:id` | The ID of the no-code module to provision. | + +Each HCP Terraform organization has a list of which modules you can use to create workspaces using no-code provisioning. You can use this API to create a workspace by selecting a no-code module to enable a no-code provisioning workflow. + +-> **Note**: This endpoint can not be accessed with [organization tokens](/terraform/enterprise/users-teams-organizations/api-tokens#organization-api-tokens). You must access it with a [user token](/terraform/enterprise/users-teams-organizations/users#api-tokens) or [team token](/terraform/enterprise/users-teams-organizations/api-tokens#team-api-tokens). + +| Status | Response | Reason | +| ------- | -------------------------------------------- | -------------------------------------------------------------------------------- | +| [200][] | [JSON API document][] (`type: "workspaces"`) | Successfully created a workspace from a no-code module for no-code provisioning. | +| [404][] | [JSON API error object][] | Not found, or the user is unauthorized to perform this action. | +| [422][] | [JSON API error object][] | Malformed request body (e.g., missing attributes, wrong types, etc.). | +| [500][] | [JSON API error object][] | Internal system failure. | + +### Request Body + +This POST endpoint requires a JSON object with the following properties as a request payload. + +Properties without a default value are required. + +| Key path | Type | Default | Description | | +| ------------------------------------------------------- | ------- | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | - | +| `data.type` | string | none | Must be `"workspaces"`. | | +| `data.attributes.agent-pool-id` | string | none | Required when `execution-mode` is set to `agent`. The ID of the agent pool belonging to the workspace's organization. This value must not be specified if `execution-mode` is set to `remote`. | | +| `data.attributes.auto_apply` | boolean | `false` | If `true`, Terraform automatically applies changes when a Terraform `plan` is successful. | | +| `data.attributes.description` | string | `""` | A description for the workspace. | | +| `data.attributes.execution-mode` | string | none | Which [execution mode](/terraform/enterprise/workspaces/settings#execution-mode) to use. Valid values are `remote`, and `agent`. When not provided, defaults to `remote`. | | +| `data.attributes.name` | string | none | The name of the workspace. You can only include letters, numbers, `-`, and `_`. Terraform uses this value to identify the workspace and must be unique in the organization. | | +| `data.attributes.source-name` | string | none | A friendly name for the application or client creating this workspace. If set, this will be displayed on the workspace as "Created via ``". | | +| `data.attributes.source-url` | string | (nothing) | A URL for the application or client creating this workspace. This can be the URL of a related resource in another app, or a link to documentation or other info about the client. | | +| `data.attributes.terraform-version` | string | latest release | Specifies the version of Terraform to use for this workspace. You can specify an exact version or a [version constraint](/terraform/language/expressions/version-constraints) such as `~> 1.0.0`. If you specify a constraint, the workspace always uses the newest release that meets that constraint. If omitted when creating a workspace, this defaults to the latest released version. | | +| `data.relationships.project.data.id` | string | | The ID of the project to create the workspace in. You must have permission to create workspaces in the project, either by organization-level permissions or team admin access to a specific project. | | +| `data.relationships.project.data.type` | string | | Must be `"project"`. | | +| `data.relationships.vars.data[].type` | string | | Must be `"vars"`. | | +| `data.relationships.vars.data[].attributes.key` | string | | The name of the variable. | | +| `data.relationships.vars.data[].attributes.value` | string | `""` | The value of the variable. | | +| `data.relationships.vars.data[].attributes.description` | string | | The description of the variable. | | +| `data.relationships.vars.data[].attributes.category` | string | | Whether this is a Terraform or environment variable. Valid values are `"terraform"` or `"env"`. | | +| `data.relationships.vars.data[].attributes.hcl` | boolean | `false` | Whether to evaluate the value of the variable as a string of HCL code. Has no effect for environment variables. | | +| `data.relationships.vars.data[].attributes.sensitive` | boolean | `false` | Whether the value is sensitive. If `true` then the variable is written once and not visible thereafter. | | + +### Sample Payload + +```json +{ + "data": { + "type": "workspaces", + "attributes": { + "name": "no-code-workspace", + "description": "A workspace to enable the no-code provisioning workflow." + }, + "relationships": { + "project": { + "data": { + "id": "prj-yuEN6sJVra5t6XVy", + "type": "project" + } + }, + "vars": { + "data": [ + { + "type": "vars", + "attributes": { + "key": "region", + "value": "eu-central-1", + "category": "terraform", + "hcl": true, + "sensitive": false, + } + }, + { + "type": "vars", + "attributes": { + "key": "ami", + "value": "ami‑077062", + "category": "terraform", + "hcl": true, + "sensitive": false, + } + } + ] + } + } + } +} + +``` + +### Sample Request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request POST \ + --data @payload.json \ + https://app.terraform.io/api/v2/no-code-modules/nocode-WGckovT2RQxupyt1/workspaces +``` + +### Sample Response + +```json +{ + "data": { + "id": "ws-qACTToFUM5BvDKhC", + "type": "workspaces", + "attributes": { + "allow-destroy-plan": true, + "auto-apply": false, + "auto-destroy-at": null, + "auto-destroy-status": null, + "created-at": "2023-09-08T10:36:04.391Z", + "environment": "default", + "locked": false, + "name": "no-code-workspace", + "queue-all-runs": false, + "speculative-enabled": true, + "structured-run-output-enabled": true, + "terraform-version": "1.5.6", + "working-directory": null, + "global-remote-state": true, + "updated-at": "2023-09-08T10:36:04.427Z", + "resource-count": 0, + "apply-duration-average": null, + "plan-duration-average": null, + "policy-check-failures": null, + "run-failures": null, + "workspace-kpis-runs-count": null, + "latest-change-at": "2023-09-08T10:36:04.391Z", + "operations": true, + "execution-mode": "remote", + "vcs-repo": null, + "vcs-repo-identifier": null, + "permissions": { + "can-update": true, + "can-destroy": true, + "can-queue-run": true, + "can-read-variable": true, + "can-update-variable": true, + "can-read-state-versions": true, + "can-read-state-outputs": true, + "can-create-state-versions": true, + "can-queue-apply": true, + "can-lock": true, + "can-unlock": true, + "can-force-unlock": true, + "can-read-settings": true, + "can-manage-tags": true, + "can-manage-run-tasks": true, + "can-force-delete": true, + "can-manage-assessments": true, + "can-manage-ephemeral-workspaces": true, + "can-read-assessment-results": true, + "can-queue-destroy": true + }, + "actions": { + "is-destroyable": true + }, + "description": null, + "file-triggers-enabled": true, + "trigger-prefixes": [], + "trigger-patterns": [], + "assessments-enabled": false, + "last-assessment-result-at": null, + "source": "tfe-module", + "source-name": null, + "source-url": null, + "source-module-id": "private/my-organization/lambda/aws/1.0.9", + "no-code-upgrade-available": false, + "tag-names": [], + "setting-overwrites": { + "execution-mode": false, + "agent-pool": false + } + }, + "relationships": { + "organization": { + "data": { + "id": "my-organization", + "type": "organizations" + } + }, + "current-run": { + "data": null + }, + "latest-run": { + "data": null + }, + "outputs": { + "data": [] + }, + "remote-state-consumers": { + "links": { + "related": "/api/v2/workspaces/ws-qACTToFUM5BvDKhC/relationships/remote-state-consumers" + } + }, + "current-state-version": { + "data": null + }, + "current-configuration-version": { + "data": { + "id": "cv-vizi2p3mnrt3utgA", + "type": "configuration-versions" + }, + "links": { + "related": "/api/v2/configuration-versions/cv-vizi2p3mnrt3utgA" + } + }, + "agent-pool": { + "data": null + }, + "readme": { + "data": null + }, + "project": { + "data": { + "id": "prj-yuEN6sJVra5t6XVy", + "type": "projects" + } + }, + "current-assessment-result": { + "data": null + }, + "no-code-module-version": { + "data": { + "id": "nocodever-vFcQjZLs3ZHTe4TU", + "type": "no-code-module-versions" + } + }, + "vars": { + "data": [] + } + }, + "links": { + "self": "/api/v2/organizations/my-organization/workspaces/no-code-workspace", + "self-html": "/app/my-organization/workspaces/no-code-workspace" + } + } +} +``` + +## Initiate a no-code workspace update + +Upgrading a workspace's no-code provisioning settings is a multi-step process. + +1. Use this API to initiate the update. HCP Terraform starts a new plan, which describes the resources to add, update, or remove from the workspace. +2. Poll the [read workspace upgrade plan status API](#read-workspace-upgrade-plan-status) to wait for HCP Terraform to complete the plan. +3. Use the [confirm and apply a workspace upgrade plan API](#confirm-and-apply-a-workspace-upgrade-plan) to complete the workspace upgrade. + +`POST /no-code-modules/:no_code_module_id/workspaces/:id/upgrade` + +| Parameter | Description | +| -------------------- | ----------------------------- | +| `:no_code_module_id` | The ID of the no-code module. | +| `:id` | The ID of the workspace. | + +### Request Body + +This POST endpoint requires a JSON object with the following properties as a request payload. + +Properties without a default value are required. + +| Key path | Type | Default | Description | +| ------------------------------------------------------- | ------- | ------- | --------------------------------------------------------------------------------------------------------------- | +| `data.type` | string | | Must be `"workspaces"`. | +| `data.relationships.vars.data[].type` | string | | Must be `"vars"`. | +| `data.relationships.vars.data[].attributes.key` | string | | The name of the variable. | +| `data.relationships.vars.data[].attributes.value` | string | `""` | The value of the variable. | +| `data.relationships.vars.data[].attributes.description` | string | | The description of the variable. | +| `data.relationships.vars.data[].attributes.category` | string | | Whether this is a Terraform or environment variable. Valid values are `"terraform"` or `"env"`. | +| `data.relationships.vars.data[].attributes.hcl` | boolean | `false` | Whether to evaluate the value of the variable as a string of HCL code. Has no effect for environment variables. | +| `data.relationships.vars.data[].attributes.sensitive` | boolean | `false` | Whether the value is sensitive. If `true` then the variable is written once and not visible thereafter. | + +### Sample Payload + +```json +{ + "data": { + "type": "workspaces", + "relationships": { + "vars": { + "data": [ + { + "type": "vars", + "attributes": { + "key": "region", + "value": "eu-central-1", + "category": "terraform", + "hcl": true, + "sensitive": false, + } + }, + { + "type": "vars", + "attributes": { + "key": "ami", + "value": "ami‑077062", + "category": "terraform", + "hcl": true, + "sensitive": false, + } + } + ] + } + } + } +} + +``` + +### Sample Request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request POST \ + --data @payload.json \ + https://app.terraform.io/api/v2/no-code-modules/nocode-WGckovT2RQxupyt1/workspaces/ws-qACTToFUM5BvDKhC/upgrade +``` + +### Sample Response + +```json +{ + "data": { + "id": "run-Cyij8ctBHM1g5xdX", + "type": "workspace-upgrade", + "attributes": { + "status": "planned", + "plan-url": "https://app.terraform.io/app/my-organization/no-code-workspace/runs/run-Cyij8ctBHM1g5xdX" + }, + "relationships": { + "workspace": { + "data": { + "id": "ws-VvKtcfueHNkR6GqP", + "type": "workspaces" + } + } + } + } +} +``` + +## Read workspace upgrade plan status + +This endpoint returns the plan details and status for updating a workspace to new no-code provisioning settings. + +`GET /no-code-modules/:no_code_module_id/workspaces/:workspace_id/upgrade/:id` + +| Parameter | Description | +| -------------------- | ----------------------------- | +| `:no_code_module_id` | The ID of the no-code module. | +| `:workspace_id` | The ID of workspace. | +| `:id` | The ID of update. | + +Returns the details of a no-code workspace update run, including the run's current state, such as `pending`, `fetching`, `planning`, `planned`, or `cost_estimated`. Refer to [Run States and Stages](/terraform/enterprise/run/states) for more information on the states a run can return. + +| Status | Response | Reason | +| ------- | --------------------------------------------------- | ------------------------------------------------------------------- | +| [200][] | [JSON API document][] (`type: "workspace-upgrade"`) | Success | +| [404][] | [JSON API error object][] | Workspace upgrade not found, or user unauthorized to perform action | + +### Sample Request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request GET \ + https://app.terraform.io/api/v2/no-code-modules/nocode-WGckovT2RQxupyt1/workspaces/ws-qACTToFUM5BvDKhC/upgrade/run-Cyij8ctBHM1g5xdX +``` + +### Sample Response + +```json +{ + "data": { + "id": "run-Cyij8ctBHM1g5xdX", + "type": "workspace-upgrade", + "attributes": { + "status": "planned_and_finished", + "plan-url": "https://app.terraform.io/app/my-organization/no-code-workspace/runs/run-Cyij8ctBHM1g5xdX" + }, + "relationships": { + "workspace": { + "data": { + "id": "ws-VvKtcfueHNkR6GqP", + "type": "workspaces" + } + } + } + } +} +``` + +## Confirm and apply a workspace upgrade plan + +Use this endpoint to confirm an update and finalize the update for a workspace to use new no-code provisioning settings. + +`POST /no-code-modules/:no_code_module_id/workspaces/:workspace_id/upgrade/:id` + +| Parameter | Description | +| -------------------- | ----------------------------- | +| `:no_code_module_id` | The ID of the no-code module. | +| `:workspace_id` | The ID of workspace. | +| `:id` | The ID of update. | + +### Sample Request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request POST \ + https://app.terraform.io/api/v2/no-code-modules/nocode-WGckovT2RQxupyt1/workspaces/ws-qACTToFUM5BvDKhC/upgrade/run-Cyij8ctBHM1g5xdX +``` + +### Sample Response + +```json +{ "Workspace update completed" } +``` diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/notification-configurations.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/notification-configurations.mdx new file mode 100644 index 0000000000..2fa78f811f --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/notification-configurations.mdx @@ -0,0 +1,1437 @@ +--- +page_title: /notification-configurations API reference for Terraform Enterprise +description: >- + Use the Terraform Enterprise API's `/notification-configurations` endpoint to + read, create, update, verify, and delete workspace configurations and create + team configurations. +source: terraform-docs-common +--- + +[200]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/200 + +[201]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/201 + +[202]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/202 + +[204]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/204 + +[400]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/400 + +[401]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/401 + +[403]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/403 + +[404]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404 + +[409]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/409 + +[412]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/412 + +[422]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/422 + +[429]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/429 + +[500]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500 + +[504]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/504 + +[JSON API document]: /terraform/enterprise/api-docs#json-api-documents + +[JSON API error object]: https://jsonapi.org/format/#error-objects + +# Notification configurations API reference + +HCP Terraform can send notifications for run state transitions and workspace events. You can specify a destination URL, request type, and what events will trigger the notification. Each workspace can have up to 20 notification configurations, and they apply to all runs for that workspace. + +Interacting with notification configurations requires admin access to the relevant workspace. ([More about permissions](/terraform/enterprise/users-teams-organizations/permissions).) + +-> **Note:** [Speculative plans](/terraform/enterprise/run/modes-and-options#plan-only-speculative-plan) and workspaces configured with `Local` [execution mode](/terraform/enterprise/workspaces/settings#execution-mode) do not support notifications. + +[permissions-citation]: #intentionally-unused---keep-for-maintainers + +## Notification triggers + +Notifications are sent in response to triggers related to workspace events, and can be defined at workspace and team levels. You can specify workspace events in the `triggers` array attribute. + +### Workspace notification triggers + +The following triggers are available for workspace notifications. + +| Display Name | Value | Description | +| ------------------------ | -------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Created | `"run:created"` | A run is created and enters the [Pending stage](/terraform/enterprise/run/states#the-pending-stage) | +| Planning | `"run:planning"` | A run acquires the lock and starts to execute. | +| Needs Attention | `"run:needs_attention"` | A plan has changes and Terraform requires user input to continue. This input may include approving the plan or a [policy override](/terraform/enterprise/run/states#the-policy-check-stage). | +| Applying | `"run:applying"` | A run enters the [Apply stage](/terraform/enterprise/run/states#the-apply-stage), where Terraform makes the infrastructure changes described in the plan. | +| Completed | `"run:completed"` | A run completes successfully. | +| Errored | `"run:errored"` | A run terminates early due to error or cancellation. | +| Drifted | `"assessment:drifted"` | HCP Terraform detected configuration drift. This option is only available if you enabled drift detection for the workspace. | +| Checks Failed | `"assessment:check_failure"` | One or more continuous validation checks did not pass. This option is only available if you enabled drift detection for the workspace. | +| Health Assessment Failed | `"assessment:failed"` | A health assessment failed. This option is only available if you enable health assessments for the workspace. | +| Auto Destroy Reminder | `"workspace:auto_destro_reminder"` | An automated workspace destroy run is imminent. | +| Auto Destroy Results | `"workspace:auto_destroy_run_results"` | HCP Terraform attempted an automated workspace destroy run. | + +### Team notification triggers + +The following triggers are available for [team notifications](#team-notification-configuration). + +| Display Name | Value | Description | +| -------------- | ----------------------- | -------------------------------------------------------------------------------------------------- | +| Change Request | `"team:change_request"` | HCP Terraform sent a change request to a workspace that the specified team has explicit access to. | + +## Notification payload + +The notification is an HTTP POST request with a detailed payload. The content depends on the type of notification. + +For Slack and Microsoft Teams notifications, the payload conforms to the respective webhook API and results in a notification message with informational attachments. Refer to [Slack Notification Payloads](/terraform/enterprise/workspaces/settings/notifications#slack) and [Microsoft Teams Notification Payloads](/terraform/enterprise/workspaces/settings/notifications#microsoft-teams) for examples. For generic notifications, the payload varies based on whether the notification contains information about run events or workspace events. + +### Run notification payload + +Run events include detailed information about a specific run, including the time it began and the associated workspace and organization. Generic notifications for run events contain the following information: + +| Name | Type | Description | +| -------------------------------- | ------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `payload_version` | number | Always "1". | +| `notification_configuration_id` | string | The ID of the configuration associated with this notification. | +| `run_url` | string | URL used to access the run UI page. | +| `run_id` | string | ID of the run which triggered this notification. | +| `run_message` | string | The reason the run was queued. | +| `run_created_at` | string | Timestamp of the run's creation. | +| `run_created_by` | string | Username of the user who created the run. | +| `workspace_id` | string | ID of the run's workspace. | +| `workspace_name` | string | Human-readable name of the run's workspace. | +| `organization_name` | string | Human-readable name of the run's organization. | +| `notifications` | array | List of events which caused this notification to be sent, with each event represented by an object. At present, this is always one event, but in the future HCP Terraform may roll up several notifications for a run into a single request. | +| `notifications[].message` | string | Human-readable reason for the notification. | +| `notifications[].trigger` | string | Value of the trigger which caused the notification to be sent. | +| `notifications[].run_status` | string | Status of the run at the time of notification. | +| `notifications[].run_updated_at` | string | Timestamp of the run's update. | +| `notifications[].run_updated_by` | string | Username of the user who caused the run to update. | + +#### Sample payload + +```json +{ + "payload_version": 1, + "notification_configuration_id": "nc-AeUQ2zfKZzW9TiGZ", + "run_url": "https://app.terraform.io/app/acme-org/my-workspace/runs/run-FwnENkvDnrpyFC7M", + "run_id": "run-FwnENkvDnrpyFC7M", + "run_message": "Add five new queue workers", + "run_created_at": "2019-01-25T18:34:00.000Z", + "run_created_by": "sample-user", + "workspace_id": "ws-XdeUVMWShTesDMME", + "workspace_name": "my-workspace", + "organization_name": "acme-org", + "notifications": [ + { + "message": "Run Canceled", + "trigger": "run:errored", + "run_status": "canceled", + "run_updated_at": "2019-01-25T18:37:04.000Z", + "run_updated_by": "sample-user" + } + ] +} +``` + +### Change request notification payload + +Change request events contain the following fields in their payloads. + +| Name | Type | Description | +| ------------------------------- | ------ | -------------------------------------------------------------- | +| `payload_version` | number | Always "1". | +| `notification_configuration_id` | string | The ID of the configuration associated with this notification. | +| `change_request_url` | string | URL used to access the change request UI page. | +| `change_request_subject` | string | title of the change request which triggered this notification. | +| `change_request_message` | string | The contents of the change request. | +| `change_request_created_at` | string | Timestamp of the change request's creation. | +| `change_request_created_by` | string | Username of the user who created the change_request. | +| `workspace_id` | string | ID of the run's workspace. | +| `workspace_name` | string | Human-readable name of the run's workspace. | +| `organization_name` | string | Human-readable name of the run's organization. | + +##### `Send a test` payload + +This is a sample payload you can send to test if notifications are working. The payload does not have a `run` or `workspace` context, resulting in null values. + +You can trigger a test notification from the workspace notification settings page. You can read more about verifying a [notification configuration](/terraform/enterprise/workspaces/settings/notifications#enabling-and-verifying-a-configuration). + +```json +{ + "payload_version": 1, + "notification_configuration_id": "nc-jWvVsmp5VxsaCeXm", + "run_url": null, + "run_id": null, + "run_message": null, + "run_created_at": null, + "run_created_by": null, + "workspace_id": null, + "workspace_name": null, + "organization_name": null, + "notifications": [ + { + "message": "Verification of test", + "trigger": "verification", + "run_status": null, + "run_updated_at": null, + "run_updated_by": null, + } + ] +} +``` + +### Workspace notification payload + +Workspace events include detailed information about workspace-level validation events like [health assessments](/terraform/enterprise/workspaces/health) if you enable them for the workspace. Much of the information provides details about the associated [assessment result](/terraform/enterprise/api-docs/assessment-results), which HCP Terraform uses to track instances of continuous validation. + +HCP Terraform returns different types of attributes returned in the payload details, depending on the type of `trigger_scope`. There are two main values for `trigger_scope`: `assessment` and `workspace`, examples of which you can see below. + +#### Health assessments + +Health assessment notifications for workspace events contain the following information: + + + +@include 'tfc-package-callouts/health-assessments.mdx' + + + +| Name | Type | Description | +| ------------------------------------------------------ | ------ | ------------------------------------------------------------------------------------------- | +| `payload_version` | number | Always "2". | +| `notification_configuration_id` | string | The ID of the configuration associated with this notification. | +| `notification_configuration_url` | string | URL to get the notification configuration from the HCP Terraform API. | +| `trigger_scope` | string | Always "assessment" for workspace assessment notifications. | +| `trigger` | string | Value of the trigger that caused the notification to be sent. | +| `message` | string | Human-readable reason for the notification. | +| `details` | object | Object containing details specific to the notification. | +| `details.new_assessment_result` | object | The most recent assessment result. This result triggered the notification. | +| `details.new_assessment_result.id` | string | ID of the assessment result. | +| `details.new_assessment_result.url` | string | URL to get the assessment result from the HCP Terraform API. | +| `details.new_assessment_result.succeeded` | bool | Whether assessment succeeded. | +| `details.new_assessment_result.all_checks_succeeded` | bool | Whether all conditions passed. | +| `details.new_assessment_result.checks_passed` | number | The number of resources, data sources, and outputs passing their conditions. | +| `details.new_assessment_result.checks_failed` | number | The number of resources, data sources, and outputs with one or more failing conditions. | +| `details.new_assessment_result.checks_errored` | number | The number of resources, data sources, and outputs that had a condition error. | +| `details.new_assessment_result.checks_unknown` | number | The number of resources, data sources, and outputs that had conditions left unevaluated. | +| `details.new_assessment_result.drifted` | bool | Whether assessment detected drift. | +| `details.new_assessment_result.resources_drifted` | number | The number of resources whose configuration does not match from the workspace's state file. | +| `details.new_assessment_result.resources_undrifted` | number | The number of real resources whose configuration matches the workspace's state file. | +| `details.new_assessment_result.created_at` | string | Timestamp for when HCP Terraform created the assessment result. | +| `details.prior_assessment_result` | object | The assessment result immediately prior to the one that triggered the notification. | +| `details.prior_assessment_result.id` | string | ID of the assessment result. | +| `details.prior_assessment_result.url` | string | URL to get the assessment result from the HCP Terraform API. | +| `details.prior_assessment_result.succeeded` | bool | Whether assessment succeeded. | +| `details.prior_assessment_result.all_checks_succeeded` | bool | Whether all conditions passed. | +| `details.prior_assessment_result.checks_passed` | number | The number of resources, data sources, and outputs passing their conditions. | +| `details.prior_assessment_result.checks_failed` | number | The number of resources, data sources, and outputs with one or more failing conditions. | +| `details.prior_assessment_result.checks_errored` | number | The number of resources, data sources, and outputs that had a condition error. | +| `details.prior_assessment_result.checks_unknown` | number | The number of resources, data sources, and outputs that had conditions left unevaluated. | +| `details.prior_assessment_result.drifted` | bool | Whether assessment detected drift. | +| `details.prior_assessment_result.resources_drifted` | number | The number of resources whose configuration does not match the workspace's state file. | +| `details.prior_assessment_result.resources_undrifted` | number | The number of resources whose configuration matches the workspace's state file. | +| `details.prior_assessment_result.created_at` | string | Timestamp of the assessment result. | +| `details.workspace_id` | string | ID of the workspace that generated the notification. | +| `details.workspace_name` | string | Human-readable name of the workspace. | +| `details.organization_name` | string | Human-readable name of the organization. | + +##### Sample payload + +Health assessment payloads have information about resource drift and continuous validation checks. + +```json +{ + "payload_version": "2", + "notification_configuration_id": "nc-SZ3V3cLFxK6sqLKn", + "notification_configuration_url": "https://app.terraform.io/api/v2/notification-configurations/nc-SZ3V3cLFxK6sqLKn", + "trigger_scope": "assessment", + "trigger": "assessment:drifted", + "message": "Drift Detected", + "details": { + "new_assessment_result": { + "id": "asmtres-vRVQxpqq64EA9V5a", + "url": "https://app.terraform.io/api/v2/assessment-results/asmtres-vRVQxpqq64EA9V5a", + "succeeded": true, + "drifted": true, + "all_checks_succeeded": true, + "resources_drifted": 4, + "resources_undrifted": 55, + "checks_passed": 33, + "checks_failed": 0, + "checks_errored": 0, + "checks_unknown": 0, + "created_at": "2022-06-09T05:23:10Z" + }, + "prior_assessment_result": { + "id": "asmtres-A6zEbpGArqP74fdL", + "url": "https://app.terraform.io/api/v2/assessment-results/asmtres-A6zEbpGArqP74fdL", + "succeeded": true, + "drifted": true, + "all_checks_succeeded": true, + "resources_drifted": 4, + "resources_undrifted": 55, + "checks_passed": 33, + "checks_failed": 0, + "checks_errored": 0, + "checks_unknown": 0, + "created_at": "2022-06-09T05:22:51Z" + }, + "workspace_id": "ws-XdeUVMWShTesDMME", + "workspace_name": "my-workspace", + "organization_name": "acme-org" + } +} +``` + +#### Automatic destroy runs + + + +@include 'tfc-package-callouts/ephemeral-workspaces.mdx' + + + +Automatic destroy run notifications for workspace events contain the following information: + +| Name | Type | Description | +| ---------------------------------- | ------ | ---------------------------------------------------------------------------------------------------------- | +| `payload_version` | string | Always 2. | +| `notification_configuration_id` | string | The ID of the notification's configuration. | +| `notification_configuration_url` | string | The URL to get the notification's configuration from the HCP Terraform API. | +| `trigger_scope` | string | Always "workspace" for ephemeral workspace notifications | +| `trigger` | string | Value of the trigger that caused HCP Terraform to send the notification. | +| `message` | string | Human-readable reason for the notification. | +| `details` | object | Object containing details specific to the notification. | +| `details.auto_destroy_at` | string | Timestamp when HCP Terraform will schedule the next destroy run. Only applies to reminder notifications. | +| `details.run_created_at` | string | Timestamp of when HCP Terraform successfully created a destroy run. Only applies to results notifications. | +| `details.run_status` | string | Status of the scheduled destroy run. Only applies to results notifications. | +| `details.run_external_id` | string | The ID of the scheduled destroy run. Only applies to results notifications. | +| `details.run_create_error_message` | string | Message detailing why the run was unable to be queued. Only applies to results notifications. | +| `details.trigger_type` | string | The type of notification, and the value is either "reminder" or "results". | +| `details.workspace_name` | string | Human-readable name of the workspace. | +| `details.organization_name` | string | Human-readable name of the organization. | + +##### Sample payload + +The shape of data in auto destroy notification payloads may differ depending on the success of the run HCP Terraform created. Refer to the specific examples below. + +###### Reminder + +Reminders that HCP Terraform will trigger a destroy run at some point in the future. + +```json +{ + "payload_version": "2", + "notification_configuration_id": "nc-SZ3V3cLFxK6sqLKn", + "notification_configuration_url": "https://app.terraform.io/api/v2/notification-configurations/nc-SZ3V3cLFxK6sqLKn", + "trigger_scope": "workspace", + "trigger": "workspace:auto_destroy_reminder", + "message": "Auto Destroy Reminder", + "details": { + "auto_destroy_at": "2025-01-01T00:00:00Z", + "run_created_at": null, + "run_status": null, + "run_external_id": null, + "run_create_error_message": null, + "trigger_type": "reminder", + "workspace_name": "learned-english-dog", + "organization_name": "acme-org" + } +} +``` + +###### Results + +The final result of the scheduled auto destroy run includes additional metadata about the run. + +```json +{ + "payload_version": "2", + "notification_configuration_id": "nc-SZ3V3cLFxK6sqLKn", + "notification_configuration_url": "https://app.terraform.io/api/v2/notification-configurations/nc-SZ3V3cLFxK6sqLKn", + "trigger_scope": "workspace", + "trigger": "workspace:auto_destroy_results", + "message": "Auto Destroy Results", + "details": { + "auto_destroy_at": null, + "run_created_at": "2022-06-09T05:22:51Z", + "run_status": "applied", + "run_external_id": "run-vRVQxpqq64EA9V5a", + "run_create_error_message": null, + "trigger_type": "results", + "workspace_name": "learned-english-dog", + "organization_name": "acme-org" + } +} +``` + +###### Failed run creation + +Run-specific values are empty when HCP Terraform was unable to create an auto destroy run. + +```json +{ + "payload_version": "2", + "notification_configuration_id": "nc-SZ3V3cLFxK6sqLKn", + "notification_configuration_url": "https://app.terraform.io/api/v2/notification-configurations/nc-SZ3V3cLFxK6sqLKn", + "trigger_scope": "workspace", + "trigger": "workspace:auto_destroy_results", + "message": "Auto Destroy Results", + "details": { + "auto_destroy_at": null, + "run_created_at": null, + "run_status": null, + "run_external_id": null, + "run_create_error_message": "Configuration version is missing", + "trigger_type": "results", + "workspace_name": "learned-english-dog", + "organization_name": "acme-org" + } +} +``` + +## Notification authenticity + +If a `token` is configured, HCP Terraform provides an HMAC signature on all `"generic"` notification requests, using the `token` as the key. This is sent in the `X-TFE-Notification-Signature` header. The digest algorithm used is SHA-512. Notification target servers should verify the source of the HTTP request by computing the HMAC of the request body using the same shared secret, and dropping any requests with invalid signatures. + +Sample Ruby code for verifying the HMAC: + +```ruby +token = SecureRandom.hex +hmac = OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new("sha512"), token, @request.body) +fail "Invalid HMAC" if hmac != @request.headers["X-TFE-Notification-Signature"] +``` + +## Notification verification and delivery responses + +When saving a configuration with `enabled` set to `true`, or when using the [verify API][], HCP Terraform sends a verification request to the configured URL. The response to this request is stored and available in the `delivery-responses` array of the `notification-configuration` resource. + +Configurations cannot be enabled if the verification request fails. Success is defined as an HTTP response with status code of `2xx`. +Configurations with `destination_type` `email` can only be verified manually, they do not require an HTTP response. + +The most recent response is stored in the `delivery-responses` array. + +Each delivery response has several fields: + +| Name | Type | Description | +| ------------ | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `body` | string | Response body (may be truncated). | +| `code` | string | HTTP status code, e.g. `400`. | +| `headers` | object | All HTTP headers received, represented as an object with keys for each header name (lowercased) and an array of string values (most arrays will be size one). | +| `sent-at` | date | The UTC timestamp when the notification was sent. | +| `successful` | bool | Whether HCP Terraform considers this response to be successful. | +| `url` | string | The URL to which the request was sent. | + +[verify API]: #verify-a-notification-configuration + +## Create a notification configuration + +`POST /workspaces/:workspace_id/notification-configurations` + +| Parameter | Description | +| --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `:workspace_id` | The ID of the workspace to list configurations for. Obtain this from the [workspace settings](/terraform/enterprise/workspaces/settings) or the [Show Workspace](/terraform/enterprise/api-docs/workspaces#show-workspace) endpoint. | + +| Status | Response | Reason | +| ------- | ------------------------------------------------------------- | -------------------------------------------------------------- | +| [201][] | [JSON API document][] (`type: "notification-configurations"`) | Successfully created a notification configuration | +| [400][] | [JSON API error object][] | Unable to complete verification request to destination URL | +| [404][] | [JSON API error object][] | Workspace not found, or user unauthorized to perform action | +| [422][] | [JSON API error object][] | Malformed request body (missing attributes, wrong types, etc.) | + +### Request body + +This POST endpoint requires a JSON object with the following properties as a request payload. + +Properties without a default value are required. + +If `enabled` is set to `true`, a verification request will be sent before saving the configuration. If this request receives no response or the response is not successful (HTTP 2xx), the configuration will not save. + +| Key path | Type | Default | Description | +| ---------------------------------- | -------------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `data.type` | string | | Must be `"notification-configuration"`. | +| `data.attributes.destination-type` | string | | Type of notification payload to send. Valid values are `"generic"`, `"email"`, `"slack"` or `"microsoft-teams"`. | +| `data.attributes.enabled` | bool | `false` | Disabled configurations will not send any notifications. | +| `data.attributes.name` | string | | Human-readable name for the configuration. | +| `data.attributes.token` | string or null | `null` | Optional write-only secure token, which can be used at the receiving server to verify request authenticity. See [Notification Authenticity][notification-authenticity] for more details. | +| `data.attributes.triggers` | array | `[]` | Array of triggers for which this configuration will send notifications. See [Notification Triggers][notification-triggers] for more details and a list of allowed values. | +| `data.attributes.url` | string | | HTTP or HTTPS URL to which notification requests will be made, only for configurations with `"destination_type:"` `"slack"`, `"microsoft-teams"` or `"generic"` | +| `data.relationships.users` | array | | Array of users part of the organization, only for configurations with `"destination_type:"` `"email"` | + +[notification-authenticity]: #notification-authenticity + +[notification-triggers]: #notification-triggers + +### Sample payload for generic notification configurations + +```json +{ + "data": { + "type": "notification-configuration", + "attributes": { + "destination-type": "generic", + "enabled": true, + "name": "Webhook server test", + "url": "https://httpstat.us/200", + "triggers": [ + "run:applying", + "run:completed", + "run:created", + "run:errored", + "run:needs_attention", + "run:planning" + ] + } + } +} +``` + +### Sample payload for email notification configurations + +```json +{ + "data": { + "type": "notification-configurations", + "attributes": { + "destination-type": "email", + "enabled": true, + "name": "Notify organization users about run", + "triggers": [ + "run:applying", + "run:completed", + "run:created", + "run:errored", + "run:needs_attention", + "run:planning" + ] + }, + "relationships": { + "users": { + "data": [ { "id": "organization-user-id", "type": "users" } ] + } + } + } +} +``` + +### Sample request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request POST \ + --data @payload.json \ + https://app.terraform.io/api/v2/workspaces/ws-XdeUVMWShTesDMME/notification-configurations +``` + +### Sample response + +```json +{ + "data": { + "id": "nc-AeUQ2zfKZzW9TiGZ", + "type": "notification-configurations", + "attributes": { + "enabled": true, + "name": "Webhook server test", + "url": "https://httpstat.us/200", + "destination-type": "generic", + "token": null, + "triggers": [ + "run:applying", + "run:completed", + "run:created", + "run:errored", + "run:needs_attention", + "run:planning" + ], + "delivery-responses": [ + { + "url": "https://httpstat.us/200", + "body": "\"200 OK\"", + "code": "200", + "headers": { + "cache-control": [ + "private" + ], + "content-length": [ + "129" + ], + "content-type": [ + "application/json; charset=utf-8" + ], + "content-encoding": [ + "gzip" + ], + "vary": [ + "Accept-Encoding" + ], + "server": [ + "Microsoft-IIS/10.0" + ], + "x-aspnetmvc-version": [ + "5.1" + ], + "access-control-allow-origin": [ + "*" + ], + "x-aspnet-version": [ + "4.0.30319" + ], + "x-powered-by": [ + "ASP.NET" + ], + "set-cookie": [ + "ARRAffinity=77c477e3e649643e5771873e1a13179fb00983bc73c71e196bf25967fd453df9;Path=/;HttpOnly;Domain=httpstat.us" + ], + "date": [ + "Tue, 08 Jan 2019 21:34:37 GMT" + ] + }, + "sent-at": "2019-01-08 21:34:37 UTC", + "successful": "true" + } + ], + "created-at": "2019-01-08T21:32:14.125Z", + "updated-at": "2019-01-08T21:34:37.274Z" + }, + "relationships": { + "subscribable": { + "data": { + "id": "ws-XdeUVMWShTesDMME", + "type": "workspaces" + } + } + }, + "links": { + "self": "/api/v2/notification-configurations/nc-AeUQ2zfKZzW9TiGZ" + } + } +} +``` + +## List notification configurations + +Use the following endpoint to list all notification configurations for a workspace. + +`GET /workspaces/:workspace_id/notification-configurations` + +| Parameter | Description | +| --------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `:workspace_id` | The ID of the workspace to list configurations from. Obtain this from the [workspace settings](/terraform/enterprise/workspaces/settings) or the [Show Workspace](/terraform/enterprise/api-docs/workspaces#show-workspace) endpoint. If neither pagination query parameters are provided, the endpoint will not be paginated and will return all results. | + +### Query parameters + +This endpoint supports pagination [with standard URL query parameters](/terraform/enterprise/api-docs#query-parameters). Remember to percent-encode `[` as `%5B` and `]` as `%5D` if your tooling doesn't automatically encode URLs. + +| Parameter | Description | +| -------------- | ------------------------------------------------------------------------------------------- | +| `page[number]` | **Optional.** If omitted, the endpoint will return the first page. | +| `page[size]` | **Optional.** If omitted, the endpoint will return 20 notification configurations per page. | + +### Sample request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request GET \ + https://app.terraform.io/api/v2/workspaces/ws-XdeUVMWShTesDMME/notification-configurations +``` + +### Sample response + +```json +{ + "data": [ + { + "id": "nc-W6VGEi8A7Cfoaf4K", + "type": "notification-configurations", + "attributes": { + "enabled": false, + "name": "Slack: #devops", + "url": "https://hooks.slack.com/services/T00000000/BC012345/0PWCpQmYyD4bTTRYZ53q4w", + "destination-type": "slack", + "token": null, + "triggers": [ + "run:errored", + "run:needs_attention" + ], + "delivery-responses": [], + "created-at": "2019-01-08T21:34:28.367Z", + "updated-at": "2019-01-08T21:34:28.367Z" + }, + "relationships": { + "subscribable": { + "data": { + "id": "ws-XdeUVMWShTesDMME", + "type": "workspaces" + } + } + }, + "links": { + "self": "/api/v2/notification-configurations/nc-W6VGEi8A7Cfoaf4K" + } + }, + { + "id": "nc-AeUQ2zfKZzW9TiGZ", + "type": "notification-configurations", + "attributes": { + "enabled": true, + "name": "Webhook server test", + "url": "https://httpstat.us/200", + "destination-type": "generic", + "token": null, + "triggers": [ + "run:applying", + "run:completed", + "run:created", + "run:errored", + "run:needs_attention", + "run:planning" + ], + "delivery-responses": [ + { + "url": "https://httpstat.us/200", + "body": "\"200 OK\"", + "code": "200", + "headers": { + "cache-control": [ + "private" + ], + "content-length": [ + "129" + ], + "content-type": [ + "application/json; charset=utf-8" + ], + "content-encoding": [ + "gzip" + ], + "vary": [ + "Accept-Encoding" + ], + "server": [ + "Microsoft-IIS/10.0" + ], + "x-aspnetmvc-version": [ + "5.1" + ], + "access-control-allow-origin": [ + "*" + ], + "x-aspnet-version": [ + "4.0.30319" + ], + "x-powered-by": [ + "ASP.NET" + ], + "set-cookie": [ + "ARRAffinity=77c477e3e649643e5771873e1a13179fb00983bc73c71e196bf25967fd453df9;Path=/;HttpOnly;Domain=httpstat.us" + ], + "date": [ + "Tue, 08 Jan 2019 21:34:37 GMT" + ] + }, + "sent-at": "2019-01-08 21:34:37 UTC", + "successful": "true" + } + ], + "created-at": "2019-01-08T21:32:14.125Z", + "updated-at": "2019-01-08T21:34:37.274Z" + }, + "relationships": { + "subscribable": { + "data": { + "id": "ws-XdeUVMWShTesDMME", + "type": "workspaces" + } + } + }, + "links": { + "self": "/api/v2/notification-configurations/nc-AeUQ2zfKZzW9TiGZ" + } + } + ] +} + +``` + +## Show a notification configuration + +`GET /notification-configurations/:notification-configuration-id` + +| Parameter | Description | +| -------------------------------- | ------------------------------------------------- | +| `:notification-configuration-id` | The id of the notification configuration to show. | + +### Sample request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request GET \ + https://app.terraform.io/api/v2/notification-configurations/nc-AeUQ2zfKZzW9TiGZ +``` + +### Sample response + +The `type` and `id` attributes in `relationships.subscribable` may also reference a `"teams"` and team ID, respectively. + +```json +{ + "data": { + "id": "nc-AeUQ2zfKZzW9TiGZ", + "type": "notification-configurations", + "attributes": { + "enabled": true, + "name": "Webhook server test", + "url": "https://httpstat.us/200", + "destination-type": "generic", + "token": null, + "triggers": [ + "run:applying", + "run:completed", + "run:created", + "run:errored", + "run:needs_attention", + "run:planning" + ], + "delivery-responses": [ + { + "url": "https://httpstat.us/200", + "body": "\"200 OK\"", + "code": "200", + "headers": { + "cache-control": [ + "private" + ], + "content-length": [ + "129" + ], + "content-type": [ + "application/json; charset=utf-8" + ], + "content-encoding": [ + "gzip" + ], + "vary": [ + "Accept-Encoding" + ], + "server": [ + "Microsoft-IIS/10.0" + ], + "x-aspnetmvc-version": [ + "5.1" + ], + "access-control-allow-origin": [ + "*" + ], + "x-aspnet-version": [ + "4.0.30319" + ], + "x-powered-by": [ + "ASP.NET" + ], + "set-cookie": [ + "ARRAffinity=77c477e3e649643e5771873e1a13179fb00983bc73c71e196bf25967fd453df9;Path=/;HttpOnly;Domain=httpstat.us" + ], + "date": [ + "Tue, 08 Jan 2019 21:34:37 GMT" + ] + }, + "sent-at": "2019-01-08 21:34:37 UTC", + "successful": "true" + } + ], + "created-at": "2019-01-08T21:32:14.125Z", + "updated-at": "2019-01-08T21:34:37.274Z" + }, + "relationships": { + "subscribable": { + "data": { + "id": "ws-XdeUVMWShTesDMME", + "type": "workspaces" + } + } + }, + "links": { + "self": "/api/v2/notification-configurations/nc-AeUQ2zfKZzW9TiGZ" + } + } +} +``` + +## Update a notification configuration + +`PATCH /notification-configurations/:notification-configuration-id` + +| Parameter | Description | +| -------------------------------- | --------------------------------------------------- | +| `:notification-configuration-id` | The id of the notification configuration to update. | + +If the `enabled` attribute is true, updating the configuration will cause HCP Terraform to send a verification request. If a response is received, it will be stored and returned in the `delivery-responses` attribute. More details in the [Notification Verification and Delivery Responses][] section above. + +[Notification Verification and Delivery Responses]: #notification-verification-and-delivery-responses + +| Status | Response | Reason | +| ------- | ------------------------------------------------------------- | ---------------------------------------------------------------------------- | +| [200][] | [JSON API document][] (`type: "notification-configurations"`) | Successfully updated the notification configuration | +| [400][] | [JSON API error object][] | Unable to complete verification request to destination URL | +| [404][] | [JSON API error object][] | Notification configuration not found, or user unauthorized to perform action | +| [422][] | [JSON API error object][] | Malformed request body (missing attributes, wrong types, etc.) | + +### Request body + +This PATCH endpoint requires a JSON object with the following properties as a request payload. + +If `enabled` is set to `true`, a verification request will be sent before saving the configuration. If this request fails to send, or the response is not successful (HTTP 2xx), the configuration will not save. + +| Key path | Type | Default | Description | +| -------------------------- | ------ | ---------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `data.type` | string | (previous value) | Must be `"notification-configuration"`. | +| `data.attributes.enabled` | bool | (previous value) | Disabled configurations will not send any notifications. | +| `data.attributes.name` | string | (previous value) | User-readable name for the configuration. | +| `data.attributes.token` | string | (previous value) | Optional write-only secure token, which can be used at the receiving server to verify request authenticity. See [Notification Authenticity][notification-authenticity] for more details. | +| `data.attributes.triggers` | array | (previous value) | Array of triggers for sending notifications. See [Notification Triggers][notification-triggers] for more details. | +| `data.attributes.url` | string | (previous value) | HTTP or HTTPS URL to which notification requests will be made, only for configurations with `"destination_type:"` `"slack"`, `"microsoft-teams"` or `"generic"` | +| `data.relationships.users` | array | | Array of users part of the organization, only for configurations with `"destination_type:"` `"email"` | + +[notification-authenticity]: #notification-authenticity + +[notification-triggers]: #notification-triggers + +### Sample payload + +```json +{ + "data": { + "id": "nc-W6VGEi8A7Cfoaf4K", + "type": "notification-configurations", + "attributes": { + "enabled": false, + "name": "Slack: #devops", + "url": "https://hooks.slack.com/services/T00000001/BC012345/0PWCpQmYyD4bTTRYZ53q4w", + "destination-type": "slack", + "token": null, + "triggers": [ + "run:created", + "run:errored", + "run:needs_attention" + ], + } + } +} +``` + +### Sample request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request PATCH \ + --data @payload.json \ + https://app.terraform.io/api/v2/notification-configurations/nc-W6VGEi8A7Cfoaf4K +``` + +### Sample response + +```json +{ + "data": { + "id": "nc-W6VGEi8A7Cfoaf4K", + "type": "notification-configurations", + "attributes": { + "enabled": false, + "name": "Slack: #devops", + "url": "https://hooks.slack.com/services/T00000001/BC012345/0PWCpQmYyD4bTTRYZ53q4w", + "destination-type": "slack", + "token": null, + "triggers": [ + "run:created", + "run:errored", + "run:needs_attention" + ], + "delivery-responses": [], + "created-at": "2019-01-08T21:34:28.367Z", + "updated-at": "2019-01-08T21:49:02.103Z" + }, + "relationships": { + "subscribable": { + "data": { + "id": "ws-XdeUVMWShTesDMME", + "type": "workspaces" + } + } + }, + "links": { + "self": "/api/v2/notification-configurations/nc-W6VGEi8A7Cfoaf4K" + } + }, +} +``` + +## Verify a notification configuration + +`POST /notification-configurations/:notification-configuration-id/actions/verify` + +| Parameter | Description | +| -------------------------------- | --------------------------------------------------- | +| `:notification-configuration-id` | The id of the notification configuration to verify. | + +This will cause HCP Terraform to send a verification request for the specified configuration. If a response is received, it will be stored and returned in the `delivery-responses` attribute. More details in the [Notification Verification and Delivery Responses][] section above. + +| Status | Response | Reason | +| ------- | ------------------------------------------------------------- | ---------------------------------------------------------- | +| [200][] | [JSON API document][] (`type: "notification-configurations"`) | Successfully verified the notification configuration | +| [400][] | [JSON API error object][] | Unable to complete verification request to destination URL | + +### Sample request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request POST \ + https://app.terraform.io/api/v2/notification-configurations/nc-AeUQ2zfKZzW9TiGZ/actions/verify +``` + +### Sample response + +```json +{ + "data": { + "id": "nc-AeUQ2zfKZzW9TiGZ", + "type": "notification-configurations", + "attributes": { + "enabled": true, + "name": "Webhook server test", + "url": "https://httpstat.us/200", + "destination-type": "generic", + "token": null, + "triggers": [ + "run:applying", + "run:completed", + "run:created", + "run:errored", + "run:needs_attention", + "run:planning" + ], + "delivery-responses": [ + { + "url": "https://httpstat.us/200", + "body": "\"200 OK\"", + "code": "200", + "headers": { + "cache-control": [ + "private" + ], + "content-length": [ + "129" + ], + "content-type": [ + "application/json; charset=utf-8" + ], + "content-encoding": [ + "gzip" + ], + "vary": [ + "Accept-Encoding" + ], + "server": [ + "Microsoft-IIS/10.0" + ], + "x-aspnetmvc-version": [ + "5.1" + ], + "access-control-allow-origin": [ + "*" + ], + "x-aspnet-version": [ + "4.0.30319" + ], + "x-powered-by": [ + "ASP.NET" + ], + "set-cookie": [ + "ARRAffinity=77c477e3e649643e5771873e1a13179fb00983bc73c71e196bf25967fd453df9;Path=/;HttpOnly;Domain=httpstat.us" + ], + "date": [ + "Tue, 08 Jan 2019 21:34:37 GMT" + ] + }, + "sent-at": "2019-01-08 21:34:37 UTC", + "successful": "true" + } + ], + "created-at": "2019-01-08T21:32:14.125Z", + "updated-at": "2019-01-08T21:34:37.274Z" + }, + "relationships": { + "subscribable": { + "data": { + "id": "ws-XdeUVMWShTesDMME", + "type": "workspaces" + } + } + }, + "links": { + "self": "/api/v2/notification-configurations/nc-AeUQ2zfKZzW9TiGZ" + } + } +} +``` + +## Delete a notification configuration + +This endpoint deletes a notification configuration. + +`DELETE /notification-configurations/:notification-configuration-id` + +| Parameter | Description | +| -------------------------------- | --------------------------------------------------- | +| `:notification-configuration-id` | The id of the notification configuration to delete. | + +| Status | Response | Reason | +| ------- | ------------------------- | ---------------------------------------------------------------------------- | +| [204][] | None | Successfully deleted the notification configuration | +| [404][] | [JSON API error object][] | Notification configuration not found, or user unauthorized to perform action | + +### Sample request + +```shell +$ curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request DELETE \ + https://app.terraform.io/api/v2/notification-configurations/nc-AeUQ2zfKZzW9TiGZ +``` + +## Team notification configuration + +Team notifications allow you to configure relevant alerts that notify teams you specify whenever a certain event occurs. + + + +@include 'tfc-package-callouts/notifications.mdx' + + + +### Create a team notification configuration + +By default, every team has a default email notification configuration with no users assigned. If a notification configuration has no users assigned, HCP Terraform sends email notifications to all team members. + +Use this endpoint to create a notification configuration to notify a team. + +`POST /teams/:team_id/notification-configurations` + +| Parameter | Description | +| ---------- | ------------------------------------------------- | +| `:team_id` | The ID of the team to create a configuration for. | + +| Status | Response | Reason | +| ------- | ------------------------------------------------------------- | -------------------------------------------------------------- | +| [201][] | [JSON API document][] (`type: "notification-configurations"`) | Successfully created a notification configuration | +| [400][] | [JSON API error object][] | Unable to complete verification request to destination URL | +| [404][] | [JSON API error object][] | Team not found, or user unauthorized to perform action | +| [422][] | [JSON API error object][] | Malformed request body (missing attributes, wrong types, etc.) | + +#### Request body + +This `POST` endpoint requires a JSON object with the following properties as a request payload. Properties without a default value are required. + +If `enabled` is set to `true`, HCP Terraform sends a verification request before saving the configuration. If this request does not receive a response or the response is not successful (HTTP 2xx), the configuration will not be saved. + +| Key path | Type | Default | Description | +| ----------------------------------- | -------------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `data.type` | string | | Must be `"notification-configuration"`. | +| `data.attributes.destination-type` | string | | Type of notification payload to send. Valid values are `"generic"`, `"email"`, `"slack"` or `"microsoft-teams"`. | +| `data.attributes.enabled` | bool | `false` | Disabled configurations will not send any notifications. | +| `data.attributes.name` | string | | Human-readable name for the configuration. | +| `data.attributes.token` | string or null | `null` | Optional write-only secure token, which can be used at the receiving server to verify request authenticity. See [Notification Authenticity][notification-authenticity] for more details. | +| `data.attributes.triggers` | array | `[]` | Array of triggers for which this configuration will send notifications. See [Notification Triggers][notification-triggers] for more details and a list of allowed values. | +| `data.attributes.url` | string | | HTTP or HTTPS URL to which notification requests will be made, only for configurations with `"destination_type:"` `"slack"`, `"microsoft-teams"` or `"generic"` | +| `data.attributes.email_all_members` | bool | | Email all team members, only for configurations with `"destination_type:" "email"`. | +| `data.relationships.users` | array | | Array of users part of the organization, only for configurations with `"destination_type:"` `"email"` | + +[notification-authenticity]: #notification-authenticity + +[notification-triggers]: #notification-triggers + +#### Sample payload for generic notification configurations + +```json +{ + "data": { + "type": "notification-configuration", + "attributes": { + "destination-type": "generic", + "enabled": true, + "name": "Webhook server test", + "url": "https://httpstat.us/200", + "triggers": [ + "change_request:created" + ] + } + } +} +``` + +#### Sample payload for email notification configurations + +```json +{ + "data": { + "type": "notification-configurations", + "attributes": { + "destination-type": "email", + "enabled": true, + "name": "Email teams about change requests", + "triggers": [ + "change_request:created" + ] + }, + "relationships": { + "users": { + "data": [ { "id": "organization-user-id", "type": "users" } ] + } + } + } +} +``` + +#### Sample request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request POST \ + --data @payload.json \ + https://app.terraform.io/api/v2/teams/team-6p5jTwJQXwqZBncC/notification-configurations +``` + +#### Sample response + +```json +{ + "data": { + "id": "nc-AeUQ2zfKZzW9TiGZ", + "type": "notification-configurations", + "attributes": { + "enabled": true, + "name": "Webhook server test", + "url": "https://httpstat.us/200", + "destination-type": "generic", + "token": null, + "triggers": [ + "change_request:created" + ], + "delivery-responses": [ + { + "url": "https://httpstat.us/200", + "body": "\"200 OK\"", + "code": "200", + "headers": { + "cache-control": [ + "private" + ], + "content-length": [ + "129" + ], + "content-type": [ + "application/json; charset=utf-8" + ], + "content-encoding": [ + "gzip" + ], + "vary": [ + "Accept-Encoding" + ], + "server": [ + "Microsoft-IIS/10.0" + ], + "x-aspnetmvc-version": [ + "5.1" + ], + "access-control-allow-origin": [ + "*" + ], + "x-aspnet-version": [ + "4.0.30319" + ], + "x-powered-by": [ + "ASP.NET" + ], + "set-cookie": [ + "ARRAffinity=77c477e3e649643e5771873e1a13179fb00983bc73c71e196bf25967fd453df9;Path=/;HttpOnly;Domain=httpstat.us" + ], + "date": [ + "Tue, 08 Jan 2024 21:34:37 GMT" + ] + }, + "sent-at": "2024-01-08 21:34:37 UTC", + "successful": "true" + } + ], + "created-at": "2024-01-08T21:32:14.125Z", + "updated-at": "2024-01-08T21:34:37.274Z" + }, + "relationships": { + "subscribable": { + "data": { + "id": "team-6p5jTwJQXwqZBncC", + "type": "teams" + } + } + }, + "links": { + "self": "/api/v2/notification-configurations/nc-AeUQ2zfKZzW9TiGZ" + } + } +} +``` + +### List team notification configurations + +Use this endpoint to list notification configurations for a team. + +`GET /teams/:team_id/notification-configurations` + +| Parameter | Description | +| ---------- | ------------------------------------------------ | +| `:team_id` | The ID of the teams to list configurations from. | + +### Query Parameters + +This endpoint supports pagination [with standard URL query parameters](/terraform/enterprise/api-docs#query-parameters). Remember to percent-encode `[` as `%5B` and `]` as `%5D` if your tooling doesn't automatically encode URLs. + +| Parameter | Description | +| -------------- | ------------------------------------------------------------------------------------------- | +| `page[number]` | **Optional.** If omitted, the endpoint will return the first page. | +| `page[size]` | **Optional.** If omitted, the endpoint will return 20 notification configurations per page. | + +#### Sample request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request GET \ + https://app.terraform.io/api/v2/teams/team-6p5jTwJQXwqZBncC/notification-configurations +``` + +#### Sample response + +```json +{ + "data": [ + { + "id": "nc-W6VGEi8A7Cfoaf4K", + "type": "notification-configurations", + "attributes": { + "enabled": false, + "name": "Slack: #devops", + "url": "https://hooks.slack.com/services/T00000000/BC012345/0PWCpQmYyD4bTTRYZ53q4w", + "destination-type": "slack", + "token": null, + "triggers": [ + "change_request:created" + ], + "delivery-responses": [], + "created-at": "2019-01-08T21:34:28.367Z", + "updated-at": "2019-01-08T21:34:28.367Z" + }, + "relationships": { + "subscribable": { + "data": { + "id": "team-TdeUVMWShTesDMME", + "type": "teams" + } + } + }, + "links": { + "self": "/api/v2/notification-configurations/nc-W6VGEi8A7Cfoaf4K" + } + }, + { + "id": "nc-AeUQ2zfKZzW9TiGZ", + "type": "notification-configurations", + "attributes": { + "enabled": true, + "name": "Webhook server test", + "url": "https://httpstat.us/200", + "destination-type": "generic", + "token": null, + "triggers": [ + "change_request:created" + ], + "delivery-responses": [ + { + "url": "https://httpstat.us/200", + "body": "\"200 OK\"", + "code": "200", + "headers": { + "cache-control": [ + "private" + ], + "content-length": [ + "129" + ], + "content-type": [ + "application/json; charset=utf-8" + ], + "content-encoding": [ + "gzip" + ], + "vary": [ + "Accept-Encoding" + ], + "server": [ + "Microsoft-IIS/10.0" + ], + "x-aspnetmvc-version": [ + "5.1" + ], + "access-control-allow-origin": [ + "*" + ], + "x-aspnet-version": [ + "4.0.30319" + ], + "x-powered-by": [ + "ASP.NET" + ], + "set-cookie": [ + "ARRAffinity=77c477e3e649643e5771873e1a13179fb00983bc73c71e196bf25967fd453df9;Path=/;HttpOnly;Domain=httpstat.us" + ], + "date": [ + "Tue, 08 Jan 2019 21:34:37 GMT" + ] + }, + "sent-at": "2019-01-08 21:34:37 UTC", + "successful": "true" + } + ], + "created-at": "2019-01-08T21:32:14.125Z", + "updated-at": "2019-01-08T21:34:37.274Z" + }, + "relationships": { + "subscribable": { + "data": { + "id": "team-XdeUVMWShTesDMME", + "type": "teams" + } + } + }, + "links": { + "self": "/api/v2/notification-configurations/nc-AeUQ2zfKZzW9TiGZ" + } + } + ] +} +``` diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/oauth-clients.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/oauth-clients.mdx new file mode 100644 index 0000000000..c16d5161c7 --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/oauth-clients.mdx @@ -0,0 +1,603 @@ +--- +page_title: /oauth-clients API reference for Terraform Enterprise +description: >- + Use the Terraform Enterprise API's `/oauth-clients` endpoint to manage + connections between VCS providers and organizations and projects. Learn how to + read, create, update, and destroy clients. +source: terraform-docs-common +--- + +[200]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/200 + +[201]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/201 + +[202]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/202 + +[204]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/204 + +[400]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/400 + +[401]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/401 + +[403]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/403 + +[404]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404 + +[409]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/409 + +[412]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/412 + +[422]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/422 + +[429]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/429 + +[500]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500 + +[504]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/504 + +[JSON API document]: /terraform/enterprise/api-docs#json-api-documents + +[JSON API error object]: https://jsonapi.org/format/#error-objects + +# OAuth client API reference + +An OAuth client represents the connection between an organization and a VCS provider. By default, you can globally access an OAuth client throughout the organization, or you can have scope access to one or more [projects](/terraform/enterprise/projects/manage). + +## List OAuth Clients + +`GET /organizations/:organization_name/oauth-clients` + +| Parameter | Description | +| -------------------- | ----------------------------- | +| `:organization_name` | The name of the organization. | + +This endpoint allows you to list VCS connections between an organization and a VCS provider (GitHub, Bitbucket, or GitLab) for use when creating or setting up workspaces. + +| Status | Response | Reason | +| ------- | ----------------------------------------------- | ---------------------- | +| [200][] | [JSON API document][] (`type: "oauth-clients"`) | Success | +| [404][] | [JSON API error object][] | Organization not found | + +### Query Parameters + +This endpoint supports pagination [with standard URL query parameters](/terraform/enterprise/api-docs#query-parameters). Remember to percent-encode `[` as `%5B` and `]` as `%5D` if your tooling doesn't automatically encode URLs. If neither pagination query parameters are provided, the endpoint will not be paginated and will return all results. + +| Parameter | Description | +| -------------- | ----------------------------------------------------------------------------- | +| `page[number]` | **Optional.** If omitted, the endpoint will return the first page. | +| `page[size]` | **Optional.** If omitted, the endpoint will return 20 oauth clients per page. | + +### Sample Request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request GET \ + https://app.terraform.io/api/v2/organizations/my-organization/oauth-clients +``` + +### Sample Response + +```json +{ + "data": [ + { + "id": "oc-XKFwG6ggfA9n7t1K", + "type": "oauth-clients", + "attributes": { + "created-at": "2018-04-16T20:42:53.771Z", + "callback-url": "https://app.terraform.io/auth/35936d44-842c-4ddd-b4d4-7c741383dc3a/callback", + "connect-path": "/auth/35936d44-842c-4ddd-b4d4-7c741383dc3a?organization_id=1", + "service-provider": "github", + "service-provider-display-name": "GitHub", + "name": null, + "http-url": "https://github.com", + "api-url": "https://api.github.com", + "key": null, + "rsa-public-key": null, + "organization-scoped": false + }, + "relationships": { + "organization": { + "data": { + "id": "my-organization", + "type": "organizations" + }, + "links": { + "related": "/api/v2/organizations/my-organization" + } + }, + "projects": { + "data": [ + { "id": "prj-AwfuCJTkdai4xj9w", "type": "projects" } + ] + }, + "oauth-tokens": { + "data": [], + "links": { + "related": "/api/v2/oauth-tokens/ot-KaeqH4cy72VPXFQT" + } + }, + "agent-pool": { + "data": { + "id": "apool-VsmjEMcYkShrckpK", + "type": "agent-pools" + }, + "links": { + "related": "/api/v2/agent-pools/apool-VsmjEMcYkShrckpK" + } + } + } + } + ] +} +``` + +## Show an OAuth Client + +`GET /oauth-clients/:id` + +| Parameter | Description | +| --------- | ---------------------------------- | +| `:id` | The ID of the OAuth Client to show | + +| Status | Response | Reason | +| ------- | ----------------------------------------------- | -------------------------------------------------------------- | +| [200][] | [JSON API document][] (`type: "oauth-clients"`) | Success | +| [404][] | [JSON API error object][] | OAuth Client not found, or user unauthorized to perform action | + +### Sample Request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request GET \ + https://app.terraform.io/api/v2/oauth-clients/oc-XKFwG6ggfA9n7t1K +``` + +### Sample Response + +```json +{ + "data": { + "id": "oc-XKFwG6ggfA9n7t1K", + "type": "oauth-clients", + "attributes": { + "created-at": "2018-04-16T20:42:53.771Z", + "callback-url": "https://app.terraform.io/auth/35936d44-842c-4ddd-b4d4-7c741383dc3a/callback", + "connect-path": "/auth/35936d44-842c-4ddd-b4d4-7c741383dc3a?organization_id=1", + "service-provider": "github", + "service-provider-display-name": "GitHub", + "name": null, + "http-url": "https://github.com", + "api-url": "https://api.github.com", + "key": null, + "rsa-public-key": null, + "organization-scoped": false + }, + "relationships": { + "organization": { + "data": { + "id": "my-organization", + "type": "organizations" + }, + "links": { + "related": "/api/v2/organizations/my-organization" + } + }, + "projects": { + "data": [ + { "id": "prj-AwfuCJTkdai4xj9w", "type": "projects" } + ] + }, + "oauth-tokens": { + "data": [], + "links": { + "related": "/api/v2/oauth-tokens/ot-KaeqH4cy72VPXFQT" + } + }, + "agent-pool": { + "data": { + "id": "apool-VsmjEMcYkShrckpK", + "type": "agent-pools" + }, + "links": { + "related": "/api/v2/agent-pools/apool-VsmjEMcYkShrckpK" + } + } + } + } +} +``` + +## Create an OAuth Client + +`POST /organizations/:organization_name/oauth-clients` + +| Parameter | Description | +| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `:organization_name` | The name of the organization that will be connected to the VCS provider. The organization must already exist in the system, and the user must have permission to manage VCS settings. ([More about permissions.](/terraform/enterprise/users-teams-organizations/permissions)) | + +[permissions-citation]: #intentionally-unused---keep-for-maintainers + +This endpoint allows you to create a VCS connection between an organization and a VCS provider (GitHub or GitLab) for use when creating or setting up workspaces. By using this API endpoint, you can provide a pre-generated OAuth token string instead of going through the process of creating a GitHub or GitLab OAuth Application. + +To learn how to generate one of these token strings for your VCS provider, you can read the following documentation: + +- [GitHub and GitHub Enterprise](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token) +- [GitLab, GitLab Community Edition, and GitLab Enterprise Edition](https://docs.gitlab.com/ee/user/profile/personal_access_tokens.html#create-a-personal-access-token) +- [Azure DevOps Server](https://docs.microsoft.com/en-us/azure/devops/organizations/accounts/use-personal-access-tokens-to-authenticate?view=azure-devops-2019&tabs=preview-page) + +| Status | Response | Reason | +| ------- | ----------------------------------------------- | -------------------------------------------------------------- | +| [201][] | [JSON API document][] (`type: "oauth-clients"`) | OAuth Client successfully created | +| [404][] | [JSON API error object][] | Organization not found or user unauthorized to perform action | +| [422][] | [JSON API error object][] | Malformed request body (missing attributes, wrong types, etc.) | + +### Request Body + +This POST endpoint requires a JSON object with the following properties as a request payload. + +Properties without a default value are required. + +| Key path | Type | Default | Description | +| ------------------------------------- | -------------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `data.type` | string | | Must be `"oauth-clients"`. | +| `data.attributes.service-provider` | string | | The VCS provider being connected with. Valid options are `"github"`, `"github_enterprise"`, `"gitlab_hosted"`, `"gitlab_community_edition"`, `"gitlab_enterprise_edition"`, or `"ado_server"`. | +| `data.attributes.name` | string | `null` | An optional display name for the OAuth Client. If left `null`, the UI will default to the display name of the VCS provider. | +| `data.attributes.key` | string | | The OAuth Client key. It can refer to a Consumer Key, Application Key, or another type of client key for the VCS provider. | +| `data.attributes.http-url` | string | | The homepage of your VCS provider (e.g. `"https://github.com"` or `"https://ghe.example.com"` or `"https://gitlab.com"`). | +| `data.attributes.api-url` | string | | The base URL as per your VCS provider's API documentation (e.g. `"https://api.github.com"`, `"https://ghe.example.com/api/v3"` or `"https://gitlab.com/api/v4"`). | +| `data.attributes.oauth-token-string` | string | | The token string you were given by your VCS provider | +| `data.attributes.private-key` | string | | **Required for Azure DevOps Server. Not used for any other providers.** The text of the SSH private key associated with your Azure DevOps Server account. | +| `data.attributes.secret` | string | | The OAuth client secret. For Bitbucket Data Center, the secret is the text of the SSH private key associated with your Bitbucket Data Center application link. | +| `data.attributes.rsa-public-key` | string | | **Required for Bitbucket Data Center in conjunction with the `secret`. Not used for any other providers.** The text of the SSH public key associated with your Bitbucket Data Center application link. | +| `data.attributes.organization-scoped` | boolean | | Whether or not the OAuth client is scoped to all projects and workspaces in the organization. Defaults to `true`. | +| `data.relationships.projects.data[]` | array\[object] | `[]` | A list of resource identifier objects that defines which projects are associated with the OAuth client. If `data.attributes.organization-scoped` is set to `false`, the OAuth client is only available to this list of projects. Each object in this list must contain a project `id` and use the `"projects"` type. For example, `{ "id": "prj-AwfuCJTkdai4xj9w", "type": "projects" }`. | +| `data.relationships.agent-pool.data` | object | `{}` | The Agent Pool associated to the VCS connection. This pool will be responsible for forwarding VCS Provider API calls and cloning VCS repositories. | + +### Sample Payload + +```json +{ + "data": { + "type": "oauth-clients", + "attributes": { + "service-provider": "github", + "http-url": "https://github.com", + "api-url": "https://api.github.com", + "oauth-token-string": "4306823352f0009d0ed81f1b654ac17a", + "organization-scoped": false + }, + "relationships": { + "projects": { + "data": [ + { "id": "prj-AwfuCJTkdai4xj9w", "type": "projects" } + ] + }, + "agent-pool": { + "data": { + "id": "apool-VsmjEMcYkShrckzzz", + "type": "agent-pools" + } + } + } + } +} +``` + +### Sample Request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request POST \ + --data @payload.json \ + https://app.terraform.io/api/v2/organizations/my-organization/oauth-clients +``` + +### Sample Response + +```json +{ + "data": { + "id": "oc-XKFwG6ggfA9n7t1K", + "type": "oauth-clients", + "attributes": { + "created-at": "2018-04-16T20:42:53.771Z", + "callback-url": "https://app.terraform.io/auth/35936d44-842c-4ddd-b4d4-7c741383dc3a/callback", + "connect-path": "/auth/35936d44-842c-4ddd-b4d4-7c741383dc3a?organization_id=1", + "service-provider": "github", + "service-provider-display-name": "GitHub", + "name": null, + "http-url": "https://github.com", + "api-url": "https://api.github.com", + "key": null, + "rsa-public-key": null, + "organization-scoped": false + }, + "relationships": { + "organization": { + "data": { + "id": "my-organization", + "type": "organizations" + }, + "links": { + "related": "/api/v2/organizations/my-organization" + } + }, + "projects": { + "data": [ + { "id": "prj-AwfuCJTkdai4xj9w", "type": "projects" } + ] + }, + "oauth-tokens": { + "data": [], + "links": { + "related": "/api/v2/oauth-tokens/ot-KaeqH4cy72VPXFQT" + } + }, + "agent-pool": { + "data": { + "id": "apool-VsmjEMcYkShrckzzz", + "type": "agent-pools" + } + } + } + } +} +``` + +## Update an OAuth Client + +`PATCH /oauth-clients/:id` + +| Parameter | Description | +| --------- | ------------------------------------- | +| `:id` | The ID of the OAuth Client to update. | + +Use caution when changing attributes with this endpoint; editing an OAuth client that workspaces are currently using can have unexpected effects. + +| Status | Response | Reason | +| ------- | ----------------------------------------------- | -------------------------------------------------------------- | +| [200][] | [JSON API document][] (`type: "oauth-clients"`) | The request was successful | +| [404][] | [JSON API error object][] | OAuth Client not found or user unauthorized to perform action | +| [422][] | [JSON API error object][] | Malformed request body (missing attributes, wrong types, etc.) | + +### Request Body + +This PATCH endpoint requires a JSON object with the following properties as a request payload. + +| Key path | Type | Default | Description | +| ------------------------------------- | -------------- | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `data.type` | string | | Must be `"oauth-clients"`. | +| `data.attributes.name` | string | (previous value) | An optional display name for the OAuth Client. If set to `null`, the UI will default to the display name of the VCS provider. | +| `data.attributes.key` | string | (previous value) | The OAuth Client key. It can refer to a Consumer Key, Application Key, or another type of client key for the VCS provider. | +| `data.attributes.secret` | string | (previous value) | The OAuth client secret. For Bitbucket Data Center, this secret is the text of the SSH private key associated with your Bitbucket Data Center application link. | +| `data.attributes.rsa-public-key` | string | (previous value) | **Required for Bitbucket Data Center in conjunction with the `secret`. Not used for any other providers.** The text of the SSH public key associated with your Bitbucket Data Center application link. | +| `data.attributes.organization-scoped` | boolean | (previous value) | Whether or not the OAuth client is available to all projects and workspaces in the organization. | +| `data.relationships.projects` | array\[object] | (previous value) | A list of resource identifier objects that defines which projects are associated with the OAuth client. If `data.attributes.organization-scoped` is set to `false`, the OAuth client is only available to this list of projects. Each object in this list must contain a project `id` and use the `"projects"` type. For example, `{ "id": "prj-AwfuCJTkdai4xj9w", "type": "projects" }`. Sending an empty array clears all project assignments. | +| `data.relationships.agent-pool.data` | object | `{}` | The Agent Pool associated to the VCS connection. This pool will be responsible for forwarding VCS Provider API calls and cloning VCS repositories. | + +### Sample Payload + +```json +{ + "data": { + "id": "oc-XKFwG6ggfA9n7t1K", + "type": "oauth-clients", + "attributes": { + "key": "key", + "secret": "secret", + "organization-scoped": false + }, + "relationships": { + "projects": { + "data": [ + { "id": "prj-AwfuCJTkdai4xj9w", "type": "projects" } + ] + }, + "agent-pool": { + "data": { + "id": "apool-VsmjEMcYkShrckzzz", + "type": "agent-pools" + } + } + } + } +} +``` + +### Sample Request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request PATCH \ + --data @payload.json \ + https://app.terraform.io/api/v2/oauth-clients/oc-XKFwG6ggfA9n7t1K +``` + +### Sample Response + +```json +{ + "data": { + "id": "oc-XKFwG6ggfA9n7t1K", + "type": "oauth-clients", + "attributes": { + "created-at": "2018-04-16T20:42:53.771Z", + "callback-url": "https://app.terraform.io/auth/35936d44-842c-4ddd-b4d4-7c741383dc3a/callback", + "connect-path": "/auth/35936d44-842c-4ddd-b4d4-7c741383dc3a?organization_id=1", + "service-provider": "github", + "service-provider-display-name": "GitHub", + "name": null, + "http-url": "https://github.com", + "api-url": "https://api.github.com", + "key": null, + "rsa-public-key": null, + "organization-scoped": false + }, + "relationships": { + "organization": { + "data": { + "id": "my-organization", + "type": "organizations" + }, + "links": { + "related": "/api/v2/organizations/my-organization" + } + }, + "projects": { + "data": [ + { "id": "prj-AwfuCJTkdai4xj9w", "type": "projects" } + ] + }, + "oauth-tokens": { + "data": [], + "links": { + "related": "/api/v2/oauth-tokens/ot-KaeqH4cy72VPXFQT" + } + }, + "agent-pool": { + "data": { + "id": "apool-VsmjEMcYkShrckzzz", + "type": "agent-pools" + } + } + } + } +} +``` + +## Destroy an OAuth Client + +`DELETE /oauth-clients/:id` + +| Parameter | Description | +| --------- | ------------------------------------- | +| `:id` | The ID of the OAuth Client to destroy | + +This endpoint allows you to remove an existing connection between an organization and a VCS provider (GitHub, Bitbucket, or GitLab). + +**Note:** Removing the OAuth Client will unlink workspaces that use this connection from their repositories, and these workspaces will need to be manually linked to another repository. + +| Status | Response | Reason | +| ------- | ------------------------- | ------------------------------------------------------------------------------ | +| [204][] | Empty response | The OAuth Client was successfully destroyed | +| [404][] | [JSON API error object][] | Organization or OAuth Client not found, or user unauthorized to perform action | + +### Sample Request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request DELETE \ + https://app.terraform.io/api/v2/oauth-clients/oc-XKFwG6ggfA9n7t1K +``` + +## Attach to a project + +`POST /oauth-clients/:id/relationships/projects` + +| Parameter | Description | +| --------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | +| `:id` | The ID of the OAuth client to attach to a project. Use the [List OAuth Clients](#list-oauth-clients) endpoint to reference your OAuth client IDs. | + +| Status | Response | Reason | +| ------- | ------------------------- | -------------------------------------------------------------------------- | +| [204][] | Nothing | The request was successful | +| [404][] | [JSON API error object][] | OAuth Client not found or user unauthorized to perform action | +| [422][] | [JSON API error object][] | Malformed request body (one or more projects not found, wrong types, etc.) | + +### Request Body + +This POST endpoint requires a JSON object with the following properties as a request payload. + +Properties without a default value are required. + +| Key path | Type | Default | Description | +| -------- | -------------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `data[]` | array\[object] | `[]` | A list of resource identifier objects that defines which projects to attach the OAuth client to. These objects must contain `id` and `type` properties, and the `type` property must be `projects` (e.g. `{ "id": "prj-AwfuCJTkdai4xj9w", "type": "projects" }`). | + +### Sample Payload + +```json +{ + "data": [ + { "id": "prj-AwfuCJTkdai4xj9w", "type": "projects" }, + { "id": "prj-2HRvNs49EWPjDqT1", "type": "projects" } + ] +} +``` + +### Sample Request + +```shell +curl \ + -H "Authorization: Bearer $TOKEN" \ + -H "Content-Type: application/vnd.api+json" \ + --request POST \ + --data @payload.json \ + https://app.terraform.io/api/v2/oauth-clients/oc-XKFwG6ggfA9n7t1K/relationships/projects +``` + +## Detach an OAuth Client from projects + +`DELETE /oauth-clients/:id/relationships/projects` + +| Parameter | Description | +| --------- | ----------------------------------------------------------------------------------------------------------------------------- | +| `:id` | The ID of the oauth client you want to detach from the specified projects. Use the "List OAuth Clients" endpoint to find IDs. | + +| Status | Response | Reason | +| ------- | ------------------------- | -------------------------------------------------------------------------- | +| [204][] | Nothing | The request was successful | +| [404][] | [JSON API error object][] | OAuth Client not found or user unauthorized to perform action | +| [422][] | [JSON API error object][] | Malformed request body (one or more projects not found, wrong types, etc.) | + +### Request Body + +This DELETE endpoint requires a JSON object with the following properties as a request payload. + +Properties without a default value are required. + +| Key path | Type | Default | Description | +| -------- | -------------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `data[]` | array\[object] | `[]` | A list of resource identifier objects that defines which projects are associated with the OAuth client. If `data.attributes.organization-scoped` is set to `false`, the OAuth client is only available to this list of projects. Each object in this list must contain a project `id` and use the `"projects"` type. For example, `{ "id": "prj-AwfuCJTkdai4xj9w", "type": "projects" }`. | + +### Sample Payload + +```json +{ + "data": [ + { "id": "prj-AwfuCJTkdai4xj9w", "type": "projects" }, + { "id": "prj-2HRvNs49EWPjDqT1", "type": "projects" } + ] +} +``` + +### Sample Request + +```shell +curl \ + -H "Authorization: Bearer $TOKEN" \ + -H "Content-Type: application/vnd.api+json" \ + --request DELETE \ + --data @payload.json \ + https://app.terraform.io/api/v2/oauth-clients/oc-XKFwG6ggfA9n7t1K/relationships/projects +``` + +### Available Related Resources + +The GET endpoints above can optionally return related resources, if requested with [the `include` query parameter](/terraform/enterprise/api-docs#inclusion-of-related-resources). The following resource types are available: + +| Resource Name | Description | +| -------------- | --------------------------------------- | +| `oauth_tokens` | The OAuth tokens managed by this client | +| `projects` | The projects scoped to this client | diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/oauth-tokens.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/oauth-tokens.mdx new file mode 100644 index 0000000000..83cdaaae1b --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/oauth-tokens.mdx @@ -0,0 +1,264 @@ +--- +page_title: /oauth-tokens API reference for Terraform Enterprise +description: >- + Use the Terraform Enterprise API's `/oauth-tokens` endpoint to manage the + OAuth tokens that connect workspaces to VCS providers. Learn how to read, + update, and destroy tokens. +source: terraform-docs-common +--- + +[200]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/200 + +[201]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/201 + +[202]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/202 + +[204]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/204 + +[400]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/400 + +[401]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/401 + +[403]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/403 + +[404]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404 + +[409]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/409 + +[412]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/412 + +[422]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/422 + +[429]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/429 + +[500]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500 + +[504]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/504 + +[JSON API document]: /terraform/enterprise/api-docs#json-api-documents + +[JSON API error object]: https://jsonapi.org/format/#error-objects + +# OAuth token API reference + +The `oauth-token` object represents a VCS configuration which includes the OAuth connection and the associated OAuth token. This object is used when creating a workspace to identify which VCS connection to use. + +## List OAuth Tokens + +List all the OAuth Tokens for a given OAuth Client + +`GET /oauth-clients/:oauth_client_id/oauth-tokens` + +| Parameter | Description | +| ------------------ | -------------------------- | +| `:oauth_client_id` | The ID of the OAuth Client | + +| Status | Response | Reason | +| ------- | ---------------------------------------------- | -------------------------------------------------------------- | +| [200][] | [JSON API document][] (`type: "oauth-tokens"`) | Success | +| [404][] | [JSON API error object][] | OAuth Client not found, or user unauthorized to perform action | + +### Query Parameters + +This endpoint supports pagination [with standard URL query parameters](/terraform/enterprise/api-docs#query-parameters). Remember to percent-encode `[` as `%5B` and `]` as `%5D` if your tooling doesn't automatically encode URLs. If neither pagination query parameters are provided, the endpoint will not be paginated and will return all results. + +| Parameter | Description | +| -------------- | ---------------------------------------------------------------------------- | +| `page[number]` | **Optional.** If omitted, the endpoint will return the first page. | +| `page[size]` | **Optional.** If omitted, the endpoint will return 20 oauth tokens per page. | + +### Sample Request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + https://app.terraform.io/api/v2/oauth-clients/oc-GhHqb5rkeK19mLB8/oauth-tokens +``` + +### Sample Response + +```json +{ + "data": [ + { + "id": "ot-hmAyP66qk2AMVdbJ", + "type": "oauth-tokens", + "attributes": { + "created-at":"2017-11-02T06:37:49.284Z", + "service-provider-user":"skierkowski", + "has-ssh-key": false + }, + "relationships": { + "oauth-client": { + "data": { + "id": "oc-GhHqb5rkeK19mLB8", + "type": "oauth-clients" + }, + "links": { + "related": "/api/v2/oauth-clients/oc-GhHqb5rkeK19mLB8" + } + } + }, + "links": { + "self": "/api/v2/oauth-tokens/ot-hmAyP66qk2AMVdbJ" + } + } + ] +} +``` + +## Show an OAuth Token + +`GET /oauth-tokens/:id` + +| Parameter | Description | +| --------- | --------------------------------- | +| `:id` | The ID of the OAuth token to show | + +| Status | Response | Reason | +| ------- | ---------------------------------------------- | ------------------------------------------------------------- | +| [200][] | [JSON API document][] (`type: "oauth-tokens"`) | Success | +| [404][] | [JSON API error object][] | OAuth Token not found, or user unauthorized to perform action | + +### Sample Request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request GET \ + https://app.terraform.io/api/v2/oauth-tokens/ot-29t7xkUKiNC2XasL +``` + +### Sample Response + +```json +{ + "data": { + "id": "ot-29t7xkUKiNC2XasL", + "type": "oauth-tokens", + "attributes": { + "created-at": "2018-08-29T14:07:22.144Z", + "service-provider-user": "EM26Jj0ikRsIFFh3fE5C", + "has-ssh-key": false + }, + "relationships": { + "oauth-client": { + "data": { + "id": "oc-WMipGbuW8q7xCRmJ", + "type": "oauth-clients" + }, + "links": { + "related": "/api/v2/oauth-clients/oc-WMipGbuW8q7xCRmJ" + } + } + }, + "links": { + "self": "/api/v2/oauth-tokens/ot-29t7xkUKiNC2XasL" + } + } +} +``` + +## Update an OAuth Token + +`PATCH /oauth-tokens/:id` + +| Parameter | Description | +| --------- | ----------------------------------- | +| `:id` | The ID of the OAuth token to update | + +| Status | Response | Reason | +| ------- | ---------------------------------------------- | -------------------------------------------------------------- | +| [200][] | [JSON API document][] (`type: "oauth-tokens"`) | OAuth Token successfully updated | +| [404][] | [JSON API error object][] | OAuth Token not found or user unauthorized to perform action | +| [422][] | [JSON API error object][] | Malformed request body (missing attributes, wrong types, etc.) | + +### Request Body + +This PATCH endpoint requires a JSON object with the following properties as a request payload. + +Properties without a default value are required. + +| Key path | Type | Default | Description | +| ------------------------- | ------ | ------- | ------------------------- | +| `data.type` | string | | Must be `"oauth-tokens"`. | +| `data.attributes.ssh-key` | string | | **Optional.** The SSH key | + +### Sample Payload + +```json +{ + "data": { + "id": "ot-29t7xkUKiNC2XasL", + "type": "oauth-tokens", + "attributes": { + "ssh-key": "..." + } + } +} +``` + +### Sample Request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request PATCH \ + --data @payload.json \ + https://app.terraform.io/api/v2/oauth-tokens/ot-29t7xkUKiNC2XasL +``` + +### Sample Response + +```json +{ + "data": { + "id": "ot-29t7xkUKiNC2XasL", + "type": "oauth-tokens", + "attributes": { + "created-at": "2018-08-29T14:07:22.144Z", + "service-provider-user": "EM26Jj0ikRsIFFh3fE5C", + "has-ssh-key": false + }, + "relationships": { + "oauth-client": { + "data": { + "id": "oc-WMipGbuW8q7xCRmJ", + "type": "oauth-clients" + }, + "links": { + "related": "/api/v2/oauth-clients/oc-WMipGbuW8q7xCRmJ" + } + } + }, + "links": { + "self": "/api/v2/oauth-tokens/ot-29t7xkUKiNC2XasL" + } + } +} +``` + +## Destroy an OAuth Token + +`DELETE /oauth-tokens/:id` + +| Parameter | Description | +| --------- | ------------------------------------ | +| `:id` | The ID of the OAuth Token to destroy | + +| Status | Response | Reason | +| ------- | ------------------------- | ------------------------------------------------------------- | +| [204][] | Empty response | The OAuth Token was successfully destroyed | +| [404][] | [JSON API error object][] | OAuth Token not found, or user unauthorized to perform action | + +### Sample Request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request DELETE \ + https://app.terraform.io/api/v2/oauth-tokens/ot-29t7xkUKiNC2XasL +``` diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/organization-memberships.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/organization-memberships.mdx new file mode 100644 index 0000000000..dec59d2968 --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/organization-memberships.mdx @@ -0,0 +1,497 @@ +--- +page_title: /organization-memberships API reference for Terraform Enterprise +description: >- + Use the Terraform Enterprise API's `/organization-memberships` endpoint to + manage membership in an organization. Invite, list, and remove members from + organizations. +source: terraform-docs-common +--- + +[200]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/200 + +[201]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/201 + +[202]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/202 + +[204]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/204 + +[400]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/400 + +[401]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/401 + +[403]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/403 + +[404]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404 + +[409]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/409 + +[412]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/412 + +[422]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/422 + +[429]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/429 + +[500]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500 + +[504]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/504 + +[JSON API document]: /terraform/enterprise/api-docs#json-api-documents + +[JSON API error object]: https://jsonapi.org/format/#error-objects + +# Organization memberships API reference + +Users are added to organizations by inviting them to join. Once accepted, they become members of the organization. The Organization Membership resource represents this membership. + +You can invite users who already have an account, as well as new users. If the user has an existing account with the same email address used to invite them, they can reuse the same login. + +-> **Note:** Once a user is a member of the organization, you can manage their team memberships using [the Team Membership API](/terraform/enterprise/api-docs/team-members). + +## Invite a User to an Organization + +`POST /organizations/:organization_name/organization-memberships` + +| Parameter | Description | +| -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | +| `:organization_name` | The name of the organization the user will be invited to join. The inviting user must have permission to manage organization memberships. | + +-> **Note:** Organization membership management is restricted to members of the owners team, the owners [team API token](/terraform/enterprise/users-teams-organizations/api-tokens#team-api-tokens), the [organization API token](/terraform/enterprise/users-teams-organizations/api-tokens#organization-api-tokens), and users or teams with one of the [Team Management](/terraform/enterprise/users-teams-organizations/permissions#team-management-permissions) permissions. + +[permissions-citation]: #intentionally-unused---keep-for-maintainers + +| Status | Response | Reason | +| ------- | ------------------------- | -------------------------------------------------------------- | +| [201][] | [JSON API document][] | Successfully invited the user | +| [400][] | [JSON API error object][] | Unable to invite user due to organization limits | +| [404][] | [JSON API error object][] | Organization not found, or user unauthorized to perform action | +| [422][] | [JSON API error object][] | Unable to invite user due to validation errors | + +### Request Body + +This POST endpoint requires a JSON object with the following properties as a request payload. + +Properties without a default value are required. + +| Key path | Type | Default | Description | +| --------------------------------- | -------------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `data.type` | string | | Must be `"organization-memberships"`. | +| `data.attributes.email` | string | | The email address of the user to be invited. | +| `data.relationships.teams.data[]` | array\[object] | | A list of resource identifier objects that defines which teams the invited user will be a member of. These objects must contain `id` and `type` properties, and the `type` property must be `teams` (e.g. `{ "id": "team-GeLZkdnK6xAVjA5H", "type": "teams" }`). Obtain team IDs from the [List Teams](/terraform/enterprise/api-docs/teams#list-teams) endpoint. All users must be added to at least one team. | + +### Sample Payload + +```json +{ + "data": { + "attributes": { + "email": "test@example.com" + }, + "relationships": { + "teams": { + "data": [ + { + "type": "teams", + "id": "team-GeLZkdnK6xAVjA5H" + } + ] + } + }, + "type": "organization-memberships" + } +} +``` + +### Sample Request + +```shell +$ curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request POST \ + --data @payload.json \ + https://app.terraform.io/api/v2/organizations/my-organization/organization-memberships +``` + +### Sample Response + +```json +{ + "data": { + "id": "ou-nX7inDHhmC3quYgy", + "type": "organization-memberships", + "attributes": { + "status": "invited" + }, + "relationships": { + "teams": { + "data": [ + { + "id": "team-GeLZkdnK6xAVjA5H", + "type": "teams" + } + ] + }, + "user": { + "data": { + "id": "user-J8oxGmRk5eC2WLfX", + "type": "users" + } + }, + "organization": { + "data": { + "id": "my-organization", + "type": "organizations" + } + } + } + }, + "included": [ + { + "id": "user-J8oxGmRk5eC2WLfX", + "type": "users", + "attributes": { + "username": null, + "is-service-account": false, + "auth-method": "hcp_sso", + "avatar-url": "https://www.gravatar.com/avatar/55502f40dc8b7c769880b10874abc9d0?s=100&d=mm", + "two-factor": { + "enabled": false, + "verified": false + }, + "email": "test@example.com", + "permissions": { + "can-create-organizations": true, + "can-change-email": true, + "can-change-username": true, + "can-manage-user-tokens": false + } + }, + "relationships": { + "authentication-tokens": { + "links": { + "related": "/api/v2/users/user-J8oxGmRk5eC2WLfX/authentication-tokens" + } + } + }, + "links": { + "self": "/api/v2/users/user-J8oxGmRk5eC2WLfX" + } + } + ] +} +``` + +## List Memberships for an Organization + +`GET /organizations/:organization_name/organization-memberships` + +| Parameter | Description | +| -------------------- | -------------------------------------------------------- | +| `:organization_name` | The name of the organization to list the memberships of. | + +### Query Parameters + +This endpoint supports pagination [with standard URL query parameters](/terraform/enterprise/api-docs#query-parameters). Remember to percent-encode `[` as `%5B` and `]` as `%5D` if your tooling doesn't automatically encode URLs. + +| Parameter | Description | +| ---------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `q` | **Optional.** A search query string. Organization memberships are searchable by user name and email. | +| `filter[status]` | **Optional.** If specified, restricts results to those with the matching status value. Valid values are `invited` and `active`. | +| `filter[email]` | **Optional.** If specified, restricts results to those with a matching user email address. If multiple comma separated values are specified, results matching any of the values are returned. | +| `page[number]` | **Optional.** If omitted, the endpoint will return the first page. | +| `page[size]` | **Optional.** If omitted, the endpoint will return 20 users per page. | + +### Sample Request + +```shell +$ curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + https://app.terraform.io/api/v2/organizations/my-organization/organization-memberships +``` + +### Sample Response + +```json +{ + "data": [ + { + "id": "ou-tTJph1AQVK5ZmdND", + "type": "organization-memberships", + "attributes": { + "status": "active" + }, + "relationships": { + "teams": { + "data": [ + { + "id": "team-yUrEehvfG4pdmSjc", + "type": "teams" + } + ] + }, + "user": { + "data": { + "id": "user-vaQqszES9JnuK4eB", + "type": "users" + } + }, + "organization": { + "data": { + "id": "my-organization", + "type": "organizations" + } + } + } + }, + { + "id": "ou-D6HPYFt4GzeBt3gB", + "type": "organization-memberships", + "attributes": { + "status": "active" + }, + "relationships": { + "teams": { + "data": [ + { + "id": "team-yUrEehvfG4pdmSjc", + "type": "teams" + } + ] + }, + "user": { + "data": { + "id": "user-oqCgH7NgTn95jTGc", + "type": "users" + } + }, + "organization": { + "data": { + "id": "my-organization", + "type": "organizations" + } + } + } + }, + { + "id": "ou-x1E2eBwYwusLDC7h", + "type": "organization-memberships", + "attributes": { + "status": "invited" + }, + "relationships": { + "teams": { + "data": [ + { + "id": "team-yUrEehvfG4pdmSjc", + "type": "teams" + } + ] + }, + "user": { + "data": { + "id": "user-UntUdBTHsVRQMzC8", + "type": "users" + } + }, + "organization": { + "data": { + "id": "my-organization", + "type": "organizations" + } + } + } + } + ], + "links": { + "self": "https://app.terraform.io/api/v2/organizations/my-organization/organization-memberships?page%5Bnumber%5D=1&page%5Bsize%5D=20", + "first": "https://app.terraform.io/api/v2/organizations/my-organization/organization-memberships?page%5Bnumber%5D=1&page%5Bsize%5D=20", + "prev": null, + "next": null, + "last": "https://app.terraform.io/api/v2/organizations/my-organization/organization-memberships?page%5Bnumber%5D=1&page%5Bsize%5D=20" + }, + "meta": { + "status-counts": { + "total": 3, + "active": 2, + "invited": 1 + }, + "pagination": { + "current-page": 1, + "prev-page": null, + "next-page": null, + "total-pages": 1, + "total-count": 3 + } + } +} +``` + +## List User's Own Memberships + +`GET /organization-memberships` + +### Sample Request + +```shell +$ curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + https://app.terraform.io/api/v2/organization-memberships +``` + +### Sample Response + +```json +{ + "data": [ + { + "id": "ou-VgJgfbDVN3APUm2F", + "type": "organization-memberships", + "attributes": { + "status": "invited" + }, + "relationships": { + "teams": { + "data": [ + { + "id": "team-4QrJKzxB3J5N4cJc", + "type": "teams" + } + ] + }, + "user": { + "data": { + "id": "user-vaQqszES9JnuK4eB", + "type": "users" + } + }, + "organization": { + "data": { + "id": "acme-corp", + "type": "organizations" + } + } + } + }, + { + "id": "ou-tTJph1AQVK5ZmdND", + "type": "organization-memberships", + "attributes": { + "status": "active" + }, + "relationships": { + "teams": { + "data": [ + { + "id": "team-yUrEehvfG4pdmSjc", + "type": "teams" + } + ] + }, + "user": { + "data": { + "id": "user-vaQqszES9JnuK4eB", + "type": "users" + } + }, + "organization": { + "data": { + "id": "my-organization", + "type": "organizations" + } + } + } + } + ] +} +``` + +## Show a Membership + +`GET /organization-memberships/:organization_membership_id` + +| Parameter | Description | +| ----------------------------- | --------------------------- | +| `:organization_membership_id` | The organization membership | + +| Status | Response | Reason | +| ------- | ---------------------------------------------------------- | ------------------------------------------------------------------------- | +| [200][] | [JSON API document][] (`type: "organization-memberships"`) | The request was successful | +| [404][] | [JSON API error object][] | Organization membership not found, or user unauthorized to perform action | + +### Sample Request + +```shell +$ curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + https://app.terraform.io/api/v2/organization-memberships/ou-kit6GaMo3zPGCzWb +``` + +### Sample Response + +```json +{ + "data": { + "id": "ou-kit6GaMo3zPGCzWb", + "type": "organization-memberships", + "attributes": { + "status": "active" + }, + "relationships": { + "teams": { + "data": [ + { + "id": "team-97LkM7QciNkwb2nh", + "type": "teams" + } + ] + }, + "user": { + "data": { + "id": "user-hn6v2WK1naDpGadd", + "type": "users" + } + }, + "organization": { + "data": { + "id": "hashicorp", + "type": "organizations" + } + } + } + } +} +``` + +## Remove User from Organization + +`DELETE /organization-memberships/:organization_membership_id` + +| Parameter | Description | +| ----------------------------- | --------------------------- | +| `:organization_membership_id` | The organization membership | + +| Status | Response | Reason | +| ------- | ------------------------- | -------------------------------------------------------------------------------------- | +| [204][] | Empty body | Successfully removed the user from the organization | +| [403][] | [JSON API error object][] | Unable to remove the user: you cannot remove yourself from organizations which you own | +| [404][] | [JSON API error object][] | Organization membership not found, or user unauthorized to perform action | + +### Sample Request + +```shell +$ curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request DELETE \ + https://app.terraform.io/api/v2/organization-memberships/ou-tTJph1AQVK5ZmdND +``` + +## Available Related Resources + +The GET endpoints above can optionally return related resources, if requested with [the `include` query parameter](/terraform/enterprise/api-docs#inclusion-of-related-resources). The following resource types are available: + +- `user` - The user associated with the membership. +- `teams` - Teams the user is a member of. diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/organization-tags.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/organization-tags.mdx new file mode 100644 index 0000000000..bcebf92fbc --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/organization-tags.mdx @@ -0,0 +1,230 @@ +--- +page_title: /tags API reference for Terraform Enterprise +description: >- + Use the Terraform Enterprise API's organization `/tags` endpoint to assign, + list, and delete tags for an organization. +source: terraform-docs-common +--- + +[200]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/200 + +[201]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/201 + +[202]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/202 + +[204]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/204 + +[400]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/400 + +[401]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/401 + +[403]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/403 + +[404]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404 + +[409]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/409 + +[412]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/412 + +[422]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/422 + +[429]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/429 + +[500]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500 + +[504]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/504 + +[JSON API document]: /terraform/enterprise/api-docs#json-api-documents + +[JSON API error object]: https://jsonapi.org/format/#error-objects + +# Organization tags API reference + +This API returns the list of tags used in workspaces across the organization. Tags can be added to this pool via workspaces. Tags deleted here will be removed from all other workspaces. Tags can be added, applied, removed and deleted in bulk. + +Tags are subject to the following rules: + +- Workspace tags or tags must be one or more characters, have a 255 character limit, and can include letters, numbers, colons, hyphens, and underscores. +- You can create tags for a workspace using the user interface or the API. After you create a tag, you can assign it to other workspaces in the same organization. +- You cannot create tags for a workspace using the CLI. +- You cannot set tags at the project level, so there is no tag inheritance from projects to workspaces. + +## List Tags + +`GET /organizations/:organization_name/tags` + +| Parameter | Description | +| -------------------- | ---------------------------------------------- | +| `:organization_name` | The name of the organization to list tags from | + +### Query Parameters + +This endpoint supports pagination [with standard URL query parameters](/terraform/enterprise/api-docs#query-parameters); remember to percent-encode `[` as `%5B` and `]` as `%5D` if your tooling doesn't automatically encode URLs. + +| Parameter | Description | +| ------------------------------- | ---------------------------------------------------------------------------------------- | +| `q` | **Optional.** A search query string. Organization tags are searchable by name likeness. | +| `filter[exclude][taggable][id]` | **Optional.** If specified, omits organization's related workspace's tags. | +| `page[number]` | **Optional.** If omitted, the endpoint will return the first page. | +| `page[size]` | **Optional.** If omitted, the endpoint will return 20 organization tags per page. | + +### Sample Request + +```shell +$ curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + https://app.terraform.io/api/v2/organizations/hashicorp/tags +``` + +### Sample Response + +```json +{ + "data": [ + { + "id": "tag-1", + "type": "tags", + "attributes": { + "name": "tag1", + "created-at": "2022-03-09T06:04:39.585Z", + "instance-count": 1 + }, + "relationships": { + "organization": { + "data": { + "id": "my-organization", + "type": "organizations" + } + } + } + }, + { + "id": "tag-2", + "type": "tags", + "attributes": { + "name": "tag2", + "created-at": "2022-03-09T06:04:39.585Z", + "instance-count": 2 + }, + "relationships": { + "organization": { + "data": { + "id": "my-organization", + "type": "organizations" + } + } + } + } + ] +} +``` + +## Delete tags + +This endpoint deletes one or more tags from an organization. The organization and tags must already +exist. Tags deleted here will be removed from all other resources. + +`DELETE /organizations/:organization_name/tags` + +| Parameter | Description | +| -------------------- | ------------------------------------------------ | +| `:organization_name` | The name of the organization to delete tags from | + +| Status | Response | Reason(s) | +| ------- | ------------------------- | -------------------------------------------------------------- | +| [204][] | No Content | Successfully removed tags from organization | +| [404][] | [JSON API error object][] | Organization not found, or user unauthorized to perform action | + +### Request Body + +This POST endpoint requires a JSON object with the following properties as a request payload. + +It is important to note that `type` and `id` are required. + +| Key path | Type | Default | Description | +| ------------- | ------ | ------- | ---------------------------- | +| `data[].type` | string | | Must be `"tags"`. | +| `data[].id` | string | | The id of the tag to remove. | + +### Sample Payload + +```json +{ + "data": [ + { + "type": "tags", + "id": "tag-Yfha4YpPievQ8wJw" + } + ] +} +``` + +### Sample Request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request DELETE \ + --data @payload.json \ + https://app.terraform.io/api/v2/organizations/hashicorp/tags +``` + +## Sample Response + +No response body. + +Status code `204`. + +## Add workspaces to a tag + +`POST /tags/:tag_id/relationships/workspaces` + +| Parameter | Description | +| --------- | ---------------------------------------------------- | +| `:tag_id` | The ID of the tag that workspaces should have added. | + +| Status | Response | Reason(s) | +| ------- | ------------------------- | ----------------------------------------------------- | +| [204][] | No Content | Successfully added workspaces to tag | +| [404][] | [JSON API error object][] | Tag not found, or user unauthorized to perform action | + +### Request Body + +This POST endpoint requires a JSON object with the following properties as a request payload. + +| Key path | Type | Default | Description | +| ------------- | ------ | ------- | ------------------------------- | +| `data[].type` | string | | Must be `"workspaces"`. | +| `data[].id` | string | | The id of the workspace to add. | + +### Sample Request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request POST \ + --data @payload.json \ + https://app.terraform.io/api/v2/tags/tag-2/relationships/workspaces +``` + +### Sample Payload + +```json +{ + "data": [ + { + "type": "workspaces", + "id": "ws-pmKTbUwH2VPiiTC4" + } + ] +} +``` + +### Sample Response + +No response body. + +Status code `204`. diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/organization-tokens.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/organization-tokens.mdx new file mode 100644 index 0000000000..75894a177c --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/organization-tokens.mdx @@ -0,0 +1,148 @@ +--- +page_title: /organizations/authentication-token API reference for Terraform Enterprise +description: >- + Use the Terraform Enterprise API's `/organizations/authentication-token` + endpoint to generate and delete organization-level API tokens. +source: terraform-docs-common +--- + +[200]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/200 + +[201]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/201 + +[202]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/202 + +[204]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/204 + +[400]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/400 + +[401]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/401 + +[403]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/403 + +[404]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404 + +[409]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/409 + +[412]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/412 + +[422]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/422 + +[429]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/429 + +[500]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500 + +[504]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/504 + +[JSON API document]: /terraform/enterprise/api-docs#json-api-documents + +[JSON API error object]: https://jsonapi.org/format/#error-objects + +# Organization tokens API reference + +## Generate a new organization token + +`POST /organizations/:organization_name/authentication-token` + +| Parameter | Description | +| -------------------- | ----------------------------------------------------- | +| `:organization_name` | The name of the organization to generate a token for. | + +Generates a new [organization API token](/terraform/enterprise/users-teams-organizations/api-tokens#organization-api-tokens), replacing any existing token. + +Only members of the owners team, the owners [team API token](/terraform/enterprise/users-teams-organizations/api-tokens#team-api-tokens), and the [organization API token](/terraform/enterprise/users-teams-organizations/api-tokens#organization-api-tokens) can access this endpoint. + +This endpoint returns the secret text of the new authentication token. You can only access this token when you create it and can not recover it later. + +[permissions-citation]: #intentionally-unused---keep-for-maintainers + +| Status | Response | Reason | +| ------- | ------------------------------------------------------- | ------------------- | +| [201][] | [JSON API document][] (`type: "authentication-tokens"`) | Success | +| [404][] | [JSON API error object][] | User not authorized | + +### Request Body + +This POST endpoint requires a JSON object with the following properties as a request payload. + +| Key path | Type | Default | Description | +| ---------------------------- | ------ | ------- | ------------------------------------------------------------------------------------------------------------------------------------------- | +| `data.type` | string | | Must be `"authentication-token"`. | +| `data.attributes.expired-at` | string | `null` | The UTC date and time that the Organization Token will expire, in ISO 8601 format. If omitted or set to `null` the token will never expire. | + +### Sample Payload + +```json +{ + "data": { + "type": "authentication-token", + "attributes": { + "expired-at": "2023-04-06T12:00:00.000Z" + } + } +} +``` + +### Sample Request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request POST \ + --data @payload.json \ + https://app.terraform.io/api/v2/organizations/my-organization/authentication-token +``` + +### Sample Response + +```json +{ + "data": { + "id": "4111756", + "type": "authentication-tokens", + "attributes": { + "created-at": "2017-11-29T19:11:28.075Z", + "last-used-at": null, + "description": null, + "token": "ZgqYdzuvlv8Iyg.atlasv1.6nV7t1OyFls341jo1xdZTP72fN0uu9VL55ozqzekfmToGFbhoFvvygIRy2mwVAXomOE", + "expired-at": "2023-04-06T12:00:00.000Z" + }, + "relationships": { + "created-by": { + "data": { + "id": "user-62goNpx1ThQf689e", + "type": "users" + } + } + } + } +} +``` + +## Delete the organization token + +`DELETE /organizations/:organization/authentication-token` + +| Parameter | Description | +| -------------------- | --------------------------------------------- | +| `:organization_name` | Which organization's token should be deleted. | + +Only members of the owners team, the owners [team API token](/terraform/enterprise/users-teams-organizations/api-tokens#team-api-tokens), and the [organization API token](/terraform/enterprise/users-teams-organizations/api-tokens#organization-api-tokens) can access this endpoint. + +[permissions-citation]: #intentionally-unused---keep-for-maintainers + +| Status | Response | Reason | +| ------- | ------------------------- | ------------------- | +| [204][] | No Content | Success | +| [404][] | [JSON API error object][] | User not authorized | + +### Sample Request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request DELETE \ + https://app.terraform.io/api/v2/organizations/my-organization/authentication-token +``` diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/organizations.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/organizations.mdx new file mode 100644 index 0000000000..dc7e999e44 --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/organizations.mdx @@ -0,0 +1,987 @@ +--- +page_title: /organizations API reference for Terraform Enterprise +description: >- + Use the Terraform Enterprise API's `/organizations` endpoint to create, + update, and destroy organizations, and read their entitlement sets, module + producers, and data retention policies. +source: terraform-docs-common +--- + +[200]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/200 + +[201]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/201 + +[202]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/202 + +[204]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/204 + +[400]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/400 + +[401]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/401 + +[403]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/403 + +[404]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404 + +[409]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/409 + +[412]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/412 + +[422]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/422 + +[429]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/429 + +[500]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500 + +[504]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/504 + +[JSON API document]: /terraform/enterprise/api-docs#json-api-documents + +[JSON API error object]: https://jsonapi.org/format/#error-objects + +# Organizations API reference + +The Organizations API is used to list, show, create, update, and destroy organizations. + +## List Organizations + +`GET /organizations` + +| Status | Response | Reason | +| ------- | ----------------------------------------------- | ------------------------------------------------------------- | +| [200][] | [JSON API document][] (`type: "organizations"`) | The request was successful | +| [404][] | [JSON API error object][] | Organization not found or user unauthorized to perform action | + +### Query Parameters + +This endpoint supports pagination [with standard URL query parameters](/terraform/enterprise/api-docs#query-parameters). Remember to percent-encode `[` as `%5B` and `]` as `%5D` if your tooling doesn't automatically encode URLs. + +Currently, this endpoint returns a full, unpaginated list of organizations (without pagination metadata) if both of the pagination query parameters are omitted. To avoid inconsistent behavior, we recommend always supplying pagination parameters when building against this API. + +| Parameter | Description | +| -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `q` | **Optional.** A search query string. Organizations are searchable by name and notification email. This query takes precedence over the attribute specific searches `q[email]` or `q[name]`. | +| `q[email]` | **Optional.** A search query string. This query searches organizations by notification email. If used with `q[name]`, it returns organizations that match both queries. | +| `q[name]` | **Optional.** A search query string. This query searches organizations by name. If used with `q[email]`, it returns organizations that match both queries. | +| `page[number]` | **Optional.** Defaults to the first page, if omitted when `page[size]` is provided. | +| `page[size]` | **Optional.** Defaults to 20 organizations per page, if omitted when `page[number]` is provided. | + +### Sample Request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request GET \ + https://app.terraform.io/api/v2/organizations\?page\[number\]\=1\&page\[size\]\=20 +``` + +### Sample Response + +**Note:** Only HCP Terraform organizations return the `two-factor-conformant` and `assessments-enforced` properties. + +```json +{ + "data": [ + { + "id": "hashicorp", + "type": "organizations", + "attributes": { + "external-id": "org-Hysjx5eUviuKVCJY", + "created-at": "2021-08-24T23:10:04.675Z", + "email": "hashicorp@example.com", + "session-timeout": null, + "session-remember": null, + "collaborator-auth-policy": "password", + "plan-expired": false, + "plan-expires-at": null, + "plan-is-trial": false, + "plan-is-enterprise": false, + "plan-identifier": "developer", + "cost-estimation-enabled": true, + "send-passing-statuses-for-untriggered-speculative-plans": true, + "aggregated-commit-status-enabled": false, + "speculative-plan-management-enabled": true, + "allow-force-delete-workspaces": true, + "name": "hashicorp", + "permissions": { + "can-update": true, + "can-destroy": true, + "can-access-via-teams": true, + "can-create-module": true, + "can-create-team": true, + "can-create-workspace": true, + "can-manage-users": true, + "can-manage-subscription": true, + "can-manage-sso": true, + "can-update-oauth": true, + "can-update-sentinel": true, + "can-update-ssh-keys": true, + "can-update-api-token": true, + "can-traverse": true, + "can-start-trial": true, + "can-update-agent-pools": true, + "can-manage-tags": true, + "can-manage-varsets": true, + "can-read-varsets": true, + "can-manage-public-providers": true, + "can-create-provider": true, + "can-manage-public-modules": true, + "can-manage-custom-providers": false, + "can-manage-run-tasks": false, + "can-read-run-tasks": false, + "can-create-project": true + }, + "fair-run-queuing-enabled": true, + "saml-enabled": false, + "owners-team-saml-role-id": null, + "two-factor-conformant": false, + "assessments-enforced": false, + "default-execution-mode": "remote" + }, + "relationships": { + "default-agent-pool": { + "data": null + }, + "oauth-tokens": { + "links": { + "related": "/api/v2/organizations/hashicorp/oauth-tokens" + } + }, + "authentication-token": { + "links": { + "related": "/api/v2/organizations/hashicorp/authentication-token" + } + }, + "entitlement-set": { + "data": { + "id": "org-Hysjx5eUviuKVCJY", + "type": "entitlement-sets" + }, + "links": { + "related": "/api/v2/organizations/hashicorp/entitlement-set" + } + }, + "subscription": { + "links": { + "related": "/api/v2/organizations/hashicorp/subscription" + } + } + }, + "links": { + "self": "/api/v2/organizations/hashicorp" + } + }, + { + "id": "hashicorp-two", + "type": "organizations", + "attributes": { + "external-id": "org-iJ5tr4WgB4WpA1hD", + "created-at": "2022-01-04T18:57:16.036Z", + "email": "hashicorp@example.com", + "session-timeout": null, + "session-remember": null, + "collaborator-auth-policy": "password", + "plan-expired": false, + "plan-expires-at": null, + "plan-is-trial": false, + "plan-is-enterprise": false, + "plan-identifier": "free", + "cost-estimation-enabled": false, + "send-passing-statuses-for-untriggered-speculative-plans": false, + "aggregated-commit-status-enabled": true, + "speculative-plan-management-enabled": true, + "allow-force-delete-workspaces": false, + "name": "hashicorp-two", + "permissions": { + "can-update": true, + "can-destroy": true, + "can-access-via-teams": true, + "can-create-module": true, + "can-create-team": false, + "can-create-workspace": true, + "can-manage-users": true, + "can-manage-subscription": true, + "can-manage-sso": false, + "can-update-oauth": true, + "can-update-sentinel": false, + "can-update-ssh-keys": true, + "can-update-api-token": true, + "can-traverse": true, + "can-start-trial": true, + "can-update-agent-pools": false, + "can-manage-tags": true, + "can-manage-varsets": true, + "can-read-varsets": true, + "can-manage-public-providers": true, + "can-create-provider": true, + "can-manage-public-modules": true, + "can-manage-custom-providers": false, + "can-manage-run-tasks": false, + "can-read-run-tasks": false, + "can-create-project": false + }, + "fair-run-queuing-enabled": true, + "saml-enabled": false, + "owners-team-saml-role-id": null, + "two-factor-conformant": false, + "assessments-enforced": false, + "default-execution-mode": "remote" + }, + "relationships": { + "default-agent-pool": { + "data": null + }, + "oauth-tokens": { + "links": { + "related": "/api/v2/organizations/hashicorp-two/oauth-tokens" + } + }, + "authentication-token": { + "links": { + "related": "/api/v2/organizations/hashicorp-two/authentication-token" + } + }, + "entitlement-set": { + "data": { + "id": "org-iJ5tr4WgB4WpA1hD", + "type": "entitlement-sets" + }, + "links": { + "related": "/api/v2/organizations/hashicorp-two/entitlement-set" + } + }, + "subscription": { + "links": { + "related": "/api/v2/organizations/hashicorp-two/subscription" + } + } + }, + "links": { + "self": "/api/v2/organizations/hashicorp-two" + } + } + ], + "links": { + "self": "https://tfe-zone-b0c8608c.ngrok.io/api/v2/organizations?page%5Bnumber%5D=1&page%5Bsize%5D=20", + "first": "https://tfe-zone-b0c8608c.ngrok.io/api/v2/organizations?page%5Bnumber%5D=1&page%5Bsize%5D=20", + "prev": null, + "next": null, + "last": "https://tfe-zone-b0c8608c.ngrok.io/api/v2/organizations?page%5Bnumber%5D=1&page%5Bsize%5D=20" + }, + "meta": { + "pagination": { + "current-page": 1, + "page-size": 20, + "prev-page": null, + "next-page": null, + "total-pages": 1, + "total-count": 2 + } + } +} +``` + +## Show an Organization + +`GET /organizations/:organization_name` + +| Parameter | Description | +| -------------------- | ------------------------------------ | +| `:organization_name` | The name of the organization to show | + +| Status | Response | Reason | +| ------- | ----------------------------------------------- | ------------------------------------------------------------- | +| [200][] | [JSON API document][] (`type: "organizations"`) | The request was successful | +| [404][] | [JSON API error object][] | Organization not found or user unauthorized to perform action | + +### Sample Request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request GET \ + https://app.terraform.io/api/v2/organizations/hashicorp +``` + +### Sample Response + +**Note:** Only HCP Terraform organizations return the `two-factor-conformant` and `assessments-enforced` properties. + +```json +{ + "data": { + "id": "hashicorp", + "type": "organizations", + "attributes": { + "external-id": "org-WV6DfwfxxXvLfvfs", + "created-at": "2020-03-26T22:13:38.456Z", + "email": "user@example.com", + "session-timeout": null, + "session-remember": null, + "collaborator-auth-policy": "password", + "plan-expired": false, + "plan-expires-at": null, + "plan-is-trial": false, + "plan-is-enterprise": false, + "cost-estimation-enabled": false, + "send-passing-statuses-for-untriggered-speculative-plans": false, + "aggregated-commit-status-enabled": true, + "speculative-plan-management-enabled": true, + "allow-force-delete-workspaces": false, + "name": "hashicorp", + "permissions": { + "can-update": true, + "can-destroy": true, + "can-access-via-teams": true, + "can-create-module": true, + "can-create-team": false, + "can-create-workspace": true, + "can-manage-users": true, + "can-manage-subscription": true, + "can-manage-sso": false, + "can-update-oauth": true, + "can-update-sentinel": false, + "can-update-ssh-keys": true, + "can-update-api-token": true, + "can-traverse": true, + "can-start-trial": true, + "can-update-agent-pools": false, + "can-manage-tags": true, + "can-manage-public-modules": true, + "can-manage-public-providers": false, + "can-manage-run-tasks": false, + "can-read-run-tasks": false, + "can-create-provider": false, + "can-create-project": true + }, + "fair-run-queuing-enabled": true, + "saml-enabled": false, + "owners-team-saml-role-id": null, + "two-factor-conformant": false, + "assessments-enforced": false, + "default-execution-mode": "remote" + }, + "relationships": { + "default-agent-pool": { + "data": null + }, + "oauth-tokens": { + "links": { + "related": "/api/v2/organizations/hashicorp/oauth-tokens" + } + }, + "authentication-token": { + "links": { + "related": "/api/v2/organizations/hashicorp/authentication-token" + } + }, + "entitlement-set": { + "data": { + "id": "org-WV6DfwfxxXvLfvfs", + "type": "entitlement-sets" + }, + "links": { + "related": "/api/v2/organizations/hashicorp/entitlement-set" + } + }, + "subscription": { + "links": { + "related": "/api/v2/organizations/hashicorp/subscription" + } + } + }, + "links": { + "self": "/api/v2/organizations/hashicorp" + } + } +} +``` + +## Create an Organization + +`POST /organizations` + +| Status | Response | Reason | +| ------- | ----------------------------------------------- | -------------------------------------------------------------- | +| [201][] | [JSON API document][] (`type: "organizations"`) | The organization was successfully created | +| [404][] | [JSON API error object][] | Organization not found or user unauthorized to perform action | +| [422][] | [JSON API error object][] | Malformed request body (missing attributes, wrong types, etc.) | + +### Request Body + +This POST endpoint requires a JSON object with the following properties as a request payload. + +Properties without a default value are required. + +| Key path | Type | Default | Description | +| ------------------------------------------------------------------------- | ------- | ---------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `data.type` | string | | Must be `"organizations"` | +| `data.attributes.name` | string | | Name of the organization | +| `data.attributes.email` | string | | Admin email address | +| `data.attributes.session-timeout` | integer | 20160 | Session timeout after inactivity (minutes) | +| `data.attributes.session-remember` | integer | 20160 | Session expiration (minutes) | +| `data.attributes.collaborator-auth-policy` | string | password | Authentication policy (`password` or `two_factor_mandatory`) | +| `data.attributes.cost-estimation-enabled` | boolean | false | Whether or not the cost estimation feature is enabled for all workspaces in the organization. Defaults to false. In Terraform Enterprise, you must also enable cost estimation in [Site Administration](/terraform/enterprise/admin/application/integration#cost-estimation-integration). | +| `data.attributes.send-passing-statuses-for-untriggered-speculative-plans` | boolean | false | Whether or not to send VCS status updates for untriggered speculative plans. This can be useful if large numbers of untriggered workspaces are exhausting request limits for connected version control service providers like GitHub. Defaults to false. In Terraform Enterprise, this setting is always false and cannot be changed but is also available in Site Administration. | +| `data.attributes.aggregated-commit-status-enabled` | boolean | true | Whether or not to aggregate VCS status updates for triggered workspaces. This is useful for monorepo projects with configuration spanning many workspaces. Defaults to `true`. You cannot use this option if `send-passing-statuses-for-untriggered-speculative-plans` is set to `true`. | +| `data.attributes.speculative-plan-management-enabled` | boolean | true | Whether or not to enable [Automatically cancel plan-only runs](/terraform/enterprise/users-teams-organizations/organizations/vcs-speculative-plan-management). Defaults to `true`. | +| `data.attributes.owners-team-saml-role-id` | string | (nothing) | **Optional.** **SAML only** The name of the ["owners" team](/terraform/enterprise/saml/team-membership#managing-membership-of-the-owners-team) | +| `data.attributes.assessments-enforced` | boolean | (false) | Whether or not to compel health assessments for all eligible workspaces. When true, health assessments occur on all compatible workspaces, regardless of the value of the workspace setting `assessments-enabled`. When false, health assessments only occur for workspaces that opt in by setting `assessments-enabled: true`. | +| `data.attributes.allow-force-delete-workspaces` | boolean | (false) | Whether workspace administrators can [delete workspaces with resources under management](/terraform/enterprise/users-teams-organizations/organizations#general). If false, only organization owners may delete these workspaces. | +| `data.attributes.default-execution-mode` | boolean | `remote` | Which [execution mode](/terraform/enterprise/workspaces/settings#execution-mode) to use by default. Valid values are `remote`, `local`, and `agent`. | +| `data.attributes.default-agent-pool-id` | string | (previous value) | Required when `default-execution-mode` is set to `agent`. The ID of the agent pool belonging to the organization. Do _not_ specify this value if you set `execution-mode` to `remote` or `local`. | + +### Sample Payload + +```json +{ + "data": { + "type": "organizations", + "attributes": { + "name": "hashicorp", + "email": "user@example.com" + } + } +} +``` + +### Sample Request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request POST \ + --data @payload.json \ + https://app.terraform.io/api/v2/organizations +``` + +### Sample Response + +**Note:** Only HCP Terraform organizations return the `two-factor-conformant` and `assessments-enforced` properties. + +```json +{ + "data": { + "id": "hashicorp", + "type": "organizations", + "attributes": { + "external-id": "org-Bzyc2JuegvVLAibn", + "created-at": "2021-08-30T18:09:57.561Z", + "email": "user@example.com", + "session-timeout": null, + "session-remember": null, + "collaborator-auth-policy": "password", + "plan-expired": false, + "plan-expires-at": null, + "plan-is-trial": false, + "plan-is-enterprise": false, + "cost-estimation-enabled": false, + "send-passing-statuses-for-untriggered-speculative-plans": false, + "aggregated-commit-status-enabled": true, + "speculative-plan-management-enabled": true, + "allow-force-delete-workspaces": false, + "name": "hashicorp", + "permissions": { + "can-update": true, + "can-destroy": true, + "can-access-via-teams": true, + "can-create-module": true, + "can-create-team": false, + "can-create-workspace": true, + "can-manage-users": true, + "can-manage-subscription": true, + "can-manage-sso": false, + "can-update-oauth": true, + "can-update-sentinel": false, + "can-update-ssh-keys": true, + "can-update-api-token": true, + "can-traverse": true, + "can-start-trial": true, + "can-update-agent-pools": false, + "can-manage-tags": true, + "can-manage-public-modules": true, + "can-manage-public-providers": false, + "can-manage-run-tasks": false, + "can-read-run-tasks": false, + "can-create-provider": false, + "can-create-project": true + }, + "fair-run-queuing-enabled": true, + "saml-enabled": false, + "owners-team-saml-role-id": null, + "two-factor-conformant": false, + "assessments-enforced": false, + "default-execution-mode": "remote" + }, + "relationships": { + "default-agent-pool": { + "data": null + }, + "oauth-tokens": { + "links": { + "related": "/api/v2/organizations/hashicorp/oauth-tokens" + } + }, + "authentication-token": { + "links": { + "related": "/api/v2/organizations/hashicorp/authentication-token" + } + }, + "entitlement-set": { + "data": { + "id": "org-Bzyc2JuegvVLAibn", + "type": "entitlement-sets" + }, + "links": { + "related": "/api/v2/organizations/hashicorp/entitlement-set" + } + }, + "subscription": { + "links": { + "related": "/api/v2/organizations/hashicorp/subscription" + } + } + }, + "links": { + "self": "/api/v2/organizations/hashicorp" + } + }, + "included": [ + { + "id": "org-Bzyc2JuegvVLAibn", + "type": "entitlement-sets", + "attributes": { + "agents": false, + "audit-logging": false, + "configuration-designer": true, + "cost-estimation": false, + "global-run-tasks": false, + "module-tests-generation": false, + "operations": true, + "policy-enforcement": false, + "policy-limit": null, + "policy-mandatory-enforcement-limit": null, + "policy-set-limit": null, + "private-module-registry": true, + "run-task-limit": null, + "run-task-mandatory-enforcement-limit": null, + "run-task-workspace-limit": null, + "run-tasks": false, + "self-serve-billing": true, + "sentinel": false, + "sso": false, + "state-storage": true, + "teams": false, + "usage-reporting": false, + "user-limit": 5, + "vcs-integrations": true, + "versioned-policy-set-limit": null + }, + "links": { + "self": "/api/v2/entitlement-sets/org-Bzyc2JuegvVLAibn" + } + } + ] +} +``` + +## Update an Organization + +`PATCH /organizations/:organization_name` + +| Parameter | Description | +| -------------------- | -------------------------------------- | +| `:organization_name` | The name of the organization to update | + +| Status | Response | Reason | +| ------- | ----------------------------------------------- | -------------------------------------------------------------- | +| [200][] | [JSON API document][] (`type: "organizations"`) | The organization was successfully updated | +| [404][] | [JSON API error object][] | Organization not found or user unauthorized to perform action | +| [422][] | [JSON API error object][] | Malformed request body (missing attributes, wrong types, etc.) | + +### Request Body + +This PATCH endpoint requires a JSON object with the following properties as a request payload. + +| Key path | Type | Default | Description | +| ------------------------------------------------------------------------- | ------- | ---------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `data.type` | string | | Must be `"organizations"` | +| `data.attributes.name` | string | | Name of the organization | +| `data.attributes.email` | string | | Admin email address | +| `data.attributes.session-timeout` | integer | 20160 | Session timeout after inactivity (minutes) | +| `data.attributes.session-remember` | integer | 20160 | Session expiration (minutes) | +| `data.attributes.collaborator-auth-policy` | string | password | Authentication policy (`password` or `two_factor_mandatory`) | +| `data.attributes.cost-estimation-enabled` | boolean | false | Whether or not the cost estimation feature is enabled for all workspaces in the organization. Defaults to false. In Terraform Enterprise, you must also enable cost estimation in [Site Administration](/terraform/enterprise/admin/application/integration#cost-estimation-integration). | +| `data.attributes.send-passing-statuses-for-untriggered-speculative-plans` | boolean | false | Whether or not to send VCS status updates for untriggered speculative plans. This can be useful if large numbers of untriggered workspaces are exhausting request limits for connected version control service providers like GitHub. Defaults to false. In Terraform Enterprise, this setting is always false and cannot be changed but is also available in Site Administration. | +| `data.attributes.aggregated-commit-status-enabled` | boolean | true | Whether or not to aggregate VCS status updates for triggered workspaces. This is useful for monorepo projects with configuration spanning many workspaces. Defaults to `true`. You cannot use this option if `send-passing-statuses-for-untriggered-speculative-plans` is set to `true`. | +| `data.attributes.speculative-plan-management-enabled` | boolean | true | Whether or not to enable [Automatically cancel plan-only runs](/terraform/enterprise/users-teams-organizations/organizations/vcs-speculative-plan-management). Defaults to `true`. | +| `data.attributes.owners-team-saml-role-id` | string | (nothing) | **Optional.** **SAML only** The name of the ["owners" team](/terraform/enterprise/saml/team-membership#managing-membership-of-the-owners-team) | +| `data.attributes.assessments-enforced` | boolean | false | Whether or not to compel health assessments for all eligible workspaces. When true, health assessments occur on all compatible workspaces, regardless of the value of the workspace setting `assessments-enabled`. When false, health assessments only occur for workspaces that opt in by setting `assessments-enabled: true`. | +| `data.attributes.allow-force-delete-workspaces` | boolean | false | Whether workspace administrators can [delete workspaces with resources under management](/terraform/enterprise/users-teams-organizations/organizations#general). If false, only organization owners may delete these workspaces. | +| `data.attributes.default-execution-mode` | boolean | `remote` | Which [execution mode](/terraform/enterprise/workspaces/settings#execution-mode) to use by default. Valid values are `remote`, `local`, and `agent`. | +| `data.attributes.default-agent-pool-id` | string | (previous value) | Required when `default-execution-mode` is set to `agent`. The ID of the agent pool belonging to the organization. Do _not_ specify this value if you set `execution-mode` to `remote` or `local`. | + +### Sample Payload + +```json +{ + "data": { + "type": "organizations", + "attributes": { + "email": "admin@example.com" + } + } +} +``` + +### Sample Request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request PATCH \ + --data @payload.json \ + https://app.terraform.io/api/v2/organizations/hashicorp +``` + +### Sample Response + +**Note:** The `two-factor-conformant` and `assessments-enforced` properties are only returned from HCP Terraform organizations. + +```json +{ + "data": { + "id": "hashicorp", + "type": "organizations", + "attributes": { + "external-id": "org-Bzyc2JuegvVLAibn", + "created-at": "2021-08-30T18:09:57.561Z", + "email": "admin@example.com", + "session-timeout": null, + "session-remember": null, + "collaborator-auth-policy": "password", + "plan-expired": false, + "plan-expires-at": null, + "plan-is-trial": false, + "plan-is-enterprise": false, + "cost-estimation-enabled": false, + "send-passing-statuses-for-untriggered-speculative-plans": false, + "aggregated-commit-status-enabled": true, + "speculative-plan-management-enabled": true, + "name": "hashicorp", + "permissions": { + "can-update": true, + "can-destroy": true, + "can-access-via-teams": true, + "can-create-module": true, + "can-create-team": false, + "can-create-workspace": true, + "can-manage-users": true, + "can-manage-subscription": true, + "can-manage-sso": false, + "can-update-oauth": true, + "can-update-sentinel": false, + "can-update-ssh-keys": true, + "can-update-api-token": true, + "can-traverse": true, + "can-start-trial": true, + "can-update-agent-pools": false, + "can-manage-tags": true, + "can-manage-public-modules": true, + "can-manage-public-providers": false, + "can-manage-run-tasks": false, + "can-read-run-tasks": false, + "can-create-provider": false, + "can-create-project": true + }, + "fair-run-queuing-enabled": true, + "saml-enabled": false, + "owners-team-saml-role-id": null, + "two-factor-conformant": false, + "assessments-enforced": false, + "default-execution-mode": "remote" + }, + "relationships": { + "default-agent-pool": { + "data": null + }, + "oauth-tokens": { + "links": { + "related": "/api/v2/organizations/hashicorp/oauth-tokens" + } + }, + "authentication-token": { + "links": { + "related": "/api/v2/organizations/hashicorp/authentication-token" + } + }, + "entitlement-set": { + "data": { + "id": "org-Bzyc2JuegvVLAibn", + "type": "entitlement-sets" + }, + "links": { + "related": "/api/v2/organizations/hashicorp/entitlement-set" + } + }, + "subscription": { + "links": { + "related": "/api/v2/organizations/hashicorp/subscription" + } + } + }, + "links": { + "self": "/api/v2/organizations/hashicorp" + } + } +} +``` + +## Destroy an Organization + +`DELETE /organizations/:organization_name` + +| Parameter | Description | +| -------------------- | --------------------------------------- | +| `:organization_name` | The name of the organization to destroy | + +| Status | Response | Reason | +| ------- | ------------------------- | ------------------------------------------------------------- | +| [204][] | | The organization was successfully destroyed | +| [404][] | [JSON API error object][] | Organization not found or user unauthorized to perform action | + +### Sample Request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request DELETE \ + https://app.terraform.io/api/v2/organizations/hashicorp +``` + +### Sample Response + +The response body will be empty if successful. + +## Show the Entitlement Set + +This endpoint shows the [entitlements](/terraform/enterprise/api-docs#feature-entitlements) for an organization. + +`GET /organizations/:organization_name/entitlement-set` + +| Parameter | Description | +| -------------------- | ------------------------------------------------------ | +| `:organization_name` | The name of the organization's entitlement set to view | + +| Status | Response | Reason | +| ------- | -------------------------------------------------- | ------------------------------------------------------------- | +| [200][] | [JSON API document][] (`type: "entitlement-sets"`) | The request was successful | +| [404][] | [JSON API error object][] | Organization not found or user unauthorized to perform action | + +### Sample Request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + https://app.terraform.io/api/v2/organizations/hashicorp/entitlement-set +``` + +### Sample Response + +```json +{ + "data": { + "id": "org-Bzyc2JuegvVLAibn", + "type": "entitlement-sets", + "attributes": { + "agents": false, + "audit-logging": false, + "configuration-designer": true, + "cost-estimation": false, + "global-run-tasks": false, + "module-tests-generation": false, + "operations": true, + "policy-enforcement": false, + "policy-limit": 5, + "policy-mandatory-enforcement-limit": null, + "policy-set-limit": 1, + "private-module-registry": true, + "private-policy-agents": false, + "private-run-tasks": true, + "private-vcs": false, + "run-task-limit": 1, + "run-task-mandatory-enforcement-limit": 1, + "run-task-workspace-limit": 10, + "run-tasks": false, + "self-serve-billing": true, + "sentinel": false, + "sso": false, + "state-storage": true, + "teams": false, + "usage-reporting": false, + "user-limit": 5, + "vcs-integrations": true, + "versioned-policy-set-limit": null + }, + "links": { + "self": "/api/v2/entitlement-sets/org-Bzyc2JuegvVLAibn" + } + } +} +``` + +## Show Module Producers + + +This endpoint is exclusive to Terraform Enterprise, and not available in HCP Terraform. + + +This endpoint shows organizations that are configured to share modules with an organization through [Module Sharing](/terraform/enterprise/admin/application/module-sharing). + +`GET /organizations/:organization_name/relationships/module-producers` + +| Parameter | Description | +| -------------------- | ------------------------------------------------------- | +| `:organization_name` | The name of the organization's module producers to view | + +| Status | Response | Reason | +| ------- | ----------------------------------------------- | ------------------------------------------------------------- | +| [200][] | [JSON API document][] (`type: "organizations"`) | The request was successful | +| [404][] | [JSON API error object][] | Organization not found or user unauthorized to perform action | + +### Query Parameters + +This endpoint supports pagination [with standard URL query parameters](/terraform/enterprise/api-docs#query-parameters). Remember to percent-encode `[` as `%5B` and `]` as `%5D` if your tooling doesn't automatically encode URLs. + +| Parameter | Description | +| -------------- | -------------------------------------------------------------------------------- | +| `page[number]` | **Optional.** If omitted, the endpoint will return the first page. | +| `page[size]` | **Optional.** If omitted, the endpoint will return 20 module producers per page. | + +### Sample Request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + https://tfe.example.com/api/v2/organizations/hashicorp/relationships/module-producers +``` + +### Sample Response + +```json +{ + "data": [ + { + "id": "hc-nomad", + "type": "organizations", + "attributes": { + "name": "hc-nomad", + "external-id": "org-ArQSQMAkFQsSUZjB" + }, + "links": { + "self": "/api/v2/organizations/hc-nomad" + } + } + ], + "links": { + "self": "https://tfe.example.com/api/v2/organizations/hashicorp/relationships/module-producers?page%5Bnumber%5D=1&page%5Bsize%5D=20", + "first": "https://tfe.example.com/api/v2/organizations/hashicorp/relationships/module-producers?page%5Bnumber%5D=1&page%5Bsize%5D=20", + "prev": null, + "next": null, + "last": "https://tfe.example.com/api/v2/organizations/hashicorp/relationships/module-producers?page%5Bnumber%5D=1&page%5Bsize%5D=20" + }, + "meta": { + "pagination": { + "current-page": 1, + "prev-page": null, + "next-page": null, + "total-pages": 1, + "total-count": 1 + } + } +} +``` + +## Show data retention policy + + +This endpoint is exclusive to Terraform Enterprise and is not available in HCP Terraform. + + +`GET /organizations/:organization_name/relationships/data-retention-policy` + +| Parameter | Description | +| -------------------- | ------------------------------------------------------------------- | +| `:organization_name` | The name of the organization to show the data retention policy for. | + +This endpoint shows the data retention policy set explicitly on the organization. + +When no data retention policy is set for the organization, the endpoint returns the default policy configured for the Terraform Enterprise installation. Read more about [organization data retention policies](/terraform/enterprise/users-teams-organizations/organizations#data-retention-policies). + +For additional information, refer to [Data Retention Policy Types](/terraform/enterprise/api-docs/data-retention-policies#data-retention-policy-types) in the Terraform Enterprise documentation. + +## Create or update data retention policy + + +This endpoint is exclusive to Terraform Enterprise and is not available in HCP Terraform. + + +`POST /organizations/:organization_name/relationships/data-retention-policy` + +| Parameter | Description | +| -------------------- | --------------------------------------------------------------------- | +| `:organization_name` | The name of the organization to update the data retention policy for. | + +This endpoint creates a data retention policy for an organization or updates the existing policy. + +Read more about [organization data retention policies](/terraform/enterprise/users-teams-organizations/organizations#data-retention-policies). + +Refer to [Data Retention Policy API](/terraform/enterprise/api-docs/data-retention-policies#create-or-update-data-retention-policy) in the Terraform Enterprise documentation for details. + +## Remove data retention policy + + +This endpoint is exclusive to Terraform Enterprise and is not available in HCP Terraform. + + +`DELETE /organizations/:organization_name/relationships/data-retention-policy` + +| Parameter | Description | +| -------------------- | --------------------------------------------------------------------- | +| `:organization_name` | The name of the organization to remove the data retention policy for. | + +This endpoint removes the data retention policy explicitly set on an organization. +When the data retention policy is deleted, the organization inherits the default policy configured for the Terraform Enterprise installation. Refer to [Data Retention Policies](/terraform/enterprise/application-administration/general#data-retention-policies) for additional information. + +Refer to [Data Retention Policies](/terraform/enterprise/users-teams-organizations/organizations#data-retention-policies) for information about configuring data retention policies for an organization. + +Refer to [Data Retention Policy API](/terraform/enterprise/api-docs/data-retention-policies#remove-data-retention-policy) in the Terraform Enterprise documentation for details. + +## Available Related Resources + +The GET endpoints above can optionally return related resources, if requested with [the `include` query parameter](/terraform/enterprise/api-docs#inclusion-of-related-resources). The following resource types are available: + +| Resource Name | Description | +| ----------------- | ------------------------------------------------------------------------------------------ | +| `entitlement_set` | The entitlement set that determines which HCP Terraform features the organization can use. | + +## Relationships + +The following relationships may be present in various responses. + +| Resource Name | Description | +| ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `module-producers` | Other organizations configured to share modules with the organization. | +| `oauth-tokens` | OAuth tokens associated with VCS configurations for the organization. | +| `authentication-token` | The API token for an organization. | +| `entitlement-set` | The entitlement set that determines which HCP Terraform features the organization can use. | +| `subscription` | The current subscription for an organization. | +| `default-agent-pool` | An organization's default agent pool. Set this value if your `default-execution-mode` is `agent`. | +| `data-retention-policy` | Specifies an organization's data retention policy. Refer to [Data Retention Policy APIs](/terraform/enterprise/api-docs/data-retention-policies) in the Terraform Enterprise documentation for more details. | diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/plan-exports.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/plan-exports.mdx new file mode 100644 index 0000000000..dabfc4fd47 --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/plan-exports.mdx @@ -0,0 +1,226 @@ +--- +page_title: /plan-exports API reference for Terraform Enterprise +description: >- + Use the `/plan-exports` endpoint to manage plan exports for a Terraform run. + Create and show plan exports, or download and delete exported plan data. +source: terraform-docs-common +--- + +# Plan exports API reference + +Plan exports allow users to download data exported from the plan of a Run in a Terraform workspace. Currently, the only supported format for exporting plan data is to generate mock data for Sentinel. + +## Create a plan export + +`POST /plan-exports` + +This endpoint exports data from a plan in the specified format. The export process is asynchronous, and the resulting data becomes downloadable when its status is `"finished"`. The data is then available for one hour before expiring. After the hour is up, a new export can be created. + +| Status | Response | Reason | +| ------- | ---------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| [201][] | [JSON API document][] (`type: "plan-exports"`) | Successfully created a plan export | +| [404][] | [JSON API error object][] | Plan not found, or user unauthorized to perform action | +| [422][] | [JSON API error object][] | Malformed request body (missing attributes, wrong types, etc.), or a plan export of the provided `data-type` is already pending or downloadable for this plan | + +[201]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/201 + +[404]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404 + +[422]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/422 + +[JSON API document]: /terraform/enterprise/api-docs#json-api-documents + +[JSON API error object]: https://jsonapi.org/format/#error-objects + +### Request Body + +This POST endpoint requires a JSON object with the following properties as a request payload. + +Properties without a default value are required. + +| Key path | Type | Default | Description | +| ------------------------------ | ------ | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `data.type` | string | | Must be `"plan-exports"`. | +| `data.attributes.data-type` | string | | The format for the export. Currently, the only supported format is `"sentinel-mock-bundle-v0"`. | +| `data.relationships.plan.data` | object | | A JSON API relationship object that represents the plan being exported. This object must have a `type` of `plans`, and the `id` of a finished Terraform plan that does not already have a downloadable export of the specified `data-type` (e.g: `{"type": "plans", "id": "plan-8F5JFydVYAmtTjET"}`) | + +### Sample Payload + +```json +{ + "data": { + "type": "plan-exports", + "attributes": { + "data-type": "sentinel-mock-bundle-v0" + }, + "relationships": { + "plan": { + "data": { + "id": "plan-8F5JFydVYAmtTjET", + "type": "plans" + } + } + } + } +} +``` + +### Sample Request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request POST \ + --data @payload.json \ + https://app.terraform.io/api/v2/plan-exports +``` + +### Sample Response + +```json +{ + "data": { + "id": "pe-3yVQZvHzf5j3WRJ1", + "type": "plan-exports", + "attributes": { + "data-type": "sentinel-mock-bundle-v0", + "status": "queued", + "status-timestamps": { + "queued-at": "2019-03-04T22:29:53+00:00", + }, + }, + "relationships": { + "plan": { + "data": { + "id": "plan-8F5JFydVYAmtTjET", + "type": "plans" + } + } + }, + "links": { + "self": "/api/v2/plan-exports/pe-3yVQZvHzf5j3WRJ1", + } + } +} +``` + +## Show a plan export + +`GET /plan-exports/:id` + +| Parameter | Description | +| --------- | ---------------------------------- | +| `id` | The ID of the plan export to show. | + +There is no endpoint to list plan exports. You can find IDs for plan exports in the +`relationships.exports` property of a plan object. + +| Status | Response | Reason | +| ------- | ---------------------------------------------- | ------------------------------------------------------------- | +| [200][] | [JSON API document][] (`type: "plan-exports"`) | The request was successful | +| [404][] | [JSON API error object][] | Plan export not found, or user unauthorized to perform action | + +[200]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/200 + +[404]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404 + +[JSON API document]: /terraform/enterprise/api-docs#json-api-documents + +[JSON API error object]: https://jsonapi.org/format/#error-objects + +### Sample Request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + https://app.terraform.io/api/v2/plan-exports/pe-3yVQZvHzf5j3WRJ1 +``` + +### Sample Response + +```json +{ + "data": { + "id": "pe-3yVQZvHzf5j3WRJ1", + "type": "plan-exports", + "attributes": { + "data-type": "sentinel-mock-bundle-v0", + "status": "finished", + "status-timestamps": { + "queued-at": "2019-03-04T22:29:53+00:00", + "finished-at": "2019-03-04T22:29:58+00:00", + "expired-at": "2019-03-04T23:29:58+00:00" + }, + }, + "relationships": { + "plan": { + "data": { + "id": "plan-8F5JFydVYAmtTjET", + "type": "plans" + } + } + }, + "links": { + "self": "/api/v2/plan-exports/pe-3yVQZvHzf5j3WRJ1", + "download": "/api/v2/plan-exports/pe-3yVQZvHzf5j3WRJ1/download" + } + } +} +``` + +## Download exported plan data + +`GET /plan-exports/:id/download` + +This endpoint generates a temporary URL to the location of the exported plan data in a `.tar.gz` archive, and then redirects to that link. If using a client that can follow redirects, you can use this endpoint to save the `.tar.gz` archive locally without needing to save the temporary URL. + +| Status | Response | Reason | +| ------- | ------------------------- | ------------------------------------------------------------- | +| [302][] | HTTP Redirect | Plan export found and temporary download URL generated | +| [404][] | [JSON API error object][] | Plan export not found, or user unauthorized to perform action | + +[302]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/302 + +[404]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404 + +[JSON API error object]: https://jsonapi.org/format/#error-objects + +### Sample Request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --location \ + https://app.terraform.io/api/v2/plan-exports/pe-3yVQZvHzf5j3WRJ1/download \ + > export.tar.gz +``` + +## Delete exported plan data + +`DELETE /plan-exports/:id` + +Plan exports expire after being available for one hour, but they can be deleted manually as well. + +| Status | Response | Reason | +| ------- | ------------------------- | ------------------------------------------------------------- | +| [204][] | No content | Plan export deleted successfully | +| [404][] | [JSON API error object][] | Plan export not found, or user unauthorized to perform action | + +[204]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/204 + +[404]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404 + +[JSON API error object]: https://jsonapi.org/format/#error-objects + +### Sample Request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + -X DELETE \ + https://app.terraform.io/api/v2/plan-exports/pe-3yVQZvHzf5j3WRJ1 +``` diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/plans.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/plans.mdx new file mode 100644 index 0000000000..aee7e4eaef --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/plans.mdx @@ -0,0 +1,203 @@ +--- +page_title: /plans API reference for Terraform Enterprise +description: >- + Use the Terraform Enterprise API's `/plans` endpoint to read a Terraform run + plan or generate JSON-formatted execution plans. +source: terraform-docs-common +--- + +[200]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/200 + +[201]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/201 + +[202]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/202 + +[204]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/204 + +[307]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/307 + +[400]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/400 + +[401]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/401 + +[403]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/403 + +[404]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404 + +[409]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/409 + +[412]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/412 + +[422]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/422 + +[429]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/429 + +[500]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500 + +[504]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/504 + +[JSON API document]: /terraform/enterprise/api-docs#json-api-documents + +[JSON API error object]: https://jsonapi.org/format/#error-objects + +# Plans API reference + +A plan represents the execution plan of a Run in a Terraform workspace. + +## Attributes + +### Plan States + +The plan state is found in `data.attributes.status`, and you can reference the following list of possible states. + +| State | Description | +| ------------------------- | ----------------------------------------------------------------------------- | +| `pending` | The initial status of a plan once it has been created. | +| `managed_queued`/`queued` | The plan has been queued, awaiting backend service capacity to run terraform. | +| `running` | The plan is running. | +| `errored` | The plan has errored. This is a final state. | +| `canceled` | The plan has been canceled. This is a final state. | +| `finished` | The plan has completed successfully. This is a final state. | +| `unreachable` | The plan will not run. This is a final state. | + +## Show a plan + +`GET /plans/:id` + +| Parameter | Description | +| --------- | --------------------------- | +| `id` | The ID of the plan to show. | + +There is no endpoint to list plans. You can find the ID for a plan in the +`relationships.plan` property of a run object. + +| Status | Response | Reason | +| ------- | --------------------------------------- | ------------------------------------------------------ | +| [200][] | [JSON API document][] (`type: "plans"`) | The request was successful | +| [404][] | [JSON API error object][] | Plan not found, or user unauthorized to perform action | + +### Sample Request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + https://app.terraform.io/api/v2/plans/plan-8F5JFydVYAmtTjET +``` + +### Sample Response + +```json +{ + "data": { + "id": "plan-8F5JFydVYAmtTjET", + "type": "plans", + "attributes": { + "execution-details": { + "mode": "remote", + }, + "generated-configuration": false, + "has-changes": true, + "resource-additions": 0, + "resource-changes": 1, + "resource-destructions": 0, + "resource-imports": 0, + "status": "finished", + "status-timestamps": { + "queued-at": "2018-07-02T22:29:53+00:00", + "pending-at": "2018-07-02T22:29:53+00:00", + "started-at": "2018-07-02T22:29:54+00:00", + "finished-at": "2018-07-02T22:29:58+00:00" + }, + "log-read-url": "https://archivist.terraform.io/v1/object/dmF1bHQ6djE6OFA1eEdlSFVHRSs4YUcwaW83a1dRRDA0U2E3T3FiWk1HM2NyQlNtcS9JS1hHN3dmTXJmaFhEYTlHdTF1ZlgxZ2wzVC9kVTlNcjRPOEJkK050VFI3U3dvS2ZuaUhFSGpVenJVUFYzSFVZQ1VZYno3T3UyYjdDRVRPRE5pbWJDVTIrNllQTENyTndYd1Y0ak1DL1dPVlN1VlNxKzYzbWlIcnJPa2dRRkJZZGtFeTNiaU84YlZ4QWs2QzlLY3VJb3lmWlIrajF4a1hYZTlsWnFYemRkL2pNOG9Zc0ZDakdVMCtURUE3dDNMODRsRnY4cWl1dUN5dUVuUzdnZzFwL3BNeHlwbXNXZWRrUDhXdzhGNnF4c3dqaXlZS29oL3FKakI5dm9uYU5ZKzAybnloREdnQ3J2Rk5WMlBJemZQTg" + }, + "relationships": { + "state-versions": { + "data": [] + } + }, + "links": { + "self": "/api/v2/plans/plan-8F5JFydVYAmtTjET", + "json-output": "/api/v2/plans/plan-8F5JFydVYAmtTjET/json-output" + } + } +} +``` + +_Using HCP Terraform agents_ + +[HCP Terraform agents](/terraform/enterprise/api-docs/agents) allow HCP Terraform to communicate with isolated, private, or on-premises infrastructure. When a workspace is set to use the agent execution mode, the plan response will include additional details about the agent pool and agent used. + +```json +{ + "data": { + "id": "plan-8F5JFydVYAmtTjET", + "type": "plans", + "attributes": { + "execution-details": { + "agent-id": "agent-S1Y7tcKxXPJDQAvq", + "agent-name": "agent_01", + "agent-pool-id": "apool-Zigq2VGreKq7nwph", + "agent-pool-name": "first-pool", + "mode": "agent", + }, + "generated-configuration": false, + "has-changes": true, + "resource-additions": 0, + "resource-changes": 1, + "resource-destructions": 0, + "resource-imports": 0, + "status": "finished", + "status-timestamps": { + "queued-at": "2018-07-02T22:29:53+00:00", + "pending-at": "2018-07-02T22:29:53+00:00", + "started-at": "2018-07-02T22:29:54+00:00", + "finished-at": "2018-07-02T22:29:58+00:00" + }, + "log-read-url": "https://archivist.terraform.io/v1/object/dmF1bHQ6djE6OFA1eEdlSFVHRSs4YUcwaW83a1dRRDA0U2E3T3FiWk1HM2NyQlNtcS9JS1hHN3dmTXJmaFhEYTlHdTF1ZlgxZ2wzVC9kVTlNcjRPOEJkK050VFI3U3dvS2ZuaUhFSGpVenJVUFYzSFVZQ1VZYno3T3UyYjdDRVRPRE5pbWJDVTIrNllQTENyTndYd1Y0ak1DL1dPVlN1VlNxKzYzbWlIcnJPa2dRRkJZZGtFeTNiaU84YlZ4QWs2QzlLY3VJb3lmWlIrajF4a1hYZTlsWnFYemRkL2pNOG9Zc0ZDakdVMCtURUE3dDNMODRsRnY4cWl1dUN5dUVuUzdnZzFwL3BNeHlwbXNXZWRrUDhXdzhGNnF4c3dqaXlZS29oL3FKakI5dm9uYU5ZKzAybnloREdnQ3J2Rk5WMlBJemZQTg" + }, + "relationships": { + "state-versions": { + "data": [] + } + }, + "links": { + "self": "/api/v2/plans/plan-8F5JFydVYAmtTjET", + "json-output": "/api/v2/plans/plan-8F5JFydVYAmtTjET/json-output" + } + } +} +``` + +## Retrieve the JSON execution plan + +`GET /plans/:id/json-output` + +`GET /runs/:id/plan/json-output` + +These endpoints generate a temporary authenticated URL to the location of the [JSON formatted execution plan](/terraform/internals/json-format#format-summary). When successful, this endpoint responds with a temporary redirect that should be followed. If using a client that can follow redirects, you can use this endpoint to save the `.json` file locally without needing to save the temporary URL. + +This temporary URL provided by the redirect has a life of **1 minute**, and should not be relied upon beyond the initial request. If you need repeat access, you should use this endpoint to generate a new URL each time. + +-> **Note:** This endpoint is available for plans using Terraform 0.12 and later. For Terraform Enterprise, this endpoint is available from v202005-1, and its stability was improved in v202007-1. + +-> **Note:** This endpoint cannot be accessed with [organization tokens](/terraform/enterprise/users-teams-organizations/api-tokens#organization-api-tokens). You must access it with a [user token](/terraform/enterprise/users-teams-organizations/users#api-tokens) or [team token](/terraform/enterprise/users-teams-organizations/api-tokens#team-api-tokens) that has admin level access to the workspace. ([More about permissions.](/terraform/enterprise/users-teams-organizations/permissions)) + +[permissions-citation]: #intentionally-unused---keep-for-maintainers + +| Status | Response | Reason | +| ------- | ------------------------- | ---------------------------------------------------------------- | +| [204][] | No Content | Plan JSON supported, but plan has not yet completed. | +| [307][] | Temporary Redirect | Plan JSON found and temporary download URL generated | +| [422][] | [JSON API error object][] | Plan does not use a supported version of terraform (< 0.12.X) | + +### Sample Request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --location \ + https://app.terraform.io/api/v2/plans/plan-8F5JFydVYAmtTjET/json-output | + > json-output.json +``` diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/policies.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/policies.mdx new file mode 100644 index 0000000000..64118b2364 --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/policies.mdx @@ -0,0 +1,564 @@ +--- +page_title: /policies API reference for Terraform Enterprise +description: >- + Use the Terraform Enterprise API's `/policies` endpoint to list, show, create, + upload, update, and delete Sentinel and OPA policies. +source: terraform-docs-common +--- + +[200]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/200 + +[201]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/201 + +[202]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/202 + +[204]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/204 + +[400]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/400 + +[401]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/401 + +[403]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/403 + +[404]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404 + +[409]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/409 + +[412]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/412 + +[422]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/422 + +[429]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/429 + +[500]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500 + +[504]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/504 + +[JSON API document]: /terraform/enterprise/api-docs#json-api-documents + +[JSON API error object]: https://jsonapi.org/format/#error-objects + +# Policies API reference + +Policies are rules that HCP Terraform enforces on Terraform runs. You can use policies to validate that the Terraform plan complies with security rules and best practices. HCP Terraform policy enforcement lets you use the policy-as-code frameworks Sentinel and Open Policy Agent (OPA) to apply policy checks to HCP Terraform workspaces. Refer to [Policy Enforcement](/terraform/enterprise/policy-enforcement) for more details. + +[Policy sets](/terraform/enterprise/policy-enforcement/manage-policy-sets) are collections of policies that you can apply globally or to specific [projects](/terraform/enterprise/projects/manage) and workspaces, in your organization. For each run in the selected workspaces, HCP Terraform checks the Terraform plan against the policy set and displays the results in the UI. + + + +@include 'tfc-package-callouts/policies.mdx' + + + +This page documents the API endpoints to create, read, update, and delete policies in an organization. To manage policy sets, use the [Policy Sets API](/terraform/enterprise/api-docs/policy-sets). To manage the policy results, use the [Runs API](/terraform/enterprise/api-docs/run). + +## Create a Policy + +`POST /organizations/:organization_name/policies` + +| Parameter | Description | +| -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `:organization_name` | The organization to create the policy in. The organization must already exist in the system, and the token authenticating the API request must have permission to manage policies. (([More about permissions.](/terraform/enterprise/users-teams-organizations/permissions)) | + +\[permissions-citation]: #intentionally-unused---keep-for-maintainers) + +This creates a new policy object for the organization, but does not upload the actual policy code. After creation, you must use the [Upload a Policy endpoint (below)](#upload-a-policy) with the new policy's upload path. (This endpoint's response body includes the upload path in its `links.upload` property.) + +| Status | Response | Reason | +| ------- | ------------------------------------------ | -------------------------------------------------------------- | +| [200][] | [JSON API document][] (`type: "policies"`) | Successfully created a policy | +| [404][] | [JSON API error object][] | Organization not found, or user unauthorized to perform action | +| [422][] | [JSON API error object][] | Malformed request body (missing attributes, wrong types, etc.) | + +### Request Body + +This POST endpoint requires a JSON object with the following properties as a request payload. + +Properties without a default value are required. + +| Key path | Type | Default | Description | +| --------------------------------------- | -------------- | ---------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `data.type` | string | | Must be `"policies"`. | +| `data.attributes.name` | string | | The name of the policy, which can include letters, numbers, `-`, and `_`. You cannot modify this name after creation. | +| `data.attributes.description` | string | `null` | Text describing the policy's purpose. This field supports Markdown and appears in the HCP Terraform UI. | +| `data.attributes.kind` | string | `sentinel` | The policy-as-code framework for the policy. Valid values are `"sentinel"` and `"opa"`. | +| `data.attributes.query` | string | | The OPA query to run. Only valid for OPA policies. | +| `data.attributes.enforcement-level` | string | | The enforcement level of the policy. For Sentinel, valid values are `"hard-mandatory"`, `"soft-mandatory"`, and `"advisory"`. For OPA, Valid values are `"mandatory"`and `"advisory"`. Refer to [Managing Policies](/terraform/enterprise/policy-enforcement/manage-policy-sets) for details. | +| `data.attributes.enforce` | array\[object] | | **DEPRECATED** Use `enforcement-level` instead. An array of enforcement configurations that map policy file paths to their enforcement modes. Policies support a single file, so this array should consist of a single element. For Sentinel, if the path in the enforcement map does not match the Sentinel policy (`.sentinel`), then HCP Terraform uses the default `hard-mandatory` enforcement level. For OPA, the default enforcement level is `advisory`. | +| `data.attributes.enforce[].path` | string | | **DEPRECATED** For Sentinel, must be `.sentinel`, where `` has the same value as `data.attributes.name`. For OPA, must be `.rego`. | +| `data.attributes.enforce[].mode` | string | | **DEPRECATED** Use `enforcement-level` instead. The enforcement level of the policy. For Sentinel, valid values are `"hard-mandatory"`, `"soft-mandatory"`, and `"advisory"`. For OPA, Valid values are `"mandatory"`and `"advisory"`. Refer to [Managing Policies](/terraform/enterprise/policy-enforcement/manage-policy-sets) for details. | +| `data.relationships.policy-sets.data[]` | array\[object] | `[]` | A list of resource identifier objects to define which policy sets include the new policy. These objects must contain `id` and `type` properties, and the `type` property must be `policy-sets`. For example,`{ "id": "polset-3yVQZvHzf5j3WRJ1","type": "policy-sets" }`. | + +### Sample Payload (Sentinel) + +```json +{ + "data": { + "attributes": { + "enforcement-level": "hard-mandatory", + "name": "my-example-policy", + "description": "An example policy.", + "kind": "sentinel" + }, + "relationships": { + "policy-sets": { + "data": [ + { "id": "polset-3yVQZvHzf5j3WRJ1", "type": "policy-sets" } + ] + } + }, + "type": "policies" + } +} +``` + +### Sample Payload (OPA) + +-> **Note**: We have deprecated the `enforce` property in requests and responses but continue to provide it for backward compatibility. The below sample uses the deprecated `enforce` property. + +```json +{ + "data": { + "attributes": { + "enforce": [ + { + "path": "my-example-policy.rego", + "mode": "advisory" + } + ], + "name": "my-example-policy", + "description": "An example policy.", + "kind": "opa", + "query": "terraform.main" + }, + "relationships": { + "policy-sets": { + "data": [ + { "id": "polset-3yVQZvHzf5j3WRJ1", "type": "policy-sets" } + ] + } + }, + "type": "policies" + } +} +``` + +### Sample Request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request POST \ + --data @payload.json \ + https://app.terraform.io/api/v2/organizations/my-organization/policies +``` + +### Sample Response (Sentinel) + +```json +{ + "data": { + "id":"pol-u3S5p2Uwk21keu1s", + "type":"policies", + "attributes": { + "name":"my-example-policy", + "description":"An example policy.", + "enforcement-level":"advisory", + "enforce": [ + { + "path":"my-example-policy.sentinel", + "mode":"advisory" + } + ], + "policy-set-count": 1, + "updated-at": null + }, + "relationships": { + "organization": { + "data": { "id": "my-organization", "type": "organizations" } + }, + "policy-sets": { + "data": [ + { "id": "polset-3yVQZvHzf5j3WRJ1", "type": "policy-sets" } + ] + } + }, + "links": { + "self":"/api/v2/policies/pol-u3S5p2Uwk21keu1s", + "upload":"/api/v2/policies/pol-u3S5p2Uwk21keu1s/upload" + } + } +} +``` + +### Sample Response (OPA) + +```json +{ + "data": { + "id":"pol-u3S5p2Uwk21keu1s", + "type":"policies", + "attributes": { + "name":"my-example-policy", + "description":"An example policy.", + "kind": "opa", + "query": "terraform.main", + "enforcement-level": "advisory", + "enforce": [ + { + "path":"my-example-policy.rego", + "mode":"advisory" + } + ], + "policy-set-count": 1, + "updated-at": null + }, + "relationships": { + "organization": { + "data": { "id": "my-organization", "type": "organizations" } + }, + "policy-sets": { + "data": [ + { "id": "polset-3yVQZvHzf5j3WRJ1", "type": "policy-sets" } + ] + } + }, + "links": { + "self":"/api/v2/policies/pol-u3S5p2Uwk21keu1s", + "upload":"/api/v2/policies/pol-u3S5p2Uwk21keu1s/upload" + } + } +} +``` + +## Show a Policy + +`GET /policies/:policy_id` + +| Parameter | Description | +| ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `:policy_id` | The ID of the policy to show. Refer to [List Policies](/terraform/enterprise/api-docs/policies#list-policies) for reference information about finding IDs. | + +| Status | Response | Reason | +| ------- | ------------------------------------------ | ------------------------------------------------------- | +| [200][] | [JSON API document][] (`type: "policies"`) | The request was successful | +| [404][] | [JSON API error object][] | Policy not found or user unauthorized to perform action | + +### Sample Request + +```shell +curl --request GET \ + -H "Authorization: Bearer $TOKEN" \ + -H "Content-Type: application/vnd.api+json" \ + https://app.terraform.io/api/v2/policies/pol-oXUppaX2ximkqp8w +``` + +### Sample Response + +```json +{ + "data": { + "id": "pol-oXUppaX2ximkqp8w", + "type": "policies", + "attributes": { + "name": "my-example-policy", + "description":"An example policy.", + "kind": "sentinel", + "enforcement-level": "soft-mandatory", + "enforce": [ + { + "path": "my-example-policy.sentinel", + "mode": "soft-mandatory" + } + ], + "policy-set-count": 1, + "updated-at": "2018-09-11T18:21:21.784Z" + }, + "relationships": { + "organization": { + "data": { "id": "my-organization", "type": "organizations" } + }, + "policy-sets": { + "data": [ + { "id": "polset-3yVQZvHzf5j3WRJ1", "type": "policy-sets" } + ] + } + }, + "links": { + "self": "/api/v2/policies/pol-oXUppaX2ximkqp8w", + "upload": "/api/v2/policies/pol-oXUppaX2ximkqp8w/upload", + "download": "/api/v2/policies/pol-oXUppaX2ximkqp8w/download" + } + } +} +``` + +## Upload a Policy + +`PUT /policies/:policy_id/upload` + +| Parameter | Description | +| ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `:policy_id` | The ID of the policy to upload code to. Refer to [List Policies](/terraform/enterprise/api-docs/policies#list-policies) for reference information about finding the policy ID. The ID also appears in the response when you create a policy. | + +This endpoint uploads code to an existing Sentinel or OPA policy. + +-> **Note**: This endpoint does not use JSON-API's conventions for HTTP headers and body serialization. + +-> **Note**: This endpoint limits the size of uploaded policies to 10MB. If a larger payload is uploaded, an HTTP 413 error will be returned, and the policy will not be saved. Consider refactoring policies into multiple smaller, more concise documents if you reach this limit. + +### Request Body + +This PUT endpoint requires the text of a valid Sentinel or OPA policy with a `Content-Type` of `application/octet-stream`. + +- Refer to [Defining Sentinel Policies](/terraform/enterprise/policy-enforcement/sentinel) for details about writing Sentinel code. +- Refer to [Defining OPA Policies](/terraform/enterprise/policy-enforcement/opa) for details about writing OPA code. + +### Sample Payload + +```plain +main = rule { true } +``` + +### Sample Request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/octet-stream" \ + --request PUT \ + --data-binary @payload.file \ + https://app.terraform.io/api/v2/policies/pol-u3S5p2Uwk21keu1s/upload +``` + +## Update a Policy + +`PATCH /policies/:policy_id` + +| Parameter | Description | +| ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `:policy_id` | The ID of the policy to update. Refer to [List Policies](/terraform/enterprise/api-docs/policies#list-policies) for reference information about finding IDs. | + +This endpoint can update the enforcement mode of an existing policy. To update the policy code itself, use the upload endpoint. + +| Status | Response | Reason | +| ------- | ------------------------------------------ | -------------------------------------------------------------- | +| [200][] | [JSON API document][] (`type: "policies"`) | Successfully updated the policy | +| [404][] | [JSON API error object][] | Policy not found, or user unauthorized to perform action | +| [422][] | [JSON API error object][] | Malformed request body (missing attributes, wrong types, etc.) | + +### Request Body + +This PATCH endpoint requires a JSON object with the following properties as a request payload. + +Properties without a default value are required. + +| Key path | Type | Default | Description | +| ----------------------------------- | -------------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `data.type` | string | | Must be `"policies"`. | +| `data.attributes.description` | string | `null` | Text describing the policy's purpose. This field supports Markdown and appears in the HCP Terraform UI. | +| `data.attributes.query` | string | | The OPA query to run. This is only valid for OPA policies. | +| `data.attributes.enforcement-level` | string | | The enforcement level of the policy. For Sentinel, valid values are `"hard-mandatory"`, `"soft-mandatory"`, and `"advisory"`. For OPA, Valid values are `"mandatory"`and `"advisory"`. Refer to [Managing Policies](/terraform/enterprise/policy-enforcement/manage-policy-sets) for details. | +| `data.attributes.enforce` | array\[object] | | **DEPRECATED** Use `enforcement-level` instead. An array of enforcement configurations that map policy file paths to their enforcement modes. Policies support a single file, so this array should consist of a single element. For Sentinel, if the path in the enforcement map does not match the Sentinel policy (`.sentinel`), then HCP Terraform uses the default `hard-mandatory` enforcement level. For OPA, the default enforcement level is `advisory`. | +| `data.attributes.enforce[].path` | string | | **DEPRECATED** For Sentinel, must be `.sentinel`, where `` has the same value as `data.attributes.name`. For OPA, must be `.rego`. | +| `data.attributes.enforce[].mode` | string | | **DEPRECATED** Use `enforcement-level` instead. The enforcement level of the policy. For Sentinel, valid values are `"hard-mandatory"`, `"soft-mandatory"`, and `"advisory"`. For OPA, Valid values are `"mandatory"`and `"advisory"`. Refer to [Managing Policies](/terraform/enterprise/policy-enforcement/manage-policy-sets) for details. | + +### Sample Payload + +```json +{ + "data": { + "attributes": { + "enforcement-level": "soft-mandatory" + }, + "type":"policies" + } +} +``` + +### Sample Request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request PATCH \ + --data @payload.json \ + https://app.terraform.io/api/v2/policies/pol-u3S5p2Uwk21keu1s +``` + +### Sample Response + +```json +{ + "data": { + "id":"pol-u3S5p2Uwk21keu1s", + "type":"policies", + "attributes": { + "name":"my-example-policy", + "description":"An example policy.", + "kind": "sentinel", + "enforcement-level": "soft-mandatory", + "enforce": [ + { + "path":"my-example-policy.sentinel", + "mode":"soft-mandatory" + } + ], + "policy-set-count": 0, + "updated-at":"2017-10-10T20:58:04.621Z" + }, + "relationships": { + "organization": { + "data": { "id": "my-organization", "type": "organizations" } + }, + }, + "links": { + "self":"/api/v2/policies/pol-u3S5p2Uwk21keu1s", + "upload":"/api/v2/policies/pol-u3S5p2Uwk21keu1s/upload", + "download":"/api/v2/policies/pol-u3S5p2Uwk21keu1s/download" + } + } +} +``` + +## List Policies + +`GET /organizations/:organization_name/policies` + +| Parameter | Description | +| -------------------- | -------------------------------------- | +| `:organization_name` | The organization to list policies for. | + +| Status | Response | Reason | +| ------- | ---------------------------------------------------- | -------------------------------------------------------------- | +| [200][] | Array of [JSON API document][]s (`type: "policies"`) | Success | +| [404][] | [JSON API error object][] | Organization not found, or user unauthorized to perform action | + +### Query Parameters + +This endpoint supports pagination [with standard URL query parameters](/terraform/enterprise/api-docs#query-parameters); remember to percent-encode `[` as `%5B` and `]` as `%5D` if your tooling doesn't automatically encode URLs. + +| Parameter | Description | | +| -------------- | --------------------------------------------------------------------------------------------------------------------------------- | - | +| `page[number]` | **Optional.** If omitted, the endpoint will return the first page. | | +| `page[size]` | **Optional.** If omitted, the endpoint will return 20 policies per page. | | +| `search[name]` | **Optional.** Allows searching the organization's policies by name. | | +| `filter[kind]` | **Optional.** If specified, restricts results to those with the matching policy kind value. Valid values are `sentinel` and `opa` | | + +### Sample Request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + https://app.terraform.io/api/v2/organizations/my-organization/policies +``` + +### Sample Response + +```json +{ + "data": [ + { + "attributes": { + "enforcement-level": "advisory", + "enforce": [ + { + "mode": "advisory", + "path": "my-example-policy.sentinel" + } + ], + "name": "my-example-policy", + "description": "An example policy.", + "policy-set-count": 0, + "updated-at": "2017-10-10T20:52:13.898Z", + "kind": "sentinel" + }, + "id": "pol-u3S5p2Uwk21keu1s", + "relationships": { + "organization": { + "data": { "id": "my-organization", "type": "organizations" } + }, + }, + "links": { + "download": "/api/v2/policies/pol-u3S5p2Uwk21keu1s/download", + "self": "/api/v2/policies/pol-u3S5p2Uwk21keu1s", + "upload": "/api/v2/policies/pol-u3S5p2Uwk21keu1s/upload" + }, + "type": "policies" + }, + { + "id":"pol-vM2rBfj7V2Faq8By", + "type":"policies", + "attributes":{ + "name":"policy1", + "description":null, + "enforcement-level": "advisory", + "enforce":[ + { + "path":"policy1.rego", + "mode":"advisory" + } + ], + "policy-set-count":1, + "updated-at":"2022-09-13T04:57:43.516Z", + "kind":"opa", + "query":"data.terraform.rules.policy1.rule" + }, + "relationships":{ + "organization":{ + "data":{ + "id":"hashicorp", + "type":"organizations" + } + }, + "policy-sets":{ + "data":[ + { + "id":"polset-FYu3k5WY5eecwwdt", + "type":"policy-sets" + } + ] + } + }, + "links":{ + "self":"/api/v2/policies/pol-vM2rBfj7V2Faq8By", + "upload":"/api/v2/policies/pol-vM2rBfj7V2Faq8By/upload", + "download":"/api/v2/policies/pol-vM2rBfj7V2Faq8By/download" + } + } + ] +} +``` + +## Delete a Policy + +`DELETE /policies/:policy_id` + +| Parameter | Description | +| ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `:policy_id` | The ID of the policy to delete. Refer to [List Policies](/terraform/enterprise/api-docs/policies#list-policies) for reference information about finding IDs. | + +| Status | Response | Reason | +| ------- | ------------------------- | -------------------------------------------------------- | +| [204][] | No Content | Successfully deleted the policy | +| [404][] | [JSON API error object][] | Policy not found, or user unauthorized to perform action | + +### Sample Request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request DELETE \ + https://app.terraform.io/api/v2/policies/pl-u3S5p2Uwk21keu1s +``` + +## Available Related Resources + +The GET endpoints above can optionally return related resources, if requested with [the `include` query parameter](/terraform/enterprise/api-docs#inclusion-of-related-resources). The following resource types are available: + +| Resource Name | Description | +| ------------- | ------------------------------------------------------ | +| `policy_sets` | Policy sets that any returned policies are members of. | diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/policy-checks.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/policy-checks.mdx new file mode 100644 index 0000000000..621f40d368 --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/policy-checks.mdx @@ -0,0 +1,265 @@ +--- +page_title: /policy-checks API reference for Terraform Enterprise +description: >- + Use the Terraform Enterprise API's `/policy-checks` endpoint to manage and + override the Sentinel policy checks that HCP Terraform performs during a run. +source: terraform-docs-common +--- + +[200]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/200 + +[201]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/201 + +[202]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/202 + +[204]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/204 + +[400]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/400 + +[401]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/401 + +[403]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/403 + +[404]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404 + +[409]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/409 + +[412]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/412 + +[422]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/422 + +[429]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/429 + +[500]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500 + +[504]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/504 + +[JSON API document]: /terraform/enterprise/api-docs#json-api-documents + +[JSON API error object]: https://jsonapi.org/format/#error-objects + +# Policy checks API reference + + + +@include 'tfc-package-callouts/policies.mdx' + + + +Policy checks are the default workflow for Sentinel. Policy checks use the latest version of the Sentinel runtime and have access to cost estimation data. +This set of APIs provides endpoints to get, list, and override policy checks. + +~> **Warning:** Policy checks are deprecated and will be permanently removed in August 2025. We recommend that you start using policy evaluations to avoid disruptions. + +## List Policy Checks + +This endpoint lists the policy checks in a run. + +-> **Note**: The `sentinel` hash in the `result` attribute structure represents low-level Sentinel details generated by the policy engine. The keys or structure may change over time. Use the data in this hash at your own risk. + +`GET /runs/:run_id/policy-checks` + +| Parameter | Description | +| --------- | -------------------------------------------- | +| `run_id` | The ID of the run to list policy checks for. | + +### Query Parameters + +This endpoint supports pagination [with standard URL query parameters](/terraform/enterprise/api-docs#query-parameters). Remember to percent-encode `[` as `%5B` and `]` as `%5D` if your tooling doesn't automatically encode URLs. If neither pagination query parameters are provided, the endpoint will not be paginated and will return all results. + +| Parameter | Description | +| -------------- | ----------------------------------------------------------------------------- | +| `page[number]` | **Optional.** If omitted, the endpoint will return the first page. | +| `page[size]` | **Optional.** If omitted, the endpoint will return 20 policy checks per page. | + +### Sample Request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + https://app.terraform.io/api/v2/runs/run-CZcmD7eagjhyXavN/policy-checks +``` + +### Sample Response + +```json +{ + "data": [ + { + "id": "polchk-9VYRc9bpfJEsnwum", + "type": "policy-checks", + "attributes": { + "result": { + "result": false, + "passed": 0, + "total-failed": 1, + "hard-failed": 0, + "soft-failed": 1, + "advisory-failed": 0, + "duration-ms": 0, + "sentinel": {...} + }, + "scope": "organization", + "status": "soft_failed", + "status-timestamps": { + "queued-at": "2017-11-29T20:02:17+00:00", + "soft-failed-at": "2017-11-29T20:02:20+00:00" + }, + "actions": { + "is-overridable": true + }, + "permissions": { + "can-override": false + } + }, + "relationships": { + "run": { + "data": { + "id": "run-veDoQbv6xh6TbnJD", + "type": "runs" + } + } + }, + "links": { + "output": "/api/v2/policy-checks/polchk-9VYRc9bpfJEsnwum/output" + } + } + ] +} +``` + +## Show Policy Check + +This endpoint gets information about a specific policy check ID. Policy check IDs can appear in audit logs. + +-> **Note**: The `sentinel` hash in the `result` attribute structure represents low-level Sentinel details generated by the policy engine. The keys or structure may change over time. Use the data in this hash at your own risk. + +`GET /policy-checks/:id` + +| Parameter | Description | +| --------- | ----------------------------------- | +| `id` | The ID of the policy check to show. | + +### Sample Request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + https://app.terraform.io/api/v2/policy-checks/polchk-9VYRc9bpfJEsnwum +``` + +### Sample Response + +```json +{ + "data": { + "id": "polchk-9VYRc9bpfJEsnwum", + "type": "policy-checks", + "attributes": { + "result": { + "result": false, + "passed": 0, + "total-failed": 1, + "hard-failed": 0, + "soft-failed": 1, + "advisory-failed": 0, + "duration-ms": 0, + "sentinel": {...} + }, + "scope": "organization", + "status": "soft_failed", + "status-timestamps": { + "queued-at": "2017-11-29T20:02:17+00:00", + "soft-failed-at": "2017-11-29T20:02:20+00:00" + }, + "actions": { + "is-overridable": true + }, + "permissions": { + "can-override": false + } + }, + "relationships": { + "run": { + "data": { + "id": "run-veDoQbv6xh6TbnJD", + "type": "runs" + } + } + }, + "links": { + "output": "/api/v2/policy-checks/polchk-9VYRc9bpfJEsnwum/output" + } + } +} +``` + +## Override Policy + +This endpoint overrides a soft-mandatory or warning policy. + +-> **Note**: The `sentinel` hash in the `result` attribute structure represents low-level Sentinel details generated by the policy engine. The keys or structure may change over time. Use the data in this hash at your own risk. + +`POST /policy-checks/:id/actions/override` + +| Parameter | Description | +| --------- | --------------------------------------- | +| `id` | The ID of the policy check to override. | + +### Sample Request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request POST \ + https://app.terraform.io/api/v2/policy-checks/polchk-EasPB4Srx5NAiWAU/actions/override +``` + +### Sample Response + +```json +{ + "data": { + "id": "polchk-EasPB4Srx5NAiWAU", + "type": "policy-checks", + "attributes": { + "result": { + "result": false, + "passed": 0, + "total-failed": 1, + "hard-failed": 0, + "soft-failed": 1, + "advisory-failed": 0, + "duration-ms": 0, + "sentinel": {...} + }, + "scope": "organization", + "status": "overridden", + "status-timestamps": { + "queued-at": "2017-11-29T20:13:37+00:00", + "soft-failed-at": "2017-11-29T20:13:40+00:00", + "overridden-at": "2017-11-29T20:14:11+00:00" + }, + "actions": { + "is-overridable": true + }, + "permissions": { + "can-override": false + } + }, + "links": { + "output": "/api/v2/policy-checks/polchk-EasPB4Srx5NAiWAU/output" + } + } +} +``` + +### Available Related Resources + +The GET endpoints above can optionally return related resources, if requested with [the `include` query parameter](/terraform/enterprise/api-docs#inclusion-of-related-resources). The following resource types are available: + +| Resource Name | Description | +| --------------- | ------------------------------------- | +| `run` | The run this policy check belongs to. | +| `run.workspace` | The associated workspace of the run. | diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/policy-evaluations.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/policy-evaluations.mdx new file mode 100644 index 0000000000..9733bfbed1 --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/policy-evaluations.mdx @@ -0,0 +1,288 @@ +--- +page_title: /policy-evaluations API reference for Terraform Enterprise +description: >- + Use the Terraform Enterprise API's `/policy-evaluations` endpoint to read + policy outcomes and evaluations from Sentinel and OPA policies that HCP + Terraform performs during a Terraform run. +source: terraform-docs-common +--- + +[200]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/200 + +[404]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404 + +[JSON API document]: /terraform/enterprise/api-docs#json-api-documents + +[JSON API error object]: https://jsonapi.org/format/#error-objects + +# Policy evaluations API reference + +Policy evaluations are run within the [HCP Terraform agents](/terraform/enterprise/api-docs/agents) in HCP Terraform's infrastructure. Policy evaluations do not have access to cost estimation data. +This set of APIs provides endpoints to list and get policy evaluations and policy outcomes. + +## List Policy Evaluations in the Task Stage + +Each run passes through several stages of action (pending, plan, policy check, apply, and completion), and shows the progress through those stages as [run states](/terraform/enterprise/run/states). +This endpoint allows you to list policy evaluations that are part of the task stage. + +`GET /task-stages/:task_stage_id/policy-evaluations` + +| Parameter | Description | +| ---------------- | ------------------------- | +| `:task_stage_id` | The task stage ID to get. | + +| Status | Response | Reason | +| ------- | ------------------------- | -------------------- | +| [200][] | [JSON API document][] | Success | +| [404][] | [JSON API error object][] | Task stage not found | + +### Query Parameters + +This endpoint supports pagination [with standard URL query parameters](/terraform/enterprise/api-docs#query-parameters). Remember to percent-encode `[` as `%5B` and `]` as `%5D` if your tooling does not automatically encode URLs. + +| Parameter | Description | +| -------------- | ----------------------------------------------------------------------- | +| `page[number]` | **Optional.** If omitted, the endpoint returns the first page. | +| `page[size]` | **Optional.** If omitted, the endpoint returns 20 agent pools per page. | + +### Sample Request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request GET \ + https://app.terraform.io/api/v2/task-stages/ts-rL5ZsuwfjqfPJcdi/policy-evaluations +``` + +### Sample Response + +```json +{ + "data":[ + { + "id":"poleval-8Jj9Hfoz892D9WMX", + "type":"policy-evaluations", + "attributes":{ + "status":"passed", + "policy-kind":"opa", + "policy-tool-version": "0.44.0", + "result-count": { + "advisory-failed":0, + "errored":0, + "mandatory-failed":0, + "passed":1 + } + "status-timestamps":{ + "passed-at":"2022-09-16T01:40:30+00:00", + "queued-at":"2022-09-16T01:40:04+00:00", + "running-at":"2022-09-16T01:40:08+00:00" + }, + "created-at":"2022-09-16T01:39:07.782Z", + "updated-at":"2022-09-16T01:40:30.010Z" + }, + "relationships":{ + "policy-attachable":{ + "data":{ + "id":"ts-yxskot8Gz5yHa38W", + "type":"task-stages" + } + }, + "policy-set-outcomes":{ + "links":{ + "related":"/api/v2/policy-evaluations/poleval-8Jj9Hfoz892D9WMX/policy-set-outcomes" + } + } + }, + "links":{ + "self":"/api/v2/policy-evaluations/poleval-8Jj9Hfoz892D9WMX" + } + } + ] +} +``` + +## List Policy Outcomes + +`GET /policy-evaluations/:policy_evaluation_id/policy-set-outcomes` + +| Parameter | Description | +| ----------------------- | ---------------------------------------------------------- | +| `:policy_evaluation_id` | The ID of the policy evaluation the outcome belongs to get | + +This endpoint allows you to list policy set outcomes that are part of the policy evaluation. + +| Status | Response | Reason | +| ------- | ------------------------- | --------------------------- | +| [200][] | [JSON API document][] | Success | +| [404][] | [JSON API error object][] | Policy evaluation not found | + +### Query Parameters + +This endpoint supports pagination [with standard URL query parameters](/terraform/enterprise/api-docs#query-parameters). Remember to percent-encode `[` as `%5B` and `]` as `%5D` if your tooling does not automatically encode URLs. + +| Parameter | Description | +| ----------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- | +| `page[number]` | **Optional.** If omitted, the endpoint returns the first page. | +| `page[size]` | **Optional.** If omitted, the endpoint returns 20 policy sets per page. | +| `filter[n][status]` | **Optional.** If omitted, the endpoint returns all policies regardless of status. Must be either "passed", "failed", or "errored". | +| `filter[n][enforcementLevel]` | **Optional.** Only used if paired with a non-errored status filter. Must be either "advisory" or "mandatory." | + +-> **Note**: You can use `filter[n]` to combine combinations of statuses and enforcement levels. Policy outcomes with an errored status do not have an enforcement level. + +### Sample Request + +The following example requests demonstrate how to call the `policy-set-outcomes` endpoint using cuRL. + +#### All Policy Outcomes + +The following example call returns all policy set outcomes. + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request GET \ + https://app.terraform.io/api/v2/policy-evaluations/poleval-8Jj9Hfoz892D9WMX/policy-set-outcomes +``` + +#### Failed and Errored Policy Outcomes + +The following example call filters the response so that it only contains failed outcomes and errors. + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request GET \ + https://app.terraform.io/api/v2/policy-evaluations/poleval-8Jj9Hfoz892D9WMX/policy-set-outcomes?filter[0][status]=errored&filter[1][status]=failed&filter[1][enforcementLevel]=mandatory +``` + +### Sample Response + +The following example response shows that the `policyVCS` policy failed. + +```json +{ + "data":[ + { + "id":"psout-cu8E9a97LBepZZXd", + "type":"policy-set-outcomes", + "attributes":{ + "outcomes":[ + { + "enforcement_level":"advisory", + "query":"data.terraform.main.main", + "status":"failed", + "policy_name":"policyVCS", + "description":"" + } + ], + "error":"", + "overridable":true, + "policy-set-name":"opa-policies-vcs", + "policy-set-description":null, + "result-count":{ + "advisory-failed":1, + "errored":0, + "mandatory-failed":0, + "passed":0 + }, + "policy-tool-version": "0.54.0" + }, + "relationships":{ + "policy-evaluation":{ + "data":{ + "id":"poleval-8Jj9Hfoz892D9WMX", + "type":"policy-evaluations" + } + } + } + } + ], + "links":{ + "self":"/api/v2/policy-evaluations/poleval-8Jj9Hfoz892D9WMX/policy-set-outcomes?page%5Bnumber%5D=1\u0026page%5Bsize%5D=20", + "first":"/api/v2/policy-evaluations/poleval-8Jj9Hfoz892D9WMX/policy-set-outcomes?page%5Bnumber%5D=1\u0026page%5Bsize%5D=20", + "prev":null, + "next":null, + "last":"/api/v2/policy-evaluations/poleval-8Jj9Hfoz892D9WMX/policy-set-outcomes?page%5Bnumber%5D=1\u0026page%5Bsize%5D=20" + }, + "meta":{ + "pagination":{ + "current-page":1, + "page-size":20, + "prev-page":null, + "next-page":null, + "total-pages":1, + "total-count":1 + } + } +} +``` + +## Show a Policy Outcome + +`GET /policy-set-outcomes/:policy_set_outcome_id` + +| Parameter | Description | +| ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------- | +| `:policy_set_outcome_id` | The ID of the policy outcome to show. Refer to [List the Policy Outcomes](#list-policy-outcomes) for reference information about finding IDs. | + +| Status | Response | Reason | +| ------- | ------------------------- | ------------------------------------------------------------------- | +| [200][] | [JSON API document][] | The request was successful | +| [404][] | [JSON API error object][] | Policy set outcome not found or user unauthorized to perform action | + +### Sample Request + +The following example request gets the outcomes for the `psout-cu8E9a97LBepZZXd` policy set. + +```shell +curl --request GET \ + -H "Authorization: Bearer $TOKEN" \ + -H "Content-Type: application/vnd.api+json" \ + https://app.terraform.io/api/v2/policy-set-outcomes/psout-cu8E9a97LBepZZXd +``` + +### Sample Response + +The following example response shows that the `policyVCS` policy failed. + +```json +{ + "data":{ + "id":"psout-cu8E9a97LBepZZXd", + "type":"policy-set-outcomes", + "attributes":{ + "outcomes":[ + { + "enforcement_level":"advisory", + "query":"data.terraform.main.main", + "status":"failed", + "policy_name":"policyVCS", + "description":"" + } + ], + "error":"", + "overridable":true, + "policy-set-name":"opa-policies-vcs", + "policy-set-description":null, + "result-count":{ + "advisory-failed":1, + "errored":0, + "mandatory-failed":0, + "passed":0 + }, + "policy-tool-version": "0.54.0" + }, + "relationships":{ + "policy-evaluation":{ + "data":{ + "id":"poleval-8Jj9Hfoz892D9WMX", + "type":"policy-evaluations" + } + } + } + } +} +``` diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/policy-set-params.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/policy-set-params.mdx new file mode 100644 index 0000000000..4065a72ca9 --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/policy-set-params.mdx @@ -0,0 +1,290 @@ +--- +page_title: /parameters API reference for Terraform Enterprise +description: >- + Use the Terraform Enterprise API's `/parameters` endpoint to manage the + key/value pairs that Sentinel uses for policy checks. Read, create, update, + and delete parameters. +source: terraform-docs-common +--- + +[200]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/200 + +[201]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/201 + +[202]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/202 + +[204]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/204 + +[400]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/400 + +[401]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/401 + +[403]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/403 + +[404]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404 + +[409]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/409 + +[412]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/412 + +[422]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/422 + +[429]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/429 + +[500]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500 + +[504]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/504 + +[JSON API document]: /terraform/enterprise/api-docs#json-api-documents + +[JSON API error object]: https://jsonapi.org/format/#error-objects + +# Policy set parameters API references + +[Sentinel parameters](/sentinel/docs/language/parameters) are a list of key/value pairs that HCP Terraform sends to the Sentinel runtime when performing policy checks on workspaces. They can help you avoid hardcoding sensitive parameters into a policy. + + + +@include 'tfc-package-callouts/policies.mdx' + + + +Parameters are only available for Sentinel policies. This set of APIs provides endpoints to create, update, list and delete parameters. + +## Create a Parameter + +`POST /policy-sets/:policy_set_id/parameters` + +| Parameter | Description | +| ---------------- | ---------------------------------------------------- | +| `:policy_set_id` | The ID of the policy set to create the parameter in. | + +### Request Body + +This POST endpoint requires a JSON object with the following properties as a request payload. + +Properties without a default value are required. + +| Key path | Type | Default | Description | +| --------------------------- | ------ | ------- | ------------------------------------------------------------------------------------------------------ | +| `data.type` | string | | Must be `"vars"`. | +| `data.attributes.key` | string | | The name of the parameter. | +| `data.attributes.value` | string | `""` | The value of the parameter. | +| `data.attributes.category` | string | | The category of the parameters. Must be `"policy-set"`. | +| `data.attributes.sensitive` | bool | `false` | Whether the value is sensitive. If true then the parameter is written once and not visible thereafter. | + +### Sample Payload + +```json +{ + "data": { + "type":"vars", + "attributes": { + "key":"some_key", + "value":"some_value", + "category":"policy-set", + "sensitive":false + } + } +} +``` + +### Sample Request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request POST \ + --data @payload.json \ + https://app.terraform.io/api/v2/policy-sets/polset-u3S5p2Uwk21keu1s/parameters +``` + +### Sample Response + +```json +{ + "data": { + "id":"var-EavQ1LztoRTQHSNT", + "type":"vars", + "attributes": { + "key":"some_key", + "value":"some_value", + "sensitive":false, + "category":"policy-set" + }, + "relationships": { + "configurable": { + "data": { + "id":"pol-u3S5p2Uwk21keu1s", + "type":"policy-sets" + }, + "links": { + "related":"/api/v2/policy-sets/polset-u3S5p2Uwk21keu1s" + } + } + }, + "links": { + "self":"/api/v2/policy-sets/polset-u3S5p2Uwk21keu1s/parameters/var-EavQ1LztoRTQHSNT" + } + } +} +``` + +## List Parameters + +`GET /policy-sets/:policy_set_id/parameters` + +| Parameter | Description | +| ---------------- | ------------------------------------------------ | +| `:policy_set_id` | The ID of the policy set to list parameters for. | + +### Query Parameters + +This endpoint supports pagination [with standard URL query parameters](/terraform/enterprise/api-docs#query-parameters). Remember to percent-encode `[` as `%5B` and `]` as `%5D` if your tooling doesn't automatically encode URLs. If neither pagination query parameters are provided, the endpoint will not be paginated and will return all results. + +| Parameter | Description | +| -------------- | -------------------------------------------------------------------------- | +| `page[number]` | **Optional.** If omitted, the endpoint will return the first page. | +| `page[size]` | **Optional.** If omitted, the endpoint will return 20 parameters per page. | + +### Sample Request + +```shell +$ curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ +"https://app.terraform.io/api/v2/policy-sets/polset-u3S5p2Uwk21keu1s/parameters" +``` + +### Sample Response + +```json +{ + "data": [ + { + "id":"var-AD4pibb9nxo1468E", + "type":"vars", + "attributes": { + "key":"name", + "value":"hello", + "sensitive":false, + "category":"policy-set", + }, + "relationships": { + "configurable": { + "data": { + "id":"pol-u3S5p2Uwk21keu1s", + "type":"policy-sets" + }, + "links": { + "related":"/api/v2/policy-sets/polset-u3S5p2Uwk21keu1s" + } + } + }, + "links": { + "self":"/api/v2/policy-sets/polset-u3S5p2Uwk21keu1s/parameters/var-AD4pibb9nxo1468E" + } + } + ] +} +``` + +## Update Parameters + +`PATCH /policy-sets/:policy_set_id/parameters/:parameter_id` + +| Parameter | Description | +| ---------------- | ------------------------------------------------- | +| `:policy_set_id` | The ID of the policy set that owns the parameter. | +| `:parameter_id` | The ID of the parameter to be updated. | + +### Request Body + +This POST endpoint requires a JSON object with the following properties as a request payload. + +Properties without a default value are required. + +| Key path | Type | Default | Description | +| ----------------- | ------ | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `data.type` | string | | Must be `"vars"`. | +| `data.id` | string | | The ID of the parameter to update. | +| `data.attributes` | object | | New attributes for the parameter. This object can include `key`, `value`, `category` and `sensitive` properties, which are described above under [create a parameter](#create-a-parameter). All of these properties are optional; if omitted, a property will be left unchanged. | + +### Sample Payload + +```json +{ + "data": { + "id":"var-yRmifb4PJj7cLkMG", + "attributes": { + "key":"name", + "value":"mars", + "category":"policy-set", + "sensitive": false + }, + "type":"vars" + } +} +``` + +### Sample Request + +```bash +$ curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request PATCH \ + --data @payload.json \ + https://app.terraform.io/api/v2/policy-sets/polset-u3S5p2Uwk21keu1s/parameters/var-yRmifb4PJj7cLkMG +``` + +### Sample Response + +```json +{ + "data": { + "id":"var-yRmifb4PJj7cLkMG", + "type":"vars", + "attributes": { + "key":"name", + "value":"mars", + "sensitive":false, + "category":"policy-set", + }, + "relationships": { + "configurable": { + "data": { + "id":"pol-u3S5p2Uwk21keu1s", + "type":"policy-sets" + }, + "links": { + "related":"/api/v2/policy-sets/polset-u3S5p2Uwk21keu1s" + } + } + }, + "links": { + "self":"/api/v2/policy-sets/polset-u3S5p2Uwk21keu1s/parameters/var-yRmifb4PJj7cLkMG" + } + } +} +``` + +## Delete Parameters + +`DELETE /policy-sets/:policy_set_id/parameters/:parameter_id` + +| Parameter | Description | +| ---------------- | ------------------------------------------------- | +| `:policy_set_id` | The ID of the policy set that owns the parameter. | +| `:parameter_id` | The ID of the parameter to be deleted. | + +### Sample Request + +```bash +$ curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request DELETE \ + https://app.terraform.io/api/v2/policy-sets/polset-u3S5p2Uwk21keu1s/parameters/var-yRmifb4PJj7cLkMG +``` diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/policy-sets.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/policy-sets.mdx new file mode 100644 index 0000000000..a0e73b0a9e --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/policy-sets.mdx @@ -0,0 +1,1298 @@ +--- +page_title: /policy-sets API reference for Terraform Enterprise +description: >- + Use the Terraform Enterprise API's `/policy-sets` endpoint to read, create, + delete, update and version Sentinel and OPA policy sets. Also, attach, + exclude, and detach policy sets to workspaces and projects. +source: terraform-docs-common +--- + +[200]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/200 + +[201]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/201 + +[202]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/202 + +[204]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/204 + +[400]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/400 + +[401]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/401 + +[403]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/403 + +[404]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404 + +[409]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/409 + +[412]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/412 + +[422]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/422 + +[429]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/429 + +[500]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500 + +[504]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/504 + +[JSON API document]: /terraform/enterprise/api-docs#json-api-documents + +[JSON API error object]: https://jsonapi.org/format/#error-objects + +# Policy sets API reference + +[Policy Enforcement](/terraform/enterprise/policy-enforcement) lets you use the policy-as-code frameworks Sentinel and Open Policy Agent (OPA) to apply policy checks to HCP Terraform workspaces. + +[Policy sets](/terraform/enterprise/policy-enforcement/manage-policy-sets) are collections of policies that you can apply globally or to specific [projects](/terraform/enterprise/projects/manage) and workspaces. For each run in the selected workspaces, HCP Terraform checks the Terraform plan against the policy set. + + + +@include 'tfc-package-callouts/policies.mdx' + + + +This API provides endpoints to create, read, update, and delete policy sets in an HCP Terraform organization. To view and manage individual policies, use the [Policies API](/terraform/enterprise/api-docs/policies). + +Many of these endpoints let you create policy sets from a designated repository in a Version Control System (VCS). For more information about how to configure a policy set VCS repository, refer to [Sentinel Policy Set VCS Repositories](/terraform/enterprise/policy-enforcement/sentinel/vcs) and [OPA Policy Set VCS Repositories](/terraform/enterprise/policy-enforcement/opa/vcs). + +Instead of connecting HCP Terraform to a VCS repository, you can use the the [Policy Set Versions endpoint](#create-a-policy-set-version) to create an entire policy set from a `tar.gz` file. + +Interacting with policy sets requires permission to manage policies. ([More about permissions](/terraform/enterprise/users-teams-organizations/permissions).) + +[permissions-citation]: #intentionally-unused---keep-for-maintainers + +## Create a policy set + +`POST /organizations/:organization_name/policy-sets` + +| Parameter | Description | +| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `:organization_name` | The organization to create the policy set in. The organization must already exist in the system, and the token authenticating the API request must have permission to manage policies. ([More about permissions.](/terraform/enterprise/users-teams-organizations/permissions)) | + +[permissions-citation]: #intentionally-unused---keep-for-maintainers + +| Status | Response | Reason | +| ------- | --------------------------------------------- | -------------------------------------------------------------- | +| [201][] | [JSON API document][] (`type: "policy-sets"`) | Successfully created a policy set | +| [404][] | [JSON API error object][] | Organization not found, or user unauthorized to perform action | +| [422][] | [JSON API error object][] | Malformed request body (missing attributes, wrong types, etc.) | + +### Request Body + +This POST endpoint requires a JSON object with the following properties as a request payload. + +Properties without a default value are required. + +| Key path | Type | Default | Description | +| ----------------------------------------------------- | -------------- | ---------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `data.type` | string | | Must be `"policy-sets"`. | +| `data.attributes.name` | string | | The name of the policy set. Can include letters, numbers, `-`, and `_`. | +| `data.attributes.description` | string | `null` | Text describing the policy set's purpose. This field supports Markdown and appears in the HCP Terraform UI. | +| `data.attributes.global` | boolean | `false` | Whether HCP Terraform should automatically apply this policy set to all of an organization's workspaces. | +| `data.attributes.kind` | string | `sentinel` | The policy-as-code framework associated with the policy. Valid values are `sentinel` and `opa`. | +| `data.attributes.overridable` | boolean | `false` | Whether or not users can override this policy when it fails during a run. Valid for sentinel policies only if `agent-enabled` is set to `true`. | +| `data.attributes.vcs-repo` | object | `null` | VCS repository information. When present, HCP Terraform sources the policies and configuration from the specified VCS repository. This attribute and `policies` relationships are mutually exclusive, and you cannot use them simultaneously. | +| `data.attributes.vcs-repo.branch` | string | `null` | The branch of the VCS repository where HCP Terraform should retrieve the policy set. If empty, HCP Terraform uses the default branch. | +| `data.attributes.vcs-repo.identifier` | string | | The VCS repository identifier in the format `/`. For example, `hashicorp/my-policy-set`. The format for Azure DevOps is `//_git/`. | +| `data.attributes.vcs-repo.oauth-token-id` | string | | The OAuth Token ID HCP Terraform should use to connect to the VCS host. This value must not be specified if `github-app-installation-id` is specified. | +| `data.attributes.vcs-repo.github-app-installation-id` | string | | The VCS Connection GitHub App Installation to use. Find this ID on the account settings page. Requires previously authorizing the GitHub App and generating a user-to-server token. Manage the token from **Account Settings** within HCP Terraform. You can not specify this value if `oauth-token-id` is specified. | +| `data.attributes.vcs-repo.ingress-submodules` | boolean | `false` | Whether HCP Terraform should instantiate repository submodules when retrieving the policy set. | +| `data.attributes.policies-path` | string | `null` | The VCS repository subdirectory that contains the policies for this policy set. HCP Terraform ignores files and directories outside of this sub-path and does not update the policy set when those files change. This attribute is only valid when you specify a VCS repository for the policy set. | +| `data.relationships.projects.data[]` | array\[object] | `[]` | A list of resource identifier objects that defines which projects are associated with the policy set. These objects must contain `id` and `type` properties, and the `type` property must be `projects`. For example, `{ "id": "prj-AwfuCJTkdai4xj9w", "type": "projects" }`. You can only specify this attribute when `data.attributes.global` is `false`. | +| `data.relationships.workspaces.data[]` | array\[object] | `[]` | A list of resource identifier objects that defines which workspaces are associated with the policy set. These objects must contain `id` and `type` properties, and the `type` property must be `workspaces`. For example, `{ "id": "ws-2HRvNs49EWPjDqT1", "type": "workspaces" }`. Obtain workspace IDs from the [workspace's settings page](/terraform/enterprise/workspaces/settings) or the [Show Workspace endpoint](/terraform/enterprise/api-docs/workspaces#show-workspace). You can only specify this attribute when `data.attributes.global` is `false`. | +| `data.relationships.workspace-exclusions.data[]` | array\[object] | `[]` | A list of resource identifier objects specifying which workspaces HCP Terraform excludes from a policy set's enforcement. These objects must contain `id` and `type` properties, and the `type` property must be `workspaces`. For example, `{ "id": "ws-FVVvzCDaykN1oHiw", "type": "workspaces" }`. | +| `data.relationships.policies.data[]` | array\[object] | `[]` | A list of resource identifier objects that defines which policies are members of the policy set. These objects must contain `id` and `type` properties, and the `type` property must be `policies`. For example, `{ "id": "pol-u3S5p2Uwk21keu1s", "type": "policies" }`. | +| `data.attributes.agent-enabled` | boolean | `false` | Only valid for `sentinel` policy sets. Whether this policy set should run as a policy evaluation in the HCP Terraform agent. | +| `data.attributes.policy-tool-version` | string | `latest` | The version of the tool that HCP Terraform uses to evaluate policies. You can only set a policy tool version for 'sentinel' policy sets if `agent-enabled` is `true`. | + +### Sample Payload + +```json +{ + "data": { + "type": "policy-sets", + "attributes": { + "name": "production", + "description": "This set contains policies that should be checked on all production infrastructure workspaces.", + "global": false, + "kind": "sentinel", + "agent-enabled": true, + "policy-tool-version": "0.23.0", + "overridable": true, + "policies-path": "/policy-sets/foo", + "vcs-repo": { + "branch": "main", + "identifier": "hashicorp/my-policy-sets", + "ingress-submodules": false, + "oauth-token-id": "ot-7Fr9d83jWsi8u23A" + } + }, + "relationships": { + "projects": { + "data": [ + { "id": "prj-AwfuCJTkdai4xj9w", "type": "projects" } + ] + }, + "workspaces": { + "data": [ + { "id": "ws-2HRvNs49EWPjDqT1", "type": "workspaces" } + ] + }, + "workspace-exclusions": { + "data": [ + { "id": "ws-FVVvzCDaykN1oHiw", "type": "workspaces" } + ] + } + } + } +} +``` + +### Sample payload with individual policy relationships + +```json +{ + "data": { + "type": "policy-sets", + "attributes": { + "name": "production", + "description": "This set contains policies that should be checked on all production infrastructure workspaces.", + "kind": "sentinel", + "global": false, + "agent-enabled": true, + "policy-tool-version": "0.23.0", + "overridable": true + }, + "relationships": { + "policies": { + "data": [ + { "id": "pol-u3S5p2Uwk21keu1s", "type": "policies" } + ] + }, + "workspaces": { + "data": [ + { "id": "ws-2HRvNs49EWPjDqT1", "type": "workspaces" } + ] + } + } + } +} +``` + +### Sample Request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request POST \ + --data @payload.json \ + https://app.terraform.io/api/v2/organizations/my-organization/policy-sets +``` + +### Sample Response + +```json +{ + "data": { + "id":"polset-3yVQZvHzf5j3WRJ1", + "type":"policy-sets", + "attributes": { + "name": "production", + "description": "This set contains policies that should be checked on all production infrastructure workspaces.", + "kind": "sentinel", + "global": false, + "agent-enabled": true, + "policy-tool-version": "0.23.0", + "overridable": true, + "workspace-count": 1, + "policies-path": "/policy-sets/foo", + "versioned": true, + "vcs-repo": { + "branch": "main", + "identifier": "hashicorp/my-policy-sets", + "ingress-submodules": false, + "oauth-token-id": "ot-7Fr9d83jWsi8u23A" + }, + "created-at": "2018-09-11T18:21:21.784Z", + "updated-at": "2018-09-11T18:21:21.784Z" + }, + "relationships": { + "organization": { + "data": { "id": "my-organization", "type": "organizations" } + }, + "projects": { + "data": [ + { "id": "prj-AwfuCJTkdai4xj9w", "type": "projects" } + ] + }, + "workspaces": { + "data": [ + { "id": "ws-2HRvNs49EWPjDqT1", "type": "workspaces" } + ] + }, + "workspace-exclusions": { + "data": [ + { "id": "ws-FVVvzCDaykN1oHiw", "type": "workspaces" } + ] + }, + }, + "links": { + "self":"/api/v2/policy-sets/polset-3yVQZvHzf5j3WRJ1" + } + } +} +``` + +### Sample response with individual policy relationships + +```json +{ + "data": { + "id":"polset-3yVQZvHzf5j3WRJ1", + "type":"policy-sets", + "attributes": { + "name": "production", + "description": "This set contains policies that should be checked on all production infrastructure workspaces.", + "kind": "sentinel", + "global": false, + "agent-enabled": true, + "policy-tool-version": "0.23.0", + "overridable": true, + "policy-count": 1, + "workspace-count": 1, + "versioned": false, + "created-at": "2018-09-11T18:21:21.784Z", + "updated-at": "2018-09-11T18:21:21.784Z" + }, + "relationships": { + "organization": { + "data": { "id": "my-organization", "type": "organizations" } + }, + "policies": { + "data": [ + { "id": "pol-u3S5p2Uwk21keu1s", "type": "policies" } + ] + }, + "workspaces": { + "data": [ + { "id": "ws-2HRvNs49EWPjDqT1", "type": "workspaces" } + ] + } + }, + "links": { + "self":"/api/v2/policy-sets/polset-3yVQZvHzf5j3WRJ1" + } + } +} +``` + +## List policy sets + +`GET /organizations/:organization_name/policy-sets` + +| Parameter | Description | +| -------------------- | ----------------------------------------- | +| `:organization_name` | The organization to list policy sets for. | + +| Status | Response | Reason | +| ------- | --------------------------------------------- | -------------------------------------------------------------- | +| [200][] | [JSON API document][] (`type: "policy-sets"`) | Request was successful | +| [404][] | [JSON API error object][] | Organization not found, or user unauthorized to perform action | + +### Query Parameters + +This endpoint supports pagination [with standard URL query parameters](/terraform/enterprise/api-docs#query-parameters); remember to percent-encode `[` as `%5B` and `]` as `%5D` if your tooling doesn't automatically encode URLs. + +| Parameter | Description | +| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `filter[versioned]` | **Optional.** Allows filtering policy sets based on whether they are versioned (VCS-managed or API-managed), or use individual policy relationships. Accepts a boolean true/false value. A `true` value returns versioned sets, and a `false` value returns sets with individual policy relationships. If omitted, all policy sets are returned. | +| `filter[kind]` | **Optional.** If specified, restricts results to those with the matching policy kind value. Valid values are `sentinel` and `opa`. | +| `include` | **Optional.** Enables you to include related resource data. Value must be a comma-separated list containing one or more of `projects`, `workspaces`, `workspace-exclusions`, `policies`, `newest_version`, or `current_version`. See the [relationships section](#relationships) for details. | +| `page[number]` | **Optional.** If omitted, the endpoint will return the first page. | +| `page[size]` | **Optional.** If omitted, the endpoint will return 20 policy sets per page. | +| `search[name]` | **Optional.** Allows searching the organization's policy sets by name. | + +### Sample Request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + https://app.terraform.io/api/v2/organizations/my-organization/policy-sets +``` + +### Sample Response + +```json +{ + "data": [ + { + "id":"polset-3yVQZvHzf5j3WRJ1", + "type":"policy-sets", + "attributes": { + "name": "production", + "description": "This set contains policies that should be checked on all production infrastructure workspaces.", + "kind": "sentinel", + "global": false, + "agent-enabled": true, + "policy-tool-version": "0.23.0", + "overridable": true, + "workspace-count": 1, + "policies-path": "/policy-sets/foo", + "versioned": true, + "vcs-repo": { + "branch": "main", + "identifier": "hashicorp/my-policy-sets", + "ingress-submodules": false, + "oauth-token-id": "ot-7Fr9d83jWsi8u23A" + }, + "created-at": "2018-09-11T18:21:21.784Z", + "updated-at": "2018-09-11T18:21:21.784Z" + }, + "relationships": { + "organization": { + "data": { "id": "my-organization", "type": "organizations" } + }, + "projects": { + "data": [ + { "id": "prj-AwfuCJTkdai4xj9w", "type": "projects" } + ] + }, + "workspaces": { + "data": [ + { "id": "ws-2HRvNs49EWPjDqT1", "type": "workspaces" } + ] + }, + "workspace-exclusions": { + "data": [ + { "id": "ws-FVVvzCDaykN1oHiw", "type": "workspaces" } + ] + }, + }, + "links": { + "self":"/api/v2/policy-sets/polset-3yVQZvHzf5j3WRJ1" + } + } + ] +} +``` + +### Sample response with individual policy relationships + +```json +{ + "data": [ + { + "id":"polset-3yVQZvHzf5j3WRJ1", + "type":"policy-sets", + "attributes": { + "name": "production", + "description": "This set contains policies that should be checked on all production infrastructure workspaces.", + "kind": "sentinel", + "global": false, + "agent-enabled": true, + "policy-tool-version": "0.23.0", + "overridable": true, + "policy-count": 1, + "workspace-count": 1, + "versioned": false, + "created-at": "2018-09-11T18:21:21.784Z", + "updated-at": "2018-09-11T18:21:21.784Z" + }, + "relationships": { + "organization": { + "data": { "id": "my-organization", "type": "organizations" } + }, + "policies": { + "data": [ + { "id": "pol-u3S5p2Uwk21keu1s", "type": "policies" } + ] + }, + "workspaces": { + "data": [ + { "id": "ws-2HRvNs49EWPjDqT1", "type": "workspaces" } + ] + } + }, + "links": { + "self":"/api/v2/policy-sets/polset-3yVQZvHzf5j3WRJ1" + } + }, + ] +} +``` + +## Show a policy set + +`GET /policy-sets/:id` + +| Parameter | Description | +| --------- | ----------------------------------------------------------------------------------------------------------------------------- | +| `:id` | The ID of the policy set to show. Refer to [List Policy Sets](#list-policy-sets) for reference information about finding IDs. | + +| Status | Response | Reason | +| ------- | --------------------------------------------- | ----------------------------------------------------------- | +| [200][] | [JSON API document][] (`type: "policy-sets"`) | The request was successful | +| [404][] | [JSON API error object][] | Policy set not found or user unauthorized to perform action | + +| Parameter | Description | +| --------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `include` | **Optional.** Enables you to include related resource data. Value must be a comma-separated list containing one or more of `projects`, `workspaces`, `workspace-exclusions`, `policies`, `newest_version`, or `current_version`. See the [relationships section](#relationships) for details. | + +### Sample Request + +```shell +curl --request GET \ + -H "Authorization: Bearer $TOKEN" \ + -H "Content-Type: application/vnd.api+json" \ + https://app.terraform.io/api/v2/policy-sets/polset-3yVQZvHzf5j3WRJ1?include=current_version +``` + +### Sample Response + +```json +{ + "data": { + "id":"polset-3yVQZvHzf5j3WRJ1", + "type":"policy-sets", + "attributes": { + "name": "production", + "description": "This set contains policies that should be checked on all production infrastructure workspaces.", + "kind": "sentinel", + "global": false, + "agent-enabled": true, + "policy-tool-version": "0.23.0", + "overridable": true, + "policy-count": 0, + "workspace-count": 1, + "policies-path": "/policy-sets/foo", + "versioned": true, + "vcs-repo": { + "branch": "main", + "identifier": "hashicorp/my-policy-sets", + "ingress-submodules": false, + "oauth-token-id": "ot-7Fr9d83jWsi8u23A" + }, + "created-at": "2018-09-11T18:21:21.784Z", + "updated-at": "2018-09-11T18:21:21.784Z" + }, + "relationships": { + "organization": { + "data": { "id": "my-organization", "type": "organizations" } + }, + "current-version": { + "data": { + "id": "polsetver-m4yhbUBCgyDVpDL4", + "type": "policy-set-versions" + } + }, + "projects": { + "data": [ + { "id": "prj-AwfuCJTkdai4xj9w", "type": "projects" } + ] + }, + "workspaces": { + "data": [ + { "id": "ws-2HRvNs49EWPjDqT1", "type": "workspaces" } + ] + }, + "workspace-exclusions": { + "data": [ + { "id": "ws-FVVvzCDaykN1oHiw", "type": "workspaces" } + ] + }, + }, + "links": { + "self":"/api/v2/policy-sets/polset-3yVQZvHzf5j3WRJ1" + } + }, + "included": [ + { + "id": "polsetver-m4yhbUBCgyDVpDL4", + "type": "policy-set-versions", + "attributes": { + "source": "github", + "status": "ready", + "status-timestamps": { + "ready-at": "2019-06-21T21:29:48+00:00", + "ingressing-at": "2019-06-21T21:29:47+00:00" + }, + "error": null, + "ingress-attributes": { + "commit-sha": "8766a423cb902887deb0f7da4d9beaed432984bb", + "commit-url": "https://github.com/hashicorp/my-policy-sets/commit/8766a423cb902887deb0f7da4d9beaed432984bb", + "identifier": "hashicorp/my-policy-sets" + }, + "created-at": "2019-06-21T21:29:47.792Z", + "updated-at": "2019-06-21T21:29:48.887Z" + }, + "relationships": { + "policy-set": { + "data": { + "id": "polset-a2mJwtmKygrA11dh", + "type": "policy-sets" + } + } + }, + "links": { + "self": "/api/v2/policy-set-versions/polsetver-E4S7jz8HMjBienLS" + } + } + ] +} +``` + +### Sample response with individual policy relationships + +```json +{ + "data": { + "id":"polset-3yVQZvHzf5j3WRJ1", + "type":"policy-sets", + "attributes": { + "name": "production", + "description": "This set contains policies that should be checked on all production infrastructure workspaces.", + "kind": "sentinel", + "global": false, + "agent-enabled": true, + "policy-tool-version": "0.23.0", + "overridable": true, + "policy-count": 1, + "workspace-count": 1, + "versioned": false, + "created-at": "2018-09-11T18:21:21.784Z", + "updated-at": "2018-09-11T18:21:21.784Z", + }, + "relationships": { + "organization": { + "data": { "id": "my-organization", "type": "organizations" } + }, + "policies": { + "data": [ + { "id": "pol-u3S5p2Uwk21keu1s", "type": "policies" } + ] + }, + "workspaces": { + "data": [ + { "id": "ws-2HRvNs49EWPjDqT1", "type": "workspaces" } + ] + } + }, + "links": { + "self":"/api/v2/policy-sets/polset-3yVQZvHzf5j3WRJ1" + } + } +} +``` + +-> **Note:** The `data.relationships.projects` and `data.relationships.workspaces` refer to the projects and workspaces attached to the policy set. HCP Terraform omits these keys for policy sets marked as global, which are implicitly related to all of the organization's workspaces. + +## Update a policy set + +`PATCH /policy-sets/:id` + +| Parameter | Description | +| --------- | ------------------------------------------------------------------------------------------------------------------------------- | +| `:id` | The ID of the policy set to update. Refer to [List Policy Sets](#list-policy-sets) for reference information about finding IDs. | + +| Status | Response | Reason | +| ------- | --------------------------------------------- | -------------------------------------------------------------- | +| [200][] | [JSON API document][] (`type: "policy-sets"`) | The request was successful | +| [404][] | [JSON API error object][] | Policy set not found or user unauthorized to perform action | +| [422][] | [JSON API error object][] | Malformed request body (missing attributes, wrong types, etc.) | + +### Request Body + +This PATCH endpoint requires a JSON object with the following properties as a request payload. + +Properties without a default value are required. + +| Key path | Type | Default | Description | +| --------------------------------------------- | -------------- | ---------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `data.type` | string | | Must be `"policy-sets"`. | +| `data.attributes.name` | string | (previous value) | The name of the policy set. Can include letters, numbers, `-`, and `_`. | +| `data.attributes.description` | string | (previous value) | A description of the set's purpose. This field supports markdown and appears in the HCP Terraform UI. | +| `data.attributes.global` | boolean | (previous value) | Whether or not the policies in this set should be checked in all of the organization's workspaces or only in workspaces directly attached to the set. | +| `data.attributes.vcs-repo` | object | (previous value) | VCS repository information. When present, HCP Terraform sources the policies and configuration from the specified VCS repository instead of using definitions from HCP Terraform. Note that this option and `policies` relationships are mutually exclusive and may not be used simultaneously. | +| `data.attributes.vcs-repo.branch` | string | (previous value) | The branch of the VCS repo. When empty, HCP Terraform uses the VCS provider's default branch value. | +| `data.attributes.vcs-repo.identifier` | string | (previous value) | The VCS repository identifier in the the following format: `/`. An example identifier in GitHub is `hashicorp/my-policy-set`. The format for Azure DevOps is `//_git/`. | +| `data.attributes.vcs-repo.oauth-token-id` | string | (previous value) | The OAuth token ID to use to connect to the VCS host. | +| `data.attributes.vcs-repo.ingress-submodules` | boolean | (previous value) | Determines whether HCP Terraform instantiates repository submodules during the clone operation. | +| `data.attributes.policies-path` | boolean | (previous value) | The subdirectory of the attached VCS repository that contains the policies for this policy set. HCP Terraform ignores files and directories outside of the sub-path. Changes to the unrelated files do not update the policy set. You can only enable this option when a VCS repository is present. | +| `data.relationships.projects` | array\[object] | (previous value) | An array of references to projects that the policy set should be assigned to. Sending an empty array clears all project assignments. You can only specify this attribute when `data.attributes.global` is `false`. | +| `data.relationships.workspaces` | array\[object] | (previous value) | An array of references to workspaces that the policy set should be assigned to. Sending an empty array clears all workspace assignments. You can only specify this attribute when `data.attributes.global` is `false`. | +| `data.relationships.workspace-exclusions` | array\[object] | (previous value) | An array of references to excluded workspaces that HCP Terraform will not enforce this policy set upon. Sending an empty array clears all exclusions assignments. | +| `data.attributes.agent-enabled` | boolean | `false` | Only valid for `sentinel` policy sets. Whether this policy set should run as a policy evaluation in the HCP Terraform agent. | +| `data.attributes.policy-tool-version` | string | `latest` | The version of the tool that HCP Terraform uses to evaluate policies. You can only set a policy tool version for 'sentinel' policy sets if `agent-enabled` is `true`. | + +### Sample Payload + +```json +{ + "data": { + "type": "policy-sets", + "attributes": { + "name": "workspace-scoped-policy-set", + "description": "Policies added to this policy set will be enforced on specified workspaces", + "global": false, + "agent-enabled": true, + "policy-tool-version": "0.23.0" + }, + "relationships": { + "projects": { + "data": [ + { "id": "prj-AwfuCJTkdai4xj9w", "type": "projects" } + ] + }, + "workspaces": { + "data": [ + { "id": "ws-2HRvNs49EWPjDqT1", "type": "workspaces" } + ] + }, + "workspace-exclusions": { + "data": [ + { "id": "ws-FVVvzCDaykN1oHiw", "type": "workspaces" } + ] + } + } + } +} +``` + +### Sample Request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request PATCH \ + --data @payload.json \ + https://app.terraform.io/api/v2/policy-sets/polset-3yVQZvHzf5j3WRJ1 +``` + +### Sample Response + +```json +{ + "data": { + "id":"polset-3yVQZvHzf5j3WRJ1", + "type":"policy-sets", + "attributes": { + "name": "workspace-scoped-policy-set", + "description": "Policies added to this policy set will be enforced on specified workspaces", + "global": false, + "kind": "sentinel", + "agent-enabled": true, + "policy-tool-version": "0.23.0", + "overridable": true, + "policy-count": 1, + "workspace-count": 1, + "versioned": false, + "created-at": "2018-09-11T18:21:21.784Z", + "updated-at": "2018-09-11T18:21:21.784Z" + }, + "relationships": { + "organization": { + "data": { "id": "my-organization", "type": "organizations" } + }, + "policies": { + "data": [ + { "id": "pol-u3S5p2Uwk21keu1s", "type": "policies" } + ] + }, + "projects": { + "data": [ + { "id": "prj-AwfuCJTkdai4xj9w", "type": "projects" } + ] + }, + "workspaces": { + "data": [ + { "id": "ws-2HRvNs49EWPjDqT1", "type": "workspaces" } + ] + }, + "workspace-exclusions": { + "data": [ + { "id": "ws-FVVvzCDaykN1oHiw", "type": "workspaces" } + ] + } + }, + "links": { + "self":"/api/v2/policy-sets/polset-3yVQZvHzf5j3WRJ1" + } + } +} +``` + +## Add policies to the policy set + +`POST /policy-sets/:id/relationships/policies` + +| Parameter | Description | +| --------- | ---------------------------------------------------------------------------------------------------------------------------------------- | +| `:id` | The ID of the policy set to add policies to. Refer to [List Policy Sets](#list-policy-sets) for reference information about finding IDs. | + +| Status | Response | Reason | +| ------- | ------------------------- | -------------------------------------------------------------------------- | +| [204][] | No Content | The request was successful | +| [404][] | [JSON API error object][] | Policy set not found or user unauthorized to perform action | +| [422][] | [JSON API error object][] | Malformed request body (one or more policies not found, wrong types, etc.) | + +~> **Note:** This endpoint may only be used when there is no VCS repository associated with the policy set. + +### Request Body + +This POST endpoint requires a JSON object with the following properties as a request payload. + +Properties without a default value are required. + +| Key path | Type | Default | Description | +| -------- | -------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `data[]` | array\[object] | | A list of resource identifier objects that defines which policies will be added to the set. These objects must contain `id` and `type` properties, and the `type` property must be `policies` (e.g. `{ "id": "pol-u3S5p2Uwk21keu1s", "type": "policies" }`). | + +### Sample Payload + +```json +{ + "data": [ + { "id": "pol-u3S5p2Uwk21keu1s", "type": "policies" }, + { "id": "pol-2HRvNs49EWPjDqT1", "type": "policies" } + ] +} +``` + +### Sample Request + +```shell +curl \ + -H "Authorization: Bearer $TOKEN" \ + -H "Content-Type: application/vnd.api+json" \ + --request POST \ + --data @payload.json \ + https://app.terraform.io/api/v2/policy-sets/polset-3yVQZvHzf5j3WRJ1/relationships/policies +``` + +## Attach a policy set to projects + +`POST /policy-sets/:id/relationships/projects` + +| Parameter | Description | +| --------- | ------------------------------------------------------------------------------------------------------------------------------------------- | +| `:id` | The ID of the policy set to attach to projects. Refer to [List Policy Sets](#list-policy-sets) for reference information about finding IDs. | + +-> **Note:** You can not attach global policy sets to individual projects. + +| Status | Response | Reason | +| ------- | ------------------------- | -------------------------------------------------------------------------- | +| [204][] | Nothing | The request was successful | +| [404][] | [JSON API error object][] | Policy set not found or user unauthorized to perform action | +| [422][] | [JSON API error object][] | Malformed request body (one or more projects not found, wrong types, etc.) | + +### Request Body + +This POST endpoint requires a JSON object with the following properties as a request payload. + +Properties without a default value are required. + +| Key path | Type | Default | Description | +| -------- | -------------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `data[]` | array\[object] | | A list of resource identifier objects that defines which projects to attach the policy set to. These objects must contain `id` and `type` properties, and the `type` property must be `projects` (e.g. `{ "id": "prj-AwfuCJTkdai4xj9w", "type": "projects" }`). | + +### Sample Payload + +```json +{ + "data": [ + { "id": "prj-AwfuCJTkdai4xj9w", "type": "projects" }, + { "id": "prj-2HRvNs49EWPjDqT1", "type": "projects" } + ] +} +``` + +### Sample Request + +```shell +curl \ + -H "Authorization: Bearer $TOKEN" \ + -H "Content-Type: application/vnd.api+json" \ + --request POST \ + --data @payload.json \ + https://app.terraform.io/api/v2/policy-sets/polset-3yVQZvHzf5j3WRJ1/relationships/projects +``` + +## Attach a policy set to workspaces + +`POST /policy-sets/:id/relationships/workspaces` + +| Parameter | Description | +| --------- | --------------------------------------------------------------------------------------------------------------------------------------------- | +| `:id` | The ID of the policy set to attach to workspaces. Refer to [List Policy Sets](#list-policy-sets) for reference information about finding IDs. | + +-> **Note:** Policy sets marked as global cannot be attached to individual workspaces. + +| Status | Response | Reason | +| ------- | ------------------------- | ---------------------------------------------------------------------------- | +| [204][] | No Content | The request was successful | +| [404][] | [JSON API error object][] | Policy set not found or user unauthorized to perform action | +| [422][] | [JSON API error object][] | Malformed request body (one or more workspaces not found, wrong types, etc.) | + +### Request Body + +This POST endpoint requires a JSON object with the following properties as a request payload. + +Properties without a default value are required. + +| Key path | Type | Default | Description | +| -------- | -------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `data[]` | array\[object] | | A list of resource identifier objects that defines the workspaces the policy set will be attached to. These objects must contain `id` and `type` properties, and the `type` property must be `workspaces` (e.g. `{ "id": "ws-2HRvNs49EWPjDqT1", "type": "workspaces" }`). | + +### Sample Payload + +```json +{ + "data": [ + { "id": "ws-u3S5p2Uwk21keu1s", "type": "workspaces" }, + { "id": "ws-2HRvNs49EWPjDqT1", "type": "workspaces" } + ] +} +``` + +### Sample Request + +```shell +curl \ + -H "Authorization: Bearer $TOKEN" \ + -H "Content-Type: application/vnd.api+json" \ + --request POST \ + --data @payload.json \ + https://app.terraform.io/api/v2/policy-sets/polset-3yVQZvHzf5j3WRJ1/relationships/workspaces +``` + +## Exclude a workspace from a policy set + +`POST /policy-sets/:id/relationships/workspace-exclusions` + +| Parameter | Description | +| --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `:id` | The ID of a policy set that you want HCP Terraform to exclude from the workspaces you specify. Refer to [List Policy Sets](#list-policy-sets) for reference information about finding IDs. | + +| Status | Response | Reason | +| ------- | ------------------------- | ------------------------------------------------------------------------------------- | +| [204][] | No Content | The request was successful | +| [404][] | [JSON API error object][] | Policy set not found or user unauthorized to perform action | +| [422][] | [JSON API error object][] | Malformed request body (one or more excluded workspaces not found, wrong types, etc.) | + +### Request Body + +This POST endpoint requires a JSON object with the following properties as a request payload. + +Properties without a default value are required. + +| Key path | Type | Default | Description | +| -------- | -------------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `data[]` | array\[object] | | A list of resource identifier objects that defines the excluded workspaces the policy set will be attached to. These objects must contain `id` and `type` properties, and the `type` property must be `workspaces` (e.g. `{ "id": "ws-2HRvNs49EWPjDqT1", "type": "workspaces" }`). | + +### Sample Payload + +```json +{ + "data": [ + { "id": "ws-u3S5p2Uwk21keu1s", "type": "workspaces" }, + { "id": "ws-2HRvNs49EWPjDqT1", "type": "workspaces" } + ] +} +``` + +### Sample Request + +```shell +curl \ + -H "Authorization: Bearer $TOKEN" \ + -H "Content-Type: application/vnd.api+json" \ + --request POST \ + --data @payload.json \ + https://app.terraform.io/api/v2/policy-sets/polset-3yVQZvHzf5j3WRJ1/relationships/workspace-exclusions +``` + +## Remove policies from the policy set + +`DELETE /policy-sets/:id/relationships/policies` + +| Parameter | Description | +| --------- | --------------------------------------------------------------------------------------------------------------------------------------------- | +| `:id` | The ID of the policy set to remove policies from. Refer to [List Policy Sets](#list-policy-sets) for reference information about finding IDs. | + +| Status | Response | Reason | +| ------- | ------------------------- | ----------------------------------------------------------- | +| [204][] | No Content | The request was successful | +| [404][] | [JSON API error object][] | Policy set not found or user unauthorized to perform action | +| [422][] | [JSON API error object][] | Malformed request body (wrong types, etc.) | + +~> **Note:** This endpoint may only be used when there is no VCS repository associated with the policy set. + +### Request Body + +This DELETE endpoint requires a JSON object with the following properties as a request payload. + +Properties without a default value are required. + +| Key path | Type | Default | Description | +| -------- | -------------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `data[]` | array\[object] | | A list of resource identifier objects that defines which policies will be removed from the set. These objects must contain `id` and `type` properties, and the `type` property must be `policies` (e.g. `{ "id": "pol-u3S5p2Uwk21keu1s", "type": "policies" }`). | + +### Sample Payload + +```json +{ + "data": [ + { "id": "pol-u3S5p2Uwk21keu1s", "type": "policies" }, + { "id": "pol-2HRvNs49EWPjDqT1", "type": "policies" } + ] +} +``` + +### Sample Request + +```shell +curl \ + -H "Authorization: Bearer $TOKEN" \ + -H "Content-Type: application/vnd.api+json" \ + --request DELETE \ + --data @payload.json \ + https://app.terraform.io/api/v2/policy-sets/polset-3yVQZvHzf5j3WRJ1/relationships/policies +``` + +## Detach a policy set from projects + +`DELETE /policy-sets/:id/relationships/projects` + +| Parameter | Description | +| --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `:id` | The ID of the policy set you want to detach from the specified projects. Refer to [List Policy Sets](#list-policy-sets) for reference information about finding IDs. | + +-> **Note:** You can not attach global policy sets to individual projects. + +| Status | Response | Reason | +| ------- | ------------------------- | -------------------------------------------------------------------------- | +| [204][] | Nothing | The request was successful | +| [404][] | [JSON API error object][] | Policy set not found or user unauthorized to perform action | +| [422][] | [JSON API error object][] | Malformed request body (one or more projects not found, wrong types, etc.) | + +### Request Body + +This DELETE endpoint requires a JSON object with the following properties as a request payload. + +Properties without a default value are required. + +| Key path | Type | Default | Description | +| -------- | -------------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `data[]` | array\[object] | | A list of resource identifier objects that defines the projects the policy set will be detached from. These objects must contain `id` and `type` properties, and the `type` property must be `projects`. For example, `{ "id": "prj-AwfuCJTkdai4xj9w", "type": "projects" }`. | + +### Sample Payload + +```json +{ + "data": [ + { "id": "prj-AwfuCJTkdai4xj9w", "type": "projects" }, + { "id": "prj-2HRvNs49EWPjDqT1", "type": "projects" } + ] +} +``` + +### Sample Request + +```shell +curl \ + -H "Authorization: Bearer $TOKEN" \ + -H "Content-Type: application/vnd.api+json" \ + --request DELETE \ + --data @payload.json \ + https://app.terraform.io/api/v2/policy-sets/polset-3yVQZvHzf5j3WRJ1/relationships/projects +``` + +## Detach the policy set from workspaces + +`DELETE /policy-sets/:id/relationships/workspaces` + +| Parameter | Description | +| --------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | +| `:id` | The ID of the policy set to detach from workspaces. Refer to [List Policy Sets](#list-policy-sets) for reference information about finding IDs. | + +-> **Note:** Policy sets marked as global cannot be detached from individual workspaces. + +| Status | Response | Reason | +| ------- | ------------------------- | ----------------------------------------------------------- | +| [204][] | No Content | The request was successful | +| [404][] | [JSON API error object][] | Policy set not found or user unauthorized to perform action | +| [422][] | [JSON API error object][] | Malformed request body (wrong types, etc.) | + +### Request Body + +This DELETE endpoint requires a JSON object with the following properties as a request payload. + +Properties without a default value are required. + +| Key path | Type | Default | Description | +| -------- | -------------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `data[]` | array\[object] | | A list of resource identifier objects that defines which workspaces the policy set will be detached from. These objects must contain `id` and `type` properties, and the `type` property must be `workspaces` (e.g. `{ "id": "ws-2HRvNs49EWPjDqT1", "type": "workspaces" }`). Obtain workspace IDs from the [workspace settings](/terraform/enterprise/workspaces/settings) or the [Show Workspace](/terraform/enterprise/api-docs/workspaces#show-workspace) endpoint. | + +### Sample Payload + +```json +{ + "data": [ + { "id": "ws-u3S5p2Uwk21keu1s", "type": "workspaces" }, + { "id": "ws-2HRvNs49EWPjDqT1", "type": "workspaces" } + ] +} +``` + +### Sample Request + +```shell +curl \ + -H "Authorization: Bearer $TOKEN" \ + -H "Content-Type: application/vnd.api+json" \ + --request DELETE \ + --data @payload.json \ + https://app.terraform.io/api/v2/policy-sets/polset-3yVQZvHzf5j3WRJ1/relationships/workspaces +``` + +## Reinclude a workspace to a policy set + +`DELETE /policy-sets/:id/relationships/workspace-exclusions` + +| Parameter | Description | +| --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `:id` | The ID of the policy set HCP Terraform should reinclude (enforce) on the specified workspaces. Refer to [List Policy Sets](#list-policy-sets) for reference information about finding IDs. | + +| Status | Response | Reason | +| ------- | ------------------------- | ----------------------------------------------------------- | +| [204][] | No Content | The request was successful | +| [404][] | [JSON API error object][] | Policy set not found or user unauthorized to perform action | +| [422][] | [JSON API error object][] | Malformed request body (wrong types, etc.) | + +### Request Body + +This DELETE endpoint requires a JSON object with the following properties as a request payload. + +Properties without a default value are required. + +| Key path | Type | Default | Description | +| -------- | -------------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `data[]` | array\[object] | | A list of resource identifier objects that defines which workspaces HCP Terraform should reinclude (enforce) this policy set on. These objects must contain `id` and `type` properties, and the `type` property must be `workspaces` (e.g. `{ "id": "ws-2HRvNs49EWPjDqT1", "type": "workspaces" }`). Obtain workspace IDs from the [workspace settings](/terraform/enterprise/workspaces/settings) or the [Show Workspace](/terraform/enterprise/api-docs/workspaces#show-workspace) endpoint. | + +### Sample Payload + +```json +{ + "data": [ + { "id": "ws-u3S5p2Uwk21keu1s", "type": "workspaces" }, + { "id": "ws-2HRvNs49EWPjDqT1", "type": "workspaces" } + ] +} +``` + +### Sample Request + +```shell +curl \ + -H "Authorization: Bearer $TOKEN" \ + -H "Content-Type: application/vnd.api+json" \ + --request DELETE \ + --data @payload.json \ + https://app.terraform.io/api/v2/policy-sets/polset-3yVQZvHzf5j3WRJ1/relationships/workspace-exclusions +``` + +## Delete a policy set + +`DELETE /policy-sets/:id` + +| Parameter | Description | +| --------- | ------------------------------------------------------------------------------------------------------------------------------- | +| `:id` | The ID of the policy set to delete. Refer to [List Policy Sets](#list-policy-sets) for reference information about finding IDs. | + +| Status | Response | Reason | +| ------- | ------------------------- | ------------------------------------------------------------ | +| [204][] | No Content | Successfully deleted the policy set | +| [404][] | [JSON API error object][] | Policy set not found, or user unauthorized to perform action | + +### Sample Request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request DELETE \ + https://app.terraform.io/api/v2/policy-sets/polset-3yVQZvHzf5j3WRJ1 +``` + +## Create a policy set version + +For versioned policy sets which have no VCS repository attached, versions of policy code may be uploaded directly to the API by creating a new policy set version and, in a subsequent request, uploading a tarball (tar.gz) of data to it. + +`POST /policy-sets/:id/versions` + +| Parameter | Description | +| --------- | ----------------------------------------------------- | +| `:id` | The ID of the policy set to create a new version for. | + +| Status | Response | Reason | +| ------- | ----------------------------------------------------- | ----------------------------------------------------------- | +| [201][] | [JSON API document][] (`type: "policy-set-versions"`) | The request was successful. | +| [404][] | [JSON API error object][] | Policy set not found or user unauthorized to perform action | +| [422][] | [JSON API error object][] | The policy set does not support uploading versions. | + +### Sample Request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request POST \ + https://app.terraform.io/api/v2/policy-sets/polset-3yVQZvHzf5j3WRJ1/versions +``` + +### Sample Response + +```json +{ + "data": { + "id": "polsetver-cXciu9nQwmk9Cfrn", + "type": "policy-set-versions", + "attributes": { + "source": "tfe-api", + "status": "pending", + "status-timestamps": {}, + "error": null, + "created-at": "2019-06-28T23:53:15.875Z", + "updated-at": "2019-06-28T23:53:15.875Z" + }, + "relationships": { + "policy-set": { + "data": { + "id": "polset-ws1CZBzm2h5K6ZT5", + "type": "policy-sets" + } + } + }, + "links": { + "self": "/api/v2/policy-set-versions/polsetver-cXciu9nQwmk9Cfrn", + "upload": "https://archivist.terraform.io/v1/object/dmF1bHQ6djE6NWJPbHQ4QjV4R1ox..." + } + } +} +``` + +The `upload` link URL in the above response is valid for one hour after creation. Make a `PUT` request to this URL directly, sending the policy set contents in `tar.gz` format as the request body. Once uploaded successfully, you can request the [Show Policy Set](#show-a-policy-set) endpoint again to verify that the status has changed from `pending` to `ready`. + +## Upload policy set versions + +`PUT https://archivist.terraform.io/v1/object/` + +The URL is provided in the `upload` attribute in the `policy-set-versions` resource. + +### Sample Request + +In the example below, `policy-set.tar.gz` is the local filename of the policy set version file to upload. + +```shell +curl \ + --header "Content-Type: application/octet-stream" \ + --request PUT \ + --data-binary @policy-set.tar.gz \ + https://archivist.terraform.io/v1/object/dmF1bHQ6djE6NWJPbHQ4QjV4R1ox... +``` + +## Show a policy set version + +`GET /policy-set-versions/:id` + +| Parameter | Description | +| --------- | ----------------------------------------- | +| `:id` | The ID of the policy set version to show. | + +| Status | Response | Reason | +| ------- | ----------------------------------------------------- | ------------------------------------------------------------------- | +| [200][] | [JSON API document][] (`type: "policy-set-versions"`) | The request was successful. | +| [404][] | [JSON API error object][] | Policy set version not found or user unauthorized to perform action | + +### Sample Request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --request GET \ + https://app.terraform.io/api/v2/policy-set-versions/polsetver-cXciu9nQwmk9Cfrn +``` + +### Sample Response + +```json +{ + "data": { + "id": "polsetver-cXciu9nQwmk9Cfrn", + "type": "policy-set-versions", + "attributes": { + "source": "tfe-api", + "status": "pending", + "status-timestamps": {}, + "error": null, + "created-at": "2019-06-28T23:53:15.875Z", + "updated-at": "2019-06-28T23:53:15.875Z" + }, + "relationships": { + "policy-set": { + "data": { + "id": "polset-ws1CZBzm2h5K6ZT5", + "type": "policy-sets" + } + } + }, + "links": { + "self": "/api/v2/policy-set-versions/polsetver-cXciu9nQwmk9Cfrn", + "upload": "https://archivist.terraform.io/v1/object/dmF1bHQ6djE6NWJPbHQ4QjV4R1ox..." + } + } +} +``` + +The `upload` link URL in the above response is valid for one hour after the `created_at` timestamp of the policy set version. Make a `PUT` request to this URL directly, sending the policy set contents in `tar.gz` format as the request body. Once uploaded successfully, you can request the [Show Policy Set Version](#show-a-policy-set-version) endpoint again to verify that the status has changed from `pending` to `ready`. + +## Available related resources + +The GET endpoints above can optionally return related resources for policy sets, if requested with [the `include` query parameter](/terraform/enterprise/api-docs#inclusion-of-related-resources). The following resource types are available: + +| Resource Name | Description | +| ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `current_version` | The most recent **successful** policy set version. | +| `newest_version` | The most recently created policy set version, regardless of status. Note that this relationship may include an errored and unusable version, and is intended to allow checking for VCS errors. | +| `policies` | Individually managed policies which are associated with the policy set. | +| `projects` | The projects this policy set applies to. | +| `workspaces` | The workspaces this policy set applies to. | +| `workspace-exclusions` | The workspaces excluded from this policy set's enforcement. | + +The following resource types may be included for policy set versions: + +| Resource Name | Description | +| ------------- | ---------------------------------------------------------------- | +| `policy_set` | The policy set associated with the specified policy set version. | + +## Relationships + +The following relationships may be present in various responses for policy sets: + +| Resource Name | Description | +| ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `current-version` | The most recent **successful** policy set version. | +| `newest-version` | The most recently created policy set version, regardless of status. Note that this relationship may include an errored and unusable version, and is intended to allow checking for VCS errors. | +| `organization` | The organization associated with the specified policy set. | +| `policies` | Individually managed policies which are associated with the policy set. | +| `projects` | The projects this policy set applies to. | +| `workspaces` | The workspaces this policy set applies to. | +| `workspace-exclusions` | The workspaces excluded from this policy set's enforcement. | + +The following relationships may be present in various responses for policy set versions: + +| Resource Name | Description | +| ------------- | ---------------------------------------------------------------- | +| `policy-set` | The policy set associated with the specified policy set version. | diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/private-registry/gpg-keys.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/private-registry/gpg-keys.mdx new file mode 100644 index 0000000000..3386ea42c3 --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/private-registry/gpg-keys.mdx @@ -0,0 +1,388 @@ +--- +page_title: /gpg-keys API reference for Terraform Enterprise +description: >- + Use the Terraform Enterprise API's `/gpg-keys` endpoint to read, add, get, + update, and delete the GPG keys that HCP Terraform uses to sign private + providers. +source: terraform-docs-common +--- + +[200]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/200 + +[201]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/201 + +[202]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/202 + +[204]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/204 + +[400]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/400 + +[401]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/401 + +[403]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/403 + +[404]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404 + +[409]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/409 + +[412]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/412 + +[422]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/422 + +[429]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/429 + +[500]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500 + +[504]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/504 + +[JSON API document]: /terraform/enterprise/api-docs#json-api-documents + +[JSON API error object]: https://jsonapi.org/format/#error-objects + +# GPG keys API reference + +These endpoints are only relevant to private providers. When you [publish a private provider](/terraform/enterprise/registry/publish-providers) to the HCP Terraform private registry, you must upload the public key of the GPG key-pair that you used to sign the release. The HCP Terraform registry supports RSA or DSA formatted GPG keys. Refer to [Preparing and adding a signing key](/terraform/registry/providers/publishing#preparing-and-adding-a-signing-key) for more details. + +You need [owners team](/terraform/enterprise/users-teams-organizations/permissions#organization-owners) or [Manage Private Registry](/terraform/enterprise/users-teams-organizations/permissions#manage-private-registry) permissions to add, update, or delete GPG keys in a private registry. + +## List GPG Keys + +`GET /api/registry/:registry_name/v2/gpg-keys` + +### Parameters + +| Parameter | Description | +| ---------------- | ------------------ | +| `:registry_name` | Must be `private`. | + +### Query Parameters + +This endpoint supports pagination [with standard URL query parameters](/terraform/enterprise/api-docs#query-parameters). Remember to percent-encode `[` as `%5B` and `]` as `%5D` if your tooling does not automatically encode URLs. + +| Parameter | Description | +| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `filter[namespace]` | **Required.** A comma-separated list of one or more namespaces. The namespaces must be an authorized HCP Terraform or Terraform Enterprise organization name. | +| `page[number]` | **Optional.** If omitted, the endpoint returns the first page. | +| `page[size]` | **Optional.** If omitted, the endpoint returns 20 GPG keys per page. | + +Gets a list of GPG keys belonging to the specified namespaces. + +| Status | Response | Reason | +| ------- | ------------------------------------------ | --------------------------------------------------------- | +| [200][] | [JSON API document][] (`type: "gpg-keys"`) | Successfully fetched GPG keys | +| [400][] | [JSON API error object][] | Error - missing namespaces in request | +| [403][] | [JSON API error object][] | Forbidden - no authorized namespaces specified in request | + +### Sample Request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request GET \ + "https://app.terraform.io/api/registry/private/v2/gpg-keys?filter%5Bnamespace%5D=my-organization,my-other-organization" +``` + +### Sample Response + +```json +{ + "data": [ + { + "type": "gpg-keys", + "id": "1", + "attributes": { + "ascii-armor": "-----BEGIN PGP PUBLIC KEY BLOCK-----...", + "created-at": "2022-02-08T19:15:47Z", + "key-id": "C4E5E6C66C79C778", + "namespace": "my-other-organization", + "source": "", + "source-url": null, + "trust-signature": "", + "updated-at": "2022-02-08T19:15:47Z" + }, + "links": { + "self": "/v2/gpg-keys/1" + } + }, + { + "type": "gpg-keys", + "id": "140", + "attributes": { + "ascii-armor": "-----BEGIN PGP PUBLIC KEY BLOCK-----...", + "created-at": "2022-04-28T21:32:11Z", + "key-id": "C4E5E6C66C79C778", + "namespace": "my-organization", + "source": "", + "source-url": null, + "trust-signature": "", + "updated-at": "2022-04-28T21:32:11Z" + }, + "links": { + "self": "/v2/gpg-keys/140" + } + } + ], + "links": { + "first": "/v2/gpg-keys?filter%5Bnamespace%5D=my-organization%2Cmy-other-organization&page%5Bnumber%5D=1&page%5Bsize%5D=15", + "last": "/v2/gpg-keys?filter%5Bnamespace%5D=my-organization%2Cmy-other-organization&page%5Bnumber%5D=1&page%5Bsize%5D=15", + "next": null, + "prev": null + }, + "meta": { + "pagination": { + "page-size": 15, + "current-page": 1, + "next-page": null, + "prev-page": null, + "total-pages": 1, + "total-count": 2 + } + } +} +``` + +## Add a GPG Key + +`POST /api/registry/:registry_name/v2/gpg-keys` + +### Parameters + +| Parameter | Description | +| ---------------- | ------------------ | +| `:registry_name` | Must be `private`. | + +Uploads a GPG Key to a private registry scoped with a namespace. The response will provide a "key-id", which is required to [Create a Provider Version](/terraform/enterprise/api-docs/private-registry/provider-versions-platforms#create-a-provider-version). + +| Status | Response | Reason | +| ------- | ------------------------------------------ | -------------------------------------------------------------- | +| [201][] | [JSON API document][] (`type: "gpg-keys"`) | Successfully uploads a GPG key to a private provider | +| [422][] | [JSON API error object][] | Malformed request body (missing attributes, wrong types, etc.) | +| [403][] | [JSON API error object][] | Forbidden - not available for public providers | +| [404][] | [JSON API error object][] | User not authorized | + +### Request Body + +This POST endpoint requires a JSON object with the following properties as a request payload. + +Properties without a default value are required. + +| Key path | Type | Default | Description | +| ----------------------------- | ------ | ------- | -------------------------------------------------------------------------------------------- | +| `data.type` | string | | Must be `"gpg-keys"`. | +| `data.attributes.namespace` | string | | The namespace of the provider. Must be the same as the `organization_name` for the provider. | +| `data.attributes.ascii-armor` | string | | A valid gpg-key string. | + +### Sample Payload + +```json +{ + "data": { + "type": "gpg-keys", + "attributes": { + "namespace": "hashicorp", + "ascii-armor": "-----BEGIN PGP PUBLIC KEY BLOCK-----\n\nmQINB...=txfz\n-----END PGP PUBLIC KEY BLOCK-----\n" + } } +} +``` + +### Sample Request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request POST \ + --data @payload.json \ + https://app.terraform.io/api/registry/private/v2/gpg-keys +``` + +### Sample Response + +```json +{ + "data": { + "type": "gpg-keys", + "id": "23", + "attributes": { + "ascii-armor": "-----BEGIN PGP PUBLIC KEY BLOCK-----\n\nmQINB...=txfz\n-----END PGP PUBLIC KEY BLOCK-----\n", + "created-at": "2022-02-11T19:16:59Z", + "key-id": "32966F3FB5AC1129", + "namespace": "hashicorp", + "source": "", + "source-url": null, + "trust-signature": "", + "updated-at": "2022-02-11T19:16:59Z" + }, + "links": { + "self": "/v2/gpg-keys/23" + } + } +} +``` + +## Get GPG Key + +`GET /api/registry/:registry_name/v2/gpg-keys/:namespace/:key_id` + +### Parameters + +| Parameter | Description | +| ---------------- | ---------------------------------------------------- | +| `:registry_name` | Must be `private`. | +| `:namespace` | The namespace of the provider scoped to the GPG key. | +| `:key_id` | The id of the GPG key. | + +Gets the content of a GPG key. + +| Status | Response | Reason | +| ------- | ------------------------------------------ | ---------------------------------------------- | +| [200][] | [JSON API document][] (`type: "gpg-keys"`) | Successfully fetched GPG key | +| [403][] | [JSON API error object][] | Forbidden - not available for public providers | +| [404][] | [JSON API error object][] | GPG key not found or user not authorized | + +### Sample Request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request GET \ + https://app.terraform.io/api/registry/private/v2/gpg-keys/hashicorp/32966F3FB5AC1129 +``` + +### Sample Response + +```json +{ + "data": { + "type": "gpg-keys", + "id": "2", + "attributes": { + "ascii-armor": "-----BEGIN PGP PUBLIC KEY BLOCK-----\n\nmQINB...=txfz\n-----END PGP PUBLIC KEY BLOCK-----\n", + "created-at": "2022-02-24T17:07:25Z", + "key-id": "32966F3FB5AC1129", + "namespace": "hashicorp", + "source": "", + "source-url": null, + "trust-signature": "", + "updated-at": "2022-02-24T17:07:25Z" + }, + "links": { + "self": "/v2/gpg-keys/2" + } + } +} +``` + +## Update a GPG Key + +`PATCH /api/registry/:registry_name/v2/gpg-keys/:namespace/:key_id` + +### Parameters + +| Parameter | Description | +| ---------------- | ---------------------------------------------------- | +| `:registry_name` | Must be `private`. | +| `:namespace` | The namespace of the provider scoped to the GPG key. | +| `:key_id` | The id of the GPG key. | + +Updates the specified GPG key. Only the `namespace` attribute can be updated, and `namespace` has to match an `organization` the user has permission to access. + +| Status | Response | Reason | +| ------- | ------------------------------------------ | -------------------------------------------------------------- | +| [201][] | [JSON API document][] (`type: "gpg-keys"`) | Successfully updates a GPG key | +| [422][] | [JSON API error object][] | Malformed request body (missing attributes, wrong types, etc.) | +| [403][] | [JSON API error object][] | Forbidden - not available for public providers | +| [404][] | [JSON API error object][] | GPG key not found or user not authorized | + +### Request Body + +This PATCH endpoint requires a JSON object with the following properties as a request payload. + +Properties without a default value are required. + +| Key path | Type | Default | Description | +| --------------------------- | ------ | ------- | -------------------------------------------------------------------------------------------- | +| `data.type` | string | | Must be `"gpg-keys"`. | +| `data.attributes.namespace` | string | | The namespace of the provider. Must be the same as the `organization_name` for the provider. | + +### Sample Payload + +```json +{ + "data": { + "type": "gpg-keys", + "attributes": { + "namespace": "new-namespace", + } + } +} +``` + +### Sample Request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request PATCH \ + --data @payload.json \ + https://app.terraform.io/api/registry/private/v2/gpg-keys/hashicorp/32966F3FB5AC1129 +``` + +### Sample Response + +```json +{ + "data": { + "type": "gpg-keys", + "id": "2", + "attributes": { + "ascii-armor": "-----BEGIN PGP PUBLIC KEY BLOCK-----\n\nmQINB...=txfz\n-----END PGP PUBLIC KEY BLOCK-----\n", + "created-at": "2022-02-24T17:07:25Z", + "key-id": "32966F3FB5AC1129", + "namespace": "new-name", + "source": "", + "source-url": null, + "trust-signature": "", + "updated-at": "2022-02-24T17:12:10Z" + }, + "links": { + "self": "/v2/gpg-keys/2" + } + } +} +``` + +## Delete a GPG Key + +`DELETE /api/registry/:registry_name/v2/gpg-keys/:namespace/:key_id` + +### Parameters + +| Parameter | Description | +| ---------------- | ---------------------------------------------------- | +| `:registry_name` | Must be `private`. | +| `:namespace` | The namespace of the provider scoped to the GPG key. | +| `:key_id` | The id of the GPG key. | + +[permissions-citation]: #intentionally-unused---keep-for-maintainers + +| Status | Response | Reason | +| ------- | ------------------------------------------ | -------------------------------------------------------------- | +| [201][] | [JSON API document][] (`type: "gpg-keys"`) | Successfully deletes a GPG key | +| [422][] | [JSON API error object][] | Malformed request body (missing attributes, wrong types, etc.) | +| [403][] | [JSON API error object][] | Forbidden - not available for public providers | +| [404][] | [JSON API error object][] | GPG key not found or user not authorized | + +### Sample Request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request DELETE \ + --data @payload.json \ + https://app.terraform.io/api/registry/private/v2/gpg-keys/hashicorp/32966F3FB5AC1129 +``` diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/private-registry/manage-module-versions.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/private-registry/manage-module-versions.mdx new file mode 100644 index 0000000000..7fe7e2a61b --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/private-registry/manage-module-versions.mdx @@ -0,0 +1,531 @@ +--- +page_title: Manage module versions API reference for Terraform Enterprise +description: >- + Use these module management endpoints to deprecate, revoke, and revert the + status of module versions you published to an organization's private registry. +source: terraform-docs-common +--- + +[200]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/200 + +[201]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/201 + +[202]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/202 + +[204]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/204 + +[400]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/400 + +[401]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/401 + +[403]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/403 + +[404]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404 + +[409]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/409 + +[412]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/412 + +[422]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/422 + +[429]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/429 + +[500]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500 + +[503]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/503 + +[JSON API document]: /terraform/enterprise/api-docs#json-api-documents + +[JSON API error object]: https://jsonapi.org/format/#error-objects + +# Manage module versions API reference + +Use the module version management API endpoints to deprecate, revoke, and revert of status of module versions in your organization’s private registry. + +<>{/* TODO: remove revoke references here */} + + + +@include "tfc-package-callouts/manage-module-versions.mdx" + + + +## Overview + +<>{/* TODO: remove revoke references here */} + +As part of the module lifecycle, you can deprecate or revoke older module versions as you stop supporting them, signaling to module consumers that they need to upgrade to newer versions. + +When you deprecate a module version, HCP Terraform adds warnings to the module's registry page and to run outputs when anyone uses the deprecated version. After deprecating a module version, you can revert that deprecated status to remove the warnings from that version in the registry and outputs. + + + +Revoking a module version adds warnings to the module's registry page, warnings in the run outputs of existing users, and blocks new users from using that version. Reverting a module version’s revocation sets the module version back to a deprecated state. + + + +For more details on deprecating or revoking module versions, refer to [Manage module versions](/terraform/enterprise/registry/manage-module-versions). + +## Deprecate a module version + +Use this endpoint to deprecate a module version. + +`PATCH /api/v2/organizations/:organization_name/registry-modules/private/:organization_name/:module_name/:module_provider/:module_version` + +| Parameter | Description | +| :------------------- | :---------------------------------------------------------- | +| `:organization_name` | The name of the organization the module belongs to. | +| `:module_name` | The name of the module whose version you want to deprecate. | +| `:module_provider` | Specifies the Terraform provider that this module uses. | +| `:module_version` | The module version you want to deprecate. | + +This endpoint allows you to deprecate a specific module version. Deprecating a module version adds warnings to the run output of any consumers using this module. + +| Status | Response | Reason | +| :----------------------------------------------------------------------------------------------------------------------------------------- | :--------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------- | +| [200](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/200) | [JSON API document](http://terraform/cloud-docs/api-docs#json-api-documents) | Successfully deprecated a module version. | +| [404](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404) | [JSON API error object](http://jsonapi.org/format/#error-objects) | This organization is not authorized to deprecate this module version, or the module version does not exist. | +| [422](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/422) | [JSON API error object](http://jsonapi.org/format/#error-objects) | Malformed request body, for example the request is missing attributes or uses the wrong types. | +| [500](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500) or [503](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/503) | [JSON API error object](http://jsonapi.org/format/#error-objects) | Failure occurred while deprecating a module version. | + +### Sample payload + +```json +{ + "data": { + "type": "module-versions", + "attributes": { + "deprecation": { + "deprecated-status": "Deprecated", + "reason": "Deprecated due to a security vulnerability issue.", + "link": "https://www.hashicorp.com/" + } + } + } +} +``` + +### Sample request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request PATCH \ + --data @payload.json \ +https://app.terraform.io/api/v2/organizations/hashicorp/registry-modules/private/hashicorp/lb-http/google/11.0.0 +``` + +### Sample response + +```json +{ + "data": { + "type": "module-versions", + "id": "1", + "relationships": { + "deprecation": { + "data": { + "id": "2", + "type": "deprecations" + } + } + } + }, + "included": [ + { + "type": "deprecations", + "id": "2", + "attributes": { + "link": "https://www.hashicorp.com/", + "reason": "Deprecated due to a security vulnerability issue. Applies will be blocked in 15 days." + } + } + ] +} +``` + +## Revert the deprecation status for a module version + +Use this endpoint to revert the deprecation of a module version. + +`PATCH /api/v2/organizations/:organization_name/registry-modules/private/:organization_name/:module_name/:module_provider/:module_version` + +| Parameter | Description | +| :------------------- | :------------------------------------------------------------ | +| `:organization_name` | The name of the organization the module belongs to. | +| `:module_name` | The name of the module you want to revert the deprecation of. | +| `:module_provider` | Specifies the Terraform provider that this module uses. | +| `:module_version` | The module version you want to revert the deprecation of. | + +Deprecating a module version adds warnings to the run output of any consumers using this module. Reverting the deprecation status removes warnings from the output of consumers and fully reinstates the module version. + +| Status | Response | Reason | +| :--------------------------------------------------------------------------- | :---------------------------------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------- | +| [200](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/200) | [JSON API document](http:///terraform/cloud-docs/api-docs#json-api-documents) | Successfully reverted a module version’s deprecation status and reinstated that version. | +| [404](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404) | [JSON API error object](http://jsonapi.org/format/#error-objects) | This organization is not authorized to revert the depreciation of this module version, or the module version does not exist. | +| [422](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/422) | [JSON API error object](http://jsonapi.org/format/#error-objects) | Malformed request body, for example the request is missing attributes or uses the wrong types. | +| [500](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500) or [503] | [JSON API error object](http://jsonapi.org/format/#error-objects) | Failure occurred while reverting the deprecation of a module version. | + +### Sample request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request PATCH \ + --data @payload.json \ +https://app.terraform.io/api/v2/organizations/hashicorp/registry-modules/private/hashicorp/lb-http/google/11.0.0 +``` + +### Sample payload + +```json +{ + "data": { + "type": "module-versions", + "attributes": { + "deprecation": { + "deprecated-status": "Undeprecated" + } + } + } +} +``` + +### Sample response + +```json +{ + "data": { + "type": "module-versions", + "id": "1" + } +} +``` + +## Fetch a module version’s deprecation data + +Send a `GET` request to the `/modules/:github-organization/:module/:provider/versions` endpoint to retrieve data about private registry modules, including the module's deprecation status. Refer to the [private registry module API example](/terraform/enterprise/api-docs/private-registry/modules#sample-registry-request-private-module) for additional information. + +For example, if you want to know the deprecation status of v0.0.1 of the `aws` provider’s `consul` module in your `my-cloud-org` organization, you could perform the following API call: + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + https://app.terraform.io/api/registry/v1/modules/my-cloud-org/consul/aws/0.0.1 +``` + +If the module is deprecated, your response includes a `deprecation` key with the details of that module version’s deprecation. + +```json +{ + "id": "hashicorp/consul/aws/0.0.1", + "owner": "gruntwork-team", + "namespace": "hashicorp", + "name": "consul", + "version": "0.0.1", + "provider": "aws", + "description": "A Terraform Module for how to run Consul on AWS using Terraform and Packer" + // ... // + "deprecation": { + "reason": "This version was deprecated due to a vulnerability issue. Please upgrade to 0.0.2.", + "link": "https://hashicorp.com" + } +} +``` + +To check the deprecation status of all of the `consul` module’s versions, you could perform the following API call: + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + https://app.terraform.io/api/registry/v1/modules/my-cloud-org/consul/aws/versions +``` + +The response includes multiple versions, and each version has a `deprecation` key listing the details of that module’s deprecation. If a module version has not been deprecated, the `deprecation` field returns `null`. + +```json +{ + "modules": [ + { + "source": "hashicorp/consul/aws", + "versions": [ + { + "version": "0.0.1", + // ... // + "deprecation": { + "reason": "security vulnerability", + "link": "www.hashicorp.com" + } + }, + { + "version": "0.0.2", + "submodules": [], + "root": { + "dependencies": [], + "providers": [ + { + "name": "template", + "version": "" + }, + { + "name": "aws", + "version": "" + } + ] + }, + "deprecation": null + } + ] + } + ] +} +``` + + + +## Revoke a module version + +Use this endpoint to revoke a module version. You must [deprecate a module version](#deprecate-a-module-version) before you can revoke it. + +`PATCH /api/v2/organizations/:organization_name/registry-modules/private/:organization_name/:module_name/:module_provider/:module_version` + +| Parameter | Description | +| :------------------- | :------------------------------------------------------- | +| `:organization_name` | The name of the organization the module belongs to. | +| `:module_name` | The name of the module whose version you want to revoke. | +| `:module_provider` | Specifies the Terraform provider that this module uses. | +| `:module_version` | The module version you want to revoke. | + +This endpoint allows you to revoke a specific module version. Revoking a module version adds warnings to the run output of current consumers of this version and blocks new users from trying to use that version. + +| Status | Response | Reason | +| :----------------------------------------------------------------------------------------------------------------------------------------- | :--------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------- | +| [200](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/200) | [JSON API document](http://terraform/cloud-docs/api-docs#json-api-documents) | Successfully revoked a module version. | +| [404](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404) | [JSON API error object](http://jsonapi.org/format/#error-objects) | This organization is not authorized to revoke this module version, or the module version does not exist. | +| [422](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/422) | [JSON API error object](http://jsonapi.org/format/#error-objects) | Malformed request body, for example the request is missing attributes or uses the wrong types. | +| [500](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500) or [503](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/503) | [JSON API error object](http://jsonapi.org/format/#error-objects) | Failure occurred while revoking a module version. | + +### Sample Payload + +```json +{ + "data": { + "type": "module-versions", + "attributes": { + "revocation": { + "status": "Revoked", + "message": "Revoked due to a security vulnerability issue.", + "link": "https://www.hashicorp.com/" + } + } + } +} +``` + +### Sample Request + +```shell-session +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request PATCH \ + --data @payload.json \ +https://app.terraform.io/api/v2/organizations/hashicorp/registry-modules/private/hashicorp/lb-http/google/11.0.0 +``` + +### Sample Response + +```json +{ + "data": { + "id": "modver-uKQCVs5vAKDmUMyw", + "type": "registry-module-versions", + "attributes": { + "source": "tfe-api", + "status": "ok", + "version": "11.0.0", + "commit-sha": null, + "branch": null, + "created-at": "2024-08-28T20:48:05.206Z", + "updated-at": "2024-08-28T20:48:09.175Z", + "revocation": { + "status": "Revoked", + "message": "Revoked due to a security vulnerability issue.", + "link": "https://www.hashicorp.com/" + } + }, + "relationships": { + "registry-module": { + "data": { + "id": "mod-yDKMcmJYx9oFrGao", + "type": "registry-modules" + } + } + }, + "links": { + "upload": "https://app.terraform.io/_archivist/v1/object/dmF1bHQ6djE6eElWekF3RH" + } + } +} +``` + +## Revert the revocation status for a module version + +Use this endpoint to revert the revocation of a module version. + +`PATCH /api/v2/organizations/:organization_name/registry-modules/private/:organization_name/:module_name/:module_provider/:module_version` + +| Parameter | Description | +| :------------------- | :----------------------------------------------------------- | +| `:organization_name` | The name of the organization the module belongs to. | +| `:module_name` | The name of the module you want to revert the revocation of. | +| `:module_provider` | Specifies the Terraform provider that this module uses. | +| `:module_version` | The module version you want to revert the revocation of. | + +When you revert the revocation of a module version, HCP Terraform sets that version as deprecated, signaling that it is still maintained and supported but not recommended. Deprecated module versions produce warnings in the registry and run outputs, but new users can still use them. + +| Status | Response | Reason | +| :--------------------------------------------------------------------------- | :---------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------- | +| [200](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/200) | [JSON API document](http:///terraform/cloud-docs/api-docs#json-api-documents) | Successfully reverted a module version’s revocation status and deprecated that version. | +| [404](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404) | [JSON API error object](http://jsonapi.org/format/#error-objects) | This organization is not authorized to revert the revocation of this module version, or the module version does not exist. | +| [422](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/422) | [JSON API error object](http://jsonapi.org/format/#error-objects) | Malformed request body, for example the request is missing attributes or uses the wrong types. | +| [500](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500) or [503] | [JSON API error object](http://jsonapi.org/format/#error-objects) | Failure occurred while reverting the revocation of a module version. | + +### Sample Request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request PATCH \ + --data @payload.json \ +https://app.terraform.io/api/v2/organizations/hashicorp/registry-modules/private/hashicorp/lb-http/google/11.0.0 +``` + +### Sample payload + +```json +{ + "data": { + "type": "module-versions", + "attributes": { + "revocation": { + "status": "Unrevoked" + } + } + } +} +``` + +### Sample Response + +```json +{ + "data": { + "id": "modver-uKQCVs5vAKDmUMyw", + "type": "registry-module-versions", + "attributes": { + "source": "tfe-api", + "status": "ok", + "version": "11.0.0", + "commit-sha": null, + "branch": null, + "created-at": "2024-08-28T20:48:05.206Z", + "updated-at": "2024-08-28T20:48:09.175Z", + "revocation": { + "status": "Unrevoked", + } + }, + "relationships": { + "registry-module": { + "data": { + "id": "mod-yDKMcmJYx9oFrGao", + "type": "registry-modules" + } + } + }, + "links": { + "upload": "https://app.terraform.io/_archivist/v1/object/dmF1bHQ6djE6eElWekF3RH" + } + } +} +``` + +## Fetch data about a module version’s revocation + +Send a `GET` request to the `/organizations/:organization_name/registry-modules/:registry_name/:namespace/:name/:provider` endpoint to retrieve data about private registry modules, including the module's revocation status. Refer to the [private registry module API example](/terraform/enterprise/api-docs/private-registry/modules#get-a-module) for additional information. + +For example, if you want to know the revocation status of v0.0.1 of the `aws` provider’s `consul` module in your `my-cloud-org` organization, you could perform the following API call: + +```shell-session +curl \ + --header "Authorization: Bearer $TOKEN" \ + https://app.terraform.io/api/v2/organizations/my-cloud-org/registry-modules/private/my-cloud-org/consul/aws/version?module_version=1.0.0 +``` + +If the module is revoked, your response includes a `revocation` key with the details of that module version’s revocation. + +```json +{ + "data": { + "id": "modver-3tKAXyRnpGYCxW7d", + "type": "registry-module-versions", + "attributes": { + "source": "github", + "status": "ok", + "version": "1.0.0", + "commit-sha": "ea18e73d8da502870fdcbfc54155046bc0cf2679", + "branch": null, + "created-at": "2024-10-11T19:53:35.082Z", + "updated-at": "2024-10-11T19:53:38.782Z", + "revocation": { + "status": "revoked", + "message": "please upgrade to version 1.0.1", + "link": "https://github.com" + } + }, + "relationships": { + "registry-module": { + "data": { + "id": "mod-u8BFFruSTSz8dN4C", + "type": "registry-modules" + } + } + }, + "links": { + "upload": "https://archivist.terraform.io/v1/object/dmF1bHQ6djQ6V1pZWWwra1" + } + } +} +``` + +To check the revocation status of all of the `consul` module’s versions, you could perform the following API call: + +```shell-session +curl \ + --header "Authorization: Bearer $TOKEN" \ + https://app.terraform.io/api/v2/organizations/my-cloud-org/registry-modules/private/my-cloud-org/consul/aws +``` + +The response includes the revoked versions of this module in the `revoked-versions` key. + +```json +{ + "data": { + "id": "mod-u8BFFruSTSz8dN4C", + "type": "registry-modules", + "attributes": { + "name": "module", + "namespace": "my-cloud-org", + // ... // + "revoked-versions": [ + "1.0.0" + ], + // ... // + }, +} +``` + + diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/private-registry/modules.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/private-registry/modules.mdx new file mode 100644 index 0000000000..d5df734ece --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/private-registry/modules.mdx @@ -0,0 +1,942 @@ +--- +page_title: /registry-modules API reference for Terraform Enterprise +description: >- + Use the Terraform Enterprise API's `/registry-modules` endpoint to read, + publish, update, delete, and add versions to modules in your organization's + private registry. +source: terraform-docs-common +--- + +[200]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/200 + +[201]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/201 + +[202]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/202 + +[204]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/204 + +[400]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/400 + +[401]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/401 + +[403]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/403 + +[404]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404 + +[409]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/409 + +[412]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/412 + +[422]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/422 + +[429]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/429 + +[500]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500 + +[504]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/504 + +[JSON API document]: /terraform/enterprise/api-docs#json-api-documents + +[JSON API error object]: https://jsonapi.org/format/#error-objects + +# Registry modules API reference + +-> **Note:** Public Module Curation is only available in HCP Terraform. Where applicable, the `registry_name` parameter must be `private` for Terraform Enterprise. + +## HCP Terraform Registry Implementation + +The HCP Terraform Module Registry implements the [Registry standard API](/terraform/registry/api-docs) for consuming/exposing private modules. Refer to the [Module Registry HTTP API](/terraform/registry/api-docs) to perform the following: + +- Browse available modules +- Search modules by keyword +- List available versions for a specific module +- Download source code for a specific module version +- List latest version of a module for all providers +- Get the latest version for a specific module provider +- Get a specific module +- Download the latest version of a module + +For publicly curated modules, the HCP Terraform Module Registry acts as a proxy to the [Terraform Registry](https://registry.terraform.io) for the following: + +- List available versions for a specific module +- Get a specific module +- Get the latest version for a specific module provider + +The HCP Terraform Module Registry endpoints differs from the Module Registry endpoints in the following ways: + +- The `:namespace` parameter should be replaced with the organization name for private modules. +- The private module registry discovery endpoints have the path prefix provided in the [discovery document](/terraform/registry/api-docs#service-discovery) which is currently `/api/registry/v1`. +- The public module registry discovery endpoints have the path prefix provided in the [discovery document](/terraform/registry/api-docs#service-discovery) which is currently `/api/registry/public/v1`. +- [Authentication](/terraform/enterprise/api-docs#authentication) is handled the same as all other HCP Terraform endpoints. + +### Sample Registry Request (private module) + +List available versions for the `consul` module for the `aws` provider on the module registry published from the Github organization `my-gh-repo-org`: + +```shell +$ curl https://registry.terraform.io/v1/modules/my-gh-repo-org/consul/aws/versions +``` + +The same request for the same module and provider on the HCP Terraform module registry for the `my-cloud-org` organization: + +```shell +$ curl \ + --header "Authorization: Bearer $TOKEN" \ + https://app.terraform.io/api/registry/v1/modules/my-cloud-org/consul/aws/versions +``` + +### Sample Proxy Request (public module) + +List available versions for the `consul` module for the `aws` provider on the module registry published from the Github organization `my-gh-repo-org`: + +```shell +$ curl https://registry.terraform.io/v1/modules/my-gh-repo-org/consul/aws/versions +``` + +The same request for the same module and provider on the HCP Terraform module registry: + +```shell +$ curl \ + --header "Authorization: Bearer $TOKEN" \ + https://app.terraform.io/api/registry/public/v1/modules/my-gh-repo-org/consul/aws/versions +``` + +## List Registry Modules for an Organization + +`GET /organizations/:organization_name/registry-modules` + +| Parameter | Description | +| -------------------- | ------------------------------------------------------------ | +| `:organization_name` | The name of the organization to list available modules from. | + +Lists the modules that are available to a given organization. This includes the full list of publicly curated and private modules and is filterable. + +| Status | Response | Reason | +| ------- | -------------------------------------------------- | -------------------------------------------------------- | +| [200][] | [JSON API document][] (`type: "registry-modules"`) | The request was successful | +| [404][] | [JSON API error object][] | Modules not found or user unauthorized to perform action | + +### Query Parameters + +This endpoint supports pagination [with standard URL query parameters](/terraform/enterprise/api-docs#query-parameters). Remember to percent-encode `[` as `%5B` and `]` as `%5D` if your tooling doesn't automatically encode URLs. + +| Parameter | Description | +| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `q` | **Optional.** A search query string. Modules are searchable by name, namespace, provider fields. | +| `filter[field name]` | **Optional.** If specified, restricts results to those with the matching field name value. Valid values are `registry_name`, `provider`, and `organization_name`. | +| `page[number]` | **Optional.** If omitted, the endpoint will return the first page. | +| `page[size]` | **Optional.** If omitted, the endpoint will return 20 registry modules per page. | + +### Sample Request + +```shell +curl \ + --request GET \ + --header "Authorization: Bearer $TOKEN" \ + https://app.terraform.io/api/v2/organizations/my-organization/registry-modules +``` + +### Sample Response + +```json +{ + "data": [ + { + "id": "mod-kwt1cBiX2SdDz38w", + "type": "registry-modules", + "attributes": { + "name": "api-gateway", + "namespace": "my-organization", + "provider": "alicloud", + "status": "setup_complete", + "version-statuses": [ + { + "version": "1.1.0", + "status": "ok" + } + ], + "created-at": "2021-04-07T19:01:18.528Z", + "updated-at": "2021-04-07T19:01:19.863Z", + "registry-name": "private", + "permissions": { + "can-delete": true, + "can-resync": true, + "can-retry": true + } + }, + "relationships": { + "organization": { + "data": { + "id": "my-organization", + "type": "organizations" + } + } + }, + "links": { + "self": "/api/v2/organizations/my-organization/registry-modules/private/my-organization/api-gateway/alicloud" + } + }, + { + "id": "mod-PopQnMtYDCcd3PRX", + "type": "registry-modules", + "attributes": { + "name": "aurora", + "namespace": "my-organization", + "provider": "aws", + "status": "setup_complete", + "version-statuses": [ + { + "version": "4.1.0", + "status": "ok" + } + ], + "created-at": "2021-04-07T19:04:41.375Z", + "updated-at": "2021-04-07T19:04:42.828Z", + "registry-name": "private", + "permissions": { + "can-delete": true, + "can-resync": true, + "can-retry": true + } + }, + "relationships": { + "organization": { + "data": { + "id": "my-organization", + "type": "organizations" + } + } + }, + "links": { + "self": "/api/v2/organizations/my-organization/registry-modules/private/my-organization/aurora/aws" + } + }, + ..., + ], + "links": { + "self": "https://app.terraform.io/api/v2/organizations/my-organization/registry-modules?page%5Bnumber%5D=1&page%5Bsize%5D=6", + "first": "https://app.terraform.io/api/v2/organizations/my-organization/registry-modules?page%5Bnumber%5D=1&page%5Bsize%5D=6", + "prev": null, + "next": "https://app.terraform.io/api/v2/organizations/my-organization/registry-modules?page%5Bnumber%5D=2&page%5Bsize%5D=6", + "last": "https://app.terraform.io/api/v2/organizations/my-organization/registry-modules?page%5Bnumber%5D=29&page%5Bsize%5D=6" + }, + "meta": { + "pagination": { + "current-page": 1, + "page-size": 6, + "prev-page": null, + "next-page": 2, + "total-pages": 29, + "total-count": 169 + } + } +} +``` + +## Publish a Private Module from a VCS + +~> **Deprecation warning**: the following endpoint `POST /registry-modules` is replaced by the below endpoint and will be removed from future versions of the API! + +`POST /organizations/:organization_name/registry-modules/vcs` + +| Parameter | Description | +| -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `:organization_name` | The name of the organization to create a module in. The organization must already exist, and the token authenticating the API request must belong to a team or team member with the **Manage modules** permission enabled. | + +Publishes a new registry private module from a VCS repository, with module versions managed automatically by the repository's tags. The publishing process will fetch all tags in the source repository that look like [SemVer](https://semver.org/) versions with optional 'v' prefix. For each version, the tag is cloned and the config parsed to populate module details (input and output variables, readme, submodules, etc.). The [Module Registry Requirements](/terraform/registry/modules/publish#requirements) define additional requirements on naming, standard module structure and tags for releases. + +| Status | Response | Reason | +| ------- | -------------------------------------------------- | -------------------------------------------------------------- | +| [201][] | [JSON API document][] (`type: "registry-modules"`) | Successfully published module | +| [422][] | [JSON API error object][] | Malformed request body (missing attributes, wrong types, etc.) | +| [404][] | [JSON API error object][] | User not authorized | + +### Request Body + +This POST endpoint requires a JSON object with the following properties as a request payload. + +Properties without a default value are required. + +| Key path | Type | Default | Description | +| ----------------------------------------------------- | ------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `data.type` | string | | Must be `"registry-modules"`. | +| `data.attributes.vcs-repo.identifier` | string | | The repository from which to ingress the configuration. | +| `data.attributes.vcs-repo.oauth-token-id` | string | | The VCS Connection (OAuth Connection + Token) to use as identified. Get this ID from the [oauth-tokens](/terraform/enterprise/api-docs/oauth-tokens) endpoint. You can not specify this value if `github-app-installation-id` is specified. | +| `data.attributes.vcs-repo.github-app-installation-id` | string | | The VCS Connection GitHub App Installation to use. Find this ID on the account settings page. Requires previously authorizing the GitHub App and generating a user-to-server token. Manage the token from **Account Settings** within HCP Terraform. You can not specify this value if `oauth-token-id` is specified. | +| `data.attributes.vcs-repo.display_identifier` | string | | The display identifier for the repository. For most VCS providers outside of Bitbucket Cloud, this identifier matches the `data.attributes.vcs-repo.identifier` string. | +| `data.attributes.no-code` | boolean | | Allows you to enable or disable the no-code publishing workflow for a module. | +| `data.attributes.vcs-repo.branch` | string | | The repository branch to publish the module from if you are using the branch-based publishing workflow. If omitted, the module will be published using the tag-based publishing workflow. | + +A VCS repository identifier is a reference to a VCS repository in the format `:org/:repo`, where `:org` and `:repo` refer to the organization, or project key for Bitbucket Data Center, and repository in your VCS provider. The format for Azure DevOps is `:org/:project/_git/:repo`. + +The OAuth Token ID identifies the VCS connection, and therefore the organization, that the module will be created in. + +### Sample Payload + +```json +{ + "data": { + "attributes": { + "vcs-repo": { + "identifier":"lafentres/terraform-aws-my-module", + "oauth-token-id":"ot-hmAyP66qk2AMVdbJ", + "display_identifier":"lafentres/terraform-aws-my-module", + "branch": "main" + }, + "no-code": true + }, + "type":"registry-modules" + } +} +``` + +### Sample Request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request POST \ + --data @payload.json \ + https://app.terraform.io/api/v2/organizations/my-organization/registry-modules/vcs +``` + +### Sample Response + +```json +{ + "data": { + "id": "mod-fZn7uHu99ZCpAKZJ", + "type": "registry-modules", + "attributes": { + "name": "my-module", + "namespace": "my-organization", + "registry-name": "private", + "provider": "aws", + "status": "pending", + "version-statuses": [], + "created-at": "2020-07-09T19:36:56.288Z", + "updated-at": "2020-07-09T19:36:56.288Z", + "vcs-repo": { + "branch": "", + "ingress-submodules": true, + "identifier": "lafentres/terraform-aws-my-module", + "display-identifier": "lafentres/terraform-aws-my-module", + "oauth-token-id": "ot-hmAyP66qk2AMVdbJ", + "webhook-url": "https://app.terraform.io/webhooks/vcs/a12b3456..." + }, + "permissions": { + "can-delete": true, + "can-resync": true, + "can-retry": true + } + }, + "relationships": { + "organization": { + "data": { + "id": "my-organization", + "type": "organizations" + } + } + }, + "links": { + "self": "/api/v2/organizations/my-organization/registry-modules/private/my-organization/my-module/aws" + } + } +} +``` + +## Create a Module (with no VCS connection) + +`POST /organizations/:organization_name/registry-modules` + +| Parameter | Description | +| -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `:organization_name` | The name of the organization to create a module in. The organization must already exist, and the token authenticating the API request must belong to a team or team member with the **Manage modules** permission enabled. | + +[permissions-citation]: #intentionally-unused---keep-for-maintainers + +Creates a new registry module without a backing VCS repository. + +#### Private modules + +After creating a module, a version must be created and uploaded in order to be usable. Modules created this way do not automatically update with new versions; instead, you must explicitly create and upload each new version with the [Create a Module Version](#create-a-module-version) endpoint. + +#### Public modules + +When created, the public module record will be available in the organization's registry module list. You cannot create versions for public modules as they are maintained in the public registry. + +| Status | Response | Reason | +| ------- | -------------------------------------------------- | -------------------------------------------------------------- | +| [201][] | [JSON API document][] (`type: "registry-modules"`) | Successfully published module | +| [422][] | [JSON API error object][] | Malformed request body (missing attributes, wrong types, etc.) | +| [403][] | [JSON API error object][] | Forbidden - public module curation disabled | +| [404][] | [JSON API error object][] | User not authorized | + +### Request Body + +This POST endpoint requires a JSON object with the following properties as a request payload. + +Properties without a default value are required. + +| Key path | Type | Default | Description | +| ------------------------------- | ------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `data.type` | string | | Must be `"registry-modules"`. | +| `data.attributes.name` | string | | The name of this module. May contain alphanumeric characters, with dashes and underscores allowed in non-leading or trailing positions. Maximum length is 64 characters. | +| `data.attributes.provider` | string | | Specifies the Terraform provider that this module is used for. May contain lowercase alphanumeric characters. Maximum length is 64 characters. | +| `data.attributes.namespace` | string | | The namespace of this module. Cannot be set for private modules. May contain alphanumeric characters, with dashes and underscores allowed in non-leading or trailing positions. Maximum length is 64 characters. | +| `data.attributes.registry-name` | string | | Indicates whether this is a publicly maintained module or private. Must be either `public` or `private`. | +| `data.attributes.no-code` | boolean | | Allows you to enable or disable the no-code publishing workflow for a module. | + +### Sample Payload (private module) + +```json +{ + "data": { + "type": "registry-modules", + "attributes": { + "name": "my-module", + "provider": "aws", + "registry-name": "private", + "no-code": true + } + } +} +``` + +### Sample Payload (public module) + +```json +{ + "data": { + "type": "registry-modules", + "attributes": { + "name": "vpc", + "namespace": "terraform-aws-modules", + "provider": "aws", + "registry-name": "public", + "no-code": true + } + } +} +``` + +### Sample Request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request POST \ + --data @payload.json \ + https://app.terraform.io/api/v2/organizations/my-organization/registry-modules +``` + +### Sample Response (private module) + +```json +{ + "data": { + "id": "mod-fZn7uHu99ZCpAKZJ", + "type": "registry-modules", + "attributes": { + "name": "my-module", + "namespace": "my-organization", + "registry-name": "private", + "provider": "aws", + "status": "pending", + "version-statuses": [], + "created-at": "2020-07-09T19:36:56.288Z", + "updated-at": "2020-07-09T19:36:56.288Z", + "permissions": { + "can-delete": true, + "can-resync": true, + "can-retry": true + } + }, + "relationships": { + "organization": { + "data": { + "id": "my-organization", + "type": "organizations" + } + } + }, + "links": { + "self": "/api/v2/organizations/my-organization/registry-modules/private/my-organization/my-module/aws" + } + } +} +``` + +### Sample Response (public module) + +```json +{ + "data": { + "id": "mod-fZn7uHu99ZCpAKZJ", + "type": "registry-modules", + "attributes": { + "name": "vpc", + "namespace": "terraform-aws-modules", + "registry-name": "public", + "provider": "aws", + "status": "pending", + "version-statuses": [], + "created-at": "2020-07-09T19:36:56.288Z", + "updated-at": "2020-07-09T19:36:56.288Z", + "permissions": { + "can-delete": true, + "can-resync": true, + "can-retry": true + } + }, + "relationships": { + "organization": { + "data": { + "id": "my-organization", + "type": "organizations" + } + } + }, + "links": { + "self": "/api/v2/organizations/my-organization/registry-modules/public/terraform-aws-modules/vpc/aws" + } + } +} +``` + +## Create a Module Version + +~> **Deprecation warning**: the following endpoint `POST /registry-modules/:organization_name/:name/:provider/versions` is replaced by the below endpoint and will be removed from future versions of the API! + +`POST /organizations/:organization_name/registry-modules/:registry_name/:namespace/:name/:provider/versions` + +| Parameter | Description | +| -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `:organization_name` | The name of the organization to create a module in. The organization must already exist, and the token authenticating the API request must belong to a team or team member with the **Manage modules** permission enabled. | +| `:namespace` | The namespace of the module for which the version is being created. For private modules this is the same as the `:organization_name` parameter | +| `:name` | The name of the module for which the version is being created. | +| `:provider` | The name of the provider for which the version is being created. | +| `:registry-name` | Must be `private`. | + +[permissions-citation]: #intentionally-unused---keep-for-maintainers + +Creates a new registry module version. This endpoint only applies to private modules without a VCS repository and VCS-linked branch based modules. VCS-linked tag-based modules automatically create new versions for new tags. After creating the version for a non-VCS backed module, you should upload the module to the link that HCP Terraform returns. + +| Status | Response | Reason | +| ------- | ---------------------------------------------------------- | -------------------------------------------------------------- | +| [201][] | [JSON API document][] (`type: "registry-module-versions"`) | Successfully published module version | +| [422][] | [JSON API error object][] | Malformed request body (missing attributes, wrong types, etc.) | +| [403][] | [JSON API error object][] | Forbidden - not available for public modules | +| [404][] | [JSON API error object][] | User not authorized | + +### Request Body + +This POST endpoint requires a JSON object with the following properties as a request payload. + +Properties without a default value are required. + +| Key path | Type | Default | Description | +| ---------------------------- | ------ | ------- | --------------------------------------------------- | +| `data.type` | string | | Must be `"registry-module-versions"`. | +| `data.attributes.version` | string | | A valid semver version string. | +| `data.attributes.commit-sha` | string | | The commit SHA to use to create the module version. | + +### Sample Payload + +```json +{ + "data": { + "type": "registry-module-versions", + "attributes": { + "version": "1.2.3", + "commit-sha": "abcdef12345" + } + } +} +``` + +### Sample Request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request POST \ + --data @payload.json \ + https://app.terraform.io/api/v2/organizations/my-organization/registry-modules/private/my-organization/my-module/aws/versions +``` + +### Sample Response + +```json +{ + "data": { + "id": "modver-qjjF7ArLXJSWU3WU", + "type": "registry-module-versions", + "attributes": { + "source": "tfe-api", + "status": "pending", + "version": "1.2.3", + "created-at": "2018-09-24T20:47:20.931Z", + "updated-at": "2018-09-24T20:47:20.931Z" + }, + "relationships": { + "registry-module": { + "data": { + "id": "1881", + "type": "registry-modules" + } + } + }, + "links": { + "upload": "https://archivist.terraform.io/v1/object/dmF1bHQ6djE6NWJPbHQ4QjV4R1ox..." + } + } +} +``` + +## Add a Module Version (Private Module) + +`PUT https://archivist.terraform.io/v1/object/` + +**The URL is provided in the `upload` links attribute in the `registry-module-versions` resource.** + +### Expected Archive Format + +HCP Terraform expects the module version uploaded to be a gzip tarball with the module in the root (not in a subdirectory). + +Given the following folder structure: + + terraform-null-test + ├── README.md + ├── examples + │   └── default + │   ├── README.md + │   └── main.tf + └── main.tf + +Package the files in an archive format by running `tar zcvf module.tar.gz *` in the module's directory. + + ~$ cd terraform-null-test + terraform-null-test$ tar zcvf module.tar.gz * + a README.md + a examples + a examples/default + a examples/default/main.tf + a examples/default/README.md + a main.tf + +### Sample Request + +```shell +curl \ + --header "Content-Type: application/octet-stream" \ + --request PUT \ + --data-binary @module.tar.gz \ + https://archivist.terraform.io/v1/object/dmF1bHQ6djE6NWJPbHQ4QjV4R1ox... +``` + +After the registry module version is successfully parsed, its status will become `"ok"`. + +## Get a Module + +~> **Deprecation warning**: the following endpoint `GET /registry-modules/show/:organization_name/:name/:provider` is replaced by the below endpoint and will be removed from future versions of the API! + +`GET /organizations/:organization_name/registry-modules/:registry_name/:namespace/:name/:provider` + +### Parameters + +| Parameter | Description | +| -------------------- | ----------------------------------------------------------------------------------------------------------- | +| `:organization_name` | The name of the organization the module belongs to. | +| `:namespace` | The namespace of the module. For private modules this is the name of the organization that owns the module. | +| `:name` | The module name. | +| `:provider` | The module provider. Must be lowercase alphanumeric. | +| `:registry-name` | Either `public` or `private`. | + +| Status | Response | Reason | +| ------- | -------------------------------------------------- | ------------------------------------------------------- | +| [200][] | [JSON API document][] (`type: "registry-modules"`) | The request was successful | +| [403][] | [JSON API error object][] | Forbidden - public module curation disabled | +| [404][] | [JSON API error object][] | Module not found or user unauthorized to perform action | + +### Sample Request (private module) + +```shell +curl \ + --request GET \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + https://app.terraform.io/api/v2/organizations/my-organization/registry-modules/private/my-organization/my-module/aws +``` + +### Sample Request (public module) + +```shell +curl \ + --request GET \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + https://app.terraform.io/api/v2/organizations/my-organization/registry-modules/public/terraform-aws-modules/vpc/aws +``` + +### Sample Response (private module) + +```json +{ + "data": { + "id": "mod-fZn7uHu99ZCpAKZJ", + "type": "registry-modules", + "attributes": { + "name": "my-module", + "provider": "aws", + "namespace": "my-organization", + "registry-name": "private", + "status": "setup_complete", + "version-statuses": [ + { + "version": "1.0.0", + "status": "ok" + } + ], + "created-at": "2020-07-09T19:36:56.288Z", + "updated-at": "2020-07-09T20:16:20.538Z", + "vcs-repo": { + "branch": "", + "ingress-submodules": true, + "identifier": "lafentres/terraform-aws-my-module", + "display-identifier": "lafentres/terraform-aws-my-module", + "oauth-token-id": "ot-hmAyP66qk2AMVdbJ", + "webhook-url": "https://app.terraform.io/webhooks/vcs/a12b3456..." + }, + "permissions": { + "can-delete": true, + "can-resync": true, + "can-retry": true + } + }, + "relationships": { + "organization": { + "data": { + "id": "my-organization", + "type": "organizations" + } + } + }, + "links": { + "self": "/api/v2/organizations/my-organization/registry-modules/private/my-organization/my-module/aws" + } + } +} +``` + +### Sample Response (public module) + +```json +{ + "data": { + "id": "mod-fZn7uHu99ZCpAKZJ", + "type": "registry-modules", + "attributes": { + "name": "vpc", + "provider": "aws", + "namespace": "terraform-aws-modules", + "registry-name": "public", + "status": "setup_complete", + "version-statuses": [], + "created-at": "2020-07-09T19:36:56.288Z", + "updated-at": "2020-07-09T20:16:20.538Z", + "permissions": { + "can-delete": true, + "can-resync": true, + "can-retry": true + } + }, + "relationships": { + "organization": { + "data": { + "id": "my-organization", + "type": "organizations" + } + } + }, + "links": { + "self": "/api/v2/organizations/my-organization/registry-modules/public/terraform-aws-modules/vpc/aws" + } + } +} +``` + +## Update a Private Registry Module + +`PATCH /organizations/:organization_name/registry-modules/private/:namespace/:name/:provider/` + +### Parameters + +| Parameter | Description | +| -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `:organization_name` | The name of the organization to update a module from. The organization must already exist, and the token authenticating the API request must belong to the `owners` team or a member of the `owners` team. | +| `:namespace` | The module namespace that the update affects. For private modules this is the name of the organization that owns the module. | +| `:name` | The module name that the update affects. | +| `:provider` | The name of the provider of the module that is being updated. | + +### Request Body + +These PATCH endpoints require a JSON object with the following properties as a request payload. + +Properties without a default value are required. + +| Key path | Type | Default | Description | +| ------------------------------------------- | ------- | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `data.type` | string | | Must be `"registry-modules"`. | +| `data.attributes.vcs-repo.branch` | string | (previous value) | The repository branch that Terraform executes tests and publishes new versions from. This cannot be used with the `data.attributes.vcs-repo.tags` key. | +| `data.attributes.vcs-repo.tags` | boolean | (previous value) | Whether the registry module should be tag-based. This cannot be used with the `data.attributes.vcs-repo.branch` key. | +| `data.attributes.test-config.tests-enabled` | boolean | (previous value) | Allows you to enable or disable tests for the module. | + +### Sample Payload + +```json +{ + "data": { + "attributes": { + "vcs-repo": { + "branch": "main", + "tags": false + }, + "test-config": { + "tests-enabled": true + } + }, + "type": "registry-modules" + } +} +``` + +### Sample Request + +```shell +$ curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request PATCH \ + --data @payload.json \ + https://app.terraform.io/api/v2/organizations/my-organization/registry-modules/private/my-organization/registry-name/registry-provider/ +``` + +### Sample Response + +```json +{ + "data": { + "id": "mod-fZn7uHu99ZCpAKZJ", + "type": "registry-modules", + "attributes": { + "name": "my-module", + "namespace": "my-organization", + "registry-name": "private", + "provider": "aws", + "status": "pending", + "version-statuses": [], + "created-at": "2020-07-09T19:36:56.288Z", + "updated-at": "2020-07-09T19:36:56.288Z", + "vcs-repo": { + "branch": "main", + "ingress-submodules": true, + "identifier": "lafentres/terraform-aws-my-module", + "display-identifier": "lafentres/terraform-aws-my-module", + "oauth-token-id": "ot-hmAyP66qk2AMVdbJ", + "webhook-url": "https://app.terraform.io/webhooks/vcs/a12b3456..." + }, + "permissions": { + "can-delete": true, + "can-resync": true, + "can-retry": true + }, + "test-config": { + "id": "tc-tcR6bxV5zE75Zb3B", + "tests-enabled": true + } + }, + "relationships": { + "organization": { + "data": { + "id": "my-organization", + "type": "organizations" + } + } + }, + "links": { + "self": "/api/v2/organizations/my-organization/registry-modules/private/my-organization/my-module/aws" + } + } +} +``` + +## Delete a Module + +
+ **Deprecation warning**: the following endpoints: + +- `POST /registry-modules/actions/delete/:organization_name/:name/:provider/:version` +- `POST /registry-modules/actions/delete/:organization_name/:name/:provider` +- `POST /registry-modules/actions/delete/:organization_name/:name` + +are replaced by the below endpoints and will be removed from future versions of the API! + +
+ +- `DELETE /organizations/:organization_name/registry-modules/:registry_name/:namespace/:name/:provider/:version` +- `DELETE /organizations/:organization_name/registry-modules/:registry_name/:namespace/:name/:provider` +- `DELETE /organizations/:organization_name/registry-modules/:registry_name/:namespace/:name` + +### Parameters + +| Parameter | Description | +| -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `:organization_name` | The name of the organization to delete a module from. The organization must already exist, and the token authenticating the API request must belong to the "owners" team or a member of the "owners" team. | +| `:namespace` | The module namespace that the deletion will affect. For private modules this is the name of the organization that owns the module. | +| `:name` | The module name that the deletion will affect. | +| `:provider` | If specified, the provider for the module that the deletion will affect. | +| `:version` | If specified, the version for the module and provider that will be deleted. | +| `:registry_name` | Either `public` or `private` | + +[permissions-citation]: #intentionally-unused---keep-for-maintainers + +When removing modules, there are three versions of the endpoint, depending on how many parameters are specified. + +- If all parameters (module namespace, name, provider, and version) are specified, the specified version for the given provider of the module is deleted. +- If module namespace, name, and provider are specified, the specified provider for the given module is deleted along with all its versions. +- If only module namespace and name are specified, the entire module is deleted. + +For public modules, only the the endpoint specifying the module namespace and name is valid. The other DELETE endpoints will 404. +For public modules, this only removes the record from the organization's HCP Terraform Registry and does not remove the public module from registry.terraform.io. + +If a version deletion would leave a provider with no versions, the provider will be deleted. If a provider deletion would leave a module with no providers, the module will be deleted. + +| Status | Response | Reason | +| ------- | ------------------------- | ------------------------------------------------------------- | +| [204][] | No Content | Success | +| [403][] | [JSON API error object][] | Forbidden - public module curation disabled | +| [404][] | [JSON API error object][] | Module, provider, or version not found or user not authorized | + +### Sample Request (private module) + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request DELETE \ + https://app.terraform.io/api/v2/organizations/my-organization/registry-modules/private/my-organization/my-module/aws/2.0.0 +``` + +### Sample Request (public module) + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request DELETE \ + https://app.terraform.io/api/v2/organizations/my-organization/registry-modules/public/terraform-aws-modules/vpc/aws +``` diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/private-registry/provider-versions-platforms.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/private-registry/provider-versions-platforms.mdx new file mode 100644 index 0000000000..029dda55df --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/private-registry/provider-versions-platforms.mdx @@ -0,0 +1,707 @@ +--- +page_title: /registry-providers API reference for Terraform Enterprise +description: >- + Use the Terraform Enterprise API's `/registry-providers` endpoint to read, + create, and delete private providers versions and platforms in your private + registry. +source: terraform-docs-common +--- + +[200]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/200 + +[201]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/201 + +[202]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/202 + +[204]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/204 + +[400]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/400 + +[401]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/401 + +[403]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/403 + +[404]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404 + +[409]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/409 + +[412]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/412 + +[422]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/422 + +[429]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/429 + +[500]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500 + +[504]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/504 + +[JSON API document]: /terraform/enterprise/api-docs#json-api-documents + +[JSON API error object]: https://jsonapi.org/format/#error-objects + +# Private provider versions and platforms API reference + +These endpoints are only relevant to private providers. When you [publish a private provider](/terraform/enterprise/registry/publish-providers) to the HCP Terraform private registry, you must also create at least one version and at least one platform for that version before consumers can use the provider in configurations. Unlike the public Terraform Registry, the private registry does not automatically upload new releases. You must manually add new provider versions and the associated release files. + +All members of an organization can view and use both public and private providers, but you need [owners team](/terraform/enterprise/users-teams-organizations/permissions#organization-owners) or [Manage Private Registry](/terraform/enterprise/users-teams-organizations/permissions#manage-private-registry) permissions to add, update, or delete provider versions and platforms in private registry. + +## Create a Provider Version + +`POST /organizations/:organization_name/registry-providers/:registry_name/:namespace/:name/versions` + +The private registry does not automatically update private providers when you release new versions. You must use this endpoint to add each new version. Consumers cannot use new versions until you upload all [required release files](/terraform/enterprise/registry/publish-providers#release-files) and [Create a Provider Platform](#create-a-provider-platform). + +### Parameters + +| Parameter | Description | +| -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `:organization_name` | The name of the organization to create a provider in. The organization must already exist, and the token authenticating the API request must belong to the "owners" team or a member of the "owners" team. | +| `:registry_name` | Must be `private`. | +| `:namespace` | The namespace of the provider for which the version is being created. For private providers this is the same as the `:organization_name` parameter. | +| `:name` | The name of the provider for which the version is being created. | + +Creates a new registry provider version. This endpoint only applies to private providers. + +| Status | Response | Reason | +| ------- | ------------------------------------------------------------ | -------------------------------------------------------------- | +| [201][] | [JSON API document][] (`type: "registry-provider-versions"`) | Success | +| [422][] | [JSON API error object][] | Malformed request body (missing attributes, wrong types, etc.) | +| [403][] | [JSON API error object][] | Forbidden - not available for public providers | +| [404][] | [JSON API error object][] | User not authorized | + +### Request Body + +This POST endpoint requires a JSON object with the following properties as a request payload. + +Properties without a default value are required. + +| Key path | Type | Default | Description | +| --------------------------- | ------ | ------- | ----------------------------------------------------------------------------------------------------------------------------------------- | +| `data.type` | string | | Must be `"registry-provider-versions"`. | +| `data.attributes.version` | string | | A valid semver version string. | +| `data.attributes.key-id` | string | | A valid gpg-key string. | +| `data.attributes.protocols` | array | | An array of Terraform provider API versions that this version supports. Must be one or all of the following values `["4.0","5.0","6.0"]`. | + +-> **Note:** Only Terraform 0.13 and later support third-party provider registries, and that Terraform version requires provider API version 5.0 or later. So you do not need to list major versions 4.0 or earlier in the `protocols` attribute. + +### Sample Payload + +```json +{ + "data": { + "type": "registry-provider-versions", + "attributes": { + "version": "3.1.1", + "key-id": "32966F3FB5AC1129", + "protocols": ["5.0"] + } + } +} +``` + +### Sample Request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request POST \ + --data @payload.json \ + https://app.terraform.io/api/v2/organizations/hashicorp/registry-providers/private/hashicorp/aws/versions +``` + +### Sample Response + +```json +{ + "data": { + "id": "provver-y5KZUsSBRLV9zCtL", + "type": "registry-provider-versions", + "attributes": { + "version": "3.1.1", + "created-at": "2022-02-11T19:16:59.876Z", + "updated-at": "2022-02-11T19:16:59.876Z", + "key-id": "32966F3FB5AC1129", + "protocols": ["5.0"], + "permissions": { + "can-delete": true, + "can-upload-asset": true + }, + "shasums-uploaded": false, + "shasums-sig-uploaded": false + }, + "relationships": { + "registry-provider": { + "data": { + "id": "prov-cmEmLstBfjNNA9F3", + "type": "registry-providers" + } + }, + "platforms": { + "data": [], + "links": { + "related": "/api/v2/organizations/hashicorp/registry-providers/private/hashicorp/aws/versions/3.1.1/platforms" + } + } + }, + "links": { + "shasums-upload": "https://archivist.terraform.io/v1/object/dmF1b...", + "shasums-sig-upload": "https://archivist.terraform.io/v1/object/dmF1b..." + } + } +} + +``` + +## Get All Versions for a Single Provider + +`GET /organizations/:organization_name/registry-providers/:registry_name/:namespace/:name/versions/` + +### Parameters + +| Parameter | Description | +| -------------------- | -------------------------------------------------------------------------------------------- | +| `:organization_name` | The name of the organization the provider belongs to. | +| `:registry_name` | Must be `private`. | +| `:namespace` | The namespace of the provider. Must be the same as the `organization_name` for the provider. | +| `:name` | The provider name. | + +| Status | Response | Reason | +| ------- | ---------------------------------------------------- | --------------------------------------------------------- | +| [200][] | [JSON API document][] (`type: "registry-providers"`) | Success | +| [403][] | [JSON API error object][] | Forbidden - public provider curation disabled | +| [404][] | [JSON API error object][] | Provider not found or user unauthorized to perform action | + +### Sample Request + +```shell +curl \ + --request GET \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + https://app.terraform.io/api/v2/organizations/hashicorp/registry-providers/private/hashicorp/aws/versions +``` + +### Sample Response + +```json +{ + "data": [ + { + "id": "provver-y5KZUsSBRLV9zCtL", + "type": "registry-provider-versions", + "attributes": { + "version": "3.1.1", + "created-at": "2022-02-11T19:16:59.876Z", + "updated-at": "2022-02-11T19:16:59.876Z", + "key-id": "32966F3FB5AC1129", + "protocols": ["5.0"], + "permissions": { + "can-delete": true, + "can-upload-asset": true + }, + "shasums-uploaded": true, + "shasums-sig-uploaded": true + }, + "relationships": { + "registry-provider": { + "data": { + "id": "prov-cmEmLstBfjNNA9F3", + "type": "registry-providers" + } + }, + "platforms": { + "data": [ + { + "id": "provpltfrm-GSHhNzptr9s3WoLD", + "type": "registry-provider-platforms" + }, + { + "id": "provpltfrm-A1PHitiM2KkKpVoM", + "type": "registry-provider-platforms" + }, + { + "id": "provpltfrm-BLJWvWyJ2QMs525k", + "type": "registry-provider-platforms" + }, + { + "id": "provpltfrm-qQYosUguetYtXGzJ", + "type": "registry-provider-platforms" + }, + { + "id": "provpltfrm-pjDHFN46y193bS7t", + "type": "registry-provider-platforms" + } + ], + "links": { + "related": "/api/v2/organizations/hashicorp/registry-providers/private/hashicorp/aws/versions/3.1.1/platforms" + } + } + }, + "links": { + "shasums-download": "https://archivist.terraform.io/v1/object/dmF1b...", + "shasums-sig-download": "https://archivist.terraform.io/v1/object/dmF1b..." + } + } + ], + "links": { + "self": "https://app.terraform.io/api/v2/organizations/hashicorp/registry-providers/private/hashicorp/aws/versions?page%5Bnumber%5D=1&page%5Bsize%5D=20", + "first": "https://app.terraform.io/api/v2/organizations/hashicorp/registry-providers/private/hashicorp/aws/versions?page%5Bnumber%5D=1&page%5Bsize%5D=20", + "prev": null, + "next": null, + "last": "https://app.terraform.io/api/v2/organizations/hashicorp/registry-providers/private/hashicorp/aws/versions?page%5Bnumber%5D=1&page%5Bsize%5D=20" + }, + "meta": { + "pagination": { + "current-page": 1, + "page-size": 20, + "prev-page": null, + "next-page": null, + "total-pages": 1, + "total-count": 1 + } + } +} +``` + +**Note:** The `shasums-uploaded` and `shasums-sig-uploaded` properties will be false if those files have not been uploaded to Archivist. In this case, instead of including links to `shasums-download` and `shasums-sig-download`, the response will include upload links (`shasums-upload` and `shasums-sig-upload`). + +## Get a Version + +`GET /organizations/:organization_name/registry-providers/:registry_name/:namespace/:name/versions/:version` + +### Parameters + +| Parameter | Description | +| -------------------- | -------------------------------------------------------------------------------------------- | +| `:organization_name` | The name of the organization the provider belongs to. | +| `:registry_name` | Must be `private`. | +| `:namespace` | The namespace of the provider. Must be the same as the `organization_name` for the provider. | +| `:name` | The provider name. | +| `:version` | The version of the provider being created to which different platforms can be added. | + +| Status | Response | Reason | +| ------- | ---------------------------------------------------- | --------------------------------------------------------- | +| [200][] | [JSON API document][] (`type: "registry-providers"`) | Success | +| [403][] | [JSON API error object][] | Forbidden - public provider curation disabled | +| [404][] | [JSON API error object][] | Provider not found or user unauthorized to perform action | + +### Sample Request + +```shell +curl \ + --request GET \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + https://app.terraform.io/api/v2/organizations/hashicorp/registry-providers/private/hashicorp/aws/versions/3.1.1 +``` + +### Sample Response + +```json +{ + "data": { + "id": "provver-y5KZUsSBRLV9zCtL", + "type": "registry-provider-versions", + "attributes": { + "version": "3.1.1", + "created-at": "2022-02-11T19:16:59.876Z", + "updated-at": "2022-02-11T19:16:59.876Z", + "key-id": "32966F3FB5AC1129", + "protocols": ["5.0"], + "permissions": { + "can-delete": true, + "can-upload-asset": true + }, + "shasums-uploaded": true, + "shasums-sig-uploaded": true + }, + "relationships": { + "registry-provider": { + "data": { + "id": "prov-cmEmLstBfjNNA9F3", + "type": "registry-providers" + } + }, + "platforms": { + "data": [ + { + "id": "provpltfrm-GSHhNzptr9s3WoLD", + "type": "registry-provider-platforms" + }, + { + "id": "provpltfrm-A1PHitiM2KkKpVoM", + "type": "registry-provider-platforms" + }, + { + "id": "provpltfrm-BLJWvWyJ2QMs525k", + "type": "registry-provider-platforms" + }, + { + "id": "provpltfrm-qQYosUguetYtXGzJ", + "type": "registry-provider-platforms" + }, + { + "id": "provpltfrm-pjDHFN46y193bS7t", + "type": "registry-provider-platforms" + } + ], + "links": { + "related": "/api/v2/organizations/hashicorp/registry-providers/private/hashicorp/aws/versions/3.1.1/platforms" + } + } + }, + "links": { + "shasums-download": "https://archivist.terraform.io/v1/object/dmF1b...", + "shasums-sig-download": "https://archivist.terraform.io/v1/object/dmF1b..." + } + } +} +``` + +**Note:** `shasums-uploaded` and `shasums-sig-uploaded` will be false if those files haven't been uploaded to Archivist yet. In this case, instead of including links to `shasums-download` and `shasums-sig-download`, the response will include upload links (`shasums-upload` and `shasums-sig-upload`). + +## Delete a Version + +`DELETE /organizations/:organization_name/registry-providers/:registry_name/:namespace/:name/versions/:provider_version` + +### Parameters + +| Parameter | Description | +| -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `:organization_name` | The name of the organization to delete a provider version from. The organization must already exist, and the token authenticating the API request must belong to the "owners" team or a member of the "owners" team. | +| `:registry_name` | Must be `private`. | +| `:namespace` | The namespace of the provider for which the version is being deleted. For private providers this is the same as the `:organization_name` parameter. | +| `:name` | The name of the provider for which the version is being deleted. | +| `:version` | The version for the provider that will be deleted along with its corresponding platforms. | + +[permissions-citation]: #intentionally-unused---keep-for-maintainers + +| Status | Response | Reason | +| ------- | ------------------------- | ----------------------------------------------------------- | +| [204][] | No Content | Success | +| [403][] | [JSON API error object][] | Forbidden - public provider curation disabled | +| [404][] | [JSON API error object][] | Provider not found or user not authorized to perform action | + +### Sample Request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request DELETE \ + https://app.terraform.io/api/v2/organizations/hashicorp/registry-providers/public/hashicorp/aws/versions/3.1.1 +``` + +## Create a Provider Platform + +`POST /organizations/:organization_name/registry-providers/:registry_name/:namespace/:name/versions/:version/platforms` + +Platforms are binaries that allow the provider to run on a particular operating system and architecture combination (e.g., Linux and AMD64). GoReleaser creates binaries automatically when you [create a release on GitHub](/terraform/registry/providers/publishing#creating-a-github-release) or [create a release locally](/terraform/registry/providers/publishing#using-goreleaser-locally). + +You must upload one or more platforms for each version of a private provider. After you create a platform, you must upload the platform binary file to the `provider-binary-upload` URL. + +### Parameters + +| Parameter | Description | +| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `:organization_name` | The name of the organization to create a provider platform in. The organization must already exist, and the token authenticating the API request must belong to the "owners" team or a member of the "owners" team. | +| `:registry_name` | Must be `private`. | +| `:namespace` | The namespace of the provider for which the platform is being created. For private providers this is the same as the `:organization_name` parameter. | +| `:name` | The name of the provider for which the platform is being created. | +| `:version` | The provider version of the provider for which the platform is being created. | + +Creates a new registry provider platform. This endpoint only applies to private providers. + +| Status | Response | Reason | +| ------- | ------------------------------------------------------------- | -------------------------------------------------------------- | +| [201][] | [JSON API document][] (`type: "registry-provider-platforms"`) | Success | +| [422][] | [JSON API error object][] | Malformed request body (missing attributes, wrong types, etc.) | +| [403][] | [JSON API error object][] | Forbidden - not available for public providers | +| [404][] | [JSON API error object][] | User not authorized | + +### Request Body + +This POST endpoint requires a JSON object with the following properties as a request payload. + +Properties without a default value are required. + +| Key path | Type | Default | Description | +| -------------------------- | ------ | ------- | ---------------------------------------- | +| `data.type` | string | | Must be `"registry-provider-platforms"`. | +| `data.attributes.os` | string | | A valid operating system string. | +| `data.attributes.arch` | string | | A valid architecture string. | +| `data.attributes.shasum` | string | | A valid shasum string. | +| `data.attributes.filename` | string | | A valid filename string. | + +### Sample Payload + +```json +{ + "data": { + "type": "registry-provider-version-platforms", + "attributes": { + "os": "linux", + "arch": "amd64", + "shasum": "8f69533bc8afc227b40d15116358f91505bb638ce5919712fbb38a2dec1bba38", + "filename": "terraform-provider-aws_3.1.1_linux_amd64.zip" + } + } +} +``` + +### Sample Request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request POST \ + --data @payload.json \ + https://app.terraform.io/api/v2/organizations/hashicorp/registry-providers/private/hashicorp/aws/versions/3.1.1/platforms +``` + +### Sample Response + +```json +{ + "data": { + "id": "provpltfrm-BLJWvWyJ2QMs525k", + "type": "registry-provider-platforms", + "attributes": { + "os": "linux", + "arch": "amd64", + "filename": "terraform-provider-aws_3.1.1_linux_amd64.zip", + "shasum": "8f69533bc8afc227b40d15116358f91505bb638ce5919712fbb38a2dec1bba38", + "permissions": { + "can-delete": true, + "can-upload-asset": true + }, + "provider-binary-uploaded": false + }, + "relationships": { + "registry-provider-version": { + "data": { + "id": "provver-y5KZUsSBRLV9zCtL", + "type": "registry-provider-versions" + } + } + }, + "links": { + "provider-binary-upload": "https://archivist.terraform.io/v1/object/dmF1b..." + } + } +} + +``` + +## Get All Platforms for a Single Version + +`GET /organizations/:organization_name/registry-providers/:registry_name/:namespace/:name/versions/:version/platforms` + +### Parameters + +| Parameter | Description | +| -------------------- | -------------------------------------------------------------------------------------------- | +| `:organization_name` | The name of the organization the provider belongs to. | +| `:registry_name` | Must be `private`. | +| `:namespace` | The namespace of the provider. Must be the same as the `organization_name` for the provider. | +| `:name` | The provider name. | +| `:version` | The version of the provider. | + +| Status | Response | Reason | +| ------- | ---------------------------------------------------- | --------------------------------------------------------- | +| [200][] | [JSON API document][] (`type: "registry-providers"`) | Success | +| [403][] | [JSON API error object][] | Forbidden - public provider curation disabled | +| [404][] | [JSON API error object][] | Provider not found or user unauthorized to perform action | + +### Sample Request + +```shell +curl \ + --request GET \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + https://app.terraform.io/api/v2/organizations/hashicorp/registry-providers/private/hashicorp/aws/versions/3.1.1/platforms +``` + +### Sample Response + +```json +{ + "data": [ + { + "id": "provpltfrm-GSHhNzptr9s3WoLD", + "type": "registry-provider-platforms", + "attributes": { + "os": "darwin", + "arch": "amd64", + "filename": "terraform-provider-aws_3.1.1_darwin_amd64.zip", + "shasum": "fd580e71bd76d76913e1925f2641be9330c536464af9a08a5b8994da65a26cbc", + "permissions": { + "can-delete": true, + "can-upload-asset": true + }, + "provider-binary-uploaded": true + }, + "relationships": { + "registry-provider-version": { + "data": { + "id": "provver-y5KZUsSBRLV9zCtL", + "type": "registry-provider-versions" + } + } + }, + "links": { + "provider-binary-download": "https://archivist.terraform.io/v1/object/dmF1b..." + } + }, + { + "id": "provpltfrm-A1PHitiM2KkKpVoM", + "type": "registry-provider-platforms", + "attributes": { + "os": "darwin", + "arch": "arm64", + "filename": "terraform-provider-aws_3.1.1_darwin_arm64.zip", + "shasum": "de3c351d7f35a3c8c583c0da5c1c4d558b8cea3731a49b15f63de5bbbafc0165", + "permissions": { + "can-delete": true, + "can-upload-asset": true + }, + "provider-binary-uploaded": true + }, + "relationships": { + "registry-provider-version": { + "data": { + "id": "provver-y5KZUsSBRLV9zCtL", + "type": "registry-provider-versions" + } + } + }, + "links": { + "provider-binary-download": "https://archivist.terraform.io/v1/object/dmF1b..." + } + } + ], + "links": { + "self": "https://app.terraform.io/api/v2/organizations/hashicorp/registry-providers/private/hashicorp/aws/versions/3.1.1/platforms?page%5Bnumber%5D=1&page%5Bsize%5D=20", + "first": "https://app.terraform.io/api/v2/organizations/hashicorp/registry-providers/private/hashicorp/aws/versions/3.1.1/platforms?page%5Bnumber%5D=1&page%5Bsize%5D=20", + "prev": null, + "next": null, + "last": "https://app.terraform.io/api/v2/organizations/hashicorp/registry-providers/private/hashicorp/aws/versions/3.1.1/platforms?page%5Bnumber%5D=1&page%5Bsize%5D=20" + }, + "meta": { + "pagination": { + "current-page": 1, + "page-size": 20, + "prev-page": null, + "next-page": null, + "total-pages": 1, + "total-count": 2 + } + } +} +``` + +**Note:** The `provider-binary-uploaded` property will be `false` if that file has not been uploaded to Archivist. In this case, instead of including a link to `provider-binary-download`, the response will include an upload link `provider-binary-upload`. + +## Get a Platform + +`GET /organizations/:organization_name/registry-providers/:registry_name/:namespace/:name/versions/:version/platforms/:os/:arch` + +### Parameters + +| Parameter | Description | +| -------------------- | -------------------------------------------------------------------------------------------- | +| `:organization_name` | The name of the organization the provider belongs to. | +| `:registry_name` | Must be `private`. | +| `:namespace` | The namespace of the provider. Must be the same as the `organization_name` for the provider. | +| `:name` | The provider name. | +| `:version` | The version of the provider. | +| `:os` | The operating system of the provider platform. | +| `:arch` | The architecture of the provider platform. | + +| Status | Response | Reason | +| ------- | ---------------------------------------------------- | --------------------------------------------------------- | +| [200][] | [JSON API document][] (`type: "registry-providers"`) | Success | +| [403][] | [JSON API error object][] | Forbidden - public provider curation disabled | +| [404][] | [JSON API error object][] | Provider not found or user unauthorized to perform action | + +### Sample Request + +```shell +curl \ + --request GET \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + https://app.terraform.io/api/v2/organizations/hashicorp/registry-providers/private/hashicorp/aws/versions/3.1.1/platforms/linux/amd64 +``` + +### Sample Response + +```json +{ + "data": { + "id": "provpltfrm-BLJWvWyJ2QMs525k", + "type": "registry-provider-platforms", + "attributes": { + "os": "linux", + "arch": "amd64", + "filename": "terraform-provider-aws_3.1.1_linux_amd64.zip", + "shasum": "8f69533bc8afc227b40d15116358f91505bb638ce5919712fbb38a2dec1bba38", + "permissions": { + "can-delete": true, + "can-upload-asset": true + }, + "provider-binary-uploaded": true + }, + "relationships": { + "registry-provider-version": { + "data": { + "id": "provver-y5KZUsSBRLV9zCtL", + "type": "registry-provider-versions" + } + } + }, + "links": { + "provider-binary-download": "https://archivist.terraform.io/v1/object/dmF1b..." + } + } +} +``` + +**Note:** The `provider-binary-uploaded` property will be `false` if that file has not been uploaded to Archivist. In this case, instead of including a link to `provider-binary-download`, the response will include an upload link `provider-binary-upload`. + +## Delete a Platform + +`DELETE /organizations/:organization_name/registry-providers/:registry_name/:namespace/:name/versions/:version/platforms/:os/:arch` + +### Parameters + +| Parameter | Description | +| -------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `:organization_name` | The name of the organization to delete a provider platform from. The organization must already exist, and the token authenticating the API request must belong to the "owners" team or a member of the "owners" team. | +| `:registry_name` | Must be `private`. | +| `:namespace` | The namespace of the provider for which the platform is being deleted. For private providers this is the same as the `:organization_name` parameter. | +| `:name` | The name of the provider for which the platform is being deleted. | +| `:version` | The version for which the platform is being deleted. | +| `:os` | The operating system of the provider platform that is being deleted. | +| `:arch` | The architecture of the provider platform that is being deleted. | + +[permissions-citation]: #intentionally-unused---keep-for-maintainers + +| Status | Response | Reason | +| ------- | ------------------------- | ----------------------------------------------------------- | +| [204][] | No Content | Success | +| [403][] | [JSON API error object][] | Forbidden - public provider curation disabled | +| [404][] | [JSON API error object][] | Provider not found or user not authorized to perform action | + +### Sample Request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request DELETE \ + https://app.terraform.io/api/v2/organizations/hashicorp/registry-providers/private/hashicorp/aws/versions/3.1.1/platforms/linux/amd64 +``` diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/private-registry/providers.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/private-registry/providers.mdx new file mode 100644 index 0000000000..44719c2f16 --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/private-registry/providers.mdx @@ -0,0 +1,471 @@ +--- +page_title: /registry-providers API reference for Terraform Enterprise +description: >- + Use the Terraform Enterprise API's organization `/registry-providers` endpoint + to list, create, get, and delete providers in your private registry. +source: terraform-docs-common +--- + +[200]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/200 + +[201]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/201 + +[202]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/202 + +[204]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/204 + +[400]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/400 + +[401]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/401 + +[403]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/403 + +[404]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404 + +[409]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/409 + +[412]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/412 + +[422]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/422 + +[429]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/429 + +[500]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500 + +[504]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/504 + +[JSON API document]: /terraform/enterprise/api-docs#json-api-documents + +[JSON API error object]: https://jsonapi.org/format/#error-objects + +# Registry providers API reference + +You can add publicly curated providers from the [Terraform Registry](https://registry.terraform.io/) and custom, private providers to your HCP Terraform private registry. The private registry stores a pointer to public providers so that you can view their data from within HCP Terraform. This lets you clearly designate all of the providers that are recommended for the organization and makes them centrally accessible. + +All members of an organization can view and use both public and private providers, but you need [owners team](/terraform/enterprise/users-teams-organizations/permissions#organization-owners) or [Manage Private Registry](/terraform/enterprise/users-teams-organizations/permissions#manage-private-registry) permissions to add, update, or delete them them in private registry. + +## HCP Terraform Registry Implementation + +For publicly curated providers, the HCP Terraform Registry acts as a proxy to the [Terraform Registry](https://registry.terraform.io) for the following: + +- The public registry discovery endpoints have the path prefix provided in the [discovery document](/terraform/registry/api-docs#service-discovery) which is currently `/api/registry/public/v1`. +- [Authentication](/terraform/enterprise/api-docs#authentication) is handled the same as all other HCP Terraform endpoints. + +## List Terraform Registry Providers for an Organization + +`GET /organizations/:organization_name/registry-providers` + +### Parameters + +| Parameter | Description | +| -------------------- | -------------------------------------------------------------- | +| `:organization_name` | The name of the organization to list available providers from. | + +Lists the providers included in the private registry for the specified organization. + +| Status | Response | Reason | +| ------- | ---------------------------------------------------- | ---------------------------------------------------------- | +| [200][] | [JSON API document][] (`type: "registry-providers"`) | Success | +| [404][] | [JSON API error object][] | Providers not found or user unauthorized to perform action | + +### Query Parameters + +This endpoint supports pagination [with standard URL query parameters](/terraform/enterprise/api-docs#query-parameters). Remember to percent-encode `[` as `%5B` and `]` as `%5D` if your tooling doesn't automatically encode URLs. + +| Parameter | Description | +| -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- | +| `q` | **Optional.** A search query string. Providers are searchable by both their name and their namespace fields. | +| `filter[field name]` | **Optional.** If specified, restricts results to those with the matching field name value. Valid values are `registry_name`, and `organization_name`. | +| `page[number]` | **Optional.** If omitted, the endpoint will return the first page. | +| `page[size]` | **Optional.** If omitted, the endpoint will return 20 registry providers per page. | + +### Sample Request + +```shell +curl \ + --request GET \ + --header "Authorization: Bearer $TOKEN" \ + https://app.terraform.io/api/v2/organizations/my-organization/registry-providers +``` + +### Sample Response + +```json +{ + "data": [ + { + "id": "prov-kwt1cBiX2SdDz38w", + "type": "registry-providers", + "attributes": { + "name": "aws", + "namespace": "my-organization", + "created-at": "2021-04-07T19:01:18.528Z", + "updated-at": "2021-04-07T19:01:19.863Z", + "registry-name": "public", + "permissions": { + "can-delete": true + } + }, + "relationships": { + "organization": { + "data": { + "id": "my-organization", + "type": "organizations" + } + } + }, + "links": { + "self": "/api/v2/organizations/my-organization/registry-providers/public/my-organization/aws" + } + }, + { + "id": "prov-PopQnMtYDCcd3PRX", + "type": "registry-providers", + "attributes": { + "name": "aurora", + "namespace": "my-organization", + "created-at": "2021-04-07T19:04:41.375Z", + "updated-at": "2021-04-07T19:04:42.828Z", + "registry-name": "public", + "permissions": { + "can-delete": true + } + }, + "relationships": { + "organization": { + "data": { + "id": "my-organization", + "type": "organizations" + } + } + }, + "links": { + "self": "/api/v2/organizations/my-organization/registry-providers/public/my-organization/aurora" + } + }, + ..., + ], + "links": { + "self": "https://app.terraform.io/api/v2/organizations/my-organization/registry-providers?page%5Bnumber%5D=1&page%5Bsize%5D=6", + "first": "https://app.terraform.io/api/v2/organizations/my-organization/registry-providers?page%5Bnumber%5D=1&page%5Bsize%5D=6", + "prev": null, + "next": "https://app.terraform.io/api/v2/organizations/my-organization/registry-providers?page%5Bnumber%5D=2&page%5Bsize%5D=6", + "last": "https://app.terraform.io/api/v2/organizations/my-organization/registry-providers?page%5Bnumber%5D=29&page%5Bsize%5D=6" + }, + "meta": { + "pagination": { + "current-page": 1, + "page-size": 6, + "prev-page": null, + "next-page": 2, + "total-pages": 29, + "total-count": 169 + } + } +} +``` + +## Create a Provider + +`POST /organizations/:organization_name/registry-providers` + +Use this endpoint to create both public and private providers: + +- **Public providers:** The public provider record will be available in the organization's registry provider list immediately after creation. You cannot create versions for public providers; you must use the versions available on the Terraform Registry. +- **Private providers:** The private provider record will be available in the organization's registry provider list immediately after creation, but you must [create a version and upload release assets](/terraform/enterprise/registry/publish-providers#publishing-a-provider-and-creating-a-version) before consumers can use it. The private registry does not automatically update private providers when you release new versions. You must add each new version with the [Create a Provider Version](/terraform/enterprise/api-docs/private-registry/provider-versions-platforms#create-a-provider-version) endpoint. + +### Parameters + +| Parameter | Description | +| -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `:organization_name` | The name of the organization to create a provider in. The organization must already exist, and the token authenticating the API request must belong to the "owners" team or a member of the "owners" team. | + +[permissions-citation]: #intentionally-unused---keep-for-maintainers + +| Status | Response | Reason | +| ------- | ---------------------------------------------------- | -------------------------------------------------------------- | +| [201][] | [JSON API document][] (`type: "registry-providers"`) | Successfully published provider | +| [422][] | [JSON API error object][] | Malformed request body (missing attributes, wrong types, etc.) | +| [403][] | [JSON API error object][] | Forbidden - public provider curation disabled | +| [404][] | [JSON API error object][] | User not authorized | + +### Request Body + +~> **Important:** For private providers, you must also create a version, a platform, and upload release assets before consumers can use the provider. Refer to [Publishing a Private Provider](/terraform/enterprise/registry/publish-providers) for more details. + +This POST endpoint requires a JSON object with the following properties as a request payload. + +Properties without a default value are required. + +| Key path | Type | Default | Description | +| ------------------------------- | ------ | ------- | ------------------------------------------------------------------------------------------------------------ | +| `data.type` | string | | Must be `"registry-providers"`. | +| `data.attributes.name` | string | | The name of the provider. | +| `data.attributes.namespace` | string | | The namespace of the provider. For private providers this is the same as the `:organization_name` parameter. | +| `data.attributes.registry-name` | string | | Whether this is a publicly maintained provider or private. Must be either `public` or `private`. | + +### Sample Payload (Private Provider) + +```json +{ + "data": { + "type": "registry-providers", + "attributes": { + "name": "aws", + "namespace": "hashicorp", + "registry-name": "private" + } + } +} +``` + +### Sample Payload (Public Provider) + +```json +{ + "data": { + "type": "registry-providers", + "attributes": { + "name": "aws", + "namespace": "hashicorp", + "registry-name": "public" + } + } +} +``` + +### Sample Request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request POST \ + --data @payload.json \ + https://app.terraform.io/api/v2/organizations/my-organization/registry-providers +``` + +### Sample Response (Private Provider) + +```json +{ + "data": { + "id": "prov-cmEmLstBfjNNA9F3", + "type": "registry-providers", + "attributes": { + "name": "aws", + "namespace": "hashicorp", + "registry-name": "private", + "created-at": "2022-02-11T19:16:59.533Z", + "updated-at": "2022-02-11T19:16:59.533Z", + "permissions": { + "can-delete": true + } + }, + "relationships": { + "organization": { + "data": { + "id": "hashicorp", + "type": "organizations" + } + }, + "versions": { + "data": [], + "links": { + "related": "/api/v2/organizations/hashicorp/registry-providers/private/hashicorp/aws" + } + } + }, + "links": { + "self": "/api/v2/organizations/hashicorp/registry-providers/private/hashicorp/aws" + } + } +} +``` + +### Sample Response (Public Provider) + +```json +{ + "data": { + "id": "prov-fZn7uHu99ZCpAKZJ", + "type": "registry-providers", + "attributes": { + "name": "aws", + "namespace": "hashicorp", + "registry-name": "public", + "created-at": "2020-07-09T19:36:56.288Z", + "updated-at": "2020-07-09T19:36:56.288Z", + "permissions": { + "can-delete": true + } + }, + "relationships": { + "organization": { + "data": { + "id": "my-organization", + "type": "organizations" + } + } + }, + "links": { + "self": "/api/v2/organizations/my-organization/registry-providers/public/hashicorp/aws" + } + } +} +``` + +## Get a Provider + +`GET /organizations/:organization_name/registry-providers/:registry_name/:namespace/:name` + +### Parameters + +| Parameter | Description | +| -------------------- | ------------------------------------------------------------------------------------------------------------ | +| `:organization_name` | The name of the organization the provider belongs to. | +| `:registry_name` | Whether this is a publicly maintained provider or private. Must be either `public` or `private`. | +| `:namespace` | The namespace of the provider. For private providers this is the same as the `:organization_name` parameter. | +| `:name` | The provider name. | + +| Status | Response | Reason | +| ------- | ---------------------------------------------------- | --------------------------------------------------------- | +| [200][] | [JSON API document][] (`type: "registry-providers"`) | Success | +| [403][] | [JSON API error object][] | Forbidden - public provider curation disabled | +| [404][] | [JSON API error object][] | Provider not found or user unauthorized to perform action | + +### Sample Request (Private Provider) + +```shell +curl \ + --request GET \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + https://app.terraform.io/api/v2/organizations/hashicorp/registry-providers/private/hashicorp/aws +``` + +### Sample Request (Public Provider) + +```shell +curl \ + --request GET \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + https://app.terraform.io/api/v2/organizations/my-organization/registry-providers/public/hashicorp/aws +``` + +### Sample Response (Private Provider) + +```json +{ + "data": { + "id": "prov-cmEmLstBfjNNA9F3", + "type": "registry-providers", + "attributes": { + "name": "aws", + "namespace": "hashicorp", + "created-at": "2022-02-11T19:16:59.533Z", + "updated-at": "2022-02-11T19:16:59.533Z", + "registry-name": "private", + "permissions": { + "can-delete": true + } + }, + "relationships": { + "organization": { + "data": { + "id": "hashicorp", + "type": "organizations" + } + }, + "versions": { + "data": [ + { + "id": "provver-y5KZUsSBRLV9zCtL", + "type": "registry-provider-versions" + } + ], + "links": { + "related": "/api/v2/organizations/hashicorp/registry-providers/private/hashicorp/aws" + } + } + }, + "links": { + "self": "/api/v2/organizations/hashicorp/registry-providers/private/hashicorp/aws" + } + } +} +``` + +### Sample Response (Public Provider) + +```json +{ + "data": { + "id": "prov-fZn7uHu99ZCpAKZJ", + "type": "registry-providers", + "attributes": { + "name": "aws", + "namespace": "hashicorp", + "registry-name": "public", + "created-at": "2020-07-09T19:36:56.288Z", + "updated-at": "2020-07-09T20:16:20.538Z", + "permissions": { + "can-delete": true + } + }, + "relationships": { + "organization": { + "data": { + "id": "my-organization", + "type": "organizations" + } + } + }, + "links": { + "self": "/api/v2/organizations/my-organization/registry-providers/public/hashicorp/aws" + } + } +} +``` + +## Delete a Provider + +`DELETE /organizations/:organization_name/registry-providers/:registry_name/:namespace/:name` + +### Parameters + +| Parameter | Description | +| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `:organization_name` | The name of the organization to delete a provider from. The organization must already exist, and the token authenticating the API request must belong to the "owners" team or a member of the "owners" team. | +| `:registry_name` | Whether this is a publicly maintained provider or private. Must be either `public` or `private`. | +| `:namespace` | The namespace of the provider that will be deleted. | +| `:name` | The name of the provider that will be deleted. | + +[permissions-citation]: #intentionally-unused---keep-for-maintainers + +| Status | Response | Reason | +| ------- | ------------------------- | ----------------------------------------------------------- | +| [204][] | No Content | Success | +| [403][] | [JSON API error object][] | Forbidden - public provider curation disabled | +| [404][] | [JSON API error object][] | Provider not found or user not authorized to perform action | + +### Sample Request (Private Provider) + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request DELETE \ + https://app.terraform.io/api/v2/organizations/hashicorp/registry-providers/private/hashicorp/aws +``` + +### Sample Request (Public Provider) + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request DELETE \ + https://app.terraform.io/api/v2/organizations/my-organization/registry-providers/public/hashicorp/aws +``` diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/private-registry/tests.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/private-registry/tests.mdx new file mode 100644 index 0000000000..ea1b7f4b60 --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/private-registry/tests.mdx @@ -0,0 +1,755 @@ +--- +page_title: /tests API reference for Terraform Enterprise +description: >- + Use the Terraform Enterprise API's `/tests` endpoint to list, get, create, and + cancel Terraform test runs. +source: terraform-docs-common +--- + +[200]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/200 + +[201]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/201 + +[202]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/202 + +[204]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/204 + +[400]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/400 + +[401]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/401 + +[403]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/403 + +[404]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404 + +[409]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/409 + +[412]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/412 + +[422]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/422 + +[429]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/429 + +[500]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500 + +[504]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/504 + +[JSON API document]: /terraform/enterprise/api-docs#json-api-documents + +[JSON API error object]: https://jsonapi.org/format/#error-objects + +# Tests API reference + +Tests are terraform operations(runs) and are referred to as Test Runs within the HCP Terraform API. + +Performing a test on a new configuration is a multi-step process. + +1. [Create a configuration version on the registry module](#create-a-configuration-version-for-a-test). +2. [Upload configuration files to the configuration version](#upload-configuration-files-for-a-test). +3. [Create a test on the module](#create-a-test-run); HCP Terraform completes this step automatically when you upload a configuration file. + +Alternatively, you can create a test with a pre-existing configuration version, even one from another module. This is useful for promoting known good code from one module to another. + +## Attributes + +The `tests` API endpoint has the following attributes. + +### Test Run States + +The state of the test operation is found in `data.attributes.status`, and you can reference the following list of possible states. + +| State | Description | +| ---------- | ----------------------------------------------------- | +| `pending` | The initial status of a run after creation. | +| `queued` | HCP Terraform has queued the test operation to start. | +| `running` | HCP Terraform is executing the test. | +| `errored` | The test has errored. This is a final state. | +| `canceled` | The test has been canceled. This is a final state. | +| `finished` | The test has completed. This is a final state. | + +### Test run status + +The final test status is found in `data.attributes.test-status`, and you can reference the following list of possible states. + +| Status | Description | +| ------ | ---------------------------- | +| `pass` | The given tests have passed. | +| `fail` | The given tests have failed. | + +### Detailed test status + +The test results can be found via the following attributes + +| Status | Description | | +| ------------------------------- | ------------------------------------------- | - | +| `data.attributes.tests-passed` | The number of tests that have passed. | | +| `data.attributes.tests-failed` | The number of tests that have failed. | | +| `data.attributes.tests-errored` | The number of tests that have errored out. | | +| `data.attributes.tests-skipped` | The number of tests that have been skipped. | | + +### Test Sources + +List tests for a module. You can use the following sources as [tests list](/terraform/enterprise/api-docs/private-registry/tests#list-tests-for-a-module) query parameters. + +| Source | Description | +| --------------------------- | ---------------------------------------------------------------------------------------- | +| `terraform` | Indicates a test was queued from HCP Terraform CLI. | +| `tfe-api` | Indicates a test was queued from HCP Terraform API. | +| `tfe-configuration-version` | Indicates a test was queued from a Configuration Version, triggered from a VCS provider. | + +## Create a Test + +`POST /organizations/:organization_name/tests/registry-modules/private/:namespace/:name/:provider/test-runs` + +| Parameter | Description | +| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `:organization_name` | The name of the organization for the module. The organization must already exist, and the token authenticating the API request must belong to the "owners" team or a member of the "owners" team. | +| `:namespace` | The namespace of the module for which the test is being created. For private modules this is the same as the `:organization_name` parameter. | +| `:name` | The name of the module for which the test is being created. | +| `:provider` | The name of the provider for which the test is being created. | + +A test run executes tests against a registry module, using a configuration version and the modules’s current environment variables. + +Creating a test run requires permission to access the specified module. Refer to [Permissions](/terraform/enterprise/users-teams-organizations/permissions) for more information. + +When creating a test run, you may optionally provide a list of variable objects containing key and value attributes. These values apply to that test run specifically and take precedence over variables with the same key that are created within the module. All values must be expressed as an HCL literal in the same syntax you would use when writing Terraform code. + +**Sample Test Variables:** + +```json +"attributes": { + "variables": [ + { "key": "replicas", "value": "2" }, + { "key": "access_key", "value": "\"ABCDE12345\"" } + ] +} +``` + +### Request Body + +This POST endpoint requires a JSON object with the following properties as a request payload. + +Properties without a default value are required. + +| Key path | Type | Default | Description | +| -------------------------------------------------- | -------------------- | ------------- | -------------------------------------------------------------------------------------------------- | +| `data.attributes.verbose` | bool | `false` | Specifies whether Terraform should print the plan or state for each test run block as it executes. | +| `data.attributes.test-directory` | string | `"tests"` | Sets the directory where HCP Terraform executes the tests. | +| `data.attributes.filters` | array\[string] | (empty array) | When specified, HCP Terraform only executes the test files contained within this array. | +| `data.attributes.variables` | array\[{key, value}] | (empty array) | Specifies an optional list of test-specific environment variable values. | +| `data.relationships.configuration-version.data.id` | string | none | Specifies the configuration version that HCP Terraform executes the test against. | + +### Sample Payload + +```json +{ + "data": { + "attributes": { + "verbose": true, + "filters": ["tests/test.tftest.hcl"], + "test-directory": "tests", + "variables": [ + { "key" : "number", "value": 4} + ] + }, + "type":"test-runs" + } +} +``` + +### Sample Request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request POST \ + --data @payload.json \ + https://app.terraform.io/api/v2/organizations/my-organization/tests/registry-modules/private/my-organization/private/registry-provider/test-runs +``` + +### Sample Response + +```json +{ + "data": { + "id": "trun-KFg8DSiRz4E37mdJ", + "type": "test-runs", + "attributes": { + "status": "queued", + "status-timestamps": { + "queued-at": "2023-10-03T18:27:39+00:00" + }, + "created-at": "2023-10-03T18:27:39.239Z", + "updated-at": "2023-10-03T18:27:39.264Z", + "test-configurable-type": "RegistryModule", + "test-configurable-id": "mod-9rjVHLCUE9QD3k6L", + "variables": [ + { + "key": "number", + "value": "4" + } + ], + "filters": [ + "tests/test.tftest.hcl" + ], + "test-directory": "tests", + "verbose": true, + "test-status": null, + "tests-passed": null, + "tests-failed": null, + "tests-errored": null, + "tests-skipped": null, + "source": "tfe-api", + "message": "Queued manually via the Terraform Enterprise API" + }, + "relationships": { + "configuration-version": { + "data": { + "id": "cv-d3zBGFf5DfWY4GY9", + "type": "configuration-versions" + }, + "links": { + "related": "/api/v2/configuration-versions/cv-d3zBGFf5DfWY4GY9" + } + }, + "created-by": { + "data": { + "id": "user-zsRFs3AGaAHzbEfs", + "type": "users" + }, + "links": { + "related": "/api/v2/users/user-zsRFs3AGaAHzbEfs" + } + } + } + } +} +``` + +## Create a Configuration Version for a Test + +`POST /organizations/:organization_name/tests/registry-modules/private/:namespace/:name/:provider/test-runs/configuration-versions` + +| Parameter | Description | +| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `:organization_name` | The name of the organization for the module. The organization must already exist, and the token authenticating the API request must belong to the "owners" team or a member of the "owners" team. | +| `:namespace` | The namespace of the module for which the configuration version is being created. For private modules this is the same as the `:organization_name` parameter. | +| `:name` | The name of the module for which the configuration version is being created. | +| `:provider` | The name of the provider for which the configuration version is being created. | + +### Sample Request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request POST \ + https://app.terraform.io/api/v2/organizations/my-organization/tests/registry-modules/private/my-organization/registry-name/registry-provider/test-runs/configuration-versions +``` + +### Sample Response + +```json +{ + "data": { + "id": "cv-aaady7niJMY1wAvx", + "type": "configuration-versions", + "attributes": { + "auto-queue-runs": true, + "error": null, + "error-message": null, + "source": "tfe-api", + "speculative": false, + "status": "pending", + "status-timestamps": {}, + "changed-files": [], + "provisional": false, + "upload-url": "https://archivist.terraform.io/v1/object/dmF1bHQ6djM6eFliQ0l1ZEhNUDRMZmdWeExoYWZ1WnFwaCtYQUFSQjFaWVcySkEyT0tyZTZXQ0hjN3ZYQkFvbkJHWkg2Y0U2MDRHRXFvQVl6cUJqQzJ0VkppVHBXTlJNWmpVc1ZTekg5Q1hMZ0hNaUpNdUhib1hGS1RpT3czRGdRaWtPZFZ3VWpDQ1U0S2dhK2xLTUQ2ZFZDaUZ3SktiNytrMlpoVHd0cXdGVHIway8zRkFmejdzMSt0Rm9TNFBTV3dWYjZUTzJVNE1jaW9UZ2VKVFJNRnUvbjBudUp4U0l6VzFDYkNzVVFsb2VFbC9DRFlCTWFsbXBMNzZLUGQxeTJHb09ZTkxHL1d2K1NtcmlEQXptZTh1Q1BwR1dhbVBXQTRiREdlTkI3Qyt1YTRRamFkRzBWYUg3NE52TGpqT1NKbzFrZ3J3QmxnMGhHT3VaTHNhSmo0eXpv" + }, + "relationships": { + "ingress-attributes": { + "data": null, + "links": { + "related": "/api/v2/configuration-versions/cv-aaady7niJMY1wAvx/ingress-attributes" + } + } + }, + "links": { + "self": "/api/v2/configuration-versions/cv-aaady7niJMY1wAvx" + } + } +} +``` + +## Upload Configuration Files for a Test + +`PUT https://archivist.terraform.io/v1/object/` + +**The URL is provided in the `upload-url` attribute when creating a `configuration-versions` resource. After creation, the URL is hidden on the resource and no longer available.** + +### Sample Request + +**@filename is the name of the configuration file you wish to upload.** + +```shell +curl \ + --header "Content-Type: application/octet-stream" \ + --request PUT \ + --data-binary @filename \ + https://archivist.terraform.io/v1/object/4c44d964-eba7-4dd5-ad29-1ece7b99e8da +``` + +## List Tests for a Module + +`GET /organizations/:organization_name/tests/registry-modules/private/:namespace/:name/:provider/test-runs/` + +| Parameter | Description | +| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `:organization_name` | The name of the organization for the module. The organization must already exist, and the token authenticating the API request must belong to the "owners" team or a member of the "owners" team. | +| `:namespace` | The namespace of the module which the tests have executed against. For private modules this is the same as the `:organization_name` parameter. | +| `:name` | The name of the module which the tests have executed against. | +| `:provider` | The name of the provider which the tests have executed against. | + +### Query Parameters + +This endpoint supports pagination [with standard URL query parameters](/terraform/enterprise/api-docs#query-parameters); remember to percent-encode `[` as `%5B` and `]` as `%5D` if your tooling does not automatically encode URLs. + +| Parameter | Description | Required | +| ---------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | +| `page[number]` | If omitted, the endpoint returns the first page. | Optional | +| `page[size]` | If omitted, the endpoint returns 20 runs per page. | Optional | +| `filter[source]` | **Optional.** A comma-separated list of test sources; the result will only include tests that came from one of these sources. Options are listed in [Test Sources](/terraform/enterprise/api-docs/private-registry/tests#test-sources). | | + +### Sample Request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + https://app.terraform.io/api/v2/organizations/my-organization/tests/registry-modules/private/my-organization/registry-name/registry-provider/test-runs +``` + +### Sample Response + +```json +{ + "data": [ + { + "id": "trun-KFg8DSiRz4E37mdJ", + "type": "test-runs", + "attributes": { + "status": "finished", + "status-timestamps": { + "queued-at": "2023-10-03T18:27:39+00:00", + "started-at": "2023-10-03T18:27:41+00:00", + "finished-at": "2023-10-03T18:27:53+00:00" + }, + "log-read-url": "https://archivist.terraform.io/v1/object/dmF1bHQ6djM6eFliQ0l1ZEhNUDRMZmdWeExoYWZ1WnFwaCtYQUFSQjFaWVcySkEyT0tyZTZXQ0hjN3ZYQkFvbkJHWkg2Y0U2MDRHRXFvQVl6cUJqQzJ0VkppVHBXTlJNWmpVc1ZTekg5Q1hMZ0hNaUpNdUhib1hGS1RpT3czRGdRaWtPZFZ3VWpDQ1U0S2dhK2xLTUQ2ZFZDaUZ3SktiNytrMlpoVHd0cXdGVHIway8zRkFmejdzMSt0Rm9TNFBTV3dWYjZUTzJVNE1jaW9UZ2VKVFJNRnUvbjBudUp4U0l6VzFDYkNzVVFsb2VFbC9DRFlCTWFsbXBMNzZLUGQxeTJHb09ZTkxHL1d2K1NtcmlEQXptZTh1Q1BwR1dhbVBXQTRiREdlTkI3Qyt1YTRRamFkRzBWYUg3NE52TGpqT1NKbzFrZ3J3QmxnMGhHT3VaTHNhSmo0eXpv", + "created-at": "2023-10-03T18:27:39.239Z", + "updated-at": "2023-10-03T18:27:53.574Z", + "test-configurable-type": "RegistryModule", + "test-configurable-id": "mod-9rjVHLCUE9QD3k6L", + "variables": [ + { + "key": "number", + "value": "4" + } + ], + "filters": [ + "tests/test.tftest.hcl" + ], + "test-directory": "tests", + "verbose": true, + "test-status": "pass", + "tests-passed": 1, + "tests-failed": 0, + "tests-errored": 0, + "tests-skipped": 0, + "source": "tfe-api", + "message": "Queued manually via the Terraform Enterprise API" + }, + "relationships": { + "configuration-version": { + "data": { + "id": "cv-d3zBGFf5DfWY4GY9", + "type": "configuration-versions" + }, + "links": { + "related": "/api/v2/configuration-versions/cv-d3zBGFf5DfWY4GY9" + } + }, + "created-by": { + "data": { + "id": "user-zsRFs3AGaAHzbEfs", + "type": "users" + }, + "links": { + "related": "/api/v2/users/user-zsRFs3AGaAHzbEfs" + } + } + } + }, + {...} + ] +} +``` + +## Get Test Details + +`GET /organizations/:organization_name/tests/registry-modules/private/:namespace/:name/:provider/test-runs/:test_run_id` + +| Parameter | Description | +| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `:organization_name` | The name of the organization for the module. The organization must already exist, and the token authenticating the API request must belong to the "owners" team or a member of the "owners" team. | +| `:namespace` | The namespace of the module which the test was executed against. For private modules this is the same as the `:organization_name` parameter. | +| `:name` | The name of the module which the test was executed against. | +| `:provider` | The name of the provider which the test was executed against. | +| `:test_run_id` | The test ID to get. | + +### Sample Request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + https://app.terraform.io/api/v2/organizations/my-organization/tests/registry-modules/private/my-organization/registry-name/registry-provider/test-runs/trun-xFMAHM3FhkFBL6Z7 +``` + +### Sample Response + +```json +{ + "data": { + "id": "trun-KFg8DSiRz4E37mdJ", + "type": "test-runs", + "attributes": { + "status": "finished", + "status-timestamps": { + "queued-at": "2023-10-03T18:27:39+00:00", + "started-at": "2023-10-03T18:27:41+00:00", + "finished-at": "2023-10-03T18:27:53+00:00" + }, + "log-read-url": "https://archivist.terraform.io/v1/object/dmF1bHQ6djM6eFliQ0l1ZEhNUDRMZmdWeExoYWZ1WnFwaCtYQUFSQjFaWVcySkEyT0tyZTZXQ0hjN3ZYQkFvbkJHWkg2Y0U2MDRHRXFvQVl6cUJqQzJ0VkppVHBXTlJNWmpVc1ZTekg5Q1hMZ0hNaUpNdUhib1hGS1RpT3czRGdRaWtPZFZ3VWpDQ1U0S2dhK2xLTUQ2ZFZDaUZ3SktiNytrMlpoVHd0cXdGVHIway8zRkFmejdzMSt0Rm9TNFBTV3dWYjZUTzJVNE1jaW9UZ2VKVFJNRnUvbjBudUp4U0l6VzFDYkNzVVFsb2VFbC9DRFlCTWFsbXBMNzZLUGQxeTJHb09ZTkxHL1d2K1NtcmlEQXptZTh1Q1BwR1dhbVBXQTRiREdlTkI3Qyt1YTRRamFkRzBWYUg3NE52TGpqT1NKbzFrZ3J3QmxnMGhHT3VaTHNhSmo0eXpv", + "created-at": "2023-10-03T18:27:39.239Z", + "updated-at": "2023-10-03T18:27:53.574Z", + "test-configurable-type": "RegistryModule", + "test-configurable-id": "mod-9rjVHLCUE9QD3k6L", + "variables": [ + { + "key": "number", + "value": "4" + } + ], + "filters": [ + "tests/test.tftest.hcl" + ], + "test-directory": "tests", + "verbose": true, + "test-status": "pass", + "tests-passed": 1, + "tests-failed": 0, + "tests-errored": 0, + "tests-skipped": 0, + "source": "tfe-api", + "message": "Queued manually via the Terraform Enterprise API" + }, + "relationships": { + "configuration-version": { + "data": { + "id": "cv-d3zBGFf5DfWY4GY9", + "type": "configuration-versions" + }, + "links": { + "related": "/api/v2/configuration-versions/cv-d3zBGFf5DfWY4GY9" + } + }, + "created-by": { + "data": { + "id": "user-zsRFs3AGaAHzbEfs", + "type": "users" + }, + "links": { + "related": "/api/v2/users/user-zsRFs3AGaAHzbEfs" + } + } + } + } +} +``` + +## Cancel a Test + +`POST /organizations/:organization_name/tests/registry-modules/private/:namespace/:name/:provider/test-runs/:test_run_id/cancel` + +| Parameter | Description | +| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `:organization_name` | The name of the organization to create a test in. The organization must already exist, and the token authenticating the API request must belong to the "owners" team or a member of the "owners" team. | +| `:namespace` | The namespace of the module for which the test is being canceled. For private modules this is the same as the `:organization_name` parameter. | +| `:name` | The name of the module for which the test is being canceled. | +| `:provider` | The name of the provider for which the test is being canceled. | +| `:test_run_id` | The test ID to cancel. | + +Use the `cancel` action to interrupt a test that is currently running. The action sends an `INT` signal to the running Terraform process, which instructs Terraform to safely end the tests and attempt to teardown any infrastructure that your tests create. + +| Status | Response | Reason(s) | +| ------- | ------------------------- | ------------------------------------------ | +| [202][] | none | Successfully queued a cancel request. | +| [409][] | [JSON API error object][] | Test was not running; cancel not allowed. | +| [404][] | [JSON API error object][] | Test was not found or user not authorized. | + +### Sample Request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request POST \ + https://app.terraform.io/api/v2/organizations/my-organization/tests/registry-modules/private/my-organization/registry-name/registry-provider/test-runs/trun-xFMAHM3FhkFBL6Z7/cancel +``` + +## Forcefully cancel a Test + +`POST /organizations/:organization_name/tests/registry-modules/private/:namespace/:name/:provider/test-runs/:test_run_id/force-cancel` + +| Parameter | Description | +| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `:organization_name` | The name of the organization for the module. The organization must already exist, and the token authenticating the API request must belong to the `owners` team or a member of the `owners` team. | +| `:namespace` | The namespace of the module for which the test is being force-canceled. For private modules this is the same as the `:organization_name` parameter. | +| `:name` | The name of the module for which the test is being force-canceled. | +| `:provider` | The name of the provider for which the test is being force-canceled. | +| `:test_run_id` | The test ID to cancel. | + +The `force-cancel` action ends the test immediately. Once invoked, Terraform places the test into a `canceled` state and terminates the running Terraform process. + +~> **Warning:** This endpoint has potentially dangerous side-effects, including loss of any in-flight state in the running Terraform process. Use this operation with extreme caution. + +| Status | Response | Reason(s) | +| ------- | ------------------------- | -------------------------------------------------------------- | +| [202][] | none | Successfully queued a cancel request. | +| [409][] | [JSON API error object][] | Test was not running, or has not been canceled non-forcefully. | +| [404][] | [JSON API error object][] | Test was not found or user not authorized. | + +### Sample Request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request POST \ + https://app.terraform.io/api/v2/organizations/my-organization/tests/registry-modules/private/my-organization/registry-name/registry-provider/test-runs/trun-xFMAHM3FhkFBL6Z7/force-cancel +``` + +## Create an Environment Variable for Module Tests + +`POST /organizations/:organization_name/tests/registry-modules/private/:namespace/:name/:provider/vars` + +| Parameter | Description | +| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `:organization_name` | The name of the organization of the module. The organization must already exist, and the token authenticating the API request must belong to the "owners" team or a member of the "owners" team. | +| `:namespace` | The namespace of the module for which the testing environment variable is being created. For private modules this is the same as the `:organization_name` parameter. | +| `:name` | The name of the module for which the testing environment variable is being created. | +| `:provider` | The name of the provider for which the testing environment variable is being created. | + +### Request Body + +This POST endpoint requires a JSON object with the following properties as a request payload. + +Properties without a default value are required. + +| Key path | Type | Default | Description | +| ----------------------------- | ------ | ------- | -------------------------------------------------------------------------------------------------------------------------------------- | +| `data.type` | string | none | Must be `"vars"`. | +| `data.attributes.key` | string | none | The variable's name. Test variable keys must begin with a letter or underscore and can only contain letters, numbers, and underscores. | +| `data.attributes.value` | string | `""` | The value of the variable. | +| `data.attributes.description` | string | none | The description of the variable. | +| `data.attributes.category` | string | none | This must be `"env"`. | +| `data.attributes.sensitive` | bool | `false` | Whether the value is sensitive. When set to `true`, Terraform writes the variable once and is not visible thereafter. | + +### Sample Payload + +```json +{ + "data": { + "type":"vars", + "attributes": { + "key":"some_key", + "value":"some_value", + "description":"some description", + "category":"env", + "sensitive":false + } + } +} +``` + +### Sample Request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request POST \ + --data @payload.json \ + https://app.terraform.io/api/v2/organizations/my-organization/tests/registry-modules/private/my-organization/registry-name/registry-provider/vars +``` + +### Sample Response + + { + "data": { + "id": "var-xSCUzCxdqMs2ygcg", + "type": "vars", + "attributes": { + "key": "keykey", + "value": "some_value", + "sensitive": false, + "category": "env", + "hcl": false, + "created-at": "2023-10-03T19:47:05.393Z", + "description": "some description", + "version-id": "699b14ea5d5e5c02f6352fac6bfd0a1424c21d32be14d1d9eb79f5e1f28f663a" + }, + "links": { + "self": "/api/v2/vars/var-xSCUzCxdqMs2ygcg" + } + } + } + +## List Test Variables for a Module + +`GET /organizations/:organization_name/tests/registry-modules/private/:namespace/:name/:provider/vars` + +| Parameter | Description | +| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `:organization_name` | The name of the organization for the module. The organization must already exist, and the token authenticating the API request must belong to the `owners` team or a member of the `owners` team. | +| `:namespace` | The namespace of the module which the test environment variables were created for. For private modules this is the same as the `:organization_name` parameter. | +| `:name` | The name of the module which the test environment variables were created for. | +| `:provider` | The name of the provider which the test environment variables were created for. | + +### Sample Request + +```shell +$ curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + https://app.terraform.io/api/v2/organizations/my-organization/tests/registry-modules/private/my-organization/registry-name/registry-provider/vars +``` + +### Sample Response + +```json +{ + "data": [ + { + "id": "var-xSCUzCxdqMs2ygcg", + "type": "vars", + "attributes": { + "key": "keykey", + "value": "some_value", + "sensitive": false, + "category": "env", + "hcl": false, + "created-at": "2023-10-03T19:47:05.393Z", + "description": "some description", + "version-id": "699b14ea5d5e5c02f6352fac6bfd0a1424c21d32be14d1d9eb79f5e1f28f663a" + }, + "links": { + "self": "/api/v2/vars/var-xSCUzCxdqMs2ygcg" + } + } + ] +} +``` + +## Update Test Variables for a Module + +`PATCH /organizations/:organization_name/tests/registry-modules/private/:namespace/:name/:provider/vars/variable_id` + +| Parameter | Description | +| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `:organization_name` | The name of the organization for the module. The organization must already exist, and the token authenticating the API request must belong to the "owners" team or a member of the "owners" team. | +| `:namespace` | The namespace of the module for which the test environment variable is being updated. For private modules this is the same as the `:organization_name` parameter. | +| `:name` | The name of the module for which the test environment variable is being updated. | +| `:provider` | The name of the provider for which the test environment variable is being updated. | +| `:variable_id` | The ID of the variable to update. | + +### Request Body + +This PATCH endpoint requires a JSON object with the following properties as a request payload. + +Properties without a default value are required. + +| Key path | Type | Default | Description | +| ----------------- | ------ | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `data.type` | string | | Must be `"vars"`. | +| `data.attributes` | object | none | New attributes for the variable. This object can include `key`, `value`, `description`, `category`, and `sensitive` properties. Refer to [Create an Environment Variable for Module Tests](#create-an-environment-variable-for-module-tests) for additional information. All properties are optional. | + +### Sample Payload + +```json +{ + "data": { + "attributes": { + "key":"name", + "value":"mars", + "description": "new description", + "category":"env", + "sensitive": false + }, + "type":"vars" + } +} +``` + +### Sample Request + +```bash +$ curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request PATCH \ + --data @payload.json \ + https://app.terraform.io/api/v2/organizations/my-organization/tests/registry-modules/private/my-organization/registry-name/registry-provider/vars/var-yRmifb4PJj7cLkMG +``` + +### Sample Response + +```json +{ + "data": { + "id":"var-yRmifb4PJj7cLkMG", + "type":"vars", + "attributes": { + "key":"name", + "value":"mars", + "description":"new description", + "sensitive":false, + "category":"env", + "hcl":false + } + } +} +``` + +## Delete Test Variable for a Module + +`DELETE /organizations/:organization_name/tests/registry-modules/private/:namespace/:name/:provider/vars/variable_id` + +| Parameter | Description | +| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `:organization_name` | The name of the organization for the module. The organization must already exist, and the token authenticating the API request must belong to the `owners` team or a member of the `owners` team. | +| `:namespace` | The namespace of the module for which the test environment variable is being deleted. For private modules this is the same as the `:organization_name` parameter. | +| `:name` | The name of the module for which the test environment variable is being deleted. | +| `:provider` | The name of the provider for which the test environment variable is being deleted. | +| `:variable_id` | The ID of the variable to delete. | + +### Sample Request + +```bash +$ curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request DELETE \ + https://app.terraform.io/api/v2/organizations/my-organization/tests/registry-modules/private/my-organization/registry-name/registry-provider/vars/var-yRmifb4PJj7cLkMG +``` diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/project-team-access.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/project-team-access.mdx new file mode 100644 index 0000000000..84b479ca00 --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/project-team-access.mdx @@ -0,0 +1,523 @@ +--- +page_title: /team-projects API reference for Terraform Enterprise +description: >- + Use the Terraform Enterprise API's `/team-projects` endpoint to read, add, + update, and remove team access from a project. +source: terraform-docs-common +--- + +[200]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/200 + +[201]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/201 + +[202]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/202 + +[204]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/204 + +[400]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/400 + +[401]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/401 + +[403]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/403 + +[404]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404 + +[409]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/409 + +[412]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/412 + +[422]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/422 + +[429]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/429 + +[500]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500 + +[504]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/504 + +[JSON API document]: /terraform/enterprise/api-docs#json-api-documents + +[JSON API error object]: https://jsonapi.org/format/#error-objects + +# Project team access API reference + + + +@include 'tfc-package-callouts/project-permissions.mdx' + + + +The team access APIs are used to associate a team to permissions on a project. A single `team-project` resource contains the relationship between the Team and Project, including the privileges the team has on the project. + +## Resource permissions + +A `team-project` resource represents a team's local permissions on a specific project. Teams can also have organization-level permissions that grant access to projects. HCP Terraform uses the more restrictive access level. For example, a team with the **Manage projects** permission enabled has admin access on all projects, even if their `team-project` on a particular project only grants read access. For more information, refer to [Managing Project Access](/terraform/enterprise/users-teams-organizations/teams/manage#managing-project-access). + +Any member of an organization can view team access relative to their own team memberships, including secret teams of which they are a member. Organization owners and project admins can modify team access or view the full set of secret team accesses. The organization token and the owners team token can act as an owner on these endpoints. Refer to [Permissions](/terraform/enterprise/users-teams-organizations/permissions) for additional information. + +## Project Team Access Levels + +| Access Level | Description | +| ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `read` | Read project and Read workspace access role on project workspaces | +| `write` | Read project and Write workspace access role on project workspaces | +| `maintain` | Read project and Admin workspace access role on project workspaces | +| `admin` | Admin project, Admin workspace access role on project workspaces, create workspaces within project, move workspaces between projects, manage project team access | +| `custom` | Custom access permissions on project and project's workspaces | + +## List Team Access to a Project + +`GET /team-projects` + +| Status | Response | Reason | +| ------- | ----------------------------------------------- | -------------------------------------------------------- | +| [200][] | [JSON API document][] (`type: "team-projects"`) | The request was successful | +| [404][] | [JSON API error object][] | Project not found or user unauthorized to perform action | + +### Query Parameters + +[These are standard URL query parameters](/terraform/enterprise/api-docs#query-parameters); remember to percent-encode `[` as `%5B` and `]` as `%5D` if your tooling doesn't automatically encode URLs. + +This endpoint supports pagination [with standard URL query parameters](/terraform/enterprise/api-docs#query-parameters). + +| Parameter | Description | +| --------------------- | ----------------------------------------------------- | +| `filter[project][id]` | **Required.** The project ID to list team access for. | +| `page[number]` | **Optional.** | +| `page[size]` | **Optional.** | + +### Sample Request + +```shell +$ curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request GET \ + "https://app.terraform.io/api/v2/team-projects?filter%5Bproject%5D%5Bid%5D=prj-ckZoJwdERaWcFHwi" +``` + +### Sample Response + +```json +{ + "data": [ + { + "id": "tprj-TLznAnYdcsD2Dcmm", + "type": "team-projects", + "attributes": { + "access": "read", + "project-access": { + "settings": "read", + "teams": "none" + }, + "workspace-access": { + "create": false, + "move": false, + "locking": false, + "delete": false, + "runs": "read", + "variables": "read", + "state-versions": "read", + "sentinel-mocks": "none", + "run-tasks": false + } + }, + "relationships": { + "team": { + "data": { + "id": "team-KpibQGL5GqRAWBwT", + "type": "teams" + }, + "links": { + "related": "/api/v2/teams/team-KpibQGL5GqRAWBwT" + } + }, + "project": { + "data": { + "id": "prj-ckZoJwdERaWcFHwi", + "type": "projects" + }, + "links": { + "related": "/api/v2/projects/prj-ckZoJwdERaWcFHwi" + } + } + }, + "links": { + "self": "/api/v2/team-projects/tprj-TLznAnYdcsD2Dcmm" + } + } + ], + "links": { + "self": "https://app.terraform.io/api/v2/team-projects?filter%5Bproject%5D%5Bid%5D=prj-ckZoJwdERaWcFHwi&page%5Bnumber%5D=1&page%5Bsize%5D=20", + "first": "https://app.terraform.io/api/v2/team-projects?filter%5Bproject%5D%5Bid%5D=prj-ckZoJwdERaWcFHwi&page%5Bnumber%5D=1&page%5Bsize%5D=20", + "prev": null, + "next": null, + "last": "https://app.terraform.io/api/v2/team-projects?filter%5Bproject%5D%5Bid%5D=prj-ckZoJwdERaWcFHwi&page%5Bnumber%5D=1&page%5Bsize%5D=20" + }, + "meta": { + "pagination": { + "current-page": 1, + "page-size": 20, + "prev-page": null, + "next-page": null, + "total-pages": 1, + "total-count": 1 + } + } +} +``` + +## Show a Team Access relationship + +`GET /team-projects/:id` + +| Status | Response | Reason | +| ------- | ----------------------------------------------- | ------------------------------------------------------------ | +| [200][] | [JSON API document][] (`type: "team-projects"`) | The request was successful | +| [404][] | [JSON API error object][] | Team access not found or user unauthorized to perform action | + +| Parameter | Description | +| --------- | ---------------------------------------------------------------------------------------------------------------------------------------- | +| `:id` | The ID of the team/project relationship. Obtain this from the [list team access action](#list-team-access-to-a-project) described above. | + +As mentioned in [Add Team Access to a Project](#add-team-access-to-a-project) and [Update to a Project](#update-team-access-to-a-project), several permission attributes are not editable unless you set `access` to `custom`. If you set `access` to `read`, `plan`, `write`, or `admin`, certain attributes are read-only and reflect the _implicit permissions_ granted to the current access level. + +For example, if you set `access` to `read`, the implicit permission level for project settings and workspace run is "read". Conversely, if you set the access level to `admin`, the implicit permission level for the project settings is "delete", while the workspace runs permission is "apply". + +Several permission attributes are not editable unless `access` is set to `custom`. When access is `read`, `plan`, `write`, or `admin`, these attributes are read-only and reflect the implicit permissions granted to the current access level. + +For example, when access is `read`, the implicit level for the project settings and workspace runs permissions are "read". Conversely, when the access level is `admin`, the implicit level for the project settings is "delete" and the workspace runs permission is "apply". To see all of the implied permissions at different access levels, see [Implied Custom Permission Levels](#implied-custom-permission-levels). + +### Sample Request + +```shell +$ curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request GET \ + https://app.terraform.io/api/v2/team-projects/tprj-s68jV4FWCDwWvQq8 +``` + +### Sample Response + +```json +{ + "data": { + "id": "tprj-TLznAnYdcsD2Dcmm", + "type": "team-projects", + "attributes": { + "access": "read", + "project-access": { + "settings": "read", + "teams": "none" + }, + "workspace-access": { + "create": false, + "move": false, + "locking": false, + "delete": false, + "runs": "read", + "variables": "read", + "state-versions": "read", + "sentinel-mocks": "none", + "run-tasks": false + } + }, + "relationships": { + "team": { + "data": { + "id": "team-KpibQGL5GqRAWBwT", + "type": "teams" + }, + "links": { + "related": "/api/v2/teams/team-KpibQGL5GqRAWBwT" + } + }, + "project": { + "data": { + "id": "prj-ckZoJwdERaWcFHwi", + "type": "projects" + }, + "links": { + "related": "/api/v2/projects/prj-ckZoJwdERaWcFHwi" + } + } + }, + "links": { + "self": "/api/v2/team-projects/tprj-TLznAnYdcsD2Dcmm" + } + } +} +``` + +## Add Team Access to a Project + +`POST /team-projects` + +| Status | Response | Reason | +| ------- | ----------------------------------------------- | ---------------------------------------------------------------- | +| [200][] | [JSON API document][] (`type: "team-projects"`) | The request was successful | +| [404][] | [JSON API error object][] | Project or Team not found or user unauthorized to perform action | +| [422][] | [JSON API error object][] | Malformed request body (missing attributes, wrong types, etc.) | + +### Request Body + +This POST endpoint requires a JSON object with the following properties as a request payload. + +Properties without a default value are required. + +| Key path | Type | Default | Description | +| ------------------------------------------------- | ------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `data.type` | string | | Must be `"team-projects"`. | +| `data.attributes.access` | string | | The type of access to grant. Valid values are `read`, `write`, `maintain`, `admin`, or `custom`. | +| `data.relationships.project.data.type` | string | | Must be `projects`. | +| `data.relationships.project.data.id` | string | | The project ID to which the team is to be added. | +| `data.relationships.team.data.type` | string | | Must be `teams`. | +| `data.relationships.team.data.id` | string | | The ID of the team to add to the project. | +| `data.attributes.project-access.settings` | string | "read" | If `access` is `custom`, the permission to grant for the project's settings. Can only be used when `access` is `custom`. Valid values include `read`, `update`, or `delete`. | +| `data.attributes.project-access.teams` | string | "none" | If `access` is `custom`, the permission to grant for the project's teams. Can only be used when `access` is `custom`. Valid values include `none`, `read`, or `manage`. | +| `data.attributes.workspace-access.runs` | string | "read" | If `access` is `custom`, the permission to grant for the project's workspaces' runs. Can only be used when `access` is `custom`. Valid values include `read`, `plan`, or `apply`. | +| `data.attributes.workspace-access.sentinel-mocks` | string | "none" | If `access` is `custom`, the permission to grant for the project's workspaces' Sentinel mocks. Can only be used when `access` is `custom`. Valid values include `none`, or `read`. | +| `data.attributes.workspace-access.state-versions` | string | "none" | If `access` is `custom`, the permission to grant for the project's workspaces state versions. Can only be used when `access` is `custom`. Valid values include `none`, `read-outputs`, `read`, or `write`. | +| `data.attributes.workspace-access.variables` | string | "none" | If `access` is `custom`, the permission to grant for the project's workspaces' variables. Can only be used when `access` is `custom`. Valid values include `none`, `read`, or `write`. | +| `data.attributes.workspace-access.create` | boolean | false | If `access` is `custom`, this permission allows the team to create workspaces in the project. | +| `data.attributes.workspace-access.locking` | boolean | false | If `access` is `custom`, the permission granting the ability to manually lock or unlock the project's workspaces. Can only be used when `access` is `custom`. | +| `data.attributes.workspace-access.delete` | boolean | false | If `access` is `custom`, the permission granting the ability to delete the project's workspaces. Can only be used when `access` is `custom`. | +| `data.attributes.workspace-access.move` | boolean | false | If `access` is `move`, this permission allows the team to move workspaces into and out of the project. The team must also have permissions to the project(s) receiving the the workspace(s). | +| `data.attributes.workspace-access.run-tasks` | boolean | false | If `access` is `custom`, this permission allows the team to manage run tasks within the project's workspaces. | + +### Sample Payload + +```json +{ + "data": { + "attributes": { + "access": "read" + }, + "relationships": { + "project": { + "data": { + "type": "projects", + "id": "prj-ckZoJwdERaWcFHwi" + } + }, + "team": { + "data": { + "type": "teams", + "id": "team-xMGyoUhKmTkTzmAy" + } + } + }, + "type": "team-projects" + } +} +``` + +### Sample Request + +```shell +$ curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request POST \ + --data @payload.json \ + https://app.terraform.io/api/v2/team-projects +``` + +### Sample Response + +```json +{ + "data": { + "id": "tprj-WbG7p5KnT7S7HZqw", + "type": "team-projects", + "attributes": { + "access": "read", + "project-access": { + "settings": "read", + "teams": "none" + }, + "workspace-access": { + "create": false, + "move": false, + "locking": false, + "runs": "read", + "variables": "read", + "state-versions": "read", + "sentinel-mocks": "none", + "run-tasks": false + } + }, + "relationships": { + "team": { + "data": { + "id": "team-xMGyoUhKmTkTzmAy", + "type": "teams" + }, + "links": { + "related": "/api/v2/teams/team-xMGyoUhKmTkTzmAy" + } + }, + "project": { + "data": { + "id": "prj-ckZoJwdERaWcFHwi", + "type": "projects" + }, + "links": { + "related": "/api/v2/projects/prj-ckZoJwdERaWcFHwi" + } + } + }, + "links": { + "self": "/api/v2/team-projects/tprj-WbG7p5KnT7S7HZqw" + } + } +} +``` + +## Update Team Access to a Project + +`PATCH /team-projects/:id` + +| Status | Response | Reason | +| ------- | ----------------------------------------------- | -------------------------------------------------------------- | +| [200][] | [JSON API document][] (`type: "team-projects"`) | The request was successful | +| [404][] | [JSON API error object][] | Team Access not found or user unauthorized to perform action | +| [422][] | [JSON API error object][] | Malformed request body (missing attributes, wrong types, etc.) | + +| Parameter | | | Description | +| ------------------------ | ------ | - | ---------------------------------------------------------------------------------------------------------------------------------------- | +| `:id` | | | The ID of the team/project relationship. Obtain this from the [list team access action](#list-team-access-to-a-project) described above. | +| `data.attributes.access` | string | | The type of access to grant. Valid values are `read`, `write`, `maintain`, `admin`, or `custom`. | + +### Sample Request + +```shell +$ curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request PATCH \ + --data @payload.json \ + https://app.terraform.io/api/v2/team-projects/tprj-WbG7p5KnT7S7HZqw +``` + +### Sample Payload + +```json +{ + "data": { + "id": "tprj-WbG7p5KnT7S7HZqw", + "attributes": { + "access": "custom", + "project-access": { + "settings": "delete" + "teams": "manage", + }, + "workspace-access" : { + "runs": "apply", + "sentinel-mocks": "read", + "state-versions": "write", + "variables": "write", + "create": true, + "locking": true, + "delete": true, + "move": true, + "run-tasks": true + } + } + } +} +``` + +### Sample Response + +```json +{ + "data": { + "id": "tprj-WbG7p5KnT7S7HZqw", + "type": "team-projects", + "attributes": { + "access": "custom", + "project-access": { + "settings": "delete" + "teams": "manage", + }, + "workspace-access" : { + "runs": "apply", + "sentinel-mocks": "read", + "state-versions": "write", + "variables": "write", + "create": true, + "locking": true, + "delete": true, + "move": true, + "run-tasks": true + } + }, + "relationships": { + "team": { + "data": { + "id": "team-xMGyoUhKmTkTzmAy", + "type": "teams" + }, + "links": { + "related": "/api/v2/teams/team-xMGyoUhKmTkTzmAy" + } + }, + "project": { + "data": { + "id": "prj-ckZoJwdERaWcFHwi", + "type": "projects" + }, + "links": { + "related": "/api/v2/projects/prj-ckZoJwdERaWcFHwi" + } + } + }, + "links": { + "self": "/api/v2/team-projects/tprj-WbG7p5KnT7S7HZqw" + } + } +} +``` + +## Remove Team Access from a Project + +`DELETE /team-projects/:id` + +| Status | Response | Reason | +| ------- | ------------------------- | ------------------------------------------------------------ | +| [204][] | | The Team Access was successfully destroyed | +| [404][] | [JSON API error object][] | Team Access not found or user unauthorized to perform action | + +| Parameter | Description | +| --------- | ---------------------------------------------------------------------------------------------------------------------------------------- | +| `:id` | The ID of the team/project relationship. Obtain this from the [list team access action](#list-team-access-to-a-project) described above. | + +### Sample Request + +```shell +$ curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request DELETE \ + https://app.terraform.io/api/v2/team-projects/tprj-WbG7p5KnT7S7HZqw +``` + +## Implied Custom Permission Levels + +As mentioned above, when setting team access levels (`read`, `write`, `maintain`, or `admin`), you can individually set the following permissions if you use the `custom` access level. +The below table lists each access level alongside its implicit custom permission level. If you use the `custom` access level and do not specify a certain permission's level, that permission uses the default value listed below. + +| Permissions | `read` | `write` | `maintain` | `admin ` | `custom` default | +| ------------------------------- | ------ | ------- | ---------- | -------- | ---------------- | +| project-access.settings | "read" | "read" | "read" | "delete" | "read" | +| project-access.teams | "none" | "none" | "none" | "manage" | "none" | +| workspace-access.runs | "read" | "apply" | "apply" | "apply" | "read" | +| workspace-access.sentinel-mocks | "none" | "read" | "read" | "read" | "none" | +| workspace-access.state-versions | "read" | "write" | "write" | "write" | "none" | +| workspace-access.variables | "read" | "write" | "write" | "write" | "none" | +| workspace-access.create | false | false | true | true | false | +| workspace-access.locking | false | true | true | true | false | +| workspace-access.delete | false | false | true | true | false | +| workspace-access.move | false | false | false | true | false | +| workspace-access.run-tasks | false | false | true | true | false | diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/projects.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/projects.mdx new file mode 100644 index 0000000000..ef2f797665 --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/projects.mdx @@ -0,0 +1,718 @@ +--- +page_title: /projects API reference for Terraform Enterprise +description: >- + Use the Terraform Enterprise API's `/projects` endpoint to list, show, create, + update, and delete an organization's projects. +source: terraform-docs-common +--- + +[200]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/200 + +[201]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/201 + +[202]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/202 + +[204]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/204 + +[400]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/400 + +[401]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/401 + +[403]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/403 + +[404]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404 + +[409]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/409 + +[412]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/412 + +[422]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/422 + +[429]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/429 + +[500]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500 + +[504]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/504 + +[JSON API document]: /terraform/enterprise/api-docs#json-api-documents + +[JSON API error object]: https://jsonapi.org/format/#error-objects + +[speculative plans]: /terraform/enterprise/run/remote-operations#speculative-plans + +# Projects API reference + +This topic provides reference information about the projects API. + +The scope of the API includes the following endpoints: + +| Method | Path | Action | +| -------- | ---------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `POST` | `/organizations/:organization_name/projects` | Call this endpoint to [create a project](#create-a-project). | +| `PATCH` | `/projects/:project_id` | Call this endpoint to [update an existing project](#update-a-project). | +| `GET` | `/organizations/:organization_name/projects` | Call this endpoint to [list existing projects](#list-projects). | +| `GET` | `/projects/:project_id` | Call this endpoint to [show project details](#show-project). | +| `DELETE` | `/projects/:project_id` | Call this endpoint to [delete a project](#delete-a-project). | +| `GET` | `/projects/:project_id/tag-bindings` | Call this endpoint to [list project tag bindings](#list-project-tag-bindings). (For projects, this returns the same result set as the `effective-tag-bindings` endpoint.) | +| `GET` | `/projects/:project_id/effective-tag-bindings` | Call this endpoint to [list project effective tag bindings](#list-project-tag-bindings). (For projects, this returns the same result set as the `tag-bindings` endpoint.) | + +## Requirements + +You must be on a team with one of the **Owners** or **Manage projects** permissions settings enabled to create and manage projects. Refer to [Permissions](/terraform/enterprise/users-teams-organizations/permissions) for additional information. + +You can also provide an organization API token to call project API endpoints. Refer to [Organization API Tokens](/terraform/enterprise/users-teams-organizations/api-tokens#organization-api-tokens) for additional information. + +## Create a Project + +`POST /organizations/:organization_name/projects` + +| Parameter | Description | +| -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `:organization_name` | The name of the organization to create the project in. The organization must already exist in the system, and the user must have permissions to create new projects. | + +### Request Body + +This POST endpoint requires a JSON object with the following properties as a request payload. + +Properties without a default value are required. + +| Key path | Type | Default | Description | +| ------------------------------------------------------- | --------------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `data.type` | string | none | Must be `"projects"`. | +| `data.attributes.name` | string | | The name of the project. The name can contain letters, numbers, spaces, `-`, and `_`, but cannot start or end with spaces. It must be at least three characters long and no more than 40 characters long. | +| `data.attributes.description` | string | none | Optional. The description of the project. It must be no more than 256 characters long. | +| `data.attributes.auto-destroy-activity-duration` | string | none | Specifies the default for how long each workspace in the project should wait before automatically destroying its infrastructure. You can specify a duration up to four digits that is greater than `0` followed by either a `d` for days or `h` hours. For example, to queue destroy runs after fourteen days of inactivity set `auto-destroy-activity-duration: "14d"`. All future workspaces in this project inherit this default value. Refer to [Automatically destroy inactive workspaces](/terraform/enterprise/projects/managing#automatically-destroy-inactive-workspaces) for additional information. | +| `data.relationships.tag-bindings.data` | list of objects | none | Specifies a list of tags to bind to the project. Workspaces inherit the tags bound to their project. | +| `data.relationships.tag-bindings.data.type` | string | none | Must be `tag-bindings` for each object in the list. | +| `data.relationships.tag-bindings.data.attributes.key` | string | none | Specifies the tag key for each object in the list. | +| `data.relationships.tag-bindings.data.attributes.value` | string | none | Specifies the tag value for each object in the list. | + +### Sample Payload + +```json +{ + "data": { + "attributes": { + "name": "Test Project", + "description": "An example project for documentation.", + }, + "type": "projects", + "relationships": { + "tag-bindings": { + "data": [ + { + "type": "tag-bindings", + "attributes": { + "key": "environment", + "value": "development" + } + }, + ] + } + } + } +} +``` + +### Sample Request + +```shell-session +$ curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request POST \ + --data @payload.json \ + https://app.terraform.io/api/v2/organizations/my-organization/projects +``` + +### Sample Response + +```json +{ + "data": { + "id": "prj-WsVcWRr7SfxRci1v", + "type": "projects", + "attributes": { + "name": "Test Project", + "description": "An example project for documentation.", + "permissions": { + "can-update": true + } + }, + "relationships": { + "organization": { + "data": { + "id": "my-organization", + "type": "organizations" + }, + "links": { + "related": "/api/v2/organizations/my-organization" + } + }, + "tag-bindings": { + "links": { + "related": "/api/v2/projects/prj-WsVcWRr7SfxRci1v/tag-bindings" + } + }, + "effective-tag-bindings": { + "links": { + "related": "/api/v2/projects/prj-WsVcWRr7SfxRci1v/effective-tag-bindings" + } + } + }, + "links": { + "self": "/api/v2/projects/prj-WsVcWRr7SfxRci1v" + } + } +} +``` + +## Update a Project + +Call the following endpoint to update a project: + +`PATCH /projects/:project_id` + +| Parameter | Description | +| ------------- | ------------------------------- | +| `:project_id` | The ID of the project to update | + +### Request Body + +These PATCH endpoints require a JSON object with the following properties as a request payload. + +Properties without a default value are required. + +| Key path | Type | Default | Description | +| ------------------------------------------------------- | --------------- | -------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `data.type` | string | none | Must be `"projects"`. | +| `data.attributes.name` | string | existing value | A new name for the project. The name can contain letters, numbers, spaces, `-`, and `_`, but cannot start or end with spaces. It must be at least 3 characters long and no more than 40 characters long. | +| `data.attributes.description` | string | existing value | The new description for the project. It must be no more than 256 characters long. | +| `data.attributes.auto-destroy-activity-duration` | string | none | Specifies how long each workspace in the project should wait before automatically destroying its infrastructure by default. You can specify a duration up to four digits that is greater than `0` followed by either a `d` for days or `h` hours. For example, to queue destroy runs after fourteen days of inactivity set `auto-destroy-activity-duration: "14d"`. When you update this value, all workspaces in the project receive the new value unless you previously configured an override. Refer to [Automatically destroy inactive workspaces](/terraform/enterprise/projects/managing#automatically-destroy-inactive-workspaces) for additional information. | +| `data.relationships.tag-bindings.data` | list of objects | none | Specifies a list of tags to bind to the project. Workspaces inherit the tags bound to their project. | +| `data.relationships.tag-bindings.data.type` | string | none | Must be `tag-bindings` for each object in the list. | +| `data.relationships.tag-bindings.data.attributes.key` | string | none | Specifies the tag key for each object in the list. | +| `data.relationships.tag-bindings.data.attributes.value` | string | none | Specifies the tag value for each object in the list. | + +### Sample Payload + +```json +{ + "data": { + "attributes": { + "name": "Infrastructure Project" + }, + "type": "projects", + "relationships": { + "tag-bindings": { + "data": [ + { + "type": "tag-bindings", + "attributes": { + "key": "environment", + "value": "staging" + } + } + ] + } + } + } +} +``` + +### Sample Request + +```shell-session +$ curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request PATCH \ + --data @payload.json \ + https://app.terraform.io/api/v2/projects/prj-WsVcWRr7SfxRci1v +``` + +### Sample Response + +```json +{ + "data": { + "id": "prj-WsVcWRr7SfxRci1v", + "type": "projects", + "attributes": { + "name": "Infrastructure Project", + "description": null, + "workspace-count": 4, + "team-count": 2, + "permissions": { + "can-update": true, + "can-destroy": true, + "can-create-workspace": true + } + }, + "relationships": { + "organization": { + "data": { + "id": "my-organization", + "type": "organizations" + }, + "links": { + "related": "/api/v2/organizations/my-organization" + } + }, + "tag-bindings": { + "links": { + "related": "/api/v2/projects/prj-WsVcWRr7SfxRci1v/tag-bindings" + } + }, + "effective-tag-bindings": { + "links": { + "related": "/api/v2/projects/prj-WsVcWRr7SfxRci1v/effective-tag-bindings" + } + } + }, + "links": { + "self": "/api/v2/projects/prj-WsVcWRr7SfxRci1v" + } + } +} +``` + +## List projects + +This endpoint lists projects in the organization. + +`GET /organizations/:organization_name/projects` + +| Parameter | Description | +| -------------------- | ----------------------------------------------------- | +| `:organization_name` | The name of the organization to list the projects of. | + +### Query Parameters + +This endpoint supports pagination [with standard URL query parameters](/terraform/enterprise/api-docs#query-parameters). Remember to percent-encode `[` as `%5B` and `]` as `%5D` if your tooling doesn't automatically encode URLs. + +| Parameter | Description | +| --------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `page[number]` | **Optional.** If omitted, the endpoint will return the first page. | +| `page[size]` | **Optional.** If omitted, the endpoint will return 20 projects per page. | +| `q` | **Optional.** A search query string. This query searches projects by name. This search is case-insensitive. If both `q` and `filter[names]` are specified, `filter[names]` will be used. | +| `filter[names]` | **Optional.** If specified, returns the project with the matching name. This filter is case-insensitive. If multiple comma separated values are specified, projects matching any of the names are returned. | +| `filter[permissions][create-workspace]` | **Optional.** If present, returns a list of projects that the authenticated user can create workspaces in. | +| `filter[permissions][update]` | **Optional.** If present, returns a list of projects that the authenticated user can update. | +| `sort` | **Optional.** Allows sorting the organization's projects by `"name"`. Prepending a hyphen to the sort parameter reverses the order. For example, `"-name"` sorts by name in reverse alphabetical order. If omitted, the default sort order is arbitrary but stable. | + +### Sample Request + +```shell-session +$ curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + https://app.terraform.io/api/v2/organizations/my-organization/projects +``` + +### Sample Response + +```json +{ + "data": [ + { + "id": "prj-W6k9K23oSXRHGpj3", + "type": "projects", + "attributes": { + "name": "Default Project", + "description": null, + "workspace-count": 2, + "team-count": 1, + "permissions": { + "can-update": true, + "can-destroy": true, + "can-create-workspace": true + } + }, + "relationships": { + "organization": { + "data": { + "id": "my-organization", + "type": "organizations" + }, + "links": { + "related": "/api/v2/organizations/my-organization" + } + }, + "tag-bindings": { + "links": { + "related": "/api/v2/projects/prj-W6k9K23oSXRHGpj3/tag-bindings" + } + }, + "effective-tag-bindings": { + "links": { + "related": "/api/v2/projects/prj-W6k9K23oSXRHGpj3/effective-tag-bindings" + } + } + }, + "links": { + "self": "/api/v2/projects/prj-W6k9K23oSXRHGpj3" + } + }, + { + "id": "prj-YoriCxAawTMDLswn", + "type": "projects", + "attributes": { + "name": "Infrastructure Project", + "description": null, + "workspace-count": 4, + "team-count": 2, + "permissions": { + "can-update": true, + "can-destroy": true, + "can-create-workspace": true + } + }, + "relationships": { + "organization": { + "data": { + "id": "my-organization", + "type": "organizations" + }, + "links": { + "related": "/api/v2/organizations/my-organization" + } + }, + "tag-bindings": { + "links": { + "related": "/api/v2/projects/prj-YoriCxAawTMDLswn/tag-bindings" + } + }, + "effective-tag-bindings": { + "links": { + "related": "/api/v2/projects/prj-YoriCxAawTMDLswn/effective-tag-bindings" + } + } + }, + "links": { + "self": "/api/v2/projects/prj-YoriCxAawTMDLswn" + } + } + ], + "links": { + "self": "https://app.terraform.io/api/v2/organizations/my-organization/projects?page%5Bnumber%5D=1&page%5Bsize%5D=20", + "first": "https://app.terraform.io/api/v2/organizations/my-organization/projects?page%5Bnumber%5D=1&page%5Bsize%5D=20", + "prev": null, + "next": null, + "last": "https://app.terraform.io/api/v2/organizations/my-organization/projects?page%5Bnumber%5D=1&page%5Bsize%5D=20" + }, + "meta": { + "status-counts": { + "total": 2, + "matching": 2 + }, + "pagination": { + "current-page": 1, + "page-size": 20, + "prev-page": null, + "next-page": null, + "total-pages": 1, + "total-count": 2 + } + } +} +``` + +## Show project + +`GET /projects/:project_id` + +| Parameter | Description | +| ------------- | -------------- | +| `:project_id` | The project ID | + +### Sample Request + +```shell-session +$ curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + https://app.terraform.io/api/v2/projects/prj-WsVcWRr7SfxRci1v +``` + +### Sample Response + +```json +{ + "data": { + "id": "prj-WsVcWRr7SfxRci1v", + "type": "projects", + "attributes": { + "name": "Infrastructure Project", + "description": null, + "workspace-count": 4, + "team-count": 2, + "permissions": { + "can-update": true, + "can-destroy": true, + "can-create-workspace": true + }, + "auto-destroy-activity-duration": "2d" + }, + "relationships": { + "organization": { + "data": { + "id": "my-organization", + "type": "organizations" + }, + "links": { + "related": "/api/v2/organizations/my-organization" + } + }, + "tag-bindings": { + "links": { + "related": "/api/v2/projects/prj-WsVcWRr7SfxRci1v/tag-bindings" + } + }, + "effective-tag-bindings": { + "links": { + "related": "/api/v2/projects/prj-WsVcWRr7SfxRci1v/effective-tag-bindings" + } + } + }, + "links": { + "self": "/api/v2/projects/prj-WsVcWRr7SfxRci1v" + } + } +} +``` + +## Delete a project + +A project cannot be deleted if it contains stacks or workspaces. + +`DELETE /projects/:project_id` + +| Parameter | Description | +| ------------- | ------------------------------- | +| `:project_id` | The ID of the project to delete | + +| Status | Response | Reason(s) | +| ------- | ------------------------- | ----------------------------------------------------------------- | +| [204][] | No Content | Successfully deleted the project | +| [403][] | [JSON API error object][] | Not authorized to perform a force delete on the project | +| [404][] | [JSON API error object][] | Project not found, or user unauthorized to perform project delete | + +### Sample Request + +```shell-session +$ curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request DELETE \ + https://app.terraform.io/api/v2/projects/prj-WsVcWRr7SfxRci1v +``` + +## List project tag bindings + +Call the following endpoints to list the tags bound to a project. + +- `GET /projects/:project_id/tag-bindings`: Lists the set of tags associated with the project. Tags set on the project are inherited by its workspaces. + +| Parameter | Description | +| ------------- | ---------------------- | +| `:project_id` | The ID of the project. | + +### Sample request + +The following request returns all tags bound to a project with the ID +`prj-WsVcWRr7SfxRci1v`. + +```shell-session +$ curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + https://app.terraform.io/api/v2/projects/prj-WsVcWRr7SfxRci1v/tag-bindings +``` + +### Sample response + +```json +{ + "data": [ + { + "type": "tag-bindings", + "attributes": { + "key": "added", + "value": "new", + "created-at": "2024-11-20T00:04:47.948Z" + } + }, + { + "type": "tag-bindings", + "attributes": { + "key": "aww", + "value": "aww", + "created-at": "2024-11-20T00:04:47.948Z" + } + } + ] +} +``` + +## Add or update tag bindings on a project + +Use this endpoint to add key-value tag bindings to an existing resource or to update +existing tag binding values on the resource. You cannot use this endpoint to remove tag bindings from the resource. This endpoint is useful for ensuring that a modification is additive. + +Tag bindings have the following constraints and behaviors: + +- A project can have up to 10 tags. +- All workspaces within the project inherit its tag bindings. +- Keys can have up to 128 characters. +- Keys support all alphanumeric characters and the symbols `_`, `.`, `=`, `+`, `-`, `@`, `:`. +- Values can have up to 256 characters. +- Values support all alphanumeric characters and the symbols `_`, `.`, `=`, `+`, `-`, `@`, `:`. +- You cannot use `hc:` and `hcp:` as key prefixes. + +`PATCH /projects/:project_id/tag-bindings` + +| Parameter | Description | +| ------------- | ------------------------------- | +| `:project_id` | The ID of the project to update | + +### Request Body + +This PATCH endpoint requires a JSON object with the following properties as a request payload. + +It is important to note that for each data item, `type`, as well as `attributes.key` is required. + +| Key path | Type | Default | Description | +| ------------------------- | ------ | ------- | ------------------------------------- | +| `data[].type` | string | | Must be `"tag-bindings"`. | +| `data[].attributes.key` | string | | The key of the tag to add or update. | +| `data[].attributes.value` | string | | The name of the tag to add or update. | + +### Sample Payload + +```json +{ + "data": [ + { + "type": "tag-bindings", + "attributes": { + "key": "costcenter", + "value": "123" + } + }, + { + "type": "tag-bindings", + "attributes": { + "key": "bar", + "value": "baz" + } + } + ] +} +``` + +### Sample Request + +```shell-session +$ curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request PATCH \ + --data @payload.json \ + https://app.terraform.io/api/v2/projects/prj-82d2281aa259ba09/tag-bindings +``` + +### Sample Response + +Example status code 200 response after updating tag bindings: + + + +```json +{ + "data": [ + { + "id": "tb-e4a5847b2cf06559", + "type": "tag-bindings", + "attributes": { + "key": "costcenter", + "value": "123" + } + }, + { + "id": "tb-97ce954636f93a6c", + "type": "tag-bindings", + "attributes": { + "key": "bar", + "value": "baz" + } + } + ] +} +``` + + + +## Move workspaces into a project + +This endpoint allows you to move one or more workspaces into a project. You must have permission to move workspaces on +the destination project as well as any source project(s). If you are not authorized to move any of the workspaces in the +request, or if any workspaces in the request are not found, then no workspaces will be moved. + +`POST /projects/:project_id/relationships/workspaces` + +| Parameter | Description | +| ------------- | --------------------------------- | +| `:project_id` | The ID of the destination project | + +This POST endpoint requires a JSON object with the following properties as a request payload. + +| Key path | Type | Default | Description | +| ------------- | ------ | ------- | ---------------------------------------------------------- | +| `data[].type` | string | | Must be `"workspaces"` | +| `data[].id` | string | | The ids of workspaces to move into the destination project | + +| Status | Response | Reason(s) | +| ------- | ------------------------- | -------------------------------------------------------------------------------------------------------- | +| [204][] | No Content | Successfully moved workspace(s) | +| [403][] | [JSON API error object][] | Workspace(s) not found, or user is not authorized to move all workspaces out of their current project(s) | +| [404][] | [JSON API error object][] | Project not found, or user unauthorized to move workspaces into project | + +### Sample Payload + +```json +{ + "data": [ + { + "type": "workspaces", + "id": "ws-AQEct2XFuH4HBsmS" + } + ] +} + +``` + +### Sample Request + +```shell-session +$ curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request POST \ + --data @payload.json \ + https://app.terraform.io/api/v2/projects/prj-zXm4y2BjeGPgHtkp/relationships/workspaces +``` + +### Sample Error Response + +```json +{ + "errors": [ + { + "status": "403", + "title": "forbidden", + "detail": "Workspace(s) not found, or you are not authorized to move them: ws-AQEct2XFuH4HBmS" + } + ] +} +``` diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/reserved-tag-keys.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/reserved-tag-keys.mdx new file mode 100644 index 0000000000..51655fd031 --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/reserved-tag-keys.mdx @@ -0,0 +1,271 @@ +--- +page_title: /reserved-tag-keys API reference for Terraform Enterprise +description: >- + Use the `/reserved-tag-keys` API endpoints to reserve tag keys that have + special meaning for your organization. +source: terraform-docs-common +--- + +[200]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/200 + +[201]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/201 + +[202]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/202 + +[204]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/204 + +[400]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/400 + +[401]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/401 + +[403]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/403 + +[404]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404 + +[409]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/409 + +[412]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/412 + +[422]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/422 + +[429]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/429 + +[500]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500 + +[504]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/504 + +[JSON API document]: /terraform/enterprise/api-docs#json-api-documents + +[JSON API error object]: https://jsonapi.org/format/#error-objects + +[speculative plans]: /terraform/enterprise/run/remote-operations#speculative-plans + +# Reserved tag keys API reference + +Use the `/reserved-tag-keys` API endpoints to define and manage tag keys that +have special meaning for your organization. Reserving tag keys enable project +and workspace managers to follow a consistent tagging strategy across the +organization. You can also use them to provide project managers with a means of +disabling overrides for inherited tags. + +The following table describes the available endpoints: + +| Method | Path | Description | +| -------- | ----------------------------------------------------- | --------------------------------------------------------------------------------- | +| `GET` | `/organizations/:organization_name/reserved-tag-keys` | [List reserved tag keys](#list-reserved-tag-keys) for the specified organization. | +| `POST` | `/organizations/:organization_name/reserved-tag-keys` | [Add a reserved tag key](#add-a-reserved-tag-key) to the specified organization. | +| `PATCH` | `/reserved-tags/:reserved_tag_key_id` | [Update a reserved tag key](#add-a-reserved-tag-value) with the specified ID. | +| `DELETE` | `/reserved-tags/:reserved_tag_key_id` | [Delete a reserved tag key](#delete-a-reserved-tag-key) with the specified ID. | + +## Path parameters + +The `/reserved-tag-keys/` API endpoints require the following path parameters: + +| Parameter | Description | +| ---------------------- | ---------------------------------------------------------- | +| `:reserved_tag_key_id` | The external ID of the reserved tag key. | +| `:organization_name` | The name of the organization containing the reserved tags. | + +## List reserved tag keys + +`GET /organizations/:organization_name/reserved-tag-keys` + +### Sample payload + +This endpoint does not require a payload. + +### Sample request + +```shell-session +$ curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request GET \ + https://app.terraform.io/api/v2/organizations/my-organization/reserved-tag-keys +``` + +### Sample response + +```json +{ + "data": [ + { + "id": "rtk-jjnTseo8NN1jACbk", + "type": "reserved-tag-keys", + "attributes": { + "key": "environment", + "disable-overrides": false, + "created-at": "2024-08-13T23:06:42.523Z", + "updated-at": "2024-08-13T23:06:42.523Z" + } + }, + { + "id": "rtk-F1s7kKUShAQxhA1b", + "type": "reserved-tag-keys", + "attributes": { + "key": "cost-center", + "disable-overrides": false, + "created-at": "2024-08-13T23:06:51.445Z", + "updated-at": "2024-08-13T23:06:51.445Z" + } + }, + ], + "links": { + "self": "https://app.terraform.io/api/v2/organizations/my-organization/reserved-tag-keys?page%5Bnumber%5D=1&page%5Bsize%5D=20", + "first": "https://app.terraform.io/api/v2/organizations/my-organization/reserved-tag-keys?page%5Bnumber%5D=1&page%5Bsize%5D=20", + "prev": null, + "next": null, + "last": "https://app.terraform.io/api/v2/organizations/my-organization/reserved-tag-keys?page%5Bnumber%5D=1&page%5Bsize%5D=20" + }, + "meta": { + "pagination": { + "current-page": 1, + "page-size": 20, + "prev-page": null, + "next-page": null, + "total-pages": 1, + "total-count": 2 + } + } +} + +``` + +## Create a reserved tag key + +`POST /organizations/:organization_name/reserved-tag-keys` + +This POST endpoint requires a JSON object with the following properties as a request payload. + +Properties without a default value are required. + +| Key path | Type | Default | Description | +| ------------------------------------------------------------------------ | ------- | ------- | --------------------------------- | +| `data.type` | string | none | Must be `reserved-tag-keys`. | +| `data.attributes.key` | string | none | The key targeted by this reserved | +| tag key. | | | | +| `data.attributes.disable-overrides` | boolean | none | If `true`, disables | +| overriding inherited tags with the specified key at the workspace level. | | | | + +### Sample payload + +```json +{ + "data": { + "type": "reserved-tag-keys", + "attributes": { + "key": "environment", + "disable-overrides": false + } + } +} +``` + +### Sample request + +```shell-session +$ curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request POST \ + https://app.terraform.io/api/v2/organizations/${ORGANIZATION_NAME}/reserved-tag-keys +``` + +### Sample response + +```json +{ + "data": { + "id": "rtk-Tj86UdGahKGDiYXY", + "type": "reserved-tag-keys", + "attributes": { + "key": "environment", + "disable-overrides": false, + "created-at": "2024-09-04T05:02:06.794Z", + "updated-at": "2024-09-04T05:02:06.794Z" + } + } +} +``` + +## Update a reserved tag key + +`PATCH /reserved-tags/:reserved_tag_key_id` + +This PATCH endpoint requires a JSON object with the following properties as a request payload. + +Properties without a default value are required. + +| Key path | Type | Default | Description | +| ------------------------------------------------------------------------ | ------- | ------- | --------------------------------- | +| `data.type` | string | none | Must be `reserved-tag-keys`. | +| `data.attributes.key` | string | none | The key targeted by this reserved | +| tag key. | | | | +| `data.attributes.disable-overrides` | boolean | none | If `true`, disables | +| overriding inherited tags with the specified key at the workspace level. | | | | + +### Sample payload + +```json +{ + "data": { + "id": "rtk-Tj86UdGahKGDiYXY", + "type": "reserved-tag-keys", + "attributes": { + "key": "env", + "disable-overrides": true, + "created-at": "2024-09-04T05:02:06.794Z", + "updated-at": "2024-09-04T05:02:06.794Z" + } + } +} +``` + +### Sample request + +```shell-session +$ curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request PATCH \ + https://app.terraform.io/api/v2/reserved-tags/${RESERVED_TAG_ID} +``` + +### Sample response + +```json +{ + "data": { + "id": "rtk-zMtWLDftAjY3b5pA", + "type": "reserved-tag-keys", + "attributes": { + "key": "env", + "disable-overrides": true, + "created-at": "2024-09-04T05:05:10.449Z", + "updated-at": "2024-09-04T05:05:13.486Z" + } + } +} +``` + +## Delete a reserved tag key + +`DELETE /reserved-tags/:reserved_tag_key_id` + +### Sample payload + +This endpoint does not require a payload. + +### Sample request + +```shell-session +$ curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request DELETE \ + https://app.terraform.io/api/v2/reserved-tags/rtk-zMtWLDftAjY3b5pA +``` + +### Sample response + +This endpoint does not return a response body. diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/run-tasks/run-task-stages-and-results.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/run-tasks/run-task-stages-and-results.mdx new file mode 100644 index 0000000000..40c8a1f250 --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/run-tasks/run-task-stages-and-results.mdx @@ -0,0 +1,366 @@ +--- +page_title: /task-stages API reference for Terraform Enterprise +description: >- + Use the Terraform Enterprise API's `/task-stages` endpoint to read run tasks + and their results, and override run task stages. +source: terraform-docs-common +--- + +[200]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/200 + +[404]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404 + +[JSON API document]: /terraform/enterprise/api-docs#json-api-documents + +[JSON API documents]: /terraform/enterprise/api-docs#json-api-documents + +[JSON API error object]: https://jsonapi.org/format/#error-objects + +[run]: /terraform/enterprise/run/states + +# Run task stages and results API reference + +HCP Terraform uses run task stages and run task results to track [run task](/terraform/enterprise/workspaces/settings/run-tasks) execution. + + + +@include 'tfc-package-callouts/run-tasks.mdx' + + + +When HCP Terraform creates a [run][], it reads the run tasks associated to the workspace. Each run task in the workspace is configured to begin during a specific [run stage](/terraform/enterprise/run/states). HCP Terraform creates a run task stage object for each run stage that triggers run tasks. You can configure run tasks during the [Pre-Plan Stage](/terraform/enterprise/run/states#the-pre-plan-stage), [Post-Plan Stage](/terraform/enterprise/run/states#the-post-plan-stage), [Pre-Apply Stage](/terraform/enterprise/run/states#the-pre-apply-stage) and [Post-Apply Stage](/terraform/enterprise/run/states#the-post-apply-stage). + +Run task stages then create a run task result for each run task. For example, a workspace has two run tasks called `alpha` and `beta`. For each run, HCP Terraform creates one run task stage called `post-plan`. That run task stage has two run task results: one for the `alpha` run task and one for the `beta` run task. + +This page lists the endpoints to retrieve run task stages and run task results. Refer to the [Run Tasks API](/terraform/enterprise/api-docs/run-tasks/run-tasks) for endpoints to create and manage run tasks within HCP Terraform. Refer to the [Run Tasks Integration API](/terraform/enterprise/api-docs/run-tasks/run-tasks-integration) for endpoints to build custom run tasks for the HCP Terraform ecosystem. + +## Attributes + +### Run Task Stage Status + +The run task stage status is found in `data.attributes.status`, and you can reference the following list of possible values. + +| Status | Description | +| ------------------- | -------------------------------------------------------------------------------------------------------------------------------------- | +| `pending` | The initial status of a run task stage after creation. | +| `running` | The run task stage is executing one or more tasks, which have not yet completed. | +| `passed` | All of the run task results in the stage passed. | +| `failed` | One more results in the run task stage failed. | +| `awaiting_override` | The task stage is waiting for user input. Once a user manually overrides the failed run tasks, the run returns to the `running` state. | +| `errored` | The run task stage has errored. | +| `canceled` | The run task stage has been canceled. | +| `unreachable` | The run task stage could not be executed. | + +### Run Task Result Status + +The run task result status is found in `data.attributes.status`, and you can reference the following list of possible values. + +| Status | Description | +| ------------- | --------------------------------------------------------------------- | +| `pending` | The initial status of a run task result after creation. | +| `running` | The associated run task is begun execution and has not yet completed. | +| `passed` | The associated run task executed and returned a passing result. | +| `failed` | The associated run task executed and returned a failed result. | +| `errored` | The associated run task has errored during execution. | +| `canceled` | The associated run task execution has been canceled. | +| `unreachable` | The associated run task could not be executed. | + +## List the Run Task Stages in a Run + +`GET /runs/:run_id/task-stages` + +| Parameter | Description | +| --------- | ----------------------------------- | +| `run_id` | The run ID to list task stages for. | + +| Status | Response | Reason | +| ------- | ------------------------------------------------------- | ------------------------------- | +| [200][] | Array of [JSON API documents][] (`type: "task-stages"`) | Successfully listed task-stages | + +### Query Parameters + +This endpoint supports pagination [with standard URL query parameters](/terraform/enterprise/api-docs#query-parameters); remember to percent-encode `[` as `%5B` and `]` as `%5D` if your tooling doesn't automatically encode URLs. + +| Parameter | Description | +| -------------- | -------------------------------------------------------------------- | +| `page[number]` | **Optional.** If omitted, the endpoint will return the first page. | +| `page[size]` | **Optional.** If omitted, the endpoint will return 20 runs per page. | + +### Sample Request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + https://app.terraform.io/api/v2/runs/run-XdgtChJuuUwLoSmw/task-stages +``` + +### Sample Response + +```json +{ + "data": [ + { + "id": "ts-rL5ZsuwfjqfPJcdi", + "type": "task-stages", + "attributes": { + "status": "passed", + "stage": "post_plan", + "status-timestamps": { + "passed-at": "2022-06-08T20:32:12+08:00", + "running-at": "2022-06-08T20:32:11+08:00" + }, + "created-at": "2022-06-08T12:31:56.94Z", + "updated-at": "2022-06-08T12:32:12.315Z" + }, + "relationships": { + "run": { + "data": { + "id": "run-XdgtChJuuUwLoSmw", + "type": "runs" + } + }, + "task-results": { + "data": [ + { + "id": "taskrs-EmnmsEDL1jgd1GTP", + "type": "task-results" + } + ] + }, + "policy-evaluations":{ + "data":[ + { + "id":"poleval-iouaha9KLgGWkBRQ", + "type":"policy-evaluations" + } + ] + } + }, + "links": { + "self": "/api/v2/task-stages/ts-rL5ZsuwfjqfPJcdi" + } + } + ] +} +``` + +## Show a Run Task Stage + +`GET /task-stages/:task_stage_id` + +| Parameter | Description | +| ---------------- | ----------------------------- | +| `:task_stage_id` | The run task stage ID to get. | + +This endpoint shows details of a specific task stage. + +| Status | Response | Reason | +| ------- | --------------------------------------------- | ------------------------------------------- | +| [200][] | [JSON API document][] (`type: "task-stages"`) | Success | +| [404][] | [JSON API error object][] | Task stage not found or user not authorized | + +### Sample Request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + https://app.terraform.io/api/v2/task-stages/ts-rL5ZsuwfjqfPJcdi +``` + +### Sample Response + +```json +{ + "data": { + "id": "ts-rL5ZsuwfjqfPJcdi", + "type": "task-stages", + "attributes": { + "status": "passed", + "stage": "post_plan", + "status-timestamps": { + "passed-at": "2022-06-08T20:32:12+08:00", + "running-at": "2022-06-08T20:32:11+08:00" + }, + "created-at": "2022-06-08T12:31:56.94Z", + "updated-at": "2022-06-08T12:32:12.315Z" + }, + "relationships": { + "run": { + "data": { + "id": "run-XdgtChJuuUwLoSmw", + "type": "runs" + } + }, + "task-results": { + "data": [ + { + "id": "taskrs-EmnmsEDL1jgd1GTP", + "type": "task-results" + } + ] + }, + "policy-evaluations":{ + "data":[ + { + "id":"poleval-iouaha9KLgGWkBRQ", + "type":"policy-evaluations" + } + ] + } + }, + "links": { + "self": "/api/v2/task-stages/ts-rL5ZsuwfjqfPJcdi" + } + } +} +``` + +## Show a Run Task Result + +`GET /task-results/:task_result_id` + +| Parameter | Description | +| ----------------- | ------------------------------ | +| `:task_result_id` | The run task result ID to get. | + +This endpoint shows the details for a specific run task result. + +| Status | Response | Reason | +| ------- | ---------------------------------------------- | -------------------------------------------- | +| [200][] | [JSON API document][] (`type: "task-results"`) | Success | +| [404][] | [JSON API error object][] | Task result not found or user not authorized | + +### Sample Request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + https://app.terraform.io/api/v2/task-results/taskrs-EmnmsEDL1jgd1GZz +``` + +### Sample Response + +```json +{ + "data": { + "id": "taskrs-EmnmsEDL1jgd1GZz", + "type": "task-results", + "attributes": { + "message": "No issues found.\nSeverity threshold is set to low.", + "status": "passed", + "status-timestamps": { + "passed-at": "2022-06-08T20:32:12+08:00", + "running-at": "2022-06-08T20:32:11+08:00" + }, + "url": "https://external.service/project/task-123abc", + "created-at": "2022-06-08T12:31:56.954Z", + "updated-at": "2022-06-08T12:32:12.27Z", + "task-id": "task-b6MaHZmGopHDtqhn", + "task-name": "example-task", + "task-url": "https://external.service/task-123abc", + "stage": "post_plan", + "is-speculative": false, + "workspace-task-id": "wstask-258juqenQeWb3DZz", + "workspace-task-enforcement-level": "mandatory" + }, + "relationships": { + "task-stage": { + "data": { + "id": "ts-rL5ZsuwfjqfPJczZ", + "type": "task-stages" + } + } + }, + "links": { + "self": "/api/v2/task-results/taskrs-EmnmsEDL1jgd1GZz" + } + } +} +``` + +## Available Related Resources + +### Task Stage + +The GET endpoints above can optionally return related resources, if requested with [the `include` query parameter](/terraform/enterprise/api-docs#inclusion-of-related-resources). The following resource types are available: + +| Resource | Description | +| -------------------- | ---------------------------------------------------------- | +| `run` | Information about the associated run. | +| `run.workspace` | Information about the associated workspace. | +| `task-results` | Information about the results for a task-stage. | +| `policy-evaluations` | Information about the policy evaluations for a task-stage. | + +## Override a Task Stage + +`POST /task-stages/:task_stage_id/actions/override` + +| Parameter | Description | +| ---------------- | ------------------------------------- | +| `:task_stage_id` | The ID of the task stage to override. | + +### Sample Request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request POST \ + https://app.terraform.io/api/v2/task-stages/ts-rL5ZsuwfjqfPJcdi/actions/override +``` + +### Sample Response + +```json +{ + "data":{ + "id":"ts-F7MumZQcJzVh1ZZk", + "type":"task-stages", + "attributes":{ + "status":"running", + "stage":"post_plan", + "status-timestamps":{ + "running-at":"2022-09-21T06:36:54+00:00", + "awaiting-override-at":"2022-09-21T06:31:50+00:00" + }, + "created-at":"2022-09-21T06:29:44.632Z", + "updated-at":"2022-09-21T06:36:54.952Z", + "permissions":{ + "can-override-policy":true, + "can-override-tasks":false, + "can-override":true + }, + "actions":{ + "is-overridable":false + } + }, + "relationships":{ + "run":{ + "data":{ + "id":"run-K6N4BAz8NfUyR2QB", + "type":"runs" + } + }, + "task-results":{ + "data":[ + + ] + }, + "policy-evaluations":{ + "data":[ + { + "id":"poleval-atNKxwvjYy4Gwk3k", + "type":"policy-evaluations" + } + ] + } + }, + "links":{ + "self":"/api/v2/task-stages/ts-F7MumZQcJzVh1ZZk" + } + } +} +``` diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/run-tasks/run-tasks-integration.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/run-tasks/run-tasks-integration.mdx new file mode 100644 index 0000000000..eaa3fe1fca --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/run-tasks/run-tasks-integration.mdx @@ -0,0 +1,298 @@ +--- +page_title: Run tasks integration API for Terraform Enterprise +description: >- + Use Terraform Enterprise API's run task integration API to trigger run tasks + at specific run phases and learn how to read run task responses. +source: terraform-docs-common +--- + +[200]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/200 + +[401]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/401 + +[422]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/422 + +[JSON API error object]: https://jsonapi.org/format/#error-objects + +# Run tasks integration API + +[Run tasks](/terraform/enterprise/workspaces/settings/run-tasks) allow HCP Terraform to interact with external systems at specific points in the HCP Terraform run lifecycle. +This page lists the API endpoints used to trigger a run task and the expected response from the integration. + + + +@include 'tfc-package-callouts/run-tasks.mdx' + + + +Refer to [run tasks](/terraform/enterprise/api-docs/run-tasks/run-tasks) for the API endpoints to create and manage run tasks within HCP Terraform. You can also access a complete list of all run tasks in the [Terraform Registry](https://registry.terraform.io/browse/run-tasks). + +## Run Task Request + +When a run reaches the appropriate phase and a run task is triggered, HCP Terraform will send a request to the run task's URL. + +Either HCP Terraform itself, or a self-hosted HCP Terraform agent using [request forwarding](/terraform/cloud-docs/agents/request-forwarding) can make the request to a run task's URL. Your organization [must be entitled to `private-run-tasks`](/terraform/enterprise/api-docs#feature-entitlements) to use request forwarding. +The service receiving the run task request should respond with `200 OK`, or HCP Terraform will retry to trigger the run task. + +`POST :url` + +| Parameter | Description | +| --------- | ------------------------------------------------------- | +| `:url` | The URL configured in the run task to send requests to. | + +| Status | Response | Reason | +| ------- | ---------- | --------------------------------- | +| [200][] | No Content | Successfully submitted a run task | + +### Request Body + +The POST request submits a JSON object with the following properties as a request payload. + +#### Common Properties + +All request payloads contain the following properties. + +| Key path | Type | Values | Description | +| ------------------------------------ | ------- | -------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `payload_version` | integer | `1` | Schema version of the payload. Only `1` is supported. | +| `stage` | string | `pre_plan`, `post_plan`, `pre_apply`, `post_apply` | The [run stage](/terraform/enterprise/run/states) when HCP Terraform triggers the run task. | +| `access_token` | string | | Bearer token to use when calling back to HCP Terraform. | +| `capabilities` | object | | A map of the capabilities that the caller supports. | +| `capabilities.outcomes` | bool | | A flag indicating the caller accepts detailed run task outcomes. | +| `configuration_version_download_url` | string | | The URL to [download the configuration version](/terraform/enterprise/api-docs/configuration-versions#download-configuration-files). This is `null` if the configuration version is not available to download. | +| `configuration_version_id` | string | | The ID of the [configuration version](/terraform/enterprise/api-docs/configuration-versions) for the run. | +| `is_speculative` | bool | | Whether the task is part of a [speculative run](/terraform/enterprise/run/remote-operations#speculative-plans). | +| `organization_name` | string | | Name of the organization the task is configured within. | +| `run_app_url` | string | | URL within HCP Terraform to the run. | +| `run_created_at` | string | | When the run was started. | +| `run_created_by` | string | | Who created the run. | +| `run_id` | string | | Id of the run this task is part of. | +| `run_message` | string | | Message that was associated with the run. | +| `task_result_callback_url` | string | | URL that should called back with the result of this task. | +| `task_result_enforcement_level` | string | `mandatory`, `advisory` | Enforcement level for this task. | +| `task_result_id` | string | | ID of task result within HCP Terraform. | +| `vcs_branch` | string | | Repository branch that the workspace executes from. This is `null` if the workspace does not have a VCS repository. | +| `vcs_commit_url` | string | | URL to the commit that triggered this run. This is `null` if the workspace does not a VCS repository. | +| `vcs_pull_request_url` | string | | URL to the Pull Request/Merge Request that triggered this run. This is `null` if the run was not triggered. | +| `vcs_repo_url` | string | | URL to the workspace's VCS repository. This is `null` if the workspace does not have a VCS repository. | +| `workspace_app_url` | string | | URL within HCP Terraform to the workspace. | +| `workspace_id` | string | | Id of the workspace the task is associated with. | +| `workspace_name` | string | | Name of the workspace. | +| `workspace_working_directory` | string | | The working directory specified in the run's [workspace settings](/terraform/enterprise/workspaces/settings#terraform-working-directory). | + +#### Post-Plan, Pre-Apply, and Post-Apply Properties + +Requests with `stage` set to `post_plan`, `pre_apply` or `post_apply` contain the following additional properties. + +| Key path | Type | Values | Description | +| ------------------- | ------ | ------ | --------------------------------------------------------- | +| `plan_json_api_url` | string | | The URL to retrieve the JSON Terraform plan for this run. | + +### Sample Payload + +```json +{ + "payload_version": 1, + "stage": "post_plan", + "access_token": "4QEuyyxug1f2rw.atlasv1.iDyxqhXGVZ0ykes53YdQyHyYtFOrdAWNBxcVUgWvzb64NFHjcquu8gJMEdUwoSLRu4Q", + "capabilities": { + "outcomes": true + }, + "configuration_version_download_url": "https://app.terraform.io/api/v2/configuration-versions/cv-ntv3HbhJqvFzamy7/download", + "configuration_version_id": "cv-ntv3HbhJqvFzamy7", + "is_speculative": false, + "organization_name": "hashicorp", + "plan_json_api_url": "https://app.terraform.io/api/v2/plans/plan-6AFmRJW1PFJ7qbAh/json-output", + "run_app_url": "https://app.terraform.io/app/hashicorp/my-workspace/runs/run-i3Df5to9ELvibKpQ", + "run_created_at": "2021-09-02T14:47:13.036Z", + "run_created_by": "username", + "run_id": "run-i3Df5to9ELvibKpQ", + "run_message": "Triggered via UI", + "task_result_callback_url": "https://app.terraform.io/api/v2/task-results/5ea8d46c-2ceb-42cd-83f2-82e54697bddd/callback", + "task_result_enforcement_level": "mandatory", + "task_result_id": "taskrs-2nH5dncYoXaMVQmJ", + "vcs_branch": "main", + "vcs_commit_url": "https://github.com/hashicorp/terraform-random/commit/7d8fb2a2d601edebdb7a59ad2088a96673637d22", + "vcs_pull_request_url": null, + "vcs_repo_url": "https://github.com/hashicorp/terraform-random", + "workspace_app_url": "https://app.terraform.io/app/hashicorp/my-workspace", + "workspace_id": "ws-ck4G5bb1Yei5szRh", + "workspace_name": "tfr_github_0", + "workspace_working_directory": "/terraform" +} +``` + +### Request Headers + +The POST request submits the following properties as the request headers. + +| Name | Value | Description | +| ---------------------- | ------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `Content-Type` | `application/json` | Specifies the type of data in the request body | +| `User-Agent` | `TFC/1.0 (+https://app.terraform.io; TFC)` | Identifies the request is coming from HCP Terraform | +| `X-TFC-Task-Signature` | string | If the run task is configured with an [HMAC Key](/terraform/enterprise/integrations/run-tasks#securing-your-run-task), this header contains the signed SHA512 sum of the request payload using the configured HMAC key. Otherwise, this is an empty string. | + +## Run Task Callback + +While a run task runs, it may send progressive updates to HCP Terraform with a `running` status. Once an integrator determines that Terraform supports detailed run task outcomes, they can send these outcomes by appending to the run task's callback payload. + +Once the external integration fulfills the request, that integration must call back into HCP Terraform with the overall result of either `passed` or `failed`. Terraform expects this callback within 10 minutes, or the request is considered errored. + +You can send outcomes with a status of `running`, `passed`, or `failed`, but it is a good practice only to send outcomes when a run task is `running`. + +`PATCH :callback_url` + +| Parameter | Description | +| --------------- | ----------------------------------------------------------------------------------------------------------- | +| `:callback_url` | The `task_result_callback_url` specified in the run task request. Typically `/task-results/:guid/callback`. | + +| Status | Response | Reason | +| ------- | ------------------------- | --------------------------------------------------------------------------------------------------------------- | +| [200][] | No Content | Successfully submitted a run task result | +| [401][] | [JSON API error object][] | Not authorized to perform action | +| [422][] | [JSON API error object][] | Invalid response payload. This could be caused by invalid attributes, or sending a status that is not accepted. | + +### Request Body + +The PATCH request submits a JSON object with the following properties as a request payload. This payload is also described in the [JSON API schema for run task results](https://github.com/hashicorp/terraform-docs-common/blob/main/website/public/schema/run-tasks/runtask-result.json). + +| Key path | Type | Description | +| ----------------------------- | ------ | --------------------------------------------------------------------------------- | +| `data.type` | string | Must be `"task-results"`. | +| `data.attributes.status` | string | The current status of the task. Only `passed`, `failed` or `running` are allowed. | +| `data.attributes.message` | string | (Recommended, but optional) A short message describing the status of the task. | +| `data.attributes.url` | string | (Optional) A URL where users can obtain more information about the task. | +| `relationships.outcomes.data` | array | (Recommended, but optional) A collection of detailed run task outcomes. | + +Status values other than passed, failed, or running return an error. Both the passed and failed statuses represent a final state for a run task. The running status allows one or more partial updates until the task has reached a final state. + +```json +{ + "data": { + "type": "task-results", + "attributes": { + "status": "passed", + "message": "4 passed, 0 skipped, 0 failed", + "url": "https://external.service.dev/terraform-plan-checker/run-i3Df5to9ELvibKpQ" + }, + "relationships": { + "outcomes": { + "data": [...] + } + } + } +} +``` + +#### Outcomes Payload Body + +A run task result may optionally contain one or more detailed outcomes, which improves result visibility and content in the HCP Terraform user interface. The following attributes define the outcome. + +| Key path | Type | Description | +| ------------------ | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `outcome-id` | string | A partner supplied identifier for this outcome. | +| `description` | string | A one-line description of the result. | +| `body` | string | (Optional) A detailed message for the result in markdown format. We recommend limiting messages to under 1MB with a maximum allowable limit of 5MB. | +| `url` | string | (Optional) A URL that a user can navigate to for more information about this result. | +| `tags` | object | (Optional) An object containing tag arrays, named by the property key. | +| `tags.key` | string | The two or three word name of the header tag. [Special handling](#severity-and-status-tags) is given to `severity` and `status` keys. | +| `tags.key[].label` | string | The text value of the tag. | +| `tags.key[].level` | enum string | (Optional) The error level for the tag. Defaults to `none`, but accepts `none`, `info`, `warning`, or `error`. For levels other than `none`, labels render with a color and icon for that level. | + +##### Severity and Status Tags + +Run task outcomes with tags named "severity" or "status" are enriched within the outcomes display list in HCP Terraform, enabling an earlier response to issues with severity and status. + +```json +{ + "type": "task-result-outcomes", + "attributes": { + "outcome-id": "PRTNR-CC-TF-127", + "description": "ST-2942: S3 Bucket will not enforce MFA login on delete requests", + "tags": { + "Status": [ + { + "label": "Denied", + "level": "error" + } + ], + "Severity": [ + { + "label": "High", + "level": "error" + }, + { + "label": "Recoverable", + "level": "info" + } + ], + "Cost Centre": [ + { + "label": "IT-OPS" + } + ] + }, + "body": "# Resolution for issue ST-2942\n\n## Impact\n\nFollow instructions in the [AWS S3 docs](https://docs.aws.amazon.com/AmazonS3/latest/userguide/MultiFactorAuthenticationDelete.html) to manually configure the MFA setting.\n—-- Payload truncated —--", + "url": "https://external.service.dev/result/PRTNR-CC-TF-127" + } +} +``` + +##### Complete Callback Payload Example + +The example below shows a complete payload explaining the data structure of a callback payload, including all the necessary fields. + +```json +{ + "data": { + "type": "task-results", + "attributes": { + "status": "failed", + "message": "0 passed, 0 skipped, 1 failed", + "url": "https://external.service.dev/terraform-plan-checker/run-i3Df5to9ELvibKpQ" + }, + "relationships": { + "outcomes": { + "data": [ + { + "type": "task-result-outcomes", + "attributes": { + "outcome-id": "PRTNR-CC-TF-127", + "description": "ST-2942: S3 Bucket will not enforce MFA login on delete requests", + "tags": { + "Status": [ + { + "label": "Denied", + "level": "error" + } + ], + "Severity": [ + { + "label": "High", + "level": "error" + }, + { + "label": "Recoverable", + "level": "info" + } + ], + "Cost Centre": [ + { + "label": "IT-OPS" + } + ] + }, + "body": "# Resolution for issue ST-2942\n\n## Impact\n\nFollow instructions in the [AWS S3 docs](https://docs.aws.amazon.com/AmazonS3/latest/userguide/MultiFactorAuthenticationDelete.html) to manually configure the MFA setting.\n—-- Payload truncated —--", + "url": "https://external.service.dev/result/PRTNR-CC-TF-127" + } + } + ] + } + } + } +} +``` + +### Request Headers + +The PATCH request must use the token supplied in the originating request (`access_token`) for [authentication](/terraform/enterprise/api-docs#authentication). diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/run-tasks/run-tasks.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/run-tasks/run-tasks.mdx new file mode 100644 index 0000000000..c0b0a8fcc7 --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/run-tasks/run-tasks.mdx @@ -0,0 +1,831 @@ +--- +page_title: /tasks API reference for Terraform Enterprise +description: >- + Use Terraform Enterprise API's `/tasks` endpoint to read, create, update, and + delete run tasks, and read, update, delete and associate run tasks to + workspaces. +source: terraform-docs-common +--- + +[200]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/200 + +[201]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/201 + +[202]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/202 + +[204]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/204 + +[400]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/400 + +[401]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/401 + +[403]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/403 + +[404]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404 + +[409]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/409 + +[412]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/412 + +[422]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/422 + +[429]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/429 + +[500]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500 + +[504]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/504 + +[JSON API document]: /terraform/enterprise/api-docs#json-api-documents + +[JSON API error object]: https://jsonapi.org/format/#error-objects + +[JSON API Schema document]: https://github.com/hashicorp/terraform-docs-common/blob/main/website/public/schema/run-tasks/runtask-results.json + +# Run tasks API reference + +[Run tasks](/terraform/enterprise/workspaces/settings/run-tasks) allow HCP Terraform to interact with external systems at specific points in the HCP Terraform run lifecycle. Run tasks are reusable configurations that you can associate to any workspace in an organization. This page lists the API endpoints for run tasks in an organization and explains how to associate run tasks to workspaces. + + + +@include 'tfc-package-callouts/run-tasks.mdx' + + + +Refer to [run tasks Integration](/terraform/enterprise/api-docs/run-tasks/run-tasks-integration) for the API endpoints related triggering run tasks and the expected integration response. + +## Required Permissions + +To interact with run tasks on an organization, you need the [Manage Run Tasks permission](/terraform/enterprise/users-teams-organizations/permissions#manage-run-tasks). To associate or dissociate run tasks in a workspace, you need the [Manage Workspace Run Tasks permission](/terraform/enterprise/users-teams-organizations/permissions#general-workspace-permissions) on that particular workspace. + +[permissions-citation]: #intentionally-unused---keep-for-maintainers + +## Create a Run Task + +`POST /organizations/:organization_name/tasks` + +| Parameter | Description | +| -------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `:organization_name` | The organization to create a run task in. The organization must already exist in HCP Terraform, and the token authenticating the API request must have [owner permission](/terraform/enterprise/users-teams-organizations/permissions). | + +[permissions-citation]: #intentionally-unused---keep-for-maintainers + +| Status | Response | Reason | +| ------- | --------------------------------------- | -------------------------------------------------------------- | +| [201][] | [JSON API document][] (`type: "tasks"`) | Successfully created a run task | +| [404][] | [JSON API error object][] | Organization not found, or user unauthorized to perform action | +| [422][] | [JSON API error object][] | Malformed request body (missing attributes, wrong types, etc.) | + +### Request Body + +This POST endpoint requires a JSON object with the following properties as a request payload. + +Properties without a default value are required unless otherwise specified. + +| Key path | Type | Default | Description | +| -------------------------------------------------------- | ------ | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `data.type` | string | | Must be `"tasks"`. | +| `data.attributes.name` | string | | The name of the task. Can include letters, numbers, `-`, and `_`. | +| `data.attributes.url` | string | | URL to send a run task payload. | +| `data.attributes.description` | string | | The description of the run task. Can be up to 300 characters long including spaces, letters, numbers, and special characters. | +| `data.attributes.category` | string | | Must be `"task"`. | +| `data.attributes.hmac-key` | string | | (Optional) HMAC key to verify run task. | +| `data.attributes.enabled` | bool | true | (Optional) Whether the task will be run. | +| `data.attributes.global-configuration.enabled` | bool | false | (Optional) Whether the task will be associated on all workspaces. | +| `data.attributes.global-configuration.stages` | array | | (Optional) An array of strings representing the stages of the run lifecycle when the run task should begin. Must be one or more of `"pre_plan"`, `"post_plan"`, `"pre_apply"`, or `"post_apply"`. | +| `data.attributes.global-configuration.enforcement-level` | string | | (Optional) The enforcement level of the workspace task. Must be `"advisory"` or `"mandatory"`. | +| `data.relationships.agent-pool.data.id` | string | | (Optional) The agent pool that HCP Terraform uses to make requests for the run task. Requires the [`private_run_tasks` feature entitlement](/terraform/enterprise/api-docs#feature-entitlements) and a self-hosted HCP Terraform agent with [request forwarding](/terraform/cloud-docs/agents/request-forwarding). | + +### Sample Payload + +```json +{ + "data": { + "type": "tasks", + "attributes": { + "name": "example", + "url": "http://example.com", + "description": "Simple description", + "hmac_key": "secret", + "enabled": "true", + "category": "task", + "global-configuration": { + "enabled": true, + "stages": ["pre_plan"], + "enforcement-level": "mandatory" + } + }, + "relationships": { + "agent-pool": { + "data": { + "id": "apool-yoGUFz5zcRMMz53i", + "type": "agent-pools" + } + } + } + } +} +``` + +### Sample Request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request POST \ + --data @payload.json \ + https://app.terraform.io/api/v2/organizations/my-organization/tasks +``` + +### Sample Response + +```json +{ + "data": { + "id": "task-7oD7doVTQdAFnMLV", + "type": "tasks", + "attributes": { + "category": "task", + "name": "my-run-task", + "url": "http://example.com", + "description": "Simple description", + "enabled": "true", + "hmac-key": null, + "global-configuration": { + "enabled": true, + "stages": ["pre_plan"], + "enforcement-level": "mandatory" + } + }, + "relationships": { + "organization": { + "data": { + "id": "hashicorp", + "type": "organizations" + } + }, + "tasks": { + "data": [] + }, + "agent-pool": { + "data": { + "id": "apool-yoGUFz5zcRMMz53i", + "type": "agent-pools" + } + } + }, + "links": { + "self": "/api/v2/tasks/task-7oD7doVTQdAFnMLV" + } + } +} +``` + +## List Run Tasks + +`GET /organizations/:organization_name/tasks` + +| Parameter | Description | +| -------------------- | ----------------------------------- | +| `:organization_name` | The organization to list tasks for. | + +| Status | Response | Reason | +| ------- | --------------------------------------- | -------------------------------------------------------------- | +| [200][] | [JSON API document][] (`type: "tasks"`) | Request was successful | +| [404][] | [JSON API error object][] | Organization not found, or user unauthorized to perform action | + +### Query Parameters + +This endpoint supports pagination [with standard URL query parameters](/terraform/enterprise/api-docs#query-parameters). Remember to percent-encode `[` as `%5B` and `]` as `%5D` if your tooling doesn't automatically encode URLs. + +| Parameter | Description | +| -------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `include` | **Optional.** Allows including related resource data. Value must be a comma-separated list containing one or more of `workspace_tasks` or `workspace_tasks.workspace`. | +| `page[number]` | **Optional.** If omitted, the endpoint will return the first page. | +| `page[size]` | **Optional.** If omitted, the endpoint will return 20 run tasks per page. | + +### Sample Request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + https://app.terraform.io/api/v2/organizations/my-organization/tasks +``` + +### Sample Response + +```json +{ + "data": [ + { + "id": "task-7oD7doVTQdAFnMLV", + "type": "tasks", + "attributes": { + "category": "task", + "name": "my-task", + "url": "http://example.com", + "description": "Simple description", + "enabled": "true", + "hmac-key": null, + "global-configuration": { + "enabled": true, + "stages": [ + "pre_plan" + ], + "enforcement-level": "mandatory" + } + }, + "relationships": { + "organization": { + "data": { + "id": "hashicorp", + "type": "organizations" + } + }, + "tasks": { + "data": [] + } + }, + "links": { + "self": "/api/v2/tasks/task-7oD7doVTQdAFnMLV" + } + } + ], + "links": { + "self": "https://app.terraform.io/api/v2/organizations/hashicorp/tasks?page%5Bnumber%5D=1&page%5Bsize%5D=20", + "first": "https://app.terraform.io/api/v2/organizations/hashicorp/tasks?page%5Bnumber%5D=1&page%5Bsize%5D=20", + "prev": null, + "next": null, + "last": "https://app.terraform.io/api/v2/organizations/hashicorp/tasks?page%5Bnumber%5D=1&page%5Bsize%5D=20" + }, + "meta": { + "pagination": { + "current-page": 1, + "page-size": 20, + "prev-page": null, + "next-page": null, + "total-pages": 1, + "total-count": 1 + } + } +} +``` + +## Show a Run Task + +`GET /tasks/:id` + +| Parameter | Description | +| --------- | --------------------------------------------------------------------------------------------- | +| `:id` | The ID of the task to show. Use the ["List Run Tasks"](#list-run-tasks) endpoint to find IDs. | + +| Status | Response | Reason | +| ------- | --------------------------------------- | --------------------------------------------------------- | +| [200][] | [JSON API document][] (`type: "tasks"`) | The request was successful | +| [404][] | [JSON API error object][] | Run task not found or user unauthorized to perform action | + +| Parameter | Description | +| --------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `include` | **Optional.** Allows including related resource data. Value must be a comma-separated list containing one or more of `workspace_tasks` or `workspace_tasks.workspace`. | + +### Sample Request + +```shell +curl --request GET \ + -H "Authorization: Bearer $TOKEN" \ + -H "Content-Type: application/vnd.api+json" \ + https://app.terraform.io/api/v2/tasks/task-7oD7doVTQdAFnMLV +``` + +### Sample Response + +```json +{ + "data": { + "id": "task-7oD7doVTQdAFnMLV", + "type": "tasks", + "attributes": { + "category": "task", + "name": "my-task", + "url": "http://example.com", + "description": "Simple description", + "enabled": "true", + "hmac-key": null, + }, + "relationships": { + "organization": { + "data": { + "id": "hashicorp", + "type": "organizations" + } + }, + "tasks": { + "data": [ + { + "id": "task-xjKZw9KaeXda61az", + "type": "tasks" + } + ] + } + }, + "links": { + "self": "/api/v2/tasks/task-7oD7doVTQdAFnMLV" + } + } +} +``` + +## Update a Run Task + +`PATCH /tasks/:id` + +| Parameter | Description | +| --------- | ----------------------------------------------------------------------------------------------- | +| `:id` | The ID of the task to update. Use the ["List Run Tasks"](#list-run-tasks) endpoint to find IDs. | + +| Status | Response | Reason | +| ------- | --------------------------------------- | -------------------------------------------------------------- | +| [200][] | [JSON API document][] (`type: "tasks"`) | The request was successful | +| [404][] | [JSON API error object][] | Run task not found or user unauthorized to perform action | +| [422][] | [JSON API error object][] | Malformed request body (missing attributes, wrong types, etc.) | + +### Request Body + +This PATCH endpoint requires a JSON object with the following properties as a request payload. + +Properties without a default value are required unless otherwise specified. + +| Key path | Type | Default | Description | +| -------------------------------------------------------- | ------ | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `data.type` | string | | Must be `"tasks"`. | +| `data.attributes.name` | string | (previous value) | The name of the run task. Can include letters, numbers, `-`, and `_`. | +| `data.attributes.url` | string | (previous value) | URL to send a run task payload. | +| `data.attributes.description` | string | | The description of the run task. Can be up to 300 characters long including spaces, letters, numbers, and special characters. | +| `data.attributes.category` | string | (previous value) | Must be `"task"`. | +| `data.attributes.hmac-key` | string | (previous value) | (Optional) HMAC key to verify run task. | +| `data.attributes.enabled` | bool | (previous value) | (Optional) Whether the task will be run. | +| `data.attributes.global-configuration.enabled` | bool | (previous value) | (Optional) Whether the task will be associated on all workspaces. | +| `data.attributes.global-configuration.stages` | array | (previous value) | (Optional) An array of strings representing the stages of the run lifecycle when the run task should begin. Must be one or more of `"pre_plan"`, `"post_plan"`, `"pre_apply"`, or `"post_apply"`. | +| `data.attributes.global-configuration.enforcement-level` | string | (previous value) | (Optional) The enforcement level of the workspace task. Must be `"advisory"` or `"mandatory"`. | + +### Sample Payload + +```json +{ + "data": { + "type": "tasks", + "attributes": { + "name": "new-example", + "url": "http://new-example.com", + "description": "New description", + "hmac_key": "new-secret", + "enabled": "false", + "category": "task", + "global-configuration": { + "enabled": false + } + } + } +} +``` + +### Sample Request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request PATCH \ + --data @payload.json \ + https://app.terraform.io/api/v2/tasks/task-7oD7doVTQdAFnMLV +``` + +### Sample Response + +```json +{ + "data": { + "id": "task-7oD7doVTQdAFnMLV", + "type": "tasks", + "attributes": { + "category": "task", + "name": "new-example", + "url": "http://new-example.com", + "description": "New description", + "enabled": "false", + "hmac-key": null, + "global-configuration": { + "enabled": false, + "stages": ["pre_plan"], + "enforcement-level": "mandatory" + } + }, + "relationships": { + "organization": { + "data": { + "id": "hashicorp", + "type": "organizations" + } + }, + "tasks": { + "data": [ + { + "id": "wstask-xjKZw9KaeXda61az", + "type": "workspace-tasks" + } + ] + } + }, + "links": { + "self": "/api/v2/tasks/task-7oD7doVTQdAFnMLV" + } + } +} +``` + +## Delete a Run Task + +`DELETE /tasks/:id` + +| Parameter | Description | +| --------- | --------------------------------------------------------------------------------------------------- | +| `:id` | The ID of the run task to delete. Use the ["List Run Tasks"](#list-run-tasks) endpoint to find IDs. | + +| Status | Response | Reason | +| ------- | ------------------------- | ---------------------------------------------------------- | +| [204][] | No Content | Successfully deleted the run task | +| [404][] | [JSON API error object][] | Run task not found, or user unauthorized to perform action | + +### Sample Request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request DELETE \ + https://app.terraform.io/api/v2/tasks/task-7oD7doVTQdAFnMLV +``` + +## Associate a Run Task to a Workspace + +`POST /workspaces/:workspace_id/tasks` + +| Parameter | Description | +| --------------- | ------------------------ | +| `:workspace_id` | The ID of the workspace. | + +This endpoint associates an existing run task to a specific workspace. + +This involves setting the run task enforcement level, which determines whether the run task blocks runs from completing. + +- Advisory run tasks can not block a run from completing. If the task fails, the run will proceed with a warning. + +- Mandatory run tasks block a run from completing. If the task fails (including a timeout or unexpected remote error condition), the run stops with an error. + +You may also configure the run task to begin during specific [run stages](/terraform/enterprise/run/states). Run tasks use the [Post-Plan Stage](/terraform/enterprise/run/states#the-post-plan-stage) by default. + +| Status | Response | Reason | +| ------- | ------------------------- | ---------------------------------------------------------------------- | +| [204][] | No Content | The request was successful | +| [404][] | [JSON API error object][] | Workspace or run task not found or user unauthorized to perform action | +| [422][] | [JSON API error object][] | Malformed request body | + +### Request Body + +This POST endpoint requires a JSON object with the following properties as a request payload. + +Properties without a default value are required. + +| Key path | Type | Default | Description | +| ----------------------------------- | ------ | --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `data.type` | string | | Must be `"workspace-tasks"`. | +| `data.attributes.enforcement-level` | string | | The enforcement level of the workspace task. Must be `"advisory"` or `"mandatory"`. | +| `data.attributes.stage` | string | `"post_plan"` | **DEPRECATED** Use `stages` instead. The stage in the run lifecycle when the run task should begin. Must be `"pre_plan"`, `"post_plan"`, `"pre_apply"`, or `"post_apply"`. | +| `data.attributes.stages` | array | `["post_plan"]` | An array of strings representing the stages of the run lifecycle when the run task should begin. Must be one or more of `"pre_plan"`, `"post_plan"`, `"pre_apply"`, or `"post_apply"`. | +| `data.relationships.task.data.id` | string | | The ID of the run task. | +| `data.relationships.task.data.type` | string | | Must be `"tasks"`. | + +### Sample Payload + +```json +{ + "data": { + "type": "workspace-tasks", + "attributes": { + "enforcement-level": "advisory", + "stages": ["post_plan"] + }, + "relationships": { + "task": { + "data": { + "id": "task-7oD7doVTQdAFnMLV", + "type": "tasks" + } + } + } + } +} +``` + +### Sample Request + +```shell +curl \ + -H "Authorization: Bearer $TOKEN" \ + -H "Content-Type: application/vnd.api+json" \ + --request POST \ + --data @payload.json \ + https://app.terraform.io/api/v2/workspaces/ws-PphL7ix3yGasYGrq/tasks +``` + +### Sample Response + +```json +{ + "data": { + "id": "wstask-tBXYu8GVAFBpcmPm", + "type": "workspace-tasks", + "attributes": { + "enforcement-level": "advisory", + "stage": "post_plan", + "stages": ["post_plan"] + }, + "relationships": { + "task": { + "data": { + "id": "task-7oD7doVTQdAFnMLV", + "type": "tasks" + } + }, + "workspace": { + "data": { + "id": "ws-PphL7ix3yGasYGrq", + "type": "workspaces" + } + } + }, + "links": { + "self": "/api/v2/workspaces/ws-PphL7ix3yGasYGrq/tasks/task-tBXYu8GVAFBpcmPm" + } + } +} +``` + +## List Workspace Run Tasks + +`GET /workspaces/:workspace_id/tasks` + +| Parameter | Description | +| --------------- | -------------------------------- | +| `:workspace_id` | The workspace to list tasks for. | + +| Status | Response | Reason | +| ------- | --------------------------------------- | ----------------------------------------------------------- | +| [200][] | [JSON API document][] (`type: "tasks"`) | Request was successful | +| [404][] | [JSON API error object][] | Workspace not found, or user unauthorized to perform action | + +### Query Parameters + +This endpoint supports pagination [with standard URL query parameters](/terraform/enterprise/api-docs#query-parameters). Remember to percent-encode `[` as `%5B` and `]` as `%5D` if your tooling doesn't automatically encode URLs. + +| Parameter | Description | +| -------------- | ------------------------------------------------------------------------- | +| `page[number]` | **Optional.** If omitted, the endpoint will return the first page. | +| `page[size]` | **Optional.** If omitted, the endpoint will return 20 run tasks per page. | + +### Sample Request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + https://app.terraform.io/api/v2/workspaces/ws-kRsDRPtTmtcEme4t/tasks +``` + +### Sample Response + +```json +{ + "data": [ + { + "id": "wstask-tBXYu8GVAFBpcmPm", + "type": "workspace-tasks", + "attributes": { + "enforcement-level": "advisory", + "stage": "post_plan", + "stages": ["post_plan"] + }, + "relationships": { + "task": { + "data": { + "id": "task-hu74ST39g566Q4m5", + "type": "tasks" + } + }, + "workspace": { + "data": { + "id": "ws-kRsDRPtTmtcEme4t", + "type": "workspaces" + } + } + }, + "links": { + "self": "/api/v2/workspaces/ws-kRsDRPtTmtcEme4t/tasks/task-tBXYu8GVAFBpcmPm" + } + } + ], + "links": { + "self": "https://app.terraform.io/api/v2/workspaces/ws-kRsDRPtTmtcEme4t/tasks?page%5Bnumber%5D=1&page%5Bsize%5D=20", + "first": "https://app.terraform.io/api/v2/workspaces/ws-kRsDRPtTmtcEme4t/tasks?page%5Bnumber%5D=1&page%5Bsize%5D=20", + "prev": null, + "next": null, + "last": "https://app.terraform.io/api/v2/workspaces/ws-kRsDRPtTmtcEme4t/tasks?page%5Bnumber%5D=1&page%5Bsize%5D=20" + }, + "meta": { + "pagination": { + "current-page": 1, + "page-size": 20, + "prev-page": null, + "next-page": null, + "total-pages": 1, + "total-count": 1 + } + } +} +``` + +## Show Workspace Run Task + +`GET /workspaces/:workspace_id/tasks/:id` + +| Parameter | Description | +| --------- | --------------------------------------------------------------------------------------------------------------------------- | +| `:id` | The ID of the workspace task to show. Use the ["List Workspace Run Tasks"](#list-workspace-run-tasks) endpoint to find IDs. | + +| Status | Response | Reason | +| ------- | --------------------------------------- | ------------------------------------------------------------------- | +| [200][] | [JSON API document][] (`type: "tasks"`) | The request was successful | +| [404][] | [JSON API error object][] | Workspace run task not found or user unauthorized to perform action | + +### Sample Request + +```shell +curl --request GET \ + -H "Authorization: Bearer $TOKEN" \ + -H "Content-Type: application/vnd.api+json" \ + https://app.terraform.io/api/v2/workspaces/ws-kRsDRPtTmtcEme4t/tasks/wstask-tBXYu8GVAFBpcmPm +``` + +### Sample Response + +```json +{ + "data": { + "id": "wstask-tBXYu8GVAFBpcmPm", + "type": "workspace-tasks", + "attributes": { + "enforcement-level": "advisory", + "stage": "post_plan", + "stages": ["post_plan"] + }, + "relationships": { + "task": { + "data": { + "id": "task-hu74ST39g566Q4m5", + "type": "tasks" + } + }, + "workspace": { + "data": { + "id": "ws-kRsDRPtTmtcEme4t", + "type": "workspaces" + } + } + }, + "links": { + "self": "/api/v2/workspaces/ws-kRsDRPtTmtcEme4t/tasks/wstask-tBXYu8GVAFBpcmPm" + } + } +} +``` + +## Update Workspace Run Task + +`PATCH /workspaces/:workspace_id/tasks/:id` + +| Parameter | Description | +| --------- | ------------------------------------------------------------------------------------------------------------------- | +| `:id` | The ID of the task to update. Use the ["List Workspace Run Tasks"](#list-workspace-run-tasks) endpoint to find IDs. | + +| Status | Response | Reason | +| ------- | --------------------------------------- | ------------------------------------------------------------------- | +| [200][] | [JSON API document][] (`type: "tasks"`) | The request was successful | +| [404][] | [JSON API error object][] | Workspace run task not found or user unauthorized to perform action | +| [422][] | [JSON API error object][] | Malformed request body (missing attributes, wrong types, etc.) | + +### Request Body + +This PATCH endpoint requires a JSON object with the following properties as a request payload. + +Properties without a default value are required. + +| Key path | Type | Default | Description | +| ----------------------------------- | ------ | ---------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `data.type` | string | (previous value) | Must be `"workspace-tasks"`. | +| `data.attributes.enforcement-level` | string | (previous value) | The enforcement level of the workspace run task. Must be `"advisory"` or `"mandatory"`. | +| `data.attributes.stage` | string | (previous value) | **DEPRECATED** Use `stages` instead. The stage in the run lifecycle when the run task should begin. Must be `"pre_plan"` or `"post_plan"`. | +| `data.attributes.stages` | array | (previous value) | An array of strings representing the stages of the run lifecycle when the run task should begin. Must be one or more of `"pre_plan"`, `"post_plan"`, `"pre_apply"`, or `"post_apply"`. | + +### Sample Payload + +```json +{ + "data": { + "type": "workspace-tasks", + "attributes": { + "enforcement-level": "mandatory", + "stages": ["post_plan"] + } + } +} +``` + +#### Deprecated Payload + +```json +{ + "data": { + "type": "workspace-tasks", + "attributes": { + "enforcement-level": "mandatory", + "stages": ["post_plan"] + } + } +} +``` + +### Sample Request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request PATCH \ + --data @payload.json \ + https://app.terraform.io/api/v2/workspaces/ws-kRsDRPtTmtcEme4t/tasks/wstask-tBXYu8GVAFBpcmPm +``` + +### Sample Response + +```json +{ + "data": { + "id": "wstask-tBXYu8GVAFBpcmPm", + "type": "workspace-tasks", + "attributes": { + "enforcement-level": "mandatory", + "stage": "post_plan", + "stages": ["post_plan"] + }, + "relationships": { + "task": { + "data": { + "id": "task-hu74ST39g566Q4m5", + "type": "tasks" + } + }, + "workspace": { + "data": { + "id": "ws-kRsDRPtTmtcEme4t", + "type": "workspaces" + } + } + }, + "links": { + "self": "/api/v2/workspaces/ws-kRsDRPtTmtcEme4t/tasks/task-tBXYu8GVAFBpcmPm" + } + } +} +``` + +## Delete Workspace Run Task + +`DELETE /workspaces/:workspace_id/tasks/:id` + +| Parameter | Description | +| --------- | --------------------------------------------------------------------------------------------------------------------------------- | +| `:id` | The ID of the Workspace run task to delete. Use the ["List Workspace Run Tasks"](#list-workspace-run-tasks) endpoint to find IDs. | + +| Status | Response | Reason | +| ------- | ------------------------- | -------------------------------------------------------------------- | +| [204][] | No Content | Successfully deleted the workspace run task | +| [404][] | [JSON API error object][] | Workspace run task not found, or user unauthorized to perform action | + +### Sample Request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request DELETE \ + https://app.terraform.io/api/v2/workspaces/ws-kRsDRPtTmtcEme4t/tasks/wstask-tBXYu8GVAFBpcmPm +``` diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/run-triggers.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/run-triggers.mdx new file mode 100644 index 0000000000..e557a25a36 --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/run-triggers.mdx @@ -0,0 +1,349 @@ +--- +page_title: /run-triggers API reference for Terraform Enterprise +description: >- + Use the Terraform Enterprise API's `/run-triggers` endpoint to read, create, + and delete run triggers. +source: terraform-docs-common +--- + +[200]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/200 + +[201]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/201 + +[202]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/202 + +[204]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/204 + +[400]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/400 + +[401]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/401 + +[403]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/403 + +[404]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404 + +[409]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/409 + +[412]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/412 + +[422]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/422 + +[429]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/429 + +[500]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500 + +[504]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/504 + +[JSON API document]: /terraform/enterprise/api-docs#json-api-documents + +[JSON API error object]: https://jsonapi.org/format/#error-objects + +# Run triggers API reference + +## Create a Run Trigger + +`POST /workspaces/:workspace_id/run-triggers` + +| Parameter | Description | +| --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `:workspace_id` | The ID of the workspace to create the run trigger in. Obtain this from the [workspace settings](/terraform/enterprise/workspaces/settings) or the [Show Workspace](/terraform/enterprise/api-docs/workspaces#show-workspace) endpoint. | + +| Status | Response | Reason | +| ------- | ---------------------------------------------- | ------------------------------------------------------------------------ | +| [201][] | [JSON API document][] (`type: "run-triggers"`) | Successfully created a run trigger | +| [404][] | [JSON API error object][] | Workspace or sourceable not found or user unauthorized to perform action | +| [422][] | [JSON API error object][] | Malformed request body (missing attributes, wrong types, etc.) | + +### Permissions + +In order to create a run trigger, the user must have admin access to the specified workspace and permission to read runs for the sourceable workspace. ([More about permissions.](/terraform/enterprise/users-teams-organizations/permissions)) + +[permissions-citation]: #intentionally-unused---keep-for-maintainers + +### Request Body + +This POST endpoint requires a JSON object with the following properties as a request payload. + +Properties without a default value are required. + +| Key path | Type | Default | Description | +| ------------------------------------ | ------ | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `data.relationships.sourceable.data` | object | | A JSON API relationship object that represents the source workspace for the run trigger. This object must have `id` and `type` properties, and the `type` property must be `workspaces` (e.g. `{ "id": "ws-2HRvNs49EWPjDqT1", "type": "workspaces" }`). Obtain workspace IDs from the [workspace settings](/terraform/enterprise/workspaces/settings) or the [Show Workspace](/terraform/enterprise/api-docs/workspaces#show-workspace) endpoint. | + +### Sample Payload + +```json +{ + "data": { + "relationships": { + "sourceable": { + "data": { + "id": "ws-2HRvNs49EWPjDqT1", + "type": "workspaces" + } + } + } + } +} +``` + +### Sample Request + +```shell +curl \ + --request POST \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --data @payload.json \ + https://app.terraform.io/api/v2/workspaces/ws-XdeUVMWShTesDMME/run-triggers +``` + +### Sample Response + +```json +{ + "data": { + "id": "rt-3yVQZvHzf5j3WRJ1", + "type": "run-triggers", + "attributes": { + "workspace-name": "workspace-1", + "sourceable-name": "workspace-2", + "created-at": "2018-09-11T18:21:21.784Z" + }, + "relationships": { + "workspace": { + "data": { + "id": "ws-XdeUVMWShTesDMME", + "type": "workspaces" + } + }, + "sourceable": { + "data": { + "id": "ws-2HRvNs49EWPjDqT1", + "type": "workspaces" + } + } + }, + "links": { + "self": "/api/v2/run-triggers/rt-3yVQZvHzf5j3WRJ1" + } + } +} +``` + +## List Run Triggers + +`GET /workspaces/:workspace_id/run-triggers` + +| Parameter | Description | +| --------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `:workspace_id` | The ID of the workspace to list run triggers for. Obtain this from the [workspace settings](/terraform/enterprise/workspaces/settings) or the [Show Workspace](/terraform/enterprise/api-docs/workspaces#show-workspace) endpoint. | + +| Status | Response | Reason | +| ------- | ---------------------------------------------- | -------------------------------------------------------------------------------------------- | +| [200][] | [JSON API document][] (`type: "run-triggers"`) | Request was successful | +| [400][] | [JSON API error object][] | Required parameter `filter[run-trigger][type]` is missing or has been given an invalid value | +| [404][] | [JSON API error object][] | Workspace not found or user unauthorized to perform action | + +### Query Parameters + +This endpoint supports pagination [with standard URL query parameters](/terraform/enterprise/api-docs#query-parameters); remember to percent-encode `[` as `%5B` and `]` as `%5D` if your tooling doesn't automatically encode URLs. + +| Parameter | Description | +| --------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `filter[run-trigger][type]` | **Required** Which type of run triggers to list; valid values are `inbound` or `outbound`. `inbound` run triggers create runs in the specified workspace, and `outbound` run triggers create runs in other workspaces. | +| `page[number]` | **Optional.** If omitted, the endpoint will return the first page. | +| `page[size]` | **Optional.** If omitted, the endpoint will return 20 run triggers per page. | + +### Permissions + +In order to list run triggers, the user must have permission to read runs for the specified workspace. ([More about permissions.](/terraform/enterprise/users-teams-organizations/permissions)) + +[permissions-citation]: #intentionally-unused---keep-for-maintainers + +### Sample Request + +```shell +curl \ + --request GET \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + https://app.terraform.io/api/v2/workspaces/ws-XdeUVMWShTesDMME/run-triggers?filter%5Brun-trigger%5D%5Btype%5D=inbound +``` + +### Sample Response + +```json +{ + "data": [ + { + "id": "rt-WygcwSBuYaQWrM39", + "type": "run-triggers", + "attributes": { + "workspace-name": "workspace-1", + "sourceable-name": "workspace-2", + "created-at": "2018-09-11T18:21:21.784Z" + }, + "relationships": { + "workspace": { + "data": { + "id": "ws-XdeUVMWShTesDMME", + "type": "workspaces" + } + }, + "sourceable": { + "data": { + "id": "ws-2HRvNs49EWPjDqT1", + "type": "workspaces" + } + } + }, + "links": { + "self": "/api/v2/run-triggers/rt-WygcwSBuYaQWrM39" + } + }, + { + "id": "rt-8F5JFydVYAmtTjET", + "type": "run-triggers", + "attributes": { + "workspace-name": "workspace-1", + "sourceable-name": "workspace-3", + "created-at": "2018-09-11T18:21:21.784Z" + }, + "relationships": { + "workspace": { + "data": { + "id": "ws-XdeUVMWShTesDMME", + "type": "workspaces" + } + }, + "sourceable": { + "data": { + "id": "ws-BUHBEM97xboT8TVz", + "type": "workspaces" + } + } + }, + "links": { + "self": "/api/v2/run-triggers/rt-8F5JFydVYAmtTjET" + } + } + ], + "links": { + "self": "https://app.terraform.io/api/v2/workspaces/ws-xdiJLyGpCugbFDE1/run-triggers?filter%5Brun-trigger%5D%5Btype%5D=inbound&page%5Bnumber%5D=1&page%5Bsize%5D=20", + "first": "https://app.terraform.io/api/v2/workspaces/ws-xdiJLyGpCugbFDE1/run-triggers?filter%5Brun-trigger%5D%5Btype%5D=inbound&page%5Bnumber%5D=1&page%5Bsize%5D=20", + "prev": null, + "next": null, + "last": "https://app.terraform.io/api/v2/workspaces/ws-xdiJLyGpCugbFDE1/run-triggers?filter%5Brun-trigger%5D%5Btype%5D=inbound&page%5Bnumber%5D=1&page%5Bsize%5D=20" + }, + "meta": { + "pagination": { + "current-page": 1, + "prev-page": null, + "next-page": null, + "total-pages": 1, + "total-count": 2 + } + } +} +``` + +## Show a Run Trigger + +`GET /run-triggers/:run_trigger_id` + +| Parameter | Description | +| ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `:run_trigger_id` | The ID of the run trigger to show. Send a `GET` request to the `run-triggers` endpoint to find IDs. Refer to [List Run Triggers](#list-run-triggers) for details. | + +| Status | Response | Reason | +| ------- | ---------------------------------------------- | ------------------------------------------------------------ | +| [200][] | [JSON API document][] (`type: "run-triggers"`) | The request was successful | +| [404][] | [JSON API error object][] | Run trigger not found or user unauthorized to perform action | + +### Permissions + +In order to show a run trigger, the user must have permission to read runs for either the workspace or sourceable workspace of the specified run trigger. ([More about permissions.](/terraform/enterprise/users-teams-organizations/permissions)) + +[permissions-citation]: #intentionally-unused---keep-for-maintainers + +### Sample Request + +```shell +curl \ + --request GET \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + https://app.terraform.io/api/v2/run-triggers/rt-3yVQZvHzf5j3WRJ1 +``` + +### Sample Response + +```json +{ + "data": { + "id": "rt-3yVQZvHzf5j3WRJ1", + "type": "run-triggers", + "attributes": { + "workspace-name": "workspace-1", + "sourceable-name": "workspace-2", + "created-at": "2018-09-11T18:21:21.784Z" + }, + "relationships": { + "workspace": { + "data": { + "id": "ws-XdeUVMWShTesDMME", + "type": "workspaces" + } + }, + "sourceable": { + "data": { + "id": "ws-2HRvNs49EWPjDqT1", + "type": "workspaces" + } + } + }, + "links": { + "self": "/api/v2/run-triggers/rt-3yVQZvHzf5j3WRJ1" + } + } +} +``` + +## Delete a Run Trigger + +`DELETE /run-triggers/:run_trigger_id` + +| Parameter | Description | +| ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `:run_trigger_id` | The ID of the run trigger to delete. Send a `GET` request to the `run-triggers` endpoint o find IDs. Refer to [List Run Triggers](#list-run-triggers) for details. | + +| Status | Response | Reason | +| ------- | ------------------------- | ------------------------------------------------------------ | +| [204][] | No Content | Successfully deleted the run trigger | +| [404][] | [JSON API error object][] | Run trigger not found or user unauthorized to perform action | + +### Permissions + +In order to delete a run trigger, the user must have admin access to the specified workspace. ([More about permissions.](/terraform/enterprise/users-teams-organizations/permissions)) + +[permissions-citation]: #intentionally-unused---keep-for-maintainers + +### Sample Request + +```shell +curl \ + --request DELETE \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + https://app.terraform.io/api/v2/run-triggers/rt-3yVQZvHzf5j3WRJ1 +``` + +## Available Related Resources + +The GET endpoints above can optionally return related resources, if requested with [the `include` query parameter](/terraform/enterprise/api-docs#inclusion-of-related-resources). The following resource types are available: + +These includes respect read permissions. If you do not have access to read the related resource, it will not be returned. + +- `workspace` - The full workspace object. +- `sourceable` - The full source workspace object. diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/run.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/run.mdx new file mode 100644 index 0000000000..950e1d8880 --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/run.mdx @@ -0,0 +1,902 @@ +--- +page_title: /runs API reference for Terraform Enterprise +description: >- + Use the Terraform Enterprise API's `/runs` endpoint to read, get, create, + apply, discard, execute, and cancel Terraform runs. You can also list a + workspace's or organization's runs. +source: terraform-docs-common +--- + +[200]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/200 + +[201]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/201 + +[202]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/202 + +[204]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/204 + +[400]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/400 + +[401]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/401 + +[403]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/403 + +[404]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404 + +[409]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/409 + +[412]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/412 + +[422]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/422 + +[429]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/429 + +[500]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500 + +[504]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/504 + +[JSON API document]: /terraform/enterprise/api-docs#json-api-documents + +[JSON API error object]: https://jsonapi.org/format/#error-objects + +# Runs API reference + +-> **Note:** Before working with the runs or configuration versions APIs, read the [API-driven run workflow](/terraform/enterprise/run/api) page, which includes both a full overview of this workflow and a walkthrough of a simple implementation of it. + +Performing a run on a new configuration is a multi-step process. + +1. [Create a configuration version on the workspace](/terraform/enterprise/api-docs/configuration-versions#create-a-configuration-version). +2. [Upload configuration files to the configuration version](/terraform/enterprise/api-docs/configuration-versions#upload-configuration-files). +3. [Create a run on the workspace](#create-a-run); this is done automatically when a configuration file is uploaded. +4. [Create and queue an apply on the run](#apply-a-run); if the run can't be auto-applied. + +Alternatively, you can create a run with a pre-existing configuration version, even one from another workspace. This is useful for promoting known good code from one workspace to another. + +## Attributes + +### Run States + +The run state is found in `data.attributes.status`, and you can reference the following list of possible states. + +| State | Description | +| ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `pending` | The initial status of a run after creation. | +| `fetching` | The run is waiting for HCP Terraform to fetch the configuration from VCS. | +| `fetching_completed` | HCP Terraform has fetched the configuration from VCS and the run will continue. | +| `pre_plan_running` | The pre-plan phase of the run is in progress. | +| `pre_plan_completed` | The pre-plan phase of the run has completed. | +| `queuing` | HCP Terraform is queuing the run to start the planning phase. | +| `plan_queued` | HCP Terraform is waiting for its backend services to start the plan. | +| `planning` | The planning phase of a run is in progress. | +| `planned` | The planning phase of a run has completed. | +| `cost_estimating` | The cost estimation phase of a run is in progress. | +| `cost_estimated` | The cost estimation phase of a run has completed. | +| `policy_checking` | The sentinel policy checking phase of a run is in progress. | +| `policy_override` | A sentinel policy has soft failed, and a user can override it to continue the run. | +| `policy_soft_failed` | A sentinel policy has soft failed for a plan-only run. This is a final state. | +| `policy_checked` | The sentinel policy checking phase of a run has completed. | +| `confirmed` | A user has confirmed the plan. | +| `post_plan_running` | The post-plan phase of the run is in progress. | +| `post_plan_completed` | The post-plan phase of the run has completed. | +| `planned_and_finished` | The run is completed. This status only exists for plan-only runs and runs that produce a plan with no changes to apply. This is a final state. | +| `planned_and_saved` | The run has finished its planning, checks, and estimates, and can be confirmed for apply. This status is only used for saved plan runs. | +| `apply_queued` | Once the changes in the plan have been confirmed, the run will transition to `apply_queued`. This status indicates that the run should start as soon as the backend services that run terraform have available capacity. In HCP Terraform, you should seldom see this status, as our aim is to always have capacity. However, in Terraform Enterprise this status will be more common due to the self-hosted nature. | +| `applying` | Terraform is applying the changes specified in the plan. | +| `applied` | Terraform has applied the changes specified in the plan. | +| `discarded` | The run has been discarded. This is a final state. | +| `errored` | The run has errored. This is a final state. | +| `canceled` | The run has been canceled. | +| `force_canceled` | A workspace admin forcefully canceled the run. | + +### Run Operations + +The run operation specifies the Terraform execution mode. You can reference the following list of possible execution modes and use them as query parameters in the [workspace](/terraform/enterprise/api-docs/run#list-runs-in-a-workspace) and [organization](/terraform/enterprise/api-docs/run#list-runs-in-a-organization) runs lists. + +| Operation | Description | +| ---------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `plan_only` | The run does not have an apply phase. This is also called a [_speculative plan_](/terraform/enterprise/run/modes-and-options#plan-only-speculative-plan). | +| `plan_and_apply` | The run includes both plan and apply phases. | +| `save_plan` | The run is a saved plan run. It can include both plan and apply phases, but only becomes the workspace's current run if a user chooses to apply it. | +| `refresh_only` | The run should update Terraform state, but not make changes to resources. | +| `destroy` | The run should destroy all objects, regardless of configuration changes. | +| `empty_apply` | The run should perform an apply with no changes to resources. This is most commonly used to [upgrade terraform state versions](/terraform/enterprise/workspaces/state#upgrading-state). | + +### Run Sources + +You can use the following sources as query parameters in [workspace](/terraform/enterprise/api-docs/run#list-runs-in-a-workspace) and [organization](/terraform/enterprise/api-docs/run#list-runs-in-a-organization) runs lists. + +| Source | Description | +| --------------------------- | --------------------------------------------------------------------------------------- | +| `tfe-ui` | Indicates a run was queued from HCP Terraform UI. | +| `tfe-api` | Indicates a run was queued from HCP Terraform API. | +| `tfe-configuration-version` | Indicates a run was queued from a Configuration Version, triggered from a VCS provider. | + +### Run Status Groups + +The run status group specifies a collection of run states by logical category. + +| Group | Description | +| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `non_final` | Inclusive of runs that are currently running, require user confirmation, or are queued/pending. | +| `final` | Inclusive of runs that have reached their final and terminal state. | +| `discardable` | Inclusive of runs whose state falls under the following: `planned`, `planned_and_saved`, `cost_estimated`, `policy_checked`, `policy_override`, `post_plan_running`, `post_plan_completed` | + +## Create a Run + +`POST /runs` + +A run performs a plan and apply, using a configuration version and the workspace’s current variables. You can specify a configuration version when creating a run; if you don’t provide one, the run defaults to the workspace’s most recently used version. (A configuration version is “used” when it is created or used for a run in this workspace.) + +Creating a run requires permission to queue plans for the specified workspace. ([More about permissions.](/terraform/enterprise/users-teams-organizations/permissions)) + +When creating a run, you may optionally provide a list of variable objects containing key and value attributes. These values apply to that run specifically and take precedence over variables with the same key applied to the workspace(e.g., variable sets). Refer to [Variable Precedence](/terraform/enterprise/workspaces/variables#precedence) for more information. All values must be expressed as an HCL literal in the same syntax you would use when writing Terraform code. Refer to [Types](/terraform/language/expressions/types#types) in the Terraform documentation for more details. + +Setting `debugging_mode: true` enables debugging mode for the queued run only. This is equivalent to setting the `TF_LOG` environment variable to `TRACE` for this run. See [Debugging Terraform](/terraform/internals/debugging) for more information. + +**Sample Run Variables:** + +```json +"attributes": { + "variables": [ + { "key": "replicas", "value": "2" }, + { "key": "access_key", "value": "\"ABCDE12345\"" } + ] +} +``` + +[permissions-citation]: #intentionally-unused---keep-for-maintainers + +-> **Note:** This endpoint cannot be accessed with [organization tokens](/terraform/enterprise/users-teams-organizations/api-tokens#organization-api-tokens). You must access it with a [user token](/terraform/enterprise/users-teams-organizations/users#api-tokens) or [team token](/terraform/enterprise/users-teams-organizations/api-tokens#team-api-tokens). + +### Request Body + +This POST endpoint requires a JSON object with the following properties as a request payload. + +Properties without a default value are required. + +| Key path | Type | Default | Description | +| -------------------------------------------------- | -------------------- | ---------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `data.attributes.allow-empty-apply` | bool | none | Specifies whether Terraform can apply the run even when the plan [contains no changes](/terraform/enterprise/run/modes-and-options#allow-empty-apply). Use this property to [upgrade state](/terraform/enterprise/workspaces/state#upgrading-state) after upgrading a workspace to a new terraform version. | +| `data.attributes.allow-config-generation` | bool | `false` | Specifies whether Terraform can [generate resource configuration](/terraform/language/import/generating-configuration) when planning to import new resources. When set to `false`, Terraform returns an error when `import` blocks do not have a corresponding `resource` block. | +| `data.attributes.auto-apply` | bool | Defaults to the [Auto Apply](/terraform/enterprise/workspaces/settings#auto-apply-and-manual-apply) workspace setting. | Determines if Terraform automatically applies the configuration on a successful `terraform plan`. | +| `data.attributes.debugging-mode` | bool | `false` | When set to `true`, enables verbose logging for the queued plan. | +| `data.attributes.is-destroy` | bool | `false` | When set to `true`, the plan destroys all provisioned resources. Mutually exclusive with `refresh-only`. | +| `data.attributes.message` | string | `"Queued manually via the Terraform Enterprise API"` | Specifies the message associated with this run. | +| `data.attributes.refresh` | bool | `true` | Specifies whether or not to refresh the state before a plan. | +| `data.attributes.refresh-only` | bool | `false` | When set to `true`, this run refreshes the state without modifying any resources. Mutually exclusive with `is-destroy`. | +| `data.attributes.replace-addrs` | array\[string] | | Specifies an optional list of resource addresses to be passed to the `-replace` flag. | +| `data.attributes.target-addrs` | array\[string] | | Specifies an optional list of resource addresses to be passed to the `-target` flag. | +| `data.attributes.variables` | array\[{key, value}] | (empty array) | Specifies an optional list of run-specific variable values. Refer to [Run-Specific Variables](/terraform/enterprise/workspaces/variables/managing-variables#run-specific-variables) for details. | +| `data.attributes.plan-only` | bool | (from configuration version) | Specifies if this is a [speculative, plan-only](/terraform/enterprise/run/modes-and-options#plan-only-speculative-plan) run that Terraform cannot apply. Often used in conjunction with terraform-version in order to test whether an upgrade would succeed. | +| `data.attributes.save-plan` | bool | `false` | When set to `true`, the run is executed as a `save plan` run. A `save plan` run plans and checks the configuration without becoming the workspace's current run. These run types only becomes the current run if you confirm that you want to apply them when prompted. When creating new [configuration versions](/terraform/enterprise/api-docs/configuration-versions) for saved plan runs, be sure to make them `provisional`. | +| `data.attributes.terraform-version` | string | none | Specifies the Terraform version to use in this run. Only valid for plan-only runs; must be a valid Terraform version available to the organization. | +| `data.relationships.workspace.data.id` | string | none | Specifies the workspace ID to execute the run in. | +| `data.relationships.configuration-version.data.id` | string | none | Specifies the configuration version to use for this run. If the `configuration-version` object is omitted, Terraform uses the workspace's latest configuration version to create the run . | + +| Status | Response | Reason | +| ------- | -------------------------------------- | --------------------------------------------------------------------------- | +| [201][] | [JSON API document][] (`type: "runs"`) | Successfully created a run | +| [404][] | [JSON API error object][] | Organization or workspace not found, or user unauthorized to perform action | +| [422][] | [JSON API error object][] | Malformed request body (missing attributes, wrong types, etc.) | + +### Sample Payload + +```json +{ + "data": { + "attributes": { + "message": "Custom message" + }, + "type":"runs", + "relationships": { + "workspace": { + "data": { + "type": "workspaces", + "id": "ws-LLGHCr4SWy28wyGN" + } + }, + "configuration-version": { + "data": { + "type": "configuration-versions", + "id": "cv-n4XQPBa2QnecZJ4G" + } + } + } + } +} +``` + +### Sample Request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request POST \ + --data @payload.json \ + https://app.terraform.io/api/v2/runs +``` + +### Sample Response + +```json +{ + "data": { + "id": "run-CZcmD7eagjhyX0vN", + "type": "runs", + "attributes": { + "actions": { + "is-cancelable": true, + "is-confirmable": false, + "is-discardable": false, + "is-force-cancelable": false + }, + "canceled-at": null, + "created-at": "2021-05-24T07:38:04.171Z", + "has-changes": false, + "auto-apply": false, + "allow-empty-apply": false, + "allow-config-generation": false, + "is-destroy": false, + "message": "Custom message", + "plan-only": false, + "source": "tfe-api", + "status-timestamps": { + "plan-queueable-at": "2021-05-24T07:38:04+00:00" + }, + "status": "pending", + "trigger-reason": "manual", + "target-addrs": null, + "permissions": { + "can-apply": true, + "can-cancel": true, + "can-comment": true, + "can-discard": true, + "can-force-execute": true, + "can-force-cancel": true, + "can-override-policy-check": true + }, + "refresh": false, + "refresh-only": false, + "replace-addrs": null, + "save-plan": false, + "variables": [] + }, + "relationships": { + "apply": {...}, + "comments": {...}, + "configuration-version": {...}, + "cost-estimate": {...}, + "created-by": {...}, + "input-state-version": {...}, + "plan": {...}, + "run-events": {...}, + "policy-checks": {...}, + "workspace": {...}, + "workspace-run-alerts": {...} + }, + "links": { + "self": "/api/v2/runs/run-CZcmD7eagjhyX0vN" + } + } +} +``` + +## Apply a Run + +`POST /runs/:run_id/actions/apply` + +| Parameter | Description | +| --------- | ------------------- | +| `run_id` | The run ID to apply | + +Applies a run that is paused waiting for confirmation after a plan. This includes runs in the "needs confirmation" and "policy checked" states. This action is only required for runs that can't be auto-applied. Plans can be auto-applied if the auto-apply setting is enabled on the workspace and the plan was queued by a new VCS commit or by a user with permission to apply runs for the workspace. + +-> **Note:** If the run has a soft failed sentinel policy, you will need to [override the policy check](/terraform/enterprise/api-docs/policy-checks#override-policy) before Terraform can apply the run. You can find policy check details in the `relationships` section of the [run details endpoint](#get-run-details) response. + +Applying a run requires permission to apply runs for the workspace. ([More about permissions.](/terraform/enterprise/users-teams-organizations/permissions)) + +[permissions-citation]: #intentionally-unused---keep-for-maintainers + +This endpoint queues the request to perform an apply; the apply might not happen immediately. + +Since this endpoint represents an action (not a resource), it does not return any object in the response body. + +-> **Note:** This endpoint cannot be accessed with [organization tokens](/terraform/enterprise/users-teams-organizations/api-tokens#organization-api-tokens). You must access it with a [user token](/terraform/enterprise/users-teams-organizations/users#api-tokens) or [team token](/terraform/enterprise/users-teams-organizations/api-tokens#team-api-tokens). + +| Status | Response | Reason(s) | +| ------- | ------------------------- | ------------------------------------------------------- | +| [202][] | none | Successfully queued an apply request. | +| [409][] | [JSON API error object][] | Run was not paused for confirmation; apply not allowed. | + +### Request Body + +This POST endpoint allows an optional JSON object with the following properties as a request payload. + +| Key path | Type | Default | Description | +| --------- | ------ | ------- | ---------------------------------- | +| `comment` | string | `null` | An optional comment about the run. | + +### Sample Payload + +This payload is optional, so the `curl` command will work without the `--data @payload.json` option too. + +```json +{ + "comment":"Looks good to me" +} +``` + +### Sample Request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request POST \ + --data @payload.json \ + https://app.terraform.io/api/v2/runs/run-DQGdmrWMX8z9yWQB/actions/apply +``` + +## List Runs in a Workspace + +`GET /workspaces/:workspace_id/runs` + +| Parameter | Description | +| -------------- | ---------------------------------- | +| `workspace_id` | The workspace ID to list runs for. | + +By default, `plan_only` runs will be excluded from the results. To see all runs, use `filter[operation]` with all available operations included as a comma-separated list. +This endpoint has an adjusted rate limit of 30 requests per minute. Note that most endpoints are limited to 30 requests per second. + +| Status | Response | Reason | +| ------- | ------------------------------------------------ | ------------------------ | +| [200][] | Array of [JSON API document][]s (`type: "runs"`) | Successfully listed runs | + +### Query Parameters + +This endpoint supports pagination [with standard URL query parameters](/terraform/enterprise/api-docs#query-parameters); remember to percent-encode `[` as `%5B` and `]` as `%5D` if your tooling doesn't automatically encode URLs. + +| Parameter | Description | Required | +| -------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | +| `page[number]` | If omitted, the endpoint returns the first page. | Optional | +| `page[size]` | If omitted, the endpoint returns 20 runs per page. | Optional | +| `filter[operation]` | A comma-separated list of run operations. The result lists runs that perform one of these operations. For details on options, refer to [Run operations](/terraform/enterprise/api-docs/run#run-operations). | Optional | +| `filter[status]` | A comma-separated list of run statuses. The result lists runs that are in one of the statuses you specify. For details on options, refer to [Run states](/terraform/enterprise/api-docs/run#run-states). | Optional | +| `filter[agent_pool_names]` | A comma-separated list of agent pool names. The result lists runs that use one of the agent pools you specify. | Optional | +| `filter[source]` | A comma-separated list of run sources. The result lists runs that came from one of the sources you specify. Options are listed in [Run Sources](/terraform/enterprise/api-docs/run#run-sources). | Optional | +| `filter[status_group]` | A single status group. The result lists runs whose status falls under this status group. For details on options, refer to [Run status groups](/terraform/enterprise/api-docs/run#run-status-groups). | Optional | +| `filter[timeframe]` | A single year period. The result lists runs that were created within the year you specify. An integer year or the string "year" for the past year are valid values. If omitted, the endpoint returns all runs since the creation of the workspace. | Optional | +| `search[user]` | Searches for runs that match the VCS username you supply. | Optional | +| `search[commit]` | Searches for runs that match the commit sha you specify. | Optional | +| `search[basic]` | Searches for runs that match the VCS username, commit sha, run_id, or run message your specify. HCP Terraform prioritizes `search[commit]` or `search[user]` and ignores `search[basic]` in favor of the higher priority parameters if you include them in your query. | Optional | + +### Sample Request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + https://app.terraform.io/api/v2/workspaces/ws-yF7z4gyEQRhaCNG9/runs +``` + +### Sample Response + +```json +{ + "data": [ + { + "id": "run-CZcmD7eagjhyX0vN", + "type": "runs", + "attributes": { + "actions": { + "is-cancelable": true, + "is-confirmable": false, + "is-discardable": false, + "is-force-cancelable": false + }, + "canceled-at": null, + "created-at": "2021-05-24T07:38:04.171Z", + "has-changes": false, + "auto-apply": false, + "allow-empty-apply": false, + "allow-config-generation": false, + "is-destroy": false, + "message": "Custom message", + "plan-only": false, + "source": "tfe-api", + "status-timestamps": { + "plan-queueable-at": "2021-05-24T07:38:04+00:00" + }, + "status": "pending", + "trigger-reason": "manual", + "target-addrs": null, + "permissions": { + "can-apply": true, + "can-cancel": true, + "can-comment": true, + "can-discard": true, + "can-force-execute": true, + "can-force-cancel": true, + "can-override-policy-check": true + }, + "refresh": false, + "refresh-only": false, + "replace-addrs": null, + "save-plan": false, + "variables": [] + }, + "relationships": { + "apply": {...}, + "comments": {...}, + "configuration-version": {...}, + "cost-estimate": {...}, + "created-by": {...}, + "input-state-version": {...}, + "plan": {...}, + "run-events": {...}, + "policy-checks": {...}, + "workspace": {...}, + "workspace-run-alerts": {...} + }, + "links": { + "self": "/api/v2/runs/run-bWSq4YeYpfrW4mx7" + } + }, + {...} + ], + "links": { + "first": "https://app.terraform.io/api/v2/workspaces/ws-yF7z4gyEQRhaCNG9/runs?page[number]=1&page[size]=20", + "last": "https://app.terraform.io/api/v2/workspaces/ws-yF7z4gyEQRhaCNG9/runs?page[number]=19206&page[size]=20", + "self": "https://app.terraform.io/api/v2/workspaces/ws-yF7z4gyEQRhaCNG9/runs?page[number]=2&page[size]=20", + "prev": "https://app.terraform.io/api/v2/workspaces/ws-yF7z4gyEQRhaCNG9/runs?page[number]=1&page[size]=20", + "next": "https://app.terraform.io/api/v2/workspaces/ws-yF7z4gyEQRhaCNG9/runs?page[number]=3&page[size]=20" + }, + "meta": { + "pagination": { + "current-page": 2, + "next-page": 3, + "prev-page": 1, + "page-size": 20, + "total-count": 384105, + "total-pages": 19206 + } + } +} +``` + +## List Runs in an Organization + +`GET /organizations/:organization_name/runs` + +| Parameter | Description | +| ------------------- | --------------------------------------- | +| `organization_name` | The organization name to list runs for. | + +This endpoint has an adjusted rate limit of 30 requests per minute. Note that most endpoints are limited to 30 requests per second. + +Note that this endpoint differs from others in the pagination metadata included in the response, such as the exclusion of the typical `total-count` and `total-pages`. See the Sample Response below for more details. + +| Status | Response | Reason | +| ------- | ------------------------------------------------ | ------------------------ | +| [200][] | Array of [JSON API document][]s (`type: "runs"`) | Successfully listed runs | + +### Query Parameters + +This endpoint supports pagination [with standard URL query parameters](/terraform/enterprise/api-docs#query-parameters); remember to percent-encode `[` as `%5B` and `]` as `%5D` if your tooling doesn't automatically encode URLs. + +| Parameter | Description | Required | +| -------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | +| `page[number]` | If omitted, the endpoint returns the first page. | Optional | +| `page[size]` | If omitted, the endpoint returns 20 runs per page. | Optional | +| `filter[operation]` | A comma-separated list of run operations. The result lists runs that perform one of these operations. For details on options, refer to [Run operations](/terraform/enterprise/api-docs/run#run-operations). | Optional | +| `filter[status]` | A comma-separated list of run statuses. The result lists runs that are in one of the statuses you specify. For details on options, refer to [Run states](/terraform/enterprise/api-docs/run#run-states). | Optional | +| `filter[agent_pool_names]` | A comma-separated list of agent pool names. The result lists runs that use one of the agent pools you specify. | Optional | +| `filter[workspace_names]` | A comma-separated list of workspace names. The result lists runs that belong to one of the workspaces your specify. | Optional | +| `filter[source]` | A comma-separated list of run sources. The result lists runs that came from one of the sources you specify. Options are listed in [Run Sources](/terraform/enterprise/api-docs/run#run-sources). | Optional | +| `filter[status_group]` | A single status group. The result lists runs whose status falls under this status group. For details on options, refer to [Run status groups](/terraform/enterprise/api-docs/run#run-status-groups). | Optional | +| `filter[timeframe]` | A single year period. The result lists runs that were created within the year you specify. An integer year or the string "year" for the past year are valid values. If omitted, the endpoint returns runs created in the last year. | Optional | +| `search[user]` | Searches for runs that match the VCS username you supply. | Optional | +| `search[commit]` | Searches for runs that match the commit sha you specify. | Optional | +| `search[basic]` | Searches for runs that match the VCS username, commit sha, run_id, or run message your specify. HCP Terraform prioritizes `search[commit]` or `search[user]` and ignores `search[basic]` in favor of the higher priority parameters if you include them in your query. | Optional | + +### Sample Request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + https://app.terraform.io/api/v2/organizations/hashicorp/runs +``` + +### Sample Response + +```json +{ + "data": [ + { + "id": "run-CZcmD7eagjhyX0vN", + "type": "runs", + "attributes": { + "actions": { + "is-cancelable": true, + "is-confirmable": false, + "is-discardable": false, + "is-force-cancelable": false + }, + "canceled-at": null, + "created-at": "2021-05-24T07:38:04.171Z", + "has-changes": false, + "auto-apply": false, + "allow-empty-apply": false, + "allow-config-generation": false, + "is-destroy": false, + "message": "Custom message", + "plan-only": false, + "source": "tfe-api", + "status-timestamps": { + "plan-queueable-at": "2021-05-24T07:38:04+00:00" + }, + "status": "pending", + "trigger-reason": "manual", + "target-addrs": null, + "permissions": { + "can-apply": true, + "can-cancel": true, + "can-comment": true, + "can-discard": true, + "can-force-execute": true, + "can-force-cancel": true, + "can-override-policy-check": true + }, + "refresh": false, + "refresh-only": false, + "replace-addrs": null, + "save-plan": false, + "variables": [] + }, + "relationships": { + "apply": {...}, + "comments": {...}, + "configuration-version": {...}, + "cost-estimate": {...}, + "created-by": {...}, + "input-state-version": {...}, + "plan": {...}, + "run-events": {...}, + "policy-checks": {...}, + "workspace": {...}, + "workspace-run-alerts": {...} + }, + "links": { + "self": "/api/v2/runs/run-bWSq4YeYpfrW4mx7" + } + }, + {...} + ], + "links": { + "self": "https://app.terraform.io/api/v2/organizations/hashicorp/runs?page[number]=2&page[size]=20", + "prev": "https://app.terraform.io/api/v2/organizations/hashicorp/runs?page[number]=1&page[size]=20", + "next": "https://app.terraform.io/api/v2/organizations/hashicorp/runs?page[number]=3&page[size]=20" + }, + "meta": { + "pagination": { + "current-page": 1, + "next-page": 2, + "prev-page": null, + "page-size": 20 + } + } +} +``` + +## Get run details + +`GET /runs/:run_id` + +| Parameter | Description | +| --------- | ------------------ | +| `:run_id` | The run ID to get. | + +This endpoint is used for showing details of a specific run. + +| Status | Response | Reason | +| ------- | -------------------------------------- | ------------------------------------ | +| [200][] | [JSON API document][] (`type: "runs"`) | Success | +| [404][] | [JSON API error object][] | Run not found or user not authorized | + +### Sample Request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + https://app.terraform.io/api/v2/runs/run-bWSq4YeYpfrW4mx7 +``` + +### Sample Response + +```json +{ + "data": { + "id": "run-CZcmD7eagjhyX0vN", + "type": "runs", + "attributes": { + "actions": { + "is-cancelable": true, + "is-confirmable": false, + "is-discardable": false, + "is-force-cancelable": false + }, + "canceled-at": null, + "created-at": "2021-05-24T07:38:04.171Z", + "has-changes": false, + "auto-apply": false, + "allow-empty-apply": false, + "allow-config-generation": false, + "is-destroy": false, + "message": "Custom message", + "plan-only": false, + "source": "tfe-api", + "status-timestamps": { + "plan-queueable-at": "2021-05-24T07:38:04+00:00" + }, + "status": "pending", + "trigger-reason": "manual", + "target-addrs": null, + "permissions": { + "can-apply": true, + "can-cancel": true, + "can-comment": true, + "can-discard": true, + "can-force-execute": true, + "can-force-cancel": true, + "can-override-policy-check": true + }, + "refresh": false, + "refresh-only": false, + "replace-addrs": null, + "save-plan": false, + "variables": [] + }, + "relationships": { + "apply": {...}, + "comments": {...}, + "configuration-version": {...}, + "cost-estimate": {...}, + "created-by": {...}, + "input-state-version": {...}, + "plan": {...}, + "run-events": {...}, + "policy-checks": {...}, + "task-stages": {...}, + "workspace": {...}, + "workspace-run-alerts": {...} + }, + "links": { + "self": "/api/v2/runs/run-bWSq4YeYpfrW4mx7" + } + } +} +``` + +## Discard a Run + +`POST /runs/:run_id/actions/discard` + +| Parameter | Description | +| --------- | --------------------- | +| `run_id` | The run ID to discard | + +The `discard` action can be used to skip any remaining work on runs that are paused waiting for confirmation or priority. This includes runs in the "pending," "needs confirmation," "policy checked," and "policy override" states. + +Discarding a run requires permission to apply runs for the workspace. ([More about permissions.](/terraform/enterprise/users-teams-organizations/permissions)) + +[permissions-citation]: #intentionally-unused---keep-for-maintainers + +This endpoint queues the request to perform a discard; the discard might not happen immediately. After discarding, the run is completed and later runs can proceed. + +This endpoint represents an action as opposed to a resource. As such, it does not return any object in the response body. + +-> **Note:** This endpoint cannot be accessed with [organization tokens](/terraform/enterprise/users-teams-organizations/api-tokens#organization-api-tokens). You must access it with a [user token](/terraform/enterprise/users-teams-organizations/users#api-tokens) or [team token](/terraform/enterprise/users-teams-organizations/api-tokens#team-api-tokens). + +| Status | Response | Reason(s) | +| ------- | ------------------------- | --------------------------------------------------------------------- | +| [202][] | none | Successfully queued a discard request. | +| [409][] | [JSON API error object][] | Run was not paused for confirmation or priority; discard not allowed. | + +### Request Body + +This POST endpoint allows an optional JSON object with the following properties as a request payload. + +| Key path | Type | Default | Description | +| --------- | ------ | ------- | ------------------------------------------------------ | +| `comment` | string | `null` | An optional explanation for why the run was discarded. | + +### Sample Payload + +This payload is optional, so the `curl` command will work without the `--data @payload.json` option too. + +```json +{ + "comment": "This run was discarded" +} +``` + +### Sample Request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request POST \ + --data @payload.json \ + https://app.terraform.io/api/v2/runs/run-DQGdmrWMX8z9yWQB/actions/discard +``` + +## Cancel a Run + +`POST /runs/:run_id/actions/cancel` + +| Parameter | Description | +| --------- | -------------------- | +| `run_id` | The run ID to cancel | + +The `cancel` action can be used to interrupt a run that is currently planning or applying. Performing a cancel is roughly equivalent to hitting ctrl+c during a Terraform plan or apply on the CLI. The running Terraform process is sent an `INT` signal, which instructs Terraform to end its work and wrap up in the safest way possible. + +Canceling a run requires permission to apply runs for the workspace. ([More about permissions.](/terraform/enterprise/users-teams-organizations/permissions)) + +[permissions-citation]: #intentionally-unused---keep-for-maintainers + +This endpoint queues the request to perform a cancel; the cancel might not happen immediately. After canceling, the run is completed and later runs can proceed. + +This endpoint represents an action as opposed to a resource. As such, it does not return any object in the response body. + +-> **Note:** This endpoint cannot be accessed with [organization tokens](/terraform/enterprise/users-teams-organizations/api-tokens#organization-api-tokens). You must access it with a [user token](/terraform/enterprise/users-teams-organizations/users#api-tokens) or [team token](/terraform/enterprise/users-teams-organizations/api-tokens#team-api-tokens). + +| Status | Response | Reason(s) | +| ------- | ------------------------- | ----------------------------------------------------- | +| [202][] | none | Successfully queued a cancel request. | +| [409][] | [JSON API error object][] | Run was not planning or applying; cancel not allowed. | +| [404][] | [JSON API error object][] | Run was not found or user not authorized. | + +### Request Body + +This POST endpoint allows an optional JSON object with the following properties as a request payload. + +| Key path | Type | Default | Description | +| --------- | ------ | ------- | ----------------------------------------------------- | +| `comment` | string | `null` | An optional explanation for why the run was canceled. | + +### Sample Payload + +This payload is optional, so the `curl` command will work without the `--data @payload.json` option too. + +```json +{ + "comment": "This run was stuck and would never finish." +} +``` + +### Sample Request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request POST \ + --data @payload.json \ + https://app.terraform.io/api/v2/runs/run-DQGdmrWMX8z9yWQB/actions/cancel +``` + +## Forcefully cancel a run + +`POST /runs/:run_id/actions/force-cancel` + +| Parameter | Description | +| --------- | -------------------- | +| `run_id` | The run ID to cancel | + +The `force-cancel` action is like [cancel](#cancel-a-run), but ends the run immediately. Once invoked, the run is placed into a `canceled` state, and the running Terraform process is terminated. The workspace is immediately unlocked, allowing further runs to be queued. The `force-cancel` operation requires admin access to the workspace. ([More about permissions.](/terraform/enterprise/users-teams-organizations/permissions)) + +[permissions-citation]: #intentionally-unused---keep-for-maintainers + +This endpoint enforces a prerequisite that a [non-forceful cancel](#cancel-a-run) is performed first, and a cool-off period has elapsed. To determine if this criteria is met, it is useful to check the `data.attributes.is-force-cancelable` value of the [run details endpoint](#get-run-details). The time at which the force-cancel action will become available can be found using the [run details endpoint](#get-run-details), in the key `data.attributes.force_cancel_available_at`. Note that this key is only present in the payload after the initial cancel has been initiated. + +This endpoint represents an action as opposed to a resource. As such, it does not return any object in the response body. + +-> **Note:** This endpoint cannot be accessed with [organization tokens](/terraform/enterprise/users-teams-organizations/api-tokens#organization-api-tokens). You must access it with a [user token](/terraform/enterprise/users-teams-organizations/users#api-tokens) or [team token](/terraform/enterprise/users-teams-organizations/api-tokens#team-api-tokens). + +~> **Warning:** This endpoint has potentially dangerous side-effects, including loss of any in-flight state in the running Terraform process. Use this operation with extreme caution. + +| Status | Response | Reason(s) | +| ------- | ------------------------- | ------------------------------------------------------------------------------------------------------------------ | +| [202][] | none | Successfully queued a cancel request. | +| [409][] | [JSON API error object][] | Run was not planning or applying, has not been canceled non-forcefully, or the cool-off period has not yet passed. | +| [404][] | [JSON API error object][] | Run was not found or user not authorized. | + +### Request Body + +This POST endpoint allows an optional JSON object with the following properties as a request payload. + +| Key path | Type | Default | Description | +| --------- | ------ | ------- | ----------------------------------------------------- | +| `comment` | string | `null` | An optional explanation for why the run was canceled. | + +### Sample Payload + +This payload is optional, so the `curl` command will work without the `--data @payload.json` option too. + +```json +{ + "comment": "This run was stuck and would never finish." +} +``` + +### Sample Request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request POST \ + --data @payload.json \ + https://app.terraform.io/api/v2/runs/run-DQGdmrWMX8z9yWQB/actions/force-cancel +``` + +## Forcefully execute a run + +`POST /runs/:run_id/actions/force-execute` + +| Parameter | Description | +| --------- | --------------------- | +| `run_id` | The run ID to execute | + +The force-execute action cancels all prior runs that are not already complete, unlocking the run's workspace and allowing the run to be executed. (It initiates the same actions as the "Run this plan now" button at the top of the view of a pending run.) + +Force-executing a run requires permission to apply runs for the workspace. ([More about permissions.](/terraform/enterprise/users-teams-organizations/permissions)) + +[permissions-citation]: #intentionally-unused---keep-for-maintainers + +This endpoint enforces the following prerequisites: + +- The target run is in the "pending" state. +- The workspace is locked by another run. +- The run locking the workspace can be discarded. + +This endpoint represents an action as opposed to a resource. As such, it does not return any object in the response body. + +-> **Note:** This endpoint cannot be accessed with [organization tokens](/terraform/enterprise/users-teams-organizations/api-tokens#organization-api-tokens). You must access it with a [user token](/terraform/enterprise/users-teams-organizations/users#api-tokens) or [team token](/terraform/enterprise/users-teams-organizations/api-tokens#team-api-tokens). + +~> **Note:** While useful at times, force-executing a run circumvents the typical workflow of applying runs using HCP Terraform. It is not intended for regular use. If you find yourself using it frequently, please reach out to HashiCorp Support for help in developing an alternative approach. + +| Status | Response | Reason(s) | +| ------- | ------------------------- | --------------------------------------------------------------------------------------------- | +| [202][] | none | Successfully initiated the force-execution process. | +| [403][] | [JSON API error object][] | Run is not pending, its workspace was not locked, or its workspace association was not found. | +| [409][] | [JSON API error object][] | The run locking the workspace was not in a discardable state. | +| [404][] | [JSON API error object][] | Run was not found or user not authorized. | + +### Request Body + +This POST endpoint does not take a request body. + +### Sample Request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request POST \ + https://app.terraform.io/api/v2/runs/run-DQGdmrWMX8z9yWQB/actions/force-execute +``` + +## Available Related Resources + +The GET endpoints above can optionally return related resources, if requested with [the `include` query parameter](/terraform/enterprise/api-docs#inclusion-of-related-resources). The following resource types are available: + +- `plan` - Additional information about plans. +- `apply` - Additional information about applies. +- `created_by` - Full user records of the users responsible for creating the runs. +- `cost_estimate` - Additional information about cost estimates. +- `configuration_version` - The configuration record used in the run. +- `configuration_version.ingress_attributes` - The commit information used in the run. diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/ssh-keys.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/ssh-keys.mdx new file mode 100644 index 0000000000..70f2e54828 --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/ssh-keys.mdx @@ -0,0 +1,320 @@ +--- +page_title: /ssh-keys API reference for Terraform Enterprise +description: >- + Use the Terraform Enterprise API's `/ssh-keys` endpoint to read, get, create, + update, and delete an organization's SSH keys. +source: terraform-docs-common +--- + +[200]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/200 + +[201]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/201 + +[202]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/202 + +[204]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/204 + +[400]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/400 + +[401]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/401 + +[403]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/403 + +[404]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404 + +[409]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/409 + +[412]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/412 + +[422]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/422 + +[429]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/429 + +[500]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500 + +[504]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/504 + +[JSON API document]: /terraform/enterprise/api-docs#json-api-documents + +[JSON API error object]: https://jsonapi.org/format/#error-objects + +# SSH keys API reference + +The `ssh-key` object represents an SSH key which includes a name and the SSH private key. An organization can have multiple SSH keys available. + +SSH keys can be used in two places: + +- You can assign them to VCS provider integrations, which are available in the API as `oauth-tokens`. Refer to [OAuth Tokens](/terraform/enterprise/api-docs/oauth-tokens) for additional information. Azure DevOps Server and Bitbucket Data Center require an SSH key. Other providers only require an SSH key when your repositories include submodules that are only accessible using an SSH connection instead of your VCS provider's API. +- They can be [assigned to workspaces](/terraform/enterprise/api-docs/workspaces#assign-an-ssh-key-to-a-workspace) and used when Terraform needs to clone modules from a Git server. This is only necessary when your configurations directly reference modules from a Git server; you do not need to do this if you use HCP Terraform's [private module registry](/terraform/enterprise/registry). + +Listing and viewing SSH keys requires either permission to manage VCS settings for the organization, or admin access to at least one workspace. ([More about permissions.](/terraform/enterprise/users-teams-organizations/permissions)) + +[permissions-citation]: #intentionally-unused---keep-for-maintainers + +~> **Important:** The list and read methods on this API only provide metadata about SSH keys. The actual private key text is write-only, and HCP Terraform never provides it to users via the API or UI. + +## List SSH Keys + +`GET /organizations/:organization_name/ssh-keys` + +| Parameter | Description | +| -------------------- | -------------------------------------------------- | +| `:organization_name` | The name of the organization to list SSH keys for. | + +-> **Note:** This endpoint cannot be accessed with [organization tokens](/terraform/enterprise/users-teams-organizations/api-tokens#organization-api-tokens). You must access it with a [user token](/terraform/enterprise/users-teams-organizations/users#api-tokens) or [team token](/terraform/enterprise/users-teams-organizations/api-tokens#team-api-tokens). + +| Status | Response | Reason | +| ------- | ---------------------------------------------------- | --------------------------------------------- | +| [200][] | Array of [JSON API document][]s (`type: "ssh-keys"`) | Success | +| [404][] | [JSON API error object][] | Organization not found or user not authorized | + +### Query Parameters + +This endpoint supports pagination [with standard URL query parameters](/terraform/enterprise/api-docs#query-parameters). Remember to percent-encode `[` as `%5B` and `]` as `%5D` if your tooling doesn't automatically encode URLs. If neither pagination query parameters are provided, the endpoint will not be paginated and will return all results. + +| Parameter | Description | +| -------------- | ------------------------------------------------------------------------ | +| `page[number]` | **Optional.** If omitted, the endpoint will return the first page. | +| `page[size]` | **Optional.** If omitted, the endpoint will return 20 ssh keys per page. | + +### Sample Request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + https://app.terraform.io/api/v2/organizations/my-organization/ssh-keys +``` + +### Sample Response + +```json +{ + "data": [ + { + "attributes": { + "name": "SSH Key" + }, + "id": "sshkey-GxrePWre1Ezug7aM", + "links": { + "self": "/api/v2/ssh-keys/sshkey-GxrePWre1Ezug7aM" + }, + "type": "ssh-keys" + } + ] +} +``` + +## Get an SSH Key + +`GET /ssh-keys/:ssh_key_id` + +| Parameter | Description | +| ------------- | ---------------------- | +| `:ssh_key_id` | The SSH key ID to get. | + +This endpoint is for looking up the name associated with an SSH key ID. It does not retrieve the key text. + +-> **Note:** This endpoint cannot be accessed with [organization tokens](/terraform/enterprise/users-teams-organizations/api-tokens#organization-api-tokens). You must access it with a [user token](/terraform/enterprise/users-teams-organizations/users#api-tokens) or [team token](/terraform/enterprise/users-teams-organizations/api-tokens#team-api-tokens). + +| Status | Response | Reason | +| ------- | ------------------------------------------ | ---------------------------------------- | +| [200][] | [JSON API document][] (`type: "ssh-keys"`) | Success | +| [404][] | [JSON API error object][] | SSH key not found or user not authorized | + +### Sample Request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + https://app.terraform.io/api/v2/ssh-keys/sshkey-GxrePWre1Ezug7aM +``` + +### Sample Response + +```json +{ + "data": { + "attributes": { + "name": "SSH Key" + }, + "id": "sshkey-GxrePWre1Ezug7aM", + "links": { + "self": "/api/v2/ssh-keys/sshkey-GxrePWre1Ezug7aM" + }, + "type": "ssh-keys" + } +} +``` + +## Create an SSH Key + +`POST /organizations/:organization_name/ssh-keys` + +| Parameter | Description | +| -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `:organization_name` | The name of the organization to create an SSH key in. The organization must already exist, and the token authenticating the API request must have permission to manage VCS settings. ([More about permissions.](/terraform/enterprise/users-teams-organizations/permissions)) | + +[permissions-citation]: #intentionally-unused---keep-for-maintainers + +-> **Note:** This endpoint cannot be accessed with [organization tokens](/terraform/enterprise/users-teams-organizations/api-tokens#organization-api-tokens). You must access it with a [user token](/terraform/enterprise/users-teams-organizations/users#api-tokens) or [team token](/terraform/enterprise/users-teams-organizations/api-tokens#team-api-tokens). + +| Status | Response | Reason | +| ------- | ------------------------------------------ | -------------------------------------------------------------- | +| [201][] | [JSON API document][] (`type: "ssh-keys"`) | Success | +| [422][] | [JSON API error object][] | Malformed request body (missing attributes, wrong types, etc.) | +| [404][] | [JSON API error object][] | User not authorized | + +### Request Body + +This POST endpoint requires a JSON object with the following properties as a request payload. + +Properties without a default value are required. + +| Key path | Type | Default | Description | +| ----------------------- | ------ | ------- | -------------------------------- | +| `data.type` | string | | Must be `"ssh-keys"`. | +| `data.attributes.name` | string | | A name to identify the SSH key. | +| `data.attributes.value` | string | | The text of the SSH private key. | + +### Sample Payload + +```json +{ + "data": { + "type": "ssh-keys", + "attributes": { + "name": "SSH Key", + "value": "-----BEGIN RSA PRIVATE KEY-----\nMIIEowIBAAKCAQEAm6+JVgl..." + } + } +} +``` + +### Sample Request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request POST \ + --data @payload.json \ + https://app.terraform.io/api/v2/organizations/my-organization/ssh-keys +``` + +### Sample Response + +```json +{ + "data": { + "attributes": { + "name": "SSH Key" + }, + "id": "sshkey-GxrePWre1Ezug7aM", + "links": { + "self": "/api/v2/ssh-keys/sshkey-GxrePWre1Ezug7aM" + }, + "type": "ssh-keys" + } +} +``` + +## Update an SSH Key + +`PATCH /ssh-keys/:ssh_key_id` + +| Parameter | Description | +| ------------- | ------------------------- | +| `:ssh_key_id` | The SSH key ID to update. | + +This endpoint replaces the name of an existing SSH key. + +Editing SSH keys requires permission to manage VCS settings. ([More about permissions.](/terraform/enterprise/users-teams-organizations/permissions)) + +[permissions-citation]: #intentionally-unused---keep-for-maintainers + +-> **Note:** This endpoint cannot be accessed with [organization tokens](/terraform/enterprise/users-teams-organizations/api-tokens#organization-api-tokens). You must access it with a [user token](/terraform/enterprise/users-teams-organizations/users#api-tokens) or [team token](/terraform/enterprise/users-teams-organizations/api-tokens#team-api-tokens). + +| Status | Response | Reason | +| ------- | ------------------------------------------ | ---------------------------------------- | +| [200][] | [JSON API document][] (`type: "ssh-keys"`) | Success | +| [404][] | [JSON API error object][] | SSH key not found or user not authorized | + +### Request Body + +This POST endpoint requires a JSON object with the following properties as a request payload. + +Properties without a default value are required. + +| Key path | Type | Default | Description | +| ---------------------- | ------ | --------- | --------------------------------------------------------------------------- | +| `data.type` | string | | Must be `"ssh-keys"`. | +| `data.attributes.name` | string | (nothing) | A name to identify the SSH key. If omitted, the existing name is preserved. | + +### Sample Payload + +```json +{ + "data": { + "attributes": { + "name": "SSH Key for GitHub" + } + } +} +``` + +### Sample Request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request PATCH \ + --data @payload.json \ + https://app.terraform.io/api/v2/ssh-keys/sshkey-GxrePWre1Ezug7aM +``` + +### Sample Response + +```json +{ + "data": { + "attributes": { + "name": "SSH Key for GitHub" + }, + "id": "sshkey-GxrePWre1Ezug7aM", + "links": { + "self": "/api/v2/ssh-keys/sshkey-GxrePWre1Ezug7aM" + }, + "type": "ssh-keys" + } +} +``` + +## Delete an SSH Key + +`DELETE /ssh-keys/:ssh_key_id` + +| Parameter | Description | +| ------------- | ------------------------- | +| `:ssh_key_id` | The SSH key ID to delete. | + +Deleting SSH keys requires permission to manage VCS settings. ([More about permissions.](/terraform/enterprise/users-teams-organizations/permissions)) + +[permissions-citation]: #intentionally-unused---keep-for-maintainers + +-> **Note:** This endpoint cannot be accessed with [organization tokens](/terraform/enterprise/users-teams-organizations/api-tokens#organization-api-tokens). You must access it with a [user token](/terraform/enterprise/users-teams-organizations/users#api-tokens) or [team token](/terraform/enterprise/users-teams-organizations/api-tokens#team-api-tokens). + +| Status | Response | Reason | +| ------- | ------------------------- | ---------------------------------------- | +| [204][] | No Content | Success | +| [404][] | [JSON API error object][] | SSH key not found or user not authorized | + +### Sample Request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request DELETE \ + https://app.terraform.io/api/v2/ssh-keys/sshkey-GxrePWre1Ezug7aM +``` diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/stability-policy.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/stability-policy.mdx new file mode 100644 index 0000000000..1b59e2d1f2 --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/stability-policy.mdx @@ -0,0 +1,25 @@ +--- +page_title: API stability policy for Terraform Enterprise +description: >- + Learn how HashiCorp plans for stability, backward compatibility, and + deprecation for the Terraform Enterprise APIs. +source: terraform-docs-common +--- + +# API stability policy + +The HCP Terraform API will continue to evolve, but we consider it stable for general use, and HashiCorp will maintain all stable API endpoints in a backwards compatible manner. (Stable endpoints are any endpoints _not_ marked as beta.) If we need to make a change that we consider backwards incompatible, then we will create a new endpoint that serves the same purpose; the old endpoint will be maintained until declared [deprecated](#deprecation-policy). + +The following changes are considered to be backwards compatible: + +- Adding new API endpoints. +- Adding new attributes, links, or relationships to existing API requests and responses. +- Adding new optional query parameters to existing API requests. + +Security vulnerabilities are an exception to this stability policy; we will make backwards incompatible changes to stable endpoints if it is necessary to protect our security or the security of our users. + +Endpoints that are in beta are subject to change without notice. + +## Deprecation Policy + +The deprecation policy provides users the opportunity to continue to consume API endpoints for a period of time after they have been superseded. Deprecation notices for endpoints should be readily available through various channels of communication, including documentation and HTTP responses. An endpoint should be available for at least three (3) months from the date on which it has been declared deprecated. (This time is cited as a minimum; endpoint availability may be longer based on contracted agreements.) diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/state-version-outputs.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/state-version-outputs.mdx new file mode 100644 index 0000000000..cb5ef168b0 --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/state-version-outputs.mdx @@ -0,0 +1,244 @@ +--- +page_title: /state-version-outputs API reference for Terraform Enterprise +description: >- + Use the Terraform Enterprise API's `/state-version-outputs` endpoint to read + the outputs from a specified Terraform state version or a workspace's current + outputs. +source: terraform-docs-common +--- + +[200]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/200 + +[404]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404 + +[JSON API document]: /terraform/enterprise/api-docs#json-api-documents + +[JSON API error object]: https://jsonapi.org/format/#error-objects + +# State version outputs API reference + +State version outputs are the [output values](/terraform/language/values/outputs) from a Terraform state file. They include +the name and value of the output, as well as a sensitive boolean if the value should be hidden by default in UIs. + +~> **Important:** The state version outputs for a state version (as well as some other information about it) might be **populated asynchronously** by HCP Terraform. These values might not be immediately available after the state version is uploaded. The `resources-processed` property on the associated [state version object](/terraform/enterprise/api-docs/state-versions) indicates whether or not HCP Terraform has finished any necessary asynchronous processing. If you need to use these values, be sure to wait for `resources-processed` to become `true` before assuming that the values are in fact empty. + +## List State Version Outputs + +`GET /state-versions/:state_version_id/outputs` + +Listing state version outputs requires permission to read state outputs for the workspace. ([More about permissions.](/terraform/enterprise/users-teams-organizations/permissions)) + +| Parameter | Description | +| ------------------- | ------------------------------------ | +| `:state_version_id` | The ID of the desired state version. | + +| Status | Response | Reason | +| ------- | ------------------------- | -------------------------------------------------------------------- | +| [200][] | [JSON API document][] | Successfully returned a list of outputs for the given state version. | +| [404][] | [JSON API error object][] | State version not found, or user unauthorized to perform action. | + +### Query Parameters + +This endpoint supports pagination [with standard URL query parameters](/terraform/enterprise/api-docs#query-parameters). Remember to percent-encode `[` as `%5B` and `]` as `%5D` if your tooling doesn't automatically encode URLs. + +| Parameter | Description | +| -------------- | ------------------------------------------------------------------------------------- | +| `page[number]` | **Optional.** If omitted, the endpoint will return the first page. | +| `page[size]` | **Optional.** If omitted, the endpoint will return 20 state version outputs per page. | + +### Sample Request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + https://app.terraform.io/api/v2/state-versions/sv-SDboVZC8TCxXEneJ/outputs +``` + +### Sample Response + +```json +{ + "data": [ + { + "id": "wsout-xFAmCR3VkBGepcee", + "type": "state-version-outputs", + "attributes": { + "name": "fruits", + "sensitive": false, + "type": "array", + "value": [ + "apple", + "strawberry", + "blueberry", + "rasberry" + ], + "detailed_type": [ + "tuple", + [ + "string", + "string", + "string", + "string" + ] + ] + }, + "links": { + "self": "/api/v2/state-version-outputs/wsout-xFAmCR3VkBGepcee" + } + }, + { + "id": "wsout-vspuB754AUNkfxwo", + "type": "state-version-outputs", + "attributes": { + "name": "vegetables", + "sensitive": false, + "type": "array", + "value": [ + "carrots", + "potato", + "tomato", + "onions" + ], + "detailed_type": [ + "tuple", + [ + "string", + "string", + "string", + "string" + ] + ] + }, + "links": { + "self": "/api/v2/state-version-outputs/wsout-vspuB754AUNkfxwo" + } + } + ], + "links": { + "self": "https://app.terraform.io/api/v2/state-versions/sv-SVB5wMrDL1XUgJ4G/outputs?page%5Bnumber%5D=1&page%5Bsize%5D=20", + "first": "https://app.terraform.io/api/v2/state-versions/sv-SVB5wMrDL1XUgJ4G/outputs?page%5Bnumber%5D=1&page%5Bsize%5D=20", + "prev": null, + "next": null, + "last": "https://app.terraform.io/api/v2/state-versions/sv-SVB5wMrDL1XUgJ4G/outputs?page%5Bnumber%5D=1&page%5Bsize%5D=20" + }, + "meta": { + "pagination": { + "current-page": 1, + "page-size": 20, + "prev-page": null, + "next-page": null, + "total-pages": 1, + "total-count": 2 + } + } +} +``` + +## Show a State Version Output + +`GET /state-version-outputs/:state_version_output_id` + +| Parameter | Description | +| -------------------------- | ------------------------------------------- | +| `:state_version_output_id` | The ID of the desired state version output. | + +State version output IDs must be obtained from a [state version object](/terraform/enterprise/api-docs/state-versions). When requesting a state version, you can optionally add `?include=outputs` to include full details for all of that state version's outputs. + +| Status | Response | Reason | +| ------- | ------------------------------------------------------- | ------------------------------------------------------ | +| [200][] | [JSON API document][] (`type: "state-version-outputs"`) | Success. | +| [404][] | [JSON API error object][] | State version output not found or user not authorized. | + +### Sample Request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + https://app.terraform.io/api/v2/state-version-outputs/wsout-J2zM24JPFbfc7bE5 +``` + +### Sample Response + +```json +{ + "data": { + "id": "wsout-J2zM24JPFbfc7bE5", + "type": "state-version-outputs", + "attributes": { + "name": "flavor", + "sensitive": false, + "type": "string", + "value": "Peanut Butter", + "detailed-type": "string" + }, + "links": { + "self": "/api/v2/state-version-outputs/wsout-J2zM24JPFbfc7bE5" + } + } +} +``` + +## Show Current State Version Outputs for a Workspace + +This endpoint allows organization users, who do not have permissions to read state versions, to fetch the latest [output values](/terraform/language/values/outputs) for a workspace. ([More about permissions.](/terraform/enterprise/users-teams-organizations/permissions)) + +-> **Note:** Sensitive values are not revealed and will be returned as `null`. To fetch an output including sensitive values see [Show a State Version Output](/terraform/enterprise/api-docs/state-version-outputs#show-a-state-version-output). + +`GET /workspaces/:workspace_id/current-state-version-outputs` + +| Parameter | Description | +| --------------- | --------------------------------------------- | +| `:workspace_id` | The ID of the workspace to read outputs from. | + +| Status | Response | Reason | +| ------- | ------------------------------------------------------- | ------------------------------------------------------------------------------- | +| [200][] | [JSON API document][] (`type: "state-version-outputs"`) | Successfully returned a list of outputs for the given workspace. | +| [404][] | [JSON API error object][] | State version outputs not found or user not authorized. | +| [503][] | [JSON API error object][] | State version outputs are being processed and are not ready. Retry the request. | + +### Sample Request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + https://app.terraform.io/api/v2/workspaces/ws-G4zM299PFbfc10E5/current-state-version-outputs +``` + +### Sample Response + +```json +{ + "data": [ + { + "id": "wsout-J2zM24JPFbfc7bE5", + "type": "state-version-outputs", + "attributes": { + "name": "flavor", + "sensitive": false, + "type": "string", + "value": "Peanut Butter", + "detailed-type": "string" + }, + "links": { + "self": "/api/v2/state-version-outputs/wsout-J2zM24JPFbfc7bE5" + } + }, + { + "id": "wsout-FLzM23Gcd5f37bE5", + "type": "state-version-outputs", + "attributes": { + "name": "recipe", + "sensitive": true, + "type": "string", + "value": "Don Douglas' Peanut Butter Frenzy", + "detailed-type": "string" + }, + "links": { + "self": "/api/v2/state-version-outputs/wsout-FLzM23Gcd5f37bE5" + } + } + ] +} +``` diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/state-versions.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/state-versions.mdx new file mode 100644 index 0000000000..9e8873612d --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/state-versions.mdx @@ -0,0 +1,1241 @@ +--- +page_title: /state-versions API reference for Terraform Enterprise +description: >- + Use the Terraform Enterprise API's `/state-versions` endpoint to read, create, + upload, fetch, rollback, delete, and mark state versions for garbage + collection. +source: terraform-docs-common +--- + +[200]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/200 + +[201]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/201 + +[202]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/202 + +[204]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/204 + +[400]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/400 + +[401]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/401 + +[403]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/403 + +[404]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404 + +[409]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/409 + +[412]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/412 + +[422]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/422 + +[429]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/429 + +[500]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500 + +[504]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/504 + +[JSON API document]: /terraform/enterprise/api-docs#json-api-documents + +[JSON API error object]: https://jsonapi.org/format/#error-objects + +# State versions API reference + +## Attributes + +State version API objects represent an instance of Terraform state data, but do not directly contain the stored state. Instead, they contain information about the state, its properties, and its contents, and include one or more URLs from which the state can be downloaded. + +Some of the information returned in a state version API object might be **populated asynchronously** by HCP Terraform. This includes resources, modules, providers, and the [state version outputs](/terraform/enterprise/api-docs/state-version-outputs) associated with the state version. These values might not be immediately available after the state version is uploaded. The `resources-processed` property on the state version object indicates whether or not HCP Terraform has finished any necessary asynchronous processing. If you need to use these values, be sure to wait for `resources-processed` to become `true` before assuming that the values are in fact empty. + +| Attribute | Description | +| -------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `billable-rum-count` | Count of billable Resources Under Management (RUM). Only present for organization members on HCP Terraform RUM plans with visibility of billable RUM usage. | +| `hosted-json-state-download-url` | A URL from which you can download the state data in a [stable format](/terraform/internals/json-format) appropriate for external integrations to consume. Only available if the state was created by Terraform 1.3+. | +| `hosted-state-download-url` | A URL from which you can download the raw state data, in the format used internally by Terraform. | +| `hosted-json-state-upload-url` | A URL to which you can upload state data in a [stable format](/terraform/internals/json-format) appropriate for external integrations to consume. You can upload JSON state content once per state version. | +| `hosted-state-upload-url` | A URL to which you can upload state data in the format used Terraform uses internally. You can upload state data once per state version. | +| `modules` | Extracted information about the Terraform modules in this state data. Populated asynchronously. | +| `providers` | Extracted information about the Terraform providers used for resources in this state data. Populated asynchronously. | +| `intermediate` | A boolean flag that indicates the state version is a snapshot and not yet set as the current state version for a workspace. The last intermediate state version becomes the current state version when the workspace is unlocked. Not yet supported in Terraform Enterprise. | +| `resources` | Extracted information about the resources in this state data. Populated asynchronously. | +| `resources-processed` | A Boolean flag indicating whether HCP Terraform has finished asynchronously extracting outputs, resources, and other information about this state data. | +| `serial` | The serial number of this state instance, which increases every time Terraform creates new state in the workspace. | +| `state-version` | The version of the internal state format used for this state. Different Terraform versions read and write different format versions, but it only changes infrequently. | +| `status` | Indicates a state version's content upload [status](/terraform/enterprise/api-docs/state-versions#state-version-status). This status can be `pending`, `finalized` or `discarded`. | +| `terraform-version` | The Terraform version that created this state. Populated asynchronously. | +| `vcs-commit-sha` | The SHA of the configuration commit used in the Terraform run that produced this state. Only present if the workspace is connected to a VCS repository. | +| `vcs-commit-url` | A link to the configuration commit used in the Terraform run that produced this state. Only present if the workspace is connected to a VCS repository. | + +### State Version Status + +The state version status is found in `data.attributes.status`, and you can reference the following list of possible statuses. +A state version created through the API or CLI will only be listed in the UI if it is has a `finalized` status. + +| State | Description | +| ---------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `pending` | Indicates that a state version has been created but the state data is not encoded within the request. Pending state versions do not contain state data and do not appear in the UI. You cannot unlock the workspace until the latest state version is finalized. | +| `finalized` | Indicates that the state version has been successfully uploaded to HCP Terraform or that the state version was created with a valid `state` attribute. | +| `discarded` | The state version was discarded because it was superseded by a newer state version before it could be uploaded. | +| `backing_data_soft_deleted` | The backing files associated with this state version are marked for garbage collection. Terraform permanently deletes backing files associated with this state version after a set number of days, but you can restore the backing data associated with it before it is permanently deleted. | +| `backing_data_permanently_deleted` | The backing files associated with this state version have been permanently deleted and can no longer be restored. | + +## Create a State Version + +> **Hands-on:** Try the [Version Remote State with the HCP Terraform API](/terraform/tutorials/cloud/cloud-state-api) tutorial to download a remote state file and use the Terraform API to create a new state version. + +`POST /workspaces/:workspace_id/state-versions` + +| Parameter | Description | +| --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `:workspace_id` | The workspace ID to create the new state version in. Obtain this from the [workspace settings](/terraform/enterprise/workspaces/settings) or the [Show Workspace](/terraform/enterprise/api-docs/workspaces#show-workspace) endpoint. | + +Creates a state version and sets it as the current state version for the given workspace. The workspace must be locked by the user creating a state version. The workspace may be locked [with the API](/terraform/enterprise/api-docs/workspaces#lock-a-workspace) or [with the UI](/terraform/enterprise/workspaces/settings#locking). This is most useful for migrating existing state from Terraform Community edition into a new HCP Terraform workspace. + +Creating state versions requires permission to read and write state versions for the workspace. ([More about permissions.](/terraform/enterprise/users-teams-organizations/permissions)) + +[permissions-citation]: #intentionally-unused---keep-for-maintainers + +!> **Warning:** Use caution when uploading state to workspaces that have already performed Terraform runs. Replacing state improperly can result in orphaned or duplicated infrastructure resources. + +-> **Note:** For Free Tier organizations, HCP Terraform always retains at least the last 100 states (across all workspaces) and at least the most recent state for every workspace. Additional states beyond the last 100 are retained for six months, and are then deleted. + +-> **Note:** You cannot access this endpoint with [organization tokens](/terraform/enterprise/users-teams-organizations/api-tokens#organization-api-tokens). You must access it with a [user token](/terraform/enterprise/users-teams-organizations/users#api-tokens) or [team token](/terraform/enterprise/users-teams-organizations/api-tokens#team-api-tokens). + +| Status | Response | Reason | +| ------- | ------------------------- | ----------------------------------------------------------------- | +| [201][] | [JSON API document][] | Successfully created a state version. | +| [404][] | [JSON API error object][] | Workspace not found, or user unauthorized to perform action. | +| [409][] | [JSON API error object][] | Conflict; check the error object for more information. | +| [412][] | [JSON API error object][] | Precondition failed; check the error object for more information. | +| [422][] | [JSON API error object][] | Malformed request body (missing attributes, wrong types, etc.). | + +### Request Body + +This POST endpoint requires a JSON object with the following properties as a request payload. + +Properties without a default value are required. + +| Key path | Type | Default | Description | +| ------------------------------------ | ------- | --------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `data.type` | string | | Must be `"state-versions"`. | +| `data.attributes.serial` | integer | | The serial of the state version. Must match the serial value extracted from the raw state file. | +| `data.attributes.md5` | string | | An MD5 hash of the raw state version. | +| `data.attributes.state` | string | (nothing) | **Optional** Base64 encoded raw state file. If omitted, you must use the upload method below to complete the state version creation. The workspace may not be unlocked normally until the state version is uploaded. | +| `data.attributes.lineage` | string | (nothing) | **Optional** Lineage of the state version. Should match the lineage extracted from the raw state file. Early versions of terraform did not have the concept of lineage, so this is an optional attribute. | +| `data.attributes.json-state` | string | (nothing) | **Optional** Base64 encoded json state, as expressed by `terraform show -json`. See [JSON Output Format](/terraform/internals/json-format) for more details. | +| `data.attributes.json-state-outputs` | string | (nothing) | **Optional** Base64 encoded output values as represented by `terraform show -json` (the contents of the values/outputs key). If provided, the workspace outputs populate immediately. If omitted, HCP Terraform populates the workspace outputs from the given state after a short time. | +| `data.relationships.run.data.id` | string | (nothing) | **Optional** The ID of the run to associate with the state version. | + +### Sample Payload + +```json +{ + "data": { + "type":"state-versions", + "attributes": { + "serial": 1, + "md5": "d41d8cd98f00b204e9800998ecf8427e", + "lineage": "871d1b4a-e579-fb7c-ffdb-f0c858a647a7", + "state": "...", + "json-state": "...", + "json-state-outputs": "..." + }, + "relationships": { + "run": { + "data": { + "type": "runs", + "id": "run-bWSq4YeYpfrW4mx7" + } + } + } + } +} +``` + +### Sample Request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request POST \ + --data @payload.json \ + https://app.terraform.io/api/v2/workspaces/ws-6fHMCom98SDXSQUv/state-versions +``` + +### Sample Response + +```json +{ + "data": { + "id": "sv-DmoXecHePnNznaA4", + "type": "state-versions", + "attributes": { + "vcs-commit-sha": null, + "vcs-commit-url": null, + "created-at": "2018-07-12T20:32:01.490Z", + "hosted-state-download-url": "https://archivist.terraform.io/v1/object/f55b739b-ff03-4716-b436-726466b96dc4", + "hosted-json-state-download-url": "https://archivist.terraform.io/v1/object/4fde7951-93c0-4414-9a40-f3abc4bac490", + "hosted-state-upload-url": null, + "hosted-json-state-upload-url": null, + "status": "finalized", + "intermediate": true, + "serial": 1 + }, + "links": { + "self": "/api/v2/state-versions/sv-DmoXecHePnNznaA4" + } + } +} +``` + +## Upload State and JSON State + + You can upload state version content in the same request when creating a state version. However, we _strongly_ recommend that you upload content separately. + +`PUT https://archivist.terraform.io/v1/object/` + +HCP Terraform returns a `hosted-state-upload-url` or `hosted-json-state-upload-url` returned when you create a `state-version`. Once you upload state content, this URL is hidden on the resource and _no longer available_. + +### Sample Request + +In the below example, `@filename` is the name of Terraform state file you wish to upload. + +```shell +curl \ + --header "Content-Type: application/octet-stream" \ + --request PUT \ + --data-binary @filename \ + https://archivist.terraform.io/v1/object/4c44d964-eba7-4dd5-ad29-1ece7b99e8da +``` + +## List State Versions for a Workspace + +`GET /state-versions` + +Listing state versions requires permission to read state versions for the workspace. ([More about permissions.](/terraform/enterprise/users-teams-organizations/permissions)) + +[permissions-citation]: #intentionally-unused---keep-for-maintainers + +### Query Parameters + +This endpoint supports pagination [with standard URL query parameters](/terraform/enterprise/api-docs#query-parameters). Remember to percent-encode `[` as `%5B` and `]` as `%5D` if your tooling doesn't automatically encode URLs. + +| Parameter | Description | +| ---------------------------- | -------------------------------------------------------------------------------------- | +| `filter[workspace][name]` | **Required** The name of one workspace to list versions for. | +| `filter[organization][name]` | **Required** The name of the organization that owns the desired workspace. | +| `filter[status]` | **Optional.** Filter state versions by status: `pending`, `finalized`, or `discarded`. | +| `page[number]` | **Optional.** If omitted, the endpoint will return the first page. | +| `page[size]` | **Optional.** If omitted, the endpoint will return 20 state versions per page. | + +### Sample Request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + "https://app.terraform.io/api/v2/state-versions?filter%5Bworkspace%5D%5Bname%5D=my-workspace&filter%5Borganization%5D%5Bname%5D=my-organization" +``` + +### Sample Response + +```json +{ + "data": [ + { + "id": "sv-g4rqST72reoHMM5a", + "type": "state-versions", + "attributes": { + "created-at": "2021-06-08T01:22:03.794Z", + "size": 940, + "hosted-state-download-url": "https://archivist.terraform.io/v1/object/...", + "hosted-state-upload-url": null, + "hosted-json-state-download-url": "https://archivist.terraform.io/v1/object/...", + "hosted-json-state-upload-url": null, + "status": "finalized", + "intermediate": false, + "modules": { + "root": { + "null-resource": 1, + "data.terraform-remote-state": 1 + } + }, + "providers": { + "provider[\"terraform.io/builtin/terraform\"]": { + "data.terraform-remote-state": 1 + }, + "provider[\"registry.terraform.io/hashicorp/null\"]": { + "null-resource": 1 + } + }, + "resources": [ + { + "name": "other_username", + "type": "data.terraform_remote_state", + "count": 1, + "module": "root", + "provider": "provider[\"terraform.io/builtin/terraform\"]" + }, + { + "name": "random", + "type": "null_resource", + "count": 1, + "module": "root", + "provider": "provider[\"registry.terraform.io/hashicorp/null\"]" + } + ], + "resources-processed": true, + "serial": 9, + "state-version": 4, + "terraform-version": "0.15.4", + "vcs-commit-url": "https://gitlab.com/my-organization/terraform-test/-/commit/abcdef12345", + "vcs-commit-sha": "abcdef12345" + }, + "relationships": { + "run": { + "data": { + "id": "run-YfmFLWpgTv31VZsP", + "type": "runs" + } + }, + "created-by": { + "data": { + "id": "user-onZs69ThPZjBK2wo", + "type": "users" + }, + "links": { + "self": "/api/v2/users/user-onZs69ThPZjBK2wo", + "related": "/api/v2/runs/run-YfmFLWpgTv31VZsP/created-by" + } + }, + "workspace": { + "data": { + "id": "ws-noZcaGXsac6aZSJR", + "type": "workspaces" + } + }, + "outputs": { + "data": [ + { + "id": "wsout-V22qbeM92xb5mw9n", + "type": "state-version-outputs" + }, + { + "id": "wsout-ymkuRnrNFeU5wGpV", + "type": "state-version-outputs" + }, + { + "id": "wsout-v82BjkZnFEcscipg", + "type": "state-version-outputs" + } + ] + } + }, + "links": { + "self": "/api/v2/state-versions/sv-g4rqST72reoHMM5a" + } + }, + { + "id": "sv-QYKf6GvNv75ZPTBr", + "type": "state-versions", + "attributes": { + "created-at": "2021-06-01T21:40:25.941Z", + "size": 819, + "hosted-state-download-url": "https://archivist.terraform.io/v1/object/...", + "hosted-state-upload-url": null, + "hosted-json-state-download-url": "https://archivist.terraform.io/v1/object/...", + "hosted-json-state-upload-url": null, + "status": "finalized", + "intermediate": false, + "modules": { + "root": { + "data.terraform-remote-state": 1 + } + }, + "providers": { + "provider[\"terraform.io/builtin/terraform\"]": { + "data.terraform-remote-state": 1 + } + }, + "resources": [ + { + "name": "other_username", + "type": "data.terraform_remote_state", + "count": 1, + "module": "root", + "provider": "provider[\"terraform.io/builtin/terraform\"]" + } + ], + "resources-processed": true, + "serial": 8, + "state-version": 4, + "terraform-version": "0.15.4", + "vcs-commit-url": "https://gitlab.com/my-organization/terraform-test/-/commit/12345abcdef", + "vcs-commit-sha": "12345abcdef" + }, + "relationships": { + "run": { + "data": { + "id": "run-cVtxks6R8wsjCZMD", + "type": "runs" + } + }, + "created-by": { + "data": { + "id": "user-onZs69ThPZjBK2wo", + "type": "users" + }, + "links": { + "self": "/api/v2/users/user-onZs69ThPZjBK2wo", + "related": "/api/v2/runs/run-YfmFLWpgTv31VZsP/created-by" + } + }, + "workspace": { + "data": { + "id": "ws-noZcaGXsac6aZSJR", + "type": "workspaces" + } + }, + "outputs": { + "data": [ + { + "id": "wsout-MmqMhmht6jFmLRvh", + "type": "state-version-outputs" + }, + { + "id": "wsout-Kuo9TCHg3oDLDQqa", + "type": "state-version-outputs" + } + ] + } + }, + "links": { + "self": "/api/v2/state-versions/sv-QYKf6GvNv75ZPTBr" + } + } + ], + "links": { + "self": "https://app.terraform.io/api/v2/state-versions?filter%5Borganization%5D%5Bname%5D=hashicorp&filter%5Bworkspace%5D%5Bname%5D=my-workspace&page%5Bnumber%5D=1&page%5Bsize%5D=20", + "first": "https://app.terraform.io/api/v2/state-versions?filter%5Borganization%5D%5Bname%5D=hashicorp&filter%5Bworkspace%5D%5Bname%5D=my-workspace&page%5Bnumber%5D=1&page%5Bsize%5D=20", + "prev": null, + "next": null, + "last": "https://app.terraform.io.io/api/v2/state-versions?filter%5Borganization%5D%5Bname%5D=hashicorp&filter%5Bworkspace%5D%5Bname%5D=my-workspace&page%5Bnumber%5D=1&page%5Bsize%5D=20" + }, + "meta": { + "pagination": { + "current-page": 1, + "page-size": 20, + "prev-page": null, + "next-page": null, + "total-pages": 1, + "total-count": 10 + } + } +} +``` + +## Fetch the Current State Version for a Workspace + +`GET /workspaces/:workspace_id/current-state-version` + +| Parameter | Description | +| --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `:workspace_id` | The ID for the workspace whose current state version you want to fetch. Obtain this from the [workspace settings](/terraform/enterprise/workspaces/settings) or the [Show Workspace](/terraform/enterprise/api-docs/workspaces#show-workspace) endpoint. | + +Fetches the current state version for the given workspace. This state version +will be the input state when running terraform operations. + +Viewing state versions requires permission to read state versions for the workspace. ([More about permissions.](/terraform/enterprise/users-teams-organizations/permissions)) + +[permissions-citation]: #intentionally-unused---keep-for-maintainers + +| Status | Response | Reason | +| ------- | ------------------------- | ------------------------------------------------------------------------------------------------------------- | +| [200][] | [JSON API document][] | Successfully returned current state version for the given workspace. | +| [404][] | [JSON API error object][] | Workspace not found, workspace does not have a current state version, or user unauthorized to perform action. | + +### Sample Request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + https://app.terraform.io/api/v2/workspaces/ws-6fHMCom98SDXSQUv/current-state-version +``` + +### Sample Response + +```json +{ + "data": { + "id": "sv-g4rqST72reoHMM5a", + "type": "state-versions", + "attributes": { + "billable-rum-count": 0, + "created-at": "2021-06-08T01:22:03.794Z", + "size": 940, + "hosted-state-download-url": "https://archivist.terraform.io/v1/object/...", + "hosted-state-upload-url": null, + "hosted-json-state-download-url": "https://archivist.terraform.io/v1/object/...", + "hosted-json-state-upload-url": null, + "status": "finalized", + "intermediate": false, + "modules": { + "root": { + "null-resource": 1, + "data.terraform-remote-state": 1 + } + }, + "providers": { + "provider[\"terraform.io/builtin/terraform\"]": { + "data.terraform-remote-state": 1 + }, + "provider[\"registry.terraform.io/hashicorp/null\"]": { + "null-resource": 1 + } + }, + "resources": [ + { + "name": "other_username", + "type": "data.terraform_remote_state", + "count": 1, + "module": "root", + "provider": "provider[\"terraform.io/builtin/terraform\"]" + }, + { + "name": "random", + "type": "null_resource", + "count": 1, + "module": "root", + "provider": "provider[\"registry.terraform.io/hashicorp/null\"]" + } + ], + "resources-processed": true, + "serial": 9, + "state-version": 4, + "terraform-version": "0.15.4", + "vcs-commit-url": "https://gitlab.com/my-organization/terraform-test/-/commit/abcdef12345", + "vcs-commit-sha": "abcdef12345" + }, + "relationships": { + "run": { + "data": { + "id": "run-YfmFLWpgTv31VZsP", + "type": "runs" + } + }, + "created-by": { + "data": { + "id": "user-onZs69ThPZjBK2wo", + "type": "users" + }, + "links": { + "self": "/api/v2/users/user-onZs69ThPZjBK2wo", + "related": "/api/v2/runs/run-YfmFLWpgTv31VZsP/created-by" + } + }, + "workspace": { + "data": { + "id": "ws-noZcaGXsac6aZSJR", + "type": "workspaces" + } + }, + "outputs": { + "data": [ + { + "id": "wsout-V22qbeM92xb5mw9n", + "type": "state-version-outputs" + }, + { + "id": "wsout-ymkuRnrNFeU5wGpV", + "type": "state-version-outputs" + }, + { + "id": "wsout-v82BjkZnFEcscipg", + "type": "state-version-outputs" + } + ] + } + }, + "links": { + "self": "/api/v2/state-versions/sv-g4rqST72reoHMM5a" + } + } +} +``` + +## Show a State Version + +`GET /state-versions/:state_version_id` + +Viewing state versions requires permission to read state versions for the workspace. ([More about permissions.](/terraform/enterprise/users-teams-organizations/permissions)) + +[permissions-citation]: #intentionally-unused---keep-for-maintainers + +| Parameter | Description | +| ------------------- | ------------------------------------ | +| `:state_version_id` | The ID of the desired state version. | + +| Status | Response | Reason | +| ------- | ------------------------- | ------------------------------------------------------------------------------------------------------------- | +| [200][] | [JSON API document][] | Successfully returned current state version for the given workspace. | +| [404][] | [JSON API error object][] | Workspace not found, workspace does not have a current state version, or user unauthorized to perform action. | + +### Sample Request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + https://app.terraform.io/api/v2/state-versions/sv-SDboVZC8TCxXEneJ +``` + +### Sample Response + +```json +{ + "data": { + "id": "sv-g4rqST72reoHMM5a", + "type": "state-versions", + "attributes": { + "created-at": "2021-06-08T01:22:03.794Z", + "size": 940, + "hosted-state-download-url": "https://archivist.terraform.io/v1/object/...", + "hosted-state-upload-url": null, + "hosted-json-state-download-url": "https://archivist.terraform.io/v1/object/...", + "hosted-json-state-upload-url": null, + "status": "finalized", + "intermediate": false, + "modules": { + "root": { + "null-resource": 1, + "data.terraform-remote-state": 1 + } + }, + "providers": { + "provider[\"terraform.io/builtin/terraform\"]": { + "data.terraform-remote-state": 1 + }, + "provider[\"registry.terraform.io/hashicorp/null\"]": { + "null-resource": 1 + } + }, + "resources": [ + { + "name": "other_username", + "type": "data.terraform_remote_state", + "count": 1, + "module": "root", + "provider": "provider[\"terraform.io/builtin/terraform\"]" + }, + { + "name": "random", + "type": "null_resource", + "count": 1, + "module": "root", + "provider": "provider[\"registry.terraform.io/hashicorp/null\"]" + } + ], + "resources-processed": true, + "serial": 9, + "state-version": 4, + "terraform-version": "0.15.4", + "vcs-commit-url": "https://gitlab.com/my-organization/terraform-test/-/commit/abcdef12345", + "vcs-commit-sha": "abcdef12345" + }, + "relationships": { + "run": { + "data": { + "id": "run-YfmFLWpgTv31VZsP", + "type": "runs" + } + }, + "created-by": { + "data": { + "id": "user-onZs69ThPZjBK2wo", + "type": "users" + }, + "links": { + "self": "/api/v2/users/user-onZs69ThPZjBK2wo", + "related": "/api/v2/runs/run-YfmFLWpgTv31VZsP/created-by" + } + }, + "workspace": { + "data": { + "id": "ws-noZcaGXsac6aZSJR", + "type": "workspaces" + } + }, + "outputs": { + "data": [ + { + "id": "wsout-V22qbeM92xb5mw9n", + "type": "state-version-outputs" + }, + { + "id": "wsout-ymkuRnrNFeU5wGpV", + "type": "state-version-outputs" + }, + { + "id": "wsout-v82BjkZnFEcscipg", + "type": "state-version-outputs" + } + ] + } + }, + "links": { + "self": "/api/v2/state-versions/sv-g4rqST72reoHMM5a" + } + } +} +``` + +## Rollback to a Previous State Version + +`PATCH /workspaces/:workspace_id/state-versions` + +| Parameter | Description | +| --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `:workspace_id` | The workspace ID to create the new state version in. Obtain this from the [workspace settings](/terraform/enterprise/workspaces/settings) or the [Show Workspace](/terraform/enterprise/api-docs/workspaces#show-workspace) endpoint. | + +Creates a state version by duplicating the specified state version and sets it as the current state version for the given workspace. The workspace must be locked by the user creating a state version. The workspace may be locked [with the API](/terraform/enterprise/api-docs/workspaces#lock-a-workspace) or [with the UI](/terraform/enterprise/workspaces/settings#locking). This is most useful for rolling back to a known-good state after an operation such as a Terraform upgrade didn't go as planned. + +Creating state versions requires permission to read and write state versions for the workspace. ([More about permissions.](/terraform/enterprise/users-teams-organizations/permissions)) + +[permissions-citation]: #intentionally-unused---keep-for-maintainers + +!> **Warning:** Use caution when rolling back to a previous state. Replacing state improperly can result in orphaned or duplicated infrastructure resources. + +-> **Note:** You cannot access this endpoint with [organization tokens](/terraform/enterprise/users-teams-organizations/api-tokens#organization-api-tokens). You must access it with a [user token](/terraform/enterprise/users-teams-organizations/users#api-tokens) or [team token](/terraform/enterprise/users-teams-organizations/api-tokens#team-api-tokens). + +| Status | Response | Reason | +| ------- | ------------------------- | --------------------------------------------------------------- | +| [201][] | [JSON API document][] | Successfully rolled back. | +| [404][] | [JSON API error object][] | Workspace not found, or user unauthorized to perform action. | +| [409][] | [JSON API error object][] | Conflict; check the error object for more information. | +| [422][] | [JSON API error object][] | Malformed request body (missing attributes, wrong types, etc.). | + +### Request Body + +This PATCH endpoint requires a JSON object with the following properties as a request payload. + +Properties without a default value are required. + +| Key path | Type | Default | Description | +| --------------------------------------------------- | ------ | ------- | -------------------------------------------------------------- | +| `data.type` | string | | Must be `"state-versions"`. | +| `data.relationships.rollback-state-version.data.id` | string | | The ID of the state version to use for the rollback operation. | + +### Sample Payload + +```json +{ + "data": { + "type":"state-versions", + "relationships": { + "rollback-state-version": { + "data": { + "type": "state-versions", + "id": "sv-bWfq4Y1YpRKW4mx7" + } + } + } + } +} +``` + +### Sample Request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request PATCH \ + --data @payload.json \ + https://app.terraform.io/api/v2/workspaces/ws-6fHMCom98SDXSQUv/state-versions +``` + +### Sample Response + +```json +{ + "data": { + "id": "sv-DmoXecHePnNznaA4", + "type": "state-versions", + "attributes": { + "created-at": "2022-11-22T01:22:03.794Z", + "size": 940, + "hosted-state-download-url": "https://archivist.terraform.io/v1/object/...", + "hosted-state-upload-url": null, + "hosted-json-state-download-url": "https://archivist.terraform.io/v1/object/...", + "hosted-json-state-upload-url": null, + "modules": { + "root": { + "null-resource": 1, + "data.terraform-remote-state": 1 + } + }, + "providers": { + "provider[\"terraform.io/builtin/terraform\"]": { + "data.terraform-remote-state": 1 + }, + "provider[\"registry.terraform.io/hashicorp/null\"]": { + "null-resource": 1 + } + }, + "resources": [ + { + "name": "other_username", + "type": "data.terraform_remote_state", + "count": 1, + "module": "root", + "provider": "provider[\"terraform.io/builtin/terraform\"]" + }, + { + "name": "random", + "type": "null_resource", + "count": 1, + "module": "root", + "provider": "provider[\"registry.terraform.io/hashicorp/null\"]" + } + ], + "resources-processed": true, + "serial": 9, + "state-version": 4, + "terraform-version": "1.3.5" + }, + "relationships": { + "rollback-state-version": { + "data": { + "id": "sv-YfmFLgTv31VZsP", + "type": "state-versions" + } + } + }, + "links": { + "self": "/api/v2/state-versions/sv-DmoXecHePnNznaA4" + } + } +} +``` + +## Mark a State Version for Garbage Collection + + +This endpoint is exclusive to Terraform Enterprise, and not available in HCP Terraform. Learn more about Terraform Enterprise. + + +`POST /api/v2/state-versions/:state_version_id/actions/soft_delete_backing_data` + +This endpoint directs Terraform Enterprise to _soft delete_ the backing files associated with this state version. Soft deletion marks the state version for garbage collection. Terraform permanently deletes state versions after a set number of days unless the state version is restored. Once a state version is soft deleted, any attempts to read the state version will fail. Refer to [State Version Status](#state-version-status) for information about all data states. + +This endpoint can only soft delete state versions that are in an [`finalized` state](#state-version-status) and are not the current state version. Otherwise, calling this endpoint results in an error. + +You must have organization owner permissions to soft delete state versions. Refer to [Permissions](/terraform/enterprise/users-teams-organizations/permissions) for additional information about permissions. + +[permissions-citation]: #intentionally-unused---keep-for-maintainers + +| Parameter | Description | +| ------------------- | ----------------------------------------------------------- | +| `:state_version_id` | The ID of the state version to mark for garbage collection. | + +| Status | Response | Reason | +| ------- | ------------------------- | --------------------------------------------------------------------------------------------------- | +| [200][] | [JSON API document][] | Terraform successfully marked the data for garbage collection. | +| [400][] | [JSON API error object][] | Terraform failed to transition the state to `backing_data_soft_deleted`. | +| [404][] | [JSON API error object][] | Terraform did not find the state version or the user is not authorized to modify the state version. | + +### Sample Request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request POST \ + https://app.terraform.io/api/v2/state-versions/sv-ntv3HbhJqvFzamy7/actions/soft_delete_backing_data + --data {"data": {"attributes": {"delete-older-than-n-days": 23}}} +``` + +### Sample Response + +```json +{ + "data": { + "id": "sv-g4rqST72reoHMM5a", + "type": "state-versions", + "attributes": { + "created-at": "2021-06-08T01:22:03.794Z", + "size": 940, + "hosted-state-download-url": "https://archivist.terraform.io/v1/object/...", + "hosted-state-upload-url": null, + "hosted-json-state-download-url": "https://archivist.terraform.io/v1/object/...", + "hosted-json-state-upload-url": null, + "status": "backing_data_soft_deleted", + "intermediate": false, + "delete-older-than-n-days": 23, + "modules": { + "root": { + "null-resource": 1, + "data.terraform-remote-state": 1 + } + }, + "providers": { + "provider[\"terraform.io/builtin/terraform\"]": { + "data.terraform-remote-state": 1 + }, + "provider[\"registry.terraform.io/hashicorp/null\"]": { + "null-resource": 1 + } + }, + "resources": [ + { + "name": "other_username", + "type": "data.terraform_remote_state", + "count": 1, + "module": "root", + "provider": "provider[\"terraform.io/builtin/terraform\"]" + }, + { + "name": "random", + "type": "null_resource", + "count": 1, + "module": "root", + "provider": "provider[\"registry.terraform.io/hashicorp/null\"]" + } + ], + "resources-processed": true, + "serial": 9, + "state-version": 4, + "terraform-version": "0.15.4", + "vcs-commit-url": "https://gitlab.com/my-organization/terraform-test/-/commit/abcdef12345", + "vcs-commit-sha": "abcdef12345" + }, + "relationships": { + "run": { + "data": { + "id": "run-YfmFLWpgTv31VZsP", + "type": "runs" + } + }, + "created-by": { + "data": { + "id": "user-onZs69ThPZjBK2wo", + "type": "users" + }, + "links": { + "self": "/api/v2/users/user-onZs69ThPZjBK2wo", + "related": "/api/v2/runs/run-YfmFLWpgTv31VZsP/created-by" + } + }, + "workspace": { + "data": { + "id": "ws-noZcaGXsac6aZSJR", + "type": "workspaces" + } + }, + "outputs": { + "data": [ + { + "id": "wsout-V22qbeM92xb5mw9n", + "type": "state-version-outputs" + }, + { + "id": "wsout-ymkuRnrNFeU5wGpV", + "type": "state-version-outputs" + }, + { + "id": "wsout-v82BjkZnFEcscipg", + "type": "state-version-outputs" + } + ] + } + }, + "links": { + "self": "/api/v2/state-versions/sv-g4rqST72reoHMM5a" + } + } +} +``` + +## Restore a State Version Marked for Garbage Collection + + +This endpoint is exclusive to Terraform Enterprise, and not available in HCP Terraform. Learn more about Terraform Enterprise. + + +`POST /api/v2/state-versions/:state_version_id/actions/restore_backing_data` + +This endpoint directs Terraform Enterprise to restore backing files associated with this state version. This endpoint can only restore state versions that are not in a [`backing_data_permanently_deleted` state](#state-version-status). Terraform restores applicable state versions back to their `finalized` state. Otherwise, calling this endpoint results in an error. + +You must have organization owner permissions to restore state versions. Refer to [Permissions](/terraform/enterprise/users-teams-organizations/permissions) for additional information about permissions. + +[permissions-citation]: #intentionally-unused---keep-for-maintainers + +| Parameter | Description | +| ------------------- | --------------------------------------- | +| `:state_version_id` | The ID of the state version to restore. | + +| Status | Response | Reason | +| ------- | ------------------------- | --------------------------------------------------------------------------------------------------- | +| [200][] | [JSON API document][] | Terraform successfully initiated the restore process. | +| [400][] | [JSON API error object][] | Terraform failed to transition the state to `finalized`. | +| [404][] | [JSON API error object][] | Terraform did not find the state version or the user is not authorized to modify the state version. | + +### Sample Request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request POST \ + https://app.terraform.io/api/v2/state-versions/sv-ntv3HbhJqvFzamy7/actions/restore_backing_data +``` + +### Sample Response + +```json +{ + "data": { + "id": "sv-g4rqST72reoHMM5a", + "type": "state-versions", + "attributes": { + "created-at": "2021-06-08T01:22:03.794Z", + "size": 940, + "hosted-state-download-url": "https://archivist.terraform.io/v1/object/...", + "hosted-state-upload-url": null, + "hosted-json-state-download-url": "https://archivist.terraform.io/v1/object/...", + "hosted-json-state-upload-url": null, + "status": "uploaded", + "intermediate": false, + "modules": { + "root": { + "null-resource": 1, + "data.terraform-remote-state": 1 + } + }, + "providers": { + "provider[\"terraform.io/builtin/terraform\"]": { + "data.terraform-remote-state": 1 + }, + "provider[\"registry.terraform.io/hashicorp/null\"]": { + "null-resource": 1 + } + }, + "resources": [ + { + "name": "other_username", + "type": "data.terraform_remote_state", + "count": 1, + "module": "root", + "provider": "provider[\"terraform.io/builtin/terraform\"]" + }, + { + "name": "random", + "type": "null_resource", + "count": 1, + "module": "root", + "provider": "provider[\"registry.terraform.io/hashicorp/null\"]" + } + ], + "resources-processed": true, + "serial": 9, + "state-version": 4, + "terraform-version": "0.15.4", + "vcs-commit-url": "https://gitlab.com/my-organization/terraform-test/-/commit/abcdef12345", + "vcs-commit-sha": "abcdef12345" + }, + "relationships": { + "run": { + "data": { + "id": "run-YfmFLWpgTv31VZsP", + "type": "runs" + } + }, + "created-by": { + "data": { + "id": "user-onZs69ThPZjBK2wo", + "type": "users" + }, + "links": { + "self": "/api/v2/users/user-onZs69ThPZjBK2wo", + "related": "/api/v2/runs/run-YfmFLWpgTv31VZsP/created-by" + } + }, + "workspace": { + "data": { + "id": "ws-noZcaGXsac6aZSJR", + "type": "workspaces" + } + }, + "outputs": { + "data": [ + { + "id": "wsout-V22qbeM92xb5mw9n", + "type": "state-version-outputs" + }, + { + "id": "wsout-ymkuRnrNFeU5wGpV", + "type": "state-version-outputs" + }, + { + "id": "wsout-v82BjkZnFEcscipg", + "type": "state-version-outputs" + } + ] + } + }, + "links": { + "self": "/api/v2/state-versions/sv-g4rqST72reoHMM5a" + } + } +} +``` + +## Permanently Delete a State Version + + +This endpoint is exclusive to Terraform Enterprise, and not available in HCP Terraform. Learn more about Terraform Enterprise. + + +`POST /api/v2/state-versions/:state_version_id/actions/permanently_delete_backing_data` + +This endpoint directs Terraform Enterprise to permanently delete backing files associated with this state version. This endpoint can only permanently delete state versions that are in an [`backing_data_soft_deleted` state](#state-version-status) and are not the current state version. Otherwise, calling this endpoint results in an error. + +You must have organization owner permissions to permanently delete state versions. Refer to [Permissions](/terraform/enterprise/users-teams-organizations/permissions) for additional information about permissions. + +[permissions-citation]: #intentionally-unused---keep-for-maintainers + +| Parameter | Description | +| ------------------- | -------------------------------------------------- | +| `:state_version_id` | The ID of the state version to permanently delete. | + +| Status | Response | Reason | +| ------- | ------------------------- | -------------------------------------------------------------------------------------------------------- | +| [200][] | [JSON API document][] | Terraform deleted the data permanently. | +| [400][] | [JSON API error object][] | Terraform failed to transition the state to `backing_data_permanently_deleted`. | +| [404][] | [JSON API error object][] | Terraform did not find the state version or the user is not authorized to modify the state version data. | + +### Sample Request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request POST \ + https://app.terraform.io/api/v2/state-versions/sv-ntv3HbhJqvFzamy7/actions/permanently_delete_backing_data +``` + +### Sample Response + +```json +{ + "data": { + "id": "sv-g4rqST72reoHMM5a", + "type": "state-versions", + "attributes": { + "created-at": "2021-06-08T01:22:03.794Z", + "size": 940, + "hosted-state-download-url": "https://archivist.terraform.io/v1/object/...", + "hosted-state-upload-url": null, + "hosted-json-state-download-url": "https://archivist.terraform.io/v1/object/...", + "hosted-json-state-upload-url": null, + "status": "backing_data_permanently_deleted", + "intermediate": false, + "modules": { + "root": { + "null-resource": 1, + "data.terraform-remote-state": 1 + } + }, + "providers": { + "provider[\"terraform.io/builtin/terraform\"]": { + "data.terraform-remote-state": 1 + }, + "provider[\"registry.terraform.io/hashicorp/null\"]": { + "null-resource": 1 + } + }, + "resources": [ + { + "name": "other_username", + "type": "data.terraform_remote_state", + "count": 1, + "module": "root", + "provider": "provider[\"terraform.io/builtin/terraform\"]" + }, + { + "name": "random", + "type": "null_resource", + "count": 1, + "module": "root", + "provider": "provider[\"registry.terraform.io/hashicorp/null\"]" + } + ], + "resources-processed": true, + "serial": 9, + "state-version": 4, + "terraform-version": "0.15.4", + "vcs-commit-url": "https://gitlab.com/my-organization/terraform-test/-/commit/abcdef12345", + "vcs-commit-sha": "abcdef12345" + }, + "relationships": { + "run": { + "data": { + "id": "run-YfmFLWpgTv31VZsP", + "type": "runs" + } + }, + "created-by": { + "data": { + "id": "user-onZs69ThPZjBK2wo", + "type": "users" + }, + "links": { + "self": "/api/v2/users/user-onZs69ThPZjBK2wo", + "related": "/api/v2/runs/run-YfmFLWpgTv31VZsP/created-by" + } + }, + "workspace": { + "data": { + "id": "ws-noZcaGXsac6aZSJR", + "type": "workspaces" + } + }, + "outputs": { + "data": [ + { + "id": "wsout-V22qbeM92xb5mw9n", + "type": "state-version-outputs" + }, + { + "id": "wsout-ymkuRnrNFeU5wGpV", + "type": "state-version-outputs" + }, + { + "id": "wsout-v82BjkZnFEcscipg", + "type": "state-version-outputs" + } + ] + } + }, + "links": { + "self": "/api/v2/state-versions/sv-g4rqST72reoHMM5a" + } + } +} +``` + +## List State Version Outputs + +The output values from a state version are also available via the API. For details, see the [state version outputs documentation.](/terraform/enterprise/api-docs/state-version-outputs#list-state-version-outputs) + +### Available Related Resources + +The GET endpoints above can optionally return related resources, if requested with [the `include` query parameter](/terraform/enterprise/api-docs#inclusion-of-related-resources). The following resource types are available: + +- `created_by` - The user that created the state version. For state versions created via a run executed by HCP Terraform, this is an internal user account. +- `run` - The run that created the state version, if applicable. +- `run.created_by` - The user that manually triggered the run, if applicable. +- `run.configuration_version` - The configuration version used in the run. +- `outputs` - The parsed outputs for this state version. diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/team-access.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/team-access.mdx new file mode 100644 index 0000000000..54ed8a467c --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/team-access.mdx @@ -0,0 +1,433 @@ +--- +page_title: /team-workspaces API reference for Terraform Enterprise +description: >- + Use the Terraform Enterprise API's `/team-workspaces` endpoint to manage team + access to a workspace. Read, add, update, and remove a team's access to + workspaces. +source: terraform-docs-common +--- + +[200]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/200 + +[201]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/201 + +[202]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/202 + +[204]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/204 + +[400]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/400 + +[401]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/401 + +[403]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/403 + +[404]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404 + +[409]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/409 + +[412]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/412 + +[422]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/422 + +[429]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/429 + +[500]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500 + +[504]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/504 + +[JSON API document]: /terraform/enterprise/api-docs#json-api-documents + +[JSON API error object]: https://jsonapi.org/format/#error-objects + +# Team access API reference + + + +@include 'tfc-package-callouts/team-management.mdx' + + + +The team access APIs are used to associate a team to permissions on a workspace. A single `team-workspace` resource contains the relationship between the Team and Workspace, including the privileges the team has on the workspace. + +## Resource permissions + +A `team-workspace` resource represents a team's local permissions on a specific workspace. Teams can also have organization-level permissions that grant access to workspaces. HCP Terraform uses the more restrictive access level. For example, a team with the **Manage workspaces** permission enabled has admin access on all workspaces, even if their `team-workspace` on a particular workspace only grants read access. For more information, refer to [Managing Workspace Access](/terraform/enterprise/users-teams-organizations/teams/manage#managing-workspace-access). + +Any member of an organization can view team access relative to their own team memberships, including secret teams of which they are a member. Organization owners and workspace admins can modify team access or view the full set of secret team accesses. The organization token and the owners team token can act as an owner on these endpoints. Refer to [Permissions](/terraform/enterprise/users-teams-organizations/permissions) for additional information. + +## List Team Access to a Workspace + +`GET /team-workspaces` + +| Status | Response | Reason | +| ------- | ------------------------------------------------- | ---------------------------------------------------------- | +| [200][] | [JSON API document][] (`type: "team-workspaces"`) | The request was successful | +| [404][] | [JSON API error object][] | Workspace not found or user unauthorized to perform action | + +### Query Parameters + +[These are standard URL query parameters](/terraform/enterprise/api-docs#query-parameters); remember to percent-encode `[` as `%5B` and `]` as `%5D` if your tooling doesn't automatically encode URLs. + +This endpoint supports pagination [with standard URL query parameters](/terraform/enterprise/api-docs#query-parameters). If neither pagination query parameters are provided, the endpoint will not be paginated and will return all results. + +| Parameter | Description | +| ----------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `filter[workspace][id]` | **Required.** The workspace ID to list team access for. Obtain this from the [workspace settings](/terraform/enterprise/workspaces/settings) or the [Show Workspace](/terraform/enterprise/api-docs/workspaces#show-workspace) endpoint. | +| `page[number]` | **Optional.** | +| `page[size]` | **Optional.** | + +### Sample Request + +```shell +$ curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request GET \ + "https://app.terraform.io/api/v2/team-workspaces?filter%5Bworkspace%5D%5Bid%5D=ws-XGA52YVykdTgryTN" +``` + +### Sample Response + +```json +{ + "data": [ + { + "id": "tws-19iugLwoNgtWZbKP", + "type": "team-workspaces", + "attributes": { + "access": "custom", + "runs": "apply", + "variables": "none", + "state-versions": "none", + "sentinel-mocks": "none", + "workspace-locking": false, + "run-tasks": false + }, + "relationships": { + "team": { + "data": { + "id": "team-DBycxkdQrGFf5zEM", + "type": "teams" + }, + "links": { + "related": "/api/v2/teams/team-DBycxkdQrGFf5zEM" + } + }, + "workspace": { + "data": { + "id": "ws-XGA52YVykdTgryTN", + "type": "workspaces" + }, + "links": { + "related": "/api/v2/organizations/my-organization/workspaces/my-workspace" + } + } + }, + "links": { + "self": "/api/v2/team-workspaces/tws-19iugLwoNgtWZbKP" + } + } + ] +} +``` + +## Show a Team Access relationship + +`GET /team-workspaces/:id` + +| Status | Response | Reason | +| ------- | ------------------------------------------------- | ------------------------------------------------------------ | +| [200][] | [JSON API document][] (`type: "team-workspaces"`) | The request was successful | +| [404][] | [JSON API error object][] | Team access not found or user unauthorized to perform action | + +| Parameter | Description | +| --------- | -------------------------------------------------------------------------------------------------------------------------------------------- | +| `:id` | The ID of the team/workspace relationship. Obtain this from the [list team access action](#list-team-access-to-a-workspace) described above. | + +-> **Note:** As mentioned in [Add Team Access to a Workspace](#add-team-access-to-a-workspace) and [Update Team Access +to a Workspace](#update-team-access-to-a-workspace), several permission attributes are not editable unless `access` is +set to `custom`. When access is `read`, `plan`, `write`, or `admin`, these attributes are read-only and reflect the +implicit permissions granted to the current access level. + +### Sample Request + +```shell +$ curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request GET \ + https://app.terraform.io/api/v2/team-workspaces/tws-s68jV4FWCDwWvQq8 +``` + +### Sample Response + +```json +{ + "data": { + "id": "tws-s68jV4FWCDwWvQq8", + "type": "team-workspaces", + "attributes": { + "access": "write", + "runs": "apply", + "variables": "write", + "state-versions": "write", + "sentinel-mocks": "read", + "workspace-locking": true, + "run-tasks": false + }, + "relationships": { + "team": { + "data": { + "id": "team-DBycxkdQrGFf5zEM", + "type": "teams" + }, + "links": { + "related": "/api/v2/teams/team-DBycxkdQrGFf5zEM" + } + }, + "workspace": { + "data": { + "id": "ws-XGA52YVykdTgryTN", + "type": "workspaces" + }, + "links": { + "related": "/api/v2/organizations/my-organization/workspaces/my-workspace" + } + } + }, + "links": { + "self": "/api/v2/team-workspaces/tws-s68jV4FWCDwWvQq8" + } + } +} +``` + +## Add Team Access to a Workspace + +`POST /team-workspaces` + +| Status | Response | Reason | +| ------- | ------------------------------------------------- | ------------------------------------------------------------------ | +| [200][] | [JSON API document][] (`type: "team-workspaces"`) | The request was successful | +| [404][] | [JSON API error object][] | Workspace or Team not found or user unauthorized to perform action | +| [422][] | [JSON API error object][] | Malformed request body (missing attributes, wrong types, etc.) | + +### Request Body + +This POST endpoint requires a JSON object with the following properties as a request payload. + +Properties without a default value are required. + +| Key path | Type | Default | Description | +| ---------------------------------------- | ------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `data.type` | string | | Must be `"team-workspaces"`. | +| `data.attributes.access` | string | | The type of access to grant. Valid values are `read`, `plan`, `write`, `admin`, or `custom`. | +| `data.attributes.runs` | string | "read" | If `access` is `custom`, the permission to grant for the workspace's runs. Can only be used when `access` is `custom`. Valid values include `read`, `plan`, or `apply`. | +| `data.attributes.variables` | string | "none" | If `access` is `custom`, the permission to grant for the workspace's variables. Can only be used when `access` is `custom`. Valid values include `none`, `read`, or `write`. | +| `data.attributes.state-versions` | string | "none" | If `access` is `custom`, the permission to grant for the workspace's state versions. Can only be used when `access` is `custom`. Valid values include `none`, `read-outputs`, `read`, or `write`. | +| `data.attributes.sentinel-mocks` | string | "none" | If `access` is `custom`, the permission to grant for the workspace's Sentinel mocks. Can only be used when `access` is `custom`. Valid values include `none`, or `read`. | +| `data.attributes.workspace-locking` | boolean | false | If `access` is `custom`, the permission granting the ability to manually lock or unlock the workspace. Can only be used when `access` is `custom`. | +| `data.attributes.run-tasks` | boolean | false | If `access` is `custom`, this permission allows the team to manage run tasks within the workspace. | +| `data.relationships.workspace.data.type` | string | | Must be `workspaces`. | +| `data.relationships.workspace.data.id` | string | | The workspace ID to which the team is to be added. | +| `data.relationships.team.data.type` | string | | Must be `teams`. | +| `data.relationships.team.data.id` | string | | The ID of the team to add to the workspace. | + +### Sample Payload + +```json +{ + "data": { + "attributes": { + "access": "custom", + "runs": "apply", + "variables": "none", + "state-versions": "read-outputs", + "plan-outputs": "none", + "sentinel-mocks": "read", + "workspace-locking": false, + "run-tasks": false + }, + "relationships": { + "workspace": { + "data": { + "type": "workspaces", + "id": "ws-XGA52YVykdTgryTN" + } + }, + "team": { + "data": { + "type": "teams", + "id": "team-DBycxkdQrGFf5zEM" + } + } + }, + "type": "team-workspaces" + } +} +``` + +### Sample Request + +```shell +$ curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request POST \ + --data @payload.json \ + https://app.terraform.io/api/v2/team-workspaces +``` + +### Sample Response + +```json +{ + "data": { + "id": "tws-sezDAcCYWLnd3xz2", + "type": "team-workspaces", + "attributes": { + "access": "custom", + "runs": "apply", + "variables": "none", + "state-versions": "read-outputs", + "sentinel-mocks": "read", + "workspace-locking": false, + "run-tasks": false + }, + "relationships": { + "team": { + "data": { + "id": "team-DBycxkdQrGFf5zEM", + "type": "teams" + }, + "links": { + "related": "/api/v2/teams/team-DBycxkdQrGFf5zEM" + } + }, + "workspace": { + "data": { + "id": "ws-XGA52YVykdTgryTN", + "type": "workspaces" + }, + "links": { + "related": "/api/v2/organizations/my-organization/workspaces/my-workspace" + } + } + }, + "links": { + "self": "/api/v2/team-workspaces/tws-sezDAcCYWLnd3xz2" + } + } +} +``` + +## Update Team Access to a Workspace + +`PATCH /team-workspaces/:id` + +| Status | Response | Reason | +| ------- | ------------------------------------------------- | -------------------------------------------------------------- | +| [200][] | [JSON API document][] (`type: "team-workspaces"`) | The request was successful | +| [404][] | [JSON API error object][] | Team Access not found or user unauthorized to perform action | +| [422][] | [JSON API error object][] | Malformed request body (missing attributes, wrong types, etc.) | + +| Parameter | | | Description | +| ----------------------------------- | ------- | ------ | -------------------------------------------------------------------------------------------------------------------------------------------------- | +| `:id` | | | The ID of the team/workspace relationship. Obtain this from the [list team access action](#list-team-access-to-a-workspace) described above. | +| `data.attributes.access` | string | | The type of access to grant. Valid values are `read`, `plan`, `write`, `admin`, or `custom`. | +| `data.attributes.runs` | string | "read" | If `access` is `custom`, the permission to grant for the workspace's runs. Can only be used when `access` is `custom`. | +| `data.attributes.variables` | string | "none" | If `access` is `custom`, the permission to grant for the workspace's variables. Can only be used when `access` is `custom`. | +| `data.attributes.state-versions` | string | "none" | If `access` is `custom`, the permission to grant for the workspace's state versions. Can only be used when `access` is `custom`. | +| `data.attributes.sentinel-mocks` | string | "none" | If `access` is `custom`, the permission to grant for the workspace's Sentinel mocks. Can only be used when `access` is `custom`. | +| `data.attributes.workspace-locking` | boolean | false | If `access` is `custom`, the permission granting the ability to manually lock or unlock the workspace. Can only be used when `access` is `custom`. | +| `data.attributes.run-tasks` | boolean | false | If `access` is `custom`, this permission allows the team to manage run tasks within the workspace. | + +### Sample Request + +```shell +$ curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request PATCH \ + --data @payload.json \ + https://app.terraform.io/api/v2/team-workspaces/tws-s68jV4FWCDwWvQq8 +``` + +### Sample Payload + +```json +{ + "data": { + "attributes": { + "access": "custom", + "state-versions": "none" + } + } +} +``` + +### Sample Response + +```json +{ + "data": { + "id": "tws-s68jV4FWCDwWvQq8", + "type": "team-workspaces", + "attributes": { + "access": "custom", + "runs": "apply", + "variables": "write", + "state-versions": "none", + "sentinel-mocks": "read", + "workspace-locking": true, + "run-tasks": true + }, + "relationships": { + "team": { + "data": { + "id": "team-DBycxkdQrGFf5zEM", + "type": "teams" + }, + "links": { + "related": "/api/v2/teams/team-DBycxkdQrGFf5zEM" + } + }, + "workspace": { + "data": { + "id": "ws-XGA52YVykdTgryTN", + "type": "workspaces" + }, + "links": { + "related": "/api/v2/organizations/my-organization/workspaces/my-workspace" + } + } + }, + "links": { + "self": "/api/v2/team-workspaces/tws-s68jV4FWCDwWvQq8" + } + } +} +``` + +## Remove Team Access to a Workspace + +`DELETE /team-workspaces/:id` + +| Status | Response | Reason | +| ------- | ------------------------- | ------------------------------------------------------------ | +| [204][] | | The Team Access was successfully destroyed | +| [404][] | [JSON API error object][] | Team Access not found or user unauthorized to perform action | + +| Parameter | Description | +| --------- | -------------------------------------------------------------------------------------------------------------------------------------------- | +| `:id` | The ID of the team/workspace relationship. Obtain this from the [list team access action](#list-team-access-to-a-workspace) described above. | + +### Sample Request + +```shell +$ curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request DELETE \ + https://app.terraform.io/api/v2/team-workspaces/tws-sezDAcCYWLnd3xz2 +``` diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/team-members.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/team-members.mdx new file mode 100644 index 0000000000..310dfa6121 --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/team-members.mdx @@ -0,0 +1,249 @@ +--- +page_title: /relationships API reference for Terraform Enterprise +description: >- + Use the Terraform Enterprise API's `/relationships` endpoints to add and + remove users from teams using an account or organization membership ID. +source: terraform-docs-common +--- + +[200]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/200 + +[201]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/201 + +[202]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/202 + +[204]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/204 + +[400]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/400 + +[401]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/401 + +[403]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/403 + +[404]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404 + +[409]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/409 + +[412]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/412 + +[422]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/422 + +[429]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/429 + +[500]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500 + +[504]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/504 + +[JSON API document]: /terraform/enterprise/api-docs#json-api-documents + +[JSON API error object]: https://jsonapi.org/format/#error-objects + +# Team membership API reference + + + +-> **Note:** Team management is available in HCP Terraform **Standard**, **Plus**, and **Premium** editions. Free organizations can also use this API, but can only manage membership of their owners team. [Learn more about HCP Terraform pricing here](https://www.hashicorp.com/products/terraform/pricing). + + + +The Team Membership API is used to add or remove users from teams. The [Team API](/terraform/enterprise/api-docs/teams) is used to create or destroy teams. + +## Organization Membership + +-> **Note:** To add users to a team, they must first receive and accept the invitation to join the organization by email. This process ensures that you do not accidentally add the wrong person by mistyping a username. Refer to [the Organization Memberships API documentation](/terraform/enterprise/api-docs/organization-memberships) for more information. + +## Add a User to Team (With user ID) + +This method adds multiple users to a team using the user ID. Both users and teams must already exist. + +`POST /teams/:team_id/relationships/users` + +| Parameter | Description | +| ---------- | ------------------- | +| `:team_id` | The ID of the team. | + +### Request Body + +This POST endpoint requires a JSON object with the following properties as a request payload. + +Properties without a default value are required. + +| Key path | Type | Default | Description | +| ------------- | ------ | ------- | ------------------------------------------------ | +| `data[].type` | string | | Must be `"users"`. | +| `data[].id` | string | | The ID of the user you want to add to this team. | + +### Sample Payload + +```json +{ + "data": [ + { + "type": "users", + "id": "myuser1" + }, + { + "type": "users", + "id": "myuser2" + } + ] +} +``` + +### Sample Request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request POST \ + --data @payload.json \ + https://app.terraform.io/api/v2/teams/257525/relationships/users +``` + +## Add a User to Team (With organization membership ID) + +This method adds multiple users to a team using the organization membership ID. Unlike the user ID method, the user only needs an invitation to the organization. + +`POST /teams/:team_id/relationships/organization-memberships` + +| Parameter | Description | +| ---------- | ------------------- | +| `:team_id` | The ID of the team. | + +### Request Body + +This POST endpoint requires a JSON object with the following properties as a request payload. + +Properties without a default value are required. + +| Key path | Type | Default | Description | +| ------------- | ------ | ------- | -------------------------------------------------- | +| `data[].type` | string | | Must be `"organization-memberships"`. | +| `data[].id` | string | | The organization membership ID of the user to add. | + +### Sample Payload + +```json +{ + "data": [ + { + "type": "organization-memberships", + "id": "ou-nX7inDHhmC3quYgy" + }, + { + "type": "organization-memberships", + "id": "ou-tTJph1AQVK5ZmdND" + } + ] +} +``` + +### Sample Request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request POST \ + --data @payload.json \ + https://app.terraform.io/api/v2/teams/257525/relationships/organization-memberships +``` + +## Delete a User from Team (With user ID) + +This method removes multiple users from a team using the user ID. Both users and teams must already exist. This method only removes a user from this team. It does not delete that user overall. + +`DELETE /teams/:team_id/relationships/users` + +| Parameter | Description | +| ---------- | ------------------- | +| `:team_id` | The ID of the team. | + +### Request Body + +This DELETE endpoint requires a JSON object with the following properties as a request payload. + +Properties without a default value are required. + +| Key path | Type | Default | Description | +| ------------- | ------ | ------- | -------------------------------------------- | +| `data[].type` | string | | Must be `"users"`. | +| `data[].id` | string | | The ID of the user to remove from this team. | + +### Sample Payload + +```json +{ + "data": [ + { + "type": "users", + "id": "myuser1" + }, + { + "type": "users", + "id": "myuser2" + } + ] +} +``` + +### Sample Request + +```shell +$ curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request DELETE \ + --data @payload.json \ + https://app.terraform.io/api/v2/teams/257525/relationships/users +``` + +## Delete a User from Team (With organization membership ID) + +This method removes multiple users from a team using the organization membership ID. This method only removes a user from this team. It does not delete that user overall. + +`DELETE /teams/:team_id/relationships/organization-memberships` + +| Parameter | Description | +| ---------- | ------------------- | +| `:team_id` | The ID of the team. | + +### Request Body + +This DELETE endpoint requires a JSON object with the following properties as a request payload. + +Properties without a default value are required. + +| Key path | Type | Default | Description | +| ------------- | ------ | ------- | ----------------------------------------------------- | +| `data[].type` | string | | Must be `"organization-memberships"`. | +| `data[].id` | string | | The organization membership ID of the user to remove. | + +### Sample Payload + +```json +{ + "data": [ + { + "type": "organization-memberships", + "id": "ou-nX7inDHhmC3quYgy" + }, + { + "type": "organization-memberships", + "id": "ou-tTJph1AQVK5ZmdND" + } + ] +} +``` + +### Sample Request + +```shell +$ curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request DELETE \ + --data @payload.json \ + https://app.terraform.io/api/v2/teams/257525/relationships/organization-memberships +``` diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/team-tokens.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/team-tokens.mdx new file mode 100644 index 0000000000..9db2a1196a --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/team-tokens.mdx @@ -0,0 +1,558 @@ +--- +page_title: '/teams/:team_id/authentication-tokens API reference for Terraform Enterprise' +description: >- + Use the Terraform Enterprise API's `/teams/:team_id/authentication-tokens` + endpoint to generate, delete, and list a team's API tokens. +source: terraform-docs-common +--- + +[200]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/200 + +[201]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/201 + +[202]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/202 + +[204]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/204 + +[400]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/400 + +[401]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/401 + +[403]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/403 + +[404]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404 + +[409]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/409 + +[412]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/412 + +[422]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/422 + +[429]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/429 + +[500]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500 + +[504]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/504 + +[JSON API document]: /terraform/enterprise/api-docs#json-api-documents + +[JSON API error object]: https://jsonapi.org/format/#error-objects + +# Team tokens API reference + +Team API tokens grant access to a team's workspaces. Teams are not limited to a single token, and can have multiple tokens at a time. Team tokens are not associated with a specific user. + +Teams relying on the [**legacy**](/terraform/enterprise/api-docs/team-tokens#legacy-team-tokens-api-reference) team token API (`/teams/:team_id/authentication-token`), can only create a **single**, valid token at a time. Generating a new legacy token when one already exists for the team revokes the existing legacy token, replacing it with a new team token. + +You can create and delete team tokens and list an organization's team tokens. + +## Generate a new team token + +Generates a new team token. + +| Method | Path | +| :----- | :------------------------------------ | +| POST | /teams/:team_id/authentication-tokens | + +This endpoint returns the secret text of the new authentication token. You can only access the secret text when you create it and cannot recover it later. + +### Parameters + +- `:team_id` (`string: `) - specifies the team ID for generating the team token + +### Request body + +This POST endpoint requires a JSON object with the following properties as a request payload. + +| Key path | Type | Default | Description | +| ----------------------------- | ------ | ------- | ----------------------------------------------------------------------------------------------------------------------------------- | +| `data.type` | string | | Must be `"authentication-tokens"`. | +| `data.attributes.description` | string | | The description of the team token. Each description **must** be unique within the context of the team. | +| `data.attributes.expired-at` | string | `null` | The UTC date and time that the Team Token will expire, in ISO 8601 format. If omitted or set to `null` the token will never expire. | + +### Sample payload + +```json +{ + "data": { + "type": "authentication-tokens", + "attributes": { + "description": "Team API token for team ABC", + "expired-at": "2023-04-06T12:00:00.000Z" + } + } +} +``` + +### Sample request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request POST \ + --data @payload.json \ + https://app.terraform.io/api/v2/teams/team-BUHBEM97xboT8TVz/authentication-tokens +``` + +### Sample response + +```json +{ + "data": { + "id": "4111797", + "type": "authentication-tokens", + "attributes": { + "created-at": "2017-11-29T19:18:09.976Z", + "last-used-at": null, + "description": "Team API token for team ABC", + "token": "QnbSxjjhVMHJgw.atlasv1.gxZnWIjI5j752DGqdwEUVLOFf0mtyaQ00H9bA1j90qWb254lEkQyOdfqqcq9zZL7Sm0", + "expired-at": "2023-04-06T12:00:00.000Z" + }, + "relationships": { + "team": { + "data": { + "id": "team-Y7RyjccPVBKVEdp7", + "type": "teams" + } + }, + "created-by": { + "data": { + "id": "user-62goNpx1ThQf689e", + "type": "users" + } + } + } + } +} + +``` + +## Delete a team token + +| Method | Path | +| :----- | :------------------------------- | +| DELETE | /authentication-tokens/:token_id | + +### Parameters + +- `:token_id` (`string: `) - specifies the token_id from which to delete the token + +### Sample request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request DELETE \ + https://app.terraform.io/api/v2/authentication-tokens/at-6yEmxNAhaoQLH1Da +``` + +## List team tokens + +Lists the team tokens for the team. + +`GET /organizations/:organization_id/team-tokens` + +| Parameter | Description | +| ------------------ | -------------------------------------------------------------- | +| `:organization_id` | The ID of the organization whose team tokens you want to list. | + +This endpoint returns object metadata and does not include secret authentication details of tokens. You can only view a token when you create it and cannot recover it later. + +| Status | Response | Reason | +| ------- | --------------------------------------------- | -------------------------------------- | +| [200][] | [JSON API document][] (`type: "team-tokens"`) | The request was successful. | +| [200][] | Empty [JSON API document][] | The specified team has no team tokens. | +| [404][] | [JSON API error object][] | Team not found. | + +### Query parameters + +This endpoint supports pagination [with standard URL query parameters](/terraform/enterprise/api-docs#query-parameters) and searching with the `q` parameter. Remember to percent-encode `[` as `%5B` and `]` as `%5D` if your tooling doesn't automatically encode URLs. + +| Parameter | Description | +| -------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `page[number]` | **Optional.** If omitted, the endpoint returns the first page. | +| `page[size]` | **Optional.** If omitted, the endpoint returns 20 tokens per page. | +| `q` | **Optional.** A search query string. You can search for a team authentication token using the team name. | +| `sort` | **Optional.** Allows sorting the team tokens by `"created-by"`, `"expired-at"`, and `"last-used-at"`. Prepending a hyphen to the sort parameter reverses the order. If omitted, the default sort order ascending. | + +### Sample response + +```json +{ + "data": [ + { + "id": "at-TLhN8cc6ro6qYDvp", + "type": "authentication-tokens", + "attributes": { + "created-at": "2017-11-29T19:18:09.976Z", + "last-used-at": null, + "description": "Team API token for team ABC", + "token": "QnbSxjjhVMHJgw.atlasv1.gxZnWIjI5j752DGqdwEUVLOFf0mtyaQ00H9bA1j90qWb254lEkQyOdfqqcq9zZL7Sm0", + "expired-at": "2023-04-06T12:00:00.000Z" + }, + "relationships": { + "team": { + "data": { + "id": "team-Y7RyjccPVBKVEdp7", + "type": "teams" + } + }, + "created-by": { + "data": { + "id": "user-ccU6h629sszLJBpY", + "type": "users" + } + } + } + }, + { + "id": "at-qfc2wqqJ1T5sCamM", + "type": "authentication-tokens", + "attributes": { + "created-at": "2024-06-19T18:44:44.051Z", + "last-used-at": null, + "description": "Team API token for team XYZ", + "token": null, + "expired-at": "2024-07-19T18:44:43.818Z" + }, + "relationships": { + "team": { + "data": { + "id": "team-58pFiBffTLMxLphR", + "type": "teams" + } + }, + "created-by": { + "data": { + "id": "user-ccU6h629hhzLJBpY", + "type": "users" + } + } + } + }, + ] +} +``` + +## Show a team token + +Use this endpoint to display a particular [team token](/terraform/enterprise/users-teams-organizations/teams#api-tokens). + +`GET /authentication-tokens/:token_id` + +| Parameter | Description | +| ----------- | ------------------------- | +| `:token_id` | The ID of the Team Token. | + +The object returned by this endpoint only contains metadata, and does not include the secret text of the authentication token. A token's secret test is only shown upon creation, and cannot be recovered later. + +| Status | Response | Reason | +| ------- | ------------------------------------------------------- | ------------------------------------------------------------ | +| [200][] | [JSON API document][] (`type: "authentication-tokens"`) | The request was successful | +| [404][] | [JSON API error object][] | Team Token not found, or unauthorized to view the Team Token | + +### Sample request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request GET \ + https://app.terraform.io/api/v2/authentication-tokens/at-6yEmxNAhaoQLH1Da +``` + +### Sample response + +```json +{ + "data": { + "id": "at-6yEmxNAhaoQLH1Da", + "type": "authentication-tokens", + "attributes": { + "created-at": "2017-11-29T19:18:09.976Z", + "last-used-at": null, + "description": "Team API token for team ABC", + "token": "QnbSxjjhVMHJgw.atlasv1.gxZnWIjI5j752DGqdwEUVLOFf0mtyaQ00H9bA1j90qWb254lEkQyOdfqqcq9zZL7Sm0", + "expired-at": "2023-04-06T12:00:00.000Z" + }, + "relationships": { + "team": { + "data": { + "id": "team-LnREdjodkvZFGdXL", + "type": "teams" + } + }, + "created-by": { + "data": { + "id": "user-MA4GL63FmYRpSFxa", + "type": "users" + } + } + } + } +} +``` + +# Legacy team tokens API reference + +Legacy team API tokens grant access to a team's workspaces. Each team can have a single legacy API token that is not associated with a specific user. +You can create and delete team tokens and list an organization's team tokens. +The [team tokens API](/terraform/enterprise/api-docs/team-tokens) includes the same functionality as legacy team tokens, and allows you to provision multiple tokens with descriptions per team. + +## Generate a new team token + +Generates a new team token and overrides existing token if one exists. + +| Method | Path | +| :----- | :----------------------------------- | +| POST | /teams/:team_id/authentication-token | + +This endpoint returns the secret text of the new authentication token. You can only access the secret when you create it and cannot recover it later. + +### Parameters + +- `:team_id` (`string: `) - specifies the team ID for generating the team token + +### Request body + +This POST endpoint requires a JSON object with the following properties as a request payload. + +| Key path | Type | Default | Description | +| ---------------------------- | ------ | ------- | ----------------------------------------------------------------------------------------------------------------------------------- | +| `data.type` | string | | Must be `"authentication-token"`. | +| `data.attributes.expired-at` | string | `null` | The UTC date and time that the Team Token will expire, in ISO 8601 format. If omitted or set to `null` the token will never expire. | + +### Sample payload + +```json +{ + "data": { + "type": "authentication-token", + "attributes": { + "expired-at": "2023-04-06T12:00:00.000Z" + } + } +} +``` + +### Sample request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request POST \ + --data @payload.json \ + https://app.terraform.io/api/v2/teams/team-BUHBEM97xboT8TVz/authentication-token +``` + +### Sample response + +```json +{ + "data": { + "id": "4111797", + "type": "authentication-tokens", + "attributes": { + "created-at": "2017-11-29T19:18:09.976Z", + "last-used-at": null, + "description": null, + "token": "QnbSxjjhVMHJgw.atlasv1.gxZnWIjI5j752DGqdwEUVLOFf0mtyaQ00H9bA1j90qWb254lEkQyOdfqqcq9zZL7Sm0", + "expired-at": "2023-04-06T12:00:00.000Z" + }, + "relationships": { + "team": { + "data": { + "id": "team-Y7RyjccPVBKVEdp7", + "type": "teams" + } + }, + "created-by": { + "data": { + "id": "user-62goNpx1ThQf689e", + "type": "users" + } + } + } + } +} +``` + +## Delete the team token + +| Method | Path | +| :----- | :----------------------------------- | +| DELETE | /teams/:team_id/authentication-token | + +### Parameters + +- `:team_id` (`string: `) - specifies the team_id from which to delete the token + +### Sample request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request DELETE \ + https://app.terraform.io/api/v2/teams/team-BUHBEM97xboT8TVz/authentication-token +``` + +## List team tokens + +Lists the [team tokens](/terraform/enterprise/users-teams-organizations/teams#api-tokens) in an organization. + +`GET organizations/:organization_name/team-tokens` + +| Parameter | Description | +| -------------------- | ---------------------------------------------------------------- | +| `:organization_name` | The name of the organization whose team tokens you want to list. | + +This endpoint returns object metadata and does not include secret authentication details of tokens. You can only view a token when you create it and cannot recover it later. + +By default, this endpoint returns tokens by ascending expiration date. + +| Status | Response | Reason | +| ------- | --------------------------------------------- | ---------------------------------------------- | +| [200][] | [JSON API document][] (`type: "team-tokens"`) | The request was successful. | +| [200][] | Empty [JSON API document][] | The specified organization has no team tokens. | +| [404][] | [JSON API error object][] | Organization not found. | + +### Query parameters + +This endpoint supports pagination [with standard URL query parameters](/terraform/enterprise/api-docs#query-parameters) and searching with the `q` parameter. Remember to percent-encode `[` as `%5B` and `]` as `%5D` if your tooling doesn't automatically encode URLs. + +| Parameter | Description | +| -------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `page[number]` | **Optional.** If omitted, the endpoint returns the first page. | +| `page[size]` | **Optional.** If omitted, the endpoint returns 20 tokens per page. | +| `q` | **Optional.** A search query string. You can search for a team authentication token using the team name. | +| `sort` | **Optional.** Allows sorting the team tokens by `"team-name"`, `"created-by"`, `"expired-at"`, and `"last-used-at"`. Prepending a hyphen to the sort parameter reverses the order. For example, `"-team-name"` sorts by name in reverse alphabetical order. If omitted, the default sort order ascending. | + +### Sample response + +```json +{ + "data": [ + { + "id": "at-TLhN8cc6ro6qYDvp", + "type": "authentication-tokens", + "attributes": { + "created-at": "2024-06-19T18:28:25.267Z", + "last-used-at": null, + "description": null, + "token": null, + "expired-at": "2024-07-19T18:28:25.030Z" + }, + "relationships": { + "team": { + "data": { + "id": "team-Y7RyjccPVBKVEdp7", + "type": "teams" + } + }, + "created-by": { + "data": { + "id": "user-ccU6h629sszLJBpY", + "type": "users" + } + } + } + }, + { + "id": "at-qfc2wqqJ1T5sCamM", + "type": "authentication-tokens", + "attributes": { + "created-at": "2024-06-19T18:44:44.051Z", + "last-used-at": null, + "description": null, + "token": null, + "expired-at": "2024-07-19T18:44:43.818Z" + }, + "relationships": { + "team": { + "data": { + "id": "team-58pFiBffTLMxLphR", + "type": "teams" + } + }, + "created-by": { + "data": { + "id": "user-ccU6h629hhzLJBpY", + "type": "users" + } + } + } + }, + ] +} +``` + +## Show a team token + +Use this endpoint to display a [team token](/terraform/enterprise/users-teams-organizations/teams#api-tokens) for a particular team. + +`GET /teams/:team_id/authentication-token` + +| Parameter | Description | +| ---------- | ------------------- | +| `:team_id` | The ID of the Team. | + +You can also fetch a team token directly by using the token's ID with the `authentication-tokens/` endpoint. + +`GET /authentication-tokens/:token_id` + +| Parameter | Description | +| ----------- | ------------------------- | +| `:token_id` | The ID of the Team Token. | + +The object returned by this endpoint only contains metadata, and does not include the secret text of the authentication token. A token's secret text is only shown upon creation, and cannot be recovered later. + +| Status | Response | Reason | +| ------- | ------------------------------------------------------- | ------------------------------------------------------------ | +| [200][] | [JSON API document][] (`type: "authentication-tokens"`) | The request was successful | +| [404][] | [JSON API error object][] | Team Token not found, or unauthorized to view the Team Token | + +### Sample request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request GET \ + https://app.terraform.io/api/v2/teams/team-6yEmxNAhaoQLH1Da/authentication-token +``` + +### Sample response + +```json +{ + "data": { + "id": "at-6yEmxNAhaoQLH1Da", + "type": "authentication-tokens", + "attributes": { + "created-at": "2023-11-25T22:31:30.624Z", + "last-used-at": "2023-11-26T20:34:59.487Z", + "description": null, + "token": null, + "expired-at": "2024-04-06T12:00:00.000Z" + }, + "relationships": { + "team": { + "data": { + "id": "team-LnREdjodkvZFGdXL", + "type": "teams" + } + }, + "created-by": { + "data": { + "id": "user-MA4GL63FmYRpSFxa", + "type": "users" + } + } + } + } +} +``` diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/teams.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/teams.mdx new file mode 100644 index 0000000000..1f96792b02 --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/teams.mdx @@ -0,0 +1,466 @@ +--- +page_title: /teams API reference for Terraform Enterprise +description: >- + Use the Terraform Enterprise API's `/teams` endpoint to read, create, update, + and delete teams. +source: terraform-docs-common +--- + +[200]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/200 + +[201]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/201 + +[202]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/202 + +[204]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/204 + +[400]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/400 + +[401]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/401 + +[403]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/403 + +[404]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404 + +[409]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/409 + +[412]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/412 + +[422]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/422 + +[429]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/429 + +[500]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500 + +[504]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/504 + +[JSON API document]: /terraform/enterprise/api-docs#json-api-documents + +[JSON API error object]: https://jsonapi.org/format/#error-objects + +# Teams API reference + +The Teams API is used to create, edit, and destroy teams as well as manage a team's organization-level permissions. The [Team Membership API](/terraform/enterprise/api-docs/team-members) is used to add or remove users from a team. Use the [Team Access API](/terraform/enterprise/api-docs/team-access) to associate a team with privileges on an individual workspace. + + + +@include 'tfc-package-callouts/team-management.mdx' + + + +Any member of an organization can view visible teams and any secret teams they are a member of. Only organization owners can modify teams or view the full set of secret teams. The organization token and the owners team token can act as an owner on these endpoints. ([More about permissions.](/terraform/enterprise/users-teams-organizations/permissions)) + +[permissions-citation]: #intentionally-unused---keep-for-maintainers + +## Organization Membership + +-> **Note:** Users must be invited to join organizations before they can be added to teams. See [the Organization Memberships API documentation](/terraform/enterprise/api-docs/organization-memberships) for more information. Invited users who have not yet accepted will not appear in Teams API responses. + +## List teams + +`GET organizations/:organization_name/teams` + +| Parameter | Description | +| -------------------- | ------------------------------------------------ | +| `:organization_name` | The name of the organization to list teams from. | + +The response may identify HashiCorp API service accounts, for example `api-team_XXXXXX`, as a members of a team. However, API service accounts do not appear in the UI. As a result, there may be differences between the number of team members reported by the UI and the API. For example, the UI may report `0` members on a team when and the API reports `1`. + +### Query Parameters + +This endpoint supports pagination [with standard URL query parameters](/terraform/enterprise/api-docs#query-parameters). Remember to percent-encode `[` as `%5B` and `]` as `%5D` if your tooling doesn't automatically encode URLs. + +| Parameter | Description | +| --------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `q` | **Optional.** Allows querying a list of teams by name. This search is case-insensitive. | +| `filter[names]` | **Optional.** If specified, restricts results to a team with a matching name. If multiple comma separated values are specified, teams matching any of the names are returned. | +| `page[number]` | **Optional.** If omitted, the endpoint will return the first page. | +| `page[size]` | **Optional.** If omitted, the endpoint will return 20 teams per page. | + +### Sample Request + +```shell +$ curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request GET \ + https://app.terraform.io/api/v2/organizations/my-organization/teams +``` + +### Sample Response + +The `sso-team-id` attribute is only returned in Terraform Enterprise 202204-1 and later, or in HCP Terraform. +The `allow-member-token-management` attribute is set to `false` for Terraform Enterprise versions older than 202408-1. + +```json +{ + "data": [ + { + "id": "team-6p5jTwJQXwqZBncC", + "type": "teams", + "attributes": { + "name": "team-creation-test", + "sso-team-id": "cb265c8e41bddf3f9926b2cf3d190f0e1627daa4", + "users-count": 0, + "visibility": "organization", + "allow-member-token-management": true, + "permissions": { + "can-update-membership": true, + "can-destroy": true, + "can-update-organization-access": true, + "can-update-api-token": true, + "can-update-visibility": true + }, + "organization-access": { + "manage-policies": true, + "manage-policy-overrides": false, + "manage-run-tasks": true, + "manage-workspaces": false, + "manage-vcs-settings": false, + "manage-agent-pools": false, + "manage-projects": false, + "read-projects": false, + "read-workspaces": false + } + }, + "relationships": { + "users": { + "data": [] + }, + "authentication-token": { + "meta": {} + } + }, + "links": { + "self": "/api/v2/teams/team-6p5jTwJQXwqZBncC" + } + } + ] +} +``` + +## Create a Team + +`POST /organizations/:organization_name/teams` + +| Parameter | Description | +| -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `:organization_name` | The name of the organization to create the team in. The organization must already exist in the system, and the user must have permissions to create new teams. | + +| Status | Response | Reason | +| ------- | --------------------------------------- | -------------------------------------------------------------- | +| [200][] | [JSON API document][] (`type: "teams"`) | Successfully created a team | +| [400][] | [JSON API error object][] | Invalid `include` parameter | +| [404][] | [JSON API error object][] | Organization not found, or user unauthorized to perform action | +| [422][] | [JSON API error object][] | Malformed request body (missing attributes, wrong types, etc.) | +| [500][] | [JSON API error object][] | Failure during team creation | + +### Request Body + +This POST endpoint requires a JSON object with the following properties as a request payload. + +Properties without a default value are required. + +-> **Note:** You cannot set `manage-workspaces` to `false` when setting `manage-projects` to `true`, since project permissions cascade down to workspaces. This is also the case for `read-workspaces` and `read-projects`. If `read-projects` is `true`, `read-workspaces` must be `true` as well. + +| Key path | Type | Default | Description | +| ------------------------------------- | ------ | ---------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `data.type` | string | | Must be `"teams"`. | +| `data.attributes.name` | string | | The name of the team, which can only include letters, numbers, `-`, and `_`. This will be used as an identifier and must be unique in the organization. | +| `data.attributes.sso-team-id` | string | (nothing) | The unique identifier of the team from the SAML `MemberOf` attribute. Only available in Terraform Enterprise 202204-1 and later, or in HCP Terraform. | +| `data.attributes.organization-access` | object | (nothing) | Settings for the team's organization access. This object can include the `manage-policies`, `manage-policy-overrides`, `manage-run-tasks`, `manage-workspaces`, `manage-vcs-settings`, `manage-agent-pools`, `manage-providers`, `manage-modules`, `manage-projects`, `read-projects`, `read-workspaces`, `manage-membership`, `manage-teams`, and `manage-organization-access` properties with boolean values. All properties default to `false`. | +| `data.attributes.visibility` | string | `"secret"` | The team's visibility. Must be `"secret"` or `"organization"` (visible). | + +### Sample Payload + +```json +{ + "data": { + "type": "teams", + "attributes": { + "name": "team-creation-test", + "sso-team-id": "cb265c8e41bddf3f9926b2cf3d190f0e1627daa4", + "organization-access": { + "manage-workspaces": true + } + } + } +} +``` + +### Sample Request + +```shell +$ curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request POST \ + --data @payload.json \ + https://app.terraform.io/api/v2/organizations/my-organization/teams +``` + +### Sample Response + +The `sso-team-id` attribute is only returned in Terraform Enterprise 202204-1 and later, or in HCP Terraform. + +```json +{ + "data": { + "attributes": { + "name": "team-creation-test", + "sso-team-id": "cb265c8e41bddf3f9926b2cf3d190f0e1627daa4", + "organization-access": { + "manage-policies": false, + "manage-policy-overrides": false, + "manage-run-tasks": false, + "manage-vcs-settings": false, + "manage-agent-pools": false, + "manage-workspaces": true, + "manage-providers": false, + "manage-modules": false, + "manage-projects": false, + "read-projects": false, + "read-workspaces": true, + "manage-membership": false, + "manage-teams": false, + "manage-organization-access": false + }, + "permissions": { + "can-update-membership": true, + "can-destroy": true, + "can-update-organization-access": true, + "can-update-api-token": true, + "can-update-visibility": true + }, + "users-count": 0, + "visibility": "secret", + "allow-member-token-management": true + }, + "id": "team-6p5jTwJQXwqZBncC", + "links": { + "self": "/api/v2/teams/team-6p5jTwJQXwqZBncC" + }, + "relationships": { + "authentication-token": { + "meta": {} + }, + "users": { + "data": [] + } + }, + "type": "teams" + } +} +``` + +## Show Team Information + +`GET /teams/:team_id` + +| Parameter | Description | +| ---------- | ------------------------ | +| `:team_id` | The team ID to be shown. | + +### Sample Request + +```shell +$ curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request GET \ + https://app.terraform.io/api/v2/teams/team-6p5jTwJQXwqZBncC +``` + +### Sample Response + +The `sso-team-id` attribute is only returned in Terraform Enterprise 202204-1 and later, or in HCP Terraform. + +```json +{ + "data": { + "id": "team-6p5jTwJQXwqZBncC", + "type": "teams", + "attributes": { + "name": "team-creation-test", + "sso-team-id": "cb265c8e41bddf3f9926b2cf3d190f0e1627daa4", + "users-count": 0, + "visibility": "organization", + "allow-member-token-management": true, + "permissions": { + "can-update-membership": true, + "can-destroy": true, + "can-update-organization-access": true, + "can-update-api-token": true, + "can-update-visibility": true + }, + "organization-access": { + "manage-policies": true, + "manage-policy-overrides": false, + "manage-run-tasks": true, + "manage-workspaces": false, + "manage-vcs-settings": false, + "manage-agent-pools": false, + "manage-providers": false, + "manage-modules": false, + "manage-projects": false, + "read-projects": false, + "read-workspaces": false, + "manage-membership": false, + "manage-teams": false, + "manage-organization-access": false + } + }, + "relationships": { + "users": { + "data": [] + }, + "authentication-token": { + "meta": {} + } + }, + "links": { + "self": "/api/v2/teams/team-6p5jTwJQXwqZBncC" + } + } +} +``` + +## Update a Team + +`PATCH /teams/:team_id` + +| Parameter | Description | +| ---------- | -------------------------- | +| `:team_id` | The team ID to be updated. | + +| Status | Response | Reason | +| ------- | --------------------------------------- | -------------------------------------------------------------- | +| [200][] | [JSON API document][] (`type: "teams"`) | Successfully created a team | +| [400][] | [JSON API error object][] | Invalid `include` parameter | +| [404][] | [JSON API error object][] | Team not found, or user unauthorized to perform action | +| [422][] | [JSON API error object][] | Malformed request body (missing attributes, wrong types, etc.) | +| [500][] | [JSON API error object][] | Failure during team creation | + +### Request Body + +This PATCH endpoint requires a JSON object with the following properties as a request payload. + +Properties without a default value are required. + +-> **Note:** You cannot set `manage-workspaces` to `false` when setting `manage-projects` to `true`, since project permissions cascade down to workspaces. This is also the case for `read-workspaces` and `read-projects`. If `read-projects` is `true`, `read-workspaces` must be `true` as well. + +| Key path | Type | Default | Description | +| --------------------------------------------- | ------- | ---------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `data.type` | string | | Must be `"teams"`. | +| `data.attributes.name` | string | (previous value) | The name of the team, which can only include letters, numbers, `-`, and `_`. This will be used as an identifier and must be unique in the organization. | +| `data.attributes.sso-team-id` | string | (previous value) | The unique identifier of the team from the SAML `MemberOf` attribute. Only available in Terraform Enterprise 202204-1 and later, or in HCP Terraform. | +| `data.attributes.organization-access` | object | (previous value) | Settings for the team's organization access. This object can include the `manage-policies`, `manage-policy-overrides`, `manage-run-tasks`, `manage-workspaces`, `manage-vcs-settings`, `manage-agent-pools`, `manage-providers`, `manage-modules`, `manage-projects`, `read-projects`, `read-workspaces`, `manage-membership`, `manage-teams`, and `manage-organization-access` properties with boolean values. All properties default to `false`. | +| `data.attributes.visibility` | string | (previous value) | The team's visibility. Must be `"secret"` or `"organization"` (visible). | +| `data.attributes.allow-team-token-management` | boolean | (previous value) | The ability to enable and disable team token management for a team. Defaults to true. | + +### Sample Payload + +```json +{ + "data": { + "type": "teams", + "attributes": { + "visibility": "organization", + "allow-member-token-management": true, + "organization-access": { + "manage-vcs-settings": true + } + } + } +} +``` + +### Sample Request + +```shell +$ curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request PATCH \ + --data @payload.json \ + https://app.terraform.io/api/v2/teams/team-6p5jTwJQXwqZBncC +``` + +### Sample Response + +The `sso-team-id` attribute is only returned in Terraform Enterprise 202204-1 and later, or in HCP Terraform. + +```json +{ + "data": { + "attributes": { + "name": "team-creation-test", + "sso-team-id": "cb265c8e41bddf3f9926b2cf3d190f0e1627daa4", + "organization-access": { + "manage-policies": false, + "manage-policy-overrides": false, + "manage-run-tasks": true, + "manage-vcs-settings": true, + "manage-agent-pools": false, + "manage-workspaces": true, + "manage-providers": false, + "manage-modules": false, + "manage-projects": false, + "read-projects": false, + "read-workspaces": true, + "manage-membership": false, + "manage-teams": false, + "manage-organization-access": false + }, + "visibility": "organization", + "allow-member-token-management": true, + "permissions": { + "can-update-membership": true, + "can-destroy": true, + "can-update-organization-access": true, + "can-update-api-token": true, + "can-update-visibility": true + }, + "users-count": 0 + }, + "id": "team-6p5jTwJQXwqZBncC", + "links": { + "self": "/api/v2/teams/team-6p5jTwJQXwqZBncC" + }, + "relationships": { + "authentication-token": { + "meta": {} + }, + "users": { + "data": [] + } + }, + "type": "teams" + } +} +``` + +## Delete a Team + +`DELETE /teams/:team_id` + +| Parameter | Description | +| ---------- | -------------------------- | +| `:team_id` | The team ID to be deleted. | + +### Sample Request + +```shell +$ curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request DELETE \ + https://app.terraform.io/api/v2/teams/team-6p5jTwJQXwqZBncC +``` + +## Available Related Resources + +The GET endpoints above can optionally return related resources, if requested with [the `include` query parameter](/terraform/enterprise/api-docs#inclusion-of-related-resources). The following resource types are available: + +- `users` (`string`) - Returns the full user record for every member of a team. +- `organization-memberships` (`string`) - Returns the full organization membership record for every member of a team. diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/user-tokens.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/user-tokens.mdx new file mode 100644 index 0000000000..a26352ea06 --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/user-tokens.mdx @@ -0,0 +1,286 @@ +--- +page_title: /users/authentication-tokens API reference for Terraform Enterprise +description: >- + Use the Terraform Enterprise API's `/users/authentication-tokens` endpoint to + read, create, and destroy user-specific API tokens. +source: terraform-docs-common +--- + +[200]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/200 + +[201]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/201 + +[202]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/202 + +[204]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/204 + +[400]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/400 + +[401]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/401 + +[403]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/403 + +[404]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404 + +[409]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/409 + +[412]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/412 + +[422]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/422 + +[429]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/429 + +[500]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500 + +[504]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/504 + +[JSON API document]: /terraform/enterprise/api-docs#json-api-documents + +[JSON API error object]: https://jsonapi.org/format/#error-objects + +# User tokens API reference + +## List User Tokens + +`GET /users/:user_id/authentication-tokens` + +| Parameter | Description | +| ---------- | ------------------- | +| `:user_id` | The ID of the User. | + +Use the [Account API](/terraform/enterprise/api-docs/account) to find your own user ID. + +The objects returned by this endpoint only contain metadata, and do not include the secret text of any authentication tokens. A token is only shown upon creation, and cannot be recovered later. + +-> **Note:** You must access this endpoint with a [user token](/terraform/enterprise/users-teams-organizations/users#api-tokens), and it will only return useful data for that token's user account. + +| Status | Response | Reason | +| ------- | ------------------------------------------------------- | ------------------------------------------------------------------------------------- | +| [200][] | [JSON API document][] (`type: "authentication-tokens"`) | The request was successful | +| [200][] | Empty [JSON API document][] (no type) | User has no authentication tokens, or request was made by someone other than the user | +| [404][] | [JSON API error object][] | User not found | + +### Query Parameters + +This endpoint supports pagination [with standard URL query parameters](/terraform/enterprise/api-docs#query-parameters). Remember to percent-encode `[` as `%5B` and `]` as `%5D` if your tooling doesn't automatically encode URLs. If neither pagination query parameters are provided, the endpoint will not be paginated and will return all results. + +| Parameter | Description | +| -------------- | --------------------------------------------------------------------------- | +| `page[number]` | **Optional.** If omitted, the endpoint will return the first page. | +| `page[size]` | **Optional.** If omitted, the endpoint will return 20 user tokens per page. | + +### Sample Request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request GET \ + https://app.terraform.io/api/v2/users/user-MA4GL63FmYRpSFxa/authentication-tokens +``` + +### Sample Response + +```json +{ + "data": [ + { + "id": "at-QmATJea6aWj1xR2t", + "type": "authentication-tokens", + "attributes": { + "created-at": "2018-11-06T22:56:10.203Z", + "last-used-at": null, + "description": null, + "token": null, + "expired-at": null + }, + "relationships": { + "created-by": { + "data": null + } + } + }, + { + "id": "at-6yEmxNAhaoQLH1Da", + "type": "authentication-tokens", + "attributes": { + "created-at": "2018-11-25T22:31:30.624Z", + "last-used-at": "2018-11-26T20:27:54.931Z", + "description": "api", + "token": null, + "expired-at": "2023-04-06T12:00:00.000Z" + }, + "relationships": { + "created-by": { + "data": { + "id": "user-MA4GL63FmYRpSFxa", + "type": "users" + } + } + } + } + ] +} +``` + +## Show a User Token + +`GET /authentication-tokens/:id` + +| Parameter | Description | +| --------- | ------------------------- | +| `:id` | The ID of the User Token. | + +The objects returned by this endpoint only contain metadata, and do not include the secret text of any authentication tokens. A token is only shown upon creation, and cannot be recovered later. + +-> **Note:** You must access this endpoint with a [user token](/terraform/enterprise/users-teams-organizations/users#api-tokens), and it will only return useful data for that token's user account. + +| Status | Response | Reason | +| ------- | ------------------------------------------------------- | ------------------------------------------------------------ | +| [200][] | [JSON API document][] (`type: "authentication-tokens"`) | The request was successful | +| [404][] | [JSON API error object][] | User Token not found, or unauthorized to view the User Token | + +### Sample Request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request GET \ + https://app.terraform.io/api/v2/authentication-tokens/at-6yEmxNAhaoQLH1Da +``` + +### Sample Response + +```json +{ + "data": { + "id": "at-6yEmxNAhaoQLH1Da", + "type": "authentication-tokens", + "attributes": { + "created-at": "2018-11-25T22:31:30.624Z", + "last-used-at": "2018-11-26T20:34:59.487Z", + "description": "api", + "token": null, + "expired-at": "2023-04-06T12:00:00.000Z" + }, + "relationships": { + "created-by": { + "data": { + "id": "user-MA4GL63FmYRpSFxa", + "type": "users" + } + } + } + } +} +``` + +## Create a User Token + +`POST /users/:user_id/authentication-tokens` + +| Parameter | Description | +| ---------- | ------------------- | +| `:user_id` | The ID of the User. | + +Use the [Account API](/terraform/enterprise/api-docs/account) to find your own user ID. + +This endpoint returns the secret text of the created authentication token. A token is only shown upon creation, and cannot be recovered later. + +-> **Note:** You must access this endpoint with a [user token](/terraform/enterprise/users-teams-organizations/users#api-tokens), and it will only create new tokens for that token's user account. + +| Status | Response | Reason | +| ------- | ------------------------------------------------------- | -------------------------------------------------------------- | +| [201][] | [JSON API document][] (`type: "authentication-tokens"`) | The request was successful | +| [404][] | [JSON API error object][] | User not found or user unauthorized to perform action | +| [422][] | [JSON API error object][] | Malformed request body (missing attributes, wrong types, etc.) | +| [500][] | [JSON API error object][] | Failure during User Token creation | + +### Request Body + +This POST endpoint requires a JSON object with the following properties as a request payload. + +Properties without a default value are required. + +| Key path | Type | Default | Description | +| ----------------------------- | ------ | ------- | ----------------------------------------------------------------------------------------------------------------------------------- | +| `data.type` | string | | Must be `"authentication-tokens"`. | +| `data.attributes.description` | string | | The description for the User Token. | +| `data.attributes.expired-at` | string | `null` | The UTC date and time that the User Token will expire, in ISO 8601 format. If omitted or set to `null` the token will never expire. | + +### Sample Payload + +```json +{ + "data": { + "type": "authentication-tokens", + "attributes": { + "description":"api", + "expired-at": "2023-04-06T12:00:00.000Z" + } + } +} +``` + +### Sample Request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request POST \ + --data @payload.json \ + https://app.terraform.io/api/v2/users/user-MA4GL63FmYRpSFxa/authentication-tokens +``` + +### Sample Response + +```json +{ + "data": { + "id": "at-MKD1X3i4HS3AuD41", + "type": "authentication-tokens", + "attributes": { + "created-at": "2018-11-26T20:48:35.054Z", + "last-used-at": null, + "description": "api", + "token": "6tL24nM38M7XWQ.atlasv1.KmWckRfzeNmUVFNvpvwUEChKaLGznCSD6fPf3VPzqMMVzmSxFU0p2Ibzpo2h5eTGwPU", + "expired-at": "2023-04-06T12:00:00.000Z" + }, + "relationships": { + "created-by": { + "data": { + "id": "user-MA4GL63FmYRpSFxa", + "type": "users" + } + } + } + } +} +``` + +## Destroy a User Token + +`DELETE /authentication-tokens/:id` + +| Parameter | Description | +| --------- | ------------------------------------ | +| `:id` | The ID of the User Token to destroy. | + +-> **Note:** You must access this endpoint with a [user token](/terraform/enterprise/users-teams-organizations/users#api-tokens), and it will only delete tokens for that token's user account. + +| Status | Response | Reason | +| ------- | ------------------------- | ------------------------------------------------------------ | +| [204][] | Empty response | The User Token was successfully destroyed | +| [404][] | [JSON API error object][] | User Token not found, or user unauthorized to perform action | + +### Sample Request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request DELETE \ + https://app.terraform.io/api/v2/authentication-tokens/at-6yEmxNAhaoQLH1Da +``` diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/users.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/users.mdx new file mode 100644 index 0000000000..4c8625e8c7 --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/users.mdx @@ -0,0 +1,102 @@ +--- +page_title: /users API reference for Terraform Enterprise +description: Use the Terraform Enterprise API's `/users` endpoint to read a user's details. +source: terraform-docs-common +--- + +[200]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/200 + +[201]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/201 + +[202]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/202 + +[204]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/204 + +[400]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/400 + +[401]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/401 + +[403]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/403 + +[404]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404 + +[409]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/409 + +[412]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/412 + +[422]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/422 + +[429]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/429 + +[500]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500 + +[504]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/504 + +[JSON API document]: /terraform/enterprise/api-docs#json-api-documents + +[JSON API error object]: https://jsonapi.org/format/#error-objects + +# Users API reference + +HCP Terraform's user objects do not contain any identifying information about a user, other than their HCP Terraform username and avatar image; they are intended for displaying names and avatars in contexts that refer to a user by ID, like lists of team members or the details of a run. Most of these contexts can already include user objects via an `?include` parameter, so you shouldn't usually need to make a separate call to this endpoint. + +## Show a User + +Shows details for a given user. + +`GET /users/:user_id` + +| Parameter | Description | +| ---------- | --------------------------- | +| `:user_id` | The ID of the desired user. | + +To find the ID that corresponds to a given username, you can request a [team object](/terraform/enterprise/api-docs/teams) for a team that user belongs to, specify `?include=users` in the request, and look for the user's name in the included list of user objects. + +| Status | Response | Reason | +| ------- | --------------------------------------- | ------------------------------------------------ | +| [200][] | [JSON API document][] (`type: "users"`) | The request was successful | +| [401][] | [JSON API error object][] | Unauthorized | +| [404][] | [JSON API error object][] | User not found, or unauthorized to view the user | + +### Sample Request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request GET \ + https://app.terraform.io/api/v2/users/user-MA4GL63FmYRpSFxa +``` + +### Sample Response + +```json +{ + "data": { + "id": "user-MA4GL63FmYRpSFxa", + "type": "users", + "attributes": { + "username": "admin", + "is-service-account": false, + "auth-method": "hcp_sso", + "avatar-url": "https://www.gravatar.com/avatar/fa1f0c9364253d351bf1c7f5c534cd40?s=100&d=mm", + "v2-only": true, + "permissions": { + "can-create-organizations": false, + "can-change-email": true, + "can-change-username": true + } + }, + "relationships": { + "authentication-tokens": { + "links": { + "related": "/api/v2/users/user-MA4GL63FmYRpSFxa/authentication-tokens" + } + } + }, + "links": { + "self": "/api/v2/users/user-MA4GL63FmYRpSFxa" + } + } +} +``` diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/variable-sets.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/variable-sets.mdx new file mode 100644 index 0000000000..8bca544f1a --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/variable-sets.mdx @@ -0,0 +1,1018 @@ +--- +page_title: /varsets API reference for Terraform Enterprise +description: >- + Use the Terraform Enterprise API's `/varsets` endpoint to read, create, + update, and delete variable sets, and apply or remove variable sets from + workspaces and projects. +source: terraform-docs-common +--- + +# Variable sets API reference + +A [variable set](/terraform/enterprise/workspaces/variables#scope) is a resource that allows you to reuse the same variables across multiple workspaces and projects. For example, you could define a variable set of provider credentials and automatically apply it to a selection of workspaces, all workspaces in a project, or all workspaces in an organization. + +Projects and organizations can both own variable sets. The owner of a variable set can determine the precedence of that set. Refer to [**Manage variable sets**](/terraform/enterprise/workspaces/variables/managing-variables#permissions) for more details on variable set permissions. + +To view the variables applied from a variable set on a particular workspace, you must have [**Read** variables permission](/terraform/enterprise/users-teams-organizations/permissions#general-workspace-permissions) on that workspace. + +## Create a Variable Set + +`POST organizations/:organization_name/varsets` + +| Parameter | Description | +| -------------------- | --------------------------------------------------------- | +| `:organization_name` | The name of the organization the variable set belongs to. | + +### Request Body + +Properties without a default value are required. + +| Key path | Type | Default | Description | +| ------------------------------------- | ------- | ------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------- | +| `data.attributes.name` | string | | The name of the variable set. | +| `data.attributes.description` | string | `""` | Text displayed in the UI to contextualize the variable set and its purpose. | +| `data.attributes.global` | boolean | `false` | When true, HCP Terraform automatically applies the variable set to all current and future workspaces in the organization. | +| `data.attributes.priority` | boolean | `false` | When true, the variables in the set override any other variable values with a more specific scope, including values set on the command line. | +| `data.relationships.workspaces` | array | `[]` | Array of references to workspaces that the variable set should be assigned to. | +| `data.relationships.projects` | array | `[]` | Array of references to projects that the variable set should be assigned to. | +| `data.relationships.vars` | array | `[]` | Array of complete variable definitions that comprise the variable set. | +| `data.relationships.parent` | object | Organization that the variable set belongs to | The parent that owns this variable set. If the parent is a project, `data.attributes.global` must be `false`. | +| `data.relationships.parent.data.type` | string | `"organizations"` | The resource type of the parent that owns this variable set. Valid values are `organizations` or `projects`. | +| `data.relationships.parent.data.id` | string | Name of organization that the variable set belongs to. | The ID of the parent that owns the variable set. For organizations, use name instead of ID. | + +HCP Terraform does not allow different global variable sets to contain conflicting variables with the same name and type. You will receive a 422 response if you try to create a global variable set that contains conflicting variables. + +| Status | Response | Reason(s) | +| ------- | ------------------------- | --------------------------------------------------------------------- | +| [200][] | [JSON API document][] | Successfully added variable set | +| [404][] | [JSON API error object][] | Organization not found, or user unauthorized to perform action | +| [422][] | [JSON API error object][] | Problem with payload or request; details provided in the error object | + +### Sample Payload + +```json +{ + "data": { + "type": "varsets", + "attributes": { + "name": "MyVarset", + "description": "Full of vars and such for mass reuse", + "global": false, + "priority": false, + }, + "relationships": { + "workspaces": { + "data": [ + { + "id": "ws-z6YvbWEYoE168kpq", + "type": "workspaces" + } + ] + }, + "vars": { + "data": [ + { + "type": "vars", + "attributes": { + "key": "c2e4612d993c18e42ef30405ea7d0e9ae", + "value": "8676328808c5bf56ac5c8c0def3b7071", + "category": "terraform" + } + } + ] + }, + "parent": { + "data": { + "id": "prj-kFjgSzcZSr5c3imE", + "type": "projects" + } + } + } + } +} +``` + +### Sample Request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request POST \ + --data @payload.json \ + https://app.terraform.io/api/v2/organizations/my-organization/varsets +``` + +### Sample Response + +```json +{ + "data": { + "id": "varset-kjkN545LH2Sfercv", + "type": "varsets", + "attributes": { + "name": "MyVarset", + "description": "Full of vars and such for mass reuse", + "global": false, + "priority": false, + }, + "relationships": { + "workspaces": { + "data": [ + { + "id": "ws-z6YvbWEYoE168kpq", + "type": "workspaces" + } + ] + }, + "projects": { + "data": [ + { + "id": "prj-kFjgSzcZSr5c3imE", + "type": "projects" + } + ] + }, + "vars": { + "data": [ + { + "id": "var-Nh0doz0hzj9hrm34qq", + "type": "vars", + "attributes": { + "key": "c2e4612d993c18e42ef30405ea7d0e9ae", + "value": "8676328808c5bf56ac5c8c0def3b7071", + "category": "terraform" + } + } + ] + }, + "parent": { + "data": { + "id": "prj-kFjgSzcZSr5c3imE", + "type": "projects" + } + } + } + } +} +``` + +## Update a Variable Set + +`PUT/PATCH varsets/:varset_id` + +| Parameter | Description | +| ------------ | ------------------- | +| `:varset_id` | The variable set ID | + +HCP Terraform does not allow global variable sets to contain conflicting variables with the same name and type. You will receive a 422 response if you try to create a global variable set that contains conflicting variables. + +HCP Terraform does not allow you to change the parent organization or project of a variable set. Instead, you must delete the variable set and recreate it in the desired organization or project. + +### Request Body + +| Key path | Type | Default | Description | +| ------------------------------- | ------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | +| `data.attributes.name` | string | | The name of the variable set. | +| `data.attributes.description` | string | | Text displayed in the UI to contextualize the variable set and its purpose. | +| `data.attributes.global` | boolean | | When true, HCP Terraform automatically applies the variable set to all current and future workspaces in the organization. | +| `data.attributes.priority` | boolean | `false` | When true, the variables in the set override any other variable values set with a more specific scope, including values set on the command line. | +| `data.relationships.workspaces` | array | | **Optional** Array of references to workspaces that the variable set should be assigned to. Sending an empty array clears all workspace assignments. | +| `data.relationships.projects` | array | | **Optional** Array of references to projects that the variable set should be assigned to. Sending an empty array clears all project assignments. | +| `data.relationships.vars` | array | | **Optional** Array of complete variable definitions to add to the variable set. | + +| Status | Response | Reason(s) | +| ------- | ------------------------- | ------------------------------------------------------------------------------ | +| [200][] | [JSON API document][] | Successfully updated variable set | +| [404][] | [JSON API error object][] | Organization or variable set not found, or user unauthorized to perform action | +| [422][] | [JSON API error object][] | Problem with payload or request; details provided in the error object | + +### Sample Payload + +```json +{ + "data": { + "type": "varsets", + "attributes": { + "name": "MyVarset", + "description": "Full of vars and such for mass reuse. Now global!", + "global": true, + "priority": true, + }, + "relationships": { + "workspaces": { + "data": [ + { + "id": "ws-FRFwkYoUoGn1e34b", + "type": "workspaces" + } + ] + }, + "projects": { + "data": [ + { + "id": "prj-kFjgSzcZSr5c3imE", + "type": "projects" + } + ] + }, + "vars": { + "data": [ + { + "type": "vars", + "attributes": { + "key": "c2e4612d993c18e42ef30405ea7d0e9ae", + "value": "8676328808c5bf56ac5c8c0def3b7071", + "category": "terraform" + } + } + ] + } + } + } +} +``` + +### Sample Request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request PATCH \ + --data @payload.json \ + https://app.terraform.io/api/v2/varsets/varset-kjkN545LH2Sfercv +``` + +### Sample Response + +```json +{ + "data": { + "id": "varset-kjkN545LH2Sfercv", + "type": "varsets", + "attributes": { + "name": "MyVarset", + "description": "Full of vars and such for mass reuse. Now global!", + "global": true, + "priority": true + }, + "relationships": { + "organization": { + "data": { + "id": "hashicorp", + "type": "organizations" + } + }, + "workspaces": { + "data": [ + { + "id": "ws-FRFwkYoUoGn1e34b", + "type": "workspaces" + } + ] + }, + "projects": { + "data": [ + { + "id": "prj-kFjgSzcZSr5c3imE", + "type": "projects" + } + ] + }, + "vars": { + "data": [ + { + "id": "var-Nh0doz0hzj9hrm34qq", + "type": "vars", + "attributes": { + "key": "c2e4612d993c18e42ef30405ea7d0e9ae", + "value": "8676328808c5bf56ac5c8c0def3b7071", + "category": "terraform" + } + } + ] + }, + "parent": { + "data": { + "id": "hashicorp", + "type": "organizations" + } + } + } + } +} +``` + +## Delete a Variable Set + +`DELETE varsets/:varset_id` + +| Parameter | Description | +| ------------ | ------------------- | +| `:varset_id` | The variable set ID | + +### Sample Request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request DELETE \ + https://app.terraform.io/api/v2/varsets/varset-kjkN545LH2Sfercv +``` + +On success, this endpoint responds with no content. + +## Show Variable Set + +Fetch details about the specified variable set. + +`GET varsets/:varset_id` + +| Parameter | Description | +| ------------ | ------------------- | +| `:varset_id` | The variable set ID | + +### Sample Request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request GET \ + https://app.terraform.io/api/v2/varsets/varset-kjkN545LH2Sfercv +``` + +### Sample Response + +```json +{ + "data": { + "id": "varset-kjkN545LH2Sfercv", + "type": "varsets", + "attributes": { + "name": "MyVarset", + "description": "Full of vars and such for mass reuse", + "global": false, + "priority": false, + "updated-at": "2023-03-06T21:48:33.588Z", + "var-count": 5, + "workspace-count": 2, + "project-count": 2 + }, + "relationships": { + "organization": { + "data": { + "id": "hashicorp", + "type": "organizations" + } + }, + "vars": { + "data": [ + { + "id": "var-mMqadSCxZtrQJAv8", + "type": "vars" + }, + { + "id": "var-hFxUUKSk35QMsRVH", + "type": "vars" + }, + { + "id": "var-fkd6N48tXRmoaPxH", + "type": "vars" + }, + { + "id": "var-abcbBMBMWcZw3WiV", + "type": "vars" + }, + { + "id": "var-vqvRKK1ZoqQCiMwN", + "type": "vars" + } + ] + }, + "workspaces": { + "data": [ + { + "id": "ws-UohFdKAHUGsQ8Dtf", + "type": "workspaces" + }, + { + "id": "ws-XhGhaaCrsx9ATson", + "type": "workspaces" + } + ] + }, + "projects": { + "data": [ + { + "id": "prj-1JMwvPHFsdpsPhnt", + "type": "projects" + }, + { + "id": "prj-SLDGqbYqELXE1obp", + "type": "projects" + } + ] + }, + "parent": { + "data": { + "id": "hashicorp", + "type": "organizations" + } + } + } + } +} +``` + +## List Variable Sets + +List all variable sets for an organization. + +`GET organizations/:organization_name/varsets` + +| Parameter | Description | +| -------------------- | -------------------------------------------------------- | +| `:organization_name` | The name of the organization the variable sets belong to | + +List all variable sets for a project. This includes global variable sets from the project's organization. + +`GET projects/:project_id/varsets` + +| Parameter | Description | +| ------------- | -------------- | +| `:project_id` | The project ID | + +List all variable sets for a workspace. This includes global variable sets from the workspace's organization and variable sets +attached to the project this workspace is contained within. + +`GET workspaces/:workspace_id/varsets` + +| Parameter | Description | +| --------------- | ---------------- | +| `:workspace_id` | The workspace ID | + +### Query Parameters + +All list endpoints support pagination [with standard URL query parameters](/terraform/enterprise/api-docs#query-parameters) and searching with the `q` parameter. Remember to percent-encode `[` as `%5B` and `]` as `%5D` if your tooling doesn't automatically encode URLs. + +| Parameter | Description | +| -------------- | -------------------------------------------------------------------------------------- | +| `page[number]` | **Optional.** If omitted, the endpoint returns the first page. | +| `page[size]` | **Optional.** If omitted, the endpoint returns 20 varsets per page. | +| `q` | **Optional.** A search query string. You can search for a variable set using its name. | + +### Sample Response + +```json +{ + "data": [ + { + "id": "varset-kjkN545LH2Sfercv", + "type": "varsets", + "attributes": { + "name": "MyVarset", + "description": "Full of vars and such for mass reuse", + "global": false, + "priority": false, + "updated-at": "2023-03-06T21:48:33.588Z", + "var-count": 5, + "workspace-count": 2, + "project-count": 2 + }, + "relationships": { + "organization": { + "data": { + "id": "hashicorp", + "type": "organizations" + } + }, + "vars": { + "data": [ + { + "id": "var-mMqadSCxZtrQJAv8", + "type": "vars" + }, + { + "id": "var-hFxUUKSk35QMsRVH", + "type": "vars" + }, + { + "id": "var-fkd6N48tXRmoaPxH", + "type": "vars" + }, + { + "id": "var-abcbBMBMWcZw3WiV", + "type": "vars" + }, + { + "id": "var-vqvRKK1ZoqQCiMwN", + "type": "vars" + } + ] + }, + "workspaces": { + "data": [ + { + "id": "ws-UohFdKAHUGsQ8Dtf", + "type": "workspaces" + }, + { + "id": "ws-XhGhaaCrsx9ATson", + "type": "workspaces" + } + ] + }, + "projects": { + "data": [ + { + "id": "prj-1JMwvPHFsdpsPhnt", + "type": "projects" + }, + { + "id": "prj-SLDGqbYqELXE1obp", + "type": "projects" + } + ] + }, + "parent": { + "data": { + "id": "hashicorp", + "type": "organizations" + } + } + } + } + ], + "links": { + "self": "https://app.terraform.io/api/v2/organizations/hashicorp/varsets?page%5Bnumber%5D=1&page%5Bsize%5D=20", + "first": "https://app.terraform.io/api/v2/organizations/hashicorp/varsets?page%5Bnumber%5D=1&page%5Bsize%5D=20", + "prev": null, + "next": null, + "last": "https://app.terraform.io/api/v2/organizations/hashicorp/varsets?page%5Bnumber%5D=1&page%5Bsize%5D=20" + }, + "meta": { + "pagination": { + "current-page": 1, + "page-size": 20, + "prev-page": null, + "next-page": null, + "total-pages": 1, + "total-count": 1 + } + } +} +``` + +### Variable Relationships + +## Add Variable + +`POST varsets/:varset_external_id/relationships/vars` + +| Parameter | Description | +| ------------ | ------------------- | +| `:varset_id` | The variable set ID | + +### Request Body + +This POST endpoint requires a JSON object with the following properties as a request payload. + +Properties without a default value are required. + +| Key path | Type | Default | Description | +| ----------------------------- | ------ | ------- | --------------------------------------------------------------------------------------------------------------- | +| `data.type` | string | | Must be `"vars"`. | +| `data.attributes.key` | string | | The name of the variable. | +| `data.attributes.value` | string | `""` | The value of the variable. | +| `data.attributes.description` | string | | The description of the variable. | +| `data.attributes.category` | string | | Whether this is a Terraform or environment variable. Valid values are `"terraform"` or `"env"`. | +| `data.attributes.hcl` | bool | `false` | Whether to evaluate the value of the variable as a string of HCL code. Has no effect for environment variables. | +| `data.attributes.sensitive` | bool | `false` | Whether the value is sensitive. If true, variable is not visible in the UI. | + +HCP Terraform does not allow different global variable sets to contain conflicting variables with the same name and type. You will receive a 422 response if you try to add a conflicting variable to a global variable set. + +| Status | Response | Reason(s) | +| ------- | ------------------------- | --------------------------------------------------------------------- | +| [200][] | [JSON API document][] | Successfully added variable to variable set | +| [404][] | [JSON API error object][] | Variable set not found, or user unauthorized to perform action | +| [422][] | [JSON API error object][] | Problem with payload or request; details provided in the error object | + +### Sample Payload + +```json +{ + "data": { + "type": "vars", + "attributes": { + "key": "g6e45ae7564a17e81ef62fd1c7fa86138", + "value": "61e400d5ccffb3782f215344481e6c82", + "description": "cheeeese", + "sensitive": false, + "category": "terraform", + "hcl": false + } + } +} +``` + +### Sample Request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request POST \ + --data @payload.json \ + https://app.terraform.io/api/v2/varsets/varset-4q8f7H0NHG733bBH/relationships/vars +``` + +### Sample Response + +```json +{ + "data": { + "id":"var-EavQ1LztoRTQHSNT", + "type": "vars", + "attributes": { + "key": "g6e45ae7564a17e81ef62fd1c7fa86138", + "value": "61e400d5ccffb3782f215344481e6c82", + "description": "cheeeese", + "sensitive": false, + "category": "terraform", + "hcl": false + } + } +} +``` + +## Update a Variable in a Variable Set + +`PATCH varsets/:varset_id/relationships/vars/:var_id` + +| Parameter | Description | +| ------------ | -------------------------------- | +| `:varset_id` | The variable set ID | +| `:var_id` | The ID of the variable to delete | + +HCP Terraform does not allow different global variable sets to contain conflicting variables with the same name and type. You will receive a 422 response if you try to add a conflicting variable to a global variable set. + +| Status | Response | Reason(s) | +| ------- | ------------------------- | --------------------------------------------------------------------- | +| [200][] | [JSON API document][] | Successfully updated variable for variable set | +| [404][] | [JSON API error object][] | Variable set not found, or user unauthorized to perform action | +| [422][] | [JSON API error object][] | Problem with payload or request; details provided in the error object | + +### Sample Payload + +```json +{ + "data": { + "type": "vars", + "attributes": { + "key": "g6e45ae7564a17e81ef62fd1c7fa86138", + "value": "61e400d5ccffb3782f215344481e6c82", + "description": "new cheeeese", + "sensitive": false, + "category": "terraform", + "hcl": false + } + } +} +``` + +### Sample Request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request PATCH \ + --data @payload.json \ + https://app.terraform.io/api/v2/varsets/varset-4q8f7H0NHG733bBH/relationships/vars/var-EavQ1LztoRTQHSNT +``` + +### Sample Response + +```json +{ + "data": { + "id":"var-EavQ1LztoRTQHSNT", + "type": "vars", + "attributes": { + "key": "g6e45ae7564a17e81ef62fd1c7fa86138", + "value": "61e400d5ccffb3782f215344481e6c82", + "description": "new cheeeese", + "sensitive": false, + "category": "terraform", + "hcl": false + } + } +} +``` + +## Delete a Variable in a Variable Set + +`DELETE varsets/:varset_id/relationships/vars/:var_id` + +| Parameter | Description | +| ------------ | -------------------------------- | +| `:varset_id` | The variable set ID | +| `:var_id` | The ID of the variable to delete | + +### Sample Request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request DELETE \ + https://app.terraform.io/api/v2/varsets/varset-4q8f7H0NHG733bBH/relationships/vars/var-EavQ1LztoRTQHSNT +``` + +On success, this endpoint responds with no content. + +## List Variables in a Variable Set + +`GET varsets/:varset_id/relationships/vars` + +| Parameter | Description | +| ------------ | ------------------- | +| `:varset_id` | The variable set ID | + +### Sample Response + +```json +{ + "data": [ + { + "id": "var-134r1k34nj5kjn", + "type": "vars", + "attributes": { + "key": "F115037558b045dd82da40b089e5db745", + "value": "1754288480dfd3060e2c37890422905f", + "sensitive": false, + "category": "terraform", + "hcl": false, + "created-at": "2021-10-29T18:54:29.379Z", + "description": "" + }, + "relationships": { + "varset": { + "data": { + "id": "varset-992UMULdeDuebi1x", + "type": "varsets" + }, + "links": { "related": "/api/v2/varsets/1" } + } + }, + "links": { "self": "/api/v2/vars/var-BEPU9NjPVCiCfrXj" } + } + ], + "links": { + "self": "app.terraform.io/app/varsets/varset-992UMULdeDuebi1x/vars", + "first": "app.terraform.io/app/varsets/varset-992UMULdeDuebi1x/vars?page=1", + "prev": null, + "next": null, + "last": "app.terraform.io/app/varsets/varset-992UMULdeDuebi1x/vars?page=1" + } +} +``` + +## Apply Variable Set to Workspaces + +Accepts a list of workspaces to add the variable set to. + +`POST varsets/:varset_id/relationships/workspaces` + +| Parameter | Description | +| ------------ | ------------------- | +| `:varset_id` | The variable set ID | + +Properties without a default value are required. + +| Key path | Type | Default | Description | +| ------------- | ------ | ------- | -------------------------------------------------- | +| `data[].type` | string | | Must be `"workspaces"` | +| `data[].id` | string | | The id of the workspace to add the variable set to | + +| Status | Response | Reason(s) | +| ------- | ------------------------- | --------------------------------------------------------------------- | +| [204][] | | Successfully added variable set to the requested workspaces | +| [404][] | [JSON API error object][] | Variable set not found or user unauthorized to perform action | +| [422][] | [JSON API error object][] | Problem with payload or request; details provided in the error object | + +### Sample Payload + +```json +{ + "data": [ + { + "type": "workspaces", + "id": "ws-YwfuBJZkdai4xj9w" + } + ] +} +``` + +### Sample Request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request POST \ + --data @payload.json \ + https://app.terraform.io/api/v2/varsets/varset-kjkN545LH2Sfercv/relationships/workspaces +``` + +## Remove a Variable Set from Workspaces + +Accepts a list of workspaces to remove the variable set from. + +`DELETE varsets/:varset_id/relationships/workspaces` + +| Parameter | Description | +| ------------ | ------------------- | +| `:varset_id` | The variable set ID | + +Properties without a default value are required. + +| Key path | Type | Default | Description | +| ------------- | ------ | ------- | ------------------------------------------------------- | +| `data[].type` | string | | Must be `"workspaces"` | +| `data[].id` | string | | The id of the workspace to delete the variable set from | + +| Status | Response | Reason(s) | +| ------- | ------------------------- | --------------------------------------------------------------------- | +| [204][] | | Successfully removed variable set from the requested workspaces | +| [404][] | [JSON API error object][] | Variable set not found or user unauthorized to perform action | +| [422][] | [JSON API error object][] | Problem with payload or request; details provided in the error object | + +### Sample Payload + +```json +{ + "data": [ + { + "type": "workspaces", + "id": "ws-YwfuBJZkdai4xj9w" + }, + { + "type": "workspaces", + "id": "ws-YwfuBJZkdai4xj9w" + } + ] +} +``` + +### Sample Request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request DELETE \ + --data @payload.json \ + https://app.terraform.io/api/v2/varsets/varset-kjkN545LH2Sfercv/relationships/workspaces +``` + +## Apply Variable Set to Projects + +Accepts a list of projects to add the variable set to. When you apply a variable set to a project, all the workspaces in that project will have the variable set applied to them. + +`POST varsets/:varset_id/relationships/projects` + +| Parameter | Description | +| ------------ | ------------------- | +| `:varset_id` | The variable set ID | + +Properties without a default value are required. + +| Key path | Type | Default | Description | +| ------------- | ------ | ------- | ------------------------------------------------ | +| `data[].type` | string | | Must be `"projects"` | +| `data[].id` | string | | The id of the project to add the variable set to | + +| Status | Response | Reason(s) | +| ------- | ------------------------- | --------------------------------------------------------------------- | +| [204][] | | Successfully added variable set to the requested projects | +| [404][] | [JSON API error object][] | Variable set not found or user unauthorized to perform action | +| [422][] | [JSON API error object][] | Problem with payload or request; details provided in the error object | + +### Sample Payload + +```json +{ + "data": [ + { + "type": "projects", + "id": "prj-YwfuBJZkdai4xj9w" + } + ] +} +``` + +### Sample Request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request POST \ + --data @payload.json \ + https://app.terraform.io/api/v2/varsets/varset-kjkN545LH2Sfercv/relationships/projects +``` + +## Remove a Variable Set from Projects + +Accepts a list of projects to remove the variable set from. + +`DELETE varsets/:varset_id/relationships/projects` + +| Parameter | Description | +| ------------ | ------------------- | +| `:varset_id` | The variable set ID | + +Properties without a default value are required. + +| Key path | Type | Default | Description | +| ------------- | ------ | ------- | ----------------------------------------------------- | +| `data[].type` | string | | Must be `"projects"` | +| `data[].id` | string | | The id of the project to delete the variable set from | + +| Status | Response | Reason(s) | +| ------- | ------------------------- | --------------------------------------------------------------------- | +| [204][] | | Successfully removed variable set from the requested projects | +| [404][] | [JSON API error object][] | Variable set not found or user unauthorized to perform action | +| [422][] | [JSON API error object][] | Problem with payload or request; details provided in the error object | + +### Sample Payload + +```json +{ + "data": [ + { + "type": "projects", + "id": "prj-YwfuBJZkdai4xj9w" + }, + { + "type": "projects", + "id": "prj-lkjasdfiojwerlkj" + } + ] +} +``` + +### Sample Request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request DELETE \ + --data @payload.json \ + https://app.terraform.io/api/v2/varsets/varset-kjkN545LH2Sfercv/relationships/projects +``` + +[200]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/200 + +[201]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/201 + +[202]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/202 + +[204]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/204 + +[400]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/400 + +[401]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/401 + +[403]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/403 + +[404]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404 + +[409]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/409 + +[412]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/412 + +[422]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/422 + +[429]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/429 + +[500]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500 + +[504]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/504 + +[json api document]: /terraform/enterprise/api-docs#json-api-documents + +[json api error object]: https://jsonapi.org/format/#error-objects + +## Available Related Resources + +The GET endpoints above can optionally return related resources, if requested with [the `include` query parameter](/terraform/enterprise/api-docs#inclusion-of-related-resources). The following resource types are available: + +| Resource Name | Description | +| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `vars` | Show each variable in a variable set and all of their attributes including `id`, `key`, `value`, `sensitive`, `category`, `hcl`, `created_at`, and `description`. | diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/variables.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/variables.mdx new file mode 100644 index 0000000000..987dca3556 --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/variables.mdx @@ -0,0 +1,307 @@ +--- +page_title: /vars API reference for Terraform Enterprise +description: >- + Use the Terraform Enterprise API's `/vars` endpoint to manage + organization-level variables. Learn how to read, create, update, and delete + variables using the API. +source: terraform-docs-common +--- + +[200]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/200 + +[201]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/201 + +[202]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/202 + +[204]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/204 + +[400]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/400 + +[401]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/401 + +[403]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/403 + +[404]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404 + +[409]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/409 + +[412]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/412 + +[422]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/422 + +[429]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/429 + +[500]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500 + +[504]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/504 + +[JSON API document]: /terraform/enterprise/api-docs#json-api-documents + +[JSON API error object]: https://jsonapi.org/format/#error-objects + +# Variables API reference + +~> **Important**: The Variables API is **deprecated** and will be removed in a future release. All existing integrations with this API should transition to the [Workspace Variables API](/terraform/enterprise/api-docs/workspace-variables). + +This set of APIs covers create, update, list and delete operations on variables. + +## Create a Variable + +`POST /vars` + +### Request Body + +This POST endpoint requires a JSON object with the following properties as a request payload. + +Properties without a default value are required. + +| Key path | Type | Default | Description | +| ---------------------------------------- | ------ | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `data.type` | string | | Must be `"vars"`. | +| `data.attributes.key` | string | | The name of the variable. | +| `data.attributes.value` | string | `""` | The value of the variable. | +| `data.attributes.description` | string | | The description of the variable. | +| `data.attributes.category` | string | | Whether this is a Terraform or environment variable. Valid values are `"terraform"` or `"env"`. | +| `data.attributes.hcl` | bool | `false` | Whether to evaluate the value of the variable as a string of HCL code. Has no effect for environment variables. | +| `data.attributes.sensitive` | bool | `false` | Whether the value is sensitive. If true then the variable is written once and not visible thereafter. | +| `data.relationships.workspace.data.type` | string | | Must be `"workspaces"`. | +| `data.relationships.workspace.data.id` | string | | The ID of the workspace that owns the variable. Obtain workspace IDs from the [workspace settings](/terraform/enterprise/workspaces/settings) or the [Show Workspace](/terraform/enterprise/api-docs/workspaces#show-workspace) endpoint. | + +**Deprecation warning**: The custom `filter` properties are replaced by JSON API `relationships` and will be removed from future versions of the API! + +| Key path | Type | Default | Description | +| -------------------------- | ------ | ------- | ----------------------------------------------------- | +| `filter.workspace.name` | string | | The name of the workspace that owns the variable. | +| `filter.organization.name` | string | | The name of the organization that owns the workspace. | + +### Sample Payload + +```json +{ + "data": { + "type":"vars", + "attributes": { + "key":"some_key", + "value":"some_value", + "description":"some description", + "category":"terraform", + "hcl":false, + "sensitive":false + }, + "relationships": { + "workspace": { + "data": { + "id":"ws-4j8p6jX1w33MiDC7", + "type":"workspaces" + } + } + } + } +} +``` + +### Sample Request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request POST \ + --data @payload.json \ + https://app.terraform.io/api/v2/vars +``` + +### Sample Response + +```json +{ + "data": { + "id":"var-EavQ1LztoRTQHSNT", + "type":"vars", + "attributes": { + "key":"some_key", + "value":"some_value", + "description":"some description", + "sensitive":false, + "category":"terraform", + "hcl":false + }, + "relationships": { + "configurable": { + "data": { + "id":"ws-4j8p6jX1w33MiDC7", + "type":"workspaces" + }, + "links": { + "related":"/api/v2/organizations/my-organization/workspaces/my-workspace" + } + } + }, + "links": { + "self":"/api/v2/vars/var-EavQ1LztoRTQHSNT" + } + } +} +``` + +## List Variables + +`GET /vars` + +### Query Parameters + +[These are standard URL query parameters](/terraform/enterprise/api-docs#query-parameters). Remember to percent-encode `[` as `%5B` and `]` as `%5D` if your tooling doesn't automatically encode URLs. + +| Parameter | Description | +| ---------------------------- | -------------------------------------------------------------------------- | +| `filter[workspace][name]` | **Required** The name of one workspace to list variables for. | +| `filter[organization][name]` | **Required** The name of the organization that owns the desired workspace. | + +These two parameters are optional but linked; if you include one, you must include both. Without a filter, this method lists variables for all workspaces where you have permission to read variables. ([More about permissions.](/terraform/enterprise/users-teams-organizations/permissions)) + +[permissions-citation]: #intentionally-unused---keep-for-maintainers + +### Sample Request + +```shell +$ curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ +"https://app.terraform.io/api/v2/vars?filter%5Borganization%5D%5Bname%5D=my-organization&filter%5Bworkspace%5D%5Bname%5D=my-workspace" +# ?filter[organization][name]=my-organization&filter[workspace][name]=demo01 +``` + +### Sample Response + +```json +{ + "data": [ + { + "id":"var-AD4pibb9nxo1468E", + "type":"vars","attributes": { + "key":"name", + "value":"hello", + "description":"some description", + "sensitive":false, + "category":"terraform", + "hcl":false + }, + "relationships": { + "configurable": { + "data": { + "id":"ws-cZE9LERN3rGPRAmH", + "type":"workspaces" + }, + "links": { + "related":"/api/v2/organizations/my-organization/workspaces/my-workspace" + } + } + }, + "links": { + "self":"/api/v2/vars/var-AD4pibb9nxo1468E" + } + } + ] +} +``` + +## Update Variables + +`PATCH /vars/:variable_id` + +| Parameter | Description | +| -------------- | ------------------------------------- | +| `:variable_id` | The ID of the variable to be updated. | + +### Request Body + +This POST endpoint requires a JSON object with the following properties as a request payload. + +Properties without a default value are required. + +| Key path | Type | Default | Description | +| ----------------- | ------ | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `data.type` | string | | Must be `"vars"`. | +| `data.id` | string | | The ID of the variable to update. | +| `data.attributes` | object | | New attributes for the variable. This object can include `key`, `value`, `description`, `category`, `hcl`, and `sensitive` properties, which are described above under [create a variable](#create-a-variable). All of these properties are optional; if omitted, a property will be left unchanged. | + +### Sample Payload + +```json +{ + "data": { + "id":"var-yRmifb4PJj7cLkMG", + "attributes": { + "key":"name", + "value":"mars", + "description": "new description", + "category":"terraform", + "hcl": false, + "sensitive": false + }, + "type":"vars" + } +} +``` + +### Sample Request + +```bash +$ curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request PATCH \ + --data @payload.json \ + https://app.terraform.io/api/v2/vars/var-yRmifb4PJj7cLkMG +``` + +### Sample Response + +```json +{ + "data": { + "id":"var-yRmifb4PJj7cLkMG", + "type":"vars", + "attributes": { + "key":"name", + "value":"mars", + "description":"new description", + "sensitive":false, + "category":"terraform", + "hcl":false + }, + "relationships": { + "configurable": { + "data": { + "id":"ws-4j8p6jX1w33MiDC7", + "type":"workspaces" + }, + "links": { + "related":"/api/v2/organizations/workspace-v2-06/workspaces/workspace-v2-06" + } + } + }, + "links": { + "self":"/api/v2/vars/var-yRmifb4PJj7cLkMG" + } + } +} +``` + +## Delete Variables + +`DELETE /vars/:variable_id` + +| Parameter | Description | +| -------------- | ------------------------------------- | +| `:variable_id` | The ID of the variable to be deleted. | + +### Sample Request + +```bash +$ curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request DELETE \ + https://app.terraform.io/api/v2/vars/var-yRmifb4PJj7cLkMG +``` diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/vcs-events.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/vcs-events.mdx new file mode 100644 index 0000000000..7c97d1181d --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/vcs-events.mdx @@ -0,0 +1,132 @@ +--- +page_title: /vcs-events API reference for Terraform Enterprise +description: >- + Use the Terraform Enterprise API's `/vcs-events` endpoint to list VCS-related + events within your organization. +source: terraform-docs-common +--- + +# VCS events API reference + +-> **Note**: The VCS Events API is still in beta as support is being added for additional VCS providers. Currently only GitLab.com connections established after December 2020 are supported. + +VCS (version control system) events describe changes within your organization for VCS-related actions. Events are only stored for 10 days. If information about the [OAuth Client](/terraform/enterprise/api-docs/oauth-clients) or [OAuth Token](/terraform/enterprise/api-docs/oauth-tokens) are available at the time of the event, it will be logged with the event. + +## List VCS events + +This endpoint lists VCS events for an organization + +`GET /organizations/:organization_name/vcs-events` + +| Parameter | Description | +| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `:organization_name` | The name of the organization to list VCS events from. The organization must already exist in the system and the user must have permissions to manage VCS settings. | + +-> **Note:** Viewing VCS events is restricted to the owners team, teams with the "Manage VCS Settings", and the [organization API token](/terraform/enterprise/users-teams-organizations/api-tokens#organization-api-tokens). ([More about permissions](/terraform/enterprise/users-teams-organizations/permissions).) + +### Query Parameters + +This endpoint supports pagination [with standard URL query parameters](/terraform/enterprise/api-docs#query-parameters). Remember to percent-encode `[` as `%5B` and `]` as `%5D` if your tooling doesn't automatically encode URLs. + +| Parameter | Description | +| ----------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `page[number]` | **Optional.** If omitted, the endpoint will return the first page. | +| `page[size]` | **Optional.** If omitted, the endpoint will return 20 workspaces per page. | +| `filter[from]` | **Optional.** Must be RFC3339 formatted and in UTC. If omitted, the endpoint will default to 10 days ago. | +| `filter[to]` | **Optional.** Must be RFC3339 formatted and in UTC. If omitted, the endpoint will default to now. | +| `filter[oauth_client_external_ids]` | **Optional.** Format as a comma-separated string. If omitted, the endpoint will return all events. | +| `filter[levels]` | **Optional.** `info` and `error` are the only accepted values. If omitted, the endpoint will return both info and error events. | +| `include` | **Optional.** Allows including related resource data. This endpoint only supports `oauth_client` as a value. Only the `name`, `service-provider`, and `id` will be returned on the OAuth Client object in the `included` block. | + +### Sample Request + +```shell +$ curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + https://app.terraform.io/api/v2/organizations/my-organization/vcs-events?filter%5Bfrom%5D=2021-02-02T14%3A09%3A00Z&filter%5Bto%5D=2021-02-12T14%3A09%3A59Z&filter%5Boauth_client_external_ids%5D=oc-hhTM7WNUUgbXJpkW&filter%5Blevels%5D=info&include=oauth_client +``` + +### Sample Response + +```json +{ + "data": [ + { + "id": "ve-DJpbEwZc98ZedHZG", + "type": "vcs-events", + "attributes": { + "created-at": "2021-02-09 20:07:49.686182 +0000 UTC", + "level": "info", + "message": "Loaded 11 repositories", + "organization-id": "org-SBVreZxVessAmCZG" + }, + "relationships": { + "oauth-client": { + "data": { + "id": "oc-LePsVhHXhCM6jWf3", + "type": "oauth-clients" + }, + "links": { + "related": "/api/v2/oauth-clients/oc-LePsVhHXhCM6jWf3" + } + }, + "oauth-token": { + "data": { + "id": "ot-Ma2cs8tzjv3LYZHw", + "type": "oauth-tokens" + }, + "links": { + "related": "/api/v2/oauth-tokens/ot-Ma2cs8tzjv3LYZHw" + } + } + } + } + ], + "included": [ + { + "id": "oc-LePsVhHXhCM6jWf3", + "type": "oauth-clients", + "attributes": { + "name": "working", + "service-provider": "gitlab_hosted" + }, + "relationships": { + "organization": { + "data": { + "id": "my-organization", + "type": "organizations" + }, + "links": { + "related": "/api/v2/organizations/my-organization" + } + }, + "oauth-tokens": { + "data": [ + { + "id": "ot-Ma2cs8tzjv3LYZHw", + "type": "oauth-tokens" + } + ] + } + } + } + ], + "links": { + "self": "https://app.terraform.io/api/v2/organizations/my-organization/vcs-events?filter%5Bfrom%5D=2021-02-02T14%3A09%3A00Z\u0026filter%5Blevels%5D=info\u0026filter%5Boauth_client_external_ids%5D=oc-LePsVhHXhCM6jWf3\u0026filter%5Bto%5D=2021-02-12T14%3A09%3A59Z\u0026include=oauth_client\u0026organization_name=my-organization\u0026page%5Bnumber%5D=1\u0026page%5Bsize%5D=20", + "first": "https://app.terraform.io/api/v2/organizations/my-organization/vcs-events?filter%5Bfrom%5D=2021-02-02T14%3A09%3A00Z\u0026filter%5Blevels%5D=info\u0026filter%5Boauth_client_external_ids%5D=oc-LePsVhHXhCM6jWf3\u0026filter%5Bto%5D=2021-02-12T14%3A09%3A59Z\u0026include=oauth_client\u0026organization_name=my-organization\u0026page%5Bnumber%5D=1\u0026page%5Bsize%5D=20", + "prev": null, + "next": null, + "last": "https://app.terraform.io/api/v2/organizations/my-organization/vcs-events?filter%5Bfrom%5D=2021-02-02T14%3A09%3A00Z\u0026filter%5Blevels%5D=info\u0026filter%5Boauth_client_external_ids%5D=oc-LePsVhHXhCM6jWf3\u0026filter%5Bto%5D=2021-02-12T14%3A09%3A59Z\u0026include=oauth_client\u0026organization_name=my-organization\u0026page%5Bnumber%5D=1\u0026page%5Bsize%5D=20" + }, + "meta": { + "pagination": { + "current-page": 1, + "prev-page": null, + "next-page": null, + "total-pages": 1, + "total-count": 8 + } + } +} +``` diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/workspace-resources.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/workspace-resources.mdx new file mode 100644 index 0000000000..9f53485fc2 --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/workspace-resources.mdx @@ -0,0 +1,126 @@ +--- +page_title: /workspaces/resources API reference for Terraform Enterprise +description: >- + Use the Terraform Enterprise API's `/workspaces/resources` endpoint to list + all of a workspace's resources. +source: terraform-docs-common +--- + +[200]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/200 + +[201]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/201 + +[202]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/202 + +[204]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/204 + +[400]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/400 + +[401]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/401 + +[403]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/403 + +[404]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404 + +[409]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/409 + +[412]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/412 + +[422]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/422 + +[429]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/429 + +[500]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500 + +[504]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/504 + +[JSON API document]: /terraform/enterprise/api-docs#json-api-documents + +[JSON API error object]: https://jsonapi.org/format/#error-objects + +# Workspace resources API reference + +## List Workspace Resources + +`GET /workspaces/:workspace_id/resources` + +| Parameter | Description | +| --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `:workspace_id` | The ID of the workspace to retrieve resources from. Obtain this from the [workspace settings](/terraform/enterprise/workspaces/settings) or the [show workspace](/terraform/enterprise/api-docs/workspaces#show-workspace) endpoint. | + +| Status | Response | Reason | +| ------- | ------------------------------------------- | ----------------------------------------------------------- | +| [200][] | [JSON API document][] (`type: "resources"`) | Request was successful. | +| [404][] | [JSON API error object][] | Workspace not found or user unauthorized to perform action. | + +### Query Parameters + +This endpoint supports pagination [with standard URL query parameters](/terraform/enterprise/api-docs#query-parameters). Remember to percent-encode `[` as `%5B` and `]` as `%5D` if your tooling doesn't automatically encode URLs. + +| Parameter | Description | +| -------------- | ----------------------------------------------------------------------------------- | +| `page[number]` | **Optional.** If omitted, the endpoint will return the first page. | +| `page[size]` | **Optional.** If omitted, the endpoint will return 20 workspace resources per page. | + +### Permissions + +To list resources the user must have permission to read resources for the specified workspace. ([More about permissions.](/terraform/enterprise/users-teams-organizations/permissions)) + +[permissions-citation]: #intentionally-unused---keep-for-maintainers + +### Sample Request + +```shell +curl \ + --request GET \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + https://app.terraform.io/api/v2/workspaces/ws-DiTzUDRpjrArAfSS/resources +``` + +### Sample Response + +```json +{ + "data": [ + { + "id": "wsr-KNYb3Jj3JTBgoBFs", + "type": "resources", + "attributes": { + "address": "random_pet.animal", + "name": "animal", + "created-at": "2021-10-27", + "updated-at": "2021-10-27", + "module": "root", + "provider": "hashicorp/random", + "provider-type": "random_pet", + "modified-by-state-version-id": "sv-y4pjfGHkGUBAa9AX", + "name-index": null + } + }, + { + "id": "wsr-kYsf5A3hQ1y9zFWq", + "type": "resources", + "attributes": { + "address": "random_pet.animal2", + "name": "animal2", + "created-at": "2021-10-27", + "updated-at": "2021-10-27", + "module": "root", + "provider": "hashicorp/random", + "provider-type": "random_pet", + "modified-by-state-version-id": "sv-y4pjfGHkGUBAa9AX", + "name-index": null + } + } + ], + "links": { + "self": "https://app.terraform.io/api/v2/workspaces/ws-DiTzUDRpjrArAfSS/resources?page%5Bnumber%5D=1&page%5Bsize%5D=20", + "first": "https://app.terraform.io/api/v2/workspaces/ws-DiTzUDRpjrArAfSS/resources?page%5Bnumber%5D=1&page%5Bsize%5D=20", + "prev": null, + "next": null, + "last": "https://app.terraform.io/api/v2/workspaces/ws-DiTzUDRpjrArAfSS/resources?page%5Bnumber%5D=1&page%5Bsize%5D=20" + }, + ... +} +``` diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/workspace-variables.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/workspace-variables.mdx new file mode 100644 index 0000000000..d3a92c657e --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/workspace-variables.mdx @@ -0,0 +1,298 @@ +--- +page_title: /workspaces/vars API reference for Terraform Enterprise +description: >- + Use the Terraform Enterprise API's workspace `/workspaces/vars` endpoint to + manage workspace-specific variables. Read, create, update, and delete + workspace variables. +source: terraform-docs-common +--- + +[200]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/200 + +[201]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/201 + +[202]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/202 + +[204]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/204 + +[400]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/400 + +[401]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/401 + +[403]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/403 + +[404]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404 + +[409]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/409 + +[412]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/412 + +[422]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/422 + +[429]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/429 + +[500]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500 + +[504]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/504 + +[JSON API document]: /terraform/enterprise/api-docs#json-api-documents + +[JSON API error object]: https://jsonapi.org/format/#error-objects + +# Workspace variables API reference + +This set of APIs covers create, update, list and delete operations on workspace variables. + +Viewing variables requires permission to read variables for their workspace. Creating, updating, and deleting variables requires permission to read and write variables for their workspace. ([More about permissions.](/terraform/enterprise/users-teams-organizations/permissions)) + +[permissions-citation]: #intentionally-unused---keep-for-maintainers + +## Create a Variable + +`POST /workspaces/:workspace_id/vars` + +| Parameter | Description | +| --------------- | -------------------------------------------------- | +| `:workspace_id` | The ID of the workspace to create the variable in. | + +### Request Body + +This POST endpoint requires a JSON object with the following properties as a request payload. + +Properties without a default value are required. + +| Key path | Type | Default | Description | +| ----------------------------- | ------ | ------- | --------------------------------------------------------------------------------------------------------------- | +| `data.type` | string | | Must be `"vars"`. | +| `data.attributes.key` | string | | The name of the variable. | +| `data.attributes.value` | string | `""` | The value of the variable. | +| `data.attributes.description` | string | | The description of the variable. | +| `data.attributes.category` | string | | Whether this is a Terraform or environment variable. Valid values are `"terraform"` or `"env"`. | +| `data.attributes.hcl` | bool | `false` | Whether to evaluate the value of the variable as a string of HCL code. Has no effect for environment variables. | +| `data.attributes.sensitive` | bool | `false` | Whether the value is sensitive. If true then the variable is written once and not visible thereafter. | + +**Deprecation warning**: The custom `filter` properties are replaced by JSON API `relationships` and will be removed from future versions of the API! + +| Key path | Type | Default | Description | +| -------------------------- | ------ | ------- | ----------------------------------------------------- | +| `filter.workspace.name` | string | | The name of the workspace that owns the variable. | +| `filter.organization.name` | string | | The name of the organization that owns the workspace. | + +### Sample Payload + +```json +{ + "data": { + "type":"vars", + "attributes": { + "key":"some_key", + "value":"some_value", + "description":"some description", + "category":"terraform", + "hcl":false, + "sensitive":false + } + } +} +``` + +### Sample Request + +```shell +curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request POST \ + --data @payload.json \ + https://app.terraform.io/api/v2/workspaces/ws-4j8p6jX1w33MiDC7/vars +``` + +### Sample Response + +```json +{ + "data": { + "id":"var-EavQ1LztoRTQHSNT", + "type":"vars", + "attributes": { + "key":"some_key", + "value":"some_value", + "description":"some description", + "sensitive":false, + "category":"terraform", + "hcl":false, + "version-id":"1aa07d63ea8ff4df941c94ca9ddfd5d2bd04" + }, + "relationships": { + "configurable": { + "data": { + "id":"ws-4j8p6jX1w33MiDC7", + "type":"workspaces" + }, + "links": { + "related":"/api/v2/organizations/my-organization/workspaces/my-workspace" + } + } + }, + "links": { + "self":"/api/v2/workspaces/ws-4j8p6jX1w33MiDC7/vars/var-EavQ1LztoRTQHSNT" + } + } +} +``` + +## List Variables + +`GET /workspaces/:workspace_id/vars` + +| Parameter | Description | +| --------------- | ---------------------------------------------- | +| `:workspace_id` | The ID of the workspace to list variables for. | + +### Sample Request + +```shell +$ curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ +"https://app.terraform.io/api/v2/workspaces/ws-cZE9LERN3rGPRAmH/vars" +``` + +### Sample Response + +```json +{ + "data": [ + { + "id":"var-AD4pibb9nxo1468E", + "type":"vars","attributes": { + "key":"name", + "value":"hello", + "description":"some description", + "sensitive":false, + "category":"terraform", + "hcl":false, + "version-id":"1aa07d63ea8ff4df941c94ca9ddfd5d2bd04" + }, + "relationships": { + "configurable": { + "data": { + "id":"ws-cZE9LERN3rGPRAmH", + "type":"workspaces" + }, + "links": { + "related":"/api/v2/organizations/my-organization/workspaces/my-workspace" + } + } + }, + "links": { + "self":"/api/v2/workspaces/ws-cZE9LERN3rGPRAmH/vars/var-AD4pibb9nxo1468E" + } + } + ] +} +``` + +## Update Variables + +`PATCH /workspaces/:workspace_id/vars/:variable_id` + +| Parameter | Description | +| --------------- | ----------------------------------------------- | +| `:workspace_id` | The ID of the workspace that owns the variable. | +| `:variable_id` | The ID of the variable to be updated. | + +### Request Body + +This PATCH endpoint requires a JSON object with the following properties as a request payload. + +Properties without a default value are required. + +| Key path | Type | Default | Description | +| ----------------- | ------ | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `data.type` | string | | Must be `"vars"`. | +| `data.id` | string | | The ID of the variable to update. | +| `data.attributes` | object | | New attributes for the variable. This object can include `key`, `value`, `description`, `category`, `hcl`, and `sensitive` properties, which are described above under [create a variable](#create-a-variable). All of these properties are optional; if omitted, a property will be left unchanged. | + +### Sample Payload + +```json +{ + "data": { + "id":"var-yRmifb4PJj7cLkMG", + "attributes": { + "key":"name", + "value":"mars", + "description":"some description", + "category":"terraform", + "hcl": false, + "sensitive": false + }, + "type":"vars" + } +} +``` + +### Sample Request + +```bash +$ curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request PATCH \ + --data @payload.json \ + https://app.terraform.io/api/v2/workspaces/ws-4j8p6jX1w33MiDC7/vars/var-yRmifb4PJj7cLkMG +``` + +### Sample Response + +```json +{ + "data": { + "id":"var-yRmifb4PJj7cLkMG", + "type":"vars", + "attributes": { + "key":"name", + "value":"mars", + "description":"some description", + "sensitive":false, + "category":"terraform", + "hcl":false, + "version-id":"1aa07d63ea8ff4df941c94ca9ddfd5d2bd04" + }, + "relationships": { + "configurable": { + "data": { + "id":"ws-4j8p6jX1w33MiDC7", + "type":"workspaces" + }, + "links": { + "related":"/api/v2/organizations/workspace-v2-06/workspaces/workspace-v2-06" + } + } + }, + "links": { + "self":"/api/v2/workspaces/ws-4j8p6jX1w33MiDC7/vars/var-yRmifb4PJj7cLkMG" + } + } +} +``` + +## Delete Variables + +`DELETE /workspaces/:workspace_id/vars/:variable_id` + +| Parameter | Description | +| --------------- | ----------------------------------------------- | +| `:workspace_id` | The ID of the workspace that owns the variable. | +| `:variable_id` | The ID of the variable to be deleted. | + +### Sample Request + +```bash +$ curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request DELETE \ + https://app.terraform.io/api/v2/workspaces/ws-4j8p6jX1w33MiDC7/vars/var-yRmifb4PJj7cLkMG +``` diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/workspaces.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/workspaces.mdx new file mode 100644 index 0000000000..b29e7c83d8 --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/api-docs/workspaces.mdx @@ -0,0 +1,1689 @@ +--- +page_title: /workspaces API reference for Terraform Enterprise +description: >- + Use the Terraform Enterprise API's `/workspaces` endpoint to read, create, + update, lock, unlock, and delete workspaces and manage SSH keys, tags, and + remote state consumers. +source: terraform-docs-common +--- + +[200]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/200 + +[201]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/201 + +[202]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/202 + +[204]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/204 + +[400]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/400 + +[401]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/401 + +[403]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/403 + +[404]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404 + +[409]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/409 + +[412]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/412 + +[422]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/422 + +[429]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/429 + +[500]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500 + +[504]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/504 + +[JSON API document]: /terraform/enterprise/api-docs#json-api-documents + +[JSON API error object]: https://jsonapi.org/format/#error-objects + +[speculative plans]: /terraform/enterprise/run/remote-operations#speculative-plans + +# Workspaces API reference + +This topic provides reference information about the workspaces AP. Workspaces represent running infrastructure managed by Terraform. + +## Overview + +The scope of the API includes the following endpoints: + +| Method | Path | Action | +| -------- | ------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `POST` | `/organizations/:organization_name/workspaces` | Call this endpoint to [create a workspace](#create-a-workspace). You can apply tags stored as key-value pairs when creating the workspace. | +| `POST` | `/organizations/:organization_name/workspaces/:name/actions/safe-delete` | Call this endpoint to [safely delete a workspace](#safe-delete-a-workspace) by querying the organization and workspace names. | +| `POST` | `/workspaces/:workspace_id/actions/safe-delete` | Call this endpoint [safely delete a workspace](#safe-delete-a-workspace) by querying the workspace ID. | +| `POST` | `/workspaces/:workspace_id/actions/lock` | Call this endpoint to [lock a workspace](#lock-a-workspace). | +| `POST` | `/workspaces/:workspace_id/actions/unlock` | Call this endpoint to [unlock a workspace](#unlock-a-workspace). | +| `POST` | `/workspaces/:workspace_id/actions/force-unlock` | Call this endpoint to [force a workspace to unlock](#force-unlock-a-workspace). | +| `POST` | `/workspaces/:workspace_id/relationships/remote-state-consumers` | Call this endpoint to [add remote state consumers](#get-remote-state-consumers). | +| `POST` | `/workspaces/:workspace_id/relationships/tags` | Call this endpoint to [bind flat string tags to an existing workspace](#add-tags-to-a-workspace). | +| `POST` | `/workspaces/:workspace_id/relationships/data-retention-policy` | Call this endpoint to [show the workspace data retention policy](#show-data-retention-policy). | +| `GET` | `/organizations/:organization_name/workspaces` | Call this endpoint to [list existing workspaces](#list-workspaces). Each project in the response contains a link to `effective-tag-bindings` and `tag-bindings` collections. You can filter the response by tag keys and values using a query string parameter. | +| `GET` | `/organizations/:organization_name/workspaces/:name` | Call this endpoint to [show workspace details](#show-workspace) by querying the organization and workspace names. | +| `GET` | `/workspaces/:workspace_id` | Call this endpoint to [show workspace details](#show-workspace). | +| `GET` | `/workspaces/:workspace_id/relationships/remote-state-consumers` | Call this endpoint to [list remote state consumers](#get-remote-state-consumers). | +| `GET` | `/workspaces/:workspace_id/relationships/tags` | Call this endpoint to [list flat string workspace tags](#get-tags). | +| `GET` | `/workspaces/:workspace_id/tag-bindings` | Call this endpoint to [list workspace key-value tags](#get-tags) bound directly to this workspace. | +| `GET` | `/workspaces/:workspace_id/effective-tag-bindings` | Call this endpoint to [list all workspace key-value tags](#get-tags), including both those bound directly to the workspace as well as those inherited from the parent project. | +| `GET` | `/workspaces/:workspace_id/relationships/data-retention-policy` | Call this endpoint to [show the workspace data retention policy](#show-data-retention-policy). | +| `PATCH` | `/workspaces/:workspace_id/relationships/ssh-key` | Call this endpoint to manage SSH key assignments for workspaces. Refer to [Assign an SSH key to a workspace](#assign-an-ssh-key-to-a-workspace) and [Unassign an SSH key from a workspace](#unassign-an-ssh-key-from-a-workspace) for instructions. | +| `PATCH` | `/workspaces/:workspace_id` | Call this endpoint to [update a workspace](#update-a-workspace). You can apply tags stored as key-value pairs when updating the workspace. | +| `PATCH` | `/organizations/:organization_name/workspaces/:name` | Call this endpoint to [update a workspace](#update-a-workspace) by querying the organization and workspace names. | +| `PATCH` | `/workspaces/:workspace_id/relationships/remote-state-consumers` | Call this endpoint to [replace remote state consumers](#replace-remote-state-consumers). | +| `DELETE` | `/workspaces/:workspace_id/relationships/remote-state-consumers` | Call this endpoint to [delete remote state consumers](#delete-remote-state-consumers). | +| `DELETE` | `/workspaces/:workspace_id/relationships/tags` | Call this endpoint to [delete flat string workspace tags](#remove-tags-from-workspace) from the workspace. | +| `DELETE` | `/workspaces/:workspace_id/relationships/data-retention-policy` | Call this endpoint to [remove a workspace data retention policy](#remove-data-retention-policy). | +| `DELETE` | `/workspaces/:workspace_id` | Call this endpoint to [force delete a workspace](#force-delete-a-workspace), which deletes the workspace without first checking for managed resources. | +| `DELETE` | `/organizations/:organization_name/workspaces/:name` | Call this endpoint to [force delete a workspace](#force-delete-a-workspace), which deletes the workspace without first checking for managed resources, by querying the organization and workspace names. | + +## Requirements + +- You must be a member of a team with the **Read** permission enabled for Terraform runs to view workspaces. +- You must be a member of a team with the **Admin** permissions enabled on the workspace to change settings and force-unlock it. +- You must be a member of a team with the **Lock/unlock** permission enabled to lock and unlock the workspace. +- You must meet one of the following requirements to create a workspace: + - Be the team owner + - Be on a team with the **Manage all workspaces** permission enabled + - Present an [organization API token](/terraform/enterprise/users-teams-organizations/api-tokens#organization-api-tokens) when calling the API. + +Refer to [Workspace Permissions](/terraform/enterprise/users-teams-organizations/permissions#workspace-permissions) for additional information. + +[permissions-citation]: #intentionally-unused---keep-for-maintainers + +## Create a Workspace + +Use the following endpoint to create a new workspace: + +`POST /organizations/:organization_name/workspaces` + +| Parameter | Description | +| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `:organization_name` | The name of the organization to create the workspace in. The organization must already exist in the system, and the user must have permissions to create new workspaces. | + +### Request Body + +This POST endpoint requires a JSON object with the following properties as a request payload. + +Properties without a default value are required. + +By supplying the necessary attributes under a `vcs-repository` object, you can create a workspace that is configured against a VCS Repository. + +| Key path | Type | Default | Description | +| ------------------------------------------------------- | --------------- | --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `data.type` | string | none | Must be `"workspaces"`. | +| `data.attributes.name` | string | none | The name of the workspace. Workspace names can only include letters, numbers, `-`, and `_`. The name a unique identifier n the organization. | +| `data.attributes.agent-pool-id` | string | none | Required when `execution-mode` is set to `agent`. The ID of the agent pool belonging to the workspace's organization. This value must not be specified if `execution-mode` is set to `remote` or `local` or if `operations` is set to `true`. | +| `data.attributes.allow-destroy-plan` | boolean | `true` | Whether destroy plans can be queued on the workspace. | +| `data.attributes.assessments-enabled` | boolean | `false` | (previously `drift-detection`) Whether or not HCP Terraform performs health assessments for the workspace. May be overridden by the organization setting `assessments-enforced`. Only available for Plus tier organizations, in workspaces running Terraform version 0.15.4+ and operating in [Remote execution mode](/terraform/enterprise/workspaces/settings#execution-mode). | +| `data.attributes.auto-apply` | boolean | `false` | Whether to automatically apply changes when a Terraform plan is successful in runs initiated by VCS, UI or CLI, [with some exceptions](/terraform/enterprise/workspaces/settings#auto-apply-and-manual-apply). | +| `data.attributes.auto-apply-run-trigger` | boolean | `false` | Whether to automatically apply changes when a Terraform plan is successful in runs initiated by run triggers. | +| `data.attributes.auto-destroy-at` | string | (nothing) | Timestamp when the next scheduled destroy run will occur, refer to [Scheduled Destroy](/terraform/enterprise/workspaces/settings/deletion#automatically-destroy). | +| `data.attributes.auto-destroy-activity-duration` | string | (nothing) | Value and units for [automatically scheduled destroy runs based on workspace activity](/terraform/enterprise/workspaces/settings/deletion#automatically-destroy). Valid values are greater than 0 and four digits or less. Valid units are `d` and `h`. For example, to queue destroy runs after fourteen days of inactivity set `auto-destroy-activity-duration: "14d"`. | +| `data.attributes.description` | string | (nothing) | A description for the workspace. | +| `data.attributes.execution-mode` | string | (nothing) | Which [execution mode](/terraform/enterprise/workspaces/settings#execution-mode) to use. Valid values are `remote`, `local`, and `agent`. When set to `local`, the workspace will be used for state storage only. This value _must not_ be specified if `operations` is specified, and _must_ be specified if `setting-overwrites.execution-mode` is set to `true`. | +| `data.attributes.file-triggers-enabled` | boolean | `true` | Whether to filter runs based on the changed files in a VCS push. If enabled, it uses either `trigger-prefixes` in conjunction with `working_directory` or `trigger-patterns` to describe the set of changed files that will start a run. If disabled, any push triggers a run. | +| `data.attributes.global-remote-state` | boolean | `false` | Whether the workspace should allow all workspaces in the organization to [access its state data](/terraform/enterprise/workspaces/state) during runs. If `false`, then only specifically approved workspaces can access its state. Manage allowed workspaces using the [Remote State Consumers](/terraform/enterprise/api-docs/workspaces#get-remote-state-consumers) endpoints, documented later on this page. Terraform Enterprise admins can choose the default value for new workspaces if this attribute is omitted. | +| `data.attributes.operations` | boolean | `true` | **DEPRECATED** Use `execution-mode` instead. Whether to use remote execution mode. When set to `false`, the workspace will be used for state storage only. This value must not be specified if `execution-mode` is specified. | +| `data.attributes.queue-all-runs` | boolean | `false` | Whether runs should be queued immediately after workspace creation. When set to false, runs triggered by a VCS change will not be queued until at least one run is manually queued. | +| `data.attributes.source-name` | string | none | A friendly name for the application or client creating this workspace. If set, this will be displayed on the workspace as "Created via ``". | +| `data.attributes.source-url` | string | none | A URL for the application or client creating this workspace. This can be the URL of a related resource in another app, or a link to documentation or other info about the client. | +| `data.attributes.speculative-enabled` | boolean | `true` | Whether this workspace allows automatic [speculative plans][]. Setting this to `false` prevents HCP Terraform from running plans on pull requests, which can improve security if the VCS repository is public or includes untrusted contributors. It doesn't prevent manual speculative plans via the CLI or the runs API. | +| `data.attributes.terraform-version` | string | latest release | Specifies the version of Terraform to use for this workspace. You can specify an exact version or a [version constraint](/terraform/language/expressions/version-constraints) such as `~> 1.0.0`. If you specify a constraint, the workspace always uses the newest release that meets that constraint. If omitted when creating a workspace, this defaults to the latest released version. | +| `data.attributes.trigger-patterns` | array | `[]` | List of glob patterns that describe the files HCP Terraform monitors for changes. Trigger patterns are always appended to the root directory of the repository. | +| `data.attributes.trigger-prefixes` | array | `[]` | List of trigger prefixes that describe the paths HCP Terraform monitors for changes, in addition to the working directory. Trigger prefixes are always appended to the root directory of the repository. HCP Terraform starts a run when files are changed in any directory path matching the provided set of prefixes. | +| `data.attributes.vcs-repo.branch` | string | repository's default branch | The repository branch that Terraform executes from. If omitted or submitted as an empty string, this defaults to the repository's default branch. | +| `data.attributes.vcs-repo.identifier` | string | none | A reference to your VCS repository in the format :org/:repo where :org and :repo refer to the organization and repository in your VCS provider. The format for Azure DevOps is `:org/:project/_git/:repo`. | +| `data.attributes.vcs-repo.ingress-submodules` | boolean | `false` | Whether submodules should be fetched when cloning the VCS repository. | +| `data.attributes.vcs-repo.oauth-token-id` | string | none | Specifies the VCS OAuth connection and token. Call the [`oauth-tokens`](/terraform/enterprise/api-docs/oauth-tokens) endpoint to retrieve the OAuth ID. | +| `data.attributes.vcs-repo.tags-regex` | string | none | A regular expression used to match Git tags. HCP Terraform triggers a run when this value is present and a VCS event occurs that contains a matching Git tag for the regular expression. | +| `data.attributes.vcs-repo` | object | none | Settings for the workspace's VCS repository. If omitted, the workspace is created without a VCS repo. If included, you must specify at least the `oauth-token-id` and `identifier` keys. | +| `data.attributes.working-directory` | string | (nothing) | A relative path that Terraform will execute within. This defaults to the root of your repository and is typically set to a subdirectory matching the environment when multiple environments exist within the same repository. | +| `data.attributes.setting-overwrites` | object | none | The keys in this object are attributes that have organization-level defaults. Each attribute key stores a boolean value which is `true` by default. To overwrite the default inherited value, set an attribute's value to `false`. For example, to set `execution-mode` as the organization default, set `setting-overwrites.execution-mode` to `false`. | +| `data.relationships` | object | none | Specifies a group of workspace associations. | +| `data.relationships.project.data.id` | string | default project | The ID of the project to create the workspace in. If left blank, Terraform creates the workspace in the organization's default project. You must have permission to create workspaces in the project, either by organization-level permissions or team admin access to a specific project. | +| `data.relationships.tag-bindings.data` | list of objects | none | Specifies a list of tags to attach to the workspace. | +| `data.relationships.tag-bindings.data.type` | string | none | Must be `tag-bindings` for each object in the list. | +| `data.relationships.tag-bindings.data.attributes.key` | string | none | Specifies the tag key for each object in the list. | +| `data.relationships.tag-bindings.data.attributes.value` | string | none | Specifies the tag value for each object in the list. | + +### Sample Payload + +_Without a VCS repository_ + +```json +{ + "data": { + "attributes": { + "name": "workspace-1" + }, + "type": "workspaces" + } +} +``` + +_With Key/Value Tags_ + +```json +{ + "data": { + "attributes": { + "name": "workspace-1" + }, + "type": "workspaces", + "relationships": { + "tag-bindings": { + { + "data": [{ + "type": "tag-bindings", + "attributes": { "key": "env", "value": "test"} + }] + } + } + } + } +} +``` + +_With a VCS repository_ + +```json +{ + "data": { + "attributes": { + "name": "workspace-2", + "terraform_version": "0.11.1", + "working-directory": "", + "vcs-repo": { + "identifier": "example/terraform-test-proj", + "oauth-token-id": "ot-hmAyP66qk2AMVdbJ", + "branch": "", + "tags-regex": null + } + }, + "type": "workspaces" + } +} +``` + +_Using Git Tags_ + +HCP Terraform triggers a run when you push a Git tag that matches the regular expression (SemVer): `1.2.3`, `22.33.44`, etc. + +```json +{ + "data": { + "attributes": { + "name": "workspace-3", + "terraform_version": "0.12.1", + "file-triggers-enabled": false, + "working-directory": "/networking", + "vcs-repo": { + "identifier": "example/terraform-test-proj-monorepo", + "oauth-token-id": "ot-hmAyP66qk2AMVdbJ", + "branch": "", + "tags-regex": "\d+.\d+.\d+" + } + }, + "type": "workspaces" + } +} +``` + +_For a monorepo using trigger prefixes_ + +A run will be triggered in this workspace when changes are detected in any of the specified directories: `/networking`, `/modules`, or `/vendor`. + +```json +{ + "data": { + "attributes": { + "name": "workspace-3", + "terraform_version": "0.12.1", + "file-triggers-enabled": true, + "trigger-prefixes": ["/modules", "/vendor"], + "working-directory": "/networking", + "vcs-repo": { + "identifier": "example/terraform-test-proj-monorepo", + "oauth-token-id": "ot-hmAyP66qk2AMVdbJ", + "branch": "" + }, + "updated-at": "2017-11-29T19:18:09.976Z" + }, + "type": "workspaces" + } +} +``` + +_For a monorepo using trigger patterns_ + +A run will be triggered in this workspace when HCP Terraform detects any of the following changes: + +- A file with the extension `tf` in any directory structure in which the last folder is named `networking` (e.g., `root/networking` and `root/module/networking`) +- Any file changed in the folder `/base`, no subfolders are included +- Any file changed in the folder `/submodule` and all of its subfolders + +```json +{ + "data": { + "attributes": { + "name": "workspace-4", + "terraform_version": "1.2.2", + "file-triggers-enabled": true, + "trigger-patterns": ["/**/networking/*.tf", "/base/*", "/submodule/**/*"], + "vcs-repo": { + "identifier": "example/terraform-test-proj-monorepo", + "oauth-token-id": "ot-hmAyP66qk2AMVdbJ", + "branch": "" + }, + "updated-at": "2022-06-09T19:18:09.976Z" + }, + "type": "workspaces" + } +} +``` + +_Using HCP Terraform agents_ + +[HCP Terraform agents](/terraform/enterprise/api-docs/agents) allow HCP Terraform to communicate with isolated, private, or on-premises infrastructure. + +```json +{ + "data": { + "attributes": { + "name":"workspace-1", + "execution-mode": "agent", + "agent-pool-id": "apool-ZjT6A7mVFm5WHT5a" + } + }, + "type": "workspaces" +} +``` + +_Using an organization default execution mode_ + +```json +{ + "data": { + "attributes": { + "name":"workspace-with-default", + "setting-overwrites": { + "execution-mode": false + } + } + }, + "type": "workspaces" +} + +``` + +_With a project_ + +```json +{ + "data": { + "type": "workspaces", + "attributes": { + "name": "workspace-in-project" + }, + "relationships": { + "project": { + "data": { + "type": "projects", + "id": "prj-jT92VLSFpv8FwKtc" + } + } + } + } +} +``` + +_With key-value tags_ + +```json +{ + "data": { + "type": "workspaces", + "attributes": { + "name": "workspace-in-project" + } + } +} +``` + +### Sample Request + +```shell-session +$ curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request POST \ + --data @payload.json \ + https://app.terraform.io/api/v2/organizations/my-organization/workspaces +``` + +### Sample Response + +_Without a VCS repository_ + +**Note:** The `assessments-enabled` property is only accepted by or returned from HCP Terraform. + +@include 'api-code-blocks/workspace.mdx' + +_With a VCS repository_ + +@include 'api-code-blocks/workspace-with-vcs.mdx' + +_With a project_ + +```json +{ + "data": { + "id": "ws-HRkJLSYWF97jucqQ", + "type": "workspaces", + "attributes": { + "allow-destroy-plan": true, + "auto-apply": false, + "auto-apply-run-trigger": false, + "auto-destroy-at": null, + "auto-destroy-activity-duration": null, + "created-at": "2022-12-05T20:57:13.829Z", + "environment": "default", + "locked": false, + "locked-reason": "", + "name": "workspace-in-project", + "queue-all-runs": false, + "speculative-enabled": true, + "structured-run-output-enabled": true, + "terraform-version": "1.3.5", + "working-directory": null, + "global-remote-state": true, + "updated-at": "2022-12-05T20:57:13.829Z", + "resource-count": 0, + "apply-duration-average": null, + "plan-duration-average": null, + "policy-check-failures": null, + "run-failures": null, + "workspace-kpis-runs-count": null, + "latest-change-at": "2022-12-05T20:57:13.829Z", + "operations": true, + "execution-mode": "remote", + "vcs-repo": null, + "vcs-repo-identifier": null, + "permissions": { + "can-update": true, + "can-destroy": true, + "can-queue-run": true, + "can-read-variable": true, + "can-update-variable": true, + "can-read-state-versions": true, + "can-read-state-outputs": true, + "can-create-state-versions": true, + "can-queue-apply": true, + "can-lock": true, + "can-unlock": true, + "can-force-unlock": true, + "can-read-settings": true, + "can-manage-tags": true, + "can-manage-run-tasks": false, + "can-force-delete": true, + "can-manage-assessments": true, + "can-read-assessment-results": true, + "can-queue-destroy": true + }, + "actions": { + "is-destroyable": true + }, + "description": null, + "file-triggers-enabled": true, + "trigger-prefixes": [], + "trigger-patterns": [], + "assessments-enabled": false, + "last-assessment-result-at": null, + "source": "tfe-api", + "source-name": null, + "source-url": null, + "tag-names": [], + "setting-overwrites": { + "execution-mode": false, + "agent-pool": false + } + }, + "relationships": { + "organization": { + "data": { + "id": "my-organization", + "type": "organizations" + } + }, + "current-run": { + "data": null + }, + "effective-tag-bindings": { + "links": { + "related": "/api/v2/workspaces/ws-HRkJLSYWF97jucqQ/effective-tag-bindings" + } + }, + "latest-run": { + "data": null + }, + "outputs": { + "data": [] + }, + "remote-state-consumers": { + "links": { + "related": "/api/v2/workspaces/ws-HRkJLSYWF97jucqQ/relationships/remote-state-consumers" + } + }, + "current-state-version": { + "data": null + }, + "current-configuration-version": { + "data": null + }, + "agent-pool": { + "data": null + }, + "readme": { + "data": null + }, + "project": { + "data": { + "id": "prj-jT92VLSFpv8FwKtc", + "type": "projects" + } + }, + "current-assessment-result": { + "data": null + }, + "tag-bindings": { + "links": { + "related": "/api/v2/workspaces/ws-HRkJLSYWF97jucqQ/tag-bindings" + } + }, + "vars": { + "data": [] + }, + }, + "links": { + "self": "/api/v2/organizations/my-organization/workspaces/workspace-in-project" + } + } +} +``` + +## Update a Workspace + +Use one of the following endpoint to update a workspace: + +- `PATCH /organizations/:organization_name/workspaces/:name` +- `PATCH /workspaces/:workspace_id` + +| Parameter | Description | +| -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | +| `:workspace_id` | The ID of the workspace to update | +| `:organization_name` | The name of the organization the workspace belongs to. | +| `:name` | The name of the workspace to update. Workspace names are unique identifiers in the organization and can only include letters, numbers, `-`, and `_`. | + +### Request Body + +These PATCH endpoints require a JSON object with the following properties as a request payload. + +Properties without a default value are required. + +| Key path | Type | Default | Description | +| ------------------------------------------------------- | --------------- | ---------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `data.type` | string | | Must be `"workspaces"`. | +| `data.attributes.name` | string | (previous value) | A new name for the workspace, which can only include letters, numbers, `-`, and `_`. This will be used as an identifier and must be unique in the organization. **Warning:** Changing a workspace's name changes its URL in the API and UI. | +| `data.attributes.agent-pool-id` | string | (previous value) | Required when `execution-mode` is set to `agent`. The ID of the agent pool belonging to the workspace's organization. This value must not be specified if `execution-mode` is set to `remote` or `local` or if `operations` is set to `true`. | +| `data.attributes.allow-destroy-plan` | boolean | (previous value) | Whether destroy plans can be queued on the workspace. | +| `data.attributes.assessments-enabled` | boolean | `false` | (previously `drift-detection`) Whether or not HCP Terraform performs health assessments for the workspace. May be overridden by the organization setting `assessments-enforced`. Only available for Plus tier organizations, in workspaces running Terraform version 0.15.4+ and operating in [Remote execution mode](/terraform/enterprise/workspaces/settings#execution-mode). | +| `data.attributes.auto-apply` | boolean | (previous value) | Whether to automatically apply changes when a Terraform plan is successful in runs initiated by VCS, UI or CLI, [with some exceptions](/terraform/enterprise/workspaces/settings#auto-apply-and-manual-apply). | +| `data.attributes.auto-apply-run-trigger` | boolean | (previous value) | Whether to automatically apply changes when a Terraform plan is successful in runs initiated by run triggers. | +| `data.attributes.auto-destroy-at` | string | (previous value) | Timestamp when the next scheduled destroy run will occur, refer to [Scheduled Destroy](/terraform/enterprise/workspaces/settings/deletion#automatically-destroy). | +| `data.attributes.auto-destroy-activity-duration` | string | (previous value) | Value and units for [automatically scheduled destroy runs based on workspace activity](/terraform/enterprise/workspaces/settings/deletion#automatically-destroy). Valid values are greater than 0 and four digits or less. Valid units are `d` and `h`. For example, to queue destroy runs after fourteen days of inactivity set `auto-destroy-activity-duration: "14d"`. | +| `data.attributes.description` | string | (previous value) | A description for the workspace. | +| `data.attributes.execution-mode` | string | (previous value) | Which [execution mode](/terraform/enterprise/workspaces/settings#execution-mode) to use. Valid values are `remote`, `local`, and `agent`. When set to `local`, the workspace will be used for state storage only. This value _must not_ be specified if `operations` is specified, and _must_ be specified if `setting-overwrites.execution-mode` is set to `true`. | +| `data.attributes.file-triggers-enabled` | boolean | (previous value) | Whether to filter runs based on the changed files in a VCS push. If enabled, it uses either `trigger-prefixes` in conjunction with `working_directory` or `trigger-patterns` to describe the set of changed files that will start a run. If disabled, any push will trigger a run. | +| `data.attributes.global-remote-state` | boolean | (previous value) | Whether the workspace should allow all workspaces in the organization to [access its state data](/terraform/enterprise/workspaces/state) during runs. If `false`, then only specifically approved workspaces can access its state. Manage allowed workspaces using the [Remote State Consumers](/terraform/enterprise/api-docs/workspaces#get-remote-state-consumers) endpoints, documented later on this page. | +| `data.attributes.operations` | boolean | (previous value) | **DEPRECATED** Use `execution-mode` instead. Whether to use remote execution mode. When set to `false`, the workspace will be used for state storage only. This value must not be specified if `execution-mode` is specified. | +| `data.attributes.queue-all-runs` | boolean | (previous value) | Whether runs should be queued immediately after workspace creation. When set to false, runs triggered by a VCS change will not be queued until at least one run is manually queued. | +| `data.attributes.speculative-enabled` | boolean | (previous value) | Whether this workspace allows automatic [speculative plans][]. Setting this to `false` prevents HCP Terraform from running plans on pull requests, which can improve security if the VCS repository is public or includes untrusted contributors. It doesn't prevent manual speculative plans via the CLI or the runs API. | +| `data.attributes.terraform-version` | string | (previous value) | The version of Terraform to use for this workspace. This can be either an exact version or a [version constraint](/terraform/language/expressions/version-constraints) (like `~> 1.0.0`); if you specify a constraint, the workspace will always use the newest release that meets that constraint. | +| `data.attributes.trigger-patterns` | array | (previous value) | List of glob patterns that describe the files HCP Terraform monitors for changes. Trigger patterns are always appended to the root directory of the repository. | +| `data.attributes.trigger-prefixes` | array | (previous value) | List of trigger prefixes that describe the paths HCP Terraform monitors for changes, in addition to the working directory. Trigger prefixes are always appended to the root directory of the repository. HCP Terraform will start a run when files are changed in any directory path matching the provided set of prefixes. | +| `data.attributes.vcs-repo.branch` | string | (previous value) | The repository branch that Terraform will execute from. | +| `data.attributes.vcs-repo.identifier` | string | (previous value) | A reference to your VCS repository in the format :org/:repo where :org and :repo refer to the organization and repository in your VCS provider. The format for Azure DevOps is `:org/:project/_git/:repo`. | +| `data.attributes.vcs-repo.ingress-submodules` | boolean | (previous value) | Whether submodules should be fetched when cloning the VCS repository. | +| `data.attributes.vcs-repo.oauth-token-id` | string | | The VCS Connection (OAuth Connection + Token) to use as identified. Get this ID from the [oauth-tokens](/terraform/enterprise/api-docs/oauth-tokens) endpoint. You can not specify this value if `github-app-installation-id` is specified. | +| `data.attributes.vcs-repo.github-app-installation-id` | string | | The VCS Connection GitHub App Installation to use. Find this ID on the account settings page. Requires previously authorizing the GitHub App and generating a user-to-server token. Manage the token from **Account Settings** within HCP Terraform. You can not specify this value if `oauth-token-id` is specified. | +| `data.attributes.vcs-repo.tags-regex` | string | (previous value) | A regular expression used to match Git tags. HCP Terraform triggers a run when this value is present and a VCS event occurs that contains a matching Git tag for the regular expression. | +| `data.attributes.vcs-repo` | object or null | (previous value) | To delete a workspace's existing VCS repo, specify `null` instead of an object. To modify a workspace's existing VCS repo, include whichever of the keys below you wish to modify. To add a new VCS repo to a workspace that didn't previously have one, include at least the `oauth-token-id` and `identifier` keys. | +| `data.attributes.working-directory` | string | (previous value) | A relative path that Terraform will execute within. This defaults to the root of your repository and is typically set to a subdirectory matching the environment when multiple environments exist within the same repository. | +| `data.attributes.setting-overwrites` | object | | The keys in this object are attributes that have organization-level defaults. Each attribute key stores a boolean value which is `true` by default. To overwrite the default inherited value, set an attribute's value to `false`. For example, to set `execution-mode` as the organization default, you set `setting-overwrites.execution-mode = false`. | +| `data.relationships` | object | none | Specifies a group of workspace relationships. | +| `data.relationships.project.data.id` | string | existing value | The ID of the project to move the workspace to. If left blank or unchanged, the workspace will not be moved. You must have admin permissions on both the source project and destination project in order to move a workspace between projects. | +| `data.relationships.tag-bindings.data` | list of objects | none | Specifies a list of tags to attach to the workspace. | +| `data.relationships.tag-bindings.data.type` | string | none | Must be `tag-bindings` for each object in the list. | +| `data.relationships.tag-bindings.data.attributes.key` | string | none | Specifies the tag key for each object in the list. | +| `data.relationships.tag-bindings.data.attributes.value` | string | none | Specifies the tag value for each object in the list. | + +### Sample Payload + +```json +{ + "data": { + "attributes": { + "name": "workspace-2", + "resource-count": 0, + "terraform_version": "0.11.1", + "working-directory": "", + "vcs-repo": { + "identifier": "example/terraform-test-proj", + "branch": "", + "ingress-submodules": false, + "oauth-token-id": "ot-hmAyP66qk2AMVdbJ" + }, + "updated-at": "2017-11-29T19:18:09.976Z" + }, + "relationships": { + "project": { + "data": { + "type": "projects", + "id": "prj-7HWWPGY3fYxztELU" + } + }, + "tag-bindings": { + "data": [ + { + "type": "tag-bindings", + "attributes": { + "key": "environment", + "value": "development" + } + }, + ] + } + }, + "type": "workspaces" + } +} +``` + +### Sample Request + +```shell-session +$ curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request PATCH \ + --data @payload.json \ + https://app.terraform.io/api/v2/organizations/my-organization/workspaces/workspace-2 +``` + +### Sample Response + +@include 'api-code-blocks/workspace-with-vcs.mdx' + +## List workspaces + +This endpoint lists workspaces in the organization. + +`GET /organizations/:organization_name/workspaces` + +| Parameter | Description | +| -------------------- | ------------------------------------------------------- | +| `:organization_name` | The name of the organization to list the workspaces of. | + +### Query Parameters + +This endpoint supports pagination [with standard URL query parameters](/terraform/enterprise/api-docs#query-parameters). Remember to percent-encode `[` as `%5B` and `]` as `%5D` if your tooling doesn't automatically encode URLs. + +| Parameter | Description | +| ----------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `page[number]` | **Optional.** If omitted, the endpoint will return the first page. | +| `page[size]` | **Optional.** If omitted, the endpoint will return 20 workspaces per page. | +| `search[name]` | **Optional.** If specified, restricts results to workspaces with a name that matches the search string using a fuzzy search. | +| `search[tags]` | **Optional.** If specified, restricts results to workspaces with that tag. If multiple comma separated values are specified, results matching all of the tags are returned. | +| `search[exclude-tags]` | **Optional.** If specified, results exclude workspaces with that tag. If multiple comma separated values are specified, workspaces with tags matching any of the tags are excluded. | +| `search[wildcard-name]` | **Optional.** If specified, restricts results to workspaces with partial matching, using `*` on prefix, suffix, or both. For example, `search[wildcard-name]=*-prod` returns all workspaces ending in `-prod`, `search[wildcard-name]=prod-*` returns all workspaces beginning with `prod-`, and `search[wildcard-name]=*-prod-*` returns all workspaces with substring `-prod-` regardless of prefix and/or suffix. | +| `sort` | **Optional.** Allows sorting the organization's workspaces by a provided value. You can sort by `"name"`, `"current-run.created-at"` (the time of the current run), and `"latest-change-at"` (the creation time of the latest state version or the workspace itself if no state version exists). Prepending a hyphen to the sort parameter reverses the order. For example, `"-name"` sorts by name in reverse alphabetical order. If omitted, the default sort order is arbitrary but stable. | +| `filter[project][id]` | **Optional.** If specified, restricts results to workspaces in the specific project. | +| `filter[current-run][status]` | **Optional.** If specified, restricts results to workspaces that match the status of a current run. | +| `filter[tagged][i][key]` | **Optional.** If specified, restricts results to workspaces that are tagged with the provided key. Use a value of "0" for `i` if you are only using a single filter. For multiple tag filters, use an incrementing integer value for each filter. Multiple tag filters will be combined together with a logical AND when filtering results. | +| `filter[tagged][i][value]` | **Optional.** If specified, restricts results to workspaces that are tagged with the provided value. This is useful when combined with a `key` filter for more specificity. Use a value of "0" for `i` if you are only using a single filter. For multiple tag filters, use an incrementing integer value for each filter. Multiple tag filters will be combined together with a logical AND when filtering results. | + +### Sample Request + +```shell-session +$ curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + https://app.terraform.io/api/v2/organizations/my-organization/workspaces +``` + +_With multiple tag filters_ + +```shell-session +$ curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + https://app.terraform.io/api/v2/organizations/my-organization/workspaces?filter%5B%tagged5D%5B0%5D%5Bkey%5D=environment&filter%5B%tagged5D%5B0%5D%5Bvalue%5D=development&filter%5B%tagged5D%5B1%5D%5Bkey%5D=meets-compliance +``` + +### Sample Response + +@include 'api-code-blocks/workspaces-list.mdx' + +## Show workspace + +Details on a workspace can be retrieved from two endpoints, which behave identically. + +One refers to a workspace by its ID: + +`GET /workspaces/:workspace_id` + +| Parameter | Description | +| --------------- | ---------------- | +| `:workspace_id` | The workspace ID | + +The other refers to a workspace by its name and organization: + +`GET /organizations/:organization_name/workspaces/:name` + +| Parameter | Description | +| -------------------- | ----------------------------------------------------------------------------------------------------- | +| `:organization_name` | The name of the organization the workspace belongs to. | +| `:name` | The name of the workspace to show details for, which can only include letters, numbers, `-`, and `_`. | + +### Workspace performance attributes + +The following attributes are helpful in determining the overall health and performance of your workspace configuration. These metrics refer to the **past 30 runs that have either resulted in an error or successfully applied**. + +| Parameter | Type | Description | +| ------------------------------------------ | ------ | --------------------------------------------------------------------------------------- | +| `data.attributes.apply-duration-average` | number | This is the average time runs spend in the **apply** phase, represented in milliseconds | +| `data.attributes.plan-duration-average` | number | This is the average time runs spend in the **plan** phase, represented in milliseconds | +| `data.attributes.policy-check-failures` | number | Reports the number of run failures resulting from a policy check failure | +| `data.attributes.run-failures` | number | Reports the number of failed runs | +| `data.attributes.workspace-kpis-run-count` | number | Total number of runs taken into account by these metrics | + +### Sample Request + +```shell-session +$ curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + https://app.terraform.io/api/v2/organizations/my-organization/workspaces/workspace-1 +``` + +### Sample Response + +@include 'api-code-blocks/workspace.mdx' + +## Safe Delete a workspace + +When you delete an HCP Terraform workspace with resources, Terraform can no longer track or manage that infrastructure. During a safe delete, HCP Terraform only deletes the workspace if it is not managing resources. + +You can safe delete a workspace using two endpoints that behave identically. The first endpoint identifies a workspace with the workspace ID, and the other identifies the workspace by its name and organization. + +`POST /workspaces/:workspace_id/actions/safe-delete` + +| Parameter | Description | +| --------------- | ---------------------------------- | +| `:workspace_id` | The ID of the workspace to delete. | + +`POST /organizations/:organization_name/workspaces/:name/actions/safe-delete` + +| Parameter | Description | +| -------------------- | ------------------------------------------------------------------------------------------- | +| `:organization_name` | The name of the workspace's organization. | +| `:name` | The name of the workspace to delete, which can only include letters, numbers, `-`, and `_`. | + +| Status | Response | Reason(s) | +| ------- | ------------------------- | --------------------------------------------------------------------- | +| [204][] | No Content | Successfully deleted the workspace | +| [404][] | [JSON API error object][] | Workspace not found, or user unauthorized to perform workspace delete | +| [409][] | [JSON API error object][] | Workspace is not safe to delete because it is managing resources | + +## Force Delete a workspace + +During a force delete, HCP Terraform removes the specified workspace without checking whether it is managing resources. We recommend using the [safe delete endpoint](#safe-delete-a-workspace) instead, when possible. + +!> **Warning:** Terraform cannot track or manage the workspace's infrastructure after deletion. We recommend [destroying the workspace's infrastructure](/terraform/enterprise/run/modes-and-options#destroy-mode) before you delete it. + +By default, only organization owners can force delete workspaces. Organization owners can also update [organization's settings]\(/terraform/cloud-docs/users-teams organizations/organizations#general) to let workspace admins force delete their own workspaces. + +You can use two endpoints to force delete a workspace, which behave identically. One endpoint identifies the workspace with its workspace ID and the other endpoint identifies the workspace with its name and organization. + +`DELETE /workspaces/:workspace_id` + +| Parameter | Description | +| --------------- | --------------------------------- | +| `:workspace_id` | The ID of the workspace to delete | + +`DELETE /organizations/:organization_name/workspaces/:name` + +| Parameter | Description | +| -------------------- | ------------------------------------------------------------------------------------------- | +| `:organization_name` | The name of the organization the workspace belongs to. | +| `:name` | The name of the workspace to delete, which can only include letters, numbers, `-`, and `_`. | + +| Status | Response | Reason(s) | +| ------- | ------------------------- | --------------------------------------------------------------------- | +| [204][] | No Content | Successfully deleted the workspace | +| [403][] | [JSON API error object][] | Not authorized to perform a force delete on the workspace | +| [404][] | [JSON API error object][] | Workspace not found, or user unauthorized to perform workspace delete | + +### Sample Request + +```shell-session +$ curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request DELETE \ + https://app.terraform.io/api/v2/organizations/my-organization/workspaces/workspace-1 +``` + +## Lock a workspace + +This endpoint locks a workspace. + +`POST /workspaces/:workspace_id/actions/lock` + +| Parameter | Description | +| --------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `:workspace_id` | The workspace ID to lock. Obtain this from the [workspace settings](/terraform/enterprise/workspaces/settings) or the [Show Workspace](#show-workspace) endpoint. | + +| Status | Response | Reason(s) | +| ------- | -------------------------------------------- | ----------------------------------------------------------- | +| [200][] | [JSON API document][] (`type: "workspaces"`) | Successfully locked the workspace | +| [404][] | [JSON API error object][] | Workspace not found, or user unauthorized to perform action | +| [409][] | [JSON API error object][] | Workspace already locked | + +### Request Body + +This POST endpoint requires a JSON object with the following properties as a request payload. + +Properties without a default value are required. + +| Key path | Type | Default | Description | +| -------- | ------ | ------- | ------------------------------------- | +| `reason` | string | `""` | The reason for locking the workspace. | + +### Sample Payload + +```json +{ + "reason": "Locking workspace-1" +} +``` + +### Sample Request + +```shell-session +$ curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request POST \ + --data @payload.json \ + https://app.terraform.io/api/v2/workspaces/ws-SihZTyXKfNXUWuUa/actions/lock +``` + +### Sample Response + +@include 'api-code-blocks/workspace.mdx' + +## Unlock a workspace + +This endpoint unlocks a workspace. Unlocking a workspace sets the current state version to the latest finalized intermediate state version. If intermediate state versions are available, but HCP Terraform has not yet finalized the latest intermediate state version, the unlock will fail with a 503 response. For this particular error, it's recommended to retry the unlock operation for a short period of time until the platform finalizes the state version. If you must force-unlock a workspace under these conditions, ensure that state was saved successfully by inspecting the latest state version using the [State Version List API](/terraform/enterprise/api-docs/state-versions#list-state-versions-for-a-workspace) + +`POST /workspaces/:workspace_id/actions/unlock` + +| Parameter | Description | +| --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `:workspace_id` | The workspace ID to unlock. Obtain this from the [workspace settings](/terraform/enterprise/workspaces/settings) or the [Show Workspace](#show-workspace) endpoint. | + +| Status | Response | Reason(s) | +| ------- | -------------------------------------------- | ----------------------------------------------------------- | +| [200][] | [JSON API document][] (`type: "workspaces"`) | Successfully unlocked the workspace | +| [404][] | [JSON API error object][] | Workspace not found, or user unauthorized to perform action | +| [409][] | [JSON API error object][] | Workspace already unlocked, or locked by a different user | + +### Sample Request + +```shell-session +$ curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request POST \ + https://app.terraform.io/api/v2/workspaces/ws-SihZTyXKfNXUWuUa/actions/unlock +``` + +### Sample Response + +@include 'api-code-blocks/workspace.mdx' + +## Force Unlock a workspace + +This endpoint force unlocks a workspace. Only users with admin access are authorized to force unlock a workspace. + +`POST /workspaces/:workspace_id/actions/force-unlock` + +| Parameter | Description | +| --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `:workspace_id` | The workspace ID to force unlock. Obtain this from the [workspace settings](/terraform/enterprise/workspaces/settings) or the [Show Workspace](#show-workspace) endpoint. | + +| Status | Response | Reason(s) | +| ------- | -------------------------------------------- | ----------------------------------------------------------- | +| [200][] | [JSON API document][] (`type: "workspaces"`) | Successfully force unlocked the workspace | +| [404][] | [JSON API error object][] | Workspace not found, or user unauthorized to perform action | +| [409][] | [JSON API error object][] | Workspace already unlocked | + +### Sample Request + +```shell-session +$ curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request POST \ + https://app.terraform.io/api/v2/workspaces/ws-SihZTyXKfNXUWuUa/actions/force-unlock +``` + +### Sample Response + +@include 'api-code-blocks/workspace-with-vcs.mdx' + +## Assign an SSH key to a workspace + +This endpoint assigns an SSH key to a workspace. + +`PATCH /workspaces/:workspace_id/relationships/ssh-key` + +| Parameter | Description | +| --------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `:workspace_id` | The workspace ID to assign the SSH key to. Obtain this from the [workspace settings](/terraform/enterprise/workspaces/settings) or the [Show Workspace](#show-workspace) endpoint. | + +### Request Body + +This POST endpoint requires a JSON object with the following properties as a request payload. + +Properties without a default value are required. + +| Key path | Type | Default | Description | +| -------------------- | ------ | ------- | ------------------------------------------------------------------------------------------------------------ | +| `data.type` | string | | Must be `"workspaces"`. | +| `data.attributes.id` | string | | The SSH key ID to assign. Obtain this from the [ssh-keys](/terraform/enterprise/api-docs/ssh-keys) endpoint. | + +#### Sample Payload + +```json +{ + "data": { + "attributes": { + "id": "sshkey-GxrePWre1Ezug7aM" + }, + "type": "workspaces" + } +} +``` + +### Sample Request + +```shell-session +$ curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request PATCH \ + --data @payload.json \ + https://app.terraform.io/api/v2/workspaces/ws-SihZTyXKfNXUWuUa/relationships/ssh-key +``` + +### Sample Response + +@include 'api-code-blocks/workspace-with-vcs.mdx' + +## Unassign an SSH key from a workspace + +This endpoint unassigns the currently assigned SSH key from a workspace. + +`PATCH /workspaces/:workspace_id/relationships/ssh-key` + +| Parameter | Description | +| --------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `:workspace_id` | The workspace ID to assign the SSH key to. Obtain this from the [workspace settings](/terraform/enterprise/workspaces/settings) or the [Show Workspace](#show-workspace) endpoint. | + +### Request Body + +This POST endpoint requires a JSON object with the following properties as a request payload. + +Properties without a default value are required. + +| Key path | Type | Default | Description | +| -------------------- | ------ | ------- | ----------------------- | +| `data.type` | string | | Must be `"workspaces"`. | +| `data.attributes.id` | string | | Must be `null`. | + +### Sample Payload + +```json +{ + "data": { + "attributes": { + "id": null + }, + "type": "workspaces" + } +} +``` + +### Sample Request + +```shell-session +$ curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request PATCH \ + --data @payload.json \ + https://app.terraform.io/api/v2/workspaces/ws-SihZTyXKfNXUWuUa/relationships/ssh-key +``` + +### Sample Response + +@include 'api-code-blocks/workspace-with-vcs.mdx' + +## Get Remote State Consumers + +`GET /workspaces/:workspace_id/relationships/remote-state-consumers` + +| Parameter | Description | +| --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `:workspace_id` | The workspace ID to get remote state consumers for. Obtain this from the [workspace settings](/terraform/enterprise/workspaces/settings) or the [Show Workspace](#show-workspace) endpoint. | + +This endpoint retrieves the list of other workspaces that are allowed to access the given workspace's state during runs. + +- If `global-remote-state` is set to false for the workspace, this will return the list of other workspaces that are specifically authorized to access the workspace's state. +- If `global-remote-state` is set to true, this will return a list of every workspace in the organization except for the subject workspace. + +The list returned by this endpoint is subject to the caller's normal workspace permissions; it will not include workspaces that the provided API token is unable to read. + +### Query Parameters + +This endpoint supports pagination [with standard URL query parameters](/terraform/enterprise/api-docs#query-parameters). Remember to percent-encode `[` as `%5B` and `]` as `%5D` if your tooling doesn't automatically encode URLs. + +| Parameter | Description | +| -------------- | -------------------------------------------------------------------------- | +| `page[number]` | **Optional.** If omitted, the endpoint will return the first page. | +| `page[size]` | **Optional.** If omitted, the endpoint will return 20 workspaces per page. | + +### Sample Request + +```shell-session +$ curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + https://app.terraform.io/api/v2/workspaces/ws-SihZTyXKfNXUWuUa/relationships/remote-state-consumers +``` + +### Sample Response + +@include 'api-code-blocks/workspaces-list.mdx' + +## Replace Remote State Consumers + +`PATCH /workspaces/:workspace_id/relationships/remote-state-consumers` + +| Parameter | Description | +| --------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `:workspace_id` | The workspace ID to replace remote state consumers for. Obtain this from the [workspace settings](/terraform/enterprise/workspaces/settings) or the [Show Workspace](#show-workspace) endpoint. | + +This endpoint updates the workspace's remote state consumers to be _exactly_ the list of workspaces specified in the payload. It can only be used for workspaces where `global-remote-state` is false. + +This endpoint can only be used by teams with permission to manage workspaces for the entire organization — only those who can _view_ the entire list of consumers can _replace_ the entire list. ([More about permissions.](/terraform/enterprise/users-teams-organizations/permissions)) Teams with admin permissions on specific workspaces can still modify remote state consumers for those workspaces, but must use the add (POST) and remove (DELETE) endpoints listed below instead of this PATCH endpoint. + +[permissions-citation]: #intentionally-unused---keep-for-maintainers + +| Status | Response | Reason(s) | +| ------- | ------------------------- | --------------------------------------------------------------------- | +| [204][] | No Content | Successfully updated remote state consumers | +| [404][] | [JSON API error object][] | Workspace not found, or user unauthorized to perform action | +| [422][] | [JSON API error object][] | Problem with payload or request; details provided in the error object | + +### Request Body + +This PATCH endpoint requires a JSON object with the following properties as a request payload. + +Properties without a default value are required. + +| Key path | Type | Default | Description | +| ------------- | ------ | ------- | ----------------------------------------------------------- | +| `data[].type` | string | | Must be `"workspaces"`. | +| `data[].id` | string | | The ID of a workspace to be set as a remote state consumer. | + +### Sample Payload + +```json +{ + "data": [ + { + "id": "ws-7aiqKYf6ejMFdtWS", + "type": "workspaces" + } + ] +} +``` + +### Sample Request + +```shell-session +$ curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request PATCH \ + --data @payload.json \ + https://app.terraform.io/api/v2/workspaces/ws-UYv6RYM8fVhzeGG5/relationships/remote-state-consumers +``` + +### Response + +No response body. + +Status code `204`. + +## Add Remote State Consumers + +`POST /workspaces/:workspace_id/relationships/remote-state-consumers` + +| Parameter | Description | +| --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `:workspace_id` | The workspace ID to add remote state consumers for. Obtain this from the [workspace settings](/terraform/enterprise/workspaces/settings) or the [Show Workspace](#show-workspace) endpoint. | + +This endpoint adds one or more remote state consumers to the workspace. It can only be used for workspaces where `global-remote-state` is false. + +- The workspaces specified as consumers must be readable to the API token that makes the request. +- A workspace cannot be added as a consumer of itself. (A workspace can always read its own state, regardless of access settings.) +- You can safely add a consumer workspace that is already present; it will be ignored, and the rest of the consumers in the request will be processed normally. + +| Status | Response | Reason(s) | +| ------- | ------------------------- | --------------------------------------------------------------------- | +| [204][] | No Content | Successfully updated remote state consumers | +| [404][] | [JSON API error object][] | Workspace not found, or user unauthorized to perform action | +| [422][] | [JSON API error object][] | Problem with payload or request; details provided in the error object | + +### Request Body + +This POST endpoint requires a JSON object with the following properties as a request payload. + +Properties without a default value are required. + +| Key path | Type | Default | Description | +| ------------- | ------ | ------- | ----------------------------------------------------------- | +| `data[].type` | string | | Must be `"workspaces"`. | +| `data[].id` | string | | The ID of a workspace to be set as a remote state consumer. | + +### Sample Payload + +```json +{ + "data": [ + { + "id": "ws-7aiqKYf6ejMFdtWS", + "type": "workspaces" + } + ] +} +``` + +### Sample Request + +```shell-session +$ curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request POST \ + --data @payload.json \ + https://app.terraform.io/api/v2/workspaces/ws-UYv6RYM8fVhzeGG5/relationships/remote-state-consumers +``` + +### Response + +No response body. + +Status code `204`. + +## Delete Remote State Consumers + +`DELETE /workspaces/:workspace_id/relationships/remote-state-consumers` + +| Parameter | Description | +| --------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `:workspace_id` | The workspace ID to remove remote state consumers for. Obtain this from the [workspace settings](/terraform/enterprise/workspaces/settings) or the [Show Workspace](#show-workspace) endpoint. | + +This endpoint removes one or more remote state consumers from a workspace, according to the contents of the payload. It can only be used for workspaces where `global-remote-state` is false. + +- The workspaces specified as consumers must be readable to the API token that makes the request. +- You can safely remove a consumer workspace that is already absent; it will be ignored, and the rest of the consumers in the request will be processed normally. + +| Status | Response | Reason(s) | +| ------- | ------------------------- | --------------------------------------------------------------------- | +| [204][] | No Content | Successfully updated remote state consumers | +| [404][] | [JSON API error object][] | Workspace not found, or user unauthorized to perform action | +| [422][] | [JSON API error object][] | Problem with payload or request; details provided in the error object | + +### Request Body + +This POST endpoint requires a JSON object with the following properties as a request payload. + +Properties without a default value are required. + +| Key path | Type | Default | Description | +| ------------- | ------ | ------- | ---------------------------------------------------------------- | +| `data[].type` | string | | Must be `"workspaces"`. | +| `data[].id` | string | | The ID of a workspace to remove from the remote state consumers. | + +### Sample Payload + +```json +{ + "data": [ + { + "id": "ws-7aiqKYf6ejMFdtWS", + "type": "workspaces" + } + ] +} +``` + +### Sample Request + +```shell-session +$ curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request DELETE \ + --data @payload.json \ + https://app.terraform.io/api/v2/workspaces/ws-UYv6RYM8fVhzeGG5/relationships/remote-state-consumers +``` + +### Response + +No response body. + +Status code `204`. + +## List workspace tags + +Workspace tags are [organization tags](/terraform/enterprise/api-docs/organization-tags) added to a workspace. They are a flat list of keys that can only be applied to workspaces when using the `tags` attribute in the Terraform `cloud` block in Terraform v1.9 and older. To list key-value tags supported in Terraform v1.10 and newer, refer to [List workspace tag bindings](#list-workspace-tag-bindings). + +`GET /workspaces/:workspace_id/relationships/tags`: Paginated list of flat string tags attached to the workspace. + +### Path parameters + +| Parameter | Description | +| --------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `:workspace_id` | The workspace ID to fetch tags for. Obtain this from the [workspace settings](/terraform/enterprise/workspaces/settings) or the [Show Workspace](#show-workspace) endpoint. | + +### Query Parameters + +Only the flat string tags endpoint supports pagination [with standard URL query parameters](/terraform/enterprise/api-docs#query-parameters). Remember to percent-encode `[` as `%5B` and `]` as `%5D` if your tooling doesn't automatically encode URLs. Conversely, all tags are returned when using fetching tag-bindings or effective-tag-bindings endpoints. + +| Parameter | Description | +| -------------- | -------------------------------------------------------------------------- | +| `page[number]` | **Optional.** If omitted, the endpoint will return the first page. | +| `page[size]` | **Optional.** If omitted, the endpoint will return 20 workspaces per page. | + +### Sample Requests + + + +```shell-session +$ curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + https://app.terraform.io/api/v2/workspaces/ws-db61c9eb5cab5ae2/relationships/tags +``` + + + +### Sample Responses + + + +```json +{ + "data": [ + { + "id": "tag-1", + "type": "tags", + "attributes": { + "name": "tag1", + "created-at": "2022-03-09T06:04:39.585Z", + "instance-count": 1 + }, + "relationships": { + "organization": { + "data": { + "id": "my-organization", + "type": "organizations" + } + } + } + }, + { + "id": "tag-2", + "type": "tags", + "attributes": { + "name": "tag2", + "created-at": "2022-03-09T06:04:39.585Z", + "instance-count": 2 + }, + "relationships": { + "organization": { + "data": { + "id": "my-organization", + "type": "organizations" + } + } + } + } + ] +} +``` + + + +## List workspace tag bindings + +Call the following endpoints to list the tags attached to a workspace: + +- `GET /workspaces/:workspace_id/tag-bindings`: Lists key-value tags directly bound to the workspace. +- `GET /workspaces/:workspace_id/effective-tag-bindings`: Lists all key-value tags bound to the workspace, including those inherited from the parent project. + +### Path parameters + +| Parameter | Description | +| --------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `:workspace_id` | The workspace ID to fetch tags for. Obtain this from the [workspace settings](/terraform/enterprise/workspaces/settings) or the [Show Workspace](#show-workspace) endpoint. | + +### Sample Requests + + + +```shell-session +$ curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + https://app.terraform.io/api/v2/workspaces/ws-db61c9eb5cab5ae2/tag-bindings +``` + + + + + +```shell-session +$ curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + https://app.terraform.io/api/v2/workspaces/ws-db61c9eb5cab5ae2/effective-tag-bindings +``` + + + +### Sample Responses + + + +```json +{ + "data": [ + { + "id": "tb-232e23631380f79e", + "type": "tag-bindings", + "attributes": { + "key": "costcenter", + "value": "123", + "created-at": "2024-11-19T23:59:24.648Z" + } + } + ], + "links": { + "target": "/api/v2/workspaces/ws-db61c9eb5cab5ae2" + } +} +``` + + +
+ + +```json +{ + "data": [ + { + "id": "07cc44202a430fc2", + "type": "effective-tag-bindings", + "attributes": { + "key": "costcenter", + "value": "123" + } + }, + { + "id": "f8b11951f98e11f8", + "type": "effective-tag-bindings", + "attributes": { + "key": "dept", + "value": "r+d" + }, + "relationships": { + "inherited-from": { + "links": { + "related": "/api/v2/projects/prj-af7d174fa1ea7423" + } + } + } + } + ] +} +``` + + + +## Add flat string tags to a workspace + +`POST /workspaces/:workspace_id/relationships/tags` + +To add key-value tags to an existing workspace, call the `PATCH /workspaces/:workspace_id` and provide workspace tag bindings in the JSON payload. Refer to [Update a workspace](#update-a-workspace) for additional information. + +You can also bind key-value tags when creating a workspace. Refer to [Create a workspace](#create-a-workspace) for additional information. + +Refer to [Define project tags](/terraform/enterprise/projects/manage#define-project-tags) for information about supported tag values. + +| Parameter | Description | +| --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `:workspace_id` | The workspace ID to add tags to. Obtain this from the [workspace settings](/terraform/enterprise/workspaces/settings) or the [Show Workspace](#show-workspace) endpoint. | + +| Status | Response | Reason(s) | +| ------- | ------------------------- | ----------------------------------------------------------- | +| [204][] | No Content | Successfully added tags to workspace | +| [404][] | [JSON API error object][] | Workspace not found, or user unauthorized to perform action | + +### Request Body + +This POST endpoint requires a JSON object with the following properties as a request payload. + +It is important to note that `type`, as well as one of `id` _or_ `attributes.name` is required. + +| Key path | Type | Default | Description | +| ------------------------ | ------ | ------- | --------------------------- | +| `data[].type` | string | | Must be `"tags"`. | +| `data[].id` | string | | The ID of the tag to add. | +| `data[].attributes.name` | string | | The name of the tag to add. | + +### Sample Payload + +```json +{ + "data": [ + { + "type": "tags", + "attributes": { + "name": "foo" + } + }, + { + "type": "tags", + "attributes": { + "name": "bar" + } + } + ] +} +``` + +### Sample Request + +```shell-session +$ curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request POST \ + --data @payload.json \ + https://app.terraform.io/api/v2/workspaces/workspace-2/relationships/tags +``` + +### Sample Response + +No response body. + +Status code `204`. + +## Remove tags from workspace + +This endpoint removes one or more tags from a workspace. The workspace must already exist, and tag +element that supplies an `id` attribute must exist. If the `name` attribute is used, and no matching +organization tag is found, no action will occur for that entry. Tags removed from all workspaces will be +removed from the organization-wide list. + +To remove key-value tags to an existing workspace, call the `PATCH /workspaces/:workspace_id` and provide workspace tag bindings in the JSON payload. Refer to [Update a workspace](#update-a-workspace) for additional information. + +`DELETE /workspaces/:workspace_id/relationships/tags` + +| Parameter | Description | +| --------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `:workspace_id` | The workspace ID to remove tags from. Obtain this from the [workspace settings](/terraform/enterprise/workspaces/settings) or the [Show Workspace](#show-workspace) endpoint. | + +| Status | Response | Reason(s) | +| ------- | ------------------------- | ----------------------------------------------------------- | +| [204][] | No Content | Successfully removed tags to workspace | +| [404][] | [JSON API error object][] | Workspace not found, or user unauthorized to perform action | + +### Request Body + +This POST endpoint requires a JSON object with the following properties as a request payload. + +It is important to note that `type`, as well as one of `id` _or_ `attributes.name` is required. + +| Key path | Type | Default | Description | +| ------------------------ | ------ | ------- | ------------------------------ | +| `data[].type` | string | | Must be `"tags"`. | +| `data[].id` | string | | The ID of the tag to remove. | +| `data[].attributes.name` | string | | The name of the tag to remove. | + +### Sample Payload + +```json +{ + "data": [ + { + "type": "tags", + "id": "tag-Yfha4YpPievQ8wJw" + } + ] +} +``` + +### Sample Request + +```shell-session +$ curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request DELETE \ + --data @payload.json \ + https://app.terraform.io/api/v2/workspaces/workspace-2/relationships/tags +``` + +### Sample Response + +No response body. + +Status code `204`. + +## Add/update tag-bindings on a workspace + +This endpoint adds keys and values or updates values of tag-bindings on an existing resource by key. +It does not remove any keys from the collection. This endpoint is useful when you want to ensure a +modification is additive. + +Tag bindings have special constraints and behaviors: + +- You can apply up to 10 tags per workspace. +- Workspaces can inherit an additional 10 tags from its project. +- Keys can have up to 128 characters. +- Keys can contain all alphanumeric characters and the symbols `_`, `.`, `=`, `+`, `-`, `@`, `:`. +- values can have up to 256 characters. +- Values can contain all alphanumeric characters and the symbols `_`, `.`, `=`, `+`, `-`, `@`, `:`. +- You cannot use `hc:` and `hcp:` as key prefixes. + +`PATCH /workspaces/:workspace_id/tag-bindings` + +| Parameter | Description | +| --------------- | --------------------------------- | +| `:workspace_id` | The ID of the workspace to update | + +### Request Body + +This PATCH endpoint requires a JSON object with the following properties as a request payload. + +It is important to note that for each data item, `type`, as well as `attributes.key` is required. + +| Key path | Type | Default | Description | +| ------------------------- | ------ | ------- | ---------------------------------- | +| `data[].type` | string | | Must be `"tag-bindings"`. | +| `data[].attributes.key` | string | | The key of the tag to add/update. | +| `data[].attributes.value` | string | | The name of the tag to add/update. | + +### Sample Payload + +```json +{ + "data": [ + { + "type": "tag-bindings", + "attributes": { + "key": "costcenter", + "value": "123" + } + }, + { + "type": "tag-bindings", + "attributes": { + "key": "bar", + "value": "baz" + } + } + ] +} +``` + +### Sample Request + +```shell-session +$ curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request PATCH \ + --data @payload.json \ + https://app.terraform.io/api/v2/workspaces/ws-82d2281aa259ba09/tag-bindings +``` + +### Sample Response + +Status Code 200 + + + +```json +{ + "data": [ + { + "id": "tb-e4a5847b2cf06559", + "type": "tag-bindings", + "attributes": { + "key": "costcenter", + "value": "123" + } + }, + { + "id": "tb-97ce954636f93a6c", + "type": "tag-bindings", + "attributes": { + "key": "bar", + "value": "baz" + } + } + ] +} +``` + + + +## Show data retention policy + + +This endpoint is exclusive to Terraform Enterprise and is not available in HCP Terraform. + + +`GET /workspaces/:workspace_id/relationships/data-retention-policy` + +| Parameter | Description | +| --------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `:workspace_id` | The ID of the workspace to show the data retention policy for. Obtain this from the [workspace settings](/terraform/enterprise/workspaces/settings) or by sending a `GET` request to the [`/workspaces`](#show-workspace) endpoint. | + +This endpoint shows the data retention policy set explicitly on the workspace. +When no data retention policy is set for the workspace, the endpoint returns the default policy configured for the organization. Refer to [Data Retention Policies](/terraform/enterprise/workspaces/settings/deletion#data-retention-policies) for instructions on configuring data retention policies for workspaces. + +Refer to [Data Retention Policy API](/terraform/enterprise/api-docs/data-retention-policies#show-data-retention-policy) in the Terraform Enterprise documentation for details. + +## Create or update data retention policy + + +This endpoint is exclusive to Terraform Enterprise and is not available in HCP Terraform. + + +`POST /workspaces/:workspace_id/relationships/data-retention-policy` + +| Parameter | Description | +| --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `:workspace_id` | The workspace ID to update the data retention policy for. Obtain this from the [workspace settings](/terraform/enterprise/workspaces/settings) or by sending a `GET` request to the [`/workspaces`](#show-workspace) endpoint. | + +This endpoint creates a data retention policy for a workspace or updates the existing policy. +Refer to [Data Retention Policies](/terraform/enterprise/workspaces/settings/deletion#data-retention-policies) for additional information. + +Refer to [Data Retention Policy API](/terraform/enterprise/api-docs/data-retention-policies#create-or-update-data-retention-policy) in the Terraform Enterprise documentation for details. + +## Remove data retention policy + + +This endpoint is exclusive to Terraform Enterprise and is not available in HCP Terraform. + + +`DELETE /workspaces/:workspace_id/relationships/data-retention-policy` + +| Parameter | Description | +| --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `:workspace_id` | The workspace ID to remove the data retenetion policy for. Obtain this from the [workspace settings](/terraform/enterprise/workspaces/settings) or by sending a `GET` request to the [`/workspaces`](#show-workspace) endpoint. | + +This endpoint removes the data retention policy explicitly set on a workspace. +When no data retention policy is set for the workspace, the endpoint returns the default policy configured for the organization. Refer to [Data Retention Policies](/terraform/enterprise/users-teams-organizations/organizations#destruction-and-deletion) for instructions on configuring data retention policies for organizations. + +Read more about [workspace data retention policies](/terraform/enterprise/workspaces/settings/deletion#data-retention-policies). + +Refer to [Data Retention Policy API](/terraform/enterprise/api-docs/data-retention-policies#remove-data-retention-policy) in the Terraform Enterprise documentation for details. + +## Available Related Resources + +The GET endpoints above can optionally return related resources, if requested with [the `include` query parameter](/terraform/enterprise/api-docs#inclusion-of-related-resources). The following resource types are available: + +- `current_configuration_version` - The last configuration this workspace received, excluding plan-only configurations. Terraform uses this configuration for new runs, unless you provide a different one. +- `current_configuration_version.ingress_attributes` - The commit information for the current configuration version. +- `current_run` - Additional information about the current run. +- `current_run.configuration_version` - The configuration used in the current run. +- `current_run.configuration_version.ingress_attributes` - The commit information used in the current run. +- `current_run.plan` - The plan used in the current run. +- `locked_by` - The user, team, or run responsible for locking the workspace, if the workspace is currently locked. +- `organization` - The full organization record. +- `outputs` - The outputs for the most recently applied run. +- `project` - The full project record. +- `readme` - The most recent workspace README.md. diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/architectural-details/data-security.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/architectural-details/data-security.mdx new file mode 100644 index 0000000000..ccdd823720 --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/architectural-details/data-security.mdx @@ -0,0 +1,75 @@ +--- +page_title: Data security for Terraform Enterprise +description: >- + Learn about the parts of the Terraform Enterprise application that contain + sensitive data and the storage and encryption methods that HCP Terraform uses + to secure them. +source: terraform-docs-common +--- + +# Data security + +HCP Terraform takes the security of the data it manages +seriously. This table lists which parts of the HCP Terraform and Terraform Enterprise app can contain sensitive data, what storage is used, and what encryption is used. + +### HCP Terraform and Terraform Enterprise + +| Object | Storage | Encrypted | +| :----------------------------------- | :----------- | :----------------------- | +| Terraform Configuration (VCS data) | Blob Storage | Vault Transit Encryption | +| Private Registry Modules | Blob Storage | Vault Transit Encryption | +| Sentinel Policies | PostgreSQL | Vault Transit Encryption | +| Terraform/Environment Variables | PostgreSQL | Vault Transit Encryption | +| Terraform/Provider Credentials | PostgreSQL | Vault Transit Encryption | +| Terraform State File | Blob Storage | Vault Transit Encryption | +| Terraform Plan Result | Blob Storage | Vault Transit Encryption | +| Terraform Audit Trails | PostgreSQL | No | +| Organization/Workspace/Team Settings | PostgreSQL | No | + +### HCP Terraform and Terraform Enterprise Secrets + +| Object | Storage | Encrypted | +| :---------------------------- | :--------- | :----------------------- | +| Account Password | PostgreSQL | bcrypt | +| 2FA Recovery Codes | PostgreSQL | Vault Transit Encryption | +| SSH Keys | PostgreSQL | Vault Transit Encryption | +| User/Team/Organization Tokens | PostgreSQL | HMAC SHA512 | +| OAuth Client ID + Secret | PostgreSQL | Vault Transit Encryption | +| OAuth User Tokens | PostgreSQL | Vault Transit Encryption | + +### HCP Terraform Specific + +| Object | Storage | Encrypted | +| :--------------------------- | :-------- | :-------- | +| Cloud Data Backups | Amazon S3 | SSE-S3 | +| Cloud Copy of Backups for DR | Amazon S3 | SSE-S3 | + +Please see HashiCorp Cloud [subprocessors](https://www.hashicorp.com/trust/privacy/subprocessors) for third-parties engaged by HashiCorp to deliver cloud services. + +### Terraform Enterprise Specific + +| Object | Storage | Encrypted | +| :--------------------------- | :--------- | :----------------------- | +| Twilio Account Configuration | PostgreSQL | Vault Transit Encryption | +| SMTP Configuration | PostgreSQL | Vault Transit Encryption | +| SAML Configuration | PostgreSQL | Vault Transit Encryption | +| Vault Unseal Key | PostgreSQL | ChaCha20+Poly1305 | + +## Vault Transit Encryption + +The [Vault Transit Secret Engine](/vault/docs/secrets/transit) +handles encryption for data in-transit and is used when encrypting data from the +application to persistent storage. + +## Blob Storage Encryption + +All objects persisted to blob storage are symmetrically encrypted prior to being +written. Each object is encrypted with a unique encryption key. Objects are +encrypted using 128 bit AES in CTR mode. The key material is processed +through the [Vault transit secret engine](/vault/docs/secrets/transit), +which uses the default transit encryption cipher (AES-GCM with a 256-bit AES key +and a 96-bit nonce), and stored alongside the object. This pattern is called envelope encryption. + +The Vault transit secret engine's +[datakey generation](/vault/api-docs/secret/transit#generate-data-key) creates the encryption key material using bit material from the kernel's cryptographically secure pseudo-random +number generator (CSPRNG) as the `context` value. Blob storage encryption generates a unique key for each object and relies on envelope encryption, so Vault does not rotate the encryption key material for individual objects. The root encryption keys within the envelope encryption scheme are rotated automatically by HCP Terraform every 365 days. These keys are not automatically rotated within TFE. diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/cost-estimation/aws.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/cost-estimation/aws.mdx new file mode 100644 index 0000000000..f777c0c65e --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/cost-estimation/aws.mdx @@ -0,0 +1,158 @@ +--- +page_title: AWS resources included in cost estimation in Terraform Enterprise +description: Learn which AWS resources Terraform Enterprise includes in cost estimation. +source: terraform-docs-common +--- + +# AWS resources included in cost estimation + +HCP Terraform can estimate monthly costs for many AWS Terraform resources. + +-> **Note:** Terraform Enterprise requires AWS credentials to support cost estimation. These credentials are configured at the instance level, not the organization level. See the [Application Administration docs](/terraform/enterprise/admin/application/integration) for more details. + +## Supported Resources + +Cost estimation supports the following resources. Not all possible values for attributes of each resource are supported, ex. newer instance types or EBS volume types. + +| Resource | Incurs Cost | +| ------------------------------------------- | ----------- | +| `aws_alb` | X | +| `aws_autoscaling_group` | X | +| `aws_cloudhsm_v2_hsm` | X | +| `aws_cloudwatch_dashboard` | X | +| `aws_cloudwatch_metric_alarm` | X | +| `aws_db_instance` | X | +| `aws_dynamodb_table` | X | +| `aws_ebs_volume` | X | +| `aws_elasticache_cluster` | X | +| `aws_elasticsearch_domain` | X | +| `aws_elb` | X | +| `aws_instance` | X | +| `aws_kms_key` | X | +| `aws_lb` | X | +| `aws_rds_cluster_instance` | X | +| `aws_acm_certificate_validation` | | +| `aws_alb_listener` | | +| `aws_alb_listener_rule` | | +| `aws_alb_target_group` | | +| `aws_alb_target_group_attachment` | | +| `aws_api_gateway_api_key` | | +| `aws_api_gateway_deployment` | | +| `aws_api_gateway_integration` | | +| `aws_api_gateway_integration_response` | | +| `aws_api_gateway_method` | | +| `aws_api_gateway_method_response` | | +| `aws_api_gateway_resource` | | +| `aws_api_gateway_usage_plan_key` | | +| `aws_appautoscaling_policy` | | +| `aws_appautoscaling_target` | | +| `aws_autoscaling_lifecycle_hook` | | +| `aws_autoscaling_policy` | | +| `aws_cloudformation_stack` | | +| `aws_cloudfront_distribution` | | +| `aws_cloudfront_origin_access_identity` | | +| `aws_cloudwatch_event_rule` | | +| `aws_cloudwatch_event_target` | | +| `aws_cloudwatch_log_group` | | +| `aws_cloudwatch_log_metric_filter` | | +| `aws_cloudwatch_log_stream` | | +| `aws_cloudwatch_log_subscription_filter` | | +| `aws_codebuild_webhook` | | +| `aws_codedeploy_deployment_group` | | +| `aws_cognito_identity_provider` | | +| `aws_cognito_user_pool` | | +| `aws_cognito_user_pool_client` | | +| `aws_cognito_user_pool_domain` | | +| `aws_config_config_rule` | | +| `aws_customer_gateway` | | +| `aws_db_parameter_group` | | +| `aws_db_subnet_group` | | +| `aws_dynamodb_table_item` | | +| `aws_ecr_lifecycle_policy` | | +| `aws_ecr_repository_policy` | | +| `aws_ecs_cluster` | | +| `aws_ecs_task_definition` | | +| `aws_efs_mount_target` | | +| `aws_eip_association` | | +| `aws_elastic_beanstalk_application` | | +| `aws_elastic_beanstalk_application_version` | | +| `aws_elastic_beanstalk_environment` | | +| `aws_elasticache_parameter_group` | | +| `aws_elasticache_subnet_group` | | +| `aws_flow_log` | | +| `aws_iam_access_key` | | +| `aws_iam_account_alias` | | +| `aws_iam_account_password_policy` | | +| `aws_iam_group` | | +| `aws_iam_group_membership` | | +| `aws_iam_group_policy` | | +| `aws_iam_group_policy_attachment` | | +| `aws_iam_instance_profile` | | +| `aws_iam_policy` | | +| `aws_iam_policy_attachment` | | +| `aws_iam_role` | | +| `aws_iam_role_policy` | | +| `aws_iam_role_policy_attachment` | | +| `aws_iam_saml_provider` | | +| `aws_iam_service_linked_role` | | +| `aws_iam_user` | | +| `aws_iam_user_group_membership` | | +| `aws_iam_user_login_profile` | | +| `aws_iam_user_policy` | | +| `aws_iam_user_policy_attachment` | | +| `aws_iam_user_ssh_key` | | +| `aws_internet_gateway` | | +| `aws_key_pair` | | +| `aws_kms_alias` | | +| `aws_lambda_alias` | | +| `aws_lambda_event_source_mapping` | | +| `aws_lambda_function` | | +| `aws_lambda_layer_version` | | +| `aws_lambda_permission` | | +| `aws_launch_configuration` | | +| `aws_lb_listener` | | +| `aws_lb_listener_rule` | | +| `aws_lb_target_group` | | +| `aws_lb_target_group_attachment` | | +| `aws_network_acl` | | +| `aws_network_acl_rule` | | +| `aws_network_interface` | | +| `aws_placement_group` | | +| `aws_rds_cluster_parameter_group` | | +| `aws_route` | | +| `aws_route53_record` | | +| `aws_route53_zone_association` | | +| `aws_route_table` | | +| `aws_route_table_association` | | +| `aws_s3_bucket` | | +| `aws_s3_bucket_notification` | | +| `aws_s3_bucket_object` | | +| `aws_s3_bucket_policy` | | +| `aws_s3_bucket_public_access_block` | | +| `aws_security_group` | | +| `aws_security_group_rule` | | +| `aws_service_discovery_service` | | +| `aws_sfn_state_machine` | | +| `aws_sns_topic` | | +| `aws_sns_topic_subscription` | | +| `aws_sqs_queue` | | +| `aws_sqs_queue_policy` | | +| `aws_ssm_maintenance_window` | | +| `aws_ssm_maintenance_window_target` | | +| `aws_ssm_maintenance_window_task` | | +| `aws_ssm_parameter` | | +| `aws_subnet` | | +| `aws_volume_attachment` | | +| `aws_vpc` | | +| `aws_vpc_dhcp_options` | | +| `aws_vpc_dhcp_options_association` | | +| `aws_vpc_endpoint` | | +| `aws_vpc_endpoint_route_table_association` | | +| `aws_vpc_endpoint_service` | | +| `aws_vpc_ipv4_cidr_block_association` | | +| `aws_vpc_peering_connection_accepter` | | +| `aws_vpc_peering_connection_options` | | +| `aws_vpn_connection_route` | | +| `aws_waf_ipset` | | +| `aws_waf_rule` | | +| `aws_waf_web_acl` | | diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/cost-estimation/azure.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/cost-estimation/azure.mdx new file mode 100644 index 0000000000..5647698512 --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/cost-estimation/azure.mdx @@ -0,0 +1,56 @@ +--- +page_title: Azure resources included in cost estimation in Terraform Enterprise +description: Learn which Azure resources Terraform Enterprise includes in cost estimation. +source: terraform-docs-common +--- + +# Azure resources included in cost estimation + +HCP Terraform can estimate monthly costs for many Azure Terraform resources. + +-> **Note:** Terraform Enterprise requires Azure credentials to support cost estimation. These credentials are configured at the instance level, not the organization level. See the [Application Administration docs](/terraform/enterprise/admin/application/integration) for more details. + +## Supported Resources + +Cost estimation supports the following resources. Not all possible values for attributes of each resource are supported, ex. newer VM sizes or managed disk types. + +| Resource | Incurs Cost | +| ------------------------------------------------------ | ----------- | +| `azurerm_app_service_custom_hostname_binding` | X | +| `azurerm_app_service_environment` | X | +| `azurerm_app_service_plan` | X | +| `azurerm_app_service_virtual_network_swift_connection` | X | +| `azurerm_cosmosdb_sql_database` | X | +| `azurerm_databricks_workspace` | X | +| `azurerm_firewall` | X | +| `azurerm_hdinsight_hadoop_cluster` | X | +| `azurerm_hdinsight_hbase_cluster` | X | +| `azurerm_hdinsight_interactive_query_cluster` | X | +| `azurerm_hdinsight_kafka_cluster` | X | +| `azurerm_hdinsight_spark_cluster` | X | +| `azurerm_integration_service_environment` | X | +| `azurerm_linux_virtual_machine` | X | +| `azurerm_linux_virtual_machine_scale_set` | X | +| `azurerm_managed_disk` | X | +| `azurerm_mariadb_server` | X | +| `azurerm_mssql_elasticpool` | X | +| `azurerm_mysql_server` | X | +| `azurerm_postgresql_server` | X | +| `azurerm_sql_database` | X | +| `azurerm_virtual_machine` | X | +| `azurerm_virtual_machine_scale_set` | X | +| `azurerm_windows_virtual_machine` | X | +| `azurerm_windows_virtual_machine_scale_set` | X | +| `azurerm_app_service` | | +| `azurerm_cosmosdb_account` | | +| `azurerm_cosmosdb_sql_container` | | +| `azurerm_cosmosdb_table` | | +| `azurerm_mysql_database` | | +| `azurerm_network_security_group` | | +| `azurerm_postgresql_database` | | +| `azurerm_resource_group` | | +| `azurerm_sql_server` | | +| `azurerm_sql_virtual_network_rule` | | +| `azurerm_subnet` | | +| `azurerm_subnet_route_table_association` | | +| `azurerm_virtual_network` | | diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/cost-estimation/gcp.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/cost-estimation/gcp.mdx new file mode 100644 index 0000000000..e1660de815 --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/cost-estimation/gcp.mdx @@ -0,0 +1,42 @@ +--- +page_title: GCP resources included in cost estimation in Terraform Enterprise +description: Learn which GCP resources Terraform Enterprise includes in cost estimation. +source: terraform-docs-common +--- + +# GCP resources included in cost estimation + +HCP Terraform can estimate monthly costs for many GCP Terraform resources. + +-> **Note:** Terraform Enterprise requires GCP credentials to support cost estimation. These credentials are configured at the instance level, not the organization level. See the [Application Administration docs](/terraform/enterprise/admin/application/integration) for more details. + +## Supported Resources + +Cost estimation supports the following resources. Not all possible values for attributes of each resource are supported, ex. new or custom machine types. + +| Resource | Incurs Cost | +| --------------------------------------- | ----------- | +| `google_compute_disk` | X | +| `google_compute_instance` | X | +| `google_sql_database_instance` | X | +| `google_billing_account_iam_member` | | +| `google_compute_address` | | +| `google_compute_subnetwork_iam_member` | | +| `google_folder_iam_member` | | +| `google_folder_iam_policy` | | +| `google_kms_crypto_key_iam_member` | | +| `google_kms_key_ring_iam_member` | | +| `google_kms_key_ring_iam_policy` | | +| `google_organization_iam_member` | | +| `google_project` | | +| `google_project_iam_member` | | +| `google_project_iam_policy` | | +| `google_project_service` | | +| `google_pubsub_subscription_iam_member` | | +| `google_pubsub_subscription_iam_policy` | | +| `google_pubsub_topic_iam_member` | | +| `google_service_account` | | +| `google_service_account_iam_member` | | +| `google_service_account_key` | | +| `google_storage_bucket_iam_member` | | +| `google_storage_bucket_iam_policy` | | diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/cost-estimation/index.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/cost-estimation/index.mdx new file mode 100644 index 0000000000..d81c7e6c17 --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/cost-estimation/index.mdx @@ -0,0 +1,58 @@ +--- +page_title: Cost estimation overview for Terraform Enterprise +description: >- + Terraform Enterprise can estimate the total cost and delta of resources from + your Terraform configuration. Use cost estimation to get hourly and monthly + cost estimates for each resource. +source: terraform-docs-common +--- + +# Cost estimation overview + +> **Hands-on:** Try the [Control Costs with Policies](/terraform/tutorials/cloud-get-started/cost-estimation) tutorial to practice enabling cost estimation and define a policy to check the total monthly delta. + +HCP Terraform provides cost estimates for many resources found in your Terraform configuration. For each resource an hourly and monthly cost is shown, along with the monthly delta. The total cost and delta of all estimable resources is also shown. + +## Enabling Cost Estimation + +HCP Terraform disables cost estimation by default. To enable cost estimation: + +1. Sign in to [HCP Terraform](https://app.terraform.io/) or Terraform Enterprise, then navigate to the organization where you want to enable cost estimation. +2. Choose **Settings** from the sidebar, then **Cost Estimation**. +3. Toggle the **Enable cost estimation for all workspaces** setting. +4. Click **Update settings**. + +## Viewing a Cost Estimate + +When enabled, HCP Terraform performs a cost estimate for every run. Estimated costs appear in the run UI as an extra run phase, between the plan and apply. + +The estimate displays a total monthly cost by default; you can expand the estimate to see an itemized list of resource costs, as well as the list of unestimated resources. + +Note that this is just an estimate; some resources don't have cost information available or have unpredictable usage-based pricing. Supported resources are listed in this document's sub-pages. + +## Verifying Costs in Policies + +You can use a Sentinel policy to validate your configuration's cost estimates using the [`tfrun`](/terraform/enterprise/policy-enforcement/import-reference/tfrun) import. The example policy below checks that the new cost delta is no more than $100. A new `t3.nano` instance should be well below that. A `decimal` import is available for more accurate math when working with currency numbers. + +```python +import "tfrun" +import "decimal" + +delta_monthly_cost = decimal.new(tfrun.cost_estimate.delta_monthly_cost) + +if delta_monthly_cost.greater_than(100) { + print("This policy prevents a user from increasing their spending by more than $100 per month in a single run without a warning.") +} + +main = rule { + delta_monthly_cost.less_than(100) +} +``` + +## Supported Resources + +Cost estimation in HCP Terraform supports Terraform resources within three major cloud providers. + +- [AWS](/terraform/enterprise/cost-estimation/aws) +- [GCP](/terraform/enterprise/cost-estimation/gcp) +- [Azure](/terraform/enterprise/cost-estimation/azure) diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/integrations/aws-service-catalog/index.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/integrations/aws-service-catalog/index.mdx new file mode 100644 index 0000000000..e06a14e929 --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/integrations/aws-service-catalog/index.mdx @@ -0,0 +1,27 @@ +--- +page_title: Terraform Enterprise for AWS Service Catalog overview +description: >- + Terraform Enterprise for AWS Service Catalog lets you provision infrastructure + with HCP Terraform inside AWS Service Catalog. +source: terraform-docs-common +--- + +# HCP Terraform for AWS Service Catalog overview + +This integration allows administrators to curate a portfolio of pre-approved Terraform configurations on AWS Service Catalog. This enables end users like engineers, database administrators, and data scientists to deploy these Terraform configurations with a single action from the AWS interface. By combining HCP Terraform with AWS Service Catalog, we’re combining a self-service interface that many customers are familiar with, AWS Service Catalog, with the existing workflows and policy guardrails of HCP Terraform. + + + +@include 'tfc-package-callouts/aws-service-catalog.mdx' + + + +## Installation & Configuration + +To start using this integration, you'll need to install the [AWS Service Catalog Engine for Terraform Cloud](https://github.com/hashicorp/aws-service-catalog-engine-for-tfc) provided by HashiCorp on GitHub by following the [setup instructions](https://github.com/hashicorp/aws-service-catalog-engine-for-tfc#getting-started) provided in the README. If you run into any setup troubles along the way, the README also includes [troubleshooting steps](https://github.com/hashicorp/aws-service-catalog-engine-for-tfc#troubleshooting) that should help resolve common issues that you may encounter. + +With the engine installed, the necessary code and infrastructure to integrate the HCP Terraform engine with AWS Service Catalog will automatically be configured. The setup can be completed in just a few minutes, and it only needs to be done once. Once the setup is complete, you can immediately start using AWS Service Catalog to develop and manage AWS Service Catalog products, and make them accessible to your end users across all your accounts. + +## Usage + +You can access this new feature through the AWS Service Catalog console in any AWS regions where AWS Service Catalog is supported and follow the AWS Service Catalog Administrator Guide to [create your first Terraform product](https://docs.aws.amazon.com/servicecatalog/latest/adminguide/getstarted-product-Terraform.html). diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/integrations/index.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/integrations/index.mdx new file mode 100644 index 0000000000..dc8851bfba --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/integrations/index.mdx @@ -0,0 +1,26 @@ +--- +page_title: Integrations overview for Terraform Enterprise +description: >- + Use Terraform Enterprise integrations to connect HCP Terraform with + third-party platforms and systems. +source: terraform-docs-common +--- + +# Overview + +The HCP Terraform ecosystem features a variety of integrations to let HCP +Terraform connect with third-party systems and platforms. The following list +contains HashiCorp's official HCP Terraform integrations, which use HCP +Terraform's native APIs: + +- The [HCP Terraform Operator for Kubernetes](/terraform/enterprise/integrations/kubernetes) integration can manage HCP Terraform resources with Kubernetes custom resources. +- The [ServiceNow Service Catalog for Terraform](/terraform/enterprise/integrations/service-now/service-catalog-terraform) lets you provision self-serve infrastructure using ServiceNow. +- The [ServiceNow Service Graph Connector for Terraform](/terraform/enterprise/integrations/service-now/service-graph) integration lets you securely import HCP Terraform resources into your ServiceNow instance. +- The [HCP Terraform for Splunk](/terraform/enterprise/integrations/splunk) integration lets you pull HCP Terraform logs into Splunk. +- The [HCP Terraform for AWS Service Catalog](/terraform/enterprise/integrations/aws-service-catalog) integration lets you create pre-approved Terraform configurations on the AWS Service Catalog. + +If the platform you want to integrate HCP Terraform with does not have an +official integration, you can build a custom run task to integrate with a tool +of your choice. Run tasks can access plan details, display custom messages in +the run pipeline, and prevent runs from applying. Learn more about [Run +tasks](/terraform/enterprise/integrations/run-tasks). diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/integrations/kubernetes/annotations-and-labels.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/integrations/kubernetes/annotations-and-labels.mdx new file mode 100644 index 0000000000..e38bdb08c1 --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/integrations/kubernetes/annotations-and-labels.mdx @@ -0,0 +1,26 @@ +--- +page_title: Terraform Enterprise Operator for Kubernetes annotations and labels +description: >- + Use annotations and labels with the Terraform Enterprise Operator for + Kubernetes to manage Terraform runs. +source: terraform-docs-common +--- + +# HCP Terraform Operator for Kubernetes annotations and labels + +This topic contains reference information about the annotations and labels the HCP Terraform and Terraform Enterprise operators use for Kubernetes. + +## Annotations + +| Annotation key | Target resources | Possible values | Description | +| -------------------------------------------------- | ---------------- | --------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `workspace.app.terraform.io/run-new` | Workspace | `"true"` | Set this annotation to `"true"` to trigger a new run. Example: `kubectl annotate workspace workspace.app.terraform.io/run-new="true"`. | +| `workspace.app.terraform.io/run-type` | Workspace | `plan`, `apply`, `refresh` | Specifies the run type. Changing this annotation does not start a new run. Refer to [Run Modes and Options](/terraform/enterprise/run/modes-and-options) for more information. Defaults to `"plan"`. | +| `workspace.app.terraform.io/run-terraform-version` | Workspace | Any valid Terraform version | Specifies the Terraform version to use. Changing this annotation does not start a new run. Only valid when the annotation `workspace.app.terraform.io/run-type` is set to `plan`. Defaults to the Workspace version. | + +## Labels + +| Label key | Target resources | Possible values | Description | +| -------------------------------------- | ---------------- | ------------------------ | ------------------------------------------------------------------------------------------- | +| `agentpool.app.terraform.io/pool-name` | Pod[Agent] | Any valid AgentPool name | Associate the resource with a specific agent pool by specifying the name of the agent pool. | +| `agentpool.app.terraform.io/pool-id` | Pod[Agent] | Any valid AgentPool ID | Associate the resource with a specific agent pool by specifying the ID of the agent pool. | diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/integrations/kubernetes/api-reference.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/integrations/kubernetes/api-reference.mdx new file mode 100644 index 0000000000..1c1be52818 --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/integrations/kubernetes/api-reference.mdx @@ -0,0 +1,796 @@ +--- +page_title: Terraform Enterprise Operator for Kubernetes API reference +description: >- + Use the Terraform Enterprise Operator for Kubernetes API to manage HCP + Terraform workspaces, modules, and agent pools. +source: terraform-docs-common +--- + +# HCP Terraform Operator for Kubernetes API reference + +## Packages + +- [app.terraform.io/v1alpha2](#appterraformiov1alpha2) + +## app.terraform.io/v1alpha2 + +Package v1alpha2 contains API Schema definitions for the app v1alpha2 API group. + +### Resource Types + +- [AgentPool](#agentpool) +- [Module](#module) +- [Project](#project) +- [Workspace](#workspace) + +#### AgentDeployment + +_Appears in:_ + +- [AgentPoolSpec](#agentpoolspec) + +| Field | Description | +| -------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------- | +| `replicas` _integer_ | | +| `spec` _[PodSpec](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.27/#podspec-v1-core)_ | | +| `annotations` _object (keys:string, values:string)_ | The annotations that the operator will apply to the pod template in the deployment. | +| `labels` _object (keys:string, values:string)_ | The labels that the operator will apply to the pod template in the deployment. | + +#### AgentDeploymentAutoscaling + +AgentDeploymentAutoscaling allows you to configure the operator to scale the deployment for an AgentPool up and down to meet demand. + +_Appears in:_ + +- [AgentPoolSpec](#agentpoolspec) + +| Field | Description | +| -------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `maxReplicas` _integer_ | MaxReplicas is the maximum number of replicas for the Agent deployment. | +| `minReplicas` _integer_ | MinReplicas is the minimum number of replicas for the Agent deployment. | +| `targetWorkspaces` _[TargetWorkspace](#targetworkspace)_ | TargetWorkspaces is a list of HCP Terraform Workspaces which the agent pool should scale up to meet demand. When this field is omitted the autoscaler will target all workspaces that are associated with the AgentPool. | +| `cooldownPeriodSeconds` _integer_ | CooldownPeriodSeconds is the time to wait between scaling events. Defaults to 300. | +| `cooldownPeriod` _[AgentDeploymentAutoscalingCooldownPeriod](#agentdeploymentautoscalingcooldownperiod)_ | CoolDownPeriod configures the period to wait between scaling up and scaling down | + +#### AgentDeploymentAutoscalingCooldownPeriod + +AgentDeploymentAutoscalingCooldownPeriod configures the period to wait between scaling up and scaling down, + +_Appears in:_ + +- [AgentDeploymentAutoscaling](#agentdeploymentautoscaling) + +| Field | Description | +| ---------------------------- | --------------------------------------------------------- | +| `scaleUpSeconds` _integer_ | ScaleUpSeconds is the time to wait before scaling up. | +| `scaleDownSeconds` _integer_ | ScaleDownSeconds is the time to wait before scaling down. | + +#### AgentDeploymentAutoscalingStatus + +AgentDeploymentAutoscalingStatus + +_Appears in:_ + +- [AgentPoolStatus](#agentpoolstatus) + +| Field | Description | +| -------------------------------------------------------------------------------------------------------------- | ----------------------------------- | +| `desiredReplicas` _integer_ | Desired number of agent replicas | +| `lastScalingEvent` _[Time](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.27/#time-v1-meta)_ | Last time the agent pool was scaled | + +#### AgentPool + +AgentPool manages HCP Terraform Agent Pools, HCP Terraform Agent Tokens and can perform HCP Terraform Agent scaling. + +More information: + +- [Manage agent pools](/terraform/cloud-docs/agents/agent-pools) +- [Agent API tokens](/terraform/enterprise/users-teams-organizations/api-tokens#agent-api-tokens) +- [HCP Terraform agents](/terraform/cloud-docs/agents) + +| Field | Description | +| ------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `apiVersion` _string_ | `app.terraform.io/v1alpha2` | +| `kind` _string_ | `AgentPool` | +| `kind` _string_ | Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. [More information](https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds) | +| `apiVersion` _string_ | APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. [More information](https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources) | +| `metadata` _[ObjectMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.27/#objectmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | +| `spec` _[AgentPoolSpec](#agentpoolspec)_ | | + +#### AgentPoolSpec + +AgentPoolSpec defines the desired state of AgentPool. + +_Appears in:_ + +- [AgentPool](#agentpool) + +| Field | Description | +| ------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | +| `name` _string_ | Agent Pool name. [More information](/terraform/cloud-docs/agents/agent-pools). | +| `organization` _string_ | Organization name where the Workspace will be created. [More information](/terraform/enterprise/users-teams-organizations/organizations). | +| `token` _[Token](#token)_ | API Token to be used for API calls. | +| `agentTokens` _[AgentToken](#agenttoken) array_ | List of the agent tokens to generate. | +| `agentDeployment` _[AgentDeployment](#agentdeployment)_ | Agent deployment settings | +| `autoscaling` _[AgentDeploymentAutoscaling](#agentdeploymentautoscaling)_ | Agent deployment settings | + +#### AgentToken + +Agent Token is a secret token that a HCP Terraform Agent is used to connect to the HCP Terraform Agent Pool. In `spec` only the field `Name` is allowed, the rest are used in `status`. + +More information: + +- [HCP Terraform agents](/terraform/cloud-docs/agents) + +_Appears in:_ + +- [AgentPoolSpec](#agentpoolspec) +- [AgentPoolStatus](#agentpoolstatus) + +| Field | Description | +| ---------------------- | ------------------------------------------------ | +| `name` _string_ | Agent Token name. | +| `id` _string_ | Agent Token ID. | +| `createdAt` _integer_ | Timestamp of when the agent token was created. | +| `lastUsedAt` _integer_ | Timestamp of when the agent token was last used. | + +#### ConfigurationVersionStatus + +A configuration version is a resource used to reference the uploaded configuration files. + +More information: + +- [Configuration versions API reference](/terraform/enterprise/api-docs/configuration-versions) +- [The API-driven run workflow](/terraform/enterprise/run/api) + +_Appears in:_ + +- [ModuleStatus](#modulestatus) + +| Field | Description | +| ------------- | ------------------------- | +| `id` _string_ | Configuration Version ID. | + +#### ConsumerWorkspace + +ConsumerWorkspace allows access to the state for specific workspaces within the same organization. Only one of the fields `ID` or `Name` is allowed. At least one of the fields `ID` or `Name` is mandatory. + +More information: + +- [Remote state access controls](/terraform/enterprise/workspaces/state#remote-state-access-controls) + +_Appears in:_ + +- [RemoteStateSharing](#remotestatesharing) + +| Field | Description | +| --------------- | -------------------------------------------------------------- | +| `id` _string_ | Consumer Workspace ID. Must match pattern: `^ws-[a-zA-Z0-9]+$` | +| `name` _string_ | Consumer Workspace name. | + +#### CustomPermissions + +Custom permissions let you assign specific, finer-grained permissions to a team than the broader fixed permission sets provide. + +More information: + +- [Custom workspace permissions](/terraform/enterprise/users-teams-organizations/permissions#custom-workspace-permissions) + +_Appears in:_ + +- [TeamAccess](#teamaccess) + +| Field | Description | +| ---------------------------- | ------------------------------------------------------------------------------------------------------------ | +| `runs` _string_ | Run access. Must be one of the following values: `apply`, `plan`, `read`. Default: `read`. | +| `runTasks` _boolean_ | Manage Workspace Run Tasks. Default: `false`. | +| `sentinel` _string_ | Download Sentinel mocks. Must be one of the following values: `none`, `read`. Default: `none`. | +| `stateVersions` _string_ | State access. Must be one of the following values: `none`, `read`, `read-outputs`, `write`. Default: `none`. | +| `variables` _string_ | Variable access. Must be one of the following values: `none`, `read`, `write`. Default: `none`. | +| `workspaceLocking` _boolean_ | Lock/unlock workspace. Default: `false`. | + +#### CustomProjectPermissions + +Custom permissions let you assign specific, finer-grained permissions to a team than the broader fixed permission sets provide. + +More information: + +- [Custom project permissions](/terraform/enterprise/users-teams-organizations/permissions#custom-project-permissions) +- [General workspace permissions](/terraform/enterprise/users-teams-organizations/permissions#general-workspace-permissions) + +_Appears in:_ + +- [ProjectTeamAccess](#projectteamaccess) + +| Field | Description | +| ----------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `projectAccess` _[ProjectSettingsPermissionType](#projectsettingspermissiontype)_ | Project access. Must be one of the following values: `delete`, `read`, `update`. Default: `read`. | +| `teamManagement` _[ProjectTeamsPermissionType](#projectteamspermissiontype)_ | Team management. Must be one of the following values: `manage`, `none`, `read`. Default: `none`. | +| `createWorkspace` _boolean_ | Allow users to create workspaces in the project. This grants read access to all workspaces in the project. Default: `false`. | +| `deleteWorkspace` _boolean_ | Allows users to delete workspaces in the project. Default: `false`. | +| `moveWorkspace` _boolean_ | Allows users to move workspaces out of the project. A user must have this permission on both the source and destination project to successfully move a workspace from one project to another. Default: `false`. | +| `lockWorkspace` _boolean_ | Allows users to manually lock the workspace to temporarily prevent runs. When a workspace's execution mode is set to "local", users must have this permission to perform local CLI runs using the workspace's state. Default: `false`. | +| `runs` _[WorkspaceRunsPermissionType](#workspacerunspermissiontype)_ | Run access. Must be one of the following values: `apply`, `plan`, `read`. Default: `read`. | +| `runTasks` _boolean_ | Manage Workspace Run Tasks. Default: `false`. | +| `sentinelMocks` _[WorkspaceSentinelMocksPermissionType](#workspacesentinelmockspermissiontype)_ | Download Sentinel mocks. Must be one of the following values: `none`, `read`. Default: `none`. | +| `stateVersions` _[WorkspaceStateVersionsPermissionType](#workspacestateversionspermissiontype)_ | State access. Must be one of the following values: `none`, `read`, `read-outputs`, `write`. Default: `none`. | +| `variables` _[WorkspaceVariablesPermissionType](#workspacevariablespermissiontype)_ | Variable access. Must be one of the following values: `none`, `read`, `write`. Default: `none`. | + +#### DeletionPolicy + +_Underlying type:_ _string_ + +DeletionPolicy defines the strategy the Kubernetes operator uses when you delete a resource, either manually or by a system event. + +You must use one of the following values: + +- `retain`: When you delete the custom resource, the operator does not delete the workspace. +- `soft`: Attempts to delete the associated workspace only if it does not contain any managed resources. +- `destroy`: Executes a destroy operation to remove all resources managed by the associated workspace. Once the destruction of these resources is successful, the operator deletes the workspace, and then deletes the custom resource. +- `force`: Forcefully and immediately deletes the workspace and the custom resource. + +_Appears in:_ + +- [WorkspaceSpec](#workspacespec) + +#### Module + +Module implements API-driven Run Workflows. + +More information: + +- [The API-driven run workflow](/terraform/enterprise/run/api) + +| Field | Description | +| ------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `apiVersion` _string_ | `app.terraform.io/v1alpha2` | +| `kind` _string_ | `Module` | +| `kind` _string_ | Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. [More information](https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds) | +| `apiVersion` _string_ | APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. [More information](https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources) | +| `metadata` _[ObjectMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.27/#objectmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | +| `spec` _[ModuleSpec](#modulespec)_ | | + +#### ModuleOutput + +Module outputs to store in ConfigMap(non-sensitive) or Secret(sensitive). + +_Appears in:_ + +- [ModuleSpec](#modulespec) + +| Field | Description | +| --------------------- | ----------------------------------------------------------------- | +| `name` _string_ | Output name must match with the module output. | +| `sensitive` _boolean_ | Specify whether or not the output is sensitive. Default: `false`. | + +#### ModuleSource + +Module source and version to execute. + +_Appears in:_ + +- [ModuleSpec](#modulespec) + +| Field | Description | +| ------------------ | ------------------------------------------------------------------------------------------- | +| `source` _string_ | Non local Terraform module source. [More information](/terraform/language/modules/sources). | +| `version` _string_ | Terraform module version. | + +#### ModuleSpec + +ModuleSpec defines the desired state of Module. + +_Appears in:_ + +- [Module](#module) + +| Field | Description | +| ----------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `organization` _string_ | Organization name where the Workspace will be created. [More information](/terraform/enterprise/users-teams-organizations/organizations). | +| `token` _[Token](#token)_ | API Token to be used for API calls. | +| `module` _[ModuleSource](#modulesource)_ | Module source and version to execute. | +| `workspace` _[ModuleWorkspace](#moduleworkspace)_ | Workspace to execute the module. | +| `name` _string_ | Name of the module that will be uploaded and executed. Default: `this`. | +| `variables` _[ModuleVariable](#modulevariable) array_ | Variables to pass to the module, they must exist in the Workspace. | +| `outputs` _[ModuleOutput](#moduleoutput) array_ | Module outputs to store in ConfigMap(non-sensitive) or Secret(sensitive). | +| `destroyOnDeletion` _boolean_ | Specify whether or not to execute a Destroy run when the object is deleted from the Kubernetes. Default: `false`. | +| `restartedAt` _string_ | Allows executing a new Run without changing any Workspace or Module attributes. Example: ``kubectl patch KIND NAME --type=merge --patch '{"spec": \{"restartedAt": "'\`date -u -Iseconds\`'"\}\}'`` | + +#### ModuleVariable + +Variables to pass to the module. + +_Appears in:_ + +- [ModuleSpec](#modulespec) + +| Field | Description | +| --------------- | ------------------------------------------ | +| `name` _string_ | Variable name must exist in the Workspace. | + +#### ModuleWorkspace + +Workspace to execute the module. Only one of the fields `ID` or `Name` is allowed. At least one of the fields `ID` or `Name` is mandatory. + +_Appears in:_ + +- [ModuleSpec](#modulespec) + +| Field | Description | +| --------------- | ------------------------------------------------------------ | +| `id` _string_ | Module Workspace ID. Must match pattern: `^ws-[a-zA-Z0-9]+$` | +| `name` _string_ | Module Workspace Name. | + +#### Notification + +Notifications allow you to send messages to other applications based on run and workspace events. + +More information: + +- [Workspace notifications](/terraform/enterprise/workspaces/settings/notifications) + +_Appears in:_ + +- [WorkspaceSpec](#workspacespec) + +| Field | Description | +| -------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `name` _string_ | Notification name. | +| `type` _[NotificationDestinationType](#notificationdestinationtype)_ | The type of the notification. Must be one of the following values: `email`, `generic`, `microsoft-teams`, `slack`. | +| `enabled` _boolean_ | Whether the notification configuration should be enabled or not. Default: `true`. | +| `token` _string_ | The token of the notification. | +| `triggers` _[NotificationTrigger](#notificationtrigger) array_ | The list of run events that will trigger notifications. Trigger represents the different TFC notifications that can be sent as a run's progress transitions between different states. There are two categories of triggers: - Health Events: `assessment:check_failure`, `assessment:drifted`, `assessment:failed`. - Run Events: `run:applying`, `run:completed`, `run:created`, `run:errored`, `run:needs_attention`, `run:planning`. | +| `url` _string_ | The URL of the notification. Must match pattern: `^https?://.*` | +| `emailAddresses` _string array_ | The list of email addresses that will receive notification emails. It is only available for Terraform Enterprise users. It is not available in HCP Terraform. | +| `emailUsers` _string array_ | The list of users belonging to the organization that will receive notification emails. | + +#### NotificationTrigger + +_Underlying type:_ _string_ + +NotificationTrigger represents the different TFC notifications that can be sent as a run's progress transitions between different states. This must be aligned with go-tfe type `NotificationTriggerType`. + +Must be one of the following values: `run:applying`, `assessment:check_failure`, `run:completed`, `run:created`, `assessment:drifted`, `run:errored`, `assessment:failed`, `run:needs_attention`, `run:planning`. + +_Appears in:_ + +- [Notification](#notification) + +#### OutputStatus + +Outputs status. + +_Appears in:_ + +- [ModuleStatus](#modulestatus) + +| Field | Description | +| ---------------- | -------------------------------------------------- | +| `runID` _string_ | Run ID of the latest run that updated the outputs. | + +#### PlanStatus + +_Appears in:_ + +- [WorkspaceStatus](#workspacestatus) + +| Field | Description | +| --------------------------- | ------------------------------------------------------- | +| `id` _string_ | Latest plan-only/speculative plan HCP Terraform run ID. | +| `terraformVersion` _string_ | The version of Terraform to use for this run. | + +#### Project + +Project manages HCP Terraform Projects. + +More information: + +- [Manage projects](/terraform/enterprise/projects/manage) + +| Field | Description | +| ------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `apiVersion` _string_ | `app.terraform.io/v1alpha2` | +| `kind` _string_ | `Project` | +| `kind` _string_ | Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. [More information](https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds) | +| `apiVersion` _string_ | APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. [More information](https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources) | +| `metadata` _[ObjectMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.27/#objectmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | +| `spec` _[ProjectSpec](#projectspec)_ | | + +#### ProjectSpec + +ProjectSpec defines the desired state of Project. + +More information: + +- [Manage projects](/terraform/enterprise/workspaces/organize-workspaces-with-projects) + +_Appears in:_ + +- [Project](#project) + +| Field | Description | +| ------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `organization` _string_ | Organization name where the Workspace will be created. [More information](/terraform/enterprise/users-teams-organizations/organizations). | +| `token` _[Token](#token)_ | API Token to be used for API calls. | +| `name` _string_ | Name of the Project. | +| `teamAccess` _[ProjectTeamAccess](#projectteamaccess) array_ | HCP Terraform's access model is team-based. In order to perform an action within a HCP Terraform organization, users must belong to a team that has been granted the appropriate permissions. You can assign project-specific permissions to teams. More information: [Manage projects](/terraform/enterprise/workspaces/organize-workspaces-with-projects#permissions) and [Project permissions](/terraform/enterprise/users-teams-organizations/permissions#project-permissions). | + +#### ProjectTeamAccess + +HCP Terraform's access model is team-based. In order to perform an action within a HCP Terraform organization, users must belong to a team that has been granted the appropriate permissions. You can assign project-specific permissions to teams. + +More information: + +- [Manage projects](/terraform/enterprise/workspaces/organize-workspaces-with-projects#permissions) +- [Project permissions](/terraform/enterprise/users-teams-organizations/permissions#project-permissions) + +_Appears in:_ + +- [ProjectSpec](#projectspec) + +| Field | Description | +| ---------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `team` _[Team](#team)_ | Team to grant access. [More information](/terraform/enterprise/users-teams-organizations/teams). | +| `access` _[TeamProjectAccessType](#teamprojectaccesstype)_ | There are two ways to choose which permissions a given team has on a project: fixed permission sets, and custom permissions. Must be one of the following values: `admin`, `custom`, `maintain`, `read`, `write`. More information: [Project permissions](/terraform/enterprise/users-teams-organizations/permissions#project-permissions) and [General project permissions](/terraform/enterprise/users-teams-organizations/permissions#general-project-permissions). | +| `custom` _[CustomProjectPermissions](#customprojectpermissions)_ | Custom permissions let you assign specific, finer-grained permissions to a team than the broader fixed permission sets provide. [More information](/terraform/enterprise/users-teams-organizations/permissions#custom-project-permissions). | + +#### RemoteStateSharing + +RemoteStateSharing allows remote state access between workspaces. By default, new workspaces in HCP Terraform do not allow other workspaces to access their state. + +More information: + +- [Accessing state from other workspaces](/terraform/enterprise/workspaces/state#accessing-state-from-other-workspaces) + +_Appears in:_ + +- [WorkspaceSpec](#workspacespec) + +| Field | Description | +| ------------------------------------------------------------ | -------------------------------------------------------------------------------------------- | +| `allWorkspaces` _boolean_ | Allow access to the state for all workspaces within the same organization. Default: `false`. | +| `workspaces` _[ConsumerWorkspace](#consumerworkspace) array_ | Allow access to the state for specific workspaces within the same organization. | + +#### RunStatus + +_Appears in:_ + +- [ModuleStatus](#modulestatus) +- [WorkspaceStatus](#workspacestatus) + +| Field | Description | +| ------------------------------- | ------------------------------------------------------- | +| `id` _string_ | Current(both active and finished) HCP Terraform run ID. | +| `configurationVersion` _string_ | The configuration version of this run. | +| `outputRunID` _string_ | Run ID of the latest run that could update the outputs. | + +#### RunTrigger + +RunTrigger allows you to connect this workspace to one or more source workspaces. These connections allow runs to queue automatically in this workspace on successful apply of runs in any of the source workspaces. + +Only one of the fields `ID` or `Name` is allowed. + +At least one of the fields `ID` or `Name` is mandatory. + +More information: + +- [Run triggers](/terraform/enterprise/workspaces/settings/run-triggers) + +_Appears in:_ + +- [WorkspaceSpec](#workspacespec) + +| Field | Description | +| --------------- | ------------------------------------------------------------ | +| `id` _string_ | Source Workspace ID. Must match pattern: `^ws-[a-zA-Z0-9]+$` | +| `name` _string_ | Source Workspace Name. | + +#### SSHKey + +SSH key used to clone Terraform modules. + +Only one of the fields `ID` or `Name` is allowed. + +At least one of the fields `ID` or `Name` is mandatory. + +More information: + +- [Use SSH Keys for cloning modules](/terraform/enterprise/workspaces/settings/ssh-keys) + +_Appears in:_ + +- [WorkspaceSpec](#workspacespec) + +| Field | Description | +| --------------- | ------------------------------------------------------- | +| `id` _string_ | SSH key ID. Must match pattern: `^sshkey-[a-zA-Z0-9]+$` | +| `name` _string_ | SSH key name. | + +#### Tag + +_Underlying type:_ _string_ + +Tags allows you to correlate, organize, and even filter workspaces based on the assigned tags. + +Tags must be one or more characters; can include letters, numbers, colons, hyphens, and underscores; and must begin and end with a letter or number. + +Must match pattern: `^[A-Za-z0-9][A-Za-z0-9:_-]*$` + +_Appears in:_ + +- [WorkspaceSpec](#workspacespec) + +#### TargetWorkspace + +TargetWorkspace is the name or ID of the workspace you want autoscale against. + +_Appears in:_ + +- [AgentDeploymentAutoscaling](#agentdeploymentautoscaling) + +| Field | Description | +| ----------------------- | --------------------------------------------------------------------------------------- | +| `id` _string_ | Workspace ID | +| `name` _string_ | Workspace Name | +| `wildcardName` _string_ | Wildcard Name to match match workspace names using `*` on name suffix, prefix, or both. | + +#### Team + +Teams are groups of HCP Terraform users within an organization. If a user belongs to at least one team in an organization, they are considered a member of that organization. + +Only one of the fields `ID` or `Name` is allowed. + +At least one of the fields `ID` or `Name` is mandatory. + +More information: + +- [Teams overview](/terraform/enterprise/users-teams-organizations/teams) + +_Appears in:_ + +- [ProjectTeamAccess](#projectteamaccess) +- [TeamAccess](#teamaccess) + +| Field | Description | +| --------------- | -------------------------------------------------- | +| `id` _string_ | Team ID. Must match pattern: `^team-[a-zA-Z0-9]+$` | +| `name` _string_ | Team name. | + +#### TeamAccess + +HCP Terraform workspaces can only be accessed by users with the correct permissions. You can manage permissions for a workspace on a per-team basis. When a workspace is created, only the owners team and teams with the "manage workspaces" permission can access it, with full admin permissions. These teams' access can't be removed from a workspace. + +More information: + +- [Manage access to workspaces](/terraform/enterprise/workspaces/settings/access) + +_Appears in:_ + +- [WorkspaceSpec](#workspacespec) + +| Field | Description | +| -------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `team` _[Team](#team)_ | Team to grant access. [More information](/terraform/enterprise/users-teams-organizations/teams). | +| `access` _string_ | There are two ways to choose which permissions a given team has on a workspace: fixed permission sets, and custom permissions. Must be one of the following values: `admin`, `custom`, `plan`, `read`, `write`. [More information](/terraform/enterprise/users-teams-organizations/permissions#workspace-permissions). | +| `custom` _[CustomPermissions](#custompermissions)_ | Custom permissions let you assign specific, finer-grained permissions to a team than the broader fixed permission sets provide. [More information](/terraform/enterprise/users-teams-organizations/permissions#custom-workspace-permissions). | + +#### Token + +Token refers to a Kubernetes Secret object within the same namespace as the Workspace object + +_Appears in:_ + +- [AgentPoolSpec](#agentpoolspec) +- [ModuleSpec](#modulespec) +- [ProjectSpec](#projectspec) +- [WorkspaceSpec](#workspacespec) + +| Field | Description | +| ------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------ | +| `secretKeyRef` _[SecretKeySelector](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.27/#secretkeyselector-v1-core)_ | Selects a key of a secret in the workspace's namespace | + +#### ValueFrom + +ValueFrom source for the variable's value. Cannot be used if value is not empty. + +_Appears in:_ + +- [Variable](#variable) + +| Field | Description | +| --------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------- | +| `configMapKeyRef` _[ConfigMapKeySelector](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.27/#configmapkeyselector-v1-core)_ | Selects a key of a ConfigMap. | +| `secretKeyRef` _[SecretKeySelector](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.27/#secretkeyselector-v1-core)_ | Selects a key of a Secret. | + +#### Variable + +Variables let you customize configurations, modify Terraform's behavior, and store information like provider credentials. + +More information: + +- [Workspace variables](/terraform/enterprise/workspaces/variables) + +_Appears in:_ + +- [WorkspaceSpec](#workspacespec) + +| Field | Description | +| ------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `name` _string_ | Name of the variable. | +| `description` _string_ | Description of the variable. | +| `hcl` _boolean_ | Parse this field as HashiCorp Configuration Language (HCL). This allows you to interpolate values at runtime. Default: `false`. | +| `sensitive` _boolean_ | Sensitive variables are never shown in the UI or API. They may appear in Terraform logs if your configuration is designed to output them. Default: `false`. | +| `value` _string_ | Value of the variable. | +| `valueFrom` _[ValueFrom](#valuefrom)_ | Source for the variable's value. Cannot be used if value is not empty. | + +#### VariableSetStatus + +_Appears in:_ + +- [WorkspaceStatus](#workspacestatus) + +| Field | Description | +| --------------- | ----------- | +| `id` _string_ | | +| `name` _string_ | | + +#### VariableStatus + +_Appears in:_ + +- [WorkspaceStatus](#workspacestatus) + +| Field | Description | +| -------------------- | --------------------------------------------------- | +| `name` _string_ | Name of the variable. | +| `id` _string_ | ID of the variable. | +| `versionID` _string_ | VersionID is a hash of the variable on the TFC end. | +| `valueID` _string_ | ValueID is a hash of the variable on the CRD end. | +| `category` _string_ | Category of the variable. | + +#### VersionControl + +VersionControl settings for the workspace's VCS repository, enabling the UI/VCS-driven run workflow. Omit this argument to utilize the CLI-driven and API-driven workflows, where runs are not driven by webhooks on your VCS provider. + +More information: + +- [UI and VCS-driven run workflow](/terraform/enterprise/run/ui) +- [Connect to VCS Providers](/terraform/enterprise/vcs) + +_Appears in:_ + +- [WorkspaceSpec](#workspacespec) + +| Field | Description | +| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `oAuthTokenID` _string_ | The VCS Connection (OAuth Connection + Token) to use. Must match pattern: `^ot-[a-zA-Z0-9]+$` | +| `repository` _string_ | A reference to your VCS repository in the format `/` where `` and `` refer to the organization and repository in your VCS provider. | +| `branch` _string_ | The repository branch that Run will execute from. This defaults to the repository's default branch (e.g. main). | +| `speculativePlans` _boolean_ | Whether this workspace allows automatic speculative plans on PR. Default: `true`. More information: [Speculative plans on pull requests](/terraform/enterprise/run/ui#speculative-plans-on-pull-requests) and [Speculative plans](/terraform/enterprise/run/remote-operations#speculative-plans). | + +#### Workspace + +Workspace manages HCP Terraform Workspaces. + +More information: + +- [Workspaces](/terraform/enterprise/workspaces) + +| Field | Description | +| ------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `apiVersion` _string_ | `app.terraform.io/v1alpha2` | +| `kind` _string_ | `Workspace` | +| `kind` _string_ | Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. [More information](https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds) | +| `apiVersion` _string_ | APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. [More information](https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources) | +| `metadata` _[ObjectMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.27/#objectmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | +| `spec` _[WorkspaceSpec](#workspacespec)_ | | + +#### WorkspaceAgentPool + +AgentPool allows HCP Terraform to communicate with isolated, private, or on-premises infrastructure. + +Only one of the fields `ID` or `Name` is allowed. + +At least one of the fields `ID` or `Name` is mandatory. + +More information: + +- [HCP Terraform agents](/terraform/cloud-docs/agents) + +_Appears in:_ + +- [WorkspaceSpec](#workspacespec) + +| Field | Description | +| --------------- | --------------------------------------------------------- | +| `id` _string_ | Agent Pool ID. Must match pattern: `^apool-[a-zA-Z0-9]+$` | +| `name` _string_ | Agent Pool name. | + +#### WorkspaceProject + +Projects let you organize your workspaces into groups. + +Only one of the fields `ID` or `Name` is allowed. + +At least one of the fields `ID` or `Name` is mandatory. + +More information: + +- [Organize workspaces with projects](/terraform/tutorials/cloud/projects) + +_Appears in:_ + +- [WorkspaceSpec](#workspacespec) + +| Field | Description | +| --------------- | ---------------------------------------------------- | +| `id` _string_ | Project ID. Must match pattern: `^prj-[a-zA-Z0-9]+$` | +| `name` _string_ | Project name. | + +#### WorkspaceRunTask + +Run tasks allow HCP Terraform to interact with external systems at specific points in the HCP Terraform run lifecycle. + +Only one of the fields `ID` or `Name` is allowed. + +At least one of the fields `ID` or `Name` is mandatory. + +More information: + +- [Run tasks](/terraform/enterprise/workspaces/settings/run-tasks) + +_Appears in:_ + +- [WorkspaceSpec](#workspacespec) + +| Field | Description | +| --------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `id` _string_ | Run Task ID. Must match pattern: `^task-[a-zA-Z0-9]+$` | +| `name` _string_ | Run Task Name. | +| `enforcementLevel` _string_ | Run Task Enforcement Level. Can be one of `advisory` or `mandatory`. Default: `advisory`. Must be one of the following values: `advisory`, `mandatory` Default: `advisory`. | +| `stage` _string_ | Run Task Stage. Must be one of the following values: `pre_apply`, `pre_plan`, `post_plan`. Default: `post_plan`. | + +#### WorkspaceSpec + +WorkspaceSpec defines the desired state of Workspace. + +_Appears in:_ + +- [Workspace](#workspace) + +| Field | Description | +| -------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `name` _string_ | Workspace name. | +| `organization` _string_ | Organization name where the Workspace will be created. [More information](/terraform/enterprise/users-teams-organizations/organizations). | +| `token` _[Token](#token)_ | API Token to be used for API calls. | +| `applyMethod` _string_ | Define either change will be applied automatically(auto) or require an operator to confirm(manual). Must be one of the following values: `auto`, `manual`. Default: `manual`. [More information](/terraform/enterprise/workspaces/settings#auto-apply-and-manual-apply). | +| `allowDestroyPlan` _boolean_ | Allows a destroy plan to be created and applied. Default: `true`. [More information](/terraform/enterprise/workspaces/settings#destruction-and-deletion). | +| `description` _string_ | Workspace description. | +| `agentPool` _[WorkspaceAgentPool](#workspaceagentpool)_ | HCP Terraform Agents allow HCP Terraform to communicate with isolated, private, or on-premises infrastructure. [More information](/terraform/cloud-docs/agents). | +| `executionMode` _string_ | Define where the Terraform code will be executed. Must be one of the following values: `agent`, `local`, `remote`. Default: `remote`. [More information](/terraform/enterprise/workspaces/settings#execution-mode). | +| `runTasks` _[WorkspaceRunTask](#workspaceruntask) array_ | Run tasks allow HCP Terraform to interact with external systems at specific points in the HCP Terraform run lifecycle. [More information](/terraform/enterprise/workspaces/settings/run-tasks). | +| `tags` _[Tag](#tag) array_ | Workspace tags are used to help identify and group together workspaces. Tags must be one or more characters; can include letters, numbers, colons, hyphens, and underscores; and must begin and end with a letter or number. | +| `teamAccess` _[TeamAccess](#teamaccess) array_ | HCP Terraform workspaces can only be accessed by users with the correct permissions. You can manage permissions for a workspace on a per-team basis. When a workspace is created, only the owners team and teams with the "manage workspaces" permission can access it, with full admin permissions. These teams' access can't be removed from a workspace. [More information](/terraform/enterprise/workspaces/settings/access). | +| `terraformVersion` _string_ | The version of Terraform to use for this workspace. If not specified, the latest available version will be used. Must match pattern: `^\\d\{1\}\\.\\d\{1,2\}\\.\\d\{1,2\}$` [More information](/terraform/enterprise/workspaces/settings#terraform-version) | +| `workingDirectory` _string_ | The directory where Terraform will execute, specified as a relative path from the root of the configuration directory. [More information](/terraform/enterprise/workspaces/settings#terraform-working-directory) | +| `environmentVariables` _[Variable](#variable) array_ | Terraform Environment variables for all plans and applies in this workspace. Variables defined within a workspace always overwrite variables from variable sets that have the same type and the same key. More information: [Workspace variables](/terraform/enterprise/workspaces/variables) and [Environment variables](/terraform/enterprise/workspaces/variables#environment-variables). | +| `terraformVariables` _[Variable](#variable) array_ | Terraform variables for all plans and applies in this workspace. Variables defined within a workspace always overwrite variables from variable sets that have the same type and the same key. More information: [Workspace variables](/terraform/enterprise/workspaces/variables) and [Terraform variables](/terraform/enterprise/workspaces/variables#terraform-variables). | +| `remoteStateSharing` _[RemoteStateSharing](#remotestatesharing)_ | Remote state access between workspaces. By default, new workspaces in HCP Terraform do not allow other workspaces to access their state. [More information](/terraform/enterprise/workspaces/state#accessing-state-from-other-workspaces). | +| `runTriggers` _[RunTrigger](#runtrigger) array_ | Run triggers allow you to connect this workspace to one or more source workspaces. These connections allow runs to queue automatically in this workspace on successful apply of runs in any of the source workspaces. [More information](/terraform/enterprise/workspaces/settings/run-triggers). | +| `versionControl` _[VersionControl](#versioncontrol)_ | Settings for the workspace's VCS repository, enabling the UI/VCS-driven run workflow. Omit this argument to utilize the CLI-driven and API-driven workflows, where runs are not driven by webhooks on your VCS provider. More information: [UI and VCS-driven run workflow](/terraform/enterprise/run/ui) and [Connect to VCS providers](/terraform/enterprise/vcs) | +| `sshKey` _[SSHKey](#sshkey)_ | SSH key used to clone Terraform modules. [More information](/terraform/enterprise/workspaces/settings/ssh-keys). | +| `notifications` _[Notification](#notification) array_ | Notifications allow you to send messages to other applications based on run and workspace events. [More information](/terraform/enterprise/workspaces/settings/notifications). | +| `project` _[WorkspaceProject](#workspaceproject)_ | Projects let you organize your workspaces into groups. Default: default organization project. [More information](/terraform/tutorials/cloud/projects). | +| `deletionPolicy` _[DeletionPolicy](#deletionpolicy)_ | The Deletion Policy specifies the behavior of the custom resource and its associated workspace when the custom resource is deleted. - `retain`: When you delete the custom resource, the operator does not delete the workspace. - `soft`: Attempts to delete the associated workspace only if it does not contain any managed resources. - `destroy`: Executes a destroy operation to remove all resources managed by the associated workspace. Once the destruction of these resources is successful, the operator deletes the workspace, and then deletes the custom resource. - `force`: Forcefully and immediately deletes the workspace and the custom resource. Default: `retain`. | +| `variableSets` _[WorkspaceVariableSet](#workspacevariableset) array_ | HCP Terraform variable sets let you reuse variables in an efficient and centralized way. [More information](/terraform/tutorials/cloud/cloud-multiple-variable-sets) | + +#### WorkspaceVariableSet + +_Appears in:_ + +- [WorkspaceSpec](#workspacespec) + +| Field | Description | +| --------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | +| `id` _string_ | ID of the variable set. Must match pattern: `varset-[a-zA-Z0-9]+$` [More information](/terraform/tutorials/cloud/cloud-multiple-variable-sets). | +| `name` _string_ | Name of the variable set. [More information](/terraform/tutorials/cloud/cloud-multiple-variable-sets). | diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/integrations/kubernetes/index.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/integrations/kubernetes/index.mdx new file mode 100644 index 0000000000..0b3e18a397 --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/integrations/kubernetes/index.mdx @@ -0,0 +1,220 @@ +--- +page_title: Terraform Enterprise Operator for Kubernetes overview +description: >- + The Terraform Enterprise Operator for Kubernetes allows you to provision + infrastructure directly from the Kubernetes control plane. +source: terraform-docs-common +--- + +# HCP Terraform Operator for Kubernetes overview + +The [HCP Terraform Operator for Kubernetes](https://github.com/hashicorp/hcp-terraform-operator) allows you to manage HCP Terraform resources with Kubernetes custom resources. You can provision infrastructure internal or external to your Kubernetes cluster directly from the Kubernetes control plane. + +The operator's CustomResourceDefinitions (CRD) let you dynamically create HCP Terraform workspaces with Terraform modules, populate workspace variables, and provision infrastructure with Terraform runs. + +## Key benefits + +The HCP Terraform Operator for Kubernetes v2 offers several improvements over v1: + +- **Flexible resource management**: The operator now features multiple custom resources, each with separate controllers for different HCP Terraform resources. This provides additional flexibility and the ability to manage more custom resources concurrently, significantly improving performance for large-scale deployments. + +- **Namespace management**: The `--namespace` option allows you to tailor the operator's watch scope to specific namespaces, which enables more fine-grained resource management. + +- **Configurable synchronization**: The `--sync-period` option allows you to configure the synchronization frequency between custom resources and HCP Terraform, ensuring timely updates and smoother operations. + +## Supported HCP Terraform features + +The HCP Terraform Operator for Kubernetes allows you to create agent pools, deploy modules, and manage workspaces through Kubernetes controllers. These controllers enable you to automate and manage HCP Terraform resources using custom resources in Kubernetes. + +### Agent pools + +Agent pools in HCP Terraform manage the execution environment for Terraform runs. The HCP Terraform Operator for Kubernetes allows you to create and manage agent pools as part of your Kubernetes infrastructure. + +The following example creates a new agent pool with the name `agent-pool-development` and generates an agent token with the name `token-red`. + +```yaml +--- +apiVersion: app.terraform.io/v1alpha2 +kind: AgentPool +metadata: + name: my-agent-pool +spec: + organization: kubernetes-operator + token: + secretKeyRef: + name: tfc-operator + key: token + name: agent-pool-development + agentTokens: + - name: token-red +``` + +The operator stores the `token-red` agent token in a Kubernetes secret named `my-agent-pool-token-red`. + +You can also enable agent autoscaling by providing a `.spec.autoscaling` configuration in your `AgentPool` specification. + + + +```yaml +--- +apiVersion: app.terraform.io/v1alpha2 +kind: AgentPool +metadata: + name: this +spec: + organization: kubernetes-operator + token: + secretKeyRef: + name: tfc-operator + key: token + name: agent-pool-development + agentTokens: + - name: token-red + agentDeployment: + replicas: 1 + autoscaling: + targetWorkspaces: + - name: us-west-development + - id: ws-NUVHA9feCXzAmPHx + - wildcardName: eu-development-* + minReplicas: 1 + maxReplicas: 3 + cooldownPeriod: + scaleUpSeconds: 30 + scaleDownSeconds: 30 +``` + + + +In the above example, the operator ensures that at least one agent pod is continuously running and dynamically scales the number of pods up to a maximum of three based on the workload or resource demand. The operator then monitors resource demands by observing the load of the designated workspaces specified by the `name`, `id`, or `wildcardName` patterns. When the workload decreases, the operator downscales the number of agent pods. + +Refer to the [agent pool API reference](/terraform/enterprise/integrations/kubernetes/api-reference#agentpool) for the complete `AgentPool` specification. + +### Module + +The `Module` controller enforces an [API-driven Run workflow](/terraform/enterprise/run/api) and lets you deploy Terraform modules within workspaces. + +The following example deploys version `1.0.0` of the `hashicorp/module/random` module in the `workspace-name` workspace. + +```yaml +--- +apiVersion: app.terraform.io/v1alpha2 +kind: Module +metadata: + name: my-module +spec: + organization: kubernetes-operator + token: + secretKeyRef: + name: tfc-operator + key: token + module: + source: hashicorp/module/random + version: 1.0.0 + workspace: + name: workspace-name + variables: + - name: string_length + outputs: + - name: random_string +``` + +The operator passes the workspace's `string_length` variable to the module and stores the `random_string` outputs as either a Kubernetes secret or a ConfigMap. If the workspace marks the output as `sensitive`, the operator stores the `random_string` as a Kubernetes secret; otherwise, the operator stores it as a ConfigMap. The variables must be accessible within the workspace as a workspace variable, workspace variable set, or project variable set. + +Refer to the [module API reference](/terraform/enterprise/integrations/kubernetes/api-reference#module) for the complete `Module` specification. + +### Project + +Projects let you organize your workspaces and scope access to workspace resources. The `Project` controller allows you to create, configure, and manage [projects](/terraform/tutorials/cloud/projects) directly from Kubernetes. + +The following example creates a new project named `testing`. + +```yaml +--- +apiVersion: app.terraform.io/v1alpha2 +kind: Project +metadata: + name: testing +spec: + organization: kubernetes-operator + token: + secretKeyRef: + name: tfc-operator + key: token + name: project-demo +``` + +The `Project` controller allows you to manage team access [permissions](/terraform/enterprise/users-teams-organizations/permissions#project-permissions). + +The following example creates a project named `testing` and grants the `qa` team admin access to the project. + + + +```yaml +--- +apiVersion: app.terraform.io/v1alpha2 +kind: Project +metadata: + name: testing +spec: + organization: kubernetes-operator + token: + secretKeyRef: + name: tfc-operator + key: token + name: project-demo + teamAccess: + - team: + name: qa + access: admin +``` + + + +Refer to the [project API reference](/terraform/enterprise/integrations/kubernetes/api-reference#project) for the complete `Project` specification. + +### Workspace + +HCP Terraform workspaces organize and manage Terraform configurations. The HCP Terraform Operator for Kubernetes allows you to create, configure, and manage workspaces directly from Kubernetes. + +The following example creates a new workspace named `us-west-development`, configured to use Terraform version `1.6.2`. This workspace has two variables, `nodes` and `rds-secret`. The variable `rds-secret` is treated as sensitive, and the operator reads the value for the variable from a Kubernetes secret named `us-west-development-secrets`. + +```yaml +--- +apiVersion: app.terraform.io/v1alpha2 +kind: Workspace +metadata: + name: us-west-development +spec: + organization: kubernetes-operator + token: + secretKeyRef: + name: tfc-operator + key: token + name: us-west-development + description: US West development workspace + terraformVersion: 1.6.2 + applyMethod: auto + agentPool: + name: ap-us-west-development + terraformVariables: + - name: nodes + value: 2 + - name: rds-secret + sensitive: true + valueFrom: + secretKeyRef: + name: us-west-development-secrets + key: rds-secret + runTasks: + - name: rt-us-west-development + stage: pre_plan +``` + +In the above example, the `applyMethod` has the value of `auto`, so HCP Terraform automatically applies any changes to this workspace. The specification also configures the workspace to use the `ap-us-west-development` agent pool and run the `rt-us-west-development` run task at the `pre_plan` stage. + +The operator stores the value of the workspace outputs as Kubernetes secrets or ConfigMaps. If the outputs are marked as `sensitive`, they are stored as Kubernetes secrets, otherwise they are stored as ConfigMaps. + +-> **Note**: The operator rolls back any external modifications made to the workspace to match the state specified in the custom resource definition. + +Refer to the [workspace API reference](/terraform/enterprise/integrations/kubernetes/api-reference#workspace) for the complete `Workspace` specification. diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/integrations/kubernetes/ops-v2-migration.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/integrations/kubernetes/ops-v2-migration.mdx new file mode 100644 index 0000000000..49c8bc1272 --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/integrations/kubernetes/ops-v2-migration.mdx @@ -0,0 +1,342 @@ +--- +page_title: Migrate to Terraform Enterprise Operator for Kubernetes v2 +description: >- + Learn how to upgrade the Terraform Kubernetes Operator from version 1 to + version 2. +source: terraform-docs-common +--- + +# Migrate to HCP Terraform Operator for Kubernetes v2 + +~> **Warning**: Version 1 of the HCP Terraform Operator for Kubernetes is **deprecated** and no longer maintained. If you are installing the operator for the first time, refer to [Set up the HCP Terraform Operator for Kubernetes](/terraform/enterprise/integrations/kubernetes/setup) for guidance. + +To upgrade the HCP Terraform Operator for Kubernetes from version 1 to the HCP Terraform Operator for Kubernetes (version 2), there is a one-time process that you need to complete. This process upgrades the operator to the newest version and migrate your custom resources. + +## Prerequisites + +The migration process requires the following tools to be installed locally: + +- [kubectl](https://kubernetes.io/docs/tasks/tools/#kubectl) +- [Helm](https://helm.sh/docs/intro/install/) + +## Prepare for the upgrade + +Configure an environment variable named `RELEASE_NAMESPACE` with the value of the namespace that the Helm chart is installed in. + +```shell-session +$ export RELEASE_NAMESPACE= +``` + +Next, create an environment variable named `RELEASE_NAME` with the value of the name that you gave your installation for the Helm chart. + +```shell-session +$ export RELEASE_NAME= +``` + +Before you migrate to HCP Terraform Operator for Kubernetes v2, you must first update v1 of the operator to the latest version, including the custom resource definitions. + +```shell-session +$ helm upgrade --namespace ${RELEASE_NAMESPACE} ${RELEASE_NAME} hashicorp/terraform +``` + +Next, backup the workspace resources. + +```shell-session +$ kubectl get workspace --all-namespaces -o yaml > backup_tfc_operator_v1.yaml +``` + +## Manifest schema migration + +Version 2 of the HCP Terraform Operator for Kubernetes renames and moves many existing fields. When you migrate, you must update your specification to match version 2's field names. + +### Workspace controller + +The table below lists the field mapping of the `Workspace` controller between v1 and v2 of the operator. + +| Version 1 | Version 2 | Changes between versions | +| --------------------------------------- | ---------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `apiVersion: app.terraform.io/v1alpha1` | `apiVersion: app.terraform.io/v1alpha2` | The `apiVersion` is now `v1alpha2`. | +| `kind: Workspace` | `kind: Workspace` | None. | +| `metadata` | `metadata` | None. | +| `spec.organization` | `spec.organization` | None. | +| `spec.secretsMountPath` | `spec.token.secretKeyRef` | In v2 the operator keeps the HCP Terraform access token in a Kubernetes Secret. | +| `spec.vcs` | `spec.versionControl` | Renamed the `vcs` field to `versionControl`. | +| `spec.vcs.token_id` | `spec.versionControl.oAuthTokenID` | Renamed the `token_id` field to `oAuthTokenID`. | +| `spec.vcs.repo_identifier` | `spec.versionControl.repository` | Renamed the `repo_identifier` field to `repository`. | +| `spec.vcs.branch` | `spec.versionControl.branch` | None. | +| `spec.vcs.ingress_submodules` | `spec.workingDirectory` | Moved. | +| `spec.variables.[*]` | `spec.environmentVariables.[*]` OR `spec.terraformVariables.[*]` | We split variables into two possible places. In v1's CRD, if `spec.variables.environmentVariable` was `true`, migrate those variables to `spec.environmentVariables`. If `false`, migrate those variables to `spec.terraformVariables`. | +| `spec.variables.[*]key` | `spec.environmentVariables.[*]name` OR `spec.terraformVariables.[*]name` | Renamed the `key` field as `name`. [Learn more](#workspace-variables). | +| `spec.variables.[*]value` | `spec.environmentVariables.[*]value` OR `spec.terraformVariables.[*]value` | [Learn more](#workspace-variables). | +| `spec.variables.[*]valueFrom` | `spec.environmentVariables.[*]valueFrom` OR `spec.terraformVariables.[*]valueFrom` | [Learn more](#workspace-variables). | +| `spec.variables.[*]hcl` | `spec.environmentVariables.[*]hcl` OR `spec.terraformVariables.[*]hcl` | [Learn more](#workspace-variables). | +| `spec.variables.sensitive` | `spec.environmentVariables.[*]sensitive` OR `spec.terraformVariables.[*]sensitive` | [Learn more](#workspace-variables). | +| `spec.variables.environmentVariable` | N/A | Removed, variables are split between `spec.environmentVariables` and `spec.terraformVariables`. | +| `spec.runTriggers.[*]` | `spec.runTriggers.[*]` | None. | +| `spec.runTriggers.[*].sourceableName` | `spec.runTriggers.[*].name` | The `sourceableName` field is now `name`. | +| `spec.sshKeyID` | `spec.sshKey.id` | Moved the `sshKeyID` to `spec.sshKey.id`. | +| `spec.outputs` | N/A | Removed. | +| `spec.terraformVersion` | `spec.terraformVersion` | None. | +| `spec.notifications.[*]` | `spec.notifications.[*]` | None. | +| `spec.notifications.[*].type` | `spec.notifications.[*].type` | None. | +| `spec.notifications.[*].enabled` | `spec.notifications.[*].enabled` | None. | +| `spec.notifications.[*].name` | `spec.notifications.[*].name` | None. | +| `spec.notifications.[*].url` | `spec.notifications.[*].url` | None. | +| `spec.notifications.[*].token` | `spec.notifications.[*].token` | None. | +| `spec.notifications.[*].triggers.[*]` | `spec.notifications.[*].triggers.[*]` | None. | +| `spec.notifications.[*].recipients.[*]` | `spec.notifications.[*].emailAddresses.[*]` | Renamed the `recipients` field to `emailAddresses`. | +| `spec.notifications.[*].users.[*]` | `spec.notifications.[*].emailUsers.[*]` | Renamed the `users` field to `emailUsers`. | +| `spec.omitNamespacePrefix` | N/A | Removed. In v1 `spec.omitNamespacePrefix` is a boolean field that affects how the operator generates a workspace name. In v2, you must explicitly set workspace names in `spec.name`. | +| `spec.agentPoolID` | `spec.agentPool.id` | Moved the `agentPoolID` field to `spec.agentPool.id`. | +| `spec.agentPoolName` | `spec.agentPool.name` | Moved the `agentPoolName` field to `spec.agentPool.name`. | +| `spec.module` | N/A | Removed. You now configure modules with a separate `Module` CRD. [Learn more](#module-controller). | + +Below is an example of configuring a variable in v1 of the operator. + + + +```yaml +apiVersion: app.terraform.io/v1alpha1 +kind: Workspace +metadata: + name: migration + spec: + variables: + - key: username + value: "user" + hcl: true + sensitive: false + environmentVariable: false + - key: SECRET_KEY + value: "s3cr3t" + hcl: false + sensitive: false + environmentVariable: true +``` + + + +In v2 of the operator, you must configure Terraform variables in `spec.terraformVariables` and environment variables `spec.environmentVariables`. + + + +```yaml +apiVersion: app.terraform.io/v1alpha2 +kind: Workspace +metadata: + name: migration + spec: + terraformVariables: + - name: username + value: "user" + hcl: true + sensitive: false + environmentVariables: + - name: SECRET_KEY + value: "s3cr3t" + hcl: false + sensitive: false +``` + + + +### Module controller + +HCP Terraform Operator for Kubernetes v2 configures modules in a new `Module` controller separate from the `Workspace` controller. Below is a template of a custom resource manifest: + +```yaml +apiVersion: app.terraform.io/v1alpha2 +kind: Module +metadata: + name: +spec: + organization: + token: + secretKeyRef: + name: + key: + name: operator +``` + +The table below describes the mapping between the `Workspace` controller from v1 and the `Module` controller in v2 of the operator. + +| Version 1 (Workspace CRD) | Version 2 (Module CRD) | Notes | +| ---------------------------------------------------------- | -------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `spec.module` | N/A | In v2 of the operator a `Module` is a separate controller with its own CRD. | +| N/A | `spec.name: operator` | In v1 of the operator, the name of the generated module is hardcoded to `operator`. In v2, the default name of the generated module is `this`, but you can rename it. | +| `spec.module.source` | `spec.module.source` | This supports all Terraform [module sources](/terraform/language/modules/sources). | +| `spec.module.version` | `spec.module.version` | Refer to [module sources](/terraform/language/modules/sources) for versioning information for each module source. | +| `spec.variables.[*]` | `spec.variables.[*].name` | You should include variable names in the module. This is a reference to variables in the workspace that is executing the module. | +| `spec.outputs.[*].key` | `spec.outputs.[*].name` | You should include output names in the module. This is a reference to the output variables produced by the module. | +| `status.workspaceID` OR `metadata.namespace-metadata.name` | `spec.workspace.id` OR `spec.workspace.name` | The workspace where the module is executed. The workspace must be in the same organization. | + +Below is an example migration of a `Module` between v1 and v2 of the operator: + + + +```yaml +apiVersion: app.terraform.io/v1alpha1 +kind: Workspace +metadata: + name: migration +spec: + module: + source: app.terraform.io/org-name/module-name/provider + version: 0.0.42 + variables: + - key: username + value: "user" + hcl: true + sensitive: false + environmentVariable: false + - key: SECRET_KEY + value: "s3cr3t" + hcl: false + sensitive: false + environmentVariable: true +``` + + + +In v2 of the operator, separate controllers manage workspace and modules. + + + +```yaml +apiVersion: app.terraform.io/v1alpha2 +kind: Workspace +metadata: + name: migration + spec: + terraformVariables: + - name: username + value: "user" + hcl: true + sensitive: false + environmentVariables: + - name: SECRET_KEY + value: "s3cr3t" + hcl: false + sensitive: false +``` + + + + + +```yaml +apiVersion: app.terraform.io/v1alpha2 +kind: Module +metadata: + name: migration +spec: + name: operator + module: + source: app.terraform.io/org-name/module-name/provider + version: 0.0.42 + workspace: + name: migration +``` + + + +## Upgrade the operator + +Download Workspace CRD patch A: + +```shell-session +$ curl -sO https://raw.githubusercontent.com/hashicorp/hcp-terraform-operator/main/docs/migration/crds/workspaces_patch_a.yaml +``` + +View the changes that patch A applies to the workspace CRD. + +```shell-session +$ kubectl diff --filename workspaces_patch_a.yaml +``` + +Patch the workspace CRD with patch A. This patch adds `app.terraform.io/v1alpha2` support, but excludes `.status.runStatus` because it has a different format in `app.terraform.io/v1alpha1` and causes JSON un-marshalling issues. + +!> **Upgrade warning**: Once you apply a patch, Kubernetes converts existing `app.terraform.io/v1alpha1` custom resources to `app.terraform.io/v1alpha2` according to the updated schema, meaning that v1 of the operator can no longer serve custom resources. Before patching, update your existing custom resources to satisfy the v2 schema requirements. [Learn more](#manifest-schema-migration). + +```shell-session +$ kubectl patch crd workspaces.app.terraform.io --patch-file workspaces_patch_a.yaml +``` + +Install the Operator v2 Helm chart with the `helm install` command. Be sure to set the `operator.watchedNamespaces` value to the list of namespaces your Workspace resources are deployed to. If this value is not provided, the operator will watch all namespaces in the Kubernetes cluster. + +```shell-session +$ helm install \ + ${RELEASE_NAME} hashicorp/hcp-terraform-operator \ + --version 2.4.0 \ + --namespace ${RELEASE_NAMESPACE} \ + --set 'operator.watchedNamespaces={white,blue,red}' \ + --set controllers.agentPool.workers=5 \ + --set controllers.module.workers=5 \ + --set controllers.workspace.workers=5 +``` + +Next, create a Kubernetes secret to store the HCP Terraform API token following the [Usage Guide](https://github.com/hashicorp/hcp-terraform-operator/blob/main/docs/usage.md#prerequisites). The API token can be copied from the Kubernetes secret that you created for v1 of the operator. By default, this is named `terraformrc`. Use the `kubectl get secret` command to get the API token. + +```shell-session +$ kubectl --namespace ${RELEASE_NAMESPACE} get secret terraformrc -o json | jq '.data.credentials' | tr -d '"' | base64 -d +``` + +Update existing custom resources [according to the schema migration guidance](#manifest-schema-migration) and apply your changes. + +```shell-session +$ kubectl apply --filename +``` + +Download Workspace CRD patch B. + +```shell-session +$ curl -sO https://raw.githubusercontent.com/hashicorp/hcp-terraform-operator/main/docs/migration/crds/workspaces_patch_b.yaml +``` + +View the changes that patch B applies to the workspace CRD. + +```shell-session +$ kubectl diff --filename workspaces_patch_b.yaml +``` + +Patch the workspace CRD with patch B. This patch adds `.status.runStatus` support, which was excluded in patch A. + +```shell-session +$ kubectl patch crd workspaces.app.terraform.io --patch-file workspaces_patch_b.yaml +``` + +The v2 operator will fail to proceed if a custom resource has the v1 finalizer `finalizer.workspace.app.terraform.io`. If you encounter an error, check the logs for more information. + +```shell-session +$ kubectl logs -f +``` + +Specifically, look for an error message such as the following. + + ERROR Migration {"workspace": "default/", "msg": "spec contains old finalizer finalizer.workspace.app.terraform.io"} + +The `finalizer` exists to provide greater control over the migration process. Verify the custom resource, and when you’re ready to migrate it, use the `kubectl patch` command to update the `finalizer` value. + +```shell-session +$ kubectl patch workspace migration --type=merge --patch '{"metadata": {"finalizers": ["workspace.app.terraform.io/finalizer"]}}' +``` + +Review the operator logs once more and verify there are no error messages. + +```shell-session +$ kubectl logs -f +``` + +The operator reconciles resources during the next sync period. This interval is set by the `operator.syncPeriod` configuration of the operator and defaults to five minutes. + +If you have any migrated `Module` custom resources, apply them now. + +```shell-session +$ kubectl apply --filename +``` + +In v2 of the operator, the `applyMethod` is set to `manual` by default. In this case, a new run in a managed workspace requires manual approval. Run the following command for each `Workspace` resource to change it to `auto` approval. + +```shell-session +$ kubectl patch workspace --type=merge --patch '{"spec": {"applyMethod": "auto"}}' +``` diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/integrations/kubernetes/setup.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/integrations/kubernetes/setup.mdx new file mode 100644 index 0000000000..ece055aa73 --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/integrations/kubernetes/setup.mdx @@ -0,0 +1,94 @@ +--- +page_title: Set up the Terraform Enterprise Operator for Kubernetes +description: >- + Learn how to install and configure the Terraform Enterprise Operator for + Kubernetes. +source: terraform-docs-common +--- + +# Set up the HCP Terraform Operator for Kubernetes + +The HCP Terraform Operator for Kubernetes' CustomResourceDefinitions (CRD) allow you to dynamically create HCP Terraform workspaces with Terraform modules, populate workspace variables, and provision infrastructure with Terraform runs. + +You can install the operator with the official [HashiCorp Helm chart](https://github.com/hashicorp/hcp-terraform-operator). + +## Prerequisites + +All HCP Terraform users can use the HCP Terraform Operator for Kubernetes. You can use the operator to manage the supported features that your organization's pricing tier enables. + +## Networking requirements + +The HCP Terraform Operator for Kubernetes makes outbound requests over HTTPS (TCP port 443) to the HCP Terraform application APIs. This may require perimeter networking as well as container host networking changes, depending on your environment. Refer to [HCP Terraform IP Ranges](/terraform/enterprise/architectural-details/ip-ranges) for more information about IP ranges. Below, we list the services that run on specific IP ranges. + +| Hostname | Port/Protocol | Directionality | Purpose | +| ------------------ | -------------- | -------------- | --------------------------------------------------------------------------------------------------------------- | +| `app.terraform.io` | tcp/443, HTTPS | Outbound | Dynamically managing HCP Terraform workspaces and returning the output to Kubernetes with the HCP Terraform API | + +For self-managed Terraform Enterprise instances, ensure that the operator can reach your Terraform Enterprise hostname over HTTPS (TCP port 443). + +## Compatibility + +The HCP Terraform Operator for Kubernetes supports the following versions: + +- Helm 3.0.1 and above +- Kubernetes 1.15 and above + +## Install and configure + +1. Sign in to [HCP Terraform](https://app.terraform.io/) or Terraform Enterprise and navigate to the organization you want to integrate with Kubernetes. + +2. Generate an [organization token](/terraform/enterprise/users-teams-organizations/api-tokens#organization-api-tokens) within HCP Terraform or Terraform Enterprise and save it to a file. These instructions assume you are using a file named `credentials`. + +3. Set the `NAMESPACE` environment variable. This will be the namespace that you will install the Helm chart to. + + export NAMESPACE=tfc-operator-system + +4. Create the namespace. + + kubectl create namespace $NAMESPACE + +5. Create a [Kubernetes Secret](https://kubernetes.io/docs/concepts/configuration/secret/) with the HCP Terraform API credentials. + + kubectl -n $NAMESPACE create secret generic terraformrc --from-file=credentials + +6. Add sensitive variables, such as your cloud provider credentials, to the namespace. + + kubectl -n $NAMESPACE create secret generic workspacesecrets --from-literal=secret_key=abc123 + +7. Add the HashiCorp Helm repository. + + helm repo add hashicorp https://helm.releases.hashicorp.com + +8. Install the [HCP Terraform Operator for Kubernetes with Helm](https://github.com/hashicorp/hcp-terraform-operator). By default, the operator communicates with HCP Terraform at `app.terraform.io`. The following example command installs the Helm chart for HCP Terraform: + + ```shell-session + $ helm install --namespace ${RELEASE_NAMESPACE} hashicorp/hcp-terraform-operator tfc-operator + ``` + + When deploying in self-managed Terraform Enterprise, you must set the `operator.tfeAddress` to the specific hostname of the Terraform Enterprise instance: + + ```shell-session + $ helm install --namespace ${RELEASE_NAMESPACE} hashicorp/hcp-terraform-operator tfc-operator \ + --set operator.tfeAddress="TERRAFORM_ENTERPRISE_HOSTNAME" + ``` + + Alternatively, you can set the `tfeAddress` configuration for Terraform Enterprise in the [value.yaml](https://github.com/hashicorp/hcp-terraform-operator/blob/main/charts/hcp-terraform-operator/values.yaml) file. + + ```yaml + operator: + tfeAddress: + ``` + + Run the following command to apply the value.yaml file: + + ```shell-session + $ helm install --namespace ${NAMESPACE} hashicorp/hcp-terraform-operator tfc-operator -f value.yaml + ``` + +9. To create a Terraform workspace, agent pool, or other object, refer to the example YAML manifests in the [operator repository on GitHub](https://github.com/hashicorp/hcp-terraform-operator/tree/main/docs/examples). + +### Upgrade + +When a new version of the HCP Terraform Operator for Kubernetes Helm Chart is available from the HashiCorp Helm repository, you can upgrade with the following command. + + helm upgrade --namespace ${RELEASE_NAMESPACE} hashicorp/hcp-terraform-operator tfc-operator diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/integrations/run-tasks/index.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/integrations/run-tasks/index.mdx new file mode 100644 index 0000000000..c64b59a21d --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/integrations/run-tasks/index.mdx @@ -0,0 +1,109 @@ +--- +page_title: Set up Terraform Enterprise run task integrations +description: >- + Use run tasks to execute tasks in external systems at specific points in the + Terraform Enterprise run lifecycle. +source: terraform-docs-common +--- + +# Set up run task integrations + +In addition to using existing technology partners integrations, HashiCorp HCP Terraform customers can build their own custom run task integrations. Custom integrations have access to plan details in between the plan and apply phase, and can display custom messages within the run pipeline as well as prevent a run from continuing to the apply phase. + + + +@include 'tfc-package-callouts/run-tasks.mdx' + + + +## Prerequisites + +To build a custom integration, you must have a server capable of receiving requests from HCP Terraform and responding with a status update to a supplied callback URL. When creating a run task, you supply an endpoint url to receive the hook. We send a test POST to the supplied URL, and it must respond with a 200 for the run task to be created. + +This feature relies heavily on the proper parsing of [plan JSON output](/terraform/internals/json-format). When sending this output to an external system, be certain that system can properly interpret the information provided. + +## Available Run Tasks + +You can view the most up-to-date list of run tasks in the [Terraform Registry](https://registry.terraform.io/browse/run-tasks). + +## Integration Details + +When a run reaches the appropriate phase and a run task is triggered, the supplied URL will receive details about the run in a payload similar to the one below. The server receiving the run task should respond `200 OK`, or Terraform will retry to trigger the run task. + +Refer to the [Run Task Integration API](/terraform/enterprise/api-docs/run-tasks/run-tasks-integration) for the exact payload specification. + +```json +{ + "payload_version": 1, + "stage": "post_plan", + "access_token": "4QEuyyxug1f2rw.atlasv1.iDyxqhXGVZ0ykes53YdQyHyYtFOrdAWNBxcVUgWvzb64NFHjcquu8gJMEdUwoSLRu4Q", + "capabilities": { + "outcomes": true + }, + "configuration_version_download_url": "https://app.terraform.io/api/v2/configuration-versions/cv-ntv3HbhJqvFzamy7/download", + "configuration_version_id": "cv-ntv3HbhJqvFzamy7", + "is_speculative": false, + "organization_name": "hashicorp", + "plan_json_api_url": "https://app.terraform.io/api/v2/plans/plan-6AFmRJW1PFJ7qbAh/json-output", + "run_app_url": "https://app.terraform.io/app/hashicorp/my-workspace/runs/run-i3Df5to9ELvibKpQ", + "run_created_at": "2021-09-02T14:47:13.036Z", + "run_created_by": "username", + "run_id": "run-i3Df5to9ELvibKpQ", + "run_message": "Triggered via UI", + "task_result_callback_url": "https://app.terraform.io/api/v2/task-results/5ea8d46c-2ceb-42cd-83f2-82e54697bddd/callback", + "task_result_enforcement_level": "mandatory", + "task_result_id": "taskrs-2nH5dncYoXaMVQmJ", + "vcs_branch": "main", + "vcs_commit_url": "https://github.com/hashicorp/terraform-random/commit/7d8fb2a2d601edebdb7a59ad2088a96673637d22", + "vcs_pull_request_url": null, + "vcs_repo_url": "https://github.com/hashicorp/terraform-random", + "workspace_app_url": "https://app.terraform.io/app/hashicorp/my-workspace", + "workspace_id": "ws-ck4G5bb1Yei5szRh", + "workspace_name": "tfr_github_0", + "workspace_working_directory": "/terraform" +} +``` + +Once your server receives this payload, HCP Terraform expects you to callback to the supplied `task_result_callback_url` using the `access_token` as an [Authentication Header](/terraform/enterprise/api-docs#authentication) with a [jsonapi](/terraform/enterprise/api-docs#json-api-formatting) payload of the form: + +```json +{ + "data": { + "type": "task-results", + "attributes": { + "status": "running", + "message": "Hello task", + "url": "https://example.com", + "outcomes": [...] + } + } +} +``` + +HCP Terraform expects this callback within 10 minutes, or the task will be considered to have `errored`. The supplied message attribute will be displayed in HCP Terraform on the run details page. The status can be `running`, `passed` or `failed`. + +Here's what the data flow looks like: + +![Screenshot: a diagram of the user and data flow for an HCP Terraform run task](/img/docs/terraform-cloud-run-tasks-diagram.png) + +Refer to the [run task integration API](/terraform/enterprise/api-docs/run-tasks/run-tasks-integration#structured-results) for the exact payload specifications, and the [run task JSON schema](https://github.com/hashicorp/terraform-docs-common/blob/main/website/public/schema/run-tasks/runtask-result.json) for code generation and payload validation. + +## Securing your Run Task + +When creating your run task, you can supply an HMAC key which HCP Terraform will use to create a signature of the payload in the `X-Tfc-Task-Signature` header when calling your service. + +The signature is a sha512 sum of the webhook body using the provided HMAC key. The generation of the signature depends on your implementation, however an example of how to generate a signature in bash is provided below. + +```bash +$ echo -n $WEBHOOK_BODY | openssl dgst -sha512 -hmac "$HMAC_KEY" +``` + +## HCP Packer Run Task + +> **Hands On:** Try the [Set Up HCP Terraform Run Task for HCP Packer](/packer/tutorials/hcp/setup-hcp-terraform-run-task), [Standard tier run task image validation](/packer/tutorials/hcp/run-tasks-data-source-image-validation), and [Plus tier run task image validation](/packer/tutorials/hcp/run-tasks-resource-image-validation) tutorials to set up and test the HCP Terraform Run Task integration end to end. + +[Packer](https://www.packer.io/) lets you create identical machine images for multiple platforms from a single source template. The [HCP Packer registry](/hcp/docs/packer) lets you track golden images, designate images for test and production environments, and query images to use in Packer and Terraform configurations. + +The HCP Packer validation run task checks the image artifacts within a Terraform configuration. If the configuration references images marked as unusable (revoked), the run task fails and provides an error message containing the number of revoked artifacts and whether HCP Packer has metadata for newer versions. For HCP Packer Plus registries, run tasks also help you identify hardcoded and untracked images that may not meet security and compliance requirements. + +To get started, [create an HCP Packer account](https://cloud.hashicorp.com/products/packer) and follow the instructions in the [HCP Packer Run Task](/hcp/docs/packer/manage-image-use/terraform-cloud-run-tasks) documentation. diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/integrations/service-now/service-catalog-terraform/admin-guide.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/integrations/service-now/service-catalog-terraform/admin-guide.mdx new file mode 100644 index 0000000000..735371a432 --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/integrations/service-now/service-catalog-terraform/admin-guide.mdx @@ -0,0 +1,56 @@ +--- +page_title: Configure the ServiceNow Service Catalog integration +description: Learn how to configure the ServiceNow Service Catalog integration workers. +source: terraform-docs-common +--- + +# Configure the ServiceNow Service Catalog integration + +ServiceNow administrators have several options with configuring the Terraform +integration. + +If you haven't yet installed the integration, see the [installation +documentation](/terraform/enterprise/integrations/service-now/service-catalog-terraform). + +Once the integration has been installed, you can add and customize a service +catalog and VCS repositories using the [service catalog +documentation](/terraform/enterprise/integrations/service-now/service-catalog-terraform/service-catalog-config). + +You can also configure how frequently ServiceNow will poll HCP Terraform using +the documentation below. + +## Configure Polling Workers + +The integration includes 3 ServiceNow Scheduled Flows to poll the HCP Terraform +API using ServiceNow Outbound HTTP REST requests. By default, all flows +schedules are set to 5 minutes. These can be customized inside the ServiceNow +Server Studio: + +1. Select the Worker Poll Run State Flow. +2. Adjust Repeat Intervals +3. Click "Done" +4. Click "Save" +5. Click "Activate" + +### Worker Poll Apply Run + +This worker approves runs for any workspaces that have finished a Terraform plan +and are ready to apply their changes. It also adds a comment on the request item +for those workspaces notifying that a run has been triggered. + +### Worker Poll Destroy Workspace + +This worker looks for any records in the Terraform ServiceNow table that are +marked for deletion with the value `is_destroyable` set to true. It then checks +the status of the workspace to ensure it is ready to be deleted. Once the +destroy run has been completed, this work will send the delete request for the +workspace to Terraform. + +### Worker Poll Run State + +The worker synchronizes ServiceNow with the current run state of Terraform +workspaces by polling the HCP Terraform API. On state changes, the worker adds +a comment to the ServiceNow request item with the updated run state and other +metadata. + +![screenshot: ServiceNow integration comments](/img/docs/service-now-comments.png) diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/integrations/service-now/service-catalog-terraform/developer-reference.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/integrations/service-now/service-catalog-terraform/developer-reference.mdx new file mode 100644 index 0000000000..13ae4b2163 --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/integrations/service-now/service-catalog-terraform/developer-reference.mdx @@ -0,0 +1,112 @@ +--- +page_title: ServiceNow Service Catalog integration developer reference +description: Learn how developers can customize the ServiceNow Service Catalog integration. +source: terraform-docs-common +--- + +# Terraform ServiceNow Service Catalog Integration Developer Reference + +The Terraform ServiceNow integration can be customized by ServiceNow developers +using the information found in this document. + +## Terraform Variables and ServiceNow Variable Sets + +ServiceNow has the concept of a Variable Set which is a collection of ServiceNow +Variables that can be referenced in a Flow from a Service Catalog item. The +Terraform Integration codebase can create [Terraform Variables and Terraform +Environment Variables](/terraform/enterprise/workspaces/variables) via the API using the +`tf_variable.createVariablesFromSet()` function. + +This function looks for variables following these conventions: + +| ServiceNow Variable Name | HCP Terraform Variable | +| -------------------------------- | ---------------------------------------------------------- | +| `tf_var_VARIABLE_NAME` | Terraform Variable: `VARIABLE_NAME` | +| `tf_env_ENV_NAME` | Environment Variable: `ENV_NAME` | +| `sensitive_tf_var_VARIABLE_NAME` | Sensitive Terraform Variable (Write Only): `VARIABLE_NAME` | +| `sensitive_tf_env_ENV_NAME` | Sensitive Environment Variable (Write Only): `ENV_NAME` | + +This function takes the ServiceNow Variable Set and HCP Terraform workspace +ID. It will loop through the given variable set collection and create any +necessary Terraform variables or environment variables in the workspace. + +## Customizing with ServiceNow "Script Includes" Libraries + +The Terraform/ServiceNow Integration codebase includes [ServiceNow Script +Includes +Classes](https://docs.servicenow.com/csh?topicname=c_ScriptIncludes.html&version=latest) +that are used to interface with HCP Terraform. The codebase also includes +example catalog items and flows that implement the interface to the HCP Terraform API. + +These classes and examples can be used to help create ServiceNow Catalog Items +customized to your specific ServiceNow instance and requirements. + +### Script Include Classes + +The ServiceNow Script Include Classes can be found in the ServiceNow Studio > +Server Development > Script Include. + +| Class Name | Description | +| ---------------------- | -------------------------------------------------------------- | +| `tf_config` | Helper to pull values from the SN Terraform Configs Table | +| `tf_get_workspace` | Client-callable script to retrieve workspace data | +| `tf_http` | ServiceNow HTTP REST wrapper for requests to the Terraform API | +| `tf_no_code_workspace` | Resources for Terraform no-code module API requests | +| `tf_run` | Resources for Terraform run API requests | +| `tf_terraform_record` | Manage ServiceNow Terraform Table Records | +| `tf_test_config` | Client-callable script to test Terraform connectivity | +| `tf_util` | Miscellaneous helper functions | +| `tf_variable` | Resources for Terraform variable API Requests | +| `tf_vcs_record` | Manage ServiceNow Terraform VCS repositories table records | +| `tf_workspace` | Resources for Terraform workspace API requests | + +### Example Service Catalog Flows and Actions + +The ServiceNow Service Catalog for Terraform provides sample catalog items that use **Flows** +and **Workflows** as their primary process engines. **Flows** are a newer solution developed +by ServiceNow and are generally preferred over **Workflows**. To see which engine an item is using, open it +in the edit mode and navigate to the **Process Engine** tab. For example, **Create Workspace** uses a **Workflow**, +whereas **Create Workspace Flow** is built upon a **Flow**. You can access both in the **Studio**. You can also +manage **Flows** in the **Flow Designer**. To manage **Workflows**, navigate to **All > Workflow Editor**. + +You can find the ServiceNow Example Flows for Terraform in the **ServiceNow Studio > Flows** (or **All > Flow Designer**). +Search for items that belong to the **Terraform** application. By default, Flows execute when someone submits an order request +for a catalog item based on a Flow. Admins can customize the Flows and Actions to add approval flows, set approval rules based +on certain conditions, and configure multiple users or roles as approvers for specific catalog items. + +| Flow Name | Description | +| ------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Create Workspace | Creates a new HCP Terraform workspace from VCS repository. | +| Create Workspace with Vars | Creates a new HCP Terraform workspace from VCS repository and creates any variables provided. | +| Create Run | Creates and queues a new run in the HCP Terraform workspace. | +| Apply Run | Applies a run in the HCP Terraform workspace. | +| Provision Resources | Creates a new HCP Terraform workspace (with auto-apply), creates and queues a run, then applies the run when ready. | +| Provision Resources with Vars | Creates a new HCP Terraform workspace (with auto-apply), creates any variables, creates/queues a run, applies the run when ready. | +| Provision No-Code Workspace and Deploy Resources | Creates a new HCP Terraform workspace based on a no-code module configured in the private registry (with auto-apply), creates any variables, creates and queues a run, then applies the run when ready. | +| Delete Workspace | Creates a destroy run plan. | +| Worker Poll Run State | Polls the HCP Terraform API for the current run state of a workspace. | +| Worker Poll Apply Run | Polls the HCP Terraform API and applies any pending Terraform runs. | +| Worker Poll Destroy Workspace | Queries ServiceNow Terraform Records for resources marked `is_destroyable`, applies the destroy run to destroy resources, and deletes the corresponding Terraform workspace. | +| Update No-Code Workspace and Deploy Resources | Updates an existing no-code workspace to the most recent no-code module version, updates that workspace's attached variable values, and then starts a new Terraform run. | +| Update Workspace | Updates HCP Terraform workspace configurations, such as VCS repository, description, project, execution mode, and agent pool ID (if applicable). | +| Update Workspace with Vars | Allows you to change details about the HCP Terraform workspace configurations and attached variable values. | +| Update Resources | Updates HCP Terraform workspace details and starts a new Terraform run with these new values. | +| Update Resources with Vars | Updates your existing HCP Terraform workspace and its variables, then starts a Terraform run with these updated values. | + +## ServiceNow ACLs + +Access control lists (ACLs) restrict user access to objects and operations based +on permissions granted. This integration includes the following roles that can +be used to manage various components. + +| Access Control Roles | Description | +| :---------------------------------- | --------------------------------------------------------------------------------------------- | +| `x_terraform.config_user` | Can manage the connection from the ServiceNow application to your HCP Terraform organization. | +| `x_terraform.terraform_user` | Can manage all of the Terraform resources created in ServiceNow. | +| `x_terraform.vcs_repositories_user` | Can manage the VCS repositories available for catalog items to be ordered by end-users. | + +For users who only need to order from the Terraform Catalog, we recommend +creating another role with read-only permissions for +`x_terraform_vcs_repositories` to view the available repositories for ordering +infrastructure. Install the Terraform ServiceNow Service Catalog integration by +following [the installation guide](/terraform/enterprise/integrations/service-now/service-catalog-terraform). diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/integrations/service-now/service-catalog-terraform/example-customizations.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/integrations/service-now/service-catalog-terraform/example-customizations.mdx new file mode 100644 index 0000000000..c14d11a220 --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/integrations/service-now/service-catalog-terraform/example-customizations.mdx @@ -0,0 +1,174 @@ +--- +page_title: ServiceNow Service Catalog integration example configurations +description: >- + Learn from example common customizations of the ServiceNow Service Catalog + integration. +source: terraform-docs-common +--- + +# ServiceNow Service Catalog integration example configurations + +This example use case creates a Terraform Catalog Item for requesting resources +with custom variable values passed to the Terraform configuration. + +## Change the scope + +When you make a customization to the app, ensure you switch to the "Terraform" +scope. This guarantees that all items you create are correctly assigned to that +scope. To change the scope in your ServiceNow instance, click the globe icon at +the top right of the screen. For detailed instructions on changing the scope, +refer to the [ServiceNow +documentation](https://developer.servicenow.com/dev.do#!/learn/learning-plans/xanadu/new_to_servicenow/app_store_learnv2_buildneedit_xanadu_application_scope). + +## Make a copy of the existing Catalog Item + +The ServiceNow Service Catalog for Terraform application provides pre-configured [Catalog +Items](/terraform/enterprise/integrations/service-now/service-catalog-terraform/developer-reference#example-service-catalog-flows-and-actions) +for immediate use. We recommend creating a copy of the most recent version of the +Catalog Item to ensure you have access to the latest features and +improvements. Make a copy of the most appropriate Catalog Item for your specific +business requirements by following these steps: + +1. Navigate to **All > Service Catalog > Catalogs > Terraform Catalog**, and review the + Catalog Items based on flows, whose names use the suffix "Flow". + We recommend choosing Flows over Workflows because Flows provide enhanced functionality and performance and are actively developed by ServiceNow. + For more information, refer + to [Catalog Items based on Flows vs. Workflows](/terraform/enterprise/integrations/service-now/service-catalog-terraform/developer-reference#example-service-catalog-flows-and-actions). +2. Open the Catalog Item in editing mode: + 1. Click the Catalog Item to open the request form. + 2. Click **...** in the top right corner. + 3. Select **Configure Item** from the menu. + ![Screenshot: ServiceNow Configure Catalog Item](/img/docs/servicenow-catalog-configure-item.png "Screenshot of the ServiceNow Configure Catalog Item dropdown menu") +3. Click the **Process Engine** tab in the Catalog Item configuration. Take + note of the Flow name associated with the Catalog Item, because you need to + create a copy of this Flow as well. + ![Screenshot: ServiceNow Process Engine](/img/docs/servicenow-catalog-process-engine.png "Screenshot of the ServiceNow Configure Catalog Item – Process Engine tab") +4. Start the copying process: + 1. Click the **Copy** button above the **Related Links** section. + 2. Assign a new name to the copied Catalog Item. + 3. Optionally, modify the description and short description fields. + Right-click the header and select **Save**. + ![Screenshot: ServiceNow Copy Item](/img/docs/servicenow-catalog-copied-item.png "Screenshot of the copied ServiceNow Catalog Item") + +## Adjust the Variable Set + +If a Catalog Item requires users to input variable values, +you must update the variable set with those required variables. +Although some default Catalog Items come with pre-defined example variables, it +is common practice to remove these and replace them with your own custom +variables. + +1. Create a new Variable Set. + 1. On the Catalog Item's configuration page, under the **Related Links** + section, click the **Variable Sets** tab. + 2. Click the **New** button to create a new variable set. Ensure that the + variables in your new set match the variables required by your Terraform + configuration. + ![Screenshot: ServiceNow New Variable Set](/img/docs/servicenow-catalog-new-varset.png "Screenshot of the ServiceNow Catalog Item – new Variable Set") + 3. Select **Single-Row Variable Set** and provide a title and description. + 4. Click **Submit**. Upon submission, you will be redirected back to the Catalog + Item's configuration page. + ![Screenshot: ServiceNow New Variable Set Form](/img/docs/servicenow-catalog-new-varset-form.png "Screenshot of the ServiceNow Catalog Item – new Variable Set") + 5. Click the name of your newly created Variable Set and create your + variables. You must follow the [naming convention for Terraform + variables](/terraform/enterprise/integrations/service-now/service-catalog-terraform/developer-reference#terraform-variables-and-servicenow-variable-sets). + ServiceNow offers various types of variable representation (such as strings, + booleans, and dropdown menus). Refer to the [ServiceNow documentation on + variables](https://docs.servicenow.com/csh?topicname=c_ServiceCatalogVariables.html&version=latest) + and select the types that best suit your use case. You can also set default + values for the variables in the **Default Value** tab, which ServiceNow prefills for the end users. + ![Screenshot: ServiceNow New Variables](/img/docs/servicenow-catalog-variables.png "Screenshot of the ServiceNow Catalog Item – new variables") +2. Attach the newly created Variable Set to your custom Catalog Item and remove + the default Workspace Variables. + 1. Return to the **Variable Sets** tab on the Catalog Item's configuration page + and click the **Edit** button. + 2. Move the "Workspace Variables" Set from the right side to the left side + and click **Save**. Do not remove the + "Workspace Request Create" or the "Workspace Request Update" Sets. + ![Screenshot: ServiceNow Remove Example Variables](/img/docs/servicenow-catalog-remove-example-variables.png "Screenshot of the ServiceNow Catalog Item – new variables") + +## Make a copy of the Flow and Action + +1. Open the ServiceNow Studio by navigating to **All > Studio** and open the + "Terraform" application. Once in the **Terraform** application, navigate to + **Flow Designer > Flows**. + ![Screenshot: ServiceNow Flow Designer Interface](/img/docs/servicenow-catalog-original-flow.png "Screenshot of the ServiceNow Flow Designer – selecting a Flow") + + Another way to access the ServiceNow Studio is to click **All**, select + "Flow Designer", then select **Flows**. You can set the **Application** + filter to "Terraform" to quickly find the desired Flow. +2. Open the Flow referenced in your Catalog Item. Click **...** + in the top right corner of the Flow Designer interface and + select **Copy flow**. Provide a name for the copied Flow and + click **Copy**. + ![Screenshot: ServiceNow Copy Flow Interface](/img/docs/servicenow-catalog-copy-flow.png "Screenshot of the ServiceNow Flow Designer – copying a Flow") +3. Customize your newly copied Flow by clicking **Edit flow**. + ![Screenshot: ServiceNow Edit New Flow Interface](/img/docs/servicenow-catalog-edit-flow.png "Screenshot of the ServiceNow Flow Designer – editing a Flow") + 1. Do not change the **Service Catalog** trigger. + 2. Update the "Get Catalog Variables" action: + 1. Keep the "Requested Item Record" in the **Submitted Request** field. + 2. Select your newly created Catalog Item from the dropdown menu for + **Template Catalog Item**. + 3. Move all of your variables to the **Selected** side in the **Catalog + Variables** section. Remove any previous example variables from the + **Available** side. + ![Screenshot: ServiceNow Get Variables Flow Step](/img/docs/servicenow-catalog-get-variables.png "Screenshot of the ServiceNow Flow Designer – getting Variables step") + 4. Click **Done** to finish configuring this Action. +4. Unfold the second Action in the Flow and click the arrow to open it in + the Action Designer. + ![Screenshot: ServiceNow Open Action Designer](/img/docs/servicenow-catalog-open-action.png "Screenshot of the ServiceNow Action Designer") + 1. Click **...** in the top right corner and select **Copy Action**. + ![Screenshot: ServiceNow Copy Action](/img/docs/servicenow-catalog-copy-action.png "Screenshot of the ServiceNow Copy Action") + Rename it and click **Copy**. + ![Screenshot: ServiceNow Rename Action](/img/docs/servicenow-catalog-rename-action.png "Screenshot of the ServiceNow Rename Action") + 2. In the the Inputs section, remove any previous example variables. + ![Screenshot: ServiceNow Remove Variables From Action](/img/docs/servicenow-catalog-remove-example-variables-from-action.png "Screenshot of the ServiceNow Action Input Variables") + 3. Add your custom variables by clicking the **Create Input** button. Ensure that the variable names match your Catalog Item variables and select the variable type that matches each variable. Click **Save**. + ![Screenshot: ServiceNow Add Variables To Action](/img/docs/servicenow-catalog-add-variables-to-action.png "Screenshot of adding ServiceNow Action Input Variables") + 4. Open the **Script step** within the Action. Remove any example variables + and add your custom variables by clicking **Create Variable** at the + bottom. Enter the name of each variable and drag the corresponding data + pill from the right into the **Value field**. + ![Screenshot: ServiceNow Add Script Step Variables To Action](/img/docs/servicenow-catalog-adjust-script-variables.png "Screenshot of adjusting ServiceNow Action Script Variables") + 5. Click **Save** and then **Publish**. +5. Reopen the Flow and attach the newly created Action to the Flow + after "Get Catalog Variables" step: + 1. Remove the "Create Terraform Workspace with Vars" Action that you copied earlier and replace it with + your newly created Action. + ![Screenshot: ServiceNow Replace Action Step](/img/docs/servicenow-catalog-replace-action.png "Screenshot of replacing ServiceNow Action step") + 2. Connect the new Action to the Flow by dragging and dropping the data pills + from the "Get Catalog Variables" Action to the corresponding inputs of + your new Action. Click **Done** to save this step. + ![Screenshot: ServiceNow Fill Variables for Action](/img/docs/servicenow-catalog-fill-new-action-step.png "Screenshot of filling out ServiceNow Action variables") + 3. Click **Save**. + 4. Click **Activate** to enable the Flow and make it available for use. + +## Set the Flow for your Catalog Item + +1. Navigate back to the Catalog by clicking on **All** and then go to **Service + Catalog > Catalogs > Terraform Catalog**. +2. Locate your custom Catalog Item and click **...** at the top + of the item. From the dropdown menu, select **Configure item**. +3. In the configuration settings, click the **Process Engine** tab. +4. In the **Flow** field, search for the Flow you recently created. Click + the Flow then click the **Update**. + ![Screenshot: ServiceNow Update Process Engine](/img/docs/servicenow-catalog-update-process-engine.png "Screenshot of updating Process Engine for the Catalog Item") + +## Test the Catalog Item + +The new item is now available in the Terraform Service Catalog. To make the +new item accessible to your end users via the Service Portal, follow these +steps: + +1. Navigate to the configuration page of the item you want to make available. +2. Locate the **Catalogs** field on the configuration page and click the lock + icon next to it. +3. In the search bar, type "Service Catalog" and select it from the search + results. Add "Service Catalog" to the list of catalogs associated with the + item. Click the lock icon again to lock the changes. + ![Screenshot: ServiceNow Enable Service Portal](/img/docs/servicenow-catalog-service-portal.png "Screenshot of adding the Catalog Item to the Service Portal") +4. Click the **Update** button at the top of the page. + +After completing these steps, end users will be able to +access the new item through the Service Portal of your ServiceNow instance. You +can access the Service Portal by navigating to **All > Service Portal Home**. diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/integrations/service-now/service-catalog-terraform/index.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/integrations/service-now/service-catalog-terraform/index.mdx new file mode 100644 index 0000000000..83f0111836 --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/integrations/service-now/service-catalog-terraform/index.mdx @@ -0,0 +1,239 @@ +--- +page_title: Set up ServiceNow Service Catalog integration for Terraform Enterprise +description: >- + Learn how to set up the ServiceNow Service Catalog integration for Terraform + Enterprise. +source: terraform-docs-common +--- + +# Set up ServiceNow Service Catalog integration for HCP Terraform + +-> **Integration version:** v2.8.1 + +The Terraform ServiceNow Service Catalog integration enables your end-users to +provision self-serve infrastructure via ServiceNow. By connecting ServiceNow to +HCP Terraform, this integration lets ServiceNow users order Service Items, +create workspaces, and perform Terraform runs using prepared Terraform +configurations hosted in VCS repositories or as [no-code +modules](/terraform/enterprise/no-code-provisioning/module-design) for +self-service provisioning. + + + +@include 'tfc-package-callouts/servicenow-catalog.mdx' + + + +## Summary of the Setup Process + +The integration relies on Terraform ServiceNow Catalog integration software +installed within your ServiceNow instance. Installing and configuring this +integration requires administration in both ServiceNow and HCP Terraform. +Since administrators of these services within your organization are not +necessarily the same person, this documentation refers to a **ServiceNow Admin** +and a **Terraform Admin**. + +First, the Terraform Admin configures your HCP Terraform organization with a +dedicated team for the ServiceNow integration, and obtains a team API token for +that team. The Terraform Admin provides the following to your ServiceNow admin: + +- An Organization name +- A team API token +- The hostname of your HCP Terraform instance +- Any available no-code modules or version control repositories containing Terraform configurations +- Other required variables + token, the hostname of your HCP Terraform instance, and details about no-code + modules or version control repositories containing Terraform configurations and + required variables to the ServiceNow Admin. + +Next, the ServiceNow Admin will install the Terraform ServiceNow Catalog +integration to your ServiceNow instance, and configure it using the team API +token and hostname. + +Finally, the ServiceNow Admin will create a Service Catalog within ServiceNow +for the Terraform integration, and configure it using the version control +repositories or no-code modules, and variable definitions provided by the +Terraform Admin. + +| ServiceNow Admin | Terraform Admin | +| ----------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| | Prepare an organization for use with the ServiceNow Catalog. | +| | Create a team that can manage workspaces in that organization. | +| | Create a team API token so the integration can use that team's permissions. | +| | If using VCS repositories, retrieve the OAuth token IDs and repository identifiers that HCP Terraform uses to identify your VCS repositories. If using a no-code flow, [create a no-code ready module](/terraform/enterprise/no-code-provisioning/provisioning) in your organization's private registry. Learn more in [Configure VCS Repositories or No-Code Modules](/terraform/enterprise/integrations/service-now/service-catalog-terraform/service-catalog-config#configure-vcs-repositories-or-no-code-modules). | +| | Provide the API token, OAuth token ID, repository identifiers, variable definitions, and HCP Terraform hostname to the ServiceNow Admin. | +| Install the Terraform integration application from the ServiceNow App Store. | | +| Connect the integration application with HCP Terraform. | | +| Add the Terraform Service Catalog to ServiceNow. | | +| If you are using the VCS flow, configure the VCS repositories in ServiceNow. | | +| Configure variable sets for use with the VCS repositories or no-code modules. | | + +Once these steps are complete, self-serve infrastructure will be available +through the ServiceNow Catalog. HCP Terraform will provision and manage +requested infrastructure and report the status back to ServiceNow. + +## Prerequisites + +To start using Terraform with the ServiceNow Catalog Integration, you must have: + +- An administrator account on a Terraform Enterprise instance or within a + HCP Terraform organization. +- An administrator account on your ServiceNow instance. +- If you are using the VCS flow, one or more [supported version control + systems](/terraform/enterprise/vcs#supported-vcs-providers) (VCSs) with read + access to repositories with Terraform configurations. +- If you are using no-code provisioning, one or more [no-code modules](/terraform/enterprise/no-code-provisioning/provisioning) created in + your organization's private registry. Refer to the [no-code module + configuration](/terraform/enterprise/integrations/service-now/service-catalog-terraform/service-catalog-config#no-code-module-configuration) + for information about using no-code modules with the ServiceNow Service Catalog + for Terraform. + +You can use this integration on the following ServiceNow server versions: + +- Washington DC +- Xanadu +- Yokohama + +It requires the following ServiceNow plugins as dependencies: + +- Flow Designer support for the Service Catalog (`com.glideapp.servicecatalog.flow_designer`) +- ServiceNow IntegrationHub Action Step - Script (`com.glide.hub.action_step.script`) +- ServiceNow IntegrationHub Action Step - REST (`com.glide.hub.action_step.rest`) + +-> **Note:** Dependent plugins are installed on your ServiceNow instance automatically when the app is downloaded from the ServiceNow Store. + +## Configure HCP Terraform + +Before installing the ServiceNow integration, the Terraform Admin will need to +perform the following steps to configure and gather information from HCP +Terraform. + +1. Sign in to [HCP Terraform](https://app.terraform.io/) or Terraform Enterprise. +2. Either [create an + organization](/terraform/enterprise/users-teams-organizations/organizations#creating-organizations) + or choose an existing organization where ServiceNow will create new + workspaces. + **Save the organization name for later.** +3. [Create a team](/terraform/enterprise/users-teams-organizations/teams) for that + organization called "ServiceNow", and ensure that it has [permission to + manage + workspaces](/terraform/enterprise/users-teams-organizations/permissions#manage-all-workspaces). + You do not need to add any users to this team. + [permissions-citation]: #intentionally-unused---keep-for-maintainers +4. On the "ServiceNow" team's settings page, generate a [team API + token](/terraform/enterprise/users-teams-organizations/api-tokens#team-api-tokens). + **Save the team API token for later.** +5. If you are using the [VCS flow](/terraform/enterprise/integrations/service-now/service-catalog-terraform/service-catalog-config#vcs-configuration): + 1. Ensure your Terraform organization is [connected to a VCS provider](/terraform/enterprise/vcs). Repositories that are connectable to HCP Terraform workspaces can also be used as workspace templates in the ServiceNow integration. + 2. On your organization's VCS provider settings page (**Settings** > **VCS Providers**), find the OAuth Token ID for the VCS provider(s) that you intend to use with the ServiceNow integration. HCP Terraform uses the OAuth token ID to identify and authorize the VCS provider. **Save the OAuth token ID for later.** + 3. Identify the VCS repositories in the VCS provider containing Terraform configurations that the ServiceNow Terraform integration will deploy. Take note of any Terraform or environment variables used by the repositories you select. Save the Terraform and environment variables for later. +6. If using the [no-code flow](/terraform/enterprise/integrations/service-now/service-catalog-terraform/service-catalog-config#no-code-module-configuration), create one or more no-code modules in the private registry of your HCP Terraform. **Save the no-code module names for later.** +7. Provide the following information to the ServiceNow Admin: + - The organization name + - The team API token + - The hostname of your Terraform Enterprise instance, or of HCP Terraform. The hostname of HCP Terraform is `app.terraform.io`. + - The no-code module name(s) or the OAuth token ID(s) of your VCS provider(s), and the repository identifier for each VCS repository containing Terraform configurations that will be used by the integration. + - Any Terraform or environment variables required by the configurations in the + given VCS repositories. + +-> **Note:** Repository identifiers are determined by your VCS provider; they +typically use a format like `/` or +`/`. Azure DevOps repositories use the format +`//_git/`. A GitHub repository hosted at +`https://github.com/exampleorg/examplerepo/` would have the repository +identifier `exampleorg/examplerepo`. + +[permissions-citation]: #intentionally-unused---keep-for-maintainers + +For instance, if you are configuring this integration for your company, `Example +Corp`, using two GitHub repositories, you would share values like the following +with the ServiceNow Admin. + +```markdown +Terraform Enterprise Organization Name: `ServiceNowExampleOrg` + +Team API Token: `q2uPExampleELkQ.atlasv1.A7jGHmvufExampleTeamAPITokenimVYxwunJk0xD8ObVol054` + +Terraform Enterprise Hostname: `terraform.corp.example` + +OAuth Token ID (GitHub org: example-corp): `ot-DhjEXAMPLELVtFA` + - Repository ID (Developer Environment): `example-corp/developer-repo` + - Environment variables: + - `AWS_ACCESS_KEY_ID=AKIAEXAMPLEKEY` + - `AWS_SECRET_ACCESS_KEY=ZB0ExampleSecretAccessKeyGjUiJh` + - `AWS_DEFAULT_REGION=us-west-2` + - Terraform variables: + - `instance_type=t2.medium` + - Repository ID (Testing Environment): `example-corp/testing-repo` + - Environment variables: + - `AWS_ACCESS_KEY_ID=AKIAEXAMPLEKEY` + - `AWS_SECRET_ACCESS_KEY=ZB0ExampleSecretAccessKeyGjUiJh` + - `AWS_DEFAULT_REGION=us-west-2` + - Terraform variables: + - `instance_type=t2.large` +``` + +## Install the ServiceNow Integration + +Before beginning setup, the ServiceNow Admin must install the Terraform +ServiceNow Catalog integration software. + +This can be added to your ServiceNow instance from the [ServiceNow +Store](https://store.servicenow.com/sn_appstore_store.do). Search for the "Terraform" integration, +published by "HashiCorp Inc". + +![Screenshot: ServiceNow Store Page](/img/docs/service-now-store.png "Screenshot of the ServiceNow Store listing for the Terraform Integration") + +## Connect ServiceNow to HCP Terraform + +-> **ServiceNow Roles:** `admin` or `x_terraform.config_user` + +Once the integration is installed, the ServiceNow Admin can connect your +ServiceNow instance to HCP Terraform. Before you begin, you will need the +information described in the "Configure HCP Terraform" section from your +Terraform Admin. + +Once you have this information, connect ServiceNow to HCP Terraform with +the following steps. + +1. Navigate to your ServiceNow Service Management Screen. +2. Using the left-hand navigation, open the configuration table for the + integration to manage the HCP Terraform connection. + - Terraform > Configs +3. Click on "New" to create a new HCP Terraform connection. + - Set Org Name to the HCP Terraform organization name. + - Click on the "Lock" icon to set Hostname to the hostname of your Terraform + Enterprise instance. If you are using the SaaS version of HCP Terraform, + the hostname is `https://app.terraform.io`. Be sure to include "https://" + before the hostname. + - Set API Team Token to the HCP Terraform team API token. + - (Optional) To use the [MID Server](https://docs.servicenow.com/csh?topicname=mid-server-landing.html&version=latest), + select the checkbox and type the `name` in the `MID Server Name` field. +4. Click "Submit". + +![Screenshot: ServiceNow Terraform Config](/img/docs/service-now-updated-config.png "Screenshot of the ServiceNow Terraform Config New Record page") + +## Create and Populate a Service Catalog + +Now that you have connected ServiceNow to HCP Terraform, you are ready to +create a Service Catalog using the VCS repositories or no-code modules provided +by the Terraform Admin. + +Navigate to the [Service Catalog documentation](/terraform/enterprise/integrations/service-now/service-catalog-terraform/service-catalog-config) to +begin. You can also refer to this documentation whenever you need to add or +update request items. + +### ServiceNow Developer Reference + +ServiceNow developers who wish to customize the Terraform integration can refer +to the [developer documentation](/terraform/enterprise/integrations/service-now/service-catalog-terraform/developer-reference). + +### ServiceNow Administrator's Guide. + +Refer to the [ServiceNow Administrator documentation](/terraform/enterprise/integrations/service-now/service-catalog-terraform/admin-guide) for +information about configuring the integration. + +### Example Customizations + +Once the ServiceNow integration is installed, you can consult the [example +customizations documentation](/terraform/enterprise/integrations/service-now/service-catalog-terraform/example-customizations). diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/integrations/service-now/service-catalog-terraform/service-catalog-config.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/integrations/service-now/service-catalog-terraform/service-catalog-config.mdx new file mode 100644 index 0000000000..ce972ed701 --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/integrations/service-now/service-catalog-terraform/service-catalog-config.mdx @@ -0,0 +1,255 @@ +--- +page_title: Create and manage ServiceNow Service Catalog items with Terraform Enterprise +description: >- + Create and manage service catalog items to allow end users to provision + infrastructure using ServiceNow and Terraform Enterprise. +source: terraform-docs-common +--- + +# Create and manage ServiceNow Service Catalog items + +When using ServiceNow with the HCP Terraform integration, you will configure +at least one service catalog item. You will also configure one or more version +control system (VCS) repositories or no-code modules containing the Terraform +configurations which will be used to provision that infrastructure. End users +will request infrastructure from the service catalog, and HCP Terraform will +fulfill the request by creating a new workspace, applying the configuration, and +then reporting the results back to ServiceNow. + +## Prerequisites + +Before configuring a service catalog, you must install and configure the +HCP Terraform integration software on your ServiceNow instance. These steps +are covered in the [installation documentation](/terraform/enterprise/integrations/service-now/service-catalog-terraform). + +Additionally, you must have have the following information: + +1. The no-code module name or the OAuth token ID and repository identifier for + each VCS repository that HCP Terraform will use to provision + infrastructure. Your Terraform Admin will provide these to you. Learn more in + [Configure VCS Repositories or No-Code + Modules](/terraform/enterprise/integrations/service-now/service-catalog-terraform/service-catalog-config#configure-vcs-repositories-or-no-code-modules). +2. Any Terraform or environment variables required by the configurations in the + given VCS repositories or no-code modules. + +Once these steps are complete, in order for end users to provision +infrastructure with ServiceNow and HCP Terraform, the ServiceNow Admin will +perform the following steps to make Service Items available to your end users. + +1. Add at least one service catalog for use with Terraform. +2. If you are using the VCS flow, configure at least one one VCS repository in ServiceNow. +3. Create variable sets to define Terraform and environment variables to be used + by HCP Terraform to provision infrastructure. + +## Add the Terraform Service Catalog + +-> **ServiceNow Role:** `admin` + +First, add a Service Catalog for use with the Terraform integration. Depending +on your organization's needs, you might use a single service catalog, or +several. If you already have a Service Catalog to use with Terraform, skip to +the next step. + +1. In ServiceNow, open the Service Catalog > Catalogs view by searching for + "Service Catalog" in the left-hand navigation. + 1. Click the plus sign in the top right. + 2. Select "Catalogs > Terraform Catalog > Title and Image" and choose a + location to add the Service Catalog. + 3. Close the "Sections" dialog box by clicking the "x" in the upper right-hand + corner. + +-> **Note:** In step 1, be sure to choose "Catalogs", not "Catalog" from the +left-hand navigation. + +## Configure VCS Repositories or No-Code Modules + +-> **ServiceNow Roles:** `admin` or `x_terraform.vcs_repositories_user` + +Terraform workspaces created through the ServiceNow Service Catalog for +Terraform can be associated with a VCS +provider repository or be backed by a [no-code +module](/terraform/enterprise/no-code-provisioning/provisioning) in your +organization's private registry. Administrators determine which workspace type +end users can request from the Terraform Catalog. Below are the key +differences between the version control and no-code approaches. + +### VCS configuration + +To make infrastructure available to your users through version control +workspaces, you must add one or more VCS repositories containing Terraform +configurations to the Service Catalog for Terraform. + +1. In ServiceNow, open the "Terraform > VCS Repositories" table by searching for + "Terraform" in the left-hand navigation. +2. Click "New" to add a VCS repository, and fill in the following fields: + - Name: The name for this repository. This name will be visible to end + users, and does not have to be the same as the repository name as defined + by your VCS provider. Ideally it will succinctly describe the + infrastructure that will be provisioned by Terraform from the repository. + - OAuth Token ID: The OAuth token ID that from your HCP Terraform + organization's VCS providers settings. This ID specifies which VCS + provider configured in HCP Terraform hosts the desired repository. + - Identifier: The VCS repository that contains the Terraform configuration + for this workspace template. Repository identifiers are determined by your + VCS provider; they typically use a format like + `/` or `/`. Azure DevOps + repositories use the format `//_git/`. + - The remaining fields are optional. + - Branch: The branch within the repository, if different from the default + branch. + - Working Directory: The directory within the repository containing + Terraform configuration. + - Terraform Version: The version of Terraform to use. This will default to + the latest version of Terraform supported by your HCP Terraform + instance. +3. Click "Submit". + +![Screenshot: ServiceNow New VCS Repository](/img/docs/service-now-vcs-repository.png "Screenshot of the ServiceNow Terraform New VCS Repository page") + +After configuring your repositories in ServiceNow, the names of those +repositories will be available in the "VCS Repository" dropdown menu a user +orders new workspaces through the following items in the Terraform Catalog: + +- **Create Workspace** +- **Create Workspace with Variables** +- **Provision Resources** +- **Provision Resources with Variables** + +### No-Code Module Configuration + +In version 2.5.0 and newer, ServiceNow administrators can configure +Catalog Items using [no-code +modules](/terraform/enterprise/no-code-provisioning/provisioning). This release +introduces two new additions to the Terraform Catalog - no-code workspace +create and update Items. Both utilize no-code modules from the private registry +in HCP Terraform to enable end users to request infrastructure without writing +code. + + + +@include 'tfc-package-callouts/nocode.mdx' + + + +The following Catalog Items allow you to build and manage workspaces with +no-code modules: + +- **Provision No-Code Workspace and Deploy Resources**: creates a new Terraform + workspace based on a no-code module of your choice, supplies required variable + values, runs and applies Terraform. +- **Update No-Code Workspace and Deploy Resources**: Updates an existing no-code + workspace to the most recent no-code module version, updates that workspace's + attached variable values, and then starts and applies a new Terraform run. + +Administrators can skip configuring VCS repositories in ServiceNow when using +no-code modules. The only input required in the no-code workspace request form +is the name of the no-code module. + +Before utilizing a no-code module, you must publish it to the your organization's +private module registry. With this one-time configuration complete, ServiceNow +Administrators can then call the modules through Catalog requests without +repository management, simplifying the use of infrastructure-as-code. + +> **Hands On:** Try the [Self-service enablement with HCP Terraform and ServiceNow tutorial](/terraform/tutorials/it-saas/servicenow-no-code). + +## Configure a Variable Set + +Most Terraform configurations can be customized with Terraform variables or +environment variables. You can create a Variable Set within ServiceNow to +contain the variables needed for a given configuration. Your Terraform Admin +should provide these to you. + +1. In ServiceNow, open the "Service Catalog > Variable Sets" table by searching for + "variable sets" in the left-hand navigation. +2. Click "New" to add a Variable Set. +3. Select "Single-Row Variable Set". + - Title: User-visible title for the variable set. + - Internal name: The internal name for the variable set. + - Order: The order in which the variable set will be displayed. + - Type: Should be set to "Single Row" + - Application: Should be set to "Terraform" + - Display title: Whether the title is displayed to the end user. + - Layout: How the variables in the set will be displayed on the screen. + - Description: A long description of the variable set. +4. Click "Submit" to create the variable set. +5. Find and click on the title of the new variable set in the Variable Sets + table. +6. At the bottom of the variable set details page, click "New" to add a new + variable. + +- Type: Should be "Single Line Text" for most variables, or "Masked" for + variables containing sensitive values. +- Question: The user-visible question or label for the variable. +- Name: The internal name of the variable. This must be derived from the name of the + Terraform or environment variable. Consult the table below to determine the + proper prefix for each variable name. +- Tooltip: A tooltip to display for the variable. +- Example Text: Example text to show in the variable's input box. + +1. Under the "Default Value" tab, you can set a default value for the variable. +2. Continue to add new variables corresponding to the Terraform and environment + variables the configuration requires. + +When the Terraform integration applies configuration, it will map ServiceNow +variables to Terraform and environment variables using the following convention. +ServiceNow variables that begin with "sensitive\_" will be saved as sensitive +variables within HCP Terraform. + +| ServiceNow Variable Name | HCP Terraform Variable | +| -------------------------------- | ---------------------------------------------------------- | +| `tf_var_VARIABLE_NAME` | Terraform Variable: `VARIABLE_NAME` | +| `tf_env_ENV_NAME` | Environment Variable: `ENV_NAME` | +| `sensitive_tf_var_VARIABLE_NAME` | Sensitive Terraform Variable (Write Only): `VARIABLE_NAME` | +| `sensitive_tf_env_ENV_NAME` | Sensitive Environment Variable (Write Only): `ENV_NAME` | + +## Provision Infrastructure + +Once you configure the Service Catalog for Terraform, ServiceNow users +can request infrastructure to be provisioned by HCP Terraform. + +These requests will be fulfilled by HCP Terraform, which will: + +1. Create a new workspace from the no-code module or the VCS repository provided by ServiceNow. +2. Configure variables for that workspace, also provided by ServiceNow. +3. Plan and apply the change. +4. Report the results, including any outputs from Terraform, to ServiceNow. + +Once this is complete, ServiceNow will reflect that the Request Item has been +provisioned. + +-> **Note:** The integration creates workspaces with +[auto-apply](/terraform/enterprise/workspaces/settings#auto-apply-and-manual-apply) +enabled. HCP Terraform will queue an apply for these workspaces whenever +changes are merged to the associated VCS repositories. This is known as the +[VCS-driven run workflow](/terraform/enterprise/run/ui). It is important to keep in mind +that all of the ServiceNow workspaces connected to a given repository will be +updated whenever changes are merged to the associated branch in that repository. + +## Execution Mode + +If using v2.2.0 or above, the Service Catalog app allows you to set an [execution mode](/terraform/enterprise/workspaces/settings#execution-mode) for your Terraform workspaces. There are two modes to choose from: + +- The default value is "Remote", which instructs HCP Terraform to perform runs on its disposable virtual machines. +- Selecting "Agent" mode allows you to run Terraform operations on isolated, private, or on-premises infrastructure. This option requires you to create an Agent Pool in your organization beforehand, then provide that Agent Pool's id when you order a new workspace through the Service Catalog. + + + +@include 'tfc-package-callouts/agents.mdx' + + + +## Workspace Name + +Version 2.4.0 of the Service Catalog for Terraform introduces the ability to set custom names for your Terraform workspaces. You can choose a prefix for your workspace name that the Service Catalog app will append the ServiceNow RITM number to. If you do not define a workspace prefix, ServiceNow will use RITM number as the workspace name. + +Workspace names can include letters, numbers, dashes (`-`), and underscores (`_`), and should not exceed 90 characters. +Refer to the [workspace naming recommendations](/terraform/enterprise/workspaces/create#workspace-naming) for best practices. + +## Workspace Tags + +Version 2.8.0 extends support for the key-value pair tags while still also supporting flat string tags version 2.4.0 introduced. Use the "Workspace Tags" field to provide a comma-separated list of key-value pair tags in the format "env: prod, instance: test" that will be parsed and attached to the workspace in HCP Terraform. + +Tags give you an easier way to categorize, filter, and manage workspaces provisioned through the Service Catalog for Terraform. +We recommend that you set naming conventions for tags with your end users to avoid variations such as `ec2`, `aws-ec2`, `aws_ec2`. + +Workspace tags have a 255 character limit and can contain letters, numbers, colons, hyphens, and underscores. Refer to the [workspace tagging rules](/terraform/enterprise/workspaces/create#workspace-tags) for more details. diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/integrations/service-now/service-catalog-terraform/troubleshoot.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/integrations/service-now/service-catalog-terraform/troubleshoot.mdx new file mode 100644 index 0000000000..b43864651c --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/integrations/service-now/service-catalog-terraform/troubleshoot.mdx @@ -0,0 +1,120 @@ +--- +page_title: >- + Troubleshoot the ServiceNow Service Catalog Integration for Terraform + Enterprise +description: Troubleshooting tips for ServiceNow Service Catalog Integration. +source: terraform-docs-common +--- + +# Troubleshoot the ServiceNow Service Catalog integration + +This page offers troubleshooting tips for common issues with the ServiceNow Service Catalog Integration for HCP Terraform. +It also provides instructions on how to find and read logs to diagnose and resolve issues. + +## Find logs + +Logs are crucial for diagnosing issues. You can find logs in ServiceNow in the following places: + +### Workflow logs + +To find workflow logs, click on the RITM number on a failed ticket to open the request item. +Scroll down to **Related Links > Workflow Context** and open the **Workflow Log** tab. + +### Flow logs + +To find flow logs, click on the RITM number on a failed ticket to open the request item. +Scroll down to **Related Links > Flow Context > Open Context Record** and open the **Flow engine log entries** tab. + +### Application logs + +To find application logs, navigate to **All > System Log > Application Logs.** +Set the **Application** filter to "Terraform". +Search for logs around the time your issue occurred. Some records include HTTP status codes and detailed error messages. + +### Outbound requests + +ServiceNow logs all outgoing API calls, including calls to HCP Terraform. To view the log of outbound requests, navigate to **All > System Logs > Outbound HTTP Requests.** +To customize the table view, add columns like "URL," "URL Path," and "Application scope." +Logs from the Catalog app are marked with the `x_325709_terraform` scope. + +## Enable email notifications + +To enable email notifications and receive updates on your requested item tickets: + +1. Log in to your ServiceNow instance as an administrator. +2. **Click System Properties > Email Properties**. +3. In the **Outbound Email Configuration** panel, select **Yes** next to the check-box with the email that ServiceNow should send notifications to. + +To ensure you have relevant notifications configured in your instance: + +1. Navigate to **System Notification > Email > Notifications.** +2. Search for "Request Opened" and "Request Item Commented" and ensure they are activated. + +Reach out to ServiceNow customer support if you run into any issues with the global configurations. + +## Common problems + +This section details frequently encountered issues and how they can be resolved. + +### Failure to create a workspace + +If you order the "create a workspace" catalog item and nothing happens in ServiceNow and HCP Terraform does not create a workspace then there are several possible reasons why: + +Ensure your HCP Terraform token, hostname, and organization name is correct. + +1. Make sure to use a **Team API Token**. This can be found in HCP Terraform under "API Tokens". +2. Ensure the team API token has the correct permissions. +3. Double-check your organization name by copying and pasting it from HCP Terraform or Terraform Enterprise. +4. Double-check your host name. +5. Make sure you created your team API token in the same organization you are using +6. Test your configuration. First click **Update** to process any changes then \*\*Test Config to make sure the connection is working. + +Verify your VCS configuration. + +1. The **Identifier** field should not have any spaces. The ServiceNow Service Catalog Integration requires that you format repository names in the `username/repo_name` format. +2. The **Name** can be anything, but it is better to avoid special characters as per naming convention. +3. Double-check the OAuth token ID in your HCP Terraform/Terraform Enterprise settings. To retrieve your OAuth token ID, navigate to your HCP Terraform organization's settings page, then click **Provider** in the left navigation bar under **Version Control**. + +### Failure to successfully order any catalog item + +After placing an order for any catalog item, navigate to the comments section in the newly created RITM ticket. +The latest comment will contain a response from HCP Terraform. + +### Frequency of comments and outputs + +When you place an order in the Terraform Catalog, ServiceNow submits and processes the order, then attaches additional comments to the order to indicate whether HCP Terraform successfully created the workspace. + +By default, ServiceNow polls HCP Terraform every 5 minutes for the latest status of the Terraform run. ServiceNow does not show any comments until the next ping. + +To configure ServiceNow to poll HCP Terraform more frequently: + +1. Navigate to **All > Flow designer**. +2. Set the **Application** filter to **Terraform**. +3. Under the **Name** column click **Worker Poll Run State**. +4. Click on the trigger and adjust the interval to your desired schedule. +5. Click **Done > Save > Activate** to save your changes. + +### Using no-code modules feature + +If ServiceNow fails to deploy a no-code module catalog item, verify the following: + +1. Ensure that your HCP Terraform organization has an [HCP Plus tier](https://www.hashicorp.com/products/terraform/pricing) subscription. +2. Ensure the name you enter for your no-code module in the catalog user form matches the no-code module in HCP Terraform. + +### Updating no-code workspaces + +If the “update no-code workspace” catalog item returns the output message “No update has been made to the workspace”, then you have not upgraded your no-code module in HCP Terraform. + +### Application Scope + +If you are making customizations and you encounter unexpected issues, make sure to change the scope from **Global** to **Terraform** and recreate your customized items in the **Terraform scope**. +For additional instructions on customizations, refer to the [example customizations](/terraform/enterprise/integrations/service-now/service-catalog-terraform/example-customizations) documentation. + +### MID server + +If you are using a MID server in your configuration, check the connectivity by using the **Test Config** button on the configurations page. +Additionally, when ServiceNow provisions a MID server, navigate to **MID Servers > Servers** to check if the status is “up” and “validated”. + +### Configuration + +While the app allows multiple config entries, only one should be present as this can interfere with the functionality of the app. diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/integrations/service-now/service-graph/customizations.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/integrations/service-now/service-graph/customizations.mdx new file mode 100644 index 0000000000..9263ecc2a0 --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/integrations/service-now/service-graph/customizations.mdx @@ -0,0 +1,97 @@ +--- +page_title: Customize the ServiceNow Service Graph Connector for Terraform Enterprise +description: >- + Learn how to edit ETL mappings in the ServiceNow Service Graph Connector for + Terraform Enterprise integration. +source: terraform-docs-common +--- + +# Customize the ServiceNow Service Graph Connector for Terraform + +-> **ServiceNow roles:** `admin` + +-> **ServiceNow plugin requirement:** `IntegrationHub ETL` + +You can update and customize the default ETL mapping rules offered by the Service Graph Connector for Terraform. + +To ensure that your custom rules remain intact during future updates, you can clone the existing ETL record and maintain it separately from the default one. + +This documentation guides you through the process of mapping a resource using an example of an AWS virtual private network (VPC). Although this resource is already covered by the application, the principles discussed apply to any new potential resource mapping. + +Any customizations should be done from the application's scope: **Service Graph Connector for Terraform**. + +## Clone the ETL map + +Navigate to the **IntegrationHub ETL** in the top menu. Check the **SG-Terraform** record and click **Duplicate**. Refer to the [ServiceNow documentation](https://docs.servicenow.com/en-US/bundle/utah-servicenow-platform/page/product/configuration-management/task/duplicate-cmdb-transform-map.html) to create a duplicate of an existing ETL transform map. + +## Build a resource in HCP Terraform + +Create a new workspace in your HCP Terraform organization and create a Terraform resource that you would like to map. It helps to have a Terraform state record of the resource to ensure accurate mapping. + +[Configure a webhook](/terraform/enterprise/integrations/service-now/service-graph/service-graph-setup#configure-terraform-webhook) and initiate a Terraform run. + +## Download Terraform State + +Once the run is successfully completed, open your ServiceNow instance, click on **All** and navigate to **Scheduled Imports**. + +Open the **SG-Terraform Scheduled Process State** record, search for the import set corresponding to the latest webhook request. Click the **Import Set** field to open the import set. Wait for the import set to be successfully processed. + +Since there are no existing ETL rules configured for the new resource, it is ignored during the ETL process. + +Open the **Outbound Http Requests** tab to list the requests sent from your ServiceNow instance to HCP Terraform and get the latest state of the workspace. + +![ServiceNow Service Graph Connector Outbound Http Requests interface](/img/docs/service-now-service-graph-state-object-url.png) + +Open the record that starts with " by clicking on the timestamp. Copy the content of the URL field and open it you your browser to download the Terraform state file. + +Locate the resource in the state object. This JSON record will serve as a source for the future mapping. + +## Identify the CI target + +Pick a suitable Configuration Item (CI) target for your resource. For example, the AWS virtual private network (VPC) resource is mapped to Cloud Network (`cmdb_ci_network`) by the Service Graph Connector for Terraform. Refer to the [ServiceNow CMDB documentation](https://docs.servicenow.com/en-US/bundle/utah-servicenow-platform/page/product/configuration-management/reference/cmdb-tables-details.html) for more details on available CI tables. + +## Consult the CI Class Manager + +After selecting an appropriate CI target, it is important to consult the CI Class Manager for guidance on dependent relationships. Many CMDB resources rely on other CI tables. If a related class is not properly mapped, the ETL job will generate errors or warnings and fail to import your resource into the CMDB. + +In the top navigation, click on **All**, search for **CI Class Manager**, and click on **Open Hierarchy**. Search for your target CI Class and check **Dependent Relationships** tab to learn more about dependent mappings required by the resource. + +For example, according to the **CI Class Manager**, **Cloud Network** should be hosted on **Logical Datacenter** and **Cloud Service Account**. + +## Set the mapping rules + +Open the **IntegrationHub ETL** from the top navigation menu and select your cloned ETL map record prepared for customization. Refer to the [ServiceNow documentation](https://docs.servicenow.com/en-US/bundle/utah-servicenow-platform/page/product/configuration-management/concept/create-etl-transform-map.html) for instructions to create an ETL transform map. + +Click on the first **Specify Basic Details** section of the ETL Transform Map Assistant. Select the import set number containing your resource from the **Sample Import Set** dropdown and click **Mark as Complete**. + +Open the **Preview and Prepare Data** section and review the imported rows. Click **Mark as Complete**. + +The third section provides the interface for mapping resource attributes. Click on **Select CMDB Classes to Map Source Data**. Click on **Add Conditional Class** button at the top. Set the rules that will identify your resource in the import set. Use the `type` field value from the Terraform state object to identify your resource (on the ServiceNow side, field name are prefaced with `u_`). Set the target CMDB CI Class name and click **Save**. + +![ServiceNow Service Graph Connector: setting the Conditional Class rules in the ETL mapping](/img/docs/service-now-service-graph-conditional-class-mapping.png) + +To modify the mapping for your new Conditional Class record, select **Edit Mapping**. On the right side of the interface, drag the relevant data pills and drop them into the corresponding CMDB fields on the left side. Refer to the Terraform state record to verify the presence of attributes. For uniqueness, the **Source Native Key** value is typically mapped to the `arn` field when dealing with AWS resources. All resources mapped in the Service Graph Connector for Terraform will have the **Operational status** and **Name** fields populated. + +![ServiceNow Service Graph Connector: mapping resource attributes in the ETL](/img/docs/service-now-service-graph-etl-attribute-mapping.png) + +Once the mapping is completed, click on the left arrow at the top to return to the list of Conditional Classes. Map two more conditional classes in the same manner, according to the rules set in the CI Class Manager: **Logical Datacenter** (**AWS Datacenter** in case of AWS VPC) and **Cloud Service Account**. Since the AWS cloud provider is already covered by the application, these classes are already present. Click **Edit Class** to include your newly mapped resource into the listed conditional rules. Add another **OR** condition to each of them and click **Save**. + +![ServiceNow Service Graph Connector: updating conditions on existing parent records when a new resource is mapped](/img/docs/service-now-service-graph-etl-condition-update.png) + +Click **Mark as Complete** to finalize the **Select CMDB Classes to Map Source Data** section. + +## Set the required relationships + +Click **Add Relationships** to continue to the next section. Click the **Add Conditional Relationship** button at the top of the page. The following configuration tells the ETL that when a record with `aws_vpc` type is found in the import set, it should be hosted on **AWS Datacenter 1**. Click **Save**. + +![ServiceNow Service Graph Connector: setting dependent relationships in the ETL mapping interface](/img/docs/service-now-service-graph-etl-setting-relationship.png) + +A similar dependent relationship needs to be established from **AWS Datacenter** to **Cloud Service Account**. Since the AWS cloud provider is already covered by the application, the relationship record is present in the application. Click **Edit Relationship** and add another **OR** condition containing your new resource to the list. Click **Save**. + +![ServiceNow Service Graph Connector: updating existing dependent relationships in the ETL mapping interface](/img/docs/service-now-service-graph-etl-editing-relationship.png) + +Click **Mark as Complete** to finalize the **Add Relationships** section. + +## Run a test + +There are two ways to test the new resource mapping. You can utilize the **Test and Rollback Integration Results** interface of the ETL Transform Map Assistant. Alternatively, you can initiate a new run in your HCP Terraform workspace that includes the deployment of the resource. diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/integrations/service-now/service-graph/index.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/integrations/service-now/service-graph/index.mdx new file mode 100644 index 0000000000..96b511744b --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/integrations/service-now/service-graph/index.mdx @@ -0,0 +1,83 @@ +--- +page_title: ServiceNow Service Graph Connector for Terraform Enterprise overview +description: >- + Use the ServiceNow Service Graph Connector to enable users to import Terraform + Enterprise-built infrastructure into ServiceNow CMDB. +source: terraform-docs-common +--- + +# ServiceNow Service Graph Connector for Terraform overview + +-> **Integration version:** v1.3.0 + +Use the Service Graph Connector for Terraform to securely import HCP Terraform resources into your ServiceNow instance. The ServiceNow Service Graph for Terraform is a certified scoped application available in the [ServiceNow Store](https://store.servicenow.com/sn_appstore_store.do#!/store/application/0b0600891b52c150c216ebd56e4bcb32). + +The integration is based on the [Service Graph Connector](https://www.servicenow.com/products/service-graph-connectors.html) technology that provides a framework for discovering and mapping relationships between the organization's infrastructure and the ServiceNow Configuration Items (CIs), and then automatically updating the [ServiceNow CMDB (Configuration Management Database)](https://www.servicenow.com/products/servicenow-platform/configuration-management-database.html) with this information. This enables platform teams to gain a comprehensive view of the resources they support. The CMDB is a central repository within the ServiceNow platform, which provides a single source of truth for your infrastructure and offers configurable dashboards for monitoring and reporting. + +## Key benefits + +- **Enhanced visibility**: The Service Graph Connector for Terraform updates the CMDB dashboards with resources deployed in HCP Terraform. +- **Improved efficiency**: By connecting Terraform to the ServiceNow CMDB, platform teams can manage and search Terraform-provisioned resources in the CMDB alongside the rest of the company's infrastructure. +- **Consistent management**: Terraform state file changes get automatically and securely updated in the ServiceNow CMDB, capturing status changes for all technical resources in a timely manner. +- **Extensibility**: ServiceNow admins can customize mappings for additional resource types, potentially working with HashiCorp’s entire Terraform ecosystem made up of thousands of providers. + +## Technical design + +The diagram below shows how the Service Graph Connector for Terraform connects HCP Terraform to your ServiceNow instance. + +![ServiceNow Service Graph Connector for Terraform: design diagram](/img/docs/service-now-service-graph-design.png) + +The Service Graph Connector for Terraform integrates with HCP Terraform to fetch up-to-date information about your deployments. It leverages the Terraform state as the primary data source. The application doesn't make any requests to your cloud provider or require you to share any cloud credentials. + +## Import methods + +The integration offers two methods of importing your Terraform resources into CMDB. You can configure the application to periodically pull all your resources in one batch. Alternatively, you can set up webhooks in your Terraform workspaces, which will notify your ServiceNow instance about new deployments. + +### Scheduled polling + +The Service Graph Connector for Terraform can be scheduled to periodically poll HCP Terraform. Depending on the size of your infrastructure and how frequently the state of your resources needs to be refreshed in CMDB, the polling schedule can be set anywhere from once a week to every second. This option is not recommended for big environments with thousands of Terraform workspaces as the import job will take several hours to complete. + +The scheduled job makes a request to HCP Terraform to obtain all organizations that the HCP Terraform API token provided to the application has access to. It will attempt to import all relevant resources from all workspaces within each of those organizations. The processing time depends of the number of organizations and workspaces in HCP Terraform. Configuring the import job to run frequently is not recommended for big environments. + +To access the scheduler, search for **Service Graph Connector for Terraform** in the top navigation menu and select **SG-Import Schedule**. You can change the polling settings and view all previous import sets pulled into your ServiceNow instance using this method. + +### HCP Terraform Webhook Notifications + +You can configure [webhook notifications](/terraform/enterprise/workspaces/settings/notifications) for all relevant workspaces in HCP Terraform organization. Webhooks offer an event-based approach to importing your resources. The import is triggered as soon as a Terraform run is successfully completed in HCP Terraform. + +Webhook POST requests are sent to an API endpoint exposed by the Service Graph Connector for Terraform in your ServiceNow instance. Each webhook request includes an HMAC token, and the endpoint validates the signature using the secret you provide. Learn more about [HCP Terraform notification authenticity](/terraform/enterprise/workspaces/settings/notifications#notification-authenticity). + +Internally, the application uses a scheduled job as a helper to keep track of the incoming webhook requests. To activate, configure, and view the history of all webhook imports, navigate to **Scheduled Imports** and select **SG-Terraform Scheduled Process State**. By default, the job is set to run every minute. + +-> **Tip:** Both import options may be enabled, or you may choose to configure only the webhooks or the scheduled import. + +The [setup page](/terraform/enterprise/integrations/service-now/service-graph/service-graph-setup) provides configuration details for both import modes. + +## ETL (Extract, Transform, Load) + +After the application successfully imports the resources, they are temporarily stored in a staging database table. The import set records are then transferred to the ETL (Extract, Transform, Load) pipeline. Search for **IntegrationHub ETL** in the top navigation menu to view and edit the default ETL rules of the Service Graph Connector for Terraform. The application's ETL Transform Map is called **SG-Terraform**. + +To deactivate resources that you do not want imported into the CMDB, navigate to the **Select CMDB Classes to Map Source Data** section of the application's ETL record, and toggle the switch on the resource mapping record to deactivate it. + +![screenshot: ServiceNow Service Graph Connector for Terraform resource ETL deactivation button](/img/docs/service-now-service-graph-deactivate-etl.png) + +-> **Tip:** Run an import before you open the ETL map as the interface requires at least one import set stored in the memory to be able to display the rules. + +## Supported resources + +The Service Graph Connector for Terraform supports selected resources from the following cloud providers: + +- AWS +- Microsoft Azure +- Google Cloud +- VMware vSphere + +The [resource mapping](/terraform/enterprise/integrations/service-now/service-graph/resource-coverage) documentation contains tables detailing the mapping of objects and attributes between HCP Terraform and ServiceNow CMDB. + +## Destroyed resources + +After the destroy operation is completed in HCP Terraform and the application's import job is finished in your ServiceNow instance, the **Operational Status** field of all resources in the CMDB removed from the Terraform state during the deletion process will be updated to **Non-Operational**. + +## Get started + +Refer to the [setup page](/terraform/enterprise/integrations/service-now/service-graph/service-graph-setup) for information on how to configure the integration in your ServiceNow instance. diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/integrations/service-now/service-graph/resource-coverage/aws.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/integrations/service-now/service-graph/resource-coverage/aws.mdx new file mode 100644 index 0000000000..d2ccc40b18 --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/integrations/service-now/service-graph/resource-coverage/aws.mdx @@ -0,0 +1,219 @@ +--- +page_title: ServiceNow Service Graph Connector AWS resource coverage +description: >- + Use the ServiceNow Service Graph integration to import selected resources from + AWS into ServiceNow CMDB. +source: terraform-docs-common +--- + +# ServiceNow Service Graph Connector AWS resource coverage + +This page details the mapping rules for importing AWS resources, provisioned with Terraform, into ServiceNow CMDB. + +## Mapping of Terraform resources to CMDB CI Classes + +| AWS resource | Terraform resource name | ServiceNow CMDB CI Class | ServiceNow CMDB Category Name | +| ------------------------------------------------------------------------------------- | --------------------------- | -------------------------------- | ----------------------------- | +| AWS account | N/A | `cmdb_ci_cloud_service_account` | Cloud Service Account | +| AWS region | N/A | `cmdb_ci_aws_datacenter` | AWS Datacenter | +| EC2 Instance | `aws_instance` | `cmdb_ci_vm_instance` | Virtual Machine Instance | +| S3 Bucket | `aws_s3_bucket` | `cmdb_ci_cloud_object_storage` | Cloud Object Storage | +| ECS Cluster | `aws_ecs_cluster` | `cmdb_ci_cloud_ecs_cluster` | AWS Cloud ECS Cluster | +| EKS Cluster | `aws_eks_cluster` | `cmdb_ci_kubernetes_cluster` | Kubernetes Cluster | +| VPC | `aws_vpc` | `cmdb_ci_network` | Cloud Network | +| Database Instance (_non-Aurora databases: e.g., MySQL, PostgreSQL, SQL Server, etc._) | `aws_db_instance` | `cmdb_ci_cloud_database` | Cloud DataBase | +| RDS Aurora Cluster | `aws_rds_cluster` | `cmdb_ci_cloud_db_cluster` | Cloud DataBase Cluster | +| RDS Aurora Instance | `aws_rds_cluster_instance` | `cmdb_ci_cloud_database` | Cloud DataBase | +| DynamoDB Global Table | `aws_dynamodb_global_table` | `cmdb_ci_dynamodb_global_table` | DynamoDB Global Table | +| DynamoDB Table | `aws_dynamodb_table` | `cmdb_ci_dynamodb_table` | DynamoDB Table | +| Security Group | `aws_security_group` | `cmdb_ci_compute_security_group` | Compute Security Group | +| Lambda | `aws_lambda_function` | `cmdb_ci_cloud_function` | Cloud Function | +| Load Balancer | `aws_lb` | `cmdb_ci_cloud_load_balancer` | Cloud Load Balancer | +| Tags | N/A | `cmdb_key_value` | Key Value | + +## Resource relationships + +| Child CI Class | Relationship type | Parent CI Class | +| ----------------------------------------------------------- | ----------------- | --------------------------------------------------------- | +| AWS Datacenter 1 (`cmdb_ci_aws_datacenter`) | Hosted On::Hosts | Cloud Service Account 1 (`cmdb_ci_cloud_service_account`) | +| AWS Datacenter 2 (`cmdb_ci_aws_datacenter`) | Hosted On::Hosts | Cloud Service Account 6 (`cmdb_ci_cloud_service_account`) | +| Virtual Machine Instance 1 (`cmdb_ci_vm_instance`) | Hosted On::Hosts | AWS Datacenter 1 (`cmdb_ci_aws_datacenter`) | +| Virtual Machine Instance 1 (`cmdb_ci_vm_instance`) | Reference | Key Value 1 (`cmdb_key_value`) | +| AWS Cloud ECS Cluster 1 (`cmdb_ci_cloud_ecs_cluster`) | Hosted On::Hosts | AWS Datacenter 1 (`cmdb_ci_aws_datacenter`) | +| AWS Cloud ECS Cluster 1 (`cmdb_ci_cloud_ecs_cluster`) | Reference | Key Value 2 (`cmdb_key_value`) | +| Cloud Object Storage 1 (`cmdb_ci_cloud_object_storage`) | Hosted On::Hosts | AWS Datacenter 2 (`cmdb_ci_aws_datacenter`) | +| Cloud Object Storage 1 (`cmdb_ci_cloud_object_storage`) | Reference | Key Value 3 (`cmdb_key_value`) | +| Kubernetes Cluster 1 (`cmdb_ci_kubernetes_cluster`) | Hosted On::Hosts | AWS Datacenter 1 (`cmdb_ci_aws_datacenter`) | +| Kubernetes Cluster 1 (`cmdb_ci_kubernetes_cluster`) | Reference | Key Value 4 (`cmdb_key_value`) | +| Cloud Network 1 (`cmdb_ci_network`) | Hosted On::Hosts | AWS Datacenter 1 (`cmdb_ci_aws_datacenter`) | +| Cloud Network 1 (`cmdb_ci_network`) | Reference | Key Value 5 (`cmdb_key_value`) | +| Cloud DataBase 1 (`cmdb_ci_cloud_database` ) | Hosted On::Hosts | AWS Datacenter 1 (`cmdb_ci_aws_datacenter`) | +| Cloud DataBase 1 (`cmdb_ci_cloud_database` ) | Reference | Key Value 6 (`cmdb_key_value`) | +| Cloud DataBase Cluster 1 (`cmdb_ci_cloud_db_cluster`) | Hosted On::Hosts | AWS Datacenter 1 (`cmdb_ci_aws_datacenter`) | +| Cloud DataBase Cluster 1 (`cmdb_ci_cloud_db_cluster`) | Reference | Key Value 7 (`cmdb_key_value`) | +| DynamoDB Global Table 1 (`cmdb_ci_dynamodb_global_table`) | Hosted On::Hosts | Cloud Service Account 1 (`cmdb_ci_cloud_service_account`) | +| DynamoDB Table 1 (`cmdb_ci_dynamodb_table`) | Hosted On::Hosts | AWS Datacenter 1 (`cmdb_ci_aws_datacenter`) | +| DynamoDB Table 1 (`cmdb_ci_dynamodb_table`) | Reference | Key Value 8 (`cmdb_key_value`) | +| Compute Security Group 1 (`cmdb_ci_compute_security_group`) | Hosted On::Hosts | AWS Datacenter 1 (`cmdb_ci_aws_datacenter`) | +| Compute Security Group 1 (`cmdb_ci_compute_security_group`) | Reference | Key Value 10 (`cmdb_key_value`) | +| Cloud Function 1 (`cmdb_ci_cloud_function`) | Hosted On::Hosts | AWS Datacenter 1 (`cmdb_ci_aws_datacenter`) | +| Cloud Function 1 (`cmdb_ci_cloud_function`) | Reference | Key Value 11 (`cmdb_key_value`) | +| Cloud Load Balancer 1 (`cmdb_ci_cloud_load_balancer`) | Hosted On::Hosts | AWS Datacenter 1 (`cmdb_ci_aws_datacenter`) | +| Cloud Load Balancer 1 (`cmdb_ci_cloud_load_balancer`) | Reference | Key Value 12 (`cmdb_key_value`) | + +## Field attributes mapping + +### Cloud Service Account (`cmdb_ci_cloud_service_account`) + +| CMDB field | Terraform state field | +| ------------------ | -------------------------------------------- | +| Source Native Key | Resource account number extracted from `arn` | +| Account Id | Resource account number extracted from `arn` | +| Datacenter Type | Resource cloud provider extracted from `arn` | +| Object ID | Resource id extracted from `arn` | +| Name | Resource name extracted from `arn` | +| Operational Status | Defaults to "1" ("Operational") | + +### AWS Datacenter (`cmdb_ci_aws_datacenter`) + +| CMDB field | Terraform state field | +| ------------------ | --------------------------------------------------------------- | +| Source Native Key | Concatenation of region and account number extracted from `arn` | +| Object Id | Region extracted from `arn` | +| Region | Region extracted from `arn` | +| Name | Region extracted from `arn` | +| Operational Status | Defaults to "1" ("Operational") | + +### Virtual Machine Instance (`cmdb_ci_vm_instance`) + +| CMDB field | Terraform state field | +| ------------------ | ------------------------------- | +| Source Native Key | `arn` | +| Object Id | `id` | +| Placement Group ID | `placement_group` | +| IP Address | `public_ip` | +| Status | `instance_state` | +| VM Instance ID | `id` | +| Name | `id` | +| State | `state` | +| CPU | `cpu_core_count` | +| Operational Status | Defaults to "1" ("Operational") | + +### AWS Cloud ECS Cluster (`cmdb_ci_cloud_ecs_cluster`) + +| CMDB field | Terraform state field | +| ------------------ | ------------------------------- | +| Source Native Key | `arn` | +| Object Id | `id` | +| Name | `name` | +| Operational Status | Defaults to "1" ("Operational") | + +### Cloud Object Storage (`cmdb_ci_cloud_object_storage`) + +| CMDB field | Terraform state field | +| ------------------ | -------------------------------------------- | +| Source Native Key | `arn` | +| Object Id | `id` | +| Cloud Provider | Resource cloud provider extracted from `arn` | +| Name | `bucket` | +| Operational Status | Defaults to "1" ("Operational") | + +### Kubernetes Cluster (`cmdb_ci_kubernetes_cluster`) + +| CMDB field | Terraform state field | +| ------------------ | ------------------------------- | +| Source Native Key | `arn` | +| IP Address | `endpoint` | +| Port | Defaults to "6443" | +| Name | `name` | +| Operational Status | Defaults to "1" ("Operational") | + +### Cloud Network (`cmdb_ci_network`) + +| CMDB field | Terraform state field | +| ------------------ | ------------------------------- | +| Source Native Key | `arn` | +| Object Id | `id` | +| Name | `name` | +| Operational Status | Defaults to "1" ("Operational") | + +### Cloud DataBase (`cmdb_ci_cloud_database`) + +| CMDB field | Terraform state field | +| --------------------------- | -------------------------------------------- | +| Source Native Key | `arn` | +| Object Id | `id` | +| Version | `engine_version` | +| Type | `engine` | +| TCP port(s) | `port` | +| Category | `instance_class` | +| Fully qualified domain name | `endpoint` | +| Location | Region extracted from `arn` | +| Name | `name` | +| Vendor | Resource cloud provider extracted from `arn` | +| Operational Status | Defaults to "1" ("Operational") | + +### Cloud DataBase Cluster (`cmdb_ci_cloud_db_cluster`) + +| CMDB field | Terraform state field | +| --------------------------- | -------------------------------------------- | +| Source Native Key | `arn` | +| Cluster ID | `cluster_resource_id` | +| Name | `name` | +| TCP port(s) | `port` | +| Fully qualified domain name | `endpoint` | +| Vendor | Resource cloud provider extracted from `arn` | +| Operational Status | Defaults to "1" ("Operational") | + +### DynamoDB Table (`cmdb_ci_dynamodb_table`) + +| CMDB field | Terraform state field | +| ------------------ | ------------------------------- | +| Source Native Key | `arn` | +| Object Id | `arn` | +| Location | Region extracted from `arn` | +| Name | `name` | +| Operational Status | Defaults to "1" ("Operational") | + +### DynamoDB Global Table (`cmdb_ci_dynamodb_global_table`) + +| CMDB field | Terraform state field | +| ------------------ | ------------------------------- | +| Source Native Key | `arn` | +| Object Id | `arn` | +| Name | `name` | +| Operational Status | Defaults to "1" ("Operational") | + +### Compute Security Group (`cmdb_ci_compute_security_group`) + +| CMDB field | Terraform state field | +| ------------------ | ------------------------------- | +| Source Native Key | `arn` | +| Object Id | `id` | +| Location | Region extracted from `arn` | +| Name | `name` | +| Operational Status | Defaults to "1" ("Operational") | + +### Cloud Function (`cmdb_ci_cloud_function`) + +| CMDB field | Terraform state field | +| ------------------ | ------------------------------- | +| Source Native Key | `arn` | +| Object Id | `arn` | +| Language | `runtime` | +| Code Size | `source_code_size` | +| Location | Region extracted from `arn` | +| Name | `function_name` | +| Operational Status | Defaults to "1" ("Operational") | + +### Cloud Load Balancer (`cmdb_ci_cloud_load_balancer`) + +| CMDB field | Terraform state field | +| -------------------------- | ------------------------------- | +| Source Native Key | `arn` | +| Object Id | `id` | +| Canonical Hosted Zone Name | `dns_name` | +| Canonical Hosted Zone ID | `zone_id` | +| Location | Region extracted from `arn` | +| Name | `name` | +| Operational Status | Defaults to "1" ("Operational") | diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/integrations/service-now/service-graph/resource-coverage/azure.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/integrations/service-now/service-graph/resource-coverage/azure.mdx new file mode 100644 index 0000000000..0a503bec63 --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/integrations/service-now/service-graph/resource-coverage/azure.mdx @@ -0,0 +1,158 @@ +--- +page_title: ServiceNow Service Graph Connector Microsoft Azure resource coverage +description: >- + Use the ServiceNow Service Graph integration to import selected resources from + Microsoft Azure into ServiceNow CMDB. +source: terraform-docs-common +--- + +# ServiceNow Service Graph Connector Microsoft Azure resource coverage + +This page describes how Terraform-provisioned Azure resources are mapped to the classes within the ServiceNow CMDB. + +## Mapping of Terraform resources to CMDB CI Classes + +| Azure resource | Terraform resource name | ServiceNow CMDB CI Class | ServiceNow CMDB Category Name | +| ---------------------- | --------------------------------- | -------------------------------- | ----------------------------- | +| Azure account | N/A | `cmdb_ci_cloud_service_account` | Cloud Service Account | +| Azure region | N/A | `cmdb_ci_azure_datacenter` | Azure Datacenter | +| Resource Group | `azurerm_resource_group` | `cmdb_ci_resource_group` | Resource Group | +| Windows VM | `azurerm_windows_virtual_machine` | `cmdb_ci_vm_instance` | Virtual Machine Instance | +| Linux VM | `azurerm_linux_virtual_machine` | `cmdb_ci_vm_instance` | Virtual Machine Instance | +| AKS Cluster | `azurerm_kubernetes_cluster` | `cmdb_ci_kubernetes_cluster` | Kubernetes Cluster | +| Storage Container | `azurerm_storage_container` | `cmdb_ci_cloud_storage_account` | Cloud Storage Account | +| MariaDB Database | `azurerm_mariadb_server` | `cmdb_ci_cloud_database` | Cloud DataBase | +| MS SQL Database | `azurerm_mssql_server` | `cmdb_ci_cloud_database` | Cloud DataBase | +| MySQL Database | `azurerm_mysql_server` | `cmdb_ci_cloud_database` | Cloud DataBase | +| PostgreSQL Database | `azurerm_postgresql_server` | `cmdb_ci_cloud_database` | Cloud DataBase | +| Network security group | `azurerm_network_security_group` | `cmdb_ci_compute_security_group` | Compute Security Group | +| Linux Function App | `azurerm_linux_function_app` | `cmdb_ci_cloud_function` | Cloud Function | +| Windows Function App | `azurerm_windows_function_app` | `cmdb_ci_cloud_function` | Cloud Function | +| Virtual Network | `azurerm_virtual_network` | `cmdb_ci_network` | Cloud Network | +| Tags | N/A | `cmdb_key_value` | Key Value | + +## Resource relationships + +| Child CI Class | Relationship type | Parent CI Class | +| ----------------------------------------------------------- | ---------------------- | --------------------------------------------------------- | +| Azure Datacenter 1 (`cmdb_ci_azure_datacenter`) | Hosted On::Hosts | Cloud Service Account 2 (`cmdb_ci_cloud_service_account`) | +| Azure Datacenter 2 (`cmdb_ci_azure_datacenter`) | Hosted On::Hosts | Cloud Service Account 3 (`cmdb_ci_cloud_service_account`) | +| Azure Datacenter 1 (`cmdb_ci_azure_datacenter`) | Contains::Contained by | Resource Group 1 (`cmdb_ci_resource_group`) | +| Cloud Storage Account 1 (`cmdb_ci_cloud_storage_account`) | Hosted On::Hosts | Azure Datacenter 2 (`cmdb_ci_azure_datacenter`) | +| Virtual Machine Instance 2 (`cmdb_ci_vm_instance`) | Hosted On::Hosts | Azure Datacenter 1 (`cmdb_ci_azure_datacenter`) | +| Virtual Machine Instance 2 (`cmdb_ci_vm_instance`) | Reference | Key Value 14 (`cmdb_key_value`) | +| Virtual Machine Instance 3 (`cmdb_ci_vm_instance`) | Hosted On::Hosts | Azure Datacenter 1 (`cmdb_ci_azure_datacenter`) | +| Virtual Machine Instance 3 (`cmdb_ci_vm_instance`) | Reference | Key Value 15 (`cmdb_key_value`) | +| Kubernetes Cluster 2 (`cmdb_ci_kubernetes_cluster`) | Hosted On::Hosts | Azure Datacenter 1 (`cmdb_ci_azure_datacenter`) | +| Kubernetes Cluster 2 (`cmdb_ci_kubernetes_cluster`) | Reference | Key Value 16 (`cmdb_key_value`) | +| Cloud DataBase 2 (`cmdb_ci_cloud_database` ) | Hosted On::Hosts | Azure Datacenter 1 (`cmdb_ci_azure_datacenter`) | +| Cloud DataBase 2 (`cmdb_ci_cloud_database` ) | Reference | Key Value 9 (`cmdb_key_value`) | +| Compute Security Group 2 (`cmdb_ci_compute_security_group`) | Hosted On::Hosts | Azure Datacenter 1 (`cmdb_ci_azure_datacenter`) | +| Compute Security Group 2 (`cmdb_ci_compute_security_group`) | Reference | Key Value 17 (`cmdb_key_value`) | +| Cloud Function 2 (`cmdb_ci_cloud_function`) | Hosted On::Hosts | Azure Datacenter 1 (`cmdb_ci_azure_datacenter`) | +| Cloud Function 2 (`cmdb_ci_cloud_function`) | Reference | Key Value 19 (`cmdb_key_value`) | +| Cloud Network 2 (`cmdb_ci_network`) | Hosted On::Hosts | Azure Datacenter 1 (`cmdb_ci_azure_datacenter`) | +| Cloud Network 2 (`cmdb_ci_network`) | Reference | Key Value 20 (`cmdb_key_value`) | + +## Field attributes mapping + +### Cloud Service Account (`cmdb_ci_cloud_service_account`) + +| CMDB field | Terraform state field | +| ------------------ | ----------------------------------- | +| Source Native Key | Subscription ID extracted from `id` | +| Account Id | Subscription ID extracted from `id` | +| Datacenter Type | Defaults to `azure` | +| Object ID | Subscription ID extracted from `id` | +| Name | Subscription ID extracted from `id` | +| Operational Status | Defaults to "1" ("Operational") | + +### Azure Datacenter (`cmdb_ci_azure_datacenter`) + +| CMDB field | Terraform state field | +| ------------------ | ----------------------------------------------- | +| Source Native Key | Concatenation of `location` and Subscription ID | +| Object Id | `location` | +| Region | `location` | +| Name | `location` | +| Operational Status | Defaults to "1" ("Operational") | + +### Virtual Machine Instance (`cmdb_ci_vm_instance`) + +| CMDB field | Terraform state field | +| ------------------ | ------------------------------- | +| Source Native Key | `id` | +| Object Id | `id` | +| Name | `name` | +| Operational Status | Defaults to "1" ("Operational") | + +### Cloud Storage Account (`cmdb_ci_cloud_storage_account`) + +| CMDB field | Terraform state field | +| --------------------------- | ------------------------------- | +| Source Native Key | `resource_manager_id` | +| Object Id | `resource_manager_id` | +| Fully qualified domain name | `id` | +| Blob Service | `storage_account_name` | +| Name | `name` | +| Operational Status | Defaults to "1" ("Operational") | + +### Resource Group (`cmdb_ci_resource_group`) + +| CMDB field | Terraform state field | +| ------------------ | ------------------------------- | +| Source Native Key | `id` | +| Object Id | `id` | +| Name | `name` | +| Location | `location` | +| Operational Status | Defaults to "1" ("Operational") | + +### Kubernetes Cluster (`cmdb_ci_kubernetes_cluster`) + +| CMDB field | Terraform state field | +| ------------------ | ------------------------------- | +| Source Native Key | `id` | +| IP Address | `fqdn` | +| Port | Defaults to "6443" | +| Name | `name` | +| Location | `location` | +| Operational Status | Defaults to "1" ("Operational") | + +### Cloud DataBase (`cmdb_ci_cloud_database`) + +| CMDB field | Terraform state field | +| --------------------------- | ------------------------------- | +| Source Native Key | `id` | +| Object Id | `id` | +| Version | `engine_version` | +| Fully qualified domain name | `fqdn` | +| Name | `name` | +| Vendor | Defaults to `azure` | +| Operational Status | Defaults to "1" ("Operational") | + +### Compute Security Group (`cmdb_ci_compute_security_group`) + +| CMDB field | Terraform state field | +| ------------------ | ------------------------------- | +| Source Native Key | `id` | +| Object Id | `id` | +| Name | `name` | +| Operational Status | Defaults to "1" ("Operational") | + +### Cloud Function (`cmdb_ci_cloud_function`) + +| CMDB field | Terraform state field | +| ------------------ | ------------------------------- | +| Source Native Key | `id` | +| Object Id | `id` | +| Name | `name` | +| Operational Status | Defaults to "1" ("Operational") | + +### Cloud Network (`cmdb_ci_network`) + +| CMDB field | Terraform state field | +| ------------------ | ------------------------------- | +| Source Native Key | `id` | +| Object Id | `id` | +| Name | `name` | +| Operational Status | Defaults to "1" ("Operational") | diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/integrations/service-now/service-graph/resource-coverage/gcp.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/integrations/service-now/service-graph/resource-coverage/gcp.mdx new file mode 100644 index 0000000000..5681430c8b --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/integrations/service-now/service-graph/resource-coverage/gcp.mdx @@ -0,0 +1,159 @@ +--- +page_title: ServiceNow Service Graph Connector Google Cloud resource coverage +description: >- + Use the ServiceNow Service Graph for Terraform Enterprise integration to + import selected resources from Google Cloud into ServiceNow CMDB. +source: terraform-docs-common +--- + +# ServiceNow Service Graph Connector Google Cloud resource coverage + +This page provides details on how Google Cloud resources, set up using Terraform, are corresponded to the classes within the ServiceNow CMDB. + +## Mapping of Terraform resources to CMDB CI Classes + +| Google resource | Terraform resource name | ServiceNow CMDB CI Class | ServiceNow CMDB Category Name | +| ------------------------ | --------------------------------------------------------------------- | -------------------------------- | ----------------------------- | +| Project ID | N/A | `cmdb_ci_cloud_service_account` | Cloud Service Account | +| Region (location) | N/A | `cmdb_ci_google_datacenter` | Google Datacenter | +| Virtual Machine Instance | `google_compute_instance` | `cmdb_ci_vm_instance` | Virtual Machine Instance | +| Kubernetes Cluster | `google_container_cluster` | `cmdb_ci_kubernetes_cluster` | Kubernetes Cluster | +| Google Storage | `google_storage_bucket` | `cmdb_ci_cloud_storage_account` | Cloud Storage Account | +| Google BigQuery | `google_bigquery_table` | `cmdb_ci_cloud_database` | Cloud DataBase | +| Google SQL | `google_sql_database` | `cmdb_ci_cloud_database` | Cloud DataBase | +| Google Compute Firewall | `google_compute_firewall` | `cmdb_ci_compute_security_group` | Compute Security Group | +| Cloud Function | `google_cloudfunctions_function` or `google_cloudfunctions2_function` | `cmdb_ci_cloud_function` | Cloud Function | +| Load Balancer | `google_compute_forwarding_rule` | `cmdb_ci_cloud_load_balancer` | Cloud Load Balancer | +| VPC | `google_compute_network` | `cmdb_ci_network` | Cloud Network | +| Tags | N/A | `cmdb_key_value` | Key Value | + +## Resource relationships + +| Child CI Class | Relationship type | Parent CI Class | +| ----------------------------------------------------------- | ----------------- | --------------------------------------------------------- | +| Google Datacenter 1 (`cmdb_ci_google_datacenter`) | Hosted On::Hosts | Cloud Service Account 4 (`cmdb_ci_cloud_service_account`) | +| Google Datacenter 2 (`cmdb_ci_google_datacenter`) | Hosted On::Hosts | Cloud Service Account 4 (`cmdb_ci_cloud_service_account`) | +| Virtual Machine Instance 4 (`cmdb_ci_vm_instance`) | Hosted On::Hosts | Google Datacenter 1 (`cmdb_ci_google_datacenter`) | +| Virtual Machine Instance 4 (`cmdb_ci_vm_instance`) | Reference | Key Value 13 (`cmdb_key_value`) | +| Cloud Network 3 (`cmdb_ci_network`) | Hosted On::Hosts | Google Datacenter 1 (`cmdb_ci_google_datacenter`) | +| Cloud Network 3 (`cmdb_ci_network`) | Reference | Key Value 18 (`cmdb_key_value`) | +| Compute Security Group 3 (`cmdb_ci_compute_security_group`) | Hosted On::Hosts | Google Datacenter 1 (`cmdb_ci_google_datacenter`) | +| Compute Security Group 3 (`cmdb_ci_compute_security_group`) | Reference | Key Value 21 (`cmdb_key_value`) | +| Kubernetes Cluster 3 (`cmdb_ci_kubernetes_cluster`) | Hosted On::Hosts | Google Datacenter 1 (`cmdb_ci_google_datacenter`) | +| Kubernetes Cluster 3 (`cmdb_ci_kubernetes_cluster`) | Reference | Key Value 22 (`cmdb_key_value`) | +| Cloud DataBase 3 (`cmdb_ci_cloud_database` ) | Hosted On::Hosts | Google Datacenter 1 (`cmdb_ci_google_datacenter`) | +| Cloud DataBase 2 (`cmdb_ci_cloud_database` ) | Reference | Key Value 24 (`cmdb_key_value`) | +| Cloud Function 3 (`cmdb_ci_cloud_function`) | Hosted On::Hosts | Google Datacenter 1 (`cmdb_ci_google_datacenter`) | +| Cloud Function 3 (`cmdb_ci_cloud_function`) | Reference | Key Value 25 (`cmdb_key_value`) | +| Cloud Load Balancer 2 (`cmdb_ci_cloud_load_balancer`) | Hosted On::Hosts | Google Datacenter 1 (`cmdb_ci_google_datacenter`) | +| Cloud Load Balancer 2 (`cmdb_ci_cloud_load_balancer`) | Reference | Key Value 26 (`cmdb_key_value`) | +| Cloud Storage Account 2 (`cmdb_ci_cloud_storage_account`) | Hosted On::Hosts | Google Datacenter 2 (`cmdb_ci_google_datacenter`) | +| Cloud Storage Account 2 (`cmdb_ci_cloud_storage_account`) | Reference | Key Value 23 (`cmdb_key_value`) | + +## Field attributes mapping + +### Cloud Service Account (`cmdb_ci_cloud_service_account`) + +| CMDB field | Terraform state field | +| ------------------ | ------------------------------- | +| Source Native Key | `project` | +| Account Id | `project` | +| Datacenter Type | Defaults to `google` | +| Object ID | `project` | +| Name | `project` | +| Operational Status | Defaults to "1" ("Operational") | + +### Google Datacenter (`cmdb_ci_google_datacenter`) + +| CMDB field | Terraform state field | +| ------------------ | --------------------------------------------------------- | +| Source Native Key | Concatenation of `project` and region extracted from `id` | +| Object Id | Region extracted from `id` | +| Region | Region extracted from `id` | +| Name | Region extracted from `id` | +| Operational Status | Defaults to "1" ("Operational") | + +### Virtual Machine Instance (`cmdb_ci_vm_instance`) + +| CMDB field | Terraform state field | +| ------------------ | ------------------------------- | +| Source Native Key | `id` | +| Object Id | `id` | +| Name | `name` | +| Category | `machine_type` | +| Operational Status | Defaults to "1" ("Operational") | + +### Cloud Network (`cmdb_ci_network`) + +| CMDB field | Terraform state field | +| ------------------ | ------------------------------- | +| Source Native Key | `id` | +| Object Id | `id` | +| Name | `name` | +| Operational Status | Defaults to "1" ("Operational") | + +### Compute Security Group (`cmdb_ci_compute_security_group`) + +| CMDB field | Terraform state field | +| ------------------ | ------------------------------- | +| Source Native Key | `id` | +| Object Id | `id` | +| Name | `name` | +| Operational Status | Defaults to "1" ("Operational") | + +### Kubernetes Cluster (`cmdb_ci_kubernetes_cluster`) + +| CMDB field | Terraform state field | +| ------------------ | ------------------------------- | +| Source Native Key | `arn` | +| IP Address | `endpoint` | +| Port | Defaults to "6443" | +| Name | `name` | +| Operational Status | Defaults to "1" ("Operational") | + +### Cloud Object Storage (`cmdb_ci_cloud_object_storage`) + +| CMDB field | Terraform state field | +| ------------------ | -------------------------------------------- | +| Source Native Key | `arn` | +| Object Id | `id` | +| Cloud Provider | Resource cloud provider extracted from `arn` | +| Name | `bucket` | +| Operational Status | Defaults to "1" ("Operational") | + +### Cloud Storage Account (`cmdb_ci_cloud_storage_account`) + +| CMDB field | Terraform state field | +| ------------------ | ------------------------------- | +| Source Native Key | `id` | +| Object Id | `id` | +| Name | `name` | +| Name | `location` | +| Operational Status | Defaults to "1" ("Operational") | + +### Cloud DataBase (`cmdb_ci_cloud_database`) + +| CMDB field | Terraform state field | +| ------------------ | ------------------------------- | +| Source Native Key | `id` | +| Object Id | `id` | +| Name | Name extracted from `id` | +| Operational Status | Defaults to "1" ("Operational") | + +### Cloud Function (`cmdb_ci_cloud_function`) + +| CMDB field | Terraform state field | +| ------------------ | ------------------------------- | +| Source Native Key | `id` | +| Object Id | `id` | +| Name | `name` | +| Operational Status | Defaults to "1" ("Operational") | + +### Cloud Load Balancer (`cmdb_ci_cloud_load_balancer`) + +| CMDB field | Terraform state field | +| ------------------ | ------------------------------- | +| Source Native Key | `id` | +| Object Id | `id` | +| Name | `name` | +| Operational Status | Defaults to "1" ("Operational") | diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/integrations/service-now/service-graph/resource-coverage/index.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/integrations/service-now/service-graph/resource-coverage/index.mdx new file mode 100644 index 0000000000..ec81da07cc --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/integrations/service-now/service-graph/resource-coverage/index.mdx @@ -0,0 +1,37 @@ +--- +page_title: >- + ServiceNow Service Graph Connector for Terraform Enterprise resource coverage + overview +description: >- + The ServiceNow Service Graph for Terraform Enterprise integration imports + selected resources from major cloud providers into ServiceNow CMDB. +source: terraform-docs-common +--- + +# ServiceNow Service Graph Connector for Terraform resource coverage overview + +The tables provided in this section illustrate the mapping of resources from the Terraform state to the ServiceNow CMDB configuration items (CIs) by the Service Graph Connector for Terraform. + +While the default ETL map provided by the application can be utilized without modification, it is also possible to customize it according to the specific requirements of your organization. Check [customizations](/terraform/enterprise/integrations/service-now/service-graph/customizations) for more details. + +The application supports selected resources from major cloud providers. The following pages provide mapping details for each supported provider: + +- [AWS](/terraform/enterprise/integrations/service-now/service-graph/resource-coverage/aws) +- [Azure](/terraform/enterprise/integrations/service-now/service-graph/resource-coverage/azure) +- [GCP](/terraform/enterprise/integrations/service-now/service-graph/resource-coverage/gcp) +- [VMware vSphere](/terraform/enterprise/integrations/service-now/service-graph/resource-coverage/vsphere) + +# Importing Tags + +The Service Graph Connector for Terraform imports the Terraform tags associated with your resource into CMDB. Tags are mapped to the **Key Value** CI Class. +Along with the tags assigned in your Terraform code, the integration also includes `tf_organization` and `tf_workspace` tags. These tags are used to indicate the HCP Terraform organization and workspace where the resource was provisioned. + +The visibility of the **Tags** tab in CMDB varies for different configuration items. By default, not every configuration item has the **Tags** tab enabled. For instance, the **Virtual Machine Instance** class page includes the **Tags** tab, whereas the **AWS Cloud ECS Cluster** page does not. + +The following example illustrates how the **Tags** tab can be enabled for **AWS Cloud ECS Cluster** CI class in CMDB. + +1. Enter `cmdb_ci_cloud_ecs_cluster.list` in the search menu of your ServiceNow instance. +2. Open any record. Right-click on the gray bar at the top, select **Configure** and proceed to **Related Lists**. If you are in a different scope, click **Edit this view**. +3. Transfer **Key Value->Configuration item** from the left column to the right and click **Save**. Tags become available in CMDB for all AWS ECS cluster records. + +![screenshot: ServiceNow Service Graph Connector - enable the Tags tab in CMDB](/img/docs/service-now-service-graph-tags.png) diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/integrations/service-now/service-graph/resource-coverage/vsphere.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/integrations/service-now/service-graph/resource-coverage/vsphere.mdx new file mode 100644 index 0000000000..9e9f91fe20 --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/integrations/service-now/service-graph/resource-coverage/vsphere.mdx @@ -0,0 +1,84 @@ +--- +page_title: ServiceNow Service Graph Connector VMware vSphere resource coverage +description: >- + Use the ServiceNow Service Graph integration to import selected resources from + VMware vSphere into ServiceNow CMDB. +source: terraform-docs-common +--- + +# ServiceNow Service Graph Connector VMware vSphere resource coverage + +This page explains the rules of associating VMware vSphere resources, created via Terraform, with the classes in the ServiceNow CMDB. + +## Mapping of Terraform resources to CMDB CI Classes + +| vSphere resource | Terraform resource name | ServiceNow CMDB CI Class | ServiceNow CMDB Category Name | +| ------------------------- | --------------------------- | ------------------------------- | ------------------------------- | +| vCenter server | N/A | `cmdb_ci_cloud_service_account` | Cloud Service Account | +| vSphere Datacenter | `vsphere_datacenter` | `cmdb_ci_vcenter_datacenter` | VMware vCenter Datacenter | +| vSphere Virtual Machine | `vsphere_virtual_machine` | `cmdb_ci_vmware_instance` | VMware Virtual Machine Instance | +| vSphere Datastore Cluster | `vsphere_datastore_cluster` | `cmdb_ci_vcenter_datastore` | VMware vCenter Datastore | +| vSphere Network | `vsphere_network` | `cmdb_ci_vcenter_network` | VMware vCenter Network | +| Tags | N/A | `cmdb_key_value` | Key Value | + +## Resource relationships + +| Child CI Class | Relationship type | Parent CI Class | +| ------------------------------------------------------------- | ----------------- | ---------------------------------------------------------- | +| VMware vCenter Datacenter 1 (`cmdb_ci_vcenter_datacenter`) | Hosted On::Hosts | Cloud Service Account 5 (`cmdb_ci_cloud_service_account`) | +| VMware Virtual Machine Instance 1 (`cmdb_ci_vmware_instance`) | Hosted On::Hosts | VMware vCenter Datacenter 1 (`cmdb_ci_vcenter_datacenter`) | +| VMware Virtual Machine Instance 1 (`cmdb_ci_vmware_instance`) | Reference | Key Value 27 (`cmdb_key_value`) | +| VMware vCenter Network 1 (`cmdb_ci_vcenter_network`) | Hosted On::Hosts | VMware vCenter Datacenter 1 (`cmdb_ci_vcenter_datacenter`) | +| VMware vCenter Network 1 (`cmdb_ci_vcenter_network`) | Reference | Key Value 28 (`cmdb_key_value`) | +| VMware vCenter Datastore 1 (`cmdb_ci_vcenter_datastore`) | Hosted On::Hosts | VMware vCenter Datacenter 1 (`cmdb_ci_vcenter_datacenter`) | +| VMware vCenter Datastore 1 (`cmdb_ci_vcenter_datastore`) | Reference | Key Value 29 (`cmdb_key_value`) | + +## Field attributes mapping + +### Cloud Service Account (`cmdb_ci_cloud_service_account`) + +| CMDB field | Terraform state field | +| ------------------ | ------------------------------- | +| Source Native Key | Defaults to "VMware_vCenter" | +| Account Id | Defaults to "VMware_vCenter" | +| Datacenter Type | Defaults to "VMware_vCenter" | +| Object ID | Defaults to "VMware_vCenter" | +| Name | Defaults to "VMware_vCenter" | +| Operational Status | Defaults to "1" ("Operational") | + +### VMware vCenter Datacenter (`cmdb_ci_vcenter_datacenter`) + +| CMDB field | Terraform state field | +| ------------------ | ------------------------------- | +| Source Native Key | `datacenter_id` | +| Object Id | `datacenter_id` | +| Region | `datacenter_id` | +| Name | `datacenter_id` | +| Operational Status | Defaults to "1" ("Operational") | + +### VMware Virtual Machine Instance (`cmdb_ci_vmware_instance`) + +| CMDB field | Terraform state field | +| ------------------ | ------------------------------- | +| Source Native Key | `id` | +| Object Id | `id` | +| Name | `name` | +| Operational Status | Defaults to "1" ("Operational") | + +### VMware vCenter Network (`cmdb_ci_vcenter_network`) + +| CMDB field | Terraform state field | +| ------------------ | ------------------------------- | +| Source Native Key | `id` | +| Object Id | `id` | +| Name | `name` | +| Operational Status | Defaults to "1" ("Operational") | + +### VMware vCenter Datastore (`cmdb_ci_vcenter_datastore`) + +| CMDB field | Terraform state field | +| ------------------ | ------------------------------- | +| Source Native Key | `id` | +| Object Id | `id` | +| Name | `name` | +| Operational Status | Defaults to "1" ("Operational") | diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/integrations/service-now/service-graph/service-graph-setup.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/integrations/service-now/service-graph/service-graph-setup.mdx new file mode 100644 index 0000000000..c71384b3d3 --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/integrations/service-now/service-graph/service-graph-setup.mdx @@ -0,0 +1,162 @@ +--- +page_title: Set up the ServiceNow Service Graph Connector for Terraform Enterprise +description: >- + Learn how to set up the ServiceNow Service Graph Connector for Terraform + Enterprise. +source: terraform-docs-common +--- + +# Set up the ServiceNow Service Graph Connector + +-> **Note:** Follow the [Configure ServiceNow Service Graph Connector for HCP Terraform](/terraform/tutorials/it-saas/servicenow-sgc) tutorial for hands-on instructions on how to import an AWS resource deployed in your HCP Terraform organization to the ServiceNow CMDB by using the Service Graph Connector for Terraform. + +The ServiceNow Service Graph Connector for Terraform is a certified scoped application available in the ServiceNow Store. Search for ”Service Graph Connector for Terraform” published by ”HashiCorp Inc” and click **Install**. + +## Prerequisites + +To start using the Service Graph Connector for Terraform, you must have: + +- An administrator account on a Terraform Enterprise instance or within an HCP Terraform organization. +- An administrator account on your ServiceNow vendor instance. + +The Service Graph Connector for Terraform supports the following ServiceNow server versions: + +- Washington DC +- Xanadu +- Yokohama + +The following ServiceNow plugins are required dependencies: + +- ITOM Discovery License +- Integration Commons for CMDB +- Discovery and Service Mapping Patterns +- ServiceNow IntegrationHub Standard Pack + +Additionally, you can install the IntegrationHub ETL application if you want to modify the default CMDB mappings. + +-> **Note:** Dependent plugins are installed on your ServiceNow instance automatically when the app is downloaded from the ServiceNow Store. Before installing the Service Graph Connector for Terraform, you must activate the ITOM Discovery License plugin in your production instance. + +## Connect ServiceNow to HCP Terraform + +-> **ServiceNow roles:** `admin`, `x_hashi_service_gr.terraform_user` + +Once the integration is installed, you can proceed to the guided setup form where you will enter your Terraform credentials. This step will establish a secure connection between HCP Terraform and your ServiceNow instance. + +### Create and scope Terraform API token + +In order for ServiceNow to connect to HCP Terraform, you must give it an HCP Terraform API token. The permissions of this token determine what resources the Service Graph Connector will import into the CMDB. While you could use a user API token, it could import resources from multiple organizations. By providing a team API token, you can scope permissions to only import resources from specified workspaces within a single organization. + +To create a team API token: + +1. Sign in to [HCP Terraform](https://app.terraform.io/) or Terraform Enterprise and navigate to the organization where you want to create a team token. +2. Choose **Settings** from the sidebar, then **Teams**. +3. In **Team API Token** section, click **Create a team token**. + +Save this token in a safe place since HCP Terraform only displays it once. You will use it to configure ServiceNow in the next section. + +![ServiceNow Service Graph Connector Configure Team API token in HCP Terraform](/img/docs/service-now-service-graph-team-token-gen.png) + +### Configure Service Graph Connector for Terraform API token + +In the top navigation of your ServiceNow instance's control panel, click on **All**, search for **Service Graph Connector for Terraform**, and click **SG-Setup**. Next, click **Get Started**. + +Next, in the **Configure the Terraform connection** section, click **Get Started**. + +In the **Configure Terraform authentication credentials** section, click **Configure**. + +If you want to route traffic between your HCP Terraform and the ServiceNow instance through a MID server acting as a proxy, change the **Applies to** dropdown to "Specific MID servers" and select your previously configured MID server name. If you don't use MID servers, leave the default value. + +Set the **API Key** to the HCP Terraform team API token that you created in the previous section and click **Update**. + +![ServiceNow Service Graph Connector API Key Credentials configuration screen. The API key is provided, then saved by clicking the Update button](/img/docs/service-now-service-graph-apikey.png) + +In the **Configure Terraform authentication credentials** section, click **Mark as Complete**. + +### Configure Terraform Webhook Notification token + +To improve security, HCP Terraform includes an HMAC signature on all "generic" webhook notifications using a user-provided **token**. This token is an arbitrary secret string that HCP Terraform uses to sign each webhook notification. ServiceNow uses the same token to verify the request authenticity. Refer to [Notification Authenticity](/terraform/enterprise/api-docs/notification-configurations#notification-authenticity) for more information. + +Create a token and save it in a safe place. This secret token can be any value but should be treated as sensitive. + +In the **Configure Terraform Webhook token** section, click **Configure**. In the **Token** field, enter the secret token that will be shared between the HCP Terraform and your ServiceNow instance and click **Update**. + +![ServiceNow Service Graph Connector Webhook token configuration screen. The Token is provided, then saved by clicking the Update button](/img/docs/service-now-service-graph-webhook-token.png) + +In the **Configure Terraform Webhook token** section, click **Mark as Complete**. + +### Configure Terraform connection + +In the **Configure Terraform connection** section, click **Configure**. + +If you are using Terraform Enterprise, set the **Connection URL** to the URL of your Terraform Enterprise instance. If you are using HCP Terraform, leave the **Connection URL** as the default value of `https://app.terraform.io`. + +![ServiceNow Service Graph Connector HTTP Connection configuration screen. A Terraform Enterprise URL may be provided in the Connection URL field, the saved by clicking the Update button](/img/docs/service-now-service-graph-tfconn.png) + +If you want to use a MID server, check **Use MID server** box, change **MID Selection** dropdown to "Specific MID sever" and select your previously configured and validated MID server. + +Click **Update** to save these settings. In the **Configure Terraform connection** section, click **Mark as Complete**. + +## Import Resources + +Refer to the documentation explaining the difference between the [two modes of import](/terraform/enterprise/integrations/service-now/service-graph#import-methods) offered by the Service Graph Connector for Terraform. Both options may be enabled, or you may choose to enable only the webhook or scheduled import. + +### Configure scheduled import + +In the **Set up scheduled import job** section of the setup form, proceed to **Configure the scheduled jobs** and click **Configure**. + +You can use the **Execute Now** option to run a single import job, which is useful for testing. The import set will be displayed in the table below the scheduled import form, after refreshing the page. Once the import is successfully triggered, click on the **Import Set** field of the record to view the logs associated with the import run, as well as its status. + +Activate the job by checking the **Activate** box. Set the **Repeat Interval** and click **Update**. Note that the import processing time depends of the number of organizations and workspaces in your HCP Terraform. Setting the import job to run frequently is not recommended for big environments. + +![ServiceNow Service Graph Connector scheduled import screen](/img/docs/service-now-service-graph-scheduled-import.png) + +You can also access the scheduler interface by searching for **Service Graph Connector for Terraform** in the top navigation menu and selecting **SG-Import Schedule**. + +### Configure Terraform Webhook + +In the top navigation, click on **All**, search for **Scheduled Imports**, and click on **Scheduled Imports**. + +Select the **SG-Terraform Scheduled Process State** record, then click **To edit the record click here**. + +Click the **Active** checkbox to enable it. Leave the default value for the **Repeat Interval** of 5 seconds. Click **Update**. + +![ServiceNow Service Graph Connector scheduled import screen showing the Active checkbox enabled](/img/docs/service-now-service-graph-webhook-schedule.png) + +Next, create the webhook in HCP Terraform. Select a workspace and click **Settings > Notifications**. Click **Create a Notification**. + +Keep the **Destination** as the default option of **Webhook**. Choose a descriptive name **Name**. + +Set the **Webhook URL** enter `https:///api/x_hashi_service_gr/sg_terraform_webhook` and replace `` with the hostname of your ServiceNow instance. + +In the **Token** field, enter the same string you provided in **Terraform Webhook token** section the of the Service Graph guided setup form. + +Under **Health Events** choose **No events**. + +Under **Run Events** choose **Only certain events** and enable notifications only on **Completed** runs. Click **Create Notification**. + +![HCP Terraform notification creation screen, showing a webhook pointing to ServiceNow which is only triggered on completed runs](/img/docs/service-now-service-graph-webhook-tfc.png) + +Trigger a run in your workspace. Once the run is successfully completed, a webhook notification request will be sent to your ServiceNow instance. + +### Monitor the import job + +By following these steps, you can track the status of import jobs in ServiceNow and verify the completion of the import process before accessing the imported resources in the CMDB. + +For scheduled imports, navigate back to the **SG-Import Schedule** interface. For webhook imports, go to the **SG-Terraform Scheduled Process State** interface. + +Under the form, you will find a table containing all registered import sets. Locate and select the relevant import set record. + +Click on the **Import Set** field to open it and view its details. The **Outbound Http Requests** tab lists all requests made by your ServiceNow instance to HCP Terraform in order to retrieve the latest Terraform state. + +Monitor the state of the import job. Wait for it to change to **Complete**, indicated by a green mark. +Once the import job is complete, you can access the imported resources in the CMDB. + +![ServiceNow Service Graph Connector: import set with successfully completed status](/img/docs/service-now-service-graph-import-set.png) + +You can also access all import sets, regardless of the import type, by navigating to **All** and selecting **Import Sets** under the **Advanced** category. + +### View resources in ServiceNow CMDB + +In the top navigation of ServiceNow, click on **All** and search for **CMDB Workspace**, and click on **CMDB Workspace**. + +Perform a search by entering a Configuration Item (CI) name in the **Search** field (for example, **Virtual Machine Instance**). CI names supported by the application are listed on the [resource mapping page](/terraform/enterprise/integrations/service-now/service-graph/resource-coverage). diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/migrate/index.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/migrate/index.mdx new file mode 100644 index 0000000000..e7d1fdb635 --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/migrate/index.mdx @@ -0,0 +1,95 @@ +--- +page_title: Migrate Terraform state to Terraform Enterprise or Terraform Enterprise +description: >- + Learn how to migrate existing Terraform state to Terraform Enterprise or + Terraform Enterprise so that you can manage existing infrastructure without + de-provisioning. +source: terraform-docs-common +--- + +# Migrate Terraform state to HCP Terraform or Terraform Enterprise + +This topic describes how to migrate existing Terraform state files to HCP Terraform or Terraform Enterprise without first de-provisioning them. Refer to [State](/terraform/language/state) in the Terraform configuration language reference for additional information about Terraform state. + +## Overview + +Perform the following actions to migrate existing resources to one or more workspaces in HCP Terraform or Terraform Enterprise: + +1. Stop all Terraform operations associated with the state files. +2. Migrate Terraform state files using either the Terraform CLI or the HCP Terraform or Terraform Enterprise API. + +You have three options to migrate your state: + +1. Manually using the Terraform CLI +2. Automatically using `tf-migrate` +3. Manually using the HCP Terraform API + +## Requirements + +- Terraform v1.1 and later is required to configure the `cloud` block. For Terraform v1.0 and older, use the [`remote` backend](/terraform/language/settings/backends/remote) instead. +- Create an account on [HCP Terraform](https://app.terraform.io/) or on a [Terraform Enterprise instance](/terraform/enterprise/users-teams-organizations/users#creating-an-account). +- You must present a token linked to appropriate permissions to use the API. Refer to the following topics for additional information: + - [HCP Terraform API overview](/terraform/enterprise/api-docs) + - [Terraform Enterprise API overview](/terraform/enterprise/api-docs) + +## Stop Terraform operations + +Stop all Terraform operations associated with the state files. This may require locking or deleting CI jobs, restricting access to the state backend, and communicating with other teams. You should also only migrate state files into HCP Terraform or Terraform Enterprise workspaces that have never performed a run. + +## Migrate state using the CLI + +> **Hands-on:** Complete the [Migrate State to HCP Terraform](/terraform/tutorials/state/cloud-migrate) tutorial for additional guidance on how to migrate Terraform state using the CLI. + +1. Add the `cloud` block to your Terraform configuration and specify the following fields: + + 1. `hostname` field: Specify either `app.terraform.io` for HCP Terraform or the hostname of your Terraform Enterprise deployment. + 2. `organization` field: Specify your HCP Terraform or Terraform Enterprise organization. + 3. `workspaces` block: Add a `tags` or `name` field and specify one or more destination workspaces as a list of strings. + + Refer to [The `cloud` Block](/terraform/cli/cloud/settings#the-cloud-block) in the Terraform CLI documentation for additional information. The following example migrates the state associated with the configuration to the `networking` workspace on HCP Terraform: + + ```hcl + terraform { + cloud { + hostname = "app.terraform.io" + organization = "my-org" + workspaces { + tags = ["networking"] + } + } + } + ``` + +2. Run `terraform init`. Terraform creates any workspaces specified in the configuration if they do not already exist in the organization. + +## Migrate state using Terraform migrate + +You can use the Terraform migrate CLI tool to automatically migrate state to HCP Terraform and Terraform Enterprise. The tool does not ship with HCP Terraform. You must download and install the binary for the CLI tool separately. Refer to the [Terraform migrate documentation](/terraform/enterprise/migrate/tf-migrate) for more information. + +## Migrate state using the API + +1. Encode your state files as a base64 string and generate an MD5 hash. The following example generates the string and hash file for a single `terraform.tfstate` file: + + ```shell-session + $ cat terraform.tfstate | base64 + dGVycmFmb3JtLnRmc3RhdGUK + $ md5sum terraform.tfstate + 690a3f8ae079c629494a52c68757d585 terraform.tfstate + ``` +2. If the workspace does not yet exist in your organization, send a `POST` request to the `/organizations/:organization_name/workspaces` endpoint to create it. Otherwise, proceed to the next step. Refer to the following topics for details: + - [Create a workspace](/terraform/enterprise/api-docs/workspaces#create-a-workspace) in the HCP Terraform API reference documentation. + - [Create a workspace](/terraform/enterprise/api-docs/workspaces#create-a-workspace) in the Terraform Enterprise API reference documentation. +3. Send a `POST` request to the `/workspaces/:workspace_id/actions/lock` endpoint to lock the workspace. Refer to the following topics for details: + - [Lock a workspace](/terraform/enterprise/api-docs/workspaces#lock-a-workspace) in the HCP Terraform API reference documentation. + - [Lock a workspace](/terraform/enterprise/api-docs/workspaces#lock-a-workspace) in the Terraform Enterprise API reference documentation. +4. To upload your state files to the workspace, send a `POST` request to the `/workspaces/:workspace_id/state-versions` endpoint. Specify the MD5 string in the `data.attributes.md5` field and encoded state file in the `data.attributes.state` field of the request body. Refer to the following topics for details: + - [Create a state version](/terraform/enterprise/api-docs/state-versions#create-a-state-version) in the HCP Terraform API reference documentation. + - [Create a state version](/terraform/enterprise/api-docs/state-versions#create-a-state-version) in the Terraform Enterprise API reference documentation. +5. Send a `POST` request to the `/workspaces/:workspace_id/actions/unlock` endpoint to unlock the workspace. + +Refer to the following external article for an example of how to create a script in Python to automate multiple state files: +[Migrating A Lot of State with Python and the HCP Terraform (previously Terraform Cloud) API](https://medium.com/hashicorp-engineering/migrating-a-lot-of-state-with-python-and-the-terraform-cloud-api-997ec798cd11). The example uses the [Workspaces API](/terraform/enterprise/api-docs/workspaces#create-a-workspace) to create the necessary workspaces in HCP Terraform and the [State Versions API](/terraform/enterprise/api-docs/state-versions) to migrate the state files to those workspaces. + +## Migrate state using Terraform migrate + +You can use the Terraform migrate CLI tool to automatically migrate state to HCP Terraform and Terraform Enterprise. The tool does not ship with HCP Terraform. You must download and install the binary for the CLI tool separately. Refer to the [Terraform migrate documentation](/terraform/enterprise/migrate/tf-migrate) for more information. diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/migrate/tf-migrate/index.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/migrate/tf-migrate/index.mdx new file mode 100644 index 0000000000..9712d3e465 --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/migrate/tf-migrate/index.mdx @@ -0,0 +1,132 @@ +--- +page_title: Terraform migrate overview +description: Learn how to install and configure the Terraform migrate tf-migrate CLI tool. +source: terraform-docs-common +--- + +# Terraform migrate + +The Terraform migrate `tf-migrate` CLI automatically migrates your Terraform Community Edition state and variables to HCP Terraform or Terraform Enterprise. It also updates your local configuration with the new state storage location and optionally creates a pull request to update your code repository. + +**Hands-on**: Complete the [Migrate to HCP Terraform in bulk](/terraform/tutorials/cloud/bulk-migrate-hcp) tutorial to get started with `tf-migrate`. + +## Overview + +Complete the following steps to install and configure `tf-migrate`: + +1. [Download and install `tf-migrate`](#install). +2. Configure `tf-migrate` to [authenticate](#connect-to-hcp-terraform-or-enterprise-terraform) to HCP Terraform or Terraform Enterprise. +3. Enable logging so that you can troubleshoot potential issues that may occur during the migration process. + +### GitHub and GitLab connection requirements + +The `tf-migrate` tool can optionally open a pull request to update your configuration in GitHub or GitLab. + +If your Terraform files are stored in GitHub, you must configure an API token that meets the following requirements: + +- The token must be a classic token. Refer to the [GitHub documentation](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens) for additional information. +- The token must have the `repo` OAuth scope. + +If your Terraform files are stored in GitLab Cloud, you must configure an API token that meets the following requirements: + +- The token must be a personal access token. Refer to the [GitHub documentation](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens) for additional information. +- The token must be have `read_repository` and `write_repository` scopes. + +## Install + + + + + +HashiCorp distributes `tf-migrate` as a binary package. To install `tf-migrate`, find the [appropriate binary](https://releases.hashicorp.com/tf-migrate/) for your operating system and download it as a zip archive. + +After you download `tf-migrate`, unzip the archive. + +Finally, make sure that the `tf-migrate` binary is available in a directory that is in your system's `PATH`. + +### Verify the installation + +Every build of `tf-migrate` includes a `SHA256SUMS` and a `SHA256SUMS.sig` file to validate your downloaded binary. Refer to the [verify HashiCorp binary downloads tutorial](https://developer.hashicorp.com/well-architected-framework/operational-excellence/verify-hashicorp-binary) for more information. + + + + + +[Homebrew](https://brew.sh/) is a free and open-source package management system for macOS. You can install the official [`tf-migrate`](https://github.com/hashicorp/homebrew-tap) formula from the terminal. + +First, install the HashiCorp tap, a repository of all our Homebrew packages. + + $ brew tap hashicorp/tap + +Now, install `tf-migrate` with the `hashicorp/tap/tf-migrate` formula. + + $ brew install hashicorp/tap/tf-migrate + + + + + +## Connect to HCP Terraform or Enterprise Terraform + +The `tf-migrate` tool uses your locally configured Terraform CLI API token. If you have not authenticated your local Terraform installation with HCP Terraform, use the `terraform login` command to create an authentication token. + + $ terraform login + + Terraform will request an API token for app.terraform.io using your browser. + + If login is successful, Terraform will store the token in plain text in + the following file for use by subsequent commands: + /Users/redacted/.terraform.d/credentials.tfrc.json + + Do you want to proceed? + Only 'yes' will be accepted to confirm. + + Enter a value: yes + +Terraform opens a browser to the HCP Terraform sign in screen, where you can then enter a token name in the web UI, or leave the default name. Click **Create API token** to generate the authentication token. + +HCP Terraform only displays your token once. Copy this token, then when the Terraform CLI prompts you, paste the user token exactly once into your terminal. Press **Enter** to complete the authentication process. + +`tf-migrate` can optionally create a pull request that updates the state storage location specified in your Terraform configuration. To do this, `tf-migrate` uses the GitHub or GitLab Cloud API, and requires an API token with permissions to modify your Git repository. + +To configure your API token, set the `TF_GIT_PAT_TOKEN` environment variable + + $ export TF_GIT_PAT_TOKEN= + +## Supported backends + +The `tf-migrate` tool supports migrating state from the following backends to HCP Terraform or Terraform Enterprise: + +- [local](/terraform/language/backend/local) +- [azurerm](/terraform/language/backend/azurerm) +- [consul](/terraform/language/backend/consul) +- [cos](/terraform/language/backend/cos) +- [gcs](/terraform/language/backend/gcs) +- [http](/terraform/language/backend/http) +- [Kubernetes](/terraform/language/backend/kubernetes) +- [oss](/terraform/language/backend/oss) +- [pg](/terraform/language/backend/pg) +- [s3](/terraform/language/backend/s3) + +`tf-migrate` does not support migrating state from an existing `cloud` integration or `remote` backend. + +## Enable logging + +You can enable detailed logging by setting the `TF_MIGRATE_ENABLE_LOG` environment variable to `true`. When you enable this setting, `tf-migrate` writes the logs to the following locations, depending on your operating system: + +| Platform | Location | +| --------------- | --------------------------------------------------------------- | +| macOS and Linux | `/Users//.tf-migrate/logs//.log` | +| Windows | `C:\Users\\.tf-migrate\logs\\.log` | + +You can set the `TF_MIGRATE_LOG_LEVEL` environment variable to one of the following values to change the verbosity of the logs in order of decreasing verbosity: + +- `TRACE` +- `DEBUG` +- `INFO` +- `WARN` +- `ERROR` + +## Additional configuration + +You can create an optional configuration file to modify the `tf-migrate` CLI behavior and specify the path to the configuration file when you run `tf-migrate prepare`. Any command-line flags you provide with these commands override the configuration file. Refer to the [configuration reference](/terraform/enterprise/migrate/tf-migrate/reference/configuration) for additional information. diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/migrate/tf-migrate/reference/configuration.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/migrate/tf-migrate/reference/configuration.mdx new file mode 100644 index 0000000000..215f5b9a2b --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/migrate/tf-migrate/reference/configuration.mdx @@ -0,0 +1,88 @@ +--- +page_title: Configuration file reference +description: >- + You can configure `tf-migrate` to control how it migrate your state to + Terraform Enterprise or Terraform Enterprise. +source: terraform-docs-common +--- + +# `tf-migrate` configuration file reference + +This topic describes the parameters for configuring the `tf-migrate` CLI. + +## Configuration model + +A `tf-migrate` configuration file supports the following parameters. + +- [`skip-dir`](#skip-dir): list of strings +- [`projects`](#projects): list of objects + - [`dir`](#projects-dir): string + - [`workspaces`](#projects-workspaces): list of objects + - [`name`](#projects-workspaces): string + - [`env-vars`](#projects-workspaces): + - [`terraform-vars`](#projects-workspaces): list of strings + +## Specification + +This section provides details about the fields you can configure in a `tf-migrate` configuration file. + +### `skip-dir` + +Specifies a list of paths to directories you want `tf-migrate` to skip. This parameter is identical to using the [`-skip-dir` command-line flag](/terraform/enterprise/migrate/tf-migrate/reference/prepare#available-options). By default, `tf-migrate` processes all child directories containing Terraform configuration files. + +- Data type: List of strings +- Default: None + +### `projects` + +Specifies a list of project configurations that align with local directories. The `tf-migrate` tool creates one project in HCP Terraform or Terraform Enterprise per configuration. In each project, `tf-migrate` creates one workspace per local workspace. + +- Data type: List of objects +- Default: None + +### `projects.dir` + +Specifies the relative or absolute path to the Terraform configuration to migrate. + +- Data type: String +- Default: None + +### `projects.workspaces` + +Specifies workspace configurations in the project. Terraform creates a workspace in HCP Terraform or Terraform Enterprise for each workspace configuration. The following table describes the attributes you can configure in each item in the list of workspaces: + +| Attribute | Description | Data type | Default | +| ---------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------- | ------- | +| `name` | Specifies the name of the local workspace. | String | None | +| `env-vars` | Specifies a map of environment variables to set during the migration. Each key must start with `TF_VAR`. You must specify `env-vars`, `terraform-vars`, or both. | Object | None | +| `terraform-vars` | Specifies a list of Terraform variables files to use for configuring the workspace. Each file must end with either `.tfvars` or `tfvars.json`. You must specify `terraform-vars`, `env-vars`, or both. The path can be a relative or absolute path. | List of strings | None | + +- Data type: List of objects +- Default: None + +## Example configuration file + +In the following example, Terraform creates one project using data from the `example/project1` directory. The project has a workspace named `staging` and a workspace named `dev`: + +```hcl +skip-dir = ["example/skip/dir1", "example/skip/dir2"] + +projects = [ + { + dir = "example/project1" + workspaces = [ + { + name = "staging" + env-vars = { + "TF_VAR_region": "us-east-2" + } + terraform-vars = ["staging.tfvars"] + }, + { + name = "dev" + terraform-vars = ["dev.tfvars"] + } + ] + } +] +``` diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/migrate/tf-migrate/reference/execute.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/migrate/tf-migrate/reference/execute.mdx new file mode 100644 index 0000000000..511175e82a --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/migrate/tf-migrate/reference/execute.mdx @@ -0,0 +1,65 @@ +--- +page_title: tf-migrate execute reference +description: >- + The `tf-migrate execute` command runs a prepared migration to migrate locally + existing state to Terraform Enterprise or Terraform Enterprise. +source: terraform-docs-common +--- + +# `tf-migrate execute` reference + +The `tf-migrate execute` command directs Terraform to run the `init`, `plan`, and `apply` commands on the configuration generated with the `tf-migrate prepare` command. + +## Usage + + $ tf-migrate execute + +## Description + +The `tf-migrate execute` command creates the project and workspace in HCP Terraform or Terraform Enterprise, migrates the existing state, and updates your configuration to replace the `backend` block with the `cloud` block. If you responded to the prompt in the prepare workflow to create a pull request, `tf-migrate` creates the pull request after it completes the migration. + +When Terraform migrate completes the migration, it displays the number of workspaces migrated, a link to each HCP Terraform workspace, and a link to the GitHub pull request if you configured it to create one. + +## Example + +The `tf-migrate execute` command automatically performs the migration and code updates. + + + + $ tf-migrate execute + ✓ Init command ran successfully + ✓ Plan command ran successfully and changes are detected + ✓ Apply command ran successfully + Apply complete! Resources: 7 added, 0 changed, 0 destroyed. + + + Migration Summary + ┌───────────────────────────────┬───────┐ + │ Metric │ Count │ + ├───────────────────────────────┼───────┤ + │ Number of Projects Migrated │ 2 │ + │ Number of Directories Skipped │ 0 │ + │ Number of New Workspaces │ 2 │ + │ Number of Variables Migrated │ 8 │ + └───────────────────────────────┴───────┘ + ┌───────────────────────────────────────────────────────────────────────────────────────────────────┐ + │ Workspace URLs │ + ├───────────────────────────────────────────────────────────────────────────────────────────────────┤ + │ https://app.terraform.io//workspaces/web_default │ + │ https://app.terraform.io//workspaces/api_default │ + └───────────────────────────────────────────────────────────────────────────────────────────────────┘ + ┌────────────────────────────────────────────────────────────────────────┐ + │ Pull Request Link │ + ├────────────────────────────────────────────────────────────────────────┤ + │ https://github.com//learn-terraform-migrate/pull/1 │ + └────────────────────────────────────────────────────────────────────────┘ + + + +## Available options + +You can include the following flags when you run the `tf-migrate execute` command: + +| Option | Description | Default | Required | +| ---------- | ------------------------------------------------------------------------------------------------------------------- | ------- | -------- | +| `-dry-run` | If set, Terraform migrate only shows the output from the `terraform plan` step, and does not perform the migration. | None | No | diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/migrate/tf-migrate/reference/prepare.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/migrate/tf-migrate/reference/prepare.mdx new file mode 100644 index 0000000000..ae735703bf --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/migrate/tf-migrate/reference/prepare.mdx @@ -0,0 +1,104 @@ +--- +page_title: tf-migrate prepare reference +description: >- + The `tf-migrate prepare` command gathers information and creates a plan to + migrate your Terraform Community Edition state. +source: terraform-docs-common +--- + +# `tf-migrate prepare` reference + +The `tf-migrate prepare` command recursively scans the current directory for Terraform state files, then generates new Terraform configuration to migrate the state to HCP Terraform or Terraform Enterprise. + +## Usage + + $ tf-migrate prepare [options] + +## Description + +The `tf-migrate prepare` command prompts you for the following information: + +- The HCP Terraform or Terraform Enterprise organization to migrate your state to. +- If you would like to create a new branch named `hcp-migrate-` where `` is the name of the branch you currently have checked out. +- If you would like it to automatically create a pull request with the updated code change when the migration is complete. + +The `tf-migrate prepare` command generates a new Terraform configuration in the `_hcp-migrate-configs` directory to perform the migration. This configuration creates the following resources: + +- One workspace per state file. The `tf-migrate` tool names the workspace following the `-` pattern. The `tf-migrate` tool also creates workspace variables from the Terraform configuration's variables. +- One project to store all workspaces. The `tf-migrate` tool uses the directory path to the state file as the project name. For example, if your configuration is stored at `./frontend/networking/terraform.tfstate`, `tf-migrate` names the project "frontend_networking". Because of this, your directory path must be between 3-40 characters and only include letters, numbers, inner spaces, hyphens, and underscores. +- A new local git branch if you responded to the prompt to create a new branch with `yes`. +- A new pull request in the remote git repository if you responded to the prompt to create a pull request with `yes`. + +The `tf-migrate` CLI tool adds the generated configuration to the `.gitignore` file so that the configuration is not committed to source control. + +The `tf-migrate` tool creates the following structure in HCP Terraform or Terraform Enterprise depending on your local configuration: + +| Source | Result | +| :------------------------------------------------------------------------- | :----------------------------------------------------------- | +| Single configuration, single state | Single HCP workspace | +| Single configuration, multiple states for each Community Edition workspace | One HCP workspace per state | +| Multiple configurations, one state per configuration | One HCP workspace per configuration | +| Multiple configurations, multiple states per configuration | One HCP workspace per combination of configuration and state | + +## Example + +The `tf-migrate prepare` command generates the configuration to migrate this state to a single HCP Terraform workspace. + + + + $ tf-migrate prepare + ✓ Current working directory: /tmp/learn-terraform-migrate + ✓ Environment readiness checks completed + ✓ Found 3 HCP Terraform organizations + ┌────────────────────────────┐ + │ Available Orgs │ + ├────────────────────────────┤ + │ my-org-1 │ + │ my-org-2 │ + │ my-org-3 │ + └────────────────────────────┘ + Enter the name of the HCP Terraform organization to migrate to: my-org-1 + ✓ You have selected organization my-org-1 for migration + ✓ Found 2 directories with Terraform files + ┌────────────────────────────────┐ + │ Terraform File Directories │ + ├────────────────────────────────┤ + │ web │ + │ api │ + └────────────────────────────────┘ + Create a local branch named hcp-migrate-main from the current branch main: ... ? + + + Only 'yes or no' will be accepted as input. + Type 'yes' to approve the step + Type 'no' to to skip + + + Enter a value: yes + + ✓ Successfully created branch hcp-migrate-main + Do you want to open a pull request from hcp-migrate-main ... ? + + + Only 'yes or no' will be accepted as input. + Type 'yes' to approve the step + Type 'no' to to skip + + + Enter a value: yes + + ✓ Migration config generation completed + ✓ Successfully updated .gitignore + + + +## Available options + +You can include the following flags when you run the `tf-migrate prepare` command: + +| Option | Description | Default | Required | +| --------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------- | -------- | +| `-config` | Specifies the path to an optional configuration file. Refer to [`tf-migrate` configuration file reference](/terraform/enterprise/migrate/tf-migrate/reference/configuration) for more information. | None | No | +| `-hostname` | The hostname of your Terraform Enterprise server. If you do not provide a hostname, `tf-migrate` defaults to HCP Terraform. | `app.terraform.io` | No | +| `-skip-dir` | Specifies a comma-separated list of relative paths to exclude from the migration. | None | No | +| `--parallelism` | Specifies the number of threads `tf-migrate` uses to scan the local directory and prepare the migration. Set this value to `1` to disable parallelism. | The number of logical CPUs in the system. | No | diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/no-code-provisioning/module-design.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/no-code-provisioning/module-design.mdx new file mode 100644 index 0000000000..8b62ff5f99 --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/no-code-provisioning/module-design.mdx @@ -0,0 +1,72 @@ +--- +page_title: Design no-code ready modules for Terraform Enterprise +description: >- + No-code ready modules let users deploy a module's resources without writing + configuration. Learn how to prepare modules for no-code provisioning. +source: terraform-docs-common +--- + +# Design no-code ready modules + +Terraform [modules](/terraform/language/modules) let you define standardized collections of infrastructure resources that downstream users can more easily deploy. No-code ready modules build on these advantages by letting users deploy a module's resources without writing any Terraform configuration. This practice is called no-code provisioning. + + + +@include 'tfc-package-callouts/nocode.mdx' + + + +No-code provisioning enables a self-service workflow that lets users provision approved collections of resources without learning Terraform or infrastructure best practices. You can enable no-code provisioning for any public or private module in your [private registry](/terraform/enterprise/registry). Users can then [provision no-code infrastructure](/terraform/enterprise/no-code-provisioning/provisioning), set the module's input variables, and deploy its resources. + +> **Hands On:** Try the [Create and Use No-Code Ready Modules tutorial](/terraform/tutorials/cloud/no-code-provisioning). + +The same best practices apply to both standard and no-code ready module design. However, no-code modules have additional requirements and considerations. + +## Requirements + +A no-code ready module must meet the following requirements: + +- **Root Module Structure:** The module must follow [standard module structure](/terraform/language/modules/develop/structure) and define its resources in the root directory of the repository. This structure allows the public and private registries to generate documentation, track resource usage, and parse submodules and examples. +- **Provider Configuration:** A no-code ready module must declare the required provider(s) directly in the module. This configuration differs from the recommendations for [modules used in written configuration](/terraform/language/modules/develop/providers#legacy-shared-modules-with-provider-configurations). + +### Provider credentials + +Organization administrators must determine how no-code workspaces access credentials for provider authentication and design modules accordingly. + +When module consumers follow the no-code workflow, HCP Terraform automatically creates a new workspace for the resources and attempts to provision them. New workspaces must be able to access credentials for all providers defined within the module. + +You can grant new no-code workspace provider credentials using one of the following methods: + +- Recommended: Create a [project-scoped variable set](/terraform/enterprise/workspaces/variables/managing-variables#variable-sets) that HCP Terraform applies to all existing and future workspaces within a project. This approach allows you to create specific teams for those less familiar with Terraform, then give those teams access to your no-code projects. +- Create a [global variable set](/terraform/enterprise/workspaces/variables/managing-variables#variable-sets) that HCP Terraform applies to all existing and future workspaces in the organization. This action automatically grants newly-created workspaces access to the required provider credentials. +- Expose provider credentials as sensitive outputs in another workspace. You must add additional configuration to the module to access these values through [remote state data sources](/terraform/language/state/remote-state-data) and then reference them in provider configuration. This approach provides more control over access to these credentials than placing them in a global variable set. +- Elect to let the first run in new no-code workspaces fail and have module users add credentials directly to the workspace after creation. This approach provides the most control over access to provider credentials, but requires manual intervention. Module users must manually start a new run to provision infrastructure after they configure the credentials. + +## Module Design Recommendations + +Similarly to a [standard module](/terraform/language/modules/develop#when-to-write-a-module), a well-designed no-code ready module composes resources so that they are easy for others to deploy. However, no-code module users are less familiar with Terraform, so we recommend the following best practices for no-code module design. + +### Build For a Specific Use Case + +No-code ready module users are typically less familiar with Terraform and infrastructure management. Reduce the amount of technical decision-making required to deploy the module by scoping it to a single, specific use case. This approach lets users focus on business concerns instead of infrastructure concerns. + +For example, you could build modules that satisfy the following well-scoped use cases: + +- Deploying all resources needed for a three-tier web application +- Deploying a database with constraints on resource allocation and deployment region + +### Updating a Module's Version + +When you enable no-code provisioning for a module, HCP Terraform pins the **No-code Ready** designation to the specific module version of your choice. HCP Terraform deploys that selected module version whenever a user provisions a workspace using that module, ensuring that no-code users always provision the correct version. Pinning the **No-code Ready** designation to a specific module version lets you set variable input options, which are tied to that specific module version. + +By default, a module selects the latest version available. If you pinned a specific module version and a newer one becomes available, you can always update your module's version. + +### Provide Variable Defaults When Possible + +The no-code provisioning workflow prompts users to set values for the module version's input variables before creating the new workspace and deploying resources. We recommend setting reasonable defaults when possible to reduce the effort and expertise needed to deploy the module. Remember that the workspace can also access variable values set through global or project level variable sets in your organization. + +### Define Dropdown Options for Variables without Defaults + +If your module has variables without defaults, you can define options to limit the values a user can input when you enable a module for no-code provisioning. You can define input options using the HCP Terraform UI, the [No-Code Provisioning API](/terraform/enterprise/api-docs/no-code-provisioning), or the [TFE provider](https://registry.terraform.io/providers/hashicorp/tfe/latest/docs/resources/no_code_module). We recommend using the TFE provider if you want to track variable input options through a code approval process, therefore keeping your configuration as code. + +HCP Terraform surfaces any subsequent changes to a variable’s input options when no-code users provision a new module version. If you update the selected module version enabled for no-code provisioning, consider revisiting the variables and adjusting the defined input values accordingly. diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/no-code-provisioning/provisioning.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/no-code-provisioning/provisioning.mdx new file mode 100644 index 0000000000..46c36667f2 --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/no-code-provisioning/provisioning.mdx @@ -0,0 +1,89 @@ +--- +page_title: Provision no-code infrastructure in Terraform Enterprise +description: >- + No-code ready modules let users deploy a module's resources without writing + configuration. Learn how to provision infrastructure from a no-code ready + module. +source: terraform-docs-common +--- + +# Provision no-code infrastructure + +No-code provisioning lets you deploy infrastructure resources in a new HCP Terraform workspace without writing any Terraform configuration. You can create a no-code workspace from any module version labeled **No-code Ready** in your organization's [private registry](/terraform/enterprise/registry). + + + +@include 'tfc-package-callouts/nocode.mdx' + + + +> **Hands On:** Try the [Create and Use No-Code Ready Modules tutorial](/terraform/tutorials/cloud/no-code-provisioning). + +## Permissions + +To use no-code provisioning, you must be a member of a team with [manage all projects permissions](/terraform/enterprise/users-teams-organizations/permissions#manage-all-projects), [manage all workspaces permissions](/terraform/enterprise/users-teams-organizations/permissions#manage-all-workspaces), or [admin permissions for a project](/terraform/enterprise/users-teams-organizations/permissions#project-admins). When using [custom project permissions](/terraform/enterprise/users-teams-organizations/permissions#custom-project-permissions), your team must be able to create workspaces, write variables, and apply runs in a project. + +## Provider Credentials + +Terraform automatically starts a new run to provision no-code infrastructure upon workspace creation. No-code modules contain provider blocks in their configuration, but still require provider credentials for successful deployment. Organization administrators determine how new workspaces should [access provider credentials](/terraform/enterprise/no-code-provisioning/module-design#provider-credentials), which may require specific module design. + +## Creating a Workspace and Deploying Resources + +The no-code provisioning workflow creates a new HCP Terraform workspace to deploy and manage the no-code ready module's resources. HCP Terraform automatically starts a run to provision the module's resources in the new workspace. Depending on the workspace's settings, Terraform either automatically applies the plan or prompts you for approval to provision the infrastructure. + +To launch the no-code workflow: + +1. Sign in to [HCP Terraform](https://app.terraform.io/) or Terraform Enterprise and navigate to the organization with the module you want to provision. + +2. Click **Registry** in the main HCP Terraform navigation to access your organization's private registry. + +3. Click **Modules** to view the list of available modules in the private registry. You can filter for no-code ready modules in your registry. No-code enabled modules have a **No-code Ready** badge next to their names. + +4. Select the no-code ready module to view its details, then click **Provision workspace**. The **Configure module inputs** page appears. + + HCP Terraform scans the module configuration for input variables and prompts for values for any variables without defaults or undefined in an existing global variable set. Terraform requires values for these variables to successfully complete runs in the workspace. HCP Terraform performs type validation for the variable values if the module configuration specifies a type. + +5. (Optional) Set values for the input variables. If your organization has defined options for a variable's values, these options appear in a dropdown menu. You can skip this step and configure the variables later in your workspace. However, HCP Terraform does not prompt you for these values again, and your Terraform runs may fail. + +6. Click **Next: Workspace settings**. + +7. Enter a **Workspace Name**. The name must be unique within the organization and can include letters, numbers, dashes (-), and underscores (\_). Refer to the [workspace naming recommendations](/terraform/enterprise/workspaces/create#workspace-naming) for more guidance. + +8. Choose a **Project** for the workspace. Teams with access to the specified project can view the workspace automatically. Refer to [Organizing Workspaces with Projects](/terraform/enterprise/projects/manage) for more details. If the specified project contains any project-scoped variable sets, HCP Terraform automatically applies those sets to the workspace. + +9. Add an optional **Description** for the workspace. + +10. Select an apply method for the workspace. **Auto apply** automatically applies any successful runs in the workspace, including the initial run on workspace creation. **Manual apply** prompts operators to review and confirm the changes in a run. **Auto apply** is the default option for a no-code workspace. + +11. Click **Create workspace**. HCP Terraform creates a new workspace and starts a run. Depending on the apply method, it automatically applies your infrastructure or prompts you for approval to create the no-code module's resources. + +## Operations in No-Code Workspaces + +No-code workspaces have a limited feature set because you cannot access the resource configuration. However, you can edit workspace variables and settings, including notifications, permissions, and run triggers. You can use run triggers to connect the workspace to one or more source workspaces, start new runs when you change workspace variables, or queue destroy runs. + +### Updating Variables + +To change a variable's options after provisioning, go to the **Variables** section in your workspace to see your workspace's variables listed. To edit a variable: + +1. Click the ellipses next to the variable you want to edit and select **Edit**. +2. Enter your desired value and click **Save variable**. + +Start a new run in your workspace to update your existing infrastructure with your new variable value. + +### Module Version Updates + +When you [update the module version](/terraform/enterprise/no-code-provisioning/module-design#updating-a-module-s-version) designated for no-code provisioning, every workspace provisioned from the module is notified that an updated version is available on the workspace overview page. HCP Terraform does not automatically update workspaces. A workspace admin must respond to the update notification to upgrade the workspace, and HCP Terraform prompts for values for any new input variables. + +To change the version of the module that users can deploy: + +1. Sign in to [HCP Terraform](https://app.terraform.io/) or Terraform Enterprise and navigate to your organization. + +2. Choose **Registry** from the sidebar and navigate to the module in your organization's registry. + +3. Click **Configure Settings**. + +4. Click **Edit version and variable options**. + +5. Choose the desired module version from the **Module version** dropdown. + +6. Click **Save**. diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/policy-enforcement/define-policies/custom-sentinel.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/policy-enforcement/define-policies/custom-sentinel.mdx new file mode 100644 index 0000000000..16334c8807 --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/policy-enforcement/define-policies/custom-sentinel.mdx @@ -0,0 +1,260 @@ +--- +page_title: Define Sentinel policies in Terraform Enterprise +description: >- + Learn how to use the Sentinel policy language to create policies in Terraform + Enterprise. +source: terraform-docs-common +--- + +# Define Sentinel policies in HCP Terraform + +This topic describes how to create and manage custom policies using Sentinel policy language. For instructions about how to use pre-written Sentinel policies from the registry, refer to [Run pre-written Sentinel policies](/terraform/enterprise/policy-enforcement/prewritten-sentinel). + +## Overview + +To define a policy, create a file and declare an `import` function to include reusable libraries, external data, and other functions. Sentinel policy language includes several types of elements you can import using the `import` function. + +Declare and configure additional Sentinel policy language elements. The details depend on which elements you want to use in your policy. Refer to the [Sentinel documentation](/sentinel/docs) for additional information. + + + +@include 'tfc-package-callouts/policies.mdx' + + + +## Declare an `import` function + +A policy can include imports that enable a policy to access reusable libraries, external data, and functions. Refer to [imports](/sentinel/docs/concepts/imports) in the Sentinel documentation for more details. + +HCP Terraform provides four imports to define policy rules for the plan, configuration, state, and run associated with a policy check. + +- [tfplan](/terraform/enterprise/policy-enforcement/import-reference/tfplan-v2) - Access a Terraform plan, which is the file created as a result of the [`terraform plan` command](/terraform/cli/commands/plan). The plan represents the changes that Terraform must make to reach the desired infrastructure state described in the configuration. +- [tfconfig](/terraform/enterprise/policy-enforcement/import-reference/tfconfig-v2) - Access a Terraform configuration. The configuration is the set of `.tf` files that describe the desired infrastructure state. +- [tfstate](/terraform/enterprise/policy-enforcement/import-reference/tfstate-v2) - Access the Terraform [state](/terraform/language/state). Terraform uses state to map real-world resources to your configuration. +- [tfrun](/terraform/enterprise/policy-enforcement/import-reference/tfrun) - Access data associated with a [run in HCP Terraform](/terraform/enterprise/run/remote-operations). For example, you could retrieve the run's workspace. + +You can create mocks of these imports to use with the the [Sentinel +CLI](/sentinel/docs/commands) mocking and testing features. Refer to [Mocking Terraform Sentinel Data](/terraform/enterprise/policy-enforcement/test-sentinel) for more details. + + + +HCP Terraform does not support custom imports. + + + +## Declare additional elements + +The following functions and idioms will be useful as you start writing Sentinel +policies for Terraform. + +### Iterate over modules and find resources + +The most basic Sentinel task for Terraform is to enforce a rule on all resources +of a given type. Before you can do that, you need to get a collection of all the +relevant resources from all modules. The easiest way to do that is to copy and +use a function like the following into your policies. + +The following example uses the `tfplan` import. Refer to our [guides for other examples](https://github.com/hashicorp/terraform-guides/tree/master/governance/second-generation/common-functions) of +functions that iterate over the `tfconfig` and `tfstate` imports. + +```python +import "tfplan" +import "strings" + +# Find all resources of specific type from all modules using the tfplan import +find_resources_from_plan = func(type) { + resources = {} + for tfplan.module_paths as path { + for tfplan.module(path).resources[type] else {} as name, instances { + for instances as index, r { + # Get the address of the resource instance + if length(path) == 0 { + # root module + address = type + "." + name + "[" + string(index) + "]" + } else { + # non-root module + address = "module." + strings.join(path, ".module.") + "." + + type + "." + name + "[" + string(index) + "]" + } + # Add the instance to resources, setting the key to the address + resources[address] = r + } + } + } + return resources +} +``` + +Call the function to get all resources of a desired type by passing the +type as a string in quotation marks: + +```python +aws_instances = find_resources_from_plan("aws_instance") +``` + +This example function does several useful things while finding resources: + +- It checks every module (including the root module) for resources of the + specified type by iterating over the `module_paths` namespace. The top-level + `resources` namespace is more convenient, but it only reveals resources from + the root module. +- It iterates over the named resources and [resource + instances](/terraform/language/expressions/references#resources) + found in each module, starting with `tfplan.module(path).resources[type]` + which is a series of nested maps keyed by resource names and instance counts. +- It uses the Sentinel [`else` + operator](/sentinel/docs/language/spec#else-operator) to + recover from `undefined` values which would occur for modules that don't have + any resources of the specified type. +- It builds a flat `resources` map of all resource instances of the specified + type. Using a flat map simplifies the code used by Sentinel policies to + evaluate rules. +- It computes an `address` variable for each resource instance and uses this as + the key in the `resources` map. This allows writers of Sentinel policies to + print the full [address](/terraform/cli/state/resource-addressing) of each + resource instance that violate a policy, using the same address format used in + plan and apply logs. Doing this tells users who see violation messages exactly + which resources they need to modify in their Terraform code to comply with the + Sentinel policies. +- It sets the value of the `resources` map to the data associated with the + resource instance (`r`). This is the data that Sentinel policies apply rules + against. + +### Validate resource attributes + +Once you have a collection of resources instances of a desired type indexed by +their addresses, you usually want to validate that one or more resource +attributes meets some conditions by iterating over the resource instances. + +While you could use Sentinel's [`all` and `any` +expressions](/sentinel/docs/language/boolexpr#any-all-expressions) +directly inside Sentinel rules, your rules would only report the first violation +because Sentinel uses short-circuit logic. It is therefore usually preferred to +use a [`for` loop](/sentinel/docs/language/loops) outside +of your rules so that you can report all violations that occur. You can do this +inside functions or directly in the policy itself. + +Here is a function that calls the `find_resources_from_plan` function and +validates that the instance types of all EC2 instances being provisioned are in +a given list: + +```python +# Validate that all EC2 instances have instance_type in the allowed_types list +validate_ec2_instance_types = func(allowed_types) { + validated = true + aws_instances = find_resources_from_plan("aws_instance") + for aws_instances as address, r { + # Determine if the attribute is computed + if r.diff["instance_type"].computed else false is true { + print("EC2 instance", address, + "has attribute, instance_type, that is computed.") + } else { + # Validate that each instance has allowed value + if (r.applied.instance_type else "") not in allowed_types { + print("EC2 instance", address, "has instance_type", + r.applied.instance_type, "that is not in the allowed list:", + allowed_types) + validated = false + } + } + } + return validated +} +``` + +The boolean variable `validated` is initially set to `true`, but it is set to +`false` if any resource instance violates the condition requiring that the +`instance_type` attribute be in the `allowed_types` list. Since the function +returns `true` or `false`, it can be called inside Sentinel rules. + +Note that this function prints a warning message for **every** resource instance +that violates the condition. This allows writers of Terraform code to fix all +violations after just one policy check. It also prints warnings when the +attribute being evaluated is +[computed](/terraform/enterprise/policy-enforcement/import-reference/tfplan-v2#value-computed) and does +not evaluate the condition in this case since the applied value will not be +known. + +While this function allows a rule to validate an attribute against a list, some +rules will only need to validate an attribute against a single value; in those +cases, you could either use a list with a single value or embed that value +inside the function itself, drop the `allowed_types` parameter from the function +definition, and use the `is` operator instead of the `in` operator to compare +the resource attribute against the embedded value. + +### Write Rules + +Having used the standardized `find_resources_from_plan` function and having +written your own function to validate that resources instances of a specific +type satisfy some condition, you can define a list with allowed values and write +a rule that evaluates the value returned by your validation function. + +```python +# Allowed Types +allowed_types = [ + "t2.small", + "t2.medium", + "t2.large", +] + +# Main rule +main = rule { + validate_ec2_instance_types(allowed_types) +} + +``` + +### Validate multiple conditions in a single policy + +If you want a policy to validate multiple conditions against resources of a +specific type, you could define a separate validation function for each +condition or use a single function to evaluate all the conditions. In the latter +case, you would make this function return a list of boolean values, using one +for each condition. You can then use multiple Sentinel rules that evaluate +those boolean values or evaluate all of them in your `main` rule. Here is a +partial example: + +```python +# Function to validate that S3 buckets have private ACL and use KMS encryption +validate_private_acl_and_kms_encryption = func() { + result = { + "private": true, + "encrypted_by_kms": true, + } + s3_buckets = find_resources_from_plan("aws_s3_bucket") + # Iterate over resource instances and check that S3 buckets + # have private ACL and are encrypted by a KMS key + # If an S3 bucket is not private, set result["private"] to false + # If an S3 bucket is not encrypted, set result["encrypted_by_kms"] to false + for s3_buckets as joined_path, resource_map { + #... + } + return result +} + +# Call the validation function +validations = validate_private_acl_and_kms_encryption() + +# ACL rule +is_private = rule { + validations["private"] +} + +# KMS Encryption Rule +is_encrypted_by_kms = rule { + validations["encrypted_by_kms"] +} + +# Main rule +main = rule { + is_private and is_encrypted_by_kms +} +``` + +You can write similar functions and policies to restrict Terraform configurations using the `tfconfig` import and to restrict Terraform state using the `tfstate` import. + +## Next steps + +1. Group your policies into sets and apply them to your workspaces. Refer to [Create policy sets](/terraform/enterprise/policy-enforcement/manage-policy-sets#create-policy-sets) for additional information. +2. View results and address Terraform runs that do not comply with your policies. Refer to [View results](/terraform/enterprise/policy-enforcement/view-results) for additional information. +3. You can also view Sentinel policy results in JSON format. Refer to [View Sentinel JSON results](/terraform/enterprise/policy-enforcement/view-results/json) for additional information. diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/policy-enforcement/define-policies/index.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/policy-enforcement/define-policies/index.mdx new file mode 100644 index 0000000000..bc5881f611 --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/policy-enforcement/define-policies/index.mdx @@ -0,0 +1,19 @@ +--- +page_title: Define policies overview +description: >- + You can define policies using HashiCorp Sentinel or Open Policy Agent (OPA). + Learn how to define policies for governing how Terraform provisions + infrastructure. +source: terraform-docs-common +--- + +# Define policies overview + +This topic provides overview information about how to define policies as code. Policies are rules for enforcing how Terraform provisions infrastructure as code for your workspaces and projects. + +## Workflows + +You can use two policy-as-code frameworks to define fine-grained, logic-based policies: Sentinel and Open Policy Agent (OPA). Depending on the settings, policies can act as advisory warnings or firm requirements that prevent Terraform from provisioning infrastructure. + +- **Sentinel:** You define policies with the [Sentinel policy language](/sentinel/docs/concepts/language) and use imports to parse the Terraform plan, state, and configuration. Refer to [Define custom Sentinel policies](/terraform/enterprise/policy-enforcement/define-policies/custom-sentinel) for details. +- **OPA:** You define policies with the [Rego policy language](https://www.openpolicyagent.org/docs/latest/policy-language/). Refer to [Defining OPA Policies](/terraform/enterprise/policy-enforcement/define-policies/opa) for details. diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/policy-enforcement/define-policies/opa.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/policy-enforcement/define-policies/opa.mdx new file mode 100644 index 0000000000..92305f8e9f --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/policy-enforcement/define-policies/opa.mdx @@ -0,0 +1,259 @@ +--- +page_title: Define Open Policy Agent policies for Terraform Enterprise +description: >- + Use the Rego policy language to define Open Policy Agent (OPA) policies for + Terraform Enterprise. +source: terraform-docs-common +--- + +# Define Open Policy Agent policies for HCP Terraform + +This topic describes how to create and manage custom policies using the open policy agent (OPA) framework. Refer to the following topics for instructions on using HashiCorp Sentinel policies: + +- [Define custom Sentinel policies](/terraform/enterprise/policy-enforcement/define-policies/custom-sentinel) +- [Copy pre-written Sentinel policies](/terraform/enterprise/define-policies/prewritten-sentinel) + + + +@include 'tfc-package-callouts/policies.mdx' + + + +## Overview + +> **Hands-on:** Try the [Detect Infrastructure Drift and Enforce OPA Policies](/terraform/tutorials/cloud/drift-and-opa) tutorial. + +You can write OPA policies using the Rego policy language, which is the native query language for the OPA framework. Refer to the following topics in the [OPA documentation](https://www.openpolicyagent.org/docs/latest/policy-language/) for additional information: + +- [How Do I Write Rego Policies?](https://www.openpolicyagent.org/docs/v0.13.5/how-do-i-write-policies/) +- [Rego Policy Playground](https://play.openpolicyagent.org/) + +## OPA query + +You must write a query to identify a specific policy rule within your Rego code. The query may evaluate code from multiple Rego files. + +The result of each query must return an array, which HCP Terraform uses to determine whether the policy has passed or failed. If the array is empty, HCP Terraform reports that the policy has passed. + +The query is typically a combination of the policy package name and rule name, such as `data.terraform.deny`. + +## OPA input + +HCP Terraform combines the output from the Terraform run and plan into a single JSON file and passes that file to OPA as input. Refer to the [OPA Overview documentation](https://www.openpolicyagent.org/docs/latest/#the-input-document) for more details about how OPA uses JSON input data. + +The run data contains information like workspace details and the organization name. To access the properties from the Terraform plan data in your policies, use `input.plan`. To access properties from the Terraform run, use `input.run`. + +The following example shows sample OPA input data. + +```json +{ +"plan": { + "format_version": "1.1", + "output_changes": { + }, + "planned_values": { + }, + "resource_changes": [ + ], + "terraform_version": "1.2.7" +}, + +"run": { + "organization": { + "name": "hashicorp" + }, + "workspace": { + } +} +} +``` + +Use the [Retrieve JSON Execution Plan endpoint](/terraform/enterprise/api-docs/plans#retrieve-the-json-execution-plan) to retrieve Terraform plan output data for testing. Refer to [Terraform Run Data](#terraform-run-data) for the properties included in Terraform run output data. + +## Example Policies + +The following example policy parses a Terraform plan and checks whether it includes security group updates that allow ingress traffic from all CIDRs (`0.0.0.0/0`). + +The OPA query for this example policy is `data.terraform.policies.public_ingress.deny`. + +```rego +package terraform.policies.public_ingress + +import input.plan as plan + +deny[msg] { + r := plan.resource_changes[_] + r.type == "aws_security_group" + r.change.after.ingress[_].cidr_blocks[_] == "0.0.0.0/0" + msg := sprintf("%v has 0.0.0.0/0 as allowed ingress", [r.address]) +} +``` + +The following example policy ensures that databases are no larger than 128 GB. + +The OPA query for this policy is `data.terraform.policies.fws.database.fws_db_001.rule`. + +```rego +package terraform.policies.fws.database.fws_db_001 + +import future.keywords.in +import input.plan as tfplan + +actions := [ + ["no-op"], + ["create"], + ["update"], +] + +db_size := 128 + +resources := [resource_changes | + resource_changes := tfplan.resource_changes[_] + resource_changes.type == "fakewebservices_database" + resource_changes.mode == "managed" + resource_changes.change.actions in actions +] + +violations := [resource | + resource := resources[_] + not resource.change.after.size == db_size +] + +violators[address] { + address := violations[_].address +} + +rule[msg] { + count(violations) != 0 + msg := sprintf( + "%d %q severity resource violation(s) have been detected.", + [count(violations), rego.metadata.rule().custom.severity] + ) +} +``` + +## Test policies + +You can write tests for your policies by [mocking](https://www.openpolicyagent.org/docs/latest/policy-testing/#data-and-function-mocking) the input data the policies use during Terraform runs. + +The following example policy called `block_auto_apply_runs` checks whether or not an HCP Terraform workspace has been configured to automatically apply a successful Terraform plan. + +```rego +package terraform.tfc.block_auto_apply_runs + +import input.run as run + +deny[msg] { + run.workspace.auto_apply != false + msg := sprintf( + "HCP Terraform workspace %s has been configured to automatically provision Terraform infrastructure. Change the workspace Apply Method settings to 'Manual Apply'", + [run.workspace.name], + ) +} +``` + +The following test validates `block_auto_apply_runs`. The test is written in rego and uses the OPA [test format](https://www.openpolicyagent.org/docs/latest/policy-testing/#test-format) to check that the workspace [apply method](/terraform/enterprise/workspaces/settings#apply-method) is not configured to auto apply. You can run this test with the `opa test` CLI command. Refer to [Policy Testing](https://www.openpolicyagent.org/docs/latest/policy-testing/) in the OPA documentation for more details. + +```rego +package terraform.tfc.block_auto_apply_runs + +import future.keywords + +test_run_workspace_auto_apply if { + deny with input as {"run": {"workspace": {"auto_apply": true}}} +} +``` + +## Terraform run data + +Each [Terraform run](/terraform/docs/glossary#run) outputs data describing the run settings and the associated workspace. + +### Schema + +The following code shows the schema for Terraform run data. + + run + ├── id (string) + ├── created_at (string) + ├── created_by (string) + ├── message (string) + ├── commit_sha (string) + ├── is_destroy (boolean) + ├── refresh (boolean) + ├── refresh_only (boolean) + ├── replace_addrs (array of strings) + ├── speculative (boolean) + ├── target_addrs (array of strings) + └── project + │ ├── id (string) + │ └── name (string) + ├── variables (map of keys) + ├── organization + │ └── name (string) + └── workspace + ├── id (string) + ├── name (string) + ├── created_at (string) + ├── description (string) + ├── execution_mode (string) + ├── auto_apply (bool) + ├── tags (array of strings) + ├── working_directory (string) + └── vcs_repo (map of keys) + +### Properties + +The following sections contain details about each property in Terraform run data. + +#### Run namespace + +The following table contains the attributes for the `run` namespace. + +| Properties Name | Type | Description | +| --------------- | ------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `id` | String | The ID associated with the current Terraform run | +| `created_at` | String | The time Terraform created the run. The timestamp follows the [standard timestamp format in RFC 3339](https://datatracker.ietf.org/doc/html/rfc3339). | +| `created_by` | String | A string that specifies the user name of the HCP Terraform user for the specific run. | +| `message` | String | The message associated with the Terraform run. The default value is "Queued manually via the Terraform Enterprise API". | +| `commit_sha` | String | The checksum hash (SHA) that identifies the commit | +| `is_destroy` | Boolean | Whether the plan is a destroy plan that destroys all provisioned resources | +| `refresh` | Boolean | Whether the state refreshed prior to the plan | +| `refresh_only` | Boolean | Whether the plan is in refresh-only mode. In refresh-only mode, Terraform ignores configuration changes and updates state with any changes made outside of Terraform. | +| `replace_addrs` | An array of strings representing [resource addresses](/terraform/cli/state/resource-addressing) | The targets specified using the [`-replace`](/terraform/cli/commands/plan#replace-address) flag in the CLI or the `replace-addrs` property in the API. Undefined if there are no specified resource targets. | +| `speculative` | Boolean | Whether the plan associated with the run is a [speculative plan](/terraform/enterprise/run/remote-operations#speculative-plans) only | +| `target_addrs` | An array of strings representing [resource addresses](/terraform/cli/state/resource-addressing). | The targets specified using the [`-target`](/terraform/cli/commands/plan#resource-targeting) flag in the CLI or the `target-addrs` property in the API. Undefined if there are no specified resource targets. | +| `variables` | A string-keyed map of values. | Provides the variables configured within the run. Each variable `name` maps to two properties: `category` and `sensitive`. The `category` property is a string indicating the variable type, either "input" or "environment". The `sensitive` property is a boolean, indicating whether the variable is a [sensitive value](/terraform/enterprise/workspaces/variables/managing-variables#sensitive-values). | + +#### Project Namespace + +The following table contains the properties for the `project` namespace. + +| Property Name | Type | Description | +| ------------- | ------ | --------------------------------------------------------------------------------------- | +| `id` | String | The ID associated with the Terraform project | +| `name` | String | The name of the project, which can only include letters, numbers, spaces, `-`, and `_`. | + +#### Organization namespace + +The `organization` namespace has one property called `name`. The `name` property is a string that specifies the name of the HCP Terraform organization for the run. + +#### Workspace namespace + +The following table contains the properties for the `workspace` namespace. + +| Property Name | Type | Description | +| ------------------- | ----------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `id` | String | The ID associated with the Terraform workspace | +| `name` | String | The name of the workspace, which can only include letters, numbers, `-`, and `_` | +| `created_at` | String | The time of the workspace's creation. The timestamp follows the [standard timestamp format in RFC 3339](https://datatracker.ietf.org/doc/html/rfc3339). | +| `description` | String | The description for the workspace. This value can be `null`. | +| `auto_apply` | Boolean | The workspace's [auto-apply](/terraform/enterprise/workspaces/settings#apply-method) setting | +| `tags` | Array of strings | The list of tag names for the workspace | +| `working_directory` | String | The configured [Terraform working directory](/terraform/enterprise/workspaces/settings#terraform-working-directory) of the workspace. This value can be `null`. | +| `execution_mode` | String | The configured Terraform execution mode of the workspace. The default value is `remote`. | +| `vcs_repo` | A string-keyed map to objects | Data associated with a VCS repository connected to the workspace. The map contains `identifier` (string), ` display_identifier` (string), `branch` (string), and `ingress_submodules` (boolean). Refer to the HCP Terraform [Workspaces API documentation](/terraform/enterprise/api-docs/workspaces) for details about each property. This value can be `null`. | + +## Next steps + +- Group your policies into sets and apply them to your workspaces. Refer to [Create policy sets](/policy-enforcement/manage-policy-sets#create-policy-sets) for additional information. +- View results and address Terraform runs that do not comply with your policies. Refer to [View results](/terraform/enterprise/policy-enforcement/view-results) for additional information. +- You can also view Sentinel policy results in JSON format. Refer to [View Sentinel JSON results](/terraform/enterprise/policy-enforcement/view-results/json) for additional information. diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/policy-enforcement/import-reference/index.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/policy-enforcement/import-reference/index.mdx new file mode 100644 index 0000000000..eebf3b47f7 --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/policy-enforcement/import-reference/index.mdx @@ -0,0 +1,32 @@ +--- +page_title: Sentinel import function reference +description: >- + Use the Sentinel import function to configure policy behaviors in custom + Sentinel policies. +source: terraform-docs-common +--- + +# `import` function reference overview + +This topic provides an overview of the Sentinel `import` function, which you can use to import Sentinel libraries into your custom Sentinel policies. Refer to [Define custom Sentinel policies](/terraform/enterprise/policy-enforcement/define-policies/custom-sentinel) for additional information about how to use the `import` function. + +## Functions for Terraform + +You can add Sentinel the `import` function, which enables a policy to access reusable libraries, external data, and other functions. Refer to the [Sentinel imports documentation](/sentinel/docs/language/imports) for more details. + +HCP Terraform provides the following importable libraries to define policy rules for the plan, configuration, state, and run associated with a policy check. + +- [`tfplan`](/terraform/enterprise/policy-enforcement/import-reference/tfplan) : Provides access to a Terraform plan, which is the file created when you run the `terraform plan` command. This library is deprecated. Use `tfplanv/2` instead. +- [`tfplan/v2`](/terraform/enterprise/policy-enforcement/import-reference/tfplan-v2): Provides access to a Terraform plan, which is the file created when you run the `terraform plan` command. +- [`tfconfig`](/terraform/enterprise/policy-enforcement/import-reference/tfconfig): Provides access to a Terraform configuration. The configuration is the set of `.tf` files that describe the desired infrastructure state. This library is deprecated. Use `tfconfig/v2` instead. +- [`tfconfig/v2`](/terraform/enterprise/policy-enforcement/import-reference/tfconfig-v2): Provides access to a Terraform configuration. The configuration is the set of `.tf` files that describe the desired infrastructure state. +- [`tfstate`](/terraform/enterprise/policy-enforcement/import-reference/tfstate): Provides access to the Terraform state. Terraform uses state to map real-world resources to your configuration. This library is deprecated. Use `tfstate/v2` instead. +- [`tfstate/v2`](/terraform/enterprise/policy-enforcement/import-reference/tfstate-v2): Provides access to the Terraform state. Terraform uses state to map real-world resources to your configuration. +- [`tfrun`](/terraform/enterprise/policy-enforcement/import-reference/tfrun): Provides access to data associated with a run in HCP Terraform. For example, you could retrieve the run's workspace. + +## Test `import` functions + +You can create mocks of these functions and test them using the Sentinel CLI. Refer to the following topics for additional information: + +- [Test Sentinel policies](/terraform/enterprise/policy-enforcement/test-sentinel) +- [Sentinel CLI documentation](/sentinel/docs/commands) diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/policy-enforcement/import-reference/tfconfig-v2.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/policy-enforcement/import-reference/tfconfig-v2.mdx new file mode 100644 index 0000000000..637d596f01 --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/policy-enforcement/import-reference/tfconfig-v2.mdx @@ -0,0 +1,422 @@ +--- +page_title: tfconfig/v2 Sentinel import +description: >- + Use the tfconfig/v2 import to give Sentinel access to a Terraform + configuration. +source: terraform-docs-common +--- + +-> **Note:** This is documentation for the next version of the `tfconfig` +Sentinel import, designed specifically for Terraform 0.12. This import requires +Terraform 0.12 or higher, and must currently be loaded by path, using an alias, +example: `import "tfconfig/v2" as tfconfig`. + +# tfconfig/v2 Sentinel import + +The `tfconfig/v2` import provides access to a Terraform configuration. + +The Terraform configuration is the set of `*.tf` files that are used to +describe the desired infrastructure state. Policies using the `tfconfig` +import can access all aspects of the configuration: providers, resources, +data sources, modules, and variables. + + + +@include 'tfc-package-callouts/policies.mdx' + + + +Some use cases for `tfconfig` include: + +- **Organizational naming conventions**: requiring that configuration elements + are named in a way that conforms to some organization-wide standard. +- **Required inputs and outputs**: organizations may require a particular set + of input variable names across all workspaces or may require a particular + set of outputs for asset management purposes. +- **Enforcing particular modules**: organizations may provide a number of + "building block" modules and require that each workspace be built only from + combinations of these modules. +- **Enforcing particular providers or resources**: an organization may wish to + require or prevent the use of providers and/or resources so that configuration + authors cannot use alternative approaches to work around policy + restrictions. + +The data in the `tfconfig/v2` import is sourced from the JSON configuration file +that is generated by the [`terraform show -json`](/terraform/cli/commands/show#json-output) command. For more information on +the file format, see the [JSON Output Format](/terraform/internals/json-format) +page. + +## Import Overview + +The `tfconfig/v2` import is structured as a series of _collections_, keyed as a +specific format, such as resource address, module address, or a +specifically-formatted provider key. + + tfconfig/v2 + ├── strip_index() (function) + ├── providers + │ └── (indexed by [module_address:]provider[.alias]) + │ ├── provider_config_key (string) + │ ├── name (string) + │ ├── full_name (string) + │ ├── alias (string) + │ ├── module_address (string) + │ ├── config (block expression representation) + │ └── version_constraint (string) + ├── resources + │ └── (indexed by address) + │ ├── address (string) + │ ├── module_address (string) + │ ├── mode (string) + │ ├── type (string) + │ ├── name (string) + │ ├── provider_config_key (string) + │ ├── provisioners (list) + │ │ └── (ordered provisioners for this resource only) + │ ├── config (block expression representation) + │ ├── count (expression representation) + │ ├── for_each (expression representation) + │ └── depends_on (list of strings) + ├── provisioners + │ └── (indexed by resource_address:index) + │ ├── resource_address (string) + │ ├── type (string) + │ ├── index (string) + │ └── config (block expression representation) + ├── variables + │ └── (indexed by module_address:name) + │ ├── module_address (string) + │ ├── name (string) + │ ├── default (value) + │ └── description (string) + ├── outputs + │ └── (indexed by module_address:name) + │ ├── module_address (string) + │ ├── name (string) + │ ├── sensitive (boolean) + │ ├── value (expression representation) + │ ├── description (string) + │ └── depends_on (list of strings) + └── module_calls + └── (indexed by module_address:name) + ├── module_address (string) + ├── name (string) + ├── source (string) + ├── config (block expression representation) + ├── count (expression representation) + ├── depends_on (expression representation) + ├── for_each (expression representation) + └── version_constraint (string) + +The collections are: + +- [`providers`](#the-providers-collection) - The configuration for all provider + instances across all modules in the configuration. +- [`resources`](#the-resources-collection) - The configuration of all resources + across all modules in the configuration. +- [`variables`](#the-variables-collection) - The configuration of all variable + definitions across all modules in the configuration. +- [`outputs`](#the-outputs-collection) - The configuration of all output + definitions across all modules in the configuration. +- [`module_calls`](#the-module_calls-collection) - The configuration of all module + calls (individual [`module`](/terraform/language/modules) blocks) across + all modules in the configuration. + +These collections are specifically designed to be used with the +[`filter`](/sentinel/docs/language/collection-operations#filter-expression) +quantifier expression in Sentinel, so that one can collect a list of resources +to perform policy checks on without having to write complex module or +configuration traversal. As an example, the following code will return all +`aws_instance` resource types within the configuration, regardless of what +module they are in: + + all_aws_instances = filter tfconfig.resources as _, r { + r.mode is "managed" and + r.type is "aws_instance" + } + +You can add specific attributes to the filter to narrow the search, such as the +module address. The following code would return resources in a module named +`foo` only: + + all_aws_instances = filter tfconfig.resources as _, r { + r.module_address is "module.foo" and + r.mode is "managed" and + r.type is "aws_instance" + } + +### Address Differences Between `tfconfig`, `tfplan`, and `tfstate` + +This import deals with configuration before it is expanded into a +resource graph by Terraform. As such, it is not possible to compute an index as +the import is building its collections and computing addresses for resources and +modules. + +As such, addresses found here may not always match the expanded addresses found +in the [`tfplan/v2`](/terraform/enterprise/policy-enforcement/import-reference/tfplan-v2) and [`tfstate/v2`](/terraform/enterprise/policy-enforcement/import-reference/tfstate-v2) +imports, specifically when +[`count`](/terraform/language/resources#count-multiple-resource-instances-by-count) +and +[`for_each`](/terraform/language/resources#for_each-multiple-resource-instances-defined-by-a-map-or-set-of-strings), +are used. + +As an example, consider a resource named `null_resource.foo` with a count of `2` +located in a module named `bar`. While there will possibly be entries in the +other imports for `module.bar.null_resource.foo[0]` and +`module.bar.null_resource.foo[1]`, in `tfconfig/v2`, there will only be a +`module.bar.null_resource.foo`. As mentioned in the start of this section, this +is because configuration actually _defines_ this scaling, whereas _expansion_ +actually happens when the resource graph is built, which happens as a natural +part of the refresh and planning process. + +The `strip_index` helper function, found in this import, can assist in +removing the indexes from addresses found in the `tfplan/v2` and `tfstate/v2` +imports so that data from those imports can be used to reference data in this +one. + +## The `strip_index` Function + +The `strip_index` helper function can be used to remove indexes from addresses +found in [`tfplan/v2`](/terraform/enterprise/policy-enforcement/import-reference/tfplan-v2) and [`tfstate/v2`](/terraform/enterprise/policy-enforcement/import-reference/tfstate-v2), +by removing the indexes from each resource. + +This can be used to help facilitate cross-import lookups for data between plan, +state, and config. + + import "tfconfig/v2" as tfconfig + import "tfplan/v2" as tfplan + + main = rule { + all filter tfplan.resource_changes as _, rc { + rc.mode is "managed" and + rc.type is "aws_instance" + } as _, rc { + tfconfig.resources[tfconfig.strip_index(rc.address)].config.ami.constant_value is "ami-abcdefgh012345" + } + } + +## Expression Representations + +Most collections in this import will have one of two kinds of _expression +representations_. This is a verbose format for expressing a (parsed) +configuration value independent of the configuration source code, which is not +100% available to a policy check in HCP Terraform. + + (expression representation) + ├── constant_value (value) + └── references (list of strings) + +There are two major parts to an expression representation: + +- Any _strictly constant value_ is expressed as an expression with a + `constant_value` field. +- Any expression that requires some degree of evaluation to generate the final + value - even if that value is known at plan time - is not expressed in + configuration. Instead, any particular references that are made are added to + the `references` field. More details on this field can be found in the + [expression + representation](/terraform/internals/json-format#expression-representation) + section of the JSON output format documentation. + +For example, to determine if an output is based on a particular +resource value, one could do: + + import "tfconfig/v2" as tfconfig + + main = rule { + tfconfig.outputs["instance_id"].value.references is ["aws_instance.foo"] + } + +-> **Note:** The representation does not account for +complex interpolations or other expressions that combine constants with other +expression data. For example, the partially constant data in `"foo${var.bar}"` would be lost. + +### Block Expression Representation + +Expanding on the above, a multi-value expression representation (such as the +kind found in a [`resources`](#the-resources-collection) collection element) is +similar, but the root value is a keyed map of expression representations. This +is repeated until a "scalar" expression value is encountered, ie: a field that +is not a block in the resource's schema. + + (block expression representation) + └── (attribute key) + ├── (child block expression representation) + │ └── (...) + ├── constant_value (value) + └── references (list of strings) + +As an example, one can validate expressions in an +[`aws_instance`](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/instance) resource using the +following: + + import "tfconfig/v2" as tfconfig + + main = rule { + tfconfig.resources["aws_instance.foo"].config.ami.constant_value is "ami-abcdefgh012345" + } + +Note that _nested blocks_, sometimes known as _sub-resources_, will be nested in +configuration as a list of blocks (reflecting their ultimate nature as a list +of objects). An example would be the `aws_instance` resource's +[`ebs_block_device`](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/instance#ebs-ephemeral-and-root-block-devices) block: + + import "tfconfig/v2" as tfconfig + + main = rule { + tfconfig.resources["aws_instance.foo"].config.ebs_block_device[0].volume_size < 10 + } + +## The `providers` Collection + +The `providers` collection is a collection representing the configurations of +all provider instances across all modules in the configuration. + +This collection is indexed by an opaque key. This is currently +`module_address:provider.alias`, the same value as found in the +`provider_config_key` field. `module_address` and the colon delimiter are +omitted for the root module. + +The `provider_config_key` field is also found in the `resources` collection and +can be used to locate a provider that belongs to a configured resource. + +The fields in this collection are as follows: + +- `provider_config_key` - The opaque configuration key, used as the index key. +- `name` - The name of the provider, ie: `aws`. +- `full_name` - The fully-qualified name of the provider, e.g. `registry.terraform.io/hashicorp/aws`. +- `alias` - The alias of the provider, ie: `east`. Empty for a default provider. +- `module_address` - The address of the module this provider appears in. +- `config` - A [block expression + representation](#block-expression-representation) with provider configuration + values. +- `version_constraint` - The defined version constraint for this provider. + +## The `resources` Collection + +The `resources` collection is a collection representing all of the resources +found in all modules in the configuration. + +This collection is indexed by the resource address. + +The fields in this collection are as follows: + +- `address` - The resource address. This is the index of the collection. +- `module_address` - The module address that this resource was found in. +- `mode` - The resource mode, either `managed` (resources) or `data` (data + sources). +- `type` - The type of resource, ie: `null_resource` in `null_resource.foo`. +- `name` - The name of the resource, ie: `foo` in `null_resource.foo`. +- `provider_config_key` - The opaque configuration key that serves as the index + of the [`providers`](#the-providers-collection) collection. +- `provisioners` - The ordered list of provisioners for this resource. The + syntax of the provisioners matches those found in the + [`provisioners`](#the-provisioners-collection) collection, but is a list + indexed by the order the provisioners show up in the resource. +- `config` - The [block expression + representation](#block-expression-representation) of the configuration values + found in the resource. +- `count` - The [expression data](#expression-representations) for the `count` + value in the resource. +- `for_each` - The [expression data](#expression-representations) for the + `for_each` value in the resource. +- `depends_on` - The contents of the `depends_on` config directive, which + declares explicit dependencies for this resource. + +## The `provisioners` Collection + +The `provisioners` collection is a collection of all of the provisioners found +across all resources in the configuration. + +While normally bound to a resource in an ordered fashion, this collection allows +for the filtering of provisioners within a single expression. + +This collection is indexed with a key following the format +`resource_address:index`, with each field matching their respective field in the +particular element below: + +- `resource_address`: The address of the resource that the provisioner was found + in. This can be found in the [`resources`](#the-resources-collection) + collection. +- `type`: The provisioner type, ie: `local_exec`. +- `index`: The provisioner index as it shows up in the resource provisioner + order. +- `config`: The [block expression + representation](#block-expression-representation) of the configuration values + in the provisioner. + +## The `variables` Collection + +The `variables` collection is a collection of all variables across all modules +in the configuration. + +Note that this tracks variable definitions, not values. See the [`tfplan/v2` +`variables` collection](/terraform/enterprise/policy-enforcement/import-reference/tfplan-v2#the-variables-collection) for variable +values set within a plan. + +This collection is indexed by the key format `module_address:name`, with each +field matching their respective name below. `module_address` and the colon +delimiter are omitted for the root module. + +- `module_address` - The address of the module the variable was found in. +- `name` - The name of the variable. +- `default` - The defined default value of the variable. +- `description` - The description of the variable. + +## The `outputs` Collection + +The `outputs` collection is a collection of all outputs across all modules in +the configuration. + +Note that this tracks variable definitions, not values. See the [`tfstate/v2` +`outputs` collection](/terraform/enterprise/policy-enforcement/import-reference/tfstate-v2#the-outputs-collection) for the final +values of outputs set within a state. The [`tfplan/v2` `output_changes` +collection](/terraform/enterprise/policy-enforcement/import-reference/tfplan-v2#the-output_changes-collection) also contains a more +complex collection of planned output changes. + +This collection is indexed by the key format `module_address:name`, with each +field matching their respective name below. `module_address` and the colon +delimiter are omitted for the root module. + +- `module_address` - The address of the module the output was found in. +- `name` - The name of the output. +- `sensitive` - Indicates whether or not the output was marked as + [`sensitive`](/terraform/language/values/outputs#sensitive-suppressing-values-in-cli-output). +- `value` - An [expression representation](#expression-representations) for the output. +- `description` - The description of the output. +- `depends_on` - A list of resource names that the output depends on. These are + the hard-defined output dependencies as defined in the + [`depends_on`](/terraform/language/values/outputs#depends_on-explicit-output-dependencies) + field in an output declaration, not the dependencies that get derived from + natural evaluation of the output expression (these can be found in the + `references` field of the expression representation). + +## The `module_calls` Collection + +The `module_calls` collection is a collection of all module declarations at all +levels within the configuration. + +Note that this is the +[`module`](/terraform/language/modules#calling-a-child-module) stanza in +any particular configuration, and not the module itself. Hence, a declaration +for `module.foo` would actually be declared in the root module, which would be +represented by a blank field in `module_address`. + +This collection is indexed by the key format `module_address:name`, with each +field matching their respective name below. `module_address` and the colon +delimiter are omitted for the root module. + +- `module_address` - The address of the module the declaration was found in. +- `name` - The name of the module. +- `source` - The contents of the `source` field. +- `config` - A [block expression + representation](#block-expression-representation) for all parameter values + sent to the module. +- `count` - An [expression representation](#expression-representations) for the + `count` field. +- `depends_on`: An [expression representation](#expression-representations) for the + `depends_on` field. +- `for_each` - An [expression representation](#expression-representations) for + the `for_each` field. +- `version_constraint` - The string value found in the `version` field of the + module declaration. diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/policy-enforcement/import-reference/tfconfig.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/policy-enforcement/import-reference/tfconfig.mdx new file mode 100644 index 0000000000..dd83787db5 --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/policy-enforcement/import-reference/tfconfig.mdx @@ -0,0 +1,976 @@ +--- +page_title: tfconfig Sentinel import +description: Use tfconfig import to give Sentinel access to a Terraform configuration. +source: terraform-docs-common +--- + +# tfconfig Sentinel import + +~> **Warning:** The `tfconfig` import is now deprecated and will be permanently removed in August 2025. We recommend that you start using the updated [tfconfig/v2](/terraform/enterprise/policy-enforcement/import-reference/tfconfig-v2) import as soon as possible to avoid disruptions. The `tfconfig/v2` import offers improved functionality and is designed to better support your policy enforcement needs. + +The `tfconfig` import provides access to a Terraform configuration. + +The Terraform configuration is the set of `*.tf` files that are used to +describe the desired infrastructure state. Policies using the `tfconfig` +import can access all aspects of the configuration: providers, resources, +data sources, modules, and variables. + + + +@include 'tfc-package-callouts/policies.mdx' + + + +Some use cases for `tfconfig` include: + +- **Organizational naming conventions**: requiring that configuration elements + are named in a way that conforms to some organization-wide standard. +- **Required inputs and outputs**: organizations may require a particular set + of input variable names across all workspaces or may require a particular + set of outputs for asset management purposes. +- **Enforcing particular modules**: organizations may provide a number of + "building block" modules and require that each workspace be built only from + combinations of these modules. +- **Enforcing particular providers or resources**: an organization may wish to + require or prevent the use of providers and/or resources so that configuration + authors cannot use alternative approaches to work around policy + restrictions. + +Note with these use cases that this import is concerned with object _names_ +in the configuration. Since this is the configuration and not an invocation +of Terraform, you can't see values for variables, the state, or the diff for +a pending plan. If you want to write policy around expressions used +within configuration blocks, you likely want to use the +[`tfplan`](/terraform/enterprise/policy-enforcement/import-reference/tfplan-v2) import. + +## Namespace Overview + +The following is a tree view of the import namespace. For more detail on a +particular part of the namespace, see below. + +-> **Note:** The root-level alias keys shown here (`data`, `modules`, +`providers`, `resources`, and `variables`) are shortcuts to a [module +namespace](#namespace-module) scoped to the root module. For more details, see +the section on [root namespace aliases](#root-namespace-aliases). + + tfconfig + ├── module() (function) + │ └── (module namespace) + │ ├── data + │ │ └── TYPE.NAME + │ │ ├── config (map of keys) + │ │ ├── references (map of keys) (TF 0.12 and later) + │ │ └── provisioners + │ │ └── NUMBER + │ │ ├── config (map of keys) + │ │ ├── references (map of keys) (TF 0.12 and later) + │ │ └── type (string) + │ ├── modules + │ │ └── NAME + │ │ ├── config (map of keys) + │ │ ├── references (map of keys) (TF 0.12 and later) + │ │ ├── source (string) + │ │ └── version (string) + │ ├──outputs + │ │ └── NAME + │ │ ├── depends_on (list of strings) + │ │ ├── description (string) + │ │ ├── sensitive (boolean) + │ │ ├── references (list of strings) (TF 0.12 and later) + │ │ └── value (value) + │ ├── providers + │ │ └── TYPE + │ │ ├── alias + │ │ │ └── ALIAS + │ │ │ ├── config (map of keys) + │ │ | ├── references (map of keys) (TF 0.12 and later) + │ │ │ └── version (string) + │ │ ├── config (map of keys) + │ │ ├── references (map of keys) (TF 0.12 and later) + │ │ └── version (string) + │ ├── resources + │ │ └── TYPE.NAME + │ │ ├── config (map of keys) + │ │ ├── references (map of keys) (TF 0.12 and later) + │ │ └── provisioners + │ │ └── NUMBER + │ │ ├── config (map of keys) + │ │ ├── references (map of keys) (TF 0.12 and later) + │ │ └── type (string) + │ └── variables + │ └── NAME + │ ├── default (value) + │ └── description (string) + ├── module_paths ([][]string) + │ + ├── data (root module alias) + ├── modules (root module alias) + ├── outputs (root module alias) + ├── providers (root module alias) + ├── resources (root module alias) + └── variables (root module alias) + +### `references` with Terraform 0.12 + +**With Terraform 0.11 or earlier**, if a configuration value is defined as an +expression (and not a static value), the value will be accessible in its raw, +non-interpolated string (just as with a constant value). + +As an example, consider the following resource block: + +```hcl +resource "local_file" "accounts" { + content = "some text" + filename = "${var.subdomain}.${var.domain}/accounts.txt" +} +``` + +In this example, one might want to ensure `domain` and `subdomain` input +variables are used within `filename` in this configuration. With Terraform 0.11 or +earlier, the following policy would evaluate to `true`: + +```python +import "tfconfig" + +# filename_value is the raw, non-interpolated string +filename_value = tfconfig.resources.local_file.accounts.config.filename + +main = rule { + filename_value contains "${var.domain}" and + filename_value contains "${var.subdomain}" +} +``` + +**With Terraform 0.12 or later**, any non-static +values (such as interpolated strings) are not present within the +configuration value and `references` should be used instead: + +```python +import "tfconfig" + +# filename_references is a list of string values containing the references used in the expression +filename_references = tfconfig.resources.local_file.accounts.references.filename + +main = rule { + filename_references contains "var.domain" and + filename_references contains "var.subdomain" +} +``` + +The `references` value is present in any namespace where non-constant +configuration values can be expressed. This is essentially every namespace +which has a `config` value as well as the `outputs` namespace. + +-> **Note:** Remember, this import enforces policy around the literal Terraform +configuration and not the final values as a result of invoking Terraform. If +you want to write policy around the _result_ of expressions used within +configuration blocks (for example, if you wanted to ensure the final value of +`filename` above includes `accounts.txt`), you likely want to use the +[`tfplan`](/terraform/enterprise/policy-enforcement/import-reference/tfplan-v2) import. + +## Namespace: Root + +The root-level namespace consists of the values and functions documented below. + +In addition to this, the root-level `data`, `modules`, `providers`, `resources`, +and `variables` keys all alias to their corresponding namespaces within the +[module namespace](#namespace-module). + + + +### Function: `module()` + + module = func(ADDR) + +- **Return Type:** A [module namespace](#namespace-module). + +The `module()` function in the [root namespace](#namespace-root) returns the +[module namespace](#namespace-module) for a particular module address. + +The address must be a list and is the module address, split on the period (`.`), +excluding the root module. + +Hence, a module with an address of simply `foo` (or `root.foo`) would be +`["foo"]`, and a module within that (so address `foo.bar`) would be read as +`["foo", "bar"]`. + +[`null`][ref-null] is returned if a module address is invalid, or if the module +is not present in the configuration. + +[ref-null]: /sentinel/docs/language/spec#null + +As an example, given the following module block: + +```hcl +module "foo" { + # ... +} +``` + +If the module contained the following content: + +```hcl +resource "null_resource" "foo" { + triggers = { + foo = "bar" + } +} +``` + +The following policy would evaluate to `true`: + +```python +import "tfconfig" + +main = rule { subject.module(["foo"]).resources.null_resource.foo.config.triggers[0].foo is "bar" } +``` + + + +### Value: `module_paths` + +- **Value Type:** List of a list of strings. + +The `module_paths` value within the [root namespace](#namespace-root) is a list +of all of the modules within the Terraform configuration. + +Modules not present in the configuration will not be present here, even if they +are present in the diff or state. + +This data is represented as a list of a list of strings, with the inner list +being the module address, split on the period (`.`). + +The root module is included in this list, represented as an empty inner list. + +As an example, if the following module block was present within a Terraform +configuration: + +```hcl +module "foo" { + # ... +} +``` + +The value of `module_paths` would be: + + [ + [], + ["foo"], + ] + +And the following policy would evaluate to `true`: + +```python +import "tfconfig" + +main = rule { tfconfig.module_paths contains ["foo"] } +``` + +#### Iterating Through Modules + +Iterating through all modules to find particular resources can be useful. This +[example][iterate-over-modules] shows how to use `module_paths` with the +[`module()` function](#function-module-) to find all resources of a +particular type from all modules using the `tfplan` import. By changing `tfplan` +in this function to `tfconfig`, you could make a similar function find all +resources of a specific type in the Terraform configuration. + +[iterate-over-modules]: /terraform/enterprise/policy-enforcement/sentinel#sentinel-imports + +## Namespace: Module + +The **module namespace** can be loaded by calling [`module()`](#root-function-module) +for a particular module. + +It can be used to load the following child namespaces: + +- `data` - Loads the [resource namespace](#namespace-resources-data-sources), + filtered against data sources. +- `modules` - Loads the [module configuration + namespace](#namespace-module-configuration). +- `outputs` - Loads the [output namespace](#namespace-outputs). +- `providers` - Loads the [provider namespace](#namespace-providers). +- `resources` - Loads the [resource + namespace](#namespace-resources-data-sources), filtered against resources. +- `variables` - Loads the [variable namespace](#namespace-variables). + +### Root Namespace Aliases + +The root-level `data`, `modules`, `providers`, `resources`, and `variables` keys +all alias to their corresponding namespaces within the module namespace, loaded +for the root module. They are the equivalent of running `module([]).KEY`. + + + +## Namespace: Resources/Data Sources + +The **resource namespace** is a namespace _type_ that applies to both resources +(accessed by using the `resources` namespace key) and data sources (accessed +using the `data` namespace key). + +Accessing an individual resource or data source within each respective namespace +can be accomplished by specifying the type and name, in the syntax +`[resources|data].TYPE.NAME`. + +In addition, each of these namespace levels is a map, allowing you to filter +based on type and name. Some examples of multi-level access are below: + +- To fetch all `aws_instance` resources within the root module, you can specify + `tfconfig.resources.aws_instance`. This would give you a map of resource + namespaces indexed from the names of each resource (`foo`, `bar`, and so + on). +- To fetch all resources within the root module, irrespective of type, use + `tfconfig.resources`. This is indexed by type, as shown above with + `tfconfig.resources.aws_instance`, with names being the next level down. + +As an example, perhaps you wish to deny use of the `local_file` resource +in your configuration. Consider the following resource block: + +```hcl +resource "local_file" "foo" { + content = "foo!" + filename = "${path.module}/foo.bar" +} +``` + +The following policy would fail: + +```python +import "tfconfig" + +main = rule { tfconfig.resources not contains "local_file" } +``` + +Further explanation of the namespace will be in the context of resources. As +mentioned, when operating on data sources, use the same syntax, except with +`data` in place of `resources`. + + + +### Value: `config` + +- **Value Type:** A string-keyed map of values. + +The `config` value within the [resource +namespace](#namespace-resources-data-sources) is a map of key-value pairs that +directly map to Terraform config keys and values. + +-> **With Terraform 0.11 or earlier**, if the config value is defined as an +expression (and not a static value), the value will be in its raw, +non-interpolated string. **With Terraform 0.12 or later**, any non-static +values (such as interpolated strings) are not present and +[`references`](#resources-value-references) should be used instead. + +As an example, consider the following resource block: + +```hcl +resource "local_file" "accounts" { + content = "some text" + filename = "accounts.txt" +} +``` + +In this example, one might want to access `filename` to validate that the correct +file name is used. Given the above example, the following policy would evaluate to `true`: + +```python +import "tfconfig" + +main = rule { + tfconfig.resources.local_file.accounts.config.filename is "accounts.txt" +} +``` + + + +### Value: `references` + +- **Value Type:** A string-keyed map of list values containing strings. + +-> **Note:** This value is only present when using Terraform 0.12 or later. + +The `references` value within the [resource namespace](#namespace-resources-data-sources) +contains the identifiers within non-constant expressions found in [`config`](#resources-value-config). +See the [documentation on `references`](#references-with-terraform-0-12) for more information. + + + +### Value: `provisioners` + +- **Value Type:** List of [provisioner namespaces](#namespace-provisioners). + +The `provisioners` value within the [resource namespace](#namespace-resources) +represents the [provisioners][ref-tf-provisioners] within a specific resource. + +Provisioners are listed in the order they were provided in the configuration +file. + +While the `provisioners` value will be present within data sources, it will +always be an empty map (in Terraform 0.11) or `null` (in Terraform 0.12) since +data sources cannot actually have provisioners. + +The data within a provisioner can be inspected via the returned [provisioner +namespace](#namespace-provisioners). + +[ref-tf-provisioners]: /terraform/language/resources/provisioners/syntax + +## Namespace: Provisioners + +The **provisioner namespace** represents the configuration for a particular +[provisioner][ref-tf-provisioners] within a specific resource. + + + +### Value: `config` + +- **Value Type:** A string-keyed map of values. + +The `config` value within the [provisioner namespace](#namespace-provisioners) +represents the values of the keys within the provisioner. + +-> **With Terraform 0.11 or earlier**, if the config value is defined as an +expression (and not a static value), the value will be in its raw, +non-interpolated string. **With Terraform 0.12 or later**, any non-static +values (such as interpolated strings) are not present and +[`references`](#provisioners-value-references) should be used instead. + +As an example, given the following resource block: + +```hcl +resource "null_resource" "foo" { + # ... + + provisioner "local-exec" { + command = "echo ${self.private_ip} > file.txt" + } +} +``` + +The following policy would evaluate to `true`: + +```python +import "tfconfig" + +main = rule { + tfconfig.resources.null_resource.foo.provisioners[0].config.command is "echo ${self.private_ip} > file.txt" +} +``` + + + +### Value: `references` + +- **Value Type:** A string-keyed map of list values containing strings. + +-> **Note:** This value is only present when using Terraform 0.12 or later. + +The `references` value within the [provisioner namespace](#namespace-provisioners) +contains the identifiers within non-constant expressions found in [`config`](#provisioners-value-config). +See the [documentation on `references`](#references-with-terraform-0-12) for more information. + + + +### Value: `type` + +- **Value Type:** String. + +The `type` value within the [provisioner namespace](#namespace-provisioners) +represents the type of the specific provisioner. + +As an example, in the following resource block: + +```hcl +resource "null_resource" "foo" { + # ... + + provisioner "local-exec" { + command = "echo ${self.private_ip} > file.txt" + } +} +``` + +The following policy would evaluate to `true`: + +```python +import "tfconfig" + +main = rule { tfconfig.resources.null_resource.foo.provisioners[0].type is "local-exec" } +``` + +## Namespace: Module Configuration + +The **module configuration** namespace displays data on _module configuration_ +as it is given within a `module` block. This means that the namespace concerns +itself with the contents of the declaration block (example: the `source` +parameter and variable assignment keys), not the data within the module +(example: any contained resources or data sources). For the latter, the module +instance would need to be looked up with the [`module()` +function](#root-function-module). + + + +### Value: `source` + +- **Value Type:** String. + +The `source` value within the [module configuration +namespace](#namespace-module-configuration) represents the module source path as +supplied to the module configuration. + +As an example, given the module declaration block: + +```hcl +module "foo" { + source = "./foo" +} +``` + +The following policy would evaluate to `true`: + +```python +import "tfconfig" + +main = rule { tfconfig.modules.foo.source is "./foo" } +``` + + + +### Value: `version` + +- **Value Type:** String. + +The `version` value within the [module configuration +namespace](#namespace-module-configuration) represents the [version +constraint][module-version-constraint] for modules that support it, such as +modules within the [Terraform Module Registry][terraform-module-registry] or the +[HCP Terraform private module registry][tfe-private-registry]. + +[module-version-constraint]: /terraform/language/modules#module-versions + +[terraform-module-registry]: https://registry.terraform.io/ + +[tfe-private-registry]: /terraform/enterprise/registry + +As an example, given the module declaration block: + +```hcl +module "foo" { + source = "foo/bar" + version = "~> 1.2" +} +``` + +The following policy would evaluate to `true`: + +```python +import "tfconfig" + +main = rule { tfconfig.modules.foo.version is "~> 1.2" } +``` + + + +### Value: `config` + +- **Value Type:** A string-keyed map of values. + +-> **With Terraform 0.11 or earlier**, if the config value is defined as an +expression (and not a static value), the value will be in its raw, +non-interpolated string. **With Terraform 0.12 or later**, any non-static +values (such as interpolated strings) are not present and +[`references`](#modules-value-references) should be used instead. + +The `config` value within the [module configuration +namespace](#namespace-module-configuration) represents the values of the keys +within the module configuration. This is every key within a module declaration +block except [`source`](#modules-value-source) and [`version`](#modules-value-version), which +have their own values. + +As an example, given the module declaration block: + +```hcl +module "foo" { + source = "./foo" + + bar = "baz" +} +``` + +The following policy would evaluate to `true`: + +```python +import "tfconfig" + +main = rule { tfconfig.modules.foo.config.bar is "baz" } +``` + + + +### Value: `references` + +- **Value Type:** A string-keyed map of list values containing strings. + +-> **Note:** This value is only present when using Terraform 0.12 or later. + +The `references` value within the [module configuration namespace](#namespace-module-configuration) +contains the identifiers within non-constant expressions found in [`config`](#modules-value-config). +See the [documentation on `references`](#references-with-terraform-0-12) for more information. + +## Namespace: Outputs + +The **output namespace** represents _declared_ output data within a +configuration. As such, configuration for the [`value`](#outputs-value-value) attribute +will be in its raw form, and not yet interpolated. For fully interpolated output +values, see the [`tfstate` import][ref-tfe-sentinel-tfstate]. + +[ref-tfe-sentinel-tfstate]: /terraform/enterprise/policy-enforcement/import-reference/tfstate-v2 + +This namespace is indexed by output name. + + + +### Value: `depends_on` + +- **Value Type:** A list of strings. + +The `depends_on` value within the [output namespace](#namespace-outputs) +represents any _explicit_ dependencies for this output. For more information, +see the [depends_on output setting][ref-depends_on] within the general Terraform +documentation. + +[ref-depends_on]: /terraform/language/values/outputs#depends_on + +As an example, given the following output declaration block: + +```hcl +output "id" { + depends_on = ["null_resource.bar"] + value = "${null_resource.foo.id}" +} +``` + +The following policy would evaluate to `true`: + +```python +import "tfconfig" + +main = rule { tfconfig.outputs.id.depends_on[0] is "null_resource.bar" } +``` + + + +### Value: `description` + +- **Value Type:** String. + +The `description` value within the [output namespace](#namespace-outputs) +represents the defined description for this output. + +As an example, given the following output declaration block: + +```hcl +output "id" { + description = "foobar" + value = "${null_resource.foo.id}" +} +``` + +The following policy would evaluate to `true`: + +```python +import "tfconfig" + +main = rule { tfconfig.outputs.id.description is "foobar" } +``` + + + +### Value: `sensitive` + +- **Value Type:** Boolean. + +The `sensitive` value within the [output namespace](#namespace-outputs) +represents if this value has been marked as sensitive or not. + +As an example, given the following output declaration block: + +```hcl +output "id" { + sensitive = true + value = "${null_resource.foo.id}" +} +``` + +The following policy would evaluate to `true`: + +```python +import "tfconfig" + +main = rule { subject.outputs.id.sensitive } +``` + + + +### Value: `value` + +- **Value Type:** Any primitive type, list or map. + +The `value` value within the [output namespace](#namespace-outputs) represents +the defined value for the output as declared in the configuration. Primitives +will bear the implicit type of their declaration (string, int, float, or bool), +and maps and lists will be represented as such. + +-> **With Terraform 0.11 or earlier**, if the config value is defined as an +expression (and not a static value), the value will be in its raw, +non-interpolated string. **With Terraform 0.12 or later**, any non-static +values (such as interpolated strings) are not present and +[`references`](#outputs-value-references) should be used instead. + +As an example, given the following output declaration block: + +```hcl +output "id" { + value = "${null_resource.foo.id}" +} +``` + +With Terraform 0.11 or earlier the following policy would evaluate to `true`: + +```python +import "tfconfig" + +main = rule { tfconfig.outputs.id.value is "${null_resource.foo.id}" } +``` + + + +### Value: `references` + +- **Value Type:**. List of strings. + +-> **Note:** This value is only present when using Terraform 0.12 or later. + +The `references` value within the [output namespace](#namespace-outputs) +contains the names of any referenced identifiers when [`value`](#outputs-value-value) +is a non-constant expression. + +As an example, given the following output declaration block: + +```hcl +output "id" { + value = "${null_resource.foo.id}" +} +``` + +The following policy would evaluate to `true`: + +```python +import "tfconfig" + +main = rule { tfconfig.outputs.id.references contains "null_resource.foo.id" } +``` + +## Namespace: Providers + +The **provider namespace** represents data on the declared providers within a +namespace. + +This namespace is indexed by provider type and _only_ contains data about +providers when actually declared. If you are using a completely implicit +provider configuration, this namespace will be empty. + +This namespace is populated based on the following criteria: + +- The top-level namespace [`config`](#providers-value-config) and + [`version`](#providers-value-version) values are populated with the configuration and + version information from the default provider (the provider declaration that + lacks an alias). +- Any aliased providers are added as namespaces within the + [`alias`](#providers-value-alias) value. +- If a module lacks a default provider configuration, the top-level `config` and + `version` values will be empty. + + + +### Value: `alias` + +- **Value Type:** A map of [provider namespaces](#namespace-providers), indexed + by alias. + +The `alias` value within the [provider namespace](#namespace-providers) +represents all declared [non-default provider +instances][ref-tf-provider-instances] for a specific provider type, indexed by +their specific alias. + +[ref-tf-provider-instances]: /terraform/language/providers/configuration#alias-multiple-provider-configurations + +The return type is a provider namespace with the data for the instance in +question loaded. The `alias` key will not be available within this namespace. + +As an example, given the following provider declaration block: + +```hcl +provider "aws" { + alias = "east" + region = "us-east-1" +} +``` + +The following policy would evaluate to `true`: + +```python +import "tfconfig" + +main = rule { tfconfig.providers.aws.alias.east.config.region is "us-east-1" } +``` + + + +### Value: `config` + +- **Value Type:** A string-keyed map of values. + +-> **With Terraform 0.11 or earlier**, if the config value is defined as an +expression (and not a static value), the value will be in its raw, +non-interpolated string. **With Terraform 0.12 or later**, any non-static +values (such as interpolated strings) are not present and +[`references`](#providers-value-references) should be used instead. + +The `config` value within the [provider namespace](#namespace-providers) +represents the values of the keys within the provider's configuration, with the +exception of the provider version, which is represented by the +[`version`](#providers-value-version) value. [`alias`](#providers-value-alias) is also not included +when the provider is aliased. + +As an example, given the following provider declaration block: + +```hcl +provider "aws" { + region = "us-east-1" +} +``` + +The following policy would evaluate to `true`: + +```python +import "tfconfig" + +main = rule { tfconfig.providers.aws.config.region is "us-east-1" } +``` + + + +### Value: `references` + +- **Value Type:** A string-keyed map of list values containing strings. + +-> **Note:** This value is only present when using Terraform 0.12 or later. + +The `references` value within the [provider namespace](#namespace-providers) +contains the identifiers within non-constant expressions found in [`config`](#providers-value-config). +See the [documentation on `references`](#references-with-terraform-0-12) for more information. + + + +### Value: `version` + +- **Value Type:** String. + +The `version` value within the [provider namespace](#namespace-providers) +represents the explicit expected version of the supplied provider. This includes +the pessimistic operator. + +As an example, given the following provider declaration block: + +```hcl +provider "aws" { + version = "~> 1.34" +} +``` + +The following policy would evaluate to `true`: + +```python +import "tfconfig" + +main = rule { tfconfig.providers.aws.version is "~> 1.34" } +``` + +## Namespace: Variables + +The **variable namespace** represents _declared_ variable data within a +configuration. As such, static data can be extracted, such as defaults, but not +dynamic data, such as the current value of a variable within a plan (although +this can be extracted within the [`tfplan` import][ref-tfe-sentinel-tfplan]). + +[ref-tfe-sentinel-tfplan]: /terraform/enterprise/policy-enforcement/import-reference/tfplan-v2 + +This namespace is indexed by variable name. + + + +### Value: `default` + +- **Value Type:** Any primitive type, list, map, or `null`. + +The `default` value within the [variable namespace](#namespace-variables) +represents the default for the variable as declared in the configuration. + +The actual value will be as configured. Primitives will bear the implicit type +of their declaration (string, int, float, or bool), and maps and lists will be +represented as such. + +If no default is present, the value will be [`null`][ref-sentinel-null] (not to +be confused with [`undefined`][ref-sentinel-undefined]). + +[ref-sentinel-null]: /sentinel/docs/language/spec#null + +[ref-sentinel-undefined]: /sentinel/docs/language/undefined + +As an example, given the following variable blocks: + +```hcl +variable "foo" { + default = "bar" +} + +variable "number" { + default = 42 +} +``` + +The following policy would evaluate to `true`: + +```python +import "tfconfig" + +default_foo = rule { tfconfig.variables.foo.default is "bar" } +default_number = rule { tfconfig.variables.number.default is 42 } + +main = rule { default_foo and default_number } +``` + + + +### Value: `description` + +- **Value Type:** String. + +The `description` value within the [variable namespace](#namespace-variables) +represents the description of the variable, as provided in configuration. + +As an example, given the following variable block: + +```hcl +variable "foo" { + description = "foobar" +} +``` + +The following policy would evaluate to `true`: + +```python +import "tfconfig" + +main = rule { tfconfig.variables.foo.description is "foobar" } +``` diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/policy-enforcement/import-reference/tfplan-v2.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/policy-enforcement/import-reference/tfplan-v2.mdx new file mode 100644 index 0000000000..68d18aa3dd --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/policy-enforcement/import-reference/tfplan-v2.mdx @@ -0,0 +1,388 @@ +--- +page_title: tfplan/v2 Sentinel import +description: Use tfplan/v2 import to give Sentinel access to a Terraform plan. +source: terraform-docs-common +--- + +-> **Note:** This is documentation for the next version of the `tfplan` Sentinel +import, designed specifically for Terraform 0.12. This import requires +Terraform 0.12 or higher, and must currently be loaded by path, using an alias, +example: `import "tfplan/v2" as tfplan`. + +# tfplan/v2 Sentinel import + +The `tfplan/v2` import provides access to a Terraform plan. + +A Terraform plan is the file created as a result of `terraform plan` and is the +input to `terraform apply`. The plan represents the changes that Terraform needs +to make to infrastructure to reach the desired state represented by the +configuration. + + + +@include 'tfc-package-callouts/policies.mdx' + + + +In addition to the diff data available in the plan, there is a "planned state" +that is available through this import, via the +[`planned_values`](#the-planned_values-collection) collection. This collection +presents the Terraform state as how it might look after the plan data is +applied, but is not guaranteed to be the final state. + +The data in the `tfplan/v2` import is sourced from the JSON configuration file +that is generated by the [`terraform show -json`](/terraform/cli/commands/show#json-output) command. For more information on +the file format, see the [JSON Output Format](/terraform/internals/json-format) +page. + +The entirety of the JSON output file is exposed as a Sentinel map via the +[`raw`](#the-raw-collection) collection. This allows direct, low-level access to +the JSON data, but should only be used in complex situations where the +higher-level collections do not serve the purpose. + +## Import Overview + +The `tfplan/v2` import is structured as a series of _collections_, keyed as a +specific format depending on the collection. + + tfplan/v2 + ├── terraform_version (string) + ├── variables + │ └── (indexed by name) + │ ├── name (string) + │ └── value (value) + ├── planned_values + │ ├── outputs (tfstate/v2 outputs representation) + │ └── resources (tfstate/v2 resources representation) + ├── resource_changes + │ └── (indexed by address[:deposed]) + │ ├── address (string) + │ ├── module_address (string) + │ ├── mode (string) + │ ├── type (string) + │ ├── name (string) + │ ├── index (float (number) or string) + │ ├── provider_name (string) + │ ├── deposed (string) + │ └── change (change representation) + ├── resource_drift + │ └── (indexed by address[:deposed]) + │ ├── address (string) + │ ├── module_address (string) + │ ├── mode (string) + │ ├── type (string) + │ ├── name (string) + │ ├── index (float (number) or string) + │ ├── provider_name (string) + │ ├── deposed (string) + │ └── change (change representation) + ├── output_changes + │ └── (indexed by name) + │ ├── name (string) + │ └── change (change representation) + └── raw (map) + +The collections are: + +- [`variables`](#the-variables-collection) - The values of variables that have + been set in the plan itself. This collection only contains variables set in + the root module. +- [`planned_values`](#the-planned_values-collection) - The state representation + of _planned values_, or an estimation of what the state will look like after + the plan is applied. +- [`resource_changes`](#the-resource_changes-and-resource_drift-collections) - The set of change + operations for resources and data sources within this plan. +- [`resource_drift`](#the-resource_changes-and-resource_drift-collections) - A description of the + changes Terraform detected when it compared the most recent state to the prior saved state. +- [`output_changes`](#the-output_changes-collection) - The changes to outputs + within this plan. This collection only contains outputs set in the root + module. +- [`raw`](#the-raw-collection) - Access to the raw plan data as stored by + HCP Terraform. + +These collections are specifically designed to be used with the +[`filter`](/sentinel/docs/language/collection-operations#filter-expression) +quantifier expression in Sentinel, so that one can collect a list of resources +to perform policy checks on without having to write complex discovery code. As +an example, the following code will return all `aws_instance` resource changes, +across all modules in the plan: + + all_aws_instances = filter tfplan.resource_changes as _, rc { + rc.mode is "managed" and + rc.type is "aws_instance" + } + +You can add specific attributes to the filter to narrow the search, such as the +module address, or the operation being performed. The following code would +return resources in a module named `foo` only, and further narrow the search +down to only resources that were being created: + + all_aws_instances = filter tfplan.resource_changes as _, rc { + rc.module_address is "module.foo" and + rc.mode is "managed" and + rc.type is "aws_instance" and + rc.change.actions is ["create"] + } + +### Change Representation + +Certain collections in this import contain a _change representation_, an object +with details about changes to a particular entity, such as a resource (within +the [`resource_changes`](#the-resource_changes-collection) collection), or +output (within the [`output_changes`](#the-output_changes-collection) +collection). + + (change representation) + ├── actions (list) + ├── before (value, or map) + ├── after (value, or map) + └── after_unknown (boolean, or map of booleans) + +This change representation contains the following fields: + +- `actions` - A list of actions being carried out for this change. The order is + important, for example a regular replace operation is denoted by `["delete", + "create"]`, but a + [`create_before_destroy`](/terraform/language/meta-arguments/lifecycle#create_before_destroy) + resource will have an operation order of `["create", "delete"]`. +- `before` - The representation of the resource data object value before the + action. For create-only actions, this is unset. For no-op actions, this value + will be identical with `after`. +- `after` - The representation of the resource data object value after the + action. For delete-only actions, this is unset. For no-op actions, this value + will be identical with `before`. Note that unknown values will not show up in + this field. +- `after_unknown` - A deep object of booleans that denotes any values that are + unknown in a resource. These values were previously referred to as "computed" + values. If the value cannot be found in this map, then its value should be + available within `after`, so long as the operation supports it. + +#### Actions + +As mentioned above, actions show up within the `actions` field of a change +representation and indicate the type of actions being performed as part of the +change, and the order that they are being performed in. + +The current list of actions are as follows: + +- `create` - The action will create the associated entity. Depending on the + order this appears in, the entity may be created alongside a copy of the + entity before replacing it. +- `read` - The action will read the associated entity. In practice, seeing this + change type should be rare, as reads generally happen before a plan is + executed (usually during a refresh). +- `update` - The action will update the associated entity in a way that alters its state + in some way. +- `delete` - The action will remove the associated entity, deleting any + applicable state and associated real resources or infrastructure. +- `no-op` - No action will be performed on the associated entity. + +The `actions` field is a list, as some real-world actions are actually a +composite of more than one primitive action. At this point in time, this +is generally only applicable to resource replacement, in which the following +action orders apply: + +- **Normal replacement:** `["delete", "create"]` - Applies to default lifecycle + configurations. +- **Create-before-destroy:** `["create", "delete"]` - Applies when + [`create_before_destroy`](/terraform/language/meta-arguments/lifecycle#create_before_destroy) + is used in a lifecycle configuration. + +Note that, in most situations, the plan will list all "changes", including no-op +changes. This makes filtering on change type crucial to the accurate selection +of data if you are concerned with the state change of a particular resource. + +To filter on a change type, use exact list comparison. For example, the +following example from the [Import Overview](#import-overview) filters on +exactly the resources being created _only_: + + all_aws_instances = filter tfplan.resource_changes as _, rc { + rc.module_address is "module.foo" and + rc.mode is "managed" and + rc.type is "aws_instance" and + rc.change.actions is ["create"] + } + +#### `before`, `after`, and `after_unknown` + +The exact attribute changes for a particular operation are outlined in the +`before` and `after` attributes. Depending on the entity being operated on, this +will either be a map (as with +[`resource_changes`](#the-resource_changes-collection)) or a singular value (as +with [`output_changes`](#the-output_changes-collection)). + +What you can expect in these fields varies depending on the operation: + +- For fresh create operations, `before` will generally be `null`, and `after` + will contain the data you can expect to see after the change. +- For full delete operations, this will be reversed - `before` will contain + data, and `after` will be `null`. +- Update or replace operations will have data in both fields relevant to their + states before and after the operation. +- No-op operations should have identical data in `before` and `after`. + +For resources, if a field cannot be found in `after`, it generally means one of +two things: + +- The attribute does not exist in the resource schema. Generally, known + attributes that do not have a value will show up as `null` or otherwise empty + in `after`. +- The attribute is _unknown_, that is, it was unable to be determined at plan + time and will only be available after apply-time values have been able to be + calculated. + +In the latter case, there should be a value for the particular attribute in +`after_unknown`, which can be checked to assert that the value is indeed +unknown, versus invalid: + + import "tfplan/v2" as tfplan + + no_unknown_amis = rule { + all filter tfplan.resource_changes as _, rc { + rc.module_address is "module.foo" and + rc.mode is "managed" and + rc.type is "aws_instance" and + rc.change.actions is ["create"] + } as _, rc { + rc.change.after_unknown.ami else false is false + } + } + +For output changes, `after_unknown` will simply be `true` if the value won't be +known until the plan is applied. + +## The `terraform_version` Value + +The top-level `terraform_version` value in this import gives the Terraform +version that made the plan. This can be used to do version validation. + + import "tfplan/v2" as tfplan + import "strings" + + v = strings.split(tfplan.terraform_version, ".") + version_major = int(v[1]) + version_minor = int(v[2]) + + main = rule { + version_major is 12 and version_minor >= 19 + } + +-> **NOTE:** The above example will give errors when working with pre-release +versions (example: `0.12.0beta1`). Future versions of this import will include +helpers to assist with processing versions that will account for these kinds of +exceptions. + +## The `variables` Collection + +The `variables` collection is a collection of the variables set in the root +module when creating the plan. + +This collection is indexed on the name of the variable. + +The valid values are: + +- `name` - The name of the variable, also used as the collection key. +- `value` - The value of the variable assigned during the plan. + +## The `planned_values` Collection + +The `planned_values` collection is a special collection in that it contains two +fields that alias to state collections with the _planned_ state set. This is the +best prediction of what the state will look like after the plan is executed. + +The two fields are: + +- `outputs` - The prediction of what output values will look like after the + state is applied. For more details on the structure of this collection, see + the [`outputs`](/terraform/enterprise/policy-enforcement/import-reference/tfstate-v2#the-outputs-collection) collection in the + [`tfstate/v2`](/terraform/enterprise/policy-enforcement/import-reference/tfstate-v2) documentation. +- `resources` - The prediction of what resource values will look like after the + state is applied. For more details on the structure of this collection, see + the [`resources`](/terraform/enterprise/policy-enforcement/import-reference/tfstate-v2#the-resources-collection) collection in the + [`tfstate/v2`](/terraform/enterprise/policy-enforcement/import-reference/tfstate-v2) documentation. + +-> **NOTE:** Unknown values are omitted from the `planned_values` state +representations, regardless of whether or not they existed before. Use +[`resource_changes`](#the-resource_changes-collection) if awareness of unknown +data is important. + +## The `resource_changes` and `resource_drift` Collections + +The `resource_changes` and `resource_drift` collections are a set of change operations for resources +and data sources within this plan. + +The `resource_drift` collection provides a description of the changes Terraform detected +when it compared the most recent state to the prior saved state. + +The `resource_changes` collection includes all resources that have been found in the configuration and state, +regardless of whether or not they are changing. + +~> When [resource targeting](/terraform/cli/commands/plan#resource-targeting) is in effect, the `resource_changes` collection will only include the resources specified as targets for the run. This may lead to unexpected outcomes if a policy expects a resource to be present in the plan. To prohibit targeted runs altogether, ensure [`tfrun.target_addrs`](/terraform/enterprise/policy-enforcement/import-reference/tfrun#value-target_addrs) is undefined or empty. + +This collection is indexed on the complete resource address as the key. If +`deposed` is non-empty, it is appended to the end, and may look something like +`aws_instance.foo:deposed-abc123`. + +An element contains the following fields: + +- `address` - The absolute resource address - also the key for the collection's + index, if `deposed` is empty. + +- `module_address` - The module portion of the absolute resource address. + +- `mode` - The resource mode, either `managed` (resources) or `data` (data + sources). + +- `type` - The resource type, example: `aws_instance` for `aws_instance.foo`. + +- `name` - The resource name, example: `foo` for `aws_instance.foo`. + +- `index` - The resource index. Can be either a number or a string. + +- `provider_name` - The name of the provider this resource belongs to. This + allows the provider to be interpreted unambiguously in the unusual situation + where a provider offers a resource type whose name does not start with its own + name, such as the `googlebeta` provider offering `google_compute_instance`. + + -> **Note:** Starting with Terraform 0.13, the `provider_name` field contains the + _full_ source address to the provider in the Terraform Registry. Example: + `registry.terraform.io/hashicorp/null` for the null provider. + +- `deposed` - An identifier used during replacement operations, and can be used + to identify the exact resource being replaced in state. + +- `change` - The data describing the change that will be made to this resource. + For more details, see [Change Representation](#change-representation). + +## The `output_changes` Collection + +The `output_changes` collection is a collection of the change operations for +outputs within this plan. + +Only outputs for the root module are included. + +This collection is indexed by the name of the output. The fields in a collection +value are below: + +- `name` - The name of the output, also the index key. +- `change` - The data describing the change that will be made to this output. + For more details, see [Change Representation](#change-representation). + +## The `raw` Collection + +The `raw` collection exposes the raw, unprocessed plan data, direct from the +data stored by HCP Terraform. + +This is the same data that is produced by [`terraform show -json`](/terraform/cli/commands/show#json-output) on the plan file for the run this +policy check is attached to. + +Use of this data is only recommended in expert situations where the data the +collections present may not exactly serve the needs of the policy. For more +information on the file format, see the [JSON Output +Format](/terraform/internals/json-format) page. + +-> **NOTE:** Although designed to be relatively stable, the actual makeup for +the JSON output format is a Terraform CLI concern and as such not managed by +HCP Terraform. Use at your own risk, follow the [Terraform CLI +project](https://github.com/hashicorp/terraform), and watch the file format +documentation for any changes. diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/policy-enforcement/import-reference/tfplan.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/policy-enforcement/import-reference/tfplan.mdx new file mode 100644 index 0000000000..12832dd583 --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/policy-enforcement/import-reference/tfplan.mdx @@ -0,0 +1,604 @@ +--- +page_title: tfplan Sentinel import reference +description: Use the tfplan import to give Sentinel access to a Terraform plan. +source: terraform-docs-common +--- + +# tfplan Sentinel import reference + +~> **Warning:** The `tfplan` import is now deprecated and will be permanently removed in August 2025. We recommend that you start using the updated [tfplan/v2](/terraform/enterprise/policy-enforcement/import-reference/tfplan-v2) import as soon as possible to avoid disruptions. The `tfplan/v2` import offers improved functionality and is designed to better support your policy enforcement needs. + +The `tfplan` import provides access to a Terraform plan. A Terraform plan is the +file created as a result of `terraform plan` and is the input to `terraform +apply`. The plan represents the changes that Terraform needs to make to +infrastructure to reach the desired state represented by the configuration. + +In addition to the diff data available in the plan, there is an +[`applied`](#value-applied) state available that merges the plan with the state +to create the planned state after apply. + +Finally, this import also allows you to access the configuration files and the +Terraform state at the time the plan was run. See the section on [accessing a +plan's state and configuration +data](#accessing-a-plan-39-s-state-and-configuration-data) for more information. + + + +@include 'tfc-package-callouts/policies.mdx' + + + +## Namespace Overview + +The following is a tree view of the import namespace. For more detail on a +particular part of the namespace, see below. + +-> Note that the root-level alias keys shown here (`data`, `path`, and +`resources`) are shortcuts to a [module namespace](#namespace-module) scoped to +the root module. For more details, see the section on [root namespace +aliases](#root-namespace-aliases). + + tfplan + ├── module() (function) + │ └── (module namespace) + │ ├── path ([]string) + │ ├── data + │ │ └── TYPE.NAME[NUMBER] + │ │ ├── applied (map of keys) + │ │ └── diff + │ │ └── KEY + │ │ ├── computed (bool) + │ │ ├── new (string) + │ │ └── old (string) + │ └── resources + │ └── TYPE.NAME[NUMBER] + │ ├── applied (map of keys) + │ ├── destroy (bool) + │ ├── requires_new (bool) + │ └── diff + │ └── KEY + │ ├── computed (bool) + │ ├── new (string) + │ └── old (string) + ├── module_paths ([][]string) + ├── terraform_version (string) + ├── variables (map of keys) + │ + ├── data (root module alias) + ├── path (root module alias) + ├── resources (root module alias) + │ + ├── config (tfconfig namespace alias) + └── state (tfstate import alias) + +## Namespace: Root + +The root-level namespace consists of the values and functions documented below. + +In addition to this, the root-level `data`, `path`, and `resources` keys alias +to their corresponding namespaces or values within the [module +namespace](#namespace-module). + +### Accessing a Plan's State and Configuration Data + +The `config` and `state` keys alias to the [`tfconfig`][import-tfconfig] and +[`tfstate`][import-tfstate] namespaces, respectively, with the data sourced from +the Terraform _plan_ (as opposed to actual configuration and state). + +[import-tfconfig]: /terraform/enterprise/policy-enforcement/import-reference/tfconfig-v2 + +[import-tfstate]: /terraform/enterprise/policy-enforcement/import-reference/tfstate-v2 + +-> Note that these aliases are not represented as maps. While they will appear +empty when viewed as maps, the specific import namespace keys will still be +accessible. + +-> Note that while current versions of HCP Terraform source configuration and +state data from the plan for the Terraform run in question, future versions may +source data accessed through the `tfconfig` and `tfstate` imports (as opposed to +`tfplan.config` and `tfplan.state`) from actual config bundles, or state as +stored by HCP Terraform. When this happens, the distinction here will be useful - +the data in the aliased namespaces will be the config and state data as the +_plan_ sees it, versus the actual "physical" data. + +### Function: `module()` + + module = func(ADDR) + +- **Return Type:** A [module namespace](#namespace-module). + +The `module()` function in the [root namespace](#namespace-root) returns the +[module namespace](#namespace-module) for a particular module address. + +The address must be a list and is the module address, split on the period (`.`), +excluding the root module. + +Hence, a module with an address of simply `foo` (or `root.foo`) would be +`["foo"]`, and a module within that (so address `foo.bar`) would be read as +`["foo", "bar"]`. + +[`null`][ref-null] is returned if a module address is invalid, or if the module +is not present in the diff. + +[ref-null]: /sentinel/docs/language/spec#null + +As an example, given the following module block: + +```hcl +module "foo" { + # ... +} +``` + +If the module contained the following content: + +```hcl +resource "null_resource" "foo" { + triggers = { + foo = "bar" + } +} +``` + +The following policy would evaluate to `true`: + +```python +import "tfplan" + +main = rule { tfplan.module(["foo"]).resources.null_resource.foo[0].applied.triggers.foo is "bar" } +``` + +### Value: `module_paths` + +- **Value Type:** List of a list of strings. + +The `module_paths` value within the [root namespace](#namespace-root) is a list +of all of the modules within the Terraform diff for the current plan. + +Modules not present in the diff will not be present here, even if they are +present in the configuration or state. + +This data is represented as a list of a list of strings, with the inner list +being the module address, split on the period (`.`). + +The root module is included in this list, represented as an empty inner list, as +long as there are changes. + +As an example, if the following module block was present within a Terraform +configuration: + +```hcl +module "foo" { + # ... +} +``` + +The value of `module_paths` would be: + + [ + [], + ["foo"], + ] + +And the following policy would evaluate to `true`: + +```python +import "tfplan" + +main = rule { tfplan.module_paths contains ["foo"] } +``` + +-> Note the above example only applies if the module is present in the diff. + +#### Iterating Through Modules + +Iterating through all modules to find particular resources can be useful. This +[example][iterate-over-modules] shows how to use `module_paths` with the +[`module()` function](#function-module-) to find all resources of a +particular type from all modules that have pending changes using the `tfplan` +import. + +[iterate-over-modules]: /terraform/enterprise/policy-enforcement/sentinel#sentinel-imports + +### Value: `terraform_version` + +- **Value Type:** String. + +The `terraform_version` value within the [root namespace](#namespace-root) +represents the version of Terraform used to create the plan. This can be used to +enforce a specific version of Terraform in a policy check. + +As an example, the following policy would evaluate to `true`, as long as the +plan was made with a version of Terraform in the 0.11.x series, excluding any +pre-release versions (example: `-beta1` or `-rc1`): + +```python +import "tfplan" + +main = rule { tfplan.terraform_version matches "^0\\.11\\.\\d+$" } +``` + +### Value: `variables` + +- **Value Type:** A string-keyed map of values. + +The `variables` value within the [root namespace](#namespace-root) represents +all of the variables that were set when creating the plan. This will only +contain variables set for the root module. + +Note that unlike the [`default`][import-tfconfig-variables-default] value in the +[`tfconfig` variables namespace][import-tfconfig-variables], primitive values +here are stringified, and type conversion will need to be performed to perform +comparison for int, float, or boolean values. This only applies to variables +that are primitives themselves and not primitives within maps and lists, which +will be their original types. + +[import-tfconfig-variables-default]: /terraform/enterprise/policy-enforcement/import-reference/tfconfig-v2#value-default + +[import-tfconfig-variables]: /terraform/enterprise/policy-enforcement/import-reference/tfconfig-v2#namespace-variables + +If a default was accepted for the particular variable, the default value will be +populated here. + +As an example, given the following variable blocks: + +```hcl +variable "foo" { + default = "bar" +} + +variable "number" { + default = 42 +} + +variable "map" { + default = { + foo = "bar" + number = 42 + } +} +``` + +The following policy would evaluate to `true`, if no values were entered to +change these variables: + +```python +import "tfplan" + +default_foo = rule { tfplan.variables.foo is "bar" } +default_number = rule { tfplan.variables.number is "42" } +default_map_string = rule { tfplan.variables.map["foo"] is "bar" } +default_map_int = rule { tfplan.variables.map["number"] is 42 } + +main = rule { default_foo and default_number and default_map_string and default_map_int } +``` + +## Namespace: Module + +The **module namespace** can be loaded by calling +[`module()`](#function-module-) for a particular module. + +It can be used to load the following child namespaces, in addition to the values +documented below: + +- `data` - Loads the [resource namespace](#namespace-resources-data-sources), + filtered against data sources. +- `resources` - Loads the [resource + namespace](#namespace-resources-data-sources), filtered against resources. + +### Root Namespace Aliases + +The root-level `data` and `resources` keys both alias to their corresponding +namespaces within the module namespace, loaded for the root module. They are the +equivalent of running `module([]).KEY`. + +### Value: `path` + +- **Value Type:** List of strings. + +The `path` value within the [module namespace](#namespace-module) contains the +path of the module that the namespace represents. This is represented as a list +of strings. + +As an example, if the following module block was present within a Terraform +configuration: + +```hcl +module "foo" { + # ... +} +``` + +The following policy would evaluate to `true` _only_ if the diff had changes for +that module: + +```python +import "tfplan" + +main = rule { tfplan.module(["foo"]).path contains "foo" } +``` + +## Namespace: Resources/Data Sources + +The **resource namespace** is a namespace _type_ that applies to both resources +(accessed by using the `resources` namespace key) and data sources (accessed +using the `data` namespace key). + +Accessing an individual resource or data source within each respective namespace +can be accomplished by specifying the type, name, and resource number (as if the +resource or data source had a `count` value in it) in the syntax +`[resources|data].TYPE.NAME[NUMBER]`. Note that NUMBER is always needed, even if +you did not use `count` in the resource. + +In addition, each of these namespace levels is a map, allowing you to filter +based on type and name. + +-> The (somewhat strange) notation here of `TYPE.NAME[NUMBER]` may imply that +the inner resource index map is actually a list, but it's not - using the square +bracket notation over the dotted notation (`TYPE.NAME.NUMBER`) is required here +as an identifier cannot start with a number. + +Some examples of multi-level access are below: + +- To fetch all `aws_instance.foo` resource instances within the root module, you + can specify `tfplan.resources.aws_instance.foo`. This would then be indexed by + resource count index (`0`, `1`, `2`, and so on). Note that as mentioned above, + these elements must be accessed using square-bracket map notation (so `[0]`, + `[1]`, `[2]`, and so on) instead of dotted notation. +- To fetch all `aws_instance` resources within the root module, you can specify + `tfplan.resources.aws_instance`. This would be indexed from the names of + each resource (`foo`, `bar`, and so on), with each of those maps containing + instances indexed by resource count index as per above. +- To fetch all resources within the root module, irrespective of type, use + `tfplan.resources`. This is indexed by type, as shown above with + `tfplan.resources.aws_instance`, with names being the next level down, and so + on. + +~> When [resource targeting](/terraform/cli/commands/plan#resource-targeting) is in effect, `tfplan.resources` will only include the resources specified as targets for the run. This may lead to unexpected outcomes if a policy expects a resource to be present in the plan. To prohibit targeted runs altogether, ensure [`tfrun.target_addrs`](/terraform/enterprise/policy-enforcement/import-reference/tfrun#value-target_addrs) is undefined or empty. + +Further explanation of the namespace will be in the context of resources. As +mentioned, when operating on data sources, use the same syntax, except with +`data` in place of `resources`. + +### Value: `applied` + +- **Value Type:** A string-keyed map of values. + +The `applied` value within the [resource +namespace](#namespace-resources-data-sources) contains a "predicted" +representation of the resource's state post-apply. It's created by merging the +pending resource's diff on top of the existing data from the resource's state +(if any). The map is a complex representation of these values with data going +as far down as needed to represent any state values such as maps, lists, and +sets. + +As an example, given the following resource: + +```hcl +resource "null_resource" "foo" { + triggers = { + foo = "bar" + } +} +``` + +The following policy would evaluate to `true` if the resource was in the diff: + +```python +import "tfplan" + +main = rule { tfplan.resources.null_resource.foo[0].applied.triggers.foo is "bar" } +``` + +-> Note that some values will not be available in the `applied` state because +they cannot be known until the plan is actually applied. In Terraform 0.11 or +earlier, these values are represented by a placeholder (the UUID value +`74D93920-ED26-11E3-AC10-0800200C9A66`) and in Terraform 0.12 or later they +are `undefined`. **In either case**, you should instead use the +[`computed`](#value-computed) key within the [diff +namespace](#namespace-resource-diff) to determine that a computed value will +exist. + +-> If a resource is being destroyed, its `applied` value is omitted from the +namespace and trying to fetch it will return undefined. + +### Value: `diff` + +- **Value Type:** A map of [diff namespaces](#namespace-resource-diff). + +The `diff` value within the [resource +namespace](#namespace-resources-data-sources) contains the diff for a particular +resource. Each key within the map links to a [diff +namespace](#namespace-resource-diff) for that particular key. + +Note that unlike the [`applied`](#value-applied) value, this map is not complex; +the map is only 1 level deep with each key possibly representing a diff for a +particular complex value within the resource. + +See the below section for more details on the diff namespace, in addition to +usage examples. + +### Value: `destroy` + +- **Value Type:** Boolean. + +The `destroy` value within the [resource +namespace](#namespace-resources-data-sources) is `true` if a resource is being +destroyed for _any_ reason, including cases where it's being deleted as part of +a resource re-creation, in which case [`requires_new`](#value-requires_new) will +also be set. + +As an example, given the following resource: + +```hcl +resource "null_resource" "foo" { + triggers = { + foo = "bar" + } +} +``` + +The following policy would evaluate to `true` when `null_resource.foo` is being +destroyed: + +```python +import "tfplan" + +main = rule { tfplan.resources.null_resource.foo[0].destroy } +``` + +### Value: `requires_new` + +- **Value Type:** Boolean. + +The `requires_new` value within the [resource +namespace](#namespace-resources-data-sources) is `true` if the resource is still +present in the configuration, but must be replaced to satisfy its current diff. +Whenever `requires_new` is `true`, [`destroy`](#value-destroy) is also `true`. + +As an example, given the following resource: + +```hcl +resource "null_resource" "foo" { + triggers = { + foo = "bar" + } +} +``` + +The following policy would evaluate to `true` if one of the `triggers` in +`null_resource.foo` was being changed: + +```python +import "tfplan" + +main = rule { tfplan.resources.null_resource.foo[0].requires_new } +``` + +## Namespace: Resource Diff + +The **diff namespace** is a namespace that represents the diff for a specific +attribute within a resource. For details on reading a particular attribute, +see the [`diff`](#value-diff) value in the [resource +namespace](#namespace-resources-data-sources). + +### Value: `computed` + +- **Value Type:** Boolean. + +The `computed` value within the [diff namespace](#namespace-resource-diff) is +`true` if the resource key in question depends on another value that isn't yet +known. Typically, that means the value it depends on belongs to a resource that +either doesn't exist yet, or is changing state in such a way as to affect the +dependent value so that it can't be known until the apply is complete. + +-> Keep in mind that when using `computed` with complex structures such as maps, +lists, and sets, it's sometimes necessary to test the count attribute for the +structure, versus a key within it, depending on whether or not the diff has +marked the whole structure as computed. This is demonstrated in the example +below. Count keys are `%` for maps, and `#` for lists and sets. If you are +having trouble determining the type of specific field within a resource, contact +the support team. + +As an example, given the following resource: + +```hcl +resource "null_resource" "foo" { + triggers = { + foo = "bar" + } +} + +resource "null_resource" "bar" { + triggers = { + foo_id = "${null_resource.foo.id}" + } +} +``` + +The following policy would evaluate to `true`, if the `id` of +`null_resource.foo` was currently not known, such as when the resource is +pending creation, or is being deleted and re-created: + +```python +import "tfplan" + +main = rule { tfplan.resources.null_resource.bar[0].diff["triggers.%"].computed } +``` + +### Value: `new` + +- **Value Type:** String. + +The `new` value within the [diff namespace](#namespace-resource-diff) contains +the new value of a changing attribute, _if_ the value is known at plan time. + +-> `new` will be an empty string if the attribute's value is currently unknown. +For more details on detecting unknown values, see [`computed`](#value-computed). + +Note that this value is _always_ a string, regardless of the actual type of the +value changing. [Type conversion][ref-sentinel-type-conversion] within policy +may be necessary to achieve the comparison needed. + +[ref-sentinel-type-conversion]: /sentinel/docs/language/values#type-conversion + +As an example, given the following resource: + +```hcl +resource "null_resource" "foo" { + triggers = { + foo = "bar" + } +} +``` + +The following policy would evaluate to `true`, if the resource was in the diff +and each of the concerned keys were changing to new values: + +```python +import "tfplan" + +main = rule { tfplan.resources.null_resource.foo[0].diff["triggers.foo"].new is "bar" } +``` + +### Value: `old` + +- **Value Type:** String. + +The `old` value within the [diff namespace](#namespace-resource-diff) contains +the old value of a changing attribute. + +Note that this value is _always_ a string, regardless of the actual type of the +value changing. [Type conversion][ref-sentinel-type-conversion] within policy +may be necessary to achieve the comparison needed. + +If the value did not exist in the previous state, `old` will always be an empty +string. + +As an example, given the following resource: + +```hcl +resource "null_resource" "foo" { + triggers = { + foo = "baz" + } +} +``` + +If that resource was previously in config as: + +```hcl +resource "null_resource" "foo" { + triggers = { + foo = "bar" + } +} +``` + +The following policy would evaluate to `true`: + +```python +import "tfplan" + +main = rule { tfplan.resources.null_resource.foo[0].diff["triggers.foo"].old is "bar" } +``` diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/policy-enforcement/import-reference/tfrun.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/policy-enforcement/import-reference/tfrun.mdx new file mode 100644 index 0000000000..51d03dfd0e --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/policy-enforcement/import-reference/tfrun.mdx @@ -0,0 +1,320 @@ +--- +page_title: tfrun Sentinel import reference +description: >- + Use tfrun import to give Sentinel access to data associated with a Terraform + run. +source: terraform-docs-common +--- + +# tfrun Sentinel import reference + +The `tfrun` import provides access to data associated with a [Terraform run][run-glossary]. + + + +@include 'tfc-package-callouts/policies.mdx' + + + +This import currently consists of run attributes, as well as namespaces for the `organization`, `workspace` and `cost-estimate`. Each namespace provides static data regarding the HCP Terraform application that can then be consumed by Sentinel during a policy evaluation. + + tfrun + ├── id (string) + ├── created_at (string) + ├── created_by (string) + ├── message (string) + ├── commit_sha (string) + ├── is_destroy (boolean) + ├── refresh (boolean) + ├── refresh_only (boolean) + ├── replace_addrs (array of strings) + ├── speculative (boolean) + ├── target_addrs (array of strings) + ├── project + │ ├── id (string) + │ └── name (string) + ├── variables (map of keys) + ├── organization + │ └── name (string) + ├── workspace + │ ├── id (string) + │ ├── name (string) + │ ├── created_at (string) + │ ├── description (string) + │ ├── execution_mode (string) + │ ├── auto_apply (bool) + │ ├── tags (array of strings) + | ├── tag_bindings (array of objects) + │ ├── working_directory (string) + │ └── vcs_repo (map of keys) + └── cost_estimate + ├── prior_monthly_cost (string) + ├── proposed_monthly_cost (string) + └── delta_monthly_cost (string) + +-> **Note:** When writing policies using this import, keep in mind that workspace +data is generally editable by users outside of the context of policy +enforcement. For example, consider the case of omitting the enforcement of +policy rules for development workspaces by the workspace name (allowing the +policy to pass if the workspace ends in `-dev`). While this is useful for +extremely granular exceptions, the workspace name could be edited by +workspace admins, effectively bypassing the policy. In this case, where an +extremely strict separation of policy managers vs. workspace practitioners is +required, using [policy sets](/terraform/enterprise/policy-enforcement/manage-policy-sets) +to only enforce the policy on non-development workspaces is more appropriate. + +[run-glossary]: /terraform/docs/glossary#run + +[workspace-glossary]: /terraform/docs/glossary#workspace + +## Namespace: root + +The **root namespace** contains data associated with the current run. + +### Value: `id` + +- **Value Type:** String. + +Specifies the ID that is associated with the current Terraform run. + +### Value: `created_at` + +- **Value Type:** String. + +The `created_at` value within the [root namespace](#namespace-root) specifies the time that the run was created. The timestamp returned follows the format outlined in [RFC3339](https://datatracker.ietf.org/doc/html/rfc3339). + +Users can use the `time` import to [load](/sentinel/docs/imports/time#time-load-timeish) a run timestamp and create a new timespace from the specified value. See the `time` import [documentation](/sentinel/docs/imports/time#import-time) for available actions that can be performed on timespaces. + +### Value: `created_by` + +- **Value Type:** String. + +The `created_by` value within the [root namespace](#namespace-root) is string that specifies the user name of the HCP Terraform user for the specific run. + +### Value: `message` + +- **Value Type:** String. + +Specifies the message that is associated with the Terraform run. + +The default value is _"Queued manually via the Terraform Enterprise API"_. + +### Value: `commit_sha` + +- **Value Type:** String. + +Specifies the checksum hash (SHA) that identifies the commit. + +### Value: `is_destroy` + +- **Value Type:** Boolean. + +Specifies if the plan is a destroy plan, which will destroy all provisioned resources. + +### Value: `refresh` + +- **Value Type:** Boolean. + +Specifies whether the state was refreshed prior to the plan. + +### Value: `refresh_only` + +- **Value Type:** Boolean. + +Specifies whether the plan is in refresh-only mode, which ignores configuration changes and updates state with any changes made outside of Terraform. + +### Value: `replace_addrs` + +- **Value Type:** An array of strings representing [resource addresses](/terraform/cli/state/resource-addressing). + +Provides the targets specified using the [`-replace`](/terraform/cli/commands/plan#resource-targeting) flag in the CLI or the `replace-addrs` attribute in the API. Will be null if no resource targets are specified. + +### Value: `speculative` + +- **Value Type:** Boolean. + +Specifies whether the plan associated with the run is a [speculative plan](/terraform/enterprise/run/remote-operations#speculative-plans) only. + +### Value: `target_addrs` + +- **Value Type:** An array of strings representing [resource addresses](/terraform/cli/state/resource-addressing). + +Provides the targets specified using the [`-target`](/terraform/cli/commands/plan#resource-targeting) flag in the CLI or the `target-addrs` attribute in the API. Will be null if no resource targets are specified. + +To prohibit targeted runs altogether, make sure the `target_addrs` value is null or empty: + + import "tfrun" + + main = tfrun.target_addrs is null or tfrun.target_addrs is empty + +### Value: `variables` + +- **Value Type:** A string-keyed map of values. + +Provides the names of the variables that are configured within the run and the [sensitivity](/terraform/enterprise/workspaces/variables/managing-variables#sensitive-values) state of the value. + + variables (map of keys) + └── name (string) + └── category (string) + └── sensitive (boolean) + +## Namespace: project + +The **project namespace** contains data associated with the current run's [projects](/terraform/enterprise/api-docs/projects). + +### Value: `id` + +- **Value Type:** String. + +Specifies the ID that is associated with the current project. + +### Value: `name` + +- **Value Type:** String. + +Specifies the name assigned to the HCP Terraform project. + +## Namespace: organization + +The **organization namespace** contains data associated with the current run's HCP Terraform [organization](/terraform/enterprise/users-teams-organizations/organizations). + +### Value: `name` + +- **Value Type:** String. + +Specifies the name assigned to the HCP Terraform organization. + +## Namespace: workspace + +The **workspace namespace** contains data associated with the current run's workspace. + +### Value: `id` + +- **Value Type:** String. + +Specifies the ID that is associated with the Terraform workspace. + +### Value: `name` + +- **Value Type:** String. + +The name of the workspace, which can only include letters, numbers, `-`, and `_`. + +As an example, in a workspace named `app-us-east-dev` the following policy would evaluate to `true`: + + # Enforces production rules on all non-development workspaces + + import "tfrun" + import "strings" + + # (Actual policy logic omitted) + evaluate_production_policy = rule { ... } + + main = rule when strings.has_suffix(tfrun.workspace.name, "-dev") is false { + evaluate_production_policy + } + +### Value: `created_at` + +- **Value Type:** String. + +Specifies the time that the workspace was created. The timestamp returned follows the format outlined in [RFC3339](https://datatracker.ietf.org/doc/html/rfc3339). + +Users can use the `time` import to [load](/sentinel/docs/imports/time#time-load-timeish) a workspace timestamp, and create a new timespace from the specified value. See the `time` import [documentation](/sentinel/docs/imports/time#import-time) for available actions that can be performed on timespaces. + +### Value: `description` + +- **Value Type:** String. + +Contains the description for the workspace. + +This value can be `null`. + +### Value: `auto_apply` + +- **Value Type:** Boolean. + +Contains the workspace's [auto-apply](/terraform/enterprise/workspaces/settings#auto-apply-and-manual-apply) setting. + +### Value: `tags` + +- **Value Type:** Array of strings. + +Contains the list of tag names for the workspace, as well as the keys from tag bindings. + +### Value: `tag_bindings` + +- **Value Type:** Array of objects. + +Contains the complete list of tag bindings for the workspace, which includes inherited tag bindings, as well as the workspace key-only tags. Each binding has a string `key`, a nullable string `value`, as well as a boolean `inherited` properties. + + tag_bindings (array of objects) + ├── key (string) + ├── value (string or null) + └── inherited (boolean) + +### Value: `working_directory` + +- **Value Type:** String. + +Contains the configured [Terraform working directory](/terraform/enterprise/workspaces/settings#terraform-working-directory) of the workspace. + +This value can be `null`. + +### Value: `execution_mode` + +- **Value Type:** String. + +Contains the configured [Terraform execution mode](/terraform/enterprise/workspaces/settings#execution-mode) of the workspace. + +The default value is `remote`. + +### Value: `vcs_repo` + +- **Value Type:** A string-keyed map of values. + +Contains data associated with a VCS repository connected to the workspace. + +Details regarding each attribute can be found in the documentation for the HCP Terraform [Workspaces API](/terraform/enterprise/api-docs/workspaces). + +This value can be `null`. + + vcs_repo (map of keys) + ├── identifier (string) + ├── display_identifier (string) + ├── branch (string) + └── ingress_submodules (bool) + +## Namespace: cost_estimate + +The **cost_estimation namespace** contains data associated with the current run's cost estimate. + +This namespace is only present if a cost estimate is available. + +-> Cost estimation is disabled for runs using [resource targeting](/terraform/cli/commands/plan#resource-targeting), which may cause unexpected failures. + +-> **Note:** Cost estimates are not available for Terraform 0.11. + +### Value: `prior_monthly_cost` + +- **Value Type:** String. + +Contains the monthly cost estimate at the beginning of a plan. + +This value contains a positive decimal and can be `"0.0"`. + +### Value: `proposed_monthly_cost` + +- **Value Type:** String. + +Contains the monthly cost estimate if the plan were to be applied. + +This value contains a positive decimal and can be `"0.0"`. + +### Value: `delta_monthly_cost` + +- **Value Type:** String. + +Contains the difference between the prior and proposed monthly cost estimates. + +This value may contain a positive or negative decimal and can be `"0.0"`. diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/policy-enforcement/import-reference/tfstate-v2.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/policy-enforcement/import-reference/tfstate-v2.mdx new file mode 100644 index 0000000000..0de4f5cbd5 --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/policy-enforcement/import-reference/tfstate-v2.mdx @@ -0,0 +1,180 @@ +--- +page_title: tfstate/v2 Sentinel import +description: Use tfstate/v2 import to give Sentinel access to Terraform state. +source: terraform-docs-common +--- + +-> **Note:** This is documentation for the next version of the `tfstate` +Sentinel import, designed specifically for Terraform 0.12. This import requires +Terraform 0.12 or higher, and must currently be loaded by path, using an alias, +example: `import "tfstate/v2" as tfstate`. + +# tfstate/v2 Sentinel import + +The `tfstate/v2` import provides access to a Terraform state. + + + +@include 'tfc-package-callouts/policies.mdx' + + + +The _state_ is the data that Terraform has recorded about a workspace at a +particular point in its lifecycle, usually after an apply. You can read more +general information about how Terraform uses state +[here](/terraform/language/state). + +-> **NOTE:** Since HCP Terraform currently only supports policy checks at plan +time, the usefulness of this import is somewhat limited, as it will usually give +you the state _prior_ to the plan the policy check is currently being run for. +Depending on your needs, you may find the +[`planned_values`](/terraform/enterprise/policy-enforcement/import-reference/tfplan-v2#the-planned_values-collection) collection in +`tfplan/v2` more useful, which will give you a _predicted_ state by applying +plan data to the data found here. The one exception to this rule is _data +sources_, which will always give up to date data here, as long as the data +source could be evaluated at plan time. + +The data in the `tfstate/v2` import is sourced from the JSON configuration file +that is generated by the [`terraform show -json`](/terraform/cli/commands/show#json-output) command. For more information on +the file format, see the [JSON Output Format](/terraform/internals/json-format) +page. + +## Import Overview + +The `tfstate/v2` import is structured as currently two _collections_, keyed in +resource address and output name, respectively. + + (tfstate/v2) + ├── terraform_version (string) + ├── resources + │ └── (indexed by address) + │ ├── address (string) + │ ├── module_address (string) + │ ├── mode (string) + │ ├── type (string) + │ ├── name (string) + │ ├── index (float (number) or string) + │ ├── provider_name (string) + │ ├── values (map) + │ ├── depends_on (list of strings) + │ ├── tainted (boolean) + │ └── deposed_key (string) + └── outputs + └── (indexed by name) + ├── name (string) + ├── sensitive (boolean) + └── value (value) + +The collections are: + +- [`resources`](#the-resources-collection) - The state of all resources across + all modules in the state. +- [`outputs`](#the-outputs-collection) - The state of all outputs from the root module in the state. + +These collections are specifically designed to be used with the +[`filter`](/sentinel/docs/language/collection-operations#filter-expression) +quantifier expression in Sentinel, so that one can collect a list of resources +to perform policy checks on without having to write complex module traversal. As +an example, the following code will return all `aws_instance` resource types +within the state, regardless of what module they are in: + + all_aws_instances = filter tfstate.resources as _, r { + r.mode is "managed" and + r.type is "aws_instance" + } + +You can add specific attributes to the filter to narrow the search, such as the +module address. The following code would return resources in a module named +`foo` only: + + all_aws_instances = filter tfstate.resources as _, r { + r.module_address is "module.foo" and + r.mode is "managed" and + r.type is "aws_instance" + } + +## The `terraform_version` Value + +The top-level `terraform_version` value in this import gives the Terraform +version that recorded the state. This can be used to do version validation. + + import "tfstate/v2" as tfstate + import "strings" + + v = strings.split(tfstate.terraform_version, ".") + version_major = int(v[1]) + version_minor = int(v[2]) + + main = rule { + version_major is 12 and version_minor >= 19 + } + +-> **NOTE:** The above example will give errors when working with pre-release +versions (example: `0.12.0beta1`). Future versions of this import will include +helpers to assist with processing versions that will account for these kinds of +exceptions. + +## The `resources` Collection + +The `resources` collection is a collection representing all of the resources in +the state, across all modules. + +This collection is indexed on the complete resource address as the key. + +An element in the collection has the following values: + +- `address` - The absolute resource address - also the key for the collection's + index. + +- `module_address` - The address portion of the absolute resource address. + +- `mode` - The resource mode, either `managed` (resources) or `data` (data + sources). + +- `type` - The resource type, example: `aws_instance` for `aws_instance.foo`. + +- `name` - The resource name, example: `foo` for `aws_instance.foo`. + +- `index` - The resource index. Can be either a number or a string. + +- `provider_name` - The name of the provider this resource belongs to. This + allows the provider to be interpreted unambiguously in the unusual situation + where a provider offers a resource type whose name does not start with its own + name, such as the `googlebeta` provider offering `google_compute_instance`. + + -> **Note:** Starting with Terraform 0.13, the `provider_name` field contains the + _full_ source address to the provider in the Terraform Registry. Example: + `registry.terraform.io/hashicorp/null` for the null provider. + +- `values` - An object (map) representation of the attribute values of the + resource, whose structure depends on the resource type schema. When accessing + proposed state through the [`planned_values`](/terraform/enterprise/policy-enforcement/import-reference/tfplan-v2#the-planned_values-collection) + collection of the tfplan/v2 import, unknown values will be omitted. + +- `depends_on` - The addresses of the resources that this resource depends on. + +- `tainted` - `true` if the resource has been explicitly marked as + [tainted](/terraform/cli/commands/taint) in the state. + +- `deposed_key` - Set if the resource has been marked deposed and will be + destroyed on the next apply. This matches the deposed field in the + [`resource_changes`](/terraform/enterprise/policy-enforcement/import-reference/tfplan-v2#the-resource_changes-collection) + collection in the [`tfplan/v2`](/terraform/enterprise/policy-enforcement/import-reference/tfplan-v2) import. + +## The `outputs` Collection + +The `outputs` collection is a collection of outputs from the root module of the +state. + +Note that no child modules are included in this output set, and there is no way +to fetch child module output values. This is to encourage the correct flow of +outputs to the recommended root consumption level. + +The collection is indexed on the output name, with the following fields: + +- `name`: The name of the output, also the collection key. +- `sensitive`: Whether or not the value was marked as + [sensitive](/terraform/language/values/outputs#sensitive-suppressing-values-in-cli-output) + in + configuration. +- `value`: The value of the output. diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/policy-enforcement/import-reference/tfstate.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/policy-enforcement/import-reference/tfstate.mdx new file mode 100644 index 0000000000..42e2590196 --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/policy-enforcement/import-reference/tfstate.mdx @@ -0,0 +1,550 @@ +--- +page_title: tfstate Sentinel import +description: Use the tfstate import to give Sentinel access to Terraform state. +source: terraform-docs-common +--- + +# Import: tfstate + +~> **Warning:** The `tfstate` import is now deprecated and will be permanently removed in August 2025. We recommend that you start using the updated [tfstate/v2](/terraform/enterprise/policy-enforcement/import-reference/tfstate-v2) import as soon as possible to avoid disruptions. The `tfstate/v2` import offers improved functionality and is designed to better support your policy enforcement needs. + +The `tfstate` import provides access to the Terraform state. + + + +@include 'tfc-package-callouts/policies.mdx' + + + +The _state_ is the data that Terraform has recorded about a workspace at a +particular point in its lifecycle, usually after an apply. You can read more +general information about how Terraform uses state [here][ref-tf-state]. + +[ref-tf-state]: /terraform/language/state + +-> **Note:** Since HCP Terraform currently only supports policy checks at plan +time, the usefulness of this import is somewhat limited, as it will usually give +you the state _prior_ to the plan the policy check is currently being run for. +Depending on your needs, you may find the +[`applied`](/terraform/enterprise/policy-enforcement/import-reference/tfplan-v2#value-applied) collection in `tfplan` more useful, +which will give you a _predicted_ state by applying plan data to the data found +here. The one exception to this rule is _data sources_, which will always give +up to date data here, as long as the data source could be evaluated at plan +time. + +## Namespace Overview + +The following is a tree view of the import namespace. For more detail on a +particular part of the namespace, see below. + +-> Note that the root-level alias keys shown here (`data`, `outputs`, `path`, +and `resources`) are shortcuts to a [module namespace](#namespace-module) scoped +to the root module. For more details, see the section on [root namespace +aliases](#root-namespace-aliases). + + tfstate + ├── module() (function) + │ └── (module namespace) + │ ├── path ([]string) + │ ├── data + │ │ └── TYPE.NAME[NUMBER] + │ │ ├── attr (map of keys) + │ │ ├── depends_on ([]string) + │ │ ├── id (string) + │ │ └── tainted (boolean) + │ ├── outputs (root module only in TF 0.12 or later) + │ │ └── NAME + │ │ ├── sensitive (bool) + │ │ ├── type (string) + │ │ └── value (value) + │ └── resources + │ └── TYPE.NAME[NUMBER] + │ ├── attr (map of keys) + │ ├── depends_on ([]string) + │ ├── id (string) + │ └── tainted (boolean) + │ + ├── module_paths ([][]string) + ├── terraform_version (string) + │ + ├── data (root module alias) + ├── outputs (root module alias) + ├── path (root module alias) + └── resources (root module alias) + +## Namespace: Root + +The root-level namespace consists of the values and functions documented below. + +In addition to this, the root-level `data`, `outputs`, `path`, and `resources` +keys alias to their corresponding namespaces or values within the [module +namespace](#namespace-module). + +### Function: `module()` + + module = func(ADDR) + +- **Return Type:** A [module namespace](#namespace-module). + +The `module()` function in the [root namespace](#namespace-root) returns the +[module namespace](#namespace-module) for a particular module address. + +The address must be a list and is the module address, split on the period (`.`), +excluding the root module. + +Hence, a module with an address of simply `foo` (or `root.foo`) would be +`["foo"]`, and a module within that (so address `foo.bar`) would be read as +`["foo", "bar"]`. + +[`null`][ref-null] is returned if a module address is invalid, or if the module +is not present in the state. + +[ref-null]: /sentinel/docs/language/spec#null + +As an example, given the following module block: + +```hcl +module "foo" { + # ... +} +``` + +If the module contained the following content: + +```hcl +resource "null_resource" "foo" { + triggers = { + foo = "bar" + } +} +``` + +The following policy would evaluate to `true` if the resource was present in +the state: + +```python +import "tfstate" + +main = rule { tfstate.module(["foo"]).resources.null_resource.foo[0].attr.triggers.foo is "bar" } +``` + +### Value: `module_paths` + +- **Value Type:** List of a list of strings. + +The `module_paths` value within the [root namespace](#namespace-root) is a list +of all of the modules within the Terraform state at plan-time. + +Modules not present in the state will not be present here, even if they are +present in the configuration or the diff. + +This data is represented as a list of a list of strings, with the inner list +being the module address, split on the period (`.`). + +The root module is included in this list, represented as an empty inner list, as +long as it is present in state. + +As an example, if the following module block was present within a Terraform +configuration: + +```hcl +module "foo" { + # ... +} +``` + +The value of `module_paths` would be: + + [ + [], + ["foo"], + ] + +And the following policy would evaluate to `true`: + +```python +import "tfstate" + +main = rule { tfstate.module_paths contains ["foo"] } +``` + +-> Note the above example only applies if the module is present in the state. + +#### Iterating Through Modules + +Iterating through all modules to find particular resources can be useful. This +[example][iterate-over-modules] shows how to use `module_paths` with the +[`module()` function](#function-module-) to find all resources of a +particular type from all modules using the `tfplan` import. By changing `tfplan` +in this function to `tfstate`, you could make a similar function find all +resources of a specific type in the current state. + +[iterate-over-modules]: /terraform/enterprise/policy-enforcement/sentinel#sentinel-imports + +### Value: `terraform_version` + +- **Value Type:** String. + +The `terraform_version` value within the [root namespace](#namespace-root) +represents the version of Terraform in use when the state was saved. This can be +used to enforce a specific version of Terraform in a policy check. + +As an example, the following policy would evaluate to `true` as long as the +state was made with a version of Terraform in the 0.11.x series, excluding any +pre-release versions (example: `-beta1` or `-rc1`): + +```python +import "tfstate" + +main = rule { tfstate.terraform_version matches "^0\\.11\\.\\d+$" } +``` + +-> **NOTE:** This value is also available via the [`tfplan`](/terraform/enterprise/policy-enforcement/import-reference/tfplan-v2) +import, which will be more current when a policy check is run against a plan. +It's recommended you use the value in `tfplan` until HCP Terraform +supports policy checks in other stages of the workspace lifecycle. See the +[`terraform_version`][import-tfplan-terraform-version] reference within the +`tfplan` import for more details. + +[import-tfplan-terraform-version]: /terraform/enterprise/policy-enforcement/import-reference/tfplan-v2#value-terraform_version + +## Namespace: Module + +The **module namespace** can be loaded by calling +[`module()`](#function-module-) for a particular module. + +It can be used to load the following child namespaces, in addition to the values +documented below: + +- `data` - Loads the [resource namespace](#namespace-resources-data-sources), + filtered against data sources. +- `outputs` - Loads the [output namespace](#namespace-outputs), which supply the + outputs present in this module's state. Note that with Terraform 0.12 or + later, this value is only available for the root namespace. +- `resources` - Loads the [resource + namespace](#namespace-resources-data-sources), filtered against resources. + +### Root Namespace Aliases + +The root-level `data`, `outputs`, and `resources` keys both alias to their +corresponding namespaces within the module namespace, loaded for the root +module. They are the equivalent of running `module([]).KEY`. + +### Value: `path` + +- **Value Type:** List of strings. + +The `path` value within the [module namespace](#namespace-module) contains the +path of the module that the namespace represents. This is represented as a list +of strings. + +As an example, if the following module block was present within a Terraform +configuration: + +```hcl +module "foo" { + # ... +} +``` + +The following policy would evaluate to `true`, _only_ if the module was present +in the state: + +```python +import "tfstate" + +main = rule { tfstate.module(["foo"]).path contains "foo" } +``` + +## Namespace: Resources/Data Sources + +The **resource namespace** is a namespace _type_ that applies to both resources +(accessed by using the `resources` namespace key) and data sources (accessed +using the `data` namespace key). + +Accessing an individual resource or data source within each respective namespace +can be accomplished by specifying the type, name, and resource number (as if the +resource or data source had a `count` value in it) in the syntax +`[resources|data].TYPE.NAME[NUMBER]`. Note that NUMBER is always needed, even if +you did not use `count` in the resource. + +In addition, each of these namespace levels is a map, allowing you to filter +based on type and name. + +-> The (somewhat strange) notation here of `TYPE.NAME[NUMBER]` may imply that +the inner resource index map is actually a list, but it's not - using the square +bracket notation over the dotted notation (`TYPE.NAME.NUMBER`) is required here +as an identifier cannot start with number. + +Some examples of multi-level access are below: + +- To fetch all `aws_instance.foo` resource instances within the root module, you + can specify `tfstate.resources.aws_instance.foo`. This would then be indexed + by resource count index (`0`, `1`, `2`, and so on). Note that as mentioned + above, these elements must be accessed using square-bracket map notation (so + `[0]`, `[1]`, `[2]`, and so on) instead of dotted notation. +- To fetch all `aws_instance` resources within the root module, you can specify + `tfstate.resources.aws_instance`. This would be indexed from the names of + each resource (`foo`, `bar`, and so on), with each of those maps containing + instances indexed by resource count index as per above. +- To fetch all resources within the root module, irrespective of type, use + `tfstate.resources`. This is indexed by type, as shown above with + `tfstate.resources.aws_instance`, with names being the next level down, and so + on. + +Further explanation of the namespace will be in the context of resources. As +mentioned, when operating on data sources, use the same syntax, except with +`data` in place of `resources`. + +### Value: `attr` + +- **Value Type:** A string-keyed map of values. + +The `attr` value within the [resource +namespace](#namespace-resources-data-sources) is a direct mapping to the state +of the resource. + +The map is a complex representation of these values with data going as far down +as needed to represent any state values such as maps, lists, and sets. + +As an example, given the following resource: + +```hcl +resource "null_resource" "foo" { + triggers = { + foo = "bar" + } +} +``` + +The following policy would evaluate to `true` if the resource was in the state: + +```python +import "tfstate" + +main = rule { tfstate.resources.null_resource.foo[0].attr.triggers.foo is "bar" } +``` + +### Value: `depends_on` + +- **Value Type:** A list of strings. + +The `depends_on` value within the [resource +namespace](#namespace-resources-data-sources) contains the dependencies for the +resource. + +This is a list of full resource addresses, relative to the module (example: +`null_resource.foo`). + +As an example, given the following resources: + +```hcl +resource "null_resource" "foo" { + triggers = { + foo = "bar" + } +} + +resource "null_resource" "bar" { + # ... + + depends_on = [ + "null_resource.foo", + ] +} +``` + +The following policy would evaluate to `true` if the resource was in the state: + +```python +import "tfstate" + +main = rule { tfstate.resources.null_resource.bar[0].depends_on contains "null_resource.foo" } +``` + +### Value: `id` + +- **Value Type:** String. + +The `id` value within the [resource +namespace](#namespace-resources-data-sources) contains the id of the resource. + +-> **NOTE:** The example below uses a _data source_ here because the +[`null_data_source`][ref-tf-null-data-source] data source gives a static ID, +which makes documenting the example easier. As previously mentioned, data +sources share the same namespace as resources, but need to be loaded with the +`data` key. For more information, see the +[synopsis](#namespace-resources-data-sources) for the namespace itself. + +[ref-tf-null-data-source]: https://registry.terraform.io/providers/hashicorp/null/latest/docs/data-sources/data_source + +As an example, given the following data source: + +```hcl +data "null_data_source" "foo" { + # ... +} +``` + +The following policy would evaluate to `true`: + +```python +import "tfstate" + +main = rule { tfstate.data.null_data_source.foo[0].id is "static" } +``` + +### Value: `tainted` + +- **Value Type:** Boolean. + +The `tainted` value within the [resource +namespace](#namespace-resources-data-sources) is `true` if the resource is +marked as tainted in Terraform state. + +As an example, given the following resource: + +```hcl +resource "null_resource" "foo" { + triggers = { + foo = "bar" + } +} +``` + +The following policy would evaluate to `true`, if the resource was marked as +tainted in the state: + +```python +import "tfstate" + +main = rule { tfstate.resources.null_resource.foo[0].tainted } +``` + +## Namespace: Outputs + +The **output namespace** represents all of the outputs present within a +[module](#namespace-module). Outputs are present in a state if they were saved +during a previous apply, or if they were updated with known values during the +pre-plan refresh. + +**With Terraform 0.11 or earlier** this can be used to fetch both the outputs +of the root module, and the outputs of any module in the state below the root. +This makes it possible to see outputs that have not been threaded to the root +module. + +**With Terraform 0.12 or later** outputs are available in the top-level (root +module) namespace only and not accessible within submodules. + +This namespace is indexed by output name. + +### Value: `sensitive` + +- **Value Type:** Boolean. + +The `sensitive` value within the [output namespace](#namespace-outputs) is +`true` when the output has been [marked as sensitive][ref-tf-sensitive-outputs]. + +[ref-tf-sensitive-outputs]: /terraform/language/values/outputs#sensitive-suppressing-values-in-cli-output + +As an example, given the following output: + +```hcl +output "foo" { + sensitive = true + value = "bar" +} +``` + +The following policy would evaluate to `true`: + +```python +import "tfstate" + +main = rule { tfstate.outputs.foo.sensitive } +``` + +### Value: `type` + +- **Value Type:** String. + +The `type` value within the [output namespace](#namespace-outputs) gives the +output's type. This will be one of `string`, `list`, or `map`. These are +currently the only types available for outputs in Terraform. + +As an example, given the following output: + +```hcl +output "string" { + value = "foo" +} + +output "list" { + value = [ + "foo", + "bar", + ] +} + +output "map" { + value = { + foo = "bar" + } +} +``` + +The following policy would evaluate to `true`: + +```python +import "tfstate" + +type_string = rule { tfstate.outputs.string.type is "string" } +type_list = rule { tfstate.outputs.list.type is "list" } +type_map = rule { tfstate.outputs.map.type is "map" } + +main = rule { type_string and type_list and type_map } +``` + +### Value: `value` + +- **Value Type:** String, list, or map. + +The `value` value within the [output namespace](#namespace-outputs) is the value +of the output in question. + +Note that the only valid primitive output type in Terraform is currently a +string, which means that any int, float, or boolean value will need to be +converted before it can be used in comparison. This does not apply to primitives +within maps and lists, which will be their original types. + +As an example, given the following output blocks: + +```hcl +output "foo" { + value = "bar" +} + +output "number" { + value = "42" +} + +output "map" { + value = { + foo = "bar" + number = 42 + } +} +``` + +The following policy would evaluate to `true`: + +```python +import "tfstate" + +value_foo = rule { tfstate.outputs.foo.value is "bar" } +value_number = rule { int(tfstate.outputs.number.value) is 42 } +value_map_string = rule { tfstate.outputs.map.value["foo"] is "bar" } +value_map_int = rule { tfstate.outputs.map.value["number"] is 42 } + +main = rule { value_foo and value_number and value_map_string and value_map_int } +``` diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/policy-enforcement/index.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/policy-enforcement/index.mdx new file mode 100644 index 0000000000..7a53f5ced2 --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/policy-enforcement/index.mdx @@ -0,0 +1,50 @@ +--- +page_title: Terraform Enterprise policy enforcement overview +description: >- + Policies are rules for provisioning infrastructure that you can use to + validate Terraform plans. Learn how to use Sentinel and OPA to enforce + policies. +source: terraform-docs-common +--- + +# HCP Terraform policy enforcement overview + +This topic provides overview information about policies in HCP Terraform. Policies are rules for Terraform runs that let you validate that Terraform plans comply with security rules and best practices. + + + +@include 'tfc-package-callouts/policies.mdx' + + + +> **Hands-on:** Try the [Enforce Policy with Sentinel](/terraform/tutorials/policy) and [Detect Infrastructure Drift and Enforce OPA Policies](/terraform/tutorials/cloud/drift-and-opa) tutorials. + +## Introduction + +You can implement policies that check for any number of conditions, such as whether infrastructure configuration adheres to security standards or best practices. For example, you may want to write a policy to check whether Terraform plans to deploy production infrastructure to the correct region. + +You can also use policies to enforce standards for your organization’s workflows. For example, you could write a policy to prevent new infrastructure deployments on Fridays, reducing the risk of production incidents outside of your team’s working hours. + +## Workflow + +The following workflow describes how to create and manage policies manually. + +### Define policy + +You can use either the Sentinel or OPA framework to create custom policies. You can also copy pre-written Sentinel policies created and maintained by HashiCorp. + +### Create and apply policy sets + +Policy sets are collections of policies you can apply globally or to specific [projects](/terraform/enterprise/projects/manage) and workspaces in your organization. For each run in the selected workspaces, HCP Terraform checks the Terraform plan against the policy set. + +You can also exclude specific workspaces from global or project-scoped policy sets. HCP Terraform won't enforce a policy set's policies on any runs in an excluded workspace. For example, if you attach a policy set to a project and then exclude one of the project's workspaces from that policy set, HCP Terraform will not enforce the policy set on the excluded workspace. + +You can create policy sets from the [user interface](/terraform/enterprise/policy-enforcement/manage-policy-sets#create-policy-sets), the API, or by connecting HCP Terraform to your version control system. A policy set can only contain policies written in a single policy framework, but you can add Sentinel or OPA policy sets to the same workspace. + +Refer to [Managing Policy Sets](/terraform/enterprise/policy-enforcement/manage-policy-sets) for details. + +### Review policy results + +The HCP Terraform UI displays policy results for each policy set you apply to the workspace. Depending on their [enforcement level](/terraform/enterprise/policy-enforcement/manage-policy-sets#policy-enforcement-levels), failed policies can stop the run. You can override failed policies with the right permissions. + +Refer to [Policy Results](/terraform/enterprise/policy-enforcement/view-results) for details. diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/policy-enforcement/manage-policy-sets/index.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/policy-enforcement/manage-policy-sets/index.mdx new file mode 100644 index 0000000000..c64a99593a --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/policy-enforcement/manage-policy-sets/index.mdx @@ -0,0 +1,227 @@ +--- +page_title: Manage policies and policy sets in Terraform Enterprise +description: >- + Learn how to create and manage policies and policy sets in Terraform + Enterprise. +source: terraform-docs-common +--- + +# Manage policies and policy sets in HCP Terraform + +Policies are rules that HCP Terraform enforces on Terraform runs. You can define policies using either the [Sentinel](/terraform/enterprise/policy-enforcement/sentinel) or [Open Policy Agent (OPA)](/terraform/enterprise/policy-enforcement/opa) policy-as-code frameworks. + + + +@include 'tfc-package-callouts/policies.mdx' + + + +Policy sets are collections of policies you can apply globally or to specific [projects](/terraform/enterprise/projects/manage) and workspaces in your organization. For each run in the applicable workspaces, HCP Terraform checks the Terraform plan against the policy set. Depending on the [enforcement level](#policy-enforcement-levels), failed policies can stop a run in a workspace. If you do not want to enforce a policy set on a specific workspace, you can exclude the workspace from that set. + +## Permissions + +To view and manage policies and policy sets, you must have [manage policy permissions](/terraform/enterprise/users-teams-organizations/permissions#manage-policies) for your organization. + +## Policy checks versus policy evaluations + +Policy checks and evaluations can access different types of data and enable slightly different workflows. + +### Policy checks + +Only Sentinel policies can run as policy checks. Checks can access cost estimation data but can only use the latest version of Sentinel. + +~> **Warning:** Policy checks are deprecated and will be permanently removed in August 2025. We recommend that you start using policy evaluations to avoid disruptions. + +### Policy evaluations + +OPA policy sets can only run as policy evaluations, and you can enable policy evaluations for Sentinel policy sets by selecting the `Agent` policy set type. + +HCP Terraform runs a workspace's policy evaluation in your self-managed agent pool if you meet the following requirements: + +- You are on the HCP Terraform **Premium** edition. +- You configure the workspace to run Terraform operations in your self-managed agent pool. Refer to [Configure Workspaces to use the Agent](/terraform/cloud-docs/agents/agent-pools#configure-workspaces-to-use-the-agent) for more information. +- You configure at least one agent in the agent pool to accept `policy` jobs. Refer to the [HCP Terraform agent reference](/terraform/cloud-docs/agents/agents#accept) for more information. + +If you do not meet all of the above requirements, then policy evaluations run within HCP Terraform's infrastructure. + +For Sentinel policy sets, using policy evaluations lets you: + +- Enable overrides for soft-mandatory and hard-mandatory policies, letting any user with [Manage Policy Overrides permission](/terraform/enterprise/users-teams-organizations/permissions#manage-policy-overrides) proceed with a run in the event of policy failure. +- Select a specific Sentinel runtime version for the policy set. + +Policy evaluations **cannot** access cost estimation data, so use policy checks if your policies rely on cost estimates. + +~> **Tip:** Sentinel runtime version pinning is supported only for Sentinel 0.23.1 and above, as well as HCP Terraform agent versions 1.13.1 and above + +## Policy enforcement levels + +You can set an enforcement level for each policy that determines what happens when a Terraform plan does not pass the policy rule. Sentinel and OPA policies have different enforcement levels available. + +### Sentinel + +Sentinel provides three policy enforcement levels: + +- **advisory:** Failed policies never interrupt the run. They provide information about policy check failures in the UI. +- **soft mandatory:** Failed policies stop the run, but any user with [Manage Policy Overrides permission](/terraform/enterprise/users-teams-organizations/permissions#manage-policy-overrides) can override these failures and allow the run to complete. +- **hard mandatory:** Failed policies stop the run. Terraform does not apply runs with failed **hard mandatory** policies until a user fixes the issue that caused the failure. + +### OPA + +OPA provides two policy enforcement levels: + +- **advisory** Failed policies never interrupt the run. They provide information about policy failures in the UI. +- **mandatory:** Failed policies stop the run, but any user with [Manage Policy Overrides permission](/terraform/enterprise/users-teams-organizations/permissions#manage-policy-overrides) can override these failures and allow the run to complete. + +## Policy publishing workflows + +You can create policies and policy sets for your HCP Terraform organization in one of three ways: + +- **HCP Terraform web UI:** Add individually-managed policies manually in the HCP Terraform UI, and store your policy code in HCP Terraform. This workflow is ideal for initial experimentation with policy enforcement, but we do not recommend it for organizations with large numbers of policies. +- **Version control:** Connect HCP Terraform to a version control repository containing a policy set. When you push changes to the repository, HCP Terraform automatically uses the updated policy set. +- **Automated:** Push versions of policy sets to HCP Terraform with the [HCP Terraform Policy Sets API](/terraform/enterprise/api-docs/policy-sets#create-a-policy-set-version) or the `tfe` provider [`tfe_policy_set`](https://registry.terraform.io/providers/hashicorp/tfe/latest/docs/resources/policy_set) resource. This workflow is ideal for automated Continuous Integration and Deployment (CI/CD) pipelines. + +### Manage individual policies in the web UI + +You can add policies directly to HCP Terraform using the web UI. This process requires you to paste completed, valid Sentinel or Rego code into the UI. We recommend validating your policy code before adding it to HCP Terraform. + +#### Add managed policies + +To add an individually managed policy: + +1. Sign in to [HCP Terraform](https://app.terraform.io/) or Terraform Enterprise and navigate to the organization you want to add policies to. +2. Choose **Settings** from the sidebar, then **Policies**. A list of managed policies in HCP Terraform appears. Each policy designates its policy framework (Sentinel or OPA) and associated policy sets. +3. Click **Create a new policy**. +4. Choose the **Policy framework** you want to use. You can only create a policy set from policies written using the same framework. You cannot change the framework type after you create the policy. +5. Complete the following fields to define the policy: + - **Policy Name:** Add a name containing letters, numbers, `-`, and `_`. HCP Terraform displays this name in the UI. The name must be unique within your organization. + - **Description:** Describe the policy’s purpose. The description supports Markdown rendering, and HCP Terraform displays this text in the UI. + - **Enforcement mode:** Choose whether this policy can stop Terraform runs and whether users can override it. Refer to [policy enforcement levels](#policy-enforcement-levels) for more details. + - **(OPA Only) Query:** Write a query to identify a specific policy rule within your rego code. HCP Terraform uses this query to determine the result of the policy. The query is typically a combination of the policy package name and rule name, such as `terraform.deny`. The result of this query must be an array. The policy passes when the array is empty. + - **Policy code**: Paste the code for the policy: either Sentinel code or Rego code for OPA policies. The UI provides syntax highlighting for the policy language. + - **(Optional) Policy sets:** Select one or more existing managed policy sets where you want to add the new policy. You can only select policy sets compatible with the chosen policy set framework. If there are no policy sets available, you can [create a new one](#create-policy-sets). + +The policy is now available in the HCP Terraform UI for you to edit and add to one or more policy sets. + +#### Edit managed policies + +To edit a managed policy: + +1. Sign in to [HCP Terraform](https://app.terraform.io/) or Terraform Enterprise and navigate to the organization you want to edit policies for. +2. Choose **Settings** from the sidebar, then **Policies**. +3. Click the policy you want to edit to go to its details page. +4. Edit the policy's fields and then click **Update policy**. + +#### Delete managed policies + +~> **Warning:** Deleting a policy that applies to an active run causes that run’s policy evaluation stage to error. We recommend warning other members of your organization before you delete widely used policies. + +You can not restore policies after deletion. You must manually re-add them to HCP Terraform. You may want to save the policy code in a separate location before you delete the policy. + +To delete a managed policy: + +1. Sign in to [HCP Terraform](https://app.terraform.io/) or Terraform Enterprise and navigate to the organization you want to delete a policy in. +2. Choose **Settings** from the sidebar, then **Policies**. +3. Click the policy you want to delete to go to its details page. +4. Click **Delete policy** and then click **Yes, delete policy** to confirm. + +The policy no longer appears in HCP Terraform and in any associated policy sets. + +## Manage policy sets + +Policy sets are collections of policies that you can apply globally or to specific [projects](/terraform/enterprise/projects/manage) and workspaces. + +To view and manage policy sets, go to the **Policy Sets** section of your organization’s settings. This page contains all of the policy sets available in the organization, including those added through the API. + +The way you set up and configure a new policy set depends on your workflow and where you store policies. + +- For [managed policies](#managed-policies), you use the UI to create a policy set and add managed policies. +- For policy sets in a version control system, you use the UI to create a policy set connected to that repository. HCP Terraform automatically refreshes the policy set when you change relevant files in that repository. Version control policy sets have specific organization and formatting requirements. Refer to [Sentinel VCS Repositories](/terraform/enterprise/policy-enforcement/sentinel/vcs) and [OPA VCS Repositories](/terraform/enterprise/policy-enforcement/opa/vcs) for details. +- For automated workflows like continuous deployment, you can use the UI to create an empty policy set and then use the [Policy Sets API](/terraform/enterprise/api-docs/policy-sets) to add policies. You can also use the API or the [`tfe` provider (Sentinel Only)](https://registry.terraform.io/providers/hashicorp/tfe/latest/docs/resources/policy_set) to add an entire, packaged policy set. + +### Create policy sets + +To create a policy set: + +1. Sign in to [HCP Terraform](https://app.terraform.io/) or Terraform Enterprise and navigate to the organization you want to create a policy set in. + +2. Choose **Settings** from the sidebar, then **Policies**. + +3. Click **Connect a new policy set**. + +4. Choose your workflow. + - For managed policies, click **create a policy set with individually managed policies**. HCP Terraform shows a form to create a policy set and add individually managed policies. + - For version control policies, choose a version control provider and then select the repository with your policy set. HCP Terraform shows a form to create a policy set connected to that repository. + - For automated workflows, click **No VCS Connection**. HCP Terraform shows a form to create an empty policy set. You can use the API to add policies to this empty policy set later. + +5. Choose a **Policy framework** for the policies you want to add. A policy set can only contain policies that use the same framework (OPA or Sentinel). You cannot change a policy set's framework type after creation. + +6. Choose a policy set scope: + - **Policies enforced globally:** HCP Terraform automatically enforces this global policy set on all of an organization's existing and future workspaces. + - **Policies enforced on selected projects and workspaces:** Use the text fields to find and select the workspaces and projects to enforce this policy set on. This affects all current and future workspaces for any chosen projects. + +7. **(Optional)** Add **Policy exclusions** for this policy set. Specify any workspaces in the policy set's scope that HCP Terraform will not enforce this policy set on. + +8. **(Sentinel Only)** Choose a policy set type: + - **Standard:** This is the default workflow. A Sentinel policy set uses a [policy check](#policy-checks) in HCP Terraform and lets you access cost estimation data. + - **Agent:** A Sentinel policy set uses a [policy evaluation](#policy-evaluations) in HCP Terraform. This lets you enable policy overrides and enforce a Sentinel runtime version + +9. **(OPA Only)** Select a **Runtime version** for this policy set. + +10. **(OPA Only)** Allow **Overrides**, which enables users with override policy permissions to apply plans that have [mandatory policy](#policy-enforcement-levels) failures. + +11. **(VCS Only)** Optionally specify the **VCS branch** within your VCS repository where HCP Terraform should import new versions of policies. If you do not set this field, HCP Terraform uses your selected VCS repository's default branch. + +12. **(VCS Only)** Specify where your policy set files live using the **Policies path**. This lets you maintain multiple policy sets within a single repository. Use a relative path from your root directory to the directory that contains either the `sentinel.hcl` (Sentinel) or `policies.hcl` (OPA) configuration files. If you do not set this field, HCP Terraform uses the repository's root directory. + +13. **(Managed Policies Only)** Select managed **Policies** to add to the policy set. You can only add policies written with the same policy framework you selected for this set. + +14. Choose a descriptive and unique **Name** for the policy set. You can use any combination of letters, numbers, `-`, and `_`. + +15. Write an optional **Description** that tells other users about the purpose of the policy set and what it contains. + +### Edit policy sets + +To edit a policy set: + +1. Sign in to [HCP Terraform](https://app.terraform.io/) or Terraform Enterprise and navigate to the organization you want to edit a policy set in. +2. Choose **Settings** from the sidebar, then **Policies**. +3. Click the policy set you want to edit to go to its settings page. +4. Adjust the settings and click **Update policy set**. + +### Evaluate a policy runtime upgrade + +You can validate that changing a policy runtime version does not introduce any breaking changes. + +To perform a policy evaluation: + +1. Sign in to [HCP Terraform](https://app.terraform.io/) or Terraform Enterprise and navigate to your organization. +2. Choose **Settings** from the sidebar, then **Policies** in your organization’s settings. +3. Click the policy set you want to upgrade. +4. Click the **Evaluate** tab. +5. Select the **Runtime version** you wish to upgrade to. +6. Select a **Workspace** to test the policy and upgraded version against. +7. Click **Evaluate**. + +HCP Terraform will execute the policy set using the specified version and the latest plan data for the selected workspace. It will display the evaluation results. If the evaluation returns a `Failed` status, inspect the JSON output to determine whether the issue is related to a non-compliant resource or is due to a syntax issue. +If the evaluation results in an error, check that the policy configuration is valid. + +### Delete policy sets + +~> **Warning:** Deleting a policy set that applies to an active run causes that run’s policy evaluation stage to error. We recommend warning other members of your organization before you delete widely used policy sets. + +You can not restore policy sets after deletion. You must manually re-add them to HCP Terraform. + +To delete a policy set: + +1. Sign in to [HCP Terraform](https://app.terraform.io/) or Terraform Enterprise and navigate to the organization you want to delete a policy set in. +2. Choose **Settings** from the sidebar, then **Policies** in your organization’s settings. +3. Click the policy set you want to delete to go to its details page. +4. Click **Delete policy** and then click **Yes, delete policy set** to confirm. + +The policy set no longer appears on the UI and HCP Terraform no longer applies it to any workspaces. For managed policy sets, all of the individual policies are still available in HCP Terraform. You must delete each policy individually to remove it from your organization. + +### (Sentinel only) Sentinel parameters + +[Sentinel parameters](/sentinel/docs/language/parameters) are a list of key/value pairs that HCP Terraform sends to the Sentinel runtime when performing policy checks on workspaces. If the value parses as JSON, HCP Terraform sends it to Sentinel as the corresponding type (string, boolean, integer, map, or list). If the value fails JSON validation, HCP Terraform sends it as a string. + +You can set Sentinel parameters when you [edit a policy set](#edit-policy-sets). diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/policy-enforcement/manage-policy-sets/opa-vcs.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/policy-enforcement/manage-policy-sets/opa-vcs.mdx new file mode 100644 index 0000000000..adb5027c99 --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/policy-enforcement/manage-policy-sets/opa-vcs.mdx @@ -0,0 +1,48 @@ +--- +page_title: Configure an OPA policy set with a VCS repository +description: Use a VCS repository to configure an OPA policy set in Terraform Enterprise. +source: terraform-docs-common +--- + +# Configure an OPA policy set with a VCS repository + +To enable policy enforcement, you must group OPA policies into policy sets and apply those policy sets globally or to specific [projects](/terraform/enterprise/projects/manage) and workspaces. + +> **Hands-on:** Try the [Detect Infrastructure Drift and Enforce OPA Policies](/terraform/tutorials/cloud/drift-and-opa) tutorial. + +One way to create policy sets is by connecting HCP Terraform to a version control repository. When you push changes to the repository, HCP Terraform automatically uses the updated policy set. Refer to [Managing Policy Sets](/terraform/enterprise/policy-enforcement/manage-policy-sets) for more details. + +An OPA policy set repository contains a HashiCorp Configuration Language (HCL) configuration file and policy files. + + + +@include 'tfc-package-callouts/policies.mdx' + + + +## Configuration File + +The root directory of your policy set repository must contain a configuration file named either `policies.hcl` or `policies.json`. Policy enforcement supports both HCL and the JSON variant of HCL syntax. +The configuration file contains one or more `policy` blocks that define each policy in the policy set. Unlike Sentinel, OPA policies do not need to be in separate files. You use an [OPA query](/terraform/enterprise/policy-enforcement/opa#opa-query) to identify each policy rule. + +The following example uses a query to define a policy named `policy1`. This query may evaluate across multiple files, or a single file. + +```hcl +policy "policy1" { + query = "data.terraform.policy1.deny" +} +``` + +Optionally, you can also provide a `description` and an `enforcement_level` property. If you do not specify an enforcement level, HCP Terraform uses `advisory`, meaning policy failures produce warnings but do not block Terraform runs. Refer to [Policy Enforcement Levels](/terraform/enterprise/policy-enforcement/manage-policy-sets#policy-enforcement-levels) for more details. + +```hcl +policy "policy1" { + query = "data.terraform.policy1.deny" + enforcement_level = "mandatory" + description = "policy1 description" +} +``` + +## Policy Code Files + +All Rego policy files must end with `.rego` and exist in the local GitHub repository for the policy set. You can store them in separate directories from the configuration file. diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/policy-enforcement/manage-policy-sets/sentinel-vcs.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/policy-enforcement/manage-policy-sets/sentinel-vcs.mdx new file mode 100644 index 0000000000..3bb8a81579 --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/policy-enforcement/manage-policy-sets/sentinel-vcs.mdx @@ -0,0 +1,139 @@ +--- +page_title: Configure a Sentinel policy set with a VCS repository +description: >- + Use a VCS repository to configure a Sentinel policy set in Terraform + Enterprise. +source: terraform-docs-common +--- + +# Configure a Sentinel policy set with a VCS repository + +To enable policy enforcement, you must group Sentinel policies into policy sets. You can then apply those policy sets globally or to specific [projects](/terraform/enterprise/projects/manage) and workspaces. + + + +@include 'tfc-package-callouts/policies.mdx' + + + +One way to create policy sets is by connecting HCP Terraform to a version control repository. When you push changes to the repository, HCP Terraform automatically uses the updated policy set. Refer to [Managing Policy Sets](/terraform/enterprise/policy-enforcement/manage-policy-sets) for more details. + +A Sentinel policy set repository contains a Sentinel configuration file, policy files, and module files. + +## Configuration File + +Your repository must contain a configuration file named `sentinel.hcl` that defines the following features of the policy set: + +- Each policy included in the set. The policy name must match the names of individual [policy code files](#policy-code-files) exactly. HCP Terraform ignores policy files in the repository that are not listed in the configuration file. For each policy, the configuration file must designate the policy’s [enforcement level](/terraform/enterprise/policy-enforcement/manage-policy-sets#policy-enforcement-levels) and [source](#policy-source). +- [Terraform modules](#modules) that policies in the set need to access. + +The following example shows a portion of a `sentinel.hcl` configuration file that defines a policy named `terraform-maintenance-windows`. The policy has a `hard-mandatory` enforcement level, meaning that it can block Terraform runs when it fails and users cannot override it. + +```hcl +policy "terraform-maintenance-windows" { + source = "./terraform-maintenance-windows.sentinel" + enforcement_level = "hard-mandatory" +} +``` + +To configure a module, add a `module` entry to your `sentinel.hcl` file. The following example adds a module called `timezone`. + +```hcl +module "timezone" { + source = "./modules/timezone.sentinel" +} +``` + +The repositories for [policy libraries on the Terraform Registry](https://registry.terraform.io/browse/policies) contain more examples. + +## Policy Code Files + +Define each Sentinel policy in a separate file within your repository. All local policy files must reside in the same directory as the `sentinel.hcl` configuration file and end with the `.sentinel` suffix. + +### Policy Source + +A policy's `source` field can either reference a file within the policy repository, or it can reference a remote source. For example, the configuration could reference a policy from HashiCorp's [foundational policies library](https://github.com/hashicorp/terraform-foundational-policies-library). Sentinel only supports HTTP and HTTPS remote sources. + +To specify a local source, prefix the `source` with a `./`, or `../`. The following example shows how to reference a local source policy called `terraform-maintenance-windows.sentinel`. + +```hcl +policy "terraform-maintenance-windows" { + source = "./terraform-maintenance-windows.sentinel" + enforcement_level = "hard-mandatory" +} +``` + +To specify a remote source, supply the URL as the `source`. The following example references a policy from HashiCorp's foundational policies library. + +```hcl +policy "deny-public-ssh-nsg-rules" { + source = "https://registry.terraform.io/v2/policies/hashicorp/azure-networking-terraform/1.0.2/policy/deny-public-ssh-nsg-rules.sentinel?checksum=sha256:75c95bf1d6eb48153cb31f15c49c237bf7829549beebe20effa07bcdd3f3cb74" + enforcement_level = "advisory" +} +``` + +For GitHub, you must use the URL of the raw policy content. Other URL types cause HCP Terraform to error when checking the policy. For example, do not use `https://github.com/hashicorp/policy-library-azure-networking-terraform/blob/main/policies/deny-public-ssh-nsg-rules/deny-public-ssh-nsg-rules.sentinel`. + +To access the raw URL, open the Sentinel file in your Github repository, right-click **Raw** on the top right of the page, and save the link address. + +### Example Policy + +The following example policy uses the `time` and `tfrun` imports and a custom `timezone` module to do the following tasks: + +1. Load the time when the Terraform run occurred +2. Convert the loaded time with the correct offset using the [Timezone API](https://timezoneapi.io/) +3. Verify that the provisioning operation occurs only on a specific day + +The example policy also uses a [rule expression](/sentinel/docs/language/spec#rule-expressions) with the `when` predicate. If the value of `tfrun.workspace.auto_apply` is false, the rule is not evaluated and returns true. + +Finally, the example uses parameters to facilitate module reuse within Terraform. Refer to the [Sentinel parameter documentation](/sentinel/docs/language/parameters) for details. + +```hcl +import "time" +import "tfrun" +import "timezone" + +param token default "WbNKULOBheqV" +param maintenance_days default ["Friday", "Saturday", "Sunday"] +param timezone_id default "America/Los_Angeles" + +tfrun_created_at = time.load(tfrun.created_at) + +supported_maintenance_day = rule when tfrun.workspace.auto_apply is true { + tfrun_created_at.add(time.hour * timezone.offset(timezone_id, token)).weekday_name in maintenance_days +} + +main = rule { + supported_maintenance_day +} +``` + +To expand the policy, you could use the [time.hour](/sentinel/docs/imports/time#time-hour) function to also restrict provisioning to specific times of day. + +## Modules + +HCP Terraform supports [Sentinel modules](/sentinel/docs/extending/modules). Modules let you write reusable policy code that you can import and use within several policies at once. + +You can store modules locally or retrieve them from a remote HTTP or HTTPS source. + +-> **Note:** We recommend reviewing [Sentinel runtime's modules documentation](/sentinel/docs/extending/modules) to learn how to use modules within Sentinel. However, the configuration examples in the runtime documentation are relevant to the Sentinel CLI and not HCP Terraform. + +The following example module loads the code at `./modules/timezone.sentinel` relative to the policy set working directory. Other modules can access this code with the statement `import "timezone"`. + +```hcl +import "http" +import "json" +import "decimal" + +httpGet = func(id, token){ + uri = "https://timezoneapi.io/api/timezone/?" + id + "&token=" + token + request = http.get(uri) + return json.unmarshal(request.body) +} + +offset = func(id, token) { + tz = httpGet(id, token) + offset = decimal.new(tz.data.datetime.offset_hours).int + return offset +} +``` diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/policy-enforcement/prewritten-library.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/policy-enforcement/prewritten-library.mdx new file mode 100644 index 0000000000..fc023061da --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/policy-enforcement/prewritten-library.mdx @@ -0,0 +1,26 @@ +--- +page_title: Pre-written policy library +description: >- + HashiCorp authors and maintains a library of pre-written Sentinel policies + that enforce CIS and other compliance standards. Learn about the available + pre-written policies. +source: terraform-docs-common +--- + +# Pre-written policy library reference + +This topic provides reference information about the Sentinel policy libraries that HashiCorp authors and maintains. For instructions on how to run the policy libraries, refer to [Run pre-written Sentinel policies ](/terraform/enterprise/policy-enforcement/prewritten-sentinel). + +## Center for Internet Security (CIS) + +The Center for Internet Security (CIS) is a non-profit organization that publishes standards for configuring secure cloud services. Refer to the [CIS website](https://www.cisecurity.org) for additional information. + +HashiCorp publishes pre-written policies that support the following CIS benchmarks. + +### AWS + +- Amazon Web Services Foundations version 1.2. Refer to the [AWS documentation](https://docs.aws.amazon.com/securityhub/latest/userguide/cis-aws-foundations-benchmark.html#cis1v2-standard) for additional information about this version. +- Amazon Web Services Foundations version 1.4. Refer to the [AWS documentation](https://docs.aws.amazon.com/securityhub/latest/userguide/cis-aws-foundations-benchmark.html#cis1v4-standard) for additional information about this version. +- Amazon Web Services Foundations version 3.0. Refer to the [AWS documentation](https://docs.aws.amazon.com/securityhub/latest/userguide/cis-aws-foundations-benchmark.html#cis3v0-standard) for additional information about this version. + +Refer to the [CIS policy set for AWS GitHub repository](https://github.com/hashicorp/policy-library-CIS-Policy-Set-for-AWS-Terraform) for details about these policies. diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/policy-enforcement/prewritten-sentinel.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/policy-enforcement/prewritten-sentinel.mdx new file mode 100644 index 0000000000..ea73b949a1 --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/policy-enforcement/prewritten-sentinel.mdx @@ -0,0 +1,133 @@ +--- +page_title: Run pre-written Sentinel policies +description: >- + Learn how to download and install pre-written Sentinel policies created and + maintained by HashiCorp. +source: terraform-docs-common +--- + +# Run pre-written Sentinel policies + +This topic describes how to run Sentinel policies created and maintained by HashiCorp. For instructions about how to create your own custom Sentinel policies, refer to [Define custom Sentinel policies](/terraform/enterprise/policy-enforcement/define-policies/custom-sentinel). + +## Overview + +Pre-written Sentinel policy libraries streamline your compliance processes and enhance security across your infrastructure. HashiCorp's ready-to-use policies can help you enforce best practices and security standards across your AWS environment. + +Complete the following steps to implement pre-written Sentinel policies in your workspaces: + +1. Obtain the policies you want to implement. Download policies directly into your repository or create a fork of the HashiCorp repositories. Alternatively, you can add the Terraform module to your configuration, which acquires the policies and connects them to your workspaces in a single step. +2. Connect policies to your workspace. After you download policies or fork policy repositories, you must connect them to your HCP Terraform or Terraform Enterprise workspaces. + +Refer to the [Sentinel documentation](/sentinel/docs) for information about the Sentinel language. + +## Requirements + +You must use one of the following Terraform applications: + +- HCP Terraform +- Terraform Enterprise v202406-1 or newer + +### Permissions + +To create new policy sets and policies, your HCP Terraform or Terraform Enterprise user account must either be a member of the owners team or have the **Manage Policies** organization-level permissions enabled. Refer to the following topics for additional information: + +- [Organization owners](/terraform/enterprise/users-teams-organizations/permissions#organization-owners) +- [Manage policies](/terraform/enterprise/users-teams-organizations/permissions#manage-policies) + +### Version control system + +You must have a GitHub account connected to HCP Terraform or Terraform Enterprise to manually connect policy sets to your workspaces. Refer to [Connecting VCS Providers](/terraform/enterprise/vcs) for instructions. + +## Get policies + +Refer to the [pre-written policy library reference](/terraform/enterprise/policy-enforcement/prewritten-library) for a complete list of available policy sets. You can also [browse the registry](https://registry.terraform.io/search/policies?q=Pre-written) to discover additional policy libraries. + +Use one of the following methods to get pre-written policies: + +- **Download policies from the registry**: Use this method if you want to assemble custom policy sets without customizing policies. +- **Fork the HashiCorp policy GitHub repository**: Use this method if you intend to customize the policies. +- **Add the Terraform module to your configuration**: Use this method to implement specific versions of the policies as-is. This method also connects the policies to workspaces in the Terraform configuration file instead of connecting them as a separate step. + + + + + +Complete the following steps to download policies from the registry and apply them directly to your workspaces. + +1. Browse the policy libraries available in the [Terraform registry](https://registry.terraform.io/search/policies?q=Pre-written). +2. Click on a policy library and click **Choose policies**. +3. Select the policies you want to implement. The registry generates code in the **USAGE INSTRUCTIONS** box. +4. Click **Copy Code Snippet** to copy the code to your clipboard. +5. Create a GitHub repository to store the policies and the policy set configuration file. +6. Create a file called `sentinel.hcl` in the repository. +7. Paste the code from your clipboard into `sentinel.hcl` and commit your changes. +8. Complete the instructions for [connecting the policies to your workspace](#connect-policies-to-your-workspace). + + + + +Create a fork of the repository containing the policies you want to implement. Refer to the [GitHub documentation](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/fork-a-repo) for instructions on how to create a fork. + +The following repositories are available: + +- [policy-library-CIS-Policy-Set-for-AWS-Terraform](https://github.com/hashicorp/policy-library-CIS-Policy-Set-for-AWS-Terraform) + +HashiCorp Sentinel policy libraries include a `sentinel.hcl` file. The file defines an example policy set using the policies included in the library. Modify the file to customize your policy set. Refer to [Sentinel Policy Set VCS Repositories](/terraform/enterprise/policy-enforcement/manage-policy-sets/sentinel-vcs) for additional information. + +After forking the repository, complete the instructions for [connecting the policies to your workspace](#connect-policies-to-your-workspace). + + + + +This method enables you to connect the policies to workspaces in the Terraform configuration file. As a result, you can skip the instructions described in [Connect policies to your workspaces](#connect-policies-to-your-workspaces). + +1. Go to the [module in the Terraform registry](https://registry.terraform.io/modules/hashicorp/CIS-Policy-Set/AWS/latest) and copy the code generated in the **Provision Instructions** tile. + +2. Add the `module` block to your Terraform configuration and define the following arguments: + + - `source`: Specify the path to the module you downloaded. + - `tfe_organization`: Specify the name of your organization on Terraform Enterprise or HCP Terraform. + - `policy_set_workspace_names`: Specify a list of workspace names that you want to apply the policies to. + + The following example configuration applies invokes the module for `target_workspace_1`: + + ```hcl + module "cis_v1-2-0_policies" { + source = "../prewritten-policy" + name = "cis-1-2-0" + tfe_organization = "" + policy_set_workspace_names = ["target_workspace_1"] + } + ``` + +3. Run `terraform plan` to view the plan. + +4. Run `terraform apply` to apply the changes. After running the command, Terraform will evaluate Sentinel policies for each following run of the workspaces you specified. + + + + +## Connect policies to your workspace + +Skip this step if you [added the Terraform module](#add-the-terraform-module-to-your-configuration) to your configuration. When you use the module, the `policy_set_workspace_names` argument instructs Terraform to connect the policies to the HCP Terraform workspaces specified in the configuration. + +1. Sign in to [HCP Terraform](https://app.terraform.io/) or Terraform Enterprise and navigate to the organization with workspaces you want to connect policies to. +2. Choose **Settings** from the sidebar. +3. Click **Policy Sets** and click **Connect a new policy set**. +4. Click the **Version control provider (VCS)** tile. +5. Enable the **Sentinel** option as the policy framework. +6. Specify a name and description for the set. +7. Configure any additional options for the policy set and click **Next**. +8. Choose the GitHub connection type, then choose the repository you created in [Set up a repository for the policies](#set-up-a-repository-for-the-policies). +9. If the `sentinel.hcl` policy set file is stored in a subfolder, specify the path to the file in the **Policies path** field. The default is the root directory. +10. If you want to apply updated policy sets to the workspace from a specific branch, specify the name in the **VCS branch** field. The default is the default branch configured for the repository. +11. Click **Next** and specify any additional parameters you want to pass to the Sentinel runtime and click **Connect policy set** to finish applying the policies to the workspace. + +Run a plan in the workspace to trigger the connected policies. Refer to [Start a Terraform run](/terraform/enterprise/run/remote-operations#starting-runs) for additional information. + +## Next steps + +- Group your policies into sets and apply them to your workspaces. Refer to [Create policy sets](/terraform/enterprise/policy-enforcement/manage-policy-sets#create-policy-sets) for additional information. +- View results and address Terraform runs that do not comply with your policies. Refer to [View results](/terraform/enterprise/policy-enforcement/view-results) for additional information. +- You can also view Sentinel policy results in JSON format. Refer to [View Sentinel JSON results](/terraform/enterprise/policy-enforcement/view-results/json) for additional information. diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/policy-enforcement/test-sentinel.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/policy-enforcement/test-sentinel.mdx new file mode 100644 index 0000000000..91be1391fe --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/policy-enforcement/test-sentinel.mdx @@ -0,0 +1,277 @@ +--- +page_title: Generate mock Sentinel data with Terraform Enterprise +description: >- + Learn how to generate mock Sentinel data to test your policies with Terraform + Enterprise. +source: terraform-docs-common +--- + +# Generate mock Sentinel data with Terraform + +We recommend that you test your Sentinel policies extensively before deploying +them within HCP Terraform. An important part of this process is mocking +the data that you wish your policies to operate on. + + + +@include 'tfc-package-callouts/policies.mdx' + + + +Due to the highly variable structure of data that can be produced by an +individual Terraform configuration, HCP Terraform provides the ability to +generate mock data from existing configurations. This can be used to create +sample data for a new policy, or data to reproduce issues in an existing one. + +Testing policies is done using the [Sentinel +CLI](/sentinel/docs/commands). More general information on +testing Sentinel policies can be found in the [Testing +section](/sentinel/docs/writing/testing) of the [Sentinel +runtime documentation](https://docs.hashicorp.com/sentinel). + +~> **Be careful!** Mock data generated by HCP Terraform directly exposes any +and all data within the configuration, plan, and state. Terraform attempts to +scrub sensitive data from these mocks, but we do not guarantee 100% accuracy. +Treat this data with care, and avoid generating mocks with live sensitive data +when possible. Access to this information requires [permission to download +Sentinel mocks](/terraform/enterprise/users-teams-organizations/permissions) for the +workspace where the data was generated. + +[permissions-citation]: #intentionally-unused---keep-for-maintainers + +## Generating Mock Data Using the UI + +Mock data can be generated using the UI by expanding the plan status section of +the run page, and clicking on the **Download Sentinel mocks** button. + +![sentinel mock generate ui](/img/docs/download-mocks.png) + +For more information on creating a run, see the +[Terraform Runs and Remote Operations](/terraform/enterprise/run/remote-operations) section of the docs. + +If the button is not visible, then the plan is ineligible for mock generation or +the user doesn't have the necessary permissions. See [Mock Data +Availability](#mock-data-availability) for more details. + +## Generating Mock Data Using the API + +Mock data can also be created with the [Plan Export +API](/terraform/enterprise/api-docs/plan-exports). + +Multiple steps are required for mock generation. The export process is +asynchronous, so you must monitor the request to know when the data is generated +and available for download. + +1. Get the plan ID for the run that you want to generate the mock for by + [getting the run details](/terraform/enterprise/api-docs/run#get-run-details). + Look for the `id` of the `plan` object within the `relationships` section of + the return data. +2. [Request a plan + export](/terraform/enterprise/api-docs/plan-exports#create-a-plan-export) using the + discovered plan ID. Supply the Sentinel export type `sentinel-mock-bundle-v0`. +3. Monitor the export request by [viewing the plan + export](/terraform/enterprise/api-docs/plan-exports#show-a-plan-export). When the + status is `finished`, the data is ready for download. +4. Finally, [download the export + data](/terraform/enterprise/api-docs/plan-exports#download-exported-plan-data). + You have up to an hour from the completion of the export request - after + that, the mock data expires and must be re-generated. + +## Using Mock Data + +-> **Note:** The v2 mock files are only available on Terraform 0.12 and higher. + +Mock data is supplied as a bundled tarball, containing the following files: + + mock-tfconfig.sentinel # tfconfig mock data + mock-tfconfig-v2.sentinel # tfconfig/v2 mock data + mock-tfplan.sentinel # tfplan mock data + mock-tfplan-v2.sentinel # tfplan/v2 mock data + mock-tfstate.sentinel # tfstate mock data + mock-tfstate-v2.sentinel # tfstate/v2 mock data + mock-tfrun.sentinel # tfrun mock data + sentinel.hcl # sample configuration file + +The sample `sentinel.hcl` file contains mappings to the mocks so that you +can get started testing with `sentinel apply` right away. For `sentinel test`, +however, we recommend a more detailed layout. + +We recommend placing the files for `sentinel test` in a subdirectory +of the repository holding your policies, so they don't interfere with the +command's automatic policy detection. While the test data is Sentinel code, it's +not a policy and will produce errors if evaluated like one. + + . + ├── foo.sentinel + ├── sentinel.hcl + ├── test + │   └── foo + │   ├── fail.hcl + │   └── pass.hcl + └── testdata + ├── mock-tfconfig.sentinel + ├── mock-tfconfig-v2.sentinel + ├── mock-tfplan.sentinel + ├── mock-tfplan-v2.sentinel + ├── mock-tfstate.sentinel + ├── mock-tfstate-v2.sentinel + └── mock-tfrun.sentinel + +Each configuration that needs access to the mock should reference the mock data +files within the `mock` block in the Sentinel configuration file. + +For `sentinel apply`, this path is relative to the working directory. Assuming +you always run this command from the repository root, the `sentinel.hcl` +configuration file would look like: + +```hcl +mock "tfconfig" { + module { + source = "testdata/mock-tfconfig.sentinel" + } +} + +mock "tfconfig/v1" { + module { + source = "testdata/mock-tfconfig.sentinel" + } +} + +mock "tfconfig/v2" { + module { + source = "testdata/mock-tfconfig-v2.sentinel" + } +} + +mock "tfplan" { + module { + source = "testdata/mock-tfplan.sentinel" + } +} + +mock "tfplan/v1" { + module { + source = "testdata/mock-tfplan.sentinel" + } +} + +mock "tfplan/v2" { + module { + source = "testdata/mock-tfplan-v2.sentinel" + } +} + +mock "tfstate" { + module { + source = "testdata/mock-tfstate.sentinel" + } +} + +mock "tfstate/v1" { + module { + source = "testdata/mock-tfstate.sentinel" + } +} + +mock "tfstate/v2" { + module { + source = "testdata/mock-tfstate-v2.sentinel" + } +} + +mock "tfrun" { + module { + source = "testdata/mock-tfrun.sentinel" + } +} +``` + +For `sentinel test`, the paths are relative to the specific test configuration +file. For example, the contents of `pass.hcl`, asserting that the result of the +`main` rule was `true`, would be: + + mock "tfconfig" { + module { + source = "../../testdata/mock-tfconfig.sentinel" + } + } + + mock "tfconfig/v1" { + module { + source = "../../testdata/mock-tfconfig.sentinel" + } + } + + mock "tfconfig/v2" { + module { + source = "../../testdata/mock-tfconfig-v2.sentinel" + } + } + + mock "tfplan" { + module { + source = "../../testdata/mock-tfplan.sentinel" + } + } + + mock "tfplan/v1" { + module { + source = "../../testdata/mock-tfplan.sentinel" + } + } + + mock "tfplan/v2" { + module { + source = "../../testdata/mock-tfplan-v2.sentinel" + } + } + + mock "tfstate" { + module { + source = "../../testdata/mock-tfstate.sentinel" + } + } + + mock "tfstate/v1" { + module { + source = "../../testdata/mock-tfstate.sentinel" + } + } + + mock "tfstate/v2" { + module { + source = "../../testdata/mock-tfstate-v2.sentinel" + } + } + + mock "tfrun" { + module { + source = "../../testdata/mock-tfrun.sentinel" + } + } + + test { + rules = { + main = true + } + } + +## Mock Data Availability + +The following factors can prevent you from generating mock data: + +- You do not have permission to download Sentinel mocks for the workspace. + ([More about permissions.](/terraform/enterprise/users-teams-organizations/permissions)) + Permission is required to protect the possibly sensitive data which can be + produced via mock generation. +- The run has not progressed past the planning stage, or did not create a plan + successfully. +- The run progressed past the planning stage prior to July 23, 2021. Prior to this date, HCP Terraform only kept JSON plans for 7 days. + +[permissions-citation]: #intentionally-unused---keep-for-maintainers + +If a plan cannot have its mock data exported due to any of these reasons, the +**Download Sentinel mocks** button within the plan status section of the UI will +not be visible. + +-> **Note:** Only a successful plan is required for mock generation. Sentinel can still generate the data if apply or policy checks fail. diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/policy-enforcement/view-results/index.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/policy-enforcement/view-results/index.mdx new file mode 100644 index 0000000000..9c744110c5 --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/policy-enforcement/view-results/index.mdx @@ -0,0 +1,74 @@ +--- +page_title: View policy enforcement results in Terraform Enterprise +description: >- + Learn how to view and override policy enforcement results in Terraform + Enterprise. +source: terraform-docs-common +--- + +# View policy enforcement results + +When you add [policy sets](/terraform/enterprise/policy-enforcement/manage-policy-sets) to a workspace, HCP Terraform enforces those policy sets on every Terraform run. HCP Terraform displays the policy enforcement results in the UI for each run. Depending on each policy’s [enforcement level](/terraform/enterprise/policy-enforcement/manage-policy-sets#policy-enforcement-levels), policy failures can also stop the run and prevent Terraform from provisioning infrastructure. + + + +@include 'tfc-package-callouts/policies.mdx' + + + +## Policy Evaluation Run Stages + +HCP Terraform only evaluates policies for successful plans. HCP Terraform evaluates Sentinel and OPA policy sets separately and at different points in the run. + +- Sentinel policy checks occur after Terraform completes the plan and after both [run tasks](/terraform/enterprise/workspaces/settings/run-tasks) and [cost estimation](https://terraform.io/cloud-dodcs/cost-estimation). This order lets you write Sentinel policies to restrict costs based on the data in the cost estimates. +- Sentinel policy evaluations occur after Terraform completes the plan and after any run tasks. HCP Terraform evaluates Sentinel policy evaluations immediately before cost estimation. +- OPA policy evaluations occur after Terraform completes the plan and after any run tasks. HCP Terraform evaluates OPA policies immediately before cost estimation. + +Refer to [Run States and Stages](/terraform/enterprise/run/states) for more details. + +## View Policy Results + +To view the policy results for both Sentinel and OPA policies: + +1. Go to your workspace and navigate to the **Runs** page. +2. Click a run to view its details. + +HCP Terraform displays a timeline of the run’s events. For workspaces with both Sentinel and OPA policy sets, the run details page displays two separate run events: **OPA policies** for OPA policy sets and **Policy check** for Sentinel policy sets. + +Click a policy evaluation event to view policy results and details about any failed policies. + +-> **Note:** For Sentinel, the Terraform CLI also prints policy results for [CLI-driven runs](/terraform/enterprise/run/cli). CLI support for policy results is not available for OPA. + +## Override Policies + +You need [manage policy overrides](/terraform/enterprise/users-teams-organizations/permissions#manage-policy-overrides) permissions to override failed Sentinel and OPA policies. + +Sentinel and OPA have different policy enforcement levels that determine when you need to override failed policies to allow a run to continue. +To override failed policies, go to the run details page and click **Override and Continue** at the bottom. + +For Sentinel only, you can also override `soft-mandatory` policies with the Terraform CLI. Run the `terraform apply` command and then enter `override` when prompted. + +-> **Note:** HCP Terraform does not allow policy overrides for [no-operation plans containing no infrastructure changes](/terraform/enterprise/run/modes-and-options#allow-empty-apply), unless you choose the **Allow empty apply** option when starting the run. + +### Sentinel + +#### Policy checks + +Policies with an `advisory` enforcement level never stop runs. If they fail, HCP Terraform displays a warning in the policy results and the run continues. + +You can override `soft-mandatory` policies to allow the run to continue. Overriding failed policies on a run does not affect policy evaluations on future runs in that workspace. + +You cannot override `hard-mandatory` policies, and all of these policies must pass for the run to continue. + +#### Policy evaluations + +Policies with an `advisory` enforcement level never stop runs. If they fail, HCP Terraform displays a warning in the policy results and the run continues. + +When running Sentinel policies as policy evaluations, `soft-mandatory` and `hard-mandatory` enforcement levels are internally converted to `mandatory` enforcement level. +You can override `mandatory` policies to allow the run to continue. + +### OPA + +Policies with an `advisory` enforcement level never stop runs. If they fail, HCP Terraform displays a warning in the policy results and the run continues. + +You can override `mandatory` policies to allow the run to continue. diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/policy-enforcement/view-results/json.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/policy-enforcement/view-results/json.mdx new file mode 100644 index 0000000000..c0fa9f3707 --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/policy-enforcement/view-results/json.mdx @@ -0,0 +1,59 @@ +--- +page_title: View and filter Sentinel JSON data +description: Learn how to view and filter Sentinel JSON data. +source: terraform-docs-common +--- + +# View and filter Sentinel JSON data + +When using the HCP Terraform UI, Sentinel policy check results are available +both in a human-readable log form, and in a more detailed, lower-level JSON +form. While the logs may suppress some output that would make the logs harder +to read, the JSON output exposes the lower-level output directly to you. Being +able to parse this data in its entirety is especially important when working +with [non-boolean rule +data](/sentinel/docs/language/rules#non-boolean-values) in +a policy designed to work with Sentinel 0.17.0 and higher. + + + +@include 'tfc-package-callouts/policies.mdx' + + + +-> The JSON data exposed is the same as you would see when using the [policy +checks API](/terraform/enterprise/api-docs/policy-checks), with the data starting at the +`sentinel` key. + +## Viewing JSON Data + +To view the JSON data, expand the policy check on the [runs +page](/terraform/enterprise/run/manage) if it is not already expanded. The logs are +always displayed first, so click the **View JSON Data** button to view the JSON +data. You can click the **View Logs** button to switch back to the log view. + +![viewing json data](/img/docs/sentinel-view-json.png) + +## Filtering JSON Data + +The JSON data is filterable using a [jq](https://stedolan.github.io/jq/)-subset +filtering language. See the [JSON +filtering](/terraform/enterprise/workspaces/json-filtering) page for more details on +the filtering language. + +Filters are entered by putting the filter in the aptly named **filter** box in +the JSON viewer. After entering the filter, pressing **Apply** or the enter key +on your keyboard will apply the filter. The filtered results, if any, are +displayed in result box. Clearing the filter will restore the original JSON +data. + +![entering a json filter](/img/docs/sentinel-json-enter-filter.png) + +### Quick-Filtering `main` Rules + +Clicking the **Filter "main" rules** button will quickly apply a filter that +shows you the results of the `main` rule for every policy in the policy set. You +can use this to quickly get the results of each policy in the set, without +having navigate through the rest of the policy result data. + +![using the quick filter](/img/docs/sentinel-json-quick-filter.png) diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/projects/best-practices.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/projects/best-practices.mdx new file mode 100644 index 0000000000..b1b60daf38 --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/projects/best-practices.mdx @@ -0,0 +1,40 @@ +--- +page_title: Best Practices - Projects - Terraform Enterprise +description: >- + Best practices to structure your configuration and Terraform Enterprise + projects +source: terraform-docs-common +--- + +# Project Best Practices + +Projects let you group and scope access to your workspaces. You can group related workspaces into projects and give teams more permissive access to individual projects rather than granting them permissions to the entire organization. + +Projects offer several advantages to help you further develop your workspace strategy: + +- **Focused workspace view**: You can scope which workspaces HCP Terraform displays by project, allowing for a more organized view. +- **Simplified workspace management**: You can create project-level permissions and variable sets that apply to all current and future workspaces in the project. For example, you can create a project variable set containing your cloud provider credentials for all workspaces in the project to access. +- **Reduced risk with centralized control**: You can scope project permissions to only grant teams administrator access to the projects and workspaces they need. + +## Recommendations + +When using projects, we recommend the following: + +- **Automate with Terraform**: Automate the creation of projects, variable sets, and teams together using the [TFE provider](https://registry.terraform.io/providers/hashicorp/tfe/latest/docs). +- **Designate a landing zone project**: Landing zone projects contain workspaces used to create all other projects, teams, and workspaces. This lets you have a variable set that includes the organization token, which the TFE provider can use to create other resources in your organization. You can also create a [Sentinel policy](/terraform/enterprise/policy-enforcement) to prevent users in other projects from accessing the organization token. +- **Maintain least privilege**: Restrict the number of project administrators to maintain the principal of least privilege. + +## Project boundaries + +Finally, decide on the logical boundaries for your projects. Some considerations to keep in mind include: + +- **Provider boundaries**: For smaller organizations, creating one project per cloud account may make it easier to manage access. Projects can use [dynamic credentials](/terraform/tutorials/cloud/dynamic-credentials) by configuring a project variable set to avoid hard-coding long-lived static credentials. +- **Least privilege**: You can create teams and grant them access to projects with workspaces of similar areas of ownership. For example, a production networking workspace should be in a separate project from a development compute workspace. +- **Use variable sets**: Project-wide variable sets let you configure and reuse values such as default tags for cost-codes, owners, and support contacts. Projects can own variable sets, enabling you to separate management and access to sets between projects. +- **Practitioner efficiency**: Consider if it makes sense for a practitioner to need to visit multiple projects to complete a deployment. + +## Next steps + +This article introduces some considerations to keep in mind as your organization matures their project usage. Being deliberate about how you use these to organize your infrastructure will ensure smoother and safer operations. To learn more about HCP Terraform and Terraform Enterprise best practices, refer to [Workspace Best Practices](/terraform/enterprise/workspaces/best-practices). To learn best practices for writing Terraform configuration, refer to the [Terraform Style Guide](/terraform/language/style). + +[HCP Terraform](/terraform/tutorials/cloud-get-started) provides a place to try these concepts hands-on, and you can [get started for free](https://app.terraform.io/public/signup/account). diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/projects/index.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/projects/index.mdx new file mode 100644 index 0000000000..62f7b9a260 --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/projects/index.mdx @@ -0,0 +1,51 @@ +--- +page_title: Projects - Terraform Enterprise +description: >- + Use projects to organize and group workspaces and create ownership boundaries + across your infrastructure. +source: terraform-docs-common +--- + +# Overview + + + +@include 'tfc-package-callouts/project-permissions.mdx' + + + +Projects let you organize your workspaces and scope access to workspace +resources. Each project has a separate permissions set that you can use to grant +teams access to all workspaces in the project, defining access control +boundaries for teams and their resources. Project-level permissions are more +granular than organization-level permissions, but more specific than individual +workspace-level grants. + +When deciding how to structure your projects, consider which groups of resources +need distinct access rules. You may wish to define projects by business units, +departments, subsidiaries, or technical teams. + +> **Hands On:** Try our [Managing +> Projects](/terraform/tutorials/cloud/projects) +> tutorial. + +## Default Project + +Every workspace must belong to exactly one project. By default, all workspaces +belong to an organization's **Default Project**. You can rename the default +project, but you cannot delete it. You can specify a workspace's project at the +time of creation and move it to a different project later. + +The “Manage Workspaces” team permission lets users create and manage workspaces. +Users with this permission can read and manage all workspaces, but new +workspaces are automatically added to the “Default Project” and users cannot +access the metadata for other projects. To create workspaces under other +projects, users also need the "Manage Projects & Workspaces" permission or the +admin role for the project they wish to use. + +## Managing Projects + +The "Manage all Projects" team permission lets users manage projects. Users with +this permission can view, edit, delete, and assign team access to all of an +organization's projects. Refer to [Managing +Projects](/terraform/enterprise/projects/manage) for more details. diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/projects/manage.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/projects/manage.mdx new file mode 100644 index 0000000000..3ad84ad7e5 --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/projects/manage.mdx @@ -0,0 +1,124 @@ +--- +page_title: Manage projects in Terraform Enterprise +description: |- + Use projects to organize and group workspaces and create ownership boundaries + across your infrastructure. +source: terraform-docs-common +--- + +# Manage projects + +This topic describes how to create and manage projects in HCP Terraform and Terraform Enterprise. A project is a folder containing one or more workspaces. + +## Requirements + +You must have the following permissions to manage projects: + +- You must be a member of a team with the **Manage all Projects** permissions enabled to create a project. Refer to [Organization Permissions](/terraform/enterprise/users-teams-organizations/permissions#organization-permissions) for additional information. +- You must be a member of a team with the **Visible** option enabled under **Visibility** in the organization settings to configure a new team's access to the project. Refer to [Team Visibility](/terraform/enterprise/users-teams-organizations/teams/manage#team-visibility) for additional information. +- You must be a member of a team with update and delete permissions to be able to update and delete teams respectively. + +To delete tags on a project, you must be member of a team with the **Admin** permission group enabled for the project. + +To create tags for a project, you must be member of a team with the **Write** permission group enabled for the project. + +## View a project + +To view your organization's projects: + +1. Sign in to [HCP Terraform](https://app.terraform.io/) or Terraform Enterprise and select **Projects** from the sidebar. +2. Search for a project that you want to view. You can use the following methods: + - Sort by column header. + - Use the search bar to search on the name of a project or a tag. +3. Click on a project's name to view more details. + +## Create a project + +1. Sign in to [HCP Terraform](https://app.terraform.io/) or Terraform Enterprise and select **Projects** from the sidebar. +2. Click **+ New project**. +3. Specify a name for the project. The name must be unique within the organization and can only include letters, numbers, inner spaces, hyphens, and underscores. +4. Add a description for the project. This field is optional. +5. Open the **Add key value tags** menu to add tags to your project. Workspaces you create within the project inherit project tags. Refer to [Define project tags](#define-project-tags) for additional information. +6. Click **+Add tag** and specify a tag key and tag value. If your organization has defined reserved tag keys, they appear in the **Tag key** field as suggestions. Refer to [Create and manage reserved tags](/terraform/enterprise/users-teams-organizations/organizations/manage-reserved-tags) for additional information. +7. Click **+ Add tag** to attach any additional tags. +8. Click **Create** to finish creating the project. + +HCP Terraform returns a new project page displaying all the project +information. + +## Edit a project + +1. Sign in to [HCP Terraform](https://app.terraform.io/) or Terraform Enterprise and select **Projects** from the sidebar. +2. Click on a project name of the project you want to edit. +3. Choose **Settings** from the sidebar. + +On this **General settings** page, you can update the project name, project +description, and delete the project. On the **Team access** page, you can modify +team access to the project. + +## Automatically destroy inactive workspaces + + + +@include 'tfc-package-callouts/ephemeral-workspaces.mdx' + + + +You can configure HCP Terraform to automatically destroy each workspace's +infrastructure in a project after a period of inactivity. A workspace +is inactive if the workspace's state has not changed within your designated +time period. + +If you configure a project to auto-destroy its infrastructure when inactive, +any run that updates Terraform state further delays the scheduled auto-destroy +time by the length of your designated timeframe. + + + +The user interface does not prompt you to approve automated destroy plans. We recommend only using this setting for development environments. + + + +To schedule an auto-destroy run after a period of workspace inactivity: + +1. Sign in to [HCP Terraform](https://app.terraform.io/) or Terraform Enterprise and navigate to the project with workspaces you want to destroy. +2. Choose **Settings** from the sidebar, then **Auto-destroy Workspaces**. +3. Click **Set up default**. +4. Select or customize a desired timeframe of inactivity. +5. Click **Confirm default**. + +You can configure an individual workspace's auto-destroy settings to override +this default configuration. Refer to [automatically destroy workspaces](/terraform/enterprise/workspaces/settings/deletion#automatically-destroy) for more information. + +## Delete a project + +You can only delete projects that do not contain stacks or workspaces. + +To delete an empty project: + +1. Sign in to [HCP Terraform](https://app.terraform.io/) or Terraform Enterprise. +2. Click **Projects**. +3. Search for a project that you want to review by scrolling down the table or + searching for a project name in the search bar above the project table. +4. Choose **Settings** from the sidebar. +5. Click the **Delete** button. A **Delete project** modal appears. +6. Click the **Delete** button to confirm the deletion. + +HCP Terraform returns to the **Projects** view with the deleted project +removed from the list. + +## Define project tags + +You can define tags stored as key-value pairs to help you organize your projects and track resource consumption. Workspaces created in the project automatically inherit the tags, but workspace administrators with appropriate permissions can attach new key-value pairs to their workspaces to override inherited tags. Refer to [Create workspace tags](/terraform/enterprise/workspaces/tags) for additional information about using tags in workspaces. + +The following rules apply to tag keys and values: + +- Tags must be one or more characters. +- Tags have a 255 character limit. +- Tags can include letters, numbers, colons, hyphens, and underscores. +- Tag values are optional. +- You can create up to 10 unique tags per workspace and 10 unique tags per project. As a result, each workspace can have up to 20 tags. +- You cannot use the following strings at the beginning of a tag key: + - `hcp` + - `hc` + - `ibm` diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/registry/add.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/registry/add.mdx new file mode 100644 index 0000000000..50b3130f06 --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/registry/add.mdx @@ -0,0 +1,79 @@ +--- +page_title: Add public providers and modules to the Terraform Enterprise private registry +description: >- + Learn how to add providers and modules from the public Terraform registry to + your organization's private registry. +source: terraform-docs-common +--- + +[vcs]: /terraform/enterprise/vcs + +# Add public providers and modules to the HCP Terraform private registry + +You can add providers and modules from the public [Terraform Registry](/terraform/registry) to your HCP Terraform private registry. The private registry stores a pointer to these public providers and modules so that you can view their data from within HCP Terraform. This lets you clearly designate which public providers and modules are recommended for the organization and makes their supporting documentation and examples centrally accessible. + +-> **Note:** Your Terraform Enterprise instance must allow access to `registry.terraform.io` and `https://yy0ffni7mf-dsn.algolia.net/`. + +[permissions-citation]: #intentionally-unused---keep-for-maintainers + +You can add providers and modules through the UI as detailed below or through the [Registry Providers API](/terraform/enterprise/api-docs/private-registry/providers) and the [Registry Modules API](/terraform/enterprise/api-docs/private-registry/modules#create-a-module-with-no-vcs-connection-). + +## Permissions + +All members of an organization can view and use public providers and modules. Members of the [owners team](/terraform/enterprise/users-teams-organizations/permissions#organization-owners) and teams with [Manage Private Registry permissions](/terraform/enterprise/users-teams-organizations/permissions#manage-private-registry) can add and delete them from the private registry. + +## Adding a Public Provider or Module + +> **Hands-on:** Try the [Add Public Providers and Modules to your Private Registry](/terraform/tutorials/modules/private-registry-add) tutorial and [Share Modules in the Private Registry](/terraform/tutorials/modules/module-private-registry-share) tutorials. + +To add a public provider or module: + +1. Sign in to [HCP Terraform](https://app.terraform.io/) or Terraform Enterprise and navigate to the organization you want to add a public provider or module to. + +2. Select **Registry** from the sidebar. The organization's private registry appears with a list of available providers and modules. + +3. Click **Search public registry**. The **Public Registry Search** page appears. + +4. Enter any combination of namespaces (such as hashicorp), and module or provider names into the search field. You can click **Providers** and **Modules** to toggle between lists of providers and modules that meet the search criteria. + +5. Do one of the following to add a provider or module to your private registry: + - Hover over the provider or module and click **+ Add**. + - Click the provider or module to view its details and then click **Add to HCP Terraform**. + +6. Click **Add to organization** in the dialog box. Members of your organization can now begin using it from the private registry. + +## Enabling and Disabling No-Code Provisioning + + + +@include 'tfc-package-callouts/nocode.mdx' + + + +You can enable no-code provisioning for public modules after adding them to your registry. + +To support the auto-apply workflow, ensure that downstream users can automatically load provider credentials into their new no-code workspaces. You can enable access by either creating a [global or project-scoped variable set](/terraform/enterprise/workspaces/variables/managing-variables#variable-sets) with the credentials for the module's provider, or by accessing outputs with credentials from other workspaces. Refer to [Provider Credentials](/terraform/enterprise/no-code-provisioning/module-design#provider-credentials) for more details. + +To enable no-code provisioning: + +1. Verify that the module meets the [requirements for no-code provisioning](/terraform/enterprise/no-code-provisioning/module-design#requirements). +2. Click the module to view its details. +3. Select **Enable no-code provisioning** from the **Manage Module for Organization** dropdown. + +Your module’s details page now has a **No-Code Ready** badge to indicate that it supports no-code provisioning. + +To disable no-code provisioning, select **Disable no-code provisioning** from the **Manage Module for Organization** dropdown. Disabling also removes the **No-Code Ready** badge from the module’s details page. + +## Removing a Public Provider or Module + +Removing a public provider or module from a private registry does not remove it from the public Terraform Registry. Users in the organization can still use the removed provider or module without changing their configurations. + +To remove a public provider or module from an organization's private registry: + +1. Sign in to [HCP Terraform](https://app.terraform.io/) or Terraform Enterprise and navigate to the organization whose registry you want to remove a provider or module from. + +2. Select **Registry** from the sidebar. The organization's private registry appears with a list of available providers and modules. + +3. Select the provider or module to view its details, open the **Manage for Organization** menu, and click **Remove from organization** (providers) or **Delete module** (modules). + +4. Enter the provider or module name in the dialog box to confirm and then click **Remove** (providers) or **Delete** (modules). The provider or module no longer appears in the organization's private registry. diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/registry/design.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/registry/design.mdx new file mode 100644 index 0000000000..935081361e --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/registry/design.mdx @@ -0,0 +1,64 @@ +--- +page_title: Use the configuration designer in Terraform Enterprise +description: >- + Learn how to outline a configuration with private modules and define variables + with the Terraform Enterprise configuration designer. +source: terraform-docs-common +--- + +# Use the configuration designer + +HCP Terraform's private registry includes a configuration designer that can help you spend less time writing boilerplate code in a module-centric Terraform workflow. + +The configuration designer lets you outline a configuration for a new workspace by choosing any number of private modules. It then lists those modules' variables as a fillable HTML form, with a helper interface for finding values that you can interpolate. When you are finished, the designer returns the text of a `main.tf` configuration. This is the same Terraform code you would have written in your text editor. + +## Accessing the Configuration Designer + +Go to your organization's private registry, and then click **<> Design Configuration**. + +The **Select Modules** page appears. + +## Adding Modules + +Filter and search the left side of the **Select Modules** page to find private modules that you can add to your configuration. + +Click **Add Module** for all of the modules you want to use in your configuration. These modules appear in the **Selected Modules** list on the right side of the page. + +### Setting Versions + +Selecting a module adds its most recent version to the configuration. To specify a different version: + +1. Click the module's version number from the **Selected Modules** list on the right. +2. Select an alternate version from the menu. + +## Setting Variables + +When you finish selecting modules, click **Next »** to go to the **Set Variables** page. + +The left side of this page lists your chosen modules, and the right side lists all variables for the currently selected module. Each variable is labeled as required or optional. + +You can switch between modules without losing your work; click a module's **Configure** button to switch to its variable list. + +Once you set a value for all of a module's required variables, its **Configure** button changes to a green **Configured** button. + +### Interpolation Searching + +Variable values can be literal strings, or can interpolate other values. When you start typing an interpolation token (`${`), the designer displays a help message. As you continue typing, it searches the available outputs in your other selected modules, as well as outputs from workspaces where you are authorized to read state outputs. You can select one of these search results, or type a full name if you need to reference a value HCP Terraform does not know about. + +[permissions-citation]: #intentionally-unused---keep-for-maintainers + +### Deferring Variables + +Sometimes, configuration users should be able to set certain variables according to their use cases. + +Select the **Deferred** checkbox to delegate a variable to configuration users. This ties the variable's value to a new top-level Terraform variable with no default value. All users that create a workspace from your configuration will have to provide a value for that variable. + +## The Output Configuration + +When all modules are configured, click **Next »**. + +The **Publish** page appears. Use the **Preview configuration** menu to review the generated code. + +The configuration designer does not create any repositories or workspaces. To create workspaces with the configuration, you must download the generated code, save it as the `main.tf` file in a new directory, and commit it to version control. After you download the code, you can make any necessary changes or additions. For example, you may want to add non-module resources. + +When you are sure you have downloaded the results, click **Done** to discard the configuration. HCP Terraform does not save output from previous configuration designer runs. diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/registry/index.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/registry/index.mdx new file mode 100644 index 0000000000..0405c24452 --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/registry/index.mdx @@ -0,0 +1,27 @@ +--- +page_title: Terraform Enterprise private registry overview +description: >- + The Terraform Enterprise private registry lets you share private modules and + providers across your organization. +source: terraform-docs-common +--- + +# HCP Terraform private registry overview + +HCP Terraform's private registry works similarly to the [public Terraform Registry](/terraform/registry) and helps you share [Terraform providers](/terraform/language/providers) and [Terraform modules](/terraform/language/modules) across your organization. It includes support for versioning and a searchable list of available providers and modules. + +> **Hands-on:** Try the [Add Public Providers and Modules to your Private Registry](/terraform/tutorials/modules/private-registry-add) tutorial and [Share Modules in the Private Registry](/terraform/tutorials/modules/module-private-registry-share) tutorials. + +## Public Providers and Modules + +[Public modules and providers](/terraform/enterprise/registry/add) are hosted on the public Terraform Registry and HCP Terraform can automatically synchronize them to an organization's private registry. This lets you clearly designate which public providers and modules are recommended for the organization and makes their supporting documentation and examples centrally accessible. + +-> **Note:** Your Terraform Enterprise instance must allow access to `registry.terraform.io` and `https://yy0ffni7mf-dsn.algolia.net/`. + +## Private Providers and Modules + +[Private providers](/terraform/enterprise/registry/publish-providers) and [private modules](/terraform/enterprise/registry/publish-modules) are hosted on an organization's private registry and are only available to members of that organization. In Terraform Enterprise, private providers and modules are also available to other organizations that are [configured to share](/terraform/enterprise/admin/application/registry-sharing) with that organization. + +## Managing Usage + +You can create [Sentinel policies](/terraform/enterprise/policy-enforcement) to manage how members of your organization can use modules from the private registry. For example, you can mandate that all non-root modules in Terraform configurations must be private or public modules from your own private registry. You can also apply a policy that requires all modules to use recent versions. Refer to our [example policy on GitHub](https://github.com/hashicorp/terraform-sentinel-policies/blob/main/cloud-agnostic/http-examples/use-recent-versions-from-pmr.sentinel). diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/registry/manage-module-versions.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/registry/manage-module-versions.mdx new file mode 100644 index 0000000000..2c83eeefec --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/registry/manage-module-versions.mdx @@ -0,0 +1,218 @@ +--- +page_title: Manage module versions in Terraform Enterprise +description: >- + Learn how to deprecate and revoke module versions to signal the end of support + for those versions. Deprecating a module version warns users to upgrade soon, + and revoking a module version signals you no longer maintain support for a + module version and blocks new consumers. +source: terraform-docs-common +--- + +# Manage module versions + +<>{/* TODO: remove revoke references here */} + +You can manage the lifecycle of module versions in your organization’s private registry by deprecating or revoking module versions as you stop maintaining and supporting those versions. + + + +@include "tfc-package-callouts/manage-module-versions.mdx" + + + +Deprecating a module version adds warnings to the module's registry page and warnings in the run outputs of any users of that version. + + + +Revoking a module version adds warnings to the module's registry page, warnings in the run outputs of existing users, and prevents new users from consuming that version. + + + +You can also [deprecate or revoke module versions using the HCP Terraform API](/terraform/enterprise/api-docs/private-registry/manage-module-versions). + +<>{/* TODO: remove revoke references here */} + +## Overview + +Deprecating a private module version enables platform teams and module authors to signal to consumers that a version is still maintained and supported but is not recommended. HCP Terraform urges existing and new users of deprecated versions to upgrade that version in their configuration. The private registry also denotes which module versions are deprecated, alerting new consumers to use a non-deprecated version. + +You can revert a module version’s deprecation if you decide to continue supporting that version. Reverting a version’s deprecation removes all warnings from that version in both the module’s registry page and the run outputs of that version’s consumers. + + + +You can revoke a private module to mark that you not longer support it. Revoking a module version adds similar warnings to deprecation in a module's registry page and the run outputs of current consumers. Revoking a module version also blocks the runs of any new users attempting to add that version to their configuration. + +Reverting a module version’s revocation sets it back to a deprecated state, signaling that the version is still maintained and supported but not recommended. + + + +## Requirements + +To deprecate a module version or to revert a version’s deprecation: + +- you must have permission to manage [private registry modules](/terraform/enterprise/users-teams-organizations/permissions#manage-private-registry) +- the module must be in the [private](/terraform/enterprise/registry/publish-modules) registry + +- you must be a member of an organization on the HCP Terraform **Plus** edition + + + + +To revoke or to revert a module version’s status: + +- you must have permission to manage [private registry modules](/terraform/enterprise/users-teams-organizations/permissions#manage-private-registry) +- the module must be in an organization's [private registry](/terraform/enterprise/registry/publish-modules) +- you must be a member of an organization on the HCP Terraform **Premium** edition + + +<>{/* TODO: don't forget to exclude PNP above when remove tags */} + +## Deprecate a module version + +To deprecate a module version, perform the following steps: + +1. Sign in to [HCP Terraform](https://app.terraform.io/) or Terraform Enterprise and navigate to your organization. +2. Choose **Registry** in the sidebar then find the module you want to deprecate a version of. +3. Open the **Manage module for organization** dropdown. +4. Select a module version for deprecation. +5. You can optionally provide an explanation in the **Reason for module deprecation** field to help users understand why this module version is being deprecated. This custom message is displayed to module users in deprecation warnings. +6. You can optionally enter a URL into the **Link to additional information** field if there is a website where consumers can learn more about that module version’s deprecation. +7. Click **Deprecate**. + +If the module version you are deprecating has the [**No-code ready**](/terraform/enterprise/no-code-provisioning/module-design#updating-a-module-s-version) pin, then HCP Terraform lets you select another version to create no-code modules from. We recommend adding the **No-code ready** pin to another non-deprecated module version so that users provisioning workspaces from your module can use a version that you plan to continue supporting. + +### Deprecation warnings + +After you deprecate a module version, consumers of that version receive warnings in their operation outputs urging them to update that version in both HCP Terraform and the Terraform CLI. + +~> **Note**: Only workspaces in the [remote or agent execution modes](/terraform/enterprise/workspaces/settings#execution-mode) can receive warnings for a module version’s deprecation. + +If you provided a reason for a module version’s deprecation, then deprecation warnings contain that reason: + + + +```shell +Found the following deprecated module versions, consider using an updated version. + +``` + + + +A run’s output mode affects where a version's deprecation warning appears. If a workspace is in the default [**Structured Run Output**](/terraform/enterprise/workspaces/settings#user-interface) mode, then module deprecation warnings show up under a run’s **Diagnostics** dropdown. + +If a run is in the **Console UI** mode, module deprecation warnings appear in the run’s logs: + + + +```shell +Warning: Deprecated modules found, consider installing an updating version. The following are affected: +Version X.X.X of +``` + + + +## Revert the deprecation of a module version + +To revert a module version’s deprecation, perform the following steps: + +1. Sign in to [HCP Terraform](https://app.terraform.io/) or Terraform Enterprise and navigate to your organization. +2. Choose **Registry** in the sidebar, then find the module version you want to revert the deprecation of. +3. Open the **Manage module for organization** dropdown. +4. Select **Revert module version deprecation X.X.X**. +5. Click **Revert Deprecation**. + +Reverting the deprecation of a module version removes all warnings from that version in both the module’s registry page and in the run outputs of that module version’s consumers. + +<>{/* TODO: add TFE to bullet points */} + + + +## Revoke a module version + +To revoke a module version, perform the following steps: + +1. Sign in to [HCP Terraform](https://app.terraform.io/) and navigate to your organization. +2. Choose **Registry** in the sidebar, then select the module version you want to revoke. +3. If you have not already, [deprecate the module version](#deprecate-a-module-version) you want to revoke. +4. Open the **Manage module for organization** dropdown. +5. Optionally explain why you are deprecating this module version in the **Reason for module revocation** field. This custom message is displayed to users in revocation run warnings and on the registry page. +6. Optionally, enter a URL into the **Link to additional information** field if there is a website where consumers can learn more about that module version’s revocation. +7. Click **Revoke**. + +If the module version you are revoking has the [**No-code ready**](/terraform/enterprise/no-code-provisioning/module-design#updating-a-module-s-version) pin, you must select another version to create no-code modules from. This is to ensure users provisioning workspaces from your module use a version that you are currently supporting. + +### Revocation warnings + +Like deprecation, revoking a module version displays a warning on the private registry and sends current consumers warnings in operation outputs in HCP Terraform and the Terraform CLI. The difference between deprecation and revocation is that Terraform blocks new consumers from using a revoked module version by erroring out in runs. + +~> **Note**: Only workspaces in the [remote or agent execution modes](/terraform/enterprise/workspaces/settings#execution-mode) can receive warnings for a module version’s revocation. + +After you revoke a module version, current consumers of that version receive warnings in their operation outputs in HCP Terraform and the Terraform CLI. If you provided a reason for a module version’s revocation, HCP Terraform displays that reason to users in run outputs: + + + +```shell +This run references revoked module versions, but this will proceed due to referencing a previously used module version. +Module version X.X.X of is revoked. + + +``` + + + +A run’s output mode affects where a version's revocation warnings appear. If a workspace is in the default [**Structured Run Output**](/terraform/enterprise/workspaces/settings#user-interface) mode, then module revocation warnings show up under a run’s **Diagnostics** dropdown. + +If a run is in the **Console UI** mode, module revocation warnings appear in the run’s logs: + + + +```shell +Warning: This run will continue because it references a revoked module version that was previously used in this workspace. Net new workspaces that reference this module version will be blocked from making new runs. +Version X.X.X of + + +``` + + + +If a new user attempts to add a revoked module version to their configuration, their runs fail. If you provided a reason for a module version’s revocation, HCP Terraform displays that reason to users in run outputs: + + + +```shell +This run references revoked module versions, this run will not proceed. +Module version of is revoked. + +``` + + + +A run’s output mode affects where a module revocation’s warning appears. If a run set to the default [**Structured Run Output**](/terraform/enterprise/workspaces/settings#user-interface) mode, then module revocation warnings show up under a run’s **Diagnostics** dropdown. + +If a run is in the **Console UI** mode, module revocation warnings appear in the run’s logs: + + + +```shell +│ Alert: Revoked modules found, this run will not continue. The following modules have been revoked: +│ +│ Version X.X.X of +│ +│ +``` + + + +## Revert the revocation of a module version + +When you revert the revocation of a module version, HCP Terraform sets that version as deprecated, signaling that it is still maintained and supported but not recommended. Deprecated module versions still produce warnings in the registry and run outputs, but new users can still use them. + +To revert a module version’s revocation, perform the following steps: + +1. Sign in to [HCP Terraform](https://app.terraform.io/) and navigate to your organization. +2. Choose **Registry** in the sidebar, then find the revoked module version you want to revert. +3. Open the **Manage module for organization** dropdown. +4. Select **Revert module version revocation X.X.X**. +5. Click **Revert**. + + diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/registry/publish-modules.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/registry/publish-modules.mdx new file mode 100644 index 0000000000..657f740572 --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/registry/publish-modules.mdx @@ -0,0 +1,165 @@ +--- +page_title: Publish private modules to the Terraform Enterprise private registry +description: >- + Use the Terraform Enterprise private registry to publish and share private + modules across your organization. +source: terraform-docs-common +--- + +[vcs]: /terraform/enterprise/vcs + +# Publish private modules to the HCP Terraform private registry + +> **Hands-on:** Try the [Share Modules in the Private Module Registry](/terraform/tutorials/modules/module-private-registry-share) tutorial. + +In addition to [adding modules from the Terraform Registry](/terraform/enterprise/registry/add), you can publish private modules to an organization's HCP Terraform private registry. The registry handles downloads and controls access with HCP Terraform API tokens, so consumers do not need access to the module's source repository, even when running Terraform from the command line. + +The private registry uses your configured [Version Control System (VCS) integrations][vcs] and defers to your VCS provider for most management tasks. For example, your VCS provider handles new version releases. The only manual tasks are adding a new module and deleting module versions. + +[permissions-citation]: #intentionally-unused---keep-for-maintainers + +## Permissions + +Private modules are only available to members of the organization where you add them. In Terraform Enterprise, they are also available to organizations that you configure to [share modules](/terraform/enterprise/admin/application/registry-sharing) with that organization. + +Members of the [owners team](/terraform/enterprise/users-teams-organizations/permissions#organization-owners) and teams with [Manage Private Registry permissions](/terraform/enterprise/users-teams-organizations/permissions#manage-private-registry) can publish and delete modules from the private registry. + +## Preparing a Module Repository + +After you configure at least one [connection to a VCS provider][vcs], you can publish a new module by specifying a properly formatted VCS repository (details below). The registry automatically detects the rest of the information it needs, including the module's name and its available versions. + +A module repository must meet all of the following requirements before you can add it to the registry: + +- **Location and permissions:** The repository must be in one of + your configured [VCS providers][vcs], and HCP Terraform's VCS user account must have admin access to the repository. The registry needs admin access to create the webhooks to import new module versions. GitLab repositories must be in the main organization or group, and not in any subgroups. + +- **Named `terraform--`:** Module repositories must use this + three-part name format, where `` reflects the type of infrastructure + the module manages and `` is the main provider where it creates that + infrastructure. The `` segment must be all lowercase. The `` + segment can contain additional hyphens. Examples: `terraform-google-vault` or + `terraform-aws-ec2-instance`. + +- **Standard module structure:** The module must adhere to the + [standard module structure](/terraform/language/modules/develop/structure). + This allows the registry to inspect your module and generate documentation, + track resource usage, and more. + +## Publishing a New Module + +You can publish modules through the UI as shown below or with the [Registry Modules API](/terraform/enterprise/api-docs/private-registry/modules). The API also supports publishing modules without a VCS repo as the source, which is not possible in the UI. + +To publish a new module: + +1. Sign in to [HCP Terraform](https://app.terraform.io/) or Terraform Enterprise and navigate to the organization where you want to publish a module. + +2. Select **Registry** from the sidebar. + +3. Click **Publish** and select **Module**. + + The **Add Module** page appears with a list of available repositories. + +4. Select the repository containing the module you want to publish. + + You can search the list by typing part or all of a repository name into the filter field. Remember that VCS providers use `/` strings to locate repositories. The namespace is an organization name for most providers, but Bitbucket Data Center, not Bitbucket Cloud, uses project keys, like `INFRA`. + +5. When prompted, choose either the **Tag** or **Branch** module publishing type. + +6. (Optional) If this module is a [no-code ready module](/terraform/enterprise/no-code-provisioning/module-design), select the **Add Module to no-code provision allowlist** checkbox. + +7. Click **Publish module**. + + HCP Terraform displays a loading page while it imports the module versions and then takes you to the new module's details page. On the details page, you can view available versions, read documentation, and copy a usage example. + + + +@include 'tfc-package-callouts/nocode.mdx' + + + +### Tag-based publishing considerations + +When using the **Tag** module publishing type, the registry uses `x.y.z` formatted release tags to identify module versions. Your repository must contain at least one release tag for you to publish a module. Release tag names must be a [semantic version](http://semver.org), which you can optionally prefix with a `v`. For example, `v1.0.4` and `0.9.2`. The registry ignores tags that do not match these formats. + + + +### Branch-based publishing considerations + +When using the **Branch** module publishing type, you must provide the name of an existing branch in your VCS repository and give the module a **Module version**. Your VCS repository does not need to contain a matching tag or release. + +You can only enable testing on modules published using branch-based publishing. Refer to the [test-integrated modules](/terraform/enterprise/registry/test) documentation for more information. + + + +## Releasing New Versions of a Module + + + +The process to release a new module version differs between the tag-based and branch-based publishing workflows. + +### Tag-Based Publishing Workflow + + + +To release a new version of a module in the tag-based publishing workflow, push a new release tag to its VCS repository. The registry automatically imports the new version. + +Refer to [Preparing a Module Repository](#preparing-a-module-repository) for details about release tag requirements. + + + +### Branch-Based Publishing Workflow + +To release a new version of a module using the branch-based publishing workflow, navigate to the module overview screen, then click the **Publish New Version** button. Select the commit SHA that the new version will point to, and assign a new module version. You cannot re-use an existing module version. + +## Update Publish Settings + +After publishing your module, you can change between tag-based and branch-based publishing. To update your module's publish settings, navigate to the the module overview page, click the **Manage Module for Organization** dropdown, and then click **Publish Settings**. + +- To change from tag-based to branch-based publishing, you must configure a **Module branch** and [create a new module version](#branch-based-publishing-workflow), as HCP Terraform will not automatically create one. + +- To change from branch-based publishing to tag-based publishing, you must create at least one tag in your VCS repository. + + + +## Deleting Versions and Modules + +-> **Note:** Deleting a tag from your VCS repository does not automatically remove the version from the private registry. + +You can delete individual versions of a module or the entire module. If deleting a module version would leave a module with no versions, HCP Terraform removes the entire module. To delete a module or version: + +1. Sign in to [HCP Terraform](https://app.terraform.io/) or Terraform Enterprise and navigate to the module's details page. + +2. If you want to delete a single version, use the **Versions** menu to select it. + +3. Click **Delete module**. + +4. Select an action from the menu: + + - **Delete only this module version:** Deletes only the version of the module you were viewing when you clicked **Delete module**. + - **Delete all versions for this provider for this module:** Deletes the entire module for a single provider. This action is important if you have modules with the same name but with different providers. For example, if you have module repos named `terraform-aws-appserver` and `terraform-azure-appserver`, the registry treats them as alternate providers of the same `appserver` module. + - **Delete all providers and versions for this module:** Deletes all modules with this name, even if they are from different providers. For example, this action deletes both `terraform-aws-appserver` and `terraform-azure-appserver`. + +5. Type the module name and click **Delete**. + +### Restoring a Deleted Module or Version + +Deletion is permanent, but there are ways to restore deleted modules and module versions. + +- To restore a deleted module, re-add it as a new module. +- To restore a deleted version, either delete the corresponding tag from your VCS and push a new tag with the same name, or delete the entire module from the registry and re-add it. + +## Sharing Modules Across Organizations + +HCP Terraform does not typically allow one organization's workspaces to use private modules from a different organization. This restriction is because HCP Terraform gives Terraform temporary credentials to access modules that are only valid for that workspace's organization. Although it is possible to mix modules from multiple organizations when you run Terraform on the command line, we strongly recommend against it. + +Instead, you can share modules across organizations by sharing the underlying VCS repository. Grant each organization access to the module's repository, and then add the module to each organization's registry. When you push tags to publish new module versions, both organizations update accordingly. + +Terraform Enterprise administrators can configure [module sharing](/terraform/enterprise/admin/application/registry-sharing) to allow organizations to use private modules from other organizations. + +## Generating Module Tests (Beta) + +You can generate and run generated tests for your module with [the `terraform test` command](/terraform/cli/commands/test). + +Before you can generate tests for a module, it must have at least one version published. Tests can only be generated once per module and are intended to be reviewed by the module's authors before being checked into version control and maintained with the rest of the module's content. If the module's configuration files exceed 128KB in total size, HCP Terraform will not be able to generate tests for that module. + +You must have [permission to manage registry modules](/terraform/enterprise/users-teams-organizations/permissions#manage-private-registry) and [permission to manage module test generation](/terraform/enterprise/users-teams-organizations/permissions#manage-module-test-generation-beta) to generate tests. diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/registry/publish-providers.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/registry/publish-providers.mdx new file mode 100644 index 0000000000..a7a1d6f811 --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/registry/publish-providers.mdx @@ -0,0 +1,266 @@ +--- +page_title: Publish private providers to the Terraform Enterprise private registry +description: >- + Use the Terraform Enterprise private registry to publish and share private + providers across your organization. +source: terraform-docs-common +--- + +# Publish private providers to the HCP Terraform private registry + +In addition to [curating public providers from the Terraform Registry](/terraform/enterprise/registry/add), you can publish private providers to an organization's HCP Terraform private registry. Once you have published a private provider through the API, members of your organization can search for it in the private registry UI and use it in configurations. + +## Requirements + +Review the following before publishing a new provider or provider version. + +### Permissions + +Users must be members of an organization to access its registry and private providers. In Terraform Enterprise, providers are also available to organizations that you configure to [share registry access](/terraform/enterprise/admin/application/registry-sharing). + +You must be a member of the [owners team](/terraform/enterprise/users-teams-organizations/permissions#organization-owners) or a team with [Manage Private Registry permissions](/terraform/enterprise/users-teams-organizations/permissions#manage-private-registry) to publish and delete private providers from the private registry. + +### Release files + +You must publish at least one version of your provider that follows [semantic versioning format](http://semver.org). For each version, you must upload the `SHA256SUMS` file, `SHA256SUMS.sig` file, and one or more provider binaries. Using GoReleaser to [create a release on GitHub](/terraform/registry/providers/publishing#creating-a-github-release) or [create a release locally](/terraform/registry/providers/publishing#using-goreleaser-locally) generates these files automatically. The private registry does not have strict naming conventions, but we recommend using GoReleaser file naming schemes for consistency. + +Private providers do not currently support documentation. + +### Signed releases + +GPG signing is required for private providers, and you must upload the public key of the GPG keypair used to sign the release. Refer to [Preparing and Adding a Signing Key](/terraform/registry/providers/publishing#preparing-and-adding-a-signing-key) for more details. Unlike the public Terraform Registry, the private registry does not automatically upload new releases. You must manually add new provider versions and the associated release files. + +-> **Note**: If you are using the [provider API](/terraform/enterprise/api-docs/private-registry/providers) to upload an official HashiCorp public provider into your private registry, use [HashiCorp's public PGP key](https://www.hashicorp.com/.well-known/pgp-key.txt). You do not need to upload this public key, and it is automatically included in Terraform Enterprise version v202309-1 and newer. + +## Publishing a provider + +Before consumers can use a private provider, you must do the following: + +1. [Create the provider](#create-the-provider) +2. [Upload a GPG signing key](#add-your-public-key) +3. [Create at least one version](#create-a-version) +4. [Create at least one platform for that version](#create-a-provider-platform) +5. [Upload release files](#upload-provider-binary) + +### Create the provider + +Create a file named `provider.json` with the following contents. Replace `PROVIDER_NAME` with the name of your provider and replace `ORG_NAME` with the name of your organization. + +```json +{ + "data": { + "type": "registry-providers", + "attributes": { + "name": "PROVIDER_NAME", + "namespace": "ORG_NAME", + "registry-name": "private" + } + } +} +``` + +Use the [Create a Provider endpoint](/terraform/enterprise/api-docs/private-registry/providers#create-a-provider) to create the provider in HCP Terraform. Replace `TOKEN` in the `Authorization` header with your HCP Terraform API token and replace `ORG_NAME` with the name of your organization. + +```shell-session +$ curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request POST \ + --data @provider.json \ + https://app.terraform.io/api/v2/organizations/ORG_NAME/registry-providers +``` + +The provider is now available in your organization’s HCP Terraform private registry, but consumers cannot use it until you add a version and a platform. + +To create a version and a platform, you need the following resources: + +- The Provider binaries +- A public GPG signing key +- A `SHA256SUMS` file +- A `SHA256SUMS.sig` file from at least one release + +### Add your public key + +-> **Note**: If you are uploading an official HashiCorp public provider into your private registry, skip this step and instead use [HashiCorp's public PGP key](https://www.hashicorp.com/.well-known/pgp-key.txt) in the the [create a version](#create-a-version) step. The key ID for HashiCorp's public ID is `34365D9472D7468F`, and you can verify the ID by [importing the public key locally](/terraform/tutorials/cli/verify-archive#download-and-import-hashicorp-s-public-key). + +Create a file named `key.json` with the following contents. Replace `ORG_NAME` with the name of your organization, and input your public key in an RSA or DSA format in the `ascii-armor` field. + +```hcl +{ + "data": { + "type": "gpg-keys", + "attributes": { + "namespace": "ORG_NAME", + "ascii-armor": "-----BEGIN PGP PUBLIC KEY BLOCK-----\n\nmQINB...=txfz\n-----END PGP PUBLIC KEY BLOCK-----\n" + } } +} +``` + +Use the [Add a GPG key endpoint](/terraform/enterprise/api-docs/private-registry/gpg-keys#add-a-gpg-key) to add the public key that matches the signing key for the release. Replace `TOKEN` in the `Authorization` header with your HCP Terraform API token. + +```shell-session +$ curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request POST \ + --data @key.json \ + https://app.terraform.io/api/registry/private/v2/gpg-keys +``` + +The response contains a `key-id` that you will use to create a provider version. + +```json +"key-id": "34365D9472D7468F" +``` + +### Create a version + +Create a file named `version.json` with the following contents. Replace the value of the `version` field with the version of your provider, and replace the `key-id` field with the id of the GPG key that you created in the [Add your public key](#add-your-public-key) step. If you are uploading an official HashiCorp public provider, use the value `34365D9472D7468F` for your `key-id`. + +```hcl +{ + "data": { + "type": "registry-provider-versions", + "attributes": { + "version": "5.14.0", + "key-id": "34365D9472D7468F", + "protocols": ["5.0"] + } + } +} +``` + +Use the [Create a Provider Version endpoint](/terraform/enterprise/api-docs/private-registry/provider-versions-platforms#create-a-provider-version) to create a version for your provider. Replace `TOKEN` in the `Authorization` header with your HCP Terraform API token, and replace both instances of `ORG_NAME` with the name of your organization. If are not using the `aws` provider, then replace `aws` with your provider's name. + +```shell-session +$ curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request POST \ + --data @version.json \ + https://app.terraform.io/api/v2/organizations/ORG_NAME/registry-providers/private/ORG_NAME/aws/versions +``` + + The response includes URL links that you will use to upload the `SHA256SUMS` and `SHA256.sig` files. + +```json +"links": { + "shasums-upload": "https://archivist.terraform.io/v1/object/dmF1b64hd73ghd63", + "shasums-sig-upload": "https://archivist.terraform.io/v1/object/dmF1b37dj37dh33d" + } +``` + +### Upload signatures + +Upload the `SHA256SUMS` and `SHA256SUMS.sig` files to the URLs [returned in the previous step](#create-a-version). The example command below uploads the files from your local machine. First upload the `SHA256SUMS` file to the URL returned in the `shasums-upload` field. + +```shell-session +$ curl \ + -T terraform-provider-aws_5.14.0_SHA256SUMS \ + https://archivist.terraform.io/v1/object/dmF1b64hd73ghd63... +``` + +Next, upload the `SHA256SUMS.sig` file to the URL returned in the `shasums-sig-upload` field. + +```shell-session +$ curl \ + -T terraform-provider-aws_5.14.0_SHA256SUMS.72D7468F.sig \ + https://archivist.terraform.io/v1/object/dmF1b37dj37dh33d... +``` + +### Create a provider platform + +First, calculate the SHA256 hash of the provider binary that you intend to upload. This should match the SHA256 hash of the file listed in the `SHA256SUMS` file. + +```shell-session +$ shasum -a 256 terraform-provider-aws_5.14.0_linux_amd64.zip +f1d83b3e5a29bae471f9841a4e0153eac5bccedbdece369e2f6186e9044db64e terraform-provider-aws_5.14.0_linux_amd64.zip +``` + +Next, create a file named `platform.json`. Replace the `os`, `arch`, `filename`, and `shasum` fields with the values that match the provider you intend to upload. + +```json +{ + "data": { + "type": "registry-provider-version-platforms", + "attributes": { + "os": "linux", + "arch": "amd64", + "shasum": "f1d83b3e5a29bae471f9841a4e0153eac5bccedbdece369e2f6186e9044db64e", + "filename": "terraform-provider-aws_5.14.0_linux_amd64.zip" + } + } +} +``` + +Use the [Create a Provider Platform endpoint](/terraform/enterprise/api-docs/private-registry/provider-versions-platforms#create-a-provider-platform) to create a platform for the version. Platforms are binaries that allow the provider to run on a particular operating system and architecture combination (e.g., Linux and AMD64). Replace `TOKEN` in the `Authorization` header with your HCP Terraform API token and replace both instances of `ORG_NAME` with the name of your organization. + +```shell-session +$ curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request POST \ + --data @platform.json \ + https://app.terraform.io/api/v2/organizations/ORG_NAME/registry-providers/private/ORG_NAME/aws/versions/5.14.0/platforms +``` + +The response includes a `provider-binary-upload` URL that you will use to upload the binary file for the platform. + +```json +"links": { + "provider-binary-upload": "https://archivist.terraform.io/v1/object/dmF1b45c367djh45nj78" + } +``` + +### Upload provider binary + +Upload the platform binary file to the `provider-binary-upload` URL returned in the [previous step](#create-a-version). The example command below uploads the binary from your local machine. + +```shell-session +$ curl -T local-example/terraform-provider-random_5.14.0_linux_amd64.zip + https://archivist.terraform.io/v1/object/dmF1b45c367djh45nj78 +``` + +The version is available in the HCP Terraform user interface. Consumers can now begin using this provider version in configurations. You can repeat these steps starting from [Create a provider platform](#create-a-provider-platform) to add additional platform binaries for the release. + +## Checking Release Files + +Consumers cannot use a private provider version until you upload all required [release files](#release-files). To determine whether these files have been uploaded: + +1. Sign in to [HCP Terraform](https://app.terraform.io/) or Terraform Enterprise and navigate to the organization where you want to publish a private provider. +2. Click **Registry** and click the private provider to go to its details page. +3. Use the version menu to navigate to the version you want to check. The UI shows a warning banner for versions that do not have all required release files. +4. Open the **Manage Provider** menu and select **Show release files**. The **Release Files** page appears containing lists of uploaded and missing files for the current version. + +## Managing private providers + +Use the HCP Terraform API to create, read, update, and delete the following: + +- [GPG keys](/terraform/enterprise/api-docs/private-registry/gpg-keys) +- [Private providers](/terraform/enterprise/api-docs/private-registry/providers) +- [Provider versions and platforms](/terraform/enterprise/api-docs/private-registry/provider-versions-platforms) + +## Deleting private providers and versions + +In addition to the [Registry Providers API](/terraform/enterprise/api-docs/private-registry/providers#delete-a-provider), you can delete providers and provider versions through the HCP Terraform UI. To delete providers and versions in the UI: + +1. Sign in to [HCP Terraform](https://app.terraform.io/) or Terraform Enterprise and navigate to the organization whose private registry you want to delete a provider or provider version from. + +2. Click **Registry** and click the private provider to go to its details page. + +3. If you want to delete a single version, use the **Versions** menu to select it. + +4. Open the **Manage Provider** menu and select **Delete Provider**. The **Delete Provider from Organization** box appears. + +5. Select an action from the menu: + + - **Delete only this provider version:** Deletes only the version of the provider you are currently viewing. + - **Delete all versions for this provider:** Deletes the entire provider and all associated versions. + +6. Type the provider name into the confirmation box and click **Delete**. + +The provider version or entire provider has been deleted from this organization's private registry and its data has been removed. Consumers will no longer be able to reference it in configurations. + +### Restoring a deleted provider + +Deletion is permanent, but you can restore a deleted private provider by re-adding it to your organization and recreating its versions and platforms. diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/registry/test.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/registry/test.mdx new file mode 100644 index 0000000000..377e7f3402 --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/registry/test.mdx @@ -0,0 +1,69 @@ +--- +page_title: Test private modules in the Terraform Enterprise private registry +description: Use the Terraform Enterprise private registry to run module tests. +source: terraform-docs-common +--- + +# Test private modules in the HCP Terraform private registry + +You can configure HCP Terraform to automatically run tests for modules in your private registry. When enabled, HCP Terraform will run tests for every commit to the designated branch. This lets you verify that it is safe to publish new module versions. + +## Enable testing + +If your module uses the [branch-based publishing workflow](/terraform/enterprise/registry/publish-modules#branch-based-publishing) and its source code includes [tests](/terraform/language/v1.6.x/tests), you can enable testing at any time. + +To enable testing when you publish your module: + +- Choose the **Branch** module publishing type +- Assign a branch and a module version +- Under testing, click the **Enable testing for module** checkbox +- Click **Publish module** + +To enable testing after you publishing your branch-based module: + +- Navigate to the module overview screen +- Click **Configure Tests** to open the **Tests Settings** screen +- Click **Enable testing for module** + +## Run tests remotely from the CLI + +After publishing and enabling testing for your module, you can use the Terraform CLI locally to trigger remotely-executed tests in HCP Terraform. This lets you test your module changes using the credentials configured in HCP Terraform without committing your changes to version control. + +To run your tests remotely, use the `-cloud-run` flag with the path to your module in your private registry. + +```shell +terraform test -cloud-run=app.terraform.io/:ORG/:MODULE_NAME/:PROVIDER +``` + +## Configure environment variables + +You can define test-specific environment variables that HCP Terraform will use for testing. If your tests provision infrastructure, you must configure provider credentials for the module. + +To add environment variables to your module's tests: + +1. On the module overview screen, click **Configure Tests**. +2. In the **Variables** section on the **Tests Settings** screen, click **+ Add variable**. +3. Provide a **Key** and **Value** for your environment variable, and if you want to protect the variable's value, click the **Sensitive** checkbox. `TF_VAR_x` variables of a string type that are not defined in a config must be wrapped in double-quotes. +4. Click **Add variable** to save it. + + + +## Generated module tests + +@include 'tfc-package-callouts/tests.mdx' + +HCP Terraform can generate [test files](/terraform/language/tests) for any private module in your registry. You can only generate tests one time per module. + +To generate tests for your module: + +1. On the module overview screen, click **Generate tests**. +2. Click **Confirm**. It will take a few minutes to generate your module tests. +3. HCP Terraform displays generated configuration. To download all of the test files, click **Download generated tests**. +4. Create a `tests` directory in your configuration. +5. Unzip the downloaded files into the new `tests` directory. + +Generated test files remain available on the module overview page for later retrieval. Click **View test files** to view and download any previously generated tests. + +Organization owners can control this feature on the organization's [General Settings](/terraform/enterprise/users-teams-organizations/organizations#organization-settings) page. + + diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/registry/using.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/registry/using.mdx new file mode 100644 index 0000000000..98542f2bd7 --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/registry/using.mdx @@ -0,0 +1,149 @@ +--- +page_title: Use providers and modules from the Terraform Enterprise private registry +description: >- + Learn how to use providers and modules from the Terraform Enterprise private + registry in your Terraform configuration. +source: terraform-docs-common +--- + +# Use providers and modules from the HCP Terraform private registry + +All users in an organization can view the HCP Terraform private registry and use the available providers and modules. A private registry has some key requirements and differences from the [public Terraform Registry](/terraform/registry): + +- **Location:** Search for providers, modules, and usage examples in the HCP Terraform private registry UI. +- **Provider and Module block `source` argument:** Private providers and modules use a [different format](/terraform/enterprise/registry/using#using-private-providers-and-modules-in-configurations). +- **Terraform version:** HCP Terraform workspaces using version 0.11 and higher can automatically access your private modules during Terraform runs, and workspaces using version 0.13 and higher can also automatically access private providers. +- **Authentication:** If you run Terraform on the command line, you must [authenticate](/terraform/enterprise/registry/using#authentication) to HCP Terraform or your instance to use providers and modules in your organization’s private registry. + +HCP Terraform supports using modules in written configuration or through the [no-code provisioning workflow](/terraform/enterprise/no-code-provisioning/provisioning). + +## Finding Providers and Modules + +To find available providers and modules, click the **Registry** button. The **Registry** page appears. + +Click **Providers** and **Modules** to toggle back and forth between lists of available providers and modules in the private registry. You can also use the search field to filter for titles that contain a specific keyword. The search does not include READMEs or resource details. + +### Shared Providers and Modules - Terraform Enterprise + +On Terraform Enterprise, your [registry sharing](/terraform/enterprise/admin/application/registry-sharing) configuration may grant you access to another organization's providers and modules. Providers and modules that are shared with your current organization have a **Shared** badge in the private registry (below). Providers and modules in your current organization that are shared with other organizations have a badge that says **Sharing**. + +### Viewing Provider and Module Details and Versions + +Click a provider or module to view its details page. Use the **Versions** menu in the upper right to switch between the available versions, and use the **Readme**, **Inputs**, **Outputs**, **Dependencies**, and **Resources** tabs to view more information about the selected version. + +### Viewing Nested Modules and Examples + +Use the **Submodules** menu to navigate to the detail pages for any nested modules. Use the **Examples** menu to navigate to the detail pages for any available example modules. + +## Provisioning Infrastructure from No-Code Ready Modules + +You can use modules marked **No-Code Ready** to create a new workspace and automatically provision the module's resources without writing any Terraform configuration. Refer to [Provisioning No-Code Infrastructure](/terraform/enterprise/no-code-provisioning/provisioning) for details. + +## Using Public Providers and Modules in Configurations + +> **Hands-on:** Try the [Use Modules from the Registry](/terraform/tutorials/modules/module-use) tutorial. + +The syntax for public providers in a private registry is the same as for providers that you use directly from the public Terraform Registry. The syntax for the [provider block](/terraform/language/providers/configuration#provider-configuration-1) `source` argument is `/`. + +```hcl +terraform { + required_providers { + google = { + source = "hashicorp/google" + version = "4.0.0" + } + } +``` + +The syntax for referencing public modules in the [module block](/terraform/language/modules/syntax) `source` argument is `//`. + +```hcl +module "subnets" { + source = "hashicorp/subnets/cidr" + version = "1.0.0" +} +``` + +## Using Private Providers and Modules in Configurations + +The syntax for referencing private providers in the [provider block](/terraform/language/providers/configuration#provider-configuration-1) `source` argument is `//`. For the SaaS version of HCP Terraform, the hostname is `app.terraform.io`. + +```hcl +terraform { + required_providers { + random = { + source = "app.terraform.io/demo-custom-provider/random" + version = "1.1.0" + } + } +``` + +The syntax for referencing private modules in the [module block](/terraform/language/modules/syntax) `source` argument is `///`. + +- **Hostname:** For the SaaS version of HCP Terraform, use `app.terraform.io`. In Terraform Enterprise, use the hostname for your instance or the [generic hostname](/terraform/enterprise/registry/using#generic-hostname-terraform-enterprise). +- **Organization:** If you are using a shared module with Terraform Enterprise, the module's organization name may be different from your organization's name. Check the source string at the top of the module's registry page to find the proper organization name. + +```hcl +module "vpc" { + source = "app.terraform.io/example_corp/vpc/aws" + version = "1.0.4" +} +``` + +### Generic Hostname - HCP Terraform and Terraform Enterprise + +You can use the generic hostname `localterraform.com` in module sources to reference modules without modifying the HCP Terraform or Terraform Enterprise instance. When you run Terraform, it automatically requests any `localterraform.com` modules from the instance it runs on. + +```hcl +module "vpc" { + source = "localterraform.com/example_corp/vpc/aws" + version = "1.0.4" +} +``` + +~> **Important**: CLI-driven workflows require Terraform CLI v1.4.0 or above. + +To test configurations on a developer workstation without the remote backend configured, you must replace the generic hostname with a literal hostname in all module sources and then change them back before committing to VCS. We are working on making this workflow smoother, but we only recommend `localterraform.com` for large organizations that use multiple Terraform Enterprise instances. + +### Provider and Module Availability + +A workspace can only use private providers and modules from its own organization's registry. When using providers or modules from multiple organizations in the same configuration, we recommend: + +- **HCP Terraform:** [Add providers and modules to the registry](/terraform/enterprise/registry/publish-modules#sharing-modules-across-organizations) for each organization that requires access. + +- **Terraform Enterprise:** Check your site's [registry sharing](/terraform/enterprise/admin/application/registry-sharing) configuration. Workspaces can also use private providers and modules from organizations that are sharing with the workspace's organization. + +## Running Configurations with Private Providers and Modules + +### Version Requirements + +Terraform version 0.11 or later is required to use private modules in HCP Terraform workspaces and to use the CLI to apply configurations with private modules. Terraform version 0.13 and later is required to use private providers in HCP Terraform workspaces and apply configurations with private providers. + +### Authentication + +To authenticate with HCP Terraform, you can use either a [user token](/terraform/enterprise/users-teams-organizations/users#api-tokens) or a [team token](/terraform/enterprise/users-teams-organizations/api-tokens#team-api-tokens). The type of token you choose may grant different permissions. + +- **User Token**: Allows you to access providers and modules from any organization in which you are a member. You are a member of an organization if you belong to any team in that organization. You can also access modules from any organization that is sharing modules with any of your organizations. + + -> **Note:** When SAML SSO is enabled, there is a [session timeout for user API tokens](/terraform/enterprise/saml/login#api-token-expiration), requiring you to periodically re-authenticate through the web UI. Expired tokens produce a _401 Unauthorized_ error. A SAML SSO account with [IsServiceAccount](/terraform/enterprise/saml/attributes#isserviceaccount) is treated as a service account and will not have the session timeout. + +- **Team Token**: Allows you to access the private registry of that team's organization and the registries from any other organizations that have configured sharing. + +_Permissions Example_ + +A user belongs to three organizations (1, 2, and 3), and organizations 1 and 2 share access with each other. In this case, the user's token gives them access to the private registries for all of the organizations they belong to: 1, 2, and 3. However, a team token from a team in organization 1 only gives the user access to the private registry in organizations 1 and 2. + +#### Configure Authentication + +To configure authentication to HCP Terraform or your Terraform Enterprise instance, you can: + +- (Terraform 0.12.21 or later) Use the [`terraform login`](/terraform/cli/commands/login) command to obtain and save a user API token. +- Create a token and [manually configure credentials in the CLI config file][cli-credentials]. + +Make sure the hostname matches the hostname you use in provider and module sources because if the same HCP Terraform server is available at two hostnames, Terraform will not know that they reference the same server. To support multiple hostnames for provider and module sources, use the `terraform login` command multiple times and specify a different hostname each time. + +[user-token]: /terraform/enterprise/users-teams-organizations/users#api-tokens + +[cli-credentials]: /terraform/cli/config/config-file#credentials + +[permissions-citation]: #intentionally-unused---keep-for-maintainers diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/run/api.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/run/api.mdx new file mode 100644 index 0000000000..eaf923fce2 --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/run/api.mdx @@ -0,0 +1,197 @@ +--- +page_title: The API-driven run workflow in Terraform Enterprise +description: >- + Use Terraform Enterprise's API-driven run workflow to enable external tools to + upload Terraform configurations and trigger new runs. +source: terraform-docs-common +--- + +# The API-driven run workflow + +HCP Terraform has three workflows for managing Terraform runs. + +- The [UI/VCS-driven run workflow](/terraform/enterprise/run/ui), which is the primary mode of operation. +- The API-driven run workflow described below, which is more flexible but requires you to create some tooling. +- The [CLI-driven run workflow](/terraform/enterprise/run/cli), which uses Terraform's standard CLI tools to execute runs in HCP Terraform. + +## Summary + +In the API-driven workflow, workspaces are not directly associated with a VCS repo, and runs are not driven by webhooks on your VCS provider. + +Instead, one of your organization's other tools is in charge of deciding when configuration has changed and a run should occur. Usually this is something like a CI system, or something else capable of monitoring changes to your Terraform code and performing actions in response. + +Once your other tooling has decided a run should occur, it must make a series of calls to HCP Terraform's `runs` and `configuration-versions` APIs to upload configuration files and perform a run with them. For the exact series of API calls, see the [pushing a new configuration version](#pushing-a-new-configuration-version) section. + +The most significant difference in this workflow is that HCP Terraform _does not_ fetch configuration files from version control. Instead, your own tooling must upload the configurations as a `.tar.gz` file. This allows you to work with configurations from unsupported version control systems, automatically generate Terraform configurations from some other source of data, or build a variety of other integrations. + +~> **Important:** The script below is provided to illustrate the run process, and is not intended for production use. If you want to drive HCP Terraform runs from the command line, please see the [CLI-driven run workflow](/terraform/enterprise/run/cli). + +## Pushing a New Configuration Version + +Pushing a new configuration to an existing workspace is a multi-step process. This section walks through each step in detail, using an example bash script to illustrate. + +You need queue plans permission to create new configuration versions for the workspace. Refer to the [permissions](/terraform/enterprise/users-teams-organizations/permissions#general-workspace-permissions) documentation for more details. + +[permissions-citation]: #intentionally-unused---keep-for-maintainers + +### 1. Define Variables + +To perform an upload, a few user parameters must be set: + +- **path_to_content_directory** is the folder with the terraform configuration. There must be at least one `.tf` file in the root of this path. +- **organization** is the organization name (not ID) for your HCP Terraform organization. +- **workspace** is the workspace name (not ID) in the HCP Terraform organization. +- **$TOKEN** is the API Token used for [authenticating with the HCP Terraform API](/terraform/enterprise/api-docs#authentication). + +This script extracts the `path_to_content_directory`, `organization`, and `workspace` from command line arguments, and expects the `$TOKEN` as an environment variable. + +```bash +#!/bin/bash + +if [ -z "$1" ] || [ -z "$2" ]; then + echo "Usage: $0 /" + exit 0 +fi + +CONTENT_DIRECTORY="$1" +ORG_NAME="$(cut -d'/' -f1 <<<"$2")" +WORKSPACE_NAME="$(cut -d'/' -f2 <<<"$2")" +``` + +### 2. Create the File for Upload + +The [configuration version API](/terraform/enterprise/api-docs/configuration-versions) requires a `tar.gz` file to use the configuration version for a run, so you must package the directory containing the Terraform configuration into a `tar.gz` file. + +~> **Important:** The configuration directory must be the root of the tar file, with no intermediate directories. In other words, when the tar file is extracted the result must be paths like `./main.tf` rather than `./terraform-appserver/main.tf`. + +```bash +UPLOAD_FILE_NAME="./content-$(date +%s).tar.gz" +tar -zcvf "$UPLOAD_FILE_NAME" -C "$CONTENT_DIRECTORY" . +``` + +### 3. Look Up the Workspace ID + +The first step identified the organization name and the workspace name; however, the [configuration version API](/terraform/enterprise/api-docs/configuration-versions) expects the workspace ID. As such, the ID has to be looked up. If the workspace ID is already known, this step can be skipped. This step uses the [`jq` tool](https://stedolan.github.io/jq/) to parse the JSON output and extract the ID value into the `WORKSPACE_ID` variable. + +```bash +WORKSPACE_ID=($(curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + https://app.terraform.io/api/v2/organizations/$ORG_NAME/workspaces/$WORKSPACE_NAME \ + | jq -r '.data.id')) +``` + +### 4. Create a New Configuration Version + +Before uploading the configuration files, you must create a `configuration-version` to associate uploaded content with the workspace. This API call performs two tasks: it creates the new configuration version and it extracts the upload URL to be used in the next step. + +```bash +echo '{"data":{"type":"configuration-versions"}}' > ./create_config_version.json + +UPLOAD_URL=($(curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request POST \ + --data @create_config_version.json \ + https://app.terraform.io/api/v2/workspaces/$WORKSPACE_ID/configuration-versions \ + | jq -r '.data.attributes."upload-url"')) +``` + +### 5. Upload the Configuration Content File + +Next, upload the configuration version `tar.gz` file to the upload URL extracted from the previous step. If a file is not uploaded, the configuration version will not be usable, since it will have no Terraform configuration files. + +HCP Terraform automatically creates a new run with a plan once the new file is uploaded. If the workspace is configured to auto-apply, it will also apply if the plan succeeds; otherwise, an apply can be triggered via the [Run Apply API](/terraform/enterprise/api-docs/run#apply). If the API token used for the upload lacks permission to apply runs for the workspace, the run can't be auto-applied. ([More about permissions.](/terraform/enterprise/users-teams-organizations/permissions)) + +[permissions-citation]: #intentionally-unused---keep-for-maintainers + +```bash +curl \ + --header "Content-Type: application/octet-stream" \ + --request PUT \ + --data-binary @"$UPLOAD_FILE_NAME" \ + $UPLOAD_URL +``` + +### 6. Delete Temporary Files + +In the previous steps a few files were created; they are no longer needed, so they should be deleted. + +```bash +rm "$UPLOAD_FILE_NAME" +rm ./create_config_version.json +``` + +### Complete Script + +Combine all of the code blocks into a single file, `./terraform-enterprise-push.sh` and give execution permission to create a combined bash script to perform all of the operations. + +```shell +chmod +x ./terraform-enterprise-push.sh +./terraform-enterprise-push.sh ./content my-organization/my-workspace +``` + +**Note**: This script does not have error handling, so for a more robust script consider adding error checking. + +**`./terraform-enterprise-push.sh`:** + +```bash +#!/bin/bash + +# Complete script for API-driven runs. + +# 1. Define Variables + +if [ -z "$1" ] || [ -z "$2" ]; then + echo "Usage: $0 /" + exit 0 +fi + +CONTENT_DIRECTORY="$1" +ORG_NAME="$(cut -d'/' -f1 <<<"$2")" +WORKSPACE_NAME="$(cut -d'/' -f2 <<<"$2")" + +# 2. Create the File for Upload + +UPLOAD_FILE_NAME="./content-$(date +%s).tar.gz" +tar -zcvf "$UPLOAD_FILE_NAME" -C "$CONTENT_DIRECTORY" . + +# 3. Look Up the Workspace ID + +WORKSPACE_ID=($(curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + https://app.terraform.io/api/v2/organizations/$ORG_NAME/workspaces/$WORKSPACE_NAME \ + | jq -r '.data.id')) + +# 4. Create a New Configuration Version + +echo '{"data":{"type":"configuration-versions"}}' > ./create_config_version.json + +UPLOAD_URL=($(curl \ + --header "Authorization: Bearer $TOKEN" \ + --header "Content-Type: application/vnd.api+json" \ + --request POST \ + --data @create_config_version.json \ + https://app.terraform.io/api/v2/workspaces/$WORKSPACE_ID/configuration-versions \ + | jq -r '.data.attributes."upload-url"')) + +# 5. Upload the Configuration Content File + +curl \ + --header "Content-Type: application/octet-stream" \ + --request PUT \ + --data-binary @"$UPLOAD_FILE_NAME" \ + $UPLOAD_URL + +# 6. Delete Temporary Files + +rm "$UPLOAD_FILE_NAME" +rm ./create_config_version.json +``` + +## Advanced Use Cases + +For advanced use cases refer to the [Terraform Enterprise Automation Script](https://github.com/hashicorp/terraform-guides/tree/master/operations/automation-script) repository for automating interactions with HCP Terraform, including the creation of a workspace, uploading code, setting variables, and triggering the `plan` and `apply` operations. + +In addition to uploading configurations and starting runs, you can use HCP Terraform's APIs to create and modify workspaces, edit variable values, and more. See the [API documentation](/terraform/enterprise/api-docs) for more details. diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/run/cli.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/run/cli.mdx new file mode 100644 index 0000000000..87c74d475f --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/run/cli.mdx @@ -0,0 +1,299 @@ +--- +page_title: The CLI-driven remote run workflow for Terraform Enterprise +description: >- + Configure the Terraform CLI to trigger remote runs in Terraform Enterprise + from your terminal. +source: terraform-docs-common +--- + +[private]: /terraform/enterprise/registry + +[speculative plan]: /terraform/enterprise/run/remote-operations#speculative-plans + +[tfe-provider]: https://registry.terraform.io/providers/hashicorp/tfe/latest/docs + +# The CLI-driven remote run workflow + +> **Hands-on:** Try the [Log in to HCP Terraform from the CLI](/terraform/tutorials/0-13/cloud-login) tutorial. + +HCP Terraform has three workflows for managing Terraform runs. + +- The [UI/VCS-driven run workflow](/terraform/enterprise/run/ui), which is the primary mode of operation. +- The [API-driven run workflow](/terraform/enterprise/run/api), which is more flexible but requires you to create some tooling. +- The CLI-driven run workflow described below, which uses Terraform's standard CLI tools to execute runs in HCP Terraform. + +## Summary + +The [CLI integration](/terraform/cli/cloud) brings HCP Terraform's collaboration features into the familiar Terraform CLI workflow. It offers the best of both worlds to developers who are already comfortable with using the Terraform CLI, and it can work with existing CI/CD pipelines. + +You can start runs with the standard `terraform plan` and `terraform apply` commands and then watch the progress of the run from your terminal. These runs execute remotely in HCP Terraform, use variables from the appropriate workspace, enforce any applicable [Sentinel or OPA policies](/terraform/enterprise/policy-enforcement), and can access HCP Terraform's [private registry][private] and remote state inputs. + +HCP Terraform offers a few types of CLI-driven runs, to support different stages of your workflow: + +- `terraform plan` starts a [speculative plan][] in an HCP Terraform workspace, using configuration files from a local directory. You can quickly check the results of edits (including compliance with Sentinel policies) without needing to copy sensitive variables to your local machine. + + Speculative plans work with all workspaces, and can co-exist with the [VCS-driven workflow](/terraform/enterprise/run/ui). + +- `terraform apply` starts a standard plan and apply in an HCP Terraform workspace, using configuration files from a local directory. + + Remote `terraform apply` is for workspaces without a linked VCS repository. It replaces the VCS-driven workflow with a more traditional CLI workflow. + +- `terraform plan -out ` and `terraform apply ` perform a two-part [saved plan run](/terraform/enterprise/run/modes-and-options/#saved-plans) in an HCP Terraform workspace, using configuration files from a local directory. The first command performs and saves the plan, and the second command applies it. You can use `terraform show ` to inspect a saved plan. + + Like remote `terraform apply`, remote saved plans are for workspaces without a linked VCS repository. + + Saved plans require at least Terraform CLI v1.6.0. + +To supplement these remote operations, you can also use the optional [Terraform Enterprise Provider][tfe-provider], which interacts with the HCP Terraform-supported resources. This provider is useful for editing variables and workspace settings through the Terraform CLI. + +## Configuration + +To enable the CLI-driven workflow, you must: + +1. Create an account or sign in to [HCP Terraform](https://app.terraform.io/). + +2. Run `terraform login` to authenticate with HCP Terraform. Alternatively, you can manually configure credentials in the CLI config file or through environment variables. Refer to [CLI Configuration](/terraform/cli/config/config-file#environment-variable-credentials) for details. + +3. Add the `cloud` block to your Terraform configuration. You can define its arguments directly in your configuration file or supply them through environment variables, which can be useful for [non-interactive workflows](#non-interactive-workflows). Refer to [Using HCP Terraform](/terraform/cli/cloud) for configuration details. + + The following example shows how to map CLI workspaces to HCP Terraform workspaces with a specific tag. + + terraform { + cloud { + organization = "my-org" + workspaces { + tags = ["networking"] + } + } + } + + -> **Note:** The `cloud` block is available in Terraform v1.1 and later. Previous versions can use the [`remote` backend](/terraform/language/settings/backends/remote) to configure the CLI workflow and migrate state. + +4. Run `terraform init`. + + $ terraform init + + Initializing HCP Terraform... + + Initializing provider plugins... + - Reusing previous version of hashicorp/random from the dependency lock file + - Using previously-installed hashicorp/random v3.0.1 + + HCP Terraform has been successfully initialized! + + You may now begin working with HCP Terraform. Try running "terraform plan" + to see any changes that are required for your infrastructure. + + If you ever set or change modules or Terraform Settings, + run "terraform init" again to reinitialize your working directory. + +### Implicit Workspace Creation + +If you configure the `cloud` block to use a workspace that doesn't yet exist in your organization, HCP Terraform will create a new workspace with that name when you run `terraform init`. The output of `terraform init` will inform you when this happens. + +Automatically created workspaces might not be immediately ready to use, so use HCP Terraform's UI to check a workspace's settings and data before performing any runs. In particular, note that: + +- No Terraform variables or environment variables are created by default, unless your organization has configured one or more [global variable sets](/terraform/enterprise/workspaces/variables#scope). HCP Terraform will use `*.auto.tfvars` files if they are present, but you will usually still need to set some workspace-specific variables. +- The execution mode defaults to "Remote," so that runs occur within HCP Terraform's infrastructure instead of on your workstation. +- New workspaces are not automatically connected to a VCS repository and do not have a working directory specified. +- A new workspace's Terraform version defaults to the most recent release of Terraform at the time the workspace was created. + +### Implicit Project Creation + +If you configure the [`workspaces` block](/terraform/cli/cloud/settings#workspaces) to use a [project](/terraform/cli/cloud/settings#project) that does not yet exist in your organization, HCP Terraform will attempt to create a new project with that name when you run `terraform init` and notify you in the command output. + +If you specify both the `project` argument and [`TF_CLOUD_PROJECT`](/terraform/cli/cloud/settings#tf_cloud_project) environment variable, the `project` argument takes precedence. + +## Variables in CLI-Driven Runs + +Remote runs in HCP Terraform use: + +- Run-specific variables set through the command line or in your local environment. Terraform can use shell environment variables prefixed with `TF_VAR_` as input variables for the run, but you must still set all required environment variables, like provider credentials, inside the workspace. +- Workspace-specific Terraform and environment variables set in the workspace. +- Any variable sets applied globally, on the project containing the workspace, or on the workspace itself. +- Terraform variables from any `*.auto.tfvars` files included in the configuration. + +Refer to [Variables](/terraform/enterprise/workspaces/variables) for more details about variable types, variable scopes, variable precedence, and how to set run-specific variables through the command line. + +## Remote Working Directories + +If you manage your Terraform configurations in self-contained repositories, the remote working directory always has the same content as the local working directory. + +If you use a combined repository and [specify a working directory on workspaces](/terraform/enterprise/workspaces/settings#terraform-working-directory), you can run Terraform from either the real working directory or from the root of the combined configuration directory. In both cases, Terraform will upload the entire combined configuration directory. + +## Excluding Files from Upload + +-> **Version note:** `.terraformignore` support was added in Terraform 0.12.11. + +CLI-driven runs upload an archive of your configuration directory +to HCP Terraform. If the directory contains files you want to exclude from upload, +you can do so by defining a [`.terraformignore` file in your configuration directory](/terraform/cli/cloud/settings). + +## Remote Speculative Plans + +You can run speculative plans in any workspace where you have [permission to queue plans](/terraform/enterprise/users-teams-organizations/permissions). Speculative plans use the configuration code from the local working directory, but will use variable values from the specified workspace. + +To run a [speculative plan][] on your configuration, use the `terraform plan` command. The plan will run in HCP Terraform, and the logs will stream back to the command line along with a URL to view the plan in the HCP Terraform UI. + + $ terraform plan + + Running plan in HCP Terraform. Output will stream here. Pressing Ctrl-C + will stop streaming the logs, but will not stop the plan running remotely. + + Preparing the remote plan... + + To view this run in a browser, visit: + https://app.terraform.io/app/hashicorp-learn/docs-workspace/runs/run-cfh2trDbvMU2Rkf1 + + Waiting for the plan to start... + + [...] + + Plan: 1 to add, 0 to change, 0 to destroy. + + Changes to Outputs: + + pet_name = (known after apply) + +[permissions-citation]: #intentionally-unused---keep-for-maintainers + +## Remote Applies + +In workspaces that are not connected to a VCS repository, users with [permission to apply runs](/terraform/enterprise/users-teams-organizations/permissions#general-workspace-permissions) can use the CLI to trigger remote applies. Remote applies use the configuration code from the local working directory, but use the variable values from the specified workspace. + +~> **Note:** You cannot run remote applies in workspaces that are linked to a VCS repository, since the repository serves as the workspace’s source of truth. To apply changes in a VCS-linked workspace, merge your changes to the designated branch. + +When you are ready to apply configuration changes, use the `terraform apply` command. HCP Terraform will plan your changes, and the command line will prompt you for approval before applying them. + + $ terraform apply + + Running apply in HCP Terraform. Output will stream here. Pressing Ctrl-C + will cancel the remote apply if it's still pending. If the apply started it + will stop streaming the logs, but will not stop the apply running remotely. + + Preparing the remote apply... + + To view this run in a browser, visit: + https://app.terraform.io/app/hashicorp-learn/docs-workspace/runs/run-Rcc12TkNW1PDa7GH + + Waiting for the plan to start... + + [...] + + Plan: 1 to add, 0 to change, 0 to destroy. + + Changes to Outputs: + + pet_name = (known after apply) + + Do you want to perform these actions in workspace "docs-workspace"? + Terraform will perform the actions described above. + Only 'yes' will be accepted to approve. + + Enter a value: yes + + [...] + + Apply complete! Resources: 1 added, 0 changed, 0 destroyed. + +### Non-Interactive Workflows + +> **Hands On:** Try the [Deploy Infrastructure with HCP Terraform and CircleCI](/terraform/tutorials/automation/circle-ci) tutorial. + +External systems cannot run the traditional apply workflow because Terraform requires console input from the user to approve plans. We recommend using the [API-driven Run Workflow](/terraform/enterprise/run/api) for non-interactive workflows when possible. + +If you prefer to use the CLI in a non-interactive environment, we recommend first running a [speculative plan](/terraform/enterprise/run/remote-operations#speculative-plans) to preview the changes Terraform will make to your infrastructure. Then, use one of the following approaches with the `-auto-approve` flag based on the [execution mode](/terraform/enterprise/workspaces/settings#execution-mode) of your workspace. The [`-auto-approve`](/terraform/cli/commands/apply#auto-approve) flag skips prompting you to approve the plan. + +- **Local Execution:** Save the approved speculative plan and then run `terraform apply -auto-approve` with the saved plan. +- **Remote Execution:** HCP Terraform does not support uploading saved plans for remote execution, so we recommend running `terraform apply -auto-approve` immediately after approving the speculative plan to prevent the plan from becoming stale. + + !> **Warning:** Remote execution with non-interactive workflows requires auto-approved deployments. Minimize the risk of unpredictable infrastructure changes and configuration drift by making sure that no one can change your infrastructure outside of your automated build pipeline. + +[permissions-citation]: #intentionally-unused---keep-for-maintainers + +## Remote Saved Plans + +-> **Version note:** Saved plans require at least Terraform CLI v1.6.0. + +In workspaces that support `terraform apply`, you also have the option of performing the plan and apply as separate steps, using the standard variations of the relevant Terraform commands: + +- `terraform plan -out ` performs and saves a plan. +- `terraform apply ` applies a previously saved plan. +- `terraform show ` (and `terraform show -json `) inspect a plan you previously saved. + +Saved plan runs are halfway between [speculative plans](#remote-speculative-plans) and standard [plan and apply runs](#remote-applies). They allow you to: + +- Perform cheap exploratory plans while retaining the option of applying a specific plan you are satisfied with. +- Perform other tasks in your terminal between the plan and apply stages. +- Perform the plan and apply operations on separate machines (as is common in continuous integration workflows). + +Saved plans become _stale_ once the state Terraform planned them against is no longer valid (usually due to someone applying a different run). In HCP Terraform, stale saved plan runs are automatically detected and discarded. When examining a remote saved plan, the `terraform show` command (without the `-json` option) informs you if a plan has been discarded or is otherwise unable to be applied. + +### File Contents and Permissions + +You can only apply remote saved plans in the same remote HCP Terraform workspace that performed the plan. Additionally, you can not apply locally executed saved plans in a remote workspace. + +In order to abide by HCP Terraform's permissions model, remote saved plans do not use the same local file format as locally executed saved plans. Instead, remote saved plans are a thin reference to a remote run, and the Terraform CLI relies on authenticated network requests to inspect and apply remote plans. This helps avoid the accidental exposure of credentials or other sensitive information. + +The `terraform show -json` command requires [workspace admin permissions](/terraform/enterprise/users-teams-organizations/permissions#workspace-admins) to inspect a remote saved plan; this is because the [machine-readable JSON plan format](/terraform/internals/json-format) contains unredacted sensitive information (alongside redaction hints for use by systems that consume the format). The human-readable version of `terraform show` only requires the read runs permission, because it uses pre-redacted information. + +[permissions-citation]: #intentionally-unused---keep-for-maintainers + +## Policy Enforcement + + + +@include 'tfc-package-callouts/policies.mdx' + + + +Policies are rules that HCP Terraform enforces on Terraform runs. You can use two policy-as-code frameworks to define fine-grained, logic-based policies: Sentinel and Open Policy Agent (OPA). + +If the specified workspace uses policies, HCP Terraform runs those policies against all speculative plans and remote applies in that workspace. Failed policies can pause or prevent an apply, depending on the enforcement level. Refer to [Policy Enforcement](/terraform/enterprise/policy-enforcement) for details. + +For Sentinel, the Terraform CLI prints policy results for CLI-driven runs. CLI support for policy results is not available for OPA. + +The following example shows Sentinel policy output in the terminal. + + $ terraform apply + + [...] + + Plan: 1 to add, 0 to change, 1 to destroy. + + ------------------------------------------------------------------------ + + Organization policy check: + + Sentinel Result: false + + Sentinel evaluated to false because one or more Sentinel policies evaluated + to false. This false was not due to an undefined value or runtime error. + + 1 policies evaluated. + ## Policy 1: my-policy.sentinel (soft-mandatory) + + Result: false + + FALSE - my-policy.sentinel:1:1 - Rule "main" + + Do you want to override the soft failed policy check? + Only 'override' will be accepted to override. + + Enter a value: override + +## Options for Plans and Applies + +[Run Modes and Options](/terraform/enterprise/run/modes-and-options) contains more details about the various options available for plans and applies when you use the CLI-driven workflow. + +## Networking/Connection Issues + +Sometimes during a CLI-driven run, errors relating to network connectivity issues arise. Examples of these kinds of errors include: + +- `Client.Timeout exceeded while awaiting headers` +- `context deadline exceeded` +- `TLS handshake timeout` + +Sometimes there are network problems beyond our control. If you have network errors, verify your network connection is operational. Then, check the following common configuration settings: + +- Determine if any firewall software on your system blocks the `terraform` command and explicitly approve it. +- Verify that you have a valid DNS server IP address. +- Remove any expired TLS certificates for your system. diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/run/install-software.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/run/install-software.mdx new file mode 100644 index 0000000000..31ff8d5b1c --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/run/install-software.mdx @@ -0,0 +1,111 @@ +--- +page_title: Install software in the HCP Terrafrom run environment +description: >- + Learn how to install Terraform providers, cloud CLIs, or configuration + management tools and software on Terraform Enterprise workers. +source: terraform-docs-common +--- + +# Install software in the run environment + +Terraform relies on provider plugins to manage resources. In most cases, Terraform can automatically download the required plugins, but there are cases where plugins must be managed explicitly. + +In rare cases, it might also be necessary to install extra software on the Terraform worker, such as a configuration management tool or cloud CLI. + +## Installing Terraform Providers + +The mechanics of provider installation changed in Terraform 0.13, thanks to the introduction of the [Terraform Registry][registry] for providers which allows custom and community providers to be installed via `terraform init`. Prior to Terraform 0.13, Terraform could only automatically install providers distributed by HashiCorp. + +### Terraform 0.13 and later + +#### Providers From the Terraform Registry + +The [Terraform Registry][registry] allows anyone to publish and distribute providers which can be automatically downloaded and installed via `terraform init`. + +Terraform Enterprise instances must be able to access `registry.terraform.io` to use providers from the public registry; otherwise, you can install providers using [the `terraform-bundle` tool][bundle]. + +[registry]: https://registry.terraform.io/browse/providers + +#### In-House Providers + +If you have a custom provider that you'd rather not publish in the public Terraform Registry, you have a few options: + +- Add the provider binary to the VCS repo (or manually-uploaded configuration version). Place the compiled `linux_amd64` version of the plugin at `terraform.d/plugins/////linux_amd64`, relative to the root of the directory. + + The source host and namespace will need to match the source given in the `required_providers` block within the configuration, but can otherwise be arbitrary identifiers. For instance, if your `required_providers` block looks like this: + + terraform { + required_providers { + custom = { + source = "my-host/my-namespace/custom" + version = "1.0.0" + } + } + } + + HCP Terraform will be able to use your compiled provider if you place it at `terraform.d/plugins/my-host/my-namespace/custom/1.0.0/linux_amd64/terraform-provider-custom`. + +- Use a privately-owned provider registry service which implements the [provider registry protocol](/terraform/internals/provider-registry-protocol) to distribute custom providers. Be sure to include the full [source address](/terraform/language/providers/requirements#source-addresses), including the hostname, when referencing providers. + +- **Terraform Enterprise only:** Use [the `terraform-bundle` tool][bundle] to add custom providers. + +-> **Note:** Using a [network mirror](/terraform/internals/provider-network-mirror-protocol) to host custom providers for installation is not currently supported in HCP Terraform, since the network mirror cannot be activated without a [`provider_installation`](/terraform/cli/config/config-file#explicit-installation-method-configuration) block in the CLI configuration file. + +### Terraform 0.12 and earlier + +#### Providers Distributed by HashiCorp + +HCP Terraform can automatically install providers distributed by HashiCorp. Terraform Enterprise instances can do this as well as long as they can access `releases.hashicorp.com`. + +If that isn't feasible due to security requirements, you can manually install providers. Use [the `terraform-bundle` tool][bundle] to build a custom version of Terraform that includes the necessary providers, and configure your workspaces to use that bundled version. + +[bundle]: https://github.com/hashicorp/terraform/tree/master/tools/terraform-bundle#installing-a-bundle-in-on-premises-terraform-enterprise + +#### Custom and Community Providers + +To use community providers or your own custom providers with Terraform versions prior to 0.13, you must install them yourself. + +There are two ways to accomplish this: + +- Add the provider binary to the VCS repo (or manually-uploaded configuration version) for any workspace that uses it. Place the compiled `linux_amd64` version of the plugin at `terraform.d/plugins/linux_amd64/` (as a relative path from the root of the working directory). The plugin name should follow the [naming scheme](/terraform/language/v1.1.x/configuration-0-11/providers#plugin-names-and-versions) and the plugin file must have read and execute permissions. (Third-party plugins are often distributed with an appropriate filename already set in the distribution archive.) + + You can add plugins directly to a configuration repo, or you can add them as Git submodules and symlink the executable files into `terraform.d/plugins/`. Submodules are a good choice when many workspaces use the same custom provider, since they keep your repos smaller. If using submodules, enable the ["Include submodules on clone" setting](/terraform/enterprise/workspaces/settings/vcs#include-submodules-on-clone) on any affected workspace. + +- **Terraform Enterprise only:** Use [the `terraform-bundle` tool][bundle] to add custom providers to a custom Terraform version. This keeps custom providers out of your configuration repos entirely, and is easier to update when many workspaces use the same provider. + +## Installing Additional Tools + +### Avoid Installing Extra Software + +Whenever possible, don't install software on the worker. There are a number of reasons for this: + +- Provisioners are a last resort in Terraform; they greatly increase the risk of creating unknown states with unmanaged and partially-managed infrastructure, and the `local-exec` provisioner is especially hazardous. [The Terraform CLI docs on provisioners](/terraform/language/resources/provisioners/syntax#provisioners-are-a-last-resort) explain the hazards in more detail, with more information about the preferred alternatives. (In summary: use Packer, use cloud-init, try to make your infrastructure more immutable, and always prefer real provider features.) +- We don't guarantee the stability of the operating system on the Terraform build workers. It's currently the latest version of Ubuntu LTS, but we reserve the right to change that at any time. +- The build workers are disposable and are destroyed after each use, which makes managing extra software even more complex than when running Terraform CLI in a persistent environment. Custom software must be installed on every run, which also increases run times. + +### Only Install Standalone Binaries + +HCP Terraform does not allow you to elevate a command's permissions with `sudo` during Terraform runs. This means you cannot install packages using the worker OS's normal package management tools. However, you can install and execute standalone binaries in Terraform's working directory. + +You have two options for getting extra software into the configuration directory: + +- Include it in the configuration repository as a submodule. (Make sure the workspace is configured to clone submodules.) +- Use `local-exec` to download it with `curl`. For example: + + ```hcl + resource "aws_instance" "example" { + ami = "${var.ami}" + instance_type = "t2.micro" + provisioner "local-exec" { + command = < **Note:** Terraform Enterprise instances can be configured to allow `sudo` commands during Terraform runs. However, even when `sudo` is allowed, using the worker OS's package tools during runs is still usually a bad idea. You will have a much better experience if you can move your provisioner actions into a custom provider or an immutable machine image. diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/run/manage.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/run/manage.mdx new file mode 100644 index 0000000000..2594b81dae --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/run/manage.mdx @@ -0,0 +1,78 @@ +--- +page_title: Manage and view runs in Terraform Enterprise +description: >- + Learn how to view and interact with runs in Terraform Enterprise, and how to + unlock and lock workspaces to prevent new runs. +source: terraform-docs-common +--- + +# Manage and view runs + +Each workspace in HCP Terraform includes a list of its current, pending, and historical runs. You can view and interact with these runs in the UI. You can also lock workspaces to temporarily prevent new runs. + +## API + +Refer to the [Runs API](/terraform/enterprise/api-docs/run) and [lock a Workspace endpoint](/terraform/enterprise/api-docs/workspaces#lock-a-workspace). + +## Navigating Runs + +Go to the workspace and click the **Runs** tab to review a list of all current and past Terraform runs. + +Click a run to go to its details page. The details page contains the following information: + +- The current status of the run. +- The code commit associated with the run. +- How the run initiated, when, and which user initiated it (if applicable). +- A timeline of events related to the run. +- The output from both the `terraform plan` and `terraform apply` commands, if applicable. This output defaults to visible if the command is currently running and hidden if the command has finished. + +## Interacting with Runs + +In workspaces where you have [permission to apply runs](/terraform/enterprise/users-teams-organizations/permissions#general-workspace-permissions), you can interact with a run at the bottom of its details page. + +The following options are available, depending on the state of the run: + +| Button | Available when: | +| ------------------- | --------------------------------------------------------------------------------------------------------------- | +| Add Comment | Always. | +| Confirm & Apply | A plan needs confirmation. | +| Override & Continue | A soft-mandatory policy failed. Requires permission to manage policy overrides for the organization. | +| Discard Run | A plan needs confirmation or a soft-mandatory policy failed. | +| Cancel Run | A plan or apply is currently running. | +| Force Cancel Run | A plan or apply canceled, but HCP Terraform was unable to end the run. Requires admin access to the workspace. | +| Retry Run | A plan-only run has finished. You can also change which Terraform version to use when retrying a plan-only run. | + +[permissions-citation]: #intentionally-unused---keep-for-maintainers + +If a plan needs confirmation (with [manual apply](/terraform/enterprise/workspaces/settings#auto-apply-and-manual-apply) enabled) or a soft-mandatory policy failed, the run remains paused until a user with appropriate permissions uses these buttons to continue or discard the run. Refer to [Run States and Stages](/terraform/enterprise/run/states) for more details. + +### Canceling Runs + +If a run is currently planning or applying, users with [permission to apply runs](/terraform/enterprise/users-teams-organizations/permissions#general-workspace-permissions) for the workspace can click **Cancel Run** to stop the run before it finishes. + +[permissions-citation]: #intentionally-unused---keep-for-maintainers + +Canceling a run is roughly equivalent to typing `ctrl+c` during a Terraform plan or apply on the CLI. The running Terraform process is sent an INT signal, which instructs Terraform to end its work, update state for any resources that have already been changed, and wrap up in the safest way possible. + +In rare cases, a canceled run can fail to end, continuing to lock the workspace. You can forcefully cancel these runs, which immediately terminates the running Terraform process and unlocks the workspace. + +Force-canceling requires admin access to the workspace because it can have dangerous side-effects, including loss of state and orphaned resources. Additionally, the **Force Cancel Run** button only appears after you click **Cancel Run** and HCP Terraform has time to terminate the run safely. + +[permissions-citation]: #intentionally-unused---keep-for-maintainers + +## Locking Workspaces (Preventing Runs) + +You can lock the workspace to temporarily stop runs from proceeding. Locking a workspace requires [permission to lock and unlock the workspace](/terraform/enterprise/users-teams-organizations/permissions#general-workspace-permissions), and requires that the workspace is not currently locked by an in-progress run. + +A lock prevents HCP Terraform from performing any applies in the workspace, and also prevents many kinds of plans. New runs remain in the **Pending** state until the workspace unlocks. + +Locking **does not** affect [plan-only runs](/terraform/enterprise/run/remote-operations#speculative-plans) or the planning stages of [saved plan runs](/terraform/enterprise/run/cli#remote-saved-plans). Terraform allows these types of runs because they can not affect infrastructure resources, do not attempt to lock the workspace themselves, and might provide important information about tasks to perform before removing the lock. Note that you can not _apply_ saved-plan runs while the workspace is locked, and HCP Terraform automatically discards these runs if the workspace's state is changed before they can be applied. Terraform Enterprise does not yet support saved plans. + +HCP Terraform shows the lock status in the workspace's header, next to the **Actions** menu. + +To lock or unlock a workspace, do one of the following: + +- Open the **Actions** menu and select **Lock workspace** or **Unlock workspace**. +- Go to **Settings > Locking**. + +[permissions-citation]: #intentionally-unused---keep-for-maintainers diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/run/modes-and-options.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/run/modes-and-options.mdx new file mode 100644 index 0000000000..9a1e6a0f8d --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/run/modes-and-options.mdx @@ -0,0 +1,123 @@ +--- +page_title: Run modes and options in Terraform Enterprise +description: >- + Learn about the different run modes and options available in Terraform + Enterprise to customize behavior during runs. +source: terraform-docs-common +--- + +# Run modes and options + +HCP Terraform runs support many of the same modes and options available in the Terraform CLI. + +## Plan and Apply (Standard) + +The default run mode of HCP Terraform is to perform a plan and then apply it. If you have enabled auto-apply and are using a VCS or API workflow, a successful plan applies immediately. Otherwise, the run waits for user confirmation before applying. + +- **CLI:** Use `terraform apply` (without providing a saved plan file). +- **API:** Create a run without specifying any options that select a different mode. +- **UI:** From the workspace's overview page, click **+ New run**, and then choose **Plan and apply (standard)** as the run type. +- **VCS:** When a workspace is connected to a VCS repository, HCP Terraform automatically starts a standard plan and apply when you add new commits to the selected branch of that repository. + +## Destroy Mode + +[Destroy mode](/terraform/cli/commands/plan#planning-modes) instructs Terraform to create a plan which destroys all objects, regardless of configuration changes. + +- **CLI:** Use `terraform plan -destroy` or `terraform destroy` +- **API:** Use the `is-destroy` option. +- **UI:** Use a workspace's **Destruction and Deletion** settings page. + +## Plan Only/Speculative Plan + +This option creates a [speculative plan](/terraform/enterprise/run/remote-operations#speculative-plans). The speculative plan shows a set of possible changes and checks them against Sentinel policies, but Terraform can _not_ apply this plan. + +You can create speculative plans with a different Terraform version than the one currently selected for a workspace. This lets you check whether your configuration is compatible with a newer Terraform version without changing the workspace settings. + +Plan-only runs ignore the per-workspace run queue. Plan-only runs can proceed even if another run is in progress, can not become the workspace's current run, and do not block progress on a workspace's other runs. + +- **API:** Set the `plan-only` option to `true` and specify an available terraform version using the `terraform-version` field. +- **UI:** From the workspace's overview page, click **+ New run**, and then choose **Plan only** as the run type. +- **VCS:** When a workspace is connected to a VCS repository, HCP Terraform automatically starts a speculative plan when someone opens a pull request (or merge request) against the selected branch of that repository. The pull/merge request view in your VCS links to the speculative plan, and you can also find it in the workspace's run list. + +## Saved Plans + +-> **Version note:** Using saved plans from the CLI with HCP Terraform requires at least Terraform CLI v1.6.0. + +Saved plan runs are very similar to standard plan and apply runs: they perform a plan and then optionally apply it. There are three main differences: + +1. _No wait for planning._ Saved plan runs ignore the per-workspace run queue during their plan and checks. Like plan-only runs, saved plans can begin planning even if another run is in progress, without blocking progress on other runs. +2. _No auto-apply._ Saved plan runs are never auto-applied, even if you enabled auto-apply for the workspace. Saved plans only apply if you confirm them. +3. _Automatic discard for stale plans._ If another run is applied (or the state is otherwise modified) before a saved plan run is confirmed, HCP Terraform automatically discards that saved plan. HCP Terraform may also automatically discard saved plans if they are not confirmed within a few weeks. + +Saved plans are ideal for interactive CLI workflows, where you can perform many exploratory plans and then choose one to apply, or for custom continuous integration workflows where the default run queue behavior isn't suitable. + +- **CLI:** Use `terraform plan -out ` to perform and save a plan, then use `terraform apply ` to apply the saved plan. Use `terraform show ` to inspect a saved plan before applying it. +- **API:** Use the `save-plan` option when creating a run. If you create a new configuration version for a saved plan run, use the `provisional` option so that it will not become the workspace's current configuration version until you decide to apply the run. + +## Allow Empty Apply + +A no-operation (empty) apply enables HCP Terraform to apply a run from a plan that contains no infrastructure changes. During apply, Terraform can upgrade the state version if required. You can use this option to upgrade the state in your HCP Terraform workspace to a new Terraform version. Only some Terraform versions require this, most notably 0.13. + +To make such upgrades easier, empty apply runs will always auto-apply if their plan contains no changes. + +~> **Warning:** HCP Terraform cannot guarantee that a plan in this mode will produce no changes. We recommend checking the plan for drift before proceeding to the apply stage. + +- **API:** Set the `allow-empty-apply` field to `true`. +- **UI:** From the workspace's overview page, click **+ New run**, and then choose **Allow empty apply** as the run type. + +## Refresh-Only Mode + +> **Hands-on:** Try the [Use Refresh-Only Mode to Sync Terraform State](/terraform/tutorials/state/refresh) tutorial. + +-> **Version note:** Refresh-only support requires a workspace using at least Terraform CLI v0.15.4. + +[Refresh-only mode](/terraform/cli/commands/plan#planning-modes) instructs Terraform to create a plan that updates the Terraform state to match changes made to remote objects outside of Terraform. This is useful if state drift has occurred and you want to reconcile your state file to match the drifted remote objects. Applying a refresh-only run does not result in further changes to remote objects. + +- **CLI:** Use `terraform plan -refresh-only` or `terraform apply -refresh-only`. +- **API:** Use the `refresh-only` option. +- **UI:** From the workspace's overview page, click **+ New run**, and then choose **Refresh state** as the run type. + +## Skipping Automatic State Refresh + +The [`-refresh=false` option](/terraform/cli/commands/plan#refresh-false) is used in normal planning mode to skip the default behavior of refreshing Terraform state before checking for configuration changes. + +- **CLI:** Use `terraform plan -refresh=false` or `terraform apply -refresh=false`. +- **API:** Use the `refresh` option. + +## Replacing Selected Resources + +-> **Version note:** Replace support requires a workspace using at least Terraform CLI v0.15.2. + +The [replace option](/terraform/cli/commands/plan#replace-address) instructs Terraform to replace the object with the given resource address. + +- **CLI:** Use `terraform plan -replace=ADDRESS` or `terraform apply -replace=ADDRESS`. +- **API:** Use the `replace-addrs` option. +- **UI:** Click **+ New run** and select the **Plan and apply (standard)** run type. Then click **Additional planning options** to reveal the **Replace resources** option. Type the address of the resource that you want to replace. You can replace multiple resources. + +## Targeted Plan and Apply + +[Resource Targeting](/terraform/cli/commands/plan#resource-targeting) is intended for exceptional circumstances only and should not be used routinely. + +- **CLI:** Use `terraform plan -target=ADDRESS` or `terraform apply -target=ADDRESS`. +- **API:** Use the `target-addrs` option. + +The usual caveats for targeting in local operations imply some additional limitations on HCP Terraform features for remote plans created with targeting: + +- [Sentinel](/terraform/enterprise/policy-enforcement) policy checks for targeted plans will see only the selected subset of resource instances planned for changes in [the `tfplan` import](/terraform/enterprise/policy-enforcement/import-reference/tfplan-v2) and [the `tfplan/v2` import](/terraform/enterprise/policy-enforcement/import-reference/tfplan-v2), which may cause an unintended failure for any policy that requires a planned change to a particular resource instance selected by its address. + +- [Cost Estimation](/terraform/enterprise/cost-estimation) is disabled for any run created with `-target` set, to prevent producing a misleading underestimate of cost due to resource instances being excluded from the plan. + +You can disable or constrain use of targeting in a particular workspace using a Sentinel policy based on [the `tfrun.target_addrs` value](/terraform/enterprise/policy-enforcement/import-reference/tfrun#value-target_addrs). + +## Generating Configuration + +-> **Version note:** Support for `import` blocks and generating configuration requires a workspace using at least Terraform CLI v1.5.0. + +When using [`import` blocks](/terraform/language/import) to import existing resources, Terraform can [automatically generate configuration](/terraform/language/import/generating-configuration) during the plan for any imported resources that don't have an existing `resource` block. This option is enabled by default for runs started from the UI or from a VCS webhook. + +- **CLI:** Use `terraform plan -generate-config-out=generated.tf`. +- **API:** Use the `allow-config-generation` option. + +You can find generated configuration displayed in the plan UI. If you're using the CLI workflow, Terraform will write generated configuration to the file you specify when running `terraform plan`. + +Once Terraform has generated configuration for you, you'll need to review it, incorporate it in your Terraform configuration (including committing it to version control), then run another plan. If you try to directly apply a plan with generated configuration, the run will error. diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/run/remote-operations.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/run/remote-operations.mdx new file mode 100644 index 0000000000..8091309a5e --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/run/remote-operations.mdx @@ -0,0 +1,148 @@ +--- +page_title: Remote operations in Terraform Enterprise +description: >- + Terraform Enterprise runs Terraform operations remotely through the UI, API, + or CLI. Learn how HCP Terraform manages runs. +source: terraform-docs-common +--- + +# Remote operations + +> **Hands-on:** Try the [Get Started — HCP Terraform](/terraform/tutorials/cloud-get-started?utm_source=WEBSITE&utm_medium=WEB_IO&utm_offer=ARTICLE_PAGE&utm_content=DOCS) tutorials. + +HCP Terraform provides a central interface for running Terraform within a large collaborative organization. If you're accustomed to running Terraform from your workstation, the way HCP Terraform manages runs can be unfamiliar. + +This page describes the basics of how runs work in HCP Terraform. + +## Remote Operations + +HCP Terraform is designed as an execution platform for Terraform, and can perform Terraform runs on its own disposable virtual machines. This provides a consistent and reliable run environment, and enables advanced features like Sentinel policy enforcement, cost estimation, notifications, version control integration, and more. + +Terraform runs managed by HCP Terraform are called _remote operations._ Remote runs can be initiated by webhooks from your VCS provider, by UI controls within HCP Terraform, by API calls, or by Terraform CLI. When using Terraform CLI to perform remote operations, the progress of the run is streamed to the user's terminal, to provide an experience equivalent to local operations. + +### Disabling Remote Operations + +[execution_mode]: /terraform/enterprise/workspaces/settings#execution-mode + +Many of HCP Terraform's features rely on remote execution and are not available when using local operations. This includes features like Sentinel policy enforcement, cost estimation, and notifications. + +You can disable remote operations for any workspace by changing its [Execution Mode][execution_mode] to **Local**. This causes the workspace to act only as a remote backend for Terraform state, with all execution occurring on your own workstations or continuous integration workers. + +### Protecting Private Environments + +[HCP Terraform agents](/terraform/cloud-docs/agents) are a paid feature that allows HCP Terraform to communicate with isolated, private, or on-premises infrastructure. The agent polls HCP Terraform or Terraform Enterprise for any changes to your configuration and executes the changes locally, so you do not need to allow public ingress traffic to your resources. Agents allow you to control infrastructure in private environments without modifying your network perimeter. + +HCP Terraform agents also support running custom programs, called _hooks_, during strategic points of a Terraform run. For example, you may create a hook to dynamically download software required by the Terraform run or send an HTTP request to a system to kick off an external workflow. + +## Runs and Workspaces + +HCP Terraform always performs Terraform runs in the context of a [workspace](/terraform/enterprise/run/remote-operations). The workspace serves the same role that a persistent working directory serves when running Terraform locally: it provides the configuration, state, and variables for the run. + +### Configuration Versions + +Each workspace is associated with a particular Terraform configuration, but that configuration is expected to change over time. Thus, HCP Terraform manages configurations as a series of _configuration versions._ + +Most commonly, a workspace is linked to a VCS repository, and its configuration versions are tied to revisions in the specified VCS branch. In workspaces that aren't linked to a repository, new configuration versions can be uploaded via Terraform CLI or via the API. + +### Ordering and Timing + +Each workspace in HCP Terraform maintains its own queue of runs, and processes those runs in order. + +Whenever a new run is initiated, it's added to the end of the queue. If there's already a run in progress, the new run won't start until the current one has completely finished — HCP Terraform won't even plan the run yet, because the current run might change what a future run would do. Runs that are waiting for other runs to finish are in a _pending_ state, and a workspace might have any number of pending runs. + +There are two exceptions to the run queue, which can proceed at any time and do not block the progress of other runs: + +- Plan-only runs. +- The planning stages of [saved plan runs](/terraform/enterprise/run/modes-and-options/#saved-plans). You can only _apply_ a saved plan if no other run is in progress, and applying that plan blocks the run queue as usual. Terraform Enterprise does not yet support this workflow. + +When you initiate a run, HCP Terraform locks the run to a particular configuration version and set of variable values. If you change variables or commit new code before the run finishes, it will only affect future runs, not runs that are already pending, planning, or awaiting apply. + +### Workspace Locks + +When a workspace is _locked,_ HCP Terraform can create new runs (automatically or manually), but those runs do not begin until you unlock the workspace. + +When a run is in progress, that run locks the workspace, as described above under "Ordering and Timing". + +There are two kinds of run operation that can ignore workspace locking: + +- Plan-only runs. +- The planning stages of [saved plan runs](/terraform/enterprise/run/modes-and-options/#saved-plans). You can only _apply_ a saved plan if the workspace is unlocked, and applying that plan locks the workspace as usual. Terraform Enterprise does not yet support this workflow. + +A user or team can also deliberately lock a workspace, to perform maintenance or for any other reason. For more details, see [Locking Workspaces (Preventing Runs)](/terraform/enterprise/run/manage#locking-workspaces-preventing-runs-). + +## Starting Runs + +HCP Terraform has three main workflows for managing runs, and your chosen workflow determines when and how Terraform runs occur. For detailed information, see: + +- The [UI/VCS-driven run workflow](/terraform/enterprise/run/ui), which is the primary mode of operation. +- The [API-driven run workflow](/terraform/enterprise/run/api), which is more flexible but requires you to create some tooling. +- The [CLI-driven run workflow](/terraform/enterprise/run/cli), which uses Terraform's standard CLI tools to execute runs in HCP Terraform. + +You can use the following methods to initiate HCP Terraform runs: + +- Click the **+ New run** button on the workspace's page +- Implement VCS webhooks +- Run the standard `terraform apply` command when the CLI integration is configured +- Call [the Runs API](/terraform/enterprise/api-docs/run) using any API tool + +## Plans and Applies + +HCP Terraform enforces Terraform's division between _plan_ and _apply_ operations. It always plans first, then uses that plan's output for the apply. + +In the default configuration, HCP Terraform waits for user approval before running an apply, but you can configure workspaces to [automatically apply](/terraform/enterprise/workspaces/settings#auto-apply-and-manual-apply) successful plans. Some plans can't be auto-applied, like plans queued by [run triggers](/terraform/enterprise/workspaces/settings/run-triggers) or by users without permission to apply runs for the workspace. ([More about permissions.](/terraform/enterprise/users-teams-organizations/permissions)) + +[permissions-citation]: #intentionally-unused---keep-for-maintainers + +If a plan contains no changes, HCP Terraform does not attempt to apply it. Instead, the run ends with a status of "Planned and finished". The [allow empty apply](/terraform/enterprise/run/modes-and-options#allow-empty-apply) run mode can override this behavior. + +### Speculative Plans + +In addition to normal runs, HCP Terraform can run _speculative plans_ to test changes to a configuration during editing and code review. Speculative plans are plan-only runs. They show possible changes, and policies affected by those changes, but cannot apply any changes. + +Speculative plans can begin without waiting for other runs to finish because they don't affect real infrastructure. HCP Terraform lists past speculative plan runs alongside a workspace's other runs. + +There are three ways to run speculative plans: + +- In VCS-backed workspaces, pull requests start speculative plans, and the VCS provider's pull request interface includes a link to the plan. See [UI/VCS Runs: Speculative Plans on Pull Requests](/terraform/enterprise/run/ui#speculative-plans-on-pull-requests) for more details. +- With the [CLI integration](/terraform/cli/cloud) configured, running `terraform plan` on the command line starts a speculative plan. The plan output streams to the terminal, and a link to the plan is also included. +- The runs API creates speculative plans whenever the specified configuration version is marked as speculative. See [the `configuration-versions` API](/terraform/enterprise/api-docs/configuration-versions#create-a-configuration-version) for more information. + +#### Retry a speculative plan in the UI + +If a speculative plan fails due to an external factor, you can run it again using the "Retry Run" button on its page: + +Retrying a plan requires permission to queue plans for that workspace. ([More about permissions.](/terraform/enterprise/users-teams-organizations/permissions)) Only failed or canceled plans can be retried. + +[permissions-citation]: #intentionally-unused---keep-for-maintainers + +Retrying the run will create a new run with the same configuration version. If it is a VCS-backed workspace, the pull request interface will receive the status of the new run, along with a link to the new run. + +### Saved Plans + +-> **Version note:** Using saved plans from the CLI with HCP Terraform requires at least Terraform CLI v1.6.0. + +HCP Terraform also supports saved plan runs. If you have configured the [CLI integration](/terraform/cli/cloud) you can use `terraform plan -out ` to perform and save a plan, `terraform apply ` to apply a saved plan, and `terraform show ` to inspect a saved plan before applying it. You can also create saved plan runs via the API by using the `save-plan` option. + +Saved plan runs affect the run queue differently from normal runs, and can sometimes be automatically discarded. For more details, refer to [Run Modes and Options: Saved Plans](/terraform/enterprise/run/modes-and-options#saved-plans). + +## Planning Modes and Options + +In addition to the normal run workflows described above, HCP Terraform supports destroy runs, refresh-only runs, and several planning options that can modify the behavior of a run. For more details, see [Run Modes and Options](/terraform/enterprise/run/modes-and-options). + +## Run States + +HCP Terraform shows the progress of each run as it passes through each run state (pending, plan, policy check, apply, and completion). In some states, the run might require confirmation before continuing or ending; see [Managing Runs: Interacting with Runs](/terraform/enterprise/run/manage#interacting-with-runs) for more information. + +In the list of workspaces on HCP Terraform's main page, each workspace shows the state of the run it's currently processing. (Or, if no run is in progress, the state of the most recent completed run.) + +For full details about the stages of a run, see [Run States and Stages][]. + +[Run States and Stages]: /terraform/enterprise/run/states + +## Import + +We recommend using [`import` blocks](/terraform/language/import), introduced in Terraform 1.5, to import resources in HCP Terraform. + +HCP Terraform does not support remote execution for the `terraform import` command. For this command the workspace acts only as a remote backend for Terraform state, with all execution occurring on your own workstations or continuous integration workers. + +Since `terraform import` runs locally, environment variables defined in the workspace are not available. Any environment variables required by the provider you're importing from must be defined within your local execution scope. diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/run/run-environment.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/run/run-environment.mdx new file mode 100644 index 0000000000..ccae094a3f --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/run/run-environment.mdx @@ -0,0 +1,131 @@ +--- +page_title: Terraform Enterprise's run environment +description: >- + Learn how Terraform Enterprise's run enviornment manages virtual machines, + network access, concurrency for runs, state access authentication, and + environment variables. +source: terraform-docs-common +--- + +# HCP Terraform's run environment + +HCP Terraform is designed as an execution platform for Terraform, and most of its features are based around its ability to perform Terraform runs in a fleet of disposable worker VMs. This page describes some features of the run environment for Terraform runs managed by HCP Terraform. + +## The Terraform Worker VMs + +HCP Terraform performs Terraform runs in single-use Linux virtual machines, running on an x86_64 architecture. + +The operating system and other software installed on the worker VMs is an internal implementation detail of HCP Terraform. It is not part of a stable public interface, and is subject to change at any time. + +Before Terraform is executed, the worker VM's shell environment is populated with environment variables from the workspace, the selected version of Terraform is installed, and the run's Terraform configuration version is made available. + +Changes made to the worker during a run are not persisted to subsequent runs, since the VM is destroyed after the run is completed. Notably, this requires some additional care when installing additional software with a `local-exec` provisioner; see [Installing Additional Tools](/terraform/enterprise/run/install-software#installing-additional-tools) for more details. + +> **Hands-on:** Try the [Upgrade Terraform Version in HCP Terraform](/terraform/tutorials/cloud/cloud-versions) tutorial. + +## Network Access to VCS and Infrastructure Providers + +In order to perform Terraform runs, HCP Terraform needs network access to all of the resources Terraform is going to manage. + +If you are using the cloud version of HCP Terraform, your VCS provider and any private infrastructure providers you manage with Terraform must be internet accessible. + + + +If you are on the HCP Terraform **Premium** edition, you can use a self-hosted HCP Terraform agent to connect to private VCS providers. Refer to [Connect to private VCS providers](/terraform/enterprise/vcs/private) for more information. + + + +Terraform Enterprise instances must have network connectivity to any connected VCS providers or managed infrastructure providers. + +## Concurrency and Run Queuing + +HCP Terraform uses multiple concurrent worker VMs, which take jobs from a global queue of runs that are ready for processing. (This includes confirmed applies, and plans that have just become the current run on their workspace.) + +If the global queue has more runs than the workers can handle at once, some of them must wait until a worker becomes available. When the queue is backed up, HCP Terraform gives different priorities to different kinds of runs: + +- Applies that will make changes to infrastructure have the highest priority. +- Normal plans have the next highest priority. +- Speculative plans have the lowest priority. + +HCP Terraform can also delay some runs in order to make performance more consistent across organizations. If an organization requests a large number of runs at once, HCP Terraform queues some of them immediately, and delays the rest until some of the initial batch have finished. Queuing and delaying runs lets organizations continue performing runs even during periods of especially heavy load. Your HCP Terraform edition limits the maximum run concurrency for your organization. Refer to [HCP Terraform pricing](https://www.hashicorp.com/products/terraform/pricing?product_intent=terraform) for details. + +## State Access and Authentication + +[CLI config file]: /terraform/cli/config/config-file + +[cloud]: /terraform/cli/cloud + +HCP Terraform stores state for its workspaces. + +When you trigger runs via the [CLI workflow](/terraform/enterprise/run/cli), Terraform reads from and writes to HCP Terraform's stored state. HCP Terraform uses [the `cloud` block][cloud] for runs, overriding any existing [backend](/terraform/language/settings/backends/configuration) in the configuration. + +-> **Note:** The `cloud` block is available in Terraform v1.1 and later. Previous versions can use the [`remote` backend](/terraform/language/settings/backends/remote) to configure the CLI workflow and migrate state. + +### Autogenerated API Token + +Instead of using existing user credentials, HCP Terraform generates a unique per-run API token and provides it to the Terraform worker in the [CLI config file][]. When you run Terraform on the command line against a workspace configured for remote operations, you must have [the `cloud` block][cloud] in your configuration and have a user or team API token with the appropriate permissions specified in your [CLI config file][]. However, the run itself occurs within one of HCP Terraform's worker VMs and uses the per-run token for state access. + +The per-run token can read and write state data for the workspace associated with the run, can download modules from the [private registry](/terraform/enterprise/registry), and may be granted access to read state from other workspaces in the organization. (Refer to [cross-workspace state access](/terraform/enterprise/workspaces/state#accessing-state-from-other-workspaces) for more details.) Per-run tokens cannot make any other calls to the HCP Terraform API and are not considered to be user, team, or organization tokens. They become invalid after the run is completed. + +### User Token + +HCP Terraform uses the user token to access a workspace's state when you: + +- Run Terraform on the command line against a workspace that is _not_ configured for remote operations. The user must have permission to read and write state versions for the workspace. + +- Run Terraform's state manipulation commands against an HCP Terraform workspace. The user must have permission to read and write state versions for the workspace. + +Refer to [Permissions](/terraform/enterprise/users-teams-organizations/permissions#workspace-permissions) for more details about workspace permissions. + +### Provider Authentication + +Runs in HCP Terraform typically require some form of credentials to authenticate with infrastructure providers. Credentials can be provided statically through Environment or Terraform [variables](/terraform/enterprise/workspaces/variables), or can be generated on a per-run basis through [dynamic credentials](/terraform/enterprise/workspaces/dynamic-provider-credentials) for certain providers. Below are pros and cons to each approach. + +#### Static Credentials + +##### Pros + +- Simple to setup +- Broad support across providers + +##### Cons + +- Requires regular manual rotation for enhanced security posture +- Large blast radius if a credential is exposed and needs to be revoked + +#### Dynamic Credentials + +##### Pros + +- Eliminates the need for manual rotation of credentials on HCP Terraform +- HCP Terraform metadata - including the run's project, workspace, and run-phase - is encoded into every token to allow for granular permission scoping on the target cloud platform +- Credentials are short-lived, which reduces blast radius of potential credential exposure + +##### Cons + +- More complicated initial setup compared to using static credentials +- Not supported for all providers + +The full list of supported providers and setup instructions can be found in the [dynamic credentials](/terraform/enterprise/workspaces/dynamic-provider-credentials) documentation. + +[permissions-citation]: #intentionally-unused---keep-for-maintainers + +### Environment Variables + +HCP Terraform automatically injects the following environment variables for each run: + +| Variable Name | Description | Example | +| ------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------- | +| `TFC_RUN_ID` | A unique identifier for this run. | `run-CKuwsxMGgMd4W7Ui` | +| `TFC_WORKSPACE_NAME` | The name of the workspace used in this run. | `prod-load-balancers` | +| `TFC_WORKSPACE_SLUG` | The full slug of the configuration used in this run. This consists of the organization name and workspace name, joined with a slash. | `acme-corp/prod-load-balancers` | +| `TFC_CONFIGURATION_VERSION_GIT_BRANCH` | The name of the branch that the associated Terraform configuration version was ingressed from. | `main` | +| `TFC_CONFIGURATION_VERSION_GIT_COMMIT_SHA` | The full commit hash of the commit that the associated Terraform configuration version was ingressed from. | `abcd1234...` | +| `TFC_CONFIGURATION_VERSION_GIT_TAG` | The name of the tag that the associated Terraform configuration version was ingressed from. | `v0.1.0` | +| `TFC_PROJECT_NAME` | The name of the project used in this run. | `proj-name` | + +They are also available as Terraform input variables by defining a variable with the same name. For example: + +```terraform +variable "TFC_RUN_ID" {} +``` diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/run/states.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/run/states.mdx new file mode 100644 index 0000000000..a8f444e0a3 --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/run/states.mdx @@ -0,0 +1,218 @@ +--- +page_title: Run states and stages in Terraform Enterprise +description: >- + Learn the run stages of Terraform operations. Understanding run stages and + their states can help you follow a run's progress. +source: terraform-docs-common +--- + +# Run states and stages + +Each plan and apply run passes through several stages of action: pending, plan, cost estimation, policy check, apply, and completion. HCP Terraform shows a run's progress through each stage as a run state. + +In the list of workspaces on HCP Terraform's main page, each workspace shows the state of the run it's currently processing. If no run is in progress, HCP Terraform displays the state of the most recently completed run. + +## The Pending Stage + +_States in this stage:_ + +- **Pending:** HCP Terraform hasn't started action on a run yet. HCP Terraform processes each workspace's runs in the order they were queued, and a run remains pending until every run before it has completed. + +_Leaving this stage:_ + +- If the user discards the run before it starts, the run does not continue (**Discarded** state). +- If the run is first in the queue, it proceeds automatically to the plan stage (**Planning** state). + +## The Fetching Stage + +HCP Terraform may need to fetch the configuration from VCS prior to starting the plan. HCP Terraform automatically archives configuration versions created through VCS when all runs are complete and then re-fetches the files for subsequent runs. + +_States in this stage:_ + +- **Fetching:** If HCP Terraform has not yet fetched the configuration from VCS, the run will go into this state until the configuration is available. + +_Leaving this stage:_ + +- If HCP Terraform encounters an error when fetching the configuration from VCS, the run does not continue (**Plan Errored** state). +- If Terraform successfully fetches the configuration, the run moves to the next stage. + +## The Pre-Plan Stage + +The pre-plan phase only occurs if there are enabled [run tasks](/terraform/enterprise/workspaces/settings/run-tasks) in the workspace that are configured to begin before Terraform creates the plan. HCP Terraform sends information about the run to the configured external system and waits for a `passed` or `failed` response to determine whether the run can continue. The information sent to the external system includes the configuration version of the run. + +All runs can enter this phase, including [speculative plans](/terraform/enterprise/run/remote-operations#speculative-plans). + +_States in this stage:_ + +- **Pre-plan running:** HCP Terraform is waiting for a response from the configured external system(s). + - External systems must respond initially with a `200 OK` acknowledging the request is in progress. After that, they have 10 minutes to return a status of `passed`, `running`, or `failed`. If the timeout expires, HCP Terraform assumes that the run tasks is in the `failed` status. + +_Leaving this stage:_ + +- If any mandatory tasks failed, the run skips to completion (**Plan Errored** state). +- If any advisory tasks failed, the run proceeds to the **Planning** state, with a visible warning regarding the failed task. +- If a single run has a combination of mandatory and advisory tasks, Terraform takes the most restrictive action. For example, the run fails if there are two advisory tasks that succeed and one mandatory task that fails. +- If a user canceled the run, the run ends in the **Canceled** state. + +## The Plan Stage + +A run goes through different steps during the plan stage depending on whether or not HCP Terraform needs to fetch the configuration from VCS. HCP Terraform automatically archives configuration versions created through VCS when all runs are complete and then re-fetches the files for subsequent runs. + +_States in this stage:_ + +- **Planning:** HCP Terraform is currently running `terraform plan`. +- **Needs Confirmation:** `terraform plan` has finished. Runs sometimes pause in this state, depending on the workspace and organization settings. + +_Leaving this stage:_ + +- If the `terraform plan` command failed, the run does not continue (**Plan Errored** state). +- If a user canceled the plan by pressing the "Cancel Run" button, the run does not continue (**Canceled** state). +- If the plan succeeded with no changes and neither cost estimation nor Sentinel policy checks will be done, HCP Terraform considers the run complete (**Planned and Finished** state). +- If the plan succeeded and requires changes: + - If cost estimation is enabled, the run proceeds automatically to the cost estimation stage. + - If cost estimation is disabled and [Sentinel policies](/terraform/enterprise/policy-enforcement/sentinel) are enabled, the run proceeds automatically to the policy check stage. + - If there are no Sentinel policies and the plan can be auto-applied, the run proceeds automatically to the apply stage. Plans can be auto-applied if the auto-apply setting is enabled on the workspace and the plan was queued by a new VCS commit or by a user with permission to apply runs. ([More about permissions.](/terraform/enterprise/users-teams-organizations/permissions)) + - If there are no Sentinel policies and HCP Terraform cannot auto-apply the plan, the run pauses in the **Needs Confirmation** state until a user with permission to apply runs takes action. ([More about permissions.](/terraform/enterprise/users-teams-organizations/permissions)) If an authorized user approves the apply, the run proceeds to the apply stage. If an authorized user rejects the apply, the run does not continue (**Discarded** state). + +[permissions-citation]: #intentionally-unused---keep-for-maintainers + +Note, if you want to directly integrate third-party tools and services between your plan and apply stages, see [Run Tasks](/terraform/enterprise/workspaces/settings/run-tasks). + +## The Post-Plan Stage + +The post-plan phase only occurs if you configure [run tasks](/terraform/enterprise/workspaces/settings/run-tasks) on a workspace to begin after Terraform successfully completes a plan operation. +All runs can enter this phase, including [speculative plans](/terraform/enterprise/run/remote-operations#speculative-plans). During this phase, HCP Terraform sends information about the run to the configured external system and waits for a `passed` or `failed` response to determine whether the run can continue. + +-> **Note:** The information sent to the configured external system includes the [JSON output](/terraform/internals/json-format) of the Terraform plan. + +_States in this stage:_ + +- **Post-plan running:** HCP Terraform is waiting for a response from the configured external system(s). + - External systems must respond initially with a `200 OK` acknowledging the request is in progress. After that, they have 10 minutes to return a status of `passed`, `running`, or `failed`, or the timeout will expire and the task will be assumed to be in the `failed` status. + +_Leaving this stage:_ + +- If any mandatory tasks failed, the run skips to completion (**Plan Errored** state). +- If any advisory tasks failed, the run proceeds to the **Applying** state, with a visible warning regarding the failed task. +- If a single run has a combination of mandatory and advisory tasks, Terraform takes the most restrictive action. For example, if there are two advisory tasks that succeed and one mandatory task that failed, the run fails. If one mandatory task succeeds and two advisory tasks fail, the run succeeds with a warning. +- If a user canceled the run, the run ends in the **Canceled** state. + +## The OPA Policy Check Stage + +This stage only occurs if you enabled [Open Policy Agent (OPA) policies](/terraform/enterprise/policy-enforcement/opa) and runs after a successful `terraform plan` and before Cost Estimation. In this stage, HCP Terraform checks whether the plan adheres to the policies in the OPA policy sets for the workspace. + +_States in this stage:_ + +- **Policy Check:** HCP Terraform is checking the plan against the OPA policy sets. +- **Policy Override:** The policy check finished, but a mandatory policy failed. The run pauses, and Terraform cannot perform an apply unless a user manually overrides the policy check failure. Refer to [Policy Results](/terraform/enterprise/policy-enforcement/view-results) for details. +- **Policy Checked:** The policy check succeeded, and Terraform can apply the plan. The run may pause in this state if the workspace is not set up to auto-apply runs. + +_Leaving this stage:_ + +If any mandatory policies failed, the run pauses in the **Policy Override** state. The run completes one of the following workflows: + +- The run stops and enters the **Discarded** state when a user with [permission to apply runs](/terraform/enterprise/users-teams-organizations/permissions#general-workspace-permissions#manage-policy-overrides) discards the run. +- The run proceeds to the **Policy Checked** state when a user with [permission to manage policy overrides](/terraform/enterprise/users-teams-organizations/permissions) overrides the failed policy. The **Policy Checked** state means that no mandatory policies failed or that a user performed a manual override. + +Once the run reaches the **Policy Checked** state, the run completes one of the following workflows: + +- The run proceeds to the **Apply** stage if Terraform can automatically apply the plan. An auto-apply requires that the **Auto apply** setting is enabled on the workspace. +- If Terraform cannot automatically apply the plan, the run pauses in the **Policy Checked** state until a user with permission to apply runs takes action. If the user approves the apply, the run proceeds to the **Apply** stage. If the user rejects the apply, the run stops and enters the **Discarded** state. + +## The Cost Estimation Stage + +This stage only occurs if cost estimation is enabled. After a successful `terraform plan`, HCP Terraform uses plan data to estimate costs for each resource found in the plan. + +_States in this stage:_ + +- **Cost Estimating:** HCP Terraform is currently estimating the resources in the plan. +- **Cost Estimated:** The cost estimate completed. + +_Leaving this stage:_ + +- If cost estimation succeeded or errors, the run moves to the next stage. +- If there are no policy checks or applies, the run does not continue (**Planned and Finished** state). + +## The Sentinel Policy Check Stage + +This stage only occurs if [Sentinel policies](/terraform/enterprise/policy-enforcement/sentinel) are enabled. After a successful `terraform plan`, HCP Terraform checks whether the plan obeys policy to determine whether it can be applied. + +_States in this stage:_ + +- **Policy Check:** HCP Terraform is currently checking the plan against the organization's policies. +- **Policy Override:** The policy check finished, but a soft-mandatory policy failed, so an apply cannot proceed without approval from a user with permission to manage policy overrides for the organization. ([More about permissions.](/terraform/enterprise/users-teams-organizations/permissions)) The run pauses in this state. +- **Policy Checked:** The policy check succeeded, and Sentinel will allow an apply to proceed. The run sometimes pauses in this state, depending on workspace settings. + +[permissions-citation]: #intentionally-unused---keep-for-maintainers + +_Leaving this stage:_ + +- If any hard-mandatory policies failed, the run does not continue (**Plan Errored** state). +- If any soft-mandatory policies failed, the run pauses in the **Policy Override** state. + - If a user with permission to manage policy overrides, overrides the failed policy, the run proceeds to the **Policy Checked** state. ([More about permissions.](/terraform/enterprise/users-teams-organizations/permissions)) + - If a user with permission to apply runs discards the run, the run does not continue (**Discarded** state). ([More about permissions.](/terraform/enterprise/users-teams-organizations/permissions)) +- If the run reaches the **Policy Checked** state (no mandatory policies failed, or soft-mandatory policies were overridden): + - If the plan can be auto-applied, the run proceeds automatically to the apply stage. Plans can be auto-applied if the auto-apply setting is enabled on the workspace and the plan was queued by a new VCS commit or by a user with permission to apply runs. ([More about permissions.](/terraform/enterprise/users-teams-organizations/permissions)) + - If the plan can't be auto-applied, the run pauses in the **Policy Checked** state until a user with permission to apply runs takes action. ([More about permissions.](/terraform/enterprise/users-teams-organizations/permissions)) The run proceeds to the apply stage if they approve the apply, or does not continue (**Discarded** state) if they reject the apply. + +[permissions-citation]: #intentionally-unused---keep-for-maintainers + +## The Pre-Apply Stage + +The pre-apply phase only occurs if the workspace has [run tasks](/terraform/enterprise/workspaces/settings/run-tasks) configured to begin before Terraform creates the apply. HCP Terraform sends information about the run to the configured external system and waits for a `passed` or `failed` response to determine whether the run can continue. The information sent to the external system includes the configuration version of the run. + +Only confirmed runs can enter this phase. + +_States in this stage:_ + +- **Pre-apply running:** HCP Terraform is waiting for a response from the configured external system(s). + - External systems must respond initially with a `200 OK` acknowledging the request is in progress. After that, they have 10 minutes to return a status of `passed`, `running`, or `failed`. If the timeout expires, HCP Terraform assumes that the run tasks is in the `failed` status. + +_Leaving this stage:_ + +- If any mandatory tasks failed, the run skips to completion. +- If any advisory tasks failed, the run proceeds to the **Applying** state, with a visible warning regarding the failed task. +- If a single run has a combination of mandatory and advisory tasks, Terraform takes the most restrictive action. For example, the run fails if there are two advisory tasks that succeed and one mandatory task that fails. +- If a user canceled the run, the run ends in the **Canceled** state. + +## The Apply Stage + +_States in this stage:_ + +- **Applying:** HCP Terraform is currently running `terraform apply`. + +_Leaving this stage:_ + +After applying, the run proceeds automatically to completion. + +- If the apply succeeded, the run ends in the **Applied** state. +- If the apply failed, the run ends in the **Apply Errored** state. +- If a user canceled the apply by pressing **Cancel Run**, the run ends in the **Canceled** state. + +## The Post-Apply Stage + +The post-apply phase only occurs if you configure [run tasks](/terraform/enterprise/workspaces/settings/run-tasks) on a workspace to begin after Terraform successfully completes an apply operation. During this phase, HCP Terraform sends information about the run to the configured external system and waits for a `passed` or `failed` response. However, unlike other stages in the run task process, a failed outcome does not halt the run since HCP Terraform has already provisioned the infrastructure. + +_States in this stage:_ + +- **Post-apply running:** HCP Terraform is waiting for a response from the configured external system(s). +- External systems must respond initially with a `200 OK` acknowledging the request is in progress. After that, they have 10 minutes to return a status of `passed`, `running`, or `failed`. If the timeout expires, HCP Terraform assumes that the run tasks is in the `failed` status. + +_Leaving this stage:_ + +- There are only advisory tasks on this stage. +- If any advisory tasks failed, the run proceeds to the **Applied** state, with a visible warning regarding the failed task. +- If a user cancels the run, the run ends in the **Canceled** state. + +## Completion + +A run is complete if it finishes applying, if any part of the run fails, if there is nothing to do, or if a user chooses not to continue. Once a run completes, the next run in the queue can enter the plan stage. + +_States in this stage:_ + +- **Applied:** The run was successfully applied. +- **Planned and Finished:** `terraform plan`'s output already matches the current infrastructure state, so `terraform apply` doesn't need to do anything. +- **Apply Errored:** The `terraform apply` command failed, possibly due to a missing or misconfigured provider or an illegal operation on a provider. +- **Plan Errored:** The `terraform plan` command failed (usually requiring fixes to variables or code), or a hard-mandatory Sentinel policy failed. The run cannot be applied. +- **Discarded:** A user chose not to continue this run. +- **Canceled:** A user interrupted the `terraform plan` or `terraform apply` command with the "Cancel Run" button. diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/run/ui.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/run/ui.mdx new file mode 100644 index 0000000000..36fb9b192b --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/run/ui.mdx @@ -0,0 +1,140 @@ +--- +page_title: UI and VCS-driven run workflow in Terraform Enterprise +description: >- + Use Terraform Enterprise's UI and VCS-driven run workflow to automatically + queue runs when merging new commits to the VCS repository branch associated + with a workspace. +source: terraform-docs-common +--- + +# UI and VCS-driven run workflow + +HCP Terraform has three workflows for managing Terraform runs. + +- The UI/VCS-driven run workflow described below, which is the primary mode of operation. +- The [API-driven run workflow](/terraform/enterprise/run/api), which is more flexible but requires you to create some tooling. +- The [CLI-driven run workflow](/terraform/enterprise/run/cli), which uses Terraform's standard CLI tools to execute runs in HCP Terraform. + +## Summary + +In the UI and VCS workflow, every workspace is associated with a specific branch of a VCS repo of Terraform configurations. HCP Terraform registers webhooks with your VCS provider when you create a workspace, then automatically queues a Terraform run whenever new commits are merged to that branch of workspace's linked repository. + +HCP Terraform also performs a [speculative plan][] when a pull request is opened against that branch. HCP Terraform posts a link to the plan in the pull request, and re-runs the plan if the pull request is updated. + +[speculative plan]: /terraform/enterprise/run/remote-operations#speculative-plans + +The Terraform code for a normal run always comes from version control, and is always associated with a specific commit. + +## Automatically Starting Runs + +In a workspace linked to a VCS repository, runs start automatically when you merge or commit changes to version control. + +If you use GitHub as your VCS provider and merge a PR changing 300 or more files, HCP Terraform automatically triggers runs for every workspace connected to that repository. The GitHub API has a limit of 300 reported changed files for a PR merge. To address this, HCP Terraform initiates workspace runs proactively, preventing oversight of file changes beyond this limit. + +A workspace is linked to one branch of a VCS repository and ignores changes to other branches. You can specify which files and directories within your repository trigger runs. HCP Terraform can also automatically trigger runs when you create Git tags. Refer to [Automatic Run Triggering](/terraform/enterprise/workspaces/settings/vcs#automatic-run-triggering) for details. + +-> **Note:** A workspace with no runs will not accept new runs via VCS webhook. At least one run must be manually queued to confirm that the workspace is ready for further runs. + +A workspace will not process a webhook if the workspace previously processed a webhook with the same commit SHA and created a run. To trigger a run, create a new commit. If a workspace receives a webhook with a previously processed commit, HCP Terraform will add a new event to the [VCS Events](/terraform/enterprise/vcs#viewing-events) page documenting the received webhook. + +## Manually Starting Runs + +You can manually trigger a run using the UI. Manual runs let you apply configuration changes when you update variable values but the configuration in version control is unchanged. You must manually trigger an initial run in any new VCS-driven workspace. + +-> **Note:** When you trigger a manual run, HCP Terraform does not fetch the latest commit from your VCS repository, and instead uses the current [configuration version](/terraform/enterprise/workspaces/configurations) associated with the workspace. HCP Terraform uses [VCS Webhooks](/terraform/enterprise/vcs#webhooks) to monitor changes in your VCS repository and update your configuration version. + +To start a run: + +1. Sign in to [HCP Terraform](https://app.terraform.io/) or Terraform Enterprise and navigate to the workspace you want to start a run in. +2. Click **+ New run**, opening the **Start a new run** dialog. +3. Select the run mode and provide an optional message. + +Review the [run modes documentation](/terraform/enterprise/run/modes-and-options) for more detail on supported options. + +Run modes that have a plan phase support debugging mode. This is equivalent to setting the `TF_LOG` environment variable to `TRACE` for this run only. To enable debugging, click **Additional planning options** under the run mode and click **Enable debugging mode**. See [Debugging Terraform](/terraform/internals/debugging) for more information. + +To [replace](/terraform/enterprise/run/modes-and-options#replacing-selected-resources) specific resources as part of a standard plan and apply run, expand the **Additional planning options** section and select the resources to replace. + +Manually starting a run requires permission to queue plans for the workspace. ([More about permissions.](/terraform/enterprise/users-teams-organizations/permissions)) + +[permissions-citation]: #intentionally-unused---keep-for-maintainers + +If the workspace has a plan that is still in the [plan stage](/terraform/enterprise/run/states#the-plan-stage) when a new plan is queued, you can either wait for it to complete, or visit the **Current Run** page and click **Run this plan now**. Be aware that this action terminates the current plan and unlocks the workspace, which can lead to anomalies in behavior, but can be useful if the plans are long-running and the current plan does not have all the desired changes. + +## Automatically cancel plan-only runs triggered by outdated commits + +Refer to [Automatically cancel plan-only runs triggered by outdated commits](/terraform/enterprise/users-teams-organizations/organizations/vcs-speculative-plan-management) for additional information. + +## Confirming or Discarding Plans + +By default, run plans require confirmation before HCP Terraform will apply them. Users with permission to apply runs for the workspace can navigate to a run that has finished planning and click the "Confirm & Apply" or "Discard Plan" button to finish or cancel a run. ([More about permissions.](/terraform/enterprise/users-teams-organizations/permissions)) If necessary, use the "View Plan" button for more details about what the run will change. + +[permissions-citation]: #intentionally-unused---keep-for-maintainers + +![confirm button](/img/docs/runs-confirm.png) + +Users can also leave comments if there's something unusual involved in a run. + +Note that once the plan stage is completed, until you apply or discard a plan, HCP Terraform can't start another run in that workspace. + +### Auto apply + +If you would rather automatically apply plans that don't have errors, you can [enable auto apply](/terraform/enterprise/workspaces/settings#auto-apply-and-manual-apply) on the workspace's "General Settings" page. Some plans can't be auto-applied, like plans queued by [run triggers](/terraform/enterprise/workspaces/settings/run-triggers) or by users without permission to apply runs. ([More about permissions.](/terraform/enterprise/users-teams-organizations/permissions)) + +[permissions-citation]: #intentionally-unused---keep-for-maintainers + +## Speculative Plans on Pull Requests + +To help you review proposed changes, HCP Terraform can automatically run [speculative plans][speculative plan] for pull requests or merge requests. + +### Viewing Pull Request Plans + +You can view speculative plans in a workspace's list of normal runs. Additionally, HCP Terraform adds a link to the run in the pull request itself, along with an indicator of the run's status. + +A single pull request can include links to multiple plans, depending on how many workspaces connect to the destination branch. If you update a pull request, HCP Terraform performs new speculative plans and update the links. + +Although any contributor to the repository can see the status indicators for pull request plans, only members of your HCP Terraform organization with permission to read runs for the affected workspaces can click through and view the complete plan output. ([More about permissions.](/terraform/enterprise/users-teams-organizations/permissions)) + +[permissions-citation]: #intentionally-unused---keep-for-maintainers + +### Rules for Triggering Pull Request Plans + +Whenever a pull request is _created or updated,_ HCP Terraform checks whether it should run speculative plans in workspaces connected to that repository, based on the following rules: + +- Only pull requests that originate from within the same repository can trigger speculative plans. + + To avoid executing malicious code or exposing sensitive information, HCP Terraform doesn't run speculative plans for pull requests that originate from forks of a repository. + + -> **Note:** On Terraform Enterprise, administrators can choose to allow speculative plans on pull requests that originate from forks. To learn more about this setting, refer to the [general settings documentation](/terraform/enterprise/admin/application/general#allow-speculative-plans-on-pull-requests-from-forks) + +- Pull requests can only trigger runs in workspaces where automatic speculative plans are allowed. You can [disable automatic speculative plans](/terraform/enterprise/workspaces/settings/vcs#automatic-speculative-plans) in a workspace's VCS settings. + +- A pull request will only trigger speculative plans in workspaces that are connected to that pull request's destination branch. + + The destination branch is the branch that a pull request proposes to make changes to; this is often the repository's main branch, but not always. + +- If a workspace is configured to only treat certain directories in a repository as relevant, pull requests that don't affect those directories won't trigger speculative plans in that workspace. For more information, see [VCS settings: automatic run triggering](/terraform/enterprise/workspaces/settings/vcs#automatic-run-triggering). + + -> **Note:** If HCP Terraform skips a plan because the changes weren't relevant, it will still post a passing commit status to the pull request. + +- HCP Terraform does not update the status checks on a pull request with the status of an associated apply. This means that a commit with a successful plan but an errored apply will still show the passing commit status from the plan. + +### Contents of Pull Request Plans + +Speculative plans for pull requests use the contents of the head branch (the branch that the PR proposes to merge into the destination branch), and they compare against the workspace's current state at the time of the plan. This means that if the destination branch changes significantly after the head branch is created, the speculative plan might not accurately show the results of accepting the PR. To get a more accurate view, you can rebase the head branch onto a more recent commit, or merge the destination branch into the head branch. + +## Testing Terraform Upgrades with Speculative Plans + +You can start a new [speculative plan][speculative plan] in the UI with the workspace's current configuration version and any Terraform version available to the organization. + +1. Sign in to [HCP Terraform](https://app.terraform.io/) or Terraform Enterprise and navigate to the workspace you want to try a new Terraform version in. +2. Click **+ New run**. +3. Select **Plan only** as the run type. +4. Select a version from the **Choose Terraform version** menu. The speculative plan will use this version without changing the workspace's settings. +5. Click **Start run**. + +If the run is successful, you can change the workspace's Terraform version and [upgrade the state](/terraform/enterprise/workspaces/state#upgrading-state). + +## Speculative Plans During Development + +You can also use the CLI to run speculative plans on demand before making a pull request. Refer to the [CLI-driven run workflow](/terraform/enterprise/run/cli#remote-speculative-plans) for more details. diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/users-teams-organizations/2fa.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/users-teams-organizations/2fa.mdx new file mode 100644 index 0000000000..f9377e882c --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/users-teams-organizations/2fa.mdx @@ -0,0 +1,47 @@ +--- +page_title: Configure two-factor authentication +description: Use two-factor authentication to secure access to Terraform Enterprise. +source: terraform-docs-common +--- + +# Configure two-factor authentication + +User accounts can be additionally protected with two-factor authentication (2FA), and an organization owner can make this a requirement for all users. + +[permissions-citation]: #intentionally-unused---keep-for-maintainers + +## Setting up Two-factor Authentication + +To reach your user security settings page: + +1. Sign in to [HCP Terraform](https://app.terraform.io/) or Terraform Enterprise. +2. Click the user icon in the upper right corner of the screen. +3. Choose **Account Settings** from the menu. + +Once on this page you can set-up authentication with either a TOTP-compliant application and/or an SMS-enabled phone number. Choose your preferred authentication method and enter a phone number (optional if using an application), then follow the instructions to finish the configuration. If you are using an application, you must scan a QR code to enable it; for either method, you must enter valid authentication codes to verify a successful set-up. + +After you finish, the two-factor authentication settings will change to show your currently configured authentication method. You can click the "Reveal codes" link to view backup codes, or use the "Disable 2FA" button to disable two-factor authentication. + +## Logging in with Two-factor Authentication + +After two-factor authentication has been successfully set-up you will need to enter the code from your TOTP-compliant application or from an SMS sent to your approved SMS-enabled phone number on login. + +If necessary you can also use a backup code by clicking "Use a recovery code". Please remember that each backup code can only be used to log in once. + +## Requiring Two-factor Authentication for All Users + +If you are an organization owner you can require all users within your organization to use two-factor authentication. + +[permissions-citation]: #intentionally-unused---keep-for-maintainers + +Click **Settings** in your organization to reach your organization'a settings page, then click **Authentication**. + +Click the button "Require two-factor". Please remember that all organization owners must have two-factor authentication on before this can be set. + +## Requiring Two-factor Authentication for Users with HashiCorp Cloud Platform + +When you require two-factor authentication for all users and have users who [sign in with their HashiCorp Cloud Platform Account](/terraform/enterprise/users-teams-organizations/users#log-in-with-your-hashicorp-cloud-platform-account), the required configuration for each organization member depends on their linked HashiCorp Cloud identity: + +- **Email**: Follow the instructions in the [HashiCorp Cloud MFA](/hcp/docs/hcp/admin/iam/mfa) docs. +- **GitHub**: Follow the instructions in the [Configuring GitHub two-factor authentication](https://docs.github.com/en/authentication/securing-your-account-with-two-factor-authentication-2fa/configuring-two-factor-authentication) docs. +- **SSO**: HCP Terraform does not currently support HCP SSO accounts. diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/users-teams-organizations/api-tokens.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/users-teams-organizations/api-tokens.mdx new file mode 100644 index 0000000000..5aa5db1023 --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/users-teams-organizations/api-tokens.mdx @@ -0,0 +1,129 @@ +--- +page_title: Manage API tokens for Terraform Enterprise +description: >- + Use API tokens to authenticate with Terraform Enterprise and perform API + operations. +source: terraform-docs-common +--- + +# API Tokens + +This topic describes the distinct types of API tokens you can use to authenticate with HCP Terraform. + +Note that HCP Terraform only displays API tokens once when you initially create them and are obfuscated thereafter. If the token is lost, it must be regenerated. + +Refer to [Team Token API](/terraform/enterprise/api-docs/team-tokens) and [Organization Token API](/terraform/enterprise/api-docs/organization-tokens) for additional information about using the APIs. + +## User API Tokens + +API tokens may belong directly to a user. User tokens are the most flexible token type because they inherit permissions from the user they are associated with. For more information on user tokens and how to generate them, see the [Users](/terraform/enterprise/users-teams-organizations/users#tokens) documentation. + +## Team API Tokens + +API tokens may belong to a specific team. Team API tokens allow access to the workspaces that the team has access to, without being tied to any specific user. + +Navigate to the **Organization Settings > API Tokens > Team Tokens** tab to manage API tokens for a team or create new team tokens. + +Teams can have multiple valid tokens at a time, so long as the tokens' descriptions are unique within the context of the given team. A token without a description is considered a legacy token, and only one legacy token can exist at a given time. + +The [**legacy API**](/terraform/enterprise/api-docs/team-tokens#legacy-team-tokens-api-reference) will only operate on the legacy token, and generating a new legacy token invalidates the previous legacy token. + +The [**non-legacy API**](/terraform/enterprise/api-docs/team-tokens#team-tokens-api-reference) will support the existence of multiple, valid team tokens, meaning that when a new, non-legacy token is generated, existing tokens will remain valid. + +Owners and users with [manage teams](/terraform/enterprise/users-teams-organizations/permissions#manage-teams) permissions have the ability to enable and disable team token management for a team, which limits the actions that team members can take on a team token. Refer to [Allow Member Token Management](/terraform/enterprise/users-teams-organizations/permissions#allow-member-token-management) for more information. + +Team API tokens are designed for performing API operations on workspaces. They have the same access level to the workspaces the team has access to. For example, if a team has permission to apply runs on a workspace, the team's token can create runs and configuration versions for that workspace via the API. ([More about permissions.](/terraform/enterprise/users-teams-organizations/permissions)) + +[permissions-citation]: #intentionally-unused---keep-for-maintainers + +Note that the individual members of a team can usually perform actions the team itself cannot, since users can belong to multiple teams, can belong to multiple organizations, and can authenticate with Terraform's `atlas` backend for running Terraform locally. + +If an API token is generated for the "owners" team, then that API token will have all of the same permissions that an organization owner would. + +## Organization API Tokens + +API tokens may be generated for a specific organization. Organization API tokens allow access to the organization-level settings and resources, without being tied to any specific team or user. + +To manage the API token for an organization, go to **Organization settings > API Token** and use the controls under the "Organization Tokens" header. + +Each organization can have **one** valid API token at a time. Only [organization owners](/terraform/enterprise/users-teams-organizations/teams#the-owners-team) can generate or revoke an organization's token. + +[permissions-citation]: #intentionally-unused---keep-for-maintainers + +Organization API tokens are designed for creating and configuring workspaces and teams. We don't recommend using them as an all-purpose interface to HCP Terraform; their purpose is to do some initial setup before delegating a workspace to a team. For more routine interactions with workspaces, use [team API tokens](#team-api-tokens). + +Organization API tokens have permissions across the entire organization. They can perform all CRUD operations on most resources, but have some limitations; most importantly, they cannot start runs or create configuration versions. Any API endpoints that can't be used with an organization API token include a note like the following: + +-> **Note:** This endpoint cannot be accessed with [organization tokens](#organization-api-tokens). You must access it with a [user token](/terraform/enterprise/users-teams-organizations/users#api-tokens) or [team token](#team-api-tokens). + + + +## Audit trail tokens + +You can generate an audit trails token to read an organization's [audit trails](/terraform/enterprise/api-docs/audit-trails). Use this token type to authenticate integrations pulling audit trail data, for example, using the [HCP Terraform for Splunk](/terraform/enterprise/integrations/splunk) app. + +To manage an organization's audit trails token, go to **Organization settings > API Token** and use the settings under the "Audit Token" header. + +Each organization can only have a _single_ valid audit trails token. Only [organization owners](/terraform/enterprise/users-teams-organizations/teams#the-owners-team) can generate or revoke an organization's audit trails token. + +[permissions-citation]: #intentionally-unused---keep-for-maintainers + + + +## Agent API Tokens + +[Agent pools](/terraform/cloud-docs/agents) have their own set of API tokens which allow agents to communicate with HCP Terraform, scoped to an organization. These tokens are not valid for direct usage in the HCP Terraform API and are only used by agents. + +## Access Levels + +The following chart illustrates the various access levels for the supported API token types. Some permissions are implicit based on the token type, others are dependent on the permissions of the associated user, team, or organization. + +🔵 = Implicit for token type 🔶 = Requires explicit permission + +| | User tokens | Team tokens | Organization tokens | +| ---------------------------------- | :---------: | :---------: | :-----------------: | +| **Users** | | | | +| Manage account settings | 🔵 | | | +| Manage user tokens | 🔵 | | | +| **Workspaces** | | | | +| Read workspace variables | 🔶 | 🔶 | 🔵 | +| Write workspace variables | 🔶 | 🔶 | 🔵 | +| Plan, apply, upload states | 🔶 | 🔶 | | +| Force cancel runs | 🔶 | 🔶 | | +| Create configuration versions | 🔶 | 🔶 | | +| Create or modify workspaces | 🔶 | 🔶 | 🔵 | +| Remote operations | 🔶 | 🔶 | | +| Manage run triggers | 🔶 | 🔶 | 🔵 | +| Manage notification configurations | 🔶 | 🔶 | | +| Manage run tasks | 🔶 | 🔶 | 🔵 | +| **Teams** | | | | +| Create teams | 🔶 | 🔶 | 🔵 | +| Modify team | 🔶 | 🔶 | 🔵 | +| Read team | 🔶 | 🔶 | 🔵 | +| Manage team tokens | 🔶 | 🔶 | 🔵 | +| Manage team workspace access | 🔶 | 🔶 | 🔵 | +| Manage team membership | 🔶 | 🔶 | 🔵 | +| **Organizations** | | | | +| Create organizations | 🔵 | | | +| Modify organizations | 🔶 | | | +| Manage organization tokens | 🔶 | | | +| View audit trails | | | 🔵 | +| Invite users to organization | 🔶 | 🔶 | 🔵 | +| **Sentinel** | | | | +| Manage Sentinel policies | 🔶 | 🔶 | 🔵 | +| Manage policy sets | 🔶 | 🔶 | 🔵 | +| Override policy checks | 🔶 | 🔶 | | +| **Integrations** | | | | +| Manage VCS connections | 🔶 | 🔶 | 🔵 | +| Manage SSH keys | 🔶 | 🔶 | | +| Manage run tasks | 🔶 | 🔶 | 🔵 | +| **Modules** | | | | +| Manage Terraform modules | 🔶 | 🔵 (owners) | | + +## Token Expiration + +You can create user, team, and organization tokens with an expiration date and time. Once the expiration time has passed, the token is longer treated as valid and may not be used to authenticate to any API. Any API requests made with an expired token will fail. + +HashiCorp recommends setting an expiration on all new authentication tokens. Creating tokens with an expiration date helps reduce the risk of accidentally leaking valid tokens or forgetting to delete tokens meant for a delegated use once their intended purpose is complete. + +You can not modify the expiration of a token once you have created it. The HCP Terraform UI displays tokens relative to the current user's timezone, but all tokens are passed and displayed in UTC in ISO 8601 format through the HCP Terraform API. diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/users-teams-organizations/organizations/index.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/users-teams-organizations/organizations/index.mdx new file mode 100644 index 0000000000..543f10aaa0 --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/users-teams-organizations/organizations/index.mdx @@ -0,0 +1,338 @@ +--- +page_title: Organizations overview +description: >- + Organizations are groups of projects and workspaces that let teams + collaborate. Learn how to create and manage Terraform Enterprise + organizations. +source: terraform-docs-common +--- + +[teams]: /terraform/enterprise/users-teams-organizations/teams + +[users]: /terraform/enterprise/users-teams-organizations/users + +[owners]: /terraform/enterprise/users-teams-organizations/teams#the-owners-team + +# Organizations overview + +This topic provides overview information about how to create and manage organizations in HCP Terraform and Terraform Enterprise. An organization contains one or more projects. + +## Requirements + +The **admin** permission preset must be enabled on your profile to create and manage organizations in the HCP Terraform UI. Refer to [Permissions](/terraform/enterprise/users-teams-organizations/permissions#organization-permissions) for additional information. + +## API and Terraform Enterprise Provider + +In addition to the HCP Terraform UI, you can use the following methods to manage organizations: + +- [Organizations API](/terraform/enterprise/api-docs/organizations) +- The `tfe` provider [`tfe_organization`](https://registry.terraform.io/providers/hashicorp/tfe/latest/docs/resources/organization) resource + +## Select an organization + +HCP Terraform displays your current organization in the sidebar. To select an organization: + +1. Sign in to [HCP Terraform](https://app.terraform.io/) or Terraform Enterprise. +2. Click the current organization name to view a list of all the organizations where you are a member. +3. Click an organization to select it. HCP Terraform displays list of workspaces within that organization. + +## Join an organization + +To join an organization, the organization [owners][] or a user with specific [team management](/terraform/enterprise/users-teams-organizations/permissions#team-management-permissions) permissions must invite you, and you must accept the emailed invitation. [Learn more](#users). + +[permissions-citation]: #intentionally-unused---keep-for-maintainers + +## Leave an organization + +1. Sign in to [HCP Terraform](https://app.terraform.io/) or Terraform Enterprise and click the Terraform logo in the page header to navigate to the **Organizations** page. +2. Open the **...** ellipses menu next to the organization and select **Leave organization**. + +You do not need permission from the owners to leave an organization, but you cannot leave if you are the last member of the owners team. Either add a new owner and then leave, or [delete the organization](/terraform/enterprise/users-teams-organizations/organizations#general). + +## Create an organization + + + +On Terraform Enterprise, administrators can restrict your ability to create organizations. Refer to [Organization Creation](/terraform/enterprise/admin/application/general#organization-creation) for details. + + + +On HCP Terraform, any user can create a new organization. If you do not belong to any organizations, HCP Terraform prompts you to create one the first time you [sign in](https://app.terraform.io/). To create an organization: + +1. Click the current organization name and select **Create new organization**. The **Create a new organization** page appears. +2. Enter a unique **Organization name** Organization names can include numbers, letters, underscores (`_`), and hyphens (`-`). +3. Provide an **Email address** to receive notifications about the organization. +4. Click **Create organization**. + +HCP Terraform shows the new organization and prompts you to create a new workspace. You can also [invite other users](#users) to join the organization. + + + +## Managed resources + +Your organization’s managed resource count helps you understand the number of infrastructure resources that HCP Terraform manages across all your workspaces. + +HCP Terraform reads all the workspaces’ state files to determine the total number of managed resources. Each [resource](/terraform/language/resources/syntax) instance in the state equals one managed resource. HCP Terraform includes resources in modules and each resource created with the `count` or `for_each` meta-arguments. HCP Terraform does not include [data sources](/terraform/language/data-sources) in the count. Refer to [Managed Resources Count](/terraform/enterprise/workspaces/state#managed-resources-count) in the workspace state documentation for more details. + +You can view your organization's managed resource count on the **Usage** page. + + + +## Create and manage reserved tag keys + +~> **Reserved tag keys are in beta**: We do not recommend using beta features in production environments. + +You can define reserved tag keys that appear as suggested labels when managers want to add tags to their projects and workspaces in the organization. Refer to [Create and manage reserved tag keys](/terraform/enterprise/users-teams-organizations/organizations/manage-reserved-tags) for instructions. + +You can also view single-value tags that may already be attached to projects and workspaces. Refer to [Tags](#tags) in the organization settings reference for additional information. + +## Managing settings + +To view and manage an organization's settings, click **Settings**. + +The contents of the organization settings depends on your permissions within the organization. All users can review the organization's contact email, view the membership of any teams they belong to, and view the organization's authentication policy. [Organization owners][owners] can view and manage the entire list of organization settings. Refer to [Organization Permissions](/terraform/enterprise/users-teams-organizations/permissions#organization-permissions) for details. + +You may be able to manage the following organization settings. + +[permissions-citation]: #intentionally-unused---keep-for-maintainers + +### Organization settings + +#### General + +Review the organization name and contact email. Organization owners can choose to change the organization name, contact email, and the default execution mode, or delete the organization. When an organization owner updates the default execution mode, all workspaces configured to [inherit this value](/terraform/enterprise/workspaces/settings#execution-mode) will be affected. + +Organization owners can also choose whether [workspace administrators](/terraform/enterprise/users-teams-organizations/permissions#workspace-admins) can delete workspaces that are managing resources. Deleting a workspace with resources under management introduces risk because Terraform can no longer track or manage the infrastructure. The workspace's users must manually delete any remaining resources or [import](/terraform/cli/commands/import) them into another Terraform workspace. + + + +Organization owners using HCP Terraform Plus edition can choose whether members with [module management permissions](/terraform/enterprise/users-teams-organizations/permissions#manage-private-registry) can [generate module tests](/terraform/enterprise/registry/test#generated-module-tests). + + + +##### Renaming an organization + +!> **Warning:** Deleting or renaming an organization can be very disruptive. We strongly recommend against deleting or renaming organizations with active members. + +To rename an organization that manages infrastructure: + +1. Alert all members of the organization about the name change. +2. Sign in to [HCP Terraform](https://app.terraform.io/) or Terraform Enterprise and navigate to the organization you want to rename. +3. Cancel in progress and pending runs or wait for them to finish. HCP Terraform cannot change the name of an organization with runs in progress. +4. Lock all workspaces to ensure that no new runs will start before you change the name. +5. Rename the organization. +6. Update all components using the HCP Terraform API to the new organization name. This includes Terraform's `cloud` block CLI integration, the `tfe` Terraform provider, and any external API integrations. +7. Unlock workspaces and resume normal operations. + +#### Plan & Billing + +Review the organization's plan and any invoices for previous plan payments. Organization owners can also upgrade to one of HCP Terraform's paid plans, downgrade to a free plan, or begin a free trial of paid features. + +#### Tags + +Click the **Tags** tab in the **Tags Management** screen to view single-value tags that may have already been created in your system. The table on lists the tags in the system, the number of times a tag appears in a project or workspace, and the date the tag was created. + +The only action you can perform in the UI is deleting single-value tags from the system. You can use the following methods to delete single-value tags: + +1. Select one or more tags and click **Delete tags**. +2. Select the **Name** header to select all tags, then click **Delete tags**. +3. Click the trash icon for a tag and confirm that you want to permanently delete it when prompted. + +#### Teams + + + +@include 'tfc-package-callouts/team-management.mdx' + + + +All users in an organization can access the **Teams** page, which displays a list of [teams][] within the organization. + +Organization owners and users with the [include secret teams permission](/terraform/enterprise/users-teams-organizations/permissions#include-secret-teams) can: + +- view all [secret teams](/terraform/enterprise/users-teams-organizations/teams/manage#team-visibility) +- view each team's membership +- manage team API tokens + +HCP Terraform restricts team creation, team deletion, and management of team API tokens to organization owners and users with the [manage teams](/terraform/enterprise/users-teams-organizations/permissions#manage-teams) permission. Organization owners and users with the [manage membership](/terraform/enterprise/users-teams-organizations/permissions#manage-membership) permission can manage team membership. Remember that users must accept their organization invitations before you can add them to a team. + +[permissions-citation]: #intentionally-unused---keep-for-maintainers + +#### Users + +Organization owners and users with [manage membership](/terraform/enterprise/users-teams-organizations/permissions#manage-membership) permissions can invite HCP Terraform users into the organization, cancel invitations, and remove existing members. + +The list of users is separated into one tab for active users and one tab for invited users who have not yet accepted their invitations. For active users, the list includes usernames, email addresses, avatar icons, two-factor authentication status, and current team memberships. Use the **Search by username or email** field to filter these lists. + +User invitations are always sent by email; you cannot invite someone using their HCP Terraform username. To invite a user to an organization: + +1. Click **Invite a user**. The **invite a user** box appears. +2. Enter the user's email address and optionally add them to one or more teams. If the user accepts the invitation, HCP Terraform automatically adds them to the specified teams. + +All permissions in HCP Terraform are managed through teams. Users can join an organization without belonging to any teams, but they cannot use HCP Terraform features until they belong to a team. Refer to [permissions](/terraform/enterprise/users-teams-organizations/permissions) for details. + +[permissions-citation]: #intentionally-unused---keep-for-maintainers + +#### Variable Sets + +View all of the available variable sets and their variables. Users with [**Manage variable set** permissions](/terraform/enterprise/workspaces/variables/managing-variables#variable-sets) can create variable sets and assign them to one or more projects or workspaces. + +Variable sets let you reuse the same variables across multiple workspaces or projects in an organization. For example, you could define a variable set of provider credentials and automatically apply it to several projects or workspaces, rather than manually defining credential variables in each. Changes to variable sets instantly apply to all appropriate workspaces, saving time and reducing errors from manual updates. + +Refer to the [variables overview](/terraform/enterprise/workspaces/variables) documentation for details about variable types, scope, and precedence. Refer to [managing variables](/terraform/enterprise/workspaces/variables/managing-variables) for details about how to create and manage variable sets. + +#### Health + + + +@include 'tfc-package-callouts/health-assessments.mdx' + + + +HCP Terraform can perform automatic health assessments in a workspace to assess whether its real infrastructure matches the requirements defined in its Terraform configuration. Health assessments include the following types of evaluations: + +- Drift detection determines whether your real-world infrastructure matches your Terraform configuration. Drift detection requires Terraform version 0.15.4+. +- Continuous validation determines whether custom conditions in the workspace’s configuration continue to pass after Terraform provisions the infrastructure. Continuous validation requires Terraform version 1.3.0+. + +You can enforce health assessments for all eligible workspaces or let each workspace opt in to health assessments through workspace settings. Refer to [Health](/terraform/enterprise/workspaces/health) in the workspaces documentation for more details. + +#### Runs + +From the Workspaces page, click **Settings** in the sidebar, then **Runs** to view all of the current runs in your organization's workspaces. The **Runs** page displays: + +- The name of the run +- The run's ID +- What triggered the run +- The workspace and project where the run is taking place +- When the latest change in the run occurred +- A button allowing you to cancel that run + +You can apply the following filters to limit the runs HCP Terraform displays: + +- Click **Needs Attention** to display runs that require user input to continue, such as approving a plan or overriding a policy. +- Click **Running** to display runs that are in progress. +- Click **On Hold** to display paused runs. + +For precise filtering, click **More filters** and check the boxes to filter runs by specific [run statuses](/terraform/enterprise/run/states), [run operations](/terraform/enterprise/run/modes-and-options), workspaces, or [agent pools](/terraform/cloud-docs/agents/agent-pools). Click **Apply filters** to list the runs that match your criteria. + +You can dismiss any of your filtering criteria by clicking the **X** next to the filter name above the table displaying your runs. + +For more details about run states, refer to [Run States and Stages](/terraform/enterprise/run/states). + +### Integrations + +#### Cost Estimation + +Enable and disable the [cost estimation](/terraform/enterprise/cost-estimation) feature for all workspaces. + +#### Policies + + + +@include 'tfc-package-callouts/policies.mdx' + + + +Policies let you define and enforce rules for Terraform runs. You can write them using either the [Sentinel](/terraform/enterprise/policy-enforcement/sentinel) or [Open Policy Agent (OPA)](/terraform/enterprise/policy-enforcement/opa) policy-as-code frameworks and then group them into policy sets that you can apply to workspaces in your organization. To create policies and policy sets, you must have [permission to manage policies](/terraform/enterprise/users-teams-organizations/permissions#organization-permissions). + +#### Policy Sets + + + +@include 'tfc-package-callouts/policies.mdx' + + + +Create groups of policies and enforce those policy sets globally or on specific [projects](/terraform/enterprise/projects/manage) and workspaces. You can create policy sets through the Terraform API, by connecting a VCS repository containing policies, or directly in HCP Terraform. To create policies and policy sets, you must have [permission to manage policies](/terraform/enterprise/users-teams-organizations/permissions#organization-permissions). + +Refer to [Managing Policy Sets](/terraform/enterprise/policy-enforcement/manage-policy-sets) for details. + +[permissions-citation]: #intentionally-unused---keep-for-maintainers + +#### Run Tasks + + + +@include 'tfc-package-callouts/run-tasks.mdx' + + + +Manage the run tasks that you can add to workspaces within the organization. [Run tasks](/terraform/enterprise/workspaces/settings/run-tasks) let you integrate third-party tools and services at specific stages in the HCP Terraform run lifecycle. + +### Security + +#### Agents + + + +@include 'tfc-package-callouts/agents.mdx' + + + +Create and manage [HCP Terraform agent pools](/terraform/cloud-docs/agents). HCP Terraform agents let HCP Terraform communicate with isolated, private, or on-premises infrastructure. This is useful for on-premises infrastructure types such as vSphere, Nutanix, OpenStack, enterprise networking providers, and infrastructure within a protected enclave. + +#### API Tokens + +Organization owners can set up a special [Organization API Token](/terraform/enterprise/users-teams-organizations/api-tokens) that is not associated with a specific user or team. + +#### Authentication + +Organization owners can determine when users must reauthenticate and require [two-factor authentication](/terraform/enterprise/users-teams-organizations/2fa) for all members of the organization. + +#### SSH Keys + +Manage [SSH keys for cloning Git-based modules](/terraform/enterprise/workspaces/settings/ssh-keys) during Terraform runs. This does not include keys to access a connected VCS provider. + +#### SSO + +Organization owners can set up an SSO provider for the organization. + +### Version Control + +#### VCS General + +Configure [Automatically cancel plan-only runs triggered by outdated commits](/terraform/enterprise/users-teams-organizations/organizations/vcs-speculative-plan-management) to manage the setting. + +#### VCS Events + +-> **Note:** This feature is in beta. + +Review the event logs for GitLab.com connections. + +#### VCS Providers + +Configure [VCS providers](/terraform/enterprise/vcs) to use in the organization. You must have [permission to manage VCS settings](/terraform/enterprise/users-teams-organizations/permissions). + +[permissions-citation]: #intentionally-unused---keep-for-maintainers + +### Destruction and Deletion + +#### Data Retention Policies + + +Data retention policies are exclusive to Terraform Enterprise, and not available in HCP Terraform. Learn more about Terraform Enterprise. +
+ +An organization owner can set or override the following data retention policies: + +- **Admin default policy** +- **Do not auto-delete** +- **Auto-delete data** + +Setting the data retention policy to **Admin default policy** disables the other data retention policy settings. + +By default, the **Do not auto-delete** option is enabled for an organization. This option directs Terraform Enterprise to retain data associated with configuration and state versions, but organization owners can define configurable data retention policies that allow Terraform to _soft delete_ the backing data associated with configuration versions and state versions. Soft deleting refers to marking a data object for garbage collection so that Terraform can delete the object after a set number of days. + +Once an object is soft deleted, any attempts to read the object will fail. Until the garbage collection process begins, you can restore soft deleted objects using the APIs described in the [configuration version documentation](/terraform/enterprise/api-docs/configuration-versions) and the [state version documentation](/terraform/enterprise/api-docs/state-versions). Terraform permanently deletes the archivist storage after the garbage collection grace period elapses. + +The organization policy is the default policy applied to all workspaces, but members of individual workspaces can set overriding policies for their workspaces that take precedence over the organization policy. + +## Trial Expired Organizations + +HCP Terraform paid features are available as a free trial. When a free trial has expired, the organization displays a banner reading **TRIAL EXPIRED — Upgrade Required**. + +Organizations with expired trials return to the feature set of a free organization, but they retain any data created as part of paid features. Specifically, HCP Terraform disables the following features: + +- Teams other than `owners` and locks users who do not belong to the `owners` team out of the organization. HCP Terraform preserves team membership and permissions and re-enables them after you upgrade the organization. +- Sentinel policy checks. HCP Terraform preserves existing policies and policy sets and re-enables them after you upgrade the organization. +- Cost estimation. diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/users-teams-organizations/organizations/manage-reserved-tags.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/users-teams-organizations/organizations/manage-reserved-tags.mdx new file mode 100644 index 0000000000..60709f95c5 --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/users-teams-organizations/organizations/manage-reserved-tags.mdx @@ -0,0 +1,59 @@ +--- +page_title: Create and manage reserved tag keys +description: >- + Reserved tag keys let you organize projects and workspaces and track + consumption. Learn how to create and manage reserved tag keys. +source: terraform-docs-common +--- + +# Create and manage reserved tag keys + +This topic describes how to create and manage reserved tag keys in HCP Terraform. You can use reserved tag keys to help managers consistently label workspaces and projects in your organization. + +## Introduction + +You can define reserved tag keys that appear as suggested labels when managers want to add tags to their projects and workspaces in the organization. Doing so helps you standardize tag keys and prevent duplicates that affect your ability to track resources. + +Refer to the following topics for information about creating and managing tags attached to projects and workspaces: + +- [Create a project](/terraform/enterprise/projects/manage#create-a-project) +- [Create workspace tags](/terraform/enterprise/workspaces/tags) + +## Requirements + +The **admin** permission preset must be enabled on your profile to create and manage reserved tags. Refer to [Permissions](/terraform/enterprise/users-teams-organizations/permissions#organization-permissions) for additional information. + +## Define a reserved tag key + +You can define reserved tag keys for your organization so that project and workspace managers can use consistent labels. + +1. Sign in to [HCP Terraform](https://app.terraform.io/) or Terraform Enterprise and navigate to the organization you want to define a reserved tag for. +2. Choose **Settings** from the sidebar, then **Tags**. +3. Click on the **Reserved Keys** tab. +4. Click **New reserved tag key** and specify a key value when prompted. Keys are unique and can have up to 128 characters. You can use letters, numbers, spaces, and the following special characters: `.`, `=`, `+`, `-`, `@`, `:`, `-`, and `_`. +5. You can enable the **Disable overrides** option to prevent project and workspace managers from overriding the key. Refer to [Disable overrides for project tags](#disable-overrides-for-project-tags) for additional information. +6. Click **Save** to finish adding the key. + +## Delete a reserved key + +1. Sign in to [HCP Terraform](https://app.terraform.io/) or Terraform Enterprise and navigate to the organization where you want to delete a reserved tag. +2. Choose **Settings** from the sidebar, then **Tags**. +3. Click on the **Reserved Keys** tab. +4. Open the ellipses menu and choose **Delete <key-name>**. +5. Click **Yes, delete reserved key** when prompted. + +To re-add a key, you must manually complete the steps described in [Define a reserved tag key](#define-a-reserved-tag-key). + +## Edit a reserved key + +1. Sign in to [HCP Terraform](https://app.terraform.io/) or Terraform Enterprise and navigate to the organization where you want to edit a reserved tag. +2. Choose **Settings** from the sidebar, then **Tags**. +3. Click on the **Reserved Keys** tab. +4. Open the ellipses menu and choose **Edit <key-name>**. +5. Specify your changes and click **Save**. + +## Disable overrides for project tags + +Enable the **Disable overrides** option when creating or editing a reserved tag key to prevent project and workspace managers from overriding the tag keys. + +This option is not retroactive. When a workspace contains keys that were overridden before you enabled the **Disable overrides** option, you must first remove the tags from the workspace. You can then re-apply the keys to the workspace so that HCP Terraform can allow future updates to the workspace tag bindings. diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/users-teams-organizations/organizations/vcs-speculative-plan-management.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/users-teams-organizations/organizations/vcs-speculative-plan-management.mdx new file mode 100644 index 0000000000..8945635223 --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/users-teams-organizations/organizations/vcs-speculative-plan-management.mdx @@ -0,0 +1,45 @@ +--- +page_title: Automatically cancel plan-only runs triggered by outdated commits +description: >- + Learn how to configure Terraform Enterprise to automatically cancel Terraform + plan operations triggered by pull requests when new commits are pushed to the + VCS. +source: terraform-docs-common +--- + +# Automatically cancel plan-only runs + +This topic describes how to configure HCP Terraform to automatically cancel plan-only Terraform run triggered by pull requests in the VCS. + +## Introduction + +When connected to a VCS, HCP Terraform can automatically start a Terraform run that performs a `terraform plan` operation when someone creates a pull request (PR) in the repository. Refer to [Connecting to VCS](/terraform/enterprise/vcs) for additional information. + +When team members push new commits to the same branch, HCP Terraform starts new run that performs a `terraform plan` operation. But as team members push new commits, the queue of Terraform runs can cause delays and reduce efficiency. + +You can enable the **Automatically cancel speculative plans for outdated commits** option in the organization's settings screen so that HCP Terraform automatically cancel unfinished plan-only runs in VCS workflows. + +## Configure automatic cancellation + +1. Sign in to [HCP Terraform](https://app.terraform.io/) or Terraform Enterprise and select your organization. +2. Choose **Settings** from the sidebar. +3. Under the **Version Control** group of settings, click **General**. +4. Enable the **Automatically cancel speculative plans for outdated commits** option under the **Manage speculative plans** section. +5. Click **Update settings**. + +After enabling the option, HCP Terraform cancels ongoing or pending speculative plans when new commits are received on the same branch. + +## Automated cancellation notifications + +When the **Automatically cancel speculative plans for outdated commits** option is enabled, HCP Terraform notifies you about plan-only runs that are canceled as a result of the setting. Notifications appear in the following screens: + +- **Run details page**. Refer to [Viewing and Managing Runs](/terraform/enterprise/run/manage) for additional information. + +- **VCS status checks**. When the **Non-aggregated status checks** option is enabled in the version control settings, the notification explicitly states when a plan has been canceled automatically. + + When the **Aggregated status checks** option is enabled, HCP Terraform includes canceled plans in the result and identifies them separately from manually canceled plans. + + Refer to [VCS Status Checks](/terraform/enterprise/users-teams-organizations/organizations/vcs-status-checks) for additional information. + + +- **Aggregated status page**. HCP Terraform prints the cancellation message in the aggregated status page in the **Resources to be changed** section. The section may not reflect a complete result if all runs associated with the commit reach completion. diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/users-teams-organizations/organizations/vcs-status-checks.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/users-teams-organizations/organizations/vcs-status-checks.mdx new file mode 100644 index 0000000000..4b1983ef17 --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/users-teams-organizations/organizations/vcs-status-checks.mdx @@ -0,0 +1,49 @@ +--- +page_title: Configure VCS status checks +description: >- + VCS status checks send notifications to your version control provider. Learn + how to configure VCS status checks in Terraform Enterprise. +source: terraform-docs-common +--- + +# Configure VCS status checks + +Status checks are notifications sent to your version control system's VCS provider, providing details about specific commits, including the present status of the HCP Terraform run. Please refer to your VCS provider's documentation regarding status checks (e.g., [GitHub Status Checks](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/collaborating-on-repositories-with-code-quality-features/about-status-checks)). + +## Permissions + +To modify VCS Status Checks settings, you must have [Manage VCS Settings](/terraform/enterprise/users-teams-organizations/permissions#manage-vcs-settings) permissions. + +## Managing organization VCS status check settings + +Organization owners can choose between _aggregated_ (default) and _non-aggregated_ status checks. This setting determines whether detailed information and links are accessed directly from the VCS provider or HCP Terraform. + +This setting also determines the number of status checks directly sent to the VCS Provider in response to actions such as pull or merge requests. + +To view and manage an organization’s VCS Status Check settings, click **Settings** then **Version Control**. + +### Aggregated status checks + +Aggregated status checks offer a streamlined experience if you have a single repository containing configuration for many workspaces (a.k.a., a monorepo). + +When aggregated status checks are enabled, HCP Terraform sends one VCS status check for all runs triggered by a VCS event. If multiple workspaces rely on a shared repository, HCP Terraform aggregates the status checks for these workspaces into one summary. This summary is unique to the workspace's organization and VCS client connection. + +You can access additional information about an aggregated status check in HCP Terraform by clicking the **Details** link a status check provides. This link directs you to an HCP Terraform page that offers the consolidated status check results across multiple workspaces, highlighting details such as resource changes and issues that require attention. + +![Screenshot: Organization Aggregated status checks](/img/docs/organization-vcs-general-aggregated-status-checks.png) + +### Non-aggregated status checks + +Non-aggregated status checks send your VCS provider a status check for each triggered workspace and related run stage in response to a VCS event. For example, a VCS push triggers checks for each related workspace's run stages, including the plan operation, policy checks, cost estimation, run tasks, and more. + +If you have a manageable amount of workspaces and want to visualize status checks on your VCS Provider rather than in HCP Terraform, use non-aggregated status checks. + +![Screenshot: Organization Non-aggregated status checks](/img/docs/organization-vcs-general-non-aggregated-status-checks.png) + +#### Send passing commit statuses + +-> **Note:** Organization owners can only enable the **Send passing commit statuses** setting if the **Aggregated status checks** setting is disabled. + +Workspaces that use part of a shared repository do not typically run plans for changes that do not affect their files. This includes [speculative plans](/terraform/enterprise/run/remote-operations#speculative-plans) on pull requests. Since **pending** VCS status checks can block pull requests, workspaces automatically send passing commit statuses for any PRs that do not affect their files. + +You can disable this behavior if it creates too many status checks for your VCS provider. You may want to do this if you have a large number of workspaces sharing one VCS repository. diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/users-teams-organizations/permissions.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/users-teams-organizations/permissions.mdx new file mode 100644 index 0000000000..b57deb14a8 --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/users-teams-organizations/permissions.mdx @@ -0,0 +1,396 @@ +--- +page_title: Permission model in Terraform Enterprise +description: >- + Use the Terraform Enterprise permission model to manage user access to + organizations, projects, and workspaces. +source: terraform-docs-common +--- + +# Permission model + + + +-> **Note:** Team management is available in HCP Terraform **Standard** Edition. [Learn more about HCP Terraform pricing here](https://www.hashicorp.com/products/terraform/pricing). + + + +[permissions-citation]: #intentionally-unused---keep-for-maintainers + +> **Hands-on:** Try the [Manage Permissions in HCP Terraform](/terraform/tutorials/cloud/cloud-permissions?utm_source=WEBSITE&utm_medium=WEB_IO&utm_offer=ARTICLE_PAGE&utm_content=DOCS) tutorial. + +HCP Terraform's access model is team-based. In order to perform an action within an HCP Terraform organization, users must belong to a team that has been granted the appropriate permissions. + +The HCP Terraform permissions model is split into three levels: you can set organization-level, project-level, and workspace-level permissions. Each permission level is additive, granting a user the highest level of permissions possible, regardless of which level set that permission. + +A team's _effective permissions_ is the sum of the permissions granted to that team from all permission levels. For example, if a team has the **View all workspaces** permission at the organization-level and **Admin** permission on a specific workspace, then users on that team have the effective permission of **Admin** on that workspace. An organization-level permission does not take precedence over a workspace-level permission. + +We recommend following the principle of least privilege when configuring permissions, only giving teams access to the resources they need for their job function. When configuring permissions at a particular level, remember that a team may have permissions above or below in the hierarchy that grant a higher level of permission than what you are currently configuring. + +## Organization Owners + +Every organization has a special team called the **Owners** team, whose members have the maximum available permissions within the organization. Members of this team are often referred to as "organization owners". + +Organization owners have every available permission within the organization. This includes all organization-level permissions, and the highest level of workspace permissions on every workspace. + +There are also some actions within an organization that are only available to owners. These are generally actions that affect the permissions and membership of other teams, or are otherwise fundamental to the organization's security and integrity. + +Permissions for the owners team include: + +- Manage workspaces (refer to [Organization Permissions][] below; equivalent to admin permissions on every workspace) +- Manage projects (refer to [Organization Permissions][] below; equivalent to admin permissions on every project and workspace) +- Manage policies (refer to [Organization Permissions][] below) +- Manage policy overrides (refer to [Organization Permissions][] below) +- Manage VCS settings (refer to [Organization Permissions][] below) +- Manage the private registry (refer to [Organization Permissions][] below) +- Manage Membership (refer to [Organization Permissions][] below; invite or remove users from the organization itself, and manage the membership of its teams) +- View all secret teams (refer to [Organization Permissions][] below) +- Manage agents (refer to [Organization Permissions][] below) +- Manage organization permissions (refer to [Organization Permissions][] below) +- Manage all organization settings (owners only) +- Manage organization billing (owners only, not applicable to Terraform Enterprise) +- Delete organization (owners only) + +This list is not necessarily exhaustive. + +[Organization Permissions]: #organization-permissions + +## Workspace Permissions + +[workspace]: /terraform/enterprise/workspaces + +Most of HCP Terraform's permissions system is focused on workspaces. In general, administrators want to delegate access to specific collections of infrastructure; HCP Terraform implements this by granting permissions to teams on a per-workspace basis. + +There are two ways to choose which permissions a given team has on a workspace: fixed permission sets, and custom permissions. Additionally, there is a special "admin" permission set that grants the highest level of permissions on a workspace. + +### Implied Permissions + +Some permissions imply other permissions; for example, the run access plan permission also grants permission to read runs. + +If documentation or UI text states that an action requires a specific permission, it is also available for any permission that implies that permission. + +### General Workspace Permissions + +[General Workspace Permissions]: #general-workspace-permissions + +The following workspace permissions can be granted to teams on a per-workspace basis. They can be granted via either fixed permission sets or custom workspace permissions. + +-> **Note:** Throughout the documentation, we refer to the specific permission an action requires (like "requires permission to apply runs") rather than the fixed permission set that includes that permission (like "requires write access"). + +- **Run access:** + - **Read:** — Allows users to view information about remote Terraform runs, including the run history, the status of runs, the log output of each stage of a run (plan, apply, cost estimation, policy check), and configuration versions associated with a run. + - **Plan:** — _Implies permission to read._ Allows users to queue Terraform plans in a workspace, including both speculative plans and normal plans. Normal plans must be approved by a user with permission to apply runs. This also allows users to comment on runs. + - **Apply:** — _Implies permission to plan._ Allows users to approve and apply Terraform plans, causing changes to real infrastructure. +- **Variable access:** + - **No access:** — No access is granted to the values of Terraform variables and environment variables for the workspace. + - **Read:** — Allows users to view the values of Terraform variables and environment variables for the workspace. Note that variables marked as sensitive are write-only, and can't be viewed by any user. + - **Read and write:** — _Implies permission to read._ Allows users to edit the values of variables in the workspace. +- **State access:** + + - **No access:** — No access is granted to the state file from the workspace. + - **Read outputs only:** — Allows users to access values in the workspace's most recent Terraform state that have been explicitly marked as public outputs. Output values are often used as an interface between separate workspaces that manage loosely coupled collections of infrastructure, so their contents can be relevant to people who have no direct responsibility for the managed infrastructure but still indirectly use some of its functions. This permission is required to access the [State Version Outputs](/terraform/enterprise/api-docs/state-version-outputs) API endpoint. + + -> **Note:** **Read state versions** permission is required to use the `terraform output` command or the `terraform_remote_state` data source against the workspace. + - **Read:** — _Implies permission to read outputs only._ Allows users to read complete state files from the workspace. State files are useful for identifying infrastructure changes over time, but often contain sensitive information. + - **Read and write:** — _Implies permission to read._ Allows users to directly create new state versions in the workspace. Applying a remote Terraform run creates new state versions without this permission, but if the workspace's execution mode is set to "local", this permission is required for performing local runs. This permission is also required to use any of the Terraform CLI's state manipulation and maintenance commands against this workspace, including `terraform import`, `terraform taint`, and the various `terraform state` subcommands. +- **Other controls:** + - **Download Sentinel mocks:** — Allows users to download data from runs in the workspace in a format that can be used for developing Sentinel policies. This run data is very detailed, and often contains unredacted sensitive information. + - **Manage Workspace Run Tasks:** — Allows users to associate or dissociate run tasks with the workspace. HCP Terraform creates Run Tasks at the organization level, where you can manually associate or dissociate them with specific workspaces. + - **Lock/unlock workspace:** — Allows users to manually lock the workspace to temporarily prevent runs. When a workspace's execution mode is set to "local", users must have this permission to perform local CLI runs using the workspace's state. + +### Fixed Permission Sets + +Fixed permission sets are bundles of specific permissions for workspaces, which you can use to delegate access to workspaces easily. + +Each permissions set targets a level of authority and responsibility for a given workspace's infrastructure. A permission set can grant permissions that recipients do not require but offer a balance of simplicity and utility. + +#### Workspace Admins + +Much like the owners team has full control over an organization, each workspace has a special "admin" permissions level that grants full control over the workspace. Members of a team with admin permissions on a workspace are sometimes called "workspace admins" for that workspace. + +Admin permissions include the highest level of general permissions for the workspace. There are also some permissions that are only available to workspace admins, which generally involve changing the workspace's settings or setting access levels for other teams. + +Workspace admins have all [General Workspace Permissions](#general-workspace-permissions), as well as the ability to do the following tasks: + +- Read and write workspace settings. This includes general settings, notification configurations, run triggers, and more. +- Set or remove workspace permissions for visible teams. Workspace admins cannot view or manage teams with the [**Secret**](/terraform/enterprise/users-teams-organizations/teams/manage#team-visibility) visibility option enabled unless they are also organization owners. +- Delete the workspace + - Depending on the [organization's settings](/terraform/enterprise/users-teams-organizations/organizations#general), workspace admins may only be able to delete the workspace if it is not actively managing infrastructure. Refer to [Deleting a Workspace With Resources Under Management](/terraform/enterprise/workspaces/settings#deleting-a-workspace-with-resources-under-management) for details. + +#### Write + +The "write" permission set is for people who do most of the day-to-day work of provisioning and modifying managed infrastructure. Write access grants the following workspace permissions: + +- Run access - Apply +- Variable access - Read and write +- State access - Read and write +- Other access - Lock/unlock workspace +- Other access - Download Sentinel mocks + +See [General Workspace Permissions][] above for details about specific permissions. + +#### Plan + +The "plan" permission set is for people who might propose changes to managed infrastructure, but whose proposed changes should be approved before they are applied. Plan access grants the following workspace permissions: + +- Run access - Plan +- Variable access - Read +- State access - Read + +See [General Workspace Permissions][] above for details about specific permissions. + +#### Read + +The "read" permission set is for people who need to view information about the status and configuration of managed infrastructure in order to do their jobs, but aren't responsible for maintaining that infrastructure. Read access grants the following workspace permissions: + +- Run access - Read +- Variable access - Read +- State access - Read + +See [General Workspace Permissions][] above for details about specific permissions. + +### Custom Workspace Permissions + +Custom permissions let you assign specific, finer-grained permissions to a team than the broader fixed permission sets provide. This enables more task-focused permission sets and tighter control of sensitive information. + +You can use custom permissions to assign any of the permissions listed above under [General Workspace Permissions][], with the exception of admin-only permissions. + +The minimum custom permissions set for a workspace is the permission to read runs; the only way to grant a team lower access is to not add them to the workspace at all. + +Some permissions - such as the runs permission - are tiered: you can assign one permission per category, since higher permissions include all of the capabilities of the lower ones. + +## Project Permissions + +You can assign project-specific permissions to teams. + +### Implied Permissions + +Some permissions imply other permissions. For example, permission to update a project also grants permission to read a project. + +If an action states that it requires a specific permission level, you can perform that action if your permissions _imply_ the stated permission level. + +### General Project Permissions + +[General Project Permissions]: #general-project-permissions + +You can grant the following project permissions to teams on a per-project basis. You can grant these with either fixed permission sets or custom project permissions. + +-> **Note:** Throughout the documentation, we refer to the specific permission an action requires (like "requires permission to apply runs") rather than the fixed permission set that includes that permission (like "requires write access"). + +- **Project access:** + - **Read:** — Allows users to view information about the project including the name. + - **Update:** — _Implies permission to read._ Allows users to update the project name. + - **Delete:** — _Implies permission to update._ Allows users to delete the project. + - **Create Workspaces:** — Allow users to create workspaces in the project. This grants read access to all workspaces in the project. + - **Delete Workspaces:** — Allows users to delete workspaces in the project. + - Depending on the [organization's settings](/terraform/enterprise/users-teams-organizations/organizations#general), workspace admins may only be able to delete the workspace if it is not actively managing infrastructure. Refer to [Deleting a Workspace With Resources Under Management](/terraform/enterprise/workspaces/settings#deleting-a-workspace-with-resources-under-management) for details. + - **Move Workspaces:** — Allows users to move workspaces out of the project. A user _must_ have this permission on both the source _and_ destination project to successfully move a workspace from one project to another. +- **Team management:** + - **None:** — No access to view teams assigned to the project. + - **Read:** — Allows users to see teams assigned to the project for visible teams. + - **Manage:** — _Implies permission to read._ Allows users to set or remove project permissions for visible teams. Project admins can not view or manage [secret teams](/terraform/enterprise/users-teams-organizations/teams/manage#team-visibility) unless they are also organization owners. +- **Variable sets:** + - **None:** — No access to variable sets owned by the project. However, users with Variable access permissions can view variable sets applied to this project and its workspaces. + - **Read:** — Allows users to view variable sets owned by this project. + - **Manage:** — _Implies permission to read._ Allows users to create, update, and delete variable sets owned by the project. + +See [General Workspace Permissions](#general-workspace-permissions)for the complete list of available permissions for a project's workspaces. + +### Fixed Permission Sets + +Fixed permission sets are bundles of specific permissions for projects, which you can use to delegate access to a project's workspaces easily. + +#### Project Admin + +Each project has an "admin" permissions level that grants permissions for both the project and the workspaces that belong to that project. Members with admin permissions on a project are dubbed that project's "project admins". + +Members of teams with "admin" permissions for a project have [General Workspace Permissions](#general-workspace-permissions) for every workspace in the project, and the ability to: + +- Read and update project settings. +- Delete the project. +- Create workspaces in the project. +- Move workspaces into or out of the project. This also requires project admin permissions for the source or destination project. +- Grant or revoke project permissions for visible teams. Project admins **cannot** view or manage access for teams that are are [Secret](/terraform/enterprise/users-teams-organizations/teams/manage#team-visibility), unless those admins are also organization owners. + +#### Maintain + +The "maintain" permission is for people who need to manage existing infrastructure in a single project, while also granting the ability to create new workspaces in that project. Maintain access grants full control of everything in the project, including the following permissions: + +- Admin access for all workspaces in this project. +- Create workspaces in this project. +- Read the project name. +- Lock and unlock all workspaces in this project. +- Read and write variables for all workspaces in this project. +- Access state for all workspaces in this project. +- Approve runs for all workspaces in this project. + +#### Write + +The "write" permission set is for people who do most of the day-to-day work of provisioning and modifying managed infrastructure. Write access grants the following workspace permissions: + +- Read the project name. +- Lock and unlock all workspaces in this project. +- Read and write variables for all workspaces in this project. +- Access state for all workspaces in this project. +- Approve runs for all workspaces in this project. + +#### Read + +The "read" permission set is for people who need to view information about the status and configuration of managed infrastructure for their job function, but are not responsible for maintaining that infrastructure. Read access grants the permissions to: + +- Read the project name. +- Read the workspaces in the project. + +### Custom Project Permissions + +Custom permissions enable you to assign specific and granular permissions to a team. You can use custom permission sets to create task-focused permission sets and control sensitive information. + +You can create a set of custom permissions using any of the permissions listed under [General Project Permissions](#general-project-permissions). + +Some permissions, such as the project access permission, are tiered. You can only assign one permission per category because higher-level permissions include the capabilities of lower levels. + +## Organization Permissions + +Separate from project and workspace permissions, you can grant teams permissions to manage or view certain resources or settings across an organization. To set these permissions for a team, go to your organization's **Settings**. Then click **Teams**, and select the team name from the list. + +The following organization permissions are available: + +### Project permissions + +You must select a level of access for projects. + +#### None + +Members do not have access to projects or workspaces. You can grant permissions to individual projects or workspaces through [Project Permissions](/terraform/enterprise/users-teams-organizations/permissions#project-permissions) or [Workspace Permissions](/terraform/enterprise/users-teams-organizations/permissions#workspace-permissions). + +#### View all projects + +Members can view all projects within the organization. This lets users: + +- View project names in a given organization. + +#### Manage all projects + +Members can create and manage all projects and workspaces within the organization. In addition to the permissions granted by [“Manage all workspaces”](/terraform/enterprise/users-teams-organizations/permissions#manage-all-workspaces), this also lets users: + +- Manage other teams' access to all projects. +- Create, edit, and delete projects (otherwise only available to organization owners). +- Move workspaces between projects. + +### Workspace permissions + +You must select a level of access for workspaces. + +#### None + +Members do not have access to projects or workspaces. You can grant permissions to individual projects or workspaces through [Project Permissions](/terraform/enterprise/users-teams-organizations/permissions#project-permissions) or [Workspace Permissions](/terraform/enterprise/users-teams-organizations/permissions#workspace-permissions). + +#### View all workspaces + +Members can view all workspaces within the organization. This lets users: + +- View information and features relevant to each workspaces (e.g. runs, state versions, variables). + +#### Manage all workspaces + +Members can create and manage all workspaces within the organization. This lets users: + +- Perform any action that requires admin permissions in those workspaces. +- Create new workspaces within the organization's **Default Project**, an action that is otherwise only available to organization owners. +- Create, update, and delete [Variable Sets](/terraform/enterprise/workspaces/variables/managing-variables#variable-sets). + +### Manage Policies + +Allows members to create, edit, read, list and delete the organization's Sentinel policies. + +This permission implicitly gives permission to read runs on all workspaces, which is necessary to set enforcement of [policy sets](/terraform/enterprise/policy-enforcement/manage-policy-sets). + +### Manage Run Tasks + +Allows members to create, edit, and delete run tasks on the organization. + +### Manage Policy Overrides + +Allows members to override soft-mandatory policy checks. + +This permission implicitly gives permission to read runs on all workspaces, which is necessary to override policy checks. + +### Manage VCS Settings + +Allows members to manage the set of [VCS providers](/terraform/enterprise/vcs) and [SSH keys](/terraform/enterprise/vcs#ssh-keys) available within the organization. + +### Manage Agent Pools + +Allows members to create, edit, and delete agent pools within their organization. + +This permission implicitly grants access to read all workspaces, which is necessary for agent pool management. + +### Manage Private Registry + +Allows members to publish and delete providers, modules, or both providers and modules in the organization's [private registry](/terraform/enterprise/registry). These permissions are otherwise only available to organization owners. + +### Team Management Permissions + +HCP Terraform has three levels of team management permissions: manage membership, manage teams, and manage organization access. Each permission level grants users the ability to perform specific actions and each progressively requires prerequisite permissions. + +For example, to grant a user the manage teams permission, that user must already have manage membership permissions. To grant a user the manage organization access permission, a user must have both manage teams and manage membership permissions. + +#### Manage Membership + +Allows members to invite users to the organization, remove users from the organization, and add or remove users from teams within the organization. + +This permission grants the ability to view the list of users within the organization, and to view the organization access of other visible teams. It does not permit the creation of teams, the ability to modify the settings of existing teams, or the ability to view secret teams. + +In order to modify the membership of a team, a user with Manage Membership permissions must have visibility into the team (i.e. the team must be ["Visible"](/terraform/enterprise/users-teams-organizations/teams/manage#team-visibility), or the user must be on the team). +In order to remove a user from the organization, the holder of this permission must have visibility into all of the teams which the user is a member of. + +~> This permission is intended to allow owners of large organizations to delegate membership management to another trusted team, and should be granted to only teams of trusted users. **Assign with caution:** Users with this permission are able to add themselves to any visible team, and inherit the permissions of any visible team. + +#### Manage Teams + +Allows members to create, update, and delete teams, and generate, and revoke tokens. + +This permission grants the ability to update a team's names, SSO IDs, and token management permissions, but does not allow access to organization settings. On its own, this permission does not allow users to create, update, delete, or otherwise access secret teams. + +The manage teams permission confers all permissions granted by the manage membership permission. + +This permission allows owners of large organizations to delegate team management to another trusted team. You should only grant it to teams of trusted users. + +~> **Assign with caution**: Users with this permission can update or delete any visible team. Because this permission also confers the manage membership permission, a user with the manage teams permission can add themselves to any visible team. + +#### Manage Organization Access + +Allows members to update a team's organization access settings. + +On its own, this permission does not allow users to create, update, delete, or otherwise access secret teams. This permission confers all of the permissions granted by the manage teams and manage membership permissions. + +This permission allows owners of large organizations to delegate team management to another trusted team. You should only grant it to teams of trusted users. + +~> **Assign with caution:** Members with this permission can update all organization access settings for any team visible to them. + +### Include Secret Teams + +Allows members access to secret teams at the level permitted by that user's team permissions setting. + +This permission acts as a modifier to existing team management permissions. Members with this permission can access secret teams up to the level permitted by other team management permissions. For example, if a user has permission to include secret teams and [manage teams](/terraform/enterprise/users-teams-organizations/permissions#manage-teams), that user can create secret teams. + +### Allow Member Token Management + +Allows owners and members with [manage teams](/terraform/enterprise/users-teams-organizations/permissions#manage-teams) permissions to enable and disable team token management for team members. This permission defaults to `true`. + +When member token management is enabled, members will be able to perform actions on team tokens, including generating and revoking a team token. + +When member token management is disabled, members will be unable to perform actions on team tokens, including generating and revoking a team token. + +## Permissions Outside HCP Terraform's Scope + +This documentation only refers to permissions that are managed by HCP Terraform itself. + +Since HCP Terraform integrates with other systems, the permissions models of those systems can also be relevant to the overall security model of your HCP Terraform organization. For example: + +- When a workspace is connected to a VCS repository, anyone who can merge changes to that repository's main branch can indirectly queue plans in that workspace, regardless of whether they have explicit permission to queue plans or are even a member of your HCP Terraform organization. (And when auto-apply is enabled, merging changes will indirectly apply runs.) +- If you use HCP Terraform's API to create a Slack bot for provisioning infrastructure, anyone able to issue commands to that Slack bot can implicitly act with that bot's permissions, regardless of their own membership and permissions in the HCP Terraform organization. +- When a run task sends a request to an integrator, it provides an access token that provides access depending on the run task stage: + - For post-plan, it provides access to the runs plan json and the run task callback + - All access tokens created for run tasks have a lifetime of 10 minutes + +When integrating HCP Terraform with other systems, you are responsible for understanding the effects on your organization's security. An integrated system is able to delegate any level of access that it has been granted, so carefully consider the conditions and events that will cause it to delegate that access. diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/users-teams-organizations/teams/index.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/users-teams-organizations/teams/index.mdx new file mode 100644 index 0000000000..1be3015653 --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/users-teams-organizations/teams/index.mdx @@ -0,0 +1,59 @@ +--- +page_title: Teams overview +description: >- + Teams are a group of users within an organization. Learn about managing teams, + team membership, team permissions, and more. +source: terraform-docs-common +--- + +[organizations]: /terraform/enterprise/users-teams-organizations/organizations + +[organization settings]: /terraform/enterprise/users-teams-organizations/organizations#organization-settings + +[users]: /terraform/enterprise/users-teams-organizations/users + +# Teams overview + +Teams are groups of HCP Terraform [users][] within an [organization][organizations]. If a user belongs to at least one team in an organization, they are considered a member of that organization. + + + +@include 'tfc-package-callouts/team-management.mdx' + + + +An organization can [grant workspace permissions to teams](/terraform/enterprise/users-teams-organizations/teams/manage#managing-workspace-access) that allow its members to start Terraform runs, create workspace variables, read and write state, and more. Teams can only have permissions on workspaces within their organization, although individual users can belong to multiple teams in this and other organizations. + +> **Hands-on:** Try the [Manage Permissions in HCP Terraform](/terraform/tutorials/cloud/cloud-permissions?utm_source=WEBSITE&utm_medium=WEB_IO&utm_offer=ARTICLE_PAGE&utm_content=DOCS) tutorial. + +## Accessing teams with the API or TFE provider + +In addition to the HCP Terraform UI, you can use the following methods to manage teams: + +- [Teams API](/terraform/enterprise/api-docs/teams) to list, create, update, and delete teams +- [Team Members API](/terraform/enterprise/api-docs/team-members) to add and delete users from teams +- [Team Tokens API](/terraform/enterprise/api-docs/team-tokens) to generate and delete tokens and list an organization's team tokens +- [Team Access API](/terraform/enterprise/api-docs/team-access) to manage team access to one or more workspaces +- The `tfe` provider resources [`tfe_team`](https://registry.terraform.io/providers/hashicorp/tfe/latest/docs/resources/team), [`tfe_team_members`](https://registry.terraform.io/providers/hashicorp/tfe/latest/docs/resources/team_members), and `tfe_team_access` + +### API tokens + +Each team can have an API token not associated with a specific user. You can manage a team's API token from the **Organization settings > API Tokens > Team Token** page. You can create, regenerate, and delete team tokens on the API token page. Refer to [Team API Tokens](/terraform/enterprise/users-teams-organizations/api-tokens#team-api-tokens) for details. + +## The owners team + +Every organization has an owners team, and members of the owners team are sometimes called organization owners. An organization's creator is the first member of its owner's team. You can add and remove other members in the same way as you can with other teams. In free organizations, the owner's team is limited to five members. In paid organizations, the size of the owner's team is unlimited. + +You cannot delete or leave the owner's team empty. If only one member in an owner's team exists, you must add another user before removing the current member. + +Refer to [organization owners](/terraform/enterprise/users-teams-organizations/permissions#organization-owners) for more details about owners team permissions. + +[permissions-citation]: #intentionally-unused---keep-for-maintainers + +## Manage teams + +You can manage many things about teams, including creating and deleting a team, team membership, and team access to workspaces, projects, and organizations. Refer to [Manage teams](/terraform/enterprise/users-teams-organizations/teams/manage) to learn more. + +## Team notifications + +You can set up team notifications to notify team members on external systems whenever a particular action takes place. Refer to [Notifications](/terraform/enterprise/users-teams-organizations/teams/notifications) to learn more. diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/users-teams-organizations/teams/manage.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/users-teams-organizations/teams/manage.mdx new file mode 100644 index 0000000000..63e0331297 --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/users-teams-organizations/teams/manage.mdx @@ -0,0 +1,109 @@ +--- +page_title: Manage teams +description: >- + Learn how to manage team creation, team deletion, team membership, and team + access to workspaces, projects, and organizations. +source: terraform-docs-common +--- + +# Manage teams + +You can grant team management abilities to members of teams with either one of the manage teams or manage organization access permissions. Refer to [Team Permissions](/terraform/enterprise/users-teams-organizations/permissions#team-permissions) for details. + +[Organization owners](/terraform/enterprise/users-teams-organizations/teams#the-owners-team) can also create teams, assign team permissions, or view the full list of teams. Other users can view any teams marked as visible within the organization, plus any secret teams they are members of. Refer to [Team Visibility](#team-visibility) for details. + +To manage teams, perform the following steps: + +1. Sign in to [HCP Terraform](https://app.terraform.io/) or Terraform Enterprise and navigate to the organization where you want to manage teams. +2. Choose **Settings** from the sidebar, then **Teams**. The **Team Management** page appears, containing a list of all teams within the organization. +3. Click a team to go to its settings page, which lists the team's settings and current members. Members that have [two-factor authentication](/terraform/enterprise/users-teams-organizations/2fa) enabled have a **2FA** badge. + +You can manage a team on its settings page by adding or removing members, changing its visibility, and controlling access to workspaces, projects, and the organization. + +## Create teams + +To create a new team, perform the following steps: + +1. Sign in to [HCP Terraform](https://app.terraform.io/) or Terraform Enterprise and navigate to the organization where you want to create a team. +2. Choose **Settings** from the sidebar, then **Teams**. +3. Click **Create a team**. +4. Enter a unique team **Name** and click **Create Team**. Team names can include numbers, letters, underscores (`_`), and hyphens (`-`). + +The new team's settings page appears, where you can add new members and grant permissions. + +## Delete teams + +~> **Important:** Team deletion is permanent, and you cannot undo it. + +To delete a team, perform the following steps: + +1. Sign in to [HCP Terraform](https://app.terraform.io/) or Terraform Enterprise and navigate to the organization where you want to delete a team. +2. Choose **Settings** from the sidebar, then **Teams**. The **Team Management** page appears, containing a list of all teams within the organization. +3. Click the team you want to delete to go to its settings page. +4. Click **Delete [team name]** at the bottom of the page. The **Deleting team "[team name]"** box appears. +5. Click **Yes, delete team** to permanently delete the team and all of its data from HCP Terraform. + +## Manage team membership + +Team structure often resembles your company's organizational structure. + +### Add users + +If the user is not yet in the organization, [invite them to join the organization](/terraform/enterprise/users-teams-organizations/organizations#users) and include a list of teams they should belong to in the invitation. Once the user accepts the invitation, HCP Terraform automatically adds them to those teams. + +To add a user that is already in the organization: + +1. Sign in to [HCP Terraform](https://app.terraform.io/) or Terraform Enterprise and navigate to the organization where you want to add a user to a team. +2. Choose **Settings** from the sidebar, then **Teams**. +3. Click on a team's name to go to its settings page. +4. Choose a user under **Add a New Team Member**. Use the text field to filter the list by username or email. +5. Click the user to add them to the team. HCP Terraform now displays the user under **Members**. + +### Remove users + +To remove a user from a team: + +1. Sign in to [HCP Terraform](https://app.terraform.io/) or Terraform Enterprise and navigate to the organization where you want to remove a user from a team. +2. Choose **Settings** from the sidebar, then **Teams**. +3. Click the team to go to its settings page. +4. Click **...** next to the user's name and choose **Remove from team** from the menu. HCP Terraform removes the user from the list of team members. + +## Team visibility + +The settings under **Visibility** allow you to control who can see a team within the organization. To edit a team's visibility: + +1. Sign in to [HCP Terraform](https://app.terraform.io/) or Terraform Enterprise and navigate to the organization where you want to view teams. +2. Choose **Settings** from the sidebar, then **Teams**. +3. Click on a team's name to navigate to its settings page. +4. Enable one of the following settings: + - **Visible:** Every user in the organization can see the team and its membership. Non-members have read-only access. + - **Secret:** The default setting is that only team members and organization owners can view a team and its membership. + +We recommend making the majority of teams visible to simplify workspace administration. Secret teams should only have +[organization-level permissions](/terraform/enterprise/users-teams-organizations/permissions#organization-permissions) since workspace admins cannot manage permissions for teams they cannot view. + +## Manage workspace access + +You can grant teams various permissions on workspaces. Refer to [Workspace Permissions](/terraform/enterprise/users-teams-organizations/permissions#workspace-permissions) for details. + +HCP Terraform uses the most permissive permission level from your teams to determine what actions you can take on a particular resource. For example, if you belong to a team that only has permission to read runs for a workspace and another team with admin access to that workspace, HCP Terraform grants you admin access. + +HCP Terraform grants the most permissive permissions regardless of whether an organization, project, team, or workspace set those permissions. For example, if a team has permission to read runs for a given workspace and has permission to manage that workspace through the organization, then members of that team can manage that workspace. Refer to [organization permissions](/terraform/enterprise/users-teams-organizations/permissions#organization-permissions) and [project permissions](/terraform/enterprise/users-teams-organizations/permissions#project-permissions) for additional information. + +Another example is when a team has permission at the organization-level to read runs for all workspaces and admin access to a specific workspace. HCP Terraform grants the more permissive admin permissions to that workspace. + +To manage team permissions on a workspace: + +1. Sign in to [HCP Terraform](https://app.terraform.io/) or Terraform Enterprise and navigate to the workspace where you want to set team permissions. +2. Choose **Settings** from the sidebar, then **Team Access**. +3. Click **Add team and permissions** to select a team and assign a pre-built or custom permission set. + +## Manage project access + +You can grant teams permissions to manage a project and the workspaces that belong to it. Refer to [Project Permissions](/terraform/enterprise/users-teams-organizations/permissions#project-permissions) for details. + +## Manage organization access + +Organization owners can grant teams permissions to manage policies, projects and workspaces, team and organization membership, VCS settings, private registry providers and modules, and policy overrides across an organization. Refer to [Organization Permissions](/terraform/enterprise/users-teams-organizations/permissions#organization-permissions) for details. + +[permissions-citation]: #intentionally-unused---keep-for-maintainers diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/users-teams-organizations/teams/notifications.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/users-teams-organizations/teams/notifications.mdx new file mode 100644 index 0000000000..f13ba68a18 --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/users-teams-organizations/teams/notifications.mdx @@ -0,0 +1,132 @@ +--- +page_title: Manage team notifications +description: >- + Learn how to set up team notifications to notify team members on external + systems whenever a particular action takes place. +source: terraform-docs-common +--- + +# Manage team notifications + +HCP Terraform can use webhooks to notify external systems about run progress, change requests, and other events. Team notifications allow you to configure relevant alerts that notify teams you specify whenever a certain event occurs. + +@include 'tfc-package-callouts/notifications.mdx' + +You can configure an individual team notification to notify up to twenty teams. To set up notifications for teams using the API, refer to the [Notification API](/terraform/enterprise/api-docs/notification-configurations#team-notification-configuration). + +## Requirements + +To configure team notifications, you need the [**Manage teams**](/terraform/enterprise/users-teams-organizations/permissions#manage-teams) permissions for the team for which you want to configure notifications. + +## View notification configuration settings + +To view your current team notifications, perform the following steps: + +1. Sign in to [HCP Terraform](https://app.terraform.io/) or Terraform Enterprise and navigate to the organization you want to view the team notifications of. +2. Choose **Settings** from the sidebar, then **Teams**. +3. Select the team for which you want to view the notifications from your list of teams. +4. Select **Notifications** in the sidebar navigation. + +HCP Terraform displays a list of any notification configurations you have set up. A notification configuration defines how and when you want to send notifications, and once you enable that configuration, it can send notifications. + +### Update and enable notification configurations + +Each notification configuration includes a brief overview of each configuration’s name, type, the events that can trigger the notification, and the last time the notification was triggered. Clicking on a notification configuration opens a page where you can perform the following actions: + +- Enable your configuration to send notifications by toggling the switch. +- Delete a configuration by clicking **Delete notification**, then **Yes, delete notification configuration**. +- Test your notification’s configuration by clicking **Send test**. +- Click **Edit notification** to edit your notification configuration. + +After creating a notification configuration, you can only edit the following aspects of that configuration: + +1. The configuration’s name. +2. Whether this configuration notifies everyone on a team or specific members. +3. The workspace events that trigger notifications. You can choose from: + - **All events** triggers a notification for every event in your workspace. + - **No events** means that no workspace events trigger a notification. + - **Only certain events** lets you specify which events trigger a notification. + +After making any changes, click **Update notification** to save your changes. + +## Create and configure a notification + +To configure a new notification for a team or a user, perform the following steps: + +1. Sign in to [HCP Terraform](https://app.terraform.io/) or Terraform Enterprise and navigate to the organization you want to create a team notification in. +2. Choose **Settings** from the sidebar, then **Teams**. +3. Select the team you want to view the notifications for from your list of teams. +4. Select **Notifications** in the sidebar navigation. +5. Click **Create a notification**. + +You must complete the following fields for all new notification configurations: + +1. The **Destination** where HCP Terraform should deliver either a generic or a specifically formatted payload. Refer to [Notification payloads](#notification-payloads) for details. +2. The display **Name** for this notification configuration. +3. If you configure an email notification, you can optionally specify which **Email Recipients** will receive this notification. +4. If you choose to configure a webhook, you must also specify: + - A **Webhook URL** for the destination of your webhook payload. Your URL must accept HTTP or HTTPS `POST` requests and be able to use the chosen payload type. + - You can optionally configure a **Token** as an arbitrary secret string that HCP Terraform will use to sign its notification webhooks. Refer to [Notification authenticity](#notification-authenticity) for details. You cannot view the token after you save the notification configuration. +5. If you choose to specify either a **Slack** or **Microsoft Teams** notification, you must also configure your webhook URL for either service. For details, refer to Slack's documentation on [creating an incoming webhook](https://api.slack.com/messaging/webhooks#create_a_webhook) and Microsoft's documentation on [creating a workflow from a channel in teams](https://support.microsoft.com/en-us/office/creating-a-workflow-from-a-channel-in-teams-242eb8f2-f328-45be-b81f-9817b51a5f0e). +6. Specify which [**Workspace events**](#workspace-events) should trigger this notification. +7. After you finish configuring your notification, click **Create a notification**. + +Note that if you are create an email notification, you must have [**Manage membership**](/terraform/enterprise/users-teams-organizations/permissions#manage-membership) permissions on a team to select users from that team as email recipients. + +### Workspace events + +HCP Terraform can send notifications for all workspace events, no workspace events, or specific events. The following events are available for you to specify: + +| Event | Description | +| :-------------- | :-------------------------------------------------------------------------------------------------------------------------- | +| Change Requests | HCP Terraform will notify this team whenever someone creates a change request on a workspace to which this team has access. | + +## Enable and verify a notification + +To configure HCP Terraform to stop sending notifications for a notification configuration, disable the **Enabled** setting on a configuration's detail page . + +HCP Terraform enables notifications for email configurations by default. Before enabling any webhook notifications, HCP Terraform attempts to verify the notification’s configuration by sending a test message. If the test succeeds, HCP Terraform enables the notification. + +To verify a notification configuration, the destination must respond with a `2xx` HTTP code. If verification fails, HCP Terraform does not enable the configuration and displays an error message. + +For successful and unsuccessful verifications, click the **Last Response** box to view more information about the verification results. You can also send additional test messages by clicking **Send a Test**. + +## Notification Payloads + +Notification payloads contain different attributes depending on the integration you specified when configuring that notification. + +### Slack + +Notifications to Slack contain the following information: + +- Information about the change request, including the username and avatar of the person who created the change request. +- The event that triggered the notification and the time that event occurred. + +### Microsoft Teams + +Notifications to Microsoft Teams contain the following information: + +- Information about the change request, including the username and avatar of the person who created the change request. +- The event that triggered the notification and the time that event occurred. + +### Email + +Email notifications contain the following information: + +- Information about the change request, including the username and avatar of the person who created the change request. +- The event that triggered the notification and the time that event occurred. + +### Generic + +A generic notification contains information about the event that triggered it and the time that the event occurred. You can refer to the complete generic notification payload in the [API documentation](/terraform/enterprise/api-docs/notification-configurations#notification-payload). + +You can use some of the values in the payload to retrieve additional information through the API, such as: + +- The [workspace ID](/terraform/enterprise/api-docs/workspaces#list-workspaces) +- The [organization name](/terraform/enterprise/api-docs/organizations#show-an-organization) + +## Notification Authenticity + +Slack notifications use Slack's own protocols to verify HCP Terraform's webhook requests. + +Generic notifications can include a signature to verify the request. For notification configurations that include a secret token, HCP Terraform's webhook requests include an `X-TFE-Notification-Signature` header containing an HMAC signature computed from the token using the SHA-512 digest algorithm. The notification’s receiving service is responsible for validating the signature. For more information and an example of how to validate the signature, refer to the [API documentation](/terraform/enterprise/api-docs/notification-configurations#notification-payload). diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/users-teams-organizations/users.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/users-teams-organizations/users.mdx new file mode 100644 index 0000000000..aa24a43750 --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/users-teams-organizations/users.mdx @@ -0,0 +1,215 @@ +--- +page_title: Create and manage users in Terraform Enterprise +description: Learn how to create and manage users in Terraform Enterprise. +source: terraform-docs-common +--- + +[organizations]: /terraform/enterprise/users-teams-organizations/organizations + +[teams]: /terraform/enterprise/users-teams-organizations/teams + +[invite]: /terraform/enterprise/users-teams-organizations/organizations#users + +[owners]: /terraform/enterprise/users-teams-organizations/teams#the-owners-team + +# Create and manage users + +User accounts belong to individual people. Each user can be part of one or more [teams](/terraform/enterprise/users-teams-organizations/teams), which are granted permissions on workspaces within an organization. A user can be a member of multiple [organizations][]. + +## API + +Use the [Account API](/terraform/enterprise/api-docs/account) to get account details, update account information, and change your password. + + + +## Log in with a HashiCorp Cloud Platform account + +We recommend using a [HashiCorp Cloud Platform (HCP)](https://portal.cloud.hashicorp.com/sign-up) account to log in to HCP Terraform. Your HCP Account grants access to every HashiCorp product and the Terraform Registry. If you use an HCP Account, you manage account settings like multi-factor authentication and password resets from within HCP instead of the HCP Terraform UI. + +To log in with your HCP account, navigate to [HCP Terraform](https://app.terraform.io/) and click **Continue with HCP account**. HCP Terraform may ask if you want to link your account. + +### Linking HCP and HCP Terraform accounts + +The first time you log in with your HCP credentials, HCP Terraform searches for an existing HCP Terraform account with the same email address. If you have an unlinked account, HCP Terraform asks if you want to link it to your HCP account. Otherwise, if no account matches your HCP account's email address, HCP Terraform creates and automatically links a new HCP Terraform account to your HCP account. + +> **Note**: You can only log in with your HCP credentials after linking your HCP and HCP Terraform accounts. We do not recommend linking your account if you use an SSO provider to log in to HCP Terraform because linking your account may conflict with your existing SSO configuration. + +The only way to log in with your old HCP Terraform credentials is to unlink your HCP Terraform and HCP accounts. If HCP Terraform generated an account for you, you cannot unlink that account from your HCP account. You can unlink a pre-existing HCP Terraform account on the [HCP Account Linking page](#hcp-account-linking) in your **Account settings**. + + + +## Creating an account + +To use HCP Terraform or Enterprise, you must create an account through one of the following methods: + +- **Invitation Email:** When a user sends you an invitation to join an existing HCP Terraform organization, the email includes a sign-up link. After you create an account, you can automatically join that organization and can begin using HCP Terraform. +- **Sign-Up Page:** Creating an account requires a username, an email address, and a password. [Sign up on HCP Terraform](https://app.terraform.io/public/signup/account) or if you have a Terraform Enterprise instance, go to `https:///public/signup/account`. + +After you create an account, you do not belong to any organizations. To begin using HCP Terraform, you can either [create an organization](/terraform/enterprise/users-teams-organizations/organizations#creating-organizations) or ask an organization owner to send you an invitation email to join their organization. + + + +We recommend logging into HCP Terraform [with your HCP account](#log-in-with-your-hashicorp-cloud-platform-account) instead of creating a separate HCP Terraform account. + + + +## Joining organizations and teams + +An organization owner or a user with [**Manage Membership**](/terraform/enterprise/users-teams-organizations/permissions#manage-membership) permissions enabled must [invite you to join their organization](/terraform/enterprise/users-teams-organizations/organizations#users) and [add you to one or more teams](/terraform/enterprise/users-teams-organizations/teams/manage#manage-team-membership). + +[permissions-citation]: #intentionally-unused---keep-for-maintainers + +HCP Terraform sends user invitations by email. If the invited email address matches an existing HCP Terraform account, the invitee can join the organization with that account. Otherwise, they must create a new account and then join the organization. + +## Site admin permissions + +On Terraform Enterprise instances, some user accounts have a special site admin permission that allows them to administer the entire instance. + +Admin permissions are distinct from normal organization-level permissions, and they apply to a different set of UI controls and API endpoints. Admin users can administer any resource across the instance when using the site admin pages or the [admin API](/terraform/enterprise/api-docs/admin), but they have normal user permissions when using an organization's standard UI controls and API endpoints. These normal user permissions are determined by team membership. + +Refer to [Administering Terraform Enterprise](/terraform/enterprise/admin) for more details. + +## Account settings + +To view your settings page, click your user icon and select **Account settings**. Your **Profile** page appears, showing your username, email address, and avatar. + +### Profile + +Click **Profile** in the sidebar to view and edit the username and email address associated with your HCP Terraform account. + +~> **Important:** HCP Terraform includes your username in URL paths to resources. If external systems make requests to these resources, you must update them before you change your username. + +HCP Terraform uses [Gravatar](http://en.gravatar.com) to display a user icon if you have associated one with your email address. Refer to the [Gravatar documentation](http://en.gravatar.com/support/) for details about changing your user icon. + +### Sessions + +Click **Sessions** in the sidebar to view a list of sessions associated with your HCP Terraform account. You can revoke any sessions you do not recognize. + + + +There are two types of Terraform accounts, standalone HCP Terraform accounts and HCP Terraform accounts linked to HCP accounts. + +### Idle session timeout + +HCP Terraform automatically terminates user sessions if there has been no end-user activity for a certain time period: + +- Standalone HCP Terraform accounts can stay idle and valid for up to 14 days by default +- HCP Terraform accounts linked to an HCP account follow the HCP defaults and can stay idle for 1 hour by default + +After HCP Terraform terminates a session, you can resume it by logging back in through the HCP Terraform portal. This is a security measure to prevent unauthorized access to unmonitored devices. + +-> **Note:** HCP Terraform organization owners can reduce the idle session timeout for an organization in the authentication settings for standalone HCP Terraform accounts, but cannot modify settings for HCP Terraform accounts linked to HCP accounts. + +### Forced re-authentication + +Forced re-authentication (e.g., “remember for”) makes a user re-authenticate, regardless of activity. This is a security measure to force a new identity verification to access sensitive IT and data managed by HCP Terraform. In this case, the user must re-authenticate their credentials and may be asked to verify 2FA/MFA again. + +- By default, standalone HCP Terraform accounts are forced to re-authenticate every 14 days +- By default, HCP Terraform accounts linked to an HCP account follow the HCP defaults and are forced to re-authenticate every 48 hours + +-> **Note:** HCP Terraform organization owners can reduce the idle session timeout for standalone HCP Terraform accounts, but cannot modify settings for HCP Terraform accounts linked to HCP accounts. + +### Impact to user experience + +The default re-authentication defaults force users to re-authenticate at the beginning of each work week (Monday through Friday). Note that several actions immediately terminate active sessions, including: + +- Manually logging out of the HCP or HCP Terraform portals +- Clearing browser session/cookies +- Closing all active browser windows + +Any of these actions requires you to re-authenticate regardless of session timeout settings. + + + +### Organizations + +Click **Organizations** in the sidebar to view a list of the organizations where you are a member. If you are on the [owners team][owners], the organization is marked with an **OWNER** badge. + +To leave an organization, click the ellipses (**...**) next to the organization and select **Leave organization**. You do not need permission from the owners to leave an organization, but you cannot leave if you are the last member of the owners team. Either add a new owner and then leave, or [delete the organization](/terraform/enterprise/users-teams-organizations/organizations#general). + +### Password + +Click **Password** in the sidebar to change your password. + +-> **Note:** Password management is not available if your Terraform Enterprise instance uses [SAML single sign on](/terraform/enterprise/saml/configuration). +-> **Note:** Passwords must be at least 10 characters in length, and you can use any type of character. Password management is not available if your Terraform Enterprise instance uses [SAML single sign on](/terraform/enterprise/saml/configuration). + +### Two-factor authentication + +Click **Two Factor Authentication** in the sidebar to enable two-factor authentication. Two-factor authentication requires a TOTP-compliant application or an SMS-capable phone number. An organization can set policies that require two-factor authentication. + +Refer to [Two-Factor Authentication](/terraform/enterprise/users-teams-organizations/2fa) for details. + + + +### HCP account linking + +Click **HCP Account Linking** in the sidebar to unlink your HCP Terraform from your HCP Account. You cannot unlink an account that HCP Terraform autogenerated during the linking process. Refer to [Linked HCP and HCP Terraform Accounts](#linked-hcp-and-hcp-terraform-accounts) for more details. + +After you unlink, you can begin using your HCP Terraform credentials to log in. You cannot log in with your HCP account again unless you re-link it to your HCP Terraform account. + +### SSO identities + +Click **SSO Identities** in the sidebar to review and [remove SSO identity links](/terraform/enterprise/users-teams-organizations/single-sign-on/linking-user-account#remove-sso-identity-link) associated with your account. + +You have an SSO identity for every SSO-enabled HCP Terraform organization. HCP Terraform links each SSO identity to a single HCP Terraform user account. This link determines which account you can use to access each organization. + + + +### Tokens + +Click **Tokens** in the sidebar to create, manage, and revoke API tokens. HCP Terraform has three kinds of API tokens: user, team, and organization. Users can be members of multiple organizations, so user tokens work with any organization where the associated user is a member. Refer to [API Tokens](/terraform/enterprise/users-teams-organizations/api-tokens) for details. + +API tokens are required for the following tasks: + +- Authenticating with the [HCP Terraform API](/terraform/enterprise/api-docs). API calls require an `Authorization: Bearer ` HTTP header. +- Authenticating with the [HCP Terraform CLI integration](/terraform/cli/cloud/settings) or the [`remote` backend](/terraform/language/settings/backends/remote). These require a token in the CLI configuration file or in the backend configuration. +- Using [private modules](/terraform/enterprise/registry/using) in command-line runs on local machines. This requires [a token in the CLI configuration file](/terraform/enterprise/registry/using#authentication). + +Protect your tokens carefully because they contain the same permissions as your user account. For example, if you belong to a team with permission to read and write variables for a workspace, another user could use your API token to authenticate as your user account and also edit variables in that workspace. Refer to [permissions](/terraform/enterprise/users-teams-organizations/permissions) for more details. + +We recommend protecting your tokens by creating them with an expiration date and time. Refer to [API Token Expiration](/terraform/enterprise/users-teams-organizations/api-tokens#token-expiration) for details. + +#### Creating a token + + To create a new token: + +1. Click **Create an API token**. The **Create API token** box appears. +2. Enter a **Description** that explains what the token is for and click **Create API token**. +3. You can optionally enter the token's expiration date or time, or create a token that never expires. The UI displays a token's expiration date and time in your current time zone. +4. Copy your token from the box and save it in a secure location. HCP Terraform only displays the token once, right after you create it. If you lose it, you must revoke the old token and create a new one. + +[permissions-citation]: #intentionally-unused---keep-for-maintainers + +#### Revoking a token + +To revoke a token, click the **trash can** next to it. That token will no longer be able to authenticate as your user account. + +~> **Note**: HCP Terraform does not revoke a user API token's access to an organization when you remove the user from an SSO Identity Provider as the user may still be a member of the organization. To remove access to a user's API token, remove the user from the organization in the UI or with the [Terraform Enterprise provider](https://registry.terraform.io/providers/hashicorp/tfe/latest). + +### GitHub app OAuth token + +Click **Tokens** in the sidebar to manage your GitHub App token. This token lets you connect a workspaces to an available GitHub App installation. + +~> **Note:** Only an HCP Terraform user can own a GitHub App token. Team and Organization API tokens are not able to own a GitHub App token. + +A GitHub App token lets you: + +- Connect workspaces, policy sets, and registry modules to a GitHub App installation with the [HCP Terraform API](/terraform/enterprise/api-docs) and UI. +- View available GitHub App installations with the [HCP Terraform API](/terraform/enterprise/api-docs) and UI. + +After generating this token, you can use it to view information about your available installations for the Terraform Cloud GitHub App. + +#### Creating a GitHub app token + +To create a GitHub App token, click **Create a GitHub App token**. The **GitHub App authorization pop-up window** appears requesting authorization of the Terraform Cloud GitHub App. + +~> **Note:** This does not grant HCP Terraform access to repositories. + +#### Revoking a GitHub app token + +To revoke the GitHub App token, click the **ellipses button (...)**. The dropdown menu appears. Click the **Delete Token** option. This triggers a confirmation window to appear, which asks you to confirm that you want to revoke the token. Once confirmed, the token is revoked and you can no longer view GitHub App installations. + +#### Additional resources + +- [GitHub App permissions in HCP Terraform](/terraform/enterprise/vcs/github-app#github-permissions) diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/vcs/azure-devops-server.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/vcs/azure-devops-server.mdx new file mode 100644 index 0000000000..1995aa8bc4 --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/vcs/azure-devops-server.mdx @@ -0,0 +1,94 @@ +--- +page_title: Set up the Azure DevOps Server VCS provider +description: >- + Learn how to use an on-premises installation of Azure DevOps Server with + workspaces and private registry modules in Terraform Enterprise. +source: terraform-docs-common +--- + +# Set up the Azure DevOps Server VCS provider + +These instructions describe how to connect to on-premise installations of Azure DevOps Server so that you can use HCP Terraform's VCS features. For instructions on how to set up Azure DevOps Services using OAuth, refer to [Set up the Azure DevOps Services VCS provider using OAuth](/terraform/enterprise/vcs/azure-devops-services). For information about other supported VCS providers, refer to [Connect to VCS Providers](/terraform/enterprise/vcs). + +## Requirements + +You must have permission to manage VCS settings for the organization to configure a new VCS provider. Refer to [Permission model](/terraform/enterprise/users-teams-organizations/permissions) for additional information about permissions. + +[permissions-citation]: #intentionally-unused---keep-for-maintainers + +### Personal access token maintenance + +HCP Terraform uses personal access tokens to connect to Azure DevOps Server. This access method requires some additional configuration and ongoing maintenance: + +- [IIS Basic Authentication must be disabled](https://docs.microsoft.com/en-us/azure/devops/integrate/get-started/authentication/iis-basic-auth?view=azure-devops) on your Azure DevOps Server instance in order to use personal access tokens. +- Personal access tokens eventually expire, with a maximum allowed lifetime of one year. If HCP Terraform's token expires, it will be unable to connect to Azure DevOps Server until the token is replaced. To avoid a gap in service, do one of the following before the token expires: + - Update the expiration date of the existing token within Azure DevOps Server. + - Create a new token, and edit HCP Terraform's VCS connection to use it. + +## Step 1: On HCP Terraform, Begin Adding a New VCS Provider + +1. Sign in to [HCP Terraform](https://app.terraform.io/) or Terraform Enterprise and navigate to the organization where you want to add the VCS provider. + +2. Choose **Settings** from the sidebar, then click **Providers**. + +3. Click **Add VCS Provider**. The **VCS Providers** page appears. + +4. Select **Azure DevOps** and then select **Azure DevOps Server** from the menu. The page moves to the next step. + +5. On the "Set up provider" step there are three textboxes. Enter an optional **Name** for this VCS connection. Enter the instance URL for your Azure DevOps Server in **HTTP URL** and **API URL** textboxes. Click the "Continue" button to continue to the next step. + +Leave the page open in a browser tab. In the next step you will copy values from this page, and in later steps you will continue configuring HCP Terraform. + +## Step 2: On Azure DevOps Server, Create a New Personal Access Token + +1. In a new browser tab, open your Azure DevOps Server instance and log in as whichever account you want HCP Terraform to act as. For most organizations this should be a dedicated service user, but a personal account will also work. + + ~> **Important:** The account you use for connecting HCP Terraform **must have Project Collection Administrator access** to any projects containing repositories of Terraform configurations, since creating webhooks requires these permissions. It is not possible to create custom access roles with lower levels of privilege, as Microsoft does not currently allow delegation of this capability. + +2. Navigate to User settings -> Security -> Personal access tokens. + +3. Click the **New Token** button to generate a new personal access token with "Code (Read)" and "Code (Status)" scopes. (We recommend also granting access to "All accessible organizations.") + +4. Copy the generated token to your clipboard; you'll paste it in the next step. Leave this page open in a browser tab. + +## Step 3: Add the Personal Access Token on HCP Terraform + +1. On the "Configure settings" step there is one textbox. Enter your Azure DevOps Server **Personal Access Token** from Step 2. Click the "Continue" button to continue to the next step. + +## Step 4: Configure VCS Provider Scope on HCP Terraform (Optional) + +This step is optional. You can configure which workspaces can use repositories from this VCS provider. By default the **All Projects** option is selected, meaning this VCS provider is available to be used by all workspaces in the organization. + +To limit the scope of this VCS Provider: + +1. Select the **Selected Projects** option and use the text field that appears to search for and select projects to enable. All current and future workspaces for any selected projects can use repositories from this VCS Provider. + +2. Click the **Update VCS Provider** button to save your selections. + +## Step 5: On Workstation, Create an SSH Key for HCP Terraform + +On a secure workstation, create an SSH keypair that HCP Terraform can use to connect to Azure DevOps Server. The exact command depends on your OS, but is usually something like `ssh-keygen -t rsa -m PEM -f "/Users//.ssh/service_terraform" -C "service_terraform_enterprise"`. This creates a `service_terraform` file with the private key, and a `service_terraform.pub` file with the public key. + +This SSH key **must have an empty passphrase.** HCP Terraform cannot use SSH keys that require a passphrase. + +### Important Notes + +- Do not use your personal SSH key to connect HCP Terraform and Azure DevOps Server; generate a new one or use an existing key reserved for service access. +- In the following steps, you must provide HCP Terraform with the private key. Although HCP Terraform does not display the text of the key to users after it is entered, it retains it and will use it for authenticating to Azure DevOps Server. +- **Protect this private key carefully.** It can read code to the repositories you use to manage your infrastructure. Take note of your organization's policies for protecting important credentials and be sure to follow them. + +## Step 6: On Azure Devops Server, Add SSH Public Key + +1. Navigate to User settings -> Security -> SSH public keys on your Azure DevOps Server instance. + + ![Azure DevOps Server screenshot: the SSH keys page](/img/docs/azure-devops-server-public-keys.png) + +2. Click the **Add** button. Paste the text of the **SSH public key** you created in step 3 (from the `.pub` file) into the text field, then click the **Add key** button to confirm. + +## Step 7: On HCP Terraform, Add SSH Private Key + +1. Go back to your HCP Terraform browser tab and paste the text of the **SSH private key** you created in step 3 into the **Private SSH Key** text field of the "Set up SSH keypair" step. Click the "Add SSH key" button. + +## Finished + +At this point, Azure DevOps Server access for HCP Terraform is fully configured, and you can create Terraform workspaces based on your organization's repositories. diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/vcs/azure-devops-services-pat.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/vcs/azure-devops-services-pat.mdx new file mode 100644 index 0000000000..1da6dc1123 --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/vcs/azure-devops-services-pat.mdx @@ -0,0 +1,116 @@ +--- +page_title: Set up the Azure DevOps Services VCS provider using personal access tokens +description: >- + Learn how to use Azure DevOps Services with workspaces and private registry + modules in Terraform Enterprise using personal access tokens. +source: terraform-docs-common +--- + +# Set up the Azure DevOps Services VCS provider using personal access tokens + +These instructions describe how to use a personal access token to connect HCP Terraform to Azure DevOps Services VCS, which is hosted at `dev.azure.com`. Connecting to Azure DevOps Services VCS lets you take advantage of HCP Terraform's VCS features. + +If you do not intend to use personal access tokens to connect to Azure DevOps Services, refer to [Set up the Azure DevOps Server VCS provider](/terraform/enterprise/vcs/azure-devops-server). For information about other supported VCS providers, refer to [Connect to VCS Providers](/terraform/enterprise/vcs). + +## Requirements + +Configuring a new VCS provider requires permission to [manage VCS settings](/terraform/enterprise/users-teams-organizations/permissions#manage-vcs-settings) for the organization. + +To connect HCP Terraform to your repositories, your account must have **Project Collection Administrator** access enabled for all projects containing Terraform configurations. This permission is required to create webhooks because Microsoft doesn't allow you to create custom roles with lower-level access for this purpose. + +[permissions-citation]: #intentionally-unused---keep-for-maintainers + +## Personal access token maintenance + +HCP Terraform uses personal access tokens to connect to Azure DevOps Services. This access method requires some additional configuration and ongoing maintenance: + +- Personal access tokens have a maximum lifetime of one year. When the token expires, HCP Terraform cannot connect to Azure DevOps Services until the token is replaced. To avoid a gap in service, do one of the following actions before the token expires: + - Update the expiration date of the existing token within Azure DevOps Services. + - Create a new token and update the VCS configuration in HCP Terraform VCS connection to use it. + +Refer to the [Azure DevOps documentation](https://learn.microsoft.com/en-us/azure/devops/organizations/accounts/use-personal-access-tokens-to-authenticate?toc=%2Fazure%2Fdevops%2Forganizations%2Ftoc.json&view=azure-devops&tabs=Windows) for instructions on working with personal access tokens. + +## Step 1: Add a new VCS provider in HCP Terraform + +1. Sign in to [HCP Terraform](https://app.terraform.io/) or Terraform Enterprise and navigate to the organization where you want to add the VCS provider. + +2. Choose **Settings** from the sidebar, then click **Providers**. + +3. Click **Add VCS Provider**. The **VCS Providers** page appears. + +4. Select **Azure DevOps** and then select **Azure DevOps Services** from the menu. The page moves to the next step. + +5. Select **Configure with Personal Access Token** + +Leave the page open in a browser tab. In the next step you will copy values from this page, and in later steps you will continue configuring HCP Terraform. + +## Step 2: Create a new personal access token in Azure DevOps Services + +1. In a new browser tab, open your Azure DevOps Services instance and log in as whichever account you want HCP Terraform to act as. Many organizations use a dedicated service user but a personal account is acceptable. + +2. Navigate to User settings -> Security -> Personal access tokens. + +3. Click the **New Token** button to generate a new personal access token with **Code (Read)** and **Code (Status)** scopes. We recommend also granting access to **All accessible organizations** and setting an expiration that meets your requirements. + +4. Click **Create**. + +5. Copy the generated token to your clipboard so that you can use it in the next step. Leave this page open in a browser tab. + +## Step 3: Add the personal access token on HCP Terraform + +1. Return to the browser window you opened in [Step 1: Add a new VCS provider in HCP Terraform](#step-1-add-a-new-vcs-provider-in-hcp-terraform). +2. In the second step under **Set up provider**, specify a name for your VCS provider a name in the **Name** field. This field is optional. +3. In the second step under **Set up provider**, paste the Azure DevOps Services personal access token you copied in [step 2](#step-2-create-a-new-personal-access-token-in-azure-devops-services) into the **Personal Access Token** field. +4. Click **Connect and continue\*\*** to continue. + +## Step 4: Configure advanced settings in HCP Terraform (optional) + +It is optional, but you can configure the following settings in the **Advanced Settings** screen: + +- In the **Scope of VCS Provider** setting, enable workspaces to use repositories from this VCS provider. **All Projects** is enabled by default. This setting lets all workspaces in the organization use the VCS provider. +- In the **Set up SSH Keypair** setting, you can add or update an SSH key and OAuth credentials so that HCP Terraform can access Git submodules in repositories that require them. You configure these settings later. + +If you don't need to configure either of the advanced settings, click **Skip and Finish** to complete the setup and return to HCP Terraform's VCS Provider page, which includes your new Azure DevOps Services client. Otherwise, complete the following steps. + +### Limit the scope of this VCS provider + +1. Enable **Selected Projects** and use the text field that appears to search for and select projects to enable. All current and future workspaces for any selected projects can use repositories from this VCS Provider. + +2. Click **Update VCS Provider** to save your selections. + +3. Click the **Update VCS Provider** button to save your selections. + +### Add an SSH key pair + +HCP Terraform only uses SSH to clone Git submodules and uses HTTPS for all other Git operations. +Do not use your personal SSH key to connect HCP Terraform and Azure DevOps Services. You should generate a new key or use an existing key reserved for service access. + +In the following steps, you must provide HCP Terraform with the private key. Although HCP Terraform does not display the text of the key to users after it is entered, HCP Terraform retains the key and uses for authenticating with Azure DevOps Services. + + + +Follow security best practices and your organization's policies for protecting important credentials when handing and storing a private key. Someone with access to the key can use it to push code to the repositories you use to manage your infrastructure. + + + +1. On a secure workstation, create an SSH keypair that HCP Terraform can use to connect to the Azure DevOps Services domain. The following example uses the `ssh-keygen` command to create a `service_terraform` file with the private key and a `service_terraform.pub` file with the public key: + + ```shell-session + $ ssh-keygen -t rsa -m PEM -f "/Users//.ssh/service_terraform" -C "service_terraform_enterprise" + ``` + + + + The passphrase for the SSH key must be empty. HCP Terraform cannot use SSH keys that require a passphrase. + + + +2. Log into the Azure DevOps Services account you want HCP Terraform to act as and navigate to the **SSH Keys** settings page. + +3. Add a new SSH key and paste the value of the SSH public key you just created. + +4. In HCP Terraform's **Add VCS Provider** page, paste the text of the **SSH private key** you just created, and click the **Add SSH Key** button. + +## Next steps + +Azure DevOps Services access for HCP Terraform is fully configured. You can create Terraform workspaces based on your organization's repositories. diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/vcs/azure-devops-services.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/vcs/azure-devops-services.mdx new file mode 100644 index 0000000000..023a60d8b7 --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/vcs/azure-devops-services.mdx @@ -0,0 +1,132 @@ +--- +page_title: Set up the Azure DevOps Services VCS provider using OAuth +description: >- + Learn how to use Azure DevOps Services with workspaces and private registry + modules in Terraform Enterprise using OAuth. +source: terraform-docs-common +--- + +# Set up the Azure DevOps Services VCS provider using OAuth + +~> **Important:** Starting April 2025, Microsoft will no longer allow new Azure DevOps OAuth app registrations—see the [official announcement](https://devblogs.microsoft.com/devops/no-new-azure-devops-oauth-apps-beginning-february-2025/) for details. Until [Entra ID](https://www.microsoft.com/en-us/security/business/identity-access/microsoft-entra-id) support is shipped in HCP Terraform, we recommend using [personal access tokens](/terraform/enterprise/vcs/azure-devops-services-pat). + +These instructions are for using `dev.azure.com` for HCP Terraform's VCS features. [Other supported VCS providers](/terraform/enterprise/vcs) have separate instructions. + +This page explains the four main steps required to connect HCP Terraform to your Azure DevOps Services VCS: + +1. Create a new connection in HCP Terraform and get the callback URL. +2. On your VCS, register your HCP Terraform organization as a new application. Provide the callback URL and get the application ID and key. +3. Provide HCP Terraform with the application ID and key. Then, request VCS access. +4. On your VCS, approve the access request from HCP Terraform. + +~> **Important:** HCP Terraform only supports Azure DevOps connections that use the `dev.azure.com` domain. If your Azure DevOps project uses the older `visualstudio.com` domain, you must migrate using the [steps in the Microsoft documentation](https://docs.microsoft.com/en-us/azure/devops/release-notes/2018/sep-10-azure-devops-launch#switch-existing-organizations-to-use-the-new-domain-name-url). + +## Requirements + +Configuring a new VCS provider requires permission to [manage VCS settings](/terraform/enterprise/users-teams-organizations/permissions#manage-vcs-settings) for the organization. + +[permissions-citation]: #intentionally-unused---keep-for-maintainers + +Before you begin, enable `Third-party application access via OAuth` in Azure DevOps Services settings. + +1. Log in to [Azure DevOps Services](https://dev.azure.com/). +2. Click **Organization settings**. +3. Click **Policies** under **Security**. +4. Enable the **Third-party application access via OAuth** setting. + + ![Azure DevOps Services Screenshot: Policies Third-party application access via Oauth](/img/docs/azure-devops-services-oauth-policies.png) + +## Step 1: On HCP Terraform, Begin Adding a New VCS Provider + +1. Sign in to [HCP Terraform](https://app.terraform.io/) or Terraform Enterprise and navigate to the organization where you want to add the VCS provider. + +2. Choose **Settings** from the sidebar, then click **Providers**. + +3. Click **Add VCS Provider**. The **VCS Providers** page appears. + +4. Select **Azure DevOps** and then select **Azure DevOps Services** from the menu. The page moves to the next step. + +Leave this page open in a browser tab. You will copy values from this page into Azure DevOps in the next step, and in later steps you will continue configuring HCP Terraform. + +## Step 2: From your Azure DevOps Services Profile, Create a New Application + +1. In a new browser tab, open your [Azure DevOps Services Profile](https://aex.dev.azure.com), and log in to your Azure DevOps Services account if necessary. A page with a list of your organizations appears. + + ~> **Important:** The Azure DevOps Services account you use for connecting HCP Terraform must have Project Collection Administrator access to any projects containing repositories of Terraform configurations, since creating webhooks requires admin permissions. It is not possible to create custom access roles with lower levels of privilege, as Microsoft does not currently allow delegation of this capability. If you're unable to load the link above, you can create a new application for the next step at one of the following links: `https://aex.dev.azure.com/app/register?mkt=en-US` or `https://app.vsaex.visualstudio.com/app/register?mkt=en-US`. + +2. Go into your preferred organization. + +3. Click your user icon and then click the **ellipses (...) ** and select **User settings**. + +4. From the User settings menu, click **Profile**. Your profile page appears. + +5. Click **Authorizations**. The Authorized OAuth Apps page appears. + +6. Click the link to register a new app. A form appears asking for your company and application information. + +7. Fill out the fields and checkboxes with the corresponding values currently displayed in your HCP Terraform browser tab. HCP Terraform lists the values in the order they appear and includes controls for copying values to your clipboard. Here is an example: + + | Field name | Value | + | -------------------------- | ----------------------------------------------------------------------------- | + | Company name | HashiCorp | + | Application Name | HCP Terraform (``) | + | Application website | `https://app.terraform.io` (or the URL of your Terraform Enterprise instance) | + | Authorization callback URL | `https://app.terraform.io/` | + + In the **Authorized scopes** section, select only **Code (read)** and **Code (status)** and then click **Create Application.** + + ![Azure DevOps Services Screenshot: Required permissions when creating a new application in your Azure DevOps Services Profile](/img/docs/azure-devops-services-application-permissions.png) + + ~> **Important:** Do not add any additional scopes beyond **Code (read)** and **Code (status),** as this can prevent HCP Terraform from connecting. Note that these authorized scopes cannot be updated after the application is created; to fix incorrect scopes you must delete and re-create the application. + +8. After creating the application, the next page displays its details. Leave this page open in a browser tab. In the next step, you will copy and paste the unique **App ID** and **Client Secret** from this page. + + If you accidentally close this details page and need to find it later, you can reach it from the **Applications and Services** links in your profile. + +## Step 3: On HCP Terraform, Set up Your Provider + +1. (Optional) Enter a **Name** for this VCS connection. + +2. Enter your Azure DevOps Services application's **App ID** and **Client Secret**. These can be found in the application's details, which should still be open in the browser tab from Step 2. + +3. Click **Connect and continue.** This takes you to a page on Azure DevOps Services, asking whether you want to authorize the app. Click the **Accept** button and you'll be redirected back to HCP Terraform. + + -> **Note:** If you receive a 404 error from Azure DevOps Services, it likely means your callback URL has not been configured correctly. + +## Step 4: On HCP Terraform, Configure Advanced Settings (Optional) + +The settings in this section are optional. The Advanced Settings you can configure are: + +- **Scope of VCS Provider** - You can configure which workspaces can use repositories from this VCS provider. By default the **All Projects** option is selected, meaning this VCS provider is available to be used by all workspaces in the organization. +- **Set up SSH Keypair** - Most organizations will not need to add an SSH key. However, if the organization repositories include Git submodules that can only be accessed via SSH, an SSH key can be added along with the OAuth credentials. You can add or update the SSH key at a later time. + +### If You Don't Need to Configure Advanced Settings: + +1. Click the **Skip and Finish** button. This returns you to HCP Terraform's VCS Provider page, which now includes your new Azure DevOps Services client. + +### If You Need to Limit the Scope of this VCS Provider: + +1. Select the **Selected Projects** option and use the text field that appears to search for and select projects to enable. All current and future workspaces for any selected projects can use repositories from this VCS Provider. + +2. Click the **Update VCS Provider** button to save your selections. + +### If You Do Need an SSH Keypair: + +#### Important Notes + +- SSH will only be used to clone Git submodules. All other Git operations will still use HTTPS. +- Do not use your personal SSH key to connect HCP Terraform and Azure DevOps Services; generate a new one or use an existing key reserved for service access. +- In the following steps, you must provide HCP Terraform with the private key. Although HCP Terraform does not display the text of the key to users after it is entered, it retains it and will use it when authenticating to Azure DevOps Services. +- **Protect this private key carefully.** It can push code to the repositories you use to manage your infrastructure. Take note of your organization's policies for protecting important credentials and be sure to follow them. + +1. On a secure workstation, create an SSH keypair that HCP Terraform can use to connect to Azure DevOps Services.com. The exact command depends on your OS, but is usually something like: + `ssh-keygen -t rsa -m PEM -f "/Users//.ssh/service_terraform" -C "service_terraform_enterprise"` + This creates a `service_terraform` file with the private key, and a `service_terraform.pub` file with the public key. This SSH key **must have an empty passphrase**. HCP Terraform cannot use SSH keys that require a passphrase. + +2. While logged into the Azure DevOps Services account you want HCP Terraform to act as, navigate to the SSH Keys settings page, add a new SSH key and paste the value of the SSH public key you just created. + +3. In HCP Terraform's **Add VCS Provider** page, paste the text of the **SSH private key** you just created, and click the **Add SSH Key** button. + +## Finished + +At this point, Azure DevOps Services access for HCP Terraform is fully configured, and you can create Terraform workspaces based on your organization's repositories. diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/vcs/bitbucket-cloud.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/vcs/bitbucket-cloud.mdx new file mode 100644 index 0000000000..41e33f8676 --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/vcs/bitbucket-cloud.mdx @@ -0,0 +1,127 @@ +--- +page_title: Set up the Bitbucket Cloud VCS provider +description: >- + Learn how to use Bitbucket Cloud with workspaces and private registry modules + in Terraform Enterprise. +source: terraform-docs-common +--- + +# Set up the Bitbucket Cloud VCS provider + +This topic describes how to connect Bitbucket Cloud to HCP Terraform. Bitbucket Cloud is the cloud-hosted version of Bitbucket. For self-hosted Bitbucket Data Center instances, refer to [Configuring Bitbucket Data Center Access](/terraform/enterprise/vcs/bitbucket-data-center). Refer to [Connecting VCS Providers to HCP Terraform](/terraform/enterprise/vcs) for other supported VCS providers. + +Configuring a new VCS provider requires permission to manage VCS settings for the organization. ([More about permissions.](/terraform/enterprise/users-teams-organizations/permissions)) + +[permissions-citation]: #intentionally-unused---keep-for-maintainers + +Connecting HCP Terraform to your VCS involves four steps: + +| On your VCS | On HCP Terraform | +| -------------------------------------------------------------------------------------------- | ----------------------------------------------------------- | +|   | Create a new connection in HCP Terraform. Get callback URL. | +| Register your HCP Terraform organization as a new app. Provide callback URL. Get ID and key. |   | +|   | Provide HCP Terraform with ID and key. Request VCS access. | +| Approve access request. |   | + +The rest of this page explains the Bitbucket Cloud-specific versions of these steps. + +## Step 1: On HCP Terraform, Begin Adding a New VCS Provider + +1. Sign in to [HCP Terraform](https://app.terraform.io/) or Terraform Enterprise and navigate to the organization where you want to add the VCS provider. + +2. Choose **Settings** from the sidebar, then click **Providers**. + +3. Click **Add VCS Provider**. The **VCS Providers** page appears. + +4. Select **Bitbucket** and then select **Bitbucket Cloud** from the menu. The page moves to the next step. + +Leave the page open in a browser tab. In the next step you will copy values from this page, and in later steps you will continue configuring HCP Terraform. + +## Step 2: On Bitbucket Cloud, Create a New OAuth Consumer + +1. In a new browser tab, open [Bitbucket Cloud](https://bitbucket.org) and log in as whichever account you want HCP Terraform to act as. For most organizations this should be a dedicated service user, but a personal account will also work. + + ~> **Important:** The account you use for connecting HCP Terraform **must have admin access** to any shared repositories of Terraform configurations, since creating webhooks requires admin permissions. + +2. Navigate to Bitbucket's "Add OAuth Consumer" page. + + This page is located at `https://bitbucket.org//workspace/settings/oauth-consumers/new`. You can also reach it through Bitbucket's menus: + + - Click your profile picture and choose the workspace you want to access. + - Click "Settings". + - Click "OAuth consumers," which is in the "Apps and Features" section. + - On the OAuth settings page, click the "Add consumer" button. + +3. This page has a form with several text fields and checkboxes. + + Fill out the fields and checkboxes with the corresponding values currently displayed in your HCP Terraform browser tab. HCP Terraform lists the values in the order they appear, and includes controls for copying values to your clipboard. + + Fill out the text fields as follows: + + | Field | Value | + | ------------ | ----------------------------------------------------------------------------- | + | Name | HCP Terraform (``) | + | Description | Any description of your choice. | + | Callback URL | `https://app.terraform.io/` | + | URL | `https://app.terraform.io` (or the URL of your Terraform Enterprise instance) | + + Ensure that the "This is a private consumer" option is checked. Then, activate the following permissions checkboxes: + + | Permission type | Permission level | + | --------------- | ---------------- | + | Account | Write | + | Repositories | Admin | + | Pull requests | Write | + | Webhooks | Read and write | + +4. Click the "Save" button, which returns you to the OAuth settings page. + +5. Find your new OAuth consumer under the "OAuth Consumers" heading, and click its name to reveal its details. + + Leave this page open in a browser tab. In the next step, you will copy and paste the unique **Key** and **Secret.** + +## Step 3: On HCP Terraform, Set up Your Provider + +1. Enter the **Key** and **Secret** from the previous step, as well as an optional **Name** for this VCS connection. + +2. Click "Connect and continue." This takes you to a page on Bitbucket Cloud asking whether you want to authorize the app. + +3. Click the blue "Grant access" button to proceed. + +## Step 4: On HCP Terraform, Configure Advanced Settings (Optional) + +The settings in this section are optional. The Advanced Settings you can configure are: + +- **Scope of VCS Provider** - You can configure which workspaces can use repositories from this VCS provider. By default the **All Projects** option is selected, meaning this VCS provider is available to be used by all workspaces in the organization. +- **Set up SSH Keypair** - Most organizations will not need to add an SSH key. However, if the organization repositories include Git submodules that can only be accessed via SSH, an SSH key can be added along with the OAuth credentials. You can add or update the SSH key at a later time. + +### If You Don't Need to Configure Advanced Settings: + +1. Click the **Skip and Finish** button. This returns you to HCP Terraform's VCS Provider page, which now includes your new Bitbucket Cloud client. + +### If You Need to Limit the Scope of this VCS Provider: + +1. Select the **Selected Projects** option and use the text field that appears to search for and select projects to enable. All current and future workspaces for any selected projects can use repositories from this VCS Provider. + +2. Click the **Update VCS Provider** button to save your selections. + +### If You Do Need an SSH Keypair: + +#### Important Notes + +- SSH will only be used to clone Git submodules. All other Git operations will still use HTTPS. +- Do not use your personal SSH key to connect HCP Terraform and Bitbucket Cloud; generate a new one or use an existing key reserved for service access. +- In the following steps, you must provide HCP Terraform with the private key. Although HCP Terraform does not display the text of the key to users after it is entered, it retains it and will use it when authenticating to Bitbucket Cloud. +- **Protect this private key carefully.** It can push code to the repositories you use to manage your infrastructure. Take note of your organization's policies for protecting important credentials and be sure to follow them. + +1. On a secure workstation, create an SSH keypair that HCP Terraform can use to connect to Bitbucket Cloud. The exact command depends on your OS, but is usually something like: + `ssh-keygen -t rsa -m PEM -f "/Users//.ssh/service_terraform" -C "service_terraform_enterprise"` + This creates a `service_terraform` file with the private key, and a `service_terraform.pub` file with the public key. This SSH key **must have an empty passphrase**. HCP Terraform cannot use SSH keys that require a passphrase. + +2. While logged into the Bitbucket Cloud account you want HCP Terraform to act as, navigate to the SSH Keys settings page, add a new SSH key and paste the value of the SSH public key you just created. + +3. In HCP Terraform's **Add VCS Provider** page, paste the text of the **SSH private key** you just created, and click the **Add SSH Key** button. + +## Finished + +At this point, Bitbucket Cloud access for HCP Terraform is fully configured, and you can create Terraform workspaces based on your organization's shared repositories. diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/vcs/bitbucket-data-center.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/vcs/bitbucket-data-center.mdx new file mode 100644 index 0000000000..7951e3e2d7 --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/vcs/bitbucket-data-center.mdx @@ -0,0 +1,128 @@ +--- +page_title: Set up the Bitbucket Data Center VCS provider +description: >- + Learn how to use Bitbucket Data Center with workspaces and private registry + modules in Terraform Enterprise. +source: terraform-docs-common +--- + +# Set up the Bitbucket Data Center VCS provider + +This topic describes how to connect Bitbucket Data Center to HCP Terraform. For instructions on how to connect Bitbucket Cloud, refer to [Configuring Bitbucket Cloud Access](/terraform/enterprise/vcs/bitbucket-cloud). Refer to [Connecting VCS Providers to HCP Terraform](/terraform/enterprise/vcs) for other supported VCS providers. + +**Bitbucket Server is deprecated**. Atlassian ended support for Bitbucket Server on February 15, 2024, and recommends using either Bitbucket Data Center (v8.0 or newer) or Bitbucket Cloud instead. Refer to the [Atlassian documentation](https://bitbucket.org/blog/cloud-migration-benefits) for additional information. + +HCP Terraform will end support Bitbucket Server on August 15, 2024. Terraform Enterprise will also end support for Bitbucket Server in Terraform Enterprise v202410. [Contact HashiCorp support](https://support.hashicorp.com/hc/en-us) if you have any questions regarding this change. + +## Overview + +The following steps provide an overview of how to connect HCP Terraform and Terraform Enterprise to Bitbucket Data Center: + +1. Add a new VCS provider to HCP Terraform or Enterprise. +2. Create a new application link in Bitbucket. +3. Create an SSH key pair. SSH keys must have an empty passphrase because HCP Terraform cannot use SSH keys that require a passphrase. +4. Add an SSH key to Bitbucket. You must complete this step as a non-administrator user in Bitbucket. +5. Add the private SSH key to Terraform. + +## Requirements + +- You must have permission to manage VCS settings for the organization. Refer to [Permissions](/terraform/enterprise/users-teams-organizations/permissions) for additional information. + +[permissions-citation]: #intentionally-unused---keep-for-maintainers + +- You must have OAuth authentication credentials for Bitbucket Data Center. + +- Your instance of Bitbucket Data Center must be internet-accessible on its SSH and HTTP(S) ports. This is because HCP Terraform must be able to contact Bitbucket Data Center over both SSH and HTTP or HTTPS during setup and during normal operation. + +- HCP Terraform must have network connectivity to Bitbucket Data Center instances. Note that [Bitbucket Data Center's default ports](https://confluence.atlassian.com/bitbucketserverkb/which-ports-does-bitbucket-server-listen-on-and-what-are-they-used-for-806029586.html) are `7999` for SSH and `7990` for HTTP. Check your configuration to confirm your BitBucket instance's real ports. + +## Add a new VCS provider to Terraform + +1. Sign in to [HCP Terraform](https://app.terraform.io/) or Terraform Enterprise and navigate to the organization where you want to add the VCS provider. + +2. Choose **Settings** from the sidebar, then click **Providers**. + +3. Click **Add VCS Provider**. The **VCS Providers** page appears. + +4. Choose **Bitbucket Data Center** from the **Bitbucket** drop-down menu. + +5. (Optional) Enter a **Name** for this VCS connection. + +6. Specify the URL of your Bitbucket Data Center instance in the **HTTP URL** and **API URL** fields. If the context path is not set for your Bitbucket Data Center instance, the **API URL** is the same as the **HTTP URL**. Refer to the [Atlassian documentation](https://confluence.atlassian.com/bitbucketserver/moving-bitbucket-server-to-a-different-context-path-776640153.html) for additional information. Specify the following values if the context path is set for your Bitbucket Data Center instance: + + - Set the **HTTP URL** field to your Bitbucket Data Center instance URL and add the context path: `https:///`. + - Set the **API URL** field to your Bitbucket Data Center instance URL: `https://`. + + By default, HCP Terraform uses port `80` for HTTP and `443` for HTTPS. If Bitbucket Data Center is configured to use non-standard ports or is behind a reverse proxy, you may need to include the port number in the URL. + +7. You can either generate new consumer and public keys that you can use to create a new application link in Bitbucket Data Center described in [Create an application link](#create-an-application-link) or use keys from an existing application link: + - To generate new keys, click **Continue**. Do not leave this screen until you have copied the key values. + - To use existing keys, enable the **Use Custom Keys** option and enter them into the fields. + +## Create an application link + +1. Log into Bitbucket Data Center as an admin. + +2. Open the **Application Links** administration page using the navigation or by entering `https:///plugins/servlet/applinks/listApplicationLinks` in your browser's address bar. + +3. Click **Application Links** in the sidebar, then click **Create new link**. + +4. Choose **Atlassian product** as the link type. This option also works for external applications and lets you continue to use OAuth 1.0 integrations. + +5. Enter `https://app.terraform.io` or the hostname of your Terraform Enterprise instance when prompted. You can only specify the main URL once. To connect multiple HCP Terraform organizations to the same Bitbucket Data Center instance, enter the organization URL when creating the link instead. The organization URL is the HCP Terraform URL or Terraform Enterprise hostname appended with `/app/`. + +6. When prompted, confirm that you wish to use the URL as entered. If you specified HCP Terraform's main URL, click **Continue**. If you specified an organization URL, enable the **Use this URL** option and then click **Continue**. + +7. In the **Link applications** dialog, configure the following settings: + + - Specify `HCP Terraform ` in the **Application Name** field + - Choose **Generic Application** from the **Application Type** drop-down menu + - Enable the **Create incoming link** option + + Leave all the other fields empty. + +8. Click **Continue**. The **Link applications** screen progresses to the second configuration screen. + +9. In the **Consumer Key** and **Public Key** fields, enter the key values you created in the [Add a new VCS provider to Terraform](#add-a-new-vcs-provider-to-terraform) instructions. + +10. In the **Consumer Name** field, enter `HCP Terraform ()`. + +11. Click **Continue**. Bitbucket prompts you to authorize Terraform to make changes. Before you proceed, verify that you are logged in with the user account that HCP Terraform will use to access Bitbucket and not as a Bitbucket administrator. If Bitbucket returns a 500 error instead of the authorization screen, Terraform may have been unable to reach your Bitbucket Data Center instance. + +12. Click **Allow** and enter the SSH key when prompted. + +## Create an SSH key for Terraform + +On a secure workstation, create an SSH keypair that HCP Terraform or Terraform Enterprise can use to connect to Bitbucket Data Center. The command for generating SSH keys depends on your OS. The following example for Linux creates a `service_terraform` file with the private key and a `service_terraform.pub` file with the public key: + +```shell-session +$ ssh-keygen -t rsa -m PEM -f "/Users//.ssh/service_terraform" -C "service_terraform_enterprise" +``` + +Do not specify a passphrase because Terraform cannot use SSH keys that require a passphrase. + +## Add an SSH key to Bitbucket + +In the following steps, you must provide HCP Terraform with the private SSH key you created in [Create an SSH key for Terraform](#create-an-ssh-key-for-terraform). Although HCP Terraform does not display the text of the key to users after it is entered, it retains the key and uses it for authenticating to Bitbucket Data Center. + +1. If you are logged into Bitbucket Data Center as an administrator, log out before proceeding. +2. Log in with the Bitbucket account that you want to use for HCP Terraform or Terraform Enterprise. Many organizations use a dedicated service user account for this purpose. Because creating webhooks requires admin permissions, the account must have admin access to any shared repositories of Terraform configurations. Refer to [Requirements](#requirements) for more information. +3. Open the **SSH keys** page and click the profile icon. +4. Choose **Manage account**. +5. Click **SSH keys** or enter `https:///plugins/servlet/ssh/account/keys` in the address bar to go to the **SSH keys** screen. +6. Click **Add key** and enter the SSH public key you created in [Create an SSH key for Terraform](#create-an-ssh-key-for-terraform) into the text field. Open the `.pub` file to get the key value. +7. Click **Add key** to finish adding the key. + +## Add an SSH private key + +Complete the following steps in HCP Terraform or Terraform Enterprise to request access to Bitbucket and add the SSH private key. + +1. Open the **SSH keys** settings page and click **Add a private SSH key**. A large text field appears. +2. Enter the text of the **SSH private key** you created in [Create an SSH key for Terraform](#create-an-ssh-key-for-terraform) and click **Add SSH Key**. + +## Next steps + +After completing these instructions, you can create Terraform workspaces based on your organization's shared repositories. Refer to the following resources for additional guidance: + +- [Creating Workspaces](/terraform/enterprise/workspaces/create) in HCP Terraform +- [Creating Workspaces](/terraform/enterprise/workspaces/create) in Terraform Enterprise diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/vcs/github-enterprise.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/vcs/github-enterprise.mdx new file mode 100644 index 0000000000..ffdc1dd99f --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/vcs/github-enterprise.mdx @@ -0,0 +1,140 @@ +--- +page_title: Set up the GitHub Enterprise VCS provider +description: >- + Learn how to use an on-premise installation of GitHub Enterprise with + workspaces and private registry modules in Terraform Enterprise. +source: terraform-docs-common +--- + +# Set up the GitHub Enterprise VCS provider + +These instructions are for using a self-hosted installation of GitHub Enterprise for HCP Terraform's VCS features. [GitHub.com has separate instructions,](/terraform/enterprise/vcs/github-enterprise) as do the [other supported VCS providers.](/terraform/enterprise/vcs) + +Configuring a new VCS provider requires permission to manage VCS settings for the organization. ([More about permissions.](/terraform/enterprise/users-teams-organizations/permissions)) + +[permissions-citation]: #intentionally-unused---keep-for-maintainers + +Connecting HCP Terraform to your VCS involves four steps: + +| On your VCS | On HCP Terraform | +| -------------------------------------------------------------------------------------------- | ----------------------------------------------------------- | +|   | Create a new connection in HCP Terraform. Get callback URL. | +| Register your HCP Terraform organization as a new app. Provide callback URL. Get ID and key. |   | +|   | Provide HCP Terraform with ID and key. Request VCS access. | +| Approve access request. |   | + +The rest of this page explains the GitHub Enterprise versions of these steps. + +~> **Important:** HCP Terraform needs to contact your GitHub Enterprise instance during setup and during normal operation. For the SaaS version of HCP Terraform, this means GitHub Enterprise must be internet-accessible; for Terraform Enterprise, you must have network connectivity between your Terraform Enterprise and GitHub Enterprise instances. + +-> **Note:** Alternately, you can skip the OAuth configuration process and authenticate with a personal access token. This requires using HCP Terraform's API. For details, see [the OAuth Clients API page](/terraform/enterprise/api-docs/oauth-clients). + +## Step 1: On HCP Terraform, begin adding a new VCS provider + +1. Sign in to [HCP Terraform](https://app.terraform.io/) or Terraform Enterprise and navigate to the organization where you want to add the VCS provider. + +2. Choose **Settings** from the sidebar, then click **Providers**. + +3. Click **Add a VCS provider**. The **Add VCS Provider** page appears. + +4. Select **GitHub** and then select **GitHub Enterprise** from the menu. The page moves to the next step. + +5. In the "Set up provider" step, fill in the **HTTP URL** and **API URL** of your GitHub Enterprise instance, as well as an optional **Name** for this VCS connection. + + | Field | Value | + | -------- | ------------------------------------------- | + | HTTP URL | `https://` | + | API URL | `https:///api/v3` | + +Leave the page open in a browser tab. In the next step you will copy values from this page, and in later steps you will continue configuring HCP Terraform. + +## Step 2: On GitHub, create a new OAuth application + +1. In a new browser tab, open your GitHub Enterprise instance and log in as whichever account you want HCP Terraform to act as. For most organizations this should be a dedicated service user, but a personal account will also work. + + ~> **Important:** The account you use for connecting HCP Terraform **must have admin access** to any shared repositories of Terraform configurations, since creating webhooks requires admin permissions. + +2. Navigate to GitHub's Register a New OAuth Application page. + + This page is located at `https:///settings/applications/new`. You can also reach it through GitHub's menus: + + - Click your profile picture and choose "Settings." + - Click "OAuth Apps" (under the "Developer settings" section). + - Click the "Register a new application" button. + +3. This page has a form with four text fields. + + Fill out the fields with the corresponding values currently displayed in your HCP Terraform browser tab. HCP Terraform lists the values in the order they appear, and includes controls for copying values to your clipboard. + + Fill out the text fields as follows: + + | Field name | Value | + | -------------------------- | ----------------------------------------------------------------------------- | + | Application Name | HCP Terraform (``) | + | Homepage URL | `https://app.terraform.io` (or the URL of your Terraform Enterprise instance) | + | Application Description | Any description of your choice. | + | Authorization callback URL | `https://app.terraform.io/` | + +4. Click the "Register application" button, which creates the application and takes you to its page. + +5. Download this image of the HCP Terraform logo and upload it with the "Upload new logo" button or the drag-and-drop target. This optional step helps you identify HCP Terraform's pull request checks at a glance. + +6. Click the "Generate a new client secret" button. You will need this secret in the next step. + +7. Leave this page open in a browser tab. In the next step, you will copy and paste the unique **Client ID** and **Client Secret.** + +## Step 3: On HCP Terraform, set up your provider + +1. Enter the **Client ID** and **Client Secret** from the previous step. + +2. Click "Connect and continue." This takes you to a page on your GitHub Enterprise instance, asking whether you want to authorize the app. + +3. The authorization page lists any GitHub organizations this account belongs to. If there is a **Request** button next to the organization that owns your Terraform code repositories, click it now. Note that you need to do this even if you are only connecting workspaces to private forks of repositories in those organizations since those forks are subject to the organization's access restrictions. See [About OAuth App access restrictions](https://docs.github.com/en/organizations/managing-oauth-access-to-your-organizations-data/about-oauth-app-access-restrictions). + + If it results in a 500 error, it usually means HCP Terraform was unable to reach your GitHub Enterprise instance. + +4. Click the green "Authorize ``" button at the bottom of the authorization page. GitHub might request your password or multi-factor token to confirm the operation. + +## Step 4: On HCP Terraform, configure advanced settings (optional) + +The settings in this section are optional. The Advanced Settings you can configure are: + +- **Scope of VCS Provider** - You can configure which workspaces can use repositories from this VCS provider. By default the **All Projects** option is selected, meaning this VCS provider is available to be used by all workspaces in the organization. +- **Set up SSH Keypair** - Most organizations will not need to add an SSH key. However, if the organization repositories include Git submodules that can only be accessed via SSH, an SSH key can be added along with the OAuth credentials. You can add or update the SSH key at a later time. + +### If you don't need to configure advanced settings: + +1. Click the **Skip and finish** button. This returns you to HCP Terraform's VCS Providers page, which now includes your new GitHub Enterprise client. + +### If you need to limit the scope of this VCS provider: + +1. Select the **Selected Projects** option and use the text field that appears to search for and select projects to enable. All current and future workspaces for any selected projects can use repositories from this VCS Provider. + +2. Click the **Update VCS Provider** button to save your selections. + +### If you need an SSH keypair: + +#### Important notes + +- SSH will only be used to clone Git submodules. All other Git operations will still use HTTPS. +- Do not use your personal SSH key to connect HCP Terraform and GitHub Enterprise; generate a new one or use an existing key reserved for service access. +- In the following steps, you must provide HCP Terraform with the private key. Although HCP Terraform does not display the text of the key to users after it is entered, it retains it and will use it when authenticating to GitHub Enterprise. +- **Protect this private key carefully.** It can push code to the repositories you use to manage your infrastructure. Take note of your organization's policies for protecting important credentials and be sure to follow them. + +1. On a secure workstation, create an SSH keypair that HCP Terraform can use to connect to Github Enterprise. The exact command depends on your OS, but is usually something like: + `ssh-keygen -t rsa -m PEM -f "/Users//.ssh/service_terraform" -C "service_terraform_enterprise"` + This creates a `service_terraform` file with the private key, and a `service_terraform.pub` file with the public key. This SSH key **must have an empty passphrase**. HCP Terraform cannot use SSH keys that require a passphrase. + +2. While logged into the GitHub Enterprise account you want HCP Terraform to act as, navigate to the SSH Keys settings page, add a new SSH key and paste the value of the SSH public key you just created. + +3. In HCP Terraform's **Add VCS Provider** page, paste the text of the **SSH private key** you just created, and click the **Add SSH Key** button. + +## Step 5: Contact Your GitHub organization admins + +If your organization uses OAuth app access restrictions, you had to click a **Request** button when authorizing HCP Terraform, which sent an automated email to the administrators of your GitHub organization. An administrator must approve the request before HCP Terraform can access your organization's shared repositories. + +If you're a GitHub administrator, check your email now and respond to the request; otherwise, contact whoever is responsible for GitHub accounts in your organization, and wait for confirmation that they've approved your request. + +## Finished + +At this point, GitHub access for HCP Terraform is fully configured, and you can create Terraform workspaces based on your organization's shared GitHub Enterprise repositories. diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/vcs/github.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/vcs/github.mdx new file mode 100644 index 0000000000..0ffe1630c9 --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/vcs/github.mdx @@ -0,0 +1,145 @@ +--- +page_title: Set up the GitHub.com OAuth VCS provider +description: >- + Learn how to use GitHub.com with workspaces and private registry modules in + Terraform Enterprise with a per-organization OAuth connection. +source: terraform-docs-common +--- + +# Set up the GitHub.com OAuth VCS provider + +These instructions are for using GitHub.com for HCP Terraform's VCS features, using a per-organization OAuth connection with the permissions of one particular GitHub user. [GitHub Enterprise has separate instructions,](/terraform/enterprise/vcs/github-enterprise) as do the [other supported VCS providers.](/terraform/enterprise/vcs) + + + +For new users on HCP Terraform, we recommend using our [configuration-free GitHub App](/terraform/enterprise/vcs/github-app) to access repositories instead. + + + +For Terraform Enterprise site admins, you can create your own [GitHub App](/terraform/enterprise/admin/application/github-app-integration) to access repositories. + +Configuring a new VCS provider requires permission to manage VCS settings for the organization. ([More about permissions.](/terraform/enterprise/users-teams-organizations/permissions)) + +[permissions-citation]: #intentionally-unused---keep-for-maintainers + +Connecting HCP Terraform to your VCS involves four steps: + +| On your VCS | On HCP Terraform | +| ---------------------------------------------------------------------------- | ----------------------------------------------------------- | +|   | Create a new connection in HCP Terraform. Get callback URL. | +| Register your HCP Terraform organization as a new app. Provide callback URL. |   | +|   | Provide HCP Terraform with ID and key. Request VCS access. | +| Approve access request. |   | + +The rest of this page explains the GitHub versions of these steps. + +-> **Note:** Alternately, you can skip the OAuth configuration process and authenticate with a personal access token. This requires using HCP Terraform's API. For details, see [the OAuth Clients API page](/terraform/enterprise/api-docs/oauth-clients). + +## Step 1: On HCP Terraform, begin adding a new VCS provider + +1. Sign in to [HCP Terraform](https://app.terraform.io/) or Terraform Enterprise and navigate to the organization where you want to add the VCS provider. + +2. Choose **Settings** from the sidebar, then click **Providers**. + +3. Click **Add a VCS provider**. The **Add VCS Provider** page appears. + +4. Select **GitHub** and then select **GitHub.com (Custom)** from the menu. The page moves to the next step. + +Leave the page open in a browser tab. In the next step you will copy values from this page, and in later steps you will continue configuring HCP Terraform. + +## Step 2: On GitHub, create a new OAuth application + +On the HCP Terraform **Add VCS Provider** page, click **register a new OAuth Application**. This opens GitHub.com in a new browser tab with the OAuth application settings pre-filled. + +Alternately, create the OAuth application manually on GitHub.com. + +### Manual steps + +1. In a new browser tab, open [github.com](https://github.com) and log in as whichever account you want HCP Terraform to act as. For most organizations this should be a dedicated service user, but a personal account will also work. + + ~> **Important:** The account you use for connecting HCP Terraform **must have admin access** to any shared repositories of Terraform configurations, since creating webhooks requires admin permissions. + +2. Navigate to GitHub's [Register a New OAuth Application](https://github.com/settings/applications/new) page. + + This page is located at . You can also reach it through GitHub's menus: + + - Click your profile picture and choose "Settings." + - Click "Developer settings," then make sure you're on the "OAuth Apps" page (not "GitHub Apps"). + - Click the "New OAuth App" button. + +3. This page has a form with four text fields. + + Fill out the fields with the corresponding values currently displayed in your HCP Terraform browser tab. HCP Terraform lists the values in the order they appear, and includes controls for copying values to your clipboard. + + Fill out the text fields as follows: + + | Field name | Value | + | -------------------------- | ----------------------------------------------------------------------------- | + | Application Name | HCP Terraform (``) | + | Homepage URL | `https://app.terraform.io` (or the URL of your Terraform Enterprise instance) | + | Application Description | Any description of your choice. | + | Authorization callback URL | `https://app.terraform.io/` | + +### Register the OAuth application + +1. Click the "Register application" button, which creates the application and takes you to its page. + +2. Download this image of the HCP Terraform logo and upload it with the "Upload new logo" button or the drag-and-drop target. This optional step helps you identify HCP Terraform's pull request checks at a glance. + +3. Click the **Generate a new client secret** button. You will need this secret in the next step. + +4. Leave this page open in a browser tab. In the next step, you will copy and paste the unique **Client ID** and **Client Secret.** + +## Step 3: On HCP Terraform, set up your provider + +1. Enter the **Client ID** and **Client Secret** from the previous step, as well as an optional **Name** for this VCS connection. + +2. Click "Connect and continue." This takes you to a page on GitHub.com, asking whether you want to authorize the app. + +3. The authorization page lists any GitHub organizations this account belongs to. If there is a **Request** button next to the organization that owns your Terraform code repositories, click it now. Note that you need to do this even if you are only connecting workspaces to private forks of repositories in those organizations since those forks are subject to the organization's access restrictions. See [About OAuth App access restrictions](https://docs.github.com/en/organizations/managing-oauth-access-to-your-organizations-data/about-oauth-app-access-restrictions). + +4. Click the green "Authorize ``" button at the bottom of the authorization page. GitHub might request your password or multi-factor token to confirm the operation. + +## Step 4: On HCP Terraform, configure advanced settings (optional) + +The settings in this section are optional. The Advanced Settings you can configure are: + +- **Scope of VCS Provider** - You can configure which workspaces can use repositories from this VCS provider. By default the **All Projects** option is selected, meaning this VCS provider is available to be used by all workspaces in the organization. +- **Set up SSH Keypair** - Most organizations will not need to add an SSH key. However, if the organization repositories include Git submodules that can only be accessed via SSH, an SSH key can be added along with the OAuth credentials. You can add or update the SSH key at a later time. + +### If you don't need to configure advanced settings: + +1. Click the **Skip and finish** button. This returns you to HCP Terraform's **VCS Providers** page, which now includes your new GitHub client. + +### If you need to limit the scope of this VCS provider: + +1. Select the **Selected Projects** option and use the text field that appears to search for and select projects to enable. All current and future workspaces for any selected projects can use repositories from this VCS Provider. + +2. Click the **Update VCS Provider** button to save your selections. + +### If you need an SSH keypair: + +#### Important notes + +- SSH will only be used to clone Git submodules. All other Git operations will still use HTTPS. +- Do not use your personal SSH key to connect HCP Terraform and GitHub; generate a new one or use an existing key reserved for service access. +- In the following steps, you must provide HCP Terraform with the private key. Although HCP Terraform does not display the text of the key to users after it is entered, it retains it and will use it when authenticating to GitHub. +- **Protect this private key carefully.** It can push code to the repositories you use to manage your infrastructure. Take note of your organization's policies for protecting important credentials and be sure to follow them. + +1. On a secure workstation, create an SSH keypair that HCP Terraform can use to connect to GitHub.com. The exact command depends on your OS, but is usually something like: + `ssh-keygen -t rsa -m PEM -f "/Users//.ssh/service_terraform" -C "service_terraform_enterprise"` + This creates a `service_terraform` file with the private key, and a `service_terraform.pub` file with the public key. This SSH key **must have an empty passphrase**. HCP Terraform cannot use SSH keys that require a passphrase. + +2. While logged into the GitHub.com account you want HCP Terraform to act as, navigate to the SSH Keys settings page, add a new SSH key and paste the value of the SSH public key you just created. + +3. In HCP Terraform's **Add VCS Provider** page, paste the text of the **SSH private key** you just created, and click the **Add SSH Key** button. + +## Step 5: Contact your GitHub organization admins + +If your organization uses OAuth app access restrictions, you had to click a **Request** button when authorizing HCP Terraform, which sent an automated email to the administrators of your GitHub organization. An administrator must approve the request before HCP Terraform can access your organization's shared repositories. + +If you're a GitHub administrator, check your email now and respond to the request; otherwise, contact whoever is responsible for GitHub accounts in your organization, and wait for confirmation that they've approved your request. + +## Finished + +At this point, GitHub access for HCP Terraform is fully configured, and you can create Terraform workspaces based on your organization's shared GitHub repositories. diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/vcs/gitlab-com.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/vcs/gitlab-com.mdx new file mode 100644 index 0000000000..43bee92f73 --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/vcs/gitlab-com.mdx @@ -0,0 +1,119 @@ +--- +page_title: Set up the GitLab.com VCS provider +description: >- + Learn how to use GitLab.com repositories with workspaces and private registry + modules in Terraform Enterprise. +source: terraform-docs-common +--- + +# Set up the GitLab.com VCS provider + +These instructions are for using GitLab.com for HCP Terraform's VCS features. [GitLab CE and GitLab EE have separate instructions,](/terraform/enterprise/vcs/gitlab-eece) as do the [other supported VCS providers.](/terraform/enterprise/vcs) + +Configuring a new VCS provider requires permission to manage VCS settings for the organization. ([More about permissions.](/terraform/enterprise/users-teams-organizations/permissions)) + +[permissions-citation]: #intentionally-unused---keep-for-maintainers + +Connecting HCP Terraform to your VCS involves four steps: + +| On your VCS | On HCP Terraform | +| ---------------------------------------------------------------------------- | ------------------------------------------------------------------------- | +|   | Create a new connection in HCP Terraform. Get redirect URI. | +| Register your HCP Terraform organization as a new app. Provide redirect URI. |   | +|   | Provide HCP Terraform with application ID and secret. Request VCS access. | +| Approve access request. |   | + +The rest of this page explains the GitLab.com versions of these steps. + +-> **Note:** Alternately, you can skip the OAuth configuration process and authenticate with a personal access token. This requires using HCP Terraform's API. For details, see [the OAuth Clients API page](/terraform/enterprise/api-docs/oauth-clients). + +## Step 1: On HCP Terraform, Begin Adding a New VCS Provider + +1. Sign in to [HCP Terraform](https://app.terraform.io/) or Terraform Enterprise and navigate to the organization where you want to add the VCS provider. + +2. Choose **Settings** from the sidebar, then click **Providers**. + +3. Click **Add VCS Provider**. The **VCS Providers** page appears. + +4. Select **GitLab** and then select **GitLab.com** from the menu. The page moves to the next step. + +5. Locate the "Redirect URI" and copy it to your clipboard; you'll paste it in the next step. + +Leave the page open in a browser tab. In the next step you will copy values from this page, and in later steps you will continue configuring HCP Terraform. + +## Step 2: On GitLab, Create a New Application + +1. In a new browser tab, open [gitlab.com](https://gitlab.com) and log in as whichever account you want HCP Terraform to act as. For most organizations this should be a dedicated service user, but a personal account will also work. + + ~> **Important:** The account you use for connecting HCP Terraform **must have Maintainer access** to any shared repositories of Terraform configurations, since creating webhooks requires Maintainer permissions. Refer to [the GitLab documentation](https://docs.gitlab.com/ee/user/permissions.html#project-members-permissions) for details. + +2. Navigate to GitLab's [User Settings > Applications](https://gitlab.com/-/profile/applications) page. + + This page is located at . You can also reach it through GitLab's menus: + + - Click your profile picture and choose "Settings." + - Click "Applications." + +3. This page has a list of applications and a form for adding new ones. The form has two text fields and some checkboxes. + + Fill out the fields and checkboxes with the corresponding values currently displayed in your HCP Terraform browser tab. HCP Terraform lists the values in the order they appear, and includes controls for copying values to your clipboard. + + Fill out the form as follows: + + | Field | Value | + | ----------------------- | ---------------------------------------------------------------------------------------------- | + | Name | HCP Terraform (``) | + | Redirect URI | `https://app.terraform.io/`, the redirect URI you copied from HCP Terraform | + | Confidential (checkbox) | ✔️ (enabled) | + | Scopes (all checkboxes) | api | + + +1. Click the "Save application" button, which creates the application and takes you to its page. + +2. Leave this page open in a browser tab. In the next step, you will copy and paste the unique **Application ID** and **Secret.** + +## Step 3: On HCP Terraform, Set up Your Provider + +1. Enter the **Application ID** and **Secret** from the previous step, as well as an option **Name** for this VCS connection. + +2. Click **Connect and continue.** This takes you to a page on GitLab.com, which asks if you want to authorize the app. + +3. Click the green **Authorize** button at the bottom of the authorization page. + +## Step 4: On HCP Terraform, Configure Advanced Settings (Optional) + +The settings in this section are optional. The Advanced Settings you can configure are: + +- **Scope of VCS Provider** - You can configure which workspaces can use repositories from this VCS provider. By default the **All Projects** option is selected, meaning this VCS provider is available to be used by all workspaces in the organization. +- **Set up SSH Keypair** - Most organizations will not need to add an SSH key. However, if the organization repositories include Git submodules that can only be accessed via SSH, an SSH key can be added along with the OAuth credentials. You can add or update the SSH key at a later time. + +### If You Don't Need to Configure Advanced Settings: + +1. Click the **Skip and Finish** button. This returns you to HCP Terraform's VCS Provider page, which now includes your new GitLab client. + +### If You Need to Limit the Scope of this VCS Provider: + +1. Select the **Selected Projects** option and use the text field that appears to search for and select projects to enable. All current and future workspaces for any selected projects can use repositories from this VCS Provider. + +2. Click the **Update VCS Provider** button to save your selections. + +### If You Do Need an SSH Keypair: + +#### Important Notes + +- SSH will only be used to clone Git submodules. All other Git operations will still use HTTPS. +- Do not use your personal SSH key to connect HCP Terraform and GitLab; generate a new one or use an existing key reserved for service access. +- In the following steps, you must provide HCP Terraform with the private key. Although HCP Terraform does not display the text of the key to users after it is entered, it retains it and will use it when authenticating to GitLab. +- **Protect this private key carefully.** It can push code to the repositories you use to manage your infrastructure. Take note of your organization's policies for protecting important credentials and be sure to follow them. + +1. On a secure workstation, create an SSH keypair that HCP Terraform can use to connect to GitLab.com. The exact command depends on your OS, but is usually something like: + `ssh-keygen -t rsa -m PEM -f "/Users//.ssh/service_terraform" -C "service_terraform_enterprise"` + This creates a `service_terraform` file with the private key, and a `service_terraform.pub` file with the public key. This SSH key **must have an empty passphrase**. HCP Terraform cannot use SSH keys that require a passphrase. + +2. While logged into the GitLab.com account you want HCP Terraform to act as, navigate to the SSH Keys settings page, add a new SSH key and paste the value of the SSH public key you just created. + +3. In HCP Terraform's **Add VCS Provider** page, paste the text of the **SSH private key** you just created, and click the **Add SSH Key** button. + +## Finished + +At this point, GitLab.com access for HCP Terraform is fully configured, and you can create Terraform workspaces based on your organization's shared repositories. diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/vcs/gitlab-eece.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/vcs/gitlab-eece.mdx new file mode 100644 index 0000000000..a4b484859d --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/vcs/gitlab-eece.mdx @@ -0,0 +1,133 @@ +--- +page_title: Set up the GitLab EE and CE VCS provider +description: >- + Learn how to use on-premise installation of GitLab Enterprise Edition (EE) or + GitLab Community Edition (CE) with workspaces and private registry module in + Terraform Enterprise. +source: terraform-docs-common +--- + +# Set up the GitLab EE and CE VCS provider + +These instructions are for using an on-premise installation of GitLab Enterprise Edition (EE) or GitLab Community Edition (CE) for HCP Terraform's VCS features. [GitLab.com has separate instructions,](/terraform/enterprise/vcs/gitlab-com) as do the [other supported VCS providers.](/terraform/enterprise/vcs) + +Configuring a new VCS provider requires permission to manage VCS settings for the organization. ([More about permissions.](/terraform/enterprise/users-teams-organizations/permissions)) + +[permissions-citation]: #intentionally-unused---keep-for-maintainers + +Connecting HCP Terraform to your VCS involves four steps: + +| On your VCS | On HCP Terraform | +| -------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------- | +|   | Create a new connection in HCP Terraform. Get redirect URI. | +| Register your HCP Terraform organization as a new app. Provide redirect URI. Get ID and key. |   | +|   | Provide HCP Terraform with application ID and secret. Request VCS access. | +| Approve access request. |   | + +The rest of this page explains the on-premise GitLab versions of these steps. + +~> **Important:** HCP Terraform needs to contact your GitLab instance during setup and during normal operation. For the SaaS version of HCP Terraform, this means GitLab must be internet-accessible; for Terraform Enterprise, you must have network connectivity between your Terraform Enterprise and GitLab instances. + +-> **Note:** Alternately, you can skip the OAuth configuration process and authenticate with a personal access token. This requires using HCP Terraform's API. For details, see [the OAuth Clients API page](/terraform/enterprise/api-docs/oauth-clients). + +-> **Version Note:** HCP Terraform supports GitLab versions 9.0 and newer. HashiCorp does not test older versions of GitLab with HCP Terraform, and they might not work as expected. Also note that, although we do not deliberately remove support for versions that have reached end of life (per the [GitLab Support End of Life Policy](https://docs.gitlab.com/ee/policy/maintenance.html#patch-releases)), our ability to resolve customer issues with end of life versions might be limited. + +## Step 1: On HCP Terraform, Begin Adding a New VCS Provider + +1. Sign in to [HCP Terraform](https://app.terraform.io/) or Terraform Enterprise and navigate to the organization where you want to add the VCS provider. + +2. Choose **Settings** from the sidebar, then click **Providers**. + +3. Select **GitLab** and then select **GitLab Enterprise Edition** or **GitLab Community Edition** from the menu. The page moves to the next step. + +4. In the "Set up provider" step, fill in the **HTTP URL** and **API URL** of your GitLab Enterprise Edition or GitLab Community Edition instance, as well as an optional **Name** for this VCS connection. Click "Continue." + + | Field | Value | + | -------- | ------------------------------------------- | + | HTTP URL | `https://` | + | API URL | `https:///api/v4` | + + Note that HCP Terraform uses GitLab's v4 API. + +Leave the page open in a browser tab. In the next step you will copy values from this page, and in later steps you will continue configuring HCP Terraform. + +## Step 2: On GitLab, Create a New Application + +1. In a new browser tab, open your GitLab instance and log in as whichever account you want HCP Terraform to act as. For most organizations this should be a dedicated service user, but a personal account will also work. + + ~> **Important:** The account you use for connecting HCP Terraform **must have admin (master) access** to any shared repositories of Terraform configurations, since creating webhooks requires admin permissions. Do not create the application as an administrative application not owned by a user; HCP Terraform needs user access to repositories to create webhooks and ingress configurations. + + ~> **Important**: In GitLab CE or EE 10.6 and up, you may also need to enable **Allow requests to the local network from hooks and services** on the "Outbound requests" section inside the Admin area under Settings (`/admin/application_settings/network`). Refer to [the GitLab documentation](https://docs.gitlab.com/ee/security/webhooks.html) for details. + +2. Navigate to GitLab's "User Settings > Applications" page. + + This page is located at `https:///profile/applications`. You can also reach it through GitLab's menus: + + - Click your profile picture and choose "Settings." + - Click "Applications." + +3. This page has a list of applications and a form for adding new ones. The form has two text fields and some checkboxes. + + Fill out the fields and checkboxes with the corresponding values currently displayed in your HCP Terraform browser tab. HCP Terraform lists the values in the order they appear, and includes controls for copying values to your clipboard. + + Fill out the form as follows: + + | Field | Value | + | ------------------------------- | ---------------------------------------------- | + | Name | HCP Terraform (``) | + | Redirect URI | `https://app.terraform.io/` | + | Confidential (checkbox) | ✔️ (enabled) | + | Expire access tokens (checkbox) | (no longer required) | + | Scopes (all checkboxes) | api | + + -> **Note:** For previous versions of HCP Terraform and GitLab, we recommended disabling a setting called `Expire access tokens`. This action was required because Gitlab marked OAuth tokens as expired after 2 hours, but HCP Terraform only refreshed tokens after 6 hours. This setting does not exist on Gitlab v15+ and HCP Terraform now refreshes tokens more often. + +4. Click the "Save application" button, which creates the application and takes you to its page. + +5. Leave this page open in a browser tab. In the next step, you will copy and paste the unique **Application ID** and **Secret.** + +## Step 3: On HCP Terraform, Set up Your Provider + +1. On the "Configure settings" step on HCP Terraform, enter the **Application ID** and **Secret** from the previous step. + +2. Click **Connect and continue.** This takes you to a page on GitLab asking whether you want to authorize the app. Alternatively, if you are redirected to a 500 error, it usually means HCP Terraform was unable to reach your GitLab instance. + +3. Click the green **Authorize** button at the bottom of the authorization page. + +## Step 4: On HCP Terraform, Configure Advanced Settings (Optional) + +The settings in this section are optional. The Advanced Settings you can configure are: + +- **Scope of VCS Provider** - You can configure which workspaces can use repositories from this VCS provider. By default the **All Projects** option is selected, meaning this VCS provider is available to be used by all workspaces in the organization. +- **Set up a PEM formatted SSH Keypair** - Most organizations will not need to add an SSH key. However, if the organization repositories include Git submodules that can only be accessed via SSH, an SSH key can be added along with the OAuth credentials. You can add or update the SSH key at a later time. + +### If You Don't Need to Configure Advanced Settings: + +1. Click the **Skip and Finish** button. This returns you to HCP Terraform's VCS Provider page, which now includes your new GitLab client. + +### If You Need to Limit the Scope of this VCS Provider: + +1. Select the **Selected Projects** option and use the text field that appears to search for and select projects to enable. All current and future workspaces for any selected projects can use repositories from this VCS Provider. + +2. Click the **Update VCS Provider** button to save your selections. + +### If You Do Need a PEM formatted SSH Keypair: + +#### Important Notes + +- SSH will only be used to clone Git submodules. All other Git operations will still use HTTPS. +- Do not use your personal SSH key to connect HCP Terraform and GitLab; generate a new one or use an existing key reserved for service access. +- In the following steps, you must provide HCP Terraform with the private key. Although HCP Terraform does not display the text of the key to users after it is entered, it retains it and will use it when authenticating to GitLab. +- **Protect this private key carefully.** It can push code to the repositories you use to manage your infrastructure. Take note of your organization's policies for protecting important credentials and be sure to follow them. + +1. On a secure workstation, create a PEM formatted SSH keypair that HCP Terraform can use to connect to GitLab. The exact command depends on your OS, but is usually something like: + `ssh-keygen -t rsa -m PEM -f "/Users//.ssh/service_terraform" -C "service_terraform_enterprise"` + This creates a `service_terraform` file with the private key, and a `service_terraform.pub` file with the public key. This SSH key **must have an empty passphrase**. HCP Terraform cannot use SSH keys that require a passphrase. + +2. While logged into the GitLab account you want HCP Terraform to act as, navigate to the SSH Keys settings page, add a new SSH key and paste the value of the SSH public key you just created. + +3. In HCP Terraform's **Add VCS Provider** page, paste the text of the **SSH private key** you just created, and click the **Add SSH Key** button. + +## Finished + +At this point, GitLab access for HCP Terraform is fully configured, and you can create Terraform workspaces based on your organization's shared repositories. diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/vcs/index.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/vcs/index.mdx new file mode 100644 index 0000000000..4b1c37c0c5 --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/vcs/index.mdx @@ -0,0 +1,137 @@ +--- +page_title: Connect to VCS Providers +description: >- + Version control system (VCS) connections integrate Terraform Enterprise into + your workflow. Learn how to automate Terraform runs when you commit changes to + your code. +source: terraform-docs-common +--- + +# Connect to VCS Providers + +HCP Terraform is more powerful when you integrate it with your version control system (VCS) provider. Although you can use many of HCP Terraform's features without one, a VCS connection provides additional features and improved workflows. In particular: + +- When workspaces are linked to a VCS repository, HCP Terraform can [automatically initiate Terraform runs](/terraform/enterprise/run/ui) when changes are committed to the specified branch. +- HCP Terraform makes code review easier by [automatically predicting](/terraform/enterprise/run/ui#speculative-plans-on-pull-requests) how pull requests will affect infrastructure. +- Publishing new versions of a [private Terraform module](/terraform/enterprise/registry/publish-modules) is as easy as pushing a tag to the module's repository. + +We recommend configuring VCS access when first setting up an organization, and you might need to add additional VCS providers later depending on how your organization grows. + +Configuring a new VCS provider requires permission to manage VCS settings for the organization. ([More about permissions.](/terraform/enterprise/users-teams-organizations/permissions)) + +[permissions-citation]: #intentionally-unused---keep-for-maintainers + +## Supported VCS Providers + +HCP Terraform supports the following VCS providers: + + + +- [GitHub.com](/terraform/enterprise/vcs/github-app) + + + +- [GitHub App for TFE](/terraform/enterprise/admin/application/github-app-integration) +- [GitHub.com (OAuth)](/terraform/enterprise/vcs/github) +- [GitHub Enterprise](/terraform/enterprise/vcs/github-enterprise) +- [GitLab.com](/terraform/enterprise/vcs/gitlab-com) +- [GitLab EE and CE](/terraform/enterprise/vcs/gitlab-eece) +- [Bitbucket Cloud](/terraform/enterprise/vcs/bitbucket-cloud) +- [Bitbucket Data Center](/terraform/enterprise/vcs/bitbucket-data-center) +- [Azure DevOps Server](/terraform/enterprise/vcs/azure-devops-server) +- [Azure DevOps Services (OAuth)](/terraform/enterprise/vcs/azure-devops-services) +- [Azure DevOps Services (PAT)](/terraform/enterprise/vcs/azure-devops-services-pat) + +Use the links above to see details on configuring VCS access for each supported provider. If you use another VCS that is not supported, you can build an integration via [the API-driven run workflow](/terraform/enterprise/run/api). + +## How HCP Terraform Uses VCS Access + +Most workspaces in HCP Terraform are associated with a VCS repository, which provides Terraform configurations for that workspace. To find out which repos are available, access their contents, and create webhooks, HCP Terraform needs access to your VCS provider. + +Although HCP Terraform's API lets you create workspaces and push configurations to them without a VCS connection, the primary workflow expects every workspace to be backed by a repository. + +To use configurations from VCS, HCP Terraform needs to do several things: + +- Access a list of repositories, to let you search for repos when creating new workspaces. +- Register webhooks with your VCS provider, to get notified of new commits to a chosen branch. +- Download the contents of a repository at a specific commit in order to run Terraform with that code. + +~> **Important:** HCP Terraform usually performs VCS actions using a designated VCS user account, but it has no other knowledge about your VCS's authorization controls and does not associate HCP Terraform user accounts with VCS user accounts. This means HCP Terraform's VCS user might have a different level of access to repositories than any given HCP Terraform user. Keep this in mind when selecting a VCS user, as it may affect your security posture in one or both systems. + +### Webhooks + +HCP Terraform uses webhooks to monitor new commits and pull requests. + +- When someone adds new commits to a branch, any HCP Terraform workspaces based on that branch will begin a Terraform run. Usually a user must inspect the plan output and approve an apply, but you can also enable automatic applies on a per-workspace basis. You can prevent automatic runs by locking a workspace. A run will only occur if the workspace has not previously processed a run for the commit SHA. +- When someone submits a pull request/merge request to a branch, any HCP Terraform workspaces based on that branch will perform a [speculative plan](/terraform/enterprise/run/remote-operations#speculative-plans) with the contents of the request and links to the results on the PR's page. This helps you avoid merging PRs that cause plan failures. + +~> **Important:** In Terraform Enterprise, integration with a SaaS VCS provider (GitHub.com, GitLab.com, Bitbucket Cloud, or Azure DevOps Services) requires ingress from the public internet. This lets the inbound web hooks reach Terraform Enterprise. You should also configure appropriate security controls, such as a Web Application Firewall (WAF). + +### SSH Keys + +For most supported VCS providers, HCP Terraform does not need an SSH key. This is because Terraform can do everything it needs with the provider's API and an OAuth token. The exceptions are Azure DevOps Server and Bitbucket Data Center, which require an SSH key for downloading repository contents. Refer to the setup instructions for [Azure DevOps Server](/terraform/enterprise/vcs/azure-devops-server) and [Bitbucket Data Center](/terraform/enterprise/vcs/bitbucket-data-center) for details. + +For other VCS providers, most organizations will not need to add an SSH private key. However, if the organization repositories include Git submodules that can only be accessed via SSH, an SSH key can be added along with the OAuth credentials. + +For VCS providers where adding an SSH private key is optional, SSH will only be used to clone Git submodules. All other Git operations will still use HTTPS. + +If submodules will be cloned via SSH from a private VCS instance, SSH must be running on the standard port 22 on the VCS server. + +To add an SSH key to a VCS connection, finish configuring OAuth in the organization settings, and then use the "add a private SSH key" link on the VCS Provider settings page to add a private key that has access to the submodule repositories. When setting up a workspace, if submodules are required, select "Include submodules on clone". More at [Workspace settings](/terraform/enterprise/workspaces/settings). + +### Multiple VCS Connections + +If your infrastructure code is spread across multiple VCS providers, you can configure multiple VCS connections. You can choose which VCS connection to use whenever you create a new workspace. + +#### Scoping VCS Connections using Projects + +You can configure which projects can use repositories from a VCS connection. By default each VCS connection is enabled for all workspaces in the organization. If you need to limit which projects can use repositories from a given VCS connection, you can change this setting to enable the connection for only workspaces in the selected projects. + +## Configuring VCS Access + +HCP Terraform uses the OAuth protocol to authenticate with VCS providers. + +~> **Important:** Even if you've used OAuth before, read the instructions carefully. Since HCP Terraform's security model treats each _organization_ as a separate OAuth application, we authenticate with OAuth's developer workflow, which is more complex than the standard user workflow. + +The exact steps to authenticate are different for each VCS provider, but they follow this general order: + +| On your VCS | On HCP Terraform | +| ---------------------------------------------------------------------- | ------------------------------------------------------------------------------ | +| Register your HCP Terraform organization as a new app. Get ID and key. |   | +|   | Tell HCP Terraform how to reach VCS, and provide ID and key. Get callback URL. | +| Provide callback URL. |   | +|   | Request VCS access. | +| Approve access request. |   | + +For complete details, click the link for your VCS provider: + +- [GitHub](/terraform/enterprise/vcs/github) +- [GitHub Enterprise](/terraform/enterprise/vcs/github-enterprise) +- [GitLab.com](/terraform/enterprise/vcs/gitlab-com) +- [GitLab EE and CE](/terraform/enterprise/vcs/gitlab-eece) +- [Bitbucket Cloud](/terraform/enterprise/vcs/bitbucket-cloud) +- [Bitbucket Data Center](/terraform/enterprise/vcs/bitbucket-data-center) +- [Azure DevOps Server](/terraform/enterprise/vcs/azure-devops-server) +- [Azure DevOps Services](/terraform/enterprise/vcs/azure-devops-services) + +-> **Note:** Alternatively, you can skip the OAuth configuration process and authenticate with a personal access token. This requires using HCP Terraform's API. For details, see [the OAuth Clients API page](/terraform/enterprise/api-docs/oauth-clients). + + + +### Private VCS + +You can use self-hosted HCP Terraform Agents to connect HCP Terraform to your private VCS provider, such as GitHub Enterprise, GitLab Enterprise, and BitBucket Data Center. For more information, refer to [Connect to Private VCS Providers](/terraform/enterprise/vcs/private). + + + +## Viewing events + +-> **Note**: The VCS Events page is still in beta as support is being added for additional VCS providers. Currently only GitLab.com connections established after December 2020 are supported. + +VCS events describe changes within your organization for VCS-related actions. The VCS events page only displays events from previously processed commits in the past 30 days. The VCS page indicates previously processed commits with the message, `"Processing skipped for duplicate commit SHA"`. + +Viewing VCS events requires permission to manage VCS settings for the organization. ([More about permissions.](/terraform/enterprise/users-teams-organizations/permissions)) + +[permissions-citation]: #intentionally-unused---keep-for-maintainers + +To view VCS events for your organization, go to your organization's settings and click **Events**. The **VCS Events** page appears. diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/vcs/troubleshooting.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/vcs/troubleshooting.mdx new file mode 100644 index 0000000000..182c9ba0bb --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/vcs/troubleshooting.mdx @@ -0,0 +1,227 @@ +--- +page_title: Troubleshoot VCS providers in Terraform Enterprise +description: >- + Learn how to address common problems in VCS integrations for Terraform + Enterprise. +source: terraform-docs-common +--- + +# Troubleshoot VCS providers + +This page collects solutions to the most common problems our users encounter with VCS integration in HCP Terraform. + +## Azure DevOps + +### Required status checks not sending + +When configuring [status checks with Azure DevOps](https://learn.microsoft.com/en-us/azure/devops/repos/git/pr-status-policy) the web interface may auto populate Genre and Name fields (beneath "Status to check") with incorrect values that do not reflect what HCP Terraform is sending. +To function correctly as required checks the Genre must be populated with "Terraform Cloud" (or the first segment for a Terraform Enterprise install), and the remainder of the status check goes in the Name field. This requires using the "Enter genre/name separately" checkbox to not use the default configuration. + +In the example below the status check is named `Terraform Cloud/paul-hcp/gianni-test-1` and needs to be configured with Genre `Terraform Cloud` and Name `paul-hcp/gianni-test-1`. + +![Azure DevOps screenshot: configuring required status checks correctly](/img/docs/ado-required-status-check.png) + +With an older version of Azure DevOps Server it may be that the web interface does not allow entering the Genre and Name separately. In which case the status check will need to be created via the [API](https://learn.microsoft.com/en-us/rest/api/azure/devops/policy/configurations/create). + +## Bitbucket Data Center + +The following errors are specific to Bitbucket Data Center integrations. + +### Clicking "Connect organization ``" with Bitbucket Data Center raises an error message in HCP Terraform + +HCP Terraform uses OAuth 1 to authenticate the user to Bitbucket Data Center. The first step in the authentication process is for HCP Terraform to call Bitbucket Data Center to obtain a request token. After the call completes, HCP Terraform redirects you to Bitbucket Data Center with the request token. + +An error occurs when HCP Terraform calls to Bitbucket Data Center to obtain the request token but the request is rejected. Some common reasons for the request to be rejected are: + +- The API endpoint is unreachable; this can happen if the address or port is incorrect or the domain name doesn't resolve. +- The certificate used on Bitbucket Data Center is rejected by the HCP Terraform HTTP client because the SSL verification fails. This is often the case with self-signed certificates or when the Terraform Enterprise instance is not configured to trust the signing chain of the Bitbucket Data Center SSL certificate. + +To fix this issue, do the following: + +- Verify that the instance running Terraform Enterprise can resolve the domain name and can reach Bitbucket Data Center. +- Verify that the HCP Terraform client accepts the HTTPS connection to Bitbucket Data Center. This can be done by performing a `curl` from the Terraform Enterprise instance to Bitbucket Data Center; it should not return any SSL errors. +- Verify that the Consumer Key, Consumer Name, and the Public Key are configured properly in Bitbucket Data Center. +- Verify that the HTTP URL and API URL in HCP Terraform are correct for your Bitbucket Data Center instance. This includes the proper scheme (HTTP vs HTTPS), as well as the port. + +### Creating a workspace from a repository hangs indefinitely, displaying a spinner on the confirm button + +If you were able to connect HCP Terraform to Bitbucket Data Center but cannot create workspaces, it often means HCP Terraform isn't able to automatically add webhook URLs for that repository. + +To fix this issue: + +- Make sure you haven't manually entered any webhook URLs for the affected repository or project. Although the Bitbucket Web Post Hooks Plugin documentation describes how to manually enter a hook URL, HCP Terraform handles this automatically. Manually entered URLs can interfere with HCP Terraform's operation. + + To check the hook URLs for a repository, go to the repository's settings, then go to the "Hooks" page (in the "Workflow" section) and click on the "Post-Receive WebHooks" link. + + Also note that some Bitbucket Data Center versions might allow you to set per-project or server-wide hook URLs in addition to per-repository hooks. These should all be empty; if you set a hook URL that might affect more than one repo when installing the plugin, go back and delete it. +- Make sure you aren't trying to connect too many workspaces to a single repository. Bitbucket Data Center's webhooks plugin can only attach five hooks to a given repo. You might need to create additional repositories if you need to make more than five workspaces from a single configuration repo. + +## Bitbucket Cloud + +### HCP Terraform fails to obtain repositories + +This typically happens when the HCP Terraform application in Bitbucket Cloud wasn't configured to have the full set of permissions. Go to the OAuth section of the Bitbucket settings, find your HCP Terraform OAuth consumer, click the edit link in the "..." menu, and ensure it has the required permissions enabled: + +| Permission type | Permission level | +| --------------- | ---------------- | +| Account | Write | +| Repositories | Admin | +| Pull requests | Write | +| Webhooks | Read and write | + +## GitHub + +### "Host key verification failed" error in `terraform init` when attempting to ingress Terraform modules via Git over SSH + +This is most common when running Terraform 0.10.3 or 0.10.4, which had a bug in handling SSH submodule ingress. Try upgrading affected HCP Terraform workspaces to the latest Terraform version or 0.10.8 (the latest in the 0.10 series). + +### HCP Terraform can't ingress Git submodules, with auth errors during init + +This usually happens when an SSH key isn't associated with the VCS provider's OAuth client. + +- Go to your organization's "VCS Provider" settings page and check your GitHub client. If it still says "You can add a private SSH key to this connection to be used for git clone operations" (instead of "A private SSH key has been added..."), you need to click the "add a private SSH key" link and add a key. +- Check the settings page for affected workspaces and ensure that "Include submodules on clone" is enabled. + +Note that the "SSH Key" section in a workspace's settings is only used for mid-run operations like cloning Terraform modules. It isn't used when cloning the linked repository before a run. + +## General + +The following errors may occur for all VCS providers except Bitbucket Data Center. + +### HCP Terraform returns 500 after authenticating with the VCS provider + +The Callback URL in the OAuth application configuration in the VCS provider probably wasn't updated in the last step of the instructions and still points to the default "/" path (or an example.com link) instead of the full callback url. + +The fix is to update the callback URL in your VCS provider's application settings. You can look up the real callback URL in HCP Terraform's settings. + +### Can't delete a workspace or module, resulting in 500 errors + +This often happens when the VCS connection has been somehow broken: it might have had permissions revoked, been reconfigured, or had the repository removed. Check for these possibilities and contact HashiCorp support for further assistance, including any information you collected in your support ticket. + +### `redirect_uri_mismatch` error on "Connect" + +The domain name for HCP Terraform's SaaS release changed on 02/22 at 9AM from `atlas.hashicorp.com` to `app.terraform.io`. If the OAuth client was originally configured on the old domain, using it for a new VCS connection can result in this error. + +The fix is to update the OAuth Callback URL in your VCS provider to use app.terraform.io instead of atlas.hashicorp.com. + +### Can't trigger workspace runs from VCS webhook + +A workspace with no runs will not accept new runs from a VCS webhook. You must queue at least one run manually. + +A workspace will not process a webhook if the workspace previously processed a webhook with the same commit SHA and created a run. To trigger a run, create a new commit. If a workspace receives a webhook with a previously processed commit, HCP Terraform adds a new event to the [VCS Events](/terraform/enterprise/vcs#viewing-events) page documenting the received webhook. + +### Changing the URL for a VCS provider + +On rare occasions, you might need HCP Terraform to change the URL it uses to reach your VCS provider. This usually only happens if you move your VCS server or the VCS vendor changes their supported API versions. + +HCP Terraform does not allow you to change the API URL for an existing VCS connection, but you can create a new VCS connection and update existing resources to use it. This is most efficient if you script the necessary updates using HCP Terraform's API. In brief: + +1. [Configure a new VCS connection](/terraform/enterprise/vcs) with the updated URL. +2. Obtain the [oauth-token IDs](/terraform/enterprise/api-docs/oauth-tokens) for the old and new OAuth clients. +3. [List all workspaces](/terraform/enterprise/api-docs/workspaces#list-workspaces) (dealing with pagination if necessary), and use a JSON filtering tool like `jq` to make a list of all workspace IDs whose `attributes.vcs-repo.oauth-token-id` matches the old VCS connection. +4. Iterate over the list of workspaces and [PATCH each one](/terraform/enterprise/api-docs/workspaces#update-a-workspace) to use the new `oauth-token-id`. +5. [List all registry modules](/terraform/registry/api-docs#list-modules) and use their `source` property to determine which ones came from the old VCS connection. +6. [Delete each affected module](/terraform/enterprise/api-docs/private-registry/modules#delete-a-module), then [create a new module](/terraform/enterprise/api-docs/private-registry/modules#publish-a-private-module-from-a-vcs) from the new connection's version of the relevant repo. +7. Delete the old VCS connection. + +### Reauthorizing VCS OAuth Providers + +If a VCS OAuth connection breaks, you can reauthorize an existing VCS provider while retaining any VCS connected resources, like workspaces. We recommend only using this feature to fix broken VCS connections. We also recommend reauthorizing using the same VCS account to avoid permission changes to your repositories. + +~> **Important:** Reauthorizing is not available when the [TFE Provider](https://registry.terraform.io/providers/hashicorp/tfe/latest/docs/resources/oauth_client) is managing the OAuth Client. Instead, you can update the [oauth_token](https://registry.terraform.io/providers/hashicorp/tfe/latest/docs/resources/oauth_client#oauth_token) argument with a new token from your VCS Provider. + +To reauthorize a VCS connection, complete the following steps: + +1. Go to your organization's settings and click **Providers** under **Version Control**. +2. Click **Reauthorize** in the **OAuth Token ID** column. +3. Confirm the reauthorization. HCP Terraform redirects you to your VCS Provider where you can reauthorize the connection. + +## Certificate Errors on Terraform Enterprise + +When debugging failures of VCS connections due to certificate errors, running additional diagnostics using the OpenSSL command may provide more information about the failure. + +First, attach a bash session to the application container: + + docker exec -it ptfe_atlas sh -c "stty rows 50 && stty cols 150 && bash" + +Then run the `openssl s_client` command, using the certificate at `/tmp/cust-ca-certificates.crt` in the container: + + openssl s_client -showcerts -CAfile /tmp/cust-ca-certificates.crt -connect git-server-hostname:443 + +For example, a Gitlab server that uses a self-signed certificate might result in an error like `verify error:num=18:self signed certificate`, as shown in the output below: + + bash-4.3# openssl s_client -showcerts -CAfile /tmp/cust-ca-certificates.crt -connect gitlab.local:443 + CONNECTED(00000003) + depth=0 CN = gitlab.local + verify error:num=18:self signed certificate + verify return:1 + depth=0 CN = gitlab.local + verify return:1 + --- + Certificate chain + 0 s:/CN=gitlab.local + i:/CN=gitlab.local + -----BEGIN CERTIFICATE----- + MIIC/DCCAeSgAwIBAgIJAIhG2GWtcj7lMA0GCSqGSIb3DQEBCwUAMCAxHjAcBgNV + BAMMFWdpdGxhYi1sb2NhbC5oYXNoaS5jbzAeFw0xODA2MDQyMjAwMDhaFw0xOTA2 + MDQyMjAwMDhaMCAxHjAcBgNVBAMMFWdpdGxhYi1sb2NhbC5oYXNoaS5jbzCCASIw + DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMMgrpo3zsoy2BP/AoGIgrYwEMnj + PwSOFGNHbclmiVBCW9jvrZrtva8Qh+twU7CSQdkeSP34ZgLrRp1msmLvUuVMgPts + i7isrI5hug/IHLLOGO5xMvxOcrHknvySYJRmvYFriEBPNRPYJGJ9O1ZUVUYeNwW/ + l9eegBDpJrdsjGmFKCOzZEdUA3zu7PfNgf788uIi4UkVXZNa/OFHsZi63OYyfOc2 + Zm0/vRKOn17dewOOesHhw77yYbBH8OFsEiC10JCe5y3MD9yrhV1h9Z4niK8rHPXz + XEh3JfV+BBArodmDbvi4UtT+IGdDueUllXv7kbwqvQ67OFmmek0GZOY7ZvMCAwEA + AaM5MDcwIAYDVR0RBBkwF4IVZ2l0bGFiLWxvY2FsLmhhc2hpLmNvMBMGA1UdJQQM + MAoGCCsGAQUFBwMBMA0GCSqGSIb3DQEBCwUAA4IBAQCfkukNV/9vCA/8qoEbPt1M + mvf2FHyUD69p/Gq/04IhGty3sno4eVcwWEc5EvfNt8vv1FykFQ6zMJuWA0jL9x2s + LbC8yuRDnsAlukSBvyazCZ9pt3qseGOLskaVCeOqG3b+hJqikZihFUD95IvWNFQs + RpvGvnA/AH2Lqqeyk2ITtLYj1AcSB1hBSnG/0fdtao9zs0JQsrS59CD1lbbTPPRN + orbKtVTWF2JlJxl2watfCNTw6nTCPI+51CYd687T3MuRN7LsTgglzP4xazuNjbWB + QGAiQRd6aKj+xAJnqjzXt9wl6a493m8aNkyWrxZGHfIA1W70RtMqIC/554flZ4ia + -----END CERTIFICATE----- + --- + Server certificate + subject=/CN=gitlab.local + issuer=/CN=gitlab.local + --- + No client certificate CA names sent + Peer signing digest: SHA512 + Server Temp Key: ECDH, P-256, 256 bits + --- + SSL handshake has read 1443 bytes and written 433 bytes + --- + New, TLSv1/SSLv3, Cipher is ECDHE-RSA-AES256-GCM-SHA384 + Server public key is 2048 bit + Secure Renegotiation IS supported + Compression: NONE + Expansion: NONE + No ALPN negotiated + SSL-Session: + Protocol : TLSv1.2 + Cipher : ECDHE-RSA-AES256-GCM-SHA384 + Session-ID: AF5286FB7C7725D377B4A5F556DEB6DDC38B302153DDAE90C552ACB5DC4D86B8 + Session-ID-ctx: + Master-Key: DB75AEC12C6E7B62246C653C8CB8FC3B90DE86886D68CB09898A6A6F5D539007F7760BC25EC4563A893D34ABCFAAC28A + Key-Arg : None + PSK identity: None + PSK identity hint: None + SRP username: None + TLS session ticket lifetime hint: 300 (seconds) + TLS session ticket: + 0000 - 03 c1 35 c4 ff 6d 24 a8-6c 70 61 fb 2c dc 2e b8 ..5..m$.lpa.,... + 0010 - de 4c 6d b0 2c 13 8e b6-63 95 18 ee 4d 33 a6 dc .Lm.,...c...M3.. + 0020 - 0d 64 24 f0 8d 3f 9c aa-b8 a4 e2 4f d3 c3 4d 88 .d$..?.....O..M. + 0030 - 58 99 10 73 83 93 70 4a-2c 61 e7 2d 41 74 d3 e9 X..s..pJ,a.-At.. + 0040 - 83 8c 4a 7f ae 7b e8 56-5c 51 fc 6f fe e3 a0 ec ..J..{.V\Q.o.... + 0050 - 3c 2b 6b 13 fc a0 e5 15-a8 31 16 19 11 98 56 43 <+k......1....VC + 0060 - 16 86 c4 cd 53 e6 c3 61-e2 6c 1b 99 86 f5 a8 bd ....S..a.l...... + 0070 - 3c 49 c0 0a ce 81 a9 33-9b 95 2c e1 f4 6d 05 1e - + Best practices to structure your configuration and Terraform Enterprise + workspaces +source: terraform-docs-common +--- + +# Workspace Best Practices + +An HCP Terraform workspace manages a single state file and the lifecycle of its resources. It is the smallest collection of HCP Terraform-managed infrastructure. Any operation on a resource can potentially affect other resources managed in the same state file, so it is best to keep the potential blast radius of your operations small. To do so, manage resources in separate workspaces when possible, grouping together only necessary and logically-related resources. For example, even though your application may require both compute resources and a database, these resources can operate independently and should be in their own workspaces. +Scoping your configuration and planning your workspace strategy early in your adoption of HCP Terraform and Terraform Enterprise will simplify your operations and make them safer. + +## Name your Workspace + +We recommend using the following naming convention so you can identify and associate workspaces with specific components of your infrastructure: + +`---` + +- ``: The business unit or team that owns the workspace. +- ``: The name of the application or service that the workspace manages. +- ``: The layer of the infrastructure that the workspace manages (or example, network, compute, filestore). +- ``: The environment that the workspace manages (for example, prod, staging, QA, prod). + +If your application team does not have a `layer`, use `main` or `app` in its place to maintain consistency across the organization. + +## Group by volatility + +Volatility refers to the rate of change of the resources in a workspace. Infrastructure such as databases, VPCs, and subnets change much less frequently than infrastructure such as your web servers. By exposing your long-living infrastructure to unnecessary volatility, you introduce more opportunities for accidental changes. When planning your workspace organization, group resources by volatility. + +![An example of how workspaces can be split among Production, Staging, QA, and Dev. In this example, networking and security are grouped in one workspace, with compute, filestore, and SQL all having their own workspace. This is duplicated in each environment](/img/docs/workspace-net-infra-split.png) + +The above example groups together tightly-coupled resources like networking, security, and identity in a shared workspace. Compute, storage, and databases have separate workspaces, since they change at different frequencies. You may scale compute instances multiple times a day, but your database instances probably change far less frequently. By grouping these parts of your infrastructure into separate workspaces, you decouple unrelated resources and reduce the risk of unexpected changes. + +## Determine stateful vs stateless infrastructure + +Stateful resources are ones that you cannot delete and recreate because they persist data, such as databases and object storage. By managing stateful resources independently of stateless ones, such as separating databases from compute instances, you limit the blast radius of operations that cause the resource recreation and help protect against accidental data loss. + +Consider the workspace structure in the [Volatility section](#group-by-volatility). You could potentially manage filestore and database resources together, as they are both stateful resources. Your compute resources are stateless and should still have a separate workspace. + +## Separate privileges and responsibilities + +A best practice is to split up workspaces based on team responsibilities and required privileges. For example, consider an application that requires separate developer and production environments, each with special networking and application infrastructure. One approach is to create four different workspaces, two for the developer environment and two for production. Only the networking team has access to the networking workspaces. + +In this setup, only the networking team needs permissions to manage the resources in the networking workspaces, and others cannot manage those workspace resources. If a workspace's scope is too large, a user might need more permissions than appropriate in order to perform operations the workspace. + +![An example of how workspaces can be split among Production, Staging, QA, and Dev. In this example, networking and security are grouped in one workspace, with compute, filestore, and SQL all having their own workspace. This is duplicated in each environment](/img/docs/workspace-net-infra-combined.png) + +Splitting your workspaces by team also helps limit the responsibility per workspace and allows teams to maintain distinct areas of ownership. If you need to reference attributes of resources managed in other workspaces, you can share the outputs using the [tfe_outputs](https://registry.terraform.io/providers/hashicorp/tfe/latest/docs/data-sources/outputs) data source. By limiting the scope of each workspace and sharing just the required outputs with others, you reduce the risk of leaking potentially sensitive information in a workspace's state. To share outputs from a workspace, you must explicitly enable remote state sharing in the workspace settings. + +## Avoid large Terraform plans and applies + +HCP Terraform and Terraform Enterprise execute workloads using agents. Every time an agent refreshes a workspace's state, it builds a [dependency graph](/terraform/internals/graph) of the resources to determine how to sequence operations in the workspace. As the number of resources your workspace manages grows, these graphs become larger and more complex. As these graphs grow, they require more worker RAM to build them. If your agent's performance degrades or workloads take longer to complete, we suggest exploring ways to split up the workspace to reduce the size of the dependency graph. + +## Determine workspace concurrency vs Terraform parallelism + +Concurrency refers to the number of plan and apply operations that HCP Terraform or Terraform Enterprise can run simultaneously. + +In HCP Terraform, your edition limits the maximum concurrency for your organization. Refer to [HCP Terraform pricing](https://www.hashicorp.com/products/terraform/pricing?product_intent=terraform) for details. + +Terraform Enterprise lets you configure the concurrency, but defaults to 10 concurrent runs. As you increase concurrency, the amount of memory your Terraform Enterprise installation requires increases as well. Refer to the [Capacity and performance](/terraform/enterprise/replicated/architecture/system-overview/capacity) documentation for more information. + +Parallelism refers to the number of tasks the Terraform CLI performs simultaneously in a single workload. By default, Terraform performs a maximum of 10 operations in parallel. When running a `terraform apply` command, Terraform refreshes each resource in the state file and compares to the remote object. Every resource refresh, creation, update, or destruction is an individual operation. If your workload creates 11 resources, Terraform starts by creating the first 10 resources in its dependency graph, and will begin creating the 11th once it finishes creating one of the first 10 resources. + +You can [increase the parallelism](/terraform/enterprise/workspaces/variables#parallelism) of Terraform, but this increases a run's CPU usage. We recommend that you instead break down large Terraform configurations into smaller ones with fewer resources when possible. Long-running Terraform workloads are an early sign of a bloated workspace scope. + +## Next steps + +This article introduces some considerations to keep in mind as your organization matures their workspace usage. Being deliberate about how you use these to organize your infrastructure will ensure smoother and safer operations. [HCP Terraform](/terraform/tutorials/cloud-get-started) provides a place to try these concepts hands-on, and you can [get started for free](https://app.terraform.io/public/signup/account). + +To learn more about HCP Terraform and Terraform Enterprise best practices, refer to [Project Best Practices](/terraform/enterprise/projects/best-practices). To learn best practices for writing Terraform configuration, refer to the [Terraform Style Guide](/terraform/language/style). diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/workspaces/browse.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/workspaces/browse.mdx new file mode 100644 index 0000000000..3b7ef87d01 --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/workspaces/browse.mdx @@ -0,0 +1,72 @@ +--- +page_title: Browse workspaces +description: >- + Learn how to use the sorting and filtering interfaces in Terraform Enterprise + to create different views of the resource data that the app manages so that + you can track consumption across your organizations. +source: terraform-docs-common +--- + +# Browse workspaces + +This topic describes how to use browse, sort, and filter workspaces in the UI so that you can track consumption across your organizations. + +## Overview + +HCP Terraform and Terraform Enterprise include several interfaces for browsing, sorting, and filtering resource data so that you can effectively manage workspaces and projects. You can also use interfaces together, such as applying a tag filter and sorting by workspace name, to refine results. + + + +### Explorer view + +The explorer for workspace visibility surfaces a wider range of valuable information from across your workspaces. Refer to [Explorer for workspace visibility](/terraform/enterprise/workspaces/explorer) for additional information. + + + +## Requirements + +You must be a member of a team with the **Read** permissions enabled for Terraform runs to view workspaces associated with the run. Refer to the [permissions reference](/terraform/enterprise/users-teams-organizations/permissions) for additional information. + +If your organization contains many workspaces, you can use the filter tools at the top of the list to find the workspaces you are interested in. + +## Find a workspace + +1. Sign in to [HCP Terraform](https://app.terraform.io/) or Terraform Enterprise and select your organization. +2. Click **Workspaces** to view workspaces you have access to. +3. To view projects you have access to, click on either **Projects** in the sidebar menu or the drawer icon in the **Workspaces** bar. +4. If your organization contains several workspaces or projects, you can paginate through the workspace screen or project drawer to find the workspace you are looking for. +5. You can also use the search bar in the **Workspace** drawer to find a project by name + +## Filter workspaces + +You can use the following interfaces to sort and filter workspaces: + +- Click on a run status button to filter workspaces by one of the most common run statuses. You can filter by one of the following statuses: + + - Needs attention + - Errored + - Running + - On hold + - Applied + +- Choose one or more tag keys, values, or key-value pairs from the **Tags** drop-down to filter workspaces by tag. + +- Choose one or more run statuses from the **Status** drop-down to filter workspaces by run status. The **Status** drop-down lists all available run statuses, including the common statuses available in the run status button bar. + +- The tag filter shows a list of tags added to all workspaces, limited to the first 1,000 tags alphabetically. Choosing one or more will show only workspaces tagged with all of the chosen tags. + +- Choose a health assessment label form the **Health** drop-down to filter workspaces according to the latest health assessment results. You can filter according to the following labels: + + - Drifted + - Health error + - Check failed + +## Sort workspaces + +Click on a column header to sort workspaces by trait. Traits appear in either ascending or descending alphabetical order. You can sort according to the following traits: + +- Workspace name +- Run status +- Repository +- Latest change +- Tag diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/workspaces/configurations.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/workspaces/configurations.mdx new file mode 100644 index 0000000000..f56346f09b --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/workspaces/configurations.mdx @@ -0,0 +1,73 @@ +--- +page_title: Manage Terraform configurations +description: >- + Workspaces organize infrastructure and state. Learn how to provide + configuration versions for a workspace and organize multiple environments. +source: terraform-docs-common +--- + +# Manage Terraform configurations + +[remote operations]: /terraform/enterprise/run/remote-operations + +[execution mode]: /terraform/enterprise/workspaces/settings#execution-mode + +[Terraform configuration]: /terraform/language + +Each HCP Terraform workspace is associated with a particular [Terraform configuration][], which is expected to change and evolve over time. + +Since every organization has its own preferred source code control practices, HCP Terraform does not provide integrated version management. Instead, it expects Terraform configurations to be managed in your existing version control system (VCS). + +In order to perform [remote Terraform runs][remote operations] for a given workspace, HCP Terraform needs to periodically receive new versions of its configuration. Usually, this can be handled automatically by connecting a workspace to a VCS repository. + +-> **Note:** If a workspace's [execution mode is set to local][execution mode], it doesn't require configuration versions, since HCP Terraform won't perform runs for that workspace. + +## Providing Configuration Versions + +There are two ways to provide configuration versions for a workspace: + +- **With a connected VCS repository.** HCP Terraform can automatically fetch content from supported VCS providers, and uses webhooks to get notified of code changes. This is the most convenient way to use HCP Terraform. See [The UI- and VCS-driven Run Workflow](/terraform/enterprise/run/ui) for more information. + + A VCS connection can be configured [when a workspace is created](/terraform/enterprise/workspaces/create), or later in its [version control settings](/terraform/enterprise/workspaces/settings/vcs). + + -> **Note:** When a workspace is connected to a VCS repository, directly uploaded configuration versions can only be used for [speculative plans](/terraform/enterprise/run/remote-operations#speculative-plans). This helps ensure your VCS remains the source of truth for all real infrastructure changes. + +- **With direct uploads.** You can use a variety of tools to directly upload configuration content to HCP Terraform: + + - **Terraform CLI:** With the [CLI integration](/terraform/cli/cloud) configured, the `terraform plan` and `terraform apply` commands will perform remote runs by uploading a configuration from a local working directory. See [The CLI-driven Run Workflow](/terraform/enterprise/run/cli) for more information. + - **API:** HCP Terraform's API can accept configurations as `.tar.gz` files, which can be uploaded by a CI system or other workflow tools. See [The API-driven Run Workflow](/terraform/enterprise/run/api) for more information. + + When configuration versions are provided via the CLI or API, HCP Terraform can't automatically react to code changes in the underlying VCS repository. + +## Code Organization and Repository Structure + +### Organizing Separate Configurations + +Most organizations either keep each Terraform configuration in a separate repository, or keep many Terraform configurations as separate directories in a single repository (often called a "monorepo"). + +HCP Terraform works well with either approach, but monorepos require some extra configuration: + +- Each workspace must [specify a Terraform working directory](/terraform/enterprise/workspaces/settings#terraform-working-directory), so HCP Terraform knows which configuration to use. +- If the repository includes any shared Terraform modules, you must add those directories to the [automatic run triggering setting](/terraform/enterprise/workspaces/settings/vcs#automatic-run-triggering) for any workspace that uses those modules. + +-> **Note:** If your organization does not have a strong preference, we recommend using separate repositories for each configuration and using the private module registry to share modules. This allows for faster module development, since you don't have to update every configuration that consumes a module at the same time as the module itself. + +### Organizing Multiple Environments for a Configuration + +There are also a variety of ways to handle multiple environments. The most common approaches are: + +- All environments use the same main branch, and environment differences are handled with Terraform variables. To protect production environments, wait to apply runs until their changes are verified in staging. +- Different environments use different long-lived VCS branches. To protect production environments, merge changes to the production branch after they have been verified in staging. +- Different environments use completely separate configurations, and shared behaviors are handled with shared Terraform modules. To protect production environments, verify new module versions in staging before updating the version used in production. + +HCP Terraform works well with all of these approaches. If you used long-lived branches, be sure to specify which branch to use in each workspace's VCS connection settings. + +## Archiving Configuration Versions + +Once all runs using a particular configuration version are complete, HCP Terraform no longer needs the associated `.tar.gz` file and may discard it to save storage space. This process is handled differently depending on how the configuration version was created. + +- **Created with a connected VCS repository.** HCP Terraform will automatically archive VCS configuration versions once all runs are completed and they are no longer current for any workspace. HCP Terraform will re-fetch the configuration files from VCS as needed for new runs. + +- **Created with direct uploads via the API or CLI.** HCP Terraform does not archive CLI and API configuration versions automatically, because it cannot re-fetch the files for new runs. However, you can use the [Archive a Configuration Version](/terraform/enterprise/api-docs/configuration-versions#archive-a-configuration-version) endpoint to archive them manually. + +For Terraform Enterprise customers upgrading from a previous version, the functionality has a backfill capability that will clean up space for historical runs in batches. In each organization, Terraform Enterprise archives a batch of 100 configurations each time a run completes or a new configuration version is uploaded. This will gradually free up existing object storage space over time. diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/workspaces/create.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/workspaces/create.mdx new file mode 100644 index 0000000000..e52c6468b2 --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/workspaces/create.mdx @@ -0,0 +1,110 @@ +--- +page_title: Create workspaces in Terraform Enterprise +description: >- + Workspaces organize infrastructure and state into groups. Learn how to create + and configure Terraform Enterprise workspaces through the UI. +source: terraform-docs-common +--- + +# Create workspaces + +This topic describes how to create and manage workspaces in HCP Terraform and Terraform Enterprise UI. A workspace is a group of infrastructure resources managed by Terraform. Refer to [Workspaces overview](/terraform/enterprise/workspaces) for additional information. + +> **Hands-on:** Try the [Get Started - HCP Terraform](/terraform/tutorials/cloud-get-started?utm_source=WEBSITE&utm_medium=WEB_IO&utm_offer=ARTICLE_PAGE&utm_content=DOCS) tutorials. + +## Introduction + +Create new workspaces when you need to manage a new collection of infrastructure resources. You can use the following methods to create workspaces: + +- HCP Terraform UI: Refer to [Create a workspace](#create-a-workspace) for instructions. +- Workspaces API: Send a `POST`call to the `/organizations/:organization_name/workspaces` endpoint to create a workspace. Refer to the [API documentation](/terraform/enterprise/api-docs/workspaces#create-a-workspace) for instructions. +- Terraform Enterprise provider: Install the `tfe` provider and add the `tfe_workspace` resource to your configuration. Refer to the [`tfe` provider documentation](https://registry.terraform.io/providers/hashicorp/tfe/latest/docs/resources/workspace) in the Terraform registry for instructions. +- No-code provisioning: Use a no-code module from the registry to create a new workspace and deploy the module's resources. Refer to [Provisioning No-Code Infrastructure](/terraform/enterprise/no-code-provisioning/provisioning) for instructions. + +Each workspace belongs to a project. Refer to [Manage projects](/terraform/enterprise/projects/manage) for additional information. + +## Requirements + +You must be a member of a team with one of the following permissions enabled to create and manage workspaces: + +- **Manage all projects** +- **Manage all workspaces** +- **Admin** permission group for a project. + +[permissions-citation]: #intentionally-unused---keep-for-maintainers + +## Workspace naming + +We recommend using consistent and informative names for new workspaces. One common approach is combining the workspace's important attributes in a consistent order. Attributes can be any defining characteristic of a workspace, such as the component, the component’s run environment, and the region where the workspace is provisioning infrastructure. + +This strategy could produce the following example workspace names: + +- networking-prod-us-east +- networking-staging-us-east +- networking-prod-eu-central +- networking-staging-eu-central +- monitoring-prod-us-east +- monitoring-staging-us-east +- monitoring-prod-eu-central +- monitoring-staging-eu-central + +You can add additional attributes to your workspace names as needed. For example, you may add the infrastructure provider, datacenter, or line of business. + +We recommend using 90 characters or less for the name of your workspace. + +## Create a workspace + +[workdir]: /terraform/enterprise/workspaces/settings#terraform-working-directory + +[trigger]: /terraform/enterprise/workspaces/settings/vcs#automatic-run-triggering + +[branch]: /terraform/enterprise/workspaces/settings/vcs#vcs-branch + +[submodules]: /terraform/enterprise/workspaces/settings/vcs#include-submodules-on-clone + +Complete the following steps to use the HCP Terraform or Terraform Enterprise UI to create a workspace: + +1. Sign in to [HCP Terraform](https://app.terraform.io/) or Terraform Enterprise and choose your organization. +2. Click **New** and choose **Workspace** from the drop-down menu. +3. If you have multiple projects, HCP Terraform may prompt you to choose the project to create the workspace in. Only users on teams with permissions for the entire project or the specific workspace can access the workspace. Refer to [Manage projects](/terraform/enterprise/projects/manage) for additional information. +4. Choose a workflow type. +5. Complete the following steps if you are creating a workspace that follows the VCS workflow: + 1. Choose an existing version control provider from the list or configure a new system. You must enable the workspace project to connect to your provider. Refer to [Connecting VCS + Providers](/terraform/enterprise/vcs) for more details. + 2. If you choose the **GitHub App** provider, choose an organization and repository when prompted. The list only displays the first 100 repositories from your VCS provider. If your repository is missing from the list, enter the repository ID in the text field . + 3. Refer to the following topics for information about configuring workspaces settings in the **Advanced options** screen: + - [Terraform Working Directory][workdir] + - [Automatic Run Triggering][trigger] + - [VCS branch][branch] + - [Include submodules on clone][submodules] +6. Specify a name for the workspace. VCS workflow workspaces default to the name of the repository. The name must be unique within the organization and can include letters, numbers, hyphens, and underscores. Refer to [Workspace naming](#workspace-naming) for additional information. +7. Add an optional description for the workspace. The description appears at the top of the workspace in the HCP Terraform UI. +8. Click **Create workspace** to finish. + +For CLI or API-driven workflow, the system opens the new workspace overview. For version control workspaces, the **Configure Terraform variables** page appears. + +### Configure Terraform variables for VCS workflows + +After you create a new workspace from a version control repository, HCP Terraform scans its configuration files for [Terraform variables](/terraform/enterprise/workspaces/variables#terraform-variables) and displays variables without default values or variables that are undefined in an existing [global or project-scoped variable set](/terraform/enterprise/workspaces/variables/managing-variables#variable-sets). Terraform cannot perform successful runs in the workspace until you set values for these variables. + +Choose one of the following actions: + +- To skip this step, click **Go to workspace overview**. You can [load these variables from files](/terraform/enterprise/workspaces/variables/managing-variables#loading-variables-from-files) or create and set values for them later from within the workspace. HCP Terraform does not automatically scan your configuration again; you can only add variables from within the workspace individually. +- To configure variables, enter a value for each variable on the page. You may want to leave a variable empty if you plan to provide it through another source, like an `auto.tfvars` file. Click **Save variables** to add these variables to the workspace. + +## Next steps + +If you have already configured all Terraform variables, we recommend [manually starting a run](/terraform/enterprise/run/ui#manually-starting-runs) to prepare VCS-driven workspaces. You may also want to do one or more of the following actions: + +- [Upload configuration versions](/terraform/enterprise/workspaces/configurations#providing-configuration-versions): If you chose the API or CLI-Driven workflow, you must upload configuration versions for the workspace. +- [Edit environment variables](/terraform/enterprise/workspaces/variables): Shell environment variables store credentials and customize Terraform's behavior. +- [Edit additional workspace settings](/terraform/enterprise/workspaces/settings): This includes notifications, permissions, and run triggers to start runs automatically. +- [Learn more about running Terraform in your workspace](/terraform/enterprise/run/remote-operations): This includes how Terraform processes runs within the workspace, run modes, run states, and other operations. +- [Create workspace tags](/terraform/enterprise/workspaces/tags): Add tags to your workspaces so that you can organize and track them. +- [Browse workspaces](/terraform/enterprise/workspaces/browse): Use the interfaces available in the UI to browse, sort, and filter workspaces so that you can track resource consumption. + +### VCS Connection + +If you connected a VCS repository to the workspace, HCP Terraform automatically registers a webhook with your VCS provider. A workspace with no runs will not accept new runs from a VCS webhook, so you must [manually start at least one run](/terraform/enterprise/run/ui#manually-starting-runs). + +After you manually start a run, HCP Terraform automatically queues a plan when new commits appear in the selected branch of the linked repository or someone opens a pull request on that branch. Refer to [Webhooks](/terraform/enterprise/vcs#webhooks) for more details. diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/workspaces/dynamic-provider-credentials/aws-configuration.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/workspaces/dynamic-provider-credentials/aws-configuration.mdx new file mode 100644 index 0000000000..3d84e52e56 --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/workspaces/dynamic-provider-credentials/aws-configuration.mdx @@ -0,0 +1,165 @@ +--- +page_title: Use dynamic credentials with the AWS provider in Terraform Enterprise +description: >- + Use OpenID Connect to get short-term credentials for the AWS Terraform + provider in your Terraform Enterprise runs. +source: terraform-docs-common +--- + +# Use dynamic credentials with the AWS provider + +~> **Important:** If you are self-hosting [HCP Terraform agents](/terraform/cloud-docs/agents), ensure your agents use [v1.7.0](/terraform/cloud-docs/agents/changelog#1-7-0-03-02-2023) or above. To use the latest dynamic credentials features, [upgrade your agents to the latest version](/terraform/cloud-docs/agents/changelog). + +You can use HCP Terraform’s native OpenID Connect integration with AWS to get [dynamic credentials](/terraform/enterprise/workspaces/dynamic-provider-credentials) for the AWS provider in your HCP Terraform runs. Configuring the integration requires the following steps: + +1. **[Configure AWS](#configure-aws):** Set up a trust configuration between AWS and HCP Terraform. Then, you must create AWS roles and policies for your HCP Terraform workspaces. +2. **[Configure HCP Terraform](#configure-hcp-terraform):** Add environment variables to the HCP Terraform workspaces where you want to use Dynamic Credentials. + +Once you complete the setup, HCP Terraform automatically authenticates to AWS during each run. The AWS provider authentication is valid for the length of the plan or apply. + +## Configure AWS + +You must enable and configure an OIDC identity provider and accompanying role and trust policy on AWS. These instructions use the AWS console, but you can also use Terraform to configure AWS. Refer to our [example Terraform configuration](https://github.com/hashicorp/terraform-dynamic-credentials-setup-examples/tree/main/aws). + +### Create an OIDC Identity Provider + +AWS documentation for setting this up through the AWS console or API can be found here: [Creating OpenID Connect (OIDC) identity providers - AWS Identity and Access Management](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_providers_create_oidc.html). + +The `provider URL` should be set to the address of HCP Terraform (e.g., **without** a trailing slash), and the `audience` should be set to `aws.workload.identity` or the value of `TFC_AWS_WORKLOAD_IDENTITY_AUDIENCE`, if configured. + +### Configure a Role and Trust Policy + +You must configure a role and corresponding trust policy. Amazon documentation on setting this up can be found here: [Creating a role for web identity or OpenID Connect Federation (console) - AWS Identity and Access Management](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_create_for-idp_oidc.html). +The trust policy will be of the form: + +```json +{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": { + "Federated": "OIDC_PROVIDER_ARN" + }, + "Action": "sts:AssumeRoleWithWebIdentity", + "Condition": { + "StringEquals": { + "SITE_ADDRESS:aud": "AUDIENCE_VALUE", + "SITE_ADDRESS:sub": "organization:ORG_NAME:project:PROJECT_NAME:workspace:WORKSPACE_NAME:run_phase:RUN_PHASE" + } + } + } + ] +} +``` + +with the capitalized values replaced with the following: + +- **OIDC_PROVIDER_ARN**: The ARN from the OIDC provider resource created in the previous step +- **SITE_ADDRESS**: The address of HCP Terraform with `https://` stripped, (e.g., `app.terraform.io`) +- **AUDIENCE_VALUE**: This should be set to `aws.workload.identity` unless a non-default audience has been specified in TFC +- **ORG_NAME**: The organization name this policy will apply to, such as `my-org-name` +- **PROJECT_NAME**: The project name that this policy will apply to, such as `my-project-name` +- **WORKSPACE_NAME**: The workspace name this policy will apply to, such as `my-workspace-name` +- **RUN_PHASE**: The run phase this policy will apply to, currently one of `plan` or `apply`. + +-> **Note:** if different permissions are desired for plan and apply, then two separate roles and trust policies must be created for each of these run phases to properly match them to the correct access level. +If the same permissions will be used regardless of run phase, then the condition can be modified like the below to use `StringLike` instead of `StringEquals` for the sub and include a `*` after `run_phase:` to perform a wildcard match: + +```json +{ + "Condition": { + "StringEquals": { + "SITE_ADDRESS:aud": "AUDIENCE_VALUE" + }, + "StringLike": { + "SITE_ADDRESS:sub": "organization:ORG_NAME:project:PROJECT_NAME:workspace:WORKSPACE_NAME:run_phase:*" + } + } +} +``` + +!> **Warning**: you should always check, at minimum, the audience and the name of the organization in order to prevent unauthorized access from other HCP Terraform organizations! + +A permissions policy needs to be added to the role which defines what operations within AWS the role is allowed to perform. As an example, the below policy allows for fetching a list of S3 buckets: + +```json +{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "s3:ListBucket" + ], + "Resource": "*" + } + ] +} +``` + +## Configure HCP Terraform + +You’ll need to set some environment variables in your HCP Terraform workspace in order to configure HCP Terraform to authenticate with AWS using dynamic credentials. You can set these as workspace variables, or if you’d like to share one AWS role across multiple workspaces, you can use a variable set. When you configure dynamic provider credentials with multiple provider configurations of the same type, use either a default variable or a tagged alias variable name for each provider configuration. Refer to [Specifying Multiple Configurations](#specifying-multiple-configurations) for more details. + +### Required Environment Variables + +| Variable | Value | Notes | +| -------------------------------------------------------------------------------------------------- | ------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `TFC_AWS_PROVIDER_AUTH`
`TFC_AWS_PROVIDER_AUTH[_TAG]`
_(Default variable not supported)_ | `true` | Requires **v1.7.0** or later if self-managing agents. Must be present and set to `true`, or HCP Terraform will not attempt to authenticate to AWS. | +| `TFC_AWS_RUN_ROLE_ARN`
`TFC_AWS_RUN_ROLE_ARN[_TAG]`
`TFC_DEFAULT_AWS_RUN_ROLE_ARN` | The ARN of the role to assume in AWS. | Requires **v1.7.0** or later if self-managing agents. Optional if `TFC_AWS_PLAN_ROLE_ARN` and `TFC_AWS_APPLY_ROLE_ARN` are both provided. These variables are described [below](/terraform/enterprise/workspaces/dynamic-provider-credentials/aws-configuration#optional-environment-variables) | + +### Optional Environment Variables + +You may need to set these variables, depending on your use case. + +| Variable | Value | Notes | +| -------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- | +| `TFC_AWS_WORKLOAD_IDENTITY_AUDIENCE`
`TFC_AWS_WORKLOAD_IDENTITY_AUDIENCE[_TAG]`
`TFC_DEFAULT_AWS_WORKLOAD_IDENTITY_AUDIENCE` | Will be used as the `aud` claim for the identity token. Defaults to `aws.workload.identity`. | Requires **v1.7.0** or later if self-managing agents. | +| `TFC_AWS_PLAN_ROLE_ARN`
`TFC_AWS_PLAN_ROLE_ARN[_TAG]`
`TFC_DEFAULT_AWS_PLAN_ROLE_ARN` | The ARN of the role to use for the plan phase of a run. | Requires **v1.7.0** or later if self-managing agents. Will fall back to the value of `TFC_AWS_RUN_ROLE_ARN` if not provided. | +| `TFC_AWS_APPLY_ROLE_ARN`
`TFC_AWS_APPLY_ROLE_ARN[_TAG]`
`TFC_DEFAULT_AWS_APPLY_ROLE_ARN` | The ARN of the role to use for the apply phase of a run. | Requires **v1.7.0** or later if self-managing agents. Will fall back to the value of `TFC_AWS_RUN_ROLE_ARN` if not provided. | + +## Configure the AWS Provider + +Make sure that you’re passing a value for the `region` argument into the provider configuration block or setting the `AWS_REGION` variable in your workspace. + +Make sure that you’re not using any of the other arguments or methods mentioned in the [authentication and configuration](https://registry.terraform.io/providers/hashicorp/aws/latest/docs#authentication-and-configuration) section of the provider documentation as these settings may interfere with dynamic provider credentials. + +### Specifying Multiple Configurations + +~> **Important:** If you are self-hosting [HCP Terraform agents](/terraform/cloud-docs/agents), ensure your agents use [v1.12.0](/terraform/cloud-docs/agents/changelog#1-12-0-07-26-2023) or above. To use the latest dynamic credentials features, [upgrade your agents to the latest version](/terraform/cloud-docs/agents/changelog). + +You can add additional variables to handle multiple distinct AWS setups, enabling you to use multiple [provider aliases](/terraform/language/providers/configuration#alias-multiple-provider-configurations) within the same workspace. You can configure each set of credentials independently, or use default values by configuring the variables prefixed with `TFC_DEFAULT_`. + +For more details, see [Specifying Multiple Configurations](/terraform/enterprise/workspaces/dynamic-provider-credentials/specifying-multiple-configurations). + +#### Required Terraform Variable + +To use additional configurations, add the following code to your Terraform configuration. This lets HCP Terraform supply variable values that you can then use to map authentication and configuration details to the correct provider blocks. + +```hcl +variable "tfc_aws_dynamic_credentials" { + description = "Object containing AWS dynamic credentials configuration" + type = object({ + default = object({ + shared_config_file = string + }) + aliases = map(object({ + shared_config_file = string + })) + }) +} +``` + +#### Example Usage + +```hcl +provider "aws" { + shared_config_files = [var.tfc_aws_dynamic_credentials.default.shared_config_file] +} + +provider "aws" { + alias = "ALIAS1" + shared_config_files = [var.tfc_aws_dynamic_credentials.aliases["ALIAS1"].shared_config_file] +} +``` diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/workspaces/dynamic-provider-credentials/azure-configuration.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/workspaces/dynamic-provider-credentials/azure-configuration.mdx new file mode 100644 index 0000000000..502cd60c8d --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/workspaces/dynamic-provider-credentials/azure-configuration.mdx @@ -0,0 +1,177 @@ +--- +page_title: Use dynamic credentials with the Azure provider in Terraform Enterprise +description: >- + Use OpenID Connect to get short-term credentials for the Azure Terraform + providers in your Terraform Enterprise runs. +source: terraform-docs-common +--- + +# Use dynamic credentials with the Azure provider + +~> **Important:** Ensure you are using version **3.25.0** or later of the **AzureRM provider** and version **2.29.0** or later of the **Microsoft Entra ID provider** (previously Azure Active Directory) as required OIDC functionality was introduced in these provider versions. + +~> **Important:** If you are self-hosting [HCP Terraform agents](/terraform/cloud-docs/agents), ensure your agents use [v1.7.0](/terraform/cloud-docs/agents/changelog#1-7-0-03-02-2023) or above. To use the latest dynamic credentials features, [upgrade your agents to the latest version](/terraform/cloud-docs/agents/changelog). + +You can use HCP Terraform’s native OpenID Connect integration with Azure to get [dynamic credentials](/terraform/enterprise/workspaces/dynamic-provider-credentials) for the AzureRM or Microsoft Entra ID providers in your HCP Terraform runs. Configuring the integration requires the following steps: + +1. **[Configure Azure](#configure-azure):** Set up a trust configuration between Azure and HCP Terraform. Then, you must create Azure roles and policies for your HCP Terraform workspaces. +2. **[Configure HCP Terraform](#configure-hcp-terraform):** Add environment variables to the HCP Terraform workspaces where you want to use Dynamic Credentials. + +Once you complete the setup, HCP Terraform automatically authenticates to Azure during each run. The Azure provider authentication is valid for the length of the plan or apply. + +!> **Warning:** Dynamic credentials with the Azure providers do not work when your Terraform Enterprise instance uses a custom or self-signed certificate. This limitation is due to restrictions in Azure. + +## Configure Azure + +You must enable and configure an application and service principal with accompanying federated credentials and permissions on Azure. These instructions use the Azure portal, but you can also use Terraform to configure Azure. Refer to our [example Terraform configuration](https://github.com/hashicorp/terraform-dynamic-credentials-setup-examples/tree/main/azure). + +### Create an Application and Service Principal + +Follow the steps mentioned in the AzureRM provider docs here: [Creating the Application and Service Principal](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/guides/service_principal_oidc#creating-the-application-and-service-principal). + +As mentioned in the documentation it will be important to make note of the `client_id` for the application as you will use this later for authentication. + +-> **Note:** you will want to skip the `“Configure Microsoft Entra ID Application to Trust a GitHub Repository”` section as this does not apply here. + +### Grant the Application Access to Manage Resources in Your Azure Subscription + +You must now give the created Application permission to modify resources within your Subscription. + +Follow the steps mentioned in the AzureRM provider docs here: [Granting the Application access to manage resources in your Azure Subscription](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/guides/service_principal_oidc#granting-the-application-access-to-manage-resources-in-your-azure-subscription). + +### Configure Microsoft Entra ID Application to Trust a Generic Issuer + +Finally, you must create federated identity credentials which validate the contents of the token sent to Azure from HCP Terraform. + +Follow the steps mentioned in the AzureRM provider docs here: [Configure Azure Microsoft Entra ID Application to Trust a Generic Issuer](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/guides/service_principal_oidc#configure-azure-active-directory-application-to-trust-a-generic-issuer). + +The following information should be specified: + +- **Federated credential scenario**: Must be set to `Other issuer`. +- **Issuer**: The address of HCP Terraform (e.g., ). + - **Important**: make sure this value starts with **https://** and does _not_ have a trailing slash. +- **Subject identifier**: The subject identifier from HCP Terraform that this credential will match. This will be in the form `organization:my-org-name:project:my-project-name:workspace:my-workspace-name:run_phase:plan` where the `run_phase` can be one of `plan` or `apply`. +- **Name**: A name for the federated credential, such as `tfc-plan-credential`. Note that this cannot be changed later. + +The following is optional, but may be desired: + +- **Audience**: Enter the audience value that will be set when requesting the identity token. This will be `api://AzureADTokenExchange` by default. This should be set to the value of `TFC_AZURE_WORKLOAD_IDENTITY_AUDIENCE` if this has been configured. + +-> **Note:** because the `Subject identifier` for federated credentials is a direct string match, two federated identity credentials need to be created for each workspace using dynamic credentials: one that matches `run_phase:plan` and one that matches `run_phase:apply`. + +## Configure HCP Terraform + +You’ll need to set some environment variables in your HCP Terraform workspace in order to configure HCP Terraform to authenticate with Azure using dynamic credentials. You can set these as workspace variables. When you configure dynamic provider credentials with multiple provider configurations of the same type, use either a default variable or a tagged alias variable name for each provider configuration. Refer to [Specifying Multiple Configurations](#specifying-multiple-configurations) for more details. + +### Required Environment Variables + +| Variable | Value | Notes | +| ------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `TFC_AZURE_PROVIDER_AUTH`
`TFC_AZURE_PROVIDER_AUTH[_TAG]`
_(Default variable not supported)_ | `true` | Requires **v1.7.0** or later if self-managing agents. Must be present and set to `true`, or HCP Terraform will not attempt to authenticate to Azure. | +| `TFC_AZURE_RUN_CLIENT_ID`
`TFC_AZURE_RUN_CLIENT_ID[_TAG]`
`TFC_DEFAULT_AZURE_RUN_CLIENT_ID` | The client ID for the Service Principal / Application used when authenticating to Azure. | Requires **v1.7.0** or later if self-managing agents. Optional if `TFC_AZURE_PLAN_CLIENT_ID` and `TFC_AZURE_APPLY_CLIENT_ID` are both provided. These variables are described [below](/terraform/enterprise/workspaces/dynamic-provider-credentials/azure-configuration#optional-environment-variables) | + +### Optional Environment Variables + +You may need to set these variables, depending on your use case. + +| Variable | Value | Notes | +| -------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- | +| `TFC_AZURE_WORKLOAD_IDENTITY_AUDIENCE`
`TFC_AZURE_WORKLOAD_IDENTITY_AUDIENCE[_TAG]`
`TFC_DEFAULT_AZURE_WORKLOAD_IDENTITY_AUDIENCE` | Will be used as the `aud` claim for the identity token. Defaults to `api://AzureADTokenExchange`. | Requires **v1.7.0** or later if self-managing agents. | +| `TFC_AZURE_PLAN_CLIENT_ID`
`TFC_AZURE_PLAN_CLIENT_ID[_TAG]`
`TFC_DEFAULT_AZURE_PLAN_CLIENT_ID` | The client ID for the Service Principal / Application to use for the plan phase of a run. | Requires **v1.7.0** or later if self-managing agents. Will fall back to the value of `TFC_AZURE_RUN_CLIENT_ID` if not provided. | +| `TFC_AZURE_APPLY_CLIENT_ID`
`TFC_AZURE_APPLY_CLIENT_ID[_TAG]`
`TFC_DEFAULT_AZURE_APPLY_CLIENT_ID` | The client ID for the Service Principal / Application to use for the apply phase of a run. | Requires **v1.7.0** or later if self-managing agents. Will fall back to the value of `TFC_AZURE_RUN_CLIENT_ID` if not provided. | + +## Configure the AzureRM or Microsoft Entra ID Provider + +Make sure that you’re passing values for the `subscription_id` and `tenant_id` arguments into the provider configuration block or setting the `ARM_SUBSCRIPTION_ID` and `ARM_TENANT_ID` variables in your workspace. + +Make sure that you’re _not_ setting values for `client_id`, `use_oidc`, or `oidc_token` in the provider or setting any of `ARM_CLIENT_ID`, `ARM_USE_OIDC`, `ARM_OIDC_TOKEN`. + +### Specifying Multiple Configurations + +~> **Important:** Ensure you are using version **3.60.0** or later of the **AzureRM provider** and version **2.43.0** or later of the **Microsoft Entra ID provider** as required functionality was introduced in these provider versions. + +~> **Important:** If you are self-hosting [HCP Terraform agents](/terraform/cloud-docs/agents), ensure your agents use [v1.12.0](/terraform/cloud-docs/agents/changelog#1-12-0-07-26-2023) or above. To use the latest dynamic credentials features, [upgrade your agents to the latest version](/terraform/cloud-docs/agents/changelog). + +You can add additional variables to handle multiple distinct Azure setups, enabling you to use multiple [provider aliases](/terraform/language/providers/configuration#alias-multiple-provider-configurations) within the same workspace. You can configure each set of credentials independently, or use default values by configuring the variables prefixed with `TFC_DEFAULT_`. + +For more details, see [Specifying Multiple Configurations](/terraform/enterprise/workspaces/dynamic-provider-credentials/specifying-multiple-configurations). + +#### Required Terraform Variable + +To use additional configurations, add the following code to your Terraform configuration. This lets HCP Terraform supply variable values that you can then use to map authentication and configuration details to the correct provider blocks. + +```hcl +variable "tfc_azure_dynamic_credentials" { + description = "Object containing Azure dynamic credentials configuration" + type = object({ + default = object({ + client_id_file_path = string + oidc_token_file_path = string + }) + aliases = map(object({ + client_id_file_path = string + oidc_token_file_path = string + })) + }) +} +``` + +#### Example Usage + +##### AzureRM Provider + +```hcl +provider "azurerm" { + features {} + // use_cli should be set to false to yield more accurate error messages on auth failure. + use_cli = false + // use_oidc must be explicitly set to true when using multiple configurations. + use_oidc = true + client_id_file_path = var.tfc_azure_dynamic_credentials.default.client_id_file_path + oidc_token_file_path = var.tfc_azure_dynamic_credentials.default.oidc_token_file_path + subscription_id = "00000000-0000-0000-0000-000000000000" + tenant_id = "10000000-0000-0000-0000-000000000000" +} + +provider "azurerm" { + features {} + // use_cli should be set to false to yield more accurate error messages on auth failure. + use_cli = false + // use_oidc must be explicitly set to true when using multiple configurations. + use_oidc = true + alias = "ALIAS1" + client_id_file_path = var.tfc_azure_dynamic_credentials.aliases["ALIAS1"].client_id_file_path + oidc_token_file_path = var.tfc_azure_dynamic_credentials.aliases["ALIAS1"].oidc_token_file_path + subscription_id = "00000000-0000-0000-0000-000000000000" + tenant_id = "20000000-0000-0000-0000-000000000000" +} +``` + +##### Microsoft Entra ID Provider (formerly Azure AD) + +```hcl +provider "azuread" { + features {} + // use_cli should be set to false to yield more accurate error messages on auth failure. + use_cli = false + // use_oidc must be explicitly set to true when using multiple configurations. + use_oidc = true + client_id_file_path = var.tfc_azure_dynamic_credentials.default.client_id_file_path + oidc_token_file_path = var.tfc_azure_dynamic_credentials.default.oidc_token_file_path + subscription_id = "00000000-0000-0000-0000-000000000000" + tenant_id = "10000000-0000-0000-0000-000000000000" +} + +provider "azuread" { + features {} + // use_cli should be set to false to yield more accurate error messages on auth failure. + use_cli = false + // use_oidc must be explicitly set to true when using multiple configurations. + use_oidc = true + alias = "ALIAS1" + client_id_file_path = var.tfc_azure_dynamic_credentials.aliases["ALIAS1"].client_id_file_path + oidc_token_file_path = var.tfc_azure_dynamic_credentials.aliases["ALIAS1"].oidc_token_file_path + subscription_id = "00000000-0000-0000-0000-000000000000" + tenant_id = "20000000-0000-0000-0000-000000000000" +} +``` diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/workspaces/dynamic-provider-credentials/gcp-configuration.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/workspaces/dynamic-provider-credentials/gcp-configuration.mdx new file mode 100644 index 0000000000..400f5e996f --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/workspaces/dynamic-provider-credentials/gcp-configuration.mdx @@ -0,0 +1,174 @@ +--- +page_title: Use dynamic credentials with the GCP provider in Terraform Enterprise +description: >- + Use OpenID Connect to get short-term credentials for the GCP Terraform + provider in your Terraform Enterprise runs. +source: terraform-docs-common +--- + +# Use dynamic credentials with the GCP provider + +~> **Important:** If you are self-hosting [HCP Terraform agents](/terraform/cloud-docs/agents), ensure your agents use [v1.7.0](/terraform/cloud-docs/agents/changelog#1-7-0-03-02-2023) or above. To use the latest dynamic credentials features, [upgrade your agents to the latest version](/terraform/cloud-docs/agents/changelog). + +You can use HCP Terraform’s native OpenID Connect integration with GCP to get [dynamic credentials](/terraform/enterprise/workspaces/dynamic-provider-credentials) for the GCP provider in your HCP Terraform runs. Configuring the integration requires the following steps: + +1. **[Configure GCP](#configure-gcp):** Set up a trust configuration between GCP and HCP Terraform. Then, you must create GCP roles and policies for your HCP Terraform workspaces. +2. **[Configure HCP Terraform](#configure-hcp-terraform):** Add environment variables to the HCP Terraform workspaces where you want to use Dynamic Credentials. + +Once you complete the setup, HCP Terraform automatically authenticates to GCP during each run. The GCP provider authentication is valid for the length of the plan or apply. + +!> **Warning:** Dynamic credentials with the GCP provider do not work if your Terraform Enterprise instance uses a custom or self-signed certificate. This limitation is due to restrictions in GCP. + +## Configure GCP + +You must enable and configure a workload identity pool and provider on GCP. These instructions use the GCP console, but you can also use Terraform to configure GCP. Refer to our [example Terraform configuration](https://github.com/hashicorp/terraform-dynamic-credentials-setup-examples/tree/main/gcp). + +### Add a Workload Identity Pool and Provider + +Google documentation for setting this up can be found here: [Configuring workload identity federation with other identity providers](https://cloud.google.com/iam/docs/workload-identity-federation-with-other-providers). + +Before starting to create the resources, you must enable the APIs mentioned at the start of the [Configure workload Identity federation](https://cloud.google.com/iam/docs/workload-identity-federation-with-other-providers#configure). + +#### Add a Workload Identity Pool + +The following information should be specified: + +- **Name**: Name for the pool, such as `my-tfc-pool`. The name is also used as the pool ID. You can't change the pool ID later. + +The following is optional, but may be desired: + +- **Pool ID**: The ID for the pool. This defaults to the name as mentioned above, but can be set to another value. +- **Description**: Text that describes the purpose of the pool. + +You will also want to ensure that the `Enabled Pool` option is set to be enabled before clicking next. + +#### Add a Workload Identity Provider + +You must add a workload identity provider to the pool. The following information should be specified: + +- **Provider type**: Must be `OpenID Connect (OIDC)`. +- **Provider name**: Name for the identity provider, such as `my-tfc-provider`. The name is also used as the provider ID. You can’t change the provider ID later. +- **Issuer (URL)**: The address of the TFC/E instance, such as + - **Important**: make sure this value starts with **https://** and does _not_ have a trailing slash. +- **Audiences**: This can be left as `Default audience` if you are planning on using the default audience HCP Terraform provides. + - **Important**: you must select the `Allowed audiences` toggle and set this to the value of `TFC_GCP_WORKLOAD_IDENTITY_AUDIENCE`, if configured. +- **Provider attributes mapping**: At the minimum this must include `assertion.sub` for the `google.subject` entry. Other mappings can be added for other claims in the identity token to attributes by adding `attribute.[claim name]` on the Google side and `assertion.[claim name]` on the OIDC side of a new mapping. +- **Attribute Conditions**: Conditions to restrict which identity tokens can authenticate using the workload identity pool, such as `assertion.sub.startsWith("organization:my-org:project:my-project:workspace:my-workspace")` to restrict access to identity tokens from a specific workspace. See this page in Google documentation for more information on the expression language: [Attribute conditions](https://cloud.google.com/iam/docs/workload-identity-federation#conditions). + +!> **Warning**: you should always check, at minimum, the audience and the name of the organization in order to prevent unauthorized access from other HCP Terraform organizations! + +The following is optional, but may be desired: + +- **Provider ID**: The ID for the provider. This defaults to the name as mentioned above, but can be set to another value. + +### Add a Service Account and Permissions + +You must next add a service account and properly configure the permissions. + +#### Create a Service Account + +Google documentation for setting this up can be found here: [Creating a service account for the external workload](https://cloud.google.com/iam/docs/workload-identity-federation-with-other-providers#create_a_service_account_for_the_external_workload). + +The following information should be specified: + +- **Service account name**: Name for the service account, such as `tfc-service-account`. The name is also used as the pool ID. You can't change the pool ID later. + +The following is optional, but may be desired: + +- **Service account ID**: The ID for the service account. This defaults to the name as mentioned above, but can be set to another value. +- **Description**: Text that describes the purpose of the service account. + +#### Grant IAM Permissions + +The next step in the setup wizard will allow for granting IAM permissions for the service account. The role that is given to the service account will vary depending on your specific needs and project setup. This should in general be the most minimal set of permissions needed for the service account to properly function. + +#### Grant External Permissions + +Once the service account has been created and granted IAM permissions, you will need to grant access to the service account for the identity pool created above. Google documentation for setting this up can be found here: [Allow the external workload to impersonate the service account](https://cloud.google.com/iam/docs/workload-identity-federation-with-other-providers#allow_the_external_workload_to_impersonate_the_service_account). + +## Configure HCP Terraform + +You’ll need to set some environment variables in your HCP Terraform workspace in order to configure HCP Terraform to authenticate with GCP using dynamic credentials. You can set these as workspace variables, or if you’d like to share one GCP service account across multiple workspaces, you can use a variable set. When you configure dynamic provider credentials with multiple provider configurations of the same type, use either a default variable or a tagged alias variable name for each provider configuration. Refer to [Specifying Multiple Configurations](#specifying-multiple-configurations) for more details. + +### Required Environment Variables + +| Variable | Value | Notes | +| ----------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `TFC_GCP_PROVIDER_AUTH`
`TFC_GCP_PROVIDER_AUTH[_TAG]`
_(Default variable not supported)_ | `true` | Requires **v1.7.0** or later if self-managing agents. Must be present and set to `true`, or HCP Terraform will not attempt to use dynamic credentials to authenticate to GCP. | +| `TFC_GCP_RUN_SERVICE_ACCOUNT_EMAIL`
`TFC_GCP_RUN_SERVICE_ACCOUNT_EMAIL[_TAG]`
`TFC_DEFAULT_GCP_RUN_SERVICE_ACCOUNT_EMAIL` | The service account email HCP Terraform will use when authenticating to GCP. | Requires **v1.7.0** or later if self-managing agents. Optional if `TFC_GCP_PLAN_SERVICE_ACCOUNT_EMAIL` and `TFC_GCP_APPLY_SERVICE_ACCOUNT_EMAIL` are both provided. These variables are described [below](/terraform/enterprise/workspaces/dynamic-provider-credentials/gcp-configuration#optional-environment-variables) | + +You must also include information about the GCP Workload Identity Provider that HCP Terraform will use when authenticating to GCP. You can supply this information in two different ways: + +1. By providing one unified variable containing the canonical name of the workload identity provider. +2. By providing the project number, pool ID, and provider ID as separate variables. + +You should avoid setting both types of variables, but if you do, the unified version will take precedence. + +#### Unified Variable + +| Variable | Value | Notes | +| -------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `TFC_GCP_WORKLOAD_PROVIDER_NAME`
`TFC_GCP_WORKLOAD_PROVIDER_NAME[_TAG]`
`TFC_DEFAULT_GCP_WORKLOAD_PROVIDER_NAME` | The canonical name of the workload identity provider. This must be in the form mentioned for the `name` attribute [here](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/iam_workload_identity_pool_provider#attributes-reference) | Requires **v1.7.0** or later if self-managing agents. This will take precedence over `TFC_GCP_PROJECT_NUMBER`, `TFC_GCP_WORKLOAD_POOL_ID`, and `TFC_GCP_WORKLOAD_PROVIDER_ID` if set. | + +#### Separate Variables + +| Variable | Value | Notes | +| -------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------ | +| `TFC_GCP_PROJECT_NUMBER`
`TFC_GCP_PROJECT_NUMBER[_TAG]`
`TFC_DEFAULT_GCP_PROJECT_NUMBER` | The project number where the pool and other resources live. | Requires **v1.7.0** or later if self-managing agents. This is _not_ the project ID and is a separate number. | +| `TFC_GCP_WORKLOAD_POOL_ID`
`TFC_GCP_WORKLOAD_POOL_ID[_TAG]`
`TFC_DEFAULT_GCP_WORKLOAD_POOL_ID` | The workload pool ID. | Requires **v1.7.0** or later if self-managing agents. | +| `TFC_GCP_WORKLOAD_PROVIDER_ID`
`TFC_GCP_WORKLOAD_PROVIDER_ID[_TAG]`
`TFC_DEFAULT_GCP_WORKLOAD_PROVIDER_ID` | The workload identity provider ID. | Requires **v1.7.0** or later if self-managing agents. | + +### Optional Environment Variables + +You may need to set these variables, depending on your use case. + +| Variable | Value | Notes | +| ----------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | +| `TFC_GCP_WORKLOAD_IDENTITY_AUDIENCE`
`TFC_GCP_WORKLOAD_IDENTITY_AUDIENCE[_TAG]`
`TFC_DEFAULT_GCP_WORKLOAD_IDENTITY_AUDIENCE` | Will be used as the `aud` claim for the identity token. Defaults to a string of the form mentioned [here](https://cloud.google.com/iam/docs/workload-identity-federation-with-other-providers#oidc_1) in the GCP docs with the leading **https:** stripped. | Requires **v1.7.0** or later if self-managing agents. This is one of the default `aud` formats that GCP accepts. | +| `TFC_GCP_PLAN_SERVICE_ACCOUNT_EMAIL`
`TFC_GCP_PLAN_SERVICE_ACCOUNT_EMAIL[_TAG]`
`TFC_DEFAULT_GCP_PLAN_SERVICE_ACCOUNT_EMAIL` | The service account email to use for the plan phase of a run. | Requires **v1.7.0** or later if self-managing agents. Will fall back to the value of `TFC_GCP_RUN_SERVICE_ACCOUNT_EMAIL` if not provided. | +| `TFC_GCP_APPLY_SERVICE_ACCOUNT_EMAIL`
`TFC_GCP_APPLY_SERVICE_ACCOUNT_EMAIL[_TAG]`
`TFC_DEFAULT_GCP_APPLY_SERVICE_ACCOUNT_EMAIL` | The service account email to use for the apply phase of a run. | Requires **v1.7.0** or later if self-managing agents. Will fall back to the value of `TFC_GCP_RUN_SERVICE_ACCOUNT_EMAIL` if not provided. | + +## Configure the GCP Provider + +Make sure that you’re passing values for the `project` and `region` arguments into the provider configuration block. + +Make sure that you’re not setting values for the `GOOGLE_CREDENTIALS` or `GOOGLE_APPLICATION_CREDENTIALS` environment variables as these will conflict with the dynamic credentials authentication process. + +### Specifying Multiple Configurations + +~> **Important:** If you are self-hosting [HCP Terraform agents](/terraform/cloud-docs/agents), ensure your agents use [v1.12.0](/terraform/cloud-docs/agents/changelog#1-12-0-07-26-2023) or above. To use the latest dynamic credentials features, [upgrade your agents to the latest version](/terraform/cloud-docs/agents/changelog). + +You can add additional variables to handle multiple distinct GCP setups, enabling you to use multiple [provider aliases](/terraform/language/providers/configuration#alias-multiple-provider-configurations) within the same workspace. You can configure each set of credentials independently, or use default values by configuring the variables prefixed with `TFC_DEFAULT_`. + +For more details, see [Specifying Multiple Configurations](/terraform/enterprise/workspaces/dynamic-provider-credentials/specifying-multiple-configurations). + +#### Required Terraform Variable + +To use additional configurations, add the following code to your Terraform configuration. This lets HCP Terraform supply variable values that you can then use to map authentication and configuration details to the correct provider blocks. + +```hcl +variable "tfc_gcp_dynamic_credentials" { + description = "Object containing GCP dynamic credentials configuration" + type = object({ + default = object({ + credentials = string + }) + aliases = map(object({ + credentials = string + })) + }) +} +``` + +#### Example Usage + +```hcl +provider "google" { + credentials = var.tfc_gcp_dynamic_credentials.default.credentials +} + +provider "google" { + alias = "ALIAS1" + credentials = var.tfc_gcp_dynamic_credentials.aliases["ALIAS1"].credentials +} +``` diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/workspaces/dynamic-provider-credentials/hcp-configuration.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/workspaces/dynamic-provider-credentials/hcp-configuration.mdx new file mode 100644 index 0000000000..2414f1b7af --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/workspaces/dynamic-provider-credentials/hcp-configuration.mdx @@ -0,0 +1,116 @@ +--- +page_title: Dynamic Credentials with the HCP Provider - Workspaces - Terraform Enterprise +description: >- + Use OpenID Connect to get short-term credentials for the HCP provider in your + Terraform Enterprise runs. +source: terraform-docs-common +--- + +# Dynamic Credentials with the HCP Provider + +~> **Important:** If you are self-hosting [HCP Terraform agents](/terraform/cloud-docs/agents), ensure your agents use [v1.15.1](/terraform/cloud-docs/agents/changelog#1-15-1-05-01-2024) or above. To use the latest dynamic credentials features, [upgrade your agents to the latest version](/terraform/cloud-docs/agents/changelog). + +You can use HCP Terraform’s native OpenID Connect integration with HCP to authenticate with the HCP provider using [dynamic credentials](/terraform/enterprise/workspaces/dynamic-provider-credentials) in your HCP Terraform runs. Configuring dynamic credentials for the HCP provider requires the following steps: + +1. **[Configure HCP](#configure-hcp):** Set up a trust configuration between HCP and HCP Terraform. Then, you must create a [service principal in HPC](https://developer.hashicorp.com/hcp/docs/hcp/admin/iam/service-principals) for your HCP Terraform workspaces. +2. **[Configure HCP Terraform](#configure-hcp-terraform):** Add environment variables to the HCP Terraform workspaces where you want to use Dynamic Credentials. + +Once you complete the setup, HCP Terraform automatically authenticates to HCP during each run. + +## Configure HCP + +You must enable and configure a workload identity pool and provider on HCP. These instructions use the HCP CLI, but you can also use Terraform to configure HCP. Refer to our [example Terraform configuration](https://github.com/hashicorp/terraform-dynamic-credentials-setup-examples/tree/main/hcp). + +#### Create a Service Principal + +Create a service principal for HCP Terraform to assume during runs by running the following HCP command. Note the ID of the service principal you create because you will need it in the following steps. For all remaining steps, replace `HCP_PROJECT_ID` with the ID of the project that contains all the resources and workspaces that you want to manage with this service principal. If you wish to manage more than one project with dynamic credentials, it is recommended that you create multiple service principals, one for each project. + +```shell +hcp iam service-principals create hcp-terraform --project=HCP_PROJECT_ID +``` + +Grant your service principal the necessary permissions to manage your infrastructure during runs. + +```shell +hcp projects iam add-binding \ + --project=HCP_PROJECT_ID \ + --member=HCP_PRINCIPAL_ID \ + --role=roles/contributor +``` + +#### Add a Workload Identity Provider + +Next, create a workload identity provider that HCP uses to authenticate the HCP Terraform run. Make sure to replace `HCP_PROJECT_ID`, `ORG_NAME`, `PROJECT_NAME`, and `WORKSPACE_NAME` with their respective values before running the command. + +```shell +hcp iam workload-identity-providers create-oidc hcp-terraform-dynamic-credentials \ + --service-principal=iam/project/HCP_PROJECT_ID/service-principal/hcp-terraform \ + --issuer=https://app.terraform.io \ + --allowed-audience=hcp.workload.identity \ + --conditional-access='jwt_claims.sub matches `^organization:ORG_NAME:project:PROJECT_NAME:workspace:WORKSPACE_NAME:run_phase:.*`' \ + --description="Allow HCP Terraform agents to act as the hcp-terraform service principal" +``` + +## Configure HCP Terraform + +Next, you need to set environment variables in your HCP Terraform workspace to configure HCP Terraform to authenticate with HCP using dynamic credentials. You can set these as workspace variables or use a variable set to share one HCP service principal across multiple workspaces. When you configure dynamic provider credentials with multiple provider configurations of the same type, use either a default variable or a tagged alias variable name for each provider configuration. Refer to [Specifying Multiple Configurations](#specifying-multiple-configurations) for more details. + +### Required Environment Variables + +| Variable | Value | Notes | +| -------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `TFC_HCP_PROVIDER_AUTH`
`TFC_HCP_PROVIDER_AUTH[_TAG]`
_(Default variable not supported)_ | `true` | Requires **v1.15.1** or later if you use self-managing agents. Must be present and set to `true`, or HCP Terraform will not attempt to use dynamic credentials to authenticate to HCP. | +| `TFC_HCP_RUN_PROVIDER_RESOURCE_NAME`
`TFC_HCP_RUN_PROVIDER_RESOURCE_NAME[_TAG]`
`TFC_DEFAULT_HCP_RUN_PROVIDER_RESOURCE_NAME` | The resource name of the workload identity provider that will be used to assume the service principal | Requires **v1.15.1** or later if you use self-managing agents. Optional if you provide `PLAN_PROVIDER_RESOURCE_NAME` and `APPLY_PROVIDER_RESOURCE_NAME`. [Learn more](#optional-environment-variables). | + +### Optional Environment Variables + +You may need to set these variables, depending on your use case. + +| Variable | Value | Notes | +| -------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- | +| `TFC_HCP_WORKLOAD_IDENTITY_AUDIENCE`
`TFC_HCP_WORKLOAD_IDENTITY_AUDIENCE[_TAG]`
`TFC_DEFAULT_HCP_WORKLOAD_IDENTITY_AUDIENCE` | HCP Terraform uses this as the `aud` claim for the identity token. Defaults to the provider resource name for the current run phase, which HCP Terraform derives from the values you provide for `RUN_PROVIDER_RESOURCE_NAME`, `PLAN_PROVIDER_RESOURCE_NAME`, and `APPLY_PROVIDER_RESOURCE_NAME`. | Requires **v1.15.1** or later if you use self-managing agents. This is one of the default `aud` formats that HCP accepts. | +| `TFC_HCP_PLAN_PROVIDER_RESOURCE_NAME`
`TFC_HCP_PLAN_PROVIDER_RESOURCE_NAME[_TAG]`
`TFC_DEFAULT_HCP_PLAN_PROVIDER_RESOURCE_NAME` | The resource name of the workload identity provider that will HCP Terraform will use to authenticate the agent during the plan phase of a run. | Requires **v1.15.1** or later if self-managing agents. Will fall back to the value of `RUN_PROVIDER_RESOURCE_NAME` if not provided. | +| `TFC_HCP_APPLY_PROVIDER_RESOURCE_NAME`
`TFC_HCP_APPLY_PROVIDER_RESOURCE_NAME[_TAG]`
`TFC_DEFAULT_HCP_APPLY_PROVIDER_RESOURCE_NAME` | The resource name of the workload identity provider that will HCP Terraform will use to authenticate the agent during the apply phase of a run. | Requires **v1.15.1** or later if self-managing agents. Will fall back to the value of `RUN_PROVIDER_RESOURCE_NAME` if not provided. | + +## Configure the HCP Provider + +Do not set the `HCP_CRED_FILE` environment variable when configuring the HCP provider, or `HCP_CRED_FILE` will conflict with the dynamic credentials authentication process. + +### Specifying Multiple Configurations + +~> **Important:** If you are self-hosting [HCP Terraform agents](/terraform/cloud-docs/agents), ensure your agents use [v1.15.1](/terraform/cloud-docs/agents/changelog#1-15-1-05-01-2024) or above. To use the latest dynamic credentials features, [upgrade your agents to the latest version](/terraform/cloud-docs/agents/changelog). + +You can add additional variables to handle multiple distinct HCP setups, enabling you to use multiple [provider aliases](/terraform/language/providers/configuration#alias-multiple-provider-configurations) within the same workspace. You can configure each set of credentials independently, or use default values by configuring the variables prefixed with `TFC_DEFAULT_`. + +For more details, refer to [Specifying Multiple Configurations](/terraform/enterprise/workspaces/dynamic-provider-credentials/specifying-multiple-configurations). + +#### Required Terraform Variable + +Add the following variable to your Terraform configuration to set up additional dynamic credential configurations with the HCP provider. This variable lets HCP Terraform supply variable values that you can then use to map authentication and configuration details to the correct provider blocks. + +```hcl +variable "tfc_hcp_dynamic_credentials" { + description = "Object containing HCP dynamic credentials configuration" + type = object({ + default = object({ + credential_file = string + }) + aliases = map(object({ + credential_file = string + })) + }) +} +``` + +#### Example Usage + +```hcl +provider "hcp" { + credential_file = var.tfc_hcp_dynamic_credentials.default.credential_file +} + +provider "hcp" { + alias = "ALIAS1" + credential_file = var.tfc_hcp_dynamic_credentials.aliases["ALIAS1"].credential_file +} +``` diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/workspaces/dynamic-provider-credentials/hcp-vault-secrets-backed/aws-configuration.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/workspaces/dynamic-provider-credentials/hcp-vault-secrets-backed/aws-configuration.mdx new file mode 100644 index 0000000000..984c8ce7a7 --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/workspaces/dynamic-provider-credentials/hcp-vault-secrets-backed/aws-configuration.mdx @@ -0,0 +1,102 @@ +--- +page_title: >- + HCP Vault Secrets-Backed Dynamic Credentials with the AWS Provider - + Workspaces - Terraform Enterprise +description: >- + Use OpenID Connect and HCP Vault Secrets to get short-term credentials for the + AWS Terraform provider in your Terraform Enterprise runs. +source: terraform-docs-common +--- + +# HCP Vault Secrets-Backed Dynamic Credentials with the AWS Provider + +~> **Important:** If you are self-hosting [HCP Terraform agents](/terraform/cloud-docs/agents), ensure your agents use [v1.16.0](/terraform/cloud-docs/agents/changelog#1-16-0-10-02-2024) or above. To use the latest dynamic credentials features, [upgrade your agents to the latest version](/terraform/cloud-docs/agents/changelog). + +You can use HCP Terraform’s native OpenID Connect integration with HCP to use [HCP Vault Secrets-backed dynamic credentials](/terraform/enterprise/workspaces/dynamic-provider-credentials/hcp-vault-secrets-backed) with the AWS provider in your HCP Terraform runs. Configuring the integration requires the following steps: + +1. **[Configure HCP Provider Credentials](#configure-hcp-provider-credentials)**: Set up a trust configuration between HCP Vault Secrets and HCP Terraform, create HCP roles and policies for your HCP Terraform workspaces, and add environment variables to those workspaces. +2. **[Configure HCP Vault Secrets](#configure-hcp-vault-secrets-aws-secrets-engine)**: Set up your HCP project's AWS integration and dynamic secret. +3. **[Configure HCP Terraform](#configure-hcp-terraform)**: Add additional environment variables to the HCP Terraform workspaces where you want to use HCP Vault Secrets-backed dynamic credentials. +4. **[Configure Terraform Providers](#configure-terraform-providers)**: Configure your Terraform providers to work with HCP Vault Secrets-backed dynamic credentials. + +Once you complete this setup, HCP Terraform automatically authenticates with AWS via HCP Vault Secrets-generated credentials during the plan and apply phase of each run. The AWS provider's authentication is only valid for the length of the plan or apply phase. + +## Configure HCP Provider Credentials + +You must first set up HCP dynamic provider credentials before you can use HCP Vault Secrets-backed dynamic credentials. This includes creating a service principal, configuring trust between HCP and HCP Terraform, and populating the required environment variables in your HCP Terraform workspace. + +[See the setup instructions for HCP dynamic provider credentials](/terraform/enterprise/workspaces/dynamic-provider-credentials/hcp-configuration). + +## Configure HCP Vault Secrets AWS Secrets Engine + +Follow the instructions in the HCP Vault Secrets documentation for [setting up the AWS integration in your HCP project](/hcp/docs/vault-secrets/dynamic-secrets/aws). + +## Configure HCP Terraform + +Next, you need to set certain environment variables in your HCP Terraform workspace to authenticate HCP Terraform with AWS using HCP Vault Secrets-backed dynamic credentials. These variables are in addition to those you previously set while configuring [HCP provider credentials](#configure-hcp-provider-credentials). You can add these as workspace variables or as a [variable set](/terraform/enterprise/workspaces/variables/managing-variables#variable-sets). + +### Required Environment Variables + +| Variable | Value | Notes | +| ------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------- | +| `TFC_HVS_BACKED_AWS_AUTH`
`TFC_HVS_BACKED_AWS_AUTH[_TAG]`
_(Default variable not supported)_ | `true` | Requires **v1.16.0** or later if self-managing agents. Must be present and set to `true`, or HCP Terraform will not attempt to authenticate with AWS. | +| `TFC_HVS_BACKED_AWS_RUN_SECRET_RESOURCE_NAME` | The name of the HCP Vault Secrets dynamic secret resource to read. | Requires **v1.16.0** or later if self-managing agents. Must be present. | + +### Optional Environment Variables + +You may need to set these variables, depending on your use case. + +| Variable | Value | Notes | +| ----------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | +| `TFC_HVS_BACKED_AWS_HCP_CONFIG`
`TFC_HVS_BACKED_AWS_HCP_CONFIG[_TAG]`
`TFC_DEFAULT_HVS_BACKED_AWS_HCP_CONFIG` | The name of the non-default HCP configuration for workspaces using [multiple HCP configurations](/terraform/enterprise/workspaces/dynamic-provider-credentials/specifying-multiple-configurations). | Requires **v1.16.0** or later if self-managing agents. Will fall back to using the default HCP Vault Secrets configuration if not provided. | +| `TFC_HVS_BACKED_AWS_PLAN_SECRET_RESOURCE_NAME` | The name of the HCP Vault Secrets dynamic secret resource to read for the plan phase. | Requires **v1.16.0** or later if self-managing agents. Must be present. | +| `TFC_HVS_BACKED_AWS_APPLY_SECRET_RESOURCE_NAME` | The name of the HCP Vault Secrets dynamic secret resource to read for the apply phase. | Requires **v1.16.0** or later if self-managing agents. Must be present. | + +## Configure Terraform Providers + +The final step is to directly configure your AWS and HCP Vault Secrets providers. + +### Configure the AWS Provider + +Ensure you pass a value for the `region` argument in your AWS provider configuration block or set the `AWS_REGION` variable in your workspace. + +Ensure you are not using any of the arguments or methods mentioned in the [authentication and configuration](https://registry.terraform.io/providers/hashicorp/aws/latest/docs#authentication-and-configuration) section of the provider documentation. Otherwise, these settings may interfere with dynamic provider credentials. + +### Specifying Multiple Configurations + +~> **Important:** If you are self-hosting [HCP Terraform agents](/terraform/cloud-docs/agents), ensure your agents use [v1.16.0](/terraform/cloud-docs/agents/changelog#1-16-0-10-02-2024) or above. To use the latest dynamic credentials features, [upgrade your agents to the latest version](/terraform/cloud-docs/agents/changelog). + +You can add additional variables to handle multiple distinct HCP Vault Secrets-backed AWS setups, enabling you to use multiple [provider aliases](/terraform/language/providers/configuration#alias-multiple-provider-configurations) within the same workspace. You can configure each set of credentials independently, or use default values by configuring the variables prefixed with `TFC_DEFAULT_`. + +For more details, see [Specifying Multiple Configurations](/terraform/enterprise/workspaces/dynamic-provider-credentials/specifying-multiple-configurations). + +#### Required Terraform Variable + +To use additional configurations, add the following code to your Terraform configuration. This lets HCP Terraform supply variable values that you can then use to map authentication and configuration details to the correct provider blocks. + +```hcl +variable "tfc_hvs_backed_aws_dynamic_credentials" { + description = "Object containing HCP Vault Secrets-backed AWS dynamic credentials configuration" + type = object({ + default = object({ + shared_credentials_file = string + }) + aliases = map(object({ + shared_credentials_file = string + })) + }) +} +``` + +#### Example Usage + +```hcl +provider "aws" { + shared_credentials_files = [var.tfc_hvs_backed_aws_dynamic_credentials.default.shared_credentials_file] +} + +provider "aws" { + alias = "ALIAS1" + shared_credentials_files = [var.tfc_hvs_backed_aws_dynamic_credentials.aliases["ALIAS1"].shared_credentials_file] +} +``` diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/workspaces/dynamic-provider-credentials/hcp-vault-secrets-backed/gcp-configuration.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/workspaces/dynamic-provider-credentials/hcp-vault-secrets-backed/gcp-configuration.mdx new file mode 100644 index 0000000000..725342406e --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/workspaces/dynamic-provider-credentials/hcp-vault-secrets-backed/gcp-configuration.mdx @@ -0,0 +1,119 @@ +--- +page_title: >- + HCP Vault Secrets-Backed Dynamic Credentials with the GCP Provider - + Workspaces - Terraform Enterprise +description: >- + Use OpenID Connect and HCP Vault Secrets to get short-term credentials for the + GCP Terraform provider in your Terraform Enterprise runs. +source: terraform-docs-common +--- + +# HCP Vault Secrets-Backed Dynamic Credentials with the GCP Provider + +~> **Important:** If you are self-hosting [HCP Terraform agents](/terraform/cloud-docs/agents), ensure your agents use [v1.16.0](/terraform/cloud-docs/agents/changelog#1-16-0-10-02-2024) or above. To use the latest dynamic credentials features, [upgrade your agents to the latest version](/terraform/cloud-docs/agents/changelog). + +You can use HCP Terraform’s native OpenID Connect integration with HCP to use [HCP Vault Secrets-backed dynamic credentials](/terraform/enterprise/workspaces/dynamic-provider-credentials/hcp-vault-secrets-backed) with the GCP provider in your HCP Terraform runs. Configuring the integration requires the following steps: + +1. **[Configure HCP Provider Credentials](#configure-hcp-provider-credentials)**: Set up a trust configuration between HCP Vault Secrets and HCP Terraform, create HCP Vault Secrets roles and policies for your HCP Terraform workspaces, and add environment variables to those workspaces. +2. **[Configure HCP Vault Secrets](#configure-hcp-vault-secrets-gcp-secrets-engine)**: Set up your HCP project's GCP integration and dynamic secret. +3. **[Configure HCP Terraform](#configure-hcp-terraform)**: Add additional environment variables to the HCP Terraform workspaces where you want to use HCP Vault Secrets-backed dynamic credentials. +4. **[Configure Terraform Providers](#configure-terraform-providers)**: Configure your Terraform providers to work with HCP Vault Secrets-backed dynamic credentials. + +Once you complete this setup, HCP Terraform automatically authenticates with GCP via HCP Vault Secrets-generated credentials during the plan and apply phase of each run. The GCP provider's authentication is only valid for the length of the plan or apply phase. + +## Configure HCP Provider Credentials + +You must first set up HCP dynamic provider credentials before you can use HCP Vault Secrets-backed dynamic credentials. This includes creating a service principal, configuring trust between HCP and HCP Terraform, and populating the required environment variables in your HCP Terraform workspace. + +[See the setup instructions for HCP dynamic provider credentials](/terraform/enterprise/workspaces/dynamic-provider-credentials/hcp-configuration). + +## Configure HCP Vault Secrets GCP Secrets Engine + +Follow the instructions in the HCP Vault Secrets documentation for [setting up the GCP integration in your HCP project](/hcp/docs/vault-secrets/dynamic-secrets/gcp). + +## Configure HCP Terraform + +Next, you need to set certain environment variables in your HCP Terraform workspace to authenticate HCP Terraform with GCP using HCP Vault Secrets-backed dynamic credentials. These variables are in addition to those you previously set while configuring [HCP provider credentials](#configure-hcp-provider-credentials). You can add these as workspace variables or as a [variable set](/terraform/enterprise/workspaces/variables/managing-variables#variable-sets). + +### Required Common Environment Variables + +| Variable | Value | Notes | +| ------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------- | +| `TFC_HVS_BACKED_GCP_AUTH`
`TFC_HVS_BACKED_GCP_AUTH[_TAG]`
_(Default variable not supported)_ | `true` | Requires **v1.16.0** or later if self-managing agents. Must be present and set to `true`, or HCP Terraform will not attempt to authenticate with GCP. | +| `TFC_HVS_BACKED_GCP_RUN_SECRET_RESOURCE_NAME` | The name of the HCP Vault Secrets dynamic secret resource to read. | Requires **v1.16.0** or later if self-managing agents. Must be present. | + +### Optional Common Environment Variables + +You may need to set these variables, depending on your use case. + +| Variable | Value | Notes | +| ----------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | +| `TFC_HVS_BACKED_GCP_HCP_CONFIG`
`TFC_HVS_BACKED_GCP_HCP_CONFIG[_TAG]`
`TFC_DEFAULT_HVS_BACKED_GCP_HCP_CONFIG` | The name of the non-default HCP configuration for workspaces using [multiple HCP configurations](/terraform/enterprise/workspaces/dynamic-provider-credentials/specifying-multiple-configurations). | Requires **v1.16.0** or later if self-managing agents. Will fall back to using the default HCP Vault Secrets configuration if not provided. | +| `TFC_HVS_BACKED_GCP_PLAN_SECRET_RESOURCE_NAME` | The name of the HCP Vault Secrets dynamic secret resource to read for the plan phase. | Requires **v1.16.0** or later if self-managing agents. Must be present. | +| `TFC_HVS_BACKED_GCP_APPLY_SECRET_RESOURCE_NAME` | The name of the HCP Vault Secrets dynamic secret resource to read for the apply phase. | Requires **v1.16.0** or later if self-managing agents. Must be present. | + +## Configure Terraform Providers + +The final step is to directly configure your GCP and HCP Vault Secrets providers. + +### Configure the GCP Provider + +Ensure you pass values for the `project` and `region` arguments into the provider configuration block. + +Ensure you are not setting values or environment variables for `GOOGLE_CREDENTIALS` or `GOOGLE_APPLICATION_CREDENTIALS`. Otherwise, these values may interfere with dynamic provider credentials. + +### Specifying Multiple Configurations + +~> **Important:** If you are self-hosting [HCP Terraform agents](/terraform/cloud-docs/agents), ensure your agents use [v1.16.0](/terraform/cloud-docs/agents/changelog#1-16-0-10-02-2024) or above. To use the latest dynamic credentials features, [upgrade your agents to the latest version](/terraform/cloud-docs/agents/changelog). + +You can add additional variables to handle multiple distinct HCP Vault Secrets-backed GCP setups, enabling you to use multiple [provider aliases](/terraform/language/providers/configuration#alias-multiple-provider-configurations) within the same workspace. You can configure each set of credentials independently, or use default values by configuring the variables prefixed with `TFC_DEFAULT_`. + +For more details, see [Specifying Multiple Configurations](/terraform/enterprise/workspaces/dynamic-provider-credentials/specifying-multiple-configurations). + +#### Required Terraform Variable + +To use additional configurations, add the following code to your Terraform configuration. This lets HCP Terraform supply variable values that you can then use to map authentication and configuration details to the correct provider blocks. + +```hcl +variable "tfc_hvs_backed_gcp_dynamic_credentials" { + description = "Object containing HCP Vault Secrets-backed GCP dynamic credentials configuration" + type = object({ + default = object({ + credentials = string + access_token = string + }) + aliases = map(object({ + credentials = string + access_token = string + })) + }) +} +``` + +#### Example Usage + +##### Access Token + +```hcl +provider "google" { + access_token = var.tfc_hvs_backed_gcp_dynamic_credentials.default.access_token +} + +provider "google" { + alias = "ALIAS1" + access_token = var.tfc_hvs_backed_gcp_dynamic_credentials.aliases["ALIAS1"].access_token +} +``` + +##### Credentials + +```hcl +provider "google" { + credentials = var.tfc_hvs_backed_gcp_dynamic_credentials.default.credentials +} + +provider "google" { + alias = "ALIAS1" + credentials = var.tfc_hvs_backed_gcp_dynamic_credentials.aliases["ALIAS1"].credentials +} +``` diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/workspaces/dynamic-provider-credentials/hcp-vault-secrets-backed/index.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/workspaces/dynamic-provider-credentials/hcp-vault-secrets-backed/index.mdx new file mode 100644 index 0000000000..a60b0e4b12 --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/workspaces/dynamic-provider-credentials/hcp-vault-secrets-backed/index.mdx @@ -0,0 +1,38 @@ +--- +page_title: HCP Vault Secrets-backed dynamic credentials overview +description: >- + HCP Vault Secrets-backed dynamic credentials improve security by leveraging + HCP Vault Secrets to generate temporary credentials for Terraform runs. + Configure HCP Vault Secrets-backed dynamic credentials for supported + providers. +source: terraform-docs-common +--- + +# HCP Vault Secrets-Backed Dynamic Credentials + +This topic provides an overview of how to use HCP Vault Secrets to generate temporary credentials for providers so that you can securely use them in HCP Terraform runs. + +## Introduction + +Configuring [dynamic provider credentials](/terraform/enterprise/workspaces/dynamic-provider-credentials) with different cloud providers is suitable for most use cases, but you can use HCP Vault Secrets-backed dynamic credentials to centralize and consolidate cloud credential management. + +HCP Vault Secrets-backed dynamic credentials leverage HCP Vault Secrets' [dynamic secrets capability](/hcp/docs/vault-secrets/dynamic-secrets), which allows you to generate short-lived credentials for various providers. This means you can authenticate a HCP instance using workload identity tokens and use dynamic secret capabilities on that instance to generate dynamic credentials for the various providers. + +Refer to the [HCP Vault Secrets announcement](https://www.hashicorp.com/blog/hcp-vault-secrets-is-now-generally-available) to learn about the benefits of using HCP Vault Secrets to manage provider credentials. + +## Workflow + +Using HCP Vault Secrets-backed dynamic credentials in a workspace requires the following steps for each cloud platform: + +1. If you are self-hosting [HCP Terraform agents](/terraform/cloud-docs/agents), ensure your agents use [v1.16.0](/terraform/cloud-docs/agents/changelog#1-16-0-10-02-2024) or newer. To use the latest dynamic credentials features, [upgrade your agents to the latest version](/terraform/cloud-docs/agents/changelog). +2. Set up dynamic provider credentials with the HCP provider: You must first [configure dynamic credentials with the HCP provider](/terraform/enterprise/workspaces/dynamic-provider-credentials/hcp-configuration). +3. Configure the dynamic secrets integration: You must configure the desired Vault secrets integration in your HCP project, such as AWS or GCP. +4. Configure your HCP Terraform workspace: You must add specific environment variables to your workspace to tell HCP Terraform how to authenticate to other cloud providers during runs. Each cloud platform has its own set of environment variables that are necessary to configure dynamic credentials. +5. Complete the instructions for setting up HCP Vault Secrets-backed dynamic for [Amazon Web Services](/terraform/enterprise/workspaces/dynamic-provider-credentials/hcp-vault-secrets-backed/aws-configuration) or [Google Cloud Platform](/terraform/enterprise/workspaces/dynamic-provider-credentials/hcp-vault-secrets-backed/gcp-configuration). + +### Access to metadata endpoints + +In order to verify signed JWTs, HCP must have network access to the following static OIDC metadata endpoints within HCP Terraform: + +- `/.well-known/openid-configuration`: Standard OIDC metadata. +- `/.well-known/jwks`: HCP Terraform public keys that cloud platforms use to verify the authenticity of tokens that claim to come from HCP Terraform. diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/workspaces/dynamic-provider-credentials/index.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/workspaces/dynamic-provider-credentials/index.mdx new file mode 100644 index 0000000000..11a3902ea2 --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/workspaces/dynamic-provider-credentials/index.mdx @@ -0,0 +1,59 @@ +--- +page_title: Dynamic provider credentials in Terraform Enterprise +description: >- + Dynamic provider credentials generate temporary credentials for Terraform + Enterprise runs. Learn how dynamic credentials for Vault, AWS, Azure, + Kubernetes, and GCP can improve your security. +source: terraform-docs-common +--- + +# Dynamic provider credentials + +~> **Important:** If you are self-hosting [HCP Terraform agents](/terraform/cloud-docs/agents), ensure your agents use [v1.7.0](/terraform/cloud-docs/agents/changelog#1-7-0-03-02-2023) or above. To use the latest dynamic credentials features, [upgrade your agents to the latest version](/terraform/cloud-docs/agents/changelog). + +Using static credentials in your workspaces to authenticate providers presents a security risk, even if you rotate your credentials regularly. Dynamic provider credentials improve your security posture by letting you provision new, temporary credentials for each run. + +You can configure dynamic credentials for each HCP Terraform workspace. This workflow eliminates the need to manually manage and rotate credentials across your organization. It also lets you use the cloud platform’s authentication and authorization tools to scope permissions based on metadata, such as a run’s phase, its workspace, or its organization. + +## How Dynamic Credentials Work + +You configure a trust relationship between your cloud platform and HCP Terraform. As part of that process, you can define rules that let HCP Terraform workspaces and runs access specific resources. Then, the following process occurs for each Terraform plan and apply: + +1. HCP Terraform generates a [workload identity token](/terraform/enterprise/workspaces/dynamic-provider-credentials/workload-identity-tokens). The token is compliant with OpenID Connect protocol (OIDC) standards and includes information about the organization, workspace, and run stage. +2. When a plan or apply begins, HCP Terraform sends the workload identity token to the cloud platform, along with any other information needed to authenticate. +3. The cloud platform uses HCP Terraform’s public signing key to verify the workload identity token. +4. If verification succeeds, the cloud platform returns a set of fresh temporary credentials for HCP Terraform to use. +5. HCP Terraform sets up these credentials within the run environment for the Terraform provider to use. +6. The Terraform plan or apply proceeds. +7. When the plan or apply completes, the run environment is torn down and the temporary credentials are discarded. + +## Configure Dynamic Credentials + +Using dynamic credentials in a workspace requires the following steps for each cloud platform: + +1. **Set up a Trust Relationship:** You must configure a relationship between HCP Terraform and the other cloud platform. The exact details of this process will be different depending on the cloud platform. +2. **Configure Cloud Platform Access:** You must configure roles and policies for the cloud platform to define the workspace’s access to infrastructure resources. +3. **Configure HCP Terraform Workspace**: You must add specific environment variables to your workspace to tell HCP Terraform how to authenticate to the other cloud platform during plans and applies. Each cloud platform has its own set of environment variables to configure dynamic credentials. + +The process for each step is different for each cloud platform. Refer to the cloud platform configuration instructions for full details. You can configure dynamic credentials for the following platforms: + +- [Vault](/terraform/enterprise/workspaces/dynamic-provider-credentials/vault-configuration) +- [Amazon Web Services](/terraform/enterprise/workspaces/dynamic-provider-credentials/aws-configuration) +- [Google Cloud Platform](/terraform/enterprise/workspaces/dynamic-provider-credentials/gcp-configuration) +- [Azure](/terraform/enterprise/workspaces/dynamic-provider-credentials/azure-configuration) +- [Kubernetes](/terraform/enterprise/workspaces/dynamic-provider-credentials/kubernetes-configuration) + +You can also use Vault to generate credentials for AWS, GCP, or Azure by setting up [Vault-backed dynamic credentials](/terraform/enterprise/workspaces/dynamic-provider-credentials/vault-backed), which take advantage of Vault's [secrets engines](/vault/docs/secrets) to generate temporary credentials. + +## Terraform Enterprise Specific Requirements + +### External Access to Metadata Endpoints + +In order to verify signed JWTs, cloud platforms must have network access to the following static OIDC metadata endpoints within TFE: + +1. `/.well-known/openid-configuration` - standard OIDC metadata. +2. `/.well-known/jwks` - TFE’s public key(s) that cloud platforms use to verify the authenticity of tokens that claim to come from TFE. + +### External Vault Policy + +If you are using an external Vault instance, you must ensure that your Vault instance has the correct policies setup as detailed in the [External Vault Requirements for Terraform Enterprise](/terraform/enterprise/requirements/data-storage/vault) documentation. diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/workspaces/dynamic-provider-credentials/kubernetes-configuration.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/workspaces/dynamic-provider-credentials/kubernetes-configuration.mdx new file mode 100644 index 0000000000..6cd1fbda1f --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/workspaces/dynamic-provider-credentials/kubernetes-configuration.mdx @@ -0,0 +1,171 @@ +--- +page_title: >- + Use dynamic credentials with the Kubernetes and Helm providers in Terraform + Enterprise +description: >- + Use OpenID Connect to get short-term credentials for the Kubernetes and Helm + Terraform providers in your Terraform Enterprise runs. +source: terraform-docs-common +--- + +# Use dynamic credentials with the Kubernetes and Helm providers + +~> **Important:** If you are self-hosting [HCP Terraform agents](/terraform/cloud-docs/agents), ensure your agents use [v1.13.1](/terraform/cloud-docs/agents/changelog#1-13-1-10-25-2023) or above. To use the latest dynamic credentials features, [upgrade your agents to the latest version](/terraform/cloud-docs/agents/changelog). + +You can use HCP Terraform’s native OpenID Connect integration with Kubernetes to use [dynamic credentials](/terraform/enterprise/workspaces/dynamic-provider-credentials) for the Kubernetes and Helm providers in your HCP Terraform runs. Configuring the integration requires the following steps: + +1. **[Configure Kubernetes](#configure-kubernetes):** Set up a trust configuration between Kubernetes and HCP Terraform. Next, create Kubernetes role bindings for your HCP Terraform identities. +2. **[Configure HCP Terraform](#configure-hcp-terraform):** Add environment variables to the HCP Terraform workspaces where you want to use dynamic credentials. +3. **[Configure the Kubernetes or Helm provider](#configure-the-provider)**: Set the required attributes on the provider block. + +Once you complete the setup, HCP Terraform automatically authenticates to Kubernetes during each run. The Kubernetes and Helm providers' authentication is valid for the length of a plan or apply operation. + +## Configure Kubernetes + +You must enable and configure an OIDC identity provider in the Kubernetes API. This workflow changes based on the platform hosting your Kubernetes cluster. HCP Terraform only supports dynamic credentials with Kubernetes in AWS and GCP. + +### Configure an OIDC identity provider + +Refer to the AWS documentation for guidance on [setting up an EKS cluster for OIDC authentication](https://docs.aws.amazon.com/eks/latest/userguide/authenticate-oidc-identity-provider.html). You can also refer to our [example configuration](https://github.com/hashicorp-education/learn-terraform-dynamic-credentials/tree/main/eks/trust). + +Refer to the GCP documentation for guidance on [setting up a GKE cluster for OIDC authentication](https://cloud.google.com/kubernetes-engine/docs/how-to/oidc). You can also refer to our [example configuration](https://github.com/hashicorp-education/learn-terraform-dynamic-credentials/tree/main/gke/trust). + +When inputting an "issuer URL", use the address of HCP Terraform (`https://app.terraform.io` _without_ a trailing slash) or the URL of your Terraform Enterprise instance. The value of "client ID" is your audience in OIDC terminology, and it should match the value of the `TFC_KUBERNETES_WORKLOAD_IDENTITY_AUDIENCE` environment variable in your workspace. + +The OIDC identity resolves authentication to the Kubernetes API, but it first requires authorization to interact with that API. So, you must bind RBAC roles to the OIDC identity in Kubernetes. + +You can use both "User" and "Group" subjects in your role bindings. For OIDC identities coming from TFC, the "User" value is formatted like so: `organization::project::workspace::run_phase:`. + +You can extract the "Group" value from the token claim you configured in your cluster OIDC configuration. For details on the structure of the HCP Terraform token, refer to [Workload Identity](/terraform/enterprise/workspaces/dynamic-provider-credentials/workload-identity-tokens). + +Below, we show an example of a `RoleBinding` for the HCP Terraform OIDC identity. + +```hcl +resource "kubernetes_cluster_role_binding_v1" "oidc_role" { + metadata { + name = "odic-identity" + } + + role_ref { + api_group = "rbac.authorization.k8s.io" + kind = "ClusterRole" + name = var.rbac_group_cluster_role + } + + // Option A - Bind RBAC roles to groups + // + // Groups are extracted from the token claim designated by 'rbac_group_oidc_claim' + // + subject { + api_group = "rbac.authorization.k8s.io" + kind = "Group" + name = var.tfc_organization_name + } + + // Option B - Bind RBAC roles to user identities + // + // Users are extracted from the 'sub' token claim. + // Plan and apply phases get assigned different users identities. + // For HCP Terraform tokens, the format of the user id is always the one described bellow. + // + subject { + api_group = "rbac.authorization.k8s.io" + kind = "User" + name = "${var.tfc_hostname}#organization:${var.tfc_organization_name}:project:${var.tfc_project_name}:workspace:${var.tfc_workspace_name}:run_phase:plan" + } + + subject { + api_group = "rbac.authorization.k8s.io" + kind = "User" + name = "${var.tfc_hostname}#organization:${var.tfc_organization_name}:project:${var.tfc_project_name}:workspace:${var.tfc_workspace_name}:run_phase:apply" + } +} +``` + +If binding with "User" subjects, be aware that plan and apply phases are assigned different identities, each requiring specific bindings. Meaning you can tailor permissions for each Terraform operation. Planning operations usually require "read-only" permissions, while apply operations also require "write" access. + +!> **Warning**: Always check, at minimum, the audience and the organization's name to prevent unauthorized access from other HCP Terraform organizations. + +## Configure HCP Terraform + +You must set certain environment variables in your HCP Terraform workspace to configure HCP Terraform to authenticate with Kubernetes or Helm using dynamic credentials. You can set these as workspace variables, or if you’d like to share one Kubernetes role across multiple workspaces, you can use a variable set. When you configure dynamic provider credentials with multiple provider configurations of the same type, use either a default variable or a tagged alias variable name for each provider configuration. Refer to [Specifying Multiple Configurations](#specifying-multiple-configurations) for more details. + +### Required Environment Variables + +| Variable | Value | Notes | +| ----------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `TFC_KUBERNETES_PROVIDER_AUTH`
`TFC_KUBERNETES_PROVIDER_AUTH[_TAG]`
_(Default variable not supported)_ | `true` | Requires **v1.14.0** or later if self-managing agents. Must be present and set to `true`, or HCP Terraform will not attempt to authenticate to Kubernetes. | +| `TFC_KUBERNETES_WORKLOAD_IDENTITY_AUDIENCE`
`TFC_KUBERNETES_WORKLOAD_IDENTITY_AUDIENCE[_TAG]`
`TFC_DEFAULT_KUBERNETES_WORKLOAD_IDENTITY_AUDIENCE` | The audience name in your cluster's OIDC configuration, such as `kubernetes`. | Requires **v1.14.0** or later if self-managing agents. | + +## Configure the provider + +The Kubernetes and Helm providers share the same schema of configuration attributes for the provider block. The example below illustrates using the Kubernetes provider but the same configuration applies to the Helm provider. + +Make sure that you are not using any of the other arguments or methods listed in the [authentication](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs#authentication) section of the provider documentation as these settings may interfere with dynamic provider credentials. The only allowed provider attributes are `host` and `cluster_ca_certificate`. + +### Single provider instance + +HCP Terraform automatically sets the `KUBE_TOKEN` environment variable and includes the workload identity token. + +The provider needs to be configured with the URL of the API endpoint using the `host` attribute (or `KUBE_HOST` environment variable). In most cases, the `cluster_ca_certificate` (or `KUBE_CLUSTER_CA_CERT_DATA` environment variable) is also required. + +#### Example Usage + +```hcl +provider "kubernetes" { + host = var.cluster-endpoint-url + cluster_ca_certificate = base64decode(var.cluster-endpoint-ca) +} +``` + +### Multiple aliases + +You can add additional variables to handle multiple distinct Kubernetes clusters, enabling you to use multiple [provider aliases](/terraform/language/providers/configuration#alias-multiple-provider-configurations) within the same workspace. You can configure each set of credentials independently, or use default values by configuring the variables prefixed with `TFC_DEFAULT_`. + +For more details, see [Specifying Multiple Configurations](/terraform/enterprise/workspaces/dynamic-provider-credentials/specifying-multiple-configurations). + +#### Required Terraform Variable + +To use additional configurations, add the following code to your Terraform configuration. This lets HCP Terraform supply variable values that you can then use to map authentication and configuration details to the correct provider blocks. + +```hcl +variable "tfc_kubernetes_dynamic_credentials" { + description = "Object containing Kubernetes dynamic credentials configuration" + type = object({ + default = object({ + token_path = string + }) + aliases = map(object({ + token_path = string + })) + }) +} +``` + +#### Example Usage + +```hcl +provider "kubernetes" { + alias = "ALIAS1" + host = var.alias1-endpoint-url + cluster_ca_certificate = base64decode(var.alias1-cluster-ca) + token = file(var.tfc_kubernetes_dynamic_credentials.aliases["ALIAS1"].token_path) +} + +provider "kubernetes" { + alias = "ALIAS2" + host = var.alias1-endpoint-url + cluster_ca_certificate = base64decode(var.alias1-cluster-ca) + token = file(var.tfc_kubernetes_dynamic_credentials.aliases["ALIAS2"].token_path) +} +``` + +The `tfc_kubernetes_dynamic_credentials` variable is also available to use for single provider configurations, instead of the `KUBE_TOKEN` environment variable. + +```hcl +provider "kubernetes" { + host = var.cluster-endpoint-url + cluster_ca_certificate = base64decode(var.cluster-endpoint-ca) + token = file(var.tfc_kubernetes_dynamic_credentials.default.token_path) +} +``` diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/workspaces/dynamic-provider-credentials/manual-generation.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/workspaces/dynamic-provider-credentials/manual-generation.mdx new file mode 100644 index 0000000000..ab6be885fb --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/workspaces/dynamic-provider-credentials/manual-generation.mdx @@ -0,0 +1,42 @@ +--- +page_title: Manually generate workload identity tokens in Terraform Enterprise +description: >- + Learn how to generate workload identity tokens to allow Terraform runs to + safely authenticate with custom workflows and providers that do not natively + support dynamic credentials. +source: terraform-docs-common +--- + +# Manually generate workload identity tokens + +~> **Important:** If you are self-hosting [HCP Terraform agents](/terraform/cloud-docs/agents), ensure your agents use [v1.7.0](/terraform/cloud-docs/agents/changelog#1-7-0-03-02-2023) or above. To use the latest dynamic credentials features, [upgrade your agents to the latest version](/terraform/cloud-docs/agents/changelog). + +If required for custom auth workflows or to perform auth with providers that are not natively supported by dynamic credentials, you can request that HCP Terraform inject a [workload identity token](/terraform/enterprise/workspaces/dynamic-provider-credentials/workload-identity-tokens) into the run environment for usage in agent hooks. + +## Configure HCP Terraform + +### Required Environment Variables + +You’ll need to set the following environment variable in your HCP Terraform workspace in order to have HCP Terraform inject a workload identity token into the run environment. You can set this as a workspace variable, or if you’d like to inject tokens with the same audience value across multiple workspaces, you can use a variable set. + +| Variable | Value | Notes | +| -------------------------------- | ------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `TFC_WORKLOAD_IDENTITY_AUDIENCE` | The desired value for the token’s audience. | Requires **v1.7.0** or later if self-managing agents. Must be present and set or HCP Terraform will not inject a workload identity token into the run environment. | + +### Generating Multiple Tokens + +~> **Important:** If you are self-hosting [HCP Terraform agents](/terraform/cloud-docs/agents), ensure your agents use [v1.12.0](/terraform/cloud-docs/agents/changelog#1-12-0-07-26-2023) or above. To use the latest dynamic credentials features, [upgrade your agents to the latest version](/terraform/cloud-docs/agents/changelog). + +You can generate multiple tokens if you want distinct audience values for different consumers of your workload identity tokens. For more details, see [Specifying Multiple Configurations](/terraform/enterprise/workspaces/dynamic-provider-credentials/specifying-multiple-configurations). + +You can generate multiple tokens by specifying additional variables in the following format: `TFC_WORKLOAD_IDENTITY_AUDIENCE_[YOUR_TAG_HERE]`. + +Your tag can only contain letters, numbers, and underscores and can not use reserved keywords. The following keywords are reserved: `TYPE`. + +Each additional audience variable you specify generates an additional workload identity token that HCP Terraform stores in variables with the format: `TFC_WORKLOAD_IDENTITY_TOKEN_[YOUR_TAG_HERE]`. + +## Configure Agent Hooks + +After you've set the `TFC_WORKLOAD_IDENTITY_AUDIENCE` variable, each plan and apply will have a `TFC_WORKLOAD_IDENTITY_TOKEN` variable available in the run environment, which contains a [workload identity token](/terraform/enterprise/workspaces/dynamic-provider-credentials/workload-identity-tokens). + +You can use this environment variable in custom agent hooks to enable custom auth workflows or to perform auth with providers which are not natively supported by dynamic credentials. diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/workspaces/dynamic-provider-credentials/specifying-multiple-configurations.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/workspaces/dynamic-provider-credentials/specifying-multiple-configurations.mdx new file mode 100644 index 0000000000..24a076a110 --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/workspaces/dynamic-provider-credentials/specifying-multiple-configurations.mdx @@ -0,0 +1,94 @@ +--- +page_title: Specify multiple dynamic credential configurations in Terraform Enterprise +description: >- + Specify multiple dynamic provider credential configurations for the same + workspace to create aliases for different provider configurations. +source: terraform-docs-common +--- + +# Specify multiple dynamic credential configurations + +~> **Important:** If you are self-hosting [HCP Terraform agents](/terraform/cloud-docs/agents), ensure your agents use [v1.12.0](/terraform/cloud-docs/agents/changelog#1-12-0-07-26-2023) or above. To use the latest dynamic credentials features, [upgrade your agents to the latest version](/terraform/cloud-docs/agents/changelog). + +You can create multiple dynamic credential configurations of the same provider in a workspace. + +Each configuration generates a distinct [workload identity token](/terraform/enterprise/workspaces/dynamic-provider-credentials/workload-identity-tokens), allowing you to create [aliases for different provider configurations](/terraform/language/providers/configuration#alias-multiple-provider-configurations) within the same workspace. You can specify unique audience values per configuration, and [manually generate multiple tokens](/terraform/enterprise/workspaces/dynamic-provider-credentials/manual-generation). + +Specifying multiple dynamic credential configurations in HCP Terraform builds on the existing method of providing each provider's environment variables to a workspace. The process requires mapping well-known authentication [input variables](/terraform/language/values/variables) to the correct providers. + +## Configure HCP Terraform + +You can specify additional dynamic credentials configurations by defining and appending a “tag” to the end of your existing required environment variables: `[DYNAMIC_CREDENTIALS_VAR_NAME]_[YOUR_TAG]`. + +Your tag can only contain letters, numbers, and underscores and can not use reserved keywords. The following keywords are reserved: `TYPE`. + +### Example + +Using [Vault's dynamic credentials](/terraform/enterprise/workspaces/dynamic-provider-credentials/vault-configuration) setup as an example, we can create additional configurations by setting new tagged variables that match Vault's [required environment variables](/terraform/enterprise/workspaces/dynamic-provider-credentials/vault-configuration#required-environment-variables). So, if you want to add a configuration with the tag `ALIAS1`, you must set environment variables for `TFC_VAULT_PROVIDER_AUTH_ALIAS1`, `TFC_VAULT_ADDR_ALIAS1`, and `TFC_VAULT_RUN_ROLE_ALIAS1`. + +### Default Values for Multiple Configurations + +Each environment variable has a corresponding default variable which you can use to share values across multiple configurations. In this way, you can set values common to multiple configurations a single time, rather than repeating them for each configuration. If you explicitly set the corresponding environment variable in addition to the default variable, the explicit value is given precedence. + +#### Example + +In the [example above](/terraform/enterprise/workspaces/dynamic-provider-credentials/specifying-multiple-configurations#example), if each of your Vault configurations used the same underlying Vault instance, you could define the Vault address a single time using the `TFC_DEFAULT_VAULT_ADDR` variable, and omit `TFC_VAULT_ADDR` and `TFC_VAULT_ADDR_ALIAS1`. If you add a third Vault configuration for a different Vault instance with the tag `ALIAS2`, you could set the variable `TFC_VAULT_ADDR_ALIAS2` to override the Vault address specifically for the `ALIAS2` configuration. + +## Configure Terraform Code + +Each supported provider has input variables you must declare in your Terraform code to use dynamic credentials with that provider. Each dynamic provider's documentation page lists the required variables for that provider. HCP Terraform provides values for these variables during runs, which you can use to authenticate HCP Terraform with providers using dynamic credentials. + +Use the input variable values that HCP Terraform provides to map configuration values to the correct provider blocks. Authentication information for the default provider exists in a variable's top-level `default` object, while each additional configuration exists under a variable's `aliases` map. HCP Terraform generates the keys of the `aliases` map based on the tag you define in your HCP Terraform configuration. + +~> **Important:** If you add additional configurations to a workspace, you need to manually map authentication information for all providers _including_ the default provider. + +### Example + +Continuing from the [example above](/terraform/enterprise/workspaces/dynamic-provider-credentials/specifying-multiple-configurations#example), after setting the required environment variables for your provider, [add the following code to your Terraform configuration](/terraform/enterprise/workspaces/dynamic-provider-credentials/vault-configuration#required-terraform-variable). This lets HCP Terraform supply variable values that you can use to map authentication and configuration details to the correct provider blocks. + +```hcl +variable "tfc_vault_dynamic_credentials" { + description = "Object containing Vault dynamic credentials configuration" + type = object({ + default = object({ + token_filename = string + address = string + namespace = string + ca_cert_file = string + }) + aliases = map(object({ + token_filename = string + address = string + namespace = string + ca_cert_file = string + })) + }) +} +``` + +Use the above object to map authentication information to the correct provider block. For this example, index into the `aliases` map with your alias's tag (`ALIAS1`) and the `default` provider object. + +```hcl +provider "vault" { + // Set this to true as HCP Terraform manages the token lifecycle + skip_child_token = true + address = var.tfc_vault_dynamic_credentials.default.address + namespace = var.tfc_vault_dynamic_credentials.default.namespace + + auth_login_token_file { + filename = var.tfc_vault_dynamic_credentials.default.token_filename + } +} + +provider "vault" { + // Set this to true as HCP Terraform manages the token lifecycle + skip_child_token = true + alias = "ALIAS1" + address = var.tfc_vault_dynamic_credentials.aliases["ALIAS1"].address + namespace = var.tfc_vault_dynamic_credentials.aliases["ALIAS1"].namespace + + auth_login_token_file { + filename = var.tfc_vault_dynamic_credentials.aliases["ALIAS1"].token_filename + } +} +``` diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/workspaces/dynamic-provider-credentials/vault-backed/aws-configuration.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/workspaces/dynamic-provider-credentials/vault-backed/aws-configuration.mdx new file mode 100644 index 0000000000..31bcda6cda --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/workspaces/dynamic-provider-credentials/vault-backed/aws-configuration.mdx @@ -0,0 +1,135 @@ +--- +page_title: >- + Use Vault-backed dynamic credentials with the AWS provider in Terraform + Enterprise +description: >- + Use OpenID Connect and Vault to get short-term credentials for the AWS + Terraform provider in your Terraform Enterprise runs. +source: terraform-docs-common +--- + +# Use Vault-backed dynamic credentials with the AWS provider + +~> **Important:** If you are self-hosting [HCP Terraform agents](/terraform/cloud-docs/agents), ensure your agents use [v1.8.0](/terraform/cloud-docs/agents/changelog#1-8-0-04-18-2023) or above. To use the latest dynamic credentials features, [upgrade your agents to the latest version](/terraform/cloud-docs/agents/changelog). + +You can use HCP Terraform’s native OpenID Connect integration with Vault to use [Vault-backed dynamic credentials](/terraform/enterprise/workspaces/dynamic-provider-credentials/vault-backed) with the AWS provider in your HCP Terraform runs. Configuring the integration requires the following steps: + +1. **[Configure Vault Dynamic Provider Credentials](#configure-vault-dynamic-provider-credentials)**: Set up a trust configuration between Vault and HCP Terraform, create Vault roles and policies for your HCP Terraform workspaces, and add environment variables to those workspaces. +2. **[Configure the Vault AWS Secrets Engine](#configure-vault-aws-secrets-engine)**: Set up the AWS secrets engine in your Vault instance. +3. **[Configure HCP Terraform](#configure-hcp-terraform)**: Add additional environment variables to the HCP Terraform workspaces where you want to use Vault-Backed Dynamic Credentials. +4. **[Configure Terraform Providers](#configure-terraform-providers)**: Configure your Terraform providers to work with Vault-backed dynamic credentials. + +Once you complete this setup, HCP Terraform automatically authenticates with AWS via Vault-generated credentials during the plan and apply phase of each run. The AWS provider's authentication is only valid for the length of the plan or apply phase. + +## Configure Vault Dynamic Provider Credentials + +You must first set up Vault dynamic provider credentials before you can use Vault-backed dynamic credentials. This includes setting up the JWT auth backend in Vault, configuring trust between HCP Terraform and Vault, and populating the required environment variables in your HCP Terraform workspace. + +[See the setup instructions for Vault dynamic provider credentials](/terraform/enterprise/workspaces/dynamic-provider-credentials/vault-configuration). + +# Configure Vault AWS Secrets Engine + +Follow the instructions in the Vault documentation for [setting up the AWS secrets engine in your Vault instance](/vault/docs/secrets/aws). You can also do this configuration through Terraform. Refer to our [example Terraform configuration](https://github.com/hashicorp/terraform-dynamic-credentials-setup-examples/tree/main/vault-backed/aws). + +~> **Important**: carefully consider the limitations and differences between each supported credential type in the AWS secrets engine. These limitations carry over to HCP Terraform’s usage of these credentials for authenticating the AWS provider. + +## Configure HCP Terraform + +Next, you need to set certain environment variables in your HCP Terraform workspace to authenticate HCP Terraform with AWS using Vault-backed dynamic credentials. These variables are in addition to those you previously set while configuring [Vault dynamic provider credentials](#configure-vault-dynamic-provider-credentials). You can add these as workspace variables or as a [variable set](/terraform/enterprise/workspaces/variables/managing-variables#variable-sets). When you configure dynamic provider credentials with multiple provider configurations of the same type, use either a default variable or a tagged alias variable name for each provider configuration. Refer to [Specifying Multiple Configurations](#specifying-multiple-configurations) for more details. + +### Common Environment Variables + +The below variables apply to all AWS auth types. + +#### Required Common Environment Variables + +| Variable | Value | Notes | +| ----------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `TFC_VAULT_BACKED_AWS_AUTH`
`TFC_VAULT_BACKED_AWS_AUTH[_TAG]`
_(Default variable not supported)_ | `true` | Requires **v1.8.0** or later if self-managing agents. Must be present and set to `true`, or HCP Terraform will not attempt to authenticate with AWS. | +| `TFC_VAULT_BACKED_AWS_AUTH_TYPE`
`TFC_VAULT_BACKED_AWS_AUTH_TYPE[_TAG]`
`TFC_DEFAULT_VAULT_BACKED_AWS_AUTH_TYPE` | Specifies the type of authentication to perform with AWS. Must be one of the following: `iam_user`, `assumed_role`, or `federation_token`. | Requires **v1.8.0** or later if self-managing agents. | +| `TFC_VAULT_BACKED_AWS_RUN_VAULT_ROLE`
`TFC_VAULT_BACKED_AWS_RUN_VAULT_ROLE[_TAG]`
`TFC_DEFAULT_VAULT_BACKED_AWS_RUN_VAULT_ROLE` | The role to use in Vault. | Requires **v1.8.0** or later if self-managing agents. Optional if `TFC_VAULT_BACKED_AWS_PLAN_VAULT_ROLE` and `TFC_VAULT_BACKED_AWS_APPLY_VAULT_ROLE` are both provided. These variables are described [below](#optional-common-environment-variables). | + +#### Optional Common Environment Variables + +You may need to set these variables, depending on your use case. + +| Variable | Value | Notes | +| ----------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `TFC_VAULT_BACKED_AWS_MOUNT_PATH`
`TFC_VAULT_BACKED_AWS_MOUNT_PATH[_TAG]`
`TFC_DEFAULT_VAULT_BACKED_AWS_MOUNT_PATH` | The mount path of the AWS secrets engine in Vault. | Requires **v1.8.0** or later if self-managing agents. Defaults to `aws`. | +| `TFC_VAULT_BACKED_AWS_PLAN_VAULT_ROLE`
`TFC_VAULT_BACKED_AWS_PLAN_VAULT_ROLE[_TAG]`
`TFC_DEFAULT_VAULT_BACKED_AWS_PLAN_VAULT_ROLE` | The Vault role to use the plan phase of a run. | Requires **v1.8.0** or later if self-managing agents. Will fall back to the value of `TFC_VAULT_BACKED_AWS_RUN_VAULT_ROLE` if not provided. | +| `TFC_VAULT_BACKED_AWS_APPLY_VAULT_ROLE`
`TFC_VAULT_BACKED_AWS_APPLY_VAULT_ROLE[_TAG]`
`TFC_DEFAULT_VAULT_BACKED_AWS_APPLY_VAULT_ROLE` | The Vault role to use for the apply phase of a run. | Requires **v1.8.0** or later if self-managing agents. Will fall back to the value of `TFC_VAULT_BACKED_AWS_RUN_VAULT_ROLE` if not provided. | +| `TFC_VAULT_BACKED_AWS_SLEEP_SECONDS`
`TFC_VAULT_BACKED_AWS_SLEEP_SECONDS[_TAG]`
`TFC_DEFAULT_VAULT_BACKED_AWS_SLEEP_SECONDS` | The amount of time to wait, in seconds, after obtaining temporary credentials from Vault. e.g., `30` for 30 seconds. Must be 1500 seconds (25 minutes) or less. | Requires **v1.12.0** or later if self-managing agents. Can be used to mitigate eventual consistency issues in AWS when using the `iam_user` auth type. | +| `TFC_VAULT_BACKED_AWS_VAULT_CONFIG`
`TFC_VAULT_BACKED_AWS_VAULT_CONFIG[_TAG]`
`TFC_DEFAULT_VAULT_BACKED_AWS_VAULT_CONFIG` | The name of the non-default Vault configuration for workspaces using [multiple Vault configurations](/terraform/enterprise/workspaces/dynamic-provider-credentials/specifying-multiple-configurations). | Requires **v1.12.0** or later if self-managing agents. Will fall back to using the default Vault configuration if not provided. | + +### Assumed Role Specific Environment Variables + +These environment variables are only valid if the `TFC_VAULT_BACKED_AWS_AUTH_TYPE` is `assumed_role`. + +#### Required Assumed Role Specific Environment Variables + +| Variable | Value | Notes | +| ----------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `TFC_VAULT_BACKED_AWS_RUN_ROLE_ARN`
`TFC_VAULT_BACKED_AWS_RUN_ROLE_ARN[_TAG]`
`TFC_DEFAULT_VAULT_BACKED_AWS_RUN_ROLE_ARN` | The ARN of the role to assume in AWS. | Requires **v1.8.0** or later if self-managing agents. Optional if `TFC_VAULT_BACKED_AWS_PLAN_ROLE_ARN` and `TFC_VAULT_BACKED_AWS_APPLY_ROLE_ARN` are both provided. These variables are described [below](#optional-assume-role-specific-environment-variables). | + +#### Optional Assumed Role Specific Environment Variables + +You may need to set these variables, depending on your use case. + +| Variable | Value | Notes | +| ----------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | +| `TFC_VAULT_BACKED_AWS_PLAN_ROLE_ARN`
`TFC_VAULT_BACKED_AWS_PLAN_ROLE_ARN[_TAG]`
`TFC_DEFAULT_VAULT_BACKED_AWS_PLAN_ROLE_ARN` | The ARN of the role to use for the plan phase of a run. | Requires **v1.8.0** or later if self-managing agents. Will fall back to the value of `TFC_VAULT_BACKED_AWS_RUN_ROLE_ARN` if not provided. | +| `TFC_VAULT_BACKED_AWS_APPLY_ROLE_ARN`
`TFC_VAULT_BACKED_AWS_APPLY_ROLE_ARN[_TAG]`
`TFC_DEFAULT_VAULT_BACKED_AWS_APPLY_ROLE_ARN` | The ARN of the role to use for the apply phase of a run. | Requires **v1.8.0** or later if self-managing agents. Will fall back to the value of `TFC_VAULT_BACKED_AWS_RUN_ROLE_ARN` if not provided. | + +## Configure Terraform Providers + +The final step is to directly configure your AWS and Vault providers. + +### Configure the AWS Provider + +Ensure you pass a value for the `region` argument in your AWS provider configuration block or set the `AWS_REGION` variable in your workspace. + +Ensure you are not using any of the arguments or methods mentioned in the [authentication and configuration](https://registry.terraform.io/providers/hashicorp/aws/latest/docs#authentication-and-configuration) section of the provider documentation. Otherwise, these settings may interfere with dynamic provider credentials. + +### Configure the Vault Provider + +If you were previously using the Vault provider to authenticate the AWS provider, remove any existing usage of the AWS secrets engine from your Terraform Code. +This includes the [`vault_aws_access_credentials`](https://registry.terraform.io/providers/hashicorp/vault/latest/docs/data-sources/aws_access_credentials) data source and any instances of [`vault_generic_secret`](https://registry.terraform.io/providers/hashicorp/vault/latest/docs/data-sources/generic_secret) you previously used to generate AWS credentials. + +### Specifying Multiple Configurations + +~> **Important:** If you are self-hosting [HCP Terraform agents](/terraform/cloud-docs/agents), ensure your agents use [v1.12.0](/terraform/cloud-docs/agents/changelog#1-12-0-07-26-2023) or above. To use the latest dynamic credentials features, [upgrade your agents to the latest version](/terraform/cloud-docs/agents/changelog). + +You can add additional variables to handle multiple distinct Vault-backed AWS setups, enabling you to use multiple [provider aliases](/terraform/language/providers/configuration#alias-multiple-provider-configurations) within the same workspace. You can configure each set of credentials independently, or use default values by configuring the variables prefixed with `TFC_DEFAULT_`. + +For more details, see [Specifying Multiple Configurations](/terraform/enterprise/workspaces/dynamic-provider-credentials/specifying-multiple-configurations). + +#### Required Terraform Variable + +To use additional configurations, add the following code to your Terraform configuration. This lets HCP Terraform supply variable values that you can then use to map authentication and configuration details to the correct provider blocks. + +```hcl +variable "tfc_vault_backed_aws_dynamic_credentials" { + description = "Object containing Vault-backed AWS dynamic credentials configuration" + type = object({ + default = object({ + shared_credentials_file = string + }) + aliases = map(object({ + shared_credentials_file = string + })) + }) +} +``` + +#### Example Usage + +```hcl +provider "aws" { + shared_credentials_files = [var.tfc_vault_backed_aws_dynamic_credentials.default.shared_credentials_file] +} + +provider "aws" { + alias = "ALIAS1" + shared_credentials_files = [var.tfc_vault_backed_aws_dynamic_credentials.aliases["ALIAS1"].shared_credentials_file] +} +``` diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/workspaces/dynamic-provider-credentials/vault-backed/azure-configuration.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/workspaces/dynamic-provider-credentials/vault-backed/azure-configuration.mdx new file mode 100644 index 0000000000..138e30f69b --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/workspaces/dynamic-provider-credentials/vault-backed/azure-configuration.mdx @@ -0,0 +1,152 @@ +--- +page_title: >- + Use Vault-backed dynamic credentials with the Azure provider in Terraform + Enterprise +description: >- + Use OpenID Connect and Vault to get short-term credentials for the Azure + Terraform provider in your Terraform Enterprise runs. +source: terraform-docs-common +--- + +# Use Vault-backed dynamic credentials with the Azure provider + +~> **Important:** If you are self-hosting [HCP Terraform agents](/terraform/cloud-docs/agents), ensure your agents use [v1.8.0](/terraform/cloud-docs/agents/changelog#1-8-0-04-18-2023) or above. To use the latest dynamic credentials features, [upgrade your agents to the latest version](/terraform/cloud-docs/agents/changelog). + +You can use HCP Terraform’s native OpenID Connect integration with Vault to use [Vault-backed dynamic credentials](/terraform/enterprise/workspaces/dynamic-provider-credentials/vault-backed) with the Azure provider in your HCP Terraform runs. Configuring the integration requires the following steps: + +1. **[Configure Vault Dynamic Provider Credentials](#configure-vault-dynamic-provider-credentials)**: Set up a trust configuration between Vault and HCP Terraform, create Vault roles and policies for your HCP Terraform workspaces, and add environment variables to those workspaces. +2. **[Configure the Vault Azure Secrets Engine](#configure-vault-azure-secrets-engine)**: Set up the Azure secrets engine in your Vault instance. +3. **[Configure HCP Terraform](#configure-hcp-terraform)**: Add additional environment variables to the HCP Terraform workspaces where you want to use Vault-Backed Dynamic Credentials. +4. **[Configure Terraform Providers](#configure-terraform-providers)**: Configure your Terraform providers to work with Vault-backed Dynamic Credentials. + +Once you complete this setup, HCP Terraform automatically authenticates with Azure via Vault-generated credentials during the plan and apply phase of each run. The Azure provider's authentication is only valid for the length of the plan or apply phase. + +## Configure Vault Dynamic Provider Credentials + +You must first set up Vault dynamic provider credentials before you can use Vault-backed dynamic credentials. This includes setting up the JWT auth backend in Vault, configuring trust between HCP Terraform and Vault, and populating the required environment variables in your HCP Terraform workspace. + +[See the setup instructions for Vault dynamic provider credentials](/terraform/enterprise/workspaces/dynamic-provider-credentials/vault-configuration). + +# Configure Vault Azure Secrets Engine + +Follow the instructions in the Vault documentation for [setting up the Azure secrets engine in your Vault instance](/vault/docs/secrets/azure). You can also do this configuration through Terraform. Refer to our [example Terraform configuration](https://github.com/hashicorp/terraform-dynamic-credentials-setup-examples/tree/main/vault-backed/azure). + +## Configure HCP Terraform + +Next, you need to set certain environment variables in your HCP Terraform workspace to authenticate HCP Terraform with Azure using Vault-backed dynamic credentials. These variables are in addition to those you previously set while configuring [Vault dynamic provider credentials](#configure-vault-dynamic-provider-credentials). You can add these as workspace variables or as a [variable set](/terraform/enterprise/workspaces/variables/managing-variables#variable-sets). When you configure dynamic provider credentials with multiple provider configurations of the same type, use either a default variable or a tagged alias variable name for each provider configuration. Refer to [Specifying Multiple Configurations](#specifying-multiple-configurations) for more details. + +### Required Environment Variables + +| Variable | Value | Notes | +| ----------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `TFC_VAULT_BACKED_AZURE_AUTH`
`TFC_VAULT_BACKED_AZURE_AUTH[_TAG]`
_(Default variable not supported)_ | `true` | Requires **v1.8.0** or later if self-managing agents. Must be present and set to `true`, or HCP Terraform will not attempt to authenticate with Azure. | +| `TFC_VAULT_BACKED_AZURE_RUN_VAULT_ROLE`
`TFC_VAULT_BACKED_AZURE_RUN_VAULT_ROLE[_TAG]`
`TFC_DEFAULT_VAULT_BACKED_AZURE_RUN_VAULT_ROLE` | The role to use in Vault. | Requires **v1.8.0** or later if self-managing agents. Optional if `TFC_VAULT_BACKED_AZURE_PLAN_VAULT_ROLE` and `TFC_VAULT_BACKED_AZURE_APPLY_VAULT_ROLE` are both provided. These variables are described [below](#optional-environment-variables). | + +### Optional Environment Variables + +You may need to set these variables, depending on your use case. + +| Variable | Value | Notes | +| ----------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- | +| `TFC_VAULT_BACKED_AZURE_MOUNT_PATH`
`TFC_VAULT_BACKED_AZURE_MOUNT_PATH[_TAG]`
`TFC_DEFAULT_VAULT_BACKED_AZURE_MOUNT_PATH` | The mount path of the Azure secrets engine in Vault. | Requires **v1.8.0** or later if self-managing agents. Defaults to `azure`. | +| `TFC_VAULT_BACKED_AZURE_PLAN_VAULT_ROLE`
`TFC_VAULT_BACKED_AZURE_PLAN_VAULT_ROLE[_TAG]`
`TFC_DEFAULT_VAULT_BACKED_AZURE_PLAN_VAULT_ROLE` | The Vault role to use the plan phase of a run. | Requires **v1.8.0** or later if self-managing agents. Will fall back to the value of `TFC_VAULT_BACKED_AZURE_RUN_VAULT_ROLE` if not provided. | +| `TFC_VAULT_BACKED_AZURE_APPLY_VAULT_ROLE`
`TFC_VAULT_BACKED_AZURE_APPLY_VAULT_ROLE[_TAG]`
`TFC_DEFAULT_VAULT_BACKED_AZURE_APPLY_VAULT_ROLE` | The Vault role to use for the apply phase of a run. | Requires **v1.8.0** or later if self-managing agents. Will fall back to the value of `TFC_VAULT_BACKED_AZURE_RUN_VAULT_ROLE` if not provided. | +| `TFC_VAULT_BACKED_AZURE_SLEEP_SECONDS`
`TFC_VAULT_BACKED_AZURE_SLEEP_SECONDS[_TAG]`
`TFC_DEFAULT_VAULT_BACKED_AZURE_SLEEP_SECONDS` | The amount of time to wait, in seconds, after obtaining temporary credentials from Vault. e.g., `30` for 30 seconds. Must be 1500 seconds (25 minutes) or less. | Requires **v1.12.0** or later if self-managing agents. Can be used to mitigate eventual consistency issues in Azure. | +| `TFC_VAULT_BACKED_AZURE_VAULT_CONFIG`
`TFC_VAULT_BACKED_AZURE_VAULT_CONFIG[_TAG]`
`TFC_DEFAULT_VAULT_BACKED_AZURE_VAULT_CONFIG` | The name of the non-default Vault configuration for workspaces using [multiple Vault configurations](/terraform/enterprise/workspaces/dynamic-provider-credentials/specifying-multiple-configurations). | Requires **v1.12.0** or later if self-managing agents. Will fall back to using the default Vault configuration if not provided. | + +## Configure Terraform Providers + +The final step is to directly configure your Azure and Vault providers. + +### Configure the AzureRM or Microsoft Entra ID + +Ensure you pass a value for the `subscription_id` and `tenant_id` arguments in your provider configuration block or set the `ARM_SUBSCRIPTION_ID` and `ARM_TENANT_ID` variables in your workspace. + +Do not set values for `client_id`, `use_oidc`, or `oidc_token` in your provider configuration block. Additionally, do not set variable values for `ARM_CLIENT_ID`, `ARM_USE_OIDC`, or `ARM_OIDC_TOKEN`. + +### Configure the Vault Provider + +If you were previously using the Vault provider to authenticate the Azure provider, remove any existing usage of the Azure secrets engine from your Terraform Code. +This includes the [`vault_azure_access_credentials`](https://registry.terraform.io/providers/hashicorp/vault/latest/docs/data-sources/azure_access_credentials) data source and any instances of [`vault_generic_secret`](https://registry.terraform.io/providers/hashicorp/vault/latest/docs/data-sources/generic_secret) you previously used to generate Azure credentials. + +### Specifying Multiple Configurations + +~> **Important:** Ensure you are using version **3.60.0** or later of the **AzureRM provider** and version **2.43.0** or later of the **Microsoft Entra ID provider** (previously Azure Active Directory) as required functionality was introduced in these provider versions. + +~> **Important:** If you are self-hosting [HCP Terraform agents](/terraform/cloud-docs/agents), ensure your agents use [v1.12.0](/terraform/cloud-docs/agents/changelog#1-12-0-07-26-2023) or above. To use the latest dynamic credentials features, [upgrade your agents to the latest version](/terraform/cloud-docs/agents/changelog). + +You can add additional variables to handle multiple distinct Vault-backed Azure setups, enabling you to use multiple [provider aliases](/terraform/language/providers/configuration#alias-multiple-provider-configurations) within the same workspace. You can configure each set of credentials independently, or use default values by configuring the variables prefixed with `TFC_DEFAULT_`. + +For more details, see [Specifying Multiple Configurations](/terraform/enterprise/workspaces/dynamic-provider-credentials/specifying-multiple-configurations). + +#### Required Terraform Variable + +To use additional configurations, add the following code to your Terraform configuration. This lets HCP Terraform supply variable values that you can then use to map authentication and configuration details to the correct provider blocks. + +```hcl +variable "tfc_vault_backed_azure_dynamic_credentials" { + description = "Object containing Vault-backed Azure dynamic credentials configuration" + type = object({ + default = object({ + client_id_file_path = string + client_secret_file_path = string + }) + aliases = map(object({ + client_id_file_path = string + client_secret_file_path = string + })) + }) +} +``` + +#### Example Usage + +##### AzureRM Provider + +```hcl +provider "azurerm" { + features {} + // use_cli should be set to false to yield more accurate error messages on auth failure. + use_cli = false + client_id_file_path = var.tfc_vault_backed_azure_dynamic_credentials.default.client_id_file_path + client_secret_file_path = var.tfc_vault_backed_azure_dynamic_credentials.default.client_secret_file_path + subscription_id = "00000000-0000-0000-0000-000000000000" + tenant_id = "10000000-0000-0000-0000-000000000000" +} + +provider "azurerm" { + features {} + // use_cli should be set to false to yield more accurate error messages on auth failure. + use_cli = false + alias = "ALIAS1" + client_id_file_path = var.tfc_vault_backed_azure_dynamic_credentials.aliases["ALIAS1"].client_id_file_path + client_secret_file_path = var.tfc_vault_backed_azure_dynamic_credentials.aliases["ALIAS1"].client_secret_file_path + subscription_id = "00000000-0000-0000-0000-000000000000" + tenant_id = "20000000-0000-0000-0000-000000000000" +} +``` + +##### Microsoft Entra ID Provider (previously AzureAD) + +```hcl +provider "azuread" { + features {} + // use_cli should be set to false to yield more accurate error messages on auth failure. + use_cli = false + client_id_file_path = var.tfc_vault_backed_azure_dynamic_credentials.default.client_id_file_path + client_secret_file_path = var.tfc_vault_backed_azure_dynamic_credentials.default.client_secret_file_path + subscription_id = "00000000-0000-0000-0000-000000000000" + tenant_id = "10000000-0000-0000-0000-000000000000" +} + +provider "azuread" { + features {} + // use_cli should be set to false to yield more accurate error messages on auth failure. + use_cli = false + alias = "ALIAS1" + client_id_file_path = var.tfc_vault_backed_azure_dynamic_credentials.aliases["ALIAS1"].client_id_file_path + client_secret_file_path = var.tfc_vault_backed_azure_dynamic_credentials.aliases["ALIAS1"].client_secret_file_path + subscription_id = "00000000-0000-0000-0000-000000000000" + tenant_id = "20000000-0000-0000-0000-000000000000" +} +``` diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/workspaces/dynamic-provider-credentials/vault-backed/gcp-configuration.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/workspaces/dynamic-provider-credentials/vault-backed/gcp-configuration.mdx new file mode 100644 index 0000000000..752c2887d9 --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/workspaces/dynamic-provider-credentials/vault-backed/gcp-configuration.mdx @@ -0,0 +1,167 @@ +--- +page_title: >- + Use Vault-backed dynamic credentials with the GCP provider in Terraform + Enterprise +description: >- + Use OpenID Connect and Vault to get short-term credentials for the GCP + Terraform provider in your Terraform Enterprise runs. +source: terraform-docs-common +--- + +# Use Vault-backed dynamic credentials with the GCP provider + +~> **Important:** If you are self-hosting [HCP Terraform agents](/terraform/cloud-docs/agents), ensure your agents use [v1.8.0](/terraform/cloud-docs/agents/changelog#1-8-0-04-18-2023) or above. To use the latest dynamic credentials features, [upgrade your agents to the latest version](/terraform/cloud-docs/agents/changelog). + +You can use HCP Terraform’s native OpenID Connect integration with Vault to use [Vault-backed dynamic credentials](/terraform/enterprise/workspaces/dynamic-provider-credentials/vault-backed) with the GCP provider in your HCP Terraform runs. Configuring the integration requires the following steps: + +1. **[Configure Vault Dynamic Provider Credentials](#configure-vault-dynamic-provider-credentials)**: Set up a trust configuration between Vault and HCP Terraform, create Vault roles and policies for your HCP Terraform workspaces, and add environment variables to those workspaces. +2. **[Configure the Vault GCP Secrets Engine](#configure-vault-gcp-secrets-engine)**: Set up the GCP secrets engine in your Vault instance. +3. **[Configure HCP Terraform](#configure-hcp-terraform)**: Add additional environment variables to the HCP Terraform workspaces where you want to use Vault-Backed Dynamic Credentials. +4. **[Configure Terraform Providers](#configure-terraform-providers)**: Configure your Terraform providers to work with Vault-backed dynamic credentials. + +Once you complete this setup, HCP Terraform automatically authenticates with GCP via Vault-generated credentials during the plan and apply phase of each run. The GCP provider's authentication is only valid for the length of the plan or apply phase. + +## Configure Vault Dynamic Provider Credentials + +You must first set up Vault dynamic provider credentials before you can use Vault-backed dynamic credentials. This includes setting up the JWT auth backend in Vault, configuring trust between HCP Terraform and Vault, and populating the required environment variables in your HCP Terraform workspace. + +[See the setup instructions for Vault dynamic provider credentials](/terraform/enterprise/workspaces/dynamic-provider-credentials/vault-configuration). + +# Configure Vault GCP Secrets Engine + +Follow the instructions in the Vault documentation for [setting up the GCP secrets engine in your Vault instance](/vault/docs/secrets/gcp). You can also do this configuration through Terraform. Refer to our [example Terraform configuration](https://github.com/hashicorp/terraform-dynamic-credentials-setup-examples/tree/main/vault-backed/gcp). + +~> **Important**: carefully consider the limitations and differences between each supported credential type in the GCP secrets engine. These limitations carry over to HCP Terraform’s usage of these credentials for authenticating the GCP provider. + +## Configure HCP Terraform + +Next, you need to set certain environment variables in your HCP Terraform workspace to authenticate HCP Terraform with GCP using Vault-backed dynamic credentials. These variables are in addition to those you previously set while configuring [Vault dynamic provider credentials](#configure-vault-dynamic-provider-credentials). You can add these as workspace variables or as a [variable set](/terraform/enterprise/workspaces/variables/managing-variables#variable-sets). When you configure dynamic provider credentials with multiple provider configurations of the same type, use either a default variable or a tagged alias variable name for each provider configuration. Refer to [Specifying Multiple Configurations](#specifying-multiple-configurations) for more details. + +### Common Environment Variables + +The below variables apply to all GCP auth types. + +#### Required Common Environment Variables + +| Variable | Value | Notes | +| -------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | +| `TFC_VAULT_BACKED_GCP_AUTH`
`TFC_VAULT_BACKED_GCP_AUTH[_TAG]`
_(Default variable not supported)_ | `true` | Requires **v1.8.0** or later if self-managing agents. Must be present and set to `true`, or HCP Terraform will not attempt to authenticate with GCP. | +| `TFC_VAULT_BACKED_GCP_AUTH_TYPE`
`TFC_VAULT_BACKED_GCP_AUTH_TYPE[_TAG]`
`TFC_DEFAULT_VAULT_BACKED_GCP_AUTH_TYPE` | Specifies the type of authentication to perform with GCP. Must be one of the following: `roleset/access_token`, `roleset/service_account_key`, `static_account/access_token`, or `static_account/service_account_key`. | Requires **v1.8.0** or later if self-managing agents. | + +#### Optional Common Environment Variables + +You may need to set these variables, depending on your use case. + +| Variable | Value | Notes | +| ----------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- | +| `TFC_VAULT_BACKED_GCP_MOUNT_PATH`
`TFC_VAULT_BACKED_GCP_MOUNT_PATH[_TAG]`
`TFC_DEFAULT_VAULT_BACKED_GCP_MOUNT_PATH` | The mount path of the GCP secrets engine in Vault. | Requires **v1.8.0** or later if self-managing agents. Defaults to `gcp`. | +| `TFC_VAULT_BACKED_GCP_VAULT_CONFIG`
`TFC_VAULT_BACKED_GCP_VAULT_CONFIG[_TAG]`
`TFC_DEFAULT_VAULT_BACKED_GCP_VAULT_CONFIG` | The name of the non-default Vault configuration for workspaces using [multiple Vault configurations](/terraform/enterprise/workspaces/dynamic-provider-credentials/specifying-multiple-configurations). | Requires **v1.12.0** or later if self-managing agents. Will fall back to using the default Vault configuration if not provided. | + +### Roleset Specific Environment Variables + +These environment variables are only valid if the `TFC_VAULT_BACKED_GCP_AUTH_TYPE` is `roleset/access_token` or `roleset/service_account_key`. + +#### Required Roleset Specific Environment Variables + +| Variable | Value | Notes | +| -------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `TFC_VAULT_BACKED_GCP_RUN_VAULT_ROLESET`
`TFC_VAULT_BACKED_GCP_RUN_VAULT_ROLESET[_TAG]`
`TFC_DEFAULT_VAULT_BACKED_GCP_RUN_VAULT_ROLESET` | The roleset to use in Vault. | Requires **v1.8.0** or later if self-managing agents. Optional if `TFC_VAULT_BACKED_GCP_PLAN_VAULT_ROLESET` and `TFC_VAULT_BACKED_GCP_APPLY_VAULT_ROLESET` are both provided. These variables are described [below](#optional-roleset-specific-environment-variables). | + +#### Optional Roleset Specific Environment Variables + +You may need to set these variables, depending on your use case. + +| Variable | Value | Notes | +| -------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------- | +| `TFC_VAULT_BACKED_GCP_PLAN_VAULT_ROLESET`
`TFC_VAULT_BACKED_GCP_PLAN_VAULT_ROLESET[_TAG]`
`TFC_DEFAULT_VAULT_BACKED_GCP_PLAN_VAULT_ROLESET` | The roleset to use for the plan phase of a run. | Requires **v1.8.0** or later if self-managing agents. Will fall back to the value of `TFC_VAULT_BACKED_GCP_RUN_VAULT_ROLESET` if not provided. | +| `TFC_VAULT_BACKED_GCP_APPLY_VAULT_ROLESET`
`TFC_VAULT_BACKED_GCP_APPLY_VAULT_ROLESET[_TAG]`
`TFC_DEFAULT_VAULT_BACKED_GCP_APPLY_VAULT_ROLESET` | The roleset to use for the apply phase of a run. | Requires **v1.8.0** or later if self-managing agents. Will fall back to the value of `TFC_VAULT_BACKED_GCP_RUN_VAULT_ROLESET` if not provided. | + +### Static Account Specific Environment Variables + +These environment variables are only valid if the `TFC_VAULT_BACKED_GCP_AUTH_TYPE` is `static_account/access_token` or `static_account/service_account_key`. + +#### Required Static Account Specific Environment Variables + +| Variable | Value | Notes | +| ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `TFC_VAULT_BACKED_GCP_RUN_VAULT_STATIC_ACCOUNT`
`TFC_VAULT_BACKED_GCP_RUN_VAULT_STATIC_ACCOUNT[_TAG]`
`TFC_DEFAULT_VAULT_BACKED_GCP_RUN_VAULT_STATIC_ACCOUNT` | The static account to use in Vault. | Requires **v1.8.0** or later if self-managing agents. Optional if `TFC_VAULT_BACKED_GCP_PLAN_VAULT_STATIC_ACCOUNT` and `TFC_VAULT_BACKED_GCP_APPLY_VAULT_STATIC_ACCOUNT` are both provided. These variables are described [below](#optional-static-account-specific-environment-variables). | + +#### Optional Static Account Specific Environment Variables + +You may need to set these variables, depending on your use case. + +| Variable | Value | Notes | +| ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- | +| `TFC_VAULT_BACKED_GCP_PLAN_VAULT_STATIC_ACCOUNT`
`TFC_VAULT_BACKED_GCP_PLAN_VAULT_STATIC_ACCOUNT[_TAG]`
`TFC_DEFAULT_VAULT_BACKED_GCP_PLAN_VAULT_STATIC_ACCOUNT` | The static account to use for the plan phase of a run. | Requires **v1.8.0** or later if self-managing agents. Will fall back to the value of `TFC_VAULT_BACKED_GCP_RUN_VAULT_STATIC_ACCOUNT` if not provided. | +| `TFC_VAULT_BACKED_GCP_APPLY_VAULT_STATIC_ACCOUNT`
`TFC_VAULT_BACKED_GCP_APPLY_VAULT_STATIC_ACCOUNT[_TAG]`
`TFC_DEFAULT_VAULT_BACKED_GCP_APPLY_VAULT_STATIC_ACCOUNT` | The static account to use for the apply phase of a run. | Requires **v1.8.0** or later if self-managing agents. Will fall back to the value of `TFC_VAULT_BACKED_GCP_RUN_VAULT_STATIC_ACCOUNT` if not provided. | + +## Configure Terraform Providers + +The final step is to directly configure your GCP and Vault providers. + +### Configure the GCP Provider + +Ensure you pass values for the `project` and `region` arguments into the provider configuration block. + +Ensure you are not setting values or environment variables for `GOOGLE_CREDENTIALS` or `GOOGLE_APPLICATION_CREDENTIALS`. Otherwise, these values may interfere with dynamic provider credentials. + +### Configure the Vault Provider + +If you were previously using the Vault provider to authenticate the GCP provider, remove any existing usage of the GCP secrets engine from your Terraform Code. +This includes instances of [`vault_generic_secret`](https://registry.terraform.io/providers/hashicorp/vault/latest/docs/data-sources/generic_secret) that you previously used to generate GCP credentials. + +### Specifying Multiple Configurations + +~> **Important:** If you are self-hosting [HCP Terraform agents](/terraform/cloud-docs/agents), ensure your agents use [v1.12.0](/terraform/cloud-docs/agents/changelog#1-12-0-07-26-2023) or above. To use the latest dynamic credentials features, [upgrade your agents to the latest version](/terraform/cloud-docs/agents/changelog). + +You can add additional variables to handle multiple distinct Vault-backed GCP setups, enabling you to use multiple [provider aliases](/terraform/language/providers/configuration#alias-multiple-provider-configurations) within the same workspace. You can configure each set of credentials independently, or use default values by configuring the variables prefixed with `TFC_DEFAULT_`. + +For more details, see [Specifying Multiple Configurations](/terraform/enterprise/workspaces/dynamic-provider-credentials/specifying-multiple-configurations). + +#### Required Terraform Variable + +To use additional configurations, add the following code to your Terraform configuration. This lets HCP Terraform supply variable values that you can then use to map authentication and configuration details to the correct provider blocks. + +```hcl +variable "tfc_vault_backed_gcp_dynamic_credentials" { + description = "Object containing Vault-backed GCP dynamic credentials configuration" + type = object({ + default = object({ + credentials = string + access_token = string + }) + aliases = map(object({ + credentials = string + access_token = string + })) + }) +} +``` + +#### Example Usage + +##### Access Token + +```hcl +provider "google" { + access_token = var.tfc_vault_backed_gcp_dynamic_credentials.default.access_token +} + +provider "google" { + alias = "ALIAS1" + access_token = var.tfc_vault_backed_gcp_dynamic_credentials.aliases["ALIAS1"].access_token +} +``` + +##### Credentials + +```hcl +provider "google" { + credentials = var.tfc_vault_backed_gcp_dynamic_credentials.default.credentials +} + +provider "google" { + alias = "ALIAS1" + credentials = var.tfc_vault_backed_gcp_dynamic_credentials.aliases["ALIAS1"].credentials +} +``` diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/workspaces/dynamic-provider-credentials/vault-backed/index.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/workspaces/dynamic-provider-credentials/vault-backed/index.mdx new file mode 100644 index 0000000000..29d2cd3396 --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/workspaces/dynamic-provider-credentials/vault-backed/index.mdx @@ -0,0 +1,52 @@ +--- +page_title: Use Vault-backed dynamic credentials in Terraform Enterprise +description: >- + Vault-backed dynamic credentials leverage Vault to generate temporary + credentials for Terraform Enterprise runs. Learn how Vault-backed dynamic + credentials for AWS, Azure, and GCP can improve your security. +source: terraform-docs-common +--- + +# Use Vault-backed dynamic credentials + +~> **Important:** If you are self-hosting [HCP Terraform agents](/terraform/cloud-docs/agents), ensure your agents use [v1.8.0](/terraform/cloud-docs/agents/changelog#1-8-0-04-18-2023) or above. To use the latest dynamic credentials features, [upgrade your agents to the latest version](/terraform/cloud-docs/agents/changelog). + +For most use cases, separately configuring [dynamic provider credentials](/terraform/enterprise/workspaces/dynamic-provider-credentials) with different cloud providers works well. However, Vault-backed dynamic credentials are for those looking for a way to: + +1. Use Vault's secrets engines as a centralized way to manage and consolidate cloud credentials management. +2. Generate short-lived credentials without exposing their Terraform Enterprise instance's OIDC metadata endpoints to the broader public internet. + +The "Vault-backed" in "Vault-backed dynamic credentials" refers to Vault's [secrets engines](/vault/docs/secrets), which allow you to generate short-lived [dynamic secrets](https://www.vaultproject.io/use-cases/dynamic-secrets) for the AWS, GCP, or Azure providers. If you are using Terraform Enterprise and your Vault instance is configured within the same secure network, you can generate secrets while keeping your environment air-gapped. + +Vault-backed dynamic credentials combine the features of dynamic provider credentials and Vault's secrets engines. This means you can authenticate a Vault instance using workload identity tokens and use secrets engines on that instance to generate dynamic credentials for the AWS, GCP, and Azure providers. + +For a comparison of Vault-backed dynamic credentials and dynamic provider credentials, refer to the article [Why use Vault-backed dynamic credentials to secure HCP Terraform infrastructure?](https://www.hashicorp.com/blog/why-use-vault-backed-dynamic-credentials-to-secure-hcp-terraform-infrastructure) on the HashiCorp blog. + +## Configure Vault-Backed Dynamic Credentials + +Using Vault-backed dynamic credentials in a workspace requires the following steps for each cloud platform: + +1. **Set up Dynamic Provider Credentials with the Vault Provider:** You must first [configure dynamic credentials with the Vault provider](/terraform/enterprise/workspaces/dynamic-provider-credentials/vault-configuration). +2. **Configure the desired Secrets Engine**: You must configure the desired secrets engine in your Vault instance (i.e., AWS, GCP, or Azure). +3. **Configure HCP Terraform Workspace**: You must add specific environment variables to your workspace to tell HCP Terraform how to authenticate to other cloud providers during runs. Each cloud platform has its own set of environment variables that are necessary to configure dynamic credentials. + +Setting up Vault-backed dynamic credentials differs slightly for each cloud provider. You can configure Vault-backed dynamic credentials on the following platforms: + +- [Amazon Web Services](/terraform/enterprise/workspaces/dynamic-provider-credentials/vault-backed/aws-configuration) +- [Google Cloud Platform](/terraform/enterprise/workspaces/dynamic-provider-credentials/vault-backed/gcp-configuration) +- [Azure](/terraform/enterprise/workspaces/dynamic-provider-credentials/vault-backed/azure-configuration) + +## Terraform Enterprise Specific Requirements + +### Access to Metadata Endpoints + +In order to verify signed JWTs, Vault must have network access to the following static OIDC metadata endpoints within TFE: + +1. `/.well-known/openid-configuration` - standard OIDC metadata. +2. `/.well-known/jwks` - TFE’s public key(s) that cloud platforms use to verify the authenticity of tokens that claim to come from TFE. + +These endpoints **do not** need to be publicly exposed as long as your Vault instance can access them. + +### External Vault Policy + +If you are using an external Vault instance, you must ensure that your Vault instance has the correct policies setup as detailed in the [External Vault Requirements for Terraform Enterprise](/terraform/enterprise/requirements/data-storage/vault) documentation. diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/workspaces/dynamic-provider-credentials/vault-configuration.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/workspaces/dynamic-provider-credentials/vault-configuration.mdx new file mode 100644 index 0000000000..8ed0483c1d --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/workspaces/dynamic-provider-credentials/vault-configuration.mdx @@ -0,0 +1,225 @@ +--- +page_title: Use dynamic credentials with the Vault provider in Terraform Enterprise +description: >- + Use OpenID Connect to get short-term credentials for the Vault Terraform + provider in your Terraform Enterprise runs. +source: terraform-docs-common +--- + +# Use dynamic credentials with the Vault provider + +~> **Important:** If you are self-hosting [HCP Terraform agents](/terraform/cloud-docs/agents), ensure your agents use [v1.7.0](/terraform/cloud-docs/agents/changelog#1-7-0-03-02-2023) or above. To use the latest dynamic credentials features, [upgrade your agents to the latest version](/terraform/cloud-docs/agents/changelog). + +You can use HCP Terraform’s native OpenID Connect integration with Vault to get [dynamic credentials](/terraform/enterprise/workspaces/dynamic-provider-credentials) for the Vault provider in your HCP Terraform runs. Configuring the integration requires the following steps: + +1. **[Configure Vault](#configure-vault):** Set up a trust configuration between Vault and HCP Terraform. Then, you must create Vault roles and policies for your HCP Terraform workspaces. +2. **[Configure HCP Terraform](#configure-hcp-terraform):** Add environment variables to the HCP Terraform workspaces where you want to use Dynamic Credentials. + +Once you complete the setup, HCP Terraform automatically authenticates to Vault during each run. The Vault provider authentication is valid for the length of the plan or apply. Vault does not revoke authentication until the run is complete. + +If you are using Vault's [secrets engines](/vault/docs/secrets), you must complete the following set up before continuing to configure [Vault-backed dynamic credentials](/terraform/enterprise/workspaces/dynamic-provider-credentials/vault-backed). + +## Configure Vault + +You must enable and configure the JWT backend in Vault. These instructions use the Vault CLI commands, but you can also use Terraform to configure Vault. Refer to our [example Terraform configuration](https://github.com/hashicorp/terraform-dynamic-credentials-setup-examples/tree/main/vault). + +### Enable the JWT Auth Backend + +Run the following command to enable the JWT auth backend in Vault: + +```shell +vault auth enable jwt +``` + +### Configure Trust with HCP Terraform + +You must configure Vault to trust HCP Terraform’s identity tokens and verify them using HCP Terraform’s public key. The following command configures the `jwt` auth backend in Vault to trust HCP Terraform as an OIDC identity provider: + +```shell +vault write auth/jwt/config \ + oidc_discovery_url="https://app.terraform.io" \ + bound_issuer="https://app.terraform.io" +``` + +The `oidc_discovery_url` and `bound_issuer` should both be the root address of HCP Terraform, including the scheme and without a trailing slash. + +#### Terraform Enterprise Specific Requirements + +If you are using a custom or self-signed CA certificate you may need to specify the CA certificate or chain of certificates, in PEM format, via the [`oidc_discovery_ca_pem`](/vault/api-docs/auth/jwt#oidc_discovery_ca_pem) argument as shown in the following example command: + +```shell +vault write auth/jwt/config \ + oidc_discovery_url="https://app.terraform.io" \ + bound_issuer="https://app.terraform.io" \ + oidc_discovery_ca_pem=@my-cert.pem +``` + +In the example above, `my-cert.pem` is a PEM formatted file containing the certificate. + +### Create a Vault Policy + +You must create a Vault policy that controls what paths and secrets your HCP Terraform workspace can access in Vault. +Create a file called tfc-policy.hcl with the following content: + +```hcl +# Allow tokens to query themselves +path "auth/token/lookup-self" { + capabilities = ["read"] +} + +# Allow tokens to renew themselves +path "auth/token/renew-self" { + capabilities = ["update"] +} + +# Allow tokens to revoke themselves +path "auth/token/revoke-self" { + capabilities = ["update"] +} + +# Configure the actual secrets the token should have access to +path "secret/*" { + capabilities = ["read"] +} +``` + +Then create the policy in Vault: + +```shell +vault policy write tfc-policy tfc-policy.hcl +``` + +### Create a JWT Auth Role + +Create a Vault role that HCP Terraform can use when authenticating to Vault. + +Vault offers a lot of flexibility in determining how to map roles and permissions in Vault to workspaces in HCP Terraform. You can have one role for each workspace, one role for a group of workspaces, or one role for all workspaces in an organization. You can also configure different roles for the plan and apply phases of a run. + +-> **Note:** If you set your `user_claim` to be per workspace, then Vault ties the entity it creates to that workspace's name. If you rename the workspace tied to your `user_claim`, Vault will create an additional identity object. To avoid this, update the alias name in Vault to your new workspace name before you update it in HCP Terraform. + +The following example creates a role called `tfc-role`. The role is mapped to a single workspace and HCP Terraform can use it for both plan and apply runs. + +Create a file called `vault-jwt-auth-role.json` with the following content: + +```json +{ + "policies": ["tfc-policy"], + "bound_audiences": ["vault.workload.identity"], + "bound_claims_type": "glob", + "bound_claims": { + "sub": +"organization:my-org-name:project:my-project-name:workspace:my-workspace-name:run_phase:*" + }, + "user_claim": "terraform_full_workspace", + "role_type": "jwt", + "token_ttl": "20m" +} +``` + +Then run the following command to create a role named `tfc-role` with this configuration in Vault: + +```shell +vault write auth/jwt/role/tfc-role @vault-jwt-auth-role.json +``` + +To understand all the available options for matching bound claims, refer to the [Terraform workload identity claim specification](/terraform/enterprise/workspaces/dynamic-provider-credentials) and the [Vault documentation on configuring bound claims](/vault/docs/auth/jwt#bound-claims). To understand all the options available when configuring Vault JWT auth roles, refer to the [Vault API documentation](/vault/api-docs/auth/jwt#create-role). + +!> **Warning:** you should always check, at minimum, the audience and the name of the organization in order to prevent unauthorized access from other HCP Terraform organizations! + +#### Token TTLs + +We recommend setting token_ttl to a relatively short value. HCP Terraform can renew the token periodically until the plan or apply is complete, then revoke it to prevent it from being used further. + +## Configure HCP Terraform + +You’ll need to set some environment variables in your HCP Terraform workspace in order to configure HCP Terraform to authenticate with Vault using dynamic credentials. You can set these as workspace variables, or if you’d like to share one Vault role across multiple workspaces, you can use a variable set. When you configure dynamic provider credentials with multiple provider configurations of the same type, use either a default variable or a tagged alias variable name for each provider configuration. Refer to [Specifying Multiple Configurations](#specifying-multiple-configurations) for more details. + +### Required Environment Variables + +| Variable | Value | Notes | +| ------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `TFC_VAULT_PROVIDER_AUTH`
`TFC_VAULT_PROVIDER_AUTH[_TAG]`
_(Default variable not supported)_ | `true` | Requires **v1.7.0** or later if self-managing agents. Must be present and set to `true`, or HCP Terraform will not attempt to authenticate to Vault. | +| `TFC_VAULT_ADDR`
`TFC_VAULT_ADDR[_TAG]`
`TFC_DEFAULT_VAULT_ADDR` | The address of the Vault instance to authenticate against. | Requires **v1.7.0** or later if self-managing agents. Will also be used to set `VAULT_ADDR` in the run environment. | +| `TFC_VAULT_RUN_ROLE`
`TFC_VAULT_RUN_ROLE[_TAG]`
`TFC_DEFAULT_VAULT_RUN_ROLE` | The name of the Vault role to authenticate against (`tfc-role`, in our example). | Requires **v1.7.0** or later if self-managing agents. Optional if `TFC_VAULT_PLAN_ROLE` and `TFC_VAULT_APPLY_ROLE` are both provided. These variables are described [below](/terraform/enterprise/workspaces/dynamic-provider-credentials/vault-configuration#optional-environment-variables) | + +### Optional Environment Variables + +You may need to set these variables, depending on your Vault configuration and use case. + +| Variable | Value | Notes | +| -------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `TFC_VAULT_NAMESPACE`
`TFC_VAULT_NAMESPACE[_TAG]`
`TFC_DEFAULT_VAULT_NAMESPACE` | The namespace to use when authenticating to Vault. | Requires **v1.7.0** or later if self-managing agents. Will also be used to set `VAULT_NAMESPACE` in the run environment. | +| `TFC_VAULT_AUTH_PATH`
`TFC_VAULT_AUTH_PATH[_TAG]`
`TFC_DEFAULT_VAULT_AUTH_PATH` | The path where the JWT auth backend is mounted in Vault. Defaults to jwt. | Requires **v1.7.0** or later if self-managing agents. | +| `TFC_VAULT_WORKLOAD_IDENTITY_AUDIENCE`
`TFC_VAULT_WORKLOAD_IDENTITY_AUDIENCE[_TAG]`
`TFC_DEFAULT_VAULT_WORKLOAD_IDENTITY_AUDIENCE` | Will be used as the `aud` claim for the identity token. Defaults to `vault.workload.identity`. | Requires **v1.7.0** or later if self-managing agents. Must match the `bound_audiences` configured for the role in Vault. | +| `TFC_VAULT_PLAN_ROLE`
`TFC_VAULT_PLAN_ROLE[_TAG]`
`TFC_DEFAULT_VAULT_PLAN_ROLE` | The Vault role to use for the plan phase of a run. | Requires **v1.7.0** or later if self-managing agents. Will fall back to the value of `TFC_VAULT_RUN_ROLE` if not provided. | +| `TFC_VAULT_APPLY_ROLE`
`TFC_VAULT_APPLY_ROLE[_TAG]`
`TFC_DEFAULT_VAULT_APPLY_ROLE` | The Vault role to use for the apply phase of a run. | Requires **v1.7.0** or later if self-managing agents. Will fall back to the value of `TFC_VAULT_RUN_ROLE` if not provided. | +| `TFC_VAULT_ENCODED_CACERT`
`TFC_VAULT_ENCODED_CACERT[_TAG]`
`TFC_DEFAULT_VAULT_ENCODED_CACERT` | A PEM-encoded CA certificate that has been Base64 encoded. | Requires **v1.9.0** or later if self-managing agents. This certificate will be used when connecting to Vault. May be required when connecting to Vault instances that use a custom or self-signed certificate. | + +## Vault Provider Configuration + +Once you set up dynamic credentials for a workspace, HCP Terraform automatically authenticates to Vault for each run. Do not pass the `address`, `token`, or `namespace` arguments into the provider configuration block. HCP Terraform sets these values as environment variables in the run environment. + +You can use the Vault provider to read static secrets from Vault and use them with other Terraform resources. You can also access the other resources and data sources available in the [Vault provider documentation](https://registry.terraform.io/providers/hashicorp/vault/latest). You must adjust your [Vault policy](#create-a-vault-policy) to give your HCP Terraform workspace access to all required Vault paths. + +~> **Important:** data sources that use secrets engines to generate dynamic secrets must not be used with Vault dynamic credentials. You can use Vault's dynamic secrets engines for AWS, GCP, and Azure by adding additional configurations. For more details, see [Vault-backed dynamic credentials](/terraform/enterprise/workspaces/dynamic-provider-credentials/vault-backed). + +### Specifying Multiple Configurations + +~> **Important:** If you are self-hosting [HCP Terraform agents](/terraform/cloud-docs/agents), ensure your agents use [v1.12.0](/terraform/cloud-docs/agents/changelog#1-12-0-07-26-2023) or above. To use the latest dynamic credentials features, [upgrade your agents to the latest version](/terraform/cloud-docs/agents/changelog). + +~> **Important:** Ensure you are using version **3.18.0** or later of the **Vault provider** as the required [`auth_login_token_file`](https://registry.terraform.io/providers/hashicorp/vault/latest/docs#token-file) block was introduced in this provider version. + +You can add additional variables to handle multiple distinct Vault setups, enabling you to use multiple [provider aliases](/terraform/language/providers/configuration#alias-multiple-provider-configurations) within the same workspace. You can configure each set of credentials independently, or use default values by configuring the variables prefixed with `TFC_DEFAULT_`. + +For more details, see [Specifying Multiple Configurations](/terraform/enterprise/workspaces/dynamic-provider-credentials/specifying-multiple-configurations). + +#### Required Terraform Variable + +To use additional configurations, add the following code to your Terraform configuration. This lets HCP Terraform supply variable values that you can then use to map authentication and configuration details to the correct provider blocks. + +```hcl +variable "tfc_vault_dynamic_credentials" { + description = "Object containing Vault dynamic credentials configuration" + type = object({ + default = object({ + token_filename = string + address = string + namespace = string + ca_cert_file = string + }) + aliases = map(object({ + token_filename = string + address = string + namespace = string + ca_cert_file = string + })) + }) +} +``` + +#### Example Usage + +```hcl +provider "vault" { + // skip_child_token must be explicitly set to true as HCP Terraform manages the token lifecycle + skip_child_token = true + address = var.tfc_vault_dynamic_credentials.default.address + namespace = var.tfc_vault_dynamic_credentials.default.namespace + + auth_login_token_file { + filename = var.tfc_vault_dynamic_credentials.default.token_filename + } +} + +provider "vault" { + // skip_child_token must be explicitly set to true as HCP Terraform manages the token lifecycle + skip_child_token = true + alias = "ALIAS1" + address = var.tfc_vault_dynamic_credentials.aliases["ALIAS1"].address + namespace = var.tfc_vault_dynamic_credentials.aliases["ALIAS1"].namespace + + auth_login_token_file { + filename = var.tfc_vault_dynamic_credentials.aliases["ALIAS1"].token_filename + } +} +``` diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/workspaces/dynamic-provider-credentials/workload-identity-tokens.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/workspaces/dynamic-provider-credentials/workload-identity-tokens.mdx new file mode 100644 index 0000000000..26851ac640 --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/workspaces/dynamic-provider-credentials/workload-identity-tokens.mdx @@ -0,0 +1,99 @@ +--- +page_title: Workload identity in Terraform Enterprise +description: >- + Learn how workload identity uses OpenID Connect (OIDC) to allow Terraform + plans and applies to safely authenticate to external systems. +source: terraform-docs-common +--- + +# Workload identity + +Dynamic Provider Credentials are powered by Terraform Workload Identity, which allows HCP Terraform to present information about a Terraform workload to an external system – like its workspace, organization, or whether it’s a plan or apply – and allows other external systems to verify that the information is accurate. + +You can think of it like an identity card for your Terraform runs: one that comes with a way for another system to easily verify whether the card is genuine. If the other system can confirm that the ID card is legitimate, it can trust the information the card contains and use it to decide whether to let that Terraform workload in the door. + +The “identity card” in this analogy is a workload identity token: a JSON Web Token (JWT) that contains information about a plan or apply, is signed by HCP Terraform’s private key, and expires at the end of the plan or apply timeout. Other systems can use HCP Terraform’s [public key](https://app.terraform.io/.well-known/jwks) to verify that a token that claims to be from HCP Terraform is genuine and has not been tampered with. + +This workflow is built on the [OpenID Connect protocol](https://openid.net/connect/), a trusted standard for verifying identity across different systems. + +## Token Specification + +Workload identity tokens contain useful metadata in their payloads, known as _claims_. This is the equivalent of the name and date of birth on an identity card. Once a cloud platform verifies a token using HCP Terraform’s public key, it can look at the claims in the identity token to either match it to the correct permissions or reject it. + +You don’t need to understand the full token specification and what every claim means in order to use dynamic credentials, but it’s useful for debugging. + +### Token Structure + +The following example shows a decoded HCP Terraform workload identity token: + +#### Header + +```json +{ + "typ": "JWT", + "alg": "RS256", + "kid": "j-fFp9evPJAzV5I2_58HY5UvdCK6Q4LLB1rnPOUfQAk" +} +``` + +#### Payload + +```json +{ + "jti": "1192426d-b525-4fde-9d42-f238be437bbd", + "iss": "https://app.terraform.io", + "aud": "my-example-audience", + "iat": 1650486122, + "nbf": 1650486117, + "exp": 1650486422, + "sub": "organization:my-org:project:Default Project:workspace:my-workspace:run_phase:apply", + "terraform_organization_id": "org-GRNbCjYNpBB6NEH9", + "terraform_organization_name": "my-org", + "terraform_project_id": "prj-vegSA59s1XPwMr2t", + "terraform_project_name": "Default Project", + "terraform_workspace_id": "ws-mbsd5E3Ktt5Rg2Xm", + "terraform_workspace_name": "my-workspace", + "terraform_full_workspace": "organization:my-org:project:Default Project:workspace:my-workspace", + "terraform_run_id": "run-X3n1AUXNGWbfECsJ", + "terraform_run_phase": "apply" +} +``` + +This payload includes a number of standard claims defined in the OIDC spec as well as a number of custom claims for further customization. + +### Standard Claims + +| Claim | Value | +| ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `jti` (JWT ID) | A unique identifier for each JWT. | +| `iss` (issuer) | Full URL of HCP Terraform or the Terraform Enterprise instance which signed the JWT. | +| `iat` (issued at) | Unix Timestamp when the JWT was issued. May be required by certain relying parties. | +| `nbf` (not before) | Unix Timestamp when the JWT can start being used. This will be the same as `iat` for tokens issued by HCP Terraform, but may be required by certain relying parties. | +| `aud` (audience) | Intended audience for the JWT. For example, `aws.workload.identity` for AWS. This can be customized. | +| `exp` (expiration) | Unix Timestamp based on the timeout of the run phase that it was issued for. This will follow the `plan` and `apply` timeouts set at the organization and site admin level. | +| `sub` (subject) | Fully qualified path to a workspace, followed by the run phase. For example: `organization:my-organization-name:project:Default Project:workspace:my-workspace-name:run_phase:apply` | + +### Custom Claims + +| Claim | Value | +| ------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `terraform_organization_id` (organization ID) | ID of the HCP Terraform organization performing the run. | +| `terraform_organization_name` (organization name) | Human-readable name of the HCP Terraform organization performing the run. Note that organization names can be changed. | +| `terraform_project_id` (project ID) | ID of the HCP Terraform project performing the run. | +| `terraform_project_name` (project name) | Human-readable name of the HCP Terraform project performing the run. Note that project names can be changed. The default project name is `Default Project`. | +| `terraform_workspace_id` (workspace ID) | ID of the HCP Terraform workspace performing the run. | +| `terraform_workspace_name` (workspace name) | Human-readable name of the HCP Terraform workspace performing the run. Note that workspace names can be changed. | +| `terraform_full_workspace` (fully qualified workspace) | Fully qualified path to a workspace. For example: `organization:my-organization-name:project:my-project-name:workspace:my-workspace-name` | +| `terraform_run_id` (run ID) | ID of the run that the token was generated for. This is intended to aid in traceability and logging. | +| `terraform_run_phase` (run phase) | The phase of the run this token was issued for. For example, `plan` or `apply` | + +### Configuring Trust with your Cloud Platform + +When configuring the trust relationship between HCP Terraform and your cloud platform, you’ll set up conditions to validate the contents of the identity token provided by HCP Terraform against your roles and policies. + +At the minimum, you should match against the following claims: + +- `aud` - the audience value of the token. This ensures that, for example, a workload identity token intended for AWS can’t be used to authenticate to Vault. +- `sub` - the subject value, which includes the organization and workspace performing the run. If you don’t match against at least the organization name, any organization or workspace on HCP Terraform will be able to access your cloud resources! + +You can match on as many claims as you want, depending on your cloud platform. diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/workspaces/health.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/workspaces/health.mdx new file mode 100644 index 0000000000..21c209b819 --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/workspaces/health.mdx @@ -0,0 +1,245 @@ +--- +page_title: Health assessments in Terraform Enterprise +description: >- + Learn how Terraform Enterprise can continuously monitor workspaces to assess + whether their real infrastructure matches the requirements defined in your + Terraform configuration. +source: terraform-docs-common +--- + +# Health + +HCP Terraform can perform automatic health assessments in a workspace to assess whether its real infrastructure matches the requirements defined in its Terraform configuration. Health assessments include the following types of evaluations: + +- [Drift detection](#drift-detection) determines whether your real-world infrastructure matches your Terraform configuration. +- [Continuous validation](#continuous-validation) determines whether custom conditions in the workspace’s configuration continue to pass after Terraform provisions the infrastructure. + +When you enable health assessments, HCP Terraform periodically runs health assessments for your workspace. Refer to [Health Assessment Scheduling](#health-assessment-scheduling) for details. + + + +@include 'tfc-package-callouts/health-assessments.mdx' + + + +## Permissions + +Working with health assessments requires the following permissions: + +- To view health status for a workspace, you need read access to that workspace. +- To change organization health settings, you must be an [organization owner](/terraform/enterprise/users-teams-organizations/permissions#organization-owners). +- To change a workspace’s health settings, you must be an [administrator for that workspace](/terraform/enterprise/users-teams-organizations/permissions#workspace-admins). + +- To trigger [on-demand health assessments](/terraform/enterprise/workspaces/health#on-demand-assessments) for a workspace, you must be an [administrator for that workspace](/terraform/enterprise/users-teams-organizations/permissions#workspace-admins). + + +## Workspace requirements + +Workspaces require the following settings to receive health assessments: + +- Terraform version 0.15.4+ for drift detection only +- Terraform version 1.3.0+ for drift detection and continuous validation +- [Remote execution mode](/terraform/enterprise/workspaces/settings#execution-mode) or [Agent execution mode](/terraform/cloud-docs/agents/agent-pools#configure-workspaces-to-use-the-agent) for Terraform runs + +The latest Terraform run in the workspace must have been successful. If the most recent run ended in an errored, canceled, or discarded state, HCP Terraform pauses health assessments until there is a successfully applied run. + +The workspace must also have at least one run in which Terraform successfully applies a configuration. HCP Terraform does not perform health assessments in workspaces with no real-world infrastructure. + +## Enable health assessments + +You can enforce health assessments across all eligible workspaces in an organization within the [organization settings](/terraform/enterprise/users-teams-organizations/organizations#health). Enforcing health assessments at an organization-level overrides workspace-level settings. You can only enable health assessments within a specific workspace when HCP Terraform is not enforcing health assessments at the organization level. + +To enable health assessments within a workspace: + +1. Sign in to [HCP Terraform](https://app.terraform.io/) or Terraform Enterprise and navigate to the workspace you want to enable health assessments on. +2. Verify that your workspace satisfies the [requirements](#workspace-requirements). +3. Go to the workspace and click **Settings**, then click **Health**. +4. Select **Enable** under **Health Assessments**. +5. Click **Save settings**. + +## Health assessment scheduling + +When you enable health assessments for a workspace, HCP Terraform runs the first health assessment based on whether there are active Terraform runs for the workspace: + +- **No active runs:** A few minutes after you enable the feature. +- **Active speculative plan:** A few minutes after that plan is complete. +- **Other active runs:** During the next assessment period. + +After the first health assessment, HCP Terraform starts a new health assessment during the next assessment period if there are no active runs in the workspace. Health assessments may take longer to complete when you enable health assessments in many workspaces at once or your workspace contains a complex configuration with many resources. + +A health assessment never interrupts or interferes with runs. If you start a new run during a health assessment, HCP Terraform cancels the current assessment and runs the next assessment during the next assessment period. This behavior may prevent HCP Terraform from performing health assessments in workspaces with frequent runs. + +HCP Terraform pauses health assessments if the latest run ended in an errored state. This behavior occurs for all run types, including plan-only runs and speculative plans. Once the workspace completes a successful run, HCP Terraform restarts health assessments during the next assessment period. + +Terraform Enterprise administrators can modify their installation's [assessment frequency and number of maximum concurrent assessments](/terraform/enterprise/admin/application/general#health-assessments) from the admin settings console. + + + +### On-demand assessments + +-> **Note:** On-demand assessments are only available in the HCP Terraform user interface. + +If you are an administrator for a workspace and it satisfies all [assessment requirements](/terraform/enterprise/workspaces/health#workspace-requirements), you can trigger a new assessment by clicking **Start health assessment** on the workspace's **Health** page. + +After clicking **Start health assessment**, the workspace displays a message in the bottom lefthand corner of the page to indicate if it successfully triggered a new assessment. The time it takes to complete an assessment can vary based on network latency and the number of resources managed by the workspace. + +You cannot trigger another assessment while one is in progress. An on-demand assessment resets the scheduling for automated assessments, so HCP Terraform waits to run the next assessment until the next scheduled period. + + + +### Concurrency + +If you enable health assessments on multiple workspaces, assessments may run concurrently. Health assessments do not affect your concurrency limit. HCP Terraform also monitors and controls health assessment concurrency to avoid issues for large-scale deployments with thousands of workspaces. However, HCP Terraform performs health assessments in batches, so health assessments may take longer to complete when you enable them in a large number of workspaces. + +### Notifications + +HCP Terraform sends [notifications](/terraform/enterprise/workspaces/settings/notifications) about health assessment results according to your workspace’s settings. + +## Workspace health status + +On the organization's **Workspaces** page, HCP Terraform displays a **Health warning** status for workspaces with infrastructure drift or failed continuous validation checks. + +On the right of a workspace’s overview page, HCP Terraform displays a **Health** bar that summarizes the results of the last health assessment. + +- The **Drift** summary shows the total number of resources in the configuration and the number of resources that have drifted. +- The **Checks** summary shows the number of passed, failed, and unknown statuses for objects with continuous validation checks. + + + +### View workspace health in explorer + +The [Explorer page](/terraform/enterprise/workspaces/explorer) presents a condensed overview of the health status of the workspaces within your organization. You can see the following information: + +- Workspaces that are monitoring workspace health +- Status of any configured continuous validation checks +- Count of drifted resources for each workspace + +For additional details on the data available for reporting, refer to the [Explorer](/terraform/enterprise/workspaces/explorer) documentation. + +![Viewing Workspace Health in Explorer](/img/docs/tfc-explorer-health.png) + + + +## Drift detection + +Drift detection helps you identify situations where your actual infrastructure no longer matches the configuration defined in Terraform. This deviation is known as _configuration drift_. Configuration drift occurs when changes are made outside Terraform's regular process, leading to inconsistencies between the remote objects and your configured infrastructure. + +For example, a teammate could create configuration drift by directly updating a storage bucket's settings with conflicting configuration settings using the cloud provider's console. Drift detection could detect these differences and recommend steps to address and rectify the discrepancies. + +Configuration drift differs from state drift. Drift detection does not detect state drift. + +Configuration drift happens when external changes affecting remote objects invalidate your infrastructure configuration. State drift occurs when external changes affecting remote objects _do not_ invalidate your infrastructure configuration. Refer to [Refresh-Only Mode](/terraform/enterprise/run/modes-and-options#refresh-only-mode) to learn more about remediating state drift. + +### View workspace drift + +To view the drift detection results from the latest health assessment, go to the workspace and click **Health > Drift**. If there is configuration drift, HCP Terraform proposes the necessary changes to bring the infrastructure back in sync with its configuration. + +### Resolve drift + +You can use one of the following approaches to correct configuration drift: + +- **Overwrite drift**: If you do not want the drift's changes, queue a new plan and apply the changes to revert your real-world infrastructure to match your Terraform configuration. +- **Update Terraform configuration:** If you want the drift's changes, modify your Terraform configuration to include the changes and push a new configuration version. This prevents Terraform from reverting the drift during the next apply. Refer to the [Manage Resource Drift](/terraform/tutorials/state/resource-drift) tutorial for a detailed example. + +## Continuous validation + +Continuous validation regularly verifies whether your configuration’s custom assertions continue to pass, validating your infrastructure. For example, you can monitor whether your website returns an expected status code, or whether an API gateway certificate is valid. Identifying failed assertions helps you resolve the failure and prevent errors during your next time Terraform operation. + +Continuous validation evaluates preconditions, postconditions, and check blocks as part of an assessment, but we recommend using [check blocks](/terraform/language/checks) for post-apply monitoring. Use check blocks to create custom rules to validate your infrastructure's resources, data sources, and outputs. + +### Preventing false positives + +Health assessments create a speculative plan to access the current state of your infrastructure. Terraform evaluates any check blocks in your configuration as the last step of creating the speculative plan. If your configuration relies on data sources and the values queried by a data source change between the time of your last run and the assessment, the speculative plan will include those changes. HCP Terraform will not modify your infrastructure as part of an assessment, but it can use those updated values to evaluate checks. This may lead to false positive results for alerts since your infrastructure did not yet change. + +To ensure your checks evaluate the current state of your configuration instead of against a possible future change, use nested data sources that query your actual resource configuration, rather than a computed latest value. Refer to the [AMI image scenario](#asserting-up-to-date-amis-for-compute-instances) below for an example. + +### Example use cases + +Review the provider documentation for `check` block examples with [AWS](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/guides/continuous-validation-examples), [Azure](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/guides/tfc-check-blocks), and [GCP](https://registry.terraform.io/providers/hashicorp/google/latest/docs/guides/google-continuous-validation). + +#### Monitoring the health of a provisioned website + +The following example uses the [HTTP](https://registry.terraform.io/providers/hashicorp/http/latest/docs) Terraform provider and a [scoped data source](/terraform/language/checks#scoped-data-sources) within a [`check` block](/terraform/language/checks) to assert the Terraform website returns a `200` status code, indicating it is healthy. + +```hcl +check "health_check" { + data "http" "terraform_io" { + url = "https://www.terraform.io" + } + + assert { + condition = data.http.terraform_io.status_code == 200 + error_message = "${data.http.terraform_io.url} returned an unhealthy status code" + } +} +``` + +Continuous Validation alerts you if the website returns any status code besides `200` while Terraform evaluates this assertion. You can also find failures in your workspace's [Continuous Validation Results](#view-continuous-validation-results) page. You can configure continuous validation alerts in your workspace's [notification settings](/terraform/enterprise/workspaces/settings/notifications). + +#### Monitoring certificate expiration + +[Vault](https://www.vaultproject.io/) lets you secure, store, and tightly control access to tokens, passwords, certificates, encryption keys, and other sensitive data. The following example uses a `check` block to monitor for the expiration of a Vault certificate. + +```hcl +resource "vault_pki_secret_backend_cert" "app" { + backend = vault_mount.intermediate.path + name = vault_pki_secret_backend_role.test.name + common_name = "app.my.domain" +} + +check "certificate_valid" { + assert { + condition = !vault_pki_secret_backend_cert.app.renew_pending + error_message = "Vault cert is ready to renew." + } +} +``` + +#### Asserting up-to-date AMIs for compute instances + +[HCP Packer](/hcp/docs/packer) stores metadata about your [Packer](https://www.packer.io/) images. The following example check fails when there is a newer AMI version available. + +```hcl +data "hcp_packer_artifact" "hashiapp_image" { + bucket_name = "hashiapp" + channel_name = "latest" + platform = "aws" + region = "us-west-2" +} + +resource "aws_instance" "hashiapp" { + ami = data.hcp_packer_artifact.hashiapp_image.external_identifier + instance_type = var.instance_type + associate_public_ip_address = true + subnet_id = aws_subnet.hashiapp.id + vpc_security_group_ids = [aws_security_group.hashiapp.id] + key_name = aws_key_pair.generated_key.key_name + + tags = { + Name = "hashiapp" + } +} + +check "ami_version_check" { + data "aws_instance" "hashiapp_current" { + instance_tags = { + Name = "hashiapp" + } + } + + assert { + condition = aws_instance.hashiapp.ami == data.hcp_packer_artifact.hashiapp_image.external_identifier + error_message = "Must use the latest available AMI, ${data.hcp_packer_artifact.hashiapp_image.external_identifier}." + } +} +``` + +### View continuous validation results + +To view the continuous validation results from the latest health assessment, go to the workspace and click **Health > Continuous validation**. + +The page shows all of the resources, outputs, and data sources with custom assertions that HCP Terraform evaluated. Next to each object, HCP Terraform reports whether the assertion passed or failed. If one or more assertions fail, HCP Terraform displays the error messages for each assertion. + +The health assessment page displays each assertion by its [named value](/terraform/language/expressions/references). A `check` block's named value combines the prefix `check` with its configuration name. + +If your configuration contains multiple [preconditions and postconditions](/terraform/language/expressions/custom-conditions#preconditions-and-postconditions) within a single resource, output, or data source, HCP Terraform will not show the results of individual conditions unless they fail. If all custom conditions on the object pass, HCP Terraform reports that the entire check passed. The assessment results will display the results of any precondition and postconditions alongside the results of any assertions from `check` blocks, identified by the named values of their parent block. diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/workspaces/index.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/workspaces/index.mdx new file mode 100644 index 0000000000..2c2f763bec --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/workspaces/index.mdx @@ -0,0 +1,100 @@ +--- +page_title: Terraform Enterprise workspaces +description: >- + A workspace is a group of infrastructure resources managed by Terraform + Enterprise. Learn what workspaces contain, how they perform Terraform runs, + and how to create and organize them. +source: terraform-docs-common +--- + +# Workspaces + +This topic provides an overview of the workspaces resource in HCP Terraform and Terraform Enterprise. A workspace is a group of infrastructure resources managed by Terraform. + +## Introduction + +Working with Terraform involves managing collections of infrastructure resources, and most organizations manage many different collections. + +When run locally, Terraform manages each collection of infrastructure with a persistent working directory, which contains a configuration, state data, and variables. Since Terraform CLI uses content from the directory it runs in, you can organize infrastructure resources into meaningful groups by keeping their configurations in separate directories. + +HCP Terraform manages infrastructure collections with workspaces instead of directories. A workspace contains everything Terraform needs to manage a given collection of infrastructure, and separate workspaces function like completely separate working directories. + +> **Hands-on:** Try the [Create a Workspace](/terraform/tutorials/cloud-get-started/cloud-workspace-create) tutorial. + +## Workspace Contents + +HCP Terraform workspaces and local working directories serve the same purpose, but they store their data differently: + +| Component | Local Terraform | HCP Terraform | +| ----------------------- | ------------------------------------------------------------- | -------------------------------------------------------------------------- | +| Terraform configuration | On disk | In linked version control repository, or periodically uploaded via API/CLI | +| Variable values | As `.tfvars` files, as CLI arguments, or in shell environment | In workspace | +| State | On disk or in remote backend | In workspace | +| Credentials and secrets | In shell environment or entered at prompts | In workspace, stored as sensitive variables | + +In addition to the basic Terraform content, HCP Terraform keeps some additional data for each workspace: + +- **State versions:** Each workspace retains backups of its previous state files. Although only the current state is necessary for managing resources, the state history can be useful for tracking changes over time or recovering from problems. Refer to [Terraform State in HCP Terraform](/terraform/enterprise/workspaces/state) for more details. + +- **Run history:** When HCP Terraform manages a workspace's Terraform runs, it retains a record of all run activity, including summaries, logs, a reference to the changes that caused the run, and user comments. Refer to [Viewing and Managing Runs](/terraform/enterprise/run/manage) for more details. + + + +The top of each workspace shows a resource count, which reflects the number of resources recorded in the workspace’s state file. This includes both managed [resources](/terraform/language/resources/syntax) and [data sources](/terraform/language/data-sources). + + + +## Terraform Runs + +For workspaces with remote operations enabled (the default), HCP Terraform performs Terraform runs on its own disposable virtual machines, using that workspace's configuration, variables, and state. + +Refer to [Terraform Runs and Remote Operations](/terraform/enterprise/run/remote-operations) for more details. + +## HCP Terraform vs. Terraform CLI Workspaces + +Both HCP Terraform and Terraform CLI have features called workspaces, but they function differently. + +- HCP Terraform workspaces are required. They represent all of the collections of infrastructure in an organization. They are also a major component of role-based access in HCP Terraform. You can grant individual users and user groups permissions for one or more workspaces that dictate whether they can manage variables, perform runs, etc. You cannot manage resources in HCP Terraform without creating at least one workspace. + +- Terraform CLI workspaces are associated with a specific working directory and isolate multiple state files in the same working directory, letting you manage multiple groups of resources with a single configuration. The Terraform CLI does not require you to create CLI workspaces. Refer to [Workspaces](/terraform/language/state/workspaces) in the Terraform Language documentation for more details. + +## Planning and Organizing Workspaces + +We recommend that organizations break down large monolithic Terraform configurations into smaller ones, then assign each one to its own workspace and delegate permissions and responsibilities for them. HCP Terraform can manage monolithic configurations just fine, but managing infrastructure as smaller components is the best way to take full advantage of HCP Terraform's governance and delegation features. + +For example, the code that manages your production environment's infrastructure could be split into a networking configuration, the main application's configuration, and a monitoring configuration. After splitting the code, you would create "networking-prod", "app1-prod", "monitoring-prod" workspaces, and assign separate teams to manage them. + +Much like splitting monolithic applications into smaller microservices, this enables teams to make changes in parallel. In addition, it makes it easier to re-use configurations to manage other environments of infrastructure ("app1-dev," etc.). + +In Terraform Enterprise, administrators can use [Admin Settings](/terraform/enterprise/api-docs/admin/settings) to set the maximum number of workspaces for any single organization. You can also set a workspaces limit with the [tfe-terraform-provider](https://registry.terraform.io/providers/hashicorp/tfe/latest/docs/resources/organization#workspace_limit). + +## Organize Workspaces with Projects + +Projects let you organize your workspaces into groups. + + + +@include 'tfc-package-callouts/project-workspaces.mdx' + + + +Refer to [Organize Workspaces with Projects](/terraform/enterprise/projects/manage) for more details. + +## Creating Workspaces + +You can create workspaces through the [HCP Terraform UI](/terraform/enterprise/workspaces/create), the [Workspaces API](/terraform/enterprise/api-docs/workspaces), or the [HCP Terraform CLI integration](/terraform/cli/cloud). + +## Workspace Health + + + +@include 'tfc-package-callouts/health-assessments.mdx' + + + +HCP Terraform can perform automatic health assessments in a workspace to assess whether its real infrastructure matches the requirements defined in its Terraform configuration. Health assessments include the following types of evaluations: + +- Drift detection determines whether your real-world infrastructure matches your Terraform configuration. +- Continuous validation determines whether custom conditions in the workspace’s configuration continue to pass after Terraform provisions the infrastructure. + +You can enforce health assessments for all eligible workspaces or let each workspace opt in to health assessments through workspace settings. Refer to [Health](/terraform/enterprise/workspaces/health) in the workspaces documentation for more details. diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/workspaces/json-filtering.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/workspaces/json-filtering.mdx new file mode 100644 index 0000000000..c70eb74e3f --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/workspaces/json-filtering.mdx @@ -0,0 +1,117 @@ +--- +page_title: JSON data filtering in Terraform Enterprise +description: >- + Learn how to filter and create custom datasets on pages that display JSON data + in Terraform Enterprise. +source: terraform-docs-common +--- + +# JSON data filtering + +Certain pages where JSON data is displayed, such as the [state +viewer](/terraform/enterprise/workspaces/state) and [policy check JSON data +viewer](/terraform/enterprise/policy-enforcement/sentinel/json), allow you to filter the results. This +enables you to see just the data you need, and even create entirely new datasets +to see data in the way you want to see it! + +![entering a json filter](/img/docs/json-viewer-intro.png) + +-> **NOTE:** _Filtering_ the data in the JSON viewer is separate from +_searching_ it. To search, press Control-F (or Command-F on MacOS). You can +search and apply a filter at the same time. + +## Entering a Filter + +Filters are entered by putting the filter in the aptly named **filter** box in +the JSON viewer. After entering the filter, pressing **Apply** or the enter key +on your keyboard will apply the filter. The filtered results, if any, are +displayed in result box. Clearing the filter will restore the original JSON +data. + +![entering a json filter](/img/docs/sentinel-json-enter-filter.png) + +## Filter Language + +The JSON filter language is a small subset of the +[jq](https://stedolan.github.io/jq/) JSON filtering language. Selectors, +literals, indexes, slices, iterators, and pipes are supported, as are also array +and object construction. At this time, parentheses, and more complex operations +such as mathematical operators, conditionals, and functions are not supported. + +Below is a quick reference of some of the more basic functions to get you +started. + +### Selectors + +Selectors allow you to pick an index out of a JSON object, and are written as +`.KEY.SUBKEY`. So, as an example, given an object of +`{"foo": {"bar": "baz"}}`, and the filter `.foo.bar`, the result would be +displayed as `"baz"`. + +A single dot (`.`) without anything else always denotes the current value, +unaltered. + +### Indexes + +Indexes can be used to fetch array elements, or select non-alphanumeric object +fields. They are written as `[0]` or `["foo-bar"]`, depending on the purpose. + +Given an object of `{"foo-bar": ["baz", "qux"]}` and the filter of +`.["foo-bar"][0]`, the result would be displayed as `"baz"`. + +### Slices + +Arrays can be sliced to get a subset an array. The syntax is `[LOW:HIGH]`. + +Given an array of `[0, 1, 2, 3, 4]` and the filter of +`.[1:3]`, the result would be displayed as `[1, 2]`. This also illustrates that +the result of the slice operation is always of length HIGH-LOW. + +Slices can also be applied to strings, in which a substring is returned with the +same rules applied, with the first character of the string being index 0. + +### Iterators + +Iterators can iterate over arrays and objects. The syntax is `[]`. + +Iterators iterate over the _values_ of an object only. So given a object of +`{"foo": 1, "bar": 2}`, the filter `.[]` would yield an iteration of `1, 2`. + +Note that iteration results are not necessarily always arrays. Iterators are +handled in a special fashion when dealing with pipes and object creators (see +below). + +### Array Construction + +Wrapping an expression in brackets (`[ ... ]`) creates an array with the +sub-expressions inside the array. The results are always concatenated. + +For example, for an object of `{"foo": [1, 2], "bar": [3, 4]}`, the construction +expressions `[.foo[], .bar[]]` and `[.[][]]`, are the same, producing the +resulting array `[1, 2, 3, 4]`. + +### Object Construction + +Wrapping an expression in curly braces `{KEY: EXPRESSION, ...}` creates an +object. + +Iterators work uniquely with object construction in that an object is +constructed for each _iteration_ that the iterator produces. + +As a basic example, Consider an array `[1, 2, 3]`. While the expression +`{foo: .}` will produce `{"foo": [1, 2, 3]}`, adding an iterator to the +expression so that it reads `{foo: .[]}` will produce 3 individual objects: +`{"foo": 1}`, `{"foo": 2}`, and `{"foo": 3}`. + +### Pipes + +Pipes allow the results of one expression to be fed into another. This can be +used to re-write expressions to help reduce complexity. + +Iterators work with pipes in a fashion similar to object construction, where the +expression on the right-hand side of the pipe is evaluated once for every +iteration. + +As an example, for the object `{"foo": {"a": 1}, "bar": {"a": 2}}`, both the +expression `{z: .[].a}` and `.[] | {z: .a}` produce the same result: `{"z": 1}` +and `{"z": 2}`. diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/workspaces/settings/access.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/workspaces/settings/access.mdx new file mode 100644 index 0000000000..abad3a8b0e --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/workspaces/settings/access.mdx @@ -0,0 +1,54 @@ +--- +page_title: Manage access to workspaces in Terraform Enterprise +description: >- + Learn how to manage access to workspaces by adding teams and configuring their + permissions. +source: terraform-docs-common +--- + +# Manage access to workspaces + + + +@include 'tfc-package-callouts/team-management.mdx' + + + +HCP Terraform workspaces can only be accessed by users with the correct permissions. You can manage permissions for a workspace on a per-team basis. + +Teams with [admin access](/terraform/enterprise/users-teams-organizations/permissions) on a workspace can manage permissions for other teams on that workspace. Since newly created workspaces don't have any team permissions configured, the initial setup of a workspace's permissions requires the owners team or a team with permission to manage workspaces. ([More about permissions.](/terraform/enterprise/users-teams-organizations/permissions)) + +[permissions-citation]: #intentionally-unused---keep-for-maintainers + +-> **API:** See the [Team Access APIs](/terraform/enterprise/api-docs/team-access).
+**Terraform:** See the `tfe` provider's [`tfe_team_access`](https://registry.terraform.io/providers/hashicorp/tfe/latest/docs/resources/team_access) resource. + +## Background + +HCP Terraform manages users' permissions to workspaces with teams. + +- [Workspace-level permissions](/terraform/enterprise/users-teams-organizations/permissions#workspace-permissions) can be granted to an individual team on a particular workspace. These permissions can be managed on the workspace by anyone with admin access to the workspace. +- In addition, some [organization-level permissions](/terraform/enterprise/users-teams-organizations/permissions#organization-permissions) can be granted to a team which apply to every workspace in the organization. For example, the + [manage all workspaces](/terraform/enterprise/users-teams-organizations/permissions#manage-all-workspaces) and [manage all projects](/terraform/enterprise/users-teams-organizations/permissions#manage-all-projects) permissions grant the workspace-level admin permission to every workspace in the organization. Organization-level permissions can only be managed by organization owners. + +## Managing Workspace Access Permissions + +When a user creates a workspace, the following teams can access that workspace with full admin permissions: + +- [the owners team](/terraform/enterprise/users-teams-organizations/teams#the-owners-team) +- teams with "Manage all workspaces" and/or “Manage all projects” [organization permissions](/terraform/enterprise/users-teams-organizations/permissions#project-permissions) +- teams with “Project Admin” project permissions + +You cannot override these teams' permissions through the workspace's specific permissions. + +To manage a team's access to a workspace, select "Team Access" from the workspace's "Settings" menu. + +This screen displays all teams granted workspace-level permissions to the workspace. To add a team, select "Add team and permissions". + +HCP Terraform displays the teams you can grant workspace access to. Select a team to continue and configure that team's permissions. + +There are four [fixed permissions sets](/terraform/enterprise/users-teams-organizations/permissions#fixed-permission-sets) available for basic usage: Read, Plan, Write, and Admin. + +To enable finer-grained selection of non-admin permissions, select "Customize permissions for this team". On this screen, you can select specific permissions to grant the team for the workspace. + +For more information on permissions, see [the documentation on Workspace Permissions](/terraform/enterprise/users-teams-organizations/permissions#workspace-permissions). diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/workspaces/settings/deletion.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/workspaces/settings/deletion.mdx new file mode 100644 index 0000000000..4daa48e161 --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/workspaces/settings/deletion.mdx @@ -0,0 +1,125 @@ +--- +page_title: Destroy infrastructure resources and delete workspaces in Terraform Enterprise +description: >- + Learn how to clean up resources by destroying a workspace's infrastructure and + deleting a workspace in Terraform Enterprise. +source: terraform-docs-common +--- + +# Destroy infrastructure resources and delete workspaces + +HCP Terraform workspaces have two primary delete actions: + +- [Destroying infrastructure](#destroy-infrastructure) deletes resources managed by the HCP Terraform workspace by triggering a destroy run. +- [Deleting a workspace](#delete-workspaces) deletes the workspace itself without triggering a destroy run. + +In general, you should perform both actions in the above order when destroying a workspace to ensure resource cleanup for all of a workspace's managed infrastructure. + +## Destroy Infrastructure + +Destroy plans delete the infrastructure managed by a workspace. We recommend destroying the infrastructure managed by a workspace _before_ deleting the workspace itself. Otherwise, the unmanaged infrastructure resources will continue to exist but will become unmanaged, and you must go into your infrastructure providers to delete the resources manually. + +Before queuing a destroy plan, enable the **Allow destroy plans** toggle setting on this page. + +### Automatically Destroy + + + +@include 'tfc-package-callouts/ephemeral-workspaces.mdx' + + + +Configuring automatic infrastructure destruction for a workspace requires [admin permissions](/terraform/enterprise/users-teams-organizations/permissions#workspace-admins) for that workspace. + +There are two main ways to automatically destroy a workspace's resources: + +- Schedule a run to destroy all resources in a workspace at a specific date and time. +- Configure HCP Terraform to destroy a workspace's infrastructure after a period of workspace inactivity. + +Workspaces can inherit auto-destroy settings from their project. Refer to [managing projects](/terraform/enterprise/projects/manage#automatically-destroy-inactive-workspaces) for more information. You can configure an individual workspace's auto-destroy settings to override the project's configuration. + +You can reduce your spending on infrastructure by automatically destroying temporary resources like development environments. + +After HCP Terraform performs an auto-destroy run, it unsets the `auto-destroy-at` field on the workspace. If you continue using the workspace, you can schedule another future auto-destroy run to remove any new resources. + +!> **Note:** Automatic destroy plans _do not_ prompt you for apply approval in the HCP Terraform user interface. We recommend only using this setting for development environments. + +You can schedule an auto-destroy run using the HCP Terraform web user interface, or the [workspace API](/terraform/enterprise/api-docs/workspaces). + +You can also schedule [notifications](/terraform/enterprise/workspaces/settings/notifications) to alert you 12 and 24 hours before an auto-destroy run, and to report auto-destroy run results. + +#### Destroy at a specific day and time + +To schedule an auto-destroy run at a specific time in HCP Terraform: + +1. Sign in to [HCP Terraform](https://app.terraform.io/) or Terraform Enterprise and navigate to the workspace you want to destroy. +2. Choose **Settings** from the sidebar, then **Destruction and Deletion**. +3. Under **Automatically destroy**, click **Set up auto-destroy**. +4. Enter the desired date and time. HCP Terraform defaults to your local time zone for scheduling and displays how long until the scheduled operation. +5. Click **Confirm auto-destroy**. + +To cancel a scheduled auto-destroy run in HCP Terraform: + +1. Navigate to the workspace's **Settings** > **Destruction and Deletion** page. +2. Under **Automatically destroy**, click **Edit** next to your scheduled run's details. +3. Click **Remove**. + +#### Destroy if a workspace is inactive + +You can configure HCP Terraform to automatically destroy a workspace's infrastructure after a period of inactivity. +A workspace is _inactive_ if the workspace's state has not changed within your designated time period. + +!> **Caution:** As opposed to configuring an auto-destroy run for a specific date and time, this setting _persists_ after queueing auto-destroy runs. + +If you configure a workspace to auto-destroy its infrastructure when inactive, any run that updates Terraform state further delays the scheduled auto-destroy time by the length of your designated timeframe. + +To schedule an auto-destroy run after a period of workspace inactivity: + +1. Sign in to [HCP Terraform](https://app.terraform.io/) or Terraform Enterprise and navigate to the workspace you want to destroy. +2. Choose **Settings** from the sidebar, then **Destruction and Deletion**. +3. Under **Automatically destroy**, click **Set up auto-destroy**. +4. Click the **Destroy if inactive** toggle. +5. Select or customize a desired timeframe of inactivity. +6. Click **Confirm auto-destroy**. + +When configured for the first time, the auto-destroy duration setting displays the scheduled date and time that HCP Terraform will perform the auto-destroy run. +Subsequent auto-destroy runs and Terraform runs that update state both update the next scheduled auto-destroy date. + +After HCP Terraform completes a manual or automatic destroy run, it waits until further state updates to schedule a new auto-destroy run. + +To remove your workspace's auto-destroy run: + +1. Sign in to [HCP Terraform](https://app.terraform.io/) or Terraform Enterprise and navigate to the workspace you want to disable the auto-destroy run for. +2. Choose **Settings** from the sidebar, then **Destruction and Deletion**. +3. Under **Auto-destroy settings**, click **Edit** to change the auto-destroy settings. +4. Click **Remove**. + +When you move a workspace to a different project, it inherits the auto-destroy settings from the new project. If you configured the workspace to override the previous project's auto-destroy settings, it retains the override configuration in the new project. + +## Delete Workspace + +Terraform does not automatically destroy managed infrastructure when you delete a workspace. + +After you delete the workspace and its state file, Terraform can _no longer track or manage_ that infrastructure. You must manually delete or [import](/terraform/cli/commands/import) any remaining resources into another Terraform workspace. + +By default, [workspace administrators](/terraform/enterprise/users-teams-organizations/permissions#workspace-admins) can only delete unlocked workspaces that are not managing any infrastructure. Organization owners can force delete a workspace to override these protections. Organization owners can also configure the [organization's settings](/terraform/enterprise/users-teams-organizations/organizations#general) to let workspace administrators force delete their own workspaces. + +## Data Retention Policies + + +Data retention policies are exclusive to Terraform Enterprise, and not available in HCP Terraform. Learn more about Terraform Enterprise. + + +Define configurable data retention policies for workspaces to help reduce object storage consumption. You can define a policy that allows Terraform to _soft delete_ the backing data associated with configuration versions and state versions. Soft deleting refers to marking a data object for garbage collection so that Terraform can automatically delete the object after a set number of days. + +Once an object is soft deleted, any attempts to read the object will fail. Until the garbage collection grace period elapses, you can still restore an object using the APIs described in the [configuration version documentation](/terraform/enterprise/api-docs/configuration-versions) and [state version documentation](/terraform/enterprise/api-docs/state-versions). After the garbage collection grace period elapses, Terraform permanently deletes the archivist storage. + +The [organization policy](/terraform/enterprise/users-teams-organizations/organizations#destruction-and-deletion) is the default policy applied to workspaces, but members of individual workspaces can override the policy for their workspaces. + +The workspace policy always overrides the organization policy. A workspace admin can set or override the following data retention policies: + +- **Organization default policy** +- **Do not auto-delete** +- **Auto-delete data** + +Setting the data retention policy to **Organization default policy** disables the other data retention policy settings. diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/workspaces/settings/index.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/workspaces/settings/index.mdx new file mode 100644 index 0000000000..0dc4c0ab9c --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/workspaces/settings/index.mdx @@ -0,0 +1,213 @@ +--- +page_title: Workspaces settings in Terraform Enterprise +description: >- + Learn how to configure workspace settings for notifications, permissions, + health, locking, policies, run triggers, SSH keys, team access, version + control, and deletion. +source: terraform-docs-common +--- + +# Workspace settings + +You can change a workspace’s settings after creation. Workspace settings are separated into several pages. + +- [General](#general): Settings that determine how the workspace functions, including its name, description, associated project, Terraform version, and execution mode. +- [Health](/terraform/enterprise/workspaces/health): Settings that let you configure health assessments, including drift detection and continuous validation. +- [Locking](#locking): Locking a workspace temporarily prevents new plans and applies. +- [Notifications](#notifications): Settings that let you configure run notifications. +- [Policies](#policies): Settings that let you toggle between Sentinel policy evaluation experiences. +- [Run Triggers](#run-triggers): Settings that let you configure run triggers. Run triggers allow runs to queue automatically in your workspace when runs in other workspaces are successful. +- [SSH Key](#ssh-key): Set a private SSH key for downloading Terraform modules from Git-based module sources. +- [Team Access](#team-access): Settings that let you manage which teams can view the workspace and use it to provision infrastructure. +- [Version Control](#version-control): Manage the workspace’s VCS integration. +- [Destruction and Deletion](#destruction-and-deletion): Remove a workspace and the infrastructure it manages. + +Changing settings requires admin access to the relevant workspace. ([More about permissions.](/terraform/enterprise/users-teams-organizations/permissions)) + +[permissions-citation]: #intentionally-unused---keep-for-maintainers + +-> **API:** See the [Update a Workspace endpoint](/terraform/enterprise/api-docs/workspaces#update-a-workspace) (`PATCH /organizations/:organization_name/workspaces/:name`). + +## General + +General settings let you change a workspace's name, description, the project it belongs to, and details about how Terraform runs operate. After changing these settings, click **Save settings** at the bottom of the page. + +### ID + +Every workspace has a unique ID that you cannot change. You may need to reference the workspace's ID when using the [HCP Terraform API](/terraform/enterprise/api-docs). + +Click the icon beside the ID to copy it to your clipboard. + +### Name + +The display name of the workspace. + +!> **Warning:** Some API calls refer to a workspace by its name, so changing the name may break existing integrations. + +### Project + +The [project](/terraform/enterprise/projects) that this workspace belongs to. Changing the workspace's project can change the read and write permissions for the workspace and which users can access it. + +To move a workspace, you must have the "Manage all Projects" organization permission or explicit team admin privileges on both the source and destination projects. Remember that moving a workspace to another project may affect user visibility for that project's workspaces. Refer to [Project Permissions](/terraform/enterprise/users-teams-organizations/permissions#project-permissions) for details on workspace access. + +### Description (Optional) + +Enter a brief description of the workspace's purpose or types of infrastructure. + +### Execution Mode + +Whether to use HCP Terraform as the Terraform execution platform for this workspace. + +By default, HCP Terraform uses an organization's [default execution mode](/terraform/enterprise/users-teams-organizations/organizations#organization-settings) to choose the execution platform for a workspace. Alternatively, you can instead choose a custom execution mode for a workspace. + +Specifying the "Remote" execution mode instructs HCP Terraform to perform Terraform runs on its own disposable virtual machines. This provides a consistent and reliable run environment and enables advanced features like Sentinel policy enforcement, cost estimation, notifications, version control integration, and more. + +To disable remote execution for a workspace, change its execution mode to "Local". This mode lets you perform Terraform runs locally with the [CLI-driven run workflow](/terraform/enterprise/run/cli). The workspace will store state, which Terraform can access with the [CLI integration](/terraform/cli/cloud). HCP Terraform does not evaluate workspace variables or variable sets in local execution mode. + +If you instead need to allow HCP Terraform to communicate with isolated, private, or on-premises infrastructure, consider using [HCP Terraform agents](/terraform/cloud-docs/agents). By deploying a lightweight agent, you can establish a simple connection between your environment and HCP Terraform. + +Changing your workspace's execution mode after a run has already been planned will cause the run to error when it is applied. + +To minimize the number of runs that error when changing your workspace's execution mode, you should: + +1. Disable [auto-apply](/terraform/enterprise/workspaces/settings#auto-apply) if you have it enabled. +2. Complete any runs that are no longer in the [pending stage](/terraform/enterprise/run/states#the-pending-stage). +3. [Lock](/terraform/enterprise/workspaces/settings#locking) your workspace to prevent any new runs. +4. Change the execution mode. +5. Enable [auto-apply](/terraform/enterprise/workspaces/settings#auto-apply), if you had it enabled before changing your execution mode. +6. [Unlock](/terraform/enterprise/workspaces/settings#locking) your workspace. + + + + +### Auto-apply + +Whether or not HCP Terraform should automatically apply a successful Terraform plan. If you choose manual apply, an operator must confirm a successful plan and choose to apply it. + +The main auto-apply setting affects runs created by the HCP Terraform user interface, API, and version control webhooks. HCP Terraform also has a separate setting for runs created by [run triggers](/terraform/enterprise/workspaces/settings/run-triggers) from another workspace. + +Auto-apply has the following exceptions: + +- Runs created by the terraform CLI must use the `-auto-approve` argument flag to control auto-apply of a particular run. +- Plans queued by users without permission to apply runs for the workspace must be approved by a user who does have permission. ([More about permissions.](/terraform/enterprise/users-teams-organizations/permissions)) + +[permissions-citation]: #intentionally-unused---keep-for-maintainers + +### Terraform Version + +The Terraform version to use for all operations in the workspace. The default value is whichever release was current when HCP Terraform created the workspace. You can also update a workspace's Terraform version to an exact version or a valid [version constraint](/terraform/language/expressions/version-constraints). + +> **Hands-on:** Try the [Upgrade Terraform Version in HCP Terraform](/terraform/tutorials/cloud/cloud-versions) tutorial. + +-> **API:** You can specify a Terraform version when you [create a workspace](/terraform/enterprise/api-docs/workspaces#create-a-workspace) with the API. + +### Terraform Working Directory + +The directory where Terraform will execute, specified as a relative path from the root of the configuration directory. Defaults to the root of the configuration directory. + +HCP Terraform will change to this directory before starting a Terraform run, and will report an error if the directory does not exist. + +Setting a working directory creates a default filter for automatic run triggering, and sometimes causes CLI-driven runs to upload additional configuration content. + +#### Default Run Trigger Filtering + +In VCS-backed workspaces that specify a working directory, HCP Terraform assumes that only changes within that working directory should trigger a run. You can override this behavior with the [Automatic Run Triggering](/terraform/enterprise/workspaces/settings/vcs#automatic-run-triggering) settings. + +#### Parent Directory Uploads + +If a working directory is configured, HCP Terraform always expects the complete shared configuration directory to be available, since the configuration might use local modules from outside its working directory. + +In [runs triggered by VCS commits](/terraform/enterprise/run/ui), this is automatic. In [CLI-driven runs](/terraform/enterprise/run/cli), Terraform's CLI sometimes uploads additional content: + +- When the local working directory _does not match_ the name of the configured working directory, Terraform assumes it is the root of the configuration directory, and uploads only the local working directory. +- When the local working directory _matches_ the name of the configured working directory, Terraform uploads one or more parents of the local working directory, according to the depth of the configured working directory. (For example, a working directory of `production` is only one level deep, so Terraform would upload the immediate parent directory. `consul/production` is two levels deep, so Terraform would upload the parent and grandparent directories.) + +If you use the working directory setting, always run Terraform from a complete copy of the configuration directory. Moving one subdirectory to a new location can result in unexpected content uploads. + +### Remote State Sharing + +Which other workspaces within the organization can access the state of the workspace during [runs managed by HCP Terraform](/terraform/enterprise/run/remote-operations#remote-operations). The [`terraform_remote_state` data source](/terraform/language/state/remote-state-data) relies on state sharing to access workspace outputs. + +- If "Share state globally" is enabled, all other workspaces within the organization can access this workspace's state during runs. +- If global sharing is turned off, you can specify a list of workspaces within the organization that can access this workspace's state; no other workspaces will be allowed. + + The workspace selector is searchable; if you don't initially see a workspace you're looking for, type part of its name. + +By default, new workspaces in HCP Terraform do not allow other workspaces to access their state. We recommend that you follow the principle of least privilege and only enable state access between workspaces that specifically need information from each other. To configure remote state sharing, a user must have read access for the destination workspace. If a user does not have access to the destination workspace due to scoped project or workspace permissions, they will not have complete visibility into the list of other workspace that can access its state. + +-> **Note:** The default access permissions for new workspaces in HCP Terraform changed in April 2021. Workspaces created before this change default to allowing global access within their organization. These workspaces can be changed to more restrictive access at any time. Terraform Enterprise administrators can choose whether new workspaces on their instances default to global access or selective access. + +### User Interface + +Select the user experience for displaying plan and apply details. + +The default experience is _Structured Run Output_, which displays your plan and apply results in a human-readable format. This includes nodes that you can expand to view details about each resource and any configured output. + +The Console UI experience is the traditional Terraform experience, where live text logging is streamed in real time to the UI. This experience most closely emulates the CLI output. + +~> **Note:** Your workspace must be configured to use a Terraform version of 1.0.5 or higher for the Structured Run Output experience to be fully supported. Workspaces running versions from 0.15.2 may see partial functionality. Workspaces running versions below 0.15.2 will default to the "Console UI" experience regardless of the User Interface setting. + +## Locking + +~> **Important:** Unlike other settings, locks can also be managed by users with permission to lock and unlock the workspace. ([More about permissions.](/terraform/enterprise/users-teams-organizations/permissions)) + +[permissions-citation]: #intentionally-unused---keep-for-maintainers + +If you need to prevent Terraform runs for any reason, you can lock a workspace. This prevents all applies (and many kinds of plans) from proceeding, and affects runs created via UI, CLI, API, and automated systems. To enable runs again, a user must unlock the workspace. + +Two kinds of run operations can ignore workspace locking because they cannot affect resources or state and do not attempt to lock the workspace themselves: + +- Plan-only runs. +- The planning stages of [saved plan runs](/terraform/enterprise/run/modes-and-options.mdx#saved-plans). You can only _apply_ a saved plan if the workspace is unlocked, and applying that plan locks the workspace as usual. Terraform Enterprise does not yet support this workflow. + +Locking a workspace also restricts state uploads. In order to upload state, the workspace must be locked by the user who is uploading state. + +Users with permission to lock and unlock a workspace can't unlock a workspace which was locked by another user. Users with admin access to a workspace can force unlock a workspace even if another user has locked it. + +[permissions-citation]: #intentionally-unused---keep-for-maintainers + +Locks are managed with a single "Lock/Unlock/Force unlock ``" button. HCP Terraform asks for confirmation when unlocking. + +You can also manage the workspace's lock from the **Actions** menu. + +## Notifications + +The "Notifications" page allows HCP Terraform to send webhooks to external services whenever specific run events occur in a workspace. + +See [Run Notifications](/terraform/enterprise/workspaces/settings/notifications) for detailed information about configuring notifications. + +## Policies + +HCP Terraform offers two experiences for Sentinel policy evaluations. On the "Policies" page, you can adjust your **Sentinel Experience** settings to your preferred experience. By default, HCP Terraform enables the newest policy evaluation experience. + +To toggle between the two Sentinel policy evaluation experiences, click the **Enable the new Sentinel policy experience** toggle under the **Sentinel Experience** heading. HCP Terraform persists your changes automatically. If HCP Terraform is performing a run on a different page, you must refresh that page to see changes to your policy evaluation experience. + +## Run Triggers + +The "Run Triggers" page configures connections between a workspace and one or more source workspaces. These connections, called "run triggers", allow runs to queue automatically in a workspace on successful apply of runs in any of the source workspaces. + +See [Run Triggers](/terraform/enterprise/workspaces/settings/run-triggers) for detailed information about configuring run triggers. + +## SSH Key + +If a workspace's configuration uses [Git-based module sources](/terraform/language/modules/sources) to reference Terraform modules in private Git repositories, Terraform needs an SSH key to clone those repositories. The "SSH Key" page lets you choose which key it should use. + +See [Using SSH Keys for Cloning Modules](/terraform/enterprise/workspaces/settings/ssh-keys) for detailed information about this page. + +## Team Access + +The "Team Access" page configures which teams can perform which actions on a workspace. + +See [Managing Access to Workspaces](/terraform/enterprise/workspaces/settings/access) for detailed information. + +## Version Control + +The "Version Control" page configures an optional VCS repository that contains the workspace's Terraform configuration. Version control integration is only relevant for workspaces with [remote execution](#execution-mode) enabled. + +See [VCS Connections](/terraform/enterprise/workspaces/settings/vcs) for detailed information about this page. + +## Destruction and Deletion + +The **Destruction and Deletion** page allows [admin users](/terraform/enterprise/users-teams-organizations/permissions) to delete a workspace's managed infrastructure or delete the workspace itself. + +For details, refer to [Destruction and Deletion](/terraform/enterprise/workspaces/settings/deletion) for detailed information about this page. diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/workspaces/settings/notifications.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/workspaces/settings/notifications.mdx new file mode 100644 index 0000000000..2c9b97b15e --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/workspaces/settings/notifications.mdx @@ -0,0 +1,130 @@ +--- +page_title: Workspace notifications in Terraform Enterprise +description: >- + Use webhooks to notify external systems about run progress and other events in + Terraform Enterprise. Learn to create and enable workspace notifications. +source: terraform-docs-common +--- + +# Workspace notifications + +HCP Terraform can use webhooks to notify external systems about run progress and other events. Each workspace has its own notification settings and can notify up to 20 destinations. + +-> **Note:** [Speculative plans](/terraform/enterprise/run/modes-and-options#plan-only-speculative-plan) and workspaces configured with `Local` [execution mode](/terraform/enterprise/workspaces/settings#execution-mode) do not support notifications. + +Configuring notifications requires admin access to the workspace. Refer to [Permissions](/terraform/enterprise/users-teams-organizations/permissions) for details. + +[permissions-citation]: #intentionally-unused---keep-for-maintainers + +-> **API:** Refer to [Notification Configuration APIs](/terraform/enterprise/api-docs/notification-configurations). + +## Viewing and Managing Notification Settings + +To add, edit, or delete notifications for a workspace, go to the workspace and click **Settings > Notifications**. The **Notifications** page appears, showing existing notification configurations. + +## Creating a Notification Configuration + +A notification configuration specifies a destination URL, a payload type, and the events that should generate a notification. To create a notification configuration: + +1. Sign in to [HCP Terraform](https://app.terraform.io/) or Terraform Enterprise and select the workspace you want to configure notifications for. + +2. Click **Settings**, then **Notifications**. + +3. Click **Create a Notification**. The **Create a Notification** form appears. + +4. Configure the notifications: + +- **Destination:** HCP Terraform can deliver either a generic payload or a payload formatted specifically for Slack, Microsoft Teams, or Email. Refer to [Notification Payloads](#notification-payloads) for details. + +- **Name:** A display name for this notification configuration. + +- **Webhook URL** This URL is only available for generic, Slack, and Microsoft Teams webhooks. The webhook URL is the destination for the webhook payload. This URL must accept HTTP or HTTPS `POST` requests and should be able to use the chosen payload type. For details, refer to Slack's documentation on [creating an incoming webhook](https://api.slack.com/messaging/webhooks#create_a_webhook) and Microsoft's documentation on [creating a workflow from a channel in teams](https://support.microsoft.com/en-us/office/creating-a-workflow-from-a-channel-in-teams-242eb8f2-f328-45be-b81f-9817b51a5f0e). + +- **Token** (Optional) This notification is only available for generic webhooks. A token is an arbitrary secret string that HCP Terraform will use to sign its notification webhooks. Refer to [Notification Authenticity][inpage-hmac] for details. You cannot view the token after you save the notification configuration. + +- **Email Recipients** This notification is only available for emails. Select users that should receive notifications. + +- **Workspace Events**: HCP Terraform can send notifications for all events or only for specific events. The following events are available: + + - **Drift**: HCP Terraform detected configuration drift. This notification is only available if you enable [health assessments](/terraform/enterprise/workspaces/health) for the workspace. + - **Check Failure:** HCP Terraform detected one or more failed continuous validation checks. This notification is only available if you enable health assessments for the workspace. + - **Health Assessment Fail**: A health assessment failed. This notification is only available if you enable health assessments for the workspace. Health assessments fail when HCP Terraform cannot perform drift detection, continuous validation, or both. The notification does not specify the cause of the failure, but you can use the [Assessment Result](/terraform/enterprise/api-docs/assessment-results) logs to help diagnose the issue. + - **Auto destroy reminder**: Sends reminders 12 and 24 hours before a scheduled auto destroy run. + - **Auto destroy results**: HCP Terraform performed an auto destroy run in the workspace. Reports both successful and errored runs. + + + +@include 'tfc-package-callouts/health-assessments.mdx' + + + +- **Run Events:** HCP Terraform can send notifications for all events or only for specific events. The following events are available: + - **Created**: A run begins and enters the [Pending stage](/terraform/enterprise/run/states#the-pending-stage). + - **Planning**: A run acquires the lock and starts to execute. + - **Needs Attention**: A plan has changes and Terraform requires user input to continue. This event may include approving the plan or a [policy override](/terraform/enterprise/run/states#the-policy-check-stage). + - **Applying**: A run enters the [Apply stage](/terraform/enterprise/run/states#the-apply-stage), where Terraform makes the infrastructure changes described in the plan. + - **Completed**: A run completed successfully. + - **Errored**: A run terminated early due to error or cancellation. + +4. Click **Create a notification**. + +## Enabling and Verifying a Configuration + +To enable or disable a configuration, toggle the **Enabled/Disabled** switch on its detail page. HCP Terraform will attempt to verify the configuration for generic and slack webhooks by sending a test message, and will enable the notification configuration if the test succeeds. + +For a verification to be successful, the destination must respond with a `2xx` HTTP code. If verification fails, HCP Terraform displays the error message and the configuration will remain disabled. + +For both successful and unsuccessful verifications, click the **Last Response** box to view more information about the verification results. You can also send additional test messages with the **Send a Test** link. + +## Notification Payloads + +### Slack + +Notifications to Slack will contain the following information: + +- The run's workspace (as a link) +- The HCP Terraform username and avatar of the person that created the run +- The run ID (as a link) +- The reason the run was queued (usually a commit message or a custom message) +- The time the run was created +- The event that triggered the notification and the time that event occurred + +### Microsoft Teams + +Notifications to Microsoft Teams contain the following information: + +- The run's workspace (as a link) +- The HCP Terraform username and avatar of the person that created the run +- The run ID +- A link to view the run +- The reason the run was queued (usually a commit message or a custom message) +- The time the run was created +- The event that triggered the notification and the time that event occurred + +### Email + +Email notifications will contain the following information: + +- The run's workspace (as a link) +- The run ID (as a link) +- The event that triggered the notification, and if the run needs to be acted upon or not + +### Generic + +A generic notification will contain information about a run and its state at the time the triggering event occurred. The complete generic notification payload is described in the [API documentation][generic-payload]. + +[generic-payload]: /terraform/enterprise/api-docs/notification-configurations#notification-payload + +Some of the values in the payload can be used to retrieve additional information through the API, such as: + +- The [run ID](/terraform/enterprise/api-docs/run#get-run-details) +- The [workspace ID](/terraform/enterprise/api-docs/workspaces#list-workspaces) +- The [organization name](/terraform/enterprise/api-docs/organizations#show-an-organization) + +## Notification Authenticity + +[inpage-hmac]: #notification-authenticity + +Slack notifications use Slack's own protocols for verifying HCP Terraform's webhook requests. + +Generic notifications can include a signature for verifying the request. For notification configurations that include a secret token, HCP Terraform's webhook requests will include an `X-TFE-Notification-Signature` header, which contains an HMAC signature computed from the token using the SHA-512 digest algorithm. The receiving service is responsible for validating the signature. More information, as well as an example of how to validate the signature, can be found in the [API documentation](/terraform/enterprise/api-docs/notification-configurations#notification-authenticity). diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/workspaces/settings/run-tasks.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/workspaces/settings/run-tasks.mdx new file mode 100644 index 0000000000..baab7caf6e --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/workspaces/settings/run-tasks.mdx @@ -0,0 +1,134 @@ +--- +page_title: Terraform Enterprise run tasks +description: >- + Run tasks integrate third-party tools into the run lifecycle. Learn how to + create, delete, and associate run tasks with workspaces. +source: terraform-docs-common +--- + +[entitlement]: /terraform/enterprise/api-docs#feature-entitlements + +# Run tasks + +HCP Terraform run tasks let you directly integrate third-party tools and services at certain stages in the HCP Terraform run lifecycle. Use run tasks to validate Terraform configuration files, analyze execution plans before applying them, scan for security vulnerabilities, or perform other custom actions. + +Run tasks send data about a run to an external service at [specific run stages](#understanding-run-tasks-within-a-run). The external service processes the data, evaluates whether the run passes or fails, and sends a response to HCP Terraform. HCP Terraform then uses this response and the run task enforcement level to determine if a run can proceed. [Explore run tasks in the Terraform registry](https://registry.terraform.io/browse/run-tasks). + + + +@include 'tfc-package-callouts/run-tasks.mdx' + + + +You can manage run tasks through the HCP Terraform UI or the [Run Tasks API](/terraform/enterprise/api-docs/run-tasks/run-tasks). + +> **Hands-on:** Try the [HCP Packer validation run task](/packer/tutorials/hcp/setup-hcp-terraform-run-task) tutorial. + +## Requirements + +**Terraform Version** - You can assign run tasks to workspaces that use a Terraform version of 1.1.9 and later. You can downgrade a workspace with existing runs to use a prior Terraform version without causing an error. However, HCP Terraform no longer triggers the run tasks during plan and apply operations. + +**Permissions** - To create a run task, you must have a user account with the [Manage Run Tasks permission](/terraform/enterprise/users-teams-organizations/permissions#manage-run-tasks). To associate run tasks with a workspace, you need the [Manage Workspace Run Tasks permission](/terraform/enterprise/users-teams-organizations/permissions#general-workspace-permissions) on that particular workspace. + +## Creating a Run Task + +Explore the full list of [run tasks in the Terraform Registry](https://registry.terraform.io/browse/run-tasks). + +Run tasks send an API payload to an external service. The API payload contains run-related information, including a callback URL, which the service uses to return a pass or fail status to HCP Terraform. + +For example, the [HCP Packer integration](/terraform/enterprise/integrations/run-tasks#hcp-packer-run-task) checks image artifacts within a Terraform configuration for validity. If the configuration references images marked as unusable (revoked), then the run task fails and provides an error message. + +To create a new run task: + +1. Sign in to [HCP Terraform](https://app.terraform.io/) or Terraform Enterprise and navigate to the workspace where you want to create a run task. + +2. Choose **Settings** from the sidebar, then **Run Tasks**. + +3. Click **Create a new run task**. The **Run Tasks** page appears. + +4. Enter the information about the run task to be configured: + + - **Enabled** (optional): Whether the run task will run across all associated workspaces. New tasks are enabled by default. + - **Name** (required): A human-readable name for the run task. This will be displayed in workspace configuration pages and can contain letters, numbers, dashes and underscores. + - **Endpoint URL** (required): The URL for the external service. Run tasks will POST the [run tasks payload](/terraform/enterprise/integrations/run-tasks#integration-details) to this URL. + - **Description** (optional): A human-readable description for the run task. This information can contain letters, numbers, spaces, and special characters. + - **HMAC key** (optional): A secret key that may be required by the external service to verify request authenticity. + +5. Select a **Source**: + + - **Managed** HCP Terraform's infrastructure initiates run task requests. This is the default option. + - **Agent** HCP Terraform can initiate run task requests within your self-managed HCP Terraform agents to let run tasks communicate with isolated, private, or on-premises infrastructure. To use this option, an HCP Terraform agent in the agent pool must have [request forwarding](/terraform/cloud-docs/agents/request-forwarding) enabled, and you must be on the [HCP Terraform **Premium** edition](https://www.hashicorp.com/products/terraform/pricing). + +6. Click **Create run task**. The run task is now available within the organization, and you can associate it with one or more workspaces. + +### Global Run Tasks + +When you create a new run task, you can choose to apply it globally to every workspace in an organization. Your organization must have the `global-run-task` [entitlement][] to use global run tasks. + +1. Select the **Global** checkbox + +2. Choose when HCP Terraform should start the run task: + + - **Pre-plan**: Before Terraform creates the plan. + - **Post-plan**: After Terraform creates the plan. + - **Pre-apply**: Before Terraform applies a plan. + - **Post-apply**: After Terraform applies a plan. + +3. Choose an enforcement level: + + - **Advisory**: Run tasks can not block a run from completing. If the task fails, the run proceeds with a warning in the user interface. + - **Mandatory**: Failed run tasks can block a run from completing. If the task fails (including timeouts or unexpected remote errors), the run stops and errors with a warning in the user interface. + +## Associating Run Tasks with a Workspace + +1. Sign in to [HCP Terraform](https://app.terraform.io/) or Terraform Enterprise, and choose **Workspaces** from the sidebar. + +2. Select the workspace that you want to associate with a run task. + +3. Open the **Settings** menu and select **Run Tasks**. + +4. Click the **+** next to the task you want to add to the workspace. + +5. Choose when HCP Terraform should start the run task: + + - **Pre-plan**: Before Terraform creates the plan. + - **Post-plan**: After Terraform creates the plan. + - **Pre-apply**: Before Terraform applies a plan. + - **Post-apply**: After Terraform applies a plan. + +6. Choose an enforcement level: + + - **Advisory**: Run tasks can not block a run from completing. If the task fails, the run will proceed with a warning in the UI. + - **Mandatory**: Run tasks can block a run from completing. If the task fails (including a timeout or unexpected remote error condition), the run will transition to an Errored state with a warning in the UI. + +7. Click **Create**. Your run task is now configured. + +## Understanding Run Tasks Within a Run + +Run tasks perform actions before and after, the [plan](/terraform/enterprise/run/states#the-plan-stage) and [apply](/terraform/enterprise/run/states#the-apply-stage) stages of a [Terraform run](/terraform/enterprise/run/remote-operations). Once all run tasks complete, the run ends based on the most restrictive enforcement level in each associated run task. + +For example, if a mandatory task fails and an advisory task succeeds, the run fails. If an advisory task fails, but a mandatory task succeeds, the run succeeds and proceeds to the apply stage. Regardless of the exit status of a task, HCP Terraform displays the status and any related message data in the UI. + +## Removing a Run Task from a Workspace + +Removing a run task from a workspace does not delete it from the organization. To remove a run task from a specific workspace: + +1. Sign in to [HCP Terraform](https://app.terraform.io/) or Terraform Enterprise and navigate to the workspace where you want to remove a run task. + +2. Choose **Settings** from the sidebar, then **Run Tasks**. + +3. Click the ellipses (...) on the associated run task, and then click **Remove**. The run task will no longer be applied to runs within the workspace. + +## Deleting a Run Task + +You must remove a run task from all associated workspaces before you can delete it. To delete a run task: + +1. Sign in to [HCP Terraform](https://app.terraform.io/) or Terraform Enterprise and navigate to the workspace associated with a run task you want to delete. + +2. Choose **Settings** from the sidebar, then **Run Tasks**. + +3. Click the ellipses (...) next to the run task you want to delete, and then click **Edit**. + +4. Click **Delete run task**. + +You cannot delete run tasks that are still associated with a workspace. If you attempt this, you will see a warning in the UI containing a list of all workspaces that are associated with the run task. diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/workspaces/settings/run-triggers.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/workspaces/settings/run-triggers.mdx new file mode 100644 index 0000000000..13bda16de0 --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/workspaces/settings/run-triggers.mdx @@ -0,0 +1,51 @@ +--- +page_title: Terraform Enterprise run triggers +description: >- + Use run triggers to connect workspaces within your organization. Learn how to + view, create, and manage run triggers. +source: terraform-docs-common +--- + +# Run triggers + +> **Hands-on:** Try the [Connect Workspaces with Run Triggers](/terraform/tutorials/cloud/cloud-run-triggers?utm_source=WEBSITE&utm_medium=WEB_IO&utm_offer=ARTICLE_PAGE&utm_content=DOCS) tutorial. + +HCP Terraform provides a way to connect your workspace to one or more workspaces within your organization, known as "source workspaces". These connections, called run triggers, allow runs to queue automatically in your workspace on successful apply of runs in any of the source workspaces. You can connect each workspace to up to 20 source workspaces. + +Run triggers are designed for workspaces that rely on information or infrastructure produced by other workspaces. If a Terraform configuration uses [data sources](/terraform/language/data-sources) to read values that might be changed by another workspace, run triggers let you explicitly specify that external dependency. + +-> **API:** See the [Run Triggers APIs](/terraform/enterprise/api-docs/run-triggers). + +## Viewing and Managing Run Triggers + +To add or delete a run trigger, navigate to the desired workspace and choose "Run Triggers" from the "Settings" menu: + +This takes you to the run triggers settings page, which shows any existing run triggers. Configuring run triggers requires admin access to the workspace. ([More about permissions.](/terraform/enterprise/users-teams-organizations/permissions)) Admins are able to delete any of their workspace’s run triggers from this page. + +[permissions-citation]: #intentionally-unused---keep-for-maintainers + +## Creating a Run Trigger + +Creating run triggers requires admin access to the workspace. You must also have permission to read runs for the source workspace you wish to connect to. ([More about permissions.](/terraform/enterprise/users-teams-organizations/permissions)) + +[permissions-citation]: #intentionally-unused---keep-for-maintainers + +Under the "Source Workspaces" section, select the workspace you would like to connect as your source and click "Add workspace". You now have a run trigger established with your source workspace. Any run from that source workspace which applies successfully will now cause a new run to be queued in your workspace. + +## Run Triggers Auto-Apply Setting + +Runs initiated by a run trigger do not auto-apply unless you enable the **Auto-apply run triggers** setting. This setting operates independently of the primary workspace [auto-apply](/terraform/enterprise/workspaces/settings#auto-apply) setting. + +## Interacting with Run Triggers + +Runs which are queued in your workspace through a run trigger will include extra information in their run details section. This includes links to the source workspace and the successfully applied run that activated the run trigger. + +The source workspace includes a message in the [plan](/terraform/docs/glossary#plan-noun-1-) and [apply](/terraform/docs/glossary#apply-noun-) run details that specifies the workspaces where HCP Terraform automatically starts a run. + +## Using a Remote State Data Source + +A common way to share information between workspaces is the [`terraform_remote_state` data source](/terraform/language/state/remote-state-data), which allows a Terraform configuration to access a source workspace's root-level [outputs](/terraform/language/values/outputs). + +Before other workspaces can read the outputs of a workspace, it must be configured to allow access. For more information about cross-workspace state access in HCP Terraform, see [Terraform State in HCP Terraform](/terraform/enterprise/workspaces/state). + +~> **Important:** We recommend using the [`tfe_outputs` data source](https://registry.terraform.io/providers/hashicorp/tfe/latest/docs/data-sources/outputs) in the [HCP Terraform/Enterprise Provider](https://registry.terraform.io/providers/hashicorp/tfe/latest/docs) to access remote state outputs in HCP Terraform or Terraform Enterprise. The `tfe_outputs` data source is more secure because it does not require full access to workspace state to fetch outputs. diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/workspaces/settings/ssh-keys.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/workspaces/settings/ssh-keys.mdx new file mode 100644 index 0000000000..d2a9fde19e --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/workspaces/settings/ssh-keys.mdx @@ -0,0 +1,63 @@ +--- +page_title: Use SSH Keys to clone modules in Terraform Enterprise +description: >- + Learn how to configure the SSH keys that Terraform uses to pull modules from + private Git repositories. Learn to add, delete, and assign keys to workspaces. +source: terraform-docs-common +--- + +# Use SSH Keys for cloning modules + +Terraform configurations can pull in Terraform modules from [a variety of different sources](/terraform/language/modules/sources), and private Git repositories are a common source for private modules. + +-> **Note:** The [private module registry](/terraform/enterprise/registry) is an easier way to manage private Terraform modules in HCP Terraform, and doesn't require setting SSH keys for workspaces. The rest of this page only applies to configurations that fetch modules directly from a private Git repository. + +To access a private Git repository, Terraform either needs login credentials (for HTTPS access) or an SSH key. HCP Terraform can store private SSH keys centrally, and you can easily use them in any workspace that clones modules from a Git server. + +-> **Note:** SSH keys for cloning Terraform modules from Git repos are only used during Terraform runs. They are managed separately from any [keys used for bringing VCS content into HCP Terraform](/terraform/enterprise/vcs#ssh-keys). + +HCP Terraform manages SSH keys used to clone Terraform modules at the organization level, and allows multiple keys to be added for the organization. You can add or delete keys via the organization's settings. Once a key is uploaded, the text of the key is not displayed to users. + +To assign a key to a workspace, go to its settings and choose a previously added key from the drop-down menu on Integrations under "SSH Key". Each workspace can only use one SSH key. + +-> **API:** See the [SSH Keys API](/terraform/enterprise/api-docs/ssh-keys) and [Assign an SSH Key to a Workspace endpoint](/terraform/enterprise/api-docs/workspaces#assign-an-ssh-key-to-a-workspace).
+**Terraform:** See the `tfe` provider's [`tfe_ssh_key`](https://registry.terraform.io/providers/hashicorp/tfe/latest/docs/resources/ssh_key) resource. + +## Adding Keys + +To add a key: + +1. Sign in to [HCP Terraform](https://app.terraform.io/) or Terraform Enterprise and choose the organization you want to add a key to. +2. Choose **Settings** from the sidebar, then **SSH Keys**. This page has a form for adding new keys and a list of existing keys. +3. Obtain a PEM formatted SSH keypair that HCP Terraform can use to download modules during a Terraform run. You might already have an appropriate key. If not, create one on a secure workstation and distribute the public key to your VCS provider(s). Do not use or generate a key that has a passphrase. Git is running non-interactively and cannot prompt for it. + + The exact command to create a PEM formatted SSH keypair depends on your operating system. The following example command creates a `service_terraform` file with the private key and a `service_terraform.pub` file with the public key. + + ```bash + ssh-keygen -t rsa -m PEM -f "/Users//.ssh/service_terraform" -C "service_terraform_enterprise" + ``` +4. Enter a name for the key in the **Name** field. Choose something identifiable. Keys are only listed by name. HCP Terraform retains the text of each private key, but never displays it for any purpose. +5. Paste the text of the private key in the **Private SSH Key** field. +6. Click **Add Private SSH Key**. + +The new key appears in the list of keys on the page. + +If you upload an invalid SSH key, upload the correct key and push a new commit for the new key to take effect. + +## Deleting Keys + +Before deleting a key, you should assign a new key to any workspaces that are using it. Otherwise workspaces using the deleted key can no longer clone modules from private repositories. This inability might cause Terraform runs to fail. + +To delete a key: + +1. Sign in to [HCP Terraform](https://app.terraform.io/) or Terraform Enterprise and choose the organization you want to delete a key from. +2. Choose **Settings** from the sidebar, then **SSH Keys**. +3. Find the key you want to delete and click **Delete**. + +## Assigning Keys to Workspaces + +To assign a key to a workspace, navigate to that workspace's page and choose "SSH Key" from the "Settings" menu. + +Select a named key from the "SSH Key" dropdown menu, then click the "Update SSH key" button. + +In subsequent runs, HCP Terraform will use the selected SSH key in this workspace when cloning modules from Git. diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/workspaces/settings/vcs.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/workspaces/settings/vcs.mdx new file mode 100644 index 0000000000..a1e34e5a58 --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/workspaces/settings/vcs.mdx @@ -0,0 +1,127 @@ +--- +page_title: Configure workspace VCS connections in Terraform Enterprise +description: >- + Learn how to use the Terraform Enterprise UI to connect a workspace to a + version control system (VCS) repository that contains a Terraform + configuration. +source: terraform-docs-common +--- + +# Configure workspace VCS connections + +You can connect any HCP Terraform [workspace](/terraform/enterprise/workspaces) to a version control system (VCS) repository that contains a Terraform configuration. This page explains the workspace VCS connection settings in the HCP Terraform UI. + +Refer to [Terraform Configurations in HCP Terraform Workspaces](/terraform/enterprise/workspaces/configurations) for details on handling configuration versions and connected repositories. Refer to [Connecting VCS Providers](/terraform/enterprise/vcs) for a list of supported VCS providers and details about configuring VCS access, viewing VCS events, etc. + +## API + +You can use the [Update a Workspace endpoint](/terraform/enterprise/api-docs/workspaces#update-a-workspace) in the Workspaces API to change one or more VCS settings. We also recommend using this endpoint to automate changing VCS connections for many workspaces at once. For example, when you move a VCS server or remove a deprecated API version. + +## Version Control Settings + +To change a workspace's VCS settings: + +1. Sign in to [HCP Terraform](https://app.terraform.io/) or Terraform Enterprise and find the workspace you want to update. +2. Choose **Settings** from the sidebar, then **Version Control**. +3. Choose the settings you want, then click **Update VCS settings**. + +You can update the following types of VCS settings for the workspace. + +### VCS Connection + +You can take one of the following actions: + +- To add a new VCS connection, click **Connect to version control**. Select **Version control workflow** and follow the steps to [select a VCS provider and repository](/terraform/enterprise/workspaces/create#create-a-workspace). +- To edit an existing VCS connection, click **Change source**. Choose the **Version control workflow** and follow the steps to [select VCS provider and repository](/terraform/enterprise/workspaces/create#create-a-workspace). +- To remove the VCS connection, click **Change source**. Select either the **CLI-driven workflow** or the **API-driven workflow**, and click **Update VCS settings**. The workspace is no longer connected to VCS. + +[permissions-citation]: #intentionally-unused---keep-for-maintainers + +### Terraform Working Directory + +Specify the directory where Terraform will execute runs. This defaults to the root directory in your repository, but you may want to specify another directory if you have directories for multiple different Terraform configurations within the same repository. For example, if you had one `staging` directory and one `production` directory. + +A working directory is required when you use [trigger prefixes](#automatic-run-triggering). + +### Apply Method + +Choose a workflow for Terraform runs. + +- **Auto apply:** Terraform will apply changes from successful plans without prompting for approval. A push to the default branch of your repository will trigger a plan and apply cycle. You may want to do this in non-interactive environments, like continuous deployment workflows. + + !> **Warning:** If you choose auto apply, make sure that no one can change your infrastructure outside of your automated build pipeline. This reduces the risk of configuration drift and unexpected changes. + +- **Manual apply:** Terraform will ask for approval before applying changes from a successful plan. A push to the default branch of your repository will trigger a plan, and then Terraform will wait for confirmation. + +### Automatic Run Triggering + +HCP Terraform uses your VCS provider's API to retrieve the changed files in your repository. You can choose one of the following options to specify which changes trigger Terraform runs. + +#### Always trigger runs + +This option instructs Terraform to begin a run when changes are pushed to any file within the repository. This can be useful for repositories that do not have multiple configurations but require a working directory for some other reason. However, we do not recommend this approach for true monorepos, as it queues unnecessary runs and slows down your ability to provision infrastructure. + +#### Only trigger runs when files in specified paths change + +This option instructs Terraform to begin new runs only for changes that affect specified files and directories. This behavior also applies to [speculative plans](/terraform/enterprise/run/remote-operations#speculative-plans) on pull requests. + +You can use trigger patterns and trigger prefixes in the **Add path** field to specify groups of files and directories. + +- **Trigger Patterns:** (Recommended) Use glob patterns to specify the files that should trigger a new run. For example, `/submodule/**/*.tf`, specifies all files with the `.tf` extension that are nested below the `submodule` directory. You can also use more complex patterns like `/**/networking/**/*`, which specifies all files that have a `networking` folder in their file path. (e.g., `/submodule/service-1/networking/private/main.tf`). Refer to [Glob Patterns for Automatic Run Triggering](#glob-patterns-for-automatic-run-triggering) for details. +- **Trigger Prefixes:** HCP Terraform will queue runs for changes in any of the specified trigger directories matching the provided prefixes (including the working directory). For example, if you use a top-level `modules` directory to share Terraform code across multiple configurations, changes to the shared modules are relevant to every workspace that uses that repository. You can add `modules` as a trigger directory for each workspace to track changes to shared code. + +-> **Note:** HCP Terraform triggers runs on all attached workspaces if it does not receive a list of changed files or if that list is too large to process. When this happens, HCP Terraform may show several runs with completed plans that do not result in infrastructure changes. + +#### Trigger runs when a git tag is published + +This option instructs Terraform to begin new runs only for changes that have a specific tag format. + +The tag format can be chosen between the following options: + +- **Semantic Versioning:** It matches tags in the popular [SemVer format](https://semver.org/). For example, `0.4.2`. +- **Version contains a prefix:** It matches tags which have an additional prefix before the [SemVer format](https://semver.org/). For example, `version-0.4.2`. +- **Version contains a suffix:** It matches tags which have an additional suffix after the [SemVer format](https://semver.org/). For example `0.4.2-alpha`. +- **Custom Regular Expression:** You can define your own regex for HCP Terraform to match against tags. + +You must include an additional `\` to escape the regex pattern when you manage your workspace with the [hashicorp/tfe provider](https://registry.terraform.io/providers/hashicorp/tfe/latest/docs/resources/workspace#tags_regex) and trigger runs through matching git tags. Refer to [Terraform escape sequences](/terraform/language/expressions/strings#escape-sequences) for more details. + +| Tag Format | Regex Pattern | Regex Pattern (Escaped) | +| ----------------------------- | --------------- | ----------------------- | +| **Semantic Versioning** | `^\d+.\d+.\d+$` | `^\\d+.\\d+.\\d+$` | +| **Version contains a prefix** | `\d+.\d+.\d+$` | `\\d+.\\d+.\\d+$` | +| **Version contains a suffix** | `^\d+.\d+.\d+` | `^\\d+.\\d+.\\d+` | + +HCP Terraform triggers runs for all tags matching this pattern, regardless of the value in the [VCS Branch](#vcs-branch) setting. + +### VCS Branch + +This setting designates which branch of the repository HCP Terraform should use when the workspace is set to [Always Trigger Runs](#always-trigger-runs) or [Only trigger runs when files in specified paths change](#only-trigger-runs-when-files-in-specified-paths-change). If you leave this setting blank, HCP Terraform uses the repository's default branch. If the workspace is set to trigger runs when a [git tag is published](#trigger-runs-when-a-git-tag-is-published), all tags will trigger runs, regardless of the branch specified in this setting. + +### Automatic Speculative Plans + +Whether to perform [speculative plans on pull requests](/terraform/enterprise/run/ui#speculative-plans-on-pull-requests) to the connected repository, to assist in reviewing proposed changes. Automatic speculative plans are enabled by default, but you can disable them for any workspace. + +### Include Submodules on Clone + +Select **Include submodules on clone** to recursively clone all of the repository's Git submodules when HCP Terraform fetches a configuration. + +-> **Note:** The [SSH key for cloning Git submodules](/terraform/enterprise/vcs#ssh-keys) is set in the VCS provider settings for the organization and is not related to the workspace's SSH key for Terraform modules. + +## Glob Patterns for Automatic Run Triggering + +We support `glob` patterns to describe a set of triggers for automatic runs. Refer to [trigger patterns](#only-trigger-runs-when-files-in-specified-paths-change) for details. + +Supported wildcards: + +- `*` Matches zero or more characters. +- `?` Matches one or more characters. +- `**` Matches directories recursively. + +The following examples demonstrate how to use the supported wildcards: + +- `/**/*` matches every file in every directory +- `/module/**/*` matches all files in any directory below the `module` directory +- `/**/networking/*` matches every file that is inside any `networking` directory +- `/**/networking/**/*` matches every file that has `networking` directory on its path +- `/**/*.tf` matches every file in any directory that has the `.tf` extension +- `/submodule/*.???` matches every file inside `submodule` directory which has three characters long extension. diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/workspaces/state.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/workspaces/state.mdx new file mode 100644 index 0000000000..4b070799df --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/workspaces/state.mdx @@ -0,0 +1,257 @@ +--- +page_title: Manage workspace state in Terraform Enterprise +description: >- + Workspaces have their own separate state data. Learn how Terraform Enterprise + uses state and how to access state from across workspaces. +source: terraform-docs-common +--- + +# Manage workspace state + +Each HCP Terraform workspace has its own separate state data, used for runs within that workspace. + +-> **API:** See the [State Versions API](/terraform/enterprise/api-docs/state-versions). + +## State Usage in Terraform Runs + +In [remote runs](/terraform/enterprise/run/remote-operations), HCP Terraform automatically configures Terraform to use the workspace's state; the Terraform configuration does not need an explicit backend configuration. (If a backend configuration is present, it will be overridden.) + +In local runs (available for workspaces whose execution mode setting is set to "local"), you can use a workspace's state by configuring the [CLI integration](/terraform/cli/cloud) and authenticating with a user token that has permission to read and write state versions for the relevant workspace. When using a Terraform configuration that references outputs from another workspace, the authentication token must also have permission to read state outputs for that workspace. ([More about permissions.](/terraform/enterprise/users-teams-organizations/permissions)) + + + +During an HCP Terraform run, Terraform incrementally creates intermediate state versions and marks them as finalized once it uploads the state content. + +When a workspace is unlocked, HCP Terraform selects the latest state and sets it as the current state version, deletes all other intermediate state versions that were saved as recovery snapshots for the duration of the lock, and discards all pending intermediate state versions that were superseded by newer state versions. + +[permissions-citation]: #intentionally-unused---keep-for-maintainers + + + +## State Versions + +In addition to the current state, HCP Terraform retains historical state versions, which can be used to analyze infrastructure changes over time. + +You can view a workspace's state versions from its **States** tab. Each state in the list indicates which run and which VCS commit (if applicable) it was associated with. Click a state in the list for more details, including a diff against the previous state and a link to the raw state file. + + + +## Managed Resources Count + +-> **Note:** A managed resources count for each organization is available in your organization's settings. + +Your organization’s managed resource count helps you understand the number of infrastructure resources that HCP Terraform manages across all your workspaces. + +HCP Terraform reads all the workspaces’ state files to determine the total number of managed resources. Each [resource](/terraform/language/resources/syntax) in the state equals one managed resource. HCP Terraform includes resources in modules and each resource instance created with the `count` or `for_each` meta-arguments. For example, `"aws_instance" "servers" { count = 10 }` creates ten separate managed resources in state. HCP Terraform does not include [data sources](/terraform/language/data-sources) in the count. + +### Examples - Managed Resources + +The following Terraform state excerpt describes a `random` resource. HCP Terraform counts `random` as one managed resource because `“mode”: “managed”`. + +```json +"resources": [ +{ + "mode": "managed", + "type": "random_pet", + "name": "random", + "provider": "provider[\"registry.terraform.io/hashicorp/random\"]", + "instances": [ + { + "schema_version": 0, + "attributes": { + "id": "puma", + "keepers": null, + "length": 1, + "prefix": null, + "separator": "-" + }, + "sensitive_attributes": [] + } + ] + } +] +``` + +A single resource configuration block can describe multiple resource instances with the [`count`](/terraform/language/meta-arguments/count) or [`for_each`](/terraform/language/meta-arguments/for_each) meta-arguments. Each of these instances counts as a managed resource. + +The following example shows a Terraform state excerpt with 2 instances of a `aws_subnet` resource. HCP Terraform counts each instance of `aws_subnet` as a separate managed resource. + +```json +{ + "module": "module.vpc", + "mode": "managed", + "type": "aws_subnet", + "name": "public", + "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", + "instances": [ + { + "index_key": 0, + "schema_version": 1, + "attributes": { + "arn": "arn:aws:ec2:us-east-2:561656980159:subnet/subnet-024b05c4fba9c9733", + "assign_ipv6_address_on_creation": false, + "availability_zone": "us-east-2a", + ##... + "private_dns_hostname_type_on_launch": "ip-name", + "tags": { + "Name": "-public-us-east-2a" + }, + "tags_all": { + "Name": "-public-us-east-2a" + }, + "timeouts": null, + "vpc_id": "vpc-0f693f9721b61333b" + }, + "sensitive_attributes": [], + "private": "eyJlMmJmYjczMC1lY2FhLTExZTYtOGY4OC0zNDM2M2JjN2M0YzAiOnsiY3JlYXRlIjo2MDAwMDAwMDAwMDAsImRlbGV0ZSI6MTIwMDAwMDAwMDAwMH0sInNjaGVtYV92ZXJzaW9uIjoiMSJ9", + "dependencies": [ + "data.aws_availability_zones.available", + "module.vpc.aws_vpc.this", + "module.vpc.aws_vpc_ipv4_cidr_block_association.this" + ] + }, + { + "index_key": 1, + "schema_version": 1, + "attributes": { + "arn": "arn:aws:ec2:us-east-2:561656980159:subnet/subnet-08924f16617e087b2", + "assign_ipv6_address_on_creation": false, + "availability_zone": "us-east-2b", + ##... + "private_dns_hostname_type_on_launch": "ip-name", + "tags": { + "Name": "-public-us-east-2b" + }, + "tags_all": { + "Name": "-public-us-east-2b" + }, + "timeouts": null, + "vpc_id": "vpc-0f693f9721b61333b" + }, + "sensitive_attributes": [], + "private": "eyJlMmJmYjczMC1lY2FhLTExZTYtOGY4OC0zNDM2M2JjN2M0YzAiOnsiY3JlYXRlIjo2MDAwMDAwMDAwMDAsImRlbGV0ZSI6MTIwMDAwMDAwMDAwMH0sInNjaGVtYV92ZXJzaW9uIjoiMSJ9", + "dependencies": [ + "data.aws_availability_zones.available", + "module.vpc.aws_vpc.this", + "module.vpc.aws_vpc_ipv4_cidr_block_association.this" + ] + } + ] +} +``` + +### Example - Excluded Data Source + +The following Terraform state excerpt describes a `aws_availability_zones` data source. HCP Terraform does not include `aws_availability_zones` in the managed resource count because `”mode”: “data”`. + +```json + "resources": [ + { + "mode": "data", + "type": "aws_availability_zones", + "name": "available", + "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", + "instances": [ + { + "schema_version": 0, + "attributes": { + "all_availability_zones": null, + "exclude_names": null, + "exclude_zone_ids": null, + "filter": null, + "group_names": [ + "us-east-2" + ], + "id": "us-east-2", + "names": [ + "us-east-2a", + "us-east-2b", + "us-east-2c" + ], + "state": null, + "zone_ids": [ + "use2-az1", + "use2-az2", + "use2-az3" + ] + }, + "sensitive_attributes": [] + } + ] + } + ] +``` + + + +## State Manipulation + +Certain tasks (including importing resources, tainting resources, moving or renaming existing resources to match a changed configuration, and more) may require modifying Terraform state outside the context of a run, depending on which version of Terraform your HCP Terraform workspace is configured to use. + +Newer Terraform features like [`moved` blocks](/terraform/language/modules/develop/refactoring), [`import` blocks](/terraform/language/import), and the [`replace` option](/terraform/enterprise/run/modes-and-options#replacing-selected-resources) allow you to accomplish these tasks using the usual plan and apply workflow. However, if the Terraform version you're using doesn't support these features, you may need to fall back to manual state manipulation. + +Manual state manipulation in HCP Terraform workspaces, with the exception of [rolling back to a previous state version](#rolling-back-to-a-previous-state), requires the use of Terraform CLI, using the same commands as would be used in a local workflow (`terraform import`, `terraform taint`, etc.). To manipulate state, you must configure the [CLI integration](/terraform/cli/cloud) and authenticate with a user token that has permission to read and write state versions for the relevant workspace. ([More about permissions.](/terraform/enterprise/users-teams-organizations/permissions)) + +### Rolling Back to a Previous State + +You can rollback to a previous, known good state version using the HCP Terraform UI. Navigate to the state you want to rollback to and click the **Advanced** toggle button. This option requires that you have access to create new state and that you lock the workspace. It works by duplicating the state that you specify and making it the workspace's current state version. The workspace remains locked. To undo the rollback operation, rollback to the state version that was previously the latest state. + +-> **Note:** You can rollback to any prior state, but you should use caution because replacing state improperly can result in orphaned or duplicated infrastructure resources. This feature is provided as a convenient alternative to manually downloading older state and using state manipulation commands in the CLI to push it to HCP Terraform. + +[permissions-citation]: #intentionally-unused---keep-for-maintainers + +## Accessing State from Other Workspaces + +-> **Note:** Provider-specific [data sources](/terraform/language/data-sources) are usually the most resilient way to share information between separate Terraform configurations. `terraform_remote_state` is more flexible, but we recommend using specialized data sources whenever it is convenient to do so. + +Terraform's built-in [`terraform_remote_state` data source](/terraform/language/state/remote-state-data) lets you share arbitrary information between configurations via root module [outputs](/terraform/language/values/outputs). + +HCP Terraform automatically manages API credentials for `terraform_remote_state` access during [runs managed by HCP Terraform](/terraform/enterprise/run/remote-operations#remote-operations). This means you do not usually need to include an API token in a `terraform_remote_state` data source's configuration. + +## Upgrading State + +You can upgrade a workspace's state version to a new Terraform version without making any configuration changes. To upgrade, we recommend the following steps: + +1. Sign in to [HCP Terraform](https://app.terraform.io/) or Terraform Enterprise and navigate to the workspace you want to upgrade. +2. Run a [speculative plan](/terraform/enterprise/run/ui#testing-terraform-upgrades-with-speculative-plans) to test whether your configuration is compatible with the new Terraform version. You can run speculative plans with a Terraform version that is different than the one currently selected for the workspace. +3. Select **Settings > General** and select the desired new **Terraform Version**. +4. Click **+ New run** and then select **Allow empty apply** as the run type. An [empty apply](/terraform/enterprise/run/modes-and-options#allow-empty-apply) allows Terraform to apply a plan that produces no infrastructure changes. Terraform upgrades the state file version during the apply process. + +-> **Note:** If the desired Terraform version is incompatible with a workspace's existing state version, the run fails and HCP Terraform prompts you to run an apply with a compatible version first. Refer to the [Terraform upgrade guides](/terraform/language/upgrade-guides) for details about upgrading between versions. + +### Remote State Access Controls + +Remote state access between workspaces is subject to access controls: + +- Only workspaces within the same organization can access each other's state. +- The workspace whose state is being read must be configured to allow that access. State access permissions are configured on a workspace's [general settings page](/terraform/enterprise/workspaces/settings). There are two ways a workspace can allow access: + - Globally, to all workspaces within the same organization. + - Selectively, to a list of specific approved workspaces. + +By default, new workspaces in HCP Terraform do not allow other workspaces to access their state. We recommend that you follow the principle of least privilege and only enable state access between workspaces that specifically need information from each other. + +-> **Note:** The default access permissions for new workspaces in HCP Terraform changed in April 2021. Workspaces created before this change defaulted to allowing global access within their organization. These workspaces can be changed to more restrictive access at any time on their [general settings page](/terraform/enterprise/workspaces/settings). Terraform Enterprise administrators can choose whether new workspaces on their instances default to global access or selective access. + +### Data Source Configuration + +To configure a `tfe_outputs` data source that references an HCP Terraform workspace, specify the organization and workspace in the `config` argument. + +You must still properly configure the `tfe` provider with a valid authentication token and correct permissions to HCP Terraform. + +```hcl +data "tfe_outputs" "vpc" { + config = { + organization = "example_corp" + workspaces = { + name = "vpc-prod" + } + } +} + +resource "aws_instance" "redis_server" { + # Terraform 0.12 and later: use the "outputs." attribute + subnet_id = data.tfe_outputs.vpc.outputs.subnet_id +} +``` + +-> **Note:** Remote state access controls do not apply when using the `tfe_outputs` data source. diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/workspaces/tags.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/workspaces/tags.mdx new file mode 100644 index 0000000000..6617ddc9be --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/workspaces/tags.mdx @@ -0,0 +1,94 @@ +--- +page_title: Create workspace tags +description: >- + Learn how to create tags for your workspaces so that you can organize + workspaces. Tagging workspaces also lets you sort and filter workspaces in the + UI. +source: terraform-docs-common +--- + +# Create workspace tags + +This topic describes how to create and attach tags to your workspaces. + +## Overview + +Tagging workspaces helps organization administrators organize, sort, and filter workspaces so that they can track resource consumption. For example, you could add a `cost-center` tag so that administrators can sort workspaces according to cost center. + +HCP Terraform stores tags as key-value pairs or as key-only tags. Key-only tags enable you to associate a single Terraform configuration file with several workspaces according to tag. Refer to the following topics in the Terraform CLI and configuration language documentation for additional information: + +- [`terraform{}.cloud{}.workspaces` reference](/terraform/language/terraform#terraform-cloud-workspaces) +- [Define connection settings](/terraform/cli/cloud/settings#define-connection-settings) + +### Reserved tags + +You can reserve a set of tag keys for each organization. Reserved tag keys appear as suggestions when people create tags for projects and workspaces so that you can use consistent terms for tags. Refer to [Create and manage reserved tags](/terraform/enterprise/users-teams-organizations/organizations/manage-reserved-tags) for additional information. + +### Single-value tags + +Your system may contain single-value tags created using Terraform v1.10 and older. You can migrate existing single-value tags to the key-value scheme. Refer to [Migrate single-value tags](#migrate-single-value-tags) for instructions. + +## Requirements + +- You must be member of a team with the **Write** permission group enabled for the workspace to create tags for a workspace. +- You must be member of a team with the **Admin** permission group enabled for the workspace to delete tags on a workspace. + +You cannot create tags for a workspace using the CLI. + +## Define tags + +1. Open your workspace. + +2. Click either the count link for the **Tags** label or **Manage Tags** in the **Tags** card on the right-sidebar to open the **Manage workspace tags** drawer. + +3. Click **+Add tag** and perform one of the following actions: + + - Specify a key-value pair: Lets you sort, filter, and search on either key or value. + - Specify a tag key and leave the **Value** field empty: Lets you sort, filter, and search on only the key name. + - Choose a reserved key from the suggested tag key list and specify a value: Ensures that you are using the key name consistently and lets you sort, filter, and search on either key or value. + - Choose a reserved key from the suggested tag key list and leave the **Value** field empty: Ensures that you are using the key name consistently and lets you sort, filter, and search on only the key name. + + Refer to [Tag syntax](#Tag-syntax) for information about supported characters. + +4. Tags inherited from the project appear in the **Inherited Tags** section. You can attach new key-value pairs to their projects to override inherited tags. Refer to [Manage projects](/terraform/enterprise/projects/manage) for additional information about using tags in projects. + + You cannot override reserved tag keys when the **Disable overrides** option is enabled. Refer to [Create and manage reserved tags](/terraform/enterprise/users-teams-organizations/organizations/manage-reserved-tags) for additional information. + + You can also click on tag links in the **Inherited Tags** section to view workspaces that use the same tag. + +5. Click **Save**. + +Tags that you create appear in the tags management screen in the organization settings. Refer to [Organizations](/terraform/enterprise/users-teams-organizations/organizations) for additional information. + +## Update tags + +1. Open your workspace. +2. Click either the count link for the **Tags** label or **Manage Tags** in the **Tags** card on the right-sidebar to open the **Manage workspace tags** drawer. +3. In the **Tags applied to this resource** section, modify a key, value, or both and click **Save**. + +## Migrate single-value tags + +You can use the API to convert existing single-value tags to key-value tags. You must have permissions in the workspace to perform the following task. Refer to [Requirements](#requirements) for additional information. + +Terraform v1.10 and older adds single-value workspace tags defined in the associated Terraform configuration to workspaces selected by the configuration. As result, your workspace may include duplicate tags. Refer to the [Terraform reference documentation](/terraform/language/terraform#terraform-cloud-workspaces) for additional information. + +### Re-create existing workspace tags as resource tags + +1. Send a `GET` request to the [`/organizations/:organization_name/tags`](/terraform/enterprise/api-docs/organization-tags#list-tags) endpoint to request all workspaces for your organization. The response may span several pages. +2. For each workspace, check the `tag-names` attribute for existing tags. +3. Send a `PATCH` request to the [`/workspaces/:workspace_id`](/terraform/enterprise/api-docs/workspaces#update-a-workspace) endpoint and include the `tag-binding` relationship in the request body for each workspace tag. + +### Delete single-value workspace tags + +1. Send a `GET` request to the [`/organizations/:organization_name/tags`](/terraform/enterprise/api-docs/organization-tags#list-tags) endpoint to request all workspaces for your organization. +2. Enumerate the external IDs for all tags. +3. Send a `DELETE` request to the [`/organizations/:organization_name/tags`](/terraform/enterprise/api-docs/organization-tags#delete-tags) endpoint to delete tags. + +## Tag syntax + +The following rules apply to tags: + +- Tags must be one or more characters. +- Tags have a 255 character limit. +- Tags can include letters, numbers, colons, hyphens, and underscores. +- For tags stored as key-value pairs, tag values are optional. diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/workspaces/variables/index.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/workspaces/variables/index.mdx new file mode 100644 index 0000000000..22c1581891 --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/workspaces/variables/index.mdx @@ -0,0 +1,237 @@ +--- +page_title: Workspace variables in Terraform Enterprise +description: >- + Terraform Enterprise workspaces allow you to customize Terraform runs. Learn + how to use Terraform variables and environment variables. +source: terraform-docs-common +--- + +# Workspace variables + +HCP Terraform workspace variables let you customize configurations, modify Terraform's behavior, setup [dynamic provider credentials](/terraform/enterprise/workspaces/dynamic-provider-credentials), and store information like static provider credentials. + +You can set variables specifically for each workspace or you can create variable sets to reuse the same variables across multiple workspaces. For example, you could define a variable set of provider credentials and automatically apply it to all of the workspaces using that provider. You can use the command line to specify variable values for each plan or apply. Otherwise, HCP Terraform applies workspace variables to all runs within that workspace. + +[permissions-citation]: #intentionally-unused---keep-for-maintainers + +## Types + +You can create both environment variables and Terraform variables in HCP Terraform. + +> **Hands-on:** Try the [Create and Use a Variable Sets](/terraform/tutorials/cloud-get-started/cloud-create-variable-set) and [Create Infrastructure](/terraform/tutorials/cloud-get-started/cloud-workspace-configure) tutorials to set environment and Terraform variables in HCP Terraform. + +### Environment variables + +HCP Terraform performs Terraform runs on disposable Linux worker VMs using a POSIX-compatible shell. Before running Terraform operations, HCP Terraform uses the `export` command to populate the shell with environment variables. + +Environment variables can store provider credentials and other data. Refer to your provider's Terraform Registry documentation for a full list of supported shell environment variables (e.g., authentication variables for [AWS](https://registry.terraform.io/providers/hashicorp/aws/latest/docs#environment-variables), [Google Cloud Platform](https://registry.terraform.io/providers/hashicorp/google/latest/docs/guides/getting_started#adding-credentials), and [Azure](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs#argument-reference)). Environment variables can also [modify Terraform's behavior](/terraform/cli/config/environment-variables). For example, `TF_LOG` enables detailed logs for debugging. + +#### Parallelism + +You can use the `TFE_PARALLELISM` environment variable when your infrastructure providers produce errors on concurrent operations or use non-standard rate limiting. The `TFE_PARALLELISM` variable sets the `-parallelism=` flag for `terraform plan` and `terraform apply` ([more about `parallelism`](/terraform/internals/graph#walking-the-graph)). Valid values are between 1 and 256, inclusive, and the default is `10`. HCP Terraform agents do not support `TFE_PARALLELISM`, but you can specify flags as environment variables directly via [`TF_CLI_ARGS_name`](/terraform/cli/config/environment-variables#tf-cli-args). In these cases, use `TF_CLI_ARGS_plan="-parallelism="` or `TF_CLI_ARGS_apply="-parallelism="` instead. + +!> **Warning:** We recommend reading and understanding [Terraform parallelism](https://support.hashicorp.com/hc/en-us/articles/10348130482451) prior to setting `TFE_PARALLELISM`. You can also contact HashiCorp support for direct advice. + +#### Dynamic credentials + +You can configure [dynamic credentials](/terraform/enterprise/workspaces/dynamic-provider-credentials) for certain providers using environment variables [at the workspace level](/terraform/enterprise/workspaces/variables/managing-variables#workspace-specific-variables) or using [variable sets](/terraform/enterprise/workspaces/variables/managing-variables#variable-sets). + +Dynamic credentials allows for using temporary per-run credentials and eliminates the need to manually rotate secrets. + +### Terraform variables + +Terraform variables refer to [input variables](/terraform/language/values/variables) that define parameters without hardcoding them into the configuration. For example, you could create variables that let users specify the number and type of Amazon Web Services EC2 instances they want to provision with a Terraform module. + +```hcl +variable "instance_count" { + description = "Number of instances to provision." + type = number + default = 2 +} +``` + +You can then reference this variable in your configuration. + +```hcl +module "ec2_instances" { + source = "./modules/aws-instance" + + instance_count = var.instance_count + ## ... +} +``` + +If a required input variable is missing, Terraform plans in the workspace will fail and print an explanation in the log. + +## Scope + +Each environment and Terraform variable can have one of the following scopes: + +| Scope | Description | Resources | +| ----------------------------- | ---------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| Run-Specific | Apply to a specific run within a single workspace. | [Specify Run-Specific Variables](/terraform/enterprise/workspaces/variables/managing-variables#run-specific-variables) | +| Workspace-Specific | Apply to a single workspace. | [Create Workspace-Specific Variables](/terraform/enterprise/workspaces/variables/managing-variables#workspace-specific-variables), [Loading Variables from Files](/terraform/enterprise/workspaces/variables/managing-variables#loading-variables-from-files), [Workspace-Specific Variables API](/terraform/enterprise/api-docs/workspace-variables). | +| Workspace-Scoped Variable Set | Apply to multiple workspaces within the same organization. | [Create Variable Sets](/terraform/enterprise/workspaces/variables/managing-variables#variable-sets) and [Variable Sets API](/terraform/enterprise/api-docs/variable-sets) | +| Project-Scoped Variable Set | Automatically applied to all current and future workspaces within a project. | [Create Variable Sets](/terraform/enterprise/workspaces/variables/managing-variables#variable-sets) and [Variable Sets API](/terraform/enterprise/api-docs/variable-sets) | +| Global Variable Set | Automatically applied to all current and future workspaces within an organization. | [Create Variable Sets](/terraform/enterprise/workspaces/variables/managing-variables#variable-sets) and [Variable Sets API](/terraform/enterprise/api-docs/variable-sets) | + +## Variable set ownership + +Projects and organizations can both own variable sets. The owner of a variable set can determine the precedence of that set. + +Use organization-owned variable sets to share variables across multiple projects. Managing organization-owned variable sets [requires a higher permission level](/terraform/enterprise/workspaces/variables/managing-variables#permissions) because you can apply these sets to any project in your organization. + +Use project-owned variable sets to share variables across multiple workspaces within a single project. Project-owned variable sets only require permissions on the project itself, rather than organization-level permissions. + +Refer to [**Manage variable sets**](/terraform/enterprise/workspaces/variables/managing-variables#permissions) for more details on variable set permissions. + +## Precedence + +> **Hands On:** The [Manage Multiple Variable Sets in HCP Terraform](/terraform/tutorials/cloud/cloud-multiple-variable-sets) tutorial shows how to manage multiple variable sets and demonstrates variable precedence. + +There may be cases when a workspace contains conflicting variables of the same type with the same key. HCP Terraform marks overwritten variables in the UI. + +HCP Terraform prioritizes and overwrites conflicting variables according to the following precedence: + +### 1. Priority global variable sets + +If [prioritized](/terraform/enterprise/workspaces/variables#precedence-with-priority-variable-sets), variables in a global variable set have precedence over all other variables with the same key. + +### 2. Priority project-scoped variable set owned by an organization + +If [prioritized](/terraform/enterprise/workspaces/variables#precedence-with-priority-variable-sets), variables in a priority project-scoped variable set have precedence over variables with the same key set at a more specific scope. Prioritized variables sets owned by organizations take precedence over priority variable sets owned by projects. + +### 3. Priority workspace-scoped variable set owned by an organization + +If [prioritized](/terraform/enterprise/workspaces/variables#precedence-with-priority-variable-sets), variables in an organization-owned variable set scoped to a workspace have precedence over variables with the same key set at a more specific scope. Prioritized variables sets owned by organizations take precedence over sets owned by projects. + +### 4. Priority project-scoped variable set owned by a project + +If [prioritized](/terraform/enterprise/workspaces/variables#precedence-with-priority-variable-sets), variables in a priority project-scoped variable set have precedence over variables with the same key set at a more specific scope. + +### 5. Priority workspace-scoped variable set owned by a project + +If [prioritized](/terraform/enterprise/workspaces/variables#precedence-with-priority-variable-sets), variables in a priority workspace-scoped variable set have precedence over variables with the same key set at a more specific scope. + +### 6. Command line argument variables + +When using a CLI workflow, variables applied to a run with either `-var` or `-var-file` overwrite workspace-specific and variable set variables that have the same key. + +### 7. Local environment variables prefixed with `TF_VAR_` + +When using a CLI workflow, local environment variables prefixed with `TF_VAR_` (e.g., `TF_VAR_replicas`) overwrite workspace-specific, variable set, and `.auto.tfvars` file variables that have the same key. + +### 8. Workspace-specific variables + +Workspace-specific variables always overwrite variables from variable sets that have the same key. Refer to [overwrite variables from variable sets](/terraform/enterprise/workspaces/variables/managing-variables#overwrite-variable-sets) for details. + +### 9. Workspace-scoped variable owned by a project + +Variables in workspace-scoped variable sets are only applied to a subset of workspaces in a project. + +When workspace-scoped variable sets have conflicting variables, HCP Terraform compares the variable set names and uses values from the variable set with lexical precedence. Terraform and HCP Terraform operate on UTF-8 strings, and HCP Terraform sorts variable set names based on the lexical order of Unicode code points. + +For example, if you apply `A_Variable_Set` and `B_Variable_Set` to the same workspace, HCP Terraform will use any conflicting variables from `A_Variable_Set`. This is the case regardless of which variable set has been edited most recently. HCP Terraform only considers the lexical ordering of variable set names when determining precedence. + +Variables sets scoped to workspaces that are owned by projects take precedence over sets with the same scope that are owned by organizations. + +### 10. Project-scoped variable set owned by a project + +Variables in project-scoped variable sets are only applied to the workspaces within the specified projects. + +When project-scoped variable sets have conflicting variables, HCP Terraform compares the variable set names and uses values from the variable set with lexical precedence. Terraform and HCP Terraform operate on UTF-8 strings, and HCP Terraform sorts variable set names based the on lexical order of Unicode code points. + +For example, if you apply `A_Variable_Set` and `B_Variable_Set` to the same project, HCP Terraform uses any conflicting variables from `A_Variable_Set`. This is the case regardless of which variable set has been edited most recently. HCP Terraform only considers the lexical ordering of variable set names when determining precedence. + +Variables sets owned by projects take precedence over those owned by organizations. + +### 11. Workspace-scoped variable set owned by an organization + +Variables in workspace-scoped variable sets are only applied to the specified workspaces in an organization. + +When workspace-scoped variable sets have conflicting variables, HCP Terraform compares the variable set names and uses values from the variable set with lexical precedence. Terraform and HCP Terraform operate on UTF-8 strings, and HCP Terraform sorts variable set names based on the lexical order of Unicode code points. + +For example, if you apply `A_Variable_Set` and `B_Variable_Set` to the same workspace, HCP Terraform will use any conflicting variables from `A_Variable_Set`. This is the case regardless of which variable set has been edited most recently. HCP Terraform only considers the lexical ordering of variable set names when determining precedence. + +### 12. Project-scoped variable set owned by an organization + +Variables in project-scoped variable sets are only applied to the workspaces within the specified projects. + +When project-scoped variable sets have conflicting variables, HCP Terraform compares the variable set names and uses values from the variable set with lexical precedence. Terraform and HCP Terraform operate on UTF-8 strings, and HCP Terraform sorts variable set names based the on lexical order of Unicode code points. + +For example, if you apply `A_Variable_Set` and `B_Variable_Set` to the same project, HCP Terraform uses any conflicting variables from `A_Variable_Set`. This is the case regardless of which variable set has been edited most recently. HCP Terraform only considers the lexical ordering of variable set names when determining precedence. + +### 13. Global variable sets + +Workspace and project-scoped variable sets always take precedence over global variable sets that are applied to all workspaces within an organization. Terraform does not allow global variable sets to contain variables with the same key, so they cannot conflict. + +### 14. `*.auto.tfvars` variable files + +Variables in the HCP Terraform workspace and variables provided through the command line always overwrite variables with the same key from files ending in `.auto.tfvars`. + +### 15. `terraform.tfvars` variable file + +Variables in the `.auto.tfvars` files take precedence over variables in the `terraform.tfvars` file. + + + +Although HCP Terraform uses variables from `terraform.tfvars`, Terraform Enterprise currently ignores this file. + + + +## Precedence with priority variable sets + +You can select to prioritize all values of the variables in a variable set. +When a variable set is priority, the values take precedence over any variables with the same key set at a more specific scope. + +For example, variables in a priority global variable set would take precedence over all variables with the same key. + +If two priority variable sets with the same scope and ownership include the same variable key, HCP Terraform will determine precedence by the alphabetical order of the variable sets' names. + +While a priority variable set can enforce that Terraform variables use designated values, it does not guarantee that the configuration uses the variable. A user can still directly modify the Terraform configuration to remove usage of a variable and replace it with a hard-coded value. For stricter enforcement, we recommend using policy checks or run tasks. + +## Precedence example + +Consider an example workspace that has the following different kinds of variables and variable sets: + +| Source | Priority | Ownership | Scope | +| ------------------------------------------------------------- | -------- | ------------ | --------- | +| Priority **global** variable set | true | organization | global | +| Priority organization-owned **project-scoped** variable set | true | organization | project | +| Priority organization-owned **workspace-scoped** variable set | true | organization | workspace | +| Priority project-owned **project-scoped** variable set | true | project | project | +| Priority project-owned **workspace-scoped** variable set | true | project | workspace | +| Command line argument | N/A | N/A | run | +| Local environment variable | N/A | N/A | workspace | +| **Workspace-specific** variable | N/A | N/A | workspace | +| Project-owned **workspace-scoped** variable set | false | project | workspace | +| Project-owned **project-scoped** variable set | false | project | project | +| Organization-owned **workspace-scoped** variable set | false | organization | workspace | +| Organization-owned **project-scoped** variable set | false | organization | project | +| **Global** variable set | false | organization | global | + +If these variables and variable sets had the following variables applied: + +| Source (priority/ownership/scope) | Region | Var1 | Replicas | +| ------------------------------------------------------------- | ----------- | ---- | -------- | +| Priority **global** variable set | `us-east-1` | | | +| Priority organization-owned **project-scoped** variable set | `us-east-2` | | | +| Priority organization-owned **workspace-scoped** variable set | `us-west-1` | | | +| Priority project-owned **project-scoped** variable set | `eu-east-2` | | | +| Priority project-owned **workspace-scoped** variable set | `eu-west-1` | | | +| Command line argument | `us-west-2` | | `9` | +| Local environment variable | | | `8` | +| **Workspace-specific** variable | | `h` | `1` | +| Project-owned **workspace-scoped** variable set | | `y` | `2` | +| Project-owned **project-scoped** variable set | | | `4` | +| Organization-owned **workspace-scoped** variable set | | `z` | `3` | +| Organization-owned **project-scoped** variable set | | | `5` | +| **Global** variable set | | | `6` | + +When you trigger a run through the command line, these are the final values HCP Terraform assigns to each variable: + +| Variable | Value | +| -------- | ----------- | +| Region | `us-east-1` | +| Var1 | `h` | +| Replicas | `9` | diff --git a/content/ptfe-releases/v000011-1/docs/enterprise/workspaces/variables/managing-variables.mdx b/content/ptfe-releases/v000011-1/docs/enterprise/workspaces/variables/managing-variables.mdx new file mode 100644 index 0000000000..78dd91d594 --- /dev/null +++ b/content/ptfe-releases/v000011-1/docs/enterprise/workspaces/variables/managing-variables.mdx @@ -0,0 +1,248 @@ +--- +page_title: Manage variables and variable sets in Terraform Enterprise +description: >- + Use workspace variables and variable sets to customize Terraform Enterprise + runs. +source: terraform-docs-common +--- + +# Manage variables and variable sets + +You can set variables specifically for each workspace or you can create variable sets to reuse the same variables across multiple workspaces. Refer to the [variables overview](/terraform/enterprise/workspaces/variables) documentation for more information about variable types, scope, and precedence. You can also set variable values specifically for each run on the command line. + +You can create and edit workspace-specific variables through: + +- The HCP Terraform UI, as detailed below. +- The Variables API for [workspace-specific variables](/terraform/enterprise/api-docs/workspace-variables) and [variable sets](/terraform/enterprise/api-docs/variable-sets). +- The `tfe` provider's [`tfe_variable`](https://registry.terraform.io/providers/hashicorp/tfe/latest/docs/resources/variable) resource, which can be more convenient for bulk management. + +## Permissions + +You must have [**Read variables** permission](/terraform/enterprise/users-teams-organizations/permissions#general-workspace-permissions) to view the variables for a particular workspace and to view the variable sets in your organization. To create or edit workspace-specific variables within a workspace, you must have [**Read and write variables**](/terraform/enterprise/users-teams-organizations/permissions#general-workspace-permissions) for that workspace. + +To create, update, or delete organization-owned variable sets, you must be one of the following: + +- A member of the [owners team](/terraform/enterprise/users-teams-organizations/permissions#organization-owners) +- A member of a team with [**Manage all projects**](/terraform/enterprise/users-teams-organizations/permissions#manage-all-projects) +- A member of a team with [**Manage all workspaces**](/terraform/enterprise/users-teams-organizations/permissions#manage-all-workspaces) + +To create, edit, or apply project-owned variable sets, you must be part of a team with one of the following: + +- **Write** project permissions +- **Maintain** project permissions +- **Admin** project permissions +- [**Manage variable sets**](/terraform/enterprise/users-teams-organizations/permissions#general-project-permissions) project permissions +- [**Manage all projects**](/terraform/enterprise/users-teams-organizations/permissions#manage-all-projects) organization permissions + +## Run-Specific Variables + +Terraform 1.1 and later lets you set [Terraform variable](/terraform/enterprise/workspaces/variables#terraform-variables) values for a particular plan or apply on the command line. These variable values will overwrite workspace-specific and variable set variables with the same key. Refer to the [variable precedence](/terraform/enterprise/workspaces/variables#precedence) documentation for more details. + +You can set run-specific Terraform variable values by: + +- Specifying `-var` and `-var-file` arguments. For example: + + terraform apply -var="key=value" -var-file="testing.tfvars" +- Creating local environment variables prefixed with `TF_VAR_`. For example, if you declare a variable called `replicas` in your configuration, you could create a local environment variable called `TF_VAR_replicas` and set it to a particular value. When you use the [CLI Workflow](/terraform/enterprise/run/cli), Terraform automatically identifies these environment variables and applies their values to the run. + +Refer to the [variables on the command line](/terraform/language/values/variables#variables-on-the-command-line) documentation for more details and examples. + +## Workspace-Specific Variables + +To view and manage a workspace's variables, go to the workspace and click the **Variables** tab. + +The **Variables** page appears, showing all workspace-specific variables and variable sets applied to the workspace. This is where you can add, edit, and delete workspace-specific variables. You can also apply and remove variable sets from the workspace. + +The **Variables** page is not available for workspaces configured with `Local` [execution mode](/terraform/enterprise/workspaces/settings#execution-mode). HCP Terraform does not evaluate workspace variables or variable sets in local execution mode. + +### Add a Variable + +To add a variable: + +1. Sign in to [HCP Terraform](https://app.terraform.io/) or Terraform Enterprise and select the workspace you want to define a variable for. + +2. Go to the workspace **Variables** page and click **+ Add variable** in the **Workspace Variables** section. + +3. Choose a variable category (Terraform or environment), optionally mark the variable as [sensitive](#sensitive-values), and enter a variable key, value, and optional description. For Terraform variables only, you can check the **HCL** checkbox to enter a value in HashiCorp Configuration Language. + + Refer to [variable values and format](#variable-values-and-format) for variable limits, allowable values, and formatting. + +4. Click **Save variable**. The variable now appears in the list of the workspace's variables and HCP Terraform will apply it to runs. + +### Edit a Variable + +To edit a variable: + +1. Click the ellipses next to the variable you want to edit and select **Edit**. +2. Make any desired changes and click **Save variable**. + +### Delete a Variable + +To delete a variable: + +1. Click the ellipses next to the variable you want to delete and select **Delete**. +2. Click **Yes, delete variable** to confirm your action. + +## Loading Variables from Files + +You can set [Terraform variable](/terraform/enterprise/workspaces/variables#terraform-variables) values by providing any number of [files ending in `.auto.tfvars`](/terraform/language/values/variables#variable-files) to workspaces that use Terraform 0.10.0 or later. When you trigger a run, Terraform automatically loads and uses the variables defined in these files. If any variable from the workspace has the same key as a variable in the file, the workspace variable overwrites variable from the file. + +You can only do this with files ending in `auto.tfvars` or `terraform.tfvars`. You can apply other types of `.tfvars` files [using the command line](#run-specific-variables) for each run. + +~> **Note:** HCP Terraform loads variables from files ending in `auto.tfvars` for each Terraform run, but does not automatically persist those variables to the HCP Terraform workspace or display them in the **Variables** section of the workspace UI. + +## Variable Sets + +> **Hands On:** Try the [Manage Variable Sets in HCP Terraform tutorial](/terraform/tutorials/cloud/cloud-multiple-variable-sets) tutorial. + +Variable sets are reusable collections of variables that you can apply to multiple workspaces. You can create variable sets under an organization or a project. Whether the variable set is owned by an organization or a project determines the permissions required to manage that set. Learn more about [variable set permissions](#permissions). + +HCP Terraform does not evaluate variable sets during Terraform runs for workspaces configured with `Local` [execution mode](/terraform/enterprise/workspaces/settings#execution-mode). + +Organizations or projects can own variable sets. To view variable sets, click **Settings** in your organization or project, then click **Variable sets**. + +The **Variable sets** page lists all of the organization's or project's variable sets. Click on a variable set to open it and review details about its variables and scoping. + +### Create Variable Sets + +To create a variable set: + +1. Sign in to [HCP Terraform](https://app.terraform.io/) or Terraform Enterprise and navigate to the **Settings** page for your organization or project. + +2. Click **Variable Sets**. + +3. Click **Create variable set**. + +4. Choose a descriptive **Name** for the variable set. You can use any combination of numbers, letters, and characters. + +5. Write an optional **Description** that tells other users about the purpose of the variable set and what it contains. + +6. Choose a variable set scope: + - Organization-owned + - **Apply globally:** HCP Terraform automatically applies this global variable set to all existing and future workspaces. + - **Apply to specific projects and workspaces:** Use the text fields to search for and select workspaces and projects to apply this variable set to. This affects all current and future workspaces for any selected projects. After creation, users can also [add this variable set to their workspaces](#apply-or-remove-variable-sets-from-inside-a-workspace). + - Project-owned + - **Apply to the entire project:** HCP Terraform automatically applies this variable set to all existing and future workspaces in the project. + - **Apply to specific workspaces in the project:** Use the text fields to search for and select workspaces to apply this variable set to. After creation, users can also [add this variable set to their workspaces](#apply-or-remove-variable-sets-from-inside-a-workspace). + +7. Add one or more variables: Click **+ Add variable**, choose a variable type (Terraform or environment), optionally mark the variable as [sensitive](#sensitive-values), and enter a variable name, value, and optional description. Then, click **Save variable**. + + Refer to [variable values and format](#variable-values-and-format) for variable limits, allowable values, and formatting. + + ~> **Note:** HCP Terraform will error if you try to declare variables with the same key in multiple global variable sets. + +8. Click **Create variable set.** HCP Terraform adds the new variable set to any specified workspaces and displays it on the **Variable Sets** page. + +### Edit Variable Sets + +To edit or remove a variable set: + +1. Sign in to [HCP Terraform](https://app.terraform.io/) or Terraform Enterprise and navigate to the **Settings** page for your organization or project. + +2. Click **Variable Sets**. + +3. Select the variable set you want to edit. That specific variable set page appears, where you can change the variable set settings. Refer to [create variable sets](#create-variable-sets) for details. + +### Delete Variable Sets + +Deleting a variable set can be a disruptive action, especially if the variables are required to execute runs. We recommend informing organization, project, and workspace owners before removing a variable set. + +To delete a variable set: + +1. Sign in to [HCP Terraform](https://app.terraform.io/) or Terraform Enterprise and navigate to the **Settings** page for your organization or project. + +2. Click **Variable Sets**. + +3. Select **Delete variable set**. Enter the variable set name and click **Delete variable set** to confirm this action. HCP Terraform deletes the variable set and removes it from all workspaces. Runs within those workspaces will no longer use the variables from the variable set. + +### Apply or Remove Variable Sets From Inside a Workspace + +To apply a variable set to a specific workspace: + +1. Navigate to the workspace and click the **Variables** tab. The **Variables** page appears, showing all workspace-specific variables and variable sets applied to the workspace. + +2. In the **Variable sets** section, click **Apply Variable Set**. Select the variable set you want to apply to your workspace, and click **Apply variable set**. The variable set appears in the workspace's variable sets list and HCP Terraform will now apply the variables to runs. + +To remove a variable set from within a workspace: + +1. Navigate to the workspace and click the **Variables** tab. The **Variables** page appears, showing all workspace-specific variables and variable sets applied to the workspace. +2. Click the ellipses button next to the variable set and select **Remove variable set**. +3. Click **Remove variable set** in the dialog box. HCP Terraform removes the variable set from this workspace, but it remains available to other workspaces in the organization. + +## Overwrite Variable Sets + +You can overwrite variables defined in variable sets within a workspace. For example, you may want to use a different set of provider credentials in a specific workspace. + +To overwrite a variable from a variable set, [create a new workspace-specific variable](#workspace-specific-variables) of the same type with the same key. HCP Terraform marks any variables that you overwrite with a yellow **OVERWRITTEN** flag. When you click the overwritten variable, HCP Terraform highlights the variable it will use during runs. + +Variables within a variable set can also automatically overwrite variables with the same key in other variable sets applied to the same workspace. Though variable sets are created for the organization or project, these overwrites occur within each workspace. Refer to [variable precedence](/terraform/enterprise/workspaces/variables#precedence) for more details. + +## Priority Variable Sets + +The values in priority variable sets overwrite any variables with the same key set at more specific scopes. This includes variables set using command line flags, or through`.*auto.tfvars` and `terraform.tfvars` files. + +It is still possible for a user to directly modify the Terraform configuration and remove usage of a variable and replace it with a hard coded value. For stricter enforcement, we recommend using policy checks or run tasks. +Refer to [variable precedence](/terraform/enterprise/workspaces/variables#precedence-with-priority-variable-sets) for more details. + +## Variable Values and Format + +The limits, allowable values, and required format are the same for both workspace-specific variables and variable sets. + +### Security + +HCP Terraform encrypts all variable values securely using [Vault's transit backend](/vault/docs/secrets/transit) prior to saving them. This ensures that no out-of-band party can read these values without proper authorization. However, HCP Terraform stores variable [descriptions](#variable-description) in plain text, so be careful with the information you save in a variable description. + +We also recommend passing credentials to Terraform as environment variables instead of Terraform variables when possible, since Terraform runs receive the full text of all Terraform variable values, including [sensitive](#sensitive-values) ones. It may print the values in logs and state files if the configuration sends the value to an output or a resource parameter. Sentinel mocks downloaded from runs will also contain the sensitive values of Terraform variables. + +Although HCP Terraform does not store environment variables in state, it can include them in log files if `TF_LOG` is set to `TRACE`. + +#### Dynamic Credentials + +An alternative to passing static credentials for some providers is to use [dynamic credentials](/terraform/enterprise/workspaces/dynamic-provider-credentials). + +Dynamic credentials allows for using temporary per-run credentials and eliminates the need to manually rotate secrets. + +### Character Limits + +The following limits apply to variables: + +| Component | Limit | +| ----------- | -------------- | +| description | 512 characters | +| key | 128 characters | +| value | 256 kilobytes | + +### Multi-Line Text + +You can type or paste multi-line text into variable value text fields. + +### HashiCorp Configuration Language (HCL) + +You can use HCL for Terraform variables, but not for environment variables. The same Terraform version that performs runs in the workspace will interpret the HCL. + +Variable values are strings by default. To enter list or map values, click the variable’s **HCL** checkbox (visible when editing) and enter the value with the same HCL syntax you would use when writing Terraform code. For example: + +```hcl +{ + us-east-1 = "image-1234" + us-west-2 = "image-4567" +} +``` + +### Sensitive Values + +!> **Warning:** There are some cases when even sensitive variables are included in logs and state files. Refer to [security](#security) for more information. + +Terraform often needs cloud provider credentials and other sensitive information that should not be widely available within your organization. To protect these secrets, you can mark any Terraform or environment variable as sensitive data by clicking its **Sensitive** checkbox that is visible during editing. + +Marking a variable as sensitive makes it write-only and prevents all users (including you) from viewing its value in the HCP Terraform UI or reading it through the Variables API endpoint. + +Users with permission to read and write variables can set new values for sensitive variables, but other attributes of a sensitive variable cannot be modified. To update other attributes, delete the variable and create a new variable to replace it. + +[permissions-citation]: #intentionally-unused---keep-for-maintainers + +### Variable Description + +!> **Warning:** Variable descriptions are not encrypted, so do not include any sensitive information. + +Variable descriptions are optional, and help distinguish between similarly named variables. They are only shown on the **Variables** page and are completely independent from any variable descriptions declared in Terraform CLI. diff --git a/content/ptfe-releases/v000011-1/img/docs/ado-required-status-check.png b/content/ptfe-releases/v000011-1/img/docs/ado-required-status-check.png new file mode 100644 index 0000000000..0a584d490c Binary files /dev/null and b/content/ptfe-releases/v000011-1/img/docs/ado-required-status-check.png differ diff --git a/content/ptfe-releases/v000011-1/img/docs/azure-devops-server-public-keys.png b/content/ptfe-releases/v000011-1/img/docs/azure-devops-server-public-keys.png new file mode 100644 index 0000000000..fa9488c0dd Binary files /dev/null and b/content/ptfe-releases/v000011-1/img/docs/azure-devops-server-public-keys.png differ diff --git a/content/ptfe-releases/v000011-1/img/docs/azure-devops-services-application-permissions.png b/content/ptfe-releases/v000011-1/img/docs/azure-devops-services-application-permissions.png new file mode 100644 index 0000000000..b50cb74333 Binary files /dev/null and b/content/ptfe-releases/v000011-1/img/docs/azure-devops-services-application-permissions.png differ diff --git a/content/ptfe-releases/v000011-1/img/docs/azure-devops-services-oauth-policies.png b/content/ptfe-releases/v000011-1/img/docs/azure-devops-services-oauth-policies.png new file mode 100644 index 0000000000..1b3221636a Binary files /dev/null and b/content/ptfe-releases/v000011-1/img/docs/azure-devops-services-oauth-policies.png differ diff --git a/content/ptfe-releases/v000011-1/img/docs/download-mocks.png b/content/ptfe-releases/v000011-1/img/docs/download-mocks.png new file mode 100644 index 0000000000..4963b475b7 Binary files /dev/null and b/content/ptfe-releases/v000011-1/img/docs/download-mocks.png differ diff --git a/content/ptfe-releases/v000011-1/img/docs/json-viewer-intro.png b/content/ptfe-releases/v000011-1/img/docs/json-viewer-intro.png new file mode 100644 index 0000000000..4eb364c55a Binary files /dev/null and b/content/ptfe-releases/v000011-1/img/docs/json-viewer-intro.png differ diff --git a/content/ptfe-releases/v000011-1/img/docs/organization-vcs-general-aggregated-status-checks.png b/content/ptfe-releases/v000011-1/img/docs/organization-vcs-general-aggregated-status-checks.png new file mode 100644 index 0000000000..f4a82d0b26 Binary files /dev/null and b/content/ptfe-releases/v000011-1/img/docs/organization-vcs-general-aggregated-status-checks.png differ diff --git a/content/ptfe-releases/v000011-1/img/docs/organization-vcs-general-non-aggregated-status-checks.png b/content/ptfe-releases/v000011-1/img/docs/organization-vcs-general-non-aggregated-status-checks.png new file mode 100644 index 0000000000..c328f6ce5c Binary files /dev/null and b/content/ptfe-releases/v000011-1/img/docs/organization-vcs-general-non-aggregated-status-checks.png differ diff --git a/content/ptfe-releases/v000011-1/img/docs/runs-confirm.png b/content/ptfe-releases/v000011-1/img/docs/runs-confirm.png new file mode 100644 index 0000000000..4e0f67cbe4 Binary files /dev/null and b/content/ptfe-releases/v000011-1/img/docs/runs-confirm.png differ diff --git a/content/ptfe-releases/v000011-1/img/docs/sentinel-json-enter-filter.png b/content/ptfe-releases/v000011-1/img/docs/sentinel-json-enter-filter.png new file mode 100644 index 0000000000..aa466692bc Binary files /dev/null and b/content/ptfe-releases/v000011-1/img/docs/sentinel-json-enter-filter.png differ diff --git a/content/ptfe-releases/v000011-1/img/docs/sentinel-json-quick-filter.png b/content/ptfe-releases/v000011-1/img/docs/sentinel-json-quick-filter.png new file mode 100644 index 0000000000..89d4135702 Binary files /dev/null and b/content/ptfe-releases/v000011-1/img/docs/sentinel-json-quick-filter.png differ diff --git a/content/ptfe-releases/v000011-1/img/docs/sentinel-view-json.png b/content/ptfe-releases/v000011-1/img/docs/sentinel-view-json.png new file mode 100644 index 0000000000..b107dd2647 Binary files /dev/null and b/content/ptfe-releases/v000011-1/img/docs/sentinel-view-json.png differ diff --git a/content/ptfe-releases/v000011-1/img/docs/service-now-comments.png b/content/ptfe-releases/v000011-1/img/docs/service-now-comments.png new file mode 100644 index 0000000000..3c1587f52d Binary files /dev/null and b/content/ptfe-releases/v000011-1/img/docs/service-now-comments.png differ diff --git a/content/ptfe-releases/v000011-1/img/docs/service-now-service-graph-apikey.png b/content/ptfe-releases/v000011-1/img/docs/service-now-service-graph-apikey.png new file mode 100644 index 0000000000..ae921a6629 Binary files /dev/null and b/content/ptfe-releases/v000011-1/img/docs/service-now-service-graph-apikey.png differ diff --git a/content/ptfe-releases/v000011-1/img/docs/service-now-service-graph-conditional-class-mapping.png b/content/ptfe-releases/v000011-1/img/docs/service-now-service-graph-conditional-class-mapping.png new file mode 100644 index 0000000000..203e6d0cf0 Binary files /dev/null and b/content/ptfe-releases/v000011-1/img/docs/service-now-service-graph-conditional-class-mapping.png differ diff --git a/content/ptfe-releases/v000011-1/img/docs/service-now-service-graph-deactivate-etl.png b/content/ptfe-releases/v000011-1/img/docs/service-now-service-graph-deactivate-etl.png new file mode 100644 index 0000000000..7905293a69 Binary files /dev/null and b/content/ptfe-releases/v000011-1/img/docs/service-now-service-graph-deactivate-etl.png differ diff --git a/content/ptfe-releases/v000011-1/img/docs/service-now-service-graph-design.png b/content/ptfe-releases/v000011-1/img/docs/service-now-service-graph-design.png new file mode 100644 index 0000000000..98140a8aad Binary files /dev/null and b/content/ptfe-releases/v000011-1/img/docs/service-now-service-graph-design.png differ diff --git a/content/ptfe-releases/v000011-1/img/docs/service-now-service-graph-etl-attribute-mapping.png b/content/ptfe-releases/v000011-1/img/docs/service-now-service-graph-etl-attribute-mapping.png new file mode 100644 index 0000000000..ebbb8e21a1 Binary files /dev/null and b/content/ptfe-releases/v000011-1/img/docs/service-now-service-graph-etl-attribute-mapping.png differ diff --git a/content/ptfe-releases/v000011-1/img/docs/service-now-service-graph-etl-condition-update.png b/content/ptfe-releases/v000011-1/img/docs/service-now-service-graph-etl-condition-update.png new file mode 100644 index 0000000000..7fac298ed3 Binary files /dev/null and b/content/ptfe-releases/v000011-1/img/docs/service-now-service-graph-etl-condition-update.png differ diff --git a/content/ptfe-releases/v000011-1/img/docs/service-now-service-graph-etl-editing-relationship.png b/content/ptfe-releases/v000011-1/img/docs/service-now-service-graph-etl-editing-relationship.png new file mode 100644 index 0000000000..33c18f0722 Binary files /dev/null and b/content/ptfe-releases/v000011-1/img/docs/service-now-service-graph-etl-editing-relationship.png differ diff --git a/content/ptfe-releases/v000011-1/img/docs/service-now-service-graph-etl-setting-relationship.png b/content/ptfe-releases/v000011-1/img/docs/service-now-service-graph-etl-setting-relationship.png new file mode 100644 index 0000000000..16b6e5c765 Binary files /dev/null and b/content/ptfe-releases/v000011-1/img/docs/service-now-service-graph-etl-setting-relationship.png differ diff --git a/content/ptfe-releases/v000011-1/img/docs/service-now-service-graph-import-set.png b/content/ptfe-releases/v000011-1/img/docs/service-now-service-graph-import-set.png new file mode 100644 index 0000000000..745a6d302c Binary files /dev/null and b/content/ptfe-releases/v000011-1/img/docs/service-now-service-graph-import-set.png differ diff --git a/content/ptfe-releases/v000011-1/img/docs/service-now-service-graph-scheduled-import.png b/content/ptfe-releases/v000011-1/img/docs/service-now-service-graph-scheduled-import.png new file mode 100644 index 0000000000..c7aa394cbe Binary files /dev/null and b/content/ptfe-releases/v000011-1/img/docs/service-now-service-graph-scheduled-import.png differ diff --git a/content/ptfe-releases/v000011-1/img/docs/service-now-service-graph-state-object-url.png b/content/ptfe-releases/v000011-1/img/docs/service-now-service-graph-state-object-url.png new file mode 100644 index 0000000000..b687b2a90f Binary files /dev/null and b/content/ptfe-releases/v000011-1/img/docs/service-now-service-graph-state-object-url.png differ diff --git a/content/ptfe-releases/v000011-1/img/docs/service-now-service-graph-tags.png b/content/ptfe-releases/v000011-1/img/docs/service-now-service-graph-tags.png new file mode 100644 index 0000000000..c5db95a3e3 Binary files /dev/null and b/content/ptfe-releases/v000011-1/img/docs/service-now-service-graph-tags.png differ diff --git a/content/ptfe-releases/v000011-1/img/docs/service-now-service-graph-team-token-gen.png b/content/ptfe-releases/v000011-1/img/docs/service-now-service-graph-team-token-gen.png new file mode 100644 index 0000000000..3e2de6f2b7 Binary files /dev/null and b/content/ptfe-releases/v000011-1/img/docs/service-now-service-graph-team-token-gen.png differ diff --git a/content/ptfe-releases/v000011-1/img/docs/service-now-service-graph-tfconn.png b/content/ptfe-releases/v000011-1/img/docs/service-now-service-graph-tfconn.png new file mode 100644 index 0000000000..34e8d9aef9 Binary files /dev/null and b/content/ptfe-releases/v000011-1/img/docs/service-now-service-graph-tfconn.png differ diff --git a/content/ptfe-releases/v000011-1/img/docs/service-now-service-graph-webhook-schedule.png b/content/ptfe-releases/v000011-1/img/docs/service-now-service-graph-webhook-schedule.png new file mode 100644 index 0000000000..d4e4701c71 Binary files /dev/null and b/content/ptfe-releases/v000011-1/img/docs/service-now-service-graph-webhook-schedule.png differ diff --git a/content/ptfe-releases/v000011-1/img/docs/service-now-service-graph-webhook-tfc.png b/content/ptfe-releases/v000011-1/img/docs/service-now-service-graph-webhook-tfc.png new file mode 100644 index 0000000000..b6aa99ef80 Binary files /dev/null and b/content/ptfe-releases/v000011-1/img/docs/service-now-service-graph-webhook-tfc.png differ diff --git a/content/ptfe-releases/v000011-1/img/docs/service-now-service-graph-webhook-token.png b/content/ptfe-releases/v000011-1/img/docs/service-now-service-graph-webhook-token.png new file mode 100644 index 0000000000..e65e962b78 Binary files /dev/null and b/content/ptfe-releases/v000011-1/img/docs/service-now-service-graph-webhook-token.png differ diff --git a/content/ptfe-releases/v000011-1/img/docs/service-now-store.png b/content/ptfe-releases/v000011-1/img/docs/service-now-store.png new file mode 100644 index 0000000000..89483f4b2c Binary files /dev/null and b/content/ptfe-releases/v000011-1/img/docs/service-now-store.png differ diff --git a/content/ptfe-releases/v000011-1/img/docs/service-now-updated-config.png b/content/ptfe-releases/v000011-1/img/docs/service-now-updated-config.png new file mode 100644 index 0000000000..30f01cde82 Binary files /dev/null and b/content/ptfe-releases/v000011-1/img/docs/service-now-updated-config.png differ diff --git a/content/ptfe-releases/v000011-1/img/docs/service-now-vcs-repository.png b/content/ptfe-releases/v000011-1/img/docs/service-now-vcs-repository.png new file mode 100644 index 0000000000..7280c87164 Binary files /dev/null and b/content/ptfe-releases/v000011-1/img/docs/service-now-vcs-repository.png differ diff --git a/content/ptfe-releases/v000011-1/img/docs/servicenow-catalog-add-variables-to-action.png b/content/ptfe-releases/v000011-1/img/docs/servicenow-catalog-add-variables-to-action.png new file mode 100644 index 0000000000..a8347388c9 Binary files /dev/null and b/content/ptfe-releases/v000011-1/img/docs/servicenow-catalog-add-variables-to-action.png differ diff --git a/content/ptfe-releases/v000011-1/img/docs/servicenow-catalog-adjust-script-variables.png b/content/ptfe-releases/v000011-1/img/docs/servicenow-catalog-adjust-script-variables.png new file mode 100644 index 0000000000..90e1e4ebc3 Binary files /dev/null and b/content/ptfe-releases/v000011-1/img/docs/servicenow-catalog-adjust-script-variables.png differ diff --git a/content/ptfe-releases/v000011-1/img/docs/servicenow-catalog-configure-item.png b/content/ptfe-releases/v000011-1/img/docs/servicenow-catalog-configure-item.png new file mode 100644 index 0000000000..461b290b2d Binary files /dev/null and b/content/ptfe-releases/v000011-1/img/docs/servicenow-catalog-configure-item.png differ diff --git a/content/ptfe-releases/v000011-1/img/docs/servicenow-catalog-copied-item.png b/content/ptfe-releases/v000011-1/img/docs/servicenow-catalog-copied-item.png new file mode 100644 index 0000000000..f29726c373 Binary files /dev/null and b/content/ptfe-releases/v000011-1/img/docs/servicenow-catalog-copied-item.png differ diff --git a/content/ptfe-releases/v000011-1/img/docs/servicenow-catalog-copy-action.png b/content/ptfe-releases/v000011-1/img/docs/servicenow-catalog-copy-action.png new file mode 100644 index 0000000000..71ec9c7d0d Binary files /dev/null and b/content/ptfe-releases/v000011-1/img/docs/servicenow-catalog-copy-action.png differ diff --git a/content/ptfe-releases/v000011-1/img/docs/servicenow-catalog-copy-flow.png b/content/ptfe-releases/v000011-1/img/docs/servicenow-catalog-copy-flow.png new file mode 100644 index 0000000000..a1632b6c32 Binary files /dev/null and b/content/ptfe-releases/v000011-1/img/docs/servicenow-catalog-copy-flow.png differ diff --git a/content/ptfe-releases/v000011-1/img/docs/servicenow-catalog-edit-flow.png b/content/ptfe-releases/v000011-1/img/docs/servicenow-catalog-edit-flow.png new file mode 100644 index 0000000000..2c21653e95 Binary files /dev/null and b/content/ptfe-releases/v000011-1/img/docs/servicenow-catalog-edit-flow.png differ diff --git a/content/ptfe-releases/v000011-1/img/docs/servicenow-catalog-fill-new-action-step.png b/content/ptfe-releases/v000011-1/img/docs/servicenow-catalog-fill-new-action-step.png new file mode 100644 index 0000000000..bf51f5f65b Binary files /dev/null and b/content/ptfe-releases/v000011-1/img/docs/servicenow-catalog-fill-new-action-step.png differ diff --git a/content/ptfe-releases/v000011-1/img/docs/servicenow-catalog-get-variables.png b/content/ptfe-releases/v000011-1/img/docs/servicenow-catalog-get-variables.png new file mode 100644 index 0000000000..94821fc4da Binary files /dev/null and b/content/ptfe-releases/v000011-1/img/docs/servicenow-catalog-get-variables.png differ diff --git a/content/ptfe-releases/v000011-1/img/docs/servicenow-catalog-new-varset-form.png b/content/ptfe-releases/v000011-1/img/docs/servicenow-catalog-new-varset-form.png new file mode 100644 index 0000000000..d6bf2f3d65 Binary files /dev/null and b/content/ptfe-releases/v000011-1/img/docs/servicenow-catalog-new-varset-form.png differ diff --git a/content/ptfe-releases/v000011-1/img/docs/servicenow-catalog-new-varset.png b/content/ptfe-releases/v000011-1/img/docs/servicenow-catalog-new-varset.png new file mode 100644 index 0000000000..051ac6eba3 Binary files /dev/null and b/content/ptfe-releases/v000011-1/img/docs/servicenow-catalog-new-varset.png differ diff --git a/content/ptfe-releases/v000011-1/img/docs/servicenow-catalog-open-action.png b/content/ptfe-releases/v000011-1/img/docs/servicenow-catalog-open-action.png new file mode 100644 index 0000000000..fdc4f97274 Binary files /dev/null and b/content/ptfe-releases/v000011-1/img/docs/servicenow-catalog-open-action.png differ diff --git a/content/ptfe-releases/v000011-1/img/docs/servicenow-catalog-original-flow.png b/content/ptfe-releases/v000011-1/img/docs/servicenow-catalog-original-flow.png new file mode 100644 index 0000000000..2456b8bbe2 Binary files /dev/null and b/content/ptfe-releases/v000011-1/img/docs/servicenow-catalog-original-flow.png differ diff --git a/content/ptfe-releases/v000011-1/img/docs/servicenow-catalog-process-engine.png b/content/ptfe-releases/v000011-1/img/docs/servicenow-catalog-process-engine.png new file mode 100644 index 0000000000..979d5c3a70 Binary files /dev/null and b/content/ptfe-releases/v000011-1/img/docs/servicenow-catalog-process-engine.png differ diff --git a/content/ptfe-releases/v000011-1/img/docs/servicenow-catalog-remove-example-variables-from-action.png b/content/ptfe-releases/v000011-1/img/docs/servicenow-catalog-remove-example-variables-from-action.png new file mode 100644 index 0000000000..09d558eb39 Binary files /dev/null and b/content/ptfe-releases/v000011-1/img/docs/servicenow-catalog-remove-example-variables-from-action.png differ diff --git a/content/ptfe-releases/v000011-1/img/docs/servicenow-catalog-remove-example-variables.png b/content/ptfe-releases/v000011-1/img/docs/servicenow-catalog-remove-example-variables.png new file mode 100644 index 0000000000..214822eb84 Binary files /dev/null and b/content/ptfe-releases/v000011-1/img/docs/servicenow-catalog-remove-example-variables.png differ diff --git a/content/ptfe-releases/v000011-1/img/docs/servicenow-catalog-rename-action.png b/content/ptfe-releases/v000011-1/img/docs/servicenow-catalog-rename-action.png new file mode 100644 index 0000000000..cb18e2b96d Binary files /dev/null and b/content/ptfe-releases/v000011-1/img/docs/servicenow-catalog-rename-action.png differ diff --git a/content/ptfe-releases/v000011-1/img/docs/servicenow-catalog-replace-action.png b/content/ptfe-releases/v000011-1/img/docs/servicenow-catalog-replace-action.png new file mode 100644 index 0000000000..2757f6cdce Binary files /dev/null and b/content/ptfe-releases/v000011-1/img/docs/servicenow-catalog-replace-action.png differ diff --git a/content/ptfe-releases/v000011-1/img/docs/servicenow-catalog-service-portal.png b/content/ptfe-releases/v000011-1/img/docs/servicenow-catalog-service-portal.png new file mode 100644 index 0000000000..d54402d3ef Binary files /dev/null and b/content/ptfe-releases/v000011-1/img/docs/servicenow-catalog-service-portal.png differ diff --git a/content/ptfe-releases/v000011-1/img/docs/servicenow-catalog-update-process-engine.png b/content/ptfe-releases/v000011-1/img/docs/servicenow-catalog-update-process-engine.png new file mode 100644 index 0000000000..1c3cbfdce8 Binary files /dev/null and b/content/ptfe-releases/v000011-1/img/docs/servicenow-catalog-update-process-engine.png differ diff --git a/content/ptfe-releases/v000011-1/img/docs/servicenow-catalog-variables.png b/content/ptfe-releases/v000011-1/img/docs/servicenow-catalog-variables.png new file mode 100644 index 0000000000..0a7aa95de8 Binary files /dev/null and b/content/ptfe-releases/v000011-1/img/docs/servicenow-catalog-variables.png differ diff --git a/content/ptfe-releases/v000011-1/img/docs/terraform-cloud-run-tasks-diagram.png b/content/ptfe-releases/v000011-1/img/docs/terraform-cloud-run-tasks-diagram.png new file mode 100644 index 0000000000..f07ba3396f Binary files /dev/null and b/content/ptfe-releases/v000011-1/img/docs/terraform-cloud-run-tasks-diagram.png differ diff --git a/content/ptfe-releases/v000011-1/img/docs/tfc-explorer-health.png b/content/ptfe-releases/v000011-1/img/docs/tfc-explorer-health.png new file mode 100644 index 0000000000..de8f58bf5b Binary files /dev/null and b/content/ptfe-releases/v000011-1/img/docs/tfc-explorer-health.png differ diff --git a/content/ptfe-releases/v000011-1/img/docs/workspace-net-infra-combined.png b/content/ptfe-releases/v000011-1/img/docs/workspace-net-infra-combined.png new file mode 100644 index 0000000000..de0d770d54 Binary files /dev/null and b/content/ptfe-releases/v000011-1/img/docs/workspace-net-infra-combined.png differ diff --git a/content/ptfe-releases/v000011-1/img/docs/workspace-net-infra-split.png b/content/ptfe-releases/v000011-1/img/docs/workspace-net-infra-split.png new file mode 100644 index 0000000000..33bb2d03fb Binary files /dev/null and b/content/ptfe-releases/v000011-1/img/docs/workspace-net-infra-split.png differ diff --git a/content/terraform-docs-agents/v1.22.x/docs/cloud-docs/agents/agents.mdx b/content/terraform-docs-agents/v1.22.x/docs/cloud-docs/agents/agents.mdx index 8b04a23b5a..9b3d9375bd 100644 --- a/content/terraform-docs-agents/v1.22.x/docs/cloud-docs/agents/agents.mdx +++ b/content/terraform-docs-agents/v1.22.x/docs/cloud-docs/agents/agents.mdx @@ -123,6 +123,11 @@ The agent maintains a registration and a liveness indicator within HCP Terraform After initiating a graceful shutdown by either of these methods, the terminal user or parent program should wait for the agent to exit. The amount of time this exit takes depends on the agent's current workload. The agent waits for any current operations to complete before deregistering and exiting. + + # CLI Options @@ -247,7 +252,6 @@ After initiating a graceful shutdown by either of these methods, the terminal us Environment variable: `TFC_AGENT_ACCEPT` -@include 'policy.mdx' * `-request-forwarding`: Enable handling of forwarded HTTP requests. Enable this option only if you diff --git a/content/terraform-plugin-framework/v1.15.x/data/plugin-framework-nav-data.json b/content/terraform-plugin-framework/v1.15.x/data/plugin-framework-nav-data.json new file mode 100644 index 0000000000..c1585afa51 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/data/plugin-framework-nav-data.json @@ -0,0 +1,656 @@ +[ + { + "heading": "Framework" + }, + { + "title": "Overview", + "path": "" + }, + { + "title": "Getting Started", + "routes": [ + { + "title": "Provider Code Walkthrough", + "path": "getting-started/code-walkthrough" + }, + { + "title": "Tutorials", + "href": "https://developer.hashicorp.com/terraform/tutorials/providers-plugin-framework?utm_source=WEBSITE&utm_medium=WEB_IO&utm_offer=ARTICLE_PAGE&utm_content=DOCS" + }, + { + "title": "Clone Template Repository", + "href": "https://github.com/hashicorp/terraform-provider-scaffolding-framework" + } + ] + }, + { + "title": "Provider Servers", + "path": "provider-servers" + }, + { + "title": "Providers", + "routes": [ + { + "title": "Overview", + "path": "providers" + }, + { + "title": "Validate Configuration", + "path": "providers/validate-configuration" + } + ] + }, + { + "title": "Resources", + "routes": [ + { + "title": "Overview", + "path": "resources" + }, + { + "title": "Create", + "path": "resources/create" + }, + { + "title": "Read", + "path": "resources/read" + }, + { + "title": "Update", + "path": "resources/update" + }, + { + "title": "Delete", + "path": "resources/delete" + }, + { + "title": "Identity", + "path": "resources/identity" + }, + { + "title": "Configure Clients", + "path": "resources/configure" + }, + { + "title": "Validate Configuration", + "path": "resources/validate-configuration" + }, + { + "title": "Modify Plan", + "path": "resources/plan-modification" + }, + { + "title": "Default Attribute Values", + "path": "resources/default" + }, + { + "title": "Import State", + "path": "resources/import" + }, + { + "title": "Move State", + "path": "resources/state-move" + }, + { + "title": "Upgrade State", + "path": "resources/state-upgrade" + }, + { + "title": "Upgrade Identity", + "path": "resources/identity-upgrade" + }, + { + "title": "Manage Private State", + "path": "resources/private-state" + }, + { + "title": "Timeouts", + "path": "resources/timeouts" + }, + { + "title": "Write-only Arguments", + "path": "resources/write-only-arguments" + } + ] + }, + { + "title": "Data Sources", + "routes": [ + { + "title": "Overview", + "path": "data-sources" + }, + { + "title": "Configure Clients", + "path": "data-sources/configure" + }, + { + "title": "Validate Configuration", + "path": "data-sources/validate-configuration" + }, + { + "title": "Timeouts", + "path": "data-sources/timeouts" + } + ] + }, + { + "title": "Functions", + "routes": [ + { + "title": "Overview", + "path": "functions" + }, + { + "title": "Concepts", + "path": "functions/concepts" + }, + { + "title": "Implementation", + "path": "functions/implementation" + }, + { + "title": "Parameters", + "routes": [ + { + "title": "Overview", + "path": "functions/parameters" + }, + { + "title": "Bool", + "path": "functions/parameters/bool" + }, + { + "title": "Dynamic", + "path": "functions/parameters/dynamic" + }, + { + "title": "Float32", + "path": "functions/parameters/float32" + }, + { + "title": "Float64", + "path": "functions/parameters/float64" + }, + { + "title": "Int32", + "path": "functions/parameters/int32" + }, + { + "title": "Int64", + "path": "functions/parameters/int64" + }, + { + "title": "List", + "path": "functions/parameters/list" + }, + { + "title": "Map", + "path": "functions/parameters/map" + }, + { + "title": "Number", + "path": "functions/parameters/number" + }, + { + "title": "Object", + "path": "functions/parameters/object" + }, + { + "title": "Set", + "path": "functions/parameters/set" + }, + { + "title": "String", + "path": "functions/parameters/string" + } + ] + }, + { + "title": "Returns", + "routes": [ + { + "title": "Overview", + "path": "functions/returns" + }, + { + "title": "Bool", + "path": "functions/returns/bool" + }, + { + "title": "Dynamic", + "path": "functions/returns/dynamic" + }, + { + "title": "Float32", + "path": "functions/returns/float32" + }, + { + "title": "Float64", + "path": "functions/returns/float64" + }, + { + "title": "Int32", + "path": "functions/returns/int32" + }, + { + "title": "Int64", + "path": "functions/returns/int64" + }, + { + "title": "List", + "path": "functions/returns/list" + }, + { + "title": "Map", + "path": "functions/returns/map" + }, + { + "title": "Number", + "path": "functions/returns/number" + }, + { + "title": "Object", + "path": "functions/returns/object" + }, + { + "title": "Set", + "path": "functions/returns/set" + }, + { + "title": "String", + "path": "functions/returns/string" + } + ] + }, + { + "title": "Errors", + "path": "functions/errors" + }, + { + "title": "Testing", + "path": "functions/testing" + }, + { + "title": "Documentation", + "path": "functions/documentation" + } + ] + }, + { + "title": "Ephemeral Resources", + "routes": [ + { + "title": "Overview", + "path": "ephemeral-resources" + }, + { + "title": "Open", + "path": "ephemeral-resources/open" + }, + { + "title": "Configure Clients", + "path": "ephemeral-resources/configure" + }, + { + "title": "Validate Configuration", + "path": "ephemeral-resources/validate-configuration" + }, + { + "title": "Renew", + "path": "ephemeral-resources/renew" + }, + { + "title": "Close", + "path": "ephemeral-resources/close" + } + ] + }, + { + "title": "Handling Data", + "routes": [ + { + "title": "Terraform Concepts", + "path": "handling-data/terraform-concepts" + }, + { + "title": "Schemas", + "path": "handling-data/schemas" + }, + { + "title": "Attributes", + "routes": [ + { + "title": "Overview", + "path": "handling-data/attributes" + }, + { + "title": "Bool", + "path": "handling-data/attributes/bool" + }, + { + "title": "Dynamic", + "path": "handling-data/attributes/dynamic" + }, + { + "title": "Float32", + "path": "handling-data/attributes/float32" + }, + { + "title": "Float64", + "path": "handling-data/attributes/float64" + }, + { + "title": "Int32", + "path": "handling-data/attributes/int32" + }, + { + "title": "Int64", + "path": "handling-data/attributes/int64" + }, + { + "title": "List", + "path": "handling-data/attributes/list" + }, + { + "title": "List Nested", + "path": "handling-data/attributes/list-nested" + }, + { + "title": "Map", + "path": "handling-data/attributes/map" + }, + { + "title": "Map Nested", + "path": "handling-data/attributes/map-nested" + }, + { + "title": "Number", + "path": "handling-data/attributes/number" + }, + { + "title": "Object", + "path": "handling-data/attributes/object" + }, + { + "title": "Set", + "path": "handling-data/attributes/set" + }, + { + "title": "Set Nested", + "path": "handling-data/attributes/set-nested" + }, + { + "title": "Single Nested", + "path": "handling-data/attributes/single-nested" + }, + { + "title": "String", + "path": "handling-data/attributes/string" + } + ] + }, + { + "title": "Blocks", + "routes": [ + { + "title": "Overview", + "path": "handling-data/blocks" + }, + { + "title": "List Nested", + "path": "handling-data/blocks/list-nested" + }, + { + "title": "Set Nested", + "path": "handling-data/blocks/set-nested" + }, + { + "title": "Single Nested", + "path": "handling-data/blocks/single-nested" + } + ] + }, + { + "title": "Types", + "routes": [ + { + "title": "Overview", + "path": "handling-data/types" + }, + { + "title": "Bool", + "path": "handling-data/types/bool" + }, + { + "title": "Dynamic", + "path": "handling-data/types/dynamic" + }, + { + "title": "Float32", + "path": "handling-data/types/float32" + }, + { + "title": "Float64", + "path": "handling-data/types/float64" + }, + { + "title": "Int32", + "path": "handling-data/types/int32" + }, + { + "title": "Int64", + "path": "handling-data/types/int64" + }, + { + "title": "List", + "path": "handling-data/types/list" + }, + { + "title": "Map", + "path": "handling-data/types/map" + }, + { + "title": "Number", + "path": "handling-data/types/number" + }, + { + "title": "Object", + "path": "handling-data/types/object" + }, + { + "title": "Set", + "path": "handling-data/types/set" + }, + { + "title": "String", + "path": "handling-data/types/string" + }, + { + "title": "Tuple", + "path": "handling-data/types/tuple" + }, + { + "title": "Custom Types", + "path": "handling-data/types/custom" + } + ] + }, + { + "title": "Paths", + "path": "handling-data/paths" + }, + { + "title": "Path Expressions", + "path": "handling-data/path-expressions" + }, + { + "title": "Accessing Terraform Data", + "path": "handling-data/accessing-values" + }, + { + "title": "Writing Data", + "path": "handling-data/writing-state" + }, + { + "title": "Dynamic Data", + "path": "handling-data/dynamic-data" + } + ] + }, + { + "title": "Returning Errors and Warnings", + "path": "diagnostics" + }, + { + "title": "Validation", + "path": "validation" + }, + { + "title": "Acceptance Tests", + "path": "acctests" + }, + { + "title": "Debugging", + "path": "debugging" + }, + { + "title": "Deprecations, Removals, and Renames", + "path": "deprecations" + }, + { + "title": "Migrating from SDK", + "routes": [ + { + "title": "Overview", + "path": "migrating" + }, + { + "title": "Benefits", + "path": "migrating/benefits" + }, + { + "title": "Muxing", + "path": "migrating/mux" + }, + { + "title": "Testing", + "path": "migrating/testing" + }, + { + "title": "Schema", + "routes": [ + { + "title": "Overview", + "path": "migrating/schema" + } + ] + }, + { + "title": "Providers", + "routes": [ + { + "title": "Overview", + "path": "migrating/providers" + } + ] + }, + { + "title": "Resources", + "routes": [ + { + "title": "Overview", + "path": "migrating/resources" + }, + { + "title": "CRUD Functions", + "path": "migrating/resources/crud" + }, + { + "title": "Import", + "path": "migrating/resources/import" + }, + { + "title": "Plan Modification", + "path": "migrating/resources/plan-modification" + }, + { + "title": "State Upgraders", + "path": "migrating/resources/state-upgrade" + }, + { + "title": "Timeouts", + "path": "migrating/resources/timeouts" + } + ] + }, + { + "title": "Data Sources", + "routes": [ + { + "title": "Overview", + "path": "migrating/data-sources" + }, + { + "title": "Timeouts", + "path": "migrating/data-sources/timeouts" + } + ] + }, + { + "title": "Attributes & Blocks", + "routes": [ + { + "title": "Attribute Schema", + "path": "migrating/attributes-blocks/attribute-schema" + }, + { + "title": "Attribute Types", + "path": "migrating/attributes-blocks/types" + }, + { + "title": "Attribute Fields", + "path": "migrating/attributes-blocks/fields" + }, + { + "title": "Default Values", + "path": "migrating/attributes-blocks/default-values" + }, + { + "title": "Force New", + "path": "migrating/attributes-blocks/force-new" + }, + { + "title": "Validators - Predefined", + "path": "migrating/attributes-blocks/validators-predefined" + }, + { + "title": "Validators - Custom", + "path": "migrating/attributes-blocks/validators-custom" + }, + { + "title": "Blocks", + "path": "migrating/attributes-blocks/blocks" + }, + { + "title": "Blocks with Computed Fields", + "path": "migrating/attributes-blocks/blocks-computed" + } + ] + } + ] + }, + { + "title": "Internals", + "routes": [ + { + "title": "Overview", + "path": "internals" + }, + { + "title": "RPCs and Framework Functionality", + "path": "internals/rpcs" + } + ] + } +] diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/acctests.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/acctests.mdx new file mode 100644 index 0000000000..e23cd6fbb0 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/acctests.mdx @@ -0,0 +1,84 @@ +--- +page_title: Acceptance tests +description: >- + Learn how to write acceptance tests for providers built on the framework. + Acceptance tests help ensure your provider works as expected by imitating + Terraform operations. +--- + +# Acceptance tests + +Implement provider resource and data source acceptance tests with the [terraform-plugin-testing module](/terraform/plugin/testing). These tests are designed to execute Terraform commands against real Terraform configurations, simulating practitioner experiences with creating, refreshing, updating, and deleting infrastructure. + +This page only describes requirements of using the testing module with a framework provider. Refer to the [testing module documentation](/terraform/plugin/testing) for additional information on all available functionality in tests. + +## Requirements + +The testing module must know how to reference your provider code before Terraform commands and configurations can succeed. This is achieved by pointing the testing module at a [provider server](/terraform/plugin/framework/provider-servers) which wraps your [provider](/terraform/plugin/framework/providers). + +Use one of the [`resource.TestCase` type](/terraform/plugin/testing/acceptance-tests/testcase) [`ProtoV6ProviderFactories` field](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/helper/resource#TestCase.ProtoV6ProviderFactories) for [protocol version 6](/terraform/plugin/terraform-plugin-protocol#protocol-version-6) or [`ProtoV5ProviderFactories` field](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/helper/resource#TestCase.ProtoV5ProviderFactories) for [protocol version 5](/terraform/plugin/terraform-plugin-protocol#protocol-version-5). It is only necessary to test with the single protocol version matching the production provider server, typically defined in the `main.go` file of the provider codebase. + +### Protocol Version 6 + +Use the [`providerserver.NewProtocol6WithError`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/providerserver#NewProtocol6WithError) helper function to implement the provider server in the [`ProtoV6ProviderFactories` field](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/helper/resource#TestCase.ProtoV6ProviderFactories). + +```go +resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: map[string]func() (tfprotov6.ProviderServer, error) { + // newProvider is an example function that returns a provider.Provider + "examplecloud": providerserver.NewProtocol6WithError(newProvider()), + }, + Steps: []resource.TestStep{/* ... */}, +}) +``` + +### Protocol Version 5 + +Use the [`providerserver.NewProtocol5WithError`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/providerserver#NewProtocol5WithError) helper function to implement the provider server in the [`ProtoV5ProviderFactories` field](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/helper/resource#TestCase.ProtoV5ProviderFactories). + +```go +resource.Test(t, resource.TestCase{ + ProtoV5ProviderFactories: map[string]func() (tfprotov5.ProviderServer, error) { + // newProvider is an example function that returns a provider.Provider + "examplecloud": providerserver.NewProtocol5WithError(newProvider()), + }, + Steps: []resource.TestStep{/* ... */}, +}) +``` + +## Troubleshooting + +### No id found in attributes + + + +terraform-plugin-testing version 1.5.0 and later no longer require managed resources and data resources to implement the `id` attribute. + + + +In SDKv2, resources and data sources automatically included an implicit, root level `id` attribute. In the framework, the `id` attribute is not implicitly added. + +When testing resources and data sources without the `id` attribute, the acceptance testing framework will return errors such as: + +```text +testing_new_config.go:111: no "id" found in attributes +testing_new.go:53: no "id" found in attributes +``` + +To avoid this, add a root level `id` attribute to resource and data source schemas. Ensure the attribute value is appropriately [written to state](/terraform/plugin/framework/writing-state). Conventionally, `id` is a computed attribute that contains the identifier for the resource. + +For example, in the `Schema` method implementation of a [`datasource.DataSource`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/datasource#DataSource) or [`resource.Resource`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#Resource): + +```go +func (r *ThingResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + // ... potentially other schema configuration ... + Attributes: map[string]schema.Attribute{ + // ... potentially other schema attributes ... + "id": schema.StringAttribute{ + Computed: true, + }, + }, + } +} +``` diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/data-sources/configure.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/data-sources/configure.mdx new file mode 100644 index 0000000000..11dda382b1 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/data-sources/configure.mdx @@ -0,0 +1,104 @@ +--- +page_title: Configure data sources +description: >- + Learn how to configure data sources with provider data or clients in the + Terraform plugin framework. +--- + +# Configure data sources + +[Data sources](/terraform/plugin/framework/data-sources) may require provider-level data or remote system clients to operate correctly. The framework supports the ability to configure this data and/or clients once within the provider, then pass that information to data sources by adding the `Configure` method. + +## Prepare Provider Configure Method + +Implement the [`provider.ConfigureResponse.DataSourceData` field](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider#ConfigureResponse.DataSourceData) in the [`Provider` interface `Configure` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider#Provider.Configure). This value can be set to any type, whether an existing client or vendor SDK type, a provider-defined custom type, or the provider implementation itself. It is recommended to use pointer types so that data sources can determine if this value was configured before attempting to use it. + +During execution of the [`terraform plan`](/terraform/cli/commands/plan) and [`terraform apply`](/terraform/cli/commands/apply) commands, Terraform calls the [`ConfigureProvider`](/terraform/plugin/framework/internals/rpcs#configureprovider-rpc) RPC, in which the framework calls the [`provider.Provider` interface `Configure` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider#Provider.Configure). + +In this example, the Go standard library [`net/http.Client`](https://pkg.go.dev/net/http#Client) is configured in the provider, and made available for data sources: + +```go +// With the provider.Provider implementation +func (p *ExampleCloudProvider) Configure(ctx context.Context, req provider.ConfigureRequest, resp *provider.ConfigureResponse) { + resp.DataSourceData = &http.Client{/* ... */} +} +``` + +In this example, the code defines an `ExampleClient` type that is made available for data sources: + +```go +type ExampleClient struct { + /* ... */ +} + +// With the provider.Provider implementation +func (p *ExampleCloudProvider) Configure(ctx context.Context, req provider.ConfigureRequest, resp *provider.ConfigureResponse) { + resp.DataSourceData = &ExampleClient{/* ... */} +} +``` + +In this example, the `ExampleCloudProvider` type itself is made available for data sources: + +```go +// With the provider.Provider implementation +type ExampleCloudProvider struct { + /* ... */ +} + +func (p *ExampleCloudProvider) Configure(ctx context.Context, req provider.ConfigureRequest, resp *provider.ConfigureResponse) { + resp.DataSourceData = p +} +``` + +## Define Data Source Configure Method + +Implement the [`datasource.DataSourceWithConfigure` interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/datasource#DataSourceWithConfigure) which receives the provider configured data from the [`Provider` interface `Configure` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider#Provider.Configure) and saves it into the [`datasource.DataSource` interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/datasource#DataSource) implementation. + +The [`datasource.DataSourceWithConfigure` interface `Configure` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/datasource#DataSourceWithConfigure.Configure) is called during execution of the [`terraform validate`](/terraform/cli/commands/validate), [`terraform plan`](/terraform/cli/commands/plan) and [`terraform apply`](/terraform/cli/commands/apply) commands when the [`ValidateDataResourceConfig`](/terraform/plugin/framework/internals/rpcs#validatedataresourceconfig-rpc) RPC is sent. Additionally, the [`datasource.DataSourceWithConfigure` interface `Configure` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/datasource#DataSourceWithConfigure.Configure) is called during execution of the [`terraform plan`](/terraform/cli/commands/plan) and [`terraform apply`](/terraform/cli/commands/apply) commands when the [`ReadDataSource`](/terraform/plugin/framework/internals/rpcs#readdatasource-rpc) RPC is sent. + +-> Note that Terraform calling the [`ValidateDataResourceConfig`](/terraform/plugin/framework/internals/rpcs#validatedataresourceconfig-rpc) RPC would not call the [`ConfigureProvider`](/terraform/plugin/framework/internals/rpcs#configureprovider-rpc) RPC first, so implementations need to account for that situation. Configuration validation in Terraform occurs without provider configuration ("offline"). + +In this example, the provider configured the Go standard library [`net/http.Client`](https://pkg.go.dev/net/http#Client) which the data source uses during `Read`: + +```go +// With the datasource.DataSource implementation +type ThingDataSource struct { + client *http.Client +} + +func (d *ThingDataSource) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { + // Always perform a nil check when handling ProviderData because Terraform + // sets that data after it calls the ConfigureProvider RPC. + if req.ProviderData == nil { + return + } + + client, ok := req.ProviderData.(*http.Client) + + if !ok { + resp.Diagnostics.AddError( + "Unexpected Data Source Configure Type", + fmt.Sprintf("Expected *http.Client, got: %T. Please report this issue to the provider developers.", req.ProviderData), + ) + + return + } + + d.client = client +} + +func (d *ThingDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + // Prevent panic if the provider has not been configured. + if d.client == nil { + resp.Diagnostics.AddError( + "Unconfigured HTTP Client", + "Expected configured HTTP client. Please report this issue to the provider developers.", + ) + + return + } + + httpResp, err := d.client.Get("https://example.com") + /* ... */ +} +``` diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/data-sources/index.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/data-sources/index.mdx new file mode 100644 index 0000000000..64dfb2ecc7 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/data-sources/index.mdx @@ -0,0 +1,147 @@ +--- +page_title: Data sources +description: >- + Data sources allow Terraform to reference external data. Learn how the + framework can help you implement data sources. +--- + +# Data sources + +[Data sources](/terraform/language/data-sources) are an abstraction that allow Terraform to reference external data. Unlike [managed resources](/terraform/language/resources), Terraform does not manage the lifecycle of the resource or data. Data sources are intended to have no side-effects. + +This page describes the basic implementation details required for supporting a data source within the provider. Further documentation is available for deeper data source concepts: + +- [Configure](/terraform/plugin/framework/data-sources/configure) data sources with provider-level data types or clients. +- [Validate](/terraform/plugin/framework/data-sources/validate-configuration) practitioner configuration against acceptable values. +- [Timeouts](/terraform/plugin/framework/data-sources/timeouts) in practitioner configuration for use in a data source read function. + +## Define Data Source Type + +Implement the [`datasource.DataSource` interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/datasource#DataSource). Each of the methods is described in more detail below. + +In this example, a data source named `examplecloud_thing` with hardcoded behavior is defined: + +```go +// Ensure the implementation satisfies the desired interfaces. +var _ datasource.DataSource = &ThingDataSource{} + +type ThingDataSource struct {} + +type ThingDataSourceModel struct { + ExampleAttribute types.String `tfsdk:"example_attribute"` + ID types.String `tfsdk:"id"` +} + +func (d *ThingDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = "examplecloud_thing" +} + +func (d *ThingDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "example_attribute": schema.StringAttribute{ + Required: true, + }, + "id": schema.StringAttribute{ + Computed: true, + }, + }, + } +} + +func (d *ThingDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + var data ThingDataSourceModel + + // Read Terraform configuration data into the model + resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) + + // Typically data sources will make external calls, however this example + // hardcodes setting the id attribute to a specific value for brevity. + data.ID = types.StringValue("example-id") + + // Save data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} +``` + +### Metadata Method + +The [`datasource.DataSource` interface `Metadata` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/datasource#DataSource.Metadata) defines the data source name as it would appear in Terraform configurations. This name should include the provider type prefix, an underscore, then the data source specific name. For example, a provider named `examplecloud` and a data source that reads "thing" resources would be named `examplecloud_thing`. Ensure the [Add Data Source To Provider](#add-data-source-to-provider) documentation is followed so the data source becomes part of the provider implementation, and therefore available to practitioners. + +In this example, the data source name in an `examplecloud` provider that reads "thing" resources is hardcoded to `examplecloud_thing`: + +```go +// With the datasource.DataSource implementation +func (d *ThingDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = "examplecloud_thing" +} +``` + +To simplify data source implementations, the [`provider.MetadataResponse.TypeName` field](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider#MetadataResponse.TypeName) from the [`provider.Provider` interface `Metadata` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider#Provider.Metadata) can set the provider name so it is available in the [`datasource.MetadataRequest.ProviderTypeName` field](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/datasource#MetadataRequest.ProviderTypeName). + +In this example, the provider defines the `examplecloud` name for itself, and the data source is named `examplecloud_thing`: + +```go +// With the provider.Provider implementation +func (p *ExampleCloudProvider) Metadata(ctx context.Context, req provider.MetadataRequest, resp *provider.MetadataResponse) { + resp.TypeName = "examplecloud" +} + +// With the datasource.DataSource implementation +func (d *ThingDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_thing" +} +``` + +### Schema Method + +The [`datasource.DataSource` interface `Schema` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/datasource#DataSource.Schema) defines a [schema](/terraform/plugin/framework/schemas) describing what data is available in the data source's configuration and state. + +### Read Method + +The [`datasource.DataSource` interface `Read` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/datasource#DataSource.Read) defines how the data source updates Terraform's state to reflect the retrieved data. There is no plan or prior state to work with in `Read` requests, only configuration. + +During the [`terraform apply`](/terraform/cli/commands/apply), [`terraform plan`](/terraform/cli/commands/plan), and [`terraform refresh`](/terraform/cli/commands/refresh) commands, Terraform calls the provider [`ReadDataSource`](/terraform/plugin/framework/internals/rpcs#readdatasource-rpc) RPC, in which the framework calls the [`datasource.DataSource` interface `Read` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/datasource#DataSource.Read). + +Implement the `Read` method by: + +1. [Accessing configuration data](/terraform/plugin/framework/accessing-values) from the [`datasource.ReadRequest.Config` field](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/datasource#ReadRequest.Config). +1. Retriving any additional data, such as remote system information. +1. [Writing state data](/terraform/plugin/framework/writing-state) into the [`datasource.ReadResponse.State` field](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/datasource#ReadResponse.State). + +If the logic needs to return [warning or error diagnostics](/terraform/plugin/framework/diagnostics), they can added into the [`datasource.ReadResponse.Diagnostics` field](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/datasource#ReadResponse.Diagnostics). + +## Add Data Source to Provider + +Data sources become available to practitioners when they are included in the [provider](/terraform/plugin/framework/providers) implementation via the [`provider.ProviderWithDataSources` interface `DataSources` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider#ProviderWithDataSources.DataSources). + +In this example, the `ThingDataSource` type, which implements the `datasource.DataSource` interface, is added to the provider implementation: + +```go +// With the provider.Provider implementation +func (p *ExampleCloudProvider) DataSources(_ context.Context) []func() datasource.DataSource { + return []func() datasource.DataSource{ + func() datasource.DataSource { + return &ThingDataSource{}, + }, + } +} +``` + +To simplify provider implementations, a named function can be created with the data source implementation. + +In this example, the `ThingDataSource` code includes an additional `NewThingDataSource` function, which simplifies the provider implementation: + +```go +// With the provider.Provider implementation +func (p *ExampleCloudProvider) DataSources(_ context.Context) []func() datasource.DataSource { + return []func() datasource.DataSource{ + NewThingDataSource, + } +} + +// With the datasource.DataSource implementation +func NewThingDataSource() datasource.DataSource { + return &ThingDataSource{} +} +``` diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/data-sources/timeouts.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/data-sources/timeouts.mdx new file mode 100644 index 0000000000..7868dd4020 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/data-sources/timeouts.mdx @@ -0,0 +1,132 @@ +--- +page_title: Timeouts +description: >- + Learn how to implement timeouts with the Terraform plugin framework. +--- + +# Timeouts + +The reality of cloud infrastructure is that it typically takes time to perform operations such as booting operating systems, discovering services, and replicating state across network edges. As the provider developer you should take known delays in data source APIs into account in the `Read` function of the data source. Terraform supports configurable timeouts to assist in these situations. + +The Framework can be used in conjunction with the [terraform-plugin-framework-timeouts](https://github.com/hashicorp/terraform-plugin-framework-timeouts) module in order to allow defining timeouts in configuration and have them be available in the `Read` function. + +## Specifying Timeouts in Configuration + +Timeouts can be defined using either nested blocks or nested attributes. + +If you are writing a new provider using [terraform-plugin-framework](https://github.com/hashicorp/terraform-plugin-framework) +then we recommend using nested attributes. + +If you are [migrating a provider from SDKv2 to the Framework](/terraform/plugin/framework/migrating) and +you are already using timeouts you can either continue to use block syntax, or switch to using nested attributes. +However, switching to using nested attributes will require that practitioners that are using your provider update their +Terraform configuration. + +#### Block + +If your configuration is using a nested block to define timeouts, such as the following: + +```hcl +resource "timeouts_example" "example" { + /* ... */ + + timeouts { + read = "60m" + } +} +``` + +Import the [timeouts module](https://github.com/hashicorp/terraform-plugin-framework-timeouts). + +```go +import ( + /* ... */ + "github.com/hashicorp/terraform-plugin-framework-timeouts/datasource/timeouts" +) +```` + +You can use this module to mutate the `schema.Schema` as follows: + +```go +func (d *ThingDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) { + resp.Schema = schema.Schema{ + /* ... */ + + Blocks: map[string]schema.Block{ + "timeouts": timeouts.Block(ctx), + }, +``` + +#### Attribute + +If your configuration is using nested attributes to define timeouts, such as the following: + +```hcl +resource "timeouts_example" "example" { + /* ... */ + + timeouts = { + read = "60m" + } +} +``` + +You can use this module to mutate the `schema.Schema` as follows: + +```go +func (d *ThingDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + /* ... */ + "timeouts": timeouts.Attributes(ctx), + }, +``` + +## Updating Models + +Given a `Read` method which fetches the entire configuration: + +```go +func (e *exampleDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + var data exampleDataSourceData + + diags := req.Config.Get(ctx, &data) + resp.Diagnostics.Append(diags...) +``` + +Modify the `exampleDataSourceData` model to include a field for timeouts using a [`timeouts.Value`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-timeouts/datasource/timeouts#Value) type. + +```go +type exampleDataSourceData struct { + /* ... */ + Timeouts timeouts.Value `tfsdk:"timeouts"` +``` + +## Accessing Timeout in Read Method + +Call the [`timeouts.Read()` function](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-timeouts/datasource/timeouts#Value.Read). + +```go +func (e *exampleDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + var data exampleDataSourceData + + diags := req.Config.Get(ctx, &data) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + readTimeout, diags := data.Timeouts.Read(ctx, 20*time.Minute) + + resp.Diagnostics.Append(diags...) + + if resp.Diagnostics.HasError() { + return + } + + ctx, cancel := context.WithTimeout(ctx, readTimeout) + defer cancel() + + /* ... */ +} +``` diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/data-sources/validate-configuration.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/data-sources/validate-configuration.mdx new file mode 100644 index 0000000000..0a0a55dc67 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/data-sources/validate-configuration.mdx @@ -0,0 +1,86 @@ +--- +page_title: Validate data source configurations +description: >- + Learn how to validate data source configurations with the Terraform plugin + framework. +--- + +# Validate data source configurations + +[Data sources](/terraform/plugin/framework/data-sources) support validating an entire practitioner configuration in either declarative or imperative logic. Feedback, such as required syntax or acceptable combinations of values, is returned via [diagnostics](/terraform/plugin/framework/diagnostics). + +This page describes implementation details for validating entire data source configurations, typically referencing multiple attributes. Further documentation is available for other configuration validation concepts: + +- [Single attribute validation](/terraform/plugin/framework/validation#attribute-validation) is a schema-based mechanism for implementing attribute-specific validation logic. +- [Type validation](/terraform/plugin/framework/validation#type-validation) is a schema-based mechanism for implementing reusable validation logic for any attribute using the type. + +-> Configuration validation in Terraform occurs without provider configuration ("offline"), so therefore the data source `Configure` method will not have been called. To implement validation with a configured API client, use logic within the `Read` method, which occurs during Terraform's planning phase when possible. + +## ConfigValidators Method + +The [`datasource.DataSourceWithConfigValidators` interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/datasource#DataSourceWithConfigValidators) follows a similar pattern to attribute validation and allows for a more declarative approach. This enables consistent validation logic across multiple data sources. Each validator intended for this interface must implement the [`datasource.ConfigValidator` interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/datasource#ConfigValidator). + +During execution of the [`terraform validate`](/terraform/cli/commands/validate), [`terraform plan`](/terraform/cli/commands/plan) and [`terraform apply`](/terraform/cli/commands/apply) commands, Terraform calls the provider [`ValidateDataResourceConfig`](/terraform/plugin/framework/internals/rpcs#validatedataresourceconfig-rpc) RPC, in which the framework calls the `ConfigValidators` method on data sources that implement the [`datasource.DataSourceWithConfigValidators` interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/datasource#DataSourceWithConfigValidators). + +The [`terraform-plugin-framework-validators` Go module](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators) has a collection of common use case data source configuration validators in the [`datasourcevalidator` package](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators/datasourcevalidator). These use [path expressions](/terraform/plugin/framework/path-expressions) for matching attributes. + +This example will raise an error if a practitioner attempts to configure both `attribute_one` and `attribute_two`: + +```go +// Other methods to implement the datasource.DataSource interface are omitted for brevity +type ThingDataSource struct {} + +func (d ThingDataSource) ConfigValidators(ctx context.Context) []datasource.ConfigValidator { + return []datasource.ConfigValidator{ + datasourcevalidator.Conflicting( + path.MatchRoot("attribute_one"), + path.MatchRoot("attribute_two"), + ), + } +} +``` + +## ValidateConfig Method + +The [`datasource.DataSourceWithValidateConfig` interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/datasource#DataSourceWithValidateConfig) is more imperative in design and is useful for validating unique functionality across multiple attributes that typically applies to a single data source. + +During execution of the [`terraform validate`](/terraform/cli/commands/validate), [`terraform plan`](/terraform/cli/commands/plan) and [`terraform apply`](/terraform/cli/commands/apply) commands, Terraform calls the provider [`ValidateDataResourceConfig`](/terraform/plugin/framework/internals/rpcs#validatedataresourceconfig-rpc) RPC, in which the framework calls the `ValidateConfig` method on providers that implement the [`datasource.DatasourceWithValidateConfig` interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/datasource#DataSourceWithValidateConfig). + +This example will raise a warning if a practitioner attempts to configure `attribute_one`, but not `attribute_two`: + +```go +// Other methods to implement the datasource.DataSource interface are omitted for brevity +type ThingDataSource struct {} + +type ThingDataSourceModel struct { + AttributeOne types.String `tfsdk:"attribute_one"` + AttributeTwo types.String `tfsdk:"attribute_two"` +} + +func (d ThingDataSource) ValidateConfig(ctx context.Context, req datasource.ValidateConfigRequest, resp *datasource.ValidateConfigResponse) { + var data ThingDataSourceModel + + resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + // If attribute_one is not configured, return without warning. + if data.AttributeOne.IsNull() || data.AttributeOne.IsUnknown() { + return + } + + // If attribute_two is not null, return without warning. + if !data.AttributeTwo.IsNull() { + return + } + + resp.Diagnostics.AddAttributeWarning( + path.Root("attribute_two"), + "Missing Attribute Configuration", + "Expected attribute_two to be configured with attribute_one. "+ + "The data source may return unexpected results.", + ) +} +``` diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/debugging.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/debugging.mdx new file mode 100644 index 0000000000..ddae1484b9 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/debugging.mdx @@ -0,0 +1,35 @@ +--- +page_title: Debugging framework providers +description: >- + Learn how to implement debugger support in framework Terraform providers. +--- + +# Debugging framework Providers + +This page contains implementation details for inspecting runtime information of a Terraform provider developed with Framework via a debugger tool by adjusting the [provider server](/terraform/plugin/framework/provider-servers) implementation. Review the top level [Debugging](/terraform/plugin/debugging) page for information pertaining to the overall Terraform provider debugging process and other inspection options, such as log-based debugging. + +## Code Implementation + +Update the `main` function for the project to conditionally enable the [`providerserver/ServeOpts.Debug` field](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/providerserver#ServeOpts.Debug). Conventionally, a `-debug` flag is used to control the `Debug` value. + +This example uses a `-debug` flag to enable debugging, otherwise starting the provider normally on protocol version 6: + +```go +func main() { + var debug bool + + flag.BoolVar(&debug, "debug", false, "set to true to run the provider with support for debuggers like delve") + flag.Parse() + + opts := providerserver.ServeOpts{ + Address: "registry.terraform.io/example-namespace/example", + Debug: debug, + } + + err := providerserver.Serve(context.Background(), provider.New(), opts) + + if err != nil { + log.Fatal(err.Error()) + } +} +``` diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/deprecations.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/deprecations.mdx new file mode 100644 index 0000000000..c86ce0ae0c --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/deprecations.mdx @@ -0,0 +1,1286 @@ +--- +page_title: Deprecations, removals, and renames +description: + Use the following recommendations to handle deprecations, removals, and + renames in framework providers. +--- + +# Deprecations, removals, and renames + +Terraform is trusted for managing many facets of infrastructure across many organizations. Part of that trust is due to consistent versioning guidelines and setting expectations for various levels of upgrades. Ensuring backwards compatibility for all patch and minor releases, potentially in concert with any upcoming major changes, is recommended and supported by the Terraform development framework. This allows operators to iteratively update their Terraform configurations rather than require massive refactoring. + +This guide is designed to walk through various scenarios where existing Terraform functionality requires future removal, while maintaining backwards compatibility. Further information about the versioning terminology (e.g. `MAJOR`.`MINOR`.`PATCH`) in this guide can be found in [the versioning guidelines documentation](/terraform/plugin/best-practices/versioning). + +~> **NOTE:** Removals should only ever occur in `MAJOR` version upgrades. + +~> **NOTE:** This documentation references usage of the `DeprecationMessage` field, please see the [schema documentation](/terraform/plugin/framework/handling-data/schemas#deprecationmessage-1) for more detailed guidance on how to structure warning messages and when those warnings will be raised to practitioners. + +## Table of Contents + +- [Provider Attribute Removal](#provider-attribute-removal) +- [Provider Attribute Rename](#provider-attribute-rename) + - [Renaming a Required Attribute](#renaming-a-required-attribute) + - [Renaming an Optional Attribute](#renaming-an-optional-attribute) + - [Renaming a Computed Attribute](#renaming-a-computed-attribute) +- [Provider Data Source or Resource Removal](#provider-data-source-or-resource-removal) +- [Provider Data Source or Resource Rename](#provider-data-source-or-resource-rename) + +## Provider Attribute Removal + +The recommended process for removing an attribute from a data source or resource in a provider is as follows: + +1. Add a [`DeprecationMessage`](/terraform/plugin/framework/handling-data/schemas#deprecationmessage-1) in the attribute schema definition. Set this field to a practitioner actionable message such as `"Remove this attribute's configuration as it's no longer in use and the attribute will be removed in the next major version of the provider."` +2. Ensure the changelog has an entry noting the deprecation. +3. Release a `MINOR` version with the deprecation. +4. In the next `MAJOR` version, remove all code associated with the attribute including the schema definition. +5. Ensure the changelog has an entry noting the removal. +6. Release the `MAJOR` version. + +## Provider Attribute Rename + +When renaming an attribute from one name to another, it is important to keep backwards compatibility with both existing Terraform configurations and the [Terraform state](/terraform/language/state) while operators migrate. To accomplish this, there will be some duplicated logic to support both attributes until the next `MAJOR` release. Once both attributes are appropriately handled, the process for deprecating and removing the old attribute is the same as noted in the [Provider Attribute Removal section](#provider-attribute-removal). + +The procedure for renaming an attribute depends on what type of attribute it is: + +- [Renaming a Required Attribute](#renaming-a-required-attribute) +- [Renaming an Optional Attribute](#renaming-an-optional-attribute) +- [Renaming a Computed Attribute](#renaming-a-computed-attribute) + +### Renaming a Required Attribute + +~> **NOTE:** If the schema definition does not contain `Optional` or `Required`, see the [Renaming a Computed Attribute section](#renaming-a-computed-attribute) instead. If the schema definition contains `Optional` instead of `Required`, see the [Renaming an Optional Attribute section](#renaming-an-optional-attribute). + +-> [Required attributes](/terraform/plugin/framework/handling-data/schemas#required) are also referred to as required "arguments" throughout the Terraform documentation. + +In general, the procedure here does two things: + +- Prevents the operator from needing to define two attributes with the same value. +- Allows the operator to migrate the configuration to the new attribute at the same time requiring that any other references only work with the new attribute. This is to prevent a situation with Terraform showing a difference when the existing attribute is configured, but the new attribute is saved into the Terraform state. For example, in `terraform plan` output format: + +``` +existing_attribute: "" => "value" +new_attribute: "value" => "" +``` + +The recommended process is as follows: + +1. Replace `Required: true` with `Optional: true` in the existing attribute schema definition. +1. Replace `Required` with `Optional` in the existing attribute documentation. +1. Duplicate the schema definition of the existing attribute, renaming one of them with the new attribute name. +1. Duplicate the documentation of the existing attribute, renaming one of them with the new attribute name. +1. Add a [`DeprecationMessage`](/terraform/plugin/framework/handling-data/schemas#deprecationmessage-1) to the schema definition of the existing (now the "old") attribute, noting to use the new attribute in the message. +1. Add `**Deprecated**` to the documentation of the existing (now the "old") attribute, noting to use the new attribute. +1. Add a note to the documentation that either the existing (now the "old") attribute or new attribute must be configured. +1. Add the type-specific [validator](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators) `{type}validator.ExactlyOneOf` to the schema definition of the new attribute, with a path expression matching the old attribute. This will ensure at least one of the attributes is configured, but present an error to the operator if both are configured at the same time. For example, an attribute of type string would use the [`stringvalidator.ExactlyOneOf`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator#ExactlyOneOf) validator. +1. Add conditional logic in the `Create` and `Update` functions of the data source or resource to handle both attributes. Generally, this involves using [`{type}.IsNull()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes). +1. Follow the rest of the procedures in the [Provider Attribute Removal section](#provider-attribute-removal). When the old attribute is removed, update the schema definition and documentation of the new attribute back to `Required`, and remove the `{type}validator.ExactlyOneOf` validator. + +#### Example Renaming of a Required Attribute + +Given this sample resource: + +```go +package provider + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +var _ resource.Resource = (*exampleWidgetResource)(nil) + +type exampleWidgetResource struct{} + +func NewWidgetResource() resource.Resource { + return &exampleWidgetResource{} +} + +func (e *exampleWidgetResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_widget" +} + +func (e *exampleWidgetResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + // ... other configuration ... + + Attributes: map[string]schema.Attribute{ + // ... other attributes ... + + "existing_attribute": schema.StringAttribute{ + Required: true, + }, + }, + } +} + +type exampleWidgetResourceData struct { + // ... other attributes ... + + ExistingAttribute types.String `tfsdk:"existing_attribute"` +} + +func (e *exampleWidgetResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var data exampleWidgetResourceData + + resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) + if resp.Diagnostics.HasError() { + return + } + + // add attribute to provider create API call + + // ... other logic ... + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (e *exampleWidgetResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + // ... other logic ... +} + +func (e *exampleWidgetResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var data exampleWidgetResourceData + + resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) + if resp.Diagnostics.HasError() { + return + } + + // add attribute to provider update API call + + // ... other logic ... + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (e *exampleWidgetResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + // ... other logic ... +} +``` + +In order to support renaming `existing_attribute` to `new_attribute`, this sample can be written as the following to support both attributes simultaneously until the `existing_attribute` is removed: + +```go +package provider + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +var _ resource.Resource = (*exampleWidgetResource)(nil) + +type exampleWidgetResource struct{} + +func NewWidgetResource() resource.Resource { + return &exampleWidgetResource{} +} + +func (e *exampleWidgetResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_widget" +} + +func (e *exampleWidgetResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + // ... other configuration ... + + Attributes: map[string]schema.Attribute{ + // ... other attributes ... + + "existing_attribute": schema.StringAttribute{ + Optional: true, + DeprecationMessage: "use new_attribute instead", + }, + "new_attribute": schema.StringAttribute{ + Optional: true, + Validators: []validator.String{ + stringvalidator.ExactlyOneOf(path.Expressions{ + path.MatchRoot("existing_attribute"), + }...), + }, + }, + }, + } +} + +type exampleWidgetResourceData struct { + // ... other attributes ... + + ExistingAttribute types.String `tfsdk:"existing_attribute"` + NewAttribute types.String `tfsdk:"new_attribute"` +} + +func (e *exampleWidgetResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var data exampleWidgetResourceData + + resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) + if resp.Diagnostics.HasError() { + return + } + + if !data.NewAttribute.IsNull() { + // add NewAttribute to provider create API call + } else { + // add ExistingAttribute to provider create API call + } + + // ... other logic ... + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (e *exampleWidgetResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + // ... other logic ... +} + +func (e *exampleWidgetResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var data exampleWidgetResourceData + + resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) + if resp.Diagnostics.HasError() { + return + } + + if !data.NewAttribute.IsNull() { + // add NewAttribute to provider create API call + } else { + // add ExistingAttribute to provider create API call + } + + // ... other logic ... + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (e *exampleWidgetResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + // ... other logic ... +} +``` + +When the `existing_attribute` is ready for removal, then this can be written as: + +```go +package provider + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +var _ resource.Resource = (*exampleWidgetResource)(nil) + +type exampleWidgetResource struct{} + +func NewWidgetResource() resource.Resource { + return &exampleWidgetResource{} +} + +func (e *exampleWidgetResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_widget" +} + +func (e *exampleWidgetResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + // ... other configuration ... + + Attributes: map[string]schema.Attribute{ + // ... other attributes ... + + "new_attribute": schema.StringAttribute{ + Required: true, + }, + }, + } +} + +type exampleWidgetResourceData struct { + // ... other attributes ... + + NewAttribute types.String `tfsdk:"new_attribute"` +} + +func (e *exampleWidgetResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var data exampleWidgetResourceData + + resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) + if resp.Diagnostics.HasError() { + return + } + + // add NewAttribute to provider create API call + + // ... other logic ... + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (e *exampleWidgetResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + // ... other logic ... +} + +func (e *exampleWidgetResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var data exampleWidgetResourceData + + resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) + if resp.Diagnostics.HasError() { + return + } + + // add NewAttribute to provider create API call + + // ... other logic ... + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (e *exampleWidgetResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + // ... other logic ... +} +``` + +### Renaming an Optional Attribute + +~> **NOTE:** If the schema definition does not contain `Optional` or `Required`, see the [Renaming a Computed Attribute section](#renaming-a-computed-attribute) instead. If the schema definition contains `Required` instead of `Optional`, see the [Renaming a Required Attribute section](#renaming-a-required-attribute). + +-> [Optional attributes](/terraform/plugin/framework/handling-data/schemas#optional) are also referred to as optional "arguments" throughout the Terraform documentation. + +In general, the procedure here allows the operator to migrate the configuration to the new attribute at the same time requiring that any other references only work with the new attribute. This is to prevent a situation with Terraform showing a difference when the existing attribute is configured, but the new attribute is saved into the Terraform state. For example, in `terraform plan` output format: + +```text +existing_attribute: "" => "value" +new_attribute: "value" => "" +``` + +The recommended process is as follows: + +1. Duplicate the schema definition of the existing attribute, renaming one of them with the new attribute name. +1. Duplicate the documentation of the existing attribute, renaming one of them with the new attribute name. +1. Add a [`DeprecationMessage`](/terraform/plugin/framework/handling-data/schemas#deprecationmessage-1) to the schema definition of the existing (now the "old") attribute, noting to use the new attribute in the message. +1. Add `**Deprecated**` to the documentation of the existing (now the "old") attribute, noting to use the new attribute. +1. Add the type-specific [validator](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators) `{type}validator.ExactlyOneOf` to the schema definition of the new attribute, with a path expression matching the old attribute. This will ensure at least one of the attributes is configured, but present an error to the operator if both are configured at the same time. For example, an attribute of type string would use the [`stringvalidator.ExactlyOneOf`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator#ExactlyOneOf) validator. +1. Add conditional logic in the `Create` and `Update` functions of the data source or resource to handle both attributes. Generally, this involves using [`{type}.IsNull()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes). +1. Follow the rest of the procedures in the [Provider Attribute Removal section](#provider-attribute-removal). When the old attribute is removed, remove the `{type}validator.ExactlyOneOf` validator. + +#### Example Renaming of an Optional Attribute + +Given this sample resource: + +```go +package provider + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +var _ resource.Resource = (*exampleWidgetResource)(nil) + +type exampleWidgetResource struct{} + +func NewWidgetResource() resource.Resource { + return &exampleWidgetResource{} +} + +func (e *exampleWidgetResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_widget" +} + +func (e *exampleWidgetResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + // ... other configuration ... + + Attributes: map[string]schema.Attribute{ + // ... other attributes ... + + "existing_attribute": schema.StringAttribute{ + Optional: true, + }, + }, + } +} + +type exampleWidgetResourceData struct { + // ... other attributes ... + + ExistingAttribute types.String `tfsdk:"existing_attribute"` +} + +func (e *exampleWidgetResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var data exampleWidgetResourceData + + resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) + if resp.Diagnostics.HasError() { + return + } + + // add attribute to provider create API call + + // ... other logic ... + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (e *exampleWidgetResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + // ... other logic ... +} + +func (e *exampleWidgetResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var data exampleWidgetResourceData + + resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) + if resp.Diagnostics.HasError() { + return + } + + // add attribute to provider update API call + + // ... other logic ... + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (e *exampleWidgetResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + // ... other logic ... +} +``` + +In order to support renaming `existing_attribute` to `new_attribute`, this sample can be written as the following to support both attributes simultaneously until the `existing_attribute` is removed: + +```go +package provider + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +var _ resource.Resource = (*exampleWidgetResource)(nil) + +type exampleWidgetResource struct{} + +func NewWidgetResource() resource.Resource { + return &exampleWidgetResource{} +} + +func (e *exampleWidgetResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_widget" +} + +func (e *exampleWidgetResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + // ... other configuration ... + + Attributes: map[string]schema.Attribute{ + // ... other attributes ... + + "existing_attribute": schema.StringAttribute{ + Optional: true, + DeprecationMessage: "use new_attribute instead", + }, + + "new_attribute": schema.StringAttribute{ + Optional: true, + Validators: []validator.String{ + stringvalidator.ExactlyOneOf(path.Expressions{ + path.MatchRoot("existing_attribute"), + }...), + }, + }, + }, + } +} + +type exampleWidgetResourceData struct { + // ... other attributes ... + + ExistingAttribute types.String `tfsdk:"existing_attribute"` + NewAttribute types.String `tfsdk:"new_attribute"` +} + +func (e *exampleWidgetResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var data exampleWidgetResourceData + + resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) + if resp.Diagnostics.HasError() { + return + } + + if !data.NewAttribute.IsNull() { + // add NewAttribute to provider create API call + } else { + // add ExistingAttribute to provider create API call + } + + // ... other logic ... + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (e *exampleWidgetResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + // ... other logic ... +} + +func (e *exampleWidgetResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var data exampleWidgetResourceData + + resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) + if resp.Diagnostics.HasError() { + return + } + + if !data.NewAttribute.IsNull() { + // add NewAttribute to provider create API call + } else { + // add ExistingAttribute to provider create API call + } + + // ... other logic ... + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (e *exampleWidgetResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + // ... other logic ... +} +``` + +When the `existing_attribute` is ready for removal, then this can be written as: + +```go +package provider + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +var _ resource.Resource = (*exampleWidgetResource)(nil) + +type exampleWidgetResource struct{} + +func NewWidgetResource() resource.Resource { + return &exampleWidgetResource{} +} + +func (e *exampleWidgetResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_widget" +} + +func (e *exampleWidgetResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + // ... other configuration ... + + Attributes: map[string]schema.Attribute{ + // ... other attributes ... + + "new_attribute": schema.StringAttribute{ + Optional: true, + }, + }, + } +} + +type exampleWidgetResourceData struct { + // ... other attributes ... + + NewAttribute types.String `tfsdk:"new_attribute"` +} + +func (e *exampleWidgetResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var data exampleWidgetResourceData + + resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) + if resp.Diagnostics.HasError() { + return + } + + // add NewAttribute to provider create API call + + // ... other logic ... + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (e *exampleWidgetResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + // ... other logic ... +} + +func (e *exampleWidgetResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var data exampleWidgetResourceData + + resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) + if resp.Diagnostics.HasError() { + return + } + + // add NewAttribute to provider create API call + + // ... other logic ... + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (e *exampleWidgetResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + // ... other logic ... +} +``` + +### Renaming a Computed Attribute + +~> **NOTE:** If the schema definition contains `Optional` see the [Renaming an Optional Attribute section](#renaming-an-optional-attribute) instead. If the schema definition contains `Required` see the [Renaming a Required Attribute section](#renaming-a-required-attribute) instead. + +The recommended process is as follows: + +1. Duplicate the schema definition of the existing attribute, renaming one of them with the new attribute name. +1. Duplicate the documentation of the existing attribute, renaming one of them with the new attribute name. +1. Add a [`DeprecationMessage`](/terraform/plugin/framework/handling-data/schemas#deprecationmessage-1) to the schema definition of the existing (now the "old") attribute, noting to use the new attribute in the message. +1. Add `**Deprecated**` to the documentation of the existing (now the "old") attribute, noting to use the new attribute. +1. Set both attributes in the Terraform state in the `Create`, `Update`, and `Read` functions of the resource (`Read` only for data source). +1. Follow the rest of the procedures in the [Provider Attribute Removal section](#provider-attribute-removal). + +#### Example Renaming of a Computed Attribute + +Given this sample resource: + +```go +package provider + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +var _ resource.Resource = (*exampleWidgetResource)(nil) + +type exampleWidgetResource struct{} + +func NewWidgetResource() resource.Resource { + return &exampleWidgetResource{} +} + +func (e *exampleWidgetResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_widget" +} + +func (e *exampleWidgetResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + // ... other configuration ... + + Attributes: map[string]schema.Attribute{ + // ... other attributes ... + + "existing_attribute": schema.StringAttribute{ + Computed: true, + }, + }, + } +} + +type exampleWidgetResourceData struct { + // ... other attributes ... + + ExistingAttribute types.String `tfsdk:"existing_attribute"` +} + +func (e *exampleWidgetResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var data exampleWidgetResourceData + + resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) + if resp.Diagnostics.HasError() { + return + } + + // ... other logic ... + data.ExistingAttribute = // set to computed value + + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (e *exampleWidgetResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var data exampleWidgetResourceData + + resp.Diagnostics.Append(req.State.Get(ctx, &data)...) + if resp.Diagnostics.HasError() { + return + } + + // ... other logic ... + data.ExistingAttribute = // set to computed value + + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (e *exampleWidgetResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var data exampleWidgetResourceData + + resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) + if resp.Diagnostics.HasError() { + return + } + + // ... other logic ... + data.ExistingAttribute = // set to computed value + + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (e *exampleWidgetResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + // ... other logic ... +} +``` + +In order to support renaming `existing_attribute` to `new_attribute`, this sample can be written as the following to support both attributes simultaneously until the `existing_attribute` is removed: + +```go +package provider + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +var _ resource.Resource = (*exampleWidgetResource)(nil) + +type exampleWidgetResource struct{} + +func NewWidgetResource() resource.Resource { + return &exampleWidgetResource{} +} + +func (e *exampleWidgetResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_widget" +} + +func (e *exampleWidgetResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + // ... other configuration ... + + Attributes: map[string]schema.Attribute{ + // ... other attributes ... + + "existing_attribute": schema.StringAttribute{ + Computed: true, + DeprecationMessage: "use new_attribute instead", + }, + + "new_attribute": schema.StringAttribute{ + Computed: true, + }, + }, + } +} + +type exampleWidgetResourceData struct { + // ... other attributes ... + + ExistingAttribute types.String `tfsdk:"existing_attribute"` + NewAttribute types.String `tfsdk:"new_attribute"` +} + +func (e *exampleWidgetResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var data exampleWidgetResourceData + + resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) + if resp.Diagnostics.HasError() { + return + } + + // ... other logic ... + data.ExistingAttribute = // set to computed value + data.NewAttribute = // set to computed value + + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (e *exampleWidgetResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var data exampleWidgetResourceData + + resp.Diagnostics.Append(req.State.Get(ctx, &data)...) + if resp.Diagnostics.HasError() { + return + } + + // ... other logic ... + data.ExistingAttribute = // set to computed value + data.NewAttribute = // set to computed value + + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (e *exampleWidgetResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var data exampleWidgetResourceData + + resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) + if resp.Diagnostics.HasError() { + return + } + + // ... other logic ... + data.ExistingAttribute = // set to computed value + data.NewAttribute = // set to computed value + + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (e *exampleWidgetResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + // ... other logic ... +} +``` + +When the `existing_attribute` is ready for removal, then this can be written as: + +```go +package provider + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +var _ resource.Resource = (*exampleWidgetResource)(nil) + +type exampleWidgetResource struct{} + +func NewWidgetResource() resource.Resource { + return &exampleWidgetResource{} +} + +func (e *exampleWidgetResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_widget" +} + +func (e *exampleWidgetResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + // ... other configuration ... + + Attributes: map[string]schema.Attribute{ + // ... other attributes ... + + "new_attribute": schema.StringAttribute{ + Computed: true, + }, + }, + } +} + +type exampleWidgetResourceData struct { + // ... other attributes ... + + NewAttribute types.String `tfsdk:"new_attribute"` +} + +func (e *exampleWidgetResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var data exampleWidgetResourceData + + resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) + if resp.Diagnostics.HasError() { + return + } + + // ... other logic ... + data.NewAttribute = // set to computed value + + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (e *exampleWidgetResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var data exampleWidgetResourceData + + resp.Diagnostics.Append(req.State.Get(ctx, &data)...) + if resp.Diagnostics.HasError() { + return + } + + // ... other logic ... + data.NewAttribute = // set to computed value + + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (e *exampleWidgetResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var data exampleWidgetResourceData + + resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) + if resp.Diagnostics.HasError() { + return + } + + // ... other logic ... + data.NewAttribute = // set to computed value + + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (e *exampleWidgetResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + // ... other logic ... +} +``` + +## Provider Data Source or Resource Removal + +The recommended process for removing a data source or resource from a provider is as follows: + +1. Add a [`DeprecationMessage`](/terraform/plugin/framework/handling-data/schemas#deprecationmessage) in the data source or resource schema definition. After an operator upgrades to this version, they will be shown a warning with the message provided when using the deprecated data source or resource, but the Terraform run will still complete. +1. Ensure the changelog has an entry noting the deprecation. +1. Release a `MINOR` version with the deprecation. +1. In the next `MAJOR` version, remove all code associated with the deprecated data source or resource except for the schema and replace the `Create`, `Read`, `Update`, and `Delete` functions to always return an error diagnostic. Remove the documentation sidebar link and update the resource or data source documentation page to include information about the removal and any potential migration information. After an operator upgrades to this version, they will be shown an error about the missing data source or resource. +1. Ensure the changelog has an entry noting the removal. +1. Release the `MAJOR` version. +1. In the next `MAJOR` version, remove all code associated with the removed data source or resource. Remove the resource or data source documentation page. +1. Release the `MAJOR` version. + +### Example Resource Removal + +Given this sample provider and resource: + +```go +// ... provider implementation ... + +func (p *exampleProvider) Resources(ctx context.Context) []func() resource.Resource { + return []func() resource.Resource{ + //... other resources ... + NewWidgetResource, + } +} +``` + +```go +func NewWidgetResource() resource.Resource { + return &exampleWidgetResource{} +} + +func (e *exampleWidgetResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + // ... other configuration ... + + Attributes: map[string]schema.Attribute{ + // ... other attributes ... + }, + } +} + +// ... resource implementation ... +``` + +In order to deprecate `example_widget`, this sample can be written as: + +```go +// ... provider implementation ... + +func (p *exampleProvider) Resources(ctx context.Context) []func() resource.Resource { + return []func() resource.Resource{ + //... other resources ... + NewWidgetResource, + } +} +``` + +```go +func NewWidgetResource() resource.Resource { + return &exampleWidgetResource{} +} + +func (e *exampleWidgetResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + // ... other configuration ... + + Attributes: map[string]schema.Attribute{ + // ... other attributes ... + }, + DeprecationMessage: "use example_thing resource instead", + } +} + +// ... resource implementation ... +``` + +To soft remove `example_widget` with a friendly error message, this sample can be written as: + +```go +// ... provider implementation ... + +func (p *exampleProvider) Resources(ctx context.Context) []func() resource.Resource { + return []func() resource.Resource{ + //... other resources ... + NewWidgetResource, + } +} +``` + +```go +func NewWidgetResource() resource.Resource { + return &exampleWidgetResource{} +} + +func (e *exampleWidgetResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + // ... other configuration ... + + Attributes: map[string]schema.Attribute{ + // ... other attributes ... + }, + DeprecationMessage: "use example_thing resource instead", + } +} + +func (e *exampleWidgetResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + resp.Diagnostics.Append( + diag.NewErrorDiagnostic("example_widget resource deprecated", "use example_thing resource instead"), + ) +} + +func (e *exampleWidgetResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + resp.Diagnostics.Append( + diag.NewErrorDiagnostic("example_widget resource deprecated", "use example_thing resource instead"), + ) +} + +func (e *exampleWidgetResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + resp.Diagnostics.Append( + diag.NewErrorDiagnostic("example_widget resource deprecated", "use example_thing resource instead"), + ) +} + +func (e *exampleWidgetResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + resp.Diagnostics.Append( + diag.NewErrorDiagnostic("example_widget resource deprecated", "use example_thing resource instead"), + ) +} + +``` + +To remove `example_widget`: +```go +// ... provider implementation ... + +func (p *exampleProvider) Resources(ctx context.Context) []func() resource.Resource { + return []func() resource.Resource{ + //... other resources ... + } +} +``` + +## Provider Data Source or Resource Rename + +When renaming a resource from one name to another, it is important to keep backwards compatibility with both existing Terraform configurations and the Terraform state while operators migrate. To accomplish this, there will be some duplicated logic to support both resources until the next `MAJOR` release. Once both resources are appropriately handled, the process for deprecating and removing the old resource is the same as noted in the [Provider Data Source or Resource Removal section](#provider-data-source-or-resource-removal). + +The recommended process is as follows: + +1. Duplicate the code of the existing resource, renaming (and potentially modifying) functions as necessary. +1. Duplicate the documentation of the existing resource, renaming (and potentially modifying) as necessary. +1. Add a [`DeprecationMessage`](/terraform/plugin/framework/handling-data/schemas#deprecationmessage) to the schema definition of the existing (now the "old") resource, noting to use the new resource in the message. +1. Add `~> This resource is deprecated and will be removed in the next major version` to the documentation of the existing (now the "old") resource, noting to use the new resource. +1. Add the new resource to the provider [`Resources`](/terraform/plugin/framework/providers#resources) function +1. Follow the rest of the procedures in the [Provider Data Source or Resource Removal section](#provider-data-source-or-resource-removal). + +### Example Resource Renaming + +Given this sample provider and resource: + +```go +// ... provider implementation ... + +func (p *exampleProvider) Resources(ctx context.Context) []func() resource.Resource { + return []func() resource.Resource{ + //... other resources ... + NewExistingWidgetResource, + } +} +``` + +```go +func NewExistingWidgetResource() resource.Resource { + return &exampleExistingWidgetResource{} +} + +func (e *exampleExistingWidgetResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + // ... other configuration ... + + Attributes: map[string]schema.Attribute{ + // ... other attributes ... + }, + } +} + +// ... resource implementation ... +``` + +In order to support renaming `example_existing_widget` to `example_new_widget`, this sample can be written as the following to support both resources simultaneously until the `example_existing_widget` resource is removed: + +```go +// ... provider implementation ... + +func (p *exampleProvider) Resources(ctx context.Context) []func() resource.Resource { + return []func() resource.Resource{ + //... other resources ... + NewExistingWidgetResource, + NewWidgetResource, + } +} +``` + +```go +func NewExistingWidgetResource() resource.Resource { + return &exampleExistingWidgetResource{} +} + +func (e *exampleExistingWidgetResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + // ... other configuration ... + + Attributes: map[string]schema.Attribute{ + // ... other attributes ... + }, + DeprecationMessage: "use example_new_widget resource instead", + } +} + +// ... resource implementation ... +``` + +```go +func NewWidgetResource() resource.Resource { + return &exampleWidgetResource{} +} + +func (e *exampleWidgetResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + // ... other configuration ... + + Attributes: map[string]schema.Attribute{ + // ... other attributes ... + }, + } +} + +// ... resource implementation ... +``` + +To soft remove `example_existing_widget` with a friendly error message: + + +```go +// ... provider implementation ... + +func (p *exampleProvider) Resources(ctx context.Context) []func() resource.Resource { + return []func() resource.Resource{ + //... other resources ... + NewExistingWidgetResource, + NewWidgetResource, + } +} +``` + +```go +func NewExistingWidgetResource() resource.Resource { + return &exampleExistingWidgetResource{} +} + +func (e *exampleExistingWidgetResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + // ... other configuration ... + + Attributes: map[string]schema.Attribute{ + // ... other attributes ... + }, + DeprecationMessage: "use example_new_widget resource instead", + } +} + +func (e *exampleExistingWidgetResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + resp.Diagnostics.Append( + diag.NewErrorDiagnostic("example_existing_widget resource deprecated", "use example_new_widget resource instead"), + ) +} + +func (e *exampleExistingWidgetResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + resp.Diagnostics.Append( + diag.NewErrorDiagnostic("example_existing_widget resource deprecated", "use example_new_widget resource instead"), + ) +} + +func (e *exampleExistingWidgetResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + resp.Diagnostics.Append( + diag.NewErrorDiagnostic("example_existing_widget resource deprecated", "use example_new_widget resource instead"), + ) +} + +func (e *exampleExistingWidgetResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + resp.Diagnostics.Append( + diag.NewErrorDiagnostic("example_existing_widget resource deprecated", "use example_new_widget resource instead"), + ) +} + +// ... resource implementation ... +``` + +```go +func NewWidgetResource() resource.Resource { + return &exampleWidgetResource{} +} + +func (e *exampleWidgetResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + // ... other configuration ... + + Attributes: map[string]schema.Attribute{ + // ... other attributes ... + }, + } +} + +// ... resource implementation ... +``` + +To remove `example_existing_widget`: + +```go +// ... provider implementation ... + +func (p *exampleProvider) Resources(ctx context.Context) []func() resource.Resource { + return []func() resource.Resource{ + //... other resources ... + NewWidgetResource, + } +} +``` + +```go +func NewWidgetResource() resource.Resource { + return &exampleWidgetResource{} +} + +func (e *exampleWidgetResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + // ... other configuration ... + + Attributes: map[string]schema.Attribute{ + // ... other attributes ... + }, + } +} + +// ... resource implementation ... +``` diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/diagnostics.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/diagnostics.mdx new file mode 100644 index 0000000000..7208d4845a --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/diagnostics.mdx @@ -0,0 +1,378 @@ +--- +page_title: Errors and warnings +description: >- + Learn how to return errors and warnings from the Terraform plugin framework. +--- + +# Returning errors and warnings + +Providers use `Diagnostics` to surface errors and warnings to practitioners, +such as contextual messages returned from Terraform CLI at the end of +command output: + +```console +$ terraform plan +# ... other plan output ... +╷ +│ Error: Summary +│ +│ on example.tf line #: +│ #: source configuration line +│ +│ Details +╵ +``` + +In the framework, you may encounter them in response structs or as returns from +functions or methods: + +```go +func (m myResource) Create(ctx context.Context, + req resource.CreateRequest, resp *resource.CreateResponse) +``` + +This is the most common form for Diagnostics: a slice that has one or more +errors appended to it. This approach allows your provider to inform +practitioners about all relevant errors and warnings at the same time, allowing +practitioners to fix their configuration or environment more quickly. You +should only append to Diagnostics slices and never replace or remove +information from them. + +The next section will detail the concepts and typical behaviors of +diagnostics, while the final section will outline the typical methods for +working with diagnostics, using functionality from the available +[`diag` package](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/diag). + +## Diagnostic Concepts + +### Severity + +`Severity` specifies whether the diagnostic is an error or a warning. Neither Terraform, nor the framework, supports other severity levels. Use [logging](/terraform/plugin/log/writing) for debugging or informational purposes. + +- An **error** will be displayed to the practitioner and halt Terraform's + execution, not continuing to apply changes to later resources in the graph. + We recommend using errors to inform practitioners about a situation the + provider could not recover from. +- A **warning** will be displayed to the practitioner, but will not halt + further execution, and is considered informative only. We recommend using + warnings to inform practitioners about suboptimal situations that the + practitioner should resolve to ensure stable functioning (e.g., deprecations) + or to inform practitioners about possible unexpected behaviors. + +### Summary + +`Summary` is a short, practitioner-oriented description of the problem. Good +summaries are general—they don't contain specific details about +values—and concise. For example, "Error creating resource", "Invalid +value for foo", or "Field foo is deprecated". + +### Detail + +`Detail` is a longer, more specific practitioner-oriented description of +precisely what went wrong. Good details are specific—they tell the +practitioner exactly what they need to fix and how. For example, "The API +is currently unavailable, please try the request again.", "foo can only contain +letters, numbers, and digits.", or "foo has been deprecated in favor of bar. +Please update your configuration to use bar instead. foo will be removed in a +future release.". + +### Attribute + +`Attribute` identifies the specific part of a configuration that caused the +error or warning. Only diagnostics that pertain to a whole attribute or a +specific attribute value will include this information. + +### Argument + +`Argument` identifies the specific function argument position that caused the +error or warning. Only diagnostics that pertain to a function argument will +include this information. + +## How Errors Affect State + +**Returning an error diagnostic does not stop the state from being updated**. +Terraform will still persist the returned state even when an error diagnostic +is returned with it. This is to allow Terraform to persist the values that have +already been modified when a resource modification requires multiple API +requests or an API request fails after an earlier one succeeded. + +When returning error diagnostics, we recommend resetting the state in the +response to the prior state available in the configuration. + +## diag Package + +The framework provides the `diag` package for interacting with diagnostics. +While the [Go documentation](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/diag) +contains the complete functionality, this section will highlight the most +common methods. + +### Working With Existing Diagnostics + +#### Append + +When receiving `diag.Diagnostics` from a function or method, such as +`Config.Get()` or `State.Set()`, these should typically be appended to the +response diagnostics for the method. This can be accomplished with the +[`Append(in ...diag.Diagnostics)` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/diag#Diagnostics.Append). + +For example: + +```go +func (m myResource) Create(ctx context.Context, + req resource.CreateRequest, resp *resource.CreateResponse) { + // ... prior logic ... + diags := req.Config.Get(ctx, &resourceData) + resp.Diagnostics.Append(diags...) + // ... further logic ... +} +``` + +This method automatically ignores `nil` or empty slice diagnostics and +deduplicates where possible. + +#### HasError + +The most typical form of diagnostics checking is ensuring that execution should +not stop due to encountering an error, potentially causing further confusing +errors or crashes. The [`HasError()` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/diag#Diagnostics.HasError) +will check each of the diagnostics for error severity and return true if found. + +For example: + +```go +func (m myResource) Create(ctx context.Context, + req resource.CreateRequest, resp *resource.CreateResponse) { + // ... prior logic ... + diags := req.Config.Get(ctx, &resourceData) + resp.Diagnostics.Append(diags...) + + if resp.Diagnostics.HasError() { + return + } + // ... further logic ... +} +``` + +In this example, you will note that we opted to check `resp.Diagnostics` +instead of `diags`. Technically checking either is correct, however, checking +the response diagnostics can help ensure that any response will include the +expected diagnostics. + +### Creating Diagnostics + +When working with logic outside the framework, such as interacting with the +vendor or `net/http` library to make the actual calls to manage infrastructure +or creating custom plan modifiers and validators, it will be necessary to +create diagnostics. The `diag` package provides helper methods and allows +custom abstractions as described below. + +To craft the summary of a diagnostic, it is recommended to use a concise title +or single sentence that immediately can allow the practitioner to determine +the error cause and when it occurred. + +To craft the details portion of diagnostics, it is recommended to provide +practitioners (and potentially you as the maintainer) as much contextual, +troubleshooting, and next action information as possible. These details can +use newlines for easier readability where necessary. + +For example, with the top line as a summary and below as details: + +```text +API Error Reading Resource +``` + +```text +An unexpected error was encountered while reading the resource. + +Please check that the credentials being used are active and have sufficient +permissions to perform the Example API call against the resource. + +Region: example +ID: example123 +API Response: 403 Access Denied +``` + +#### AddError and AddWarning + +When creating diagnostics that affect an entire data source, provider, or +resource, and where a `diag.Diagnostics` is already available such as within +a response type, the [`AddError(summary string, detail string)` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/diag#Diagnostics.AddError) +and [`AddWarning(summary string, detail string)` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/diag#Diagnostics.AddWarning) +can append a new error or warning diagnostic. + +For example: + +```go +func (m myResource) Create(ctx context.Context, + req resource.CreateRequest, resp *resource.CreateResponse) { + // ... prior logic ... + resp, err := http.Post("https://example.com") + + if err != nil { + resp.Diagnostics.AddError( + "API Error Creating Resource", + fmt.Sprintf("... details ... %s", err) + ) + return + } + // ... further logic ... +} +``` + +#### AddAttributeError and AddAttributeWarning + +When creating diagnostics that affect only a single attribute, which is +typical of attribute-level plan modifiers and validators, the +[`AddAttributeError(path path.Path, summary string, detail string)` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/diag#Diagnostics.AddAttributeError) +and [`AddAttributeWarning(path path.Path, summary string, detail string)` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/diag#Diagnostics.AddAttributeWarning) +can append a new error or warning diagnostic pointing specifically at the +attribute path. This provides additional context to practitioners, such as +showing the specific line(s) and value(s) of configuration where possible. + +For example: + +```go +func (s exampleType) Validate(ctx context.Context, in tftypes.Value, path path.Path) diag.Diagnostics { + var diags diag.Diagnostics + + if !in.Type().Is(tftypes.Set{}) { + err := fmt.Errorf() + diags.AddAttributeError( + path, + "Example Type Validation Error", + "An unexpected error was encountered trying to validate an attribute value. "+ + "This is always an error in the provider. "+ + "Please report the following to the provider developer:\n\n"+ + fmt.Sprintf("Expected Set value, received %T with value: %v", in, in), + ) + return diags + } + // ... further logic ... +``` + +### Consistent Diagnostic Creation + +Create a helper function in your provider code using the diagnostic creation functions available in the [`diag` package](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/diag) to generate consistent diagnostics for types of errors/warnings. It is also possible to use [custom diagnostics types](#custom-diagnostics-types) to accomplish this same goal. + +The [`diag` package](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/diag) provides these functions to create various diagnostics: + +| Function | Description | +|---|---| +| [`diag.NewArgumentErrorDiagnostic()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/diag#NewArgumentErrorDiagnostic) | Create a new error diagnostic with a function argument position. | +| [`diag.NewArgumentWarningDiagnostic()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/diag#NewArgumentWarningDiagnostic) | Create a new warning diagnostic with a function argument position. | +| [`diag.NewAttributeErrorDiagnostic()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/diag#NewAttributeErrorDiagnostic) | Create a new error diagnostic with a [path](/terraform/plugin/framework/handling-data/paths). | +| [`diag.NewAttributeWarningDiagnostic()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/diag#NewAttributeWarningDiagnostic) | Create a new warning diagnostic with a [path](/terraform/plugin/framework/handling-data/paths). | +| [`diag.NewErrorDiagnostic()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/diag#NewErrorDiagnostic) | Create a new error diagnostic without a [path](/terraform/plugin/framework/handling-data/paths). | +| [`diag.NewWarningDiagnostic()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/diag#NewWarningDiagnostic) | Create a new warning diagnostic without a [path](/terraform/plugin/framework/handling-data/paths). | + +In this example, the provider code is setup to always convert `error` returns from the API SDK to a consistent error diagnostic. + +```go +func APIErrorDiagnostic(err error) diag.Diagnostic { + return diag.NewErrorDiagnostic( + "Unexpected API Error", + "While calling the API, an unexpected error was returned in the response. "+ + "Please contact support if you are unsure how to resolve the error.\n\n"+ + "Error: "+err.Error(), + ) +} +``` + +This enables calling code in the provider, such as: + +```go +func (r ThingResource) Read(ctx context.Context, req resource.ReadRequest, resp resource.ReadResponse) { + // ... other logic ... + + apiResp, err := examplesdk.Read(/* ... */) // example API SDK call that may return an error + + if err != nil { + resp.Diagnostics.Append(APIErrorDiagnostic(err)) + + return + } + + // ... further logic ... +} +``` + +## Custom Diagnostics Types + +Advanced provider developers may want to store additional data in diagnostics for other logic or create custom diagnostics that include specialized logic. + +The [`diag.Diagnostic` interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/diag#Diagnostic) that can be implemented with these methods: + +```go +type Diagnostic interface { + Severity() Severity + Summary() string + Detail() string + Equal(Diagnostic) bool +} +``` + +To include attribute path information, the [`diag.DiagnosticWithPath` interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/diag#DiagnosticWithPath) can be implemented with the additional `Path()` method: + +```go +type DiagnosticWithPath interface { + Diagnostic + Path() path.Path +} +``` + +To include function argument information, the [`diag.DiagnosticWithFunctionArgument` interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/diag#DiagnosticWithFunctionArgument) can be implemented with the additional `FunctionArgument()` method: + +```go +type DiagnosticWithFunctionArgument interface { + Diagnostic + FunctionArgument() int +} +``` + +In this example, a custom diagnostic type stores an underlying `error` that caused the diagnostic: + +```go +// UnderlyingErrorDiagnostic is an error diagnostic +// which also stores the underlying error. +type UnderlyingErrorDiagnostic struct { + Detail string + Summary string + UnderlyingError error +} + +func (d UnderlyingErrorDiagnostic) Detail() string { + return d.Detail +} + +func (d UnderlyingErrorDiagnostic) Equal(o SpecialDiagnostic) bool { + if d.Detail() != o.Detail() { + return false + } + + if d.Summary() != o.Summary() { + return false + } + + if d.UnderlyingError == nil { + return o.UnderlyingError == nil + } + + if o.UnderlyingError == nil { + return false + } + + if d.UnderlyingError.Error() != o.UnderlyingError.Error() { + return false + } + + return true +} + +func (d UnderlyingErrorDiagnostic) Severity() diag.Severity { + return diag.SeverityError +} + +func (d UnderlyingErrorDiagnostic) Summary() string { + return d.Summary +} +``` diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/ephemeral-resources/close.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/ephemeral-resources/close.mdx new file mode 100644 index 0000000000..dade8f2bd4 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/ephemeral-resources/close.mdx @@ -0,0 +1,94 @@ +--- +page_title: Closing ephemeral resources +description: >- + Learn how to close ephemeral resource in the Terraform plugin framework. +--- + +# Closing Ephemeral Resources + +Close is an optional part of the Terraform lifecycle for an ephemeral resource, which is different from the [managed resource lifecycle](https://github.com/hashicorp/terraform/blob/main/docs/resource-instance-change-lifecycle.md). During any Terraform operation (like [`terraform plan`](/terraform/cli/commands/plan) or [`terraform apply`](/terraform/cli/commands/apply)), when an ephemeral resource's data is needed, Terraform initially retrieves that data with the [`Open`](/terraform/plugin/framework/ephemeral-resources/open) lifecycle handler. Once the ephemeral resource data is no longer needed, Terraform calls the provider `CloseEphemeralResource` RPC, in which the framework calls the [`ephemeral.EphemeralResourceWithClose` interface `Close` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/ephemeral#EphemeralResourceWithClose). The request contains any `Private` data set in the latest `Open` or `Renew` call. + +`Close` is an optional lifecycle implementation for an ephemeral resource, other lifecycle implementations include: + +- [Open](/terraform/plugin/framework/ephemeral-resources/open) an ephemeral resource by receiving Terraform configuration, retrieving a remote object, and returning ephemeral result data to Terraform. +- [Renew](/terraform/plugin/framework/ephemeral-resources/renew) an expired remote object at a specified time. + +## Define Close Method + +The [`ephemeral.EphemeralResourceWithClose` interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/ephemeral#EphemeralResourceWithClose) on the [`ephemeral.EphemeralResource` interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/ephemeral#EphemeralResource) implementation will enable close support for an ephemeral resource. + +Implement the `Close` method by: + +1. [Accessing private data](/terraform/plugin/framework/resources/private-state#reading-private-state-data) from [`ephemeral.CloseRequest.Private` field](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/ephemeral#CloseRequest.Private) needed to close the remote object. +1. Performing logic or external calls to close the remote object. + +If the logic needs to return [warning or error diagnostics](/terraform/plugin/framework/diagnostics), they can be added into the [`ephemeral.CloseResponse.Diagnostics` field](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/ephemeral#CloseResponse.Diagnostics). + +In this example, an ephemeral resource named `examplecloud_thing` with hardcoded behavior is defined. `Private` data needed to execute `Close` is passed from the `Open` response: + +```go +var _ ephemeral.EphemeralResourceWithClose = (*ThingEphemeralResource)(nil) + +// ThingEphemeralResource defines the ephemeral resource implementation, which also implements Close. +type ThingEphemeralResource struct{} + +type ThingEphemeralResourceModel struct { + Name types.String `tfsdk:"name"` + Token types.String `tfsdk:"token"` +} + +type ThingPrivateData struct { + Name string `json:"name"` +} + +func (e *ThingEphemeralResource) Schema(ctx context.Context, req ephemeral.SchemaRequest, resp *ephemeral.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "name": schema.StringAttribute{ + Description: "Name of the thing to retrieve a token for.", + Required: true, + }, + "token": schema.StringAttribute{ + Description: "Token for the thing.", + Computed: true, + }, + }, + } +} + +func (e *ThingEphemeralResource) Open(ctx context.Context, req ephemeral.OpenRequest, resp *ephemeral.OpenResponse) { + var data ThingEphemeralResourceModel + + // Read Terraform config data into the model + resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) + if resp.Diagnostics.HasError() { + return + } + + // Typically ephemeral resources will make external calls and reference returned data, + // however this example hardcodes the setting of result and private data for brevity. + data.Token = types.StringValue("token-123") + + // When closing, pass along this data (error handling omitted for brevity). + privateData, _ := json.Marshal(ThingPrivateData{Name: data.Name.ValueString()}) + resp.Private.SetKey(ctx, "thing_data", privateData) + + // Save data into ephemeral result data + resp.Diagnostics.Append(resp.Result.Set(ctx, &data)...) +} + +func (e *ThingEphemeralResource) Close(ctx context.Context, req ephemeral.CloseRequest, resp *ephemeral.CloseResponse) { + privateBytes, diags := req.Private.GetKey(ctx, "thing_data") + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + // Unmarshal private data (error handling omitted for brevity). + var privateData ThingPrivateData + json.Unmarshal(privateBytes, &privateData) + + // Perform external call to close/clean up "thing" data +} + +``` diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/ephemeral-resources/configure.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/ephemeral-resources/configure.mdx new file mode 100644 index 0000000000..fef8e4a12a --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/ephemeral-resources/configure.mdx @@ -0,0 +1,104 @@ +--- +page_title: Configuring ephemeral resources +description: >- + Learn how to configure ephemeral resources with provider data or clients in + the Terraform plugin framework. +--- + +# Configuring ephemeral resources + +[Ephemeral Resources](/terraform/plugin/framework/ephemeral-resources) may require provider-level data or remote system clients to operate correctly. The framework supports the ability to configure this data and/or clients once within the provider, then pass that information to ephemeral resources by adding the `Configure` method. + +## Prepare Provider Configure Method + +Implement the [`provider.ConfigureResponse.EphemeralResourceData` field](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider#ConfigureResponse.EphemeralResourceData) in the [`Provider` interface `Configure` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider#Provider.Configure). This value can be set to any type, whether an existing client or vendor SDK type, a provider-defined custom type, or the provider implementation itself. It is recommended to use pointer types so that ephemeral resources can determine if this value was configured before attempting to use it. + +During execution of the [`terraform plan`](/terraform/cli/commands/plan) and [`terraform apply`](/terraform/cli/commands/apply) commands, Terraform calls the [`ConfigureProvider`](/terraform/plugin/framework/internals/rpcs#configureprovider-rpc) RPC, in which the framework calls the [`provider.Provider` interface `Configure` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider#Provider.Configure). + +In this example, the Go standard library [`net/http.Client`](https://pkg.go.dev/net/http#Client) is configured in the provider, and made available for ephemeral resources: + +```go +// With the provider.Provider implementation +func (p *ExampleCloudProvider) Configure(ctx context.Context, req provider.ConfigureRequest, resp *provider.ConfigureResponse) { + resp.EphemeralResourceData = &http.Client{/* ... */} +} +``` + +In this example, the code defines an `ExampleClient` type that is made available for ephemeral resources: + +```go +type ExampleClient struct { + /* ... */ +} + +// With the provider.Provider implementation +func (p *ExampleCloudProvider) Configure(ctx context.Context, req provider.ConfigureRequest, resp *provider.ConfigureResponse) { + resp.EphemeralResourceData = &ExampleClient{/* ... */} +} +``` + +In this example, the `ExampleCloudProvider` type itself is made available for ephemeral resources: + +```go +// With the provider.Provider implementation +type ExampleCloudProvider struct { + /* ... */ +} + +func (p *ExampleCloudProvider) Configure(ctx context.Context, req provider.ConfigureRequest, resp *provider.ConfigureResponse) { + resp.EphemeralResourceData = p +} +``` + +## Define Ephemeral Resource Configure Method + +Implement the [`ephemeral.EphemeralResourceWithConfigure` interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/ephemeral#EphemeralResourceWithConfigure) which receives the provider configured data from the [`Provider` interface `Configure` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider#Provider.Configure) and saves it into the [`ephemeral.EphemeralResource` interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/ephemeral#EphemeralResource) implementation. + +The [`ephemeral.EphemeralResourceWithConfigure` interface `Configure` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/ephemeral#EphemeralResourceWithConfigure.Configure) is called during execution of the [`terraform validate`](/terraform/cli/commands/validate), [`terraform plan`](/terraform/cli/commands/plan) and [`terraform apply`](/terraform/cli/commands/apply) commands when the `ValidateEphemeralResourceConfig` RPC is sent. Additionally, the [`ephemeral.EphemeralResourceWithConfigure` interface `Configure` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/ephemeral#EphemeralResourceWithConfigure.Configure) is called during execution of the [`terraform plan`](/terraform/cli/commands/plan) and [`terraform apply`](/terraform/cli/commands/apply) commands when the `OpenEphemeralResource` RPC is sent. + +-> Note that Terraform calling the `ValidateEphemeralResourceConfig` RPC would not call the [`ConfigureProvider`](/terraform/plugin/framework/internals/rpcs#configureprovider-rpc) RPC first, so implementations need to account for that situation. Configuration validation in Terraform occurs without provider configuration ("offline"). + +In this example, the provider configured the Go standard library [`net/http.Client`](https://pkg.go.dev/net/http#Client) which the ephemeral resource uses during `Open`: + +```go +// With the ephemeral.EphemeralResource implementation +type ThingEphemeralResource struct { + client *http.Client +} + +func (d *ThingEphemeralResource) Configure(ctx context.Context, req ephemeral.ConfigureRequest, resp *ephemeral.ConfigureResponse) { + // Always perform a nil check when handling ProviderData because Terraform + // sets that data after it calls the ConfigureProvider RPC. + if req.ProviderData == nil { + return + } + + client, ok := req.ProviderData.(*http.Client) + + if !ok { + resp.Diagnostics.AddError( + "Unexpected Ephemeral Resource Configure Type", + fmt.Sprintf("Expected *http.Client, got: %T. Please report this issue to the provider developers.", req.ProviderData), + ) + + return + } + + d.client = client +} + +func (d *ThingEphemeralResource) Open(ctx context.Context, req ephemeral.OpenRequest, resp *ephemeral.OpenResponse) { + // Prevent panic if the provider has not been configured. + if d.client == nil { + resp.Diagnostics.AddError( + "Unconfigured HTTP Client", + "Expected configured HTTP client. Please report this issue to the provider developers.", + ) + + return + } + + httpResp, err := d.client.Get("https://example.com") + /* ... */ +} +``` diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/ephemeral-resources/index.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/ephemeral-resources/index.mdx new file mode 100644 index 0000000000..194d65d5f9 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/ephemeral-resources/index.mdx @@ -0,0 +1,101 @@ +--- +page_title: Ephemeral resources +description: >- + Ephemeral resources allow Terraform to reference external data, while + guaranteeing that this data will not be persisted in plan or state. Learn how + to implement ephemeral resources in the Terraform plugin framework. +--- + +# Ephemeral resources + + + +Ephemeral resource support is in technical preview and offered without compatibility promises until Terraform 1.10 is generally available. + + + +[Ephemeral resources](/terraform/language/v1.10.x/resources/ephemeral) are an abstraction that allows Terraform to reference external data. Unlike [data sources](/terraform/language/data-sources), Terraform guarantees that ephemeral resource data will not be persisted in plan or state artifacts. The data produced by an ephemeral resource can only be referenced in [specific ephemeral contexts](/terraform/language/v1.10.x/resources/ephemeral#referencing-ephemeral-resources) or Terraform will throw an error. + +This page describes the basic implementation details required for supporting an ephemeral resource within the provider. Ephemeral resources, as a part of their lifecycle, must implement: + +- [Open](/terraform/plugin/framework/ephemeral-resources/open) an ephemeral resource by receiving Terraform configuration, retrieving a remote object, and returning ephemeral result data to Terraform. + +Further documentation is available for deeper ephemeral resource concepts: + +- [Configure](/terraform/plugin/framework/ephemeral-resources/configure) an ephemeral resource with provider-level data types or clients. +- [Validate](/terraform/plugin/framework/ephemeral-resources/validate-configuration) practitioner configuration against acceptable values. +- [Renew](/terraform/plugin/framework/ephemeral-resources/renew) an expired remote object at a specified time. +- [Close](/terraform/plugin/framework/ephemeral-resources/close) a remote object when Terraform no longer needs the data. + +## Define Ephemeral Resource Type + +Implement the [`ephemeral.EphemeralResource` interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/ephemeral#EphemeralResource). Ensure the [Add Ephemeral Resource To Provider](#add-ephemeral-resource-to-provider) documentation is followed so the ephemeral resource becomes part of the provider implementation, and therefore available to practitioners. + +### Metadata Method + +The [`ephemeral.EphemeralResource` interface `Metadata` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/ephemeral#EphemeralResource.Metadata) defines the ephemeral resource name as it would appear in Terraform configurations. This name should include the provider type prefix, an underscore, then the ephemeral resource specific name. For example, a provider named `examplecloud` and an ephemeral resource that reads "thing" ephemeral data would be named `examplecloud_thing`. + +In this example, the ephemeral resource name in an `examplecloud` provider that reads "thing" ephemeral resource data is hardcoded to `examplecloud_thing`: + +```go +// With the ephemeral.EphemeralResource implementation +func (r *ThingEphemeralResource) Metadata(ctx context.Context, req ephemeral.MetadataRequest, resp *ephemeral.MetadataResponse) { + resp.TypeName = "examplecloud_thing" +} +``` + +To simplify ephemeral resource implementations, the [`provider.MetadataResponse.TypeName` field](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider#MetadataResponse.TypeName) from the [`provider.Provider` interface `Metadata` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider#Provider.Metadata) can set the provider name so it is available in the [`ephemeral.MetadataRequest.ProviderTypeName` field](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/ephemeral#MetadataRequest.ProviderTypeName). + +In this example, the provider defines the `examplecloud` name for itself, and the ephemeral resource is named `examplecloud_thing`: + +```go +// With the provider.Provider implementation +func (p *ExampleCloudProvider) Metadata(ctx context.Context, req provider.MetadataRequest, resp *provider.MetadataResponse) { + resp.TypeName = "examplecloud" +} + +// With the ephemeral.EphemeralResource implementation +func (d *ThingEphemeralResource) Metadata(ctx context.Context, req ephemeral.MetadataRequest, resp *ephemeral.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_thing" +} +``` + +### Schema Method + +The [`ephemeral.EphemeralResource` interface `Schema` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/ephemeral#EphemeralResource.Schema) defines a [schema](/terraform/plugin/framework/handling-data/schemas) describing what data is available in the ephemeral resource's configuration and result data. + +## Add Ephemeral Resource to Provider + +Ephemeral resources become available to practitioners when they are included in the [provider](/terraform/plugin/framework/providers) implementation via the optional [`provider.ProviderWithEphemeralResources` interface `EphemeralResources` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider#ProviderWithEphemeralResources.EphemeralResource). + +In this example, the `ThingEphemeralResource` type, which implements the `ephemeral.EphemeralResource` interface, is added to the provider implementation: + +```go +var _ provider.ProviderWithEphemeralResources = (*ExampleCloudProvider)(nil) + +func (p *ExampleCloudProvider) EphemeralResources(_ context.Context) []func() ephemeral.EphemeralResource { + return []func() ephemeral.EphemeralResource{ + func() ephemeral.EphemeralResource { + return &ThingResource{}, + }, + } +} +``` + +To simplify provider implementations, a named function can be created with the ephemeral resource implementation. + +In this example, the `ThingEphemeralResource` code includes an additional `NewThingEphemeralResource` function, which simplifies the provider implementation: + +```go +// With the provider.ProviderWithEphemeralResources implementation +func (p *ExampleCloudProvider) EphemeralResources(_ context.Context) []func() ephemeral.EphemeralResource { + return []func() ephemeral.EphemeralResource{ + NewThingEphemeralResource, + } +} + +// With the ephemeral.EphemeralResource implementation +func NewThingEphemeralResource() ephemeral.EphemeralResource { + return &ThingEphemeralResource{} +} +``` diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/ephemeral-resources/open.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/ephemeral-resources/open.mdx new file mode 100644 index 0000000000..3f7ff646a2 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/ephemeral-resources/open.mdx @@ -0,0 +1,76 @@ +--- +page_title: Opening ephemeral resources +description: >- + Learn how to open ephemeral resource in the Terraform plugin framework. +--- + +# Opening ephemeral resources + +Open is part of the Terraform lifecycle for an ephemeral resource, which is different from the [managed resource lifecycle](https://github.com/hashicorp/terraform/blob/main/docs/resource-instance-change-lifecycle.md). During any Terraform operation (like [`terraform plan`](/terraform/cli/commands/plan) or [`terraform apply`](/terraform/cli/commands/apply)), when an ephemeral resource's data is needed, Terraform calls the provider `OpenEphemeralResource` RPC, in which the framework calls the [`ephemeral.EphemeralResource` interface `Open` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/ephemeral#EphemeralResource.Open). The request contains the configuration supplied to Terraform for the ephemeral resource. The response contains the ephemeral result data. The data is defined by the [schema](/terraform/plugin/framework/handling-data/schemas) of the ephemeral resource. + +`Open` is the only required lifecycle implementation for an ephemeral resource, optional lifecycle implementations include: + +- [Renew](/terraform/plugin/framework/ephemeral-resources/renew) an expired remote object at a specified time. +- [Close](/terraform/plugin/framework/ephemeral-resources/close) a remote object when Terraform no longer needs the data. + +## Define Open Method + +Implement the `Open` method by: + +1. [Accessing the `Config` data](/terraform/plugin/framework/handling-data/accessing-values) from the [`ephemeral.OpenRequest` type](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/ephemeral#OpenRequest). +1. Performing logic or external calls to read the result data for the ephemeral resource. +1. Determining if a remote object needs to be renewed, setting the [`ephemeral.OpenResponse.RenewAt` field](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/ephemeral#OpenResponse.RenewAt) to indicate to Terraform when to call the provider [`Renew`](/terraform/plugin/framework/ephemeral-resources/renew) method. +1. [Writing private data](/terraform/plugin/framework/resources/private-state#saving-private-state-data) needed to `Renew` or `Close` the ephemeral resource to the [`ephemeral.OpenResponse.Private` field](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/ephemeral#OpenResponse.Private). +1. [Writing result data](/terraform/plugin/framework/writing-state) into the [`ephemeral.OpenResponse.Result` field](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/ephemeral#OpenResponse.Result). + +If the logic needs to return [warning or error diagnostics](/terraform/plugin/framework/diagnostics), they can be added into the [`ephemeral.OpenResponse.Diagnostics` field](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/ephemeral#OpenResponse.Diagnostics). + +In this example, an ephemeral resource named `examplecloud_thing` with hardcoded behavior is defined: + +```go +// ThingEphemeralResource defines the ephemeral resource implementation. +// Some ephemeral.EphemeralResource interface methods are omitted for brevity. +type ThingEphemeralResource struct {} + +type ThingEphemeralResourceModel struct { + Name types.String `tfsdk:"name"` + Token types.String `tfsdk:"token"` +} + +func (e *ThingEphemeralResource) Schema(ctx context.Context, req ephemeral.SchemaRequest, resp *ephemeral.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "name": schema.StringAttribute{ + Description: "Name of the thing to retrieve a token for.", + Required: true, + }, + "token": schema.StringAttribute{ + Description: "Token for the thing.", + Computed: true, + }, + }, + } +} + +func (e *ThingEphemeralResource) Open(ctx context.Context, req ephemeral.OpenRequest, resp *ephemeral.OpenResponse) { + var data ThingEphemeralResourceModel + + // Read Terraform config data into the model + resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) + if resp.Diagnostics.HasError() { + return + } + + // Typically ephemeral resources will make external calls, however this example + // hardcodes setting the token attribute to a specific value for brevity. + data.Token = types.StringValue("token-123") + + // Save data into ephemeral result data + resp.Diagnostics.Append(resp.Result.Set(ctx, &data)...) +} +``` + +## Caveats + +* An error is returned if the `Result` data contains unknown values. Set all attributes to either null or known values in the response. +* An error is returned unless every non-computed known value in the request config is saved exactly as-is into the result data. Only null values marked as computed can be modified. diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/ephemeral-resources/renew.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/ephemeral-resources/renew.mdx new file mode 100644 index 0000000000..dadd729f73 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/ephemeral-resources/renew.mdx @@ -0,0 +1,113 @@ +--- +page_title: Renewing ephemeral resources +description: >- + Learn how to renew ephemeral resource in the Terraform plugin framework. +--- + +# Renewing ephemeral resources + +Renew is an optional part of the Terraform lifecycle for an ephemeral resource, which is different from the [managed resource lifecycle](https://github.com/hashicorp/terraform/blob/main/docs/resource-instance-change-lifecycle.md). During any Terraform operation (like [`terraform plan`](/terraform/cli/commands/plan) or [`terraform apply`](/terraform/cli/commands/apply)), when an ephemeral resource's data is needed, Terraform initially retrieves that data with the [`Open`](/terraform/plugin/framework/ephemeral-resources/open) lifecycle handler. During `Open`, ephemeral resources can opt to include a timestamp in the `RenewAt` response field to indicate to Terraform when a provider must renew an ephemeral resource. If an ephemeral resource's data is still in-use and the `RenewAt` timestamp has passed, Terraform calls the provider `RenewEphemeralResource` RPC, in which the framework calls the [`ephemeral.EphemeralResourceWithRenew` interface `Renew` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/ephemeral#EphemeralResourceWithRenew). The request contains any `Private` data set in the latest `Open` or `Renew` call. The response contains `Private` data and an optional `RenewAt` field for further renew executions. + + + +`Renew` cannot return new result data for the ephemeral resource instance, so this logic is only appropriate for remote objects like HashiCorp Vault leases, which can be renewed without changing their data. + + + +`Renew` is an optional lifecycle implementation for an ephemeral resource, other lifecycle implementations include: + +- [Open](/terraform/plugin/framework/ephemeral-resources/open) an ephemeral resource by receiving Terraform configuration, retrieving a remote object, and returning ephemeral result data to Terraform. +- [Close](/terraform/plugin/framework/ephemeral-resources/close) a remote object when Terraform no longer needs the data. + +## Define Renew Method + +The [`ephemeral.EphemeralResourceWithRenew` interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/ephemeral#EphemeralResourceWithRenew) on the [`ephemeral.EphemeralResource` interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/ephemeral#EphemeralResource) implementation will enable renew support for an ephemeral resource. + +Implement the `Renew` method by: + +1. [Accessing private data](/terraform/plugin/framework/resources/private-state#reading-private-state-data) from [`ephemeral.RenewRequest.Private` field](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/ephemeral#RenewRequest.Private) needed to renew the remote object. +1. Performing logic or external calls to renew the remote object. +1. Determining if a remote object needs to be renewed again, setting the [`ephemeral.RenewResponse.RenewAt` field](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/ephemeral#RenewResponse.RenewAt) to indicate to Terraform when to call the provider [`Renew`](/terraform/plugin/framework/ephemeral-resources/renew) method. +1. [Writing private data](/terraform/plugin/framework/resources/private-state#saving-private-state-data) needed to `Renew` or `Close` the ephemeral resource to the [`ephemeral.RenewResponse.Private` field](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/ephemeral#RenewResponse.Private). + +If the logic needs to return [warning or error diagnostics](/terraform/plugin/framework/diagnostics), they can be added into the [`ephemeral.RenewResponse.Diagnostics` field](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/ephemeral#RenewResponse.Diagnostics). + +In this example, an ephemeral resource named `examplecloud_thing` with hardcoded behavior is defined. It indicates a renewal should occur 5 minutes from when either the `Open` or `Renew` method is executed: + +```go +var _ ephemeral.EphemeralResourceWithRenew = (*ThingEphemeralResource)(nil) + +// ThingEphemeralResource defines the ephemeral resource implementation, which also implements Renew. +type ThingEphemeralResource struct{} + +type ThingEphemeralResourceModel struct { + Name types.String `tfsdk:"name"` + Token types.String `tfsdk:"token"` +} + +type ThingPrivateData struct { + Name string `json:"name"` +} + +func (e *ThingEphemeralResource) Schema(ctx context.Context, req ephemeral.SchemaRequest, resp *ephemeral.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "name": schema.StringAttribute{ + Description: "Name of the thing to retrieve a token for.", + Required: true, + }, + "token": schema.StringAttribute{ + Description: "Token for the thing.", + Computed: true, + }, + }, + } +} + +func (e *ThingEphemeralResource) Open(ctx context.Context, req ephemeral.OpenRequest, resp *ephemeral.OpenResponse) { + var data ThingEphemeralResourceModel + + // Read Terraform config data into the model + resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) + if resp.Diagnostics.HasError() { + return + } + + // Typically ephemeral resources will make external calls and reference returned data, + // however this example hardcodes the setting of result and private data for brevity. + data.Token = types.StringValue("token-123") + + // Renew 5 minutes from now + resp.RenewAt = time.Now().Add(5 * time.Minute) + + // When renewing, pass along this data (error handling omitted for brevity). + privateData, _ := json.Marshal(ThingPrivateData{Name: data.Name.ValueString()}) + resp.Private.SetKey(ctx, "thing_data", privateData) + + // Save data into ephemeral result data + resp.Diagnostics.Append(resp.Result.Set(ctx, &data)...) +} + +func (e *ThingEphemeralResource) Renew(ctx context.Context, req ephemeral.RenewRequest, resp *ephemeral.RenewResponse) { + privateBytes, _ := req.Private.GetKey(ctx, "thing_data") + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + // Unmarshal private data (error handling omitted for brevity). + var privateData ThingPrivateData + json.Unmarshal(privateBytes, &privateData) + + // Perform external call to renew "thing" data + + // Renew again in 5 minutes + resp.RenewAt = time.Now().Add(5 * time.Minute) + + // If needed, you can also set new `Private` data on the response. +} +``` + +## Recommendations + +* When setting the `RenewAt` response field, add extra time (usually no more than a few minutes) before an ephemeral resource expires to account for latency. diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/ephemeral-resources/validate-configuration.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/ephemeral-resources/validate-configuration.mdx new file mode 100644 index 0000000000..242dd4cfc0 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/ephemeral-resources/validate-configuration.mdx @@ -0,0 +1,86 @@ +--- +page_title: Validate ephemeral resource configurations +description: >- + Learn how to validate ephemeral resource configurations with the Terraform + plugin framework. +--- + +# Validate ephemeral resource configurations + +[Ephemeral resources](/terraform/plugin/framework/ephemeral-resources) support validating an entire practitioner configuration in either declarative or imperative logic. Feedback, such as required syntax or acceptable combinations of values, is returned via [diagnostics](/terraform/plugin/framework/diagnostics). + +This page describes implementation details for validating entire ephemeral resource configurations, typically referencing multiple attributes. Further documentation is available for other configuration validation concepts: + +- [Single attribute validation](/terraform/plugin/framework/validation#attribute-validation) is a schema-based mechanism for implementing attribute-specific validation logic. +- [Type validation](/terraform/plugin/framework/validation#type-validation) is a schema-based mechanism for implementing reusable validation logic for any attribute using the type. + +-> Configuration validation in Terraform occurs without provider configuration ("offline"), therefore the ephemeral resource `Configure` method will not have been called. To implement validation with a configured API client, use logic within the `Open` method, which occurs during Terraform's planning phase when possible. + +## ConfigValidators Method + +The [`ephemeral.EphemeralResourceWithConfigValidators` interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/ephemeral#EphemeralResourceWithConfigValidators) follows a similar pattern to attribute validation and allows for a more declarative approach. This enables consistent validation logic across multiple ephemeral resources. Each validator intended for this interface must implement the [`ephemeral.ConfigValidator` interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/ephemeral#ConfigValidator). + +During execution of the [`terraform validate`](/terraform/cli/commands/validate), [`terraform plan`](/terraform/cli/commands/plan) and [`terraform apply`](/terraform/cli/commands/apply) commands, Terraform calls the provider `ValidateEphemeralResourceConfig` RPC, in which the framework calls the `ConfigValidators` method on ephemeral resources that implement the [`ephemeral.EphemeralResourceWithConfigValidators` interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/ephemeral#EphemeralResourceWithConfigValidators). + +The [`terraform-plugin-framework-validators` Go module](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators) has a collection of common use case ephemeral resource configuration validators in the [`ephemeralvalidator` package](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators/ephemeralvalidator). These use [path expressions](/terraform/plugin/framework/path-expressions) for matching attributes. + +This example will raise an error if a practitioner attempts to configure both `attribute_one` and `attribute_two`: + +```go +// Other methods to implement the ephemeral.EphemeralResource interface are omitted for brevity +type ThingEphemeralResource struct {} + +func (d ThingEphemeralResource) ConfigValidators(ctx context.Context) []ephemeral.ConfigValidator { + return []ephemeral.ConfigValidator{ + ephemeralvalidator.Conflicting( + path.MatchRoot("attribute_one"), + path.MatchRoot("attribute_two"), + ), + } +} +``` + +## ValidateConfig Method + +The [`ephemeral.EphemeralResourceWithValidateConfig` interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/ephemeral#EphemeralResourceWithValidateConfig) is more imperative in design and is useful for validating unique functionality across multiple attributes that typically applies to a single ephemeral resource. + +During execution of the [`terraform validate`](/terraform/cli/commands/validate), [`terraform plan`](/terraform/cli/commands/plan) and [`terraform apply`](/terraform/cli/commands/apply) commands, Terraform calls the provider `ValidateEphemeralResourceConfig` RPC, in which the framework calls the `ValidateConfig` method on providers that implement the [`ephemeral.EphemeralResourceWithValidateConfig` interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/ephemeral#EphemeralResourceWithValidateConfig). + +This example will raise a warning if a practitioner attempts to configure `attribute_one`, but not `attribute_two`: + +```go +// Other methods to implement the ephemeral.EphemeralResource interface are omitted for brevity +type ThingEphemeralResource struct {} + +type ThingEphemeralResourceModel struct { + AttributeOne types.String `tfsdk:"attribute_one"` + AttributeTwo types.String `tfsdk:"attribute_two"` +} + +func (d ThingEphemeralResource) ValidateConfig(ctx context.Context, req ephemeral.ValidateConfigRequest, resp *ephemeral.ValidateConfigResponse) { + var data ThingEphemeralResourceModel + + resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + // If attribute_one is not configured, return without warning. + if data.AttributeOne.IsNull() || data.AttributeOne.IsUnknown() { + return + } + + // If attribute_two is not null, return without warning. + if !data.AttributeTwo.IsNull() { + return + } + + resp.Diagnostics.AddAttributeWarning( + path.Root("attribute_two"), + "Missing Attribute Configuration", + "Expected attribute_two to be configured with attribute_one. "+ + "The ephemeral resource may return unexpected results.", + ) +} +``` diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/concepts.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/concepts.mdx new file mode 100644 index 0000000000..23835d778b --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/concepts.mdx @@ -0,0 +1,68 @@ +--- +page_title: Provider-defined functions +description: >- + Learn how provider-defined functions enable Terraform providers to define + functions for practitions to use in their Terraform configurations. +--- + +# Provider-defined functions + +This page describes Terraform concepts relating to provider-defined functions within framework-based provider code. Provider-defined functions are supported in Terraform 1.8 and later. The [What is Terraform](/terraform/intro), [Terraform language](/terraform/language), and [Plugin Development](/terraform/plugin) documentation covers more general concepts behind Terraform's workflow, its configuration, and how it interacts with providers. + +## Purpose + +The purpose of provider-defined functions is to encapsulate offline, computational logic beyond Terraform's built-in functions to simplify practitioner configurations. Terraform expects that provider-defined functions are implemented without side-effects and as pure functions where given the same input data that they always return the same output. Refer to [HashiCorp Provider Design Principles](/terraform/plugin/best-practices/hashicorp-provider-design-principles) for additional best practice details. + +Example use cases include: + +* Transforming existing data, such as merging complex data structures using a specific algorithm or converting between encodings. +* Parsing combined data into individual, referenceable components, such as taking an Amazon Resource Name (ARN) and returning an object of region, account identifier, etc. attributes. +* Building combined data from individual components, such as returning an Amazon Resource Name (ARN) based on given region, account identifier, etc. data. +* Static data lookups when there is no remote system query available, such as returning a data value typically necessary for a practitioner configuration. + +Differences from other provider-defined concepts include: + +* [Data Sources](/terraform/plugin/framework/data-sources): Intended to perform online or provider configuration dependent data lookup, which participate in Terraform's operational graph. +* [Resources](/terraform/plugin/framework/resources): Intended to manage the full lifecycle (create, update, destroy) of a remote system component, which participate in Terraform's operational graph. + +## Terminology + +There are two main components of provider-defined functions: + +* **Definition**: Defines the expected input and output data along with documentation descriptions. +* **Call**: When a practioner configuration causes a function's logic to be run. + +Within a function definition the components are: + +* **Parameters**: An ordered list of definitions for input data. + * **Variadic Parameter**: An optional, final parameter which accepts zero, one, or multiple parts of input data. +* **Return**: The definition for output data. + +Similar to many programming languages, when the function is called, the terminology for the data is slightly different than the terminology for the definition. + +* **Arguments**: Positionally ordered data based on the definitions of the parameters. +* **Result**: Data based on the definition of the return. + +## Implementation Overview + +For each provider listed as a [required provider](/terraform/language/providers/requirements), Terraform will query the provider for its function definitions. If a configuration attempts to call a provider-defined function without listing the provider as required, Terraform will return an error. + +Terraform will typically call functions before other provider concepts are evaluated. This includes before provider configuration being evaluated, which the framework enforces by not exposing provider configuration data to function implementations. + +### Naming + +Terraform requires that function names must be valid [identifiers](/terraform/language/syntax/configuration#identifiers). + +### Argument Handling + +Terraform will statically validate that the number and types of arguments in a configuration match the definitions of parameters, otherwise returning an error. + +If a null value is given as an argument, without individual parameter definition opt-in, Terraform will return an error. If an unknown value is given as an argument, without individual parameter definition opt-in, Terraform will skip calling the provider logic entirely and set the function result to an unknown value matching the return type. + +### Result Handling + +Terraform will statically validate that the return type is appropriately used in consuming configuration, otherwise returning an error. + +Function logic must always set the result to the return type, otherwise Terraform will return an error. + +Function logic can only set the result to an unknown value if there is a parameter that opted into unknown value handling and an unknown value argument was received for one of those parameters. diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/documentation.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/documentation.mdx new file mode 100644 index 0000000000..fd731ae0f7 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/documentation.mdx @@ -0,0 +1,122 @@ +--- +page_title: Documenting functions +description: >- + Learn how to document provider-defined functions with the Terraform plugin + framework. +--- + +# Documenting functions + +When a function is [implemented](/terraform/plugin/framework/functions/implementation), ensure the function is discoverable by practitioners with usage information. + +There are two main components for function documentation: + +* [Implementation-Based Documentation](#implementation-based-documentation): Exposes function documentation to Terraform and downstream tooling, such as practitioner configuration editor integrations. +* [Registry-Based Documentation](#registry-based-documentation): Exposes function documentation to the [Terraform Registry](https://registry.terraform.io) when the [provider is published](/terraform/registry/providers/publishing), making it displayed and discoverable on the web. + +## Implementation-Based Documentation + +Add documentation directly inside the [function definition](/terraform/plugin/framework/functions/implementation#definition-method). All implementation-based documentation is passed to Terraform, which downstream tooling such as pracitioner configuration editor integrations will automatically display. + +### Definition + +The [`function.Definition` type](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/function#Definition) implements the following fields: + +| Field Name | Description | +|---|---| +| `Summary` | A short description of the function and its return, preferably a single sentence. | +| `Description` | Longer documentation about the function, its return, and pertinent implementation details in plaintext format. | +| `MarkdownDescription` | Longer documentation about the function, its return, and pertinent implementation details in Markdown format. | + +If there are no description formatting differences, set only one of `Description` or `MarkdownDescription`. When Terraform has not sent a preference for the description formatting, the framework will return `MarkdownDescription` if both are defined. + +In this example, the function definition sets summary and description documentation: + +```go +func (f *CidrContainsIpFunction) Definition(ctx context.Context, req function.DefinitionRequest, resp *function.DefinitionResponse) { + resp.Definition = function.Definition{ + // ... other fields ... + Summary: "Check if a network CIDR contains an IP", + Description: "Returns a boolean whether a RFC4632 CIDR contains an IP address", + } +} +``` + +### Parameters + +Each [parameter type](/terraform/plugin/framework/functions/parameters), whether in the definition `Parameters` or `VariadicParameter` field, implements the following fields: + +| Field Name | Description | +|-----------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------| +| `Name` | **Required**: Single word or abbreviation of parameter for function signature generation. If name is not provided, a runtime error will be generated. | +| `Description` | Documentation about the parameter and its expected values in plaintext format. | +| `MarkdownDescription` | Documentation about the parameter and its expected values in Markdown format. | + +The name must be unique in the context of the [function definition](/terraform/plugin/framework/functions/implementation#definition-method). It is used for documentation purposes and displayed in error diagnostics presented to practitioners. The name should delineate the purpose of the parameter, especially to disambiguate between multiple parameters, such as the words `cidr` and `ip` in a generated function signature like `cidr_contains_ip(cidr string, ip string) bool`. + +If there are no description formatting differences, set only one of `Description` or `MarkdownDescription`. When Terraform has not sent a preference for the description formatting, the framework will return `MarkdownDescription` if both are defined. + +In this example, the function parameters set name and description documentation: + +```go +func (f *CidrContainsIpFunction) Definition(ctx context.Context, req function.DefinitionRequest, resp *function.DefinitionResponse) { + resp.Definition = function.Definition{ + // ... other fields ... + Parameters: []function.Parameter{ + function.StringParameter{ + Name: "cidr", + Description: "RFC4632 CIDR to check whether it contains the given IP address", + }, + function.StringParameter{ + Name: "ip", + Description: "IP address to check whether its contained in the RFC4632 CIDR", + }, + }, + } +} +``` + +## Registry-Based Documentation + +Add Markdown documentation files in conventional provider codebase locations before [publishing](/terraform/registry/providers/publishing) to the [Terraform Registry](https://registry.terraform.io). The documentation is displayed and discoverable on the web. These files can be manually created or automatically generated using tooling such as [`terraform-plugin-docs`](https://github.com/hashicorp/terraform-plugin-docs). + +The [Registry provider documentation](/terraform/registry/providers/docs) covers the overall requirements, conventional file layout details, and how to enable additional features such as sub-categories for the navigation sidebar. Function documentation for most providers is expected under the `docs/functions/` directory with a file named after the function and with the extension `.md`. Older providers using the legacy file layout use `website/docs/functions/` and `.html.md`. + +Functions are conventionally documented with the following: + +* Description +* Example Usage +* Signature +* Arguments + +In this example, a `docs/functions/contains_ip.md` file (either manually or automatically created) will be displayed in the Terraform Registry after provider publishing: + +``````plain +--- +page_title: contains_ip Function - terraform-provider-cidr +description: |- + Returns a boolean whether a RFC4632 CIDR contains an IP address. +--- + +# Function: contains_ip + +Returns a boolean whether a RFC4632 CIDR contains an IP address. + +## Example Usage + +```terraform +# result: true +provider::cidr::contains_ip("10.0.0.0/8", "10.0.0.1") +``` + +## Signature + +```text +contains_ip(cidr string, ip string) bool +``` + +## Arguments + +1. `cidr` (String) RFC4632 CIDR to check whether it contains the given IP address. +2. `ip` (String) IP address to check whether its contained in the RFC4632 CIDR. +`````` diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/errors.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/errors.mdx new file mode 100644 index 0000000000..64dc74931c --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/errors.mdx @@ -0,0 +1,162 @@ +--- +page_title: Returning errors from functions +description: >- + Learn how to return errors from provider-defined functions with the Terraform + plugin framework. +--- + +# Returning errors from function + +Providers use [`FuncError`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/function#FuncError) to +surface a practitioner-facing error generated during execution of provider-defined functions. These errors are +returned from Terraform CLI at the end of command output: + +```console +$ terraform plan +# ... other plan output ... +╷ +│ Error: Error in function call +│ +│ on example.tf line #: +│ #: source configuration line +│ +│ Call to function "{FUNCTION}" failed: {TEXT}. +``` + +```console +$ terraform plan +# ... other plan output ... +╷ +│ Error: Invalid function argument +│ +│ on example.tf line #: +│ #: source configuration line +│ +│ Invalid value for "{PARAMETER_NAME}" parameter: {TEXT}. +``` + +In the framework, you may encounter them in response structs or as returns from +provider-defined function execution.: + +```go +func (f *ExampleFunction) Run(ctx context.Context, req function.RunRequest, resp *function.RunResponse) { +``` + +This is the most common form for `FuncError`: a single error whose text +is the concatenated error text from one or more errors. This approach allows +your provider to inform practitioners about all relevant errors at the same +time, allowing practitioners to fix their configuration or environment more +quickly. You should only concatenate a `FuncError` and never replace or +remove information it. + +The next section will detail the concepts and typical behaviors of +function error, while the final section will outline the typical methods for +working with function error, using functionality from the available +[`function` package](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/function). + +## Function Error Concepts + +### Text + +`Text` is a practitioner-oriented description of the problem. This should +contain sufficient detail to provide both general and more specific information +regarding the issue. For example "Error executing function: foo can only contain +letters, numbers, and digits." + +### FunctionArgument + +`FunctionArgument` is a zero-based, int64 value that identifies the specific +function argument position that caused the error. Only errors that pertain +to a function argument will include this information. + +### Working With Existing Function Errors + +#### ConcatFuncErrors + +When receiving `function.FuncError` from a function or method, such as +`Run()`, these should typically be concatenated with the +response function error for the method. This can be accomplished with the +[`ConcatFuncErrors(in ...*FuncError)` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/function#ConcatFuncErrors). + +For example: + +```go +func (f *ExampleFunction) Run(ctx context.Context, req function.RunRequest, resp *function.RunResponse) { + var boolArg bool + var stringArg string + + resp.Error = function.ConcatFuncErrors(resp.Error, req.Arguments.Get(ctx, &boolArg, &stringArg)) + + // ... other logic ... +} +``` + +This method automatically ignores `nil` function errors. + +### Creating Function Errors + +To craft the message of a function error, it is recommended to use sufficient +detail to convey both the cause of the error and as much contextual, +troubleshooting, and next action information as possible. These details can +use newlines for easier readability where necessary. + +#### NewFuncError + +When creating function errors where a `function.FunctionError` is already available, +such as within a response type, the [`ConcatFuncErrors(in ...*FuncError)` function](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/function#NewFuncError.AddError) +can be used with the [`NewFuncError(text string)` function](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/function#NewFuncError) to concatenate a new +function error. + +For example: + +```go +func (f *ExampleFunction) Run(ctx context.Context, req function.RunRequest, resp *function.RunResponse) { + // ... prior logic ... + + val, err := // operation that may return an error + + if err != nil { + resp.Error = ConcatFuncErrors(resp.Error, function.NewFuncError("Error performing operation: " + err.Error())) + return + } + + // ... further logic ... +} +``` + +#### NewArgumentFuncError + +When creating function errors that affect only a single function argument, the [`NewArgumentFuncError(functionArgument int, msg string)` function](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/function#NewArgumentFuncError) +can be used in conjunction with the [`ConcatFuncErrors(in ...*FuncError)` function](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/function#NewArgumentFuncError). This provides additional context to practitioners, such as showing the specific line(s) and value(s) of configuration where possible. + +For example: + +```go +func (f ExampleFunction) Run(ctx context.Context, req function.RunRequest, resp *function.RunResponse) { + // ... other logic ... + + // Add function error associated with first function argument position + resp.Error = function.ConcatFuncErrors(resp.Error, function.NewArgumentFuncError(0, "Example Error Summary: Example Error Detail")) + + // ... other logic ... +} +``` + +#### FuncErrorFromDiags + +A function error is created from diagnostics by using the [`FuncErrorFromDiags(context.Context, diag.Diagnostics)` function](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/function#FuncErrorFromDiags). The function error will contain the concatenated summary and details of error-level +diagnostics. + +~> **Note**: The [`FuncErrorFromDiags(context.Context, diag.Diagnostics)` function](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/function#FuncErrorFromDiags) does not include warning-level diagnostics in the function error. Warning-level diagnostics are logged instead. + +For example: + +```go +func (f *ExampleFunction) Run(ctx context.Context, req function.RunRequest, resp *function.RunResponse) { + // ... other logic ... + + _, diags := // operation that may return diagnostics + + resp.Error = function.ConcatFuncErrors(resp.Error, function.FuncErrorFromDiags(ctx, diags)) +} +``` diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/implementation.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/implementation.mdx new file mode 100644 index 0000000000..c811870ee2 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/implementation.mdx @@ -0,0 +1,335 @@ +--- +page_title: Implement provider-defined functions +description: >- + Learn how to implement provider-defined functions with the Terraform + plugin framework. +--- + +# Implement provider-defined functions + +The framework supports implementing functions based on Terraform's [concepts for provider-defined functions](/terraform/plugin/framework/functions/concepts). It is recommended to understand those concepts before implementing a function since the terminology is used throughout this page and there are details that simplify function handling as compared to other provider concepts. Provider-defined functions are supported in Terraform 1.8 and later. + +The main code components of a function implementation are: + +* [Defining the function](#define-function-type) including its name, expected data types, descriptions, and logic. +* [Adding the function to the provider](#add-function-to-provider) so it is accessible by Terraform and practitioners. + +Once the code is implemented, it is always recommended to also add: + +* [Testing](/terraform/plugin/framework/functions/testing) to ensure expected function behaviors. +* [Documentation](/terraform/plugin/framework/functions/documentation) to ensure the function is discoverable by practitioners with usage information. + +## Define Function Type + +Implement the [`function.Function` interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/function#Function). Each of the methods is described in more detail below. + +In this example, a function named `echo` is defined, which takes a string argument and returns that value as the result: + +```go +import ( + "github.com/hashicorp/terraform-plugin-framework/function" +) + +// Ensure the implementation satisfies the desired interfaces. +var _ function.Function = &EchoFunction{} + +type EchoFunction struct {} + +func (f *EchoFunction) Metadata(ctx context.Context, req function.MetadataRequest, resp *function.MetadataResponse) { + resp.Name = "echo" +} + +func (f *EchoFunction) Definition(ctx context.Context, req function.DefinitionRequest, resp *function.DefinitionResponse) { + resp.Definition = function.Definition{ + Summary: "Echo a string", + Description: "Given a string value, returns the same value.", + + Parameters: []function.Parameter{ + function.StringParameter{ + Name: "input", + Description: "Value to echo", + }, + }, + Return: function.StringReturn{}, + } +} + +func (f *EchoFunction) Run(ctx context.Context, req function.RunRequest, resp *function.RunResponse) { + var input string + + // Read Terraform argument data into the variable + resp.Error = function.ConcatFuncErrors(resp.Error, req.Arguments.Get(ctx, &input)) + + // Set the result to the same data + resp.Error = function.ConcatFuncErrors(resp.Error, resp.Result.Set(ctx, input)) +} +``` + +### Metadata Method + +The [`function.Function` interface `Metadata` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/function#Function.Metadata) defines the function name as it would appear in Terraform configurations. Unlike resources and data sources, this name should **NOT** include the provider name as the configuration language syntax for calling functions will separately include the provider name. Refer to [naming](/terraform/plugin/best-practices/naming) for additional best practice details. + +In this example, the function name is set to `example`: + +```go +// With the function.Function implementation +func (f *ExampleFunction) Metadata(ctx context.Context, req function.MetadataRequest, resp *function.MetadataResponse) { + resp.Name = "example" +} +``` + +### Definition Method + +The [`function.Function` interface `Definition` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/function#Function.Definition) defines the parameters, return, and various descriptions for documentation of the function. + +In this example, the function definition includes one string parameter, a string return, and descriptions for documentation: + +```go +func (f *ExampleFunction) Definition(ctx context.Context, req function.DefinitionRequest, resp *function.DefinitionResponse) { + resp.Definition = function.Definition{ + Summary: "Echo a string", + Description: "Given a string value, returns the same value.", + + Parameters: []function.Parameter{ + function.StringParameter{ + Description: "Value to echo", + Name: "input", + }, + }, + Return: function.StringReturn{}, + } +} +``` + +#### Return + +The `Return` field must be defined as all functions must return a result. This influences how the [Run method](#run-method) must set the result data. Refer to the [returns](/terraform/plugin/framework/functions/returns) documentation for details about all available types and how to handle data with each type. + +#### Parameters + +There may be zero or more parameters, which are defined with the `Parameters` field. They are ordered, which influences how practitioners call the function in their configurations and how the [Run method](#run-method) must read the argument data. Refer to the [parameters](/terraform/plugin/framework/functions/parameters) documentation for details about all available types and how to handle data with each type. + +An optional `VariadicParameter` field enables a final variadic parameter which accepts zero, one, or more values of the same type. It may be optionally combined with `Parameters`, meaning it represents the any argument data after the final parameter. When reading argument data, a `VariadicParameter` is represented as a tuple, with each element matching the parameter type; the tuple has zero or more elements to match the given arguments. + +By default, Terraform will not pass null or unknown values to the provider logic when a function is called. Within each parameter, use the `AllowNullValue` and/or `AllowUnknownValues` fields to explicitly allow those kinds of values. Enabling `AllowNullValue` requires using a pointer type or [framework type](/terraform/plugin/framework/handling-data/types) when reading argument data. Enabling `AllowUnknownValues` requires using a [framework type](/terraform/plugin/framework/handling-data/types) when reading argument data. + +#### Documentation + +The [function documentation](/terraform/plugin/framework/functions/documentation) page describes how to implement documentation so it is available to Terraform, downstream tooling such as practitioner configuration editor integrations, and in the [Terraform Registry](https://registry.terraform.io). + +#### Deprecation + +If a function is being deprecated, such as for future removal, the `DeprecationMessage` field should be set. The message should be actionable for practitioners, such as telling them what to do with their configuration instead of calling this function. + +### Run Method + +The [`function.Function` interface `Run` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/function#Function.Run) defines the logic that is invoked when Terraform calls the function. Only argument data is provided when a function is called. Refer to [HashiCorp Provider Design Principles](/terraform/plugin/best-practices/hashicorp-provider-design-principles) for additional best practice details. + +Implement the `Run` method by: + +1. Creating variables for argument data, based on the parameter definitions. Refer to the [parameters](/terraform/plugin/framework/functions/parameters) documentation for details about all available parameter types and how to handle data with each type. +1. Reading argument data from the [`function.RunRequest.Arguments` field](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/function#RunRequest.Arguments). +1. Performing any computational logic. +1. Setting the result value, based on the return definition, into the [`function.RunResponse.Result` field](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/function#RunResponse.Result). Refer to the [returns](/terraform/plugin/framework/functions/returns) documentation for details about all available return types and how to handle data with each type. + +If the logic needs to return a [function error](/terraform/plugin/framework/functions/errors), it can be added into the [`function.RunResponse.Error` field](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/function#RunResponse.Error). + +### Reading Argument Data + +The framework supports two methodologies for reading argument data from the [`function.RunRequest.Arguments` field](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/function#RunRequest.Arguments), which is of the [`function.ArgumentsData` type](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/function#ArgumentsData). + +The first option is using the [`(function.ArgumentsData).Get()` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/function#ArgumentsData.Get) to read all arguments at once. The framework will return an error if the number and types of target variables does not match the argument data. + +In this example, the parameters are defined as a boolean and string which are read into Go built-in `bool` and `string` variables since they do not opt into null or unknown value handling: + +```go +func (f *ExampleFunction) Definition(ctx context.Context, req function.DefinitionRequest, resp *function.DefinitionResponse) { + resp.Definition = function.Definition{ + // ... other fields ... + Parameters: []function.Parameter{ + function.BoolParameter{ + Name: "bool_param", + // ... other fields ... + }, + function.StringParameter{ + Name: "string_param", + // ... other fields ... + }, + }, + } +} + +func (f *ExampleFunction) Run(ctx context.Context, req function.RunRequest, resp *function.RunResponse) { + var boolArg bool + var stringArg string + + resp.Error = function.ConcatFuncErrors(resp.Error, req.Arguments.Get(ctx, &boolArg, &stringArg)) + + // ... other logic ... +} +``` + +The second option is using [`(function.ArgumentsData).GetArgument()` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/function#ArgumentsData.GetArgument) to read individual arguments. The framework will return an error if the argument position does not exist or if the type of the target variable does not match the argument data. + +In this example, the parameters are defined as a boolean and string and the first argument is read into a Go built-in `bool` variable since it does not opt into null or unknown value handling: + +```go +func (f *ExampleFunction) Definition(ctx context.Context, req function.DefinitionRequest, resp *function.DefinitionResponse) { + resp.Definition = function.Definition{ + // ... other fields ... + Parameters: []function.Parameter{ + function.BoolParameter{ + Name: "bool_param", + // ... other fields ... + }, + function.StringParameter{ + Name: "string_param", + // ... other fields ... + }, + }, + } +} + +func (f *ExampleFunction) Run(ctx context.Context, req function.RunRequest, resp *function.RunResponse) { + var boolArg bool + + resp.Error = function.ConcatFuncErrors(resp.Error, req.Arguments.GetArgument(ctx, 0, &boolArg)) + + // ... other logic ... +} +``` + +#### Reading Variadic Parameter Argument Data + +The optional `VariadicParameter` field in a function definition enables a final variadic parameter which accepts zero, one, or more values of the same type. It may be optionally combined with `Parameters`, meaning it represents the argument data after the final parameter. When reading argument data, a `VariadicParameter` is represented as a tuple, with each element matching the parameter type; the tuple has zero or more elements to match the given arguments. + +Use either the [framework tuple type](/terraform/plugin/framework/handling-data/types/tuple) or a Go slice of an appropriate type to match the variadic parameter `[]T`. + +In this example, there is a boolean parameter and string variadic parameter, where the variadic parameter argument data is always fetched as a slice of `string`: + +```go +func (f *ExampleFunction) Definition(ctx context.Context, req function.DefinitionRequest, resp *function.DefinitionResponse) { + resp.Definition = function.Definition{ + // ... other fields ... + Parameters: []function.Parameter{ + function.BoolParameter{ + Name: "bool_param", + // ... other fields ... + }, + }, + VariadicParameter: function.StringParameter{ + Name: "variadic_param", + // ... other fields ... + }, + } +} + +func (f *ExampleFunction) Run(ctx context.Context, req function.RunRequest, resp *function.RunResponse) { + var boolArg bool + var stringVarg []string + + resp.Error = function.ConcatFuncErrors(resp.Error, req.Arguments.Get(ctx, &boolArg, &stringVarg)) + + // ... other logic ... +} +``` + +If it is necessary to return a [function error](/terraform/plugin/framework/functions/errors) for a specific variadic argument, note that Terraform treats each zero-based argument position individually unlike how the framework exposes the argument data. Add the number of non-variadic parameters (if any) to the variadic argument tuple element index to ensure the error is aligned to the correct argument in the configuration. + +In this example with two parameters and one variadic parameter, an error is returned for variadic arguments: + +```go +func (f *ExampleFunction) Definition(ctx context.Context, req function.DefinitionRequest, resp *function.DefinitionResponse) { + resp.Definition = function.Definition{ + // ... other fields ... + Parameters: []function.Parameter{ + function.BoolParameter{ + Name: "bool_param", + // ... other fields ... + }, + function.Int64Parameter{ + Name: "int64_param", + // ... other fields ... + }, + }, + VariadicParameter: function.StringParameter{ + Name: "variadic_param", + // ... other fields ... + }, + } +} + +func (f *ExampleFunction) Run(ctx context.Context, req function.RunRequest, resp *function.RunResponse) { + var boolArg bool + var int64Arg int64 + var stringVarg []string + + resp.Error = function.ConcatFuncErrors(resp.Error, req.Arguments.Get(ctx, &boolArg, &int64arg, &stringVarg)) + + for index, element := range stringVarg { + // Added by 2 to match the definition including two parameters. + resp.Error = function.ConcatFuncErrors(resp.Error, function.NewArgumentFuncError(2+index, "example summary: example detail")) + } + + // ... other logic ... +} +``` + +### Setting Result Data + +The framework supports setting a result value into the [`function.RunResponse.Result` field](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/function#RunResponse.Result), which is of the [`function.ResultData` type](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/function#ResultData). The result value must match the return type, otherwise the framework or Terraform will return an error. + +In this example, the return is defined as a string and a string value is set: + +```go +func (f *ExampleFunction) Definition(ctx context.Context, req function.DefinitionRequest, resp *function.DefinitionResponse) { + resp.Definition = function.Definition{ + // ... other fields ... + Return: function.StringReturn{}, + } +} + +func (f *ExampleFunction) Run(ctx context.Context, req function.RunRequest, resp *function.RunResponse) { + // ... other logic ... + + // Value based on the return type. Returns can also use the framework type system. + result := "hardcoded example" + + resp.Error = function.ConcatFuncErrors(resp.Error, resp.Result.Set(ctx, result)) +} +``` + +## Add Function to Provider + +Functions become available to practitioners when they are included in the [provider](/terraform/plugin/framework/providers) implementation via the [`provider.ProviderWithFunctions` interface `Functions` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider#ProviderWithFunctions.Functions). + +In this example, the `EchoFunction` type, which implements the `function.Function` interface, is added to the provider implementation: + +```go +// With the provider.Provider implementation +func (p *ExampleCloudProvider) Functions(_ context.Context) []func() function.Function { + return []func() function.Function{ + func() function.Function { + return &EchoFunction{}, + }, + } +} +``` + +To simplify provider implementations, a named function can be created with the function implementation. + +In this example, the `EchoFunction` code includes an additional `NewEchoFunction` function, which simplifies the provider implementation: + +```go +// With the provider.Provider implementation +func (p *ExampleCloudProvider) Functions(_ context.Context) []func() function.Function { + return []func() function.Function{ + NewEchoFunction, + } +} + +// With the function.Function implementation +func NewEchoFunction() function.Function { + return &EchoFunction{} +} +``` diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/index.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/index.mdx new file mode 100644 index 0000000000..193c4ea1fa --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/index.mdx @@ -0,0 +1,34 @@ +--- +page_title: Provider-defined functions overview +description: >- + Provider-defined functions expose logic beyond Terraform's built-in functions + that practitioners can use to simplify Terraform configurations. Learn how the + plugin framework can help you implement provider-defined functions. +--- + + +# Provider-defined functions + +Functions are an abstraction that allow providers to expose computational logic beyond Terraform's [built-in functions](/terraform/language/functions) and simplify practitioner configurations. Provider-defined functions are supported in Terraform 1.8 and later. + + + +It is possible to add functions to an existing provider. If you do not have an existing provider, you will need to create your own provider to contain the functions. Please see [Getting Started - Code Walkthrough](/terraform/plugin/framework/getting-started/code-walkthrough) to learn how to create your first provider. + + + +## Concepts + +Learn about Terraform's [concepts](/terraform/plugin/framework/functions/concepts) for provider-defined functions, such as intended purpose, example use cases, and terminology. The framework's implementation details, such as naming, are based on these concepts. + +## Implementation + +Learn about how to [implement code](/terraform/plugin/framework/functions/implementation) for a provider-defined function in the framework. + +## Testing + +Learn about how to ensure a provider-defined function implementation works as expected via [unit testing and acceptance testing](/terraform/plugin/framework/functions/testing). + +## Documentation + +Learn about how to [document](/terraform/plugin/framework/functions/documentation) a provider-defined function implementation so practitioners can discover and use the function. diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/parameters/bool.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/parameters/bool.mdx new file mode 100644 index 0000000000..3bf6875623 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/parameters/bool.mdx @@ -0,0 +1,95 @@ +--- +page_title: Boolean function parameters +description: >- + Learn how to use the boolean function parameter type with the Terraform + plugin framework. +--- + +# Boolean function parameters + +Bool function parameters expect a boolean true or false value from a practitioner configuration. Values are accessible in function logic by the Go built-in `bool` type, Go built-in `*bool` type, or the [framework bool type](/terraform/plugin/framework/handling-data/types/bool). + +In this Terraform configuration example, a bool parameter is set to the value `true`: + +```hcl +provider::example::example(true) +``` + +## Function Definition + +Use the [`function.BoolParameter` type](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/function#BoolParameter) in the [function definition](/terraform/plugin/framework/functions/implementation#definition-method) to accept a bool value. + +In this example, a function definition includes a first position bool parameter: + +```go +func (f ExampleFunction) Definition(ctx context.Context, req function.DefinitionRequest, resp *function.DefinitionResponse) { + resp.Definition = function.Definition{ + // ... other Definition fields ... + Parameters: []function.Parameter{ + function.BoolParameter{ + Name: "example", + // ... potentially other BoolParameter fields ... + }, + }, + } +} +``` + +If the bool value should be the element type of a [collection parameter type](/terraform/plugin/framework/functions/parameters#collection-parameter-types), set the `ElementType` field according to the [framework bool type](/terraform/plugin/framework/handling-data/types/bool). Refer to the collection parameter type documentation for additional details. + +If the bool value should be a value type of an [object parameter type](/terraform/plugin/framework/functions/parameters#object-parameter-type), set the `AttributeTypes` map value according to the [framework bool type](/terraform/plugin/framework/handling-data/types/bool). Refer to the object parameter type documentation for additional details. + +### Allow Null Values + +By default, Terraform will not pass null values to the function logic. Use the `AllowNullValue` field to explicitly allow null values, if there is a meaningful distinction that should occur in function logic. Enabling `AllowNullValue` requires using a Go pointer type or [framework bool type](/terraform/plugin/framework/handling-data/types/bool) when reading argument data. + +### Allow Unknown Values + +By default, Terraform will not pass unknown values to the function logic. Use the `AllowUnknownValues` field to explicitly allow unknown values, if there is a meaningful distinction that should occur in function logic. Enabling `AllowUnknownValues` requires using a [framework bool type](/terraform/plugin/framework/handling-data/types/bool) when reading argument data. + +### Custom Types + +You may want to build your own data value and type implementations to allow your provider to combine validation and other behaviors into a reusable bundle. This helps avoid duplication and ensures consistency. These implementations use the `CustomType` field in the parameter type. + +Refer to [Custom Types](/terraform/plugin/framework/handling-data/types/custom) for further details on creating provider-defined types and values. + +### Documentation + +Refer to [function documentation](/terraform/plugin/framework/functions/documentation) for information about the `Name`, `Description`, and `MarkdownDescription` fields available. + +## Reading Argument Data + +The [function implementation](/terraform/plugin/framework/functions/implementation) documentation covers the general methods for reading function argument data in function logic. + +When retrieving the argument value for this parameter: + +* If `CustomType` is set, use its associated value type. +* If `AllowUnknownValues` is enabled, you must use the [framework bool type](/terraform/plugin/framework/handling-data/types/bool). +* If `AllowNullValue` is enabled, you must use the Go built-in `*bool` type or [framework bool type](/terraform/plugin/framework/handling-data/types/bool). +* Otherwise, use the Go built-in `bool` type, Go built-in `*bool` type, or [framework bool type](/terraform/plugin/framework/handling-data/types/bool). + +In this example, a function defines a single bool parameter and accesses its argument value: + +```go +func (f ExampleFunction) Definition(ctx context.Context, req function.DefinitionRequest, resp *function.DefinitionResponse) { + resp.Definition = function.Definition{ + // ... other Definition fields ... + Parameters: []function.Parameter{ + function.BoolParameter{ + Name: "bool_param", + }, + }, + } +} + +func (f ExampleFunction) Run(ctx context.Context, req function.RunRequest, resp *function.RunResponse) { + var boolArg bool + // var boolArg *bool // e.g. with AllowNullValue, where Go nil equals Terraform null + // var boolArg types.Bool // e.g. with AllowUnknownValues or AllowNullValue + + resp.Error = function.ConcatFuncErrors(resp.Error, req.Arguments.Get(ctx, &boolArg)) + + // boolArg is now populated + // ... other logic ... +} +``` diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/parameters/dynamic.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/parameters/dynamic.mdx new file mode 100644 index 0000000000..e088e5c55a --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/parameters/dynamic.mdx @@ -0,0 +1,177 @@ +--- +page_title: Dynamic function parameters +description: >- + Learn how to use dynamic fynction paramters with the Terraform plugin + framework. +--- + +# Dynamic function parameters + + + +Static types should always be preferred over dynamic types, when possible. + +Developers creating a function with a dynamic parameter will need to have extensive knowledge of the [Terraform type system](/terraform/language/expressions/types), as no type conversion will be performed to incoming argument data. + +Refer to [Dynamic Data - Considerations](/terraform/plugin/framework/handling-data/dynamic-data#considerations) for more information. + + + +Dynamic function parameters can receive **any** value type from a practitioner configuration. Values are accessible in function logic by the [framework dynamic type](/terraform/plugin/framework/handling-data/types/dynamic). + +In this Terraform configuration example, a dynamic parameter is set to the boolean value `true`: + +```hcl +provider::example::example(true) +``` + +In this example, the same dynamic parameter is set to a tuple (not a list) of string values `one` and `two`: + +```hcl +provider::example::example(["one", "two"]) +``` + +In this example, the same dynamic parameter is set to an object type with mapped values of `attr1` to `"value1"` and `attr2` to `123`: + +```hcl +provider::example::example({ + attr1 = "value1", + attr2 = 123, +}) +``` + +## Function Definition + +Use the [`function.DynamicParameter` type](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/function#DynamicParameter) in the [function definition](/terraform/plugin/framework/functions/implementation#definition-method) to accept a dynamic value. + +In this example, a function definition includes a first position dynamic parameter: + +```go +func (f ExampleFunction) Definition(ctx context.Context, req function.DefinitionRequest, resp *function.DefinitionResponse) { + resp.Definition = function.Definition{ + // ... other Definition fields ... + Parameters: []function.Parameter{ + function.DynamicParameter{ + Name: "dynamic_param", + // ... potentially other DynamicParameter fields ... + }, + }, + } +} +``` + +Dynamic values are not supported as the element type of a [collection type](/terraform/plugin/framework/handling-data/types#collection-types) or within [collection parameter types](/terraform/plugin/framework/functions/parameters#collection-parameter-types). + +If the dynamic value should be a value type of an [object parameter type](/terraform/plugin/framework/functions/parameters#object-parameter-type), set the `AttributeTypes` map value according to the [framework dynamic type](/terraform/plugin/framework/handling-data/types/dynamic). Refer to the object parameter type documentation for additional details. + +### Allow Null Values + + + +A known dynamic value with an underlying value that contains nulls (such as a list with null element values) will always be sent to the function logic, regardless of the `AllowNullValue` setting. Data handling must always account for this situation. + + + +By default, Terraform will not pass null values to the function logic. Use the `AllowNullValue` field to explicitly allow null values, if there is a meaningful distinction that should occur in function logic. + +### Allow Unknown Values + +By default, Terraform will not pass unknown values to the function logic. Use the `AllowUnknownValues` field to explicitly allow unknown values, if there is a meaningful distinction that should occur in function logic. + +### Custom Types + +You may want to build your own data value and type implementations to allow your provider to combine validation and other behaviors into a reusable bundle. This helps avoid duplication and ensures consistency. These implementations use the `CustomType` field in the parameter type. + +Refer to [Custom Types](/terraform/plugin/framework/handling-data/types/custom) for further details on creating provider-defined types and values. + +### Documentation + +Refer to [function documentation](/terraform/plugin/framework/functions/documentation) for information about the `Name`, `Description`, and `MarkdownDescription` fields available. + +## Reading Argument Data + +The [function implementation](/terraform/plugin/framework/functions/implementation) documentation covers the general methods for reading function argument data in function logic. + +When retrieving the argument value for this parameter: + +* If `CustomType` is set, use its associated value type. +* Otherwise, you must use the [framework dynamic type](/terraform/plugin/framework/handling-data/types/dynamic). + +In this example, a function defines a single dynamic parameter and accesses its argument value: + +```go +func (f ExampleFunction) Definition(ctx context.Context, req function.DefinitionRequest, resp *function.DefinitionResponse) { + resp.Definition = function.Definition{ + // ... other Definition fields ... + Parameters: []function.Parameter{ + function.DynamicParameter{ + Name: "dynamic_param", + // ... potentially other DynamicParameter fields ... + }, + }, + } +} + +func (f ExampleFunction) Run(ctx context.Context, req function.RunRequest, resp *function.RunResponse) { + var dynamicArg types.Dynamic + + resp.Error = function.ConcatFuncErrors(resp.Error, req.Arguments.Get(ctx, &dynamicArg)) + + // dynamicArg is now populated + // ... other logic ... +} +``` + +For more detail on working with dynamic values, see the [framework dynamic type](/terraform/plugin/framework/handling-data/types/dynamic) documentation. + +## Using Dynamic as a Variadic Parameter + +Utilizing `function.DynamicParameter` in the [`VariadicParameter`](/terraform/plugin/framework/functions/implementation#reading-variadic-parameter-argument-data) field will allow zero, one, or more values of **potentially different** types. + +To handle this scenario of multiple values with different types, utilize [`types.Tuple`](/terraform/plugin/framework/handling-data/types/tuple) or [`[]types.Dynamic`](/terraform/plugin/framework/handling-data/types/dynamic) when reading a dynamic variadic argument. + +```go +func (f *ExampleFunction) Definition(ctx context.Context, req function.DefinitionRequest, resp *function.DefinitionResponse) { + resp.Definition = function.Definition{ + // ... other Definition fields ... + VariadicParameter: function.DynamicParameter{ + Name: "variadic_param", + }, + } +} + +func (f *ExampleFunction) Run(ctx context.Context, req function.RunRequest, resp *function.RunResponse) { + var dynValues []types.Dynamic + + resp.Error = function.ConcatFuncErrors(resp.Error, req.Arguments.Get(ctx, &dynValues)) + if resp.Error != nil { + return + } + + for _, dynValue := range dynValues { + if dynValue.IsNull() || dynValue.IsUnknown() { + continue + } + // ... do something with argument value, i.e. dynValue.UnderlyingValue() ... + } + + // ... other logic ... +} + +``` + +In these Terraform configuration examples, the function variadic argument will receive the following value types: + +```hcl +# []types.Dynamic{} +provider::example::example() + +# []types.Dynamic{types.String} +provider::example::example("hello world") + +# []types.Dynamic{types.Bool, types.Number} +provider::example::example(true, 1) + +# []types.Dynamic{types.String, types.Tuple[types.String, types.String], types.List[types.String]} +provider::example::example("hello", ["one", "two"], tolist(["one", "two"])) +``` diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/parameters/float32.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/parameters/float32.mdx new file mode 100644 index 0000000000..423df0cb2c --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/parameters/float32.mdx @@ -0,0 +1,102 @@ +--- +page_title: Float32 function parameters +description: >- + Learn how to use the 32-bit floating point function parameter type with the + Terraform plugin framework. +--- + +# Float32 Function Parameter + + + +Use [Int32 Parameter](/terraform/plugin/framework/functions/parameters/int32) for 32-bit integer numbers. Use [Number Parameter](/terraform/plugin/framework/functions/parameters/number) for arbitrary precision numbers. + + + +Float32 function parameters expect a 32-bit floating point number value from a practitioner configuration. Values are accessible in function logic by the Go built-in `float32` type, Go built-in `*float32` type, or the [framework float32 type](/terraform/plugin/framework/handling-data/types/float32). + +In this Terraform configuration example, a float32 parameter is set to the value `1.23`: + +```hcl +provider::example::example(1.23) +``` + +## Function Definition + +Use the [`function.Float32Parameter` type](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/function#Float32Parameter) in the [function definition](/terraform/plugin/framework/functions/implementation#definition-method) to accept a float32 value. + +In this example, a function definition includes a first position float32 parameter: + +```go +func (f ExampleFunction) Definition(ctx context.Context, req function.DefinitionRequest, resp *function.DefinitionResponse) { + resp.Definition = function.Definition{ + // ... other Definition fields ... + Parameters: []function.Parameter{ + function.Float32Parameter{ + Name: "float32_param", + // ... potentially other Float32Parameter fields ... + }, + }, + } +} +``` + +If the float32 value should be the element type of a [collection parameter type](/terraform/plugin/framework/functions/parameters#collection-parameter-types), set the `ElementType` field according to the [framework float32 type](/terraform/plugin/framework/handling-data/types/float32). Refer to the collection parameter type documentation for additional details. + +If the float32 value should be a value type of an [object parameter type](/terraform/plugin/framework/functions/parameters#object-parameter-type), set the `AttributeTypes` map value according to the [framework float32 type](/terraform/plugin/framework/handling-data/types/float32). Refer to the object parameter type documentation for additional details. + +### Allow Null Values + +By default, Terraform will not pass null values to the function logic. Use the `AllowNullValue` field to explicitly allow null values, if there is a meaningful distinction that should occur in function logic. Enabling `AllowNullValue` requires using a Go pointer type or [framework float32 type](/terraform/plugin/framework/handling-data/types/float32) when reading argument data. + +### Allow Unknown Values + +By default, Terraform will not pass unknown values to the function logic. Use the `AllowUnknownValues` field to explicitly allow unknown values, if there is a meaningful distinction that should occur in function logic. Enabling `AllowUnknownValues` requires using a [framework float32 type](/terraform/plugin/framework/handling-data/types/float32) when reading argument data. + +### Custom Types + +You may want to build your own data value and type implementations to allow your provider to combine validation and other behaviors into a reusable bundle. This helps avoid duplication and ensures consistency. These implementations use the `CustomType` field in the parameter type. + +Refer to [Custom Types](/terraform/plugin/framework/handling-data/types/custom) for further details on creating provider-defined types and values. + +### Documentation + +Refer to [function documentation](/terraform/plugin/framework/functions/documentation) for information about the `Name`, `Description`, and `MarkdownDescription` fields available. + +## Reading Argument Data + +The [function implementation](/terraform/plugin/framework/functions/implementation) documentation covers the general methods for reading function argument data in function logic. + +When retrieving the argument value for this parameter: + +* If `CustomType` is set, use its associated value type. +* If `AllowUnknownValues` is enabled, you must use the [framework float32 type](/terraform/plugin/framework/handling-data/types/float32). +* If `AllowNullValue` is enabled, you must use the Go built-in `*float32` type or [framework float32 type](/terraform/plugin/framework/handling-data/types/float32). +* Otherwise, use the Go built-in `float32` type, Go built-in `*float32` type, or [framework float32 type](/terraform/plugin/framework/handling-data/types/float32). + +In this example, a function defines a single float32 parameter and accesses its argument value: + +```go +func (f ExampleFunction) Definition(ctx context.Context, req function.DefinitionRequest, resp *function.DefinitionResponse) { + resp.Definition = function.Definition{ + // ... other Definition fields ... + Parameters: []function.Parameter{ + function.Float32Parameter{ + Name: "float32_param", + // ... potentially other Float32Parameter fields ... + }, + }, + } +} + +func (f ExampleFunction) Run(ctx context.Context, req function.RunRequest, resp *function.RunResponse) { + var float32Arg float32 + // var float32Arg *float32 // e.g. with AllowNullValue, where Go nil equals Terraform null + // var float32Arg types.Float32 // e.g. with AllowUnknownValues or AllowNullValue + + resp.Error = function.ConcatFuncErrors(resp.Error, req.Arguments.Get(ctx, &float32Arg)) + + // float32Arg is now populated + // ... other logic ... +} +``` diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/parameters/float64.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/parameters/float64.mdx new file mode 100644 index 0000000000..7dbe213ac4 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/parameters/float64.mdx @@ -0,0 +1,102 @@ +--- +page_title: Float64 function parameters +description: >- + Learn how to use the 64-bit floating point function parameter type with the + Terraform plugin framework. +--- + +# Float64 function parameters + + + +Use [Int64 Parameter](/terraform/plugin/framework/functions/parameters/int64) for 64-bit integer numbers. Use [Number Parameter](/terraform/plugin/framework/functions/parameters/number) for arbitrary precision numbers. + + + +Float64 function parameters expect a 64-bit floating point number value from a practitioner configuration. Values are accessible in function logic by the Go built-in `float64` type, Go built-in `*float64` type, or the [framework float64 type](/terraform/plugin/framework/handling-data/types/float64). + +In this Terraform configuration example, a float64 parameter is set to the value `1.23`: + +```hcl +provider::example::example(1.23) +``` + +## Function Definition + +Use the [`function.Float64Parameter` type](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/function#Float64Parameter) in the [function definition](/terraform/plugin/framework/functions/implementation#definition-method) to accept a float64 value. + +In this example, a function definition includes a first position float64 parameter: + +```go +func (f ExampleFunction) Definition(ctx context.Context, req function.DefinitionRequest, resp *function.DefinitionResponse) { + resp.Definition = function.Definition{ + // ... other Definition fields ... + Parameters: []function.Parameter{ + function.Float64Parameter{ + Name: "float64_param", + // ... potentially other Float64Parameter fields ... + }, + }, + } +} +``` + +If the float64 value should be the element type of a [collection parameter type](/terraform/plugin/framework/functions/parameters#collection-parameter-types), set the `ElementType` field according to the [framework float64 type](/terraform/plugin/framework/handling-data/types/float64). Refer to the collection parameter type documentation for additional details. + +If the float64 value should be a value type of an [object parameter type](/terraform/plugin/framework/functions/parameters#object-parameter-type), set the `AttributeTypes` map value according to the [framework float64 type](/terraform/plugin/framework/handling-data/types/float64). Refer to the object parameter type documentation for additional details. + +### Allow Null Values + +By default, Terraform will not pass null values to the function logic. Use the `AllowNullValue` field to explicitly allow null values, if there is a meaningful distinction that should occur in function logic. Enabling `AllowNullValue` requires using a Go pointer type or [framework float64 type](/terraform/plugin/framework/handling-data/types/float64) when reading argument data. + +### Allow Unknown Values + +By default, Terraform will not pass unknown values to the function logic. Use the `AllowUnknownValues` field to explicitly allow unknown values, if there is a meaningful distinction that should occur in function logic. Enabling `AllowUnknownValues` requires using a [framework float64 type](/terraform/plugin/framework/handling-data/types/float64) when reading argument data. + +### Custom Types + +You may want to build your own data value and type implementations to allow your provider to combine validation and other behaviors into a reusable bundle. This helps avoid duplication and ensures consistency. These implementations use the `CustomType` field in the parameter type. + +Refer to [Custom Types](/terraform/plugin/framework/handling-data/types/custom) for further details on creating provider-defined types and values. + +### Documentation + +Refer to [function documentation](/terraform/plugin/framework/functions/documentation) for information about the `Name`, `Description`, and `MarkdownDescription` fields available. + +## Reading Argument Data + +The [function implementation](/terraform/plugin/framework/functions/implementation) documentation covers the general methods for reading function argument data in function logic. + +When retrieving the argument value for this parameter: + +* If `CustomType` is set, use its associated value type. +* If `AllowUnknownValues` is enabled, you must use the [framework float64 type](/terraform/plugin/framework/handling-data/types/float64). +* If `AllowNullValue` is enabled, you must use the Go built-in `*float64` type or [framework float64 type](/terraform/plugin/framework/handling-data/types/float64). +* Otherwise, use the Go built-in `float64` type, Go built-in `*float64` type, or [framework float64 type](/terraform/plugin/framework/handling-data/types/float64). + +In this example, a function defines a single float64 parameter and accesses its argument value: + +```go +func (f ExampleFunction) Definition(ctx context.Context, req function.DefinitionRequest, resp *function.DefinitionResponse) { + resp.Definition = function.Definition{ + // ... other Definition fields ... + Parameters: []function.Parameter{ + function.Float64Parameter{ + Name: "float64_param", + // ... potentially other Float64Parameter fields ... + }, + }, + } +} + +func (f ExampleFunction) Run(ctx context.Context, req function.RunRequest, resp *function.RunResponse) { + var float64Arg float64 + // var float64Arg *float64 // e.g. with AllowNullValue, where Go nil equals Terraform null + // var float64Arg types.Float64 // e.g. with AllowUnknownValues or AllowNullValue + + resp.Error = function.ConcatFuncErrors(resp.Error, req.Arguments.Get(ctx, &float64Arg)) + + // float64Arg is now populated + // ... other logic ... +} +``` diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/parameters/index.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/parameters/index.mdx new file mode 100644 index 0000000000..76b4e6ed2f --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/parameters/index.mdx @@ -0,0 +1,130 @@ +--- +page_title: Function parameters +description: >- + The Terraform plugin framework includes multiple built-in function parameter + types and supports dynamic parameters. Parameters are positional data + arguments in a function definition. +--- + +# Function parameters + +Parameters in [function definitions](/terraform/plugin/framework/functions/implementation#definition-method) describes how data values are passed to the function logic. Every parameter type has an associated [value type](/terraform/plugin/framework/handling-data/types), although this data handling is simplified for function implementations over other provider concepts, such as resource implementations. + +## Available Parameter Types + +Function definitions support the following parameter types: + +- [Primitive](#primitive-parameter-types): Parameter that accepts a single value, such as a boolean, number, or string. +- [Collection](#collection-parameter-types): Parameter that accepts multiple values of a single element type, such as a list, map, or set. +- [Object](#object-parameter-type): Parameter that accepts a structure of explicit attribute names. +- [Dynamic](#dynamic-parameter-type): Parameter that accepts any value type. + +### Primitive Parameter Types + +Parameter types that accepts a single data value, such as a boolean, number, or string. + +| Parameter Type | Use Case | +|----------------|----------| +| [Bool](/terraform/plugin/framework/functions/parameters/bool) | Boolean true or false | +| [Float32](/terraform/plugin/framework/functions/parameters/float32) | 32-bit floating point number | +| [Float64](/terraform/plugin/framework/functions/parameters/float64) | 64-bit floating point number | +| [Int32](/terraform/plugin/framework/functions/parameters/int32) | 32-bit integer number | +| [Int64](/terraform/plugin/framework/functions/parameters/int64) | 64-bit integer number | +| [Number](/terraform/plugin/framework/functions/parameters/number) | Arbitrary precision (generally over 64-bit, up to 512-bit) number | +| [String](/terraform/plugin/framework/functions/parameters/string) | Collection of UTF-8 encoded characters | + +### Collection Parameter Types + +Parameter types that accepts multiple values of a single element type, such as a list, map, or set. + +| Parameter Type | Use Case | +|----------------|----------| +| [List](/terraform/plugin/framework/functions/parameters/list) | Ordered collection of single element type | +| [Map](/terraform/plugin/framework/functions/parameters/map) | Mapping of arbitrary string keys to values of single element type | +| [Set](/terraform/plugin/framework/functions/parameters/set) | Unordered, unique collection of single element type | + +### Object Parameter Type + +Parameter type that accepts a structure of explicit attribute names. + +| Parameter Type | Use Case | +|----------------|----------| +| [Object](/terraform/plugin/framework/functions/parameters/object) | Single structure mapping explicit attribute names | + +### Dynamic Parameter Type + + + +Dynamic value handling is an advanced use case. Prefer static parameter types when possible unless absolutely necessary for your use case. + + + +Parameter that accepts any value type, determined by Terraform at runtime. + +| Parameter Type | Use Case | +|----------------|----------| +| [Dynamic](/terraform/plugin/framework/functions/parameters/dynamic) | Accept any value type of data, determined at runtime. | + +## Parameter Naming + +All parameter types have a `Name` field that is **required**. + +### Missing Parameter Names + +Attempting to use unnamed parameters will generate runtime errors of the following form: + +```text +│ Error: Failed to load plugin schemas +│ +│ Error while loading schemas for plugin components: Failed to obtain provider schema: Could not load the schema for provider registry.terraform.io/cloud_provider/cloud_resource: failed to +│ retrieve schema from provider "registry.terraform.io/cloud_provider/cloud_resource": Invalid Function Definition: When validating the function definition, an implementation issue was +│ found. This is always an issue with the provider and should be reported to the provider developers. +│ +│ Function "example_function" - Parameter at position 0 does not have a name. +``` + +### Parameter Errors + +Parameter names are used in runtime errors to highlight which parameter is causing the issue. For example, using a value that is incompatible with the parameter type will generate an error message such as the following: + +```text +│ Error: Invalid function argument +│ +│ on resource.tf line 10, in resource "example_resource" "example": +│ 10: configurable_attribute = provider::example::example_function("string") +│ ├──────────────── +│ │ while calling provider::example::example_function(bool_param) +│ +│ Invalid value for "bool_param" parameter: a bool is required. +``` + +## Parameter Validation + +Validation handling for provider-defined function parameters can be enabled by using [custom types](/terraform/plugin/framework/handling-data/types/custom#validation). + +Implement the [`function.ValidateableParameter` interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/function#ValidateableParameter) on the custom value type to define and enable validation handling for a provider-defined function parameter, which will automatically raise an error when a value is determined to be invalid. + +```go +// Implementation of the function.ValidateableParameter interface +func (v CustomStringValue) ValidateParameter(ctx context.Context, req function.ValidateParameterRequest, resp *function.ValidateParameterResponse) { + if v.IsNull() || v.IsUnknown() { + return + } + + _, err := time.Parse(time.RFC3339, v.ValueString()) + + if err != nil { + resp.Error = function.NewArgumentFuncError( + req.Position, + "Invalid RFC 3339 String Value: "+ + "An unexpected error occurred while converting a string value that was expected to be RFC 3339 format. "+ + "The RFC 3339 string format is YYYY-MM-DDTHH:MM:SSZ, such as 2006-01-02T15:04:05Z or 2006-01-02T15:04:05+07:00.\n\n"+ + fmt.Sprintf("Position: %d", req.Position)+"\n"+ + "Given Value: "+v.ValueString()+"\n"+ + "Error: "+err.Error(), + ) + } +} +``` + +Refer to [Custom Types](/terraform/plugin/framework/handling-data/types/custom) for further details on creating provider-defined types and values diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/parameters/int32.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/parameters/int32.mdx new file mode 100644 index 0000000000..de87e82b1c --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/parameters/int32.mdx @@ -0,0 +1,102 @@ +--- +page_title: Int32 function parameters +description: >- + Learn how to use the 32-bit integer function parameter type with the + Terraform plugin framework. +--- + +# Int32 function parameters + + + +Use [Float32 Parameter](/terraform/plugin/framework/functions/parameters/float32) for 32-bit floating point numbers. Use [Number Parameter](/terraform/plugin/framework/functions/parameters/number) for arbitrary precision numbers. + + + +Int32 function parameters expect a 32-bit integer number value from a practitioner configuration. Values are accessible in function logic by the Go built-in `int32` type, Go built-in `*int32` type, or the [framework int32 type](/terraform/plugin/framework/handling-data/types/int32). + +In this Terraform configuration example, a int32 parameter is set to the value `123`: + +```hcl +provider::example::example(123) +``` + +## Function Definition + +Use the [`function.Int32Parameter` type](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/function#Int32Parameter) in the [function definition](/terraform/plugin/framework/functions/implementation#definition-method) to accept a int32 value. + +In this example, a function definition includes a first position int32 parameter: + +```go +func (f ExampleFunction) Definition(ctx context.Context, req function.DefinitionRequest, resp *function.DefinitionResponse) { + resp.Definition = function.Definition{ + // ... other Definition fields ... + Parameters: []function.Parameter{ + function.Int32Parameter{ + Name: "int32_param", + // ... potentially other Int32Parameter fields ... + }, + }, + } +} +``` + +If the int32 value should be the element type of a [collection parameter type](/terraform/plugin/framework/functions/parameters#collection-parameter-types), set the `ElementType` field according to the [framework int32 type](/terraform/plugin/framework/handling-data/types/int32). Refer to the collection parameter type documentation for additional details. + +If the int32 value should be a value type of an [object parameter type](/terraform/plugin/framework/functions/parameters#object-parameter-type), set the `AttributeTypes` map value according to the [framework int32 type](/terraform/plugin/framework/handling-data/types/int32). Refer to the object parameter type documentation for additional details. + +### Allow Null Values + +By default, Terraform will not pass null values to the function logic. Use the `AllowNullValue` field to explicitly allow null values, if there is a meaningful distinction that should occur in function logic. Enabling `AllowNullValue` requires using a Go pointer type or [framework int32 type](/terraform/plugin/framework/handling-data/types/int32) when reading argument data. + +### Allow Unknown Values + +By default, Terraform will not pass unknown values to the function logic. Use the `AllowUnknownValues` field to explicitly allow unknown values, if there is a meaningful distinction that should occur in function logic. Enabling `AllowUnknownValues` requires using a [framework int32 type](/terraform/plugin/framework/handling-data/types/int32) when reading argument data. + +### Custom Types + +You may want to build your own data value and type implementations to allow your provider to combine validation and other behaviors into a reusable bundle. This helps avoid duplication and ensures consistency. These implementations use the `CustomType` field in the parameter type. + +Refer to [Custom Types](/terraform/plugin/framework/handling-data/types/custom) for further details on creating provider-defined types and values. + +### Documentation + +Refer to [function documentation](/terraform/plugin/framework/functions/documentation) for information about the `Name`, `Description`, and `MarkdownDescription` fields available. + +## Reading Argument Data + +The [function implementation](/terraform/plugin/framework/functions/implementation) documentation covers the general methods for reading function argument data in function logic. + +When retrieving the argument value for this parameter: + +* If `CustomType` is set, use its associated value type. +* If `AllowUnknownValues` is enabled, you must use the [framework int32 type](/terraform/plugin/framework/handling-data/types/int32). +* If `AllowNullValue` is enabled, you must use the Go built-in `*int32` type or [framework int32 type](/terraform/plugin/framework/handling-data/types/int32). +* Otherwise, use the Go built-in `int32` type, Go built-in `*int32` type, or [framework int32 type](/terraform/plugin/framework/handling-data/types/int32). + +In this example, a function defines a single int32 parameter and accesses its argument value: + +```go +func (f ExampleFunction) Definition(ctx context.Context, req function.DefinitionRequest, resp *function.DefinitionResponse) { + resp.Definition = function.Definition{ + // ... other Definition fields ... + Parameters: []function.Parameter{ + function.Int32Parameter{ + Name: "int32_param", + // ... potentially other Int32Parameter fields ... + }, + }, + } +} + +func (f ExampleFunction) Run(ctx context.Context, req function.RunRequest, resp *function.RunResponse) { + var int32Arg int32 + // var int32Arg *int32 // e.g. with AllowNullValue, where Go nil equals Terraform null + // var int32Arg types.Int32 // e.g. with AllowUnknownValues or AllowNullValue + + resp.Error = function.ConcatFuncErrors(resp.Error, req.Arguments.Get(ctx, &int32Arg)) + + // int32Arg is now populated + // ... other logic ... +} +``` diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/parameters/int64.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/parameters/int64.mdx new file mode 100644 index 0000000000..5e84115168 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/parameters/int64.mdx @@ -0,0 +1,102 @@ +--- +page_title: Int64 function parameters +description: >- + Learn how to use the 64-bit integer function parameter type with the + Terraform plugin framework. +--- + +# Int64 function parameters + + + +Use [Float64 Parameter](/terraform/plugin/framework/functions/parameters/float64) for 64-bit floating point numbers. Use [Number Parameter](/terraform/plugin/framework/functions/parameters/number) for arbitrary precision numbers. + + + +Int64 function parameters expect a 64-bit integer number value from a practitioner configuration. Values are accessible in function logic by the Go built-in `int64` type, Go built-in `*int64` type, or the [framework int64 type](/terraform/plugin/framework/handling-data/types/int64). + +In this Terraform configuration example, a int64 parameter is set to the value `123`: + +```hcl +provider::example::example(123) +``` + +## Function Definition + +Use the [`function.Int64Parameter` type](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/function#Int64Parameter) in the [function definition](/terraform/plugin/framework/functions/implementation#definition-method) to accept a int64 value. + +In this example, a function definition includes a first position int64 parameter: + +```go +func (f ExampleFunction) Definition(ctx context.Context, req function.DefinitionRequest, resp *function.DefinitionResponse) { + resp.Definition = function.Definition{ + // ... other Definition fields ... + Parameters: []function.Parameter{ + function.Int64Parameter{ + Name: "int64_param", + // ... potentially other Int64Parameter fields ... + }, + }, + } +} +``` + +If the int64 value should be the element type of a [collection parameter type](/terraform/plugin/framework/functions/parameters#collection-parameter-types), set the `ElementType` field according to the [framework int64 type](/terraform/plugin/framework/handling-data/types/int64). Refer to the collection parameter type documentation for additional details. + +If the int64 value should be a value type of an [object parameter type](/terraform/plugin/framework/functions/parameters#object-parameter-type), set the `AttributeTypes` map value according to the [framework int64 type](/terraform/plugin/framework/handling-data/types/int64). Refer to the object parameter type documentation for additional details. + +### Allow Null Values + +By default, Terraform will not pass null values to the function logic. Use the `AllowNullValue` field to explicitly allow null values, if there is a meaningful distinction that should occur in function logic. Enabling `AllowNullValue` requires using a Go pointer type or [framework int64 type](/terraform/plugin/framework/handling-data/types/int64) when reading argument data. + +### Allow Unknown Values + +By default, Terraform will not pass unknown values to the function logic. Use the `AllowUnknownValues` field to explicitly allow unknown values, if there is a meaningful distinction that should occur in function logic. Enabling `AllowUnknownValues` requires using a [framework int64 type](/terraform/plugin/framework/handling-data/types/int64) when reading argument data. + +### Custom Types + +You may want to build your own data value and type implementations to allow your provider to combine validation and other behaviors into a reusable bundle. This helps avoid duplication and ensures consistency. These implementations use the `CustomType` field in the parameter type. + +Refer to [Custom Types](/terraform/plugin/framework/handling-data/types/custom) for further details on creating provider-defined types and values. + +### Documentation + +Refer to [function documentation](/terraform/plugin/framework/functions/documentation) for information about the `Name`, `Description`, and `MarkdownDescription` fields available. + +## Reading Argument Data + +The [function implementation](/terraform/plugin/framework/functions/implementation) documentation covers the general methods for reading function argument data in function logic. + +When retrieving the argument value for this parameter: + +* If `CustomType` is set, use its associated value type. +* If `AllowUnknownValues` is enabled, you must use the [framework int64 type](/terraform/plugin/framework/handling-data/types/int64). +* If `AllowNullValue` is enabled, you must use the Go built-in `*int64` type or [framework int64 type](/terraform/plugin/framework/handling-data/types/int64). +* Otherwise, use the Go built-in `int64` type, Go built-in `*int64` type, or [framework int64 type](/terraform/plugin/framework/handling-data/types/int64). + +In this example, a function defines a single int64 parameter and accesses its argument value: + +```go +func (f ExampleFunction) Definition(ctx context.Context, req function.DefinitionRequest, resp *function.DefinitionResponse) { + resp.Definition = function.Definition{ + // ... other Definition fields ... + Parameters: []function.Parameter{ + function.Int64Parameter{ + Name: "int64_param", + // ... potentially other Int64Parameter fields ... + }, + }, + } +} + +func (f ExampleFunction) Run(ctx context.Context, req function.RunRequest, resp *function.RunResponse) { + var int64Arg int64 + // var int64Arg *int64 // e.g. with AllowNullValue, where Go nil equals Terraform null + // var int64Arg types.Int64 // e.g. with AllowUnknownValues or AllowNullValue + + resp.Error = function.ConcatFuncErrors(resp.Error, req.Arguments.Get(ctx, &int64Arg)) + + // int64Arg is now populated + // ... other logic ... +} +``` diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/parameters/list.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/parameters/list.mdx new file mode 100644 index 0000000000..edbca773a1 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/parameters/list.mdx @@ -0,0 +1,103 @@ +--- +page_title: List function parameters +description: >- + Learn how to use the list function parameter type with the + Terraform plugin framework. +--- + +# List function parameters + +List function parameters expect an ordered collection of single element type value from a practitioner configuration. Values are accessible in function logic by a Go slice of an appropriate pointer type to match the element type `[]*T` or the [framework list type](/terraform/plugin/framework/handling-data/types/list). + +In this Terraform configuration example, a list of string parameter is set to the ordered collection values `one` and `two`: + +```hcl +provider::example::example(["one", "two"]) +``` + +## Function Definition + +Use the [`function.ListParameter` type](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/function#ListParameter) in the [function definition](/terraform/plugin/framework/functions/implementation#definition-method) to accept a list value. + +The `ElementType` field must be defined, which represents the single [framework value type](/terraform/plugin/framework/handling-data/types) of every element of the list. An element type may itself contain further collection or object types, if necessary. + +In this example, a function definition includes a first position list of string parameter: + +```go +func (f ExampleFunction) Definition(ctx context.Context, req function.DefinitionRequest, resp *function.DefinitionResponse) { + resp.Definition = function.Definition{ + // ... other Definition fields ... + Parameters: []function.Parameter{ + function.ListParameter{ + ElementType: types.StringType, + Name: "list_param", + // ... potentially other ListParameter fields ... + }, + }, + } +} +``` + +If the list value should be the element type of another [collection parameter type](/terraform/plugin/framework/functions/parameters#collection-parameter-types), set the `ElementType` field according to the [framework list type](/terraform/plugin/framework/handling-data/types/list). Refer to the collection parameter type documentation for additional details. + +If the list value should be a value type of an [object parameter type](/terraform/plugin/framework/functions/parameters#object-parameter-type), set the `AttributeTypes` map value according to the [framework list type](/terraform/plugin/framework/handling-data/types/list). Refer to the object parameter type documentation for additional details. + +### Allow Null Values + + + +A known list value with null element values will always be sent to the function logic, regardless of the `AllowNullValue` setting. Data handling must always account for this situation. + + + +By default, Terraform will not pass null values to the function logic. Use the `AllowNullValue` field to explicitly allow null values, if there is a meaningful distinction that should occur in function logic. Enabling `AllowNullValue` requires no changes when reading argument data. + +### Allow Unknown Values + +By default, Terraform will not pass unknown values to the function logic. Use the `AllowUnknownValues` field to explicitly allow unknown values, if there is a meaningful distinction that should occur in function logic. Enabling `AllowUnknownValues` requires using a [framework list type](/terraform/plugin/framework/handling-data/types/list) when reading argument data. + +### Custom Types + +You may want to build your own data value and type implementations to allow your provider to combine validation and other behaviors into a reusable bundle. This helps avoid duplication and ensures consistency. These implementations use the `CustomType` field in the parameter type. + +Refer to [Custom Types](/terraform/plugin/framework/handling-data/types/custom) for further details on creating provider-defined types and values. + +### Documentation + +Refer to [function documentation](/terraform/plugin/framework/functions/documentation) for information about the `Name`, `Description`, and `MarkdownDescription` fields available. + +## Reading Argument Data + +The [function implementation](/terraform/plugin/framework/functions/implementation) documentation covers the general methods for reading function argument data in function logic. + +When retrieving the argument value for this parameter: + +* If `CustomType` is set, use its associated value type. +* If `AllowUnknownValues` is enabled, you must use the [framework list type](/terraform/plugin/framework/handling-data/types/list). +* Otherwise, use the Go slice of an appropriate pointer type to match the element type `[]*T` or [framework list type](/terraform/plugin/framework/handling-data/types/list). + +In this example, a function defines a single list of string parameter and accesses its argument value: + +```go +func (f ExampleFunction) Definition(ctx context.Context, req function.DefinitionRequest, resp *function.DefinitionResponse) { + resp.Definition = function.Definition{ + // ... other Definition fields ... + Parameters: []function.Parameter{ + function.ListParameter{ + ElementType: types.StringType, + Name: "list_param", + }, + }, + } +} + +func (f ExampleFunction) Run(ctx context.Context, req function.RunRequest, resp *function.RunResponse) { + var listArg []*string // Go nil equals Terraform null + // var listArg types.List // e.g. with AllowUnknownValues + + resp.Error = function.ConcatFuncErrors(resp.Error, req.Arguments.Get(ctx, &listArg)) + + // listArg is now populated + // ... other logic ... +} +``` diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/parameters/map.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/parameters/map.mdx new file mode 100644 index 0000000000..49d35d893c --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/parameters/map.mdx @@ -0,0 +1,107 @@ +--- +page_title: Map function parameters +description: >- + Learn how to use the map function parameter type with the + Terraform plugin framework. +--- + +# List function parameters + +Map function parameters expect a mapping of arbitrary string keys to values of single element type from a practitioner configuration. Values are accessible in function logic by a Go map of string keys to values of an appropriate pointer type to match the element type `map[string]*T` or the [framework map type](/terraform/plugin/framework/handling-data/types/map). + +In this Terraform configuration example, a map of string parameter is set to the mapped values of `"key1"` to `"value1"` and `"key2"` to `"value2"`: + +```hcl +provider::example::example({ + "key1" = "value1", + "key2" = "value2", +}) +``` + +## Function Definition + +Use the [`function.MapParameter` type](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/function#MapParameter) in the [function definition](/terraform/plugin/framework/functions/implementation#definition-method) to accept a map value. + +The `ElementType` field must be defined, which represents the single [framework value type](/terraform/plugin/framework/handling-data/types) of every element of the map. An element type may itself contain further collection or object types, if necessary. + +In this example, a function definition includes a first position map of string parameter: + +```go +func (f ExampleFunction) Definition(ctx context.Context, req function.DefinitionRequest, resp *function.DefinitionResponse) { + resp.Definition = function.Definition{ + // ... other Definition fields ... + Parameters: []function.Parameter{ + function.MapParameter{ + ElementType: types.StringType, + Name: "map_param", + // ... potentially other MapParameter fields ... + }, + }, + } +} +``` + +If the map value should be the element type of another [collection parameter type](/terraform/plugin/framework/functions/parameters#collection-parameter-types), set the `ElementType` field according to the [framework map type](/terraform/plugin/framework/handling-data/types/map). Refer to the collection parameter type documentation for additional details. + +If the map value should be a value type of an [object parameter type](/terraform/plugin/framework/functions/parameters#object-parameter-type), set the `AttributeTypes` map value according to the [framework map type](/terraform/plugin/framework/handling-data/types/map). Refer to the object parameter type documentation for additional details. + +### Allow Null Values + + + +A known map value with null element values will always be sent to the function logic, regardless of the `AllowNullValue` setting. Data handling must always account for this situation. + + + +By default, Terraform will not pass null values to the function logic. Use the `AllowNullValue` field to explicitly allow null values, if there is a meaningful distinction that should occur in function logic. Enabling `AllowNullValue` requires no changes when reading argument data. + +### Allow Unknown Values + +By default, Terraform will not pass unknown values to the function logic. Use the `AllowUnknownValues` field to explicitly allow unknown values, if there is a meaningful distinction that should occur in function logic. Enabling `AllowUnknownValues` requires using a [framework map type](/terraform/plugin/framework/handling-data/types/map) when reading argument data. + +### Custom Types + +You may want to build your own data value and type implementations to allow your provider to combine validation and other behaviors into a reusable bundle. This helps avoid duplication and ensures consistency. These implementations use the `CustomType` field in the parameter type. + +Refer to [Custom Types](/terraform/plugin/framework/handling-data/types/custom) for further details on creating provider-defined types and values. + +### Documentation + +Refer to [function documentation](/terraform/plugin/framework/functions/documentation) for information about the `Name`, `Description`, and `MarkdownDescription` fields available. + +## Reading Argument Data + +The [function implementation](/terraform/plugin/framework/functions/implementation) documentation covers the general methods for reading function argument data in function logic. + +When retrieving the argument value for this parameter: + +* If `CustomType` is set, use its associated value type. +* If `AllowUnknownValues` is enabled, you must use the [framework map type](/terraform/plugin/framework/handling-data/types/map). +* Otherwise, use the Go map of string keys to values of an appropriate pointer type to match the element type `map[string]*T` or [framework map type](/terraform/plugin/framework/handling-data/types/map). + +In this example, a function defines a single map of string parameter and accesses its argument value: + +```go +func (f ExampleFunction) Definition(ctx context.Context, req function.DefinitionRequest, resp *function.DefinitionResponse) { + resp.Definition = function.Definition{ + // ... other Definition fields ... + Parameters: []function.Parameter{ + function.MapParameter{ + ElementType: types.StringType, + Name: "map_param", + // ... potentially other MapParameter fields ... + }, + }, + } +} + +func (f ExampleFunction) Run(ctx context.Context, req function.RunRequest, resp *function.RunResponse) { + var mapArg map[string]*string // Go nil equals Terraform null + // var mapArg types.Map // e.g. with AllowUnknownValues + + resp.Error = function.ConcatFuncErrors(resp.Error, req.Arguments.Get(ctx, &mapArg)) + + // mapArg is now populated + // ... other logic ... +} +``` diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/parameters/number.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/parameters/number.mdx new file mode 100644 index 0000000000..256d521ef3 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/parameters/number.mdx @@ -0,0 +1,100 @@ +--- +page_title: Number function parameters +description: >- + Learn how to use the arbitrary precision number function parameter type with + the Terraform plugin framework. +--- + +# Number function parameters + + + +Use [Float64 Parameter](/terraform/plugin/framework/functions/parameters/float64) for 64-bit floating point numbers. Use [Int64 Parameter](/terraform/plugin/framework/functions/parameters/int64) for 64-bit integer numbers. + + + +Number function parameters expect an arbitrary precision (generally over 64-bit, up to 512-bit) number value from a practitioner configuration. Values are accessible in function logic by the Go built-in `*big.Float` type or the [framework number type](/terraform/plugin/framework/handling-data/types/number). + +In this Terraform configuration example, a number parameter is set to the value greater than 64 bits: + +```hcl +provider::example::example(pow(2, 64) + 1) +``` + +## Function Definition + +Use the [`function.NumberParameter` type](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/function#NumberParameter) in the [function definition](/terraform/plugin/framework/functions/implementation#definition-method) to accept a number value. + +In this example, a function definition includes a first position number parameter: + +```go +func (f ExampleFunction) Definition(ctx context.Context, req function.DefinitionRequest, resp *function.DefinitionResponse) { + resp.Definition = function.Definition{ + // ... other Definition fields ... + Parameters: []function.Parameter{ + function.NumberParameter{ + Name: "number_param", + // ... potentially other NumberParameter fields ... + }, + }, + } +} +``` + +If the number value should be the element type of a [collection parameter type](/terraform/plugin/framework/functions/parameters#collection-parameter-types), set the `ElementType` field according to the [framework number type](/terraform/plugin/framework/handling-data/types/number). Refer to the collection parameter type documentation for additional details. + +If the number value should be a value type of an [object parameter type](/terraform/plugin/framework/functions/parameters#object-parameter-type), set the `AttributeTypes` map value according to the [framework number type](/terraform/plugin/framework/handling-data/types/number). Refer to the object parameter type documentation for additional details. + +### Allow Null Values + +By default, Terraform will not pass null values to the function logic. Use the `AllowNullValue` field to explicitly allow null values, if there is a meaningful distinction that should occur in function logic. Enabling `AllowNullValue` requires using a Go pointer type or [framework number type](/terraform/plugin/framework/handling-data/types/number) when reading argument data. + +### Allow Unknown Values + +By default, Terraform will not pass unknown values to the function logic. Use the `AllowUnknownValues` field to explicitly allow unknown values, if there is a meaningful distinction that should occur in function logic. Enabling `AllowUnknownValues` requires using a [framework number type](/terraform/plugin/framework/handling-data/types/number) when reading argument data. + +### Custom Types + +You may want to build your own data value and type implementations to allow your provider to combine validation and other behaviors into a reusable bundle. This helps avoid duplication and ensures consistency. These implementations use the `CustomType` field in the parameter type. + +Refer to [Custom Types](/terraform/plugin/framework/handling-data/types/custom) for further details on creating provider-defined types and values. + +### Documentation + +Refer to [function documentation](/terraform/plugin/framework/functions/documentation) for information about the `Name`, `Description`, and `MarkdownDescription` fields available. + +## Reading Argument Data + +The [function implementation](/terraform/plugin/framework/functions/implementation) documentation covers the general methods for reading function argument data in function logic. + +When retrieving the argument value for this parameter: + +* If `CustomType` is set, use its associated value type. +* If `AllowUnknownValues` is enabled, you must use the [framework number type](/terraform/plugin/framework/handling-data/types/number). +* Otherwise, use the Go built-in `*big.Float` type or [framework number type](/terraform/plugin/framework/handling-data/types/number). + +In this example, a function defines a single number parameter and accesses its argument value: + +```go +func (f ExampleFunction) Definition(ctx context.Context, req function.DefinitionRequest, resp *function.DefinitionResponse) { + resp.Definition = function.Definition{ + // ... other Definition fields ... + Parameters: []function.Parameter{ + function.NumberParameter{ + Name: "number_param", + // ... potentially other NumberParameter fields ... + }, + }, + } +} + +func (f ExampleFunction) Run(ctx context.Context, req function.RunRequest, resp *function.RunResponse) { + var numberArg *big.Float + // var numberArg types.Number // e.g. with AllowUnknownValues or AllowNullValue + + resp.Error = function.ConcatFuncErrors(resp.Error, req.Arguments.Get(ctx, &numberArg)) + + // numberArg is now populated + // ... other logic ... +} +``` diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/parameters/object.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/parameters/object.mdx new file mode 100644 index 0000000000..41bdcbae42 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/parameters/object.mdx @@ -0,0 +1,128 @@ +--- +page_title: Object function parameters +description: >- + Learn how to use the object function parameter type with + the Terraform plugin framework. +--- + +# Object function parameters + +Object function parameters expect a single structure mapping explicit attribute names to type definitions from a practitioner configuration. Values are accessible in function logic by a Go structure type annotated with `tfsdk` field tags or the [framework object type](/terraform/plugin/framework/handling-data/types/object). + +Configurations must include all object attributes or a configuration error is raised. Configurations explicitly setting object attribute values to `null` will prevent this type of configuration error while leaving that object attribute value unset. The `AllowNullValue` setting does not need to be enabled for object attribute `null` values to work in this manner. + +In this Terraform configuration example, a object parameter is set to the mapped values of `attr1` to `"value1"`, `attr2` to `123`, and `attr3` to `null`: + +```hcl +provider::example::example({ + attr1 = "value1" + attr2 = 123 + attr3 = null +}) +``` + +## Function Definition + +Use the [`function.ObjectParameter` type](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/function#ObjectParameter) in the [function definition](/terraform/plugin/framework/functions/implementation#definition-method) to accept an object value. + +The `AttributeTypes` field must be defined, which represents a mapping of attribute names to [framework value types](/terraform/plugin/framework/handling-data/types). An attribute type may itself contain further collection or object types, if necessary. + +In this example, a function definition includes a first position object parameter: + +```go +func (f ExampleFunction) Definition(ctx context.Context, req function.DefinitionRequest, resp *function.DefinitionResponse) { + resp.Definition = function.Definition{ + // ... other Definition fields ... + Parameters: []function.Parameter{ + function.ObjectParameter{ + AttributeTypes: map[string]attr.Type{ + "attr1": types.StringType, + "attr2": types.Int64Type, + "attr3": types.BoolType, + }, + Name: "object_param", + // ... potentially other ObjectParameter fields ... + }, + }, + } +} +``` + +If the map value should be the element type of another [collection parameter type](/terraform/plugin/framework/functions/parameters#collection-parameter-types), set the `ElementType` field according to the [framework object type](/terraform/plugin/framework/handling-data/types/object). Refer to the collection parameter type documentation for additional details. + +If the map value should be a value type of an [object parameter type](/terraform/plugin/framework/functions/parameters#object-parameter-type), set the `AttributeTypes` map value according to the [framework object type](/terraform/plugin/framework/handling-data/types/object). Refer to the object parameter type documentation for additional details. + +### Allow Null Values + + + +A known object value with null attribute values will always be sent to the function logic, regardless of the `AllowNullValue` setting. Data handling must always account for this situation. + + + +By default, Terraform will not pass null values to the function logic. Use the `AllowNullValue` field to explicitly allow null values, if there is a meaningful distinction that should occur in function logic. Enabling `AllowNullValue` requires no changes when reading argument data. + +### Allow Unknown Values + +By default, Terraform will not pass unknown values to the function logic. Use the `AllowUnknownValues` field to explicitly allow unknown values, if there is a meaningful distinction that should occur in function logic. Enabling `AllowUnknownValues` requires using a [framework object type](/terraform/plugin/framework/handling-data/types/object) when reading argument data. + +### Custom Types + +You may want to build your own data value and type implementations to allow your provider to combine validation and other behaviors into a reusable bundle. This helps avoid duplication and ensures consistency. These implementations use the `CustomType` field in the parameter type. + +Refer to [Custom Types](/terraform/plugin/framework/handling-data/types/custom) for further details on creating provider-defined types and values. + +### Documentation + +Refer to [function documentation](/terraform/plugin/framework/functions/documentation) for information about the `Name`, `Description`, and `MarkdownDescription` fields available. + +## Reading Argument Data + +The [function implementation](/terraform/plugin/framework/functions/implementation) documentation covers the general methods for reading function argument data in function logic. + +When retrieving the argument value for this parameter: + +* If `CustomType` is set, use its associated value type. +* If `AllowUnknownValues` is enabled, you must use the [framework object type](/terraform/plugin/framework/handling-data/types/object). +* If `AllowNullValue` is enabled, you must use a pointer to the Go structure type annotated with `tfsdk` field tags or the [framework object type](/terraform/plugin/framework/handling-data/types/object). +* Otherwise, use the Go structure type annotated with `tfsdk` field tags or [framework object type](/terraform/plugin/framework/handling-data/types/object). + +In this example, a function defines a single object parameter and accesses its argument value: + +```go +func (f ExampleFunction) Definition(ctx context.Context, req function.DefinitionRequest, resp *function.DefinitionResponse) { + resp.Definition = function.Definition{ + // ... other Definition fields ... + Parameters: []function.Parameter{ + function.ObjectParameter{ + AttributeTypes: map[string]attr.Type{ + "attr1": types.StringType, + "attr2": types.Int64Type, + "attr3": types.BoolType, + }, + Name: "object_param", + }, + }, + } +} + +func (f ExampleFunction) Run(ctx context.Context, req function.RunRequest, resp *function.RunResponse) { + var objectArg struct{ + Attr1 *string `tfsdk:"attr1"` + Attr2 *int64 `tfsdk:"attr2"` + Attr3 *bool `tfsdk:"attr3"` + } + // e.g. with AllowNullValues + // var objectArg *struct{ + // Attr1 *string `tfsdk:"attr1"` + // Attr2 *int64 `tfsdk:"attr2"` + // Attr3 *bool `tfsdk:"attr3"` + // } + // var objectArg types.Object // e.g. with AllowUnknownValues or AllowNullValues + + resp.Error = function.ConcatFuncErrors(resp.Error, req.Arguments.Get(ctx, &objectArg)) + + // objectArg is now populated + // ... other logic ... +} +``` diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/parameters/set.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/parameters/set.mdx new file mode 100644 index 0000000000..dc53f9952e --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/parameters/set.mdx @@ -0,0 +1,104 @@ +--- +page_title: Set function parameters +description: >- + Learn how to use the set function parameter type with + the Terraform plugin framework. +--- + +# Set function parameters + +Set function parameters expect an unordered, unique collection of single element type value from a practitioner configuration. Values are accessible in function logic by a Go slice of an appropriate pointer type to match the element type `[]*T` or the [framework set type](/terraform/plugin/framework/handling-data/types/set). + +In this Terraform configuration example, a set of string parameter is set to the collection values `one` and `two`: + +```hcl +provider::example::example(["one", "two"]) +``` + +## Function Definition + +Use the [`function.SetParameter` type](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/function#SetParameter) in the [function definition](/terraform/plugin/framework/functions/implementation#definition-method) to accept a set value. + +The `ElementType` field must be defined, which represents the single [framework value type](/terraform/plugin/framework/handling-data/types) of every element of the set. An element type may itself contain further collection or object types, if necessary. + +In this example, a function definition includes a first position set of string parameter: + +```go +func (f ExampleFunction) Definition(ctx context.Context, req function.DefinitionRequest, resp *function.DefinitionResponse) { + resp.Definition = function.Definition{ + // ... other Definition fields ... + Parameters: []function.Parameter{ + function.SetParameter{ + ElementType: types.StringType, + Name: "set_param", + // ... potentially other SetParameter fields ... + }, + }, + } +} +``` + +If the set value should be the element type of another [collection parameter type](/terraform/plugin/framework/functions/parameters#collection-parameter-types), set the `ElementType` field according to the [framework set type](/terraform/plugin/framework/handling-data/types/set). Refer to the collection parameter type documentation for additional details. + +If the set value should be a value type of an [object parameter type](/terraform/plugin/framework/functions/parameters#object-parameter-type), set the `AttributeTypes` map value according to the [framework set type](/terraform/plugin/framework/handling-data/types/set). Refer to the object parameter type documentation for additional details. + +### Allow Null Values + + + +A known set value with null element values will always be sent to the function logic, regardless of the `AllowNullValue` setting. Data handling must always account for this situation. + + + +By default, Terraform will not pass null values to the function logic. Use the `AllowNullValue` field to explicitly allow null values, if there is a meaningful distinction that should occur in function logic. Enabling `AllowNullValue` requires no changes when reading argument data. + +### Allow Unknown Values + +By default, Terraform will not pass unknown values to the function logic. Use the `AllowUnknownValues` field to explicitly allow unknown values, if there is a meaningful distinction that should occur in function logic. Enabling `AllowUnknownValues` requires using a [framework set type](/terraform/plugin/framework/handling-data/types/set) when reading argument data. + +### Custom Types + +You may want to build your own data value and type implementations to allow your provider to combine validation and other behaviors into a reusable bundle. This helps avoid duplication and ensures consistency. These implementations use the `CustomType` field in the parameter type. + +Refer to [Custom Types](/terraform/plugin/framework/handling-data/types/custom) for further details on creating provider-defined types and values. + +### Documentation + +Refer to [function documentation](/terraform/plugin/framework/functions/documentation) for information about the `Name`, `Description`, and `MarkdownDescription` fields available. + +## Reading Argument Data + +The [function implementation](/terraform/plugin/framework/functions/implementation) documentation covers the general methods for reading function argument data in function logic. + +When retrieving the argument value for this parameter: + +* If `CustomType` is set, use its associated value type. +* If `AllowUnknownValues` is enabled, you must use the [framework set type](/terraform/plugin/framework/handling-data/types/set). +* Otherwise, use the Go slice of an appropriate pointer type to match the element type `[]*T` or [framework set type](/terraform/plugin/framework/handling-data/types/set). + +In this example, a function defines a single set of string parameter and accesses its argument value: + +```go +func (f ExampleFunction) Definition(ctx context.Context, req function.DefinitionRequest, resp *function.DefinitionResponse) { + resp.Definition = function.Definition{ + // ... other Definition fields ... + Parameters: []function.Parameter{ + function.SetParameter{ + ElementType: types.StringType, + Name: "set_param", + // ... potentially other SetParameter fields ... + }, + }, + } +} + +func (f ExampleFunction) Run(ctx context.Context, req function.RunRequest, resp *function.RunResponse) { + var setArg []*string // Go nil equals Terraform null + // var setArg types.Set // e.g. with AllowUnknownValues + + resp.Error = function.ConcatFuncErrors(resp.Error, req.Arguments.Get(ctx, &setArg)) + + // setArg is now populated + // ... other logic ... +} +``` diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/parameters/string.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/parameters/string.mdx new file mode 100644 index 0000000000..bb449009c3 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/parameters/string.mdx @@ -0,0 +1,96 @@ +--- +page_title: String function parameters +description: >- + Learn how to use the string function parameter type with + the Terraform plugin framework. +--- + +# String function parameters + +String function parameters expect a collection of UTF-8 encoded bytes from a practitioner configuration. Values are accessible in function logic by the Go built-in `string` type, Go built-in `*string` type, or the [framework string type](/terraform/plugin/framework/handling-data/types/string). + +In this Terraform configuration example, a string parameter is set to the value `"hello world"`: + +```hcl +provider::example::example("hello world") +``` + +## Function Definition + +Use the [`function.StringParameter` type](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/function#StringParameter) in the [function definition](/terraform/plugin/framework/functions/implementation#definition-method) to accept a string value. + +In this example, a function definition includes a first position string parameter: + +```go +func (f ExampleFunction) Definition(ctx context.Context, req function.DefinitionRequest, resp *function.DefinitionResponse) { + resp.Definition = function.Definition{ + // ... other Definition fields ... + Parameters: []function.Parameter{ + function.StringParameter{ + Name: "string_param", + // ... potentially other StringParameter fields ... + }, + }, + } +} +``` + +If the string value should be the element type of a [collection parameter type](/terraform/plugin/framework/functions/parameters#collection-parameter-types), set the `ElementType` field according to the [framework string type](/terraform/plugin/framework/handling-data/types/string). Refer to the collection parameter type documentation for additional details. + +If the string value should be a value type of an [object parameter type](/terraform/plugin/framework/functions/parameters#object-parameter-type), set the `AttributeTypes` map value according to the [framework string type](/terraform/plugin/framework/handling-data/types/string). Refer to the object parameter type documentation for additional details. + +### Allow Null Values + +By default, Terraform will not pass null values to the function logic. Use the `AllowNullValue` field to explicitly allow null values, if there is a meaningful distinction that should occur in function logic. Enabling `AllowNullValue` requires using a Go pointer type or [framework string type](/terraform/plugin/framework/handling-data/types/string) when reading argument data. + +### Allow Unknown Values + +By default, Terraform will not pass unknown values to the function logic. Use the `AllowUnknownValues` field to explicitly allow unknown values, if there is a meaningful distinction that should occur in function logic. Enabling `AllowUnknownValues` requires using a [framework string type](/terraform/plugin/framework/handling-data/types/string) when reading argument data. + +### Custom Types + +You may want to build your own data value and type implementations to allow your provider to combine validation and other behaviors into a reusable bundle. This helps avoid duplication and ensures consistency. These implementations use the `CustomType` field in the parameter type. + +Refer to [Custom Types](/terraform/plugin/framework/handling-data/types/custom) for further details on creating provider-defined types and values. + +### Documentation + +Refer to [function documentation](/terraform/plugin/framework/functions/documentation) for information about the `Name`, `Description`, and `MarkdownDescription` fields available. + +## Reading Argument Data + +The [function implementation](/terraform/plugin/framework/functions/implementation) documentation covers the general methods for reading function argument data in function logic. + +When retrieving the argument value for this parameter: + +* If `CustomType` is set, use its associated value type. +* If `AllowUnknownValues` is enabled, you must use the [framework string type](/terraform/plugin/framework/handling-data/types/string). +* If `AllowNullValue` is enabled, you must use the Go built-in `*string` type or [framework string type](/terraform/plugin/framework/handling-data/types/string). +* Otherwise, use the Go built-in `string` type, Go built-in `*string` type, or [framework string type](/terraform/plugin/framework/handling-data/types/string). + +In this example, a function defines a single string parameter and accesses its argument value: + +```go +func (f ExampleFunction) Definition(ctx context.Context, req function.DefinitionRequest, resp *function.DefinitionResponse) { + resp.Definition = function.Definition{ + // ... other Definition fields ... + Parameters: []function.Parameter{ + function.StringParameter{ + Name: "string_param", + // ... potentially other StringParameter fields ... + }, + }, + } +} + +func (f ExampleFunction) Run(ctx context.Context, req function.RunRequest, resp *function.RunResponse) { + var stringArg string + // var stringArg *string // e.g. with AllowNullValue, where Go nil equals Terraform null + // var stringArg types.String // e.g. with AllowUnknownValues or AllowNullValue + + resp.Error = function.ConcatFuncErrors(resp.Error, req.Arguments.Get(ctx, &stringArg)) + + // stringArg is now populated + // ... other logic ... +} +``` diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/returns/bool.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/returns/bool.mdx new file mode 100644 index 0000000000..0da2af0d09 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/returns/bool.mdx @@ -0,0 +1,66 @@ +--- +page_title: Boolean return values +description: >- + Learn how to use the boolean function return value type with the Terraform + plugin framework. +--- + +# Boolean return values + +Bool function return values expect a boolean true or false value from function logic. Set values in function logic with the Go built-in `bool` type, Go built-in `*bool` type, or the [framework bool type](/terraform/plugin/framework/handling-data/types/bool). + +## Function Definition + +Use the [`function.BoolReturn` type](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/function#BoolReturn) in the [function definition](/terraform/plugin/framework/functions/implementation#definition-method). + +In this example, a function definition includes a bool return: + +```go +func (f ExampleFunction) Definition(ctx context.Context, req function.DefinitionRequest, resp *function.DefinitionResponse) { + resp.Definition = function.Definition{ + // ... other Definition fields ... + Return: function.BoolReturn{ + // ... potentially other BoolReturn fields ... + }, + } +} +``` + +### Custom Types + +You may want to build your own data value and type implementations to allow your provider to combine validation and other behaviors into a reusable bundle. This helps avoid duplication and ensures consistency. These implementations use the `CustomType` field in the return type. + +Refer to [Custom Types](/terraform/plugin/framework/handling-data/types/custom) for further details on creating provider-defined types and values. + +### Documentation + +Return documentation is expected in the top-level function documentation. Refer to [function documentation](/terraform/plugin/framework/functions/documentation) for information about the `Summary`, `Description`, and `MarkdownDescription` fields available. + +## Setting Return Data + +The [function implementation](/terraform/plugin/framework/functions/implementation) documentation covers the general methods for setting function return data in function logic. + +When setting the value for this return: + +* If `CustomType` is set, use its associated value type. +* Otherwise, use the Go built-in `bool` type, Go built-in `*bool` type, or [framework bool type](/terraform/plugin/framework/handling-data/types/bool). + +In this example, a function defines a bool return and sets its value: + +```go +func (f ExampleFunction) Definition(ctx context.Context, req function.DefinitionRequest, resp *function.DefinitionResponse) { + resp.Definition = function.Definition{ + // ... other Definition fields ... + Return: function.BoolReturn{}, + } +} + +func (f ExampleFunction) Run(ctx context.Context, req function.RunRequest, resp *function.RunResponse) { + // ... other logic ... + + // hardcoded value for example brevity + result := true + + resp.Error = function.ConcatFuncErrors(resp.Error, resp.Result.Set(ctx, &result)) +} +``` diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/returns/dynamic.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/returns/dynamic.mdx new file mode 100644 index 0000000000..844d5926ab --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/returns/dynamic.mdx @@ -0,0 +1,78 @@ +--- +page_title: Dynamic function return values +description: >- + Learn how to use dynamic function return value types with the Terraform + plugin framework. +--- + +# Dynamic function return values + + + +Static types should always be preferred over dynamic types, when possible. + +Developers creating a function with a dynamic return will need to have extensive knowledge of the [Terraform type system](/terraform/language/expressions/types) to understand how the value type returned can impact practitioner configuration. + +Refer to [Dynamic Data - Considerations](/terraform/plugin/framework/handling-data/dynamic-data#considerations) for more information. + + + +Dynamic function return can be **any** value type from function logic. Set values in function logic with the [framework dynamic type](/terraform/plugin/framework/handling-data/types/dynamic). + +## Function Definition + +Use the [`function.DynamicReturn` type](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/function#DynamicReturn) in the [function definition](/terraform/plugin/framework/functions/implementation#definition-method). + +In this example, a function definition includes a dynamic return: + +```go +func (f ExampleFunction) Definition(ctx context.Context, req function.DefinitionRequest, resp *function.DefinitionResponse) { + resp.Definition = function.Definition{ + // ... other Definition fields ... + Return: function.DynamicReturn{ + // ... potentially other DynamicReturn fields ... + }, + } +} +``` + +### Custom Types + +You may want to build your own data value and type implementations to allow your provider to combine validation and other behaviors into a reusable bundle. This helps avoid duplication and ensures consistency. These implementations use the `CustomType` field in the return type. + +Refer to [Custom Types](/terraform/plugin/framework/handling-data/types/custom) for further details on creating provider-defined types and values. + +### Documentation + +Return documentation is expected in the top-level function documentation. Refer to [function documentation](/terraform/plugin/framework/functions/documentation) for information about the `Summary`, `Description`, and `MarkdownDescription` fields available. + +## Setting Return Data + +The [function implementation](/terraform/plugin/framework/functions/implementation) documentation covers the general methods for setting function return data in function logic. + +When setting the value for this return: + +* If `CustomType` is set, use its associated value type. +* Otherwise, use the [framework dynamic type](/terraform/plugin/framework/handling-data/types/dynamic). + +In this example, a function defines a dynamic return and sets its value to a string: + +```go +func (f ExampleFunction) Definition(ctx context.Context, req function.DefinitionRequest, resp *function.DefinitionResponse) { + resp.Definition = function.Definition{ + // ... other Definition fields ... + Return: function.DynamicReturn{}, + } +} + +func (f ExampleFunction) Run(ctx context.Context, req function.RunRequest, resp *function.RunResponse) { + // ... other logic ... + + // hardcoded value for example brevity + result := types.DynamicValue(types.StringValue("hello world!")) + + resp.Error = function.ConcatFuncErrors(resp.Error, resp.Result.Set(ctx, &result)) +} +``` + +For more detail on working with dynamic values, see the [framework dynamic type](/terraform/plugin/framework/handling-data/types/dynamic) documentation. diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/returns/float32.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/returns/float32.mdx new file mode 100644 index 0000000000..fbd0f48e58 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/returns/float32.mdx @@ -0,0 +1,72 @@ +--- +page_title: Float32 return values +description: >- + Learn how to use the 32-bit floating point function return value type with the + Terraform plugin framework. +--- + +# Float32 return values + + + +Use [Int32 Return](/terraform/plugin/framework/functions/returns/int32) for 32-bit integer numbers. Use [Number Return](/terraform/plugin/framework/functions/returns/number) for arbitrary precision numbers. + + + +Float32 function return expects a 32-bit floating point number value from function logic. Set values in function logic with the Go built-in `float32` type, Go built-in `*float32` type, or the [framework float32 type](/terraform/plugin/framework/handling-data/types/float32). + +## Function Definition + +Use the [`function.Float32Return` type](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/function#Float32Return) in the [function definition](/terraform/plugin/framework/functions/implementation#definition-method). + +In this example, a function definition includes a float32 return: + +```go +func (f ExampleFunction) Definition(ctx context.Context, req function.DefinitionRequest, resp *function.DefinitionResponse) { + resp.Definition = function.Definition{ + // ... other Definition fields ... + Return: function.Float32Return{ + // ... potentially other Float32Return fields ... + }, + } +} +``` + +### Custom Types + +You may want to build your own data value and type implementations to allow your provider to combine validation and other behaviors into a reusable bundle. This helps avoid duplication and ensures consistency. These implementations use the `CustomType` field in the return type. + +Refer to [Custom Types](/terraform/plugin/framework/handling-data/types/custom) for further details on creating provider-defined types and values. + +### Documentation + +Return documentation is expected in the top-level function documentation. Refer to [function documentation](/terraform/plugin/framework/functions/documentation) for information about the `Summary`, `Description`, and `MarkdownDescription` fields available. + +## Setting Return Data + +The [function implementation](/terraform/plugin/framework/functions/implementation) documentation covers the general methods for setting function return data in function logic. + +When setting the value for this return: + +* If `CustomType` is set, use its associated value type. +* Otherwise, use the Go built-in `float32` type, Go built-in `*float32` type, or [framework float32 type](/terraform/plugin/framework/handling-data/types/float32). + +In this example, a function defines a float32 return and sets its value: + +```go +func (f ExampleFunction) Definition(ctx context.Context, req function.DefinitionRequest, resp *function.DefinitionResponse) { + resp.Definition = function.Definition{ + // ... other Definition fields ... + Return: function.Float32Return{}, + } +} + +func (f ExampleFunction) Run(ctx context.Context, req function.RunRequest, resp *function.RunResponse) { + // ... other logic ... + + // hardcoded value for example brevity + var result float32 = 1.23 + + resp.Error = function.ConcatFuncErrors(resp.Error, resp.Result.Set(ctx, &result)) +} +``` diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/returns/float64.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/returns/float64.mdx new file mode 100644 index 0000000000..2769c1d98e --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/returns/float64.mdx @@ -0,0 +1,72 @@ +--- +page_title: Float64 return values +description: >- + Learn how to use the 64-bit floating point function return value type with the + Terraform plugin framework. +--- + +# Float64 return values + + + +Use [Int64 Return](/terraform/plugin/framework/functions/returns/int64) for 64-bit integer numbers. Use [Number Return](/terraform/plugin/framework/functions/returns/number) for arbitrary precision numbers. + + + +Float64 function return expects a 64-bit floating point number value from function logic. Set values in function logic with the Go built-in `float64` type, Go built-in `*float64` type, or the [framework float64 type](/terraform/plugin/framework/handling-data/types/float64). + +## Function Definition + +Use the [`function.Float64Return` type](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/function#Float64Return) in the [function definition](/terraform/plugin/framework/functions/implementation#definition-method). + +In this example, a function definition includes a float64 return: + +```go +func (f ExampleFunction) Definition(ctx context.Context, req function.DefinitionRequest, resp *function.DefinitionResponse) { + resp.Definition = function.Definition{ + // ... other Definition fields ... + Return: function.Float64Return{ + // ... potentially other Float64Return fields ... + }, + } +} +``` + +### Custom Types + +You may want to build your own data value and type implementations to allow your provider to combine validation and other behaviors into a reusable bundle. This helps avoid duplication and ensures consistency. These implementations use the `CustomType` field in the return type. + +Refer to [Custom Types](/terraform/plugin/framework/handling-data/types/custom) for further details on creating provider-defined types and values. + +### Documentation + +Return documentation is expected in the top-level function documentation. Refer to [function documentation](/terraform/plugin/framework/functions/documentation) for information about the `Summary`, `Description`, and `MarkdownDescription` fields available. + +## Setting Return Data + +The [function implementation](/terraform/plugin/framework/functions/implementation) documentation covers the general methods for setting function return data in function logic. + +When setting the value for this return: + +* If `CustomType` is set, use its associated value type. +* Otherwise, use the Go built-in `float64` type, Go built-in `*float64` type, or [framework float64 type](/terraform/plugin/framework/handling-data/types/float64). + +In this example, a function defines a float64 return and sets its value: + +```go +func (f ExampleFunction) Definition(ctx context.Context, req function.DefinitionRequest, resp *function.DefinitionResponse) { + resp.Definition = function.Definition{ + // ... other Definition fields ... + Return: function.Float64Return{}, + } +} + +func (f ExampleFunction) Run(ctx context.Context, req function.RunRequest, resp *function.RunResponse) { + // ... other logic ... + + // hardcoded value for example brevity + result := 1.23 + + resp.Error = function.ConcatFuncErrors(resp.Error, resp.Result.Set(ctx, &result)) +} +``` diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/returns/index.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/returns/index.mdx new file mode 100644 index 0000000000..a81c5bc830 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/returns/index.mdx @@ -0,0 +1,60 @@ +--- +page_title: Function return values +description: >- + The Terraform plugin framework includes multiple built-in function return + value types and supports dynamic return values. A return describes the output + data in a function definition. +--- + +# Return values + +A return in a [function definition](/terraform/plugin/framework/functions/implementation#definition-method) describes the result data value from function logic. Every return type has an associated [value type](/terraform/plugin/framework/handling-data/types), although this data handling is simplified for function implementations over other provider concepts, such as resource implementations. + +## Available Return Types + +Function definitions support the following return types: + +- [Primitive](#primitive-return-types): Return that expects a single value, such as a boolean, number, or string. +- [Collection](#collection-return-types): Return that expects multiple values of a single element type, such as a list, map, or set. +- [Object](#object-return-type): Return that expects a structure of explicit attribute names. +- [Dynamic](#dynamic-return-type): Return that can be any value type. + +### Primitive Return Types + +Return types that expect a single data value, such as a boolean, number, or string. + +| Return Type | Use Case | +|----------------|----------| +| [Bool](/terraform/plugin/framework/functions/returns/bool) | Boolean true or false | +| [Float32](/terraform/plugin/framework/functions/returns/float32) | 32-bit floating point number | +| [Float64](/terraform/plugin/framework/functions/returns/float64) | 64-bit floating point number | +| [Int32](/terraform/plugin/framework/functions/returns/int32) | 32-bit integer number | +| [Int64](/terraform/plugin/framework/functions/returns/int64) | 64-bit integer number | +| [Number](/terraform/plugin/framework/functions/returns/number) | Arbitrary precision (generally over 64-bit, up to 512-bit) number | +| [String](/terraform/plugin/framework/functions/returns/string) | Collection of UTF-8 encoded characters | + +### Collection Return Types + +Return types that expect multiple values of a single element type, such as a list, map, or set. + +| Return Type | Use Case | +|----------------|----------| +| [List](/terraform/plugin/framework/functions/returns/list) | Ordered collection of single element type | +| [Map](/terraform/plugin/framework/functions/returns/map) | Mapping of arbitrary string keys to values of single element type | +| [Set](/terraform/plugin/framework/functions/returns/set) | Unordered, unique collection of single element type | + +### Object Return Type + +Return type that expects a structure of explicit attribute names. + +| Return Type | Use Case | +|----------------|----------| +| [Object](/terraform/plugin/framework/functions/returns/object) | Single structure mapping explicit attribute names | + +### Dynamic Return Type + +Return type that can be any value type, determined by the provider at runtime. + +| Return Type | Use Case | +|----------------|----------| +| [Dynamic](/terraform/plugin/framework/functions/returns/dynamic) | Return any value type of data, determined at runtime. | \ No newline at end of file diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/returns/int32.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/returns/int32.mdx new file mode 100644 index 0000000000..b0c4b0c547 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/returns/int32.mdx @@ -0,0 +1,72 @@ +--- +page_title: Int32 return values +description: >- + Learn how to use the 32-bit integer function return value type with the + Terraform plugin framework. +--- + +# Int32 return values + + + +Use [Float32 Return](/terraform/plugin/framework/functions/returns/float32) for 32-bit floating point numbers. Use [Number Return](/terraform/plugin/framework/functions/returns/number) for arbitrary precision numbers. + + + +Int32 function return expects a 32-bit integer number value from function logic. Set values in function logic with the Go built-in `int32` type, Go built-in `*int32` type, or the [framework int32 type](/terraform/plugin/framework/handling-data/types/int32). + +## Function Definition + +Use the [`function.Int32Return` type](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/function#Int32Return) in the [function definition](/terraform/plugin/framework/functions/implementation#definition-method). + +In this example, a function definition includes a int32 return: + +```go +func (f ExampleFunction) Definition(ctx context.Context, req function.DefinitionRequest, resp *function.DefinitionResponse) { + resp.Definition = function.Definition{ + // ... other Definition fields ... + Return: function.Int32Return{ + // ... potentially other Int32Return fields ... + }, + } +} +``` + +### Custom Types + +You may want to build your own data value and type implementations to allow your provider to combine validation and other behaviors into a reusable bundle. This helps avoid duplication and ensures consistency. These implementations use the `CustomType` field in the return type. + +Refer to [Custom Types](/terraform/plugin/framework/handling-data/types/custom) for further details on creating provider-defined types and values. + +### Documentation + +Return documentation is expected in the top-level function documentation. Refer to [function documentation](/terraform/plugin/framework/functions/documentation) for information about the `Summary`, `Description`, and `MarkdownDescription` fields available. + +## Setting Return Data + +The [function implementation](/terraform/plugin/framework/functions/implementation) documentation covers the general methods for setting function return data in function logic. + +When setting the value for this return: + +* If `CustomType` is set, use its associated value type. +* Otherwise, use the Go built-in `int32` type, Go built-in `*int32` type, or [framework int32 type](/terraform/plugin/framework/handling-data/types/int32). + +In this example, a function defines a int32 return and sets its value: + +```go +func (f ExampleFunction) Definition(ctx context.Context, req function.DefinitionRequest, resp *function.DefinitionResponse) { + resp.Definition = function.Definition{ + // ... other Definition fields ... + Return: function.Int32Return{}, + } +} + +func (f ExampleFunction) Run(ctx context.Context, req function.RunRequest, resp *function.RunResponse) { + // ... other logic ... + + // hardcoded value for example brevity + result := 123 + + resp.Error = function.ConcatFuncErrors(resp.Error, resp.Result.Set(ctx, &result)) +} +``` diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/returns/int64.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/returns/int64.mdx new file mode 100644 index 0000000000..a134eda667 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/returns/int64.mdx @@ -0,0 +1,72 @@ +--- +page_title: Int64 return values +description: >- + Learn how to use the 64-bit integer function return value type with the + Terraform plugin framework. +--- + +# Int64 return values + + + +Use [Float64 Return](/terraform/plugin/framework/functions/returns/float64) for 64-bit floating point numbers. Use [Number Return](/terraform/plugin/framework/functions/returns/number) for arbitrary precision numbers. + + + +Int64 function return expects a 64-bit integer number value from function logic. Set values in function logic with the Go built-in `int64` type, Go built-in `*int64` type, or the [framework int64 type](/terraform/plugin/framework/handling-data/types/int64). + +## Function Definition + +Use the [`function.Int64Return` type](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/function#Int64Return) in the [function definition](/terraform/plugin/framework/functions/implementation#definition-method). + +In this example, a function definition includes a int64 return: + +```go +func (f ExampleFunction) Definition(ctx context.Context, req function.DefinitionRequest, resp *function.DefinitionResponse) { + resp.Definition = function.Definition{ + // ... other Definition fields ... + Return: function.Int64Return{ + // ... potentially other Int64Return fields ... + }, + } +} +``` + +### Custom Types + +You may want to build your own data value and type implementations to allow your provider to combine validation and other behaviors into a reusable bundle. This helps avoid duplication and ensures consistency. These implementations use the `CustomType` field in the return type. + +Refer to [Custom Types](/terraform/plugin/framework/handling-data/types/custom) for further details on creating provider-defined types and values. + +### Documentation + +Return documentation is expected in the top-level function documentation. Refer to [function documentation](/terraform/plugin/framework/functions/documentation) for information about the `Summary`, `Description`, and `MarkdownDescription` fields available. + +## Setting Return Data + +The [function implementation](/terraform/plugin/framework/functions/implementation) documentation covers the general methods for setting function return data in function logic. + +When setting the value for this return: + +* If `CustomType` is set, use its associated value type. +* Otherwise, use the Go built-in `int64` type, Go built-in `*int64` type, or [framework int64 type](/terraform/plugin/framework/handling-data/types/int64). + +In this example, a function defines a int64 return and sets its value: + +```go +func (f ExampleFunction) Definition(ctx context.Context, req function.DefinitionRequest, resp *function.DefinitionResponse) { + resp.Definition = function.Definition{ + // ... other Definition fields ... + Return: function.Int64Return{}, + } +} + +func (f ExampleFunction) Run(ctx context.Context, req function.RunRequest, resp *function.RunResponse) { + // ... other logic ... + + // hardcoded value for example brevity + result := 123 + + resp.Error = function.ConcatFuncErrors(resp.Error, resp.Result.Set(ctx, &result)) +} +``` diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/returns/list.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/returns/list.mdx new file mode 100644 index 0000000000..39cc953c70 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/returns/list.mdx @@ -0,0 +1,71 @@ +--- +page_title: List return values +description: >- + Learn how to use the list function return value type with the + Terraform plugin framework. +--- + +# List return values + +List function return expects an ordered collection of single element type value from function logic. Set values in function logic with a Go slice of an appropriate type to match the element type `[]T` or the [framework list type](/terraform/plugin/framework/handling-data/types/list). + +## Function Definition + +Use the [`function.ListReturn` type](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/function#ListReturn) in the [function definition](/terraform/plugin/framework/functions/implementation#definition-method). + +The `ElementType` field must be defined, which represents the single [framework value type](/terraform/plugin/framework/handling-data/types) of every element of the list. An element type may itself contain further collection or object types, if necessary. + +In this example, a function definition includes a list of string return: + +```go +func (f ExampleFunction) Definition(ctx context.Context, req function.DefinitionRequest, resp *function.DefinitionResponse) { + resp.Definition = function.Definition{ + // ... other Definition fields ... + Return: function.ListReturn{ + ElementType: types.StringType, + // ... potentially other ListReturn fields ... + }, + } +} +``` + +### Custom Types + +You may want to build your own data value and type implementations to allow your provider to combine validation and other behaviors into a reusable bundle. This helps avoid duplication and ensures consistency. These implementations use the `CustomType` field in the return type. + +Refer to [Custom Types](/terraform/plugin/framework/handling-data/types/custom) for further details on creating provider-defined types and values. + +### Documentation + +Return documentation is expected in the top-level function documentation. Refer to [function documentation](/terraform/plugin/framework/functions/documentation) for information about the `Summary`, `Description`, and `MarkdownDescription` fields available. + +## Setting Return Data + +The [function implementation](/terraform/plugin/framework/functions/implementation) documentation covers the general methods for setting function return data in function logic. + +When setting the value for this return: + +* If `CustomType` is set, use its associated value type. +* Otherwise, use a Go slice of an appropriate type to match the element type `[]T` or [framework list type](/terraform/plugin/framework/handling-data/types/list). + +In this example, a function defines a list of string return and sets its value: + +```go +func (f ExampleFunction) Definition(ctx context.Context, req function.DefinitionRequest, resp *function.DefinitionResponse) { + resp.Definition = function.Definition{ + // ... other Definition fields ... + Return: function.ListReturn{ + ElementType: types.StringType, + }, + } +} + +func (f ExampleFunction) Run(ctx context.Context, req function.RunRequest, resp *function.RunResponse) { + // ... other logic ... + + // hardcoded value for example brevity + result := []string{"one", "two"} + + resp.Error = function.ConcatFuncErrors(resp.Error, resp.Result.Set(ctx, &result)) +} +``` diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/returns/map.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/returns/map.mdx new file mode 100644 index 0000000000..3ae20085a8 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/returns/map.mdx @@ -0,0 +1,74 @@ +--- +page_title: Map return values +description: >- + Learn how to use the map function return value type with the + Terraform plugin framework. +--- + +# Map return values + +Map function return expects a mapping of arbitrary string keys to values of single element type from function logic. Set values in function logic with a Go map of string keys to values of an appropriate type to match the element type `map[string]T` or the [framework map type](/terraform/plugin/framework/handling-data/types/map). + +## Function Definition + +Use the [`function.MapReturn` type](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/function#MapReturn) in the [function definition](/terraform/plugin/framework/functions/implementation#definition-method). + +The `ElementType` field must be defined, which represents the single [framework value type](/terraform/plugin/framework/handling-data/types) of every element of the map. An element type may itself contain further collection or object types, if necessary. + +In this example, a function definition includes a map of string return: + +```go +func (f ExampleFunction) Definition(ctx context.Context, req function.DefinitionRequest, resp *function.DefinitionResponse) { + resp.Definition = function.Definition{ + // ... other Definition fields ... + Return: function.MapReturn{ + ElementType: types.StringType, + // ... potentially other MapReturn fields ... + }, + } +} +``` + +### Custom Types + +You may want to build your own data value and type implementations to allow your provider to combine validation and other behaviors into a reusable bundle. This helps avoid duplication and ensures consistency. These implementations use the `CustomType` field in the return type. + +Refer to [Custom Types](/terraform/plugin/framework/handling-data/types/custom) for further details on creating provider-defined types and values. + +### Documentation + +Return documentation is expected in the top-level function documentation. Refer to [function documentation](/terraform/plugin/framework/functions/documentation) for information about the `Summary`, `Description`, and `MarkdownDescription` fields available. + +## Setting Return Data + +The [function implementation](/terraform/plugin/framework/functions/implementation) documentation covers the general methods for setting function return data in function logic. + +When setting the value for this return: + +* If `CustomType` is set, use its associated value type. +* Otherwise, use a Go map of string keys to values of an appropriate type to match the element type `map[string]T` or [framework map type](/terraform/plugin/framework/handling-data/types/map). + +In this example, a function defines a map of string return and sets its value: + +```go +func (f ExampleFunction) Definition(ctx context.Context, req function.DefinitionRequest, resp *function.DefinitionResponse) { + resp.Definition = function.Definition{ + // ... other Definition fields ... + Return: function.MapReturn{ + ElementType: types.StringType, + }, + } +} + +func (f ExampleFunction) Run(ctx context.Context, req function.RunRequest, resp *function.RunResponse) { + // ... other logic ... + + // hardcoded value for example brevity + result := map[string]string{ + "key1": "value1", + "key2": "value2", + } + + resp.Error = function.ConcatFuncErrors(resp.Error, resp.Result.Set(ctx, &result)) +} +``` diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/returns/number.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/returns/number.mdx new file mode 100644 index 0000000000..74e6a2c013 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/returns/number.mdx @@ -0,0 +1,72 @@ +--- +page_title: Number return values +description: >- + Learn how to use the arbitrary precision number function return value type + with the Terraform plugin framework. +--- + +# Number return values + + + +Use [Float64 Return](/terraform/plugin/framework/functions/returns/float64) for 64-bit floating point numbers. Use [Int64 Return](/terraform/plugin/framework/functions/returns/int64) for 64-bit integer numbers. + + + +Number function return expects an arbitrary precision (generally over 64-bit, up to 512-bit) number value from function logic. Set values in function logic with the Go built-in `*big.Float` type or the [framework number type](/terraform/plugin/framework/handling-data/types/number). + +## Function Definition + +Use the [`function.NumberReturn` type](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/function#NumberReturn) in the [function definition](/terraform/plugin/framework/functions/implementation#definition-method). + +In this example, a function definition includes a number return: + +```go +func (f ExampleFunction) Definition(ctx context.Context, req function.DefinitionRequest, resp *function.DefinitionResponse) { + resp.Definition = function.Definition{ + // ... other Definition fields ... + Return: function.NumberReturn{ + // ... potentially other NumberReturn fields ... + }, + } +} +``` + +### Custom Types + +You may want to build your own data value and type implementations to allow your provider to combine validation and other behaviors into a reusable bundle. This helps avoid duplication and ensures consistency. These implementations use the `CustomType` field in the return type. + +Refer to [Custom Types](/terraform/plugin/framework/handling-data/types/custom) for further details on creating provider-defined types and values. + +### Documentation + +Return documentation is expected in the top-level function documentation. Refer to [function documentation](/terraform/plugin/framework/functions/documentation) for information about the `Summary`, `Description`, and `MarkdownDescription` fields available. + +## Setting Return Data + +The [function implementation](/terraform/plugin/framework/functions/implementation) documentation covers the general methods for setting function return data in function logic. + +When setting the value for this return: + +* If `CustomType` is set, use its associated value type. +* Otherwise, use the Go built-in `*big.Float` type or [framework number type](/terraform/plugin/framework/handling-data/types/number). + +In this example, a function defines a number return and sets its value: + +```go +func (f ExampleFunction) Definition(ctx context.Context, req function.DefinitionRequest, resp *function.DefinitionResponse) { + resp.Definition = function.Definition{ + // ... other Definition fields ... + Return: function.NumberReturn{}, + } +} + +func (f ExampleFunction) Run(ctx context.Context, req function.RunRequest, resp *function.RunResponse) { + // ... other logic ... + + // hardcoded value for example brevity + result := big.NewFloat(1.23) + + resp.Error = function.ConcatFuncErrors(resp.Error, resp.Result.Set(ctx, &result)) +} +``` diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/returns/object.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/returns/object.mdx new file mode 100644 index 0000000000..7f4a354a32 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/returns/object.mdx @@ -0,0 +1,83 @@ +--- +page_title: Object return values +description: >- + Learn how to use the object function return value type with the Terraform + plugin framework. +--- + +# Object return values + +Object function return expects a single structure mapping explicit attribute names to type definitions from function logic. Set values in function logic with a Go structure type annotated with `tfsdk` field tags or the [framework map type](/terraform/plugin/framework/handling-data/types/map). + +## Function Definition + +Use the [`function.ObjectReturn` type](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/function#ObjectReturn) in the [function definition](/terraform/plugin/framework/functions/implementation#definition-method). + +The `AttributeTypes` field must be defined, which represents a mapping of attribute names to [framework value types](/terraform/plugin/framework/handling-data/types). An attribute type may itself contain further collection or object types, if necessary. + +In this example, a function definition includes an object return: + +```go +func (f ExampleFunction) Definition(ctx context.Context, req function.DefinitionRequest, resp *function.DefinitionResponse) { + resp.Definition = function.Definition{ + // ... other Definition fields ... + Return: function.ObjectReturn{ + AttributeTypes: map[string]attr.Type{ + "attr1": types.StringType, + "attr2": types.Int64Type, + }, + // ... potentially other ObjectReturn fields ... + }, + } +} +``` + +### Custom Types + +You may want to build your own data value and type implementations to allow your provider to combine validation and other behaviors into a reusable bundle. This helps avoid duplication and ensures consistency. These implementations use the `CustomType` field in the return type. + +Refer to [Custom Types](/terraform/plugin/framework/handling-data/types/custom) for further details on creating provider-defined types and values. + +### Documentation + +Return documentation is expected in the top-level function documentation. Refer to [function documentation](/terraform/plugin/framework/functions/documentation) for information about the `Summary`, `Description`, and `MarkdownDescription` fields available. + +## Setting Return Data + +The [function implementation](/terraform/plugin/framework/functions/implementation) documentation covers the general methods for setting function return data in function logic. + +When setting the value for this return: + +* If `CustomType` is set, use its associated value type. +* Otherwise, use a Go structure type annotated with `tfsdk` field tags or [framework map type](/terraform/plugin/framework/handling-data/types/map). + +In this example, a function defines a map of string return and sets its value: + +```go +func (f ExampleFunction) Definition(ctx context.Context, req function.DefinitionRequest, resp *function.DefinitionResponse) { + resp.Definition = function.Definition{ + // ... other Definition fields ... + Return: function.ObjectReturn{ + AttributeTypes: map[string]attr.Type{ + "attr1": types.StringType, + "attr2": types.Int64Type, + }, + }, + } +} + +func (f ExampleFunction) Run(ctx context.Context, req function.RunRequest, resp *function.RunResponse) { + // ... other logic ... + + // hardcoded structure type and value for example brevity + result := struct{ + Attr1 string `tfsdk:"attr1"` + Attr2 int64 `tfsdk:"attr2"` + }{ + Attr1: "value1", + Attr2: 123, + } + + resp.Error = function.ConcatFuncErrors(resp.Error, resp.Result.Set(ctx, &result)) +} +``` diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/returns/set.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/returns/set.mdx new file mode 100644 index 0000000000..1c32dc7777 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/returns/set.mdx @@ -0,0 +1,71 @@ +--- +page_title: Set return values +description: >- + Learn how to use the set function return value type with the Terraform + plugin framework. +--- + +# Set return values + +Set function return expects an unordered, unique collection of single element type value from function logic. Set values in function logic with a Go slice of an appropriate type to match the element type `[]T` or the [framework set type](/terraform/plugin/framework/handling-data/types/set). + +## Function Definition + +Use the [`function.SetReturn` type](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/function#SetReturn) in the [function definition](/terraform/plugin/framework/functions/implementation#definition-method). + +The `ElementType` field must be defined, which represents the single [framework value type](/terraform/plugin/framework/handling-data/types) of every element of the set. An element type may itself contain further collection or object types, if necessary. + +In this example, a function definition includes a set of string return: + +```go +func (f ExampleFunction) Definition(ctx context.Context, req function.DefinitionRequest, resp *function.DefinitionResponse) { + resp.Definition = function.Definition{ + // ... other Definition fields ... + Return: function.SetReturn{ + ElementType: types.StringType, + // ... potentially other SetReturn fields ... + }, + } +} +``` + +### Custom Types + +You may want to build your own data value and type implementations to allow your provider to combine validation and other behaviors into a reusable bundle. This helps avoid duplication and ensures consistency. These implementations use the `CustomType` field in the return type. + +Refer to [Custom Types](/terraform/plugin/framework/handling-data/types/custom) for further details on creating provider-defined types and values. + +### Documentation + +Return documentation is expected in the top-level function documentation. Refer to [function documentation](/terraform/plugin/framework/functions/documentation) for information about the `Summary`, `Description`, and `MarkdownDescription` fields available. + +## Setting Return Data + +The [function implementation](/terraform/plugin/framework/functions/implementation) documentation covers the general methods for setting function return data in function logic. + +When setting the value for this return: + +* If `CustomType` is set, use its associated value type. +* Otherwise, use a Go slice of an appropriate type to match the element type `[]T` or [framework set type](/terraform/plugin/framework/handling-data/types/set). + +In this example, a function defines a set of string return and sets its value: + +```go +func (f ExampleFunction) Definition(ctx context.Context, req function.DefinitionRequest, resp *function.DefinitionResponse) { + resp.Definition = function.Definition{ + // ... other Definition fields ... + Return: function.SetReturn{ + ElementType: types.StringType, + }, + } +} + +func (f ExampleFunction) Run(ctx context.Context, req function.RunRequest, resp *function.RunResponse) { + // ... other logic ... + + // hardcoded value for example brevity + result := []string{"one", "two"} + + resp.Error = function.ConcatFuncErrors(resp.Error, resp.Result.Set(ctx, &result)) +} +``` diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/returns/string.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/returns/string.mdx new file mode 100644 index 0000000000..d80f9fa5b3 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/returns/string.mdx @@ -0,0 +1,66 @@ +--- +page_title: String return values +description: >- + Learn how to use the string function return value type with the Terraform + plugin framework. +--- + +# String return values + +String function return expects a collection of UTF-8 encoded bytes from function logic. Set values in function logic with the Go built-in `string` type, Go built-in `*string` type, or the [framework string type](/terraform/plugin/framework/handling-data/types/string). + +## Function Definition + +Use the [`function.StringReturn` type](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/function#StringReturn) in the [function definition](/terraform/plugin/framework/functions/implementation#definition-method). + +In this example, a function definition includes a string return: + +```go +func (f ExampleFunction) Definition(ctx context.Context, req function.DefinitionRequest, resp *function.DefinitionResponse) { + resp.Definition = function.Definition{ + // ... other Definition fields ... + Return: function.StringReturn{ + // ... potentially other StringReturn fields ... + }, + } +} +``` + +### Custom Types + +You may want to build your own data value and type implementations to allow your provider to combine validation and other behaviors into a reusable bundle. This helps avoid duplication and ensures consistency. These implementations use the `CustomType` field in the return type. + +Refer to [Custom Types](/terraform/plugin/framework/handling-data/types/custom) for further details on creating provider-defined types and values. + +### Documentation + +Return documentation is expected in the top-level function documentation. Refer to [function documentation](/terraform/plugin/framework/functions/documentation) for information about the `Summary`, `Description`, and `MarkdownDescription` fields available. + +## Setting Return Data + +The [function implementation](/terraform/plugin/framework/functions/implementation) documentation covers the general methods for setting function return data in function logic. + +When setting the value for this return: + +* If `CustomType` is set, use its associated value type. +* Otherwise, use the Go built-in `string` type, Go built-in `*string` type, or [framework string type](/terraform/plugin/framework/handling-data/types/string). + +In this example, a function defines a string return and sets its value: + +```go +func (f ExampleFunction) Definition(ctx context.Context, req function.DefinitionRequest, resp *function.DefinitionResponse) { + resp.Definition = function.Definition{ + // ... other Definition fields ... + Return: function.StringReturn{}, + } +} + +func (f ExampleFunction) Run(ctx context.Context, req function.RunRequest, resp *function.RunResponse) { + // ... other logic ... + + // hardcoded value for example brevity + result := "example" + + resp.Error = function.ConcatFuncErrors(resp.Error, resp.Result.Set(ctx, &result)) +} +``` diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/testing.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/testing.mdx new file mode 100644 index 0000000000..dad993547b --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/functions/testing.mdx @@ -0,0 +1,246 @@ +--- +page_title: Testing functions +description: >- + Learn how to implement tests for provider-defined functions with the Terraform + plugin framework. +--- + +# Testing functions + +When a function is [implemented](/terraform/plugin/framework/functions/implementation), ensure the function behaves as expected. Follow [recommendations](#recommendations) to cover how practitioner configurations may call the function. + +There are two methodologies for testing provider-defined functions: + +* [Acceptance Testing](#acceptance-testing): Verify implementation using real Terraform configurations and commands. +* [Unit Testing](#unit-testing): Verify implementation using with Terraform and framework implementation details. + +Similar to other provider concepts, many provider developers prefer acceptance testing over unit testing. Acceptance testing guarantees the function implementation works exactly as expected in real world use cases without trying to determine Terraform or framework implementation details. Unit testing details are provided, however, for function implementations which warrant a broad amount of input value testing, such as generic data handling functions or to perform [fuzzing](https://go.dev/security/fuzz/). + +Testing examples on this page are dependent on the example [echo function implementation](/terraform/plugin/framework/functions/implementation). + +## Recommendations + +Testing a provider-defined function should ensure at least the following behaviors are covered: + +* Known values return the expected results. +* For any list, map, object, and set parameters, null values for collection elements or object attributes. The `AllowNullValue` parameter setting does not affect Terraform sending these types of null values. +* If any parameters enable `AllowNullValue`, null values for those arguments. +* If any parameters enable `AllowUnknownValues`, unknown values for those arguments. +* Any errors, such as argument validation errors. + +## Acceptance Testing + +Use the [plugin testing Go module](/terraform/plugin/testing) to implement real world testing with Terraform configurations and commands. The documentation for that Go module covers many more available testing features, however this section example gives a high level overview of how to start writing these tests. + +In this example, a `echo_function_test.go` file is created: + +```go +package provider_test + +import ( + "testing" + + "example.com/terraform-provider-example/internal/provider" + "github.com/hashicorp/terraform-plugin-framework/providerserver" + "github.com/hashicorp/terraform-plugin-go/tfprotov6" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/knownvalue" + "github.com/hashicorp/terraform-plugin-testing/statecheck" + "github.com/hashicorp/terraform-plugin-testing/tfversion" +) + +func TestEchoFunction_Valid(t *testing.T) { + t.Parallel() + + resource.UnitTest(t, resource.TestCase{ + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(tfversion.Version1_8_0), + }, + ProtoV6ProviderFactories: map[string]func() (tfprotov6.ProviderServer, error) { + "example": providerserver.NewProtocol6WithError(provider.New()), + }, + Steps: []resource.TestStep{ + { + Config: ` + output "test" { + value = provider::example::echo("test-value") + }`, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownOutputValue("test", knownvalue.StringExact("test-value")), + }, + }, + }, + }) +} + +// The example implementation does not return any errors, however +// this acceptance test verifies how the function should behave if it did. +func TestEchoFunction_Invalid(t *testing.T) { + t.Parallel() + + resource.UnitTest(t, resource.TestCase{ + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(tfversion.Version1_8_0), + }, + ProtoV6ProviderFactories: map[string]func() (tfprotov6.ProviderServer, error) { + "example": providerserver.NewProtocol6WithError(provider.New()), + }, + Steps: []resource.TestStep{ + { + Config: ` + output "test" { + value = provider::example::echo("invalid") + }`, + ExpectError: regexp.MustCompile(`error summary`), + }, + }, + }) +} + +// The example implementation does not enable AllowNullValue, however this +// acceptance test shows how to verify the behavior. +func TestEchoFunction_Null(t *testing.T) { + t.Parallel() + + resource.UnitTest(t, resource.TestCase{ + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(tfversion.Version1_8_0), + }, + ProtoV6ProviderFactories: map[string]func() (tfprotov6.ProviderServer, error) { + "example": providerserver.NewProtocol6WithError(provider.New()), + }, + Steps: []resource.TestStep{ + { + Config: ` + output "test" { + value = provider::example::echo(null) + }`, + ExpectError: regexp.MustCompile(`Invalid Function Call`), + }, + }, + }) +} + +// The example implementation does not enable AllowUnknownValues, however this +// acceptance test shows how to verify the behavior. +func TestEchoFunction_Unknown(t *testing.T) { + resource.UnitTest(t, resource.TestCase{ + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(tfversion.Version1_8_0), + }, + ProtoV6ProviderFactories: map[string]func() (tfprotov6.ProviderServer, error) { + "example": providerserver.NewProtocol6WithError(provider.New()), + }, + Steps: []resource.TestStep{ + { + Config: ` + terraform_data "test" { + input = "test-value" + } + + output "test" { + value = provider::example::echo(terraform_data.test.output) + }`, + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectUnknownOutputValue("test"), + }, + }, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownOutputValue("test", knownvalue.StringExact("test-value")), + }, + }, + }, + }) +} +``` + +## Unit Testing + +Use the [`function.NewArgumentsData()` function](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/functions#NewArgumentsData) and [`function.NewResultData()` function](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/functions#NewResultData) as part of implementing a [Go test](https://go.dev/doc/tutorial/add-a-test). + +In this example, a `echo_function_test.go` file is created: + +```go +package provider_test + +import ( + "context" + "testing" + + "example.com/terraform-provider-example/internal/provider" + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/terraform-plugin-framework/function" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +func TestEchoFunctionRun(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + request function.RunRequest + expected function.RunResponse + }{ + // The example implementation uses the Go built-in string type, however + // if AllowNullValue was enabled and *string or types.String was used, + // this test case shows how the function would be expected to behave. + "null": { + request: function.RunRequest{ + Arguments: function.NewArgumentsData([]attr.Value{types.StringNull()}), + }, + expected: function.RunResponse{ + Result: function.NewResultData(types.StringNull()), + }, + }, + // The example implementation uses the Go built-in string type, however + // if AllowUnknownValues was enabled and types.String was used, + // this test case shows how the function would be expected to behave. + "unknown": { + request: function.RunRequest{ + Arguments: function.NewArgumentsData([]attr.Value{types.StringUnknown()}), + }, + expected: function.RunResponse{ + Result: function.NewResultData(types.StringUnknown()), + }, + }, + "value-valid": { + request: function.RunRequest{ + Arguments: function.NewArgumentsData([]attr.Value{types.StringValue("test-value")}), + }, + expected: function.RunResponse{ + Result: function.NewResultData(types.StringValue("test-value")), + }, + }, + // The example implementation does not return an error, however + // this test case shows how the function would be expected to behave if + // it did. + "value-invalid": { + request: function.RunRequest{ + Arguments: function.NewArgumentsData([]attr.Value{types.StringValue("")}), + }, + expected: function.RunResponse{ + Error: function.NewArgumentFuncError(0, "error summary: error detail"), + Result: function.NewResultData(types.StringUnknown()), + }, + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + got := function.RunResponse{ + Result: function.NewResultData(types.StringUnknown()), + } + + provider.EchoFunction{}.Run(context.Background(), testCase.request, &got) + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} +``` diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/getting-started/code-walkthrough.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/getting-started/code-walkthrough.mdx new file mode 100644 index 0000000000..8aecf55068 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/getting-started/code-walkthrough.mdx @@ -0,0 +1,384 @@ +--- +page_title: Provider code walkthrough +description: >- + The Terraform plugin framework is an SDK that you can use to implement + Terraform providers. Learn how the framework can help you create a provider + by exploring its main components and libraries. +--- + +# Provider code walkthrough + +[Terraform providers](/terraform/language/providers) let Terraform communicate with third parties, such as cloud providers, SaaS providers, and other APIs. Terraform and Terraform providers use gRPC to communicate. Terraform operates as a gRPC client and providers operate as gRPC servers. + +Each provider defines resources that let Terraform manage infrastructure objects and data sources that let Terraform read data. Terraform practitioners then write configuration to define resources, such as compute storage or networking resources. Terraform then communicates this configuration to the provider, and the provider creates the infrastructure. + +This example provider shows the relationship between the required provider components. The resources and data sources in a typical provider interact with a cloud provider through an API, but the example only stores values in state. + +## Core Provider Components + +A Terraform plugin provider requires at least the following components: + +- [provider server](#provider-server) +- [provider](#provider) +- [resource](#resource) or [data source](#data-source) + +The provider wraps the resource(s) and/or data source(s), and can be used to configure a client which communicates with a 3rd party service via an API. +Resources are used to manage infrastructure objects. +Data sources are used to read infrastructure objects. + +## Provider Server + +Each provider must implement a gRPC server that supports Terraform-specific connection and handshake handling on startup. A [provider server](/terraform/plugin/framework/provider-servers) is required in order for a Terraform provider to: + +* expose resources that can be managed by Terraform core. +* expose data sources that can be read by Terraform core. + +The `main()` function is used for defining a provider server. + +The `provider.New()` returns a function which returns a type that satisfies the `provider.Provider` interface. The `provider.Provider` interface defines functions for obtaining the resource(s) and/or data source(s) from a provider. + +```go +package main + +import ( + "context" + "flag" + "log" + + "github.com/hashicorp/terraform-plugin-framework/providerserver" + + "github.com/example_namespace/terraform-provider-example/internal/provider" +) + +func main() { + var debug bool + + flag.BoolVar(&debug, "debug", false, "set to true to run the provider with support for debuggers like delve") + flag.Parse() + + opts := providerserver.ServeOpts{ + Address: "registry.terraform.io/example_namespace/example", + Debug: debug, + } + + err := providerserver.Serve(context.Background(), provider.New(), opts) + + if err != nil { + log.Fatal(err.Error()) + } +} +``` + +Refer to [Provider Servers](/terraform/plugin/framework/provider-servers) for more details. + +## Provider + +The provider wraps resources and data sources which are typically used for interacting with cloud providers, SaaS providers, or other APIs. + +In this example the provider wraps a resource and a data source which simply interact with Terraform state. Refer to the [tutorial](/terraform/tutorials/providers-plugin-framework/providers-plugin-framework-provider-configure) for an example of provider configuration that configures an API client. + +`New()` returns a function which returns a type that satisfies the `provider.Provider` interface. The `New()` function is called by the [provider server](#provider-server) to obtain the provider. + +The `exampleProvider` struct implements the `provider.Provider` interface. This interface defines the following functions: + +- [`Schema`](/terraform/plugin/framework/handling-data/schemas): This function returns a provider `schema.Schema` struct that defines the provider schema. Schemas specify the constraints of Terraform configuration blocks. They define what fields a provider, resource, or data source configuration block has, and give Terraform metadata about those fields. +- [`Configure`](/terraform/plugin/framework/providers#configure-method): This function lets you configure provider-level data or clients. These configuration values may be from the practitioner Terraform configuration as defined by the schema, environment variables, or other means such as reading vendor-specific configuration files. +- [`Resources`](/terraform/plugin/framework/providers#resources): This function returns a slice of functions that return types that implement the `resource.Resource` interface. Resources let Terraform manage infrastructure objects, such as a compute instance, an access policy, or disk. +- [`Data Sources`](/terraform/plugin/framework/providers#datasources): This function returns a slice of functions that return types which implement the `datasource.DataSource` interface. Data sources let Terraform reference external data. For example a database instance. + +The `exampleProvider` struct also implements the `provider.ProviderWithMetadata` interface which defines the `Metadata` function. The `Metadata` function returns metadata for the provider such as a `TypeName` and `Version`. The `TypeName` is used as a prefix within a provider by for naming [resources](#resource) and [data sources](#data-source). + +```go +package provider + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/datasource" + "github.com/hashicorp/terraform-plugin-framework/provider" + "github.com/hashicorp/terraform-plugin-framework/provider/schema" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +var _ provider.Provider = (*exampleProvider)(nil) +var _ provider.ProviderWithMetadata = (*exampleProvider)(nil) + +type exampleProvider struct{} + +func New() func() provider.Provider { + return func() provider.Provider { + return &exampleProvider{} + } +} + +func (p *exampleProvider) Configure(ctx context.Context, req provider.ConfigureRequest, resp *provider.ConfigureResponse) { +} + +func (p *exampleProvider) Metadata(ctx context.Context, req provider.MetadataRequest, resp *provider.MetadataResponse) { + resp.TypeName = "example" +} + +func (p *exampleProvider) DataSources(ctx context.Context) []func() datasource.DataSource { + return []func() datasource.DataSource{ + NewDataSource, + } +} + +func (p *exampleProvider) Resources(ctx context.Context) []func() resource.Resource { + return []func() resource.Resource{ + NewResource, + } +} + +func (p *exampleProvider) Schema(ctx context.Context, req provider.SchemaRequest, resp *provider.SchemaResponse) { +} +``` + +Refer to [Providers](/terraform/plugin/framework/providers) for more details and configuration examples. + +## Resource + +A resource is typically used to manage infrastructure objects such as virtual networks and compute instances. + +In this example the resource simply interacts with Terraform state. + +`NewResource()` returns a function which returns a type that satisfies the `resource.Resource` interface. The provider calls the `NewResource()` function within `provider.Resources` to obtain an instance of the resource. + +The `exampleResource` struct implements the `resource.Resource` interface. This interface defines the following functions: + +- [`Metadata`](/terraform/plugin/framework/resources#metadata-method): This function returns the full name (`TypeName`) of the resource. The full name is used in [Terraform configuration](#resource-configuration) as `resource `. +- [`Schema`](/terraform/plugin/framework/handling-data/schemas): This function returns a resource `schema.Schema` struct that defines the resource schema. The schema specifies the constraints of the resource Terraform configuration block. It defines what fields a resource configuration block has, and gives Terraform metadata about those fields. For instance, defining whether a field is required. +- [`Create`](/terraform/plugin/framework/resources/create): This function lets the provider create a new resource of this type. +- [`Read`](/terraform/plugin/framework/resources/read): This function lets the provider read resource values in order to update state. +- [`Update`](/terraform/plugin/framework/resources/update): This function lets the provider update the resource and state. +- [`Delete`](/terraform/plugin/framework/resources/delete): This function lets the provider delete the resource. + +```go +package provider + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-log/tflog" +) + +var _ resource.Resource = (*exampleResource)(nil) + +type exampleResource struct { + provider exampleProvider +} + +func NewResource() resource.Resource { + return &exampleResource{} +} + +func (e *exampleResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_resource" +} + +func (e *exampleResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "configurable_attribute": schema.StringAttribute{ + Optional: true, + }, + "id": schema.StringAttribute{ + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + }, + } +} + +type exampleResourceData struct { + ConfigurableAttribute types.String `tfsdk:"configurable_attribute"` + Id types.String `tfsdk:"id"` +} + +func (e *exampleResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var data exampleResourceData + + diags := req.Config.Get(ctx, &data) + resp.Diagnostics.Append(diags...) + + if resp.Diagnostics.HasError() { + return + } + + // Create resource using 3rd party API. + + data.Id = types.StringValue("example-id") + + tflog.Trace(ctx, "created a resource") + + diags = resp.State.Set(ctx, &data) + resp.Diagnostics.Append(diags...) +} + +func (e *exampleResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var data exampleResourceData + + diags := req.State.Get(ctx, &data) + resp.Diagnostics.Append(diags...) + + if resp.Diagnostics.HasError() { + return + } + + // Read resource using 3rd party API. + + diags = resp.State.Set(ctx, &data) + resp.Diagnostics.Append(diags...) +} + +func (e *exampleResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var data exampleResourceData + + diags := req.Plan.Get(ctx, &data) + resp.Diagnostics.Append(diags...) + + if resp.Diagnostics.HasError() { + return + } + + // Update resource using 3rd party API. + + diags = resp.State.Set(ctx, &data) + resp.Diagnostics.Append(diags...) +} + +func (e *exampleResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var data exampleResourceData + + diags := req.State.Get(ctx, &data) + resp.Diagnostics.Append(diags...) + + if resp.Diagnostics.HasError() { + return + } + + // Delete resource using 3rd party API. +} +``` + +Refer to [Resources](/terraform/plugin/framework/resources) for more details and configuration examples. + +## Data Source + +A data source is typically used to provide a read-only view of infrastructure objects. + +In this example the data source simply interacts with Terraform state. + +`NewDataSource()` returns a function which returns a type that satisfies the `datasource.DataSource` interface. The `NewDataSource()` function is used within the `provider.DataSources` function to make the data source available to the provider. + +The `exampleDataSource` struct implements the `datasource.DataSource` interface. This interface defines the following functions: + +- [`Metadata`](/terraform/plugin/framework/data-sources#metadata-method): This function returns the full name (`TypeName`) of the data source. The full name is used in [Terraform configuration](#data-source-configuration) as `data `. +- [`Schema`](/terraform/plugin/framework/handling-data/schemas): This function returns a data source `schema.Schema` struct that defines the data source schema. The schema specifies the constraints of the data source Terraform configuration block. It defines what fields a data source configuration block has, and gives Terraform metadata about those fields. For instance, defining whether a field is optional. +- [`Read`](/terraform/plugin/framework/data-sources#read-method): This function lets the provider read data source values in order to update state. + +```go +package provider + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/datasource" + "github.com/hashicorp/terraform-plugin-framework/datasource/schema" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-log/tflog" +) + +var _ datasource.DataSource = (*exampleDataSource)(nil) + +type exampleDataSource struct { + provider exampleProvider +} + +func NewDataSource() datasource.DataSource { + return &exampleDataSource{} +} + +func (e *exampleDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_datasource" +} + +func (e *exampleDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "configurable_attribute": schema.StringAttribute{ + MarkdownDescription: "Example configurable attribute", + Optional: true, + }, + "id": schema.StringAttribute{ + MarkdownDescription: "Example identifier", + Computed: true, + }, + }, + } +} + +type exampleDataSourceData struct { + ConfigurableAttribute types.String `tfsdk:"configurable_attribute"` + Id types.String `tfsdk:"id"` +} + +func (e *exampleDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + var data exampleDataSourceData + + diags := req.Config.Get(ctx, &data) + resp.Diagnostics.Append(diags...) + + if resp.Diagnostics.HasError() { + return + } + + // Interact with 3rd party API to read data source. + + data.Id = types.StringValue("example-id") + + tflog.Trace(ctx, "read a data source") + + diags = resp.State.Set(ctx, &data) + resp.Diagnostics.Append(diags...) +} +``` + +Refer to [Data Sources](/terraform/plugin/framework/data-sources) for more details and configuration examples. + +## Terraform Configuration + +Refer to [terraform-provider-scaffolding-framework](https://github.com/hashicorp/terraform-provider-scaffolding-framework) for details on how to wire together a [provider server](#provider-server), [provider](#provider), [resource](#resource) and [data source](#data-source). + +Once wired together, run the provider by specifying configuration and executing `terraform apply`. + +### Resource Configuration + +```hcl +resource "example_resource" "example" { + configurable_attribute = "some-value" +} +``` + +The `configurable_attribute` is defined within the [schema](#resource) as a string type attribute. + +Examples of the various types of attributes and their representation within Terraform configuration and schema definitions are detailed in [Terraform Concepts](/terraform/plugin/framework/handling-data/terraform-concepts). + +### Data Source Configuration + +```hcl +data "example_datasource" "example" { + configurable_attribute = "some-value" +} +``` + +The `configurable_attribute` is defined within the [schema](#data-source) as a string type attribute. + +Examples of the various types of attributes and their representation within Terraform configuration and schema definitions are detailed in [Terraform Concepts](/terraform/plugin/framework/handling-data/terraform-concepts). diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/accessing-values.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/accessing-values.mdx new file mode 100644 index 0000000000..a3d1777f40 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/accessing-values.mdx @@ -0,0 +1,111 @@ +--- +page_title: Access state, configuration, and plan data +description: >- + Learn how to read values from Terraform's state, configuration, and plan with + the Terraform plugin framework. +--- + +# Access state, configuration, and plan data + +There are various points at which the provider needs access to the data from +the practitioner's configuration, Terraform's state, or generated plan. +The same patterns are used for accessing this data, regardless of +its source. + +The data is usually stored in a request object: + +```go +func (r ThingResource) Create(ctx context.Context, + req resource.CreateRequest, resp *resource.CreateResponse) +``` + +In this example, `req` holds the configuration and plan, and there is no state +value because the resource does not yet exist in state. + +## Get the Entire Configuration, Plan, or State + +One way to interact with configuration, plan, and state values is to convert +the entire configuration, plan, or state into a Go type, then treat them as +regular Go values. This has the benefit of letting the compiler check all your +code that accesses values, but requires defining a type to contain the values. + +Use the `Get` method to retrieve the first level of configuration, plan, and state data. + +```go +type ThingResourceModel struct { + Address types.Object `tfsdk:"address"` + Age types.Int64 `tfsdk:"age"` + Name types.String `tfsdk:"name"` + Pets types.List `tfsdk:"pets"` + Registered types.Bool `tfsdk:"registered"` + Tags types.Map `tfsdk:"tags"` +} + +func (r ThingResource) Create(ctx context.Context, + req resource.CreateRequest, resp *resource.CreateResponse) { + var plan ThingResourceModel + + diags := req.Plan.Get(ctx, &plan) + + resp.Diagnostics.Append(diags...) + + if resp.Diagnostics.HasError() { + return + } + + // values can now be accessed like plan.Name.ValueString() + // check if things are null with plan.Name.IsNull() + // check if things are unknown with plan.Name.IsUnknown() +} +``` + +The configuration, plan, and state data is represented as an object, and +accessed like an object. Refer to the [object type](/terraform/plugin/framework/handling-data/types/object) documentation for an +explanation on how objects can be converted into Go types. + +To descend into deeper nested data structures, the `types.List`, `types.Map`, and `types.Set` types each have an `ElementsAs()` method. The `types.Object` type has an `As()` method. + +## Get a Single Attribute or Block Value + +Use the `GetAttribute` method to retrieve a top level attribute or block value from the configuration, plan, and state. + +```go +func (r ThingResource) Read(ctx context.Context, + req resource.ReadRequest, resp *resource.ReadResponse) { + var name types.String + + diags := req.State.GetAttribute(ctx, path.Root("name"), &name) + + resp.Diagnostics.Append(diags...) + + if resp.Diagnostics.HasError() { + return + } + + // ... +} +``` + +## When Can a Value Be Unknown or Null? + +A lot of conversion rules say an error will be returned if a value is unknown +or null. It is safe to assume: + +* Required attributes will never be null or unknown in Create, Read, Update, or + Delete methods. +* Optional attributes that are not computed will never be unknown in Create, + Read, Update, or Delete methods. +* Computed attributes, whether optional or not, will never be null in the plan + for Create, Read, Update, or Delete methods. +* Computed attributes that are read-only (`Optional` is not `true`) will always + be unknown in the plan for Create, Read, Update, or Delete methods. They will + always be null in the configuration for Create, Read, Update, and Delete + methods. +* Required attributes will never be null in a provider's Configure method. They + may be unknown. +* The state never contains unknown values. +* The configuration for Create, Read, Update, and Delete methods never contains + unknown values. + +In any other circumstances, the provider is responsible for handling the +possibility that an unknown or null value may be presented to it. diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/attributes/bool.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/attributes/bool.mdx new file mode 100644 index 0000000000..8da6da9349 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/attributes/bool.mdx @@ -0,0 +1,143 @@ +--- +page_title: Boolean attributes +description: >- + Learn how to use boolean attributes with the Terraform plugin framework. +--- + + +# Boolean attributes + +Bool attributes store a boolean true or false value. Values are represented by a [bool type](/terraform/plugin/framework/handling-data/types/bool) in the framework. + +In this Terraform configuration example, a bool attribute named `example_attribute` is set to the value `true`: + +```hcl +resource "examplecloud_thing" "example" { + example_attribute = true +} +``` + +## Schema Definition + +Use one of the following attribute types to directly add a bool value to a [schema](/terraform/plugin/framework/handling-data/schemas) or [nested attribute type](/terraform/plugin/framework/handling-data/attributes#nested-attribute-types): + +| Schema Type | Attribute Type | +|-------------|----------------| +| [Data Source](/terraform/plugin/framework/data-sources) | [`schema.BoolAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/datasource/schema#BoolAttribute) | +| [Provider](/terraform/plugin/framework/providers) | [`schema.BoolAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider/schema#BoolAttribute) | +| [Resource](/terraform/plugin/framework/resources) | [`schema.BoolAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema#BoolAttribute) | +| [Ephemeral Resource](/terraform/plugin/framework/ephemeral-resources) | [`schema.BoolAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/ephemeral/schema#BoolAttribute) | + +In this example, a resource schema defines a top level required bool attribute named `example_attribute`: + +```go +func (r ThingResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "example_attribute": schema.BoolAttribute{ + Required: true, + // ... potentially other fields ... + }, + // ... potentially other attributes ... + }, + } +} +``` + +If the bool value should be the element type of a [collection attribute type](/terraform/plugin/framework/handling-data/attributes#collection-attribute-types), set the `ElementType` field according to the [bool type](/terraform/plugin/framework/handling-data/types/bool). Refer to the collection attribute type documentation for additional details. + +If the bool value should be a value type of an [object attribute type](/terraform/plugin/framework/handling-data/attributes#object-attribute-type), set the `AttributeTypes` map value according to the [bool type](/terraform/plugin/framework/handling-data/types/bool). Refer to the object attribute type documentation for additional details. + +### Configurability + +At least one of the `Computed`, `Optional`, or `Required` fields must be set to `true`. This defines how Terraform and the framework should expect data to set, whether the value is from the practitioner configuration or from the provider logic, such as API response value. + +The acceptable behaviors of these configurability options are: + +- `Required` only: The value must be practitioner configured to an eventually known value (not null), otherwise the framework will automatically raise an error diagnostic for the missing value. +- `Optional` only: The value may be practitioner configured to a known value or null. +- `Optional` and `Computed`: The value may be practitioner configured or the value may be set in provider logic when the practitioner configuration is null. +- `Computed` only: The value will be set in provider logic and any practitioner configuration causes the framework to automatically raise an error diagnostic for the unexpected configuration value. + +#### Resource Identity + +If creating a resource identity schema, set either `OptionalForImport` or `RequiredForImport` to `true` to inform Terraform if practitioners must set the attribute when importing with that resource's identity. + +The acceptable behaviors of these configurability options are: + +- `RequiredForImport` only: A practitioner must configure the attribute to a known value (not `null`), otherwise Terraform automatically raises an error diagnostic for the missing value. +- `OptionalForImport` only: A practitioner must configure the value to a known value or `null`. + +### Custom Types + +You may want to build your own attribute value and type implementations to allow your provider to combine validation, description, and plan customization behaviors into a reusable bundle. This helps avoid duplication or reimplementation and ensures consistency. These implementations use the `CustomType` field in the attribute type. + +Refer to [Custom Types](/terraform/plugin/framework/handling-data/types/custom) for further details on creating provider-defined types and values. + +### Deprecation + +Set the `DeprecationMessage` field to a practitioner-focused message for how to handle the deprecation. The framework will automatically raise a warning diagnostic with this message if the practitioner configuration contains a known value for the attribute. Terraform version 1.2.7 and later will raise a warning diagnostic in certain scenarios if the deprecated attribute value is referenced elsewhere in a practitioner configuration. The framework [deprecations](/terraform/plugin/framework/deprecations) documentation fully describes the recommended practices for deprecating an attribute or resource. + +Some practitioner-focused examples of a deprecation message include: + +- Configure `other_attribute` instead. This attribute will be removed in the next major version of the provider. +- Remove this attribute's configuration as it no longer is used and the attribute will be removed in the next major version of the provider. + +### Description + +The framework provides two description fields, `Description` and `MarkdownDescription`, which various tools use to show additional information about an attribute and its intended purpose. This includes, but is not limited to, [`terraform-plugin-docs`](https://github.com/hashicorp/terraform-plugin-docs) for automated provider documentation generation and [`terraform-ls`](https://github.com/hashicorp/terraform-ls) for Terraform configuration editor integrations. + +### Plan Modification + + + +Only managed resources implement this concept. + + + +The framework provides two plan modification fields for managed resource attributes, `Default` and `PlanModifiers`, which define resource and attribute value planning behaviors. The resource [default](/terraform/plugin/framework/resources/default) and [plan modification](/terraform/plugin/framework/resources/plan-modification) documentation covers these features more in-depth. + +#### Common Use Case Plan Modification + +The [`booldefault`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault) package defines common use case `Default` implementations: + +- [`StaticBool(bool)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault#StaticBool): Define a static bool default value for the attribute. + +The [`boolplanmodifier`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/boolplanmodifier) package defines common use case `PlanModifiers` implementations: + +- [`RequiresReplace()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/boolplanmodifier#RequiresReplace): Marks the resource for replacement if the resource is being updated and the plan value does not match the prior state value. +- [`RequiresReplaceIf()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/boolplanmodifier#RequiresReplaceIf): Similar to `RequiresReplace()`, but also checks if a given function returns true. +- [`RequiresReplaceIfConfigured()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/boolplanmodifier#RequiresReplaceIfConfigured): Similar to `RequiresReplace()`, but also checks if the configuration value is not null. +- [`UseStateForUnknown()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/boolplanmodifier#UseStateForUnknown): Copies a known prior state value into the planned value. Use this when it is known that an unconfigured value will remain the same after a resource update. + +### Sensitive + +Set the `Sensitive` field if the attribute value should always be considered [sensitive data](/terraform/language/state/sensitive-data). In Terraform, this will generally mask the value in practitioner output. This setting cannot be conditionally set and does not impact how data is stored in the state. + +### WriteOnly + + + + Only managed resources implement this concept. + + + +Set the `WriteOnly` field to define a [write-only argument](/terraform/plugin/framework/resources/write-only-arguments). +Write-only arguments can accept [ephemeral values](/terraform/language/resources/ephemeral) +and are not persisted in the Terraform plan or state artifacts. Write-only arguments are supported in Terraform 1.11 and later. + +### Validation + +Set the `Validators` field to define [validation](/terraform/plugin/framework/validation#attribute-validation). This validation logic is ran in addition to any validation contained within a [custom type](#custom-types). + +#### Common Use Case Validators + +HashiCorp provides the additional [`terraform-plugin-framework-validators`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators) Go module which contains validation logic for common use cases. The [`boolvalidator`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators/boolvalidator) package within that module has bool attribute validators such as defining conflicting attributes. + +## Accessing Values + +The [accessing values](/terraform/plugin/framework/handling-data/accessing-values) documentation covers general methods for reading [schema](/terraform/plugin/framework/handling-data/schemas) (configuration, plan, and state) data, which is necessary before accessing an attribute value directly. The [bool type](/terraform/plugin/framework/handling-data/types/bool#accessing-values) documentation covers methods for interacting with the attribute value itself. + +## Setting Values + +The [bool type](/terraform/plugin/framework/handling-data/types/bool#setting-values) documentation covers methods for creating or setting the appropriate value. The [writing data](/terraform/plugin/framework/handling-data/writing-state) documentation covers general methods for writing [schema](/terraform/plugin/framework/handling-data/schemas) (plan and state) data, which is necessary afterwards. diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/attributes/dynamic.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/attributes/dynamic.mdx new file mode 100644 index 0000000000..00eb1fc3e6 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/attributes/dynamic.mdx @@ -0,0 +1,159 @@ +--- +page_title: Dynamic attributes +description: >- + Learn how to use dynamic attributes with the Terraform plugin framework. +--- + +# Dynamic attribute + + + +Static attribute types should always be preferred over dynamic attribute types, when possible. + +Developers dealing with dynamic attribute data will need to have extensive knowledge of the [Terraform type system](/terraform/language/expressions/types) to properly handle all potential practitioner configuration scenarios. + +Refer to [Dynamic Data - Considerations](/terraform/plugin/framework/handling-data/dynamic-data#considerations) for more information. + + + +Dynamic attributes can store **any** value. Values are represented by a [dynamic type](/terraform/plugin/framework/handling-data/types/dynamic) in the framework. + +In this Terraform configuration example, a dynamic attribute named `example_attribute` is set to the boolean value `true`: + +```hcl +resource "examplecloud_thing" "example" { + example_attribute = true +} +``` + +In this example, the same dynamic attribute is set to a tuple (not a list) of string values `one` and `two`: + +```hcl +resource "examplecloud_thing" "example" { + example_attribute = ["one", "two"] +} +``` + +In this example, the same dynamic attribute is set to an object type with mapped values of `attr1` to `"value1"` and `attr2` to `123`: + +```hcl +resource "examplecloud_thing" "example" { + example_attribute = { + attr1 = "value1" + attr2 = 123 + } +} +``` + + +## Schema Definition + +Use one of the following attribute types to directly add a dynamic value to a [schema](/terraform/plugin/framework/handling-data/schemas) or a [single nested attribute type](/terraform/plugin/framework/handling-data/attributes/single-nested): + +| Schema Type | Attribute Type | +|-------------|----------------| +| [Data Source](/terraform/plugin/framework/data-sources) | [`schema.DynamicAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/datasource/schema#DynamicAttribute) | +| [Provider](/terraform/plugin/framework/providers) | [`schema.DynamicAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider/schema#DynamicAttribute) | +| [Resource](/terraform/plugin/framework/resources) | [`schema.DynamicAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema#DynamicAttribute) | +| [Ephemeral Resource](/terraform/plugin/framework/ephemeral-resources) | [`schema.DynamicAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/ephemeral/schema#DynamicAttribute) | + +In this example, a resource schema defines a top level required dynamic attribute named `example_attribute`: + +```go +func (r ThingResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "example_attribute": schema.DynamicAttribute{ + Required: true, + // ... potentially other fields ... + }, + // ... potentially other attributes ... + }, + } +} +``` + +Dynamic values are not supported as the element type of a [collection type](/terraform/plugin/framework/handling-data/types#collection-types) or within [collection attribute types](/terraform/plugin/framework/handling-data/attributes#collection-attribute-types). + +If the dynamic value should be a value type of an [object attribute type](/terraform/plugin/framework/handling-data/attributes#object-attribute-type), set the `AttributeTypes` map value according to the [dynamic type](/terraform/plugin/framework/handling-data/types/dynamic). Refer to the object attribute type documentation for additional details. + +### Configurability + +At least one of the `Computed`, `Optional`, or `Required` fields must be set to `true`. This defines how Terraform and the framework should expect data to set, whether the value is from the practitioner configuration or from the provider logic, such as API response value. + +The acceptable behaviors of these configurability options are: + +- `Required` only: The value must be practitioner configured to an eventually known value (not null), otherwise the framework will automatically raise an error diagnostic for the missing value. +- `Optional` only: The value may be practitioner configured to a known value or null. +- `Optional` and `Computed`: The value may be practitioner configured or the value may be set in provider logic when the practitioner configuration is null. +- `Computed` only: The value will be set in provider logic and any practitioner configuration causes the framework to automatically raise an error diagnostic for the unexpected configuration value. + +### Custom Types + +You may want to build your own attribute value and type implementations to allow your provider to combine validation, description, and plan customization behaviors into a reusable bundle. This helps avoid duplication or reimplementation and ensures consistency. These implementations use the `CustomType` field in the attribute type. + +Refer to [Custom Types](/terraform/plugin/framework/handling-data/types/custom) for further details on creating provider-defined types and values. + +### Deprecation + +Set the `DeprecationMessage` field to a practitioner-focused message for how to handle the deprecation. The framework will automatically raise a warning diagnostic with this message if the practitioner configuration contains a known value for the attribute. Terraform version 1.2.7 and later will raise a warning diagnostic in certain scenarios if the deprecated attribute value is referenced elsewhere in a practitioner configuration. The framework [deprecations](/terraform/plugin/framework/deprecations) documentation fully describes the recommended practices for deprecating an attribute or resource. + +Some practitioner-focused examples of a deprecation message include: + +- Configure `other_attribute` instead. This attribute will be removed in the next major version of the provider. +- Remove this attribute's configuration as it no longer is used and the attribute will be removed in the next major version of the provider. + +### Description + +The framework provides two description fields, `Description` and `MarkdownDescription`, which various tools use to show additional information about an attribute and its intended purpose. This includes, but is not limited to, [`terraform-plugin-docs`](https://github.com/hashicorp/terraform-plugin-docs) for automated provider documentation generation and [`terraform-ls`](https://github.com/hashicorp/terraform-ls) for Terraform configuration editor integrations. + +### Plan Modification + + + +Only managed resources implement this concept. + + + +The framework provides two plan modification fields for managed resource attributes, `Default` and `PlanModifiers`, which define resource and attribute value planning behaviors. The resource [default](/terraform/plugin/framework/resources/default) and [plan modification](/terraform/plugin/framework/resources/plan-modification) documentation covers these features more in-depth. + +#### Common Use Case Plan Modification + +The [`dynamicdefault`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/dynamicdefault) package defines common use case `Default` implementations: + +- [`StaticValue(types.Dynamic)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/dynamicdefault#StaticValue): Define a static default value for the attribute. + +The [`dynamicplanmodifier`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/dynamicplanmodifier) package defines common use case `PlanModifiers` implementations: + +- [`RequiresReplace()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/dynamicplanmodifier#RequiresReplace): Marks the resource for replacement if the resource is being updated and the plan value does not match the prior state value. +- [`RequiresReplaceIf()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/dynamicplanmodifier#RequiresReplaceIf): Similar to `RequiresReplace()`, but also checks if a given function returns true. +- [`RequiresReplaceIfConfigured()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/dynamicplanmodifier#RequiresReplaceIfConfigured): Similar to `RequiresReplace()`, but also checks if the configuration value is not null. +- [`UseStateForUnknown()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/dynamicplanmodifier#UseStateForUnknown): Copies a known prior state value into the planned value. Use this when it is known that an unconfigured value will remain the same after a resource update. + +### Sensitive + +Set the `Sensitive` field if the attribute value should always be considered [sensitive data](/terraform/language/state/sensitive-data). In Terraform, this will generally mask the value in practitioner output. This setting cannot be conditionally set and does not impact how data is stored in the state. + +### WriteOnly + + + + Only managed resources implement this concept. + + + +Set the `WriteOnly` field to define a [write-only argument](/terraform/plugin/framework/resources/write-only-arguments). +Write-only arguments can accept [ephemeral values](/terraform/language/resources/ephemeral) +and are not persisted in the Terraform plan or state artifacts. Write-only arguments are supported in Terraform 1.11 and later. + +### Validation + +Set the `Validators` field to define [validation](/terraform/plugin/framework/validation#attribute-validation). This validation logic is ran in addition to any validation contained within a [custom type](#custom-types). + +## Accessing Values + +The [accessing values](/terraform/plugin/framework/handling-data/accessing-values) documentation covers general methods for reading [schema](/terraform/plugin/framework/handling-data/schemas) (configuration, plan, and state) data, which is necessary before accessing an attribute value directly. The [dynamic type](/terraform/plugin/framework/handling-data/types/dynamic#accessing-values) documentation covers methods for interacting with the attribute value itself. + +## Setting Values + +The [dynamic type](/terraform/plugin/framework/handling-data/types/dynamic#setting-values) documentation covers methods for creating or setting the appropriate value. The [writing data](/terraform/plugin/framework/handling-data/writing-state) documentation covers general methods for writing [schema](/terraform/plugin/framework/handling-data/schemas) (plan and state) data, which is necessary afterwards. diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/attributes/float32.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/attributes/float32.mdx new file mode 100644 index 0000000000..d8678fe887 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/attributes/float32.mdx @@ -0,0 +1,149 @@ +--- +page_title: Float32 attributes +description: >- + Learn how to use 32-bit floating point attributes with the Terraform plugin + framework. +--- + +# Float32 attributes + + + +Use [Int32 Attribute](/terraform/plugin/framework/handling-data/attributes/int32) for 32-bit integer numbers. Use [Number Attribute](/terraform/plugin/framework/handling-data/attributes/number) for arbitrary precision numbers. + + + +Float32 attributes store a 32-bit floating point number. Values are represented by a [float32 type](/terraform/plugin/framework/handling-data/types/float32) in the framework. + +In this Terraform configuration example, a float32 attribute named `example_attribute` is set to the value `1.23`: + +```hcl +resource "examplecloud_thing" "example" { + example_attribute = 1.23 +} +``` + +## Schema Definition + +Use one of the following attribute types to directly add a float32 value to a [schema](/terraform/plugin/framework/handling-data/schemas) or [nested attribute type](/terraform/plugin/framework/handling-data/attributes#nested-attribute-types): + +| Schema Type | Attribute Type | +|-------------|----------------| +| [Data Source](/terraform/plugin/framework/data-sources) | [`schema.Float32Attribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/datasource/schema#Float32Attribute) | +| [Provider](/terraform/plugin/framework/providers) | [`schema.Float32Attribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider/schema#Float32Attribute) | +| [Resource](/terraform/plugin/framework/resources) | [`schema.Float32Attribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema#Float32Attribute) | +| [Ephemeral Resource](/terraform/plugin/framework/ephemeral-resources) | [`schema.Float32Attribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/ephemeral/schema#Float32Attribute) | + +In this example, a resource schema defines a top level required float32 attribute named `example_attribute`: + +```go +func (r ThingResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "example_attribute": schema.Float32Attribute{ + Required: true, + // ... potentially other fields ... + }, + // ... potentially other attributes ... + }, + } +} +``` + +If the float32 value should be the element type of a [collection attribute type](/terraform/plugin/framework/handling-data/attributes#collection-attribute-types), set the `ElementType` field according to the [float32 type](/terraform/plugin/framework/handling-data/types/float32). Refer to the collection attribute type documentation for additional details. + +If the float32 value should be a value type of an [object attribute type](/terraform/plugin/framework/handling-data/attributes#object-attribute-type), set the `AttributeTypes` map value according to the [float32 type](/terraform/plugin/framework/handling-data/types/float32). Refer to the object attribute type documentation for additional details. + +### Configurability + +At least one of the `Computed`, `Optional`, or `Required` fields must be set to `true`. This defines how Terraform and the framework should expect data to set, whether the value is from the practitioner configuration or from the provider logic, such as API response value. + +The acceptable behaviors of these configurability options are: + +- `Required` only: The value must be practitioner configured to an eventually known value (not null), otherwise the framework will automatically raise an error diagnostic for the missing value. +- `Optional` only: The value may be practitioner configured to a known value or null. +- `Optional` and `Computed`: The value may be practitioner configured or the value may be set in provider logic when the practitioner configuration is null. +- `Computed` only: The value will be set in provider logic and any practitioner configuration causes the framework to automatically raise an error diagnostic for the unexpected configuration value. + +#### Resource Identity + +If creating a resource identity schema, set either `OptionalForImport` or `RequiredForImport` to `true` to inform Terraform if practitioners must set the attribute when importing with that resource's identity. + +The acceptable behaviors of these configurability options are: + +- `RequiredForImport` only: A practitioner must configure the attribute to a known value (not `null`), otherwise Terraform automatically raises an error diagnostic for the missing value. +- `OptionalForImport` only: A practitioner must configure the value to a known value or `null`. + +### Custom Types + +You may want to build your own attribute value and type implementations to allow your provider to combine validation, description, and plan customization behaviors into a reusable bundle. This helps avoid duplication or reimplementation and ensures consistency. These implementations use the `CustomType` field in the attribute type. + +Refer to [Custom Types](/terraform/plugin/framework/handling-data/types/custom) for further details on creating provider-defined types and values. + +### Deprecation + +Set the `DeprecationMessage` field to a practitioner-focused message for how to handle the deprecation. The framework will automatically raise a warning diagnostic with this message if the practitioner configuration contains a known value for the attribute. Terraform version 1.2.7 and later will raise a warning diagnostic in certain scenarios if the deprecated attribute value is referenced elsewhere in a practitioner configuration. The framework [deprecations](/terraform/plugin/framework/deprecations) documentation fully describes the recommended practices for deprecating an attribute or resource. + +Some practitioner-focused examples of a deprecation message include: + +- Configure `other_attribute` instead. This attribute will be removed in the next major version of the provider. +- Remove this attribute's configuration as it no longer is used and the attribute will be removed in the next major version of the provider. + +### Description + +The framework provides two description fields, `Description` and `MarkdownDescription`, which various tools use to show additional information about an attribute and its intended purpose. This includes, but is not limited to, [`terraform-plugin-docs`](https://github.com/hashicorp/terraform-plugin-docs) for automated provider documentation generation and [`terraform-ls`](https://github.com/hashicorp/terraform-ls) for Terraform configuration editor integrations. + +### Plan Modification + + + +Only managed resources implement this concept. + + + +The framework provides two plan modification fields for managed resource attributes, `Default` and `PlanModifiers`, which define resource and attribute value planning behaviors. The resource [default](/terraform/plugin/framework/resources/default) and [plan modification](/terraform/plugin/framework/resources/plan-modification) documentation covers these features more in-depth. + +#### Common Use Case Plan Modification + +The [`float32default`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/float32default) package defines common use case `Default` implementations: + +- [`StaticFloat32(float32)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/float32default#StaticFloat32): Define a static float32 default value for the attribute. + +The [`float32planmodifier`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/float32planmodifier) package defines common use case `PlanModifiers` implementations: + +- [`RequiresReplace()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/float32planmodifier#RequiresReplace): Marks the resource for replacement if the resource is being updated and the plan value does not match the prior state value. +- [`RequiresReplaceIf()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/float32planmodifier#RequiresReplaceIf): Similar to `RequiresReplace()`, but also checks if a given function returns true. +- [`RequiresReplaceIfConfigured()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/float32planmodifier#RequiresReplaceIfConfigured): Similar to `RequiresReplace()`, but also checks if the configuration value is not null. +- [`UseStateForUnknown()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/float32planmodifier#UseStateForUnknown): Copies a known prior state value into the planned value. Use this when it is known that an unconfigured value will remain the same after a resource update. + +### Sensitive + +Set the `Sensitive` field if the attribute value should always be considered [sensitive data](/terraform/language/state/sensitive-data). In Terraform, this will generally mask the value in practitioner output. This setting cannot be conditionally set and does not impact how data is stored in the state. + +### WriteOnly + + + + Only managed resources implement this concept. + + + +Set the `WriteOnly` field to define a [write-only argument](/terraform/plugin/framework/resources/write-only-arguments). +Write-only arguments can accept [ephemeral values](/terraform/language/resources/ephemeral) +and are not persisted in the Terraform plan or state artifacts. Write-only arguments are supported in Terraform 1.11 and later. + +### Validation + +Set the `Validators` field to define [validation](/terraform/plugin/framework/validation#attribute-validation). This validation logic is ran in addition to any validation contained within a [custom type](#custom-types). + +#### Common Use Case Validators + +HashiCorp provides the additional [`terraform-plugin-framework-validators`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators) Go module which contains validation logic for common use cases. The [`float32validator`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators/float32validator) package within that module has float32 attribute validators such as minimum, maximum, and defining conflicting attributes. + +## Accessing Values + +The [accessing values](/terraform/plugin/framework/handling-data/accessing-values) documentation covers general methods for reading [schema](/terraform/plugin/framework/handling-data/schemas) (configuration, plan, and state) data, which is necessary before accessing an attribute value directly. The [float32 type](/terraform/plugin/framework/handling-data/types/float32#accessing-values) documentation covers methods for interacting with the attribute value itself. + +## Setting Values + +The [float32 type](/terraform/plugin/framework/handling-data/types/float32#setting-values) documentation covers methods for creating or setting the appropriate value. The [writing data](/terraform/plugin/framework/handling-data/writing-state) documentation covers general methods for writing [schema](/terraform/plugin/framework/handling-data/schemas) (plan and state) data, which is necessary afterwards. diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/attributes/float64.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/attributes/float64.mdx new file mode 100644 index 0000000000..8ede950a8d --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/attributes/float64.mdx @@ -0,0 +1,149 @@ +--- +page_title: Float64 attributes +description: >- + Learn how to use 64-bit floating point attributes with the Terraform plugin + framework. +--- + +# Float64 attributes + + + +Use [Int64 Attribute](/terraform/plugin/framework/handling-data/attributes/int64) for 64-bit integer numbers. Use [Number Attribute](/terraform/plugin/framework/handling-data/attributes/number) for arbitrary precision numbers. + + + +Float64 attributes store a 64-bit floating point number. Values are represented by a [float64 type](/terraform/plugin/framework/handling-data/types/float64) in the framework. + +In this Terraform configuration example, a float64 attribute named `example_attribute` is set to the value `1.23`: + +```hcl +resource "examplecloud_thing" "example" { + example_attribute = 1.23 +} +``` + +## Schema Definition + +Use one of the following attribute types to directly add a float64 value to a [schema](/terraform/plugin/framework/handling-data/schemas) or [nested attribute type](/terraform/plugin/framework/handling-data/attributes#nested-attribute-types): + +| Schema Type | Attribute Type | +|-------------|----------------| +| [Data Source](/terraform/plugin/framework/data-sources) | [`schema.Float64Attribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/datasource/schema#Float64Attribute) | +| [Provider](/terraform/plugin/framework/providers) | [`schema.Float64Attribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider/schema#Float64Attribute) | +| [Resource](/terraform/plugin/framework/resources) | [`schema.Float64Attribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema#Float64Attribute) | +| [Ephemeral Resource](/terraform/plugin/framework/ephemeral-resources) | [`schema.Float64Attribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/ephemeral/schema#Float64Attribute) | + +In this example, a resource schema defines a top level required float64 attribute named `example_attribute`: + +```go +func (r ThingResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "example_attribute": schema.Float64Attribute{ + Required: true, + // ... potentially other fields ... + }, + // ... potentially other attributes ... + }, + } +} +``` + +If the float64 value should be the element type of a [collection attribute type](/terraform/plugin/framework/handling-data/attributes#collection-attribute-types), set the `ElementType` field according to the [float64 type](/terraform/plugin/framework/handling-data/types/float64). Refer to the collection attribute type documentation for additional details. + +If the float64 value should be a value type of an [object attribute type](/terraform/plugin/framework/handling-data/attributes#object-attribute-type), set the `AttributeTypes` map value according to the [float64 type](/terraform/plugin/framework/handling-data/types/float64). Refer to the object attribute type documentation for additional details. + +### Configurability + +At least one of the `Computed`, `Optional`, or `Required` fields must be set to `true`. This defines how Terraform and the framework should expect data to set, whether the value is from the practitioner configuration or from the provider logic, such as API response value. + +The acceptable behaviors of these configurability options are: + +- `Required` only: The value must be practitioner configured to an eventually known value (not null), otherwise the framework will automatically raise an error diagnostic for the missing value. +- `Optional` only: The value may be practitioner configured to a known value or null. +- `Optional` and `Computed`: The value may be practitioner configured or the value may be set in provider logic when the practitioner configuration is null. +- `Computed` only: The value will be set in provider logic and any practitioner configuration causes the framework to automatically raise an error diagnostic for the unexpected configuration value. + +#### Resource Identity + +If creating a resource identity schema, set either `OptionalForImport` or `RequiredForImport` to `true` to inform Terraform if practitioners must set the attribute when importing with that resource's identity. + +The acceptable behaviors of these configurability options are: + +- `RequiredForImport` only: A practitioner must configure the attribute to a known value (not `null`), otherwise Terraform automatically raises an error diagnostic for the missing value. +- `OptionalForImport` only: A practitioner must configure the value to a known value or `null`. + +### Custom Types + +You may want to build your own attribute value and type implementations to allow your provider to combine validation, description, and plan customization behaviors into a reusable bundle. This helps avoid duplication or reimplementation and ensures consistency. These implementations use the `CustomType` field in the attribute type. + +Refer to [Custom Types](/terraform/plugin/framework/handling-data/types/custom) for further details on creating provider-defined types and values. + +### Deprecation + +Set the `DeprecationMessage` field to a practitioner-focused message for how to handle the deprecation. The framework will automatically raise a warning diagnostic with this message if the practitioner configuration contains a known value for the attribute. Terraform version 1.2.7 and later will raise a warning diagnostic in certain scenarios if the deprecated attribute value is referenced elsewhere in a practitioner configuration. The framework [deprecations](/terraform/plugin/framework/deprecations) documentation fully describes the recommended practices for deprecating an attribute or resource. + +Some practitioner-focused examples of a deprecation message include: + +- Configure `other_attribute` instead. This attribute will be removed in the next major version of the provider. +- Remove this attribute's configuration as it no longer is used and the attribute will be removed in the next major version of the provider. + +### Description + +The framework provides two description fields, `Description` and `MarkdownDescription`, which various tools use to show additional information about an attribute and its intended purpose. This includes, but is not limited to, [`terraform-plugin-docs`](https://github.com/hashicorp/terraform-plugin-docs) for automated provider documentation generation and [`terraform-ls`](https://github.com/hashicorp/terraform-ls) for Terraform configuration editor integrations. + +### Plan Modification + + + +Only managed resources implement this concept. + + + +The framework provides two plan modification fields for managed resource attributes, `Default` and `PlanModifiers`, which define resource and attribute value planning behaviors. The resource [default](/terraform/plugin/framework/resources/default) and [plan modification](/terraform/plugin/framework/resources/plan-modification) documentation covers these features more in-depth. + +#### Common Use Case Plan Modification + +The [`float64default`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/float64default) package defines common use case `Default` implementations: + +- [`StaticFloat64(float64)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/float64default#StaticFloat64): Define a static float64 default value for the attribute. + +The [`float64planmodifier`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/float64planmodifier) package defines common use case `PlanModifiers` implementations: + +- [`RequiresReplace()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/float64planmodifier#RequiresReplace): Marks the resource for replacement if the resource is being updated and the plan value does not match the prior state value. +- [`RequiresReplaceIf()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/float64planmodifier#RequiresReplaceIf): Similar to `RequiresReplace()`, but also checks if a given function returns true. +- [`RequiresReplaceIfConfigured()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/float64planmodifier#RequiresReplaceIfConfigured): Similar to `RequiresReplace()`, but also checks if the configuration value is not null. +- [`UseStateForUnknown()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/float64planmodifier#UseStateForUnknown): Copies a known prior state value into the planned value. Use this when it is known that an unconfigured value will remain the same after a resource update. + +### Sensitive + +Set the `Sensitive` field if the attribute value should always be considered [sensitive data](/terraform/language/state/sensitive-data). In Terraform, this will generally mask the value in practitioner output. This setting cannot be conditionally set and does not impact how data is stored in the state. + +### WriteOnly + + + + Only managed resources implement this concept. + + + +Set the `WriteOnly` field to define a [write-only argument](/terraform/plugin/framework/resources/write-only-arguments). +Write-only arguments can accept [ephemeral values](/terraform/language/resources/ephemeral) +and are not persisted in the Terraform plan or state artifacts. Write-only arguments are supported in Terraform 1.11 and later. + +### Validation + +Set the `Validators` field to define [validation](/terraform/plugin/framework/validation#attribute-validation). This validation logic is ran in addition to any validation contained within a [custom type](#custom-types). + +#### Common Use Case Validators + +HashiCorp provides the additional [`terraform-plugin-framework-validators`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators) Go module which contains validation logic for common use cases. The [`float64validator`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators/float64validator) package within that module has float64 attribute validators such as minimum, maximum, and defining conflicting attributes. + +## Accessing Values + +The [accessing values](/terraform/plugin/framework/handling-data/accessing-values) documentation covers general methods for reading [schema](/terraform/plugin/framework/handling-data/schemas) (configuration, plan, and state) data, which is necessary before accessing an attribute value directly. The [float64 type](/terraform/plugin/framework/handling-data/types/float64#accessing-values) documentation covers methods for interacting with the attribute value itself. + +## Setting Values + +The [float64 type](/terraform/plugin/framework/handling-data/types/float64#setting-values) documentation covers methods for creating or setting the appropriate value. The [writing data](/terraform/plugin/framework/handling-data/writing-state) documentation covers general methods for writing [schema](/terraform/plugin/framework/handling-data/schemas) (plan and state) data, which is necessary afterwards. diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/attributes/index.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/attributes/index.mdx new file mode 100644 index 0000000000..58397eacb6 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/attributes/index.mdx @@ -0,0 +1,95 @@ +--- +page_title: Attributes +description: >- + The Terraform plugin framework includes multiple built-in attribute types + and supports custom and dynamic attribute types. Each attribute and block in a + Terraform resource, data source, or provider schema maps to a framework or + custom type. +--- + +# Attributes + +Attributes are value storing fields in resource, data source, or provider [schemas](/terraform/plugin/framework/handling-data/schemas). Every attribute has an associated [value type](/terraform/plugin/framework/handling-data/types), which describes the kind of data the attribute can hold. Attributes also can describe value plan modifiers (resources only) and value validators in addition to those defined by the value type. + +## Available Attribute Types + +Schemas support the following attribute types: + +- [Primitive](#primitive-attribute-types): Attribute that contains a single value, such as a boolean, number, or string. +- [Collection](#collection-attribute-types): Attribute that contains multiple values of a single element type, such as a list, map, or set. +- [Nested](#nested-attribute-types): Attribute that defines a structure of explicit attibute names to attribute definitions, potentially with a wrapping collection type, such as a single structure of attributes or a list of structures of attributes. +- [Object](#object-attribute-type): Attribute that defines a structure of explicit attribute names to type-only definitions. +- [Dynamic](#dynamic-attribute-type): Attribute that accepts any value type. + +### Primitive Attribute Types + +Attribute types that contain a single data value, such as a boolean, number, or string. + +| Attribute Type | Use Case | +|----------------|----------| +| [Bool](/terraform/plugin/framework/handling-data/attributes/bool) | Boolean true or false | +| [Float32](/terraform/plugin/framework/handling-data/attributes/float32) | 32-bit floating point number | +| [Float64](/terraform/plugin/framework/handling-data/attributes/float64) | 64-bit floating point number | +| [Int32](/terraform/plugin/framework/handling-data/attributes/int32) | 32-bit integer number | +| [Int64](/terraform/plugin/framework/handling-data/attributes/int64) | 64-bit integer number | +| [Number](/terraform/plugin/framework/handling-data/attributes/number) | Arbitrary precision (generally over 64-bit, up to 512-bit) number | +| [String](/terraform/plugin/framework/handling-data/attributes/string) | Collection of UTF-8 encoded characters | + +### Collection Attribute Types + +Attribute types that contain multiple values of a single element type, such as a list, map, or set. + +| Attribute Type | Use Case | +|----------------|----------| +| [List](/terraform/plugin/framework/handling-data/attributes/list) | Ordered collection of single element type | +| [Map](/terraform/plugin/framework/handling-data/attributes/map) | Mapping of arbitrary string keys to values of single element type | +| [Set](/terraform/plugin/framework/handling-data/attributes/set) | Unordered, unique collection of single element type | + +### Nested Attribute Types + + + +Only supported when using [protocol version 6](/terraform/plugin/framework/provider-servers). + + + +Attribute types that define a structure of explicit attibute names to attribute definitions, potentially with a wrapping collection type, such as a single structure of attributes or a list of structures of attributes. + +| Attribute Type | Use Case | +|----------------|----------| +| [List Nested](/terraform/plugin/framework/handling-data/attributes/list-nested) | Ordered collection of structures of attributes | +| [Map Nested](/terraform/plugin/framework/handling-data/attributes/map-nested) | Mapping of arbitrary string keys to structures of attributes | +| [Set Nested](/terraform/plugin/framework/handling-data/attributes/set-nested) | Unordered, unique collection of structures of attributes | +| [Single Nested](/terraform/plugin/framework/handling-data/attributes/single-nested) | Single structure of attributes | + +### Object Attribute Type + + + +Use [nested attribute types](#nested-attribute-types) where possible. Objects have limited capabilities. + + + +Attribute type that defines a structure of explicit attribute names to type-only definitions. + +| Attribute Type | Use Case | +|----------------|----------| +| [Object](/terraform/plugin/framework/handling-data/attributes/object) | Single structure mapping explicit attribute names to type definitions | + +### Dynamic Attribute Type + + + +Static attribute types should always be preferred over dynamic attribute types, when possible. + +Developers dealing with dynamic attribute data will need to have extensive knowledge of the [Terraform type system](/terraform/language/expressions/types) to properly handle all potential practitioner configuration scenarios. + +Refer to [Dynamic Data - Considerations](/terraform/plugin/framework/handling-data/dynamic-data#considerations) for more information. + + + +Attribute type that can be any value type, determined by Terraform or the provider at runtime. + +| Attribute Type | Use Case | +|----------------|----------| +| [Dynamic](/terraform/plugin/framework/handling-data/attributes/dynamic) | Any value type of data, determined at runtime. | \ No newline at end of file diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/attributes/int32.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/attributes/int32.mdx new file mode 100644 index 0000000000..6ad24b31c9 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/attributes/int32.mdx @@ -0,0 +1,149 @@ +--- +page_title: Int32 attributes +description: >- + Learn how to use 32-bit integer attributes with the Terraform plugin + framework. +--- + +# Int32 attributes + + + +Use [Float32 Attribute](/terraform/plugin/framework/handling-data/attributes/float32) for 32-bit floating point numbers. Use [Number Attribute](/terraform/plugin/framework/handling-data/attributes/number) for arbitrary precision numbers. + + + +Int32 attributes store a 32-bit integer number. Values are represented by a [int32 type](/terraform/plugin/framework/handling-data/types/int32) in the framework. + +In this Terraform configuration example, an int32 attribute named `example_attribute` is set to the value `123`: + +```hcl +resource "examplecloud_thing" "example" { + example_attribute = 123 +} +``` + +## Schema Definition + +Use one of the following attribute types to directly add a int32 value to a [schema](/terraform/plugin/framework/handling-data/schemas) or [nested attribute type](/terraform/plugin/framework/handling-data/attributes#nested-attribute-types): + +| Schema Type | Attribute Type | +|-------------|----------------| +| [Data Source](/terraform/plugin/framework/data-sources) | [`schema.Int32Attribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/datasource/schema#Int32Attribute) | +| [Provider](/terraform/plugin/framework/providers) | [`schema.Int32Attribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider/schema#Int32Attribute) | +| [Resource](/terraform/plugin/framework/resources) | [`schema.Int32Attribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema#Int32Attribute) | +| [Ephemeral Resource](/terraform/plugin/framework/ephemeral-resources) | [`schema.Int32Attribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/ephemeral/schema#Int32Attribute) | + +In this example, a resource schema defines a top level required int32 attribute named `example_attribute`: + +```go +func (r ThingResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "example_attribute": schema.Int32Attribute{ + Required: true, + // ... potentially other fields ... + }, + // ... potentially other attributes ... + }, + } +} +``` + +If the int32 value should be the element type of a [collection attribute type](/terraform/plugin/framework/handling-data/attributes#collection-attribute-types), set the `ElementType` field according to the [int32 type](/terraform/plugin/framework/handling-data/types/int32). Refer to the collection attribute type documentation for additional details. + +If the int32 value should be a value type of an [object attribute type](/terraform/plugin/framework/handling-data/attributes#object-attribute-type), set the `AttributeTypes` map value according to the [int32 type](/terraform/plugin/framework/handling-data/types/int32). Refer to the object attribute type documentation for additional details. + +### Configurability + +At least one of the `Computed`, `Optional`, or `Required` fields must be set to `true`. This defines how Terraform and the framework should expect data to set, whether the value is from the practitioner configuration or from the provider logic, such as API response value. + +The acceptable behaviors of these configurability options are: + +- `Required` only: The value must be practitioner configured to an eventually known value (not null), otherwise the framework will automatically raise an error diagnostic for the missing value. +- `Optional` only: The value may be practitioner configured to a known value or null. +- `Optional` and `Computed`: The value may be practitioner configured or the value may be set in provider logic when the practitioner configuration is null. +- `Computed` only: The value will be set in provider logic and any practitioner configuration causes the framework to automatically raise an error diagnostic for the unexpected configuration value. + +#### Resource Identity + +If creating a resource identity schema, set either `OptionalForImport` or `RequiredForImport` to `true` to inform Terraform if practitioners must set the attribute when importing with that resource's identity. + +The acceptable behaviors of these configurability options are: + +- `RequiredForImport` only: A practitioner must configure the attribute to a known value (not `null`), otherwise Terraform automatically raises an error diagnostic for the missing value. +- `OptionalForImport` only: A practitioner must configure the value to a known value or `null`. + +### Custom Types + +You may want to build your own attribute value and type implementations to allow your provider to combine validation, description, and plan customization behaviors into a reusable bundle. This helps avoid duplication or reimplementation and ensures consistency. These implementations use the `CustomType` field in the attribute type. + +Refer to [Custom Types](/terraform/plugin/framework/handling-data/types/custom) for further details on creating provider-defined types and values. + +### Deprecation + +Set the `DeprecationMessage` field to a practitioner-focused message for how to handle the deprecation. The framework will automatically raise a warning diagnostic with this message if the practitioner configuration contains a known value for the attribute. Terraform version 1.2.7 and later will raise a warning diagnostic in certain scenarios if the deprecated attribute value is referenced elsewhere in a practitioner configuration. The framework [deprecations](/terraform/plugin/framework/deprecations) documentation fully describes the recommended practices for deprecating an attribute or resource. + +Some practitioner-focused examples of a deprecation message include: + +- Configure `other_attribute` instead. This attribute will be removed in the next major version of the provider. +- Remove this attribute's configuration as it no longer is used and the attribute will be removed in the next major version of the provider. + +### Description + +The framework provides two description fields, `Description` and `MarkdownDescription`, which various tools use to show additional information about an attribute and its intended purpose. This includes, but is not limited to, [`terraform-plugin-docs`](https://github.com/hashicorp/terraform-plugin-docs) for automated provider documentation generation and [`terraform-ls`](https://github.com/hashicorp/terraform-ls) for Terraform configuration editor integrations. + +### Plan Modification + + + +Only managed resources implement this concept. + + + +The framework provides two plan modification fields for managed resource attributes, `Default` and `PlanModifiers`, which define resource and attribute value planning behaviors. The resource [default](/terraform/plugin/framework/resources/default) and [plan modification](/terraform/plugin/framework/resources/plan-modification) documentation covers these features more in-depth. + +#### Common Use Case Plan Modification + +The [`int32default`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/int32default) package defines common use case `Default` implementations: + +- [`StaticInt32(int32)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/int32default#StaticInt32): Define a static int32 default value for the attribute. + +The [`int32planmodifier`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/int32planmodifier) package defines common use case `PlanModifiers` implementations: + +- [`RequiresReplace()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/int32planmodifier#RequiresReplace): Marks the resource for replacement if the resource is being updated and the plan value does not match the prior state value. +- [`RequiresReplaceIf()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/int32planmodifier#RequiresReplaceIf): Similar to `RequiresReplace()`, but also checks if a given function returns true. +- [`RequiresReplaceIfConfigured()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/int32planmodifier#RequiresReplaceIfConfigured): Similar to `RequiresReplace()`, but also checks if the configuration value is not null. +- [`UseStateForUnknown()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/int32planmodifier#UseStateForUnknown): Copies a known prior state value into the planned value. Use this when it is known that an unconfigured value will remain the same after a resource update. + +### Sensitive + +Set the `Sensitive` field if the attribute value should always be considered [sensitive data](/terraform/language/state/sensitive-data). In Terraform, this will generally mask the value in practitioner output. This setting cannot be conditionally set and does not impact how data is stored in the state. + +### WriteOnly + + + + Only managed resources implement this concept. + + + +Set the `WriteOnly` field to define a [write-only argument](/terraform/plugin/framework/resources/write-only-arguments). +Write-only arguments can accept [ephemeral values](/terraform/language/resources/ephemeral) +and are not persisted in the Terraform plan or state artifacts. Write-only arguments are supported in Terraform 1.11 and later. + +### Validation + +Set the `Validators` field to define [validation](/terraform/plugin/framework/validation#attribute-validation). This validation logic is ran in addition to any validation contained within a [custom type](#custom-types). + +#### Common Use Case Validators + +HashiCorp provides the additional [`terraform-plugin-framework-validators`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators) Go module which contains validation logic for common use cases. The [`int32validator`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators/int32validator) package within that module has int32 attribute validators such as minimum, maximum, and defining conflicting attributes. + +## Accessing Values + +The [accessing values](/terraform/plugin/framework/handling-data/accessing-values) documentation covers general methods for reading [schema](/terraform/plugin/framework/handling-data/schemas) (configuration, plan, and state) data, which is necessary before accessing an attribute value directly. The [int32 type](/terraform/plugin/framework/handling-data/types/int32#accessing-values) documentation covers methods for interacting with the attribute value itself. + +## Setting Values + +The [int32 type](/terraform/plugin/framework/handling-data/types/int32#setting-values) documentation covers methods for creating or setting the appropriate value. The [writing data](/terraform/plugin/framework/handling-data/writing-state) documentation covers general methods for writing [schema](/terraform/plugin/framework/handling-data/schemas) (plan and state) data, which is necessary afterwards. diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/attributes/int64.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/attributes/int64.mdx new file mode 100644 index 0000000000..82960707db --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/attributes/int64.mdx @@ -0,0 +1,149 @@ +--- +page_title: Int64 attributes +description: >- + Learn how to use 64-bit integer attributes with the Terraform plugin + framework. +--- + +# Int64 attributes + + + +Use [Float64 Attribute](/terraform/plugin/framework/handling-data/attributes/float64) for 64-bit floating point numbers. Use [Number Attribute](/terraform/plugin/framework/handling-data/attributes/number) for arbitrary precision numbers. + + + +Int64 attributes store a 64-bit integer number. Values are represented by a [int64 type](/terraform/plugin/framework/handling-data/types/int64) in the framework. + +In this Terraform configuration example, an int64 attribute named `example_attribute` is set to the value `123`: + +```hcl +resource "examplecloud_thing" "example" { + example_attribute = 123 +} +``` + +## Schema Definition + +Use one of the following attribute types to directly add a int64 value to a [schema](/terraform/plugin/framework/handling-data/schemas) or [nested attribute type](/terraform/plugin/framework/handling-data/attributes#nested-attribute-types): + +| Schema Type | Attribute Type | +|-------------|----------------| +| [Data Source](/terraform/plugin/framework/data-sources) | [`schema.Int64Attribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/datasource/schema#Int64Attribute) | +| [Provider](/terraform/plugin/framework/providers) | [`schema.Int64Attribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider/schema#Int64Attribute) | +| [Resource](/terraform/plugin/framework/resources) | [`schema.Int64Attribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema#Int64Attribute) | +| [Ephemeral Resource](/terraform/plugin/framework/ephemeral-resources) | [`schema.Int64Attribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/ephemeral/schema#Int64Attribute) | + +In this example, a resource schema defines a top level required int64 attribute named `example_attribute`: + +```go +func (r ThingResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "example_attribute": schema.Int64Attribute{ + Required: true, + // ... potentially other fields ... + }, + // ... potentially other attributes ... + }, + } +} +``` + +If the int64 value should be the element type of a [collection attribute type](/terraform/plugin/framework/handling-data/attributes#collection-attribute-types), set the `ElementType` field according to the [int64 type](/terraform/plugin/framework/handling-data/types/int64). Refer to the collection attribute type documentation for additional details. + +If the int64 value should be a value type of an [object attribute type](/terraform/plugin/framework/handling-data/attributes#object-attribute-type), set the `AttributeTypes` map value according to the [int64 type](/terraform/plugin/framework/handling-data/types/int64). Refer to the object attribute type documentation for additional details. + +### Configurability + +At least one of the `Computed`, `Optional`, or `Required` fields must be set to `true`. This defines how Terraform and the framework should expect data to set, whether the value is from the practitioner configuration or from the provider logic, such as API response value. + +The acceptable behaviors of these configurability options are: + +- `Required` only: The value must be practitioner configured to an eventually known value (not null), otherwise the framework will automatically raise an error diagnostic for the missing value. +- `Optional` only: The value may be practitioner configured to a known value or null. +- `Optional` and `Computed`: The value may be practitioner configured or the value may be set in provider logic when the practitioner configuration is null. +- `Computed` only: The value will be set in provider logic and any practitioner configuration causes the framework to automatically raise an error diagnostic for the unexpected configuration value. + +#### Resource Identity + +If creating a resource identity schema, set either `OptionalForImport` or `RequiredForImport` to `true` to inform Terraform if practitioners must set the attribute when importing with that resource's identity. + +The acceptable behaviors of these configurability options are: + +- `RequiredForImport` only: A practitioner must configure the attribute to a known value (not `null`), otherwise Terraform automatically raises an error diagnostic for the missing value. +- `OptionalForImport` only: A practitioner must configure the value to a known value or `null`. + +### Custom Types + +You may want to build your own attribute value and type implementations to allow your provider to combine validation, description, and plan customization behaviors into a reusable bundle. This helps avoid duplication or reimplementation and ensures consistency. These implementations use the `CustomType` field in the attribute type. + +Refer to [Custom Types](/terraform/plugin/framework/handling-data/types/custom) for further details on creating provider-defined types and values. + +### Deprecation + +Set the `DeprecationMessage` field to a practitioner-focused message for how to handle the deprecation. The framework will automatically raise a warning diagnostic with this message if the practitioner configuration contains a known value for the attribute. Terraform version 1.2.7 and later will raise a warning diagnostic in certain scenarios if the deprecated attribute value is referenced elsewhere in a practitioner configuration. The framework [deprecations](/terraform/plugin/framework/deprecations) documentation fully describes the recommended practices for deprecating an attribute or resource. + +Some practitioner-focused examples of a deprecation message include: + +- Configure `other_attribute` instead. This attribute will be removed in the next major version of the provider. +- Remove this attribute's configuration as it no longer is used and the attribute will be removed in the next major version of the provider. + +### Description + +The framework provides two description fields, `Description` and `MarkdownDescription`, which various tools use to show additional information about an attribute and its intended purpose. This includes, but is not limited to, [`terraform-plugin-docs`](https://github.com/hashicorp/terraform-plugin-docs) for automated provider documentation generation and [`terraform-ls`](https://github.com/hashicorp/terraform-ls) for Terraform configuration editor integrations. + +### Plan Modification + + + +Only managed resources implement this concept. + + + +The framework provides two plan modification fields for managed resource attributes, `Default` and `PlanModifiers`, which define resource and attribute value planning behaviors. The resource [default](/terraform/plugin/framework/resources/default) and [plan modification](/terraform/plugin/framework/resources/plan-modification) documentation covers these features more in-depth. + +#### Common Use Case Plan Modification + +The [`int64default`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/int64default) package defines common use case `Default` implementations: + +- [`StaticInt64(int64)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/int64default#StaticInt64): Define a static int64 default value for the attribute. + +The [`int64planmodifier`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/int64planmodifier) package defines common use case `PlanModifiers` implementations: + +- [`RequiresReplace()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/int64planmodifier#RequiresReplace): Marks the resource for replacement if the resource is being updated and the plan value does not match the prior state value. +- [`RequiresReplaceIf()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/int64planmodifier#RequiresReplaceIf): Similar to `RequiresReplace()`, but also checks if a given function returns true. +- [`RequiresReplaceIfConfigured()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/int64planmodifier#RequiresReplaceIfConfigured): Similar to `RequiresReplace()`, but also checks if the configuration value is not null. +- [`UseStateForUnknown()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/int64planmodifier#UseStateForUnknown): Copies a known prior state value into the planned value. Use this when it is known that an unconfigured value will remain the same after a resource update. + +### Sensitive + +Set the `Sensitive` field if the attribute value should always be considered [sensitive data](/terraform/language/state/sensitive-data). In Terraform, this will generally mask the value in practitioner output. This setting cannot be conditionally set and does not impact how data is stored in the state. + +### WriteOnly + + + + Only managed resources implement this concept. + + + +Set the `WriteOnly` field to define a [write-only argument](/terraform/plugin/framework/resources/write-only-arguments). +Write-only arguments can accept [ephemeral values](/terraform/language/resources/ephemeral) +and are not persisted in the Terraform plan or state artifacts. Write-only arguments are supported in Terraform 1.11 and later. + +### Validation + +Set the `Validators` field to define [validation](/terraform/plugin/framework/validation#attribute-validation). This validation logic is ran in addition to any validation contained within a [custom type](#custom-types). + +#### Common Use Case Validators + +HashiCorp provides the additional [`terraform-plugin-framework-validators`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators) Go module which contains validation logic for common use cases. The [`int64validator`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators/int64validator) package within that module has int64 attribute validators such as minimum, maximum, and defining conflicting attributes. + +## Accessing Values + +The [accessing values](/terraform/plugin/framework/handling-data/accessing-values) documentation covers general methods for reading [schema](/terraform/plugin/framework/handling-data/schemas) (configuration, plan, and state) data, which is necessary before accessing an attribute value directly. The [int64 type](/terraform/plugin/framework/handling-data/types/int64#accessing-values) documentation covers methods for interacting with the attribute value itself. + +## Setting Values + +The [int64 type](/terraform/plugin/framework/handling-data/types/int64#setting-values) documentation covers methods for creating or setting the appropriate value. The [writing data](/terraform/plugin/framework/handling-data/writing-state) documentation covers general methods for writing [schema](/terraform/plugin/framework/handling-data/schemas) (plan and state) data, which is necessary afterwards. diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/attributes/list-nested.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/attributes/list-nested.mdx new file mode 100644 index 0000000000..c3525e0101 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/attributes/list-nested.mdx @@ -0,0 +1,190 @@ +--- +page_title: List nested attributes +description: >- + Learn how to use list nested attributes with the Terraform plugin framework. +--- + +# List nested attributes + +List nested attributes store an ordered collection of nested objects. Values are represented by a [list type](/terraform/plugin/framework/handling-data/types/list) in the framework, containing elements of [object type](/terraform/plugin/framework/handling-data/types/object). + +In this Terraform configuration example, a list nested attribute named `example_attribute` is set to the ordered object values of `attr` to `"one"` and `attr` to `"two"`: + +```hcl +resource "examplecloud_thing" "example" { + example_attribute = [ + { + attr = "one" + }, + { + attr = "two" + }, + ] +} +``` + +## Schema Definition + +Use one of the following attribute types to directly add a list nested value to a [schema](/terraform/plugin/framework/handling-data/schemas) or [nested attribute type](/terraform/plugin/framework/handling-data/attributes#nested-attribute-types): + +| Schema Type | Attribute Type | +|-------------|----------------| +| [Data Source](/terraform/plugin/framework/data-sources) | [`schema.ListNestedAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/datasource/schema#ListNestedAttribute) | +| [Provider](/terraform/plugin/framework/providers) | [`schema.ListNestedAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider/schema#ListNestedAttribute) | +| [Resource](/terraform/plugin/framework/resources) | [`schema.ListNestedAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema#ListNestedAttribute) | +| [Ephemeral Resource](/terraform/plugin/framework/ephemeral-resources) | [`schema.ListNestedAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/ephemeral/schema#ListNestedAttribute) | + +The `NestedObject` field must be defined, which represents the [object value type](/terraform/plugin/framework/handling-data/types/object) of every element of the list. + +In this example, a resource schema defines a top level required list nested attribute named `example_attribute` with a required string attribute named `attr`: + +```go +func (r ThingResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "example_attribute": schema.ListNestedAttribute{ + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "attr": schema.StringAttribute{ + Required: true, + // ... potentially other fields ... + }, + }, + }, + Required: true, + // ... potentially other fields ... + }, + // ... potentially other attributes ... + }, + } +} +``` + +Its [value type](/terraform/plugin/framework/handling-data/types) would be represented as a `types.List` of `types.Object` where the `types.Object` is a mapping of `attr` to `types.String`. + +A nested attribute type may itself contain further collection or nested attribute types, if necessary. + +In this example, a resource schema defines a top level required list nested attribute named `example_attribute` with a required list of strings attribute named `attr1` and an optional single nested attribute named `attr2`: + +```go +func (r ThingResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "example_attribute": schema.ListNestedAttribute{ + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "attr1": schema.ListAttribute{ + ElementType: types.StringType, + Required: true, + // ... potentially other fields ... + }, + "attr2": schema.SingleNestedAttribute{ + Attributes: map[string]schema.Attribute{ /* ... */ }, + Optional: true, + // ... potentially other fields ... + }, + }, + }, + Required: true, + // ... potentially other fields ... + }, + // ... potentially other attributes ... + }, + } +} +``` + +Its [value type](/terraform/plugin/framework/handling-data/types) would be represented as a `types.List` of `types.Object` where the `types.Object` is a mapping of `attr1` to `types.List` of `types.String` and `attr2` to `types.Object`. + +### Configurability + + + +Only the list nested attribute itself is defined by the `schema.ListNestedAttribute` configurability fields. Nested attributes must define their own configurability fields within each attribute definition. + + + +At least one of the `Computed`, `Optional`, or `Required` fields must be set to `true`. This defines how Terraform and the framework should expect data to set, whether the value is from the practitioner configuration or from the provider logic, such as API response value. + +The acceptable behaviors of these configurability options are: + +- `Required` only: The value must be practitioner configured to an eventually known value (not null), otherwise the framework will automatically raise an error diagnostic for the missing value. +- `Optional` only: The value may be practitioner configured to a known value or null. +- `Optional` and `Computed`: The value may be practitioner configured or the value may be set in provider logic when the practitioner configuration is null. +- `Computed` only: The value will be set in provider logic and any practitioner configuration causes the framework to automatically raise an error diagnostic for the unexpected configuration value. + +### Custom Types + +You may want to build your own attribute value and type implementations to allow your provider to combine validation, description, and plan customization behaviors into a reusable bundle. This helps avoid duplication or reimplementation and ensures consistency. These implementations use the `CustomType` field in the attribute type. + +Refer to [Custom Types](/terraform/plugin/framework/handling-data/types/custom) for further details on creating provider-defined types and values. + +### Deprecation + +Set the `DeprecationMessage` field to a practitioner-focused message for how to handle the deprecation. The framework will automatically raise a warning diagnostic with this message if the practitioner configuration contains a known value for the attribute. Terraform version 1.2.7 and later will raise a warning diagnostic in certain scenarios if the deprecated attribute value is referenced elsewhere in a practitioner configuration. The framework [deprecations](/terraform/plugin/framework/deprecations) documentation fully describes the recommended practices for deprecating an attribute or resource. + +Some practitioner-focused examples of a deprecation message include: + +- Configure `other_attribute` instead. This attribute will be removed in the next major version of the provider. +- Remove this attribute's configuration as it no longer is used and the attribute will be removed in the next major version of the provider. + +### Description + +The framework provides two description fields, `Description` and `MarkdownDescription`, which various tools use to show additional information about an attribute and its intended purpose. This includes, but is not limited to, [`terraform-plugin-docs`](https://github.com/hashicorp/terraform-plugin-docs) for automated provider documentation generation and [`terraform-ls`](https://github.com/hashicorp/terraform-ls) for Terraform configuration editor integrations. + +### Plan Modification + + + +Only managed resources implement this concept. + + + +The framework provides two plan modification fields for managed resource attributes, `Default` and `PlanModifiers`, which define resource and attribute value planning behaviors. The resource [default](/terraform/plugin/framework/resources/default) and [plan modification](/terraform/plugin/framework/resources/plan-modification) documentation covers these features more in-depth. + +#### Common Use Case Plan Modification + +The [`listdefault`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/listdefault) package defines common use case `Default` implementations: + +- [`StaticValue(types.List)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/listdefault#StaticValue): Define a static list default value for the attribute. + +The [`listplanmodifier`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/listplanmodifier) package defines common use case `PlanModifiers` implementations: + +- [`RequiresReplace()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/listplanmodifier#RequiresReplace): Marks the resource for replacement if the resource is being updated and the plan value does not match the prior state value. +- [`RequiresReplaceIf()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/listplanmodifier#RequiresReplaceIf): Similar to `RequiresReplace()`, but also checks if a given function returns true. +- [`RequiresReplaceIfConfigured()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/listplanmodifier#RequiresReplaceIfConfigured): Similar to `RequiresReplace()`, but also checks if the configuration value is not null. +- [`UseStateForUnknown()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/listplanmodifier#UseStateForUnknown): Copies a known prior state value into the planned value. Use this when it is known that an unconfigured value will remain the same after a resource update. + +### Sensitive + +Set the `Sensitive` field if the attribute value should always be considered [sensitive data](/terraform/language/state/sensitive-data). In Terraform, this will generally mask the value in practitioner output. This setting cannot be conditionally set and does not impact how data is stored in the state. + +### WriteOnly + + + + Only managed resources implement this concept. + + + +Set the `WriteOnly` field to define a [write-only argument](/terraform/plugin/framework/resources/write-only-arguments). +Write-only arguments can accept [ephemeral values](/terraform/language/resources/ephemeral) +and are not persisted in the Terraform plan or state artifacts. Write-only arguments are supported in Terraform 1.11 and later. + +If a nested attribute has the `WriteOnly` field set, all child attributes must also have `WriteOnly` set. + +### Validation + +Set the `Validators` field to define [validation](/terraform/plugin/framework/validation#attribute-validation). This validation logic is ran in addition to any validation contained within a [custom type](#custom-types). + +#### Common Use Case Validators + +HashiCorp provides the additional [`terraform-plugin-framework-validators`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators) Go module which contains validation logic for common use cases. The [`listvalidator`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators/listvalidator) package within that module has list attribute validators such as defining conflicting attributes. + +## Accessing Values + +The [accessing values](/terraform/plugin/framework/handling-data/accessing-values) documentation covers general methods for reading [schema](/terraform/plugin/framework/handling-data/schemas) (configuration, plan, and state) data, which is necessary before accessing an attribute value directly. The [list type](/terraform/plugin/framework/handling-data/types/list#accessing-values) documentation covers methods for interacting with the attribute value itself. + +## Setting Values + +The [list type](/terraform/plugin/framework/handling-data/types/list#setting-values) documentation covers methods for creating or setting the appropriate value. The [writing data](/terraform/plugin/framework/handling-data/writing-state) documentation covers general methods for writing [schema](/terraform/plugin/framework/handling-data/schemas) (plan and state) data, which is necessary afterwards. diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/attributes/list.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/attributes/list.mdx new file mode 100644 index 0000000000..3d88ab1b70 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/attributes/list.mdx @@ -0,0 +1,166 @@ +--- +page_title: List attributes +description: >- + Learn how to use list attributes with the Terraform plugin framework. +--- + +# List attributes + +List attributes store an ordered collection of single element type. Values are represented by a [list type](/terraform/plugin/framework/handling-data/types/list) in the framework, containing elements of the element type. + +In this Terraform configuration example, a list of string attribute named `example_attribute` is set to the ordered values `"one"` and `"two"`: + +```hcl +resource "examplecloud_thing" "example" { + example_attribute = ["one", "two"] +} +``` + +## Schema Definition + +Use one of the following attribute types to directly add a list value to a [schema](/terraform/plugin/framework/handling-data/schemas) or [nested attribute type](/terraform/plugin/framework/handling-data/attributes#nested-attribute-types): + +| Schema Type | Attribute Type | +|-------------|----------------| +| [Data Source](/terraform/plugin/framework/data-sources) | [`schema.ListAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/datasource/schema#ListAttribute) | +| [Provider](/terraform/plugin/framework/providers) | [`schema.ListAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider/schema#ListAttribute) | +| [Resource](/terraform/plugin/framework/resources) | [`schema.ListAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema#ListAttribute) | +| [Ephemeral Resource](/terraform/plugin/framework/ephemeral-resources) | [`schema.ListAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/ephemeral/schema#ListAttribute) | + +The `ElementType` field must be defined, which represents the single [value type](/terraform/plugin/framework/handling-data/types) of every element of the list. + +In this example, a resource schema defines a top level required list of strings attribute named `example_attribute`: + +```go +func (r ThingResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "example_attribute": schema.ListAttribute{ + ElementType: types.StringType, + Required: true, + // ... potentially other fields ... + }, + // ... potentially other attributes ... + }, + } +} +``` + +An element type may itself contain further collection types, if necessary. + +In this example, a resource schema defines a top level required list of list of strings attribute named `example_attribute`: + +```go +func (r ThingResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "example_attribute": schema.ListAttribute{ + ElementType: types.ListType{ + ElemType: types.StringType, + }, + Required: true, + // ... potentially other fields ... + }, + // ... potentially other attributes ... + }, + } +} +``` + +If the list value should be the element type of a [collection attribute type](/terraform/plugin/framework/handling-data/attributes#collection-attribute-types), set the `ElementType` field according to the [list type](/terraform/plugin/framework/handling-data/types/list). Refer to the collection attribute type documentation for additional details. + +If the list value should be a value type of an [object attribute type](/terraform/plugin/framework/handling-data/attributes#object-attribute-type), set the `AttributeTypes` map value according to the [list type](/terraform/plugin/framework/handling-data/types/list). Refer to the object attribute type documentation for additional details. + +### Configurability + +At least one of the `Computed`, `Optional`, or `Required` fields must be set to `true`. This defines how Terraform and the framework should expect data to set, whether the value is from the practitioner configuration or from the provider logic, such as API response value. + +The acceptable behaviors of these configurability options are: + +- `Required` only: The value must be practitioner configured to an eventually known value (not null), otherwise the framework will automatically raise an error diagnostic for the missing value. +- `Optional` only: The value may be practitioner configured to a known value or null. +- `Optional` and `Computed`: The value may be practitioner configured or the value may be set in provider logic when the practitioner configuration is null. +- `Computed` only: The value will be set in provider logic and any practitioner configuration causes the framework to automatically raise an error diagnostic for the unexpected configuration value. + +#### Resource Identity + +If creating a resource identity schema, set either `OptionalForImport` or `RequiredForImport` to `true` to inform Terraform if practitioners must set the attribute when importing with that resource's identity. + +The acceptable behaviors of these configurability options are: + +- `RequiredForImport` only: A practitioner must configure the attribute to a known value (not `null`), otherwise Terraform automatically raises an error diagnostic for the missing value. +- `OptionalForImport` only: A practitioner must configure the value to a known value or `null`. + +### Custom Types + +You may want to build your own attribute value and type implementations to allow your provider to combine validation, description, and plan customization behaviors into a reusable bundle. This helps avoid duplication or reimplementation and ensures consistency. These implementations use the `CustomType` field in the attribute type. + +Refer to [Custom Types](/terraform/plugin/framework/handling-data/types/custom) for further details on creating provider-defined types and values. + +### Deprecation + +Set the `DeprecationMessage` field to a practitioner-focused message for how to handle the deprecation. The framework will automatically raise a warning diagnostic with this message if the practitioner configuration contains a known value for the attribute. Terraform version 1.2.7 and later will raise a warning diagnostic in certain scenarios if the deprecated attribute value is referenced elsewhere in a practitioner configuration. The framework [deprecations](/terraform/plugin/framework/deprecations) documentation fully describes the recommended practices for deprecating an attribute or resource. + +Some practitioner-focused examples of a deprecation message include: + +- Configure `other_attribute` instead. This attribute will be removed in the next major version of the provider. +- Remove this attribute's configuration as it no longer is used and the attribute will be removed in the next major version of the provider. + +### Description + +The framework provides two description fields, `Description` and `MarkdownDescription`, which various tools use to show additional information about an attribute and its intended purpose. This includes, but is not limited to, [`terraform-plugin-docs`](https://github.com/hashicorp/terraform-plugin-docs) for automated provider documentation generation and [`terraform-ls`](https://github.com/hashicorp/terraform-ls) for Terraform configuration editor integrations. + +### Plan Modification + + + +Only managed resources implement this concept. + + + +The framework provides two plan modification fields for managed resource attributes, `Default` and `PlanModifiers`, which define resource and attribute value planning behaviors. The resource [default](/terraform/plugin/framework/resources/default) and [plan modification](/terraform/plugin/framework/resources/plan-modification) documentation covers these features more in-depth. + +#### Common Use Case Plan Modification + +The [`listdefault`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/listdefault) package defines common use case `Default` implementations: + +- [`StaticValue(types.List)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/listdefault#StaticValue): Define a static list default value for the attribute. + +The [`listplanmodifier`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/listplanmodifier) package defines common use case `PlanModifiers` implementations: + +- [`RequiresReplace()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/listplanmodifier#RequiresReplace): Marks the resource for replacement if the resource is being updated and the plan value does not match the prior state value. +- [`RequiresReplaceIf()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/listplanmodifier#RequiresReplaceIf): Similar to `RequiresReplace()`, but also checks if a given function returns true. +- [`RequiresReplaceIfConfigured()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/listplanmodifier#RequiresReplaceIfConfigured): Similar to `RequiresReplace()`, but also checks if the configuration value is not null. +- [`UseStateForUnknown()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/listplanmodifier#UseStateForUnknown): Copies a known prior state value into the planned value. Use this when it is known that an unconfigured value will remain the same after a resource update. + +### Sensitive + +Set the `Sensitive` field if the attribute value should always be considered [sensitive data](/terraform/language/state/sensitive-data). In Terraform, this will generally mask the value in practitioner output. This setting cannot be conditionally set and does not impact how data is stored in the state. + +### WriteOnly + + + + Only managed resources implement this concept. + + + +Set the `WriteOnly` field to define a [write-only argument](/terraform/plugin/framework/resources/write-only-arguments). +Write-only arguments can accept [ephemeral values](/terraform/language/resources/ephemeral) +and are not persisted in the Terraform plan or state artifacts. Write-only arguments are supported in Terraform 1.11 and later. + +### Validation + +Set the `Validators` field to define [validation](/terraform/plugin/framework/validation#attribute-validation). This validation logic is ran in addition to any validation contained within a [custom type](#custom-types). + +#### Common Use Case Validators + +HashiCorp provides the additional [`terraform-plugin-framework-validators`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators) Go module which contains validation logic for common use cases. The [`listvalidator`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators/listvalidator) package within that module has list attribute validators such as defining conflicting attributes. + +## Accessing Values + +The [accessing values](/terraform/plugin/framework/handling-data/accessing-values) documentation covers general methods for reading [schema](/terraform/plugin/framework/handling-data/schemas) (configuration, plan, and state) data, which is necessary before accessing an attribute value directly. The [list type](/terraform/plugin/framework/handling-data/types/list#accessing-values) documentation covers methods for interacting with the attribute value itself. + +## Setting Values + +The [list type](/terraform/plugin/framework/handling-data/types/list#setting-values) documentation covers methods for creating or setting the appropriate value. The [writing data](/terraform/plugin/framework/handling-data/writing-state) documentation covers general methods for writing [schema](/terraform/plugin/framework/handling-data/schemas) (plan and state) data, which is necessary afterwards. diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/attributes/map-nested.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/attributes/map-nested.mdx new file mode 100644 index 0000000000..39b3a2a24a --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/attributes/map-nested.mdx @@ -0,0 +1,190 @@ +--- +page_title: Map nested attributes +description: >- + Learn how to use map nested attributes with the Terraform plugin framework. +--- + +# Map Nested Attribute + +Map nested attributes store mapping of arbitrary string keys to nested objects. Values are represented by a [map type](/terraform/plugin/framework/handling-data/types/map) in the framework, containing elements of [object type](/terraform/plugin/framework/handling-data/types/object). + +In this Terraform configuration example, a map nested attribute named `example_attribute` is set to the mapped values of `"key1"` to the object value of `attr` to `"one"` and `"key2"` to the object value of `attr` to `"two"`: + +```hcl +resource "examplecloud_thing" "example" { + example_attribute = { + "key1" = { + attr = "one" + }, + "key2" = { + attr = "two" + }, + } +} +``` + +## Schema Definition + +Use one of the following attribute types to directly add a map nested value to a [schema](/terraform/plugin/framework/handling-data/schemas) or [nested attribute type](/terraform/plugin/framework/handling-data/attributes#nested-attribute-types): + +| Schema Type | Attribute Type | +|-------------|----------------| +| [Data Source](/terraform/plugin/framework/data-sources) | [`schema.MapNestedAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/datasource/schema#MapNestedAttribute) | +| [Provider](/terraform/plugin/framework/providers) | [`schema.MapNestedAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider/schema#MapNestedAttribute) | +| [Resource](/terraform/plugin/framework/resources) | [`schema.MapNestedAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema#MapNestedAttribute) | +| [Ephemeral Resource](/terraform/plugin/framework/ephemeral-resources) | [`schema.MapNestedAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/ephemeral/schema#MapNestedAttribute) | + +The `NestedObject` field must be defined, which represents the [object value type](/terraform/plugin/framework/handling-data/types/object) of every element of the list. + +In this example, a resource schema defines a top level required map nested attribute named `example_attribute` with a required string attribute named `attr`: + +```go +func (r ThingResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "example_attribute": schema.MapNestedAttribute{ + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "attr": schema.StringAttribute{ + Required: true, + // ... potentially other fields ... + }, + }, + }, + Required: true, + // ... potentially other fields ... + }, + // ... potentially other attributes ... + }, + } +} +``` + +Its [value type](/terraform/plugin/framework/handling-data/types) would be represented as a `types.Map` of `types.Object` where the `types.Object` is a mapping of `attr` to `types.String`. + +A nested attribute type may itself contain further collection or nested attribute types, if necessary. + +In this example, a resource schema defines a top level required map nested attribute named `example_attribute` with a required list of strings attribute named `attr1` and an optional single nested attribute named `attr2`: + +```go +func (r ThingResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "example_attribute": schema.MapNestedAttribute{ + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "attr1": schema.ListAttribute{ + ElementType: types.StringType, + Required: true, + // ... potentially other fields ... + }, + "attr2": schema.SingleNestedAttribute{ + Attributes: map[string]schema.Attribute{ /* ... */ }, + Optional: true, + // ... potentially other fields ... + }, + }, + }, + Required: true, + // ... potentially other fields ... + }, + // ... potentially other attributes ... + }, + } +} +``` + +Its [value type](/terraform/plugin/framework/handling-data/types) would be represented as a `types.Map` of `types.Object` where the `types.Object` is a mapping of `attr1` to `types.List` of `types.String` and `attr2` to `types.Object`. + +### Configurability + + + +Only the map nested attribute itself is defined by the `schema.MapNestedAttribute` configurability fields. Nested attributes must define their own configurability fields within each attribute definition. + + + +At least one of the `Computed`, `Optional`, or `Required` fields must be set to `true`. This defines how Terraform and the framework should expect data to set, whether the value is from the practitioner configuration or from the provider logic, such as API response value. + +The acceptable behaviors of these configurability options are: + +- `Required` only: The value must be practitioner configured to an eventually known value (not null), otherwise the framework will automatically raise an error diagnostic for the missing value. +- `Optional` only: The value may be practitioner configured to a known value or null. +- `Optional` and `Computed`: The value may be practitioner configured or the value may be set in provider logic when the practitioner configuration is null. +- `Computed` only: The value will be set in provider logic and any practitioner configuration causes the framework to automatically raise an error diagnostic for the unexpected configuration value. + +### Custom Types + +You may want to build your own attribute value and type implementations to allow your provider to combine validation, description, and plan customization behaviors into a reusable bundle. This helps avoid duplication or reimplementation and ensures consistency. These implementations use the `CustomType` field in the attribute type. + +Refer to [Custom Types](/terraform/plugin/framework/handling-data/types/custom) for further details on creating provider-defined types and values. + +### Deprecation + +Set the `DeprecationMessage` field to a practitioner-focused message for how to handle the deprecation. The framework will automatically raise a warning diagnostic with this message if the practitioner configuration contains a known value for the attribute. Terraform version 1.2.7 and later will raise a warning diagnostic in certain scenarios if the deprecated attribute value is referenced elsewhere in a practitioner configuration. The framework [deprecations](/terraform/plugin/framework/deprecations) documentation fully describes the recommended practices for deprecating an attribute or resource. + +Some practitioner-focused examples of a deprecation message include: + +- Configure `other_attribute` instead. This attribute will be removed in the next major version of the provider. +- Remove this attribute's configuration as it no longer is used and the attribute will be removed in the next major version of the provider. + +### Description + +The framework provides two description fields, `Description` and `MarkdownDescription`, which various tools use to show additional information about an attribute and its intended purpose. This includes, but is not limited to, [`terraform-plugin-docs`](https://github.com/hashicorp/terraform-plugin-docs) for automated provider documentation generation and [`terraform-ls`](https://github.com/hashicorp/terraform-ls) for Terraform configuration editor integrations. + +### Plan Modification + + + +Only managed resources implement this concept. + + + +The framework provides two plan modification fields for managed resource attributes, `Default` and `PlanModifiers`, which define resource and attribute value planning behaviors. The resource [default](/terraform/plugin/framework/resources/default) and [plan modification](/terraform/plugin/framework/resources/plan-modification) documentation covers these features more in-depth. + +#### Common Use Case Plan Modification + +The [`mapdefault`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/mapdefault) package defines common use case `Default` implementations: + +- [`StaticValue(types.Map)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/mapdefault#StaticValue): Define a static list default value for the attribute. + +The [`mapplanmodifier`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/mapplanmodifier) package defines common use case `PlanModifiers` implementations: + +- [`RequiresReplace()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/mapplanmodifier#RequiresReplace): Marks the resource for replacement if the resource is being updated and the plan value does not match the prior state value. +- [`RequiresReplaceIf()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/mapplanmodifier#RequiresReplaceIf): Similar to `RequiresReplace()`, but also checks if a given function returns true. +- [`RequiresReplaceIfConfigured()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/mapplanmodifier#RequiresReplaceIfConfigured): Similar to `RequiresReplace()`, but also checks if the configuration value is not null. +- [`UseStateForUnknown()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/mapplanmodifier#UseStateForUnknown): Copies a known prior state value into the planned value. Use this when it is known that an unconfigured value will remain the same after a resource update. + +### Sensitive + +Set the `Sensitive` field if the attribute value should always be considered [sensitive data](/terraform/language/state/sensitive-data). In Terraform, this will generally mask the value in practitioner output. This setting cannot be conditionally set and does not impact how data is stored in the state. + +### WriteOnly + + + + Only managed resources implement this concept. + + + +Set the `WriteOnly` field to define a [write-only argument](/terraform/plugin/framework/resources/write-only-arguments). +Write-only arguments can accept [ephemeral values](/terraform/language/resources/ephemeral) +and are not persisted in the Terraform plan or state artifacts. Write-only arguments are supported in Terraform 1.11 and later. + +If a nested attribute has the `WriteOnly` field set, all child attributes must also have `WriteOnly` set. + +### Validation + +Set the `Validators` field to define [validation](/terraform/plugin/framework/validation#attribute-validation). This validation logic is ran in addition to any validation contained within a [custom type](#custom-types). + +#### Common Use Case Validators + +HashiCorp provides the additional [`terraform-plugin-framework-validators`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators) Go module which contains validation logic for common use cases. The [`mapvalidator`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators/mapvalidator) package within that module has map attribute validators such as defining conflicting attributes. + +## Accessing Values + +The [accessing values](/terraform/plugin/framework/handling-data/accessing-values) documentation covers general methods for reading [schema](/terraform/plugin/framework/handling-data/schemas) (configuration, plan, and state) data, which is necessary before accessing an attribute value directly. The [map type](/terraform/plugin/framework/handling-data/types/map#accessing-values) documentation covers methods for interacting with the attribute value itself. + +## Setting Values + +The [map type](/terraform/plugin/framework/handling-data/types/map#setting-values) documentation covers methods for creating or setting the appropriate value. The [writing data](/terraform/plugin/framework/handling-data/writing-state) documentation covers general methods for writing [schema](/terraform/plugin/framework/handling-data/schemas) (plan and state) data, which is necessary afterwards. diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/attributes/map.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/attributes/map.mdx new file mode 100644 index 0000000000..5c791e3228 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/attributes/map.mdx @@ -0,0 +1,160 @@ +--- +page_title: Map attributes +description: >- + Learn how to use map attributes with the Terraform plugin framework. +--- + +# Map attributes + +Map attributes store a mapping of arbitrary string keys to values of single element type. Values are represented by a [map type](/terraform/plugin/framework/handling-data/types/map) in the framework, containing elements of the element type. + +In this Terraform configuration example, a map of string attribute named `example_attribute` is set to the mapped values of `"key1"` to `"value1"` and `"key2"` to `"value2"`: + +```hcl +resource "examplecloud_thing" "example" { + example_attribute = { + "key1" = "value1" + "key2" = "value2" + } +} +``` + +## Schema Definition + +Use one of the following attribute types to directly add a map value to a [schema](/terraform/plugin/framework/handling-data/schemas) or [nested attribute type](/terraform/plugin/framework/handling-data/attributes#nested-attribute-types): + +| Schema Type | Attribute Type | +|-------------|----------------| +| [Data Source](/terraform/plugin/framework/data-sources) | [`schema.MapAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/datasource/schema#MapAttribute) | +| [Provider](/terraform/plugin/framework/providers) | [`schema.MapAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider/schema#MapAttribute) | +| [Resource](/terraform/plugin/framework/resources) | [`schema.MapAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema#MapAttribute) | +| [Ephemeral Resource](/terraform/plugin/framework/ephemeral-resources) | [`schema.MapAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/ephemeral/schema#MapAttribute) | + +The `ElementType` field must be defined, which represents the single [value type](/terraform/plugin/framework/handling-data/types) of every element of the map. + +In this example, a resource schema defines a top level required map of strings attribute named `example_attribute`: + +```go +func (r ThingResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "example_attribute": schema.MapAttribute{ + ElementType: types.StringType, + Required: true, + // ... potentially other fields ... + }, + // ... potentially other attributes ... + }, + } +} +``` + +An element type may itself contain further collection types, if necessary. + +In this example, a resource schema defines a top level required map of map of strings attribute named `example_attribute`: + +```go +func (r ThingResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "example_attribute": schema.MapAttribute{ + ElementType: types.MapType{ + ElemType: types.StringType, + }, + Required: true, + // ... potentially other fields ... + }, + // ... potentially other attributes ... + }, + } +} +``` + +If the map value should be the element type of a [collection attribute type](/terraform/plugin/framework/handling-data/attributes#collection-attribute-types), set the `ElementType` field according to the [map type](/terraform/plugin/framework/handling-data/types/map). Refer to the collection attribute type documentation for additional details. + +If the map value should be a value type of an [object attribute type](/terraform/plugin/framework/handling-data/attributes#object-attribute-type), set the `AttributeTypes` map value according to the [map type](/terraform/plugin/framework/handling-data/types/map). Refer to the object attribute type documentation for additional details. + +### Configurability + +At least one of the `Computed`, `Optional`, or `Required` fields must be set to `true`. This defines how Terraform and the framework should expect data to set, whether the value is from the practitioner configuration or from the provider logic, such as API response value. + +The acceptable behaviors of these configurability options are: + +- `Required` only: The value must be practitioner configured to an eventually known value (not null), otherwise the framework will automatically raise an error diagnostic for the missing value. +- `Optional` only: The value may be practitioner configured to a known value or null. +- `Optional` and `Computed`: The value may be practitioner configured or the value may be set in provider logic when the practitioner configuration is null. +- `Computed` only: The value will be set in provider logic and any practitioner configuration causes the framework to automatically raise an error diagnostic for the unexpected configuration value. + +### Custom Types + +You may want to build your own attribute value and type implementations to allow your provider to combine validation, description, and plan customization behaviors into a reusable bundle. This helps avoid duplication or reimplementation and ensures consistency. These implementations use the `CustomType` field in the attribute type. + +Refer to [Custom Types](/terraform/plugin/framework/handling-data/types/custom) for further details on creating provider-defined types and values. + +### Deprecation + +Set the `DeprecationMessage` field to a practitioner-focused message for how to handle the deprecation. The framework will automatically raise a warning diagnostic with this message if the practitioner configuration contains a known value for the attribute. Terraform version 1.2.7 and later will raise a warning diagnostic in certain scenarios if the deprecated attribute value is referenced elsewhere in a practitioner configuration. The framework [deprecations](/terraform/plugin/framework/deprecations) documentation fully describes the recommended practices for deprecating an attribute or resource. + +Some practitioner-focused examples of a deprecation message include: + +- Configure `other_attribute` instead. This attribute will be removed in the next major version of the provider. +- Remove this attribute's configuration as it no longer is used and the attribute will be removed in the next major version of the provider. + +### Description + +The framework provides two description fields, `Description` and `MarkdownDescription`, which various tools use to show additional information about an attribute and its intended purpose. This includes, but is not limited to, [`terraform-plugin-docs`](https://github.com/hashicorp/terraform-plugin-docs) for automated provider documentation generation and [`terraform-ls`](https://github.com/hashicorp/terraform-ls) for Terraform configuration editor integrations. + +### Plan Modification + + + +Only managed resources implement this concept. + + + +The framework provides two plan modification fields for managed resource attributes, `Default` and `PlanModifiers`, which define resource and attribute value planning behaviors. The resource [default](/terraform/plugin/framework/resources/default) and [plan modification](/terraform/plugin/framework/resources/plan-modification) documentation covers these features more in-depth. + +#### Common Use Case Plan Modification + +The [`mapdefault`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/mapdefault) package defines common use case `Default` implementations: + +- [`StaticValue(types.Map)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/mapdefault#StaticValue): Define a static map default value for the attribute. + +The [`mapplanmodifier`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/mapplanmodifier) package defines common use case `PlanModifiers` implementations: + +- [`RequiresReplace()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/mapplanmodifier#RequiresReplace): Marks the resource for replacement if the resource is being updated and the plan value does not match the prior state value. +- [`RequiresReplaceIf()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/mapplanmodifier#RequiresReplaceIf): Similar to `RequiresReplace()`, but also checks if a given function returns true. +- [`RequiresReplaceIfConfigured()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/mapplanmodifier#RequiresReplaceIfConfigured): Similar to `RequiresReplace()`, but also checks if the configuration value is not null. +- [`UseStateForUnknown()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/mapplanmodifier#UseStateForUnknown): Copies a known prior state value into the planned value. Use this when it is known that an unconfigured value will remain the same after a resource update. + +### Sensitive + +Set the `Sensitive` field if the attribute value should always be considered [sensitive data](/terraform/language/state/sensitive-data). In Terraform, this will generally mask the value in practitioner output. This setting cannot be conditionally set and does not impact how data is stored in the state. + +### WriteOnly + + + + Only managed resources implement this concept. + + + +Set the `WriteOnly` field to define a [write-only argument](/terraform/plugin/framework/resources/write-only-arguments). +Write-only arguments can accept [ephemeral values](/terraform/language/resources/ephemeral) +and are not persisted in the Terraform plan or state artifacts. Write-only arguments are supported in Terraform 1.11 and later. + +### Validation + +Set the `Validators` field to define [validation](/terraform/plugin/framework/validation#attribute-validation). This validation logic is ran in addition to any validation contained within a [custom type](#custom-types). + +#### Common Use Case Validators + +HashiCorp provides the additional [`terraform-plugin-framework-validators`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators) Go module which contains validation logic for common use cases. The [`mapvalidator`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators/mapvalidator) package within that module has map attribute validators such as defining conflicting attributes. + +## Accessing Values + +The [accessing values](/terraform/plugin/framework/handling-data/accessing-values) documentation covers general methods for reading [schema](/terraform/plugin/framework/handling-data/schemas) (configuration, plan, and state) data, which is necessary before accessing an attribute value directly. The [map type](/terraform/plugin/framework/handling-data/types/map#accessing-values) documentation covers methods for interacting with the attribute value itself. + +## Setting Values + +The [map type](/terraform/plugin/framework/handling-data/types/map#setting-values) documentation covers methods for creating or setting the appropriate value. The [writing data](/terraform/plugin/framework/handling-data/writing-state) documentation covers general methods for writing [schema](/terraform/plugin/framework/handling-data/schemas) (plan and state) data, which is necessary afterwards. diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/attributes/number.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/attributes/number.mdx new file mode 100644 index 0000000000..97466bbd37 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/attributes/number.mdx @@ -0,0 +1,149 @@ +--- +page_title: Number attributes +description: >- + Learn how to use arbitrary precision number attributes with the Terraform + plugin framework. +--- + +# Number attributes + + + +Use [Float64 Attribute](/terraform/plugin/framework/handling-data/attributes/float64) for 64-bit floating point numbers. Use [Int64 Attribute](/terraform/plugin/framework/handling-data/attributes/int64) for 64-bit integer numbers. + + + +Number attributes store an arbitrary precision (generally over 64-bit, up to 512-bit) number. Values are represented by a [number type](/terraform/plugin/framework/handling-data/types/number) in the framework. + +In this Terraform configuration example, an number attribute named `example_attribute` is set to a value greater than 64 bits: + +```hcl +resource "examplecloud_thing" "example" { + example_attribute = pow(2, 64) + 1 +} +``` + +## Schema Definition + +Use one of the following attribute types to directly add a number value to a [schema](/terraform/plugin/framework/handling-data/schemas) or [nested attribute type](/terraform/plugin/framework/handling-data/attributes#nested-attribute-types): + +| Schema Type | Attribute Type | +|-------------|----------------| +| [Data Source](/terraform/plugin/framework/data-sources) | [`schema.NumberAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/datasource/schema#NumberAttribute) | +| [Provider](/terraform/plugin/framework/providers) | [`schema.NumberAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider/schema#NumberAttribute) | +| [Resource](/terraform/plugin/framework/resources) | [`schema.NumberAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema#NumberAttribute) | +| [Ephemeral Resource](/terraform/plugin/framework/ephemeral-resources) | [`schema.NumberAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/ephemeral/schema#NumberAttribute) | + +In this example, a resource schema defines a top level required number attribute named `example_attribute`: + +```go +func (r ThingResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "example_attribute": schema.NumberAttribute{ + Required: true, + // ... potentially other fields ... + }, + // ... potentially other attributes ... + }, + } +} +``` + +If the number value should be the element type of a [collection attribute type](/terraform/plugin/framework/handling-data/attributes#collection-attribute-types), set the `ElementType` field according to the [number type](/terraform/plugin/framework/handling-data/types/number). Refer to the collection attribute type documentation for additional details. + +If the number value should be a value type of an [object attribute type](/terraform/plugin/framework/handling-data/attributes#object-attribute-type), set the `AttributeTypes` map value according to the [number type](/terraform/plugin/framework/handling-data/types/number). Refer to the object attribute type documentation for additional details. + +### Configurability + +At least one of the `Computed`, `Optional`, or `Required` fields must be set to `true`. This defines how Terraform and the framework should expect data to set, whether the value is from the practitioner configuration or from the provider logic, such as API response value. + +The acceptable behaviors of these configurability options are: + +- `Required` only: The value must be practitioner configured to an eventually known value (not null), otherwise the framework will automatically raise an error diagnostic for the missing value. +- `Optional` only: The value may be practitioner configured to a known value or null. +- `Optional` and `Computed`: The value may be practitioner configured or the value may be set in provider logic when the practitioner configuration is null. +- `Computed` only: The value will be set in provider logic and any practitioner configuration causes the framework to automatically raise an error diagnostic for the unexpected configuration value. + +#### Resource Identity + +If creating a resource identity schema, set either `OptionalForImport` or `RequiredForImport` to `true` to inform Terraform if practitioners must set the attribute when importing with that resource's identity. + +The acceptable behaviors of these configurability options are: + +- `RequiredForImport` only: A practitioner must configure the attribute to a known value (not `null`), otherwise Terraform automatically raises an error diagnostic for the missing value. +- `OptionalForImport` only: A practitioner must configure the value to a known value or `null`. + +### Custom Types + +You may want to build your own attribute value and type implementations to allow your provider to combine validation, description, and plan customization behaviors into a reusable bundle. This helps avoid duplication or reimplementation and ensures consistency. These implementations use the `CustomType` field in the attribute type. + +Refer to [Custom Types](/terraform/plugin/framework/handling-data/types/custom) for further details on creating provider-defined types and values. + +### Deprecation + +Set the `DeprecationMessage` field to a practitioner-focused message for how to handle the deprecation. The framework will automatically raise a warning diagnostic with this message if the practitioner configuration contains a known value for the attribute. Terraform version 1.2.7 and later will raise a warning diagnostic in certain scenarios if the deprecated attribute value is referenced elsewhere in a practitioner configuration. The framework [deprecations](/terraform/plugin/framework/deprecations) documentation fully describes the recommended practices for deprecating an attribute or resource. + +Some practitioner-focused examples of a deprecation message include: + +- Configure `other_attribute` instead. This attribute will be removed in the next major version of the provider. +- Remove this attribute's configuration as it no longer is used and the attribute will be removed in the next major version of the provider. + +### Description + +The framework provides two description fields, `Description` and `MarkdownDescription`, which various tools use to show additional information about an attribute and its intended purpose. This includes, but is not limited to, [`terraform-plugin-docs`](https://github.com/hashicorp/terraform-plugin-docs) for automated provider documentation generation and [`terraform-ls`](https://github.com/hashicorp/terraform-ls) for Terraform configuration editor integrations. + +### Plan Modification + + + +Only managed resources implement this concept. + + + +The framework provides two plan modification fields for managed resource attributes, `Default` and `PlanModifiers`, which define resource and attribute value planning behaviors. The resource [default](/terraform/plugin/framework/resources/default) and [plan modification](/terraform/plugin/framework/resources/plan-modification) documentation covers these features more in-depth. + +#### Common Use Case Plan Modification + +The [`numberdefault`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/numberdefault) package defines common use case `Default` implementations: + +- [`StaticNumber(number)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/numberdefault#StaticNumber): Define a static number default value for the attribute. + +The [`numberplanmodifier`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/numberplanmodifier) package defines common use case `PlanModifiers` implementations: + +- [`RequiresReplace()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/numberplanmodifier#RequiresReplace): Marks the resource for replacement if the resource is being updated and the plan value does not match the prior state value. +- [`RequiresReplaceIf()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/numberplanmodifier#RequiresReplaceIf): Similar to `RequiresReplace()`, but also checks if a given function returns true. +- [`RequiresReplaceIfConfigured()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/numberplanmodifier#RequiresReplaceIfConfigured): Similar to `RequiresReplace()`, but also checks if the configuration value is not null. +- [`UseStateForUnknown()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/numberplanmodifier#UseStateForUnknown): Copies a known prior state value into the planned value. Use this when it is known that an unconfigured value will remain the same after a resource update. + +### Sensitive + +Set the `Sensitive` field if the attribute value should always be considered [sensitive data](/terraform/language/state/sensitive-data). In Terraform, this will generally mask the value in practitioner output. This setting cannot be conditionally set and does not impact how data is stored in the state. + +### WriteOnly + + + + Only managed resources implement this concept. + + + +Set the `WriteOnly` field to define a [write-only argument](/terraform/plugin/framework/resources/write-only-arguments). +Write-only arguments can accept [ephemeral values](/terraform/language/resources/ephemeral) +and are not persisted in the Terraform plan or state artifacts. Write-only arguments are supported in Terraform 1.11 and later. + +### Validation + +Set the `Validators` field to define [validation](/terraform/plugin/framework/validation#attribute-validation). This validation logic is ran in addition to any validation contained within a [custom type](#custom-types). + +#### Common Use Case Validators + +HashiCorp provides the additional [`terraform-plugin-framework-validators`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators) Go module which contains validation logic for common use cases. The [`numbervalidator`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators/numbervalidator) package within that module has number attribute validators such as minimum, maximum, and defining conflicting attributes. + +## Accessing Values + +The [accessing values](/terraform/plugin/framework/handling-data/accessing-values) documentation covers general methods for reading [schema](/terraform/plugin/framework/handling-data/schemas) (configuration, plan, and state) data, which is necessary before accessing an attribute value directly. The [number type](/terraform/plugin/framework/handling-data/types/number#accessing-values) documentation covers methods for interacting with the attribute value itself. + +## Setting Values + +The [number type](/terraform/plugin/framework/handling-data/types/number#setting-values) documentation covers methods for creating or setting the appropriate value. The [writing data](/terraform/plugin/framework/handling-data/writing-state) documentation covers general methods for writing [schema](/terraform/plugin/framework/handling-data/schemas) (plan and state) data, which is necessary afterwards. diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/attributes/object.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/attributes/object.mdx new file mode 100644 index 0000000000..a2328540e0 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/attributes/object.mdx @@ -0,0 +1,194 @@ +--- +page_title: Object attributes +description: >- + Learn how to use object attributes with the Terraform plugin framework. +--- + +# Object attributes + + + +Use [nested attribute types](/terraform/plugin/framework/handling-data/attributes#nested-attribute-types) instead of object attribute types where possible. Object attributes have limited utility as they can only define type information. A [single nested attribute type](/terraform/plugin/framework/handling-data/attributes/single-nested) supports the same configuration syntax as an object while each nested attribute can be fully defined in terms of configurability, validation, etc. + + + +Object attributes are a single structure mapping explicit attribute names to type definitions. Values are represented by a [object type](/terraform/plugin/framework/handling-data/types/object) in the framework, containing sub-attribute values of the mapped types. + +In this Terraform configuration example, an object attribute named `example_attribute` is set to the mapped values of `attr1` to `"value1"` and `attr2` to `123`: + +```hcl +resource "examplecloud_thing" "example" { + example_attribute = { + attr1 = "value1" + attr2 = 123 + } +} +``` + +## Schema Definition + +Use one of the following attribute types to directly add a map value to a [schema](/terraform/plugin/framework/handling-data/schemas) or [nested attribute type](/terraform/plugin/framework/handling-data/attributes#nested-attribute-types): + +| Schema Type | Attribute Type | +|-------------|----------------| +| [Data Source](/terraform/plugin/framework/data-sources) | [`schema.ObjectAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/datasource/schema#ObjectAttribute) | +| [Provider](/terraform/plugin/framework/providers) | [`schema.ObjectAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider/schema#ObjectAttribute) | +| [Resource](/terraform/plugin/framework/resources) | [`schema.ObjectAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema#ObjectAttribute) | +| [Ephemeral Resource](/terraform/plugin/framework/ephemeral-resources) | [`schema.ObjectAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/ephemeral/schema#ObjectAttribute) | + +The `AttributeTypes` field must be defined, which represents the mapping of explicit string object attribute names to [value types](/terraform/plugin/framework/handling-data/types). + +In this example, a resource schema defines a top level required object attribute named `example_attribute` with a string sub-attribute named `attr1` and integer sub-attribute named `attr2`: + +```go +func (r ThingResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "example_attribute": schema.ObjectAttribute{ + AttributeTypes: map[string]attr.Type{ + "attr1": types.StringType, + "attr2": types.Int64Type, + }, + Required: true, + // ... potentially other fields ... + }, + // ... potentially other attributes ... + }, + } +} +``` + +A sub-attribute type may itself contain further collection types, if necessary. + +In this example, a resource schema defines a top level required object attribute named `example_attribute` with a list of strings sub-attribute named `attr1` and integer sub-attribute named `attr2`: + +```go +func (r ThingResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "example_attribute": schema.ObjectAttribute{ + AttributeTypes: map[string]attr.Type{ + "attr1": types.ListType{ + ElemType: types.StringType, + }, + "attr2": types.Int64Type, + }, + Required: true, + // ... potentially other fields ... + }, + // ... potentially other attributes ... + }, + } +} +``` + +If the object value should be the element type of a [collection attribute type](/terraform/plugin/framework/handling-data/attributes#collection-attribute-types), set the `ElementType` field according to the [object type](/terraform/plugin/framework/handling-data/types/object). Refer to the collection attribute type documentation for additional details. + +### Configurability + + + +Only the object attribute itself, not individual sub-attributes, can define its configurability. Use [nested attribute types](/terraform/plugin/framework/handling-data/attributes#nested-attribute-types) for full control of nested attribute capabilities. + + + +At least one of the `Computed`, `Optional`, or `Required` fields must be set to `true`. This defines how Terraform and the framework should expect data to set, whether the value is from the practitioner configuration or from the provider logic, such as API response value. + +The acceptable behaviors of these configurability options are: + +- `Required` only: The value must be practitioner configured to an eventually known value (not null), otherwise the framework will automatically raise an error diagnostic for the missing value. +- `Optional` only: The value may be practitioner configured to a known value or null. +- `Optional` and `Computed`: The value may be practitioner configured or the value may be set in provider logic when the practitioner configuration is null. +- `Computed` only: The value will be set in provider logic and any practitioner configuration causes the framework to automatically raise an error diagnostic for the unexpected configuration value. + +### Custom Types + +You may want to build your own attribute value and type implementations to allow your provider to combine validation, description, and plan customization behaviors into a reusable bundle. This helps avoid duplication or reimplementation and ensures consistency. These implementations use the `CustomType` field in the attribute type. + +Refer to [Custom Types](/terraform/plugin/framework/handling-data/types/custom) for further details on creating provider-defined types and values. + +### Deprecation + + + +Only the object attribute itself, not individual sub-attributes, can define deprecation. Use [nested attribute types](/terraform/plugin/framework/handling-data/attributes#nested-attribute-types) for full control of nested attribute capabilities. + + + +Set the `DeprecationMessage` field to a practitioner-focused message for how to handle the deprecation. The framework will automatically raise a warning diagnostic with this message if the practitioner configuration contains a known value for the attribute. Terraform version 1.2.7 and later will raise a warning diagnostic in certain scenarios if the deprecated attribute value is referenced elsewhere in a practitioner configuration. The framework [deprecations](/terraform/plugin/framework/deprecations) documentation fully describes the recommended practices for deprecating an attribute or resource. + +Some practitioner-focused examples of a deprecation message include: + +- Configure `other_attribute` instead. This attribute will be removed in the next major version of the provider. +- Remove this attribute's configuration as it no longer is used and the attribute will be removed in the next major version of the provider. + +### Description + + + +Only the object attribute itself, not individual sub-attributes, can define its description. Use [nested attribute types](/terraform/plugin/framework/handling-data/attributes#nested-attribute-types) for full control of nested attribute capabilities. + + + +The framework provides two description fields, `Description` and `MarkdownDescription`, which various tools use to show additional information about an attribute and its intended purpose. This includes, but is not limited to, [`terraform-plugin-docs`](https://github.com/hashicorp/terraform-plugin-docs) for automated provider documentation generation and [`terraform-ls`](https://github.com/hashicorp/terraform-ls) for Terraform configuration editor integrations. + +### Plan Modification + + + +Only managed resources implement this concept. + + + +The framework provides two plan modification fields for managed resource attributes, `Default` and `PlanModifiers`, which define resource and attribute value planning behaviors. The resource [default](/terraform/plugin/framework/resources/default) and [plan modification](/terraform/plugin/framework/resources/plan-modification) documentation covers these features more in-depth. + +#### Common Use Case Plan Modification + +The [`objectdefault`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/objectdefault) package defines common use case `Default` implementations: + +- [`StaticValue(types.Object)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/objectdefault#StaticValue): Define a static object default value for the attribute. + +The [`objectplanmodifier`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/objectplanmodifier) package defines common use case `PlanModifiers` implementations: + +- [`RequiresReplace()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/objectplanmodifier#RequiresReplace): Marks the resource for replacement if the resource is being updated and the plan value does not match the prior state value. +- [`RequiresReplaceIf()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/objectplanmodifier#RequiresReplaceIf): Similar to `RequiresReplace()`, but also checks if a given function returns true. +- [`RequiresReplaceIfConfigured()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/objectplanmodifier#RequiresReplaceIfConfigured): Similar to `RequiresReplace()`, but also checks if the configuration value is not null. +- [`UseStateForUnknown()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/objectplanmodifier#UseStateForUnknown): Copies a known prior state value into the planned value. Use this when it is known that an unconfigured value will remain the same after a resource update. + +### Sensitive + + + +Only the object attribute itself, not individual sub-attributes, can define its sensitivity. Use [nested attribute types](/terraform/plugin/framework/handling-data/attributes#nested-attribute-types) for full control of nested attribute capabilities. + + + +Set the `Sensitive` field if the attribute value should always be considered [sensitive data](/terraform/language/state/sensitive-data). In Terraform, this will generally mask the value in practitioner output. This setting cannot be conditionally set and does not impact how data is stored in the state. + +### WriteOnly + + + + Only managed resources implement this concept. + + + +Set the `WriteOnly` field to define a [write-only argument](/terraform/plugin/framework/resources/write-only-arguments). +Write-only arguments can accept [ephemeral values](/terraform/language/resources/ephemeral) +and are not persisted in the Terraform plan or state artifacts. Write-only arguments are supported in Terraform 1.11 and later. + +### Validation + +Set the `Validators` field to define [validation](/terraform/plugin/framework/validation#attribute-validation). This validation logic is ran in addition to any validation contained within a [custom type](#custom-types). + +#### Common Use Case Validators + +HashiCorp provides the additional [`terraform-plugin-framework-validators`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators) Go module which contains validation logic for common use cases. The [`objectvalidator`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators/objectvalidator) package within that module has map attribute validators such as defining conflicting attributes. + +## Accessing Values + +The [accessing values](/terraform/plugin/framework/handling-data/accessing-values) documentation covers general methods for reading [schema](/terraform/plugin/framework/handling-data/schemas) (configuration, plan, and state) data, which is necessary before accessing an attribute value directly. The [object type](/terraform/plugin/framework/handling-data/types/object#accessing-values) documentation covers methods for interacting with the attribute value itself. + +## Setting Values + +The [object type](/terraform/plugin/framework/handling-data/types/object#setting-values) documentation covers methods for creating or setting the appropriate value. The [writing data](/terraform/plugin/framework/handling-data/writing-state) documentation covers general methods for writing [schema](/terraform/plugin/framework/handling-data/schemas) (plan and state) data, which is necessary afterwards. diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/attributes/set-nested.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/attributes/set-nested.mdx new file mode 100644 index 0000000000..8a54622d4f --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/attributes/set-nested.mdx @@ -0,0 +1,176 @@ +--- +page_title: Set nested attributes +description: >- + Learn how to use set nested attributes with the Terraform plugin framework. +--- + +# Set nested attributes + +Set nested attributes store a unique, unordered collection of nested objects. Values are represented by a [set type](/terraform/plugin/framework/handling-data/types/set) in the framework, containing elements of [object type](/terraform/plugin/framework/handling-data/types/object). + +In this Terraform configuration example, a set nested attribute named `example_attribute` is set to the unordered object values of `attr` to `"one"` and `attr` to `"two"`: + +```hcl +resource "examplecloud_thing" "example" { + example_attribute = [ + { + attr = "one" + }, + { + attr = "two" + }, + ] +} +``` + +## Schema Definition + +Use one of the following attribute types to directly add a set nested value to a [schema](/terraform/plugin/framework/handling-data/schemas) or [nested attribute type](/terraform/plugin/framework/handling-data/attributes#nested-attribute-types): + +| Schema Type | Attribute Type | +|-------------|----------------| +| [Data Source](/terraform/plugin/framework/data-sources) | [`schema.SetNestedAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/datasource/schema#SetNestedAttribute) | +| [Provider](/terraform/plugin/framework/providers) | [`schema.SetNestedAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider/schema#SetNestedAttribute) | +| [Resource](/terraform/plugin/framework/resources) | [`schema.SetNestedAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema#SetNestedAttribute) | +| [Ephemeral Resource](/terraform/plugin/framework/ephemeral-resources) | [`schema.SetNestedAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/ephemeral/schema#SetNestedAttribute) | + +The `NestedObject` field must be defined, which represents the [object value type](/terraform/plugin/framework/handling-data/types/object) of every element of the set. + +In this example, a resource schema defines a top level required set nested attribute named `example_attribute` with a required string attribute named `attr`: + +```go +func (r ThingResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "example_attribute": schema.SetNestedAttribute{ + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "attr": schema.StringAttribute{ + Required: true, + // ... potentially other fields ... + }, + }, + }, + Required: true, + // ... potentially other fields ... + }, + // ... potentially other attributes ... + }, + } +} +``` + +Its [value type](/terraform/plugin/framework/handling-data/types) would be represented as a `types.Set` of `types.Object` where the `types.Object` is a mapping of `attr` to `types.String`. + +A nested attribute type may itself contain further collection or nested attribute types, if necessary. + +In this example, a resource schema defines a top level required set nested attribute named `example_attribute` with a required list of strings attribute named `attr1` and an optional single nested attribute named `attr2`: + +```go +func (r ThingResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "example_attribute": schema.SetNestedAttribute{ + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "attr1": schema.ListAttribute{ + ElementType: types.StringType, + Required: true, + // ... potentially other fields ... + }, + "attr2": schema.SingleNestedAttribute{ + Attributes: map[string]schema.Attribute{ /* ... */ }, + Optional: true, + // ... potentially other fields ... + }, + }, + }, + Required: true, + // ... potentially other fields ... + }, + // ... potentially other attributes ... + }, + } +} +``` + +Its [value type](/terraform/plugin/framework/handling-data/types) would be represented as a `types.Set` of `types.Object` where the `types.Object` is a mapping of `attr1` to `types.List` of `types.String` and `attr2` to `types.Object`. + +### Configurability + + + +Only the set nested attribute itself is defined by the `schema.SetNestedAttribute` configurability fields. Nested attributes must define their own configurability fields within each attribute definition. + + + +At least one of the `Computed`, `Optional`, or `Required` fields must be set to `true`. This defines how Terraform and the framework should expect data to set, whether the value is from the practitioner configuration or from the provider logic, such as API response value. + +The acceptable behaviors of these configurability options are: + +- `Required` only: The value must be practitioner configured to an eventually known value (not null), otherwise the framework will automatically raise an error diagnostic for the missing value. +- `Optional` only: The value may be practitioner configured to a known value or null. +- `Optional` and `Computed`: The value may be practitioner configured or the value may be set in provider logic when the practitioner configuration is null. +- `Computed` only: The value will be set in provider logic and any practitioner configuration causes the framework to automatically raise an error diagnostic for the unexpected configuration value. + +### Custom Types + +You may want to build your own attribute value and type implementations to allow your provider to combine validation, description, and plan customization behaviors into a reusable bundle. This helps avoid duplication or reimplementation and ensures consistency. These implementations use the `CustomType` field in the attribute type. + +Refer to [Custom Types](/terraform/plugin/framework/handling-data/types/custom) for further details on creating provider-defined types and values. + +### Deprecation + +Set the `DeprecationMessage` field to a practitioner-focused message for how to handle the deprecation. The framework will automatically raise a warning diagnostic with this message if the practitioner configuration contains a known value for the attribute. Terraform version 1.2.7 and later will raise a warning diagnostic in certain scenarios if the deprecated attribute value is referenced elsewhere in a practitioner configuration. The framework [deprecations](/terraform/plugin/framework/deprecations) documentation fully describes the recommended practices for deprecating an attribute or resource. + +Some practitioner-focused examples of a deprecation message include: + +- Configure `other_attribute` instead. This attribute will be removed in the next major version of the provider. +- Remove this attribute's configuration as it no longer is used and the attribute will be removed in the next major version of the provider. + +### Description + +The framework provides two description fields, `Description` and `MarkdownDescription`, which various tools use to show additional information about an attribute and its intended purpose. This includes, but is not limited to, [`terraform-plugin-docs`](https://github.com/hashicorp/terraform-plugin-docs) for automated provider documentation generation and [`terraform-ls`](https://github.com/hashicorp/terraform-ls) for Terraform configuration editor integrations. + +### Plan Modification + + + +Only managed resources implement this concept. + + + +The framework provides two plan modification fields for managed resource attributes, `Default` and `PlanModifiers`, which define resource and attribute value planning behaviors. The resource [default](/terraform/plugin/framework/resources/default) and [plan modification](/terraform/plugin/framework/resources/plan-modification) documentation covers these features more in-depth. + +#### Common Use Case Plan Modification + +The [`setdefault`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/setdefault) package defines common use case `Default` implementations: + +- [`StaticValue(types.List)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/setdefault#StaticValue): Define a static set default value for the attribute. + +The [`setplanmodifier`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/setplanmodifier) package defines common use case `PlanModifiers` implementations: + +- [`RequiresReplace()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/setplanmodifier#RequiresReplace): Marks the resource for replacement if the resource is being updated and the plan value does not match the prior state value. +- [`RequiresReplaceIf()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/setplanmodifier#RequiresReplaceIf): Similar to `RequiresReplace()`, but also checks if a given function returns true. +- [`RequiresReplaceIfConfigured()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/setplanmodifier#RequiresReplaceIfConfigured): Similar to `RequiresReplace()`, but also checks if the configuration value is not null. +- [`UseStateForUnknown()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/setplanmodifier#UseStateForUnknown): Copies a known prior state value into the planned value. Use this when it is known that an unconfigured value will remain the same after a resource update. + +### Sensitive + +Set the `Sensitive` field if the attribute value should always be considered [sensitive data](/terraform/language/state/sensitive-data). In Terraform, this will generally mask the value in practitioner output. This setting cannot be conditionally set and does not impact how data is stored in the state. + +### Validation + +Set the `Validators` field to define [validation](/terraform/plugin/framework/validation#attribute-validation). This validation logic is ran in addition to any validation contained within a [custom type](#custom-types). + +#### Common Use Case Validators + +HashiCorp provides the additional [`terraform-plugin-framework-validators`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators) Go module which contains validation logic for common use cases. The [`setvalidator`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators/setvalidator) package within that module has set attribute validators such as defining conflicting attributes. + +## Accessing Values + +The [accessing values](/terraform/plugin/framework/handling-data/accessing-values) documentation covers general methods for reading [schema](/terraform/plugin/framework/handling-data/schemas) (configuration, plan, and state) data, which is necessary before accessing an attribute value directly. The [set type](/terraform/plugin/framework/handling-data/types/set#accessing-values) documentation covers methods for interacting with the attribute value itself. + +## Setting Values + +The [set type](/terraform/plugin/framework/handling-data/types/set#setting-values) documentation covers methods for creating or setting the appropriate value. The [writing data](/terraform/plugin/framework/handling-data/writing-state) documentation covers general methods for writing [schema](/terraform/plugin/framework/handling-data/schemas) (plan and state) data, which is necessary afterwards. diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/attributes/set.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/attributes/set.mdx new file mode 100644 index 0000000000..01c6125407 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/attributes/set.mdx @@ -0,0 +1,145 @@ +--- +page_title: Set attributes +description: >- + Learn how to use set attributes with the Terraform plugin framework. +--- + +# Set attributes + +Set attributes store an unique, unordered collection of single element type. Values are represented by a [set type](/terraform/plugin/framework/handling-data/types/set) in the framework, containing elements of the element type. + +In this Terraform configuration example, a set of string attribute named `example_attribute` is set to the unordered values `"one"` and `"two"`: + +```hcl +resource "examplecloud_thing" "example" { + example_attribute = ["one", "two"] +} +``` + +## Schema Definition + +Use one of the following attribute types to directly add a set value to a [schema](/terraform/plugin/framework/handling-data/schemas) or [nested attribute type](/terraform/plugin/framework/handling-data/attributes#nested-attribute-types): + +| Schema Type | Attribute Type | +|-------------|----------------| +| [Data Source](/terraform/plugin/framework/data-sources) | [`schema.SetAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/datasource/schema#SetAttribute) | +| [Provider](/terraform/plugin/framework/providers) | [`schema.SetAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider/schema#SetAttribute) | +| [Resource](/terraform/plugin/framework/resources) | [`schema.SetAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema#SetAttribute) | +| [Ephemeral Resource](/terraform/plugin/framework/ephemeral-resources) | [`schema.SetAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/ephemeral/schema#SetAttribute) | + +The `ElementType` field must be defined, which represents the single [value type](/terraform/plugin/framework/handling-data/types) of every element of the set. + +In this example, a resource schema defines a top level required set of strings attribute named `example_attribute`: + +```go +func (r ThingResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "example_attribute": schema.SetAttribute{ + ElementType: types.StringType, + Required: true, + // ... potentially other fields ... + }, + // ... potentially other attributes ... + }, + } +} +``` + +An element type may itself contain further collection types, if necessary. + +In this example, a resource schema defines a top level required set of set of strings attribute named `example_attribute`: + +```go +func (r ThingResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "example_attribute": schema.SetAttribute{ + ElementType: types.SetType{ + ElemType: types.StringType, + }, + Required: true, + // ... potentially other fields ... + }, + // ... potentially other attributes ... + }, + } +} +``` + +If the set value should be the element type of a [collection attribute type](/terraform/plugin/framework/handling-data/attributes#collection-attribute-types), set the `ElementType` field according to the [set type](/terraform/plugin/framework/handling-data/types/set). Refer to the collection attribute type documentation for additional details. + +If the set value should be a value type of an [object attribute type](/terraform/plugin/framework/handling-data/attributes#object-attribute-type), set the `AttributeTypes` map value according to the [set type](/terraform/plugin/framework/handling-data/types/set). Refer to the object attribute type documentation for additional details. + +### Configurability + +At least one of the `Computed`, `Optional`, or `Required` fields must be set to `true`. This defines how Terraform and the framework should expect data to set, whether the value is from the practitioner configuration or from the provider logic, such as API response value. + +The acceptable behaviors of these configurability options are: + +- `Required` only: The value must be practitioner configured to an eventually known value (not null), otherwise the framework will automatically raise an error diagnostic for the missing value. +- `Optional` only: The value may be practitioner configured to a known value or null. +- `Optional` and `Computed`: The value may be practitioner configured or the value may be set in provider logic when the practitioner configuration is null. +- `Computed` only: The value will be set in provider logic and any practitioner configuration causes the framework to automatically raise an error diagnostic for the unexpected configuration value. + +### Custom Types + +You may want to build your own attribute value and type implementations to allow your provider to combine validation, description, and plan customization behaviors into a reusable bundle. This helps avoid duplication or reimplementation and ensures consistency. These implementations use the `CustomType` field in the attribute type. + +Refer to [Custom Types](/terraform/plugin/framework/handling-data/types/custom) for further details on creating provider-defined types and values. + +### Deprecation + +Set the `DeprecationMessage` field to a practitioner-focused message for how to handle the deprecation. The framework will automatically raise a warning diagnostic with this message if the practitioner configuration contains a known value for the attribute. Terraform version 1.2.7 and later will raise a warning diagnostic in certain scenarios if the deprecated attribute value is referenced elsewhere in a practitioner configuration. The framework [deprecations](/terraform/plugin/framework/deprecations) documentation fully describes the recommended practices for deprecating an attribute or resource. + +Some practitioner-focused examples of a deprecation message include: + +- Configure `other_attribute` instead. This attribute will be removed in the next major version of the provider. +- Remove this attribute's configuration as it no longer is used and the attribute will be removed in the next major version of the provider. + +### Description + +The framework provides two description fields, `Description` and `MarkdownDescription`, which various tools use to show additional information about an attribute and its intended purpose. This includes, but is not limited to, [`terraform-plugin-docs`](https://github.com/hashicorp/terraform-plugin-docs) for automated provider documentation generation and [`terraform-ls`](https://github.com/hashicorp/terraform-ls) for Terraform configuration editor integrations. + +### Plan Modification + + + +Only managed resources implement this concept. + + + +The framework provides two plan modification fields for managed resource attributes, `Default` and `PlanModifiers`, which define resource and attribute value planning behaviors. The resource [default](/terraform/plugin/framework/resources/default) and [plan modification](/terraform/plugin/framework/resources/plan-modification) documentation covers these features more in-depth. + +#### Common Use Case Plan Modification + +The [`setdefault`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/setdefault) package defines common use case `Default` implementations: + +- [`StaticValue(types.Set)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/setdefault#StaticValue): Define a static set default value for the attribute. + +The [`setplanmodifier`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/setplanmodifier) package defines common use case `PlanModifiers` implementations: + +- [`RequiresReplace()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/setplanmodifier#RequiresReplace): Marks the resource for replacement if the resource is being updated and the plan value does not match the prior state value. +- [`RequiresReplaceIf()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/setplanmodifier#RequiresReplaceIf): Similar to `RequiresReplace()`, but also checks if a given function returns true. +- [`RequiresReplaceIfConfigured()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/setplanmodifier#RequiresReplaceIfConfigured): Similar to `RequiresReplace()`, but also checks if the configuration value is not null. +- [`UseStateForUnknown()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/setplanmodifier#UseStateForUnknown): Copies a known prior state value into the planned value. Use this when it is known that an unconfigured value will remain the same after a resource update. + +### Sensitive + +Set the `Sensitive` field if the attribute value should always be considered [sensitive data](/terraform/language/state/sensitive-data). In Terraform, this will generally mask the value in practitioner output. This setting cannot be conditionally set and does not impact how data is stored in the state. + +### Validation + +Set the `Validators` field to define [validation](/terraform/plugin/framework/validation#attribute-validation). This validation logic is ran in addition to any validation contained within a [custom type](#custom-types). + +#### Common Use Case Validators + +HashiCorp provides the additional [`terraform-plugin-framework-validators`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators) Go module which contains validation logic for common use cases. The [`setvalidator`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators/setvalidator) package within that module has set attribute validators such as defining conflicting attributes. + +## Accessing Values + +The [accessing values](/terraform/plugin/framework/handling-data/accessing-values) documentation covers general methods for reading [schema](/terraform/plugin/framework/handling-data/schemas) (configuration, plan, and state) data, which is necessary before accessing an attribute value directly. The [set type](/terraform/plugin/framework/handling-data/types/set#accessing-values) documentation covers methods for interacting with the attribute value itself. + +## Setting Values + +The [set type](/terraform/plugin/framework/handling-data/types/set#setting-values) documentation covers methods for creating or setting the appropriate value. The [writing data](/terraform/plugin/framework/handling-data/writing-state) documentation covers general methods for writing [schema](/terraform/plugin/framework/handling-data/schemas) (plan and state) data, which is necessary afterwards. diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/attributes/single-nested.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/attributes/single-nested.mdx new file mode 100644 index 0000000000..fe483016a3 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/attributes/single-nested.mdx @@ -0,0 +1,186 @@ +--- +page_title: Single nested attributes +description: >- + Learn how to use single nested attributes with the Terraform plugin framework. +--- + +# Single nested attributes + +Single nested attributes are a single structure mapping explicit attribute names to nested attribute definitions. Values are represented by a [object type](/terraform/plugin/framework/handling-data/types/object) in the framework, containing nested attribute values of the mapped attributes. + +In this Terraform configuration example, a single nested attribute named `example_attribute` is set to the mapped values of `attr1` to `"value1"` and `attr2` to `123`: + +```hcl +resource "examplecloud_thing" "example" { + example_attribute = { + attr1 = "value1" + attr2 = 123 + } +} +``` + +## Schema Definition + +Use one of the following attribute types to directly add a single nested value to a [schema](/terraform/plugin/framework/handling-data/schemas) or [nested attribute type](/terraform/plugin/framework/handling-data/attributes#nested-attribute-types): + +| Schema Type | Attribute Type | +|-------------|----------------| +| [Data Source](/terraform/plugin/framework/data-sources) | [`schema.SingleNestedAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/datasource/schema#SingleNestedAttribute) | +| [Provider](/terraform/plugin/framework/providers) | [`schema.SingleNestedAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider/schema#SingleNestedAttribute) | +| [Resource](/terraform/plugin/framework/resources) | [`schema.SingleNestedAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema#SingleNestedAttribute) | +| [Ephemeral Resource](/terraform/plugin/framework/ephemeral-resources) | [`schema.SingleNestedAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/ephemeral/schema#SingleNestedAttribute) | + +In most use cases, the `Attributes` field should be defined, which represents the mapping of explicit string attribute names to nested attributes. + +In this example, a resource schema defines a top level required single nested attribute named `example_attribute` with a required string attribute named `attr1` and optional integer attribute named `attr2`: + +```go +func (r ThingResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "example_attribute": schema.SingleNestedAttribute{ + Attributes: map[string]schema.Attribute{ + "attr1": schema.StringAttribute{ + Required: true, + // ... potentially other fields ... + }, + "attr2": schema.Int64Attribute{ + Optional: true, + // ... potentially other fields ... + }, + }, + Required: true, + // ... potentially other fields ... + }, + // ... potentially other attributes ... + }, + } +} +``` + +Its [value type](/terraform/plugin/framework/handling-data/types) would be represented as a `types.Object` with a mapping of `attr1` to `types.String` and `attr2` to `types.Int64`. + +A nested attribute type may itself contain further collection or nested attribute types, if necessary. + +In this example, a resource schema defines a top level required single nested attribute named `example_attribute` with a required list of strings attribute named `attr1` and an optional single attribute named `attr2`: + +```go +func (r ThingResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "example_attribute": schema.SingleNestedAttribute{ + Attributes: map[string]schema.Attribute{ + "attr1": schema.ListAttribute{ + ElementType: types.StringType, + Required: true, + // ... potentially other fields ... + }, + "attr2": schema.SingleNestedAttribute{ + Attributes: map[string]schema.Attribute{ /* ... */ }, + Optional: true, + // ... potentially other fields ... + }, + }, + Required: true, + // ... potentially other fields ... + }, + // ... potentially other attributes ... + }, + } +} +``` + +Its [value type](/terraform/plugin/framework/handling-data/types) would be represented as a `types.Object` with a mapping of `attr1` to `types.List` of `types.String` and `attr2` to `types.Object`. + +### Configurability + + + +Only the single nested attribute itself is defined by the `schema.SingleNestedAttribute` configurability fields. Nested attributes must define their own configurability fields within each attribute definition. + + + +At least one of the `Computed`, `Optional`, or `Required` fields must be set to `true`. This defines how Terraform and the framework should expect data to set, whether the value is from the practitioner configuration or from the provider logic, such as API response value. + +The acceptable behaviors of these configurability options are: + +- `Required` only: The value must be practitioner configured to an eventually known value (not null), otherwise the framework will automatically raise an error diagnostic for the missing value. +- `Optional` only: The value may be practitioner configured to a known value or null. +- `Optional` and `Computed`: The value may be practitioner configured or the value may be set in provider logic when the practitioner configuration is null. +- `Computed` only: The value will be set in provider logic and any practitioner configuration causes the framework to automatically raise an error diagnostic for the unexpected configuration value. + +### Custom Types + +You may want to build your own attribute value and type implementations to allow your provider to combine validation, description, and plan customization behaviors into a reusable bundle. This helps avoid duplication or reimplementation and ensures consistency. These implementations use the `CustomType` field in the attribute type. + +Refer to [Custom Types](/terraform/plugin/framework/handling-data/types/custom) for further details on creating provider-defined types and values. + +### Deprecation + +Set the `DeprecationMessage` field to a practitioner-focused message for how to handle the deprecation. The framework will automatically raise a warning diagnostic with this message if the practitioner configuration contains a known value for the attribute. Terraform version 1.2.7 and later will raise a warning diagnostic in certain scenarios if the deprecated attribute value is referenced elsewhere in a practitioner configuration. The framework [deprecations](/terraform/plugin/framework/deprecations) documentation fully describes the recommended practices for deprecating an attribute or resource. + +Some practitioner-focused examples of a deprecation message include: + +- Configure `other_attribute` instead. This attribute will be removed in the next major version of the provider. +- Remove this attribute's configuration as it no longer is used and the attribute will be removed in the next major version of the provider. + +### Description + +The framework provides two description fields, `Description` and `MarkdownDescription`, which various tools use to show additional information about an attribute and its intended purpose. This includes, but is not limited to, [`terraform-plugin-docs`](https://github.com/hashicorp/terraform-plugin-docs) for automated provider documentation generation and [`terraform-ls`](https://github.com/hashicorp/terraform-ls) for Terraform configuration editor integrations. + +### Plan Modification + + + +Only managed resources implement this concept. + + + +The framework provides two plan modification fields for managed resource attributes, `Default` and `PlanModifiers`, which define resource and attribute value planning behaviors. The resource [default](/terraform/plugin/framework/resources/default) and [plan modification](/terraform/plugin/framework/resources/plan-modification) documentation covers these features more in-depth. + +#### Common Use Case Plan Modification + +The [`objectdefault`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/objectdefault) package defines common use case `Default` implementations: + +- [`StaticValue(types.Object)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/objectdefault#StaticValue): Define a static object default value for the attribute. + +The [`objectplanmodifier`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/objectplanmodifier) package defines common use case `PlanModifiers` implementations: + +- [`RequiresReplace()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/objectplanmodifier#RequiresReplace): Marks the resource for replacement if the resource is being updated and the plan value does not match the prior state value. +- [`RequiresReplaceIf()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/objectplanmodifier#RequiresReplaceIf): Similar to `RequiresReplace()`, but also checks if a given function returns true. +- [`RequiresReplaceIfConfigured()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/objectplanmodifier#RequiresReplaceIfConfigured): Similar to `RequiresReplace()`, but also checks if the configuration value is not null. +- [`UseStateForUnknown()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/objectplanmodifier#UseStateForUnknown): Copies a known prior state value into the planned value. Use this when it is known that an unconfigured value will remain the same after a resource update. + +### Sensitive + +Set the `Sensitive` field if the attribute value should always be considered [sensitive data](/terraform/language/state/sensitive-data). In Terraform, this will generally mask the value in practitioner output. This setting cannot be conditionally set and does not impact how data is stored in the state. + +### WriteOnly + + + + Only managed resources implement this concept. + + + +Set the `WriteOnly` field to define a [write-only argument](/terraform/plugin/framework/resources/write-only-arguments). +Write-only arguments can accept [ephemeral values](/terraform/language/resources/ephemeral) +and are not persisted in the Terraform plan or state artifacts. Write-only arguments are supported in Terraform 1.11 and later. + +If a nested attribute has the `WriteOnly` field set, all child attributes must also have `WriteOnly` set. + +### Validation + +Set the `Validators` field to define [validation](/terraform/plugin/framework/validation#attribute-validation). This validation logic is ran in addition to any validation contained within a [custom type](#custom-types). + +#### Common Use Case Validators + +HashiCorp provides the additional [`terraform-plugin-framework-validators`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators) Go module which contains validation logic for common use cases. The [`objectvalidator`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators/objectvalidator) package within that module has object attribute validators such as defining conflicting attributes. + +## Accessing Values + +The [accessing values](/terraform/plugin/framework/handling-data/accessing-values) documentation covers general methods for reading [schema](/terraform/plugin/framework/handling-data/schemas) (configuration, plan, and state) data, which is necessary before accessing an attribute value directly. The [object type](/terraform/plugin/framework/handling-data/types/object#accessing-values) documentation covers methods for interacting with the attribute value itself. + +## Setting Values + +The [object type](/terraform/plugin/framework/handling-data/types/object#setting-values) documentation covers methods for creating or setting the appropriate value. The [writing data](/terraform/plugin/framework/handling-data/writing-state) documentation covers general methods for writing [schema](/terraform/plugin/framework/handling-data/schemas) (plan and state) data, which is necessary afterwards. diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/attributes/string.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/attributes/string.mdx new file mode 100644 index 0000000000..76232f93e9 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/attributes/string.mdx @@ -0,0 +1,150 @@ +--- +page_title: String attributes +description: >- + Learn how to use string attributes with the Terraform plugin framework. +--- + +# String attributes + +String attributes store a collection of UTF-8 encoded bytes. Values are represented by a [string type](/terraform/plugin/framework/handling-data/types/string) in the framework. + +In this Terraform configuration example, a string attribute named `example_attribute` is set to the value `terraform`: + +```hcl +resource "examplecloud_thing" "example" { + example_attribute = "terraform" +} +``` + +## Schema Definition + +Use one of the following attribute types to directly add a string value to a [schema](/terraform/plugin/framework/handling-data/schemas) or [nested attribute type](/terraform/plugin/framework/handling-data/attributes#nested-attribute-types): + +| Schema Type | Attribute Type | +|-------------|----------------| +| [Data Source](/terraform/plugin/framework/data-sources) | [`schema.StringAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/datasource/schema#StringAttribute) | +| [Provider](/terraform/plugin/framework/providers) | [`schema.StringAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider/schema#StringAttribute) | +| [Resource](/terraform/plugin/framework/resources) | [`schema.StringAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema#StringAttribute) | +| [Ephemeral Resource](/terraform/plugin/framework/ephemeral-resources) | [`schema.StringAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/ephemeral/schema#StringAttribute) | + +In this example, a resource schema defines a top level required string attribute named `example_attribute`: + +```go +func (r ThingResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "example_attribute": schema.StringAttribute{ + Required: true, + // ... potentially other fields ... + }, + // ... potentially other attributes ... + }, + } +} +``` + +If the string value should be the element type of a [collection attribute type](/terraform/plugin/framework/handling-data/attributes#collection-attribute-types), set the `ElementType` field according to the [string type](/terraform/plugin/framework/handling-data/types/string). Refer to the collection attribute type documentation for additional details. + +If the string value should be a value type of an [object attribute type](/terraform/plugin/framework/handling-data/attributes#object-attribute-type), set the `AttrTypes` map value according to the [string type](/terraform/plugin/framework/handling-data/types/string). Refer to the object attribute type documentation for additional details. + +### Configurability + +At least one of the `Computed`, `Optional`, or `Required` fields must be set to `true`. This defines how Terraform and the framework should expect data to set, whether the value is from the practitioner configuration or from the provider logic, such as API response value. + +The acceptable behaviors of these configurability options are: + +- `Required` only: The value must be practitioner configured to an eventually known value (not null), otherwise the framework will automatically raise an error diagnostic for the missing value. +- `Optional` only: The value may be practitioner configured to a known value or null. +- `Optional` and `Computed`: The value may be practitioner configured or the value may be set in provider logic when the practitioner configuration is null. +- `Computed` only: The value will be set in provider logic and any practitioner configuration causes the framework to automatically raise an error diagnostic for the unexpected configuration value. + +#### Resource Identity + +If creating a resource identity schema, set either `OptionalForImport` or `RequiredForImport` to `true` to inform Terraform if practitioners must set the attribute when importing with that resource's identity. + +The acceptable behaviors of these configurability options are: + +- `RequiredForImport` only: A practitioner must configure the attribute to a known value (not `null`), otherwise Terraform automatically raises an error diagnostic for the missing value. +- `OptionalForImport` only: A practitioner must configure the value to a known value or `null`. + +### Custom Types + +You may want to build your own attribute value and type implementations to allow your provider to combine validation, description, and plan customization behaviors into a reusable bundle. This helps avoid duplication or reimplementation and ensures consistency. These implementations use the `CustomType` field in the attribute type. + +Refer to [Custom Types](/terraform/plugin/framework/handling-data/types/custom) for further details on creating provider-defined types and values. + +#### Common Use Case Types + +HashiCorp provides additional Go modules which contain custom string type implementations covering common use cases with validation and semantic equality logic: + +- [`terraform-plugin-framework-jsontypes`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-jsontypes): JSON encoded strings, such as exact byte strings and normalized strings +- [`terraform-plugin-framework-nettypes`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-nettypes): Networking strings, such as IPv4 addresses, IPv6 addresses, and CIDRs +- [`terraform-plugin-framework-timetypes`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-timetypes): Timestamp strings, such as RFC3339 + +### Deprecation + +Set the `DeprecationMessage` field to a practitioner-focused message for how to handle the deprecation. The framework will automatically raise a warning diagnostic with this message if the practitioner configuration contains a known value for the attribute. Terraform version 1.2.7 and later will raise a warning diagnostic in certain scenarios if the deprecated attribute value is referenced elsewhere in a practitioner configuration. The framework [deprecations](/terraform/plugin/framework/deprecations) documentation fully describes the recommended practices for deprecating an attribute or resource. + +Some practitioner-focused examples of a deprecation message include: + +- Configure `other_attribute` instead. This attribute will be removed in the next major version of the provider. +- Remove this attribute's configuration as it no longer is used and the attribute will be removed in the next major version of the provider. + +### Description + +The framework provides two description fields, `Description` and `MarkdownDescription`, which various tools use to show additional information about an attribute and its intended purpose. This includes, but is not limited to, [`terraform-plugin-docs`](https://github.com/hashicorp/terraform-plugin-docs) for automated provider documentation generation and [`terraform-ls`](https://github.com/hashicorp/terraform-ls) for Terraform configuration editor integrations. + +### Plan Modification + + + +Only managed resources implement this concept. + + + +The framework provides two plan modification fields for managed resource attributes, `Default` and `PlanModifiers`, which define resource and attribute value planning behaviors. The resource [default](/terraform/plugin/framework/resources/default) and [plan modification](/terraform/plugin/framework/resources/plan-modification) documentation covers these features more in-depth. + +#### Common Use Case Plan Modification + +The [`stringdefault`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault) package defines common use case `Default` implementations: + +- [`StaticString(string)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault#StaticString): Define a static string default value for the attribute. + +The [`stringplanmodifier`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier) package defines common use case `PlanModifiers` implementations: + +- [`RequiresReplace()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier#RequiresReplace): Marks the resource for replacement if the resource is being updated and the plan value does not match the prior state value. +- [`RequiresReplaceIf()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier#RequiresReplaceIf): Similar to `RequiresReplace()`, but also checks if a given function returns true. +- [`RequiresReplaceIfConfigured()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier#RequiresReplaceIfConfigured): Similar to `RequiresReplace()`, but also checks if the configuration value is not null. +- [`UseStateForUnknown()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier#UseStateForUnknown): Copies a known prior state value into the planned value. Use this when it is known that an unconfigured value will remain the same after a resource update. + +### Sensitive + +Set the `Sensitive` field if the attribute value should always be considered [sensitive data](/terraform/language/state/sensitive-data). In Terraform, this will generally mask the value in practitioner output. This setting cannot be conditionally set and does not impact how data is stored in the state. + +### WriteOnly + + + + Only managed resources implement this concept. + + + +Set the `WriteOnly` field to define a [write-only argument](/terraform/plugin/framework/resources/write-only-arguments). +Write-only arguments can accept [ephemeral values](/terraform/language/resources/ephemeral) +and are not persisted in the Terraform plan or state artifacts. Write-only arguments are supported in Terraform 1.11 and later. + +### Validation + +Set the `Validators` field to define [validation](/terraform/plugin/framework/validation#attribute-validation). This validation logic is ran in addition to any validation contained within a [custom type](#custom-types). + +#### Common Use Case Validators + +HashiCorp provides the additional [`terraform-plugin-framework-validators`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators) Go module which contains validation logic for common use cases. The [`stringvalidator`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator) package within that module has string attribute validators such as length, regular expression pattern, and defining conflicting attributes. + +## Accessing Values + +The [accessing values](/terraform/plugin/framework/handling-data/accessing-values) documentation covers general methods for reading [schema](/terraform/plugin/framework/handling-data/schemas) (configuration, plan, and state) data, which is necessary before accessing an attribute value directly. The [string type](/terraform/plugin/framework/handling-data/types/string#accessing-values) documentation covers methods for interacting with the attribute value itself. + +## Setting Values + +The [string type](/terraform/plugin/framework/handling-data/types/string#setting-values) documentation covers methods for creating or setting the appropriate value. The [writing data](/terraform/plugin/framework/handling-data/writing-state) documentation covers general methods for writing [schema](/terraform/plugin/framework/handling-data/schemas) (plan and state) data, which is necessary afterwards. diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/blocks/index.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/blocks/index.mdx new file mode 100644 index 0000000000..af96c50218 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/blocks/index.mdx @@ -0,0 +1,25 @@ +--- +page_title: Blocks +description: >- + Learn how to use block types with the Terraform plugin framework. Blocks are + containers for nested attributes and blocks in a resource, data source, or + provider schema. +--- + +# Blocks + + + +Use [nested attribute types](/terraform/plugin/framework/handling-data/attributes#nested-attribute-types) instead of block types for new schema implementations. Block support is mainly for migrating legacy SDK-based providers. + + + +Blocks are containers for other attributes and blocks in resource, data source, or provider [schemas](/terraform/plugin/framework/handling-data/schemas). Every block has an associated [value type](/terraform/plugin/framework/handling-data/types), which describes the kind of data the block holds. Blocks also can describe value plan modifiers (resources only) and value validators in addition to those defined by the value type. + +## Available Block Types + +| Block Type | Use Case | +|----------------|----------| +| [List Nested](/terraform/plugin/framework/handling-data/blocks/list-nested) | Ordered collection of structures of attributes/blocks | +| [Set Nested](/terraform/plugin/framework/handling-data/blocks/set-nested) | Unordered, unique collection of structures of attributes/blocks | +| [Single Nested](/terraform/plugin/framework/handling-data/blocks/single-nested) | Single structure of attributes/blocks | diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/blocks/list-nested.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/blocks/list-nested.mdx new file mode 100644 index 0000000000..373c69d0be --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/blocks/list-nested.mdx @@ -0,0 +1,169 @@ +--- +page_title: List nested blocks +description: >- + Learn how to implement the list nested block type with the Terraform plugin + framework. +--- + +# List nested blocks + + + +Use [nested attribute types](/terraform/plugin/framework/handling-data/attributes#nested-attribute-types) instead of block types for new schema implementations. Block support is mainly for migrating legacy SDK-based providers. + + + +List nested blocks are an ordered collection of structures mapping explicit attribute names to nested attribute and block definitions. Values are represented by a [list type](/terraform/plugin/framework/handling-data/types/list) in the framework, containing [object type](/terraform/plugin/framework/handling-data/types/object) of the mapped attributes and blocks. + +In this Terraform configuration example, a list nested block named `example_block` is set to an ordered collection of an object value of `attr1` to `"value1"` and `attr2` to `123` and an object value of `attr1` to `"value2"` and `attr2` to `456`: + +```hcl +resource "examplecloud_thing" "example" { + example_block { + attr1 = "value1" + attr2 = 123 + } + + example_block { + attr1 = "value2" + attr2 = 456 + } +} +``` + +## Schema Definition + + + +Blocks can only be defined on schemas or nested blocks within a schema, not underneath an attribute or nested attribute. + + + +Use one of the following block types to directly add a list nested value to a [schema](/terraform/plugin/framework/handling-data/schemas) or [block type](/terraform/plugin/framework/handling-data/blocks): + +| Schema Type | Attribute Type | +|-------------|----------------| +| [Data Source](/terraform/plugin/framework/data-sources) | [`schema.ListNestedBlock`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/datasource/schema#ListNestedBlock) | +| [Provider](/terraform/plugin/framework/providers) | [`schema.ListNestedBlock`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider/schema#ListNestedBlock) | +| [Resource](/terraform/plugin/framework/resources) | [`schema.ListNestedBlock`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema#ListNestedBlock) | +| [Ephemeral Resource](/terraform/plugin/framework/ephemeral-resources) | [`schema.ListNestedBlock`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/ephemeral/schema#ListNestedBlock) | + +The `NestedObject` field must be defined, which represents the [object value type](/terraform/plugin/framework/handling-data/types/object) of every element of the list. + +In this example, a resource schema defines a top level list nested block named `example_block` with a required string attribute named `attr1` and optional integer attribute named `attr2`: + +```go +func (r ThingResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Blocks: map[string]schema.Block{ + "example_block": schema.ListNestedBlock{ + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "attr1": schema.StringAttribute{ + Required: true, + // ... potentially other fields ... + }, + "attr2": schema.Int64Attribute{ + Optional: true, + // ... potentially other fields ... + }, + }, + }, + // ... potentially other fields ... + }, + // ... potentially other blocks ... + }, + } +} +``` + +Its [value type](/terraform/plugin/framework/handling-data/types) would be represented as `types.List` of `types.Object` with a mapping of `attr1` to `types.String` and `attr2` to `types.Int64`. + +A block type may itself contain further collection or nested attribute/block types, if necessary. + +In this example, a resource schema defines a top level list nested block named `example_block` with a required list of strings attribute named `attr` and a single nested block named `block`: + +```go +func (r ThingResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Blocks: map[string]schema.Block{ + "example_block": schema.ListNestedBlock{ + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "attr": schema.ListAttribute{ + ElementType: types.StringType, + Required: true, + // ... potentially other fields ... + }, + }, + Blocks: map[string]schema.Block{ + "block": schema.ListNestedBlock{ /* ... */ }, + }, + }, + // ... potentially other fields ... + }, + // ... potentially other blocks ... + }, + } +} +``` + +Its [value type](/terraform/plugin/framework/handling-data/types) would be represented as a `types.List` of `types.Object` with a mapping of `attr` to `types.List` of `types.String` and `block` to `types.Object`. + +### Custom Types + +You may want to build your own attribute value and type implementations to allow your provider to combine validation, description, and plan customization behaviors into a reusable bundle. This helps avoid duplication or reimplementation and ensures consistency. These implementations use the `CustomType` field in the attribute type. + +Refer to [Custom Types](/terraform/plugin/framework/handling-data/types/custom) for further details on creating provider-defined types and values. + +### Deprecation + +Set the `DeprecationMessage` field to a practitioner-focused message for how to handle the deprecation. The framework will automatically raise a warning diagnostic with this message if the practitioner configuration contains a known value for the attribute. Terraform version 1.2.7 and later will raise a warning diagnostic in certain scenarios if the deprecated attribute value is referenced elsewhere in a practitioner configuration. The framework [deprecations](/terraform/plugin/framework/deprecations) documentation fully describes the recommended practices for deprecating an attribute or resource. + +Some practitioner-focused examples of a deprecation message include: + +- Configure `other_attribute` instead. This attribute will be removed in the next major version of the provider. +- Remove this attribute's configuration as it no longer is used and the attribute will be removed in the next major version of the provider. + +### Description + +The framework provides two description fields, `Description` and `MarkdownDescription`, which various tools use to show additional information about an attribute and its intended purpose. This includes, but is not limited to, [`terraform-plugin-docs`](https://github.com/hashicorp/terraform-plugin-docs) for automated provider documentation generation and [`terraform-ls`](https://github.com/hashicorp/terraform-ls) for Terraform configuration editor integrations. + +### Plan Modification + + + +Only managed resources implement this concept. + + + +The framework provides two plan modification fields for managed resource attributes, `Default` and `PlanModifiers`, which define resource and attribute value planning behaviors. The resource [default](/terraform/plugin/framework/resources/default) and [plan modification](/terraform/plugin/framework/resources/plan-modification) documentation covers these features more in-depth. + +#### Common Use Case Plan Modification + +The [`listdefault`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/listdefault) package defines common use case `Default` implementations: + +- [`StaticValue(types.List)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/listdefault#StaticValue): Define a static list default value for the attribute. + +The [`listplanmodifier`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/listplanmodifier) package defines common use case `PlanModifiers` implementations: + +- [`RequiresReplace()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/listplanmodifier#RequiresReplace): Marks the resource for replacement if the resource is being updated and the plan value does not match the prior state value. +- [`RequiresReplaceIf()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/listplanmodifier#RequiresReplaceIf): Similar to `RequiresReplace()`, but also checks if a given function returns true. +- [`RequiresReplaceIfConfigured()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/listplanmodifier#RequiresReplaceIfConfigured): Similar to `RequiresReplace()`, but also checks if the configuration value is not null. +- [`UseStateForUnknown()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/listplanmodifier#UseStateForUnknown): Copies a known prior state value into the planned value. Use this when it is known that an unconfigured value will remain the same after a resource update. + +### Validation + +Set the `Validators` field to define [validation](/terraform/plugin/framework/validation#attribute-validation). This validation logic is ran in addition to any validation contained within a [custom type](#custom-types). + +#### Common Use Case Validators + +HashiCorp provides the additional [`terraform-plugin-framework-validators`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators) Go module which contains validation logic for common use cases. The [`listvalidator`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators/listvalidator) package within that module has list attribute validators such as defining conflicting attributes. + +## Accessing Values + +The [accessing values](/terraform/plugin/framework/handling-data/accessing-values) documentation covers general methods for reading [schema](/terraform/plugin/framework/handling-data/schemas) (configuration, plan, and state) data, which is necessary before accessing an attribute value directly. The [list type](/terraform/plugin/framework/handling-data/types/list#accessing-values) documentation covers methods for interacting with the attribute value itself. + +## Setting Values + +The [list type](/terraform/plugin/framework/handling-data/types/list#setting-values) documentation covers methods for creating or setting the appropriate value. The [writing data](/terraform/plugin/framework/handling-data/writing-state) documentation covers general methods for writing [schema](/terraform/plugin/framework/handling-data/schemas) (plan and state) data, which is necessary afterwards. diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/blocks/set-nested.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/blocks/set-nested.mdx new file mode 100644 index 0000000000..c7909aa0e8 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/blocks/set-nested.mdx @@ -0,0 +1,168 @@ +--- +page_title: Set nested blocks +description: >- + Learn to implement the set nested block type with the Terraform plugin framework. +--- + +# Set nested blocks + + + +Use [nested attribute types](/terraform/plugin/framework/handling-data/attributes#nested-attribute-types) instead of block types for new schema implementations. Block support is mainly for migrating legacy SDK-based providers. + + + +Set nested blocks are an unique, unordered collection of structures mapping explicit attribute names to nested attribute and block definitions. Values are represented by a [set type](/terraform/plugin/framework/handling-data/types/set) in the framework, containing [object type](/terraform/plugin/framework/handling-data/types/object) of the mapped attributes and blocks. + +In this Terraform configuration example, a set nested block named `example_block` is set to an unordered collection of an object value of `attr1` to `"value1"` and `attr2` to `123` and an object value of `attr1` to `"value2"` and `attr2` to `456`: + +```hcl +resource "examplecloud_thing" "example" { + example_block { + attr1 = "value1" + attr2 = 123 + } + + example_block { + attr1 = "value2" + attr2 = 456 + } +} +``` + +## Schema Definition + + + +Blocks can only be defined on schemas or nested blocks within a schema, not underneath an attribute or nested attribute. + + + +Use one of the following block types to directly add a list nested value to a [schema](/terraform/plugin/framework/handling-data/schemas) or [block type](/terraform/plugin/framework/handling-data/blocks): + +| Schema Type | Attribute Type | +|-------------|----------------| +| [Data Source](/terraform/plugin/framework/data-sources) | [`schema.SetNestedBlock`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/datasource/schema#SetNestedBlock) | +| [Provider](/terraform/plugin/framework/providers) | [`schema.SetNestedBlock`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider/schema#SetNestedBlock) | +| [Resource](/terraform/plugin/framework/resources) | [`schema.SetNestedBlock`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema#SetNestedBlock) | +| [Ephemeral Resource](/terraform/plugin/framework/ephemeral-resources) | [`schema.SetNestedBlock`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/ephemeral/schema#SetNestedBlock) | + +The `NestedObject` field must be defined, which represents the [object value type](/terraform/plugin/framework/handling-data/types/object) of every element of the set. + +In this example, a resource schema defines a top level set nested block named `example_block` with a required string attribute named `attr1` and optional integer attribute named `attr2`: + +```go +func (r ThingResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Blocks: map[string]schema.Attribute{ + "example_block": schema.SetNestedBlock{ + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "attr1": schema.StringAttribute{ + Required: true, + // ... potentially other fields ... + }, + "attr2": schema.Int64Attribute{ + Optional: true, + // ... potentially other fields ... + }, + }, + }, + // ... potentially other fields ... + }, + // ... potentially other blocks ... + }, + } +} +``` + +Its [value type](/terraform/plugin/framework/handling-data/types) would be represented as `types.Set` of `types.Object` with a mapping of `attr1` to `types.String` and `attr2` to `types.Int64`. + +A block type may itself contain further collection or nested attribute/block types, if necessary. + +In this example, a resource schema defines a top level set nested block named `example_block` with a required list of strings attribute named `attr` and a single nested block named `block`: + +```go +func (r ThingResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Blocks: map[string]schema.Block{ + "example_block": schema.SetNestedBlock{ + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "attr": schema.ListAttribute{ + ElementType: types.StringType, + Required: true, + // ... potentially other fields ... + }, + }, + Blocks: map[string]schema.Block{ + "block": schema.SetNestedBlock{ /* ... */ }, + }, + }, + // ... potentially other fields ... + }, + // ... potentially other blocks ... + }, + } +} +``` + +Its [value type](/terraform/plugin/framework/handling-data/types) would be represented as a `types.Set` of `types.Object` with a mapping of `attr` to `types.List` of `types.String` and `block` to `types.Object`. + +### Custom Types + +You may want to build your own attribute value and type implementations to allow your provider to combine validation, description, and plan customization behaviors into a reusable bundle. This helps avoid duplication or reimplementation and ensures consistency. These implementations use the `CustomType` field in the attribute type. + +Refer to [Custom Types](/terraform/plugin/framework/handling-data/types/custom) for further details on creating provider-defined types and values. + +### Deprecation + +Set the `DeprecationMessage` field to a practitioner-focused message for how to handle the deprecation. The framework will automatically raise a warning diagnostic with this message if the practitioner configuration contains a known value for the attribute. Terraform version 1.2.7 and later will raise a warning diagnostic in certain scenarios if the deprecated attribute value is referenced elsewhere in a practitioner configuration. The framework [deprecations](/terraform/plugin/framework/deprecations) documentation fully describes the recommended practices for deprecating an attribute or resource. + +Some practitioner-focused examples of a deprecation message include: + +- Configure `other_attribute` instead. This attribute will be removed in the next major version of the provider. +- Remove this attribute's configuration as it no longer is used and the attribute will be removed in the next major version of the provider. + +### Description + +The framework provides two description fields, `Description` and `MarkdownDescription`, which various tools use to show additional information about an attribute and its intended purpose. This includes, but is not limited to, [`terraform-plugin-docs`](https://github.com/hashicorp/terraform-plugin-docs) for automated provider documentation generation and [`terraform-ls`](https://github.com/hashicorp/terraform-ls) for Terraform configuration editor integrations. + +### Plan Modification + + + +Only managed resources implement this concept. + + + +The framework provides two plan modification fields for managed resource attributes, `Default` and `PlanModifiers`, which define resource and attribute value planning behaviors. The resource [default](/terraform/plugin/framework/resources/default) and [plan modification](/terraform/plugin/framework/resources/plan-modification) documentation covers these features more in-depth. + +#### Common Use Case Plan Modification + +The [`setdefault`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/setdefault) package defines common use case `Default` implementations: + +- [`StaticValue(types.Set)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/setdefault#StaticValue): Define a static list default value for the attribute. + +The [`setplanmodifier`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/setplanmodifier) package defines common use case `PlanModifiers` implementations: + +- [`RequiresReplace()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/setplanmodifier#RequiresReplace): Marks the resource for replacement if the resource is being updated and the plan value does not match the prior state value. +- [`RequiresReplaceIf()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/setplanmodifier#RequiresReplaceIf): Similar to `RequiresReplace()`, but also checks if a given function returns true. +- [`RequiresReplaceIfConfigured()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/setplanmodifier#RequiresReplaceIfConfigured): Similar to `RequiresReplace()`, but also checks if the configuration value is not null. +- [`UseStateForUnknown()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/setplanmodifier#UseStateForUnknown): Copies a known prior state value into the planned value. Use this when it is known that an unconfigured value will remain the same after a resource update. + +### Validation + +Set the `Validators` field to define [validation](/terraform/plugin/framework/validation#attribute-validation). This validation logic is ran in addition to any validation contained within a [custom type](#custom-types). + +#### Common Use Case Validators + +HashiCorp provides the additional [`terraform-plugin-framework-validators`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators) Go module which contains validation logic for common use cases. The [`setvalidator`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators/setvalidator) package within that module has set attribute validators such as defining conflicting attributes. + +## Accessing Values + +The [accessing values](/terraform/plugin/framework/handling-data/accessing-values) documentation covers general methods for reading [schema](/terraform/plugin/framework/handling-data/schemas) (configuration, plan, and state) data, which is necessary before accessing an attribute value directly. The [set type](/terraform/plugin/framework/handling-data/types/set#accessing-values) documentation covers methods for interacting with the attribute value itself. + +## Setting Values + +The [set type](/terraform/plugin/framework/handling-data/types/set#setting-values) documentation covers methods for creating or setting the appropriate value. The [writing data](/terraform/plugin/framework/handling-data/writing-state) documentation covers general methods for writing [schema](/terraform/plugin/framework/handling-data/schemas) (plan and state) data, which is necessary afterwards. diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/blocks/single-nested.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/blocks/single-nested.mdx new file mode 100644 index 0000000000..4f3d41b09d --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/blocks/single-nested.mdx @@ -0,0 +1,160 @@ +--- +page_title: Single nested blocks +description: >- + Learn to implement the single nested block type with the Terraform plugin + framework. +--- + +# Single nested blocks + + + +Use [nested attribute types](/terraform/plugin/framework/handling-data/attributes#nested-attribute-types) instead of block types for new schema implementations. Block support is mainly for migrating legacy SDK-based providers. + + + +Single nested blocks are a single structure mapping explicit attribute names to nested attribute and block definitions. Values are represented by a [object type](/terraform/plugin/framework/handling-data/types/object) in the framework, containing nested attribute values of the mapped attributes and blocks. + +In this Terraform configuration example, a single nested block named `example_block` is set to the mapped values of `attr1` to `"value1"` and `attr2` to `123`: + +```hcl +resource "examplecloud_thing" "example" { + example_block { + attr1 = "value1" + attr2 = 123 + } +} +``` + +## Schema Definition + + + +Blocks can only be defined on schemas or nested blocks within a schema, not underneath an attribute or nested attribute. + + + +Use one of the following block types to directly add a single nested value to a [schema](/terraform/plugin/framework/handling-data/schemas) or [block type](/terraform/plugin/framework/handling-data/blocks): + +| Schema Type | Attribute Type | +|-------------|----------------| +| [Data Source](/terraform/plugin/framework/data-sources) | [`schema.SingleNestedBlock`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/datasource/schema#SingleNestedBlock) | +| [Provider](/terraform/plugin/framework/providers) | [`schema.SingleNestedBlock`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider/schema#SingleNestedBlock) | +| [Resource](/terraform/plugin/framework/resources) | [`schema.SingleNestedBlock`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema#SingleNestedBlock) | +| [Ephemeral Resource](/terraform/plugin/framework/ephemeral-resources) | [`schema.SingleNestedBlock`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/ephemeral/schema#SingleNestedBlock) | + +In most use cases, the `Attributes` or `Blocks` field should be defined, which represents the mapping of explicit string attribute names to nested attributes and/or blocks. + +In this example, a resource schema defines a top level single nested block named `example_block` with a required string attribute named `attr1` and optional integer attribute named `attr2`: + +```go +func (r ThingResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Blocks: map[string]schema.Block{ + "example_block": schema.SingleNestedBlock{ + Attributes: map[string]schema.Attribute{ + "attr1": schema.StringAttribute{ + Required: true, + // ... potentially other fields ... + }, + "attr2": schema.Int64Attribute{ + Optional: true, + // ... potentially other fields ... + }, + }, + // ... potentially other fields ... + }, + // ... potentially other blocks ... + }, + } +} +``` + +Its [value type](/terraform/plugin/framework/handling-data/types) would be represented as a `types.Object` with a mapping of `attr1` to `types.String` and `attr2` to `types.Int64`. + +A block type may itself contain further collection or nested attribute/block types, if necessary. + +In this example, a resource schema defines a top level single nested block named `example_block` with a required list of strings attribute named `attr` and a single nested block named `block`: + +```go +func (r ThingResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Blocks: map[string]schema.Block{ + "example_block": schema.SingleNestedBlock{ + Attributes: map[string]schema.Attribute{ + "attr": schema.ListAttribute{ + ElementType: types.StringType, + Required: true, + // ... potentially other fields ... + }, + }, + Blocks: map[string]schema.Block{ + "block": schema.SingleNestedBlock{ /* ... */ }, + }, + // ... potentially other fields ... + }, + // ... potentially other blocks ... + }, + } +} +``` + +Its [value type](/terraform/plugin/framework/handling-data/types) would be represented as a `types.Object` with a mapping of `attr` to `types.List` of `types.String` and `block` to `types.Object`. + +### Custom Types + +You may want to build your own attribute value and type implementations to allow your provider to combine validation, description, and plan customization behaviors into a reusable bundle. This helps avoid duplication or reimplementation and ensures consistency. These implementations use the `CustomType` field in the attribute type. + +Refer to [Custom Types](/terraform/plugin/framework/handling-data/types/custom) for further details on creating provider-defined types and values. + +### Deprecation + +Set the `DeprecationMessage` field to a practitioner-focused message for how to handle the deprecation. The framework will automatically raise a warning diagnostic with this message if the practitioner configuration contains a known value for the attribute. Terraform version 1.2.7 and later will raise a warning diagnostic in certain scenarios if the deprecated attribute value is referenced elsewhere in a practitioner configuration. The framework [deprecations](/terraform/plugin/framework/deprecations) documentation fully describes the recommended practices for deprecating an attribute or resource. + +Some practitioner-focused examples of a deprecation message include: + +- Configure `other_attribute` instead. This attribute will be removed in the next major version of the provider. +- Remove this attribute's configuration as it no longer is used and the attribute will be removed in the next major version of the provider. + +### Description + +The framework provides two description fields, `Description` and `MarkdownDescription`, which various tools use to show additional information about an attribute and its intended purpose. This includes, but is not limited to, [`terraform-plugin-docs`](https://github.com/hashicorp/terraform-plugin-docs) for automated provider documentation generation and [`terraform-ls`](https://github.com/hashicorp/terraform-ls) for Terraform configuration editor integrations. + +### Plan Modification + + + +Only managed resources implement this concept. + + + +The framework provides two plan modification fields for managed resource attributes, `Default` and `PlanModifiers`, which define resource and attribute value planning behaviors. The resource [default](/terraform/plugin/framework/resources/default) and [plan modification](/terraform/plugin/framework/resources/plan-modification) documentation covers these features more in-depth. + +#### Common Use Case Plan Modification + +The [`objectdefault`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/objectdefault) package defines common use case `Default` implementations: + +- [`StaticValue(types.Object)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/objectdefault#StaticValue): Define a static object default value for the attribute. + +The [`objectplanmodifier`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/objectplanmodifier) package defines common use case `PlanModifiers` implementations: + +- [`RequiresReplace()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/objectplanmodifier#RequiresReplace): Marks the resource for replacement if the resource is being updated and the plan value does not match the prior state value. +- [`RequiresReplaceIf()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/objectplanmodifier#RequiresReplaceIf): Similar to `RequiresReplace()`, but also checks if a given function returns true. +- [`RequiresReplaceIfConfigured()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/objectplanmodifier#RequiresReplaceIfConfigured): Similar to `RequiresReplace()`, but also checks if the configuration value is not null. +- [`UseStateForUnknown()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/objectplanmodifier#UseStateForUnknown): Copies a known prior state value into the planned value. Use this when it is known that an unconfigured value will remain the same after a resource update. + +### Validation + +Set the `Validators` field to define [validation](/terraform/plugin/framework/validation#attribute-validation). This validation logic is ran in addition to any validation contained within a [custom type](#custom-types). + +#### Common Use Case Validators + +HashiCorp provides the additional [`terraform-plugin-framework-validators`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators) Go module which contains validation logic for common use cases. The [`objectvalidator`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators/objectvalidator) package within that module has object attribute validators such as defining conflicting attributes. + +## Accessing Values + +The [accessing values](/terraform/plugin/framework/handling-data/accessing-values) documentation covers general methods for reading [schema](/terraform/plugin/framework/handling-data/schemas) (configuration, plan, and state) data, which is necessary before accessing an attribute value directly. The [object type](/terraform/plugin/framework/handling-data/types/object#accessing-values) documentation covers methods for interacting with the attribute value itself. + +## Setting Values + +The [object type](/terraform/plugin/framework/handling-data/types/object#setting-values) documentation covers methods for creating or setting the appropriate value. The [writing data](/terraform/plugin/framework/handling-data/writing-state) documentation covers general methods for writing [schema](/terraform/plugin/framework/handling-data/schemas) (plan and state) data, which is necessary afterwards. diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/dynamic-data.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/dynamic-data.mdx new file mode 100644 index 0000000000..07f845f418 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/dynamic-data.mdx @@ -0,0 +1,223 @@ +--- +page_title: Handling dynamic data +description: >- + Learn how to handle data when using dynamic types in the Terraform plugin + framework. +--- + +# Handling dynamic data + + + +Static types should always be preferred over dynamic types, when possible. + + + +Dynamic data handling uses the [framework dynamic type](/terraform/plugin/framework/handling-data/types/dynamic) to communicate to Terraform that the value type of a specific field will be determined at runtime. This allows a provider developer to handle multiple value types of data with a single attribute, parameter, or return. + +Dynamic data can be defined with: +- [Dynamic attribute](/terraform/plugin/framework/handling-data/attributes/dynamic) +- A [dynamic](/terraform/plugin/framework/handling-data/types/dynamic) attribute type in an [object attribute](/terraform/plugin/framework/handling-data/attributes/object) +- [Dynamic function parameter](/terraform/plugin/framework/functions/parameters/dynamic) +- [Dynamic function return](/terraform/plugin/framework/functions/returns/dynamic) +- A [dynamic](/terraform/plugin/framework/handling-data/types/dynamic) attribute type in an [object parameter](/terraform/plugin/framework/functions/parameters/object) +- A [dynamic](/terraform/plugin/framework/handling-data/types/dynamic) attribute type in an [object return](/terraform/plugin/framework/functions/returns/object) + +Using dynamic data has a negative impact on practitioner experience when using Terraform and downstream tooling, like practitioner configuration editor integrations. Dynamics do not change how [Terraform's static type system](/terraform/language/expressions/types) behaves and all data consistency rules are applied the same as static types. Provider developers should understand all the below [considerations](#considerations) when creating a provider with a dynamic type. + +Only use a dynamic type when there is not a suitable static type alternative. + +## Considerations + +When dynamic data is used, Terraform will no longer have any static information about the value types expected for a given attribute, function parameter, or function return. This results in behaviors that the provider developer will need to account for with additional documentation, code, error messaging, etc. + +### Downstream Tooling + +Practitioner configuration editor integrations, like the Terraform VSCode extension and language server, cannot provide any static information when using dynamic data in configurations. This can result in practitioners using dynamic data in expressions (like [`for`](/terraform/language/expressions/for)) incorrectly that will only error at runtime. + +Given this example, a resource schema defines a top level computed [dynamic attribute](/terraform/plugin/framework/handling-data/attributes/dynamic) named `example_attribute`: + +```go +func (r ThingResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "example_attribute": schema.DynamicAttribute{ + Computed: true, + // ... potentially other fields ... + }, + // ... potentially other attributes ... + }, + } +} +``` + +The configuration below would be valid until a practitioner runs an apply. If the type of `example_attribute` is not iterable, then the practitioner will receive an error only when they run a command: + +```hcl +resource "examplecloud_thing" "example" {} + +output "dynamic_output" { + value = [for val in examplecloud_thing.example.example_attribute : val] +} +``` + +Results in the following error: + +```bash +│ Error: Iteration over non-iterable value +│ +│ on resource.tf line 15, in output "dynamic_output": +│ 15: value = [for val in examplecloud_thing.example.example_attribute : val] +│ ├──────────────── +│ │ examplecloud_thing.example.example_attribute is "string value" +│ +│ A value of type string cannot be used as the collection in a 'for' expression. +``` + +Dynamic data that is meant for practitioners to utilize in configurations should document all potential output types and expected usage to avoid confusing errors. + +### Handling All Possible Types + +Terraform will not [automatically convert](/terraform/language/expressions/types#type-conversion) values to conform to a static type, exposing provider developers to the Terraform type system directly. Provider developers will need to deal with this lack of type conversion by writing logic that handles [every possible type](/terraform/language/expressions/types#types) that Terraform supports. + +In this example, a resource schema defines a top level required [dynamic attribute](/terraform/plugin/framework/handling-data/attributes/dynamic) named `example_attribute`: + +```go +func (r ThingResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "example_attribute": schema.DynamicAttribute{ + Required: true, + // ... potentially other fields ... + }, + // ... potentially other attributes ... + }, + } +} +``` + +An example of handling every possible Terraform type that could be provided to a configuration would be: + +```go + // Example data model definition + // type ExampleModel struct { + // ExampleAttribute types.Dynamic `tfsdk:"example_attribute"` + // } + switch value := data.ExampleAttribute.UnderlyingValue().(type) { + case types.Bool: + // Handle boolean value + case types.Number: + // Handle float64, int64, and number values + case types.List: + // Handle list value + case types.Map: + // Handle map value + case types.Object: + // Handle object value + case types.Set: + // Handle set value + case types.String: + // Handle string value + case types.Tuple: + // Handle tuple value + } +``` + +When writing test configurations and debugging provider issues, developers will also want to understand how Terraform represents [complex type literals](/terraform/language/expressions/type-constraints#complex-type-literals). For example, Terraform does not provide any way to directly represent lists, maps, or sets. + + +### Handling Underlying Null and Unknown Values + +With dynamic data, in addition to typical [null](/terraform/plugin/framework/handling-data/terraform-concepts#null-values) and [unknown](/terraform/plugin/framework/handling-data/terraform-concepts#unknown-values) value handling, provider developers will need to implement additional logic to determine if an underlying value for a dynamic is null or unknown. + +#### Underlying Null + +In the configuration below, Terraform knows the underlying value type, `string`, but the underlying string value is null: + +```hcl +resource "examplecloud_thing" "example" { + example_attribute = var.null_string +} + +variable "null_string" { + type = string + default = null +} +``` + +This will result in a known dynamic value, with an underlying value that is a null [string type](/terraform/plugin/framework/handling-data/types/string). This can be detected utilizing the [`(types.Dynamic).IsUnderlyingValueNull()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#DynamicValue.IsUnderlyingValueNull) method. An equivalent framework value to this scenario would be: + +```go +dynValWithNullString := types.DynamicValue(types.StringNull()) +``` + +#### Underlying Unknown + +In the configuration below, Terraform knows the underlying value type of [`random_shuffle.result`](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/shuffle#result), a `list(string)`, but the underlying list value is unknown: + +```hcl +resource "random_shuffle" "example" { + input = ["one", "two"] + result_count = 2 +} + +resource "examplecloud_thing" "this" { + example_attribute = random_shuffle.example.result +} +``` + +This will result in a known dynamic value, with an underlying value that is an unknown [list of string types](/terraform/plugin/framework/handling-data/types/list). This can be detected utilizing the [`(types.Dynamic).IsUnderlyingValueUnknown()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#DynamicValue.IsUnderlyingValueUnknown) method. An equivalent framework value to this scenario would be: + +```go +dynValWithUnknownList := types.DynamicValue(types.ListUnknown(types.StringType)) +``` + +### Understanding Type Consistency + +For [managed resources](/terraform/plugin/framework/resources), Terraform core implements [data consistency rules](https://github.com/hashicorp/terraform/blob/main/docs/resource-instance-change-lifecycle.md) between configuration, plan, and state data. With [dynamic attributes](/terraform/plugin/framework/handling-data/attributes/dynamic), these consistency rules are also applied to the **type** of data. + +For example, given a dynamic `example_attribute` that is computed and optional: + +```go +func (r ThingResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "example_attribute": schema.DynamicAttribute{ + Computed: true, + Optional: true, + // ... potentially other fields ... + }, + // ... potentially other attributes ... + }, + } +} +``` + +If a practitioner configures this resource as: + +```hcl +resource "examplecloud_thing" "example" { + # This literal expression is a tuple[string, string] + example_attribute = ["one", "two"] +} +``` + +Then the exact type must be planned and stored in state during `apply` as a [tuple](/terraform/plugin/framework/handling-data/types/tuple) with two [string](/terraform/plugin/framework/handling-data/types/string) element types. If provider code attempts to store this attribute as a different type, like a [list](/terraform/plugin/framework/handling-data/types/list) of strings, even with the same data values, Terraform will produce an error during apply: + +```bash +│ Error: Provider produced inconsistent result after apply +│ +│ When applying changes to examplecloud_thing.example, provider "provider[\"TYPE\"]" produced an unexpected new value: .example_attribute: wrong final value type: tuple required. +│ +│ This is a bug in the provider, which should be reported in the providers own issue tracker. +``` + +If a practitioner configures this same resource as: + +```hcl +resource "examplecloud_thing" "example" { + example_attribute = tolist(["one", "two"]) +} +``` + +Then the exact type must be planned and stored in state during `apply` as a [list](/terraform/plugin/framework/handling-data/types/list) of strings. diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/path-expressions.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/path-expressions.mdx new file mode 100644 index 0000000000..80cca2a1d6 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/path-expressions.mdx @@ -0,0 +1,185 @@ +--- +page_title: Path expressions +description: >- + Learn how to implement path expressions in the Terraform plugin framework. + Path expressions are logic built on top of paths, which may represent one or + more actual paths within schema data. +--- + + +# Path expressions + +Path expressions are logic built on top of [paths](/terraform/plugin/framework/paths), which may represent one or more actual paths within a schema or schema-based data. Expressions enable providers to work outside the restrictions of absolute paths and steps. + +## Usage + +Example uses include: + +- [Path based attribute validators](/terraform/plugin/framework/validation#path-based-attribute-validators), such as those in the [`terraform-plugin-framework-validators` module `schemavalidator` package](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/schemavalidator). + +Use cases which require exact locations, such as [diagnostics](/terraform/plugin/framework/diagnostics), implement [paths](/terraform/plugin/framework/paths). + +## Concepts + +Path expressions are an abstraction above [paths](/terraform/plugin/framework/paths). This page assumes knowledge of path concepts and implementations. + +At its core, expressions implement the following on top of paths: + +- Information that designates whether path information is intended to be absolute, similar to paths, or relative, where it is assumed it will be merged with other absolute path information. +- Parent steps, which enables backwards traversal towards the root of a schema in relative paths, after being merged with other absolute path information. +- Path matching, which enables path information to logically return one or more actual paths. + +Similar to paths, expressions are built using steps. There are expression steps which directly correspond to exact path steps, such as `AtListIndex()`, `AtMapKey()`, `AtName()`, `AtSetValue()`. Their implementation is the same. However, there are additional expression steps, such as `AtAnyListIndex()`, which cannot be represented in paths due to the potential for ambiguity. + +Path matching is the notion that each expression step implements a method that logically determines if a given exact path step should match. For example, the `AtAnyListIndex()` expression step will accept any exact path step for a list index. Path matching with an expression is a collection of matching each expression step against each exact path step, after resolving any potential parent steps. + +Every path expression must align with the schema definition or an error diagnostic will be raised when working with path matching within the framework. Provider-defined functionality that is schema-based, such as attribute validation and attribute plan modification, are provided an accurate current path expression since that functionality would not be able to determine its own path expression. + +## Building Path Expressions + +The framework implementation for path expressions is in the [`path` package](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/path), with the [`path.Expression` type](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/path#Expression) being the main provider developer interaction point. + +### Building Absolute Path Expressions + +Call the [`path.MatchRoot()` function](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/path#MatchRoot) with an attribute name or block name at the root of the schema to begin an absolute path expression. + +Given this example schema with a root attribute named `example_root_attribute`: + +```go +schema.Schema{ + Attributes: map[string]schema.Attribute{ + "example_root_attribute": schema.StringAttribute{ + Required: true, + }, + }, +} +``` + +The call to `path.MatchRoot()` which matches the location of `example_root_attribute` string value is: + +```go +path.MatchRoot("example_root_attribute") +``` + +For blocks, the beginning of a path expression is similarly defined. Attribute and block names cannot overlap, so the framework automatically handles whether a path expression is referring to an attribute or block to start. + +Given this example schema with a root block named `example_root_block`: + +```go +schema.Schema{ + Blocks: map[string]schema.Block{ + "example_root_block": schema.ListNestedBlock{ + NestedObject: schema.NestedBlockObject{/* ... */}, + }, + }, +} +``` + +The call to `path.MatchRoot()` which matches the location of `example_root_block` list value is: + +```go +path.MatchRoot("example_root_block") +``` + +Once a `path.Expression` is started, it supports a builder pattern, which allows for chaining method calls to construct a full path. + +This example shows a hypothetical path expression that points to any element of a list attribute to highlight the builder pattern: + +```go +path.MatchRoot("example_list_attribute").AtAnyListIndex() +``` + +This pattern can be extended to as many calls as necessary. The [Building Expression Steps section](#building-expression-steps) covers the different framework schema types and any special path step methods. + +### Building Relative Path Expressions + +Relative path expressions are, by nature, contextual to the actual path where they are defined in a schema. Call the [`path.MatchRelative()` function](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/path#MatchRelative) to begin a relative path expression. + +This example shows a relative path expression which references a child attribute: + +```go +schema.Schema{ + Attributes: map[string]schema.Attribute{ + "root_list_attribute": schema.ListNestedAttribute{ + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "nested_list_attribute": schema.ListNestedAttribute{ + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "deeply_nested_string_attribute": schema.StringAttribute{ + Required: true, + }, + }, + }, + Required: true, + Validators: []validator.List{ + exampleValidatorThatAcceptsExpressions( + path.MatchRelative().AtAnyListIndex().AtName("deeply_nested_string_attribute"), + ), + }, + }, + "nested_string_attribute": schema.StringAttribute{ + Required: true, + }, + }, + }, + Required: true, + }, + }, +} +``` + +This example shows a relative path expression which references a different attribute within the same list index: + +```go +schema.Schema{ + Attributes: map[string]schema.Attribute{ + "root_list_attribute": schema.ListNestedAttribute{ + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "nested_list_attribute": schema.ListNestedAttribute{ + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "deeply_nested_string_attribute": schema.StringAttribute{ + Required: true, + }, + }, + }, + Required: true, + Validators: []validator.List{ + exampleValidatorThatAcceptsExpressions( + path.MatchRelative().AtParent().AtName("nested_string_attribute"), + ), + }, + }, + "nested_string_attribute": schema.StringAttribute{ + Required: true, + }, + }, + }, + Required: true, + }, + }, +} +``` + +### Building Expression Steps + +Expressions follow similar schema type rules as paths, in particular [Building Attribute Paths](/terraform/plugin/framework/paths#building-attribute-paths), [Building Nested Attribute Paths](/terraform/plugin/framework/paths#building-nested-attribute-paths), and [Building Block Paths](/terraform/plugin/framework/paths#building-block-paths). + +The following list shows the [`path.Expression` type](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/path#Expression) methods that behave similar to [`path.Path` type](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/path#Path) methods. + +- `AtListIndex()` +- `AtMapKey()` +- `AtName()` +- `AtSetValue()` + +The following table shows the additional [`path.Expression` type](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/path#Expression) methods and their descriptions. + +| Expression Method | Description | +| ------------------ | ----------- | +| `AtAnyListIndex()` | Will return matches for any list index. Can be used anywhere `AtListIndex()` can be used. | +| `AtAnyMapKey()` | Will return matches for any map key. Can be used anywhere `AtMapKey()` can be used. | +| `AtAnySetValue()` | Will return matches for any set value. Can be used anywhere `AtSetValue()` can be used. | +| `AtParent()` | Will remove the last expression step, or put differently, will match the path closer to the root of the schema. | + diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/paths.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/paths.mdx new file mode 100644 index 0000000000..1df50cf706 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/paths.mdx @@ -0,0 +1,556 @@ +--- +page_title: Paths +description: >- + Learn how to implement paths in the Terraform plugin framework. Paths + represent a location within a schema or schema-based data. +--- + +# Paths + +An exact location within a [schema](/terraform/plugin/framework/handling-data/schemas) or schema-based data such as [`tfsdk.Config`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/tfsdk#Config), [`tfsdk.Plan`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/tfsdk#Plan), or [`tfsdk.State`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/tfsdk#State), is referred to as a path. + +## Usage + +Example uses in the framework include: + +- [Diagnostics](/terraform/plugin/framework/diagnostics) intended for a specific attribute, which allows Terraform to show the configuration file and line information. +- [Accessing configuration, plan, or state data](/terraform/plugin/framework/accessing-values) for a specific attribute. +- [Writing plan or state data](/terraform/plugin/framework/writing-state) for a specific attribute. + +More advanced use cases, such as [path based attribute validators](/terraform/plugin/framework/validation#path-based-attribute-validators), typically implement [path expressions](/terraform/plugin/framework/path-expressions) which enables additional logic beyond exact paths. + +## Concepts + +Paths are designed around the underlying Terraform implementation of a schema and schema-based data. Terraform schemas are a tree structure based on value storing attributes, which may have their own structural component, and structural blocks. Both attributes and blocks are associated with specific data types, some of which represent structural or collection types which hold further information which can be traversed. Each traversal into that further information is referred to as a path step. Paths are always absolute and start from the root, or top level, of a schema. + +Given the tree structure of Terraform schemas, descriptions of paths and their steps borrow certain hierarchy terminology such as parent and child. A parent path describes a path without one or more of the final steps of a given path, or put differently, a partial path closer to the root of the schema. A child path describes a path with one or more additional steps beyond a given path, or put differently, a path containing the given path but further from the root of the schema. + +Every path must align with the schema definition or an error diagnostic will be raised when working with paths within the framework. Provider-defined functionality that is schema-based, such as attribute validation and attribute plan modification, are provided an accurate current path since that functionality would not be able to determine its own path. + +[Path expressions](/terraform/plugin/framework/path-expressions) are an abstraction on top of paths, which enable additional use cases with provider-defined functionality, such as relative path, parent step, and wildcard step support. + +## Building Paths + +The framework implementation for paths is in the [`path` package](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/path), with the [`path.Path` type](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/path#Path) being the main provider developer interaction point. Call the [`path.Root()` function](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/path#Root) with an attribute name or block name at the root of the schema to begin a path. + +Given this example schema with a root attribute named `example_root_attribute`: + +```go +schema.Schema{ + Attributes: map[string]schema.Attribute{ + "example_root_attribute": schema.StringAttribute{ + Required: true, + }, + }, +} +``` + +The call to `path.Root()` which matches the location of `example_root_attribute` string value is: + +```go +path.Root("example_root_attribute") +``` + +For blocks, the beginning of a path is similarly defined. Attribute and block names cannot overlap, so the framework automatically handles whether a path is referring to an attribute or block to start. + +Given this example schema with a root block named `example_root_block`: + +```go +schema.Schema{ + Blocks: map[string]schema.Block{ + "example_root_block": schema.ListNestedBlock{ + NestedObject: schema.NestedBlockObject{/* ... */}, + }, + }, +} +``` + +The call to `path.Root()` which matches the location of `example_root_block` list value is: + +```go +path.Root("example_root_block") +``` + +Once a `path.Path` is started, it supports a builder pattern, which allows for chaining method calls to construct a full path. + +This example shows a hypothetical path that points to the first element of a list attribute to highlight the builder pattern: + +```go +path.Root("example_list_attribute").AtListIndex(0) +``` + +This pattern can be extended to as many calls as necessary. The different framework schema types and their associated path step methods are shown in the following sections. + +### Building Attribute Paths + +The following table shows the different [`path.Path` type](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/path#Path) methods associated with building paths for attribute implementations. Attribute types that cannot be traversed further are shown with N/A (not applicable). + +| Attribute Type | Child Path Method | +|---------------------------|--------------------------------------------------------------------------------------------------------| +| `schema.BoolAttribute` | N/A | +| `schema.DynamicAttribute` | N/A | +| `schema.Float32Attribute` | N/A | +| `schema.Float64Attribute` | N/A | +| `schema.Int32Attribute` | N/A | +| `schema.Int64Attribute` | N/A | +| `schema.ListAttribute` | `AtListIndex()` | +| `schema.MapAttribute` | `AtMapKey()` | +| `schema.NumberAttribute` | N/A | +| `schema.ObjectAttribute` | `AtName()` | +| `schema.SetAttribute` | `AtSetValue()` | +| `schema.StringAttribute` | N/A | + +Given following schema example: + +```go +schema.Schema{ + Attributes: map[string]schema.Attribute{ + "root_map_attribute": schema.MapAttribute{ + ElementType: types.StringType, + Required: true, + }, + }, +} +``` + +The path which matches the string value associated with a hypothetical map key of `example-key` of the `root_map_attribute` attribute is: + +```go +path.Root("root_map_attribute").AtMapKey("example-key") +``` + +Any type that supports a child path may define an element type that also supports a child path. Paths can continue to be built using the associated method with each level of the attribute type. + +Given following schema example: + +```go +schema.Schema{ + Attributes: map[string]schema.Attribute{ + "root_map_attribute": schema.MapAttribute{ + ElementType: types.ListType{ + ElemType: types.StringType, + }, + Required: true, + }, + }, +} +``` + +The path which matches the third string value associated with the list value of a hypothetical map key of `example-key` of the `root_map_attribute` attribute is: + +```go +path.Root("root_map_attribute").AtMapKey("example-key").AtListIndex(2) +``` + +Unless there is a very well defined data structure involved, the level of path specificity in the example above is fairly uncommon to manually build in provider logic though. Most provider logic will typically build a path to the value of an attribute (e.g. its first type) and work with that data, or potentially one level deeper in well known cases, since each level introduces additional complexity associated with potentially null or unknown values. Provider logic can instead use an iterative path building approach when dealing with attributes that have multiple levels. + +This example shows an iterative path building approach to handle any map keys and list indices in the above schema: + +```go +attributePath := path.Root("root_map_attribute") + +// attributeValue is an example types.Map value which was previously fetched, +// potentially using the path above. +for mapKey, mapValue := range attributeValue.Elements() { + mapKeyPath := attributePath.AtMapKey(mapKey) + + // ... + + for listIndex, listValue := range mapValue.Elements() { + listIndexPath := mapKeyPath.AtListIndex(listIndex) + + // ... + } +} +``` + +### Building Nested Attribute Paths + +The following table shows the different [`path.Path` type](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/path#Path) methods associated with building paths for nested attributes. + +| Nested Attribute Type | Child Path Method | +| ------------------------------ | ----------------- | +| `schema.ListNestedAttribute` | `AtListIndex()` | +| `schema.MapNestedAttribute` | `AtMapKey()` | +| `schema.SetNestedAttribute` | `AtSetValue()` | +| `schema.SingleNestedAttribute` | `AtName()` | + +Nested attributes eventually implement attributes at child paths, which follow the methods shown in the [Building Attribute Paths section](#building-attribute-paths). + +#### Building List Nested Attributes Paths + +An attribute that implements `schema.ListNestedAttribute` conceptually is a list containing objects with attribute names. + +Given the following schema example: + +```go +schema.Schema{ + Attributes: map[string]schema.Attribute{ + "root_list_attribute": schema.ListNestedAttribute{ + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "nested_string_attribute": schema.StringAttribute{ + Required: true, + }, + }, + }, + Required: true, + }, + }, +} +``` + +The path which matches the list associated with the `root_list_attribute` attribute is: + +```go +path.Root("root_list_attribute") +``` + +The path which matches the first object in the list associated with the `root_list_attribute` attribute is: + +```go +path.Root("root_list_attribute").AtListIndex(0) +``` + +The path which matches the `nested_string_attribute` string value in the first object in the list associated with `root_list_attribute` attribute is: + +```go +path.Root("root_list_attribute").AtListIndex(0).AtName("nested_string_attribute") +``` + +#### Building Map Nested Attributes Paths + +An attribute that implements `schema.MapNestedAttribute` conceptually is a map containing values of objects with attribute names. + +Given the following schema example: + +```go +schema.Schema{ + Attributes: map[string]schema.Attribute{ + "root_map_attribute": schema.MapNestedAttribute{ + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "nested_string_attribute": schema.StringAttribute{ + Required: true, + }, + }, + }, + Required: true, + }, + }, +} +``` + +The path which matches the map associated with the `root_map_attribute` attribute is: + +```go +path.Root("root_map_attribute") +``` + +The path which matches the a hypothetical `"example-key"` object in the map associated with the `root_map_attribute` attribute is: + +```go +path.Root("root_map_attribute").AtMapKey("example-key") +``` + +The path which matches the `nested_string_attribute` string value in a hypothetical `"example-key"` object in the map associated with `root_map_attribute` attribute is: + +```go +path.Root("root_map_attribute").AtMapKey("example-key").AtName("nested_string_attribute") +``` + +#### Building Set Nested Attributes Paths + +An attribute that implements `schema.SetNestedAttribute` conceptually is a set containing objects with attribute names. Attempting to build set nested attribute paths is extremely tedius as set element selection is based on the entire value of the element, including any null or unknown values. Avoid manual set-based path building. Instead, use functionality which supports [path expressions](/terraform/plugin/framework/path-expressions) as that supports wildcard path matching ([`path.Expression` type `AtAnySetValue()` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/path#Expression.AtAnySetValue)) or relative paths. + +Given the following schema example: + +```go +schema.Schema{ + Attributes: map[string]schema.Attribute{ + "root_set_attribute": schema.SetNestedAttribute{ + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "nested_string_attribute": schema.StringAttribute{ + Required: true, + }, + }, + }, + Required: true, + }, + }, +} +``` + +The path which matches the set associated with the `root_set_attribute` attribute is: + +```go +path.Root("root_set_attribute") +``` + +Examples below will presume a `nested_string_attribute` string value of `types.StringValue("example")` for brevity. In real world usage, the string value may be `types.StringNull()`, `types.StringUnknown()` or `types.StringValue("something-else")`, which are all considered different set paths from each other. Each additional attribute or block introduces exponentially more possible paths given each attribute or block value may be null, unknown, or a unique known value. + +The path which matches the object associated with the `root_set_attribute` block is: + +```go +path.Root("root_set_attribute").AtSetValue(types.ObjectValueMust( + map[string]attr.Type{ + "nested_string_attribute": types.StringType, + }, + map[string]attr.Value{ + "nested_string_attribute": types.StringValue("example"), + } +)) +``` + +The path which matches the `nested_string_attribute` string value associated with `root_set_attribute` block is: + +```go +path.Root("root_set_attribute").AtSetValue(types.ObjectValueMust( + map[string]attr.Type{ + "nested_string_attribute": types.StringType, + }, + map[string]attr.Value{ + "nested_string_attribute": types.StringValue("example"), + } +)).AtName("nested_string_attribute") +``` + +#### Building Single Nested Attributes Paths + +An attribute that implements `schema.SingleNestedAttribute` conceptually is an object with attribute names. + +Given the following schema example: + +```go +schema.Schema{ + Attributes: map[string]schema.Attribute{ + "root_grouped_attributes": schema.SingleNestedAttribute{ + Attributes: map[string]schema.Attribute{ + "nested_string_attribute": schema.StringAttribute{ + Required: true, + }, + }, + Required: true, + }, + }, +} +``` + +The path which matches the object associated with the `root_grouped_attributes` attribute is: + +```go +path.Root("root_grouped_attributes") +``` + +The path which matches the `nested_string_attribute` string value in the object associated with the `root_grouped_attributes` attribute is: + +```go +path.Root("root_grouped_attributes").AtName("nested_string_attribute") +``` + +### Building Block Paths + +The following table shows the different [`path.Path` type](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/path#Path) methods associated with building paths for blocks. + +| Block Type | Child Path Method | +| -------------------------- | ----------------- | +| `schema.ListNestedBlock` | `AtListIndex()` | +| `schema.SetNestedBlock` | `AtSetValue()` | +| `schema.SingleNestedBlock` | `AtName()` | + +Blocks can implement nested blocks. Paths can continue to be built using the associated method with each level of the block type. + +Blocks eventually implement attributes at child paths, which follow the methods shown in the [Building Attribute Paths section](#building-attribute-paths). Blocks cannot contain nested attributes. + +#### Building List Block Paths + +A block defined with `schema.ListNestedBlock` conceptually is a list containing objects with attribute or block names. + +Given following schema example: + +```go +schema.Schema{ + Blocks: map[string]schema.Block{ + "root_list_block": schema.ListNestedBlock{ + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "block_string_attribute": schema.StringAttribute{ + Required: true, + }, + }, + Blocks: map[string]schema.Block{ + "nested_list_block": schema.ListNestedBlock{ + Attributes: map[string]schema.Attribute{ + "nested_block_string_attribute": schema.StringAttribute{ + Required: true, + }, + }, + }, + }, + }, + }, + }, +} +``` + +The path which matches the list associated with the `root_list_block` block is: + +```go +path.Root("root_list_block") +``` + +The path which matches the first object in the list associated with the `root_list_block` block is: + +```go +path.Root("root_list_block").AtListIndex(0) +``` + +The path which matches the `block_string_attribute` string value in the first object in the list associated with `root_list_block` block is: + +```go +path.Root("root_list_block").AtListIndex(0).AtName("block_string_attribute") +``` + +The path which matches the `nested_list_block` list in the first object in the list associated with `root_list_block` block is: + +```go +path.Root("root_list_block").AtListIndex(0).AtName("nested_list_block") +``` + +The path which matches the `nested_block_string_attribute` string value in the first object in the list associated with the `nested_list_block` list in the first object in the list associated with `root_list_block` block is: + +```go +path.Root("root_list_block").AtListIndex(0).AtName("nested_list_block").AtListIndex(0).AtName("nested_block_string_attribute") +``` + +#### Building Set Block Paths + +A block defined with `schema.SetNestedBlock` conceptually is a set containing objects with attribute or block names. Attempting to build set block paths is extremely tedius as set element selection is based on the entire value of the element, including any null or unknown values. Avoid manual set-based path building. Instead, use functionality which supports [path expressions](/terraform/plugin/framework/path-expressions) as that supports wildcard path matching ([`path.Expression` type `AtAnySetValue()` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/path#Expression.AtAnySetValue)) or relative paths. + +Given following schema example: + +```go +schema.Schema{ + Blocks: map[string]schema.Block{ + "root_set_block": schema.SetNestedBlock{ + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "block_string_attribute": schema.StringAttribute{ + Optional: true, + }, + }, + }, + }, + }, +} +``` + +The path which matches the set associated with the `root_set_block` block is: + +```go +path.Root("root_set_block") +``` + +Examples below will presume a `block_string_attribute` string value of `types.StringValue("example")` for brevity. In real world usage, the string value may be `types.StringNull()`, `types.StringUnknown()` or `types.StringValue("something-else")`, which are all considered different set paths from each other. Each additional attribute or block introduces exponentially more possible paths given each attribute or block value may be null, unknown, or a unique known value. + +The path which matches the object associated with the `root_set_block` block is: + +```go +path.Root("root_set_block").AtSetValue(types.ObjectValueMust( + map[string]attr.Type{ + "block_string_attribute": types.StringType, + }, + map[string]attr.Value{ + "block_string_attribute": types.StringValue("example"), + } +)) +``` + +The path which matches the `block_string_attribute` string value associated with `root_set_block` block is: + +```go +path.Root("root_set_block").AtSetValue(types.ObjectValueMust( + map[string]attr.Type{ + "block_string_attribute": types.StringType, + }, + map[string]attr.Value{ + "block_string_attribute": types.StringValue("example"), + } +)).AtName("block_string_attribute") +``` + +#### Building Single Block Paths + +A block defined with `schema.SingleNestedBlock` conceptually is an object with attribute or block names. + +Given following schema example: + +```go +schema.Schema{ + Blocks: map[string]schema.Block{ + "root_single_block": schema.SingleNestedBlock{ + Attributes: map[string]schema.Attribute{ + "block_string_attribute": schema.StringAttribute{ + Required: true, + }, + }, + Blocks: map[string]schema.Block{ + "nested_single_block": schema.SingleNestedBlock{ + Attributes: map[string]schema.Attribute{ + "nested_block_string_attribute": schema.StringAttribute{ + Required: true, + }, + }, + }, + }, + }, + }, +} +``` + +The path which matches the object associated with the `root_single_block` block is: + +```go +path.Root("root_single_block") +``` + +The path which matches the `block_string_attribute` string value in the object associated with `root_single_block` block is: + +```go +path.Root("root_single_block").AtName("block_string_attribute") +``` + +The path which matches the `nested_single_block` object in the object associated with `root_single_block` block is: + +```go +path.Root("root_single_block").AtName("nested_single_block") +``` + +The path which matches the `nested_block_string_attribute` string value in the object associated with the `nested_single_block` in the object associated with `root_single_block` block is: + +```go +path.Root("root_single_block").AtName("nested_single_block").AtName("nested_block_string_attribute") +``` + +### Building Dynamic Attribute Paths + +An attribute that implements `schema.DynamicAttribute` does not have a statically defined type, as the underlying value type is determined at runtime. When building paths for dynamic values, always target the root dynamic attribute. + +Given the following schema example: + +```go +schema.Schema{ + Attributes: map[string]schema.Attribute{ + "root_dynamic_attribute": schema.DynamicAttribute{ + Required: true, + }, + }, +} +``` + +The path which matches the dynamic value associated with the `root_dynamic_attribute` attribute is: + +```go +path.Root("root_dynamic_attribute") +``` diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/schemas.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/schemas.mdx new file mode 100644 index 0000000000..e406565e56 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/schemas.mdx @@ -0,0 +1,99 @@ +--- +page_title: Schemas +description: >- + Learn how to define a schema using the Terraform plugin framework. Schemas + specify the constraints of Terraform configuration blocks. +--- + +# Schemas + +Schemas specify the constraints of Terraform configuration blocks. They define what fields a provider, +resource, or data source configuration block has, and give Terraform metadata +about those fields. You can think of the schema as the "type information" or +the "shape" of a resource, data source, or provider. + +Each concept has its own `schema` package and `Schema` type, which defines functionality available to that concept: + +- [Providers](/terraform/plugin/framework/providers): [`provider/schema.Schema`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider/schema#Schema) +- [Resources](/terraform/plugin/framework/resources): [`resource/schema.Schema`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema#Schema) +- [Data Sources](/terraform/plugin/framework/data-sources): [`datasource/schema.Schema`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/datasource/schema#Schema) +- [Ephemeral Resources](/terraform/plugin/framework/ephemeral-resources): [`ephemeral/schema.Schema`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/ephemeral/schema#Schema) + +During execution of the [`terraform validate`](/terraform/cli/commands/validate), [`terraform plan`](/terraform/cli/commands/plan) and [`terraform apply`](/terraform/cli/commands/apply) commands, Terraform calls the provider [`GetProviderSchema`](/terraform/plugin/framework/internals/rpcs#getproviderschema-rpc) RPC, in which the framework calls the [`provider.Provider` interface `Schema` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider#Provider.Schema), the [`resource.Resource` interface `Schema` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#Resource.Schema), [`datasource.DataSource` interface `Schema` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/datasource#DataSource.Schema), and the [`ephemeral.EphemeralResource` interface `Schema` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/ephemeral#EphemeralResource.Schema) on each of the resource types, respectively. + +## Version + +-> Version is only valid for resources. + +Every schema has a version, which is an integer that allows you to track changes to your schemas. It is generally only used when +[upgrading resource state](/terraform/plugin/framework/resources/state-upgrade), to help massage resources created with earlier +schemas into the shape defined by the current schema. It will never be used for +provider or data source schemas and can be omitted. + +## DeprecationMessage + +Not every resource, data source, ephemeral resource, or provider will be supported forever. +Sometimes designs change or APIs are deprecated. Schemas that have their +`DeprecationMessage` property set will display that message as a warning when +that provider, data source, or resource is used. A good message will tell +practitioners that the provider, resource, or data source is deprecated, and +will indicate a migration strategy. + +## Description + +Various tooling like +[terraform-plugin-docs](https://github.com/hashicorp/terraform-plugin-docs) and +the [language server](https://github.com/hashicorp/terraform-ls) can use +metadata in the schema to generate documentation or offer a better editor +experience for practitioners. Use the `Description` property to add a description of a resource, data source, or provider that these tools can leverage. + +## MarkdownDescription + +Similar to the `Description` property, the `MarkdownDescription` is used to +provide a markdown-formatted version of the description to tooling like +[terraform-plugin-docs](https://github.com/hashicorp/terraform-plugin-docs). It +is a best practice to only alter the formatting, not the content, between the +`Description` and `MarkdownDescription`. + +At the moment, if the `MarkdownDescription` property is set it will always be +used instead of the `Description` property. It is possible that a different strategy may be employed in the future to surface descriptions to other tooling in a different format, so we recommend specifying both fields. + +## Unit Testing + +Schemas can be unit tested via each of the `schema.Schema` type `ValidateImplementation()` methods. This unit testing raises schema implementation issues more quickly in comparison to [acceptance tests](/terraform/plugin/framework/acctests), but does not replace the purpose of acceptance testing. + +In this example, a `thing_resource_test.go` file is created alongside the `thing_resource.go` implementation file: + +```go +import ( + "context" + "testing" + + // The fwresource import alias is so there is no collistion + // with the more typical acceptance testing import: + // "github.com/hashicorp/terraform-plugin-testing/helper/resource" + fwresource "github.com/hashicorp/terraform-plugin-framework/resource" +) + +func TestThingResourceSchema(t *testing.T) { + t.Parallel() + + ctx := context.Background() + schemaRequest := fwresource.SchemaRequest{} + schemaResponse := &fwresource.SchemaResponse{} + + // Instantiate the resource.Resource and call its Schema method + NewThingResource().Schema(ctx, schemaRequest, schemaResponse) + + if schemaResponse.Diagnostics.HasError() { + t.Fatalf("Schema method diagnostics: %+v", schemaResponse.Diagnostics) + } + + // Validate the schema + diagnostics := schemaResponse.Schema.ValidateImplementation(ctx) + + if diagnostics.HasError() { + t.Fatalf("Schema validation diagnostics: %+v", diagnostics) + } +} +``` diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/terraform-concepts.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/terraform-concepts.mdx new file mode 100644 index 0000000000..d8602e8a33 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/terraform-concepts.mdx @@ -0,0 +1,129 @@ +--- +page_title: Terraform data concepts +description: >- + Learn how the Terraform plugin framework handles data by mapping Terraform + configuration to schemas, attributes, and blocks. +--- + +# Terraform data concepts + +This page describes Terraform concepts as they relate to handling data within framework-based provider code. The [What is Terraform](/terraform/intro), [Terraform language](/terraform/language), and [Plugin Development](/terraform/plugin) documentation covers more general concepts behind Terraform's workflow, its configuration, and how it interacts with providers. + +## Schemas + +Schemas specify the data structure and types of a provider, resource, data source, or ephemeral resource that is exposed to Terraform. This includes the configuration written by practitioners, any planning data, and the state stored by Terraform which can be referenced in other configuration. Providers, resources, data sources, and ephemeral resources have their own concept-specific types and available functionality. + +Each part of the data within a schema is defined as either an attribute or block. In general, attributes set values and blocks are containers for other attributes and blocks. Each have differing configuration syntax and behaviors. + +Discover more about framework implementations of this concept in the [schema](/terraform/plugin/framework/handling-data/schemas) documentation. + +### Attributes + +Attributes set values in a schema, such as a string or list. The [Terraform type system](/terraform/language/expressions/types) documentation provides a general overview of the available kinds of data within Terraform, which is similar with type system concepts available in many programming languages. However, there are subtle differences with values discussed in the [type system](#type-system) section. + +Discover more about the framework implementations of this concept in the [attribute](/terraform/plugin/framework/handling-data/attributes) documentation. + +### Blocks + + + +Use [nested attributes](/terraform/plugin/framework/handling-data/attributes#nested-attribute-types) for new schema implementations. Block support is mainly for migrating prior SDK-based providers. + + + +A block is a container for other attributes and blocks. Terraform implements many top level blocks, such as `provider` and `resource`, while a schema supports nested blocks. + +Discover more about the framework implementations of this concept in the [block](/terraform/plugin/framework/handling-data/blocks) documentation. + +### Schema Based Data + +Schemas provide the structures and types for representing data with Terraform. This section discusses the concepts behind the different types of data influenced by a schema. + +#### Configuration + +As part of the Terraform workflow, a practitioner writes [configuration](/terraform/language) files, which are parsed into a graph of operations by Terraform commands. The structures and types of that configuration is the most visible aspect of a schema, since a schema has a direct effect on the expected configuration syntax. + +In Terraform operations where the configuration data is available to providers, the framework typically represents this as a `Config` field in the request type. + +#### Plan + + + +Only managed resources implement this data concept. + + + +As part of the Terraform workflow, any configuration changes are planned before they are applied into [state](/terraform/language/state) so practitioners have an opportunity to review whether those anticipated changes have their intended effect. Conceptually, this data represents the merging of configuration data and any prior state data. Terraform refers to this data as the proposed new state, while the framework more generally refers to this data as the plan. + +Plan data requires careful handling to prevent unexpected Terraform errors for practitioners. The framework implements various schema concepts and logic discussed in the [resource plan modification](/terraform/plugin/framework/resources/plan-modification) documentation. In-depth documentation about Terraform requirements is available in the [Terraform Resource Instance Change Lifecycle](https://github.com/hashicorp/terraform/blob/main/docs/resource-instance-change-lifecycle.md) documentation. + +In Terraform operations where the plan data is available to providers, the framework typically represents this as a `Plan` field in the request or response type. + +#### State + + + +Only managed resources and data sources implement this data concept. + + + +As part of the Terraform workflow, any data that should be stored for configuration references or future Terraform executions must be written to the [state](/terraform/language/state). This data must exactly match any configuration data, and if applicable, any plan data with [unknown values](#unknown-values) converted to known values. + +In Terraform operations where the plan data is available to providers, the framework typically represents this as a `State` field in the request or response type. + +## Type System + +The [Terraform type system](/terraform/language/expressions/types) supports deeper functionality than the standard type systems built into programming languages. While the types in the Terraform type system are similar to what is found in most languages, values have extra metadata associated with them. The framework implements its own [types](/terraform/plugin/framework/handling-data/types) to prevent the loss of this additional metadata that cannot be represented by Go built-in types. + +Important value metadata concepts when implementing a provider include: + +- [Null values](#null-values): Absence of a value. +- [Unknown values](#unknown-values): Value that is not yet known. + +As Terraform exposes additional value metadata to providers, the framework type system will be updated. Therefore, it is always recommended to use the framework type system to ensure all Terraform value functionality is available in provider code. + +### Null Values + +Null represents the absence of a Terraform value. It is usually +encountered with optional attributes that the practitioner neglected to specify +a value for, but can show up on any non-required attribute. Required attributes +can never be null. + +### Unknown Values + +Unknown represents a Terraform value that is not yet known. Terraform +uses a graph of providers, resources, and data sources to do things in the +right order, and when a provider, resource, or data source relies on a value +from another provider, resource, or data source that has not been resolved yet, +it represents that state by using the unknown value. For example: + +```hcl +resource "example_foo" "bar" { + hello = "world" + demo = true +} + +resource "example_baz" "quux" { + foo_id = example_foo.bar.id +} +``` + +In the example above, `example_baz.quux` is relying on the `id` attribute of +`example_foo.bar`. The `id` attribute of `example_foo.bar` isn't known until +after the apply. The plan would list it as `(known after apply)`. During the +plan phase, `example_baz.quux` would get an unknown value as the value for +`foo_id`. + +Because they can result from interpolations in the practitioner's config, +you have no control over what attributes may contain an unknown +value. However, by the time a resource is expected to be created, read, updated, or +deleted, only its computed attributes can be unknown. The rest are +guaranteed to have known values (or be null). + +For concepts such as resource and data source [configuration validation](/terraform/plugin/framework/validation), +this means that Terraform guarantees to call validation for a non-computed attribute +eventually with a known value, so the provider should typically avoid returning error diagnostics +when encountering an unknown value during validation. + +Provider configuration values [can also be unknown](/terraform/plugin/framework/providers#unknown-values), and +providers should handle that situation, even if that means just returning an error. diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/types/bool.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/types/bool.mdx new file mode 100644 index 0000000000..22b69d83e0 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/types/bool.mdx @@ -0,0 +1,107 @@ +--- +page_title: Boolean types +description: >- + Learn how to implement boolean value types with the Terraform plugin framework. +--- + +# Bool types + +Bool types store a boolean true or false value. + +By default, booleans from [schema](/terraform/plugin/framework/handling-data/schemas) (configuration, plan, and state) data are represented in the framework by [`types.BoolType`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#BoolType) and its associated value storage type of [`types.Bool`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#Bool). These types fully support Terraform's [type system concepts](/terraform/plugin/framework/handling-data/terraform-concepts) that cannot be represented in Go built-in types, such as `*bool`. Framework types can be [extended](#extending) by provider code or shared libraries to provide specific use case functionality. + +## Schema Definitions + +Use one of the following attribute types to directly add a bool value to a [schema](/terraform/plugin/framework/handling-data/schemas) or [nested attribute type](/terraform/plugin/framework/handling-data/attributes#nested-attribute-types): + +| Schema Type | Attribute Type | +|-------------|----------------| +| [Data Source](/terraform/plugin/framework/data-sources) | [`schema.BoolAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/datasource/schema#BoolAttribute) | +| [Provider](/terraform/plugin/framework/providers) | [`schema.BoolAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider/schema#BoolAttribute) | +| [Resource](/terraform/plugin/framework/resources) | [`schema.BoolAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema#BoolAttribute) | +| [Ephemeral Resource](/terraform/plugin/framework/ephemeral-resources) | [`schema.BoolAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/ephemeral/schema#BoolAttribute) | + +If the bool value should be the element type of a [collection attribute type](/terraform/plugin/framework/handling-data/attributes#collection-attribute-types), set the `ElemType` field to `types.BoolType` or the appropriate [custom type](#extending). + +If the bool value should be a value type of an [object attribute type](/terraform/plugin/framework/handling-data/attributes#object-attribute-type), set the `AttrTypes` map value to `types.BoolType` or the appropriate [custom type](#extending). + +## Accessing Values + + + +Review the [attribute documentation](/terraform/plugin/framework/handling-data/attributes/bool#accessing-values) to understand how schema-based data gets mapped into accessible values, such as a `types.Bool` in this case. + + + +Access `types.Bool` information via the following methods: + +* [`(types.Bool).IsNull() bool`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#BoolValue.IsNull): Returns true if the bool is null. +* [`(types.Bool).IsUnknown() bool`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#BoolValue.IsUnknown): Returns true if the bool is unknown. +* [`(types.Bool).ValueBool() bool`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#BoolValue.ValueBool): Returns the known bool, or `false` if null or unknown. +* [`(types.Bool).ValueBoolPointer() *bool`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#BoolValue.ValueBoolPointer): Returns a bool pointer to a known value, `nil` if null, or a pointer to `false` if unknown. + +In this example, a bool value is checked for being null or unknown value first, before accessing its known value: + +```go +// Example data model definition +// type ExampleModel struct { +// ExampleAttribute types.Bool `tfsdk:"example_attribute"` +// } +// +// This would be filled in, such as calling: req.Plan.Get(ctx, &data) +var data ExampleModel + +// optional logic for handling null value +if data.ExampleAttribute.IsNull() { + // ... +} + +// optional logic for handling unknown value +if data.ExampleAttribute.IsUnknown() { + // ... +} + +// myBool now contains a Go bool with the known value +myBool := data.ExampleAttribute.ValueBool() +``` + +## Setting Values + +Call one of the following to create a `types.Bool` value: + +* [`types.BoolNull()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#BoolNull): A null bool value. +* [`types.BoolUnknown()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#BoolUnknown): An unknown bool value. +* [`types.BoolValue(bool)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#BoolValue): A known value. +* [`types.BoolPointerValue(*bool)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#BoolPointerValue): A known value. + +In this example, a known bool value is created: + +```go +types.BoolValue(true) +``` + +Otherwise, for certain framework functionality that does not require `types` implementations directly, such as: + +* [`(tfsdk.State).SetAttribute()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/tfsdk#State.SetAttribute) +* [`types.ListValueFrom()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#ListValueFrom) +* [`types.MapValueFrom()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#MapValueFrom) +* [`types.ObjectValueFrom()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#ObjectValueFrom) +* [`types.SetValueFrom()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#SetValueFrom) + +A Go built-in `bool`, `*bool` (only with typed `nil`, `(*bool)(nil)`), or type alias of `bool` such as `type MyBoolType bool` can be used instead. + +In this example, a `bool` is directly used to set a bool attribute value: + +```go +diags := resp.State.SetAttribute(ctx, path.Root("example_attribute"), true) +``` + +In this example, a `types.List` of `types.Bool` is created from a `[]bool`: + +```go +listValue, diags := types.ListValueFrom(ctx, types.BoolType, []bool{true, false}) +``` + +## Extending + +The framework supports extending its base type implementations with [custom types](/terraform/plugin/framework/handling-data/types/custom). These can adjust expected provider code usage depending on their implementation. diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/types/custom.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/types/custom.mdx new file mode 100644 index 0000000000..ad47f3372d --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/types/custom.mdx @@ -0,0 +1,435 @@ +--- +page_title: Custom types +description: >- + Learn how to implement custom types with the Terraform plugin framework. +--- + +# Custom types + +Use existing custom types or develop custom types to consistently define behaviors for a kind of value across schemas. Custom types are supported on top of any framework-defined type. + +Supported behaviors for custom types include: + +- Semantic Equality: Keep a prior value if a new value is inconsequentially different, such as preventing drift detection of JSON strings with differing property ordering. +- Validation: Raise warning and/or error diagnostics based on the value, such as not following a specified format or enumeration of values. + +## Example Use Cases + +- Encoded values, such as an XML or YAML string. +- Provider-specific values, such as an AWS ARN or AzureRM location string. +- Networking values, such as a MAC address. +- Time values, such as an ISO 8601 string. + +## Common Custom Types +The following Go modules contain custom type implementations covering common use cases with validation and semantic equality logic (where appropriate). +- [`terraform-plugin-framework-jsontypes`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-jsontypes) + - JSON strings (both normalized and exact matching) +- [`terraform-plugin-framework-nettypes`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-nettypes) + - IPv4/IPv6 addresses and CIDRs +- [`terraform-plugin-framework-timetypes`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-timetypes) + - Timestamps (such as RFC3339) + +## Concepts + +Individual data value handling in the framework is performed by a pair of associated Go types: + +- Schema Types: Define the associated value type and logic to create a value. +- Value Types: Define behaviors associated with the value, data storage of the value, and data storage of the value state (null, unknown, or known). + +The framework defines a standard set these associated Go types referred to by the "base type" terminology. Extending these base types is referred to by the "custom type" terminology. + +## Using Custom Types + +Use a custom type by switching the schema definition and data handling from a framework-defined type to the custom type. + +### Schema Definition + +The framework schema types accept a `CustomType` field where applicable, such as the [`resource/schema.StringAttribute` type](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema#StringAttribute.CustomType). When the `CustomType` is omitted, the framework defaults to the associated base type. + +Implement the `CustomType` field in a schema type to switch from the base type to a custom type. + +In this example, a string attribute implements a custom type. + +```go +schema.StringAttribute{ + CustomType: CustomStringType{}, + // ... other fields ... +} +``` + +### Data Handling + +Each custom type will also include a value type, which must be used anywhere the value is referenced in data source, provider, or resource logic. + +Switch any usage of a base value type to the custom value type. Any logic will need to be updated to match the custom value type implementation. + +In this example, a custom value type is used in a data model approach: + +```go +type ThingModel struct { + // Instead of types.String + Timestamp CustomStringValue `tfsdk:"timestamp"` + // ... other fields ... +} +``` + +## Developing Custom Types + +Create a custom type by extending an existing framework schema type and its associated value type. Once created, define [semantic equality](#semantic-equality) and/or [validation](#validation) logic for the custom type. + +### Schema Type + +Extend a framework schema type by creating a Go type that implements one of the `github.com/hashicorp/terraform-plugin-framework/types/basetypes` package `*Typable` interfaces. + + + +The commonly used `types` package types are aliases to the `basetypes` package types mentioned in this table. + + + +| Framework Schema Type | Custom Schema Type Interface | +| --- | --- | +| [`basetypes.BoolType`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#BoolType) | [`basetypes.BoolTypable`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#BoolTypable) | +| [`basetypes.DynamicType`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#DynamicType) | [`basetypes.DynamicTypable`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#DynamicTypable) | +| [`basetypes.Float32Type`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#Float32Type) | [`basetypes.Float32Typable`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#Float32Typable) | +| [`basetypes.Float64Type`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#Float64Type) | [`basetypes.Float64Typable`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#Float64Typable) | +| [`basetypes.Int32Type`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#Int32Type) | [`basetypes.Int32Typable`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#Int32Typable) | +| [`basetypes.Int64Type`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#Int64Type) | [`basetypes.Int64Typable`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#Int64Typable) | +| [`basetypes.ListType`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#ListType) | [`basetypes.ListTypable`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#ListTypable) | +| [`basetypes.MapType`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#MapType) | [`basetypes.MapTypable`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#MapTypable) | +| [`basetypes.NumberType`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#NumberType) | [`basetypes.NumberTypable`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#NumberTypable) | +| [`basetypes.ObjectType`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#ObjectType) | [`basetypes.ObjectTypable`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#ObjectTypable) | +| [`basetypes.SetType`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#SetType) | [`basetypes.SetTypable`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#SetTypable) | +| [`basetypes.StringType`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#StringType) | [`basetypes.StringTypable`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#StringTypable) | + +It is recommended to use Go type embedding of the base type to simplify the implementation and ensure it is up to date with the latest data handling features of the framework. With type embedding, the following [`attr.Type`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/attr#Type) methods must be overridden by the custom type to prevent confusing errors: + +- `Equal(attr.Type) bool` +- `ValueFromTerraform(context.Context, tftypes.Value) (attr.Value, error)` +- `ValueType(context.Context) attr.Value` +- `String() string` +- `ValueFrom{TYPE}(context.Context, basetypes.{TYPE}Value) (basetypes.{TYPE}Valuable, diag.Diagnostics)` + - This method signature is different for each `*Typable` custom schema type interface listed above, for example `basetypes.StringTypable` is defined as [`ValueFromString`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#StringTypable) + +In this example, the `basetypes.StringTypable` interface is implemented to create a custom string type with an associated [value type](#value-type): + +```go +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/types/basetypes" + "github.com/hashicorp/terraform-plugin-go/tftypes" +) + +// Ensure the implementation satisfies the expected interfaces +var _ basetypes.StringTypable = CustomStringType{} + +type CustomStringType struct { + basetypes.StringType + // ... potentially other fields ... +} + +func (t CustomStringType) Equal(o attr.Type) bool { + other, ok := o.(CustomStringType) + + if !ok { + return false + } + + return t.StringType.Equal(other.StringType) +} + +func (t CustomStringType) String() string { + return "CustomStringType" +} + +func (t CustomStringType) ValueFromString(ctx context.Context, in basetypes.StringValue) (basetypes.StringValuable, diag.Diagnostics) { + // CustomStringValue defined in the value type section + value := CustomStringValue{ + StringValue: in, + } + + return value, nil +} + +func (t CustomStringType) ValueFromTerraform(ctx context.Context, in tftypes.Value) (attr.Value, error) { + attrValue, err := t.StringType.ValueFromTerraform(ctx, in) + + if err != nil { + return nil, err + } + + stringValue, ok := attrValue.(basetypes.StringValue) + + if !ok { + return nil, fmt.Errorf("unexpected value type of %T", attrValue) + } + + stringValuable, diags := t.ValueFromString(ctx, stringValue) + + if diags.HasError() { + return nil, fmt.Errorf("unexpected error converting StringValue to StringValuable: %v", diags) + } + + return stringValuable, nil +} + +func (t CustomStringType) ValueType(ctx context.Context) attr.Value { + // CustomStringValue defined in the value type section + return CustomStringValue{} +} +``` + +### Value Type + +Extend a framework value type by creating a Go type that implements one of the `github.com/hashicorp/terraform-plugin-framework/types/basetypes` package `*Valuable` interfaces. + + + +The commonly used `types` package types are aliases to the `basetypes` package types mentioned in this table. + + + +| Framework Schema Type | Custom Schema Type Interface | +| --- | --- | +| [`basetypes.BoolValue`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#BoolValue) | [`basetypes.BoolValuable`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#BoolValuable) | +| [`basetypes.DynamicValue`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#DynamicValue) | [`basetypes.DynamicValuable`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#DynamicValuable) | +| [`basetypes.Float64Value`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#Float64Value) | [`basetypes.Float64Valuable`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#Float64Valuable) | +| [`basetypes.Int64Value`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#Int64Value) | [`basetypes.Int64Valuable`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#Int64Valuable) | +| [`basetypes.ListValue`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#ListValue) | [`basetypes.ListValuable`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#ListValuable) | +| [`basetypes.MapValue`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#MapValue) | [`basetypes.MapValuable`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#MapValuable) | +| [`basetypes.NumberValue`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#NumberValue) | [`basetypes.NumberValuable`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#NumberValuable) | +| [`basetypes.ObjectValue`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#ObjectValue) | [`basetypes.ObjectValuable`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#ObjectValuable) | +| [`basetypes.SetValue`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#SetValue) | [`basetypes.SetValuable`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#SetValuable) | +| [`basetypes.StringValue`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#StringValue) | [`basetypes.StringValuable`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#StringValuable) | + +It is recommended to use Go type embedding of the base type to simplify the implementation and ensure it is up to date with the latest data handling features of the framework. With type embedding, the following [`attr.Value`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/attr#Value) methods must be overridden by the custom type to prevent confusing errors: + +- `Equal(attr.Value) bool` +- `Type(context.Context) attr.Type` + + + +The overridden `Equal(attr.Value) bool` method should not contain [Semantic Equality](#semantic-equality) logic. `Equal` should only check the type of `attr.Value` and the underlying base value. + +An example of this can be found below with the `CustomStringValue` implementation. + + + +In this example, the `basetypes.StringValuable` interface is implemented to create a custom string value type with an associated [schema type](#schema-type): + +```go +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/types/basetypes" + "github.com/hashicorp/terraform-plugin-go/tftypes" +) + +// Ensure the implementation satisfies the expected interfaces +var _ basetypes.StringValuable = CustomStringValue{} + +type CustomStringValue struct { + basetypes.StringValue + // ... potentially other fields ... +} + +func (v CustomStringValue) Equal(o attr.Value) bool { + other, ok := o.(CustomStringValue) + + if !ok { + return false + } + + return v.StringValue.Equal(other.StringValue) +} + +func (v CustomStringValue) Type(ctx context.Context) attr.Type { + // CustomStringType defined in the schema type section + return CustomStringType{} +} +``` + +From this point, the custom type can be extended with other behaviors. + +### Semantic Equality + +Semantic equality handling enables the value type to automatically keep a prior value when a new value is determined to be inconsequentially different. This handling can prevent unexpected drift detection for values and in some cases prevent Terraform data handling errors. + +This value type functionality is checked in the following scenarios: + +- When refreshing a data source, the response state value from the `Read` method logic is compared to the configuration value. +- When refreshing a resource, the response new state value from the `Read` method logic is compared to the request prior state value. +- When creating or updating a resource, the response new state value from the `Create` or `Update` method logic is compared to the request plan value. + +The framework will only call semantic equality logic if both the prior and new values are known. Null or unknown values are unnecessary to check. When working with collection types, the framework automatically calls semantic equality logic of element types. When working with object types, the framework automatically calls semantic equality of underlying attribute types. + +Implement the associated `github.com/hashicorp/terraform-plugin-framework/types/basetypes` package `*ValuableWithSemanticEquals` interface on the value type to define and enable this behavior. + +In this example, the custom string value type will preserve the prior value if the expected RFC3339 timestamps are considered equivalent: + +```go +// CustomStringValue defined in the value type section +// Ensure the implementation satisfies the expected interfaces +var _ basetypes.StringValuableWithSemanticEquals = CustomStringValue{} + +func (v CustomStringValue) StringSemanticEquals(ctx context.Context, newValuable basetypes.StringValuable) (bool, diag.Diagnostics) { + var diags diag.Diagnostics + + // The framework should always pass the correct value type, but always check + newValue, ok := newValuable.(CustomStringValue) + + if !ok { + diags.AddError( + "Semantic Equality Check Error", + "An unexpected value type was received while performing semantic equality checks. "+ + "Please report this to the provider developers.\n\n"+ + "Expected Value Type: "+fmt.Sprintf("%T", v)+"\n"+ + "Got Value Type: "+fmt.Sprintf("%T", newValuable), + ) + + return false, diags + } + + // Skipping error checking if CustomStringValue already implemented RFC3339 validation + priorTime, _ := time.Parse(time.RFC3339, v.StringValue.ValueString()) + + // Skipping error checking if CustomStringValue already implemented RFC3339 validation + newTime, _ := time.Parse(time.RFC3339, newValue.ValueString()) + + // If the times are equivalent, keep the prior value + return priorTime.Equal(newTime), diags +} +``` + +### Validation + +#### Value Validation + +Validation handling in custom value types can be enabled for schema attribute values, or provider-defined function parameters. + +Implement the [`xattr.ValidateableAttribute` interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/attr/xattr#ValidateableAttribute) on the custom value type to define and enable validation handling for a schema attribute, which will automatically raise warning and/or error diagnostics when a value is determined to be invalid. + +Implement the [`function.ValidateableParameter` interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/function#ValidateableParameter) on the custom value type to define and enable validation handling for a provider-defined function parameter, which will automatically raise an error when a value is determined to be invalid. + +If the custom value type is to be used for both schema attribute values and provider-defined function parameters, implement both interfaces. + +```go +// Implementation of the xattr.ValidateableAttribute interface +func (v CustomStringValue) ValidateAttribute(ctx context.Context, req xattr.ValidateAttributeRequest, resp *xattr.ValidateAttributeResponse) { + if v.IsNull() || v.IsUnknown() { + return + } + + err := v.validate(v.ValueString()) + + if err != nil { + resp.Diagnostics.AddAttributeError( + req.Path, + "Invalid RFC 3339 String Value", + "An unexpected error occurred while converting a string value that was expected to be RFC 3339 format. "+ + "The RFC 3339 string format is YYYY-MM-DDTHH:MM:SSZ, such as 2006-01-02T15:04:05Z or 2006-01-02T15:04:05+07:00.\n\n"+ + "Path: "+req.Path.String()+"\n"+ + "Given Value: "+v.ValueString()+"\n"+ + "Error: "+err.Error(), + ) + + return + } +} + +// Implementation of the function.ValidateableParameter interface +func (v CustomStringValue) ValidateParameter(ctx context.Context, req function.ValidateParameterRequest, resp *function.ValidateParameterResponse) { + if v.IsNull() || v.IsUnknown() { + return + } + + err := v.validate(v.ValueString()) + + if err != nil { + resp.Error = function.NewArgumentFuncError( + req.Position, + "Invalid RFC 3339 String Value: "+ + "An unexpected error occurred while converting a string value that was expected to be RFC 3339 format. "+ + "The RFC 3339 string format is YYYY-MM-DDTHH:MM:SSZ, such as 2006-01-02T15:04:05Z or 2006-01-02T15:04:05+07:00.\n\n"+ + fmt.Sprintf("Position: %d", req.Position)+"\n"+ + "Given Value: "+v.ValueString()+"\n"+ + "Error: "+err.Error(), + ) + } +} + +func (v CustomStringValue) validate(in string) error { + _, err := time.Parse(time.RFC3339, in) + + return err +} +``` + +#### Type Validation + + + +`Value` validation should be used in preference to `Type` validation. Refer to [Value Validation](#value-validation) for more information. + +The [`xattr.TypeWithValidate` interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/attr/xattr#TypeWithValidate) has been deprecated. Use the [`xattr.ValidateableAttribute` interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/attr/xattr#ValidateableAttribute), and [`function.ValidateableParameter` interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/function#ValidateableParameter) instead. + + + +Implement the [`xattr.TypeWithValidate`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/attr/xattr#TypeWithValidate) interface on the value type to define and enable this behavior. + + + +This functionality uses the lower level [`tftypes`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-go/tftypes) type system compared to other framework logic. + + + +In this example, the custom string value type will ensure the string is a valid RFC3339 timestamp: + +```go +// CustomStringType defined in the schema type section +func (t CustomStringType) Validate(ctx context.Context, value tftypes.Value, valuePath path.Path) diag.Diagnostics { + if value.IsNull() || !value.IsKnown() { + return nil + } + + var diags diag.Diagnostics + var valueString string + + if err := value.As(&valueString); err != nil { + diags.AddAttributeError( + valuePath, + "Invalid Terraform Value", + "An unexpected error occurred while attempting to convert a Terraform value to a string. "+ + "This generally is an issue with the provider schema implementation. "+ + "Please contact the provider developers.\n\n"+ + "Path: "+valuePath.String()+"\n"+ + "Error: "+err.Error(), + ) + + return diags + } + + if _, err := time.Parse(time.RFC3339, valueString); err != nil { + diags.AddAttributeError( + valuePath, + "Invalid RFC 3339 String Value", + "An unexpected error occurred while converting a string value that was expected to be RFC 3339 format. "+ + "The RFC 3339 string format is YYYY-MM-DDTHH:MM:SSZ, such as 2006-01-02T15:04:05Z or 2006-01-02T15:04:05+07:00.\n\n"+ + "Path: "+valuePath.String()+"\n"+ + "Given Value: "+valueString+"\n"+ + "Error: "+err.Error(), + ) + + return diags + } + + return diags +} +``` diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/types/dynamic.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/types/dynamic.mdx new file mode 100644 index 0000000000..5da2afc860 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/types/dynamic.mdx @@ -0,0 +1,155 @@ +--- +page_title: Dynamic types +description: >- + Learn how to implement dynamic types with the Terraform plugin framework. +--- + +# Dynamic types + + + +Static types should always be preferred over dynamic types, when possible. + +Developers dealing with dynamic data will need to have extensive knowledge of the [Terraform type system](/terraform/language/expressions/types) to properly handle all potential practitioner configuration scenarios. + +Refer to [Dynamic Data - Considerations](/terraform/plugin/framework/handling-data/dynamic-data#considerations) for more information. + + + +Dynamic is a container type that can have an underlying value of **any** type. + +By default, dynamic values from [schema](/terraform/plugin/framework/handling-data/schemas) (configuration, plan, and state) data are represented in the framework by [`types.DynamicType`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#DynamicType) and its associated value storage type of [`types.Dynamic`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#Dynamic). These types fully support Terraform's [type system concepts](/terraform/plugin/framework/handling-data/terraform-concepts) that cannot be represented in Go built-in types. Framework types can be [extended](#extending) by provider code or shared libraries to provide specific use case functionality. + +## Schema Definitions + +Use one of the following attribute types to directly add a dynamic value to a [schema](/terraform/plugin/framework/handling-data/schemas) or [nested attribute type](/terraform/plugin/framework/handling-data/attributes#nested-attribute-types): + +| Schema Type | Attribute Type | +|-------------|----------------| +| [Data Source](/terraform/plugin/framework/data-sources) | [`schema.DynamicAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/datasource/schema#DynamicAttribute) | +| [Provider](/terraform/plugin/framework/providers) | [`schema.DynamicAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider/schema#DynamicAttribute) | +| [Resource](/terraform/plugin/framework/resources) | [`schema.DynamicAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema#DynamicAttribute) | +| [Ephemeral Resource](/terraform/plugin/framework/ephemeral-resources) | [`schema.DynamicAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/ephemeral/schema#DynamicAttribute) | + +Dynamic values are not supported as the element type of a [collection type](/terraform/plugin/framework/handling-data/types#collection-types) or within [collection attribute types](/terraform/plugin/framework/handling-data/attributes#collection-attribute-types). + +If the dynamic value should be a value type of an [object attribute type](/terraform/plugin/framework/handling-data/attributes#object-attribute-type), set the `AttrTypes` map value to `types.DynamicType` or the appropriate [custom type](#extending). + +## Accessing Values + + + +Review the [attribute documentation](/terraform/plugin/framework/handling-data/attributes/dynamic#accessing-values) to understand how schema-based data gets mapped into accessible values, such as a `types.Dynamic` in this case. + + + +Access `types.Dynamic` information via the following methods: + +* [`(types.Dynamic).IsNull() bool`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#DynamicValue.IsNull): Returns true if the dynamic value is null. +* [`(types.Dynamic).IsUnderlyingValueNull() bool`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#DynamicValue.IsUnderlyingValueNull): Returns true if the dynamic value is known but the underlying value is null. See the [Dynamic Data section](/terraform/plugin/framework/handling-data/dynamic-data#handling-underlying-null-and-unknown-values) for more information about null underlying values. +* [`(types.Dynamic).IsUnknown() bool`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#DynamicValue.IsUnknown): Returns true if the dynamic value is unknown. +* [`(types.Dynamic).IsUnderlyingValueUnknown() bool`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#DynamicValue.IsUnderlyingValueUnknown): Returns true if the dynamic value is known but the underlying value is unknown. See the [Dynamic Data section](/terraform/plugin/framework/handling-data/dynamic-data#handling-underlying-null-and-unknown-values) for more information about unknown underlying values. +* [`(types.Dynamic).UnderlyingValue() attr.Value`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#DynamicValue.UnderlyingValue): Returns the underlying value of the dynamic container, will be `nil` if null or unknown. + +In this example, a dynamic value is checked for being null or unknown value first, before accessing its known value: + +```go +// Example data model definition +// type ExampleModel struct { +// ExampleAttribute types.Dynamic `tfsdk:"example_attribute"` +// } +// +// This would be filled in, such as calling: req.Plan.Get(ctx, &data) +var data ExampleModel + +// optional logic for handling null value +if data.ExampleAttribute.IsNull() { + // ... +} + +// optional logic for handling unknown value +if data.ExampleAttribute.IsUnknown() { + // ... +} + +// myDynamicVal now contains the underlying value, determined by Terraform at runtime +myDynamicVal := data.ExampleAttribute.UnderlyingValue() +``` + +### Handling the Underlying Value + +If a dynamic value is known, a [Go type switch](https://go.dev/tour/methods/16) can be used to access the type-specific methods for data handling: + +```go + switch value := data.ExampleAttribute.UnderlyingValue().(type) { + case types.Bool: + // Handle boolean value + case types.Number: + // Handle float64, int64, and number values + case types.List: + // Handle list value + case types.Map: + // Handle map value + case types.Object: + // Handle object value + case types.Set: + // Handle set value + case types.String: + // Handle string value + case types.Tuple: + // Handle tuple value + } +``` + + + +[Float64](/terraform/plugin/framework/handling-data/types/float64) and [Int64](/terraform/plugin/framework/handling-data/types/int64) framework types will never appear in the underlying value as both are represented as the Terraform type [`number`](/terraform/language/expressions/types#number). + + + +The type of the underlying value is determined at runtime by Terraform if the value is from configuration. Developers dealing with dynamic data will need to have extensive knowledge of the [Terraform type system](/terraform/language/expressions/types) to properly handle all potential practitioner configuration scenarios. + +Refer to the [Dynamic Data](/terraform/plugin/framework/handling-data/dynamic-data) documentation for more information. + +## Setting Values + +Call one of the following to create a `types.Dynamic` value: + +* [`types.DynamicNull()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#DynamicNull): A null dynamic value. +* [`types.DynamicUnknown()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#DynamicUnknown): An unknown dynamic value where the final static type is not known. Use `types.DynamicValue()` with an unknown value if the final static type is known. +* [`types.DynamicValue(attr.Value)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#DynamicValue): A known dynamic value, with an underlying value determined by the `attr.Value` input. + +In this example, a known dynamic value is created, where the underlying value is a known string value: + +```go +types.DynamicValue(types.StringValue("hello world!")) +``` + +In this example, a known dynamic value is created, where the underlying value is a known object value: + +```go +elementTypes := map[string]attr.Type{ + "attr1": types.StringType, + "attr2": types.Int64Type, +} +elements := map[string]attr.Value{ + "attr1": types.StringValue("value"), + "attr2": types.Int64Value(123), +} +objectValue, diags := types.ObjectValue(elementTypes, elements) +// ... handle any diagnostics ... + +types.DynamicValue(objectValue) +``` + +There are no reflection rules defined for creating dynamic values, meaning they must be created using the `types` implementation. + +In this example, a `types.Dynamic` with a known boolean value is used to set a dynamic attribute value: + +```go +diags := resp.State.SetAttribute(ctx, path.Root("example_attribute"), types.DynamicValue(types.BoolValue(true))) +``` + +## Extending + +The framework supports extending its base type implementations with [custom types](/terraform/plugin/framework/handling-data/types/custom). These can adjust expected provider code usage depending on their implementation. diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/types/float32.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/types/float32.mdx new file mode 100644 index 0000000000..d6f85ec9df --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/types/float32.mdx @@ -0,0 +1,122 @@ +--- +page_title: Float32 types +description: >- + Learn how to implement 32-bit floating point value types with the Terraform + plugin framework. +--- + +# Float32 types + + + +Use [Int32 Type](/terraform/plugin/framework/handling-data/types/int32) for 32-bit integer numbers. Use [Number Attribute](/terraform/plugin/framework/handling-data/types/number) for arbitrary precision numbers. + + + +Float32 types store a 32-bit floating point number. + +By default, float32 from [schema](/terraform/plugin/framework/handling-data/schemas) (configuration, plan, and state) data are represented in the framework by [`types.Float32Type`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#Float32Type) and its associated value storage type of [`types.Float32`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#Float32). These types fully support Terraform's [type system concepts](/terraform/plugin/framework/handling-data/terraform-concepts) that cannot be represented in Go built-in types, such as `*float32`. Framework types can be [extended](#extending) by provider code or shared libraries to provide specific use case functionality. + +## Schema Definitions + +Use one of the following attribute types to directly add a float32 value to a [schema](/terraform/plugin/framework/handling-data/schemas) or [nested attribute type](/terraform/plugin/framework/handling-data/attributes#nested-attribute-types): + +| Schema Type | Attribute Type | +|-------------|----------------| +| [Data Source](/terraform/plugin/framework/data-sources) | [`schema.Float32Attribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/datasource/schema#Float32Attribute) | +| [Provider](/terraform/plugin/framework/providers) | [`schema.Float32Attribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider/schema#Float32Attribute) | +| [Resource](/terraform/plugin/framework/resources) | [`schema.Float32Attribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema#Float32Attribute) | +| [Ephemeral Resource](/terraform/plugin/framework/ephemeral-resources) | [`schema.Float32Attribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/ephemeral/schema#Float32Attribute) | + +If the float32 value should be the element type of a [collection attribute type](/terraform/plugin/framework/handling-data/attributes#collection-attribute-types), set the `ElemType` field to `types.Float32Type` or the appropriate [custom type](#extending). + +If the float32 value should be a value type of an [object attribute type](/terraform/plugin/framework/handling-data/attributes#object-attribute-type), set the `AttrTypes` map value to `types.Float32Type` or the appropriate [custom type](#extending). + +## Accessing Values + + + +Review the [attribute documentation](/terraform/plugin/framework/handling-data/attributes/float32#accessing-values) to understand how schema-based data gets mapped into accessible values, such as a `types.Float32` in this case. + + + +Access `types.Float32` information via the following methods: + +* [`(types.Float32).IsNull() bool`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#Float32Value.IsNull): Returns true if the float32 is null. +* [`(types.Float32).IsUnknown() bool`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#Float32Value.IsUnknown): Returns true if the float32 is unknown. +* [`(types.Float32).ValueFloat32() float32`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#Float32Value.ValueFloat32): Returns the known float32, or `0.0` if null or unknown. +* [`(types.Float32).ValueFloat32Pointer() *float32`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#Float32Value.ValueFloat32Pointer): Returns a float32 pointer to a known value, `nil` if null, or a pointer to `0.0` if unknown. + +In this example, a float32 value is checked for being null or unknown value first, before accessing its known value: + +```go +// Example data model definition +// type ExampleModel struct { +// ExampleAttribute types.Float32 `tfsdk:"example_attribute"` +// } +// +// This would be filled in, such as calling: req.Plan.Get(ctx, &data) +var data ExampleModel + +// optional logic for handling null value +if data.ExampleAttribute.IsNull() { + // ... +} + +// optional logic for handling unknown value +if data.ExampleAttribute.IsUnknown() { + // ... +} + +// myFloat32 now contains a Go float32 with the known value +myFloat32 := data.ExampleAttribute.ValueFloat32() +``` + +## Setting Values + +Call one of the following to create a `types.Float32` value: + +* [`types.Float32Null()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#Float32Null): A null float32 value. +* [`types.Float32Unknown()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#Float32Unknown): An unknown float32 value. +* [`types.Float32Value(float32)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#Float32Value): A known value. +* [`types.Float32PointerValue(*float32)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#Float32PointerValue): A known value. + +In this example, a known float32 value is created: + +```go +types.Float32Value(1.23) +``` + +Otherwise, for certain framework functionality that does not require `types` implementations directly, such as: + +* [`(tfsdk.State).SetAttribute()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/tfsdk#State.SetAttribute) +* [`types.ListValueFrom()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#ListValueFrom) +* [`types.MapValueFrom()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#MapValueFrom) +* [`types.ObjectValueFrom()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#ObjectValueFrom) +* [`types.SetValueFrom()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#SetValueFrom) + +Numbers can be automatically converted from the following Go types, pointers to these types, or any aliases of these types, such `type MyNumber int`: + +* `int`, `int8`, `int16`, `int32`, `int64` +* `uint`, `uint8`, `uint16`, `uint32`, `uint64` +* `float32`, `float64` +* [`*big.Int`](https://pkg.go.dev/math/big#Int), [`*big.Float`](https://pkg.go.dev/math/big#Float) + +An error will be returned if the value of the number cannot be stored in the numeric type supplied because of an overflow or other loss of precision. + +In this example, a `float32` is directly used to set a float32 attribute value: + +```go +var value float32 = 1.23 +diags := resp.State.SetAttribute(ctx, path.Root("example_attribute"), value) +``` + +In this example, a `types.List` of `types.Float32` is created from a `[]float32`: + +```go +listValue, diags := types.ListValueFrom(ctx, types.Float32Type, []float32{1.2, 2.4}) +``` + +## Extending + +The framework supports extending its base type implementations with [custom types](/terraform/plugin/framework/handling-data/types/custom). These can adjust expected provider code usage depending on their implementation. diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/types/float64.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/types/float64.mdx new file mode 100644 index 0000000000..b798c90400 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/types/float64.mdx @@ -0,0 +1,121 @@ +--- +page_title: Float64 types +description: >- + Learn how to implement 64-bit floating point value types with the Terraform plugin + framework. +--- + +# Float64 types + + + +Use [Int64 Type](/terraform/plugin/framework/handling-data/types/int64) for 64-bit integer numbers. Use [Number Attribute](/terraform/plugin/framework/handling-data/types/number) for arbitrary precision numbers. + + + +Float64 types store a 64-bit floating point number. + +By default, float64 from [schema](/terraform/plugin/framework/handling-data/schemas) (configuration, plan, and state) data are represented in the framework by [`types.Float64Type`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#Float64Type) and its associated value storage type of [`types.Float64`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#Float64). These types fully support Terraform's [type system concepts](/terraform/plugin/framework/handling-data/terraform-concepts) that cannot be represented in Go built-in types, such as `*float64`. Framework types can be [extended](#extending) by provider code or shared libraries to provide specific use case functionality. + +## Schema Definitions + +Use one of the following attribute types to directly add a float64 value to a [schema](/terraform/plugin/framework/handling-data/schemas) or [nested attribute type](/terraform/plugin/framework/handling-data/attributes#nested-attribute-types): + +| Schema Type | Attribute Type | +|-------------|----------------| +| [Data Source](/terraform/plugin/framework/data-sources) | [`schema.Float64Attribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/datasource/schema#Float64Attribute) | +| [Provider](/terraform/plugin/framework/providers) | [`schema.Float64Attribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider/schema#Float64Attribute) | +| [Resource](/terraform/plugin/framework/resources) | [`schema.Float64Attribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema#Float64Attribute) | +| [Ephemeral Resource](/terraform/plugin/framework/ephemeral-resources) | [`schema.Float64Attribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/ephemeral/schema#Float64Attribute) | + +If the float64 value should be the element type of a [collection attribute type](/terraform/plugin/framework/handling-data/attributes#collection-attribute-types), set the `ElemType` field to `types.Float64Type` or the appropriate [custom type](#extending). + +If the float64 value should be a value type of an [object attribute type](/terraform/plugin/framework/handling-data/attributes#object-attribute-type), set the `AttrTypes` map value to `types.Float64Type` or the appropriate [custom type](#extending). + +## Accessing Values + + + +Review the [attribute documentation](/terraform/plugin/framework/handling-data/attributes/float64#accessing-values) to understand how schema-based data gets mapped into accessible values, such as a `types.Float64` in this case. + + + +Access `types.Float64` information via the following methods: + +* [`(types.Float64).IsNull() bool`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#Float64Value.IsNull): Returns true if the float64 is null. +* [`(types.Float64).IsUnknown() bool`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#Float64Value.IsUnknown): Returns true if the float64 is unknown. +* [`(types.Float64).ValueFloat64() float64`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#Float64Value.ValueFloat64): Returns the known float64, or `0.0` if null or unknown. +* [`(types.Float64).ValueFloat64Pointer() *float64`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#Float64Value.ValueFloat64Pointer): Returns a float64 pointer to a known value, `nil` if null, or a pointer to `0.0` if unknown. + +In this example, a float64 value is checked for being null or unknown value first, before accessing its known value: + +```go +// Example data model definition +// type ExampleModel struct { +// ExampleAttribute types.Float64 `tfsdk:"example_attribute"` +// } +// +// This would be filled in, such as calling: req.Plan.Get(ctx, &data) +var data ExampleModel + +// optional logic for handling null value +if data.ExampleAttribute.IsNull() { + // ... +} + +// optional logic for handling unknown value +if data.ExampleAttribute.IsUnknown() { + // ... +} + +// myFloat64 now contains a Go float64 with the known value +myFloat64 := data.ExampleAttribute.ValueFloat64() +``` + +## Setting Values + +Call one of the following to create a `types.Float64` value: + +* [`types.Float64Null()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#Float64Null): A null float64 value. +* [`types.Float64Unknown()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#Float64Unknown): An unknown float64 value. +* [`types.Float64Value(float64)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#Float64Value): A known value. +* [`types.Float64PointerValue(*float64)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#Float64PointerValue): A known value. + +In this example, a known float64 value is created: + +```go +types.Float64Value(1.23) +``` + +Otherwise, for certain framework functionality that does not require `types` implementations directly, such as: + +* [`(tfsdk.State).SetAttribute()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/tfsdk#State.SetAttribute) +* [`types.ListValueFrom()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#ListValueFrom) +* [`types.MapValueFrom()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#MapValueFrom) +* [`types.ObjectValueFrom()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#ObjectValueFrom) +* [`types.SetValueFrom()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#SetValueFrom) + +Numbers can be automatically converted from the following Go types, pointers to these types, or any aliases of these types, such `type MyNumber int`: + +* `int`, `int8`, `int16`, `int32`, `int64` +* `uint`, `uint8`, `uint16`, `uint32`, `uint64` +* `float32`, `float64` +* [`*big.Int`](https://pkg.go.dev/math/big#Int), [`*big.Float`](https://pkg.go.dev/math/big#Float) + +An error will be returned if the value of the number cannot be stored in the numeric type supplied because of an overflow or other loss of precision. + +In this example, a `float64` is directly used to set a float64 attribute value: + +```go +diags := resp.State.SetAttribute(ctx, path.Root("example_attribute"), 1.23) +``` + +In this example, a `types.List` of `types.Float64` is created from a `[]float64`: + +```go +listValue, diags := types.ListValueFrom(ctx, types.Float64Type, []float64{1.2, 2.4}) +``` + +## Extending + +The framework supports extending its base type implementations with [custom types](/terraform/plugin/framework/handling-data/types/custom). These can adjust expected provider code usage depending on their implementation. diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/types/index.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/types/index.mdx new file mode 100644 index 0000000000..4e5f9103a2 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/types/index.mdx @@ -0,0 +1,89 @@ +--- +page_title: Data types +description: >- + The Terraform plugin framework includes multiple built-in attribute types + and supports custom and dynamic attribute types. You can implement custom + types based off of the built-in attribute types. +--- + +# Data types + +Types are value storage and access mechanism for resource, data source, or provider [schema](/terraform/plugin/framework/handling-data/schemas) data. Every attribute and block has an associated type, which describes the kind of data. These types fully support Terraform's [type system concepts](/terraform/plugin/framework/handling-data/terraform-concepts) that cannot be represented in Go built-in types, such as `*string`. Framework types can be extended by implementing [custom types](/terraform/plugin/framework/handling-data/types/custom) in provider code or shared libraries to provide specific use case functionality. + +## Available Types + +The framework type system supports the following types: + +- [Primitive](#primitive-types): Type that contains a single value, such as a boolean, number, or string. +- [Collection](#collection-types): Type that contains multiple values of a single element type, such as a list, map, or set. +- [Object](#object-type): Type that defines a mapping of explicit attribute names to value types. +- [Tuple](#tuple-type): Type that defines an ordered collection of elements where each element has it's own type. +- [Dynamic](#dynamic-type): Container type that can contain any underlying value type. + +### Primitive Types + +Types that contain a single data value, such as a boolean, number, or string. These are directly associated with their [primitive attribute type](/terraform/plugin/framework/handling-data/attributes#primitive-attribute-types). + +| Type | Use Case | +|----------------|----------| +| [Bool](/terraform/plugin/framework/handling-data/types/bool) | Boolean true or false | +| [Float32](/terraform/plugin/framework/handling-data/types/float32) | 32-bit floating point number | +| [Float64](/terraform/plugin/framework/handling-data/types/float64) | 64-bit floating point number | +| [Int32](/terraform/plugin/framework/handling-data/types/int32) | 32-bit integer number | +| [Int64](/terraform/plugin/framework/handling-data/types/int64) | 64-bit integer number | +| [Number](/terraform/plugin/framework/handling-data/types/number) | Arbitrary precision (generally over 64-bit, up to 512-bit) number | +| [String](/terraform/plugin/framework/handling-data/types/string) | Collection of UTF-8 encoded characters | + +### Collection Types + +Types that contain multiple values of a single element type, such as a list, map, or set. + +These types are associated with: + +- [Collection attribute types](/terraform/plugin/framework/handling-data/attributes#collection-attribute-types) +- Collection-based [nested attribute type](/terraform/plugin/framework/handling-data/attributes#nested-attribute-types) (list, map, and set of [object type](#object-type)) +- Collection-based [nested block type](/terraform/plugin/framework/handling-data/blocks) (list and set of [object type](#object-type)) + +| Type | Use Case | +|----------------|----------| +| [List](/terraform/plugin/framework/handling-data/types/list) | Ordered collection of single element type | +| [Map](/terraform/plugin/framework/handling-data/types/map) | Mapping of arbitrary string keys to values of single element type | +| [Set](/terraform/plugin/framework/handling-data/types/set) | Unordered, unique collection of single element type | + +### Object Type + +Type that defines a mapping of explicit attribute names to value types. + +This type is associated with: + +- [Single nested attibute type](/terraform/plugin/framework/handling-data/attributes/single-nested) +- [Single nested block type](/terraform/plugin/framework/handling-data/blocks/single-nested) +- Nested object within collection-based [nested attribute type](/terraform/plugin/framework/handling-data/attributes#nested-attribute-types) (list, map, and set of [object type](#object-type)) +- Nested object within collection-based [nested block type](/terraform/plugin/framework/handling-data/blocks) (list and set of [object type](#object-type)) +- [Object attribute type](/terraform/plugin/framework/handling-data/attributes#collection-attribute-types) + +| Type | Use Case | +|----------------|----------| +| [Object](/terraform/plugin/framework/handling-data/types/object) | Mapping of explicit attribute names to values | + +### Tuple Type + +Type that defines an ordered collection of elements where each element has it's own type. + + + +This type intentionally includes less functionality than other types in the type system as it has limited real world application and therefore is not exposed to provider developers except when working with dynamic values. + + + +| Type | Use Case | +|----------------|----------| +| [Tuple](/terraform/plugin/framework/handling-data/types/tuple) | Ordered collection of multiple element types | + +### Dynamic Type + +Container type that can contain any underlying value type, determined by Terraform or the provider at runtime. + +| Type | Use Case | +|----------------|----------| +| [Dynamic](/terraform/plugin/framework/handling-data/types/dynamic) | Any value type of data, determined at runtime. | \ No newline at end of file diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/types/int32.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/types/int32.mdx new file mode 100644 index 0000000000..b8525bc089 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/types/int32.mdx @@ -0,0 +1,121 @@ +--- +page_title: Int32 types +description: >- + Learn how to implement 32-bit integer value types with the Terraform plugin + framework. +--- + +# Int32 types + + + +Use [Float32 Type](/terraform/plugin/framework/handling-data/types/float32) for 32-bit floating point numbers. Use [Number Attribute](/terraform/plugin/framework/handling-data/types/number) for arbitrary precision numbers. + + + +Int32 types store a 32-bit integer number. + +By default, int32 from [schema](/terraform/plugin/framework/handling-data/schemas) (configuration, plan, and state) data are represented in the framework by [`types.Int32Type`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#Int32Type) and its associated value storage type of [`types.Int32`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#Int32). These types fully support Terraform's [type system concepts](/terraform/plugin/framework/handling-data/terraform-concepts) that cannot be represented in Go built-in types, such as `*int32`. Framework types can be [extended](#extending) by provider code or shared libraries to provide specific use case functionality. + +## Schema Definitions + +Use one of the following attribute types to directly add a int32 value to a [schema](/terraform/plugin/framework/handling-data/schemas) or [nested attribute type](/terraform/plugin/framework/handling-data/attributes#nested-attribute-types): + +| Schema Type | Attribute Type | +|-------------|----------------| +| [Data Source](/terraform/plugin/framework/data-sources) | [`schema.Int32Attribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/datasource/schema#Int32Attribute) | +| [Provider](/terraform/plugin/framework/providers) | [`schema.Int32Attribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider/schema#Int32Attribute) | +| [Resource](/terraform/plugin/framework/resources) | [`schema.Int32Attribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema#Int32Attribute) | +| [Ephemeral Resource](/terraform/plugin/framework/ephemeral-resources) | [`schema.Int32Attribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/ephemeral/schema#Int32Attribute) | + +If the int32 value should be the element type of a [collection attribute type](/terraform/plugin/framework/handling-data/attributes#collection-attribute-types), set the `ElemType` field to `types.Int32Type` or the appropriate [custom type](#extending). + +If the int32 value should be a value type of an [object attribute type](/terraform/plugin/framework/handling-data/attributes#object-attribute-type), set the `AttrTypes` map value to `types.Int32Type` or the appropriate [custom type](#extending). + +## Accessing Values + + + +Review the [attribute documentation](/terraform/plugin/framework/handling-data/attributes/int32#accessing-values) to understand how schema-based data gets mapped into accessible values, such as a `types.Int32` in this case. + + + +Access `types.Int32` information via the following methods: + +* [`(types.Int32).IsNull() bool`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#Int32Value.IsNull): Returns true if the int32 is null. +* [`(types.Int32).IsUnknown() bool`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#Int32Value.IsUnknown): Returns true if the int32 is unknown. +* [`(types.Int32).ValueInt32() int32`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#Int32Value.ValueInt32): Returns the known int32, or `0` if null or unknown. +* [`(types.Int32).ValueInt32Pointer() *int32`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#Int32Value.ValueInt32Pointer): Returns a int32 pointer to a known value, `nil` if null, or a pointer to `0` if unknown. + +In this example, a int32 value is checked for being null or unknown value first, before accessing its known value: + +```go +// Example data model definition +// type ExampleModel struct { +// ExampleAttribute types.Int32 `tfsdk:"example_attribute"` +// } +// +// This would be filled in, such as calling: req.Plan.Get(ctx, &data) +var data ExampleModel + +// optional logic for handling null value +if data.ExampleAttribute.IsNull() { + // ... +} + +// optional logic for handling unknown value +if data.ExampleAttribute.IsUnknown() { + // ... +} + +// myInt32 now contains a Go int32 with the known value +myInt32 := data.ExampleAttribute.ValueInt32() +``` + +## Setting Values + +Call one of the following to create a `types.Int32` value: + +* [`types.Int32Null()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#Int32Null): A null int32 value. +* [`types.Int32Unknown()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#Int32Unknown): An unknown int32 value. +* [`types.Int32Value(int32)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#Int32Value): A known value. +* [`types.Int32PointerValue(*int32)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#Int32PointerValue): A known value. + +In this example, a known int32 value is created: + +```go +types.Int32Value(123) +``` + +Otherwise, for certain framework functionality that does not require `types` implementations directly, such as: + +* [`(tfsdk.State).SetAttribute()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/tfsdk#State.SetAttribute) +* [`types.ListValueFrom()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#ListValueFrom) +* [`types.MapValueFrom()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#MapValueFrom) +* [`types.ObjectValueFrom()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#ObjectValueFrom) +* [`types.SetValueFrom()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#SetValueFrom) + +Numbers can be automatically converted from the following Go types, pointers to these types, or any aliases of these types, such `type MyNumber int`: + +* `int`, `int8`, `int16`, `int32`, `int64` +* `uint`, `uint8`, `uint16`, `uint32`, `uint64` +* `float32`, `float64` +* [`*big.Int`](https://pkg.go.dev/math/big#Int), [`*big.Float`](https://pkg.go.dev/math/big#Float) + +An error will be returned if the value of the number cannot be stored in the numeric type supplied because of an overflow or other loss of precision. + +In this example, a `int32` is directly used to set a int32 attribute value: + +```go +diags := resp.State.SetAttribute(ctx, path.Root("example_attribute"), 123) +``` + +In this example, a `types.List` of `types.Int32` is created from a `[]int32`: + +```go +listValue, diags := types.ListValueFrom(ctx, types.Int32Type, []int32{123, 456}) +``` + +## Extending + +The framework supports extending its base type implementations with [custom types](/terraform/plugin/framework/handling-data/types/custom). These can adjust expected provider code usage depending on their implementation. diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/types/int64.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/types/int64.mdx new file mode 100644 index 0000000000..7f741f5ebb --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/types/int64.mdx @@ -0,0 +1,121 @@ +--- +page_title: Int64 types +description: >- + Learn how to implement 64-bit integer value types with the Terraform plugin + framework. +--- + +# Int64 types + + + +Use [Float64 Type](/terraform/plugin/framework/handling-data/types/float64) for 64-bit floating point numbers. Use [Number Attribute](/terraform/plugin/framework/handling-data/types/number) for arbitrary precision numbers. + + + +Int64 types store a 64-bit integer number. + +By default, int64 from [schema](/terraform/plugin/framework/handling-data/schemas) (configuration, plan, and state) data are represented in the framework by [`types.Int64Type`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#Int64Type) and its associated value storage type of [`types.Int64`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#Int64). These types fully support Terraform's [type system concepts](/terraform/plugin/framework/handling-data/terraform-concepts) that cannot be represented in Go built-in types, such as `*int64`. Framework types can be [extended](#extending) by provider code or shared libraries to provide specific use case functionality. + +## Schema Definitions + +Use one of the following attribute types to directly add a int64 value to a [schema](/terraform/plugin/framework/handling-data/schemas) or [nested attribute type](/terraform/plugin/framework/handling-data/attributes#nested-attribute-types): + +| Schema Type | Attribute Type | +|-------------|----------------| +| [Data Source](/terraform/plugin/framework/data-sources) | [`schema.Int64Attribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/datasource/schema#Int64Attribute) | +| [Provider](/terraform/plugin/framework/providers) | [`schema.Int64Attribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider/schema#Int64Attribute) | +| [Resource](/terraform/plugin/framework/resources) | [`schema.Int64Attribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema#Int64Attribute) | +| [Ephemeral Resource](/terraform/plugin/framework/ephemeral-resources) | [`schema.Int64Attribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/ephemeral/schema#Int64Attribute) | + +If the int64 value should be the element type of a [collection attribute type](/terraform/plugin/framework/handling-data/attributes#collection-attribute-types), set the `ElemType` field to `types.Int64Type` or the appropriate [custom type](#extending). + +If the int64 value should be a value type of an [object attribute type](/terraform/plugin/framework/handling-data/attributes#object-attribute-type), set the `AttrTypes` map value to `types.Int64Type` or the appropriate [custom type](#extending). + +## Accessing Values + + + +Review the [attribute documentation](/terraform/plugin/framework/handling-data/attributes/int64#accessing-values) to understand how schema-based data gets mapped into accessible values, such as a `types.Int64` in this case. + + + +Access `types.Int64` information via the following methods: + +* [`(types.Int64).IsNull() bool`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#Int64Value.IsNull): Returns true if the int64 is null. +* [`(types.Int64).IsUnknown() bool`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#Int64Value.IsUnknown): Returns true if the int64 is unknown. +* [`(types.Int64).ValueInt64() int64`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#Int64Value.ValueInt64): Returns the known int64, or `0` if null or unknown. +* [`(types.Int64).ValueInt64Pointer() *int64`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#Int64Value.ValueInt64Pointer): Returns a int64 pointer to a known value, `nil` if null, or a pointer to `0` if unknown. + +In this example, a int64 value is checked for being null or unknown value first, before accessing its known value: + +```go +// Example data model definition +// type ExampleModel struct { +// ExampleAttribute types.Int64 `tfsdk:"example_attribute"` +// } +// +// This would be filled in, such as calling: req.Plan.Get(ctx, &data) +var data ExampleModel + +// optional logic for handling null value +if data.ExampleAttribute.IsNull() { + // ... +} + +// optional logic for handling unknown value +if data.ExampleAttribute.IsUnknown() { + // ... +} + +// myInt64 now contains a Go int64 with the known value +myInt64 := data.ExampleAttribute.ValueInt64() +``` + +## Setting Values + +Call one of the following to create a `types.Int64` value: + +* [`types.Int64Null()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#Int64Null): A null int64 value. +* [`types.Int64Unknown()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#Int64Unknown): An unknown int64 value. +* [`types.Int64Value(int64)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#Int64Value): A known value. +* [`types.Int64PointerValue(*int64)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#Int64PointerValue): A known value. + +In this example, a known int64 value is created: + +```go +types.Int64Value(123) +``` + +Otherwise, for certain framework functionality that does not require `types` implementations directly, such as: + +* [`(tfsdk.State).SetAttribute()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/tfsdk#State.SetAttribute) +* [`types.ListValueFrom()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#ListValueFrom) +* [`types.MapValueFrom()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#MapValueFrom) +* [`types.ObjectValueFrom()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#ObjectValueFrom) +* [`types.SetValueFrom()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#SetValueFrom) + +Numbers can be automatically converted from the following Go types, pointers to these types, or any aliases of these types, such `type MyNumber int`: + +* `int`, `int8`, `int16`, `int32`, `int64` +* `uint`, `uint8`, `uint16`, `uint32`, `uint64` +* `float32`, `float64` +* [`*big.Int`](https://pkg.go.dev/math/big#Int), [`*big.Float`](https://pkg.go.dev/math/big#Float) + +An error will be returned if the value of the number cannot be stored in the numeric type supplied because of an overflow or other loss of precision. + +In this example, a `int64` is directly used to set a int64 attribute value: + +```go +diags := resp.State.SetAttribute(ctx, path.Root("example_attribute"), 123) +``` + +In this example, a `types.List` of `types.Int64` is created from a `[]int64`: + +```go +listValue, diags := types.ListValueFrom(ctx, types.Int64Type, []int64{123, 456}) +``` + +## Extending + +The framework supports extending its base type implementations with [custom types](/terraform/plugin/framework/handling-data/types/custom). These can adjust expected provider code usage depending on their implementation. diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/types/list.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/types/list.mdx new file mode 100644 index 0000000000..9352d8f781 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/types/list.mdx @@ -0,0 +1,125 @@ +--- +page_title: List types +description: >- + Learn how to implement list value types with the Terraform pluginprovider + framework. +--- + +# List types + +List types store an ordered collection of single element type. + +By default, lists from [schema](/terraform/plugin/framework/handling-data/schemas) (configuration, plan, and state) data are represented in the framework by [`types.ListType`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#ListType) and its associated value storage type of [`types.List`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#List). These types fully support Terraform's [type system concepts](/terraform/plugin/framework/handling-data/terraform-concepts) that cannot be represented in Go built-in types, such as a slice. Framework types can be [extended](#extending) by provider code or shared libraries to provide specific use case functionality. + +## Schema Definitions + +Use one of the following attribute types to directly add a list of a single element type to a [schema](/terraform/plugin/framework/handling-data/schemas) or [nested attribute type](/terraform/plugin/framework/handling-data/attributes#nested-attribute-types): + +| Schema Type | Attribute Type | +|-------------|----------------| +| [Data Source](/terraform/plugin/framework/data-sources) | [`schema.ListAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/datasource/schema#ListAttribute) | +| [Provider](/terraform/plugin/framework/providers) | [`schema.ListAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider/schema#ListAttribute) | +| [Resource](/terraform/plugin/framework/resources) | [`schema.ListAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema#ListAttribute) | +| [Ephemeral Resource](/terraform/plugin/framework/ephemeral-resources) | [`schema.ListAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/ephemeral/schema#ListAttribute) | + +Use one of the following attribute types to directly add a list of a nested attributes to a [schema](/terraform/plugin/framework/handling-data/schemas) or [nested attribute type](/terraform/plugin/framework/handling-data/attributes#nested-attribute-types): + +| Schema Type | Attribute or Block Type | +|-------------|-------------------------| +| [Data Source](/terraform/plugin/framework/data-sources) | [`schema.ListNestedAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/datasource/schema#ListNestedAttribute) | +| [Data Source](/terraform/plugin/framework/data-sources) | [`schema.ListNestedBlock`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/datasource/schema#ListNestedBlock) | +| [Provider](/terraform/plugin/framework/providers) | [`schema.ListNestedAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider/schema#ListNestedAttribute) | +| [Provider](/terraform/plugin/framework/providers) | [`schema.ListNestedBlock`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider/schema#ListNestedBlock) | +| [Resource](/terraform/plugin/framework/resources) | [`schema.ListNestedAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema#ListNestedAttribute) | +| [Resource](/terraform/plugin/framework/resources) | [`schema.ListNestedBlock`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema#ListNestedBlock) | +| [Ephemeral Resource](/terraform/plugin/framework/ephemeral-resources) | [`schema.ListNestedAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/ephemeral/schema#ListNestedAttribute) | +| [Ephemeral Resource](/terraform/plugin/framework/ephemeral-resources) | [`schema.ListNestedBlock`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/ephemeral/schema#ListNestedBlock) | + +If the list value should be the element type of another [collection attribute type](/terraform/plugin/framework/handling-data/attributes#collection-attribute-types), set the `ElementType` field to `types.ListType{ElemType: /* ... */}` or the appropriate [custom type](#extending). + +If the list value should be a value type of an [object attribute type](/terraform/plugin/framework/handling-data/attributes#object-attribute-type), set the `AttributeTypes` map value to `types.ListType{ElemType: /* ... */}` or the appropriate [custom type](#extending). + +## Accessing Values + + + +Review the [attribute documentation](/terraform/plugin/framework/handling-data/attributes/list#accessing-values) to understand how schema-based data gets mapped into accessible values, such as a `types.List` in this case. + + + +Access `types.List` information via the following methods: + +* [`(types.List).IsNull() bool`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#ListValue.IsNull): Returns `true` if the list is null. +* [`(types.List).IsUnknown() bool`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#ListValue.IsUnknown): Returns `true` if the list is unknown. Returns `false` if the number of elements is known, any of which may be unknown. +* [`(types.List).Elements() []attr.Value`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#ListValue.Elements): Returns the known `[]attr.Value` value, or `nil` if null or unknown. +* [`(types.List).ElementsAs(context.Context, any, bool) diag.Diagnostics`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#ListValue.ElementsAs): Converts the known values into the given Go type, if possible. It is recommended to use a slice of framework types to account for elements which may be unknown. + +In this example, a list of strings value is checked for being null or unknown value first, before accessing its known value elements as a `[]types.String`: + +```go +// Example data model definition +// type ExampleModel struct { +// ExampleAttribute types.List `tfsdk:"example_attribute"` +// } +// +// This would be filled in, such as calling: req.Plan.Get(ctx, &data) +var data ExampleModel + +// optional logic for handling null value +if data.ExampleAttribute.IsNull() { + // ... +} + +// optional logic for handling unknown value +if data.ExampleAttribute.IsUnknown() { + // ... +} + +elements := make([]types.String, 0, len(data.ExampleAttribute.Elements())) +diags := data.ExampleAttribute.ElementsAs(ctx, &elements, false) +``` + +## Setting Values + +Call one of the following to create a `types.List` value: + +* [`types.ListNull(attr.Type) types.List`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#ListNull): A null list value with the given element type. +* [`types.ListUnknown(attr.Type) types.List`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#ListUnknown): An unknown list value with the given element type. +* [`types.ListValue(attr.Type, []attr.Value) (types.List, diag.Diagnostics)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#ListValue): A known value with the given element type and values. +* [`types.ListValueFrom(context.Context, attr.Type, any) (types.List, diag.Diagnostics)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#ListValueFrom): A known value with the given element type and values. This can convert the source data from standard Go types into framework types as noted in the documentation for each element type, such as giving `[]*string` for a `types.List` of `types.String`. +* [`types.ListValueMust(attr.Type, []attr.Value) types.List`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#ListValueMust): A known value with the given element type and values. Any diagnostics are converted to a runtime panic. This is recommended only for testing or exhaustively tested logic. + +In this example, a known list value is created from framework types: + +```go +elements := []attr.Value{types.StringValue("one"), types.StringValue("two")} +listValue, diags := types.ListValue(types.StringType, elements) +``` + +Otherwise, for certain framework functionality that does not require `types` implementations directly, such as: + +* [`(tfsdk.State).SetAttribute()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/tfsdk#State.SetAttribute) +* [`types.ListValueFrom()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#ListValueFrom) +* [`types.MapValueFrom()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#MapValueFrom) +* [`types.ObjectValueFrom()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#ObjectValueFrom) +* [`types.SetValueFrom()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#SetValueFrom) + +A Go built-in slice type (`[]T`) or type alias of a slice type such as `type MyListType []T` can be used instead. + +In this example, a `[]string` is directly used to set a list attribute value: + +```go +elements := []string{"one", "two"} +diags := resp.State.SetAttribute(ctx, path.Root("example_attribute"), elements) +``` + +In this example, a `types.List` of `types.String` is created from a `[]string`: + +```go +elements := []string{"one", "two"} +listValue, diags := types.ListValueFrom(ctx, types.StringType, elements) +``` + +## Extending + +The framework supports extending its base type implementations with [custom types](/terraform/plugin/framework/handling-data/types/custom). These can adjust expected provider code usage depending on their implementation. diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/types/map.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/types/map.mdx new file mode 100644 index 0000000000..a1b0415d5d --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/types/map.mdx @@ -0,0 +1,130 @@ +--- +page_title: Map types +description: >- + Learn how to implement mapping value types with the Terraform plugin + framework. +--- + +# Map type + +Map types store an ordered collection of single element type. + +By default, maps from [schema](/terraform/plugin/framework/handling-data/schemas) (configuration, plan, and state) data are represented in the framework by [`types.MapType`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#MapType) and its associated value storage type of [`types.Map`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#Map). These types fully support Terraform's [type system concepts](/terraform/plugin/framework/handling-data/terraform-concepts) that cannot be represented in Go built-in types, such as a map. Framework types can be [extended](#extending) by provider code or shared libraries to provide specific use case functionality. + +## Schema Definitions + +Use one of the following attribute types to directly add a map of a single element type to a [schema](/terraform/plugin/framework/handling-data/schemas) or [nested attribute type](/terraform/plugin/framework/handling-data/attributes#nested-attribute-types): + +| Schema Type | Attribute Type | +|-------------|----------------| +| [Data Source](/terraform/plugin/framework/data-sources) | [`schema.MapAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/datasource/schema#MapAttribute) | +| [Provider](/terraform/plugin/framework/providers) | [`schema.MapAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider/schema#MapAttribute) | +| [Resource](/terraform/plugin/framework/resources) | [`schema.MapAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema#MapAttribute) | +| [Ephemeral Resource](/terraform/plugin/framework/ephemeral-resources) | [`schema.MapAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/ephemeral/schema#MapAttribute) | + +Use one of the following attribute types to directly add a map of a nested attributes to a [schema](/terraform/plugin/framework/handling-data/schemas) or [nested attribute type](/terraform/plugin/framework/handling-data/attributes#nested-attribute-types): + +| Schema Type | Attribute Type | +|-------------|----------------| +| [Data Source](/terraform/plugin/framework/data-sources) | [`schema.MapNestedAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/datasource/schema#MapNestedAttribute) | +| [Provider](/terraform/plugin/framework/providers) | [`schema.MapNestedAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider/schema#MapNestedAttribute) | +| [Resource](/terraform/plugin/framework/resources) | [`schema.MapNestedAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema#MapNestedAttribute) | +| [Ephemeral Resource](/terraform/plugin/framework/ephemeral-resources) | [`schema.MapNestedAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/ephemeral/schema#MapNestedAttribute) | + +If the map value should be the element type of another [collection attribute type](/terraform/plugin/framework/handling-data/attributes#collection-attribute-types), set the `ElementType` field to `types.MapType{ElemType: /* ... */}` or the appropriate [custom type](#extending). + +If the map value should be a value type of an [object attribute type](/terraform/plugin/framework/handling-data/attributes#object-attribute-type), set the `AttributeTypes` map value to `types.MapType{ElemType: /* ... */}` or the appropriate [custom type](#extending). + +## Accessing Values + + + +Review the [attribute documentation](/terraform/plugin/framework/handling-data/attributes/map#accessing-values) to understand how schema-based data gets mapped into accessible values, such as a `types.Map` in this case. + + + +Access `types.Map` information via the following methods: + +* [`(types.Map).IsNull() bool`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#MapValue.IsNull): Returns `true` if the map is null. +* [`(types.Map).IsUnknown() bool`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#MapValue.IsUnknown): Returns `true` if the map is unknown. Returns `false` if the number of elements is known, any of which may be unknown. +* [`(types.Map).Elements() map[string]attr.Value`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#MapValue.Elements): Returns the known `map[string]attr.Value` value, or `nil` if null or unknown. +* [`(types.Map).ElementsAs(context.Context, any, bool) diag.Diagnostics`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#MapValue.ElementsAs): Converts the known values into the given Go type, if possible. It is recommended to use a map of framework types to account for elements which may be unknown. + +In this example, a map of strings value is checked for being null or unknown value first, before accessing its known value elements as a `map[string]types.String`: + +```go +// Example data model definition +// type ExampleModel struct { +// ExampleAttribute types.Map `tfsdk:"example_attribute"` +// } +// +// This would be filled in, such as calling: req.Plan.Get(ctx, &data) +var data ExampleModel + +// optional logic for handling null value +if data.ExampleAttribute.IsNull() { + // ... +} + +// optional logic for handling unknown value +if data.ExampleAttribute.IsUnknown() { + // ... +} + +elements := make(map[string]types.String, len(data.ExampleAttribute.Elements())) +diags := data.ExampleAttribute.ElementsAs(ctx, &elements, false) +``` + +## Setting Values + +Call one of the following to create a `types.Map` value: + +* [`types.MapNull(attr.Type) types.Map`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#MapNull): A null list value with the given element type. +* [`types.MapUnknown(attr.Type) types.Map`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#MapUnknown): An unknown list value with the given element type. +* [`types.MapValue(attr.Type, map[string]attr.Value) (types.Map, diag.Diagnostics)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#MapValue): A known value with the given element type and values. +* [`types.MapValueFrom(context.Context, attr.Type, any) (types.Map, diag.Diagnostics)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#MapValueFrom): A known value with the given element type and values. This can convert the source data from standard Go types into framework types as noted in the documentation for each element type, such as giving `map[string]*string` for a `types.Map` of `types.String`. +* [`types.MapValueMust(map[string]attr.Type, map[string]attr.Value) types.Map`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#MapValueMust): A known value with the given element type and values. Any diagnostics are converted to a runtime panic. This is recommended only for testing or exhaustively tested logic. + +In this example, a known map value is created from framework types: + +```go +elements := map[string]attr.Value{ + "key1": types.StringValue("value1"), + "key2": types.StringValue("value2"), +} +mapValue, diags := types.MapValue(types.StringType, elements) +``` + +Otherwise, for certain framework functionality that does not require `types` implementations directly, such as: + +* [`(tfsdk.State).SetAttribute()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/tfsdk#State.SetAttribute) +* [`types.ListValueFrom()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#ListValueFrom) +* [`types.MapValueFrom()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#MapValueFrom) +* [`types.ObjectValueFrom()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#ObjectValueFrom) +* [`types.SetValueFrom()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#SetValueFrom) + +A Go built-in map of string key type (`map[string]T`) or type alias of a map of string key type such as `type MyMapType map[string]T` can be used instead. + +In this example, a `map[string]string` is directly used to set a map attribute value: + +```go +elements := map[string]string{ + "key1": "value1", + "key2": "value2", +} +diags := resp.State.SetAttribute(ctx, path.Root("example_attribute"), elements) +``` + +In this example, a `types.Map` of `types.String` is created from a `map[string]string`: + +```go +elements := map[string]string{ + "key1": "value1", + "key2": "value2", +} +mapValue, diags := types.MapValueFrom(ctx, types.StringType, elements) +``` + +## Extending + +The framework supports extending its base type implementations with [custom types](/terraform/plugin/framework/handling-data/types/custom). These can adjust expected provider code usage depending on their implementation. diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/types/number.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/types/number.mdx new file mode 100644 index 0000000000..aa15721073 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/types/number.mdx @@ -0,0 +1,117 @@ +--- +page_title: Number types +description: >- + Learn how to implement arbitrary precision number value types with the Terraform plugin + framework. +--- + +# Number types + + + +Use [Float64 Type](/terraform/plugin/framework/handling-data/types/float64) for 64-bit floating point numbers. Use [Int64 Type](/terraform/plugin/framework/handling-data/types/int64) for 64-bit integer numbers. + + + +Number types store an arbitrary precision (generally more than 64-bit, up to 512-bit) number. + +By default, number from [schema](/terraform/plugin/framework/handling-data/schemas) (configuration, plan, and state) data are represented in the framework by [`types.NumberType`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#NumberType) and its associated value storage type of [`types.Number`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#Number). These types fully support Terraform's [type system concepts](/terraform/plugin/framework/handling-data/terraform-concepts) that cannot be represented in Go built-in types, such as `*big.Float`. Framework types can be [extended](#extending) by provider code or shared libraries to provide specific use case functionality. + +## Schema Definitions + +Use one of the following attribute types to directly add a number value to a [schema](/terraform/plugin/framework/handling-data/schemas) or [nested attribute type](/terraform/plugin/framework/handling-data/attributes#nested-attribute-types): + +| Schema Type | Attribute Type | +|-------------|----------------| +| [Data Source](/terraform/plugin/framework/data-sources) | [`schema.NumberAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/datasource/schema#NumberAttribute) | +| [Provider](/terraform/plugin/framework/providers) | [`schema.NumberAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider/schema#NumberAttribute) | +| [Resource](/terraform/plugin/framework/resources) | [`schema.NumberAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema#NumberAttribute) | +| [Ephemeral Resource](/terraform/plugin/framework/ephemeral-resources) | [`schema.NumberAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/ephemeral/schema#NumberAttribute) | + +If the number value should be the element type of a [collection attribute type](/terraform/plugin/framework/handling-data/attributes#collection-attribute-types), set the `ElemType` field to `types.NumberType` or the appropriate [custom type](#extending). + +If the number value should be a value type of an [object attribute type](/terraform/plugin/framework/handling-data/attributes#object-attribute-type), set the `AttrTypes` map value to `types.NumberType` or the appropriate [custom type](#extending). + +## Accessing Values + + + +Review the [attribute documentation](/terraform/plugin/framework/handling-data/attributes/number#accessing-values) to understand how schema-based data gets mapped into accessible values, such as a `types.Number` in this case. + + + +Access `types.Number` information via the following methods: + +* [`(types.Number).IsNull() bool`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#NumberValue.IsNull): Returns true if the number is null. +* [`(types.Number).IsUnknown() bool`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#NumberValue.IsUnknown): Returns true if the number is unknown. +* [`(types.Number).ValueBigFloat() *big.Float`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#NumberValue.ValueNumber): Returns the known number, or the equivalent of `0.0` if null or unknown. + +In this example, a number value is checked for being null or unknown value first, before accessing its known value: + +```go +// Example data model definition +// type ExampleModel struct { +// ExampleAttribute types.Number `tfsdk:"example_attribute"` +// } +// +// This would be filled in, such as calling: req.Plan.Get(ctx, &data) +var data ExampleModel + +// optional logic for handling null value +if data.ExampleAttribute.IsNull() { + // ... +} + +// optional logic for handling unknown value +if data.ExampleAttribute.IsUnknown() { + // ... +} + +// myNumber now contains a Go *big.Float with the known value +myNumber := data.ExampleAttribute.ValueBigFloat() +``` + +## Setting Values + +Call one of the following to create a `types.Number` value: + +* [`types.NumberNull()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#NumberNull): A null float64 value. +* [`types.NumberUnknown()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#NumberUnknown): An unknown float64 value. +* [`types.NumberValue(*big.Float)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#NumberValue): A known value. + +In this example, a known number value is created: + +```go +types.NumberValue(big.NewFloat(1.23)) +``` + +Otherwise, for certain framework functionality that does not require `types` implementations directly, such as: + +* [`(tfsdk.State).SetAttribute()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/tfsdk#State.SetAttribute) +* [`types.ListValueFrom()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#ListValueFrom) +* [`types.MapValueFrom()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#MapValueFrom) +* [`types.ObjectValueFrom()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#ObjectValueFrom) +* [`types.SetValueFrom()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#SetValueFrom) + +Numbers can be automatically converted from the following Go types, pointers to these types, or any aliases of these types, such `type MyNumber int`: + +* `int`, `int8`, `int16`, `int32`, `int64` +* `uint`, `uint8`, `uint16`, `uint32`, `uint64` +* `float32`, `float64` +* [`*big.Int`](https://pkg.go.dev/math/big#Int), [`*big.Float`](https://pkg.go.dev/math/big#Float) + +In this example, a `*big.Float` is directly used to set a number attribute value: + +```go +diags := resp.State.SetAttribute(ctx, path.Root("example_attribute"), big.NewFloat(1.23)) +``` + +In this example, a `types.List` of `types.Number` is created from a `[]*big.Float`: + +```go +listValue, diags := types.ListValueFrom(ctx, types.NumberType, []*big.Float{big.NewFloat(1.2), big.NewFloat(2.4)}) +``` + +## Extending + +The framework supports extending its base type implementations with [custom types](/terraform/plugin/framework/handling-data/types/custom). These can adjust expected provider code usage depending on their implementation. diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/types/object.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/types/object.mdx new file mode 100644 index 0000000000..a5aa0f62e9 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/types/object.mdx @@ -0,0 +1,246 @@ +--- +page_title: Object types +description: >- + Learn how to implement object value types with the Terraform plugin framework. +--- + +# Object types + +Object types store a mapping of explicit attribute names to value types. Objects must declare all attribute values, even when null or unknown, unless the entire object is null or unknown. + +By default, objects from [schema](/terraform/plugin/framework/handling-data/schemas) (configuration, plan, and state) data are represented in the framework by [`types.ObjectType`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#ObjectType) and its associated value storage type of [`types.Object`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#Object). These types fully support Terraform's [type system concepts](/terraform/plugin/framework/handling-data/terraform-concepts) that cannot be represented in Go built-in types, such as a struct. Framework types can be [extended](#extending) by provider code or shared libraries to provide specific use case functionality. + +## Schema Definitions + + + +Use [nested attribute types](/terraform/plugin/framework/handling-data/attributes#nested-attribute-types) instead of object attribute types where possible. Object attributes have limited utility as they can only define type information. + + + +Use one of the following attribute types to directly add a single structure of a nested attributes to a [schema](/terraform/plugin/framework/handling-data/schemas) or [nested attribute type](/terraform/plugin/framework/handling-data/attributes#nested-attribute-types): + +| Schema Type | Attribute or Block Type | +|-------------|-------------------------| +| [Data Source](/terraform/plugin/framework/data-sources) | [`schema.SingleNestedAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/datasource/schema#SingleNestedAttribute) | +| [Data Source](/terraform/plugin/framework/data-sources) | [`schema.SingleNestedBlock`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/datasource/schema#SingleNestedBlock) | +| [Provider](/terraform/plugin/framework/providers) | [`schema.SingleNestedAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider/schema#SingleNestedAttribute) | +| [Provider](/terraform/plugin/framework/providers) | [`schema.SingleNestedBlock`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider/schema#SingleNestedBlock) | +| [Resource](/terraform/plugin/framework/resources) | [`schema.SingleNestedAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema#SingleNestedAttribute) | +| [Resource](/terraform/plugin/framework/resources) | [`schema.SingleNestedBlock`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema#SingleNestedBlock) | +| [Ephemeral Resource](/terraform/plugin/framework/ephemeral-resources) | [`schema.SingleNestedAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/ephemeral/schema#SingleNestedAttribute) | +| [Ephemeral Resource](/terraform/plugin/framework/ephemeral-resources) | [`schema.SingleNestedBlock`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/ephemeral/schema#SingleNestedBlock) | + +If a wrapping collection is needed on the structure of nested attributes, any of the other nested attribute and nested block types can be used. + +Use one of the following attribute types to directly add an object value directly to a [schema](/terraform/plugin/framework/handling-data/schemas) or [nested attribute type](/terraform/plugin/framework/handling-data/attributes#nested-attribute-types): + +| Schema Type | Attribute Type | +|-------------|----------------| +| [Data Source](/terraform/plugin/framework/data-sources) | [`schema.ObjectAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/datasource/schema#ObjectAttribute) | +| [Provider](/terraform/plugin/framework/providers) | [`schema.ObjectAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider/schema#ObjectAttribute) | +| [Resource](/terraform/plugin/framework/resources) | [`schema.ObjectAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema#ObjectAttribute) | +| [Ephemeral Resource](/terraform/plugin/framework/ephemeral-resources) | [`schema.ObjectAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/ephemeral/schema#ObjectAttribute) | + +If the object value should be the element type of another [collection attribute type](/terraform/plugin/framework/handling-data/attributes#collection-attribute-types), set the `ElementType` field to `types.ObjectType{AttrTypes: /* ... */}` or the appropriate [custom type](#extending). + +If the object value should be a value type of an [object attribute type](/terraform/plugin/framework/handling-data/attributes#object-attribute-type), set the `AttributeTypes` map value to `types.ObjectType{AttrTypes: /* ... */}` or the appropriate [custom type](#extending). + +## Accessing Values + + + +Review the associated [attribute documentation](/terraform/plugin/framework/handling-data/attributes) to understand how schema-based data gets mapped into accessible values, such as a `types.Object` in this case. + + + +Access `types.Object` information via the following methods: + +* [`(types.Object).IsNull() bool`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#ObjectValue.IsNull): Returns `true` if the object is null. +* [`(types.Object).IsUnknown() bool`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#ObjectValue.IsUnknown): Returns `true` if the object is unknown. +* [`(types.Object).Attributes() map[string]attr.Value`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#ObjectValue.Attributes): Returns the known `map[string]attr.Value` value, or `nil` if null or unknown. +* [`(types.Object).As(context.Context, any, ObjectAsOptions) diag.Diagnostics`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#ObjectValue.As): Converts the known values into the given Go type, if possible. It is recommended to use a struct of framework types to account for attributes which may be unknown. + +In this example, an object with a string attribute is checked for being null or unknown value first, before accessing its known value attributes as a Go struct type: + +```go +// Example data model definitions +// type ExampleModel struct { +// ExampleAttribute types.Object `tfsdk:"example_attribute"` +// } +// +// type ExampleAttributeModel struct { +// StringAttribute types.String `tfsdk:"string_attribute"` +// } +// +// This would be filled in, such as calling: req.Plan.Get(ctx, &data) +var data ExampleModel + +// optional logic for handling null value +if data.ExampleAttribute.IsNull() { + // ... +} + +// optional logic for handling unknown value +if data.ExampleAttribute.IsUnknown() { + // ... +} + +var exampleAttribute ExampleAttributeModel + +diags := data.ExampleAttribute.As(ctx, &exampleAttribute, basetypes.ObjectAsOptions{}) +// Object data now is accessible, such as: exampleAttribute.StringAttribute.StringValue() +``` + +## Setting Values + +Call one of the following to create a `types.Object` value: + +* [`types.ObjectNull(map[string]attr.Type) types.Object`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#ObjectNull): A null object value with the given element type. +* [`types.ObjectUnknown(map[string]attr.Type) types.Object`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#ObjectUnknown): An unknown object value with the given element type. +* [`types.ObjectValue(map[string]attr.Type, map[string]attr.Value) (types.Object, diag.Diagnostics)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#ObjectValue): A known value with the given attribute type mapping and attribute values mapping. +* [`types.ObjectValueFrom(context.Context, map[string]attr.Type, any) (types.Object, diag.Diagnostics)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#ObjectValueFrom): A known value with the given attribute type mapping and values. This can convert the source data from standard Go types into framework types as noted in the documentation for each type, such as giving a `struct` for a `types.Object`. +* [`types.ObjectValueMust(map[string]attr.Type, map[string]attr.Value) types.Object`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#ObjectValueMust): A known value with the given attribute type mapping and attribute value mapping. Any diagnostics are converted to a runtime panic. This is recommended only for testing or exhaustively tested logic. + +In this example, a known object value is created from framework types: + +```go +elementTypes := map[string]attr.Type{ + "attr1": types.StringType, + "attr2": types.Int64Type, +} +elements := map[string]attr.Value{ + "attr1": types.StringValue("value"), + "attr2": types.Int64Value(123), +} +objectValue, diags := types.ObjectValue(elementTypes, elements) +``` + +Otherwise, for certain framework functionality that does not require `types` implementations directly, such as: + +* [`(tfsdk.State).SetAttribute()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/tfsdk#State.SetAttribute) +* [`types.ListValueFrom()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#ListValueFrom) +* [`types.MapValueFrom()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#MapValueFrom) +* [`types.ObjectValueFrom()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#ObjectValueFrom) +* [`types.SetValueFrom()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#SetValueFrom) + +## Automatic conversion with `tfsdk` struct tags + +Objects can be automatically converted to and from any Go struct type that follows these constraints to prevent accidental data loss: + +* Every struct field must have a `tfsdk` struct tag and every attribute in the object must have a corresponding struct tag. The `tfsdk` struct tag must name an attribute in the object that it is being mapped or be set to `-` to explicitly declare it does not map to an attribute in the object. Duplicate `tfsdk` struct tags are not allowed. +* Every struct type must be an acceptable conversion type according to the type documentation, such as `*string` being acceptable for a string type. However, it is recommended to use framework types to simplify data modeling (one model type for accessing and setting data) and prevent errors when encountering unknown values from Terraform. + +In this example, a struct is directly used to set an object attribute value: + +```go +type ExampleAttributeModel struct { + Int64Attribute types.Int64 `tfsdk:"int64_attribute` + StringAttribute types.String `tfsdk:"string_attribute"` +} + +value := ExampleAttributeModel{ + Int64Attribute: types.Int64Value(123), + StringAttribute: types.StringValue("example"), +} + +diags := resp.State.SetAttribute(ctx, path.Root("example_attribute"), value) +``` + +In this example, a `types.Object` is created from a struct: + +```go +type ExampleAttributeModel struct { + Int64Attribute types.Int64 `tfsdk:"int64_attribute` + StringAttribute types.String `tfsdk:"string_attribute"` +} + +func (m ExampleAttributeModel) AttributeTypes() map[string]attr.Type { + return map[string]attr.Type{ + "int64_attribute": types.Int64Type, + "string_attribute": types.StringType, + } +} + +value := ExampleAttributeModel{ + Int64Attribute: types.Int64Value(123), + StringAttribute: types.StringValue("example"), +} + +objectValue, diags := types.ObjectValueFrom(ctx, value.AttributeTypes(), value) +``` + +### Struct Embedding + +Go struct types that utilize [struct embedding](https://go.dev/doc/effective_go#embedding) to promote fields with `tfsdk` tags are supported when converting to and from object types. + +In this example, a `types.Object` is created from a struct that embeds another struct type: + +```go +type ExampleAttributeModel struct { + Attr1 types.String `tfsdk:"attr_1"` + Attr2 types.Bool `tfsdk:"attr_2"` + CommonModel // This embedded struct type promotes the Attr3 field +} + +type CommonModel struct { + Attr3 types.Int64 `tfsdk:"attr_3"` +} + +func (m ExampleAttributeModel) AttributeTypes() map[string]attr.Type { + return map[string]attr.Type{ + "attr_1": types.StringType, + "attr_2": types.BoolType, + "attr_3": types.Int64Type, + } +} + +value := ExampleAttributeModel{ + Attr1: types.StringValue("example"), + Attr2: types.BoolValue(true), +} +// This field is promoted from CommonModel +value.Attr3 = types.Int64Value(123) + +objectValue, diags := types.ObjectValueFrom(ctx, value.AttributeTypes(), value) +``` + +#### Restrictions + +In addition to the constraints [listed above for object conversions](#automatic-conversion-with-tfsdk-struct-tags) using `tfsdk` tagged fields, embedded struct types have these additional restrictions: + +* Promoted fields cannot have duplicate `tfsdk` struct tags that conflict with any fields of structs they are embedded within. +```go +type thingResourceModel struct { + Attr1 types.String `tfsdk:"attr_1"` + Attr2 types.Bool `tfsdk:"attr_2"` + CommonModel +} + +type CommonModel struct { + // This field has a duplicate tfsdk tag, conflicting with (thingResourceModel).Attr1 + // and will raise an error diagnostic during conversion. + ConflictingAttr types.Int64 `tfsdk:"attr_1"` +} +``` +* Struct types embedded by pointers are not supported. +```go +type thingResourceModel struct { + Attr1 types.String `tfsdk:"attr_1"` + Attr2 types.Bool `tfsdk:"attr_2"` + // This type of struct embedding is not supported and will raise + // an error diagnostic during conversion. + // + // Remove the pointer to embed the struct by value. + *CommonModel +} + +type CommonModel struct { + Attr3 types.Int64 `tfsdk:"attr_3"` + Attr4 types.List `tfsdk:"attr_4"` +} +``` + +## Extending + +The framework supports extending its base type implementations with [custom types](/terraform/plugin/framework/handling-data/types/custom). These can adjust expected provider code usage depending on their implementation. diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/types/set.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/types/set.mdx new file mode 100644 index 0000000000..d8970783c7 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/types/set.mdx @@ -0,0 +1,124 @@ +--- +page_title: Set types +description: >- + Learn how to implement set value types with the Terraform plugin framework. +--- + +# Set types + +Set types store an ordered collection of single element type. + +By default, sets from [schema](/terraform/plugin/framework/handling-data/schemas) (configuration, plan, and state) data are represented in the framework by [`types.SetType`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#SetType) and its associated value storage type of [`types.Set`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#Set). These types fully support Terraform's [type system concepts](/terraform/plugin/framework/handling-data/terraform-concepts) that cannot be represented in Go built-in types, such as a slice. Framework types can be [extended](#extending) by provider code or shared libraries to provide specific use case functionality. + +## Schema Definitions + +Use one of the following attribute types to directly add a set of a single element type to a [schema](/terraform/plugin/framework/handling-data/schemas) or [nested attribute type](/terraform/plugin/framework/handling-data/attributes#nested-attribute-types): + +| Schema Type | Attribute Type | +|-------------|----------------| +| [Data Source](/terraform/plugin/framework/data-sources) | [`schema.SetAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/datasource/schema#SetAttribute) | +| [Provider](/terraform/plugin/framework/providers) | [`schema.SetAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider/schema#SetAttribute) | +| [Resource](/terraform/plugin/framework/resources) | [`schema.SetAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema#SetAttribute) | +| [Ephemeral Resource](/terraform/plugin/framework/ephemeral-resources) | [`schema.SetAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/ephemeral/schema#SetAttribute) | + +Use one of the following attribute types to directly add a set of a nested attributes to a [schema](/terraform/plugin/framework/handling-data/schemas) or [nested attribute type](/terraform/plugin/framework/handling-data/attributes#nested-attribute-types): + +| Schema Type | Attribute or Block Type | +|-------------|-------------------------| +| [Data Source](/terraform/plugin/framework/data-sources) | [`schema.SetNestedAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/datasource/schema#SetNestedAttribute) | +| [Data Source](/terraform/plugin/framework/data-sources) | [`schema.SetNestedBlock`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/datasource/schema#SetNestedBlock) | +| [Provider](/terraform/plugin/framework/providers) | [`schema.SetNestedAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider/schema#SetNestedAttribute) | +| [Provider](/terraform/plugin/framework/providers) | [`schema.SetNestedBlock`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider/schema#SetNestedBlock) | +| [Resource](/terraform/plugin/framework/resources) | [`schema.SetNestedAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema#SetNestedAttribute) | +| [Resource](/terraform/plugin/framework/resources) | [`schema.SetNestedBlock`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema#SetNestedBlock) | +| [Ephemeral Resource](/terraform/plugin/framework/ephemeral-resources) | [`schema.SetNestedAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/ephemeral/schema#SetNestedAttribute) | +| [Ephemeral Resource](/terraform/plugin/framework/ephemeral-resources) | [`schema.SetNestedBlock`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/ephemeral/schema#SetNestedBlock) | + +If the set value should be the element type of another [collection attribute type](/terraform/plugin/framework/handling-data/attributes#collection-attribute-types), set the `ElementType` field to `types.SetType{ElemType: /* ... */}` or the appropriate [custom type](#extending). + +If the set value should be a value type of an [object attribute type](/terraform/plugin/framework/handling-data/attributes#object-attribute-type), set the `AttributeTypes` map value to `types.SetType{ElemType: /* ... */}` or the appropriate [custom type](#extending). + +## Accessing Values + + + +Review the [attribute documentation](/terraform/plugin/framework/handling-data/attributes/set#accessing-values) to understand how schema-based data gets mapped into accessible values, such as a `types.Set` in this case. + + + +Access `types.Set` information via the following methods: + +* [`(types.Set).IsNull() bool`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#SetValue.IsNull): Returns `true` if the set is null. +* [`(types.Set).IsUnknown() bool`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#SetValue.IsUnknown): Returns `true` if the set is unknown. Returns `false` if the number of elements is known, any of which may be unknown. +* [`(types.Set).Elements() []attr.Value`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#SetValue.Elements): Returns the known `[]attr.Value` value, or `nil` if null or unknown. +* [`(types.Set).ElementsAs(context.Context, any, bool) diag.Diagnostics`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#SetValue.ElementsAs): Converts the known values into the given Go type, if possible. It is recommended to use a slice of framework types to account for elements which may be unknown. + +In this example, a set of strings value is checked for being null or unknown value first, before accessing its known value elements as a `[]types.String`: + +```go +// Example data model definition +// type ExampleModel struct { +// ExampleAttribute types.Set `tfsdk:"example_attribute"` +// } +// +// This would be filled in, such as calling: req.Plan.Get(ctx, &data) +var data ExampleModel + +// optional logic for handling null value +if data.ExampleAttribute.IsNull() { + // ... +} + +// optional logic for handling unknown value +if data.ExampleAttribute.IsUnknown() { + // ... +} + +elements := make([]types.String, 0, len(data.ExampleAttribute.Elements())) +diags := data.ExampleAttribute.ElementsAs(ctx, &elements, false) +``` + +## Setting Values + +Call one of the following to create a `types.Set` value: + +* [`types.SetNull(attr.Type) types.Set`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#SetNull): A null set value with the given element type. +* [`types.SetUnknown(attr.Type) types.Set`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#SetUnknown): An unknown set value with the given element type. +* [`types.SetValue(attr.Type, []attr.Value) (types.Set, diag.Diagnostics)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#SetValue): A known value with the given element type and values. +* [`types.SetValueFrom(context.Context, attr.Type, any) (types.Set, diag.Diagnostics)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#SetValueFrom): A known value with the given element type and values. This can convert the source data from standard Go types into framework types as noted in the documentation for each element type, such as giving `[]*string` for a `types.Set` of `types.String`. +* [`types.SetValueMust(attr.Type, []attr.Value) types.Set`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#SetValueMust): A known value with the given element type and values. Any diagnostics are converted to a runtime panic. This is recommended only for testing or exhaustively tested logic. + +In this example, a known set value is created from framework types: + +```go +elements := []attr.Value{types.StringValue("one"), types.StringValue("two")} +setValue, diags := types.SetValue(types.StringType, elements) +``` + +Otherwise, for certain framework functionality that does not require `types` implementations directly, such as: + +* [`(tfsdk.State).SetAttribute()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/tfsdk#State.SetAttribute) +* [`types.ListValueFrom()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#ListValueFrom) +* [`types.MapValueFrom()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#MapValueFrom) +* [`types.ObjectValueFrom()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#ObjectValueFrom) +* [`types.SetValueFrom()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#SetValueFrom) + +A Go built-in slice type (`[]T`) or type alias of a slice type such as `type MyListType []T` can be used instead. + +In this example, a `[]string` is directly used to set a set attribute value: + +```go +elements := []string{"one", "two"} +diags := resp.State.SetAttribute(ctx, path.Root("example_attribute"), elements) +``` + +In this example, a `types.Set` of `types.String` is created from a `[]string`: + +```go +elements := []string{"one", "two"} +setValue, diags := types.SetValueFrom(ctx, types.StringType, elements) +``` + +## Extending + +The framework supports extending its base type implementations with [custom types](/terraform/plugin/framework/handling-data/types/custom). These can adjust expected provider code usage depending on their implementation. diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/types/string.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/types/string.mdx new file mode 100644 index 0000000000..65eb016cc2 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/types/string.mdx @@ -0,0 +1,121 @@ +--- +page_title: String types +description: >- + Learn how to implement string value types with the Terraform plugin framework. +--- + +# String types + +String types store a collection of UTF-8 encoded bytes. + +By default, strings from [schema](/terraform/plugin/framework/handling-data/schemas) (configuration, plan, and state) data are represented in the framework by [`types.StringType`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#StringType) and its associated value storage type of [`types.String`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#String). These types fully support Terraform's [type system concepts](/terraform/plugin/framework/handling-data/terraform-concepts) that cannot be represented in Go built-in types, such as `*string`. Framework types can be [extended](#extending) by provider code or shared libraries to provide specific use case functionality. + +## Schema Definitions + +Use one of the following attribute types to directly add a string value to a [schema](/terraform/plugin/framework/handling-data/schemas) or [nested attribute type](/terraform/plugin/framework/handling-data/attributes#nested-attribute-types): + +| Schema Type | Attribute Type | +|-------------|----------------| +| [Data Source](/terraform/plugin/framework/data-sources) | [`schema.StringAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/datasource/schema#StringAttribute) | +| [Provider](/terraform/plugin/framework/providers) | [`schema.StringAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider/schema#StringAttribute) | +| [Resource](/terraform/plugin/framework/resources) | [`schema.StringAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema#StringAttribute) | +| [Ephemeral Resource](/terraform/plugin/framework/ephemeral-resources) | [`schema.StringAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/ephemeral/schema#StringAttribute) | + +If the string value should be the element type of a [collection attribute type](/terraform/plugin/framework/handling-data/attributes#collection-attribute-types), set the `ElemType` field to `types.StringType` or the appropriate [custom type](#extending). + +If the string value should be a value type of an [object attribute type](/terraform/plugin/framework/handling-data/attributes#object-attribute-type), set the `AttrTypes` map value to `types.StringType` or the appropriate [custom type](#extending). + +## Accessing Values + + + +Review the [attribute documentation](/terraform/plugin/framework/handling-data/attributes/string#accessing-values) to understand how schema-based data gets mapped into accessible values, such as a `types.String` in this case. + + + + + +The `(types.String).String()` method is reserved for debugging purposes and returns `""` if the value is null and `""` if the value is unknown. Use `(types.String).ValueString()` or `(types.String).ValueStringPointer()` for accessing a known string value. + + + +Access `types.String` information via the following methods: + +* [`(types.String).IsNull() bool`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#StringValue.IsNull): Returns true if the string is null. +* [`(types.String).IsUnknown() bool`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#StringValue.IsUnknown): Returns true if the string is unknown. +* [`(types.String).ValueString() string`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#StringValue.ValueString): Returns the known string, or an empty string if null or unknown. +* [`(types.String).ValueStringPointer() *string`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#StringValue.ValueStringPointer): Returns a string pointer to a known value, `nil` if null, or a pointer to an empty string if unknown. + +In this example, a string value is checked for being null or unknown value first, before accessing its known value: + +```go +// Example data model definition +// type ExampleModel struct { +// ExampleAttribute types.String `tfsdk:"example_attribute"` +// } +// +// This would be filled in, such as calling: req.Plan.Get(ctx, &data) +var data ExampleModel + +// optional logic for handling null value +if data.ExampleAttribute.IsNull() { + // ... +} + +// optional logic for handling unknown value +if data.ExampleAttribute.IsUnknown() { + // ... +} + +// myString now contains a Go string with the known value +myString := data.ExampleAttribute.ValueString() +``` + +## Setting Values + +Call one of the following to create a `types.String` value: + +* [`types.StringNull()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#StringNull): A null string value. +* [`types.StringUnknown()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#StringUnknown): An unknown string value. +* [`types.StringValue(string)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#StringValue): A known value. +* [`types.StringPointerValue(*string)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#StringPointerValue): A known value. + +In this example, a known string value is created: + +```go +types.StringValue("example value") +``` + +Otherwise, for certain framework functionality that does not require `types` implementations directly, such as: + +* [`(tfsdk.State).SetAttribute()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/tfsdk#State.SetAttribute) +* [`types.ListValueFrom()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#ListValueFrom) +* [`types.MapValueFrom()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#MapValueFrom) +* [`types.ObjectValueFrom()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#ObjectValueFrom) +* [`types.SetValueFrom()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#SetValueFrom) + +A Go built-in `string`, `*string` (only with typed `nil`, `(*string)(nil)`), or type alias of `string` such as `type MyStringType string` can be used instead. + +In this example, a `string` is directly used to set a string attribute value: + +```go +diags := resp.State.SetAttribute(ctx, path.Root("example_attribute"), "example value") +``` + +In this example, a `types.List` of `types.String` is created from a `[]string`: + +```go +listValue, diags := types.ListValueFrom(ctx, types.StringType, []string{"value one", "value two"}) +``` + +## Extending + +The framework supports extending its base type implementations with [custom types](/terraform/plugin/framework/handling-data/types/custom). These can adjust expected provider code usage depending on their implementation. + +### Common Use Case Types + +HashiCorp provides additional Go modules which contain custom string type implementations covering common use cases with validation and semantic equality logic: + +* [`terraform-plugin-framework-jsontypes`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-jsontypes): JSON encoded strings, such as exact byte strings and normalized strings +* [`terraform-plugin-framework-nettypes`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-nettypes): Networking strings, such as IPv4 addresses, IPv6 addresses, and CIDRs +* [`terraform-plugin-framework-timetypes`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-timetypes): Timestamp strings, such as RFC3339 diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/types/tuple.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/types/tuple.mdx new file mode 100644 index 0000000000..1276559a94 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/types/tuple.mdx @@ -0,0 +1,55 @@ +--- +page_title: Tuple types +description: >- + Learn how to implement tuple value types with the Terraform plugin framework. +--- + +# Tuple types + + + +The tuple type doesn't have associated schema attributes as it has limited real world application. Provider developers will only encounter tuples when handling provider-defined function variadic parameters or dynamic values. + + + +Tuple types store an ordered collection of elements where each element has it's own type. Values must have **exactly** the same number of elements (no more and no fewer), and the value in each position must match the specified type for that position. + +The tuple type is used to express Terraform's [tuple type constraint](/terraform/language/expressions/type-constraints#tuple). + +## Schema Definitions + +The tuple type is not supported in schema definitions of provider, data sources, ephemeral resources, or managed resources as it has limited real world application. + +## Accessing Values + +Access `types.Tuple` information via the following methods: + +* [`(types.Tuple).IsNull() bool`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#TupleValue.IsNull): Returns `true` if the tuple is null. +* [`(types.Tuple).IsUnknown() bool`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#TupleValue.IsUnknown): Returns `true` if the tuple is unknown. Returns `false` if the number of elements is known, any of which may be unknown. +* [`(types.Tuple).Elements() []attr.Value`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types/basetypes#TupleValue.Elements): Returns the known `[]attr.Value` value, or `nil` if null or unknown. + +## Setting Values + +Call one of the following to create a `types.Tuple` value: + +* [`types.TupleNull([]attr.Type) types.Tuple`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#TupleNull): A null tuple value with the given element types. +* [`types.TupleUnknown([]attr.Type) types.Tuple`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#TupleUnknown): An unknown tuple value with the given element types. +* [`types.TupleValue([]attr.Type, []attr.Value) (types.Tuple, diag.Diagnostics)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#TupleValue): A known value with the given element types and values. +* [`types.TupleValueMust([]attr.Type, []attr.Value) types.Tuple`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#TupleValueMust): A known value with the given element types and values. Any diagnostics are converted to a runtime panic. This is recommended only for testing or exhaustively tested logic. + +In this example, a known tuple value (`["one", true, 123]`) is created from framework types: + +```go +elementTypes := []attr.Type{ + types.StringType, + types.BoolType, + types.Int64Type, +} +elements := []attr.Value{ + types.StringValue("one"), + types.BoolValue(true), + types.Int64Value(123), +} + +tupleValue, diags := types.TupleValue(elementTypes, elements) +``` diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/writing-state.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/writing-state.mdx new file mode 100644 index 0000000000..200ef6501f --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/handling-data/writing-state.mdx @@ -0,0 +1,85 @@ +--- +page_title: Writing state +description: >- + Learn how to write and update the Terraform statefile with the Terraform + plugin framework. +--- + +# Writing state + +One of the primary jobs of a Terraform provider is to manage the provider's +resources and data sources in the [Terraform state](/terraform/language/state). Writing values to state +is something that provider developers will do frequently. + +The state that a provider developer wants to update is usually stored in a +response object: + +```go +func (r ThingResource) Create(ctx context.Context, + req resource.CreateRequest, resp *resource.CreateResponse) +``` + +In this example, `resp` holds the state that the provider developer should +update. + +## Replace the Entire State + +One way to set the state is to replace all the state values for a resource or +data source all at once. You need to define a type to contain the values. The benefit is that this allows the compiler to check all code that sets values on state, and only the final call to persist state can return an error. + +Use the `Set` method to store the entire state data. + +```go +type ThingResourceModel struct { + Address types.Object `tfsdk:"address"` + Age types.Int64 `tfsdk:"age"` + Name types.String `tfsdk:"name"` + Pets types.List `tfsdk:"pets"` + Registered types.Bool `tfsdk:"registered"` + Tags types.Map `tfsdk:"tags"` +} + +func (r ThingResource) Create(ctx context.Context, + req resource.CreateRequest, resp *resource.CreateResponse) { + var newState ThingResourceModel + + // ... + // update newState by modifying each property as usual for Go values + newState.Name = types.StringValue("J. Doe") + + // persist the values to state + diags := resp.State.Set(ctx, &newState) + + resp.Diagnostics.Append(diags...) + + if resp.Diagnostics.HasError() { + return + } +} +``` + +The state information is represented as an object, and gets persisted like an +object. Refer to the [object type](/terraform/plugin/framework/handling-data/types/object) documentation for an explanation on how +objects get persisted and what Go types are valid for persisting as an object. + +## Set a Single Attribute or Block Value + +Use the `SetAttribute` method to set an individual attribute or block value. + +-> The value must not be an untyped `nil`. Use a typed `nil` or `types` package null value function instead. For example with a `types.StringType` attribute, use `(*string)(nil)` or `types.StringNull()`. + +```go +func (r ThingResource) Read(ctx context.Context, + req resource.ReadRequest, resp *resource.ReadResponse) { + // ... + diags := resp.State.SetAttribute(ctx, path.Root("age"), 7) + + resp.Diagnostics.Append(diags...) + + if resp.Diagnostics.HasError() { + return + } +} +``` + +Refer to the [types](/terraform/plugin/framework/handling-data/types) documentation for more information about supported Go types. diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/index.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/index.mdx new file mode 100644 index 0000000000..3595b4f4c8 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/index.mdx @@ -0,0 +1,36 @@ +--- +page_title: Terraform plugin framework +description: >- + The Terraform plugin framework is an SDK that you can use to develop Terraform + providers. Learn how the plugin framework works with Terraform core. +--- + +# Terraform plugin framework + +The plugin framework is HashiCorp’s recommended way develop to Terraform Plugins on [protocol version 6](/terraform/plugin/terraform-plugin-protocol#protocol-version-6) or [protocol version 5](/terraform/plugin/terraform-plugin-protocol#protocol-version-5). + +We recommend using the framework to develop new providers because it offers significant advantages as compared to [Terraform Plugin SDKv2](/terraform/plugin/sdkv2). We also recommend migrating existing providers to the framework when possible. Refer to [Plugin Framework Benefits](/terraform/plugin/framework-benefits) for higher level details about how the framework makes provider development easier and [Plugin Framework Features](/terraform/plugin/framework/migrating/benefits) for a detailed functionality comparison between the SDKv2 and the framework. + +## Get Started + +- Try the [Terraform Plugin Framework tutorials](/terraform/tutorials/providers-plugin-framework). +- Clone the [terraform-provider-scaffolding-framework](https://github.com/hashicorp/terraform-provider-scaffolding-framework) template repository on GitHub. + +## Key Concepts + +- [Provider Servers](/terraform/plugin/framework/provider-servers) encapsulate all Terraform plugin details and handle all calls for provider, resource, and data source operations by implementing the [Terraform Plugin Protocol](/terraform/plugin/how-terraform-works#terraform-plugin-protocol). They are implemented as binaries that the Terraform CLI downloads, starts, and stops. +- [Providers](/terraform/plugin/framework/providers) are the top level abstraction that define the available resources and data sources for practitioners to use and may accept its own configuration, such as authentication information. +- [Schemas](/terraform/plugin/framework/handling-data/schemas) define available fields for provider, resource, or provisioner configuration block, and give Terraform metadata about those fields. +- [Resources](/terraform/plugin/framework/resources) are an abstraction that allow Terraform to manage infrastructure objects, such as a compute instance, an access policy, or disk. Providers act as a translation layer between Terraform and an API, offering one or more resources for practitioners to define in a configuration. +- [Data Sources](/terraform/plugin/framework/data-sources) are an abstraction that allow Terraform to reference external data. Providers have data sources that tell Terraform how to request external data and how to convert the response into a format that practitioners can interpolate. +- [Functions](/terraform/plugin/framework/functions) are an abstraction that allow Terraform to reference computational logic. Providers can implement their own custom logic functions to augment the Terraform configuration language [built-in functions](/terraform/language/functions). + +## Test and Publish + +- Learn to write [acceptance tests](/terraform/plugin/framework/acctests) for your provider. +- Learn to [publish your provider](/terraform/registry/providers/publishing) to the Terraform Registry. + +## Combine or Translate + +- [Combine your provider with other SDKv2 providers](/terraform/plugin/mux/combining-protocol-version-5-providers) using [protocol version 5](/terraform/plugin/how-terraform-works#protocol-version-5). +- [Combine your provider with other framework providers](/terraform/plugin/mux/combining-protocol-version-6-providers) using [protocol version 6](/terraform/plugin/how-terraform-works#protocol-version-6). diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/internals/index.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/internals/index.mdx new file mode 100644 index 0000000000..3212e2b718 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/internals/index.mdx @@ -0,0 +1,16 @@ +--- +page_title: Framework internals +description: >- + The Terraform plugin framework is a set of libraries implemented in Go. + Learn about the internal implementation details of the framework. +--- + +# Framework internals + +The following information describes some internals of the Terraform Plugin Framework in order to provide a more in-depth view of specific aspects of Framework behaviour. + +## RPCs + +Terraform core issues RPCs when Terraform commands are executed resulting in the subsequent execution of methods defined within providers. + +Refer to [RPCs](/terraform/plugin/framework/internals/rpcs) for details of the [sequence of RPCs](/terraform/plugin/terraform-plugin-protocol#rpcs-and-terraform-commands) that are issued when Terraform commands are executed and Framework functionality that is called as a consequence. diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/internals/rpcs.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/internals/rpcs.mdx new file mode 100644 index 0000000000..4e12a18fee --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/internals/rpcs.mdx @@ -0,0 +1,214 @@ +--- +page_title: Framework RPCs +description: >- + Learn how Terraform uses RPCs to support provider functionality. +--- + +# RPCs and framework functionality + +The correlation between the Terraform command, the RPCs that are issued and the Terraform plugin framework methods that are called is as follows: + +## _terraform validate_ + +| Protocol RPCs | Framework Functionality | +|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| [GetProviderSchema](#getproviderschema-rpc) | `provider.Provider`, `resource.Resource`, and `datasource.DataSource` interface `Schema` method | +| [ValidateProviderConfig](#validateproviderconfig-rpc) / [ValidateResourceConfig](#validateresourceconfig-rpc) / [ValidateDataResourceConfig](#validatedataresourceconfig-rpc) | `provider.Provider`, `resource.Resource`, and `datasource.DataSource` interface `Schema` method validators, `ConfigValidators` method, and `ValidateConfig` method | + +## _terraform plan_ + +| Protocol RPCs | Framework Functionality | +|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| [GetProviderSchema](#getproviderschema-rpc) | `provider.Provider`, `resource.Resource`, and `datasource.DataSource` interface `Schema` method | +| [ValidateProviderConfig](#validateproviderconfig-rpc) / [ValidateResourceConfig](#validateresourceconfig-rpc) / [ValidateDataResourceConfig](#validatedataresourceconfig-rpc) | `provider.Provider`, `resource.Resource`, and `datasource.DataSource` interface `Schema` method validators, `ConfigValidators` method, and `ValidateConfig` method | +| [ConfigureProvider](#configureprovider-rpc) | `provider.Provider` interface `Configure` method | +| [ReadResource](#readresource-rpc) / [ReadDataSource](#readdatasource-rpc) | `resource.Resource` and `datasource.DataSource` interface `Read` method | +| [PlanResourceChange](#planresourcechange-rpc) | `resource.Resource` interface `Schema` method plan modifiers and `ModifyPlan` method | + +## _terraform apply_ + +| Protocol RPCs | Framework Functionality | +|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| [GetProviderSchema](#getproviderschema-rpc) | `provider.Provider`, `resource.Resource`, and `datasource.DataSource` interface `Schema` method | +| [ValidateProviderConfig](#validateproviderconfig-rpc) / [ValidateResourceConfig](#validateresourceconfig-rpc) / [ValidateDataResourceConfig](#validatedataresourceconfig-rpc) | `provider.Provider`, `resource.Resource`, and `datasource.DataSource` interface `Schema` method validators, `ConfigValidators` method, and `ValidateConfig` method | +| [ConfigureProvider](#configureprovider-rpc) | `provider.Provider` interface `Configure` method | +| [ReadResource](#readresource-rpc) / [ReadDataSource](#readdatasource-rpc) | `resource.Resource` and `datasource.DataSource` interface `Read` method | +| [PlanResourceChange](#planresourcechange-rpc) | `resource.Resource` interface `Schema` method plan modifiers and `ModifyPlan` method | +| [ApplyResourceChange](#applyresourcechange-rpc) | `resource.Resource` interface `Create`, `Update`, or `Delete` method | + + +## GetProviderSchema RPC + +### Summary + +![diagram: GetProviderSchema RPC Overview](/img/get-provider-schema-overview.png) + + +When _terraform validate | plan | apply_ are executed Terraform core issues the `GetProviderSchema` RPC. The RPC flows through the Terraform plugin framework and ultimately calls the `Schema` function on the provider and on each of the resources and data sources that the provider defines. + +### Detail + +![diagram: GetProviderSchema RPC Detail](/img/get-provider-schema-detail.png) + + +Within the Terraform plugin framework, `terraform-plugin-go` is used to expose gRPC endpoints defined by the Terraform plugin protocol. These endpoints implement the `tfplugin6.ProviderServer` interface for version 6 of the Terraform plugin protocol. The `terraform-plugin-go` module also defines a `tfprotov6.ProviderServer` interface which is implemented by the `terraform-plugin-framework` module, and includes the `GetProviderSchema` function. + +The `terraform-plugin-framework` module implements the `tfprotov6.ProviderServer` interface `GetProviderSchema` function from the `terraform-plugin-go` module in `proto6server.GetProviderSchema`. The `proto6server.GetProviderSchema` function calls `fwserver.GetProviderSchema` which then calls `fwserver.ProviderSchema`, `fwserver.ResourceSchemas` and `fwserver.DataSourceSchemas` functions. The `terraform-plugin-framework` module defines `provider.Provider`, `resource.Resource` and `datasource.DataSource` interfaces for providers, resources and data sources, respectively. Each of these interfaces include a `Schema` function which is implemented by the Terraform provider code written by the provider developer. + +In summary, the schemas for the provider and each of the resources and data sources are defined by the provider developer through implementation of the `Schema` function defined on the `provider.Provider`, `resource.Resource` and `datasource.DataSource` interfaces, respectively. For the `GetProviderSchema` RPC, the implementation of the `Schema` function in the `provider.Provider`, `resource.Resource` and `datasource.DataSource` interfaces represents the "touch-point" for where the RPC sent from Terraform core interacts with the code written by the provider developer. + +## ValidateConfig RPCs + +### Summary + +![diagram: ValidateConfig RPCs Overview](/img/validate-config-overview.png) + + +When _terraform validate | plan | apply_ are executed if the Terraform configuration contains configuration for the provider then Terraform core issues the `ValidateProviderConfig` RPC. Additionally, the `ValidateResourceConfig` and `ValidateDataResourceConfig` RPCs are issued for each of the resources and data sources that appear in the Terraform configuration. There is a 1:1 match between the schema returned from the `GetProviderSchema` RPC and the `ValidateConfig` RPCs. + +The `ValidateConfig` RPCs flow through the Terraform plugin framework and ultimately call the `Validate` function on each of the `ConfigValidators`, the `ValidateConfig` function on the provider, resource or data source, and each of the `Validate` functions defined on each of the attributes and blocks within the provider, resource or data source schema. + +The `ValidateResourceConfig` and `ValidateDataResourceConfig` RPCs additionally call `resource.Configure` and `datasource.Configure`, respectively. + +### Detail + +#### ValidateProviderConfig RPC + +![diagram: ValidateProviderConfig RPC Detail](/img/validate-provider-config-detail.png) + + +#### ValidateResourceConfig RPC + +![diagram: ValidateResourceConfig RPC Detail](/img/validate-resource-config-detail.png) + + +#### ValidateDataResourceConfig RPC + +![diagram: ValidateDataResourceConfig RPC Detail](/img/validate-data-resource-config-detail.png) + + +Within the Terraform plugin framework, `terraform-plugin-go` is used to expose gRPC endpoints defined by the Terraform plugin protocol. These endpoints implement the `tfplugin6.ProviderServer` interface for version 6 of the Terraform plugin protocol. The `terraform-plugin-go` module also defines `tfprotov6.Server` interfaces which are implemented within the `terraform-plugin-framework` module. + +The `terraform-plugin-framework` module contains `proto6server.ValidateConfig` functions which are implementations of the `tfprotov6.Server` interface `ValidateConfig` functions, respectively. Each of these functions then call `fwserver.ValidateConfig` functions, respectively. + +If the provider, resource or data source implements the `.WithConfigValidators` interface defined in the `terraform-plugin-framework` module, the `.ConfigValidators` function is called to retrieve a slice of `.ConfigValidator`, and then `.Validate` is called on each element in the slice sequentially. + +If the provider, resource or data source implements the `.WithValidateConfig` interface, the `.ValidateConfig` function is called. + +The `fwserver.SchemaValidate` function is then called which iterates over each of the attributes and blocks defined within the provider, resource or data source schema and calls the `validator.Validate` for each of the validators defined on the attribute or block. + +In summary: +- The `.ConfigValidators` and `.Validate` functions are optionally defined by the provider developer through implementation of the `.WithConfigValidators` interfaces. +- The `.ValidateConfig` functions are optionally defined by the provider developer through implementation of the `.WithValidateConfig` interfaces. +- The attribute and block validators are optionally specified by the provider developer by adding a type-specific slice of validators to the attribute or block (e.g., []validator.String{...} on a schema.StringAttribute). + +## ConfigureProvider RPC + +### Summary + +![diagram: ConfigureProvider RPC Overview](/img/configure-provider-overview.png) + + +When _terraform plan | apply_ are executed Terraform core issues the `ConfigureProvider` RPC. + +The `ConfigureProvider` RPC flows through the Terraform plugin framework and ultimately calls the `Configure` function on the provider. + +### Detail + +![diagram: ConfigureProvider RPC Detail](/img/configure-provider-detail.png) + + +Within the Terraform plugin framework, `terraform-plugin-go` is used to expose gRPC endpoints defined by the Terraform plugin protocol. These endpoints implement the `tfplugin6.ProviderServer` interface for version 6 of the Terraform plugin protocol. The `terraform-plugin-go` module also defines the `tfprotov6.ProviderServer` interface which is implemented within the `terraform-plugin-framework` module. + +The `terraform-plugin-framework` module contains a `proto6server.ConfigureProvider` function which is an implementation of the `tfprotov6.ProviderServer` interface `ConfigureProvider` function. The `proto6server.ConfigureProvider` function calls the `fwserver.ConfigureProvider` function. The `terraform-plugin-framework` defines the `provider.Provider` interface which contains a `Configure` function. The `Configure` function is implemented by the provider developer, and this function is called by the `fwserver.ConfigureProvider` function. + +In summary, the `provider.Provider` interface defines a `Configure` function which must be defined by the provider developer. + +## Read RPCs + +### Summary + +![diagram: Read RPC Overview](/img/read-overview.png) + + +When _terraform plan | apply_ are executed Terraform core issues the `ReadResource` and `ReadDataSource` RPCs. Note that the `ReadResource` RPC is only issued when a resource already exists in state. + +The `ReadResource` and `ReadDataSource` RPCs flow through the Terraform plugin framework and ultimately call the `Read` function on the resource and data source, respectively. + +### Detail + +#### ReadResource RPC + +![diagram: ReadResource RPC Detail](/img/read-resource-detail.png) + + +#### ReadDataSource RPC + +![diagram: ReadDataSource RPC Detail](/img/read-data-source-detail.png) + + +Within the Terraform plugin framework, `terraform-plugin-go` is used to expose gRPC endpoints defined by the Terraform plugin protocol. These endpoints implement the `tfplugin6.ProviderServer` interface for version 6 of the Terraform plugin protocol. The `terraform-plugin-go` module also defines the `tfprotov6.ProviderServer` interface which encompasses the `tfprotov6.ResourceServer` and `tfprotov6.DataSourceServer` interfaces. The `tfprotov6.ProviderServer` interface is implemented within the `terraform-plugin-framework` module. + +The `terraform-plugin-framework` module contains a `proto6server.ReadResource` function which is an implementation of the `tfprotov6.ResourceServer` interface `ReadResource` function. The `proto6server.ReadResource` function calls the `fwserver.ReadResource` function. The `terraform-plugin-framework` defines the `resource.ResourceWithConfigure` interface which contains a `Configure` function. If the resource implements the `resource.ResourceWithConfigure` interface then the `Configure` function that has been implemented by the provider developer is called. The `terraform-plugin-framework` defines the `resource.Resource` interface which contains a `Read` function which is called by the `fwserver.ReadResource` function. + +The `terraform-plugin-framework` module contains a `proto6server.ReadDataSource` function which is an implementation of the `tfprotov6.DataSourceServer` interface `ReadDataSource` function. The `proto6server.ReadDataSource` function calls the `fwserver.ReadDataSource` function. The `terraform-plugin-framework` defines the `datasource.DataSourceWithConfigure` interface which contains a `Configure` function. If the data source implements the `datasource.DataSourceWithConfigure` interface then the `Configure` function that has been implemented by the provider developer is called. The `terraform-plugin-framework` defines the `datasource.DataSource` interface which contains a `Read` function which is called by the `fwserver.ReadDataSource` function. + +In summary, the `resource.Resource` interface defines a `Read` function which is called by the `ReadResource` RPC and the `datasource.DataSource` interface defines a `Read` function which is called by the `ReadDataSource` RPC. All resources and data sources must have provider developer defined `Read` functions. + +## PlanResourceChange RPC + +### Summary + +![diagram: PlanResourceChange RPC Overview](/img/plan-resource-change-overview.png) + + +When _terraform plan | apply_ are executed Terraform core issues the `PlanResourceChange` RPC. Note that the `PlanResourceChange` RPC is only issued when a resource exists in configuration and/or a resource already exists in state. + +The `PlanResourceChange` RPC flows through the Terraform plugin framework and ultimately calls each of the `PlanModify` functions on each of the attributes and blocks within the resource schema and `ModifyPlan` on the resource if the `resource.ResourceWithModifyPlan` interface has been implemented. + +The `PlanResourceChange` RPC also calls `resource.Configure` if the `resource.ResourceWithConfigure` interface has been implemented. + +### Detail + +![diagram: PlanResourceChange RPC Detail](/img/plan-resource-change-detail.png) + + +Within the Terraform plugin framework, `terraform-plugin-go` is used to expose gRPC endpoints defined by the Terraform plugin protocol. These endpoints implement the `tfplugin6.ProviderServer` interface for version 6 of the Terraform plugin protocol. The `terraform-plugin-go` module also defines the `tfprotov6.ResourceServer` interface which is encompassed by the `tfplugin6.ProviderServer` interface. + +The `terraform-plugin-framework` module contains the `proto6server.PlanResourceChange` function which is an implementation of the `tfprotov6.ResourceServer` interface `PlanResourceChange` function. The `PlanResourceChange` function calls the `fwserver.PlanResourceChange` function. + +If the resource implements the `resource.ResourceWithConfigure` interface, then the `resource.Configure` function defined by the provider developer is called. + +All attributes that are null in the configuration are then marked as unknown in the plan so that a provider has the opportunity to update them. The `fwserver.SchemaModifyPlan` function is then called which iterates over each of the attributes and blocks defined within the resource schema and calls `planmodifier.PlanModify` for each of the plan modifiers defined on the attribute or block. + +If the resource implements the `resource.ResourceWithModifyPlan` interface then the provider developer defined `resource.ModifyPlan` function is called. + +In summary, `resource.Configure` is called on the resource if it implements the `resource.ResourceWithConfigure` interface. The `PlanModify` functions on all the plan modifiers defined on each of the attributes and blocks within the resource schema are executed. The `resource.ModifyPlan` function is called on the resource if it implements the `resource.ResourceWithModifyPlan` interface. + +## ApplyResourceChange RPC + +### Summary + +![diagram: ApplyResourceChange RPC Overview](/img/apply-resource-change-overview.png) + + +When terraform apply is executed Terraform core issues the `ApplyResourceChange` RPC. Note that the `ApplyResourceChange RPC` is only issued when a resource exists in configuration and/or a resource already exists in state. + +The `ApplyResourceChange` RPC flows through the Terraform plugin framework and ultimately calls either `resource.Create`, `resource.Update` or `resource.Delete` on the resource depending upon the contents of the state and the plan. + +The `ApplyResourceChange` RPC also calls `resource.Configure` method if the `resource.ResourceWithConfigure` interface has been implemented. + +### Detail + +![diagram: ApplyResourceChange RPC Detail](/img/apply-resource-change-detail.png) + + +Within the Terraform plugin framework, `terraform-plugin-go` is used to expose gRPC endpoints defined by the Terraform plugin protocol. These endpoints implement the `tfplugin6.ProviderServer` interface for version 6 of the Terraform plugin protocol. The `terraform-plugin-go` module also defines the `tfprotov6.ResourceServer` interface which is encompassed by the `tfplugin6.ProviderServer` interface. + +The `terraform-plugin-framework` module contains the `proto6server.ApplyResourceChange` function which is an implementation of the `tfprotov6.ResourceServer` interface `ApplyResourceChange` function. The `ApplyResourceChange` function calls the `fwserver.ApplyResourceChange` function. + +If the resource implements the `resource.ResourceWithConfigure` interface, then the `resource.Configure` function defined by the provider developer is called. + +The `resource.Resource` interface defined within `terraform-plugin-framework` contains functions for `Create`, `Update` and `Delete`. The provider developer must implement `Create`, `Update` and `Delete` functions for each resource. Whether the `Create`, `Update` or `Delete` function on the provider is called by the `fwserver.ApplyResourceChange` function depends on the contents of the state and the plan. + +In summary, the `ApplyResourcePlan` RPC will call `resource.Configure` on the resource if the resource implements the `resource.ResourceWithConfigure` interface. One of the provider developer defined `Create`, `Update` and `Delete` functions will be called by the `ApplyResourcePlan` RPC depending upon the contents of the state and the plan. diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/migrating/attributes-blocks/attribute-schema.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/migrating/attributes-blocks/attribute-schema.mdx new file mode 100644 index 0000000000..4042f31d21 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/migrating/attributes-blocks/attribute-schema.mdx @@ -0,0 +1,97 @@ +--- +page_title: Migrating attribute schema +description: >- + Learn how to iteratively migrate from the SDKv2 to the plugin framework using + the terraform-plugin-mux Go library. +--- + +# Migrating attribute schema + +Attributes define how users can configure values for your Terraform provider, resources, and data sources. Refer to +[Schemas - Attributes](/terraform/plugin/framework/handling-data/schemas#attributes) in the Framework documentation for details. + +This page explains how to migrate an attribute from SDKv2 to the plugin Framework. + +## SDKv2 +In SDKv2, attributes are defined by the `Schema` field in the provider, resource, or data source schema. The `Schema` +field maps each attribute name (string) to the attribute's `schema.Schema` struct. Both resources and data sources are +defined using the `schema.Resource` struct. + +The following code shows a basic implementation of attribute schema for a provider in SDKv2. + +```go +func ProviderExample() *schema.Provider { + return &schema.Provider{ + Schema: map[string]*schema.Schema{ + /* ... */ +}, +``` + +In SDKv2, resource and data source attributes are defined the same way on their respective types. + +```go +func resourceExample() *schema.Resource { + return &schema.Resource{ + Schema: map[string]*schema.Schema{ + /* ... */ +``` +## Framework + +In the Framework, you define attributes by setting the `Attributes` field on your provider, resource, or data type's +schema, as returned by the `Schema` method. The `schema.Schema` type returned by `SchemaResponse` includes an +`Attributes` field that maps each attribute name (string) to the attribute's definition. + +The following code shows how to define an attribute for a resource with the Framework. + +```go +func (r *ThingResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "example": /* ... */ +``` + +## Migration Notes + +Remember the following differences between SDKv2 and the Framework when completing the migration. + +- In SDKv2, attributes are defined by a map from attribute names to `schema.Schema` structs in the `Schema` field of +your resource's schema. In the Framework, attributes are defined by a map from attribute names to `schema.Attribute` +implementations in your resource's schema, which is returned by the resource `Schema` method. +- In SDKv2, the computed string `id` attribute was implicitly included in the schema. In the Framework, it must be explicitly defined in the schema. +- There are several differences between the implementation of attributes in SDKv2 and the Framework. Refer to the other +pages in the Attributes & Blocks section of this migration guide for more details. + +## Example + +### SDKv2 + +The following example shows the implementation of the `example_attribute` attribute for the `exampleDataSource` +data source. + +```go +func exampleDataSource() *schema.Resource { + return &schema.Resource{ + /* ... */ + Schema: map[string]*schema.Schema{ + "example_attribute": { + Type: schema.TypeString, + Required: true, + }, +``` + +### Framework + +The following shows the same section of provider code after the migration. + +This code implements the `example_attribute` attribute for the `exampleDataSource` data source with the Framework. + +```go +func (d *exampleDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) { + resp.Schema = schema.Schema{ + /* ... */ + Attributes: map[string]schema.Attribute{ + "example_attribute": schema.StringAttribute{ + Required: true, + }, + /* ... */ +``` diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/migrating/attributes-blocks/blocks-computed.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/migrating/attributes-blocks/blocks-computed.mdx new file mode 100644 index 0000000000..1f4ee4bccd --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/migrating/attributes-blocks/blocks-computed.mdx @@ -0,0 +1,110 @@ +--- +page_title: Migrating computed blocks +description: >- + Learn how to igrate blocks with computed fields from SDKv2 to attribute + validators in the plugin framework. +--- + +# Migrating blocks with computed fields + +Some providers, resources, and data sources include repeatable nested blocks in their attributes. Some blocks contain +fields with `Computed: true`, which means that the provider code can define the value or that it could come from the +output of terraform apply (e.g., the ID of an EC2 instance). + +This page explains how to migrate computed-only blocks from SDKv2 to the Framework. Refer to +[Blocks](/terraform/plugin/framework/migrating/attributes-blocks/blocks) if you are looking for information about migrating blocks +that are practitioner configurable. + +## SDKv2 + +In SDKv2, blocks are defined by an attribute whose type is either `TypeList` or `TypeSet`, and whose `Elem` field is set to a +`schema.Resource` that contains a map of the block's attribute names to corresponding `schemaSchema` structs. + +```go +map[string]*schema.Schema{ + "example": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "nested_example": { + Type: schema.TypeString, + Computed: true, + /* ... */ +``` + +## Framework + +In the Framework, when working with protocol version 5, computed blocks are implemented using a `ListAttribute` which has an `ElementType` of `types.ObjectType`. + +```go +map[string]schema.Attribute{ +"example": schema.ListAttribute{ + Computed: true, + ElementType: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "nested_example": types.StringType, + /* ... */ + +``` + +In the Framework, when working with protocol version 6, we recommend that you define computed blocks using nested +attributes. This example shows usage of `ListNestedAttribute` as this provides configuration references with list index +syntax as is the case when using `schema.TypeList` in SDKv2. `SingleNestedAttribute` is a good choice for single +underlying objects which results in a breaking change but also allows dropping `[0]` in configuration references. + +```go +map[string]schema.Attribute{ +"example": schema.ListNestedAttribute{ + Computed: true, + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "nested_example": schema.StringAttribute{ + Computed: true, + /* ... */ + +``` + +## Migration Notes + +- When using protocol version 5 specify `ElementType` as `types.ObjectType` when migrating blocks that are computed from SDKv2 to Framework. +- When using protocol version 6, use [nested attributes](/terraform/plugin/framework/schemas#attributes-1) when migrating blocks that are computed from SDKv2 to Framework. + +## Example + +### SDKv2 + +The following example shows the implementation of the `example_nested_block` with SDKv2. + +```go +Schema: map[string]*schema.Schema{ +"example_nested_block": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "example_block_attribute": { + Type: schema.TypeString, + Computed: true, + /* ... */ + }, +``` + +### Framework + +The following shows the same section of provider code after the migration. + +This code defines the `example_nested_block` block as an attribute on the schema, where the +`types.ObjectType` is being used to define the nested block. + +```go +map[string]schema.Attribute{ + "example_nested_block": schema.ListAttribute{ + ElementType: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "example_block_attribute": types.StringType, + }, + }, + Computed: true, + /* ... */ +``` diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/migrating/attributes-blocks/blocks.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/migrating/attributes-blocks/blocks.mdx new file mode 100644 index 0000000000..353029dee4 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/migrating/attributes-blocks/blocks.mdx @@ -0,0 +1,159 @@ +--- +page_title: Migrating blocks +description: >- + Learn how to migrate blocks from SDKv2 to attribute validators in the plugin + framework. +--- + +# Migrating blocks + +Some providers, resources, and data sources include repeatable nested blocks in their attributes. These nested blocks +typically represent separate objects that are related to (or embedded within) the containing object. + +This page explains how to migrate nested blocks that are not computed (i.e., do not set +`Computed: true`) from SDKv2 to the Framework. Refer to +[Blocks with Computed Fields](/terraform/plugin/framework/migrating/attributes-blocks/blocks-computed) for more details +about migrating nested blocks that contain fields that are computed. + +The following table describes the mapping between [SDK Schema Fields](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-sdk/helper/schema#Schema) and the Framework. + +| SDK Schema Field | Framework | +|-----------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| Type | [ListNestedBlock](/terraform/plugin/framework/migrating/attributes-blocks/blocks), [SetNestedBlock](/terraform/plugin/framework/migrating/attributes-blocks/blocks) | +| ConfigMode | Schema must be explictly defined using [Attributes](/terraform/plugin/framework/migrating/attributes-blocks/attribute-schema) and [Blocks](/terraform/plugin/framework/migrating/attributes-blocks/blocks) | +| Required | [listvalidator.IsRequired](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators/listvalidator#IsRequired), [setvalidator.IsRequired](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators/setvalidator#IsRequired) | +| Optional | N/A - no implementation required | +| Computed | [Blocks with Computed Fields](/terraform/plugin/framework/migrating/attributes-blocks/blocks-computed) | +| ForceNew | [RequiresReplace](/terraform/plugin/framework/migrating/attributes-blocks/force-new) on `PlanModifiers` field on attribute within block or implementation of [ResourceWithModifyPlan](/terraform/plugin/framework/migrating/resources/plan-modification#framework) interface | +| DiffSuppressFunc | [PlanModifiers](/terraform/plugin/framework/migrating/resources/plan-modification#framework) field on attribute within block or implementation of [ResourceWithModifyPlan](/terraform/plugin/framework/migrating/resources/plan-modification#framework) interface | +| DiffSuppressOnRefresh | [Read](/terraform/plugin/framework/migrating/resources/crud) method on resource | +| Description | `Description` field on block | +| InputDefault | N/A - no longer valid | +| StateFunc | Requires implementation of bespoke logic before storing state, for instance in resource [Create method](/terraform/plugin/framework/migrating/resources/crud#framework-1) | +| Elem | `NestedObject` within block | +| MaxItems | Use [listValidator.SizeAtMost](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators/listvalidator#SizeAtMost) or [setvalidator.SizeAtMost](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators/setvalidator#SizeAtMost) on `Validators` field on `ListNestedBlock` or `SetNestedBlock` | +| MinItems | Use [listValidator.SizeAtLeast](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators/listvalidator#SizeAtLeast) or [setvalidator.SizeAtLeast](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators/setvalidator#SizeAtLeast) on `Validators` field on `ListNestedBlock` or `SetNestedBlock` | +| Set | N/A - no implementation required | | +| ComputedWhen | N/A - no longer valid | +| ConflictsWith | [Predefined Validators](/terraform/plugin/framework/migrating/attributes-blocks/validators-predefined) | +| ExactlyOneOf | [Predefined Validators](/terraform/plugin/framework/migrating/attributes-blocks/validators-predefined) | +| AtLeastOneOf | [Predefined Validators](/terraform/plugin/framework/migrating/attributes-blocks/validators-predefined) | +| RequiredWith | [Predefined Validators](/terraform/plugin/framework/migrating/attributes-blocks/validators-predefined) | +| Deprecated | `DeprecationMessage` field on attribute within block | +| ValidateFunc | [Predefined Validators](/terraform/plugin/framework/migrating/attributes-blocks/validators-predefined) and [Custom Validators](/terraform/plugin/framework/migrating/attributes-blocks/validators-custom) | +| ValidateDiagFunc | [Predefined Validators](/terraform/plugin/framework/migrating/attributes-blocks/validators-predefined) and [Custom Validators](/terraform/plugin/framework/migrating/attributes-blocks/validators-custom) | +| Sensitive | N/A - only supported on attributes | + + +## Nested Block Example + +The following example shows a nested block in Terraform resource configuration. The `subject` nested +block within the `tls_cert_request` resource configures the subject of a certificate request with the `common_name` and +`organization` attributes. + +```hcl +resource "tls_cert_request" "example" { + private_key_pem = file("private_key.pem") + + subject { + common_name = "example.com" + organization = "ACME Examples, Inc" + } +} +``` + + +## SDKv2 + +In SDKv2, blocks are defined by an attribute whose type is `TypeList` or `TypeSet` and whose `Elem` field is set to a +`schema.Resource` that contains a map of the block's attribute names to corresponding `schemaSchema` structs. + +```go +func resourceExample() *schema.Resource { + return &schema.Resource{ + /* ... */ + map[string]*schema.Schema{ + "example" = &schema.Schema{ + Type: schema.TypeList, + Optional: bool, + MaxItems: int, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "nested_example": { + Type: schema.TypeString, + Optional: bool, + /* ... */ +``` + +## Framework + +In the Framework, you implement nested blocks with the `Blocks` field of your provider, resource, or data source's +schema, as returned by the `Schema` method. The `Blocks` field maps the name of each block to a +`schema.Block` definition. + +```go +func (r *ThingResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + /* ... */ + Blocks: map[string]schema.Block{ + "example": schema.ListNestedBlock{ + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "nested_example": schema.StringAttribute{ + Optional: bool + /* ... */ +``` + +## Example + +### SDKv2 + +The following example shows the implementation of the `example_nested_block` nested block with SDKv2. + +```go +map[string]*schema.Schema{ + "example_attribute": &schema.Schema{ + Type: schema.TypeString, + /* ... */ + + "example_nested_block" = &schema.Schema{ + Type: schema.TypeList, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "example_block_attribute_one": { + Type: schema.TypeString, + /* ... */ + }, + "example_block_attribute_two": { + Type: schema.TypeString, + /* ... */ + }, + /* ... */ +``` + +### Framework + +The following example shows how the nested `example_nested_block` block +is defined with the Framework after the migration. + +```go +schema.Schema{ + Attributes: map[string]schema.Attribute{ + "example_attribute": schema.StringAttribute{ + /* ... */ + + Blocks: map[string]schema.Block{ + "example_nested_block": schema.ListNestedBlock{ + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "example_block_attribute_one": schema.StringAttribute{ + /* ... */ + }, + "example_block_attribute_two": schema.StringAttribute{ + /* ... */ + }, + Validators: []validator.List{ + listvalidator.SizeAtMost(1), + }, +``` diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/migrating/attributes-blocks/default-values.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/migrating/attributes-blocks/default-values.mdx new file mode 100644 index 0000000000..f358d8d621 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/migrating/attributes-blocks/default-values.mdx @@ -0,0 +1,105 @@ +--- +page_title: Migrating attribute default values +description: >- + Learn how to migrate attribute default values from SDKv2 by using an + attribute plan modifier in the plugin framework. +--- + +# Migrating attribute default values + +Default values support is only available in the Framework for resources. Handle default values for data source attributes within the [data source `Read` method](/terraform/plugin/framework/data-sources#read-method) and default values for provider attributes within the [provider `Configure` method](/terraform/plugin/framework/providers#configure-method). + +Default values set a value for an attribute when the Terraform configuration does not provide one. In SDKv2 and the +Framework default values are set via the `Default` field on an attribute's schema. +Refer to +[Default](/terraform/plugin/framework/resources/default) +in the Framework documentation for details. + +This page explains how to migrate attribute defaults in SDKv2 to the Framework. + +## SDKv2 + +In SDKv2, default values are defined for a primitive attribute type (i.e., `TypeBool`, `TypeFloat`, `TypeInt`, +`TypeString`) by the `Default` field on the attribute's schema. Alternatively, the `DefaultFunc` function is used to +compute a default value for an attribute. + +The following code shows a basic implementation of a default value for a primitive attribute type in SDKv2. + +```go +func resourceExample() *schema.Resource { + return &schema.Resource{ + /* ... */ + Schema: map[string]*schema.Schema{ + "attribute_example": { + Default: 2048, + /* ... */ + }, + /* ... */ +``` + +## Framework + +In the Framework, you set default values with the `Default` field on your attribute's definition. + +```go +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault" +) + +func (r *resourceExample) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + /* ... */ + Attributes: map[string]schema.Attribute{ + "attribute_example": schema.BoolAttribute{ + Default: booldefault.StaticBool(true), + /* ... */ +``` + +## Migration Notes + +Remember the following differences between SDKv2 and the Framework when completing the migration. + +- In SDKv2, default values are set with the `Default` or `DefaultFunc` fields on an attribute's `schema.Schema` struct. +In the Framework, you must assign set the `Default` field on an attribute to set a default value. + +## Example + +### SDKv2 + +The following example shows the implementation of the `Default` field for the +`example_attribute` attribute on the `exampleResource` resource with SDKv2. + +```go +func exampleResource() *schema.Resource { + return &schema.Resource{ + Schema: map[string]*schema.Schema{ + "example_attribute": { + Default: 2048, + /* ... */ + }, + /* ... */ +``` + +### Framework + +The following shows the same section of code after the migration. + +This code implements the `PlanModifiers` field for the `example_attribute` attribute with the Framework. + +```go +func (r *exampleResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "example_attribute": schema.Int64Attribute{ + Default: int64default.StaticInt64(2048) + /* ... */ + }, + /* ... */ + }, + }, nil +} +``` diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/migrating/attributes-blocks/fields.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/migrating/attributes-blocks/fields.mdx new file mode 100644 index 0000000000..6efe29b7ac --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/migrating/attributes-blocks/fields.mdx @@ -0,0 +1,114 @@ +--- +page_title: Migrating attribute fields +description: >- + Learn how to migrate attribute required, optional, computed, and sensitive + fields from SDKv2 to the plugin framework. +--- + +# Migrating attribute fields + +A subset of attribute fields, such as required, optional, computed, or sensitive, define attribute behavior as boolean flags. Refer to +[Schemas - Attributes](/terraform/plugin/framework/handling-data/schemas#required) in the Framework documentation for details. + +The following table describes the mapping between [SDK Schema Fields](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-sdk/helper/schema#Schema) and the Framework. + +| SDK Schema Field | Framework | +|-----------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| Type | [Attribute Types](/terraform/plugin/framework/migrating/attributes-blocks/types) | +| ConfigMode | Schema must be explictly defined using [Attributes](/terraform/plugin/framework/migrating/attributes-blocks/attribute-schema) and [Blocks](/terraform/plugin/framework/migrating/attributes-blocks/blocks) | +| Required | `Required` field on attribute | +| Optional | `Optional` field on attribute | +| Computed | `Computed` field on attribute | +| ForceNew | [RequiresReplace](/terraform/plugin/framework/migrating/attributes-blocks/force-new) on `PlanModifiers` field on attribute or implementation of [ResourceWithModifyPlan](/terraform/plugin/framework/migrating/resources/plan-modification#framework) interface | +| DiffSuppressFunc | [Custom Types](/terraform/plugin/framework/handling-data/types/custom), [PlanModifiers](/terraform/plugin/framework/migrating/resources/plan-modification#framework) field on attribute, or implementation of [ResourceWithModifyPlan](/terraform/plugin/framework/migrating/resources/plan-modification#framework) interface | +| DiffSuppressOnRefresh | [Custom Types](/terraform/plugin/framework/handling-data/types/custom) semantic equality logic or manual logic in [Read](/terraform/plugin/framework/migrating/resources/crud) method on resource | +| Default | `Default` field on attribute using one of the predefined [Defaults](/terraform/plugin/framework/resources/default#common-use-case-attribute-defaults) or implementing one of the [`schema` package](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/defaults) interfaces | +| DefaultFunc | `Default` field on attribute using one of the predefined [Defaults](/terraform/plugin/framework/resources/default#common-use-case-attribute-defaults) or implementing one of the [`schema` package](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/defaults) interfaces | +| Description | `Description` field on attribute | +| InputDefault | N/A - no longer valid | +| StateFunc | Requires implementation of bespoke logic before storing state, for instance in resource [Create method](/terraform/plugin/framework/migrating/resources/crud#framework-1) | +| Elem | `ElementType` on [ListAttribute](/terraform/plugin/framework/migrating/attributes-blocks/types), [MapAttribute](/terraform/plugin/framework/migrating/attributes-blocks/types) or [SetAttribute](/terraform/plugin/framework/migrating/attributes-blocks/types). Refer to [Blocks](/terraform/plugin/framework/migrating/attributes-blocks/blocks) if `schema.Resource` is present in `Elem`. | +| MaxItems | Use [listValidator.SizeAtMost](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators/listvalidator#SizeAtMost), [mapvalidator.SizeAtMost](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators/mapvalidator#SizeAtMost) or [setvalidator.SizeAtMost](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators/setvalidator#SizeAtMost) on `Validators` field on list, map or set attribute | +| MinItems | Use [listValidator.SizeAtLeast](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators/listvalidator#SizeAtLeast), [mapvalidator.SizeAtLeast](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators/mapvalidator#SizeAtLeast) or [setvalidator.SizeAtLeast](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators/setvalidator#SizeAtLeast) on `Validators` field on list, map or set attribute | +| Set | N/A - no implementation required | | +| ComputedWhen | N/A - no longer valid | +| ConflictsWith | [Predefined Validators](/terraform/plugin/framework/migrating/attributes-blocks/validators-predefined) | +| ExactlyOneOf | [Predefined Validators](/terraform/plugin/framework/migrating/attributes-blocks/validators-predefined) | +| AtLeastOneOf | [Predefined Validators](/terraform/plugin/framework/migrating/attributes-blocks/validators-predefined) | +| RequiredWith | [Predefined Validators](/terraform/plugin/framework/migrating/attributes-blocks/validators-predefined) | +| Deprecated | `DeprecationMessage` field on attribute | +| ValidateFunc | [Predefined Validators](/terraform/plugin/framework/migrating/attributes-blocks/validators-predefined), [Custom Validators](/terraform/plugin/framework/migrating/attributes-blocks/validators-custom), or [Custom Types](/terraform/plugin/framework/handling-data/types/custom) validation logic | +| ValidateDiagFunc | [Predefined Validators](/terraform/plugin/framework/migrating/attributes-blocks/validators-predefined), [Custom Validators](/terraform/plugin/framework/migrating/attributes-blocks/validators-custom), or [Custom Types](/terraform/plugin/framework/handling-data/types/custom) validation logic | +| Sensitive | `Sensitive` field on attribute | + + +This page explains how to migrate the required, optional, computed, and sensitive attribute fields from SDKv2 to the +Framework. + +## SDKv2 + +In SDKv2, `Required`, `Optional`, `Computed`, and `Sensitive` are boolean fields on the attribute's schema. + +```go +func resourceExample() *schema.Resource { + return &schema.Resource{ + /* ... */ + Schema: map[string]*schema.Schema{ + "attribute_example": { + Required: bool + Optional: bool + Computed: bool + Sensitive: bool + /* ... */ +``` + +## Framework + +In the Framework, you set the same fields on the `schema.Attribute` implementation, with the same behavior. + +```go +func (r *ThingResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + /* ... */ + Attributes: map[string]schema.Attribute{ + "attribute_example": schema.XXXAttribute{ + Required: bool + Optional: bool + Computed: bool + Sensitive: bool + /* ... */ +``` + +## Example + +### SDKv2 + +The following example shows how the `example_attribute` attribute on the `exampleDataSource` data source is set to +be required with SDKv2. + +```go +func exampleDataSource() *schema.Resource { + return &schema.Resource{ + Schema: map[string]*schema.Schema{ + "example_attribute": { + Required: true, + /* ... */ + }, + /* ... */ +``` + +### Framework + +The following example shows how the `example_attribute` attribute on the `exampleDataSource` data source is set +to be required with the Framework. + +```go +func (d *exampleDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "example_attribute": schema.StringAttribute{ + Required: true, + /* ... */ + }, + /* ... */ +``` diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/migrating/attributes-blocks/force-new.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/migrating/attributes-blocks/force-new.mdx new file mode 100644 index 0000000000..1a119eff63 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/migrating/attributes-blocks/force-new.mdx @@ -0,0 +1,104 @@ +--- +page_title: Migrating attribute ForceNew triggers +description: >- + Learn how to migrate attribute ForceNew triggers in SDKv2 to attribute plan + modifiers in the framework. +--- + +# Migrating attribute ForceNew triggers + +In Terraform, sometimes a resource must be replaced when the value of an attribute changes. In SDKv2, this is +accomplished via the `ForceNew` field. In the Framework, you implement the same behavior via a `RequiresReplace` plan +modifier. Refer to +[Plan Modification - Attribute Plan Modification](/terraform/plugin/framework/resources/plan-modification#attribute-plan-modification) +in the Framework documentation for details. + +This page explains how to migrate this behavior from SDKv2 to the Framework. + +## SDKv2 + +In SDKv2, setting the `ForceNew` field on an attribute's `schema.Schema` triggers a replace (i.e., a destroy-create +cycle) whenever the attribute's value is changed. + +```go +func resourceExample() *schema.Resource { + return &schema.Resource{ + /* ... */ + Schema: map[string]*schema.Schema{ + "attribute_example": { + ForceNew: true + /* ... */ +``` + +## Framework + +In the Framework, you implement the same behavior by using the `resource.RequiresReplace` plan modifier on your +attribute's `schema.Attribute` implementation. + +```go +func (r *ThingResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + /* ... */ + Attributes: map[string]schema.Attribute{ + "attribute_example": schema.StringAttribute{ + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + /* ... */ +``` + +## Migration Notes + +Remember the following differences between SDKv2 and the Framework when completing the migration. + +- In both SDKv2 and Framework, `ForceNew` and `RequiresReplace`, respectively, only trigger a replace if the attribute +is not computed. In the Framework, if an attribute which is computed requires that the resource be replaced when it is +changed, implement a plan modifier that triggers the replacement. Refer to +[RequiresReplacePlanModifier](https://github.com/hashicorp/terraform-provider-random/blob/v3.4.1/internal/planmodifiers/attribute.go#L63) +for an example, but bear in mind that each implementation requires different logic and you may need to detect whether +the plan has already been modified. + +## Example + +### SDKv2 + +The following example shows the implementation of the `ForceNew` field of the +`exampleResource` resource's `example_attribute` attribute with SDKv2. + +```go +func exampleResource() *schema.Resource { + return &schema.Resource{ + Schema: map[string]*schema.Schema{ + "example_attribute": { + ForceNew: true, + Type: schema.TypeMap, + /* ... */ + }, + /* ... */ + }, + /* ... */ + } +} +``` + +### Framework + +The following shows the same section of provider code after the migration. + +This code forces the replacement of a `exampleResource` resource when the value of the `example_attribute` attribute is changed. +The example does this using the `PlanModifiers` field within the `exampleResource` attribute's schema. + +```go +func (r *exampleResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "example_attribute": schema.MapAttribute{ + PlanModifiers: []planmodifier.Map{ + mapplanmodifier.RequiresReplace(), + }, + /* ... */ + }, + /* ... */ + }, + } +} +``` diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/migrating/attributes-blocks/types.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/migrating/attributes-blocks/types.mdx new file mode 100644 index 0000000000..86d633fa74 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/migrating/attributes-blocks/types.mdx @@ -0,0 +1,176 @@ +--- +page_title: Migrating atrribute types +description: >- + Learn how to migrate attribute type from SDKv2 to the plugin Framework. +--- + +# Migrating attribute types + +An attribute either contains a primitive type, such as an integer or a string, or contains other attributes. Attributes +that contain other attributes are referred to as nested attributes. Refer to +[Schemas - Attributes](/terraform/plugin/framework/schemas#type) in the Framework documentation for details. + +This page explains how to migrate a primitive attribute from SDKv2 to the plugin Framework. For an example of +migrating a nested block to a nested attribute, refer to [Providers](/terraform/plugin/framework/migrating/providers#example-1) in +this guide. + +## SDKv2 + +In SDKv2, attribute types are defined by the `Type` field on the attribute's `schema.Schema` struct. + +```go +func resourceExample() *schema.Resource { + return &schema.Resource{ + /* ... */ + Schema: map[string]*schema.Schema{ + "bool_example": { + Type: schema.TypeBool, + /* ... */ + }, + "float64_example": { + Type: schema.TypeFloat, + /* ... */ + }, + "int64_example": { + Type: schema.TypeInt, + /* ... */ + }, + "list_example": { + Type: schema.TypeList, + Elem: &schema.Schema{ + Type: schema.TypeBool, + }, + /* ... */ + }, + "map_example": { + Type: schema.TypeMap, + Elem: &schema.Schema{ + Type: schema.TypeFloat, + }, + /* ... */ + }, + "set_example": { + Type: schema.TypeSet, + Elem: &schema.Schema{ + Type: schema.TypeInt, + }, + /* ... */ + }, + "string_example": { + Type: schema.TypeString, + /* ... */ + }, + /* ... */ +``` +## Framework + +In the Framework, you set your attribute's type with the attribute's `schema.Attribute` implementation. + +```go +func (r *ThingResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schea.Schema{ + /* ... */ + Attributes: map[string]schema.Attribute{ + "bool_example": schema.BoolAttribute{ + /* ... */ + }, + "float64_example": schema.Float64Attribute{ + /* ... */ + }, + "int64_example": schema.Int64Attribute{ + /* ... */ + }, + "list_example": schema.ListAttribute{ + ElementType: types.BoolType, + /* ... */ + }, + "map_example": schema.MapAttribute{ + ElementType: types.Float64Type, + /* ... */ + }, + "set_example": schema.SetAttribute{ + ElementType: types.Int64Type, + /* ... */ + }, + "string_example": schema.StringAttribute{ + /* ... */ + }, + /* ... */ +``` + +## Migration Notes + +Remember the following differences between SDKv2 and the Framework when completing the migration. + +- In the Framework, the `schema.Attribute` implementation determines the required details. + +## Examples + +### SDKv2 + +The following example shows the implementation of the type field of the `example_string_attribute` attribute +for the `exampleDataSource` data source with SDKv2. + +```go +func exampleDataSource() *schema.Resource { + return &schema.Resource{ + Schema: map[string]*schema.Schema{ + "example_string_attribute": { + Type: schema.TypeString, + /* ... */ + }, + /* ... */ +``` + +### Framework + +The following example shows how the type of the `example_string_attribute` attribute for the `exampleDataSource` data +source is defined with the Framework after the migration. + +```go +func (d *exampleDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "example_string_attribute": schema.StringAttribute{ + /* ... */ + }, + /* ... */ +``` + +### SDKv2 + +The following example shows the implementation of the type field of the `example_list_attribute` +attribute with SDKv2. + +```go +func exampleResource() *schema.Resource { + return &schema.Resource{ + Schema: map[string]*schema.Schema{ + "example_list_attribute": { + Type: schema.TypeList, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + /* ... */ + }, + /* ... */ +``` + +### Framework + +The following example shows how the type of the `example_list_attribute` attribute for the `exampleResource` resource +is defined with the Framework after the migration. + +```go +func (r *exampleResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "example_list_attribute": schema.ListAttribute{ + ElementType: types.StringType, + /* ... */ + }, + /* ... */ +``` + +Refer to [Terraform Concepts - Attributes](/terraform/plugin/framework/handling-data/terraform-concepts#attributes) +for further examples of different types of schema attributes in the Framework. diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/migrating/attributes-blocks/validators-custom.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/migrating/attributes-blocks/validators-custom.mdx new file mode 100644 index 0000000000..8c33f4c018 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/migrating/attributes-blocks/validators-custom.mdx @@ -0,0 +1,148 @@ +--- +page_title: Migrating attribute custom validators +description: >- + Learn how to migrate custom attribute validation functions from SDKv2 to + attribute validators in the Framework. Providers use custom validators to + check attribute values for required syntax, types, and acceptable values. +--- + +# Migrating attribute custom validators + +You can write custom validations that give users feedback about required syntax, types, and acceptable values in your +provider. The Framework has a collection of +[predefined validators](https://github.com/hashicorp/terraform-plugin-framework-validators). Refer to +[Predefined Validators](/terraform/plugin/framework/migrating/attributes-blocks/validators-predefined) to learn how to use them. + +This page explains how to migrate attribute validation functions from SDKv2 to attribute validators in the Framework. + +## SDKv2 + +In SDKv2, arbitrary validation logic can be applied to individual attributes by using `ValidateFunc` and/or +`ValidateDiagFunc`. + +The following example shows the implementation of a validation that ensures that an integer attribute has a value +greater than one. + +```go +func resourceExample() *schema.Resource { + return &schema.Resource{ + /* ... */ + Schema: map[string]*schema.Schema{ + "attribute_example": { + ValidateDiagFunc: validation.ToDiagFunc(validation.IntAtLeast(1)), + /* ... */ +``` + +## Framework + +In the Framework, you implement either type of validation by setting the `Validators` field on the `schema.Attribute` +implementation. + +The following example shows how to implement a validation that ensures that an integer attribute has a value +greater than one. + +```go +func (r *ThingResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + /* ... */ + Attributes: map[string]schema.Attribute{ + "attribute_example": schema.Int64Attribute{ + Validators: []validator.Int64{ + int64validator.AtLeast(1), + /* ... */ +``` + +## Migration Notes + +Remember the following details when migrating from SDKv2 to the Framework. + +- In SDKv2, `ValidateDiagFunc` is a field on `schema.Schema` that you can use to define custom validation functions. In +the Framework, `Validators` is a field on each `schema.Attribute` implementation that can be used for custom validations. +- Use [predefined validators](/terraform/plugin/framework/migrating/attributes-blocks/validators-predefined) when there is a validator that meets +your requirements. + +## Example + +### SDKv2 + +The following example shows the implementation of the `ValidateDiagFunc` field for +the `exampleResource`'s `example_attribute` attribute to validate that it's value is at least 1 (greater than zero). + +```go +func exampleResource() *schema.Resource { + return &schema.Resource{ + Schema: map[string]*schema.Schema{ + "example_attribute": { + ValidateDiagFunc: validation.ToDiagFunc(validation.IntAtLeast(1)), + }, + }, + } +} +``` + +### Framework + +The following shows the same section of provider code after the migration. + +This code validates that the `exampleResource`'s `example_attribute` attribute is greater than zero by using a custom `AtLeast` +validator. + +```go +func (r *exampleResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "example_attribute": schema.Int64Attribute{ + Required: true, + Validators: []validator.Int64{ + int64validator.AtLeast(1), + }, + }, + }, + } +} +``` + +This example code illustrates how you can implement your own validators. + +```go +var _ validator.Int64 = atLeastValidator{} + +// atLeastValidator validates that an integer Attribute's value is at least a certain value. +type atLeastValidator struct { + min int64 +} + +// Description describes the validation in plain text formatting. +func (v atLeastValidator) Description(_ context.Context) string { + return fmt.Sprintf("value must be at least %d", v.min) +} + +// MarkdownDescription describes the validation in Markdown formatting. +func (v atLeastValidator) MarkdownDescription(ctx context.Context) string { + return v.Description(ctx) +} + +// Validate performs the validation. +func (v atLeastValidator) ValidateInt64(ctx context.Context, request validator.Int64Request, response *validator.Int64Response) { + if req.ConfigValue.Int64Value() < v.min { + response.Diagnostics.Append(validatordiag.InvalidAttributeValueDiagnostic( + request.Path, + v.Description(ctx), + fmt.Sprintf("%d", req.ConfigValue.Int64Value()), + )) + } +} + +// AtLeast returns an AttributeValidator which ensures that any configured +// attribute value: +// +// - Is a number, which can be represented by a 64-bit integer. +// - Is exclusively greater than the given minimum. +// +// Null (unconfigured) and unknown (known after apply) values are skipped. +func AtLeast(min int64) validator.Int64 { + return atLeastValidator{ + min: min, + } +} +``` diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/migrating/attributes-blocks/validators-predefined.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/migrating/attributes-blocks/validators-predefined.mdx new file mode 100644 index 0000000000..33c32dfd45 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/migrating/attributes-blocks/validators-predefined.mdx @@ -0,0 +1,193 @@ +--- +page_title: Migrating attribute predefined validators +description: >- + Learn how to migrate the predefined ConflictsWith, ExactlyOneOf, AtLeastOneOf + and RequiredWith validators from SDKv2 to the framework. Providers use + predefined validators to check attribute values for required syntax, types, + and acceptable values. +--- + +# Migrating predefined attribute validators + +Attribute validators ensure that attributes do or do not contain specific values. You can use predefined validators for +many use cases, or implement custom validators. Refer to [Schemas - Validators](/terraform/plugin/framework/handling-data/schemas#validators) in +the Framework documentation for details. Refer to the +[Attributes - Custom Validators](/terraform/plugin/framework/migrating/attributes-blocks/validators-custom) page in this guide to learn how to +implement custom validators. + +The following table describes the mapping between predefined validators in SDKv2 and the Framework. You should use an +attribute validator for [per-attribute validation](/terraform/plugin/framework/validation#attribute-validation), or a +[data source validator](/terraform/plugin/framework/data-sources/validate-configuration), +[provider validator](/terraform/plugin/framework/providers/validate-configuration) or +[resource validator](/terraform/plugin/framework/resources/validate-configuration) for declaring validation at the level of the +data source, provider or resource, respectively. + +| SDK Attribute Field | Framework Attribute Validator | Framework Data Source Validator | Framework Provider Validator | Framework Resource Validator | +|---------------------|---------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------| +| AtLeastOneOf | {TYPE}validator.AtLeastOneOf() | [datasourcevalidator.AtLeastOneOf()](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators/datasourcevalidator#AtLeastOneOf) | [providervalidator.AtLeastOneOf()](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators/providervalidator#AtLeastOneOf) | [resourcevalidator.AtLeastOneOf()](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators/resourcevalidator#AtLeastOneOf) | +| ConflictsWith | {TYPE}validator.ConflictsWith() | [datasourcevalidator.Conflicting()](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators/datasourcevalidator#Conflicting) | [providervalidator.Conflicting()](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators/providervalidator#Conflicting) | [resourcevalidator.Conflicting()](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators/resourcevalidator#Conflicting) | +| ExactlyOneOf | {TYPE}validator.ExactlyOneOf() | [datasourcevalidator.ExactlyOneOf()](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators/datasourcevalidator#ExactlyOneOf) | [providervalidator.ExactlyOneOf()](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators/providervalidator#ExactlyOneOf) | [resourcevalidator.ExactlyOneOf()](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators/resourcevalidator#ExactlyOneOf) | +| RequiredWith | {TYPE}validator.AlsoRequires() | [datasourcevalidator.RequiredTogether()](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators/datasourcevalidator#RequiredTogether) | [providervalidator.RequiredTogether()](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators/providervalidator#RequiredTogether) | [resourcevalidator.RequiredTogether()](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators/resourcevalidator#RequiredTogether) | + +This page explains how to migrate a predefined validator from SDKv2 to the Framework. + +## SDKv2 + +In SDKv2, the `ConflictsWith`, `ExactlyOneOf`, `AtLeastOneOf`, and `RequiredWith` fields on an attribute's +`schema.Schema` struct perform predefined validations on the list of attributes set for these fields. + +```go +func resourceExample() *schema.Resource { + return &schema.Resource{ + /* ... */ + Schema: map[string]*schema.Schema{ + "attribute_example": { + ConflictsWith: []string{ /* ... */ }, + ExactlyOneOf: []string{ /* ... */ }, + AtLeastOneOf: []string{ /* ... */ }, + RequiredWith: []string{ /* ... */ }, + /* ... */ +``` +## Framework + +In the Framework, you implement either type of validation by setting the `Validators` field on the `schema.Attribute` +implementation. Validators that perform the same checks as the +predefined validators in SDKv2 are +[available for the Framework](https://github.com/hashicorp/terraform-plugin-framework-validators). If the predefined +validators do not meet your needs, you must define +[custom validators](/terraform/plugin/framework/migrating/attributes-blocks/validators-custom). + +```go +func (r *resourceExample) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + /* ... */ + Attributes: map[string]schema.Attribute{ + "attribute_example": schema.StringAttribute{ + Validators: []validator.String{ + stringvalidator.ConflictsWith( /* ... */ ), + /* ... */ +``` + +Configuration validators can also be defined for +[providers](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators/providervalidator), +[resources](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators/resourcevalidator) and +[data sources](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators/datasourcevalidator) by +implementing `ProviderWithConfigValidators`, `ResourceWithConfigValidators`, and `DataSourceWithConfigValidators` +interfaces, respectively. + +```go +func (r *resourceExample) ConfigValidators(ctx context.Context) []resource.ConfigValidator { + return []resource.ConfigValidator{ + resourcevalidator.Conflicting( + /* ... */ + ), + /* ... */ +``` + +## Migration Notes + +Remember the following details when migrating from SDKv2 to the Framework. + +- In SDKv2, `ValidateDiagFunc` is a field on `schema.Schema` that you can use to define validation functions. In SDKv2, +there are also built-in validations. For example, `ConflictsWith` is a field on the `schema.Schema` struct in SDKv2. In +the Framework, `Validators` is a field on each `schema.Attribute` implementation. +- Validators replicating the behavior of `ConflictsWith`, `ExactlyOneOf`, `AtLeastOneOf`, and `RequiredWith` in SDKv2 are +available for the Framework in each of the type-specific packages of +[terraform-plugin-framework-validators](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators). +- Define [custom validators](/terraform/plugin/framework/migrating/attributes-blocks/validators-custom) when the predefined validators do not meet +your requirements. + +## Example + +### SDKv2 + +The following example shows the implementation of the `ConflictsWith` field on the +provider's `example_block` block's `example_attribute_one` attribute. +This validator checks that the provider does not use the `example_attribute_one` attribute +when the `example_attribute_four` is being used. The example also uses the `RequiredWith` field to ensure that the +`example_attribute_two` attribute is configured when `example_attribute_one` is, and that the +`example_attribute_three` attribute is configured when `example_attribute_two` is. + +```go +func New() (*schema.Provider, error) { + return &schema.Provider{ + Schema: map[string]*schema.Schema{ + "example_block": { + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "example_attribute_one": { + ConflictsWith: []string{"example_block.0.example_attribute_four"}, + /* ... */ + }, + "example_attribute_two": { + RequiredWith: []string{"example_block.0.example_attribute_one"}, + /* ... */ + }, + "example_attribute_three": { + RequiredWith: []string{"example_block.0.example_attribute_two"}, + /* ... */ + }, + "example_attribute_four": { + ConflictsWith: []string{ + "example_block.0.example_attribute_one", + "example_block.0.example_attribute_two", + "example_block.0.example_attribute_three", + }, + /* ... */ + }, + }, + }, + }, + }, + }, nil +} +``` + +### Framework + +The following shows the same section of provider code after the migration. + +This code implements the `ConflictsWith` and `AlsoRequires` validators with the Framework. The validators are configured +via the `Validators` field of the provider's `example_block` block's attribute schema. + +```go +func (p *TlsProvider) Schema(_ context.Context, _ provider.SchemaRequest, resp *provider.SchemaResponse) { + resp.Schema = schema.Schema{ + Blocks: map[string]schema.Block{ + "example_block": schema.ListNestedBlock{ + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "example_attribute_one": schema.StringAttribute{ + Validators: []validator.String{ + stringvalidator.ConflictsWith(path.MatchRelative().AtParent().AtName("example_attribute_four")), + }, + /* ... */ + }, + "example_attribute_two": schema.StringAttribute{ + Validators: []validator.String{ + stringvalidator.AlsoRequires(path.MatchRelative().AtParent().AtName("example_attribute_one")), + }, + /* ... */ + }, + "example_attribute_three": schema.StringAttribute{ + Validators: []validator.String{ + stringvalidator.AlsoRequires(path.MatchRelative().AtParent().AtName("example_attribute_two")), + }, + /* ... */ + }, + "example_attribute_four": schema.BoolAttribute{ + Validators: []validator.Bool{ + boolvalidator.ConflictsWith( + path.MatchRelative().AtParent().AtName("example_attribute_one"), + path.MatchRelative().AtParent().AtName("example_attribute_two"), + path.MatchRelative().AtParent().AtName("example_attribute_three"), + ), + }, + /* ... */ + }, + }, + }, + }, + } +} +``` diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/migrating/benefits.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/migrating/benefits.mdx new file mode 100644 index 0000000000..847b2c73f8 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/migrating/benefits.mdx @@ -0,0 +1,523 @@ +--- +page_title: Benefits of migration +description: >- + The plugin framework is an updated SDK for Terraform providers that includes + improved data access, more consistent schema models, and other improvements + over the previous SDKv2. +--- + +# Benefits of migrating to the plugin framework + +We recommend using the plugin framework to develop your provider because it offers significant benefits in comparison to SDKv2. We designed the framework with feedback from thousands of existing providers, so the framework significantly improves upon the functionality available in SDKv2. + +This page is a continuation of the [Framework Benefits](/terraform/plugin/framework-benefits) page, which describes the higher level coding improvements over SDKv2. The following features are only available in the framework. + +## Expanded Access to Configuration, Plan, and State Data + +Providers receive up to three sources of schema-based data during Terraform operation requests: configuration, plan, and prior state. The SDKv2 combines this data into a single [`schema.ResourceData` type](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema#ResourceData), which you implement differently depending on the operation. Certain `ResourceData` methods are only valid during certain operations and trying to get data from an explicit source is problematic in many cases. + +In the following SDKv2 example, the code comments highlight issues with the single data type: + +```go +func ThingResourceCreate(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { + d.Get("...") // plan unless unknown; no explicit access to configuration + d.GetChange("...") // extraneous old value, use d.Get() instead + d.HasChange("...") // always true, no prior state + d.Set("...") // saved into new state +} + +func ThingResourceRead(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { + d.Get("...") // prior state + d.GetChange("...") // no changes as only prior state is available + d.HasChange("...") // always false + d.Set("...") // saved into new state +} + +func ThingResourceUpdate(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { + d.Get("...") // plan unless unknown; no explicit access to configuration or prior state + d.GetChange("...") // prior state and plan unless unknown + d.HasChange("...") // comparison of prior state and plan + d.Set("...") // saved into new state +} + +func ThingResourceDelete(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { + d.Get("...") // prior state + d.GetChange("...") // no changes as only prior state is available + d.HasChange("...") // always false + d.Set("...") // extraneous, resource destroy leaves no state +} +``` + +The framework alleviates these issues by exposing configuration, plan, and state data as separate attributes on request and response types that only expose the data available to the given operation. + +In the following framework example, the code comments show the available data that matches each operation. + +```go +func (r ThingResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + req.Config // configuration data + req.Plan // plan data + // No req.State as it is always null + // No resp.Config as configuration cannot be set by provider during creation + // No resp.Plan as plan cannot be set by provider during creation + resp.State // new state data to save +} + +func (r ThingResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.CreateResponse) { + // No req.Config as configuration cannot be read by provider during read + // No req.Plan as there is no plan during read + req.State // prior state data + // No resp.Config as configuration cannot be set by provider during read + // No resp.Plan as there is no plan during read + resp.State // new state data to save +} + +func (r ThingResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + req.Config // configuration data + req.Plan // plan data + req.State // prior state data + // No resp.Config as configuration cannot be set by provider during update + // No resp.Plan as plan cannot be set by provider during update + resp.State // new state data to save +} + +func (r ThingResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + // No req.Config as configuration cannot be read by provider during delete + // No req.Plan as it is always null + req.State // prior state data + // No resp.Config as configuration cannot be set by provider during delete + // No resp.Plan as it cannot be adjusted + resp.State // only available to explicitly remove on error +} +``` + +## Schema Data Models + +In the SDKv2, you must fetch configuration, plan, and state data separately for each attribute or type. In the framework, you can fetch all of the configuration, plan, and state data at once. This approach lets you declare a single data model for a schema, which guarantees correctness and consistency across operations. + +In the following SDKv2 example, you must fetch the data for each attribute unless you save the schema as a variable and reference it in the operation logic. + +```go +attribute1 := d.Get("attribute1") // any type +attribute2 := d.Get("attribute2") // any type +attribute3 := d.Get("attribute3") // any type +``` + +Some SDKv2 providers opted to type assert during these calls, which had the potential to cause Go runtime panics if they did not also check the assertion boolean. + +```go +// Example showing panic-safe SDK data handling +attribute1, ok := d.Get("attribute1").(bool) // assuming schema.TypeBool + +if !ok { + // provider-defined error handling +} + +attribute2, ok := d.Get("attribute2").(int) // assuming schema.TypeInt + +if !ok { + // provider-defined error handling +} + +attribute3, ok := d.Get("attribute3").(string) // assuming schema.TypeString + +if !ok { + // provider-defined error handling +} +``` + +The [Fully Exposed Value States section](#fully-exposed-value-states) goes into other issues and quirks with attempting to handle SDKv2 data. + +Data with the framework can be modeled as a custom type and the operation of getting or setting the data will return framework-defined errors, if necessary. + +In the following framework example, a provider-defined type receives all schema-based data. + +```go +// Example schema data model type +type ThingResourceModel struct { + Attribute1 types.Bool `tfsdk:"attribute1"` // assuming types.BoolType attribute + Attribute2 types.Int64 `tfsdk:"attribute2"` // assuming types.Int64Type attribute + Attribute3 types.String `tfsdk:"attribute3"` // assuming types.StringType attribute +} + +// In resource logic +var data ThingResourceModel + +diags := req.Plan.Get(ctx, &data) // framework-defined errors + +resp.Diagnostics.Append(diags...) + +if resp.Diagnostics.HasError() { + return +} +``` + +With `Required` attributes, you can replace the framework types in the schema data model with standard Go types (e.g. `bool`) to further simplify data handling, if desired. + +## Fully Exposed Value States + +Terraform supports three states for any value: null (missing), unknown ("known after apply"), and known. The SDKv2 does not expose or fully support null and unknown value states to providers. Instead, the `Get()` method on these value states returns Go type zero-values such as `""` for `schema.TypeString`, `0` for `schema.TypeInt`, and `false` for `schema.TypeBool`. Other methods, such as `GetOk()` and `GetOkExists()`, have slightly different functionality for each type and operation, especially for collection types. + +In the following SDKv2 example, the code comments explain issues with the single data type. + +```go +// Assuming a schema of: +// +// "string_attribute": &schema.Schema{ +// Computed: true, +// Optional: true, +// Type: schema.TypeString, +// } +// +// and a configuration that does not set the value (null state). +// +// resource “examplecloud_thing” “example” { +// # no string_attribute = “...” +// } + +func ThingResourceUpdate(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { + d.Get("string_attribute") // "" + d.GetOk("string_attribute") // may return true depending on prior state + d.GetOkExists("string_attribute") // may return true depending on prior state +} +``` + +The framework type system fully exposes null, unknown, and known value states. You can reliably query each value with the `IsNull()` or `IsUnknown()` methods. + +In the following framework example, you can determine the correct value state. + +```go +// Assuming a schema of: +// +// "string_attribute": schema.StringAttribute{ +// Computed: true, +// Optional: true, +// } +// +// and a configuration that does not set the value (null state). +// +// resource “examplecloud_thing” “example” { +// # no string_attribute = “...” +// } + +func (r ThingResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var config, plan types.String + + req.Config.GetAttribute(ctx, path.Root("string_attribute"), &config) + req.Plan.GetAttribute(ctx, path.Root("string_attribute"), &plan) + + config.IsNull() // true + config.IsUnknown() // false + config.ValueString() // "" + plan.IsNull() // true + plan.IsUnknown() // false + plan.ValueString() // "" +} +``` + +## Unrestricted Type System + +The framework type system exposes the majority of Terraform types and values. It is also extensible because it lets you define new types that are specific to your provider. + +### Custom Attribute Types + +You can implement custom types for your provider that expose data with convenient Go types, methods, and built-in validation. + +The following framework example uses a custom `timetypes.RFC3339Type` attribute type instead of `types.StringType`. The `timetypes.RFC3339Type` attribute type is associated with a `timetypes.RFC3339` value type. The attribute type automatically validates whether the string can be parsed as an RFC3339 timestamp and the value type exposes a `Time() time.Time` method for easier usage over a regular string value. + +```go +"rfc3339": schema.StringAttribute{ + CustomType: timetypes.RFC3339Type{}, + Required: true, +}, +``` + +The following framework example uses the custom `timetypes.RFC3339` value type to expose the `time.Time` value. + +```go +// Example schema data model +type ThingResourceModel struct{ + RFC3339 timetypes.RFC3339 `tfsdk:"rfc3339"` +} + +// In resource logic, omitting diagnostics handling for brevity +var data ThingResourceModel + +req.Plan.Get(ctx, &data) + +data.RFC3339.Time() // time.Time +``` + +### Complex Map Types + +The framework type system does not have any restrictions for using complex types as the value for a map type. SDKv2 restricted map values to string, number, and boolean types. + +This framework example declares a map type with a list of string values. + +```go +schema.MapAttribute{ + // ... other fields ... + ElementType: types.ListType{ + ElemType: types.StringType, + }, +} +``` + +If you need to declare additional schema behaviors for the map values, you can use map nesting mode in [Protocol Version 6 Nested Attributes](#protocol-version-6-nested-attributes), which is also only available in the framework. + +### Object Type + +The framework type system supports the Terraform object type, which you can use to declare attribute name to value mappings without additional schema behaviors. These differ from maps by requiring specific names and their values to always exist. SDKv2 did not directly expose this type. + +The following framework example declares an object type with two attributes. + +```go +schema.ObjectAttribute{ + // ... other fields ... + AttributeTypes: map[string]attr.Type{ + "bool_attribute": types.BoolType, + "string_attribute": types.StringType, + }, +} +``` + +If you need to declare additional schema behaviors for the object values, you can use the single nesting mode in [Protocol Version 6 Nested Attributes](#protocol-version-6-nested-attributes), which is also only available in the framework. + +### Protocol Version 6 Nested Attributes + +Protocol [version 6](/terraform/plugin/terraform-plugin-protocol) is the latest version of the protocol between Terraform and providers. Only the framework supports version 6. + +Version 6 lets you declare schemas with nested attributes in addition to blocks. Nested attributes support schema behaviors and practitioners using your provider can configure them with expressions instead of with dynamic blocks. Nested attributes support includes four nesting modes: + +- List: Ordered collection of nested attributes +- Map: Collection of string keys to nested attributes. +- Set: Unordered collection of nested attributes. +- Single: Single object of nested attributes that is useful for replacing list blocks with a single element. + +In the following configuration example, a schema uses a list block that is difficult to dynamically configure. + +```hcl +locals { + calls = toset([ + {call_me: “example1”, maybe: true}, + {call_me: “example2”, maybe: false}, + ]) +} + +resource “examplecloud_thing” “example” { + dynamic “list_block” { + for_each = local.calls + + content { + call_me = list_block.value.call_me + maybe = list_block.value.maybe + } + } +} +``` + +In the following configuration example, a schema uses list nested attributes to simplify the configuration. + +```hcl +locals { + calls = [ + {call_me: “example1”, maybe: true}, + {call_me: “example2”, maybe: false}, + ] +} + +resource “examplecloud_thing” “example” { + list_nested_attributes = local.calls # or a for expression, etc. +} +``` + +The following framework example shows the schema definition for the list nested attributes. + +```go +schema.ListNestedAttribute{ + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + “call_me”: schema.StringAttribute{ + Required: true, + }, + “maybe”: schema.BoolAttribute{ + Optional: true, + Sensitive: true, + }, + }, + }, + // ... other fields ... +} +``` + +## Unrestricted Validation Capabilities + +The framework exposes many more configuration validation integration points than SDKv2. It is also extensible with provider-defined types that implement validation in the type itself. + +### Collection Type Validation + +Attribute validation in the framework is not restricted by type. + +This framework example validates all list values against a set of acceptable values. + +```go +schema.ListAttribute{ + // ... other fields ... + ElementType: types.StringType, + Validators: []validator.List{ + listvalidator.StringValuesAre( + stringvalidator.OneOf("one", "two", "three"), + ), + }, +} +``` + +This framework example checks whether map keys are between 3 and 50 characters in length using validators available in [`terraform-plugin-framework-validators`](https://github.com/hashicorp/terraform-plugin-framework-validators). + +```go +schema.MapAttribute{ + // ... other fields ... + ElementType: types.StringType, + Validators: []validator.Map{ + mapvalidator.KeysAre( + stringvalidator.LengthBetween(3, 50), + ), + }, +} +``` + +### Type-Based Validation + +Attribute validation supports attribute types that declare their own validation, in addition to any validators on the attribute itself. + +In the following framework example, the custom type ensures that the string is a valid RFC3339 string, and the attribute can declare additional validation. + +```go +schema.StringAttribute{ + // ... other fields ... + CustomType: timetypes.RFC3339Type{}, // automatically validates string is RFC3339 + Validators: []validator.String{ + // additional validation, if desired + }, +} +``` + +### Declarative Schema Validation + +The framework supports schema-level validation with reusable and declarative validators. In certain cases, such as when you would use SDKv2 `AtLeastOneOf`, this approach can reduce overlapping validation errors and make logic easier to understand. + +In the following SDKv2 example, multiple attributes can raise multiple errors. + +```go +map[string]*schema.Schema{ + “attribute_one”: { + AtLeastOneOf: []string{“attribute_two”}, // does this need attribute_one? + // ... other fields ... + }, + “attribute_two”: { + AtLeastOneOf: []string{“attribute_one”}, // is this necessary? + // ... other fields ... + }, +} +``` + +In the following framework example, the validation logic raises a single error when the resource configuration does not include at least one of the specified attributes. + +```go +func (r ThingResource) ConfigValidators(_ context.Context) []resource.ConfigValidator { + return []resource.ConfigValidator{ + resourcevalidator.AtLeastOneOf( + path.MatchRoot("attribute_one"), + path.MatchRoot("attribute_two"), + ), + } +} +``` + +### Imperative Schema Validation + +The framework supports schema-level validation with custom logic in addition to the declarative validators. This support lets you fully customize the validation to implement complex validation logic. + +In the following framework example, the resource implements custom validation logic. + +```go +func (r ThingResource) ValidateConfig(ctx context.Context, req resource.ValidateConfigRequest, resp *resource.ValidateConfigResponse) { + // custom logic +} +``` + +## Path Expressions + +The framework includes a schema path implementation that lets you target attributes of any type or nesting level. This feature lets you build paths without knowing the special string syntax of the SDKv2, instead using Go ecosystem features such as suggestions from editor integrations. + +In the following framework example, the path is absolute to the first element of a list. + +```go +path.Root("list_attribute").AtListIndex(0) +``` + +Additionally, the framework supports expressions on top of these paths, which enables logic such as matching all indices in a list, relative paths, and parent paths. + +The following framework example validates whether the two attributes within the same list element conflict with each other. + +```go +schema.ListNestedAttribute{ + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + “attribute_one”: schema.StringAttribute{ + Validators: []validator.String{ + stringvalidator.ConflictsWith( + path.MatchRelative().AtParent().AtName(“attribute_two”), + ), + }, + // ... other fields ... + }, + “attribute_two”: { /* … */ }, + }, + }, + // ... other fields ... +} +``` + +## Import Warning Diagnostics + +The framework supports diagnostics through all Terraform operations. The SDKv2 does not support diagnostics with some operations, such as import. + +The following framework example returns a warning to practitioners when they import the resource. + +```go +func (r ThingResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + resp.Diagnostics.AddWarning( + “Resource Import Considerations”, + “The API does return the password attribute, which will show as a plan ”+ + “difference in Terraform unless the lifecycle configuration block “+ + “ignore_changes argument includes password.” + ) + resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp) +} +``` + +## Destroy Plan Diagnostics + +With the framework, Terraform version 1.3 and later supports calling the provider when Terraform is planning to destroy a resource. SDKv2 does not support this functionality. + +In this framework example, the resource will raise a warning when planned for destruction to give practitioner more information: + +```go +func (r ThingResource) ModifyPlan(ctx context.Context, req resource.ModifyPlanRequest, resp *resource.ModifyPlanResponse) { + // If the entire plan is null, the resource is planned for destruction. + if req.Plan.Raw.IsNull() { + resp.Diagnostics.AddWarning( + "Resource Destruction Considerations", + "Applying this resource destruction will only remove the resource from the Terraform state "+ + "and will not call the deletion API due to API limitations. Manually use the web "+ + "interface to fully destroy this resource.", + ) + } +} +``` + +## Resource Private State Management + +Each provider can maintain resource private state data in Terraform state. Terraform never accesses resource private state or includes the information in plans, but providers can use this private data for advanced use cases. For example, a provider could use resource private state to store API ETag values that are not beneficial for practitioners. SDKv2 does not support this functionality. + +Refer to the [Manage Private State documentation](/terraform/plugin/framework/resources/private-state) for more information. diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/migrating/data-sources/index.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/migrating/data-sources/index.mdx new file mode 100644 index 0000000000..eaa78e446b --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/migrating/data-sources/index.mdx @@ -0,0 +1,167 @@ +--- +page_title: Migrating data sources +description: >- + Learn how to migrate a data source from SDKv2 to the plugin framework. +--- + +# Migrating data sources + +Data sources let Terraform reference external data. Unlike resources, Terraform does not create, update, or delete +data sources, and makes no attempt to modify the underlying API. Data Sources are a read-only resource type, so they +only implement a subset of the operations that resources do. Refer to [Data Sources](/terraform/plugin/framework/data-sources) +in the Framework documentation for details. + +This page explains how to migrate a data source from SDKv2 to the plugin Framework. We also recommend reviewing these additional guides for data sources throughout the migration: +- [Timeouts](/terraform/plugin/framework/migrating/data-sources/timeouts): The data source uses timeouts during a read operation. + +## SDKv2 + +In SDKv2, data sources are defined by the `DataSourcesMap` field on the `schema.Provider` struct, which maps data source +names (strings) to their schema. The `schema.Resource` struct is used for both resources and data sources. + +The following example shows a typical implementation. + +```go +func New() *schema.Provider { + return &schema.Provider{ + DataSourcesMap: map[string]*schema.Resource{ + /* ... */ +}, +``` + +In SDKv2, you define both resources and data sources with `schema.Resource` structs. The following example shows a +resource struct. For clarity, the example omits fields that are not available for data sources. + +```go +schema.Resource { + Schema: map[string]*schema.Schema, + Read: ReadFunc, + ReadContext: ReadContextFunc, + ReadWithoutTimeout: ReadContextFunc, + DeprecationMessage: string, + Timeouts: *ResourceTimeout, + Description: string, +} +``` + +## Framework + +In the Framework, you define data sources by adding them to the map returned by your provider's `DataSources` method. + +The `DataSources` method on your `provider.Provider` returns a slice of functions that return types +that implement the `datasource.DataSource` interface for each data source your provider supports. + +The following code shows how you add a data source to your provider with the Framework. + +```go +func (p *provider) DataSources(ctx context.Context) []func() datasource.DataSource { + return []func() datasource.DataSource{ + /* ... */ + } +} +``` + +Like the `resource.Resource` interface, `datasource.DataSource` requires `Schema` and `Metadata` methods. +These methods work the same way for data sources as they do for resources. The `Read` method is also required. + +The `Schema` method returns a `schema.Schema` struct which defines your data source's attributes. + +The `Metadata` method returns a type name that you define. + +The `Read` method implements the logic for writing into the Terraform state. + +The following code shows how you define a `datasource.DataSource` which implements these methods with the +Framework. + +```go +type dataSourceExample struct{} + +func (d *dataSourceExample) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { + /* ... */ +} + +func (d *dataSourceExample) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) { + /* ... */ +} + +func (d *dataSourceExample) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + /* ... */ +} +``` + +## Migration Notes + +Remember the following details when completing the migration from SDKv2 to the Framework. + +- As data sources are read-only, you only implement read functionality for your provider's data sources. Refer to the +[`Read` function](/terraform/plugin/framework/resources#read) for resources in the Framework documentation for more details. + +## Example + +### SDKv2 + +The following example shows an implementation of the `DataSourcesMap` field on the provider +schema with SDKv2. + +```go +func New() (*schema.Provider, error) { + return &schema.Provider { + DataSourcesMap: map[string]*schema.Resource { + "example_datasource": exampleDataSource(), + /* ... */ +``` + +The following example shows how the `ReadContext` function and `Schema` are defined for +the `exampleResource` data source with SDKv2. + +```go +func exampleDataSource() *schema.Resource { + return &schema.Resource{ + ReadContext: dataSourceRead, + + Schema: map[string]*schema.Schema{ + "example_attribute": { + Type: schema.TypeString, + Required: true, + }, + /* ... */ + }, + } +} +``` + +### Framework + +The following example shows how the `exampleDataSource` data source is defined with the Framework after +the migration. + +```go +func (p *provider) DataSources(context.Context) []func() datasource.DataSource { + return []func() datasource.DataSource{ + func() datasource.DataSource { + return &exampleDataSource{} + }, + } +} +``` + +This code defines the methods for the `exampleDataSource` data source with the +Framework. + +```go +func (d *exampleDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = "example_datasource" +} + +func (d *exampleDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "example_attribute": schema.StringAttribute{ + Required: true, + }, + /* ... */ + +func (d *exampleDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + /* ... */ +} +``` diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/migrating/data-sources/timeouts.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/migrating/data-sources/timeouts.mdx new file mode 100644 index 0000000000..7b01053168 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/migrating/data-sources/timeouts.mdx @@ -0,0 +1,130 @@ +--- +page_title: Migrating timeouts +description: >- + Learn how to migrate timeouts from SDKv2 to the framework. +--- + +# Migrating timeouts + +The Framework can be used in conjunction with the [terraform-plugin-framework-timeouts](https://github.com/hashicorp/terraform-plugin-framework-timeouts) module in order to allow defining timeouts in configuration and have them be available in `Read` functions. + +## Specifying Timeouts in Configuration + +Timeouts can be defined using either nested blocks or nested attributes. + +If you are writing a new provider using [terraform-plugin-framework](https://github.com/hashicorp/terraform-plugin-framework) +then we recommend using nested attributes. + +If you are migrating a provider from SDKv2 to the Framework and +you are already using timeouts you can either continue to use block syntax, or switch to using nested attributes. +However, switching to using nested attributes will require that practitioners that are using your provider update their +Terraform configuration. + +#### Block + +If your configuration is using a nested block to define timeouts, such as the following: + +```hcl +resource "timeouts_example" "example" { + /* ... */ + + timeouts { + read = "60m" + } +} +``` + +Import the [timeouts module](https://github.com/hashicorp/terraform-plugin-framework-timeouts). + +```go +import ( + /* ... */ + "github.com/hashicorp/terraform-plugin-framework-timeouts/datasource/timeouts" +) +```` + +You can use this module to mutate the `schema.Schema` as follows: + +```go +func (d *ThingDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) { + resp.Schema = schema.Schema{ + /* ... */ + + Blocks: map[string]schema.Block{ + "timeouts": timeouts.Block(ctx), + }, +``` + +#### Attribute + +If your configuration is using nested attributes to define timeouts, such as the following: + +```hcl +resource "timeouts_example" "example" { + /* ... */ + + timeouts = { + read = "60m" + } +} +``` + +You can use this module to mutate the `schema.Schema` as follows: + +```go +func (d *ThingDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + /* ... */ + "timeouts": timeouts.Attributes(ctx), + }, +``` + +## Updating Models + +Given a `Read` method which fetches the entire configuration: + +```go +func (e *exampleDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + var data exampleDataSourceData + + diags := req.Config.Get(ctx, &data) + resp.Diagnostics.Append(diags...) +``` + +Modify the `exampleDataSourceData` model to include a field for timeouts using a [`timeouts.Value`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-timeouts/datasource/timeouts#Value) type. + +```go +type exampleDataSourceData struct { + /* ... */ + Timeouts timeouts.Value `tfsdk:"timeouts"` +``` + +## Accessing Timeout in Read Method + +Call the [`timeouts.Read()` function](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-timeouts/datasource/timeouts#Value.Read). + +```go +func (e *exampleDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + var data exampleDataSourceData + + diags := req.Config.Get(ctx, &data) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + readTimeout, diags := data.Timeouts.Read(ctx, 20*time.Minute) + + resp.Diagnostics.Append(diags...) + + if resp.Diagnostics.HasError() { + return + } + + ctx, cancel := context.WithTimeout(ctx, readTimeout) + defer cancel() + + /* ... */ +} +``` diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/migrating/index.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/migrating/index.mdx new file mode 100644 index 0000000000..f134550391 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/migrating/index.mdx @@ -0,0 +1,48 @@ +--- +page_title: Migrating from SDKv2 to the plugin framework +description: >- + Learn how to migrate your provider from SDKv2 to the plugin framework. +--- + +# Overview + +This guide helps you migrate a Terraform provider from SDKv2 to the plugin Framework. We recommend migrating because the Framework has abstractions that make it easier to use, and it represents the future of Terraform plugin development. Refer to [Plugin Framework Benefits](/terraform/plugin/framework-benefits) for higher level details about how the framework makes provider development easier and [Feature Comparison](/terraform/plugin/framework/migrating/benefits) for a detailed functionality comparison between the SDKv2 and the framework. + +This guide provides information and examples for most common use cases, but it does not discuss every nuance of migration. You can ask additional migration questions in the [HashiCorp Discuss forum](https://discuss.hashicorp.com/c/terraform-providers/tf-plugin-sdk/43). To request additions or updates to this guide, submit issues or pull requests to the [`terraform-plugin-framework` repository](https://github.com/hashicorp/terraform-plugin-framework). + +In addition to this migration guide, we recommend referring to the main [Framework documentation](/terraform/plugin/framework) as you migrate your provider. + +## Requirements + +Before you migrate your provider to the Framework, ensure it meets the following requirements: + +- Go 1.22+ +- Built on the latest version of SDKv2 +- The provider is for use with Terraform >= 0.12.0 + +## Muxing + +Consider muxing when you need to migrate a provider that contains many resources or data sources. Muxing enables multiple underlying provider implementations to exist within the same logical provider server. This lets you migrate individual resources or data sources to the Framework one at a time. + +Refer to the [muxing](/terraform/plugin/framework/migrating/mux) page of the migration guide for additional details. + +## Testing Migration + +As you complete the migration, we recommend that you follow Test Driven Development by writing tests to ensure that the migration does not affect provider behavior. Refer to [Testing Migration](/terraform/plugin/framework/migrating/testing#testing-migration) for details and an example. + + +## Migration steps + +Take the following steps when you migrate a provider from SDKv2 to the Framework: + +- Ensure all [tests](/terraform/plugin/framework/migrating/testing#testing-migration) pass. +- Consider [finding SDKv2 resource data consistency errors](/terraform/plugin/sdkv2/resources/data-consistency-errors), which might affect migrating to the Framework. Some errors can be resolved and verified with SDKv2 code before migration, if desired, otherwise resolving these errors must be [part of the migration](/terraform/plugin/framework/migrating/resources/crud#resolving-data-consistency-errors). +- [Serve the provider](/terraform/plugin/framework/migrating/providers#serving-the-provider) via the Framework. + - Implement [muxing](/terraform/plugin/framework/migrating/mux), if you plan to migrate the provider iteratively. +- Update the [provider definition](/terraform/plugin/framework/migrating/providers#provider-definition) to use the Framework. +- Update the [provider schema](/terraform/plugin/framework/migrating/providers#provider-schema). +- Update each of the provider's resources and data sources. + - Update related [tests](/terraform/plugin/framework/migrating/testing) to use the Framework, and ensure that the tests fail. + - Migrate the [resource](/terraform/plugin/framework/migrating/resources) or [data source](/terraform/plugin/framework/migrating/data-sources). + - Verify that related tests now pass. +- If you used [muxing](/terraform/plugin/framework/migrating/mux), remove the muxing configuration. diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/migrating/mux.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/migrating/mux.mdx new file mode 100644 index 0000000000..e0e57c4b7e --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/migrating/mux.mdx @@ -0,0 +1,283 @@ +--- +page_title: Migration using muxing +description: >- + Learn how to iteratively migrate from the SDKv2 to the plugin framework using + the terraform-plugin-mux Go library. +--- + +# Muxing + +Muxing enables multiple underlying provider implementations to exist within the same logical provider server via the [terraform-plugin-mux Go module](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-mux). Each underlying provider implementation serves different managed resources and data sources. Refer to the [Combining and Translating documentation](/terraform/plugin/mux) for full details about muxing configuration. + +## Use Cases + +Use muxing when: + +- You have an existing terraform-plugin-sdk based provider. +- The provider includes more than a few managed resources and data sources. +- You want to iteratively develop or release a version of your provider with only some of the managed resources and data sources migrated to the Framework. + +Otherwise for simplicity, it is recommended to migrate directly to the framework without temporarily introducing muxing. + +## Requirements + +- Ensure `github.com/hashicorp/terraform-plugin-sdk/v2` is upgraded to the latest version. For example, running the `go get github.com/hashicorp/terraform-plugin-sdk/v2@latest` command. +- Ensure existing acceptance testing is passing. Acceptance testing can be used to verify the muxing implementation before release. + +## Implementation + +1. Introduce a Go type implementing the Framework's [`provider.Provider` interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider#Provider). Refer to the [provider definition section](/terraform/plugin/framework/migrating/providers#provider-definition) of the migration guide for additional details. +1. Implement the `provider.Provider` type `Schema` and `Configure` methods so it is compatible for muxing. The schema and configuration handling must exactly match between all underlying providers of the mux server. Refer to the [provider schema section](/terraform/plugin/framework/migrating/providers#provider-schema) of the migration guide for additional details. +1. Introduce a mux server using terraform-plugin-mux functionality. This code eventually must be referenced by the codebase's `main()` function, which is responsible for starting the provider server. Refer to the [Mux Server Examples section](#mux-server-examples) for additional details. +1. Introduce an acceptance test for the mux server implementation. Refer to the [Testing Examples section](#testing-examples) for additional details. +1. Ensure `github.com/hashicorp/terraform-plugin-mux` is added to the provider Go module dependencies. For example, running the `go get github.com/hashicorp/terraform-plugin-mux@latest` command. + +### Mux Server Examples + +#### Terraform 0.12 Compatibility Example + +The following `main.go` example shows how to set up muxing for a provider that uses Protocol Version 5 to maintain compatibility with Terraform 0.12 and later. The example also shows how to use the `debug` flag to optionally run the provider in debug mode. + +```go +import ( + "context" + "flag" + "log" + + "github.com/hashicorp/terraform-plugin-framework/providerserver" + "github.com/hashicorp/terraform-plugin-go/tfprotov5" + "github.com/hashicorp/terraform-plugin-go/tfprotov5/tf5server" + "github.com/hashicorp/terraform-plugin-mux/tf5muxserver" + + "example.com/terraform-provider-examplecloud/internal/provider" +) + +func main() { + ctx := context.Background() + + var debug bool + + flag.BoolVar(&debug, "debug", false, "set to true to run the provider with support for debuggers like delve") + flag.Parse() + + providers := []func() tfprotov5.ProviderServer{ + providerserver.NewProtocol5(provider.New()), // Example terraform-plugin-framework provider + provider.Provider().GRPCProvider, // Example terraform-plugin-sdk provider + } + + muxServer, err := tf5muxserver.NewMuxServer(ctx, providers...) + + if err != nil { + log.Fatal(err) + } + + var serveOpts []tf5server.ServeOpt + + if debug { + serveOpts = append(serveOpts, tf5server.WithManagedDebug()) + } + + err = tf5server.Serve( + "registry.terraform.io//", + muxServer.ProviderServer, + serveOpts..., + ) + + if err != nil { + log.Fatal(err) + } +} +``` + +#### Terraform 1.X Compatibility Example + +The mux server can be setup to break compatibility with Terraform 0.12 through 1.1.6, but enable Protocol Version 6 capabilities in the Framework provider, such as nested attributes. + +The following `main.go` example shows how to set up muxing for a provider that upgrades the terraform-plugin-sdk based provider to Protocol Version 6 to support those new features in the Framework provider. The example also shows how to use the `debug` flag to optionally run the provider in debug mode. + +```go +import ( + "context" + "flag" + "log" + + "github.com/hashicorp/terraform-plugin-framework/providerserver" + "github.com/hashicorp/terraform-plugin-go/tfprotov6" + "github.com/hashicorp/terraform-plugin-go/tfprotov6/tf6server" + "github.com/hashicorp/terraform-plugin-mux/tf5to6server" + "github.com/hashicorp/terraform-plugin-mux/tf6muxserver" + + "example.com/terraform-provider-examplecloud/internal/provider" +) + +func main() { + ctx := context.Background() + + var debug bool + + flag.BoolVar(&debug, "debug", false, "set to true to run the provider with support for debuggers like delve") + flag.Parse() + + upgradedSdkServer, err := tf5to6server.UpgradeServer( + ctx, + provider.Provider().GRPCProvider, // Example terraform-plugin-sdk provider + ) + + if err != nil { + log.Fatal(err) + } + + providers := []func() tfprotov6.ProviderServer{ + providerserver.NewProtocol6(provider.New()()), // Example terraform-plugin-framework provider + func() tfprotov6.ProviderServer { + return upgradedSdkServer, + }, + } + + muxServer, err := tf6muxserver.NewMuxServer(ctx, providers...) + + if err != nil { + log.Fatal(err) + } + + var serveOpts []tf6server.ServeOpt + + if debug { + serveOpts = append(serveOpts, tf6server.WithManagedDebug()) + } + + err = tf6server.Serve( + "registry.terraform.io//", + muxServer.ProviderServer, + serveOpts..., + ) + + if err != nil { + log.Fatal(err) + } +} +``` + +### Testing Examples + +#### Protocol Version 5 + +The following acceptance test example would be included in the same Go package that defines the provider code to verify the muxing setup: + +```go +import ( + "context" + "testing" + + "github.com/hashicorp/terraform-plugin-go/tfprotov5" + "github.com/hashicorp/terraform-plugin-mux/tf5muxserver" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" +) + +func TestMuxServer(t *testing.T) { + resource.Test(t, resource.TestCase{ + ProtoV5ProviderFactories: map[string]func() (tfprotov5.ProviderServer, error) { + "examplecloud": func() (tfprotov5.ProviderServer, error) { + ctx := context.Background() + providers := []func() tfprotov5.ProviderServer{ + providerserver.NewProtocol5(New()), // Example terraform-plugin-framework provider + Provider().GRPCProvider, // Example terraform-plugin-sdk provider + } + + muxServer, err := tf5muxserver.NewMuxServer(ctx, providers...) + + if err != nil { + return nil, err + } + + return muxServer.ProviderServer(), nil + }, + }, + Steps: []resource.TestStep{ + { + Config: "... configuration including simplest data source or managed resource", + }, + }, + }) +} +``` + +#### Protocol Version 6 + +The following acceptance test example would be included in the same Go package that defines the provider code to verify the muxing setup: + +```go +import ( + "context" + "testing" + + "github.com/hashicorp/terraform-plugin-go/tfprotov6" + "github.com/hashicorp/terraform-plugin-mux/tf5to6server" + "github.com/hashicorp/terraform-plugin-mux/tf6muxserver" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" +) + +func TestMuxServer(t *testing.T) { + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: map[string]func() (tfprotov6.ProviderServer, error) { + "examplecloud": func() (tfprotov6.ProviderServer, error) { + ctx := context.Background() + + upgradedSdkServer, err := tf5to6server.UpgradeServer( + ctx, + Provider().GRPCProvider, // Example terraform-plugin-sdk provider + ) + + if err != nil { + return nil, err + } + + providers := []func() tfprotov6.ProviderServer{ + providerserver.NewProtocol6(New()), // Example terraform-plugin-framework provider + func() tfprotov6.ProviderServer { + return upgradedSdkServer + }, + } + + muxServer, err := tf6muxserver.NewMuxServer(ctx, providers...) + + if err != nil { + return nil, err + } + + return muxServer.ProviderServer(), nil + }, + }, + Steps: []resource.TestStep{ + { + Config: "... configuration including simplest data source or managed resource", + }, + }, + }) +} +``` + +## Tips + +- Only acceptance tests for migrated managed resources and data sources require testing code updates as noted in the [testing](/terraform/plugin/framework/migrating/testing) page of the migration guide. + +## Troubleshooting + +### PreparedConfig response from multiple servers + +Muxed providers may receive a new error, such as: + +```text +Error: Plugin error + + with provider["registry.terraform.io/example/examplecloud"], + on line 0: + (source code not available) + +The plugin returned an unexpected error from +plugin.(*GRPCProvider).ValidateProviderConfig: rpc error: code = Unknown desc += got different PrepareProviderConfig PreparedConfig response from multiple +servers, not sure which to use +``` + +If the terraform-plugin-sdk based provider was using [`Default`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-sdk/helper/schema#Schema.Default) or [`DefaultFunc`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-sdk/helper/schema#Schema.DefaultFunc), you must remove the usage of `Default` and `DefaultFunc` in that provider implementation. Transfer the logic into the provider [ConfigureFunc](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema#Provider.ConfigureFunc) or [ConfigureContextFunc](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema#Provider.ConfigureContextFunc), similar to how it must be implemented in a terraform-plugin-framework based provider. diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/migrating/providers/index.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/migrating/providers/index.mdx new file mode 100644 index 0000000000..42fc0c54ee --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/migrating/providers/index.mdx @@ -0,0 +1,343 @@ +--- +page_title: Migrating providers from SDKv2 to the framework +description: >- + Learn how to migrate a provider definition and schema from SDKv2 to the plugin + framework. +--- + +# Migrating providers + +Providers are Terraform plugins that define resources and data sources for practitioners to use. You serve your +providers with a provider server so they can interact with Terraform. + +This page explains how to migrate a provider server, definition, and schema from SDKv2 to the plugin Framework. + +## Serving the Provider + +You must update your provider's `main.go` file to serve Framework providers. Refer to [Provider Servers](/terraform/plugin/framework/provider-servers) in the Framework documentation for details. + + + +To iteratively migrate individual resources and data sources, refer to [muxing](/terraform/plugin/framework/migrating/mux) for the `main.go` implementation instead. The latter [provider definition](#provider-definition) and [provider schema](#provider-schema) sections are still applicable when muxing. + + + +### SDKv2 + +In SDKv2, the provider package's `main` function serves the provider by calling `plugin.Serve`. + +The following code shows a basic implementation for serving an SDKv2 provider. + +```go +func main() { + plugin.Serve( + &plugin.ServeOpts{ + ProviderFunc: provider.New, + ProviderAddr: "registry.terraform.io//", + }, + ) +} +``` + +### Framework + +In the Framework, you serve your provider by calling `providerserver.Serve` in your provider package's `main` function. +Refer to [Provider Servers](/terraform/plugin/framework/provider-servers) in the Framework documentation for details. + +The following code shows an equivalent implementation for serving a provider in the Framework. + +```go +func main() { + err := providerserver.Serve( + context.Background(), + provider.New, + providerserver.ServeOpts{ + Address: "registry.terraform.io//", + }, + ) + + if err != nil { + log.Fatal(err) + } +} +``` + +## Provider Definition + +Providers built with SDKv2 use a `schema.Provider` struct to define their behavior, while Framework providers use a +type that implements the `provider.Provider` interface, which you must define. Refer to [Providers](/terraform/plugin/framework/providers) in the Framework documentation for details. + +### SDKv2 + +The [`ProviderFunc`](/terraform/plugin/framework/migrating/providers#serving-the-provider) field on +`plugin.ServeOpts` requires a pointer to `schema.Provider`. This is typically satisfied by calling a function that +returns a pointer to `schema.Provider`. + +The `ResourcesMap` and `DataSourcesMap` fields each contain a map of strings to functions that each return a pointer +to a `schema.Resource` struct. + +The following example shows a basic implementation of an SDKv2 provider. + +```go +func New() *schema.Provider { + return &schema.Provider{ + Schema: map[string]*schema.Schema{}, + ConfigureContextFunc: configureContextFunc(), + ResourcesMap: map[string]*schema.Resource{ + "resource_example": resourceExample(), + }, + DataSourcesMap: map[string]*schema.Resource{ + "dataSource_example": dataSourceExample(), + }, + /* ... */ + } +} +``` + +### Framework + +In the Framework, the second argument to your `provider.Serve` function requires a function that returns a type +satisfying the `provider.Provider` interface. + +The following code shows a typical implementation. In this implementation, the `Resources` method returns a slice +of functions that return types that implement the `resource.Resource` interface. The `DataSources` method returns a +slice of functions that return types that implement the `datasource.DataSource` interface. +Refer to the [Resources](/terraform/plugin/framework/migrating/resources) and +[Data Sources](/terraform/plugin/framework/migrating/data-sources) pages in this guide to implement these functions for your +provider. + +```go +type exampleCloudProvider struct { +} + +func New() provider.Provider { + return &exampleCloudProvider{} +} + +func (p *exampleCloudProvider) Metadata(ctx context.Context, req provider.MetadataRequest, resp *provider.MetadataResponse) { + resp.TypeName = "examplecloud" +} + +func (p *exampleCloudProvider) Schema(ctx context.Context, req provider.SchemaRequest, resp *provider.SchemaResponse) { + resp.Schema = schema.Schema{} +} + +func (p *exampleCloudProvider) Configure(ctx context.Context, req provider.ConfigureRequest, resp *provider.ConfigureResponse) { +} + +func (p *exampleCloudProvider) Resources(ctx context.Context) []func() resource.Resource { + return []func() resource.Resource { + func() resource.Resource { + return resourceExample{} + }, + } +} + +func (p *exampleCloudProvider) DataSources(ctx context.Context) []func() datasource.DataSource { + return []func() datasource.DataSource { + func() datasource.DataSource { + return dataSourceExample{} + }, + } +} +``` + +### Migration Notes + +Remember the following differences between SDKv2 and the Framework when completing the migration. + +- In SDKv2, your provider's `New` function returns a `schema.Provider` struct. In the Framework, `New` returns a type +that you define which satisfies the `provider.Provider` interface. +- In SDKv2, `Schema` is a field on `schema.Provider` that contains `map[string]*schema.Schema`, which maps attribute +names to `schema.Schema` structs. In the Framework, `Schema` is a method you define on your provider's +`provider.Provider` interface that returns your provider's `schema.Schema` struct. +- In SDKv2, `ConfigureContextFunc` is a field on `schema.Provider` containing a function that configures the provider. +In the Framework, `Configure` is a function you define on your provider that configures your provider. +- In SDKv2, `ResourcesMap` is a field on `schema.Provider` containing `map[string]*schema.Resource`, which maps resource +names to `schema.Resource` structs. In the Framework, `Resources` is a method you define on your provider that +returns `[]func() resource.Resource`, which creates resource types that you define, which satisfy the +`resource.Resource` interface. +- In SDKv2, `DataSourcesMap` is a field on `schema.Provider` containing `map[string]*schema.Resource`, which maps data +source names to `schema.Resource` structs (data sources and resources both use `schema.Resource`). In the Framework, +`DataSources` is a method you define on your provider that returns `[]func() datasource.DataSource`, which +creates data source types that you define, which satisfy the `datasource.DataSource` interface. + +### Example + +#### SDKv2 + +The following example shows how to set up a provider schema, configuration, resources, and data sources using SDKv2. + +```go +func New() (*schema.Provider, error) { + return &schema.Provider{ + Schema: map[string]*schema.Schema{ + "attribute": { + /* ... */ + }, + }, + ConfigureContextFunc: configureProvider, + ResourcesMap: map[string]*schema.Resource{ + "exampleResource": exampleResource(), + /* ... */ + }, + DataSourcesMap: map[string]*schema.Resource{ + "exampleDataSource": exampleDataSource(), + /* ... */ + }, + }, nil +} +``` + +#### Framework + +The following shows the same section of provider code after the migration. + +```go +var _ provider.Provider = (*exampleProvider)(nil) + +func New() provider.Provider { + return &exampleProvider{} +} + +func (p *exampleProvider) Resources(_ context.Context) []func() resource.Resource { + return []func() resource.Resource{ + func() resource.Resource { + return &exampleResource{} + }, + /* ... */ + } +} + +func (p *exampleProvider) DataSources(_ context.Context) []func() datasource.DataSource { + return []func() datasource.DataSource{ + func() datasource.DataSource { + return &exampleDataSource{} + }, + /* ... */ + } +} + +func (p *exampleProvider) Schema(_ context.Context, _ provider.SchemaRequest, resp *provider.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "attribute": schema.SingleNestedBlock{ + /* ... */ + }, + }, + } +} + +func (p *exampleProvider) Metadata(_ context.Context, _ provider.MetadataRequest, resp *provider.MetadataResponse) { + resp.TypeName = "example" +} + +func (p *exampleProvider) Configure(_ context.Context, _ provider.ConfigureRequest, resp *provider.ConfigureResponse) { + /* ... */ +} +``` + +## Provider Schema + +A provider schema defines the attributes and behaviors of the provider itself. For example, a provider that connects to +a third-party API may define attributes for the base URL or a required authentication token. + +### SDKv2 + +In SDKv2, you implement a provider Schema by populating the `Schema` field on the `schema.Provider` struct. The `Schema` +field contains a `map[string]*schema.Schema`. Each map entry represents the name of the attribute and pointer to a +`schema.Schema` struct that defines that attribute's behavior. + +The following example defines the provider schema in the `Schema` field within the `schema.Provider` struct. + +```go +func New() *schema.Provider { + return &schema.Provider{ + Schema: map[string]*schema.Schema{ + /* ... */ + }, +``` + +### Framework + +In the Framework, the `Schema` method returns the provider schema. The `Schema` method is part of the +`provider.Provider` interface that your provider must implement. `Schema` returns a struct containing fields for +`Attributes` and `Blocks`. These `Attributes` and `Blocks` contain `map[string]schema.Attribute` and +`map[string]schema.Block`, respectively. Refer to [Providers - Schema](/terraform/plugin/framework/providers#schema) in the +Framework documentation for details. + +The following code shows the `Schema` method, which returns the provider schema. + +```go +func (p *ExampleCloudProvider) Schema(ctx context.Context, req provider.SchemaRequest, resp *provider.SchemaResponse) { + resp.Schema = schema.Schema{ + /* ... */ + } +} +``` + +Refer to the [Attributes](/terraform/plugin/framework/migrating/attributes-blocks/attribute-schema) and +[Blocks](/terraform/plugin/framework/migrating/attributes-blocks/blocks) pages in this migration guide to learn how to migrate +those fields to the Framework. + +### Migration Notes + +Remember the following differences between SDKv2 and the Framework when completing the migration. + +- In SDKv2, `schema.Schema` is a struct that defines attributes and behaviors (e.g., `Type`, `Optional`). In the +Framework `schema.Schema` is a struct that includes attributes and blocks. + +### Example + +This example shows how to use a nested block and a nested attribute for the SDKv2 and Framework examples, +respectively. Refer to the +[Blocks with Computed Fields](/terraform/plugin/framework/migrating/attributes-blocks/blocks-computed) page in this guide for more +details. + +#### SDKv2 + +The following example shows the configuration of the `example_attribute` attribute for the provider's `example_block` configuration block. + +```go +Schema: map[string]*schema.Schema{ + "example_block": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "example_attribute": { + Type: schema.TypeString, + Optional: true, + ValidateDiagFunc: validation.ToDiagFunc(validation.IsURLWithScheme(SupportedProxySchemesStr())), + ConflictsWith: []string{"example_block.0.another_attribute"}, + }, + /* ... */ +``` + +#### Framework + +The following shows the same section of provider code after the migration. + +This code implements the `example_attribute` attribute for the `example_Block` block with the Framework. + +```go +func (p *exampleProvider) Schema(_ context.Context, _ provider.SchemaRequest, resp *provider.SchemaResponse) { + resp.Schema = schema.Schema{ + /*...*/ + Blocks: map[string]schema.Block{ + "example_block": schema.ListNestedBlock{ + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "example_attribute": schema.StringAttribute{ + Optional: true, + Validators: []validator.String{ + attribute_validator.UrlWithScheme(supportedProxySchemesStr()...), + stringvalidator.ConflictsWith(path.MatchRelative().AtParent().AtName("another_attribute")), + }, + }, + }, + Validators: []validator.List{ + listvalidator.SizeAtMost(1), + }, +``` diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/migrating/resources/crud.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/migrating/resources/crud.mdx new file mode 100644 index 0000000000..645c0aae3b --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/migrating/resources/crud.mdx @@ -0,0 +1,259 @@ +--- +page_title: CRUD functions +description: >- + Learn how to migrate resource create, read, update, and delete (CRUD) + functions from SDKv2 to the plugin framework. +--- + +# CRUD functions + +In Terraform, a resource represents a single instance of a given resource type. They modify a specific resource in the +API and in Terraform's state through a set of Create, Read, Update, and Delete (CRUD) functions. A resource's CRUD +functions implement the logic required to manage your resources with Terraform. Refer to +[Resources - Define Resources](/terraform/plugin/framework/resources#define-resources) in the Framework documentation for details. + +This page explains how to migrate a resource's CRUD functions from SDKv2 to the plugin Framework. + +## SDKv2 + +In SDKv2, a resource's CRUD functions are defined by populating the relevant fields (e.g., `CreateContext`, +`ReadContext`) on the `schema.Resource` struct. + +The following code shows a basic implementation of CRUD functions with SDKv2. + +```go +func resourceExample() *schema.Resource { + return &schema.Resource{ + CreateContext: create, + ReadContext: read, + UpdateContext: update, + DeleteContext: delete, + /* ... */ +``` + +## Framework +In the Framework, you implement CRUD functions for your resource by defining a type that implements the +`resource.Resource` interface. To define functions related to state upgrade, import, and plan modification, +implement their respective interfaces on your resource: `ResourceWithUpgradeState`, `ResourceWithImportState`, and +`ResourceWithModifyPlan`. + +The following code shows how you define a `resource.Resource` which implements CRUD functions with the Framework. + +```go +type resourceExample struct { + p provider +} + +func (r *resourceExample) Create(ctx context.Context, req resource.CreateRequest, resp +*resource.CreateResponse) { + /* ... */ +} + +func (r *resourceExample) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + /* ... */ +} + +func (r *resourceExample) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + /* ... */ +} + +func (r *resourceExample) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + /* ... */ +} +``` +## Migration Notes +Remember the following differences between SDKv2 and the Framework when completing the migration. + +- In SDKv2 it is not necessary to define functions for parts of the CRUD lifecycle that are not used by a given +resource. For instance, if the resource does not support in-place modification, you do not need to define an `Update` +function. In the Framework, you must implement each of the CRUD lifecycle functions on all resources to satisfy the +`Resource` interface, even if the function does nothing. +- In SDKv2, the `Update` function (even if empty or missing) would automatically copy the request plan to the response state. This could be problematic when the `Update` function also returned errors as additional steps were required to disable the automatic SDKv2 behavior. In the Framework, the `Update` method must be written to explicitly copy data from `req.Plan` to `resp.State` to actually update the resource's state and prevent `Provider produced inconsistent result after apply` errors from Terraform. +- In SDKv2, calling `d.SetId("")` would signal resource removal. In the Framework, this is replaced with `resp.State.RemoveResource()`. Resource removal should only occur during the `Read` method to prevent `Provider produced inconsistent result after apply` errors from Terraform during other operations. The `Delete` method will automatically call `resp.State.RemoveResource()` if there are no errors. +- In SDKv2, you get and set attribute values in Terraform's state by calling `Get()` and `Set()` on +`schema.ResourceData`. In the Framework, you get attribute values from the configuration and plan by accessing +`Config` and `Plan` on `resource.CreateRequest`. You set attribute values in Terraform's state by mutating `State` +on `resource.CreateResponse`. +- In SDKv2, certain resource schema definition and data consistency errors are only visible as Terraform warning logs by default. After migration, these errors will always be visible to practitioners and prevent further Terraform operations. The [SDKv2 resource data consistency errors documentation](/terraform/plugin/sdkv2/resources/data-consistency-errors) discusses how to find these errors in SDKv2 resources and potential solutions **prior** to migrating. See the [Resolving Data Consistency Errors](#resolving-data-consistency-errors) section for Plugin Framework solutions **during** migration. + +## Example + +### SDKv2 + +The following example from shows implementations of CRUD functions on the with SDKv2. +The `UpdateContext` function is not implemented because the provider does not support updating this resource. + +```go +func resourceExample() *schema.Resource { + return &schema.Resource{ + CreateContext: create, + ReadContext: readNil, + DeleteContext: RemoveResourceFromState, + /* ... */ +``` + +The following example shows the implementation of the `create()` function with SDKv2. The implementations of +the `readNil()` and `RemoveResourceFromState()` functions are not shown for brevity. + +```go +func create(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + if err := d.Set("example_attribute", "value"); err != nil { + diags = append(diags, diag.Errorf("err: %s", err)...) + return diags + } + + return nil +} +``` + +### Framework +The following shows the same section of provider code after the migration. + +This code implements the `Create` function for the `example_resource` resource with the Framework. + +```go +func (r *exampleResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var plan exampleModel + + diags := req.Plan.Get(ctx, &plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + plan.ExampleAttribute = types.StringValue("value") + + diags = resp.State.Set(ctx, plan) + resp.Diagnostics.Append(diags...) +} +``` + +## Resolving Data Consistency Errors + + +See the [SDKv2 data consistency errors documentation](/terraform/plugin/sdkv2/resources/data-consistency-errors) for background info, debugging tips, and potential SDKv2 solutions. + + + +### Planned Value does not match Config Value + +If an SDKv2 resource is raising this type of error or [warning log](/terraform/plugin/sdkv2/resources/data-consistency-errors#checking-for-warning-logs): + +```text +TIMESTAMP [WARN] Provider "TYPE" produced an invalid plan for ADDRESS, but we are tolerating it because it is using the legacy plugin SDK. + The following problems may be the cause of any confusing errors from downstream operations: + - .ATTRIBUTE: planned value cty.StringVal("VALUE") does not match config value cty.StringVal("value") +``` + +This occurs for attribute schema definitions that are `Optional: true` and `Computed: true`; where the planned value, returned by the provider, does not match the attribute's config value or prior state value. For example, value's for an attribute of type string must match byte-for-byte. + +An example root cause of this issue could be from API normalization, such as a JSON string being returned from an API and stored in state with differing whitespace then what was originally in config. + +#### SDKv2 Example + +Here is an example of an SDKv2 resource schema and terraform config that simulates this data consistency error: + +```go +func thingResource() *schema.Resource { + return &schema.Resource{ + // ... + Schema: map[string]*schema.Schema{ + "word": { + Type: schema.TypeString, + Optional: true, + Computed: true, + StateFunc: func(word interface{}) string { + // This simulates an API returning the 'word' attribute as all uppercase, + // which is stored to state even if it doesn't match the config or prior value. + return strings.ToUpper(word.(string)) + }, + }, + }, + } +} +``` + +```hcl +resource "examplecloud_thing" "this" { + word = "value" +} +``` + +A [warning log](/terraform/plugin/sdkv2/resources/data-consistency-errors#checking-for-warning-logs) will be produced and the resulting state after applying a new resource will be `VALUE` instead of `value`. + +#### Migrating to Plugin Framework + +When a resource with this behavior and prior state is migrated to Plugin Framework, depending on the business logic, you could potentially see: + +- Resource drift in the plan; Terraform will always detect a change between the config and state value. If no [modification](/terraform/plugin/framework/resources/plan-modification) is implemented, you could see drift in the plan: +```hcl +resource "examplecloud_thing" "this" { + word = "value" +} +``` +```text +examplecloud_thing.this: Refreshing state... + +Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols: + ~ update in-place + +Terraform will perform the following actions: + + # examplecloud_thing.this will be updated in-place + ~ resource "examplecloud_thing" "this" { + ~ word = "VALUE" -> "value" + } + +Plan: 0 to add, 1 to change, 0 to destroy. +``` +- If you mimic the original SDKv2 behavior of storing a different value from config/prior value into state in the `Update` method, you will see an error like below: +```text +examplecloud_thing.this: Modifying... +╷ +│ Error: Provider produced inconsistent result after apply +│ +│ When applying changes to examplecloud_thing.this, provider "provider[\"TYPE\"]" produced an unexpected +│ new value: .word: was cty.StringVal("value"), but now cty.StringVal("VALUE"). +│ +│ This is a bug in the provider, which should be reported in the provider's own issue tracker. +``` + +#### Recommended Solution +To solve this issue, the provider code must preserve the config value or prior state value when producing the new state. The recommended way to implement this logic is by creating a [custom type](/terraform/plugin/framework/handling-data/types/custom) with [semantic equality logic](/terraform/plugin/framework/handling-data/types/custom#semantic-equality). A custom type can be shared across multiple resource attributes and will ensure that the semantic equality logic is invoked during the `Read`, `Create`, and `Update` methods respectively. + +For the above example, the semantic equality implementation below would resolve the resource drift and error: + + + +The example code below is a partial implementation of a custom type, please see the [Custom Value Type documentation](/terraform/plugin/framework/handling-data/types/custom#value-type) for guidance. + + + +```go +type CaseInsensitive struct { + basetypes.StringValue +} + +// ... custom value type implementation + +// StringSemanticEquals returns true if the given string value is semantically equal to the current string value. (case-insensitive) +func (v CaseInsensitive) StringSemanticEquals(_ context.Context, newValuable basetypes.StringValuable) (bool, diag.Diagnostics) { + var diags diag.Diagnostics + + newValue, ok := newValuable.(CaseInsensitive) + if !ok { + diags.AddError( + "Semantic Equality Check Error", + "An unexpected value type was received while performing semantic equality checks. "+ + "Please report this to the provider developers.\n\n"+ + "Expected Value Type: "+fmt.Sprintf("%T", v)+"\n"+ + "Got Value Type: "+fmt.Sprintf("%T", newValuable), + ) + + return false, diags + } + + return strings.EqualFold(newValue.ValueString(), v.ValueString()), diags +} +``` + diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/migrating/resources/import.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/migrating/resources/import.mdx new file mode 100644 index 0000000000..c7ebb97aa1 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/migrating/resources/import.mdx @@ -0,0 +1,138 @@ +--- +page_title: Resource import +description: >- + Learn how to migrate resource import functions from SDKv2 to the plugin + framework. Practitioners import resources to bring them under the control of + their Terraform projects. +--- + +# Resource import + +Practitioners can use the [`terraform import` command](/terraform/cli/commands/import) to let Terraform +begin managing existing infrastructure by importing an existing resource into their Terraform project's state. A +resource's importer function implements the logic to add a resource to Terraform's state. Refer to +[Resources - Import](/terraform/plugin/framework/resources/import) in the Framework documentation for details. + +This page explains how to migrate import functions from SDKv2 to the plugin Framework. + +## SDKv2 + +In SDKv2, the `Importer` field on the `schema.Resource` defines how the provider imports resources to Terraform's +state. The following example implements resource import with SDKv2. + +```go +func resourceExample() *schema.Resource { + return &schema.Resource{ + Importer: &schema.ResourceImporter{ + StateContext: StateContextFunc, + }, + /* ... */ +``` + +The `StateContextFunc` is the function called to import a resource into Terraform state. Any operations that +are needed to import the resource take place within this function and may result in the mutation of the `ResourceData` +that is passed into the function. The return value is a slice of `schema.ResourceData`. This slice might be as simple as returning the `ResourceData` that was passed into the function, or it may involve multiple fan out to multiple resources, such as AWS security groups. + +## Framework + +In the Framework, you implement the `ResourceWithImportState` interface on your `resource.Resource` type to allow +users to import a given resource. This interface requires that your type implement a `ImportState` function. + +The following code shows how you define an `ImportState` function with the Framework. + +```go +func (r *resourceExample) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + /* ... */ +} +``` + +The `ImportState` function includes a `resource.ImportStateResponse`, which you use to set your resource's state. + +## Migration Notes + +Remember the following differences between SDKv2 and the Framework when completing the migration. + +- In both SDKv2 and Framework, there is no access to the configuration, state, or plan during import. The import +functions can only access the value (e.g., the resource's `ID`) supplied to the `terraform import` command. +- In SDKv2, you implement resource importing by populating the `Importer` field on the `schema.Resource` struct. In the +Framework, you define an `ImportState` function on the type which implements `resource.Resource`. This implementation +satisfies the `resource.ResourceWithImportState` interface. + +## Example + +In the simple use case in which `schema.ImportStatePassthroughContext` has been used with SDKv2, migrating to the Framework involves using the `resource.ImportStatePassthroughID` function. + +### SDKv2 + +```go +func resourceExample() *schema.Resource { + return &schema.Resource{ + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + /* ... */ + } +} +``` + +### Framework + +```go +func (r *resourceExample) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp) +} +``` + +This example also shows one way to handle populating attributes with their default values during import. + +### SDKv2 + +The following example shows the import function for the `example_resource` resource with SDKv2. + +```go +func exampleResource() *schema.Resource { + return &schema.Resource{ + Importer: &schema.ResourceImporter{ + StateContext: importFunc, + }, + /* ... */ + } +} +``` + +The following example shows the implementation of the `importFunc` function with SDKv2. + +```go +func importFunc(ctx context.Context, d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { + d.SetId("id") + + if err := d.Set("attribute", "value"); err != nil { + return nil, fmt.Errorf("resource example import failed, error setting attribute: %w", err) + } + + return []*schema.ResourceData{d}, nil +} +``` + +### Framework +The following shows the same section of provider code after the migration. + +This code implements the `ResourceWithImportState` interface on the `exampleResource` type by defining an `ImportState` +function. + +```go +func (r *exampleResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + id := req.ID + + state := exampleModel{ + ID: types.StringValue("id"), + Attribute: types.StringValue("value"), + } + + diags := resp.State.Set(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } +} +``` diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/migrating/resources/index.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/migrating/resources/index.mdx new file mode 100644 index 0000000000..73d383a3aa --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/migrating/resources/index.mdx @@ -0,0 +1,219 @@ +--- +page_title: Migrating resources +description: >- + Learn how to migrate resources from SDKv2 to the plugin framework. +--- + +# Migrating resources + +Resources are an abstraction that allow Terraform to manage infrastructure objects by defining create, read, update, +and delete functionality that maps onto API operations. Resource schemas define what fields a resource has, give +Terraform metadata about those fields, and define how the resource behaves. Refer to +[Resources](/terraform/plugin/framework/resources) in the Framework documentation for details. + +This page explains how to migrate a resource's schema from SDKv2 to the plugin Framework. We also recommend reviewing these additional guides for resources throughout the migration: +- [Create, Read, Update, and Delete functions](/terraform/plugin/framework/migrating/resources/crud): The resource defines the logic to manage resources with Terraform. +- [Default](/terraform/plugin/framework/migrating/attributes-blocks/default-values): The resource specifies default values for attributes that are null within the configuration. +- [Import](/terraform/plugin/framework/migrating/resources/import): The resource defines the logic to add a resource to Terraform's state. +- [Plan modification](/terraform/plugin/framework/migrating/resources/plan-modification): The resource customizes the Terraform plan for known values or behaviors outside the practitioner's configuration. +- [State upgrade](/terraform/plugin/framework/migrating/resources/state-upgrade): The resource updates Terraform state information in advanced use cases. +- [Timeouts](/terraform/plugin/framework/migrating/resources/timeouts): The resource uses timeouts during create, read, update or delete operations. + +## SDKv2 +In SDKv2, resources are defined by the `ResourcesMap` field in the `schema.Provider` struct, which maps resource names +(strings) to their schema. Each schema is a `schema.Resource` struct that includes: + +- A `Schema` field, which defines resource attributes +- Fields for resource lifecycle functions such as `Create` and `CreateContext` +- Fields for functions to implement state upgrade (`StateUpgraders`), import (`Importer`), and customize diff +(`CustomizeDiff`) + +The following code shows a basic implementation of resource schema with SDKv2. + +```go +func New() *schema.Provider { + return &schema.Provider{ + ResourcesMap: map[string]*schema.Resource { + "resource_example": resourceExample(), + /* ... */ + }, + /* ... */ + } +} +``` + +SDKv2 defines the `schema.Resource` struct as follows. + +```go +schema.Resource{ + Schema map[string]*Schema + SchemaVersion int + MigrateState StateMigrateFunc + StateUpgraders []StateUpgrader + Create CreateFunc + Read ReadFunc + Update UpdateFunc + Delete DeleteFunc + Exists ExistsFunc + CreateContext CreateContextFunc + ReadContext ReadContextFunc + UpdateContext UpdateContextFunc + DeleteContext DeleteContextFunc + CreateWithoutTimeout CreateContextFunc + ReadWithoutTimeout ReadContextFunc + UpdateWithoutTimeout UpdateContextFunc + DeleteWithoutTimeout DeleteContextFunc + CustomizeDiff CustomizeDiffFunc + Importer *ResourceImporter + DeprecationMessage string + Timeouts *ResourceTimeout + Description string + UseJSONNumber bool +} +``` + +## Framework + +In the Framework, you define your provider's resources by adding them to your provider's `Resources` method. + +The `Resources` method on your `provider.Provider` returns a slice of functions that return types that +implement the `resource.Resource` interface for each resource your provider supports. + +The following code shows how you add a resource to your provider with the Framework. + +```go +func (p *provider) Resources(ctx context.Context) []func() resource.Resource { + return []func() resource.Resource{ + func() resource.Resource { + return resourceTypeExample{} + }, + } +} +``` + +The `resource.Resource` interface requires `Metadata`, `Schema`, `Create`, `Read`, `Update`, and `Delete` methods. + +The `Schema` method returns a `schema.Schema` struct which defines your resource's attributes. + +The `Metadata` method returns a type name that you define. + +The following code shows how you define a `resource.Resource` which implements these methods with the +Framework. + +```go +type resourceExample struct{} + +func (r *resourceExample) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + /* ... */ +} + +func (r *resourceExample) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + /* ... */ +} + +func (r *resourceExample) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + /* ... */ +} + +func (r *resourceExample) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + /* ... */ +} + +func (r *resourceExample) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + /* ... */ +} + +func (r *resourceExample) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + /* ... */ +} +``` + +Refer to the [Resources - CRUD functions](/terraform/plugin/framework/migrating/resources/crud) page in this guide to learn how to +implement the `resource.Resource` interface. + +## Migration Notes + +Remember the following differences between SDKv2 and the Framework when completing the migration. + +- SDKv2 uses `schema.Resource` structs to define resources. These structs have a `Schema` field which holds a +`schema.Schema` to define the resource's attributes and behavior. In the Framework, you define a type that implements +the `resource.Resource` interface, which includes a `Schema` method that returns your resource's schema. +- SDKv2 implements a resource's CRUD operations as functions on the `schema.Resource`. In the Framework, you define a +type that implements the `resource.Resource` interface. The resource interface contains the functions that define your resource's +CRUD operations. +- SDKv2 by default demotes certain resource schema definition and data consistency errors to only be visible as Terraform warning logs. After migration, these errors will always be visible to practitioners and prevent further Terraform operations. The [SDKv2 resource data consistency errors documentation](/terraform/plugin/sdkv2/resources/data-consistency-errors) discusses how to find these errors in SDKv2 resources and potential solutions **prior** to migrating. See the [CRUD - Resolving Data Consistency Errors](/terraform/plugin/framework/migrating/resources/crud#resolving-data-consistency-errors) section for Plugin Framework solutions **during** migration. + +## Example + +### SDKv2 + +In SDKv2, the `ResourcesMap` field on the `schema.Provider` struct holds a `map[string]*schemaResource`. A typical +pattern is to implement a function that returns `schema.Resource`. + +```go +func New() (*schema.Provider, error) { + return &schema.Provider { + ResourcesMap: map[string]*schema.Resource { + "example_resource": exampleResource(), + /* ... */ +``` + +This code defines the `example_resource` resource by mapping the resource name to the `exampleResource` struct. + +```go +func exampleResource() *schema.Resource { + return &schema.Resource{ + CreateContext: createResource, + DeleteContext: deleteResource, + ReadContext: readResource, + + Schema: map[string]*schema.Schema{ + "attribute": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{'a', 'b'}, false)), + }, + /* ... */ +``` + +### Framework + +The following shows the same section of provider code after the migration. + +```go +func (p *exampleProvider) Resources(_ context.Context) []func() resource.Resource { + return []func() resource.Resource{ + func() resource.Resource { + return &exampleResource{} + }, + /* ... */ + } +} +``` + +This code defines the `Schema` and `Metadata` methods for the `Resource`. + +```go +func (r *exampleResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = "example_resource" +} + +func (r *exampleResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + // Required attributes + "attribute": schema.StringAttribute{ + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + Validators: []validator.String{ + stringvalidator.OneOf([]string{"a", "b"}...), + }, + }, + /* ... */ + }, + } +} +``` diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/migrating/resources/plan-modification.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/migrating/resources/plan-modification.mdx new file mode 100644 index 0000000000..af82412799 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/migrating/resources/plan-modification.mdx @@ -0,0 +1,182 @@ +--- +page_title: Plan modification +description: >- + Learn how to migrate resource CustomizeDiff functions in SDKv2 to + plan modifiers in the Terraform plugin framework. +--- + +# Plan modification + +Your provider can modify the Terraform plan to match the expected end state. This can include replacing unknown values +with expected known values or marking a resource that must be replaced. Refer to +[Plan Modification](/terraform/plugin/framework/resources/plan-modification) in the Framework documentation for details. + +This page explains how to migrate resource `CustomizeDiff` functions in SDKv2 to `PlanModifiers` in the plugin +Framework. + +## SDKv2 +In SDKv2, plan modification is implemented with the `CustomizeDiff` field on the `schema.Resource` struct. The following +code shows a basic implementation of plan modification with SDKv2. + +```go +func resourceExample() *schema.Resource { + return &schema.Resource{ + CustomizeDiff: CustomizeDiffFunc, + /* ... */ +``` + +## Framework + +In the Framework, you implement plan modification either by implementing the `ResourceWithModifyPlan` interface on your +resource type, or by implementing `PlanModifiers` on individual attributes. This page demonstrates how to implement the +plan modifiers on individual attributes. Refer to +[Attributes - Force New](/terraform/plugin/framework/migrating/attributes-blocks/force-new) in this guide for further information on how +to implement a plan modifier on an attribute. + +The `ResourceWithModifyPlan` interface requires a `ModifyPlan` function. + +The following code shows how you can implement the `ModifyPlan` function on your `resource.Resource` type. + +```go +func (r *resourceExample) ModifyPlan(ctx context.Context, req resource.ModifyPlanRequest, resp *resource.ModifyPlanResponse) { + /* ... */ +} +``` +## Migration Notes +Remember the following differences between SDKv2 and the Framework when completing the migration. + +- In SDKv2, you implement plan modification with the `CustomizeDiff` field on the `schema.Resource` struct. In the +Framework, you can either implement plan modification for the entire resource by implementing the +`ResourceWithModifyPlan` interface, or on individual attributes by adding `PlanModifiers` to your resource attributes. +- Many existing CustomizeDiff implementations may be better suited to implementation as attribute plan modifiers in the +Framework. + +## Example + +### SDKv2 + +In SDKv2, the `CustomizeDiff` field on the `schema.Resource` struct refers to a function or set of functions that +implement plan modification. + +The following example shows the use of `CustomizeDiff` to keep two attributes +synchronized (i.e., ensure that they contain the same value) with SDKv2. + +```go +func resourcePassword() *schema.Resource { + /* ... */ + customizeDiffFuncs = append(customizeDiffFuncs, planSyncIfChange("attribute_one", "attribute_two")) + customizeDiffFuncs = append(customizeDiffFuncs, planSyncIfChange("attribute_two", "attribute_one")) + + return &schema.Resource{ + /* ... */ + CustomizeDiff: customdiff.All( + customizeDiffFuncs..., + ), + } +} +``` + +The following example shows the implementation of the `planSyncIfChange` function. + +```go +func planSyncIfChange(key, keyToSync string) func(context.Context, *schema.ResourceDiff, interface{}) error { + return customdiff.IfValueChange( + key, + func(ctx context.Context, oldValue, newValue, meta interface{}) bool { + return oldValue != newValue + }, + func(_ context.Context, d *schema.ResourceDiff, _ interface{}) error { + return d.SetNew(keyToSync, d.Get(key)) + }, + ) +} +``` + +### Framework + +Many existing `CustomizeDiff` implementations would be better suited to migration to attribute plan modifiers in the +Framework. This code shows the implementation using attribute plan modifiers with the Framework. + +```go +func exampleSchema() schema.Schema { + return schema.Schema{ + /* ... */ + Attributes: map[string]schema.Attribute{ + /* ... */ + "attribute_one": schema.BoolAttribute{ + /* ... */ + PlanModifiers: []planmodifier.Bool{ + planmodifiers.SyncAttributePlanModifier(), + /* ... */ + }, + }, + + "attribute_two": schema.BoolAttribute{ + /* ... */ + PlanModifiers: []planmodifier.Bool{ + planmodifiers.SyncAttributePlanModifier(), + /* ... */ + }, + }, +``` + +The following shows an implementation of `SyncAttributePlanModifier` in the Framework. + +```go +func SyncAttributePlanModifier() planmodifier.Bool { + return &syncAttributePlanModifier{} +} + +type syncAttributePlanModifier struct { +} + +func (d *syncAttributePlanModifier) Description(ctx context.Context) string { + return "Ensures that attribute_one and attribute_two attributes are kept synchronised." +} + +func (d *syncAttributePlanModifier) MarkdownDescription(ctx context.Context) string { + return d.Description(ctx) +} + +func (d *syncAttributePlanModifier) PlanModifyBool(ctx context.Context, req planmodifier.BoolRequest, resp *planmodifier.BoolResponse) { + var attributeOne types.Bool + diags := req.Plan.GetAttribute(ctx, path.Root("attribute_one"), &attributeOne) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + var attributeTwo types.Bool + req.Plan.GetAttribute(ctx, path.Root("attribute_two"), &attributeTwo) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + if !attributeOne.IsNull() && !attributeTwo.IsNull() && (attributeOne.ValueBool() != attributeTwo.ValueBool()) { + resp.Diagnostics.AddError( + "attribute_one and attribute_two are both configured with different values", + "attribute_one is deprecated, use attribute_two instead", + ) + return + } + + // Default to true for both attribute_one and attribute_two when both are null. + if attributeOne.IsNull() && attributeTwo.IsNull() { + resp.PlanValue = types.BoolValue(true) + return + } + + // Default to using value for attribute_two if attribute_one is null + if attributeOne.IsNull() && !attributeTwo.IsNull() { + resp.PlanValue = numericConfig + return + } + + // Default to using value for attribute_one if attribute_two is null + if !attributeOne.IsNull() && attributeTwo.IsNull() { + resp.PlanValue = numberConfig + return + } +} +``` diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/migrating/resources/state-upgrade.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/migrating/resources/state-upgrade.mdx new file mode 100644 index 0000000000..7b5c05b331 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/migrating/resources/state-upgrade.mdx @@ -0,0 +1,158 @@ +--- +page_title: State upgrading +description: >- + Learn how to Migrate resource StateUpgraders in SDKv2 to UpgradeState in the + plugin framework. State upgraders let users update resources provisioned with + old schema configurations. +--- + +# State upgraders + +When you update a resource's implementation in your provider, some changes may not be compatible with old versions. You +can create state upgraders to automatically migrate resources provisioned with old schema configurations. Refer to +[State Upgrade](/terraform/plugin/framework/resources/state-upgrade) in the Framework documentation for details. + +This page explains how to migrate resource `StateUpgraders` in SDKv2 to `UpgradeState` in the plugin Framework. + +## SDKv2 + +In SDKv2, state upgraders are defined by populating the `StateUpgraders` field on the `schema.Resource` struct. Refer +to [State Migration](/terraform/plugin/sdkv2/resources/state-migration) in the SDKv2 documentation for details. + +The following code shows a basic implementation of the `stateUpgraders` field in SDKv2. + +```go +func resourceExample() *schema.Resource { + return &schema.Resource{ + StateUpgraders: []schema.StateUpgrader{ + { + Version: int, + Type: cty.Type, + Upgrade: StateUpgradeFunc, + }, + /* ... */ +``` + +## Framework + +In the Framework, you implement the `ResourceWithUpgradeState` interface on your resource to upgrade your +resource's state when required. + +The following code shows how you define an `UpgradeState` function with the Framework. + +```go +func (r *resourceExample) UpgradeState(context.Context) map[int64]resource.StateUpgrader { + return map[int64]resource.StateUpgrader{ + 0: { + PriorSchema: *schema.Schema, + StateUpgrader: func(context.Context, UpgradeStateRequest, *UpgradeStateResponse), + }, + /* ... */ +``` + +The `UpgradeState` function returns a map from state versions to structs that implement state upgrade from the given +version to the latest version. + +## Migration Notes + +Remember the following differences between SDKv2 and the Framework when completing the migration. + +- In SDKv2, you implement state upgraders populating the `StateUpgraders` field on the `schema.Resource` struct. In the +Framework, you define an `UpgradeState` function on the resource itself. +- In SDKv2, state upgraders apply each state upgrader in turn. For example, version 0 => version 1, version 1 => +version 2. In the Framework, each `UpgradeState` function is required to perform all of the necessary transformations in +a single step. For example, version 0 => version 2, version 1 => version 2. + +## Example + +### SDKv2 + +In SDKv2 the `schema.Resource` struct has a `StateUpgraders` field that holds `[]schema.StateUpgrader` struct(s). + +The following example from the shows the state upgrade functions for the `example_resource` +resource with SDKv2. + +```go +func exampleResource() *schema.Resource { + return &schema.Resource{ + Schema: exampleSchemaV2(), + SchemaVersion: 2, + StateUpgraders: []schema.StateUpgrader{ + { + Version: 0, + Type: exampleResourceV0().CoreConfigSchema().ImpliedType(), + Upgrade: exampleResourceStateUpgradeV0, + }, + { + Version: 1, + Type: exampleResourceV1().CoreConfigSchema().ImpliedType(), + Upgrade: exampleResourceStateUpgradeV1, + }, + }, + /* ... */ +``` + +The following example shows the implementation of the `exampleResourceStateUpgradeV0` function with SDKv2. + +```go +func exampleResourceStateUpgradeV0(_ context.Context, rawState map[string]interface{}, _ interface{}) (map[string]interface{}, error) { + if rawState == nil { + return nil, fmt.Errorf("example resource state upgrade failed, state is nil") + } + + rawState["example_attribute"] = "value" + + return rawState, nil +} +``` + +### Framework + +The following shows the same section of provider code after the migration. + +This code implements the `ResourceWithUpgradeState` interface on the `exampleResource` type by defining an +`UpgradeState` function. The `UpgradeState` function returns a map from each state version (int64) to a +`ResourceStateUpgrader` struct. + +```go +func (r *exampleResource) UpgradeState(context.Context) map[int64]resource.StateUpgrader { + schemaV0 := exampleSchemaV0() + schemaV1 := exampleSchemaV1() + + return map[int64]resource.StateUpgrader{ + 0: { + PriorSchema: &schemaV0, + StateUpgrader: upgradeExampleResourceStateV0toV2, + }, + 1: { + PriorSchema: &schemaV1, + StateUpgrader: upgradeExampleResourceStateV1toV2, + }, + } +} +``` + +This code implements the `upgradeExampleResourceStateV0toV2` state upgrade function. + +```go +func upgradeExampleResourceStateV0toV2(ctx context.Context, req resource.UpgradeStateRequest, resp *resource.UpgradeStateResponse) { + type modelV0 struct { + ID types.String `tfsdk:"id"` + } + + var exampleDataV0 modelV0 + + resp.Diagnostics.Append(req.State.Get(ctx, &exampleDataV0)...) + if resp.Diagnostics.HasError() { + return + } + + exampleDataV2 := exampleModelV2{ + ID: exampleDataV0.ID, + } + + exampleDataV2.ExampleAttribute = types.StringValue("value") + + diags := resp.State.Set(ctx, exampleDataV2) + resp.Diagnostics.Append(diags...) +``` diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/migrating/resources/timeouts.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/migrating/resources/timeouts.mdx new file mode 100644 index 0000000000..a7a8d76c2b --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/migrating/resources/timeouts.mdx @@ -0,0 +1,136 @@ +--- +page_title: Timeouts +description: >- + Learn how to migrate timeouts from SDKv2 to the framework. +--- + +# Timeouts + +The Framework can be used in conjunction with the [terraform-plugin-framework-timeouts](https://github.com/hashicorp/terraform-plugin-framework-timeouts) module in order to allow defining timeouts in configuration and have them be available in CRUD functions. + +## Specifying Timeouts in Configuration + +Timeouts can be defined using either nested blocks or nested attributes. + +If you are writing a new provider using [terraform-plugin-framework](https://github.com/hashicorp/terraform-plugin-framework) +then we recommend using nested attributes. + +If you are migrating a provider from SDKv2 to the Framework and +you are already using timeouts you can either continue to use block syntax, or switch to using nested attributes. +However, switching to using nested attributes will require that practitioners that are using your provider update their +Terraform configuration. + +#### Block + +If your configuration is using a nested block to define timeouts, such as the following: + +```hcl +resource "timeouts_example" "example" { + /* ... */ + + timeouts { + create = "60m" + } +} +``` + +Import the [timeouts module](https://github.com/hashicorp/terraform-plugin-framework-timeouts). + +```go +import ( + /* ... */ + "github.com/hashicorp/terraform-plugin-framework-timeouts/resource/timeouts" +) +```` + +You can use this module to mutate the `schema.Schema` as follows: + +```go +func (t *exampleResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + /* ... */ + + Blocks: map[string]schema.Block{ + "timeouts": timeouts.Block(ctx, timeouts.Opts{ + Create: true, + }), + }, +``` + +#### Attribute + +If your configuration is using nested attributes to define timeouts, such as the following: + +```hcl +resource "timeouts_example" "example" { + /* ... */ + + timeouts = { + create = "60m" + } +} +``` + +You can use this module to mutate the `schema.Schema` as follows: + +```go +func (t *exampleResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + /* ... */ + "timeouts": timeouts.Attributes(ctx, timeouts.Opts{ + Create: true, + }), + }, +``` + +## Updating Models + +In functions in which the config, state or plan is being unmarshalled, for instance, the `Create` function, the model +will need to be updated. + +```go +func (e *exampleResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var data exampleResourceData + + diags := req.Plan.Get(ctx, &data) + resp.Diagnostics.Append(diags...) +``` + +Modify the `exampleResourceData` model to include a field for timeouts using a [`timeouts.Value`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-timeouts/resource/timeouts#Value) type. + +```go +type exampleResourceData struct { + /* ... */ + Timeouts timeouts.Value `tfsdk:"timeouts"` +``` + +## Accessing Timeouts in CRUD Functions + +Once the model has been populated with the config, state or plan the duration of the timeout can be accessed by calling +the appropriate helper function (e.g., [`timeouts.Create`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-timeouts/resource/timeouts#Value.Create)) and then used to configure timeout behaviour, for instance: + +```go +func (e *exampleResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var data exampleResourceData + + diags := req.Plan.Get(ctx, &data) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + createTimeout, diags := data.Timeouts.Create(ctx, 20*time.Minute) + + resp.Diagnostics.Append(diags...) + + if resp.Diagnostics.HasError() { + return + } + + ctx, cancel := context.WithTimeout(ctx, createTimeout) + defer cancel() + + /* ... */ +} +``` diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/migrating/schema/index.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/migrating/schema/index.mdx new file mode 100644 index 0000000000..5afd215f2e --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/migrating/schema/index.mdx @@ -0,0 +1,140 @@ +--- +page_title: Migrating schema +description: >- + Learn how to migrate schema from SDKv2 to the plugin framework. +--- + +# Migrating schema + +Providers, resources, and data sources all use schema to define their attributes and behavior. Schemas specify the +constraints of Terraform configuration blocks and how the provider, resource, or data source behaves. Refer to +[Schemas](/terraform/plugin/framework/handling-data/schemas) in the Framework documentation for details. + +This page explains the differences between the schema used by SDKv2 and the Framework. We also recommend reviewing these additional schema guides throughout the migration: +/attributes-blocks/ +- [Attributes](/terraform/plugin/framework/migrating/attributes-blocks/attribute-schema) where the schema defines practitioner or provider data associated with a value and type. +- [Attribute types](/terraform/plugin/framework/migrating/attributes-blocks/types) where the schema defines the expected data structure and syntax. +- [Attribute fields](/terraform/plugin/framework/migrating/attributes-blocks/fields) where the behaviors of an attribute are defined, such as `Required`, `Optional`, `Computed`, and `Sensitive`. +- [Attribute defaults](/terraform/plugin/framework/migrating/attributes-blocks/default-values) where the schema defines a value for an attribute which should be automatically included in a Terraform plan if it is not configured by the practitioner. +- [Attributes without in-place updates](/terraform/plugin/framework/migrating/attributes-blocks/force-new) where the schema defines an attribute that requires resource replacement if the value is updated. +- [Attribute predefined validations](/terraform/plugin/framework/migrating/attributes-blocks/validators-predefined) and [custom validations](/terraform/plugin/framework/migrating/attributes-blocks/validators-custom) where the schema defines the syntax, constraints, or encoding expectations of a value. +- [Blocks](/terraform/plugin/framework/migrating/attributes-blocks/attribute-schema) and [computed blocks](/terraform/plugin/framework/migrating/attributes-blocks/blocks-computed) where the schema defines structural configuration sections of data, typically with nested attributes or further blocks. + + +## Schema Structs + +SDKv2 uses `schema.Schema` structs to define the structure, type, and behavior of values drawn from configuration, +state, or plan data. The same `schema.Schema` struct type is used for providers, resources, and data sources. The +schema struct is returned by the function that creates the provider, resource, or data source in question. + +The Framework uses `schema.Schema` structs for providers, resources, and data sources. The schema struct is returned by +a `Schema` method you define for the provider and each resource type and data source type. Refer to +[Framework](#framework) for details. + +## SDKv2 + +The following code shows basic implementations using `schema.Schema` structs to define schemas for providers, resources, +and data sources with SDKv2. + +```go +func New() *schema.Provider { + return &schema.Provider{ + Schema: map[string]*schema.Schema{}, + /* ... */ + } +} +``` + +```go +func resourceExample() *schema.Resource { + return &schema.Resource{ + Schema: map[string]*schema.Schema{}, + /* ... */ + } +} +``` + +```go +func dataSourceExample() *schema.Resource { + return &schema.Resource{ + Schema: map[string]*schema.Schema{}, + /* ... */ + } +} +``` + +SDKv2 defines the `schema.Schema` struct as follows. + +```go +type Schema struct { + Type ValueType + ConfigMode SchemaConfigMode + Required bool + Optional bool + Computed bool + ForceNew bool + DiffSuppressFunc SchemaDiffSuppressFunc + DiffSuppressOnRefresh bool + Default interface{} + DefaultFunc SchemaDefaultFunc + Description string + StateFunc SchemaStateFunc + Elem interface{} + MaxItems int + MinItems int + Set SchemaSetFunc + ConflictsWith []string + ExactlyOneOf []string + AtLeastOneOf []string + RequiredWith []string + Deprecated string + ValidateFunc SchemaValidateFunc + ValidateDiagFunc SchemaValidateDiagFunc + Sensitive bool +} +``` + +## Framework + +In the Framework, you implement `Schema` method for your provider, resources, and data sources. This function is +required by the `provider.Provider`, `resource.Resource`, and `datasource.DataSource` interfaces, respectively. + +The following code shows how you define the `Schema` method for your provider, resources, and data sources. + +```go +func (p *ExampleCloudProvider) Schema(ctx context.Context, req provider.SchemaRequest, resp *provider.SchemaResponse) { + resp.Schema = schema.Schema{/* ... */} +} +``` + +```go +func (r *resourceExample) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{/* ... */} +} +``` + +```go +func (r *dataSourceExample) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) { + resp.Schema = schema.Schema{/* ... */} +} +``` + +You use the `Attributes` field to define attributes for your provider, resources, and data sources. You use the +`Blocks` field to define named blocks. + +## Migration Notes + +Remember the following differences between SDKv2 and the Framework when completing the migration. + +- SDKv2 uses `schema.Schema` structs to define the provider, resources, and data sources. The Framework uses concept-specific +`schema.Schema` structs instead. +- In SDKv2, schema structs are returned when a provider, resource, or data type is created. In the Framework, the +provider and each resource and data type have a `Schema` method that returns the schema. +- In SDKv2, schema structs have a `Set` field which can be populated with a `SchemaSetFunc` which is used for hashing. +In the Framework, this is not required and does not need to be migrated. +- The `schema.Schema` struct includes fields that you use to define +[attributes](/terraform/plugin/framework/migrating/attributes-blocks/attribute-schema) and +[blocks](/terraform/plugin/framework/migrating/attributes-blocks/blocks) for your provider and each resource +and data source. +- When you populate the `Version` field in `schema.Schema` for a resource in the Framework, copy the `Version` +field in `schema.Schema` from the SDKv2 version of that resource. diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/migrating/testing.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/migrating/testing.mdx new file mode 100644 index 0000000000..79eb6ea741 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/migrating/testing.mdx @@ -0,0 +1,230 @@ +--- +page_title: Testing migration +description: >- + Learn how to write tests that verify that migrating from SDKv2 to the + Framework does not affect provider behavior. +--- + +# Testing + +During migration, you should [write tests](#testing-migration) to verify that the behaviour of your provider has not +been altered by the migration itself. You will also need to [update](#provider-factories) your tests too. + + + + If [muxing](/terraform/plugin/framework/migrating/mux), only migrated resources and data sources require immediate updates of testing code. + + + +## Testing Migration + +During migration, we recommend writing tests to verify that switching from SDKv2 to the Framework has not affected your +provider's behavior. These tests use identical configuration. The first test step applies a plan and generates state +with the SDKv2 version of the provider. The second test step generates a plan with the Framework version of the provider +and verifies that the plan is a no-op, indicating that migrating to the framework has not altered behaviour. + +Use the `ExternalProviders` field within a `resource.TestStep` to specify the configuration of a specific provider to +use during each test step. You can specify a version of the provider built on SDKv2 during the first test step, and +then you can use the version of the provider built on the Framework in subsequent test step(s) to verify that Terraform +CLI does not detect any planned changes. + +You must also update the [provider factories](#provider-factories) to use +the Framework. + +### Example + +These examples show how you can use external providers to generate state with a previous version of the provider +and then verify that there are no planned changes after migrating to the Framework. + +- The first `TestStep` uses `ExternalProviders` to cause `terraform apply` to execute with a previous version of the +provider, which is built on SDKv2. +- The second `TestStep` uses `ProtoV5ProviderFactories` so that the test uses the provider code contained within the +provider repository. The second step uses `ConfigPlanChecks` to verify that a no-op plan is generated. + +#### Managed Resource + +In this example configuration, all managed resource attribute values are compared between the SDK and Framework implementations: + +```go +func TestResource_UpgradeFromVersion(t *testing.T) { + /* ... */ + resource.Test(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ExternalProviders: map[string]resource.ExternalProvider{ + "": { + VersionConstraint: "", + Source: "hashicorp/", + }, + }, + Config: `resource "provider_resource" "example" { + /* ... */ + }`, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("provider_resource.example", "", ""), + /* ... */ + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "provider_resource" "example" { + /* ... */ + }`, + // ConfigPlanChecks is a terraform-plugin-testing feature. + // If acceptance testing is still using terraform-plugin-sdk/v2, + // use `PlanOnly: true` instead. When migrating to + // terraform-plugin-testing, switch to `ConfigPlanChecks` or you + // will likely experience test failures. + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectEmptyPlan(), + }, + }, + }, + }, + }) +} +``` + +#### Data Source + + + +Prefer individually testing all attribute values of a data source instead of this testing pattern. Testing individual attribute values will catch unexpected data handling changes after migration and prevent any expected introduction of new attributes from causing test failures when using this pattern. + + + +Since data sources are refreshed every Terraform plan and do not use prior state during planning, additional configuration of a separate managed resource or output value is required to track value differences. The [`terraform_data` managed resource](/terraform/language/resources/terraform-data) is one option that is implicitly available for configurations in Terraform 1.4 and later. If testing on older versions of Terraform is required, use the [`null_resource` managed resource](https://registry.terraform.io/providers/hashicorp/null/latest/docs/resources/resource) with an associated `ExternalProviders` step configuration instead. + +In this example configuration, all data source attribute values are compared between the SDK and Framework implementations: + +```go +func TestDataSource_UpgradeFromVersion(t *testing.T) { + /* ... */ + resource.Test(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ExternalProviders: map[string]resource.ExternalProvider{ + "": { + VersionConstraint: "", + Source: "hashicorp/", + }, + }, + Config: `data "provider_datasource" "test" { + /* ... */ + } + + resource "terraform_data" "test" { + input = data.provider_datasource.test + }`, + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `data "provider_datasource" "test" { + /* ... */ + } + + resource "terraform_data" "test" { + input = data.provider_datasource.test + }`, + // ConfigPlanChecks is a terraform-plugin-testing feature. + // If acceptance testing is still using terraform-plugin-sdk/v2, + // use `PlanOnly: true` instead. When migrating to + // terraform-plugin-testing, switch to `ConfigPlanChecks` or you + // will likely experience test failures. + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectEmptyPlan(), + }, + }, + }, + }, + }) +} +``` + +## Provider Factories + +Existing tests should require minimal updates when migrating from SDKv2 to the Framework. The only critical change +relates to the provider factories that create the provider during the test case. Refer to [Acceptance Tests - Specify Providers](/terraform/plugin/framework/acctests#specify-providers) in the Framework +documentation for details. + +We also recommend writing tests that verify that switching from SDKv2 to the Framework has not affected provider +behavior. Refer to [Testing Migration](#testing-migration) for details. + +### SDKv2 + +In SDKv2, you use the `ProviderFactories` field on the `resource.TestCase` struct to obtain `schema.Provider`. + +The following example shows a test written in SDKv2. + +```go +resource.UnitTest(t, resource.TestCase{ + ProviderFactories: testProviders(), +``` + +### Framework + +In the Framework, use either the `ProtoV5ProviderFactories` or `ProtoV6ProviderFactories` field on the +`resource.TestCase` struct to obtain the `provider.Provider`, depending on the +[Terraform Plugin Protocol](/terraform/plugin/how-terraform-works#terraform-plugin-protocol) version your provider is using. + +The following example shows how you can write a test in the Framework for a Provider that uses protocol version 6. + +```go +resource.UnitTest(t, resource.TestCase{ + ProtoV6ProviderFactories: protoV6ProviderFactories(), +``` + +### Example + +#### SDKv2 + +The following code sample shows how to define provider factories within a test case when using SDKv2. + +```go +func TestDataSource_Exmple(t *testing.T) { + /* ... */ + resource.UnitTest(t, resource.TestCase{ + ProviderFactories: testProviders(), + /* ... */ + }) +} +``` + +The following shows how to generate provider factories when using SDKv2. + +```go +func testProviders() map[string]func() (*schema.Provider, error) { + return map[string]func() (*schema.Provider, error){ + "example": func() (*schema.Provider, error) { return New(), nil }, + } +} +``` + +#### Framework + +The following shows how to define provider factories within a test case when using the Framework. + +```go +func TestDataSource_Example(t *testing.T) { + /* ... */ + + resource.UnitTest(t, resource.TestCase{ + ProtoV5ProviderFactories: protoV5ProviderFactories(), + /* ... */ + }) +} +``` + +The following shows how to generate provider factories when using +the Framework. The call to `New` returns an instance of the provider. Refer to +[Provider Definition](/terraform/plugin/framework/migrating/providers#provider-definition) in this guide for details. + +```go +func protoV5ProviderFactories() map[string]func() (tfprotov5.ProviderServer, error) { + return map[string]func() (tfprotov5.ProviderServer, error){ + "example": providerserver.NewProtocol5WithError(New()), + } +} +``` diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/provider-servers.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/provider-servers.mdx new file mode 100644 index 0000000000..a8d07bd5e9 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/provider-servers.mdx @@ -0,0 +1,88 @@ +--- +page_title: Provider servers +description: >- + Learn how to implement a provider server in the Terraform plugin + framework. Provider servers are plugins that allow Terraform to interact with + APIs. +--- + +# Provider servers + +Before a [provider](/terraform/plugin/framework/providers) can be used with Terraform, it must implement a [gRPC server](https://grpc.io) that supports Terraform-specific connection and handshake handling on startup. The server must then implement the [Terraform Plugin Protocol](/terraform/plugin/how-terraform-works#terraform-plugin-protocol). + +The framework handles the majority of the server implementation details, however it is useful from a provider developer perspective to understand the provider server details at least at a high level. + +## Protocol Version + +The [Terraform Plugin Protocol](/terraform/plugin/how-terraform-works#terraform-plugin-protocol) defines the compatibility between Terraform CLI and the underlying provider. It is versioned, with newer versions implementing support for enhanced provider functionality but also requiring newer Terraform CLI versions. The framework implements two versions of the protocol. + +* **Version 6**: The latest and recommended version, [protocol version 6](/terraform/plugin/how-terraform-works#protocol-version-6) implements enhanced provider functionality and requires Terraform CLI 1.0 or later. If [combining providers with terraform-plugin-sdk provider code](/terraform/plugin/mux/combining-protocol-version-6-providers), such as [migrating to the framework](/terraform/plugin/framework/migrating), then Terraform CLI 1.1 or later is required. +* **Version 5**: The prior version, [protocol version 5](/terraform/plugin/how-terraform-works#protocol-version-5) implements base provider functionality and requires Terraform CLI 0.12 or later. + +Provider developers must choose either version 6 or version 5 and should consistently use that one version across implementations. + +## Implementations + +Terraform and provider developers have multiple ways to interact with the provider server implementation: + +* **Production**: Terraform CLI expects a binary that starts the provider server on process startup and stops the provider when called. +* **Developer Overrides Testing**: The [CLI configuration file](/terraform/cli/config/config-file#development-overrides-for-provider-developers) maps provider addresses to locally built binaries, which then operate similar to production. +* **Acceptance Testing**: The [acceptance testing framework](/terraform/plugin/framework/acctests) maps provider names to functions that directly start the provider server and will automatically stop the provider between test steps. +* **Debugging**: Provider developers, typically via a code editor or debugger tool, manually start a provider server for [debugging](/terraform/plugin/framework/debugging) which Terraform CLI is then configured to use rather than a normal binary. + +### Production and Developer Overrides + +Go language programs implement startup logic via a `main` function. Conventionally, this is done in a `main.go` file at the root of a project. The `main` function must eventually call the framework functionality for managing provider servers in the [`providerserver` package](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/providerserver). + +An example `main.go` file for starting a protocol version 6 provider server: + +```go +package main + +import ( + "context" + "flag" + "log" + + "github.com/example-namespace/terraform-provider-example/internal/provider" + "github.com/hashicorp/terraform-plugin-framework/providerserver" +) + +var ( + // Example version string that can be overwritten by a release process + version string = "dev" +) + +func main() { + opts := providerserver.ServeOpts{ + // TODO: Update this string with the published name of your provider. + Address: "registry.terraform.io/example-namespace/example", + } + + err := providerserver.Serve(context.Background(), provider.New(version), opts) + + if err != nil { + log.Fatal(err.Error()) + } +} +``` + +To configure the provider server for protocol version 5, set the [`providerserver.ServeOpts` type `ProtocolVersion` field](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/providerserver#ServeOpts.ProtocolVersion) to `5`: + +```go +opts := providerserver.ServeOpts{ + // TODO: Update this string with the published name of your provider. + Address: "registry.terraform.io/example-namespace/example", + ProtocolVersion: 5, +} +``` + +It is also possible to combine provider server implementations, such as migrating resources and data sources individually from [terraform-plugin-sdk/v2](/terraform/plugin/sdkv2) to the framework. This advanced use case would alter the `main.go` code further. Refer to the [Combining and Translating Providers](/terraform/plugin/mux) page for implementation details. + +### Acceptance Testing + +Refer to the [acceptance testing](/terraform/plugin/framework/acctests) page for implementation details. + +### Debugging + +Refer to the [debugging](/terraform/plugin/framework) page for implementation details. diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/providers/index.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/providers/index.mdx new file mode 100644 index 0000000000..131047f550 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/providers/index.mdx @@ -0,0 +1,346 @@ +--- +page_title: Providers +description: >- + Learn how to implement a provider in the Terraform plugin framework. + Providers, wrapped by a provider server, are plugins that allow Terraform to + interact with APIs. +--- + +# Providers + +Providers are Terraform plugins that define [resources](/terraform/plugin/framework/resources) and [data sources](/terraform/plugin/framework/data-sources) for practitioners to use. Providers are wrapped by a [provider server](/terraform/plugin/framework/provider-servers) for interacting with Terraform. + +This page describes the basic implementation details required for defining a provider. Further documentation is available for deeper provider concepts: + +- [Configure data sources](/terraform/plugin/framework/data-sources/configure) with provider-level data types or clients. +- [Configure resources](/terraform/plugin/framework/resources/configure) with provider-level data types or clients. +- [Configure ephemeral resources](/terraform/plugin/framework/ephemeral-resources/configure) with provider-level data types or clients. +- [Validate](/terraform/plugin/framework/providers/validate-configuration) practitioner configuration against acceptable values. + +## Define Provider Type + +Implement the [provider.Provider interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider#Provider). Each of the methods described in more detail below. + +In this example, a provider implementation is scaffolded: + +```go +// Ensure the implementation satisfies the provider.Provider interface. +var _ provider.Provider = &ExampleCloudProvider{} + +type ExampleCloudProvider struct{ + // Version is an example field that can be set with an actual provider + // version on release, "dev" when the provider is built and ran locally, + // and "test" when running acceptance testing. + Version string +} + +// Metadata satisfies the provider.Provider interface for ExampleCloudProvider +func (p *ExampleCloudProvider) Metadata(ctx context.Context, req provider.MetadataRequest, resp *provider.MetadataResponse) { + resp.TypeName = // provider specific implementation +} + +// Schema satisfies the provider.Provider interface for ExampleCloudProvider. +func (p *ExampleCloudProvider) Schema(ctx context.Context, req provider.SchemaRequest, resp *provider.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + // Provider specific implementation. + }, + } +} + +// Configure satisfies the provider.Provider interface for ExampleCloudProvider. +func (p *ExampleCloudProvider) Configure(ctx context.Context, req provider.ConfigureRequest, resp *provider.ConfigureResponse) { + // Provider specific implementation. +} + +// DataSources satisfies the provider.Provider interface for ExampleCloudProvider. +func (p *ExampleCloudProvider) DataSources(ctx context.Context) []func() datasource.DataSource { + return []func() datasource.DataSource{ + // Provider specific implementation + } +} + +// Resources satisfies the provider.Provider interface for ExampleCloudProvider. +func (p *ExampleCloudProvider) Resources(ctx context.Context) []func() resource.Resource { + return []func() resource.Resource{ + // Provider specific implementation + } +} +``` + +Conventionally, many providers also create a helper function named `New` which can simplify [provider server](/terraform/plugin/framework/provider-servers) implementations. + +```go +func New(version string) func() provider.Provider { + return func() provider.Provider { + return &ExampleCloudProvider{ + Version: version, + } + } +} +``` + +### Metadata Method + +The [`provider.Provider` interface `Metadata` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider#Provider.Metadata) defines information about the provider itself, such as its type name and version. This information is used to simplify creating data sources and resources. + +In this example, the provider type name is set to `examplecloud`: + +```go +func (p *ExampleCloudProvider) Metadata(ctx context.Context, req provider.MetadataRequest, resp *provider.MetadataResponse) { + resp.TypeName = "examplecloud" +} +``` + +### Schema Method + +The [`provider.Provider` interface `Schema` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider#Provider.Schema) defines a [schema](/terraform/plugin/framework/schemas) describing what data is available in the provider's configuration. This configuration block is used to offer practitioners the opportunity to supply values to the provider and configure its behavior, rather than needing to include those values in every resource and data source. It is usually used to gather credentials, endpoints, and the other data used to authenticate with the API, but it is not limited to those uses. + +During the [`terraform validate`](/terraform/cli/commands/validate), [`terraform plan`](/terraform/cli/commands/plan) and [`terraform apply`](/terraform/cli/commands/apply) commands, Terraform calls the provider [`GetProviderSchema`](/terraform/plugin/framework/internals/rpcs#getproviderschema-rpc) RPC, in which the framework calls the [`provider.Provider` interface `Schema` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider#Provider.Schema). + +In this example, a sample configuration and schema definition are provided: + +```go +// Example Terraform configuration: +// +// provider "examplecloud" { +// api_token = "v3rYs3cr3tt0k3n" +// endpoint = "https://example.com/" +// } + +func (p *ExampleCloudProvider) Schema(ctx context.Context, req provider.SchemaRequest, resp *provider.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "api_token": schema.StringAttribute{ + Optional: true, + }, + "endpoint": schema.StringAttribute{ + Optional: true, + }, + }, + } +} +``` + +If the provider does not accept practitioner Terraform configuration, leave the method defined, but empty. + +### Configure Method + +The [`provider.Provider` interface `Configure` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider#Provider.Configure) handles the configuration of any provider-level data or clients. These configuration values may be from the practitioner Terraform configuration, environment variables, or other means such as reading vendor-specific configuration files. + +During the [`terraform plan`](/terraform/cli/commands/plan) and [`terraform apply`](/terraform/cli/commands/apply) commands, Terraform calls the provider [`ConfigureProvider`](/terraform/plugin/framework/internals/rpcs#configureprovider-rpc) RPC, in which the framework calls the [`provider.Provider` interface `Configure` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider#Provider.Configure). + +This is the only chance the provider has to configure provider-level data or clients, so they need to be persisted if other data source or resource logic will need to reference them. Refer to the [Configure Data Sources](/terraform/plugin/framework/data-sources/configure) and [Configure Resources](/terraform/plugin/framework/resources/configure) pages for additional implementation details. + +If the logic needs to return [warning or error diagnostics](/terraform/plugin/framework/diagnostics), they can added into the [`provider.ConfigureResponse.Diagnostics` field](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider#ConfigureResponse.Diagnostics). + +In this example, the provider API token and endpoint are configured via environment variable or Terraform configuration: + +```go +type ExampleCloudProvider struct {} + +type ExampleCloudProviderModel struct { + ApiToken types.String `tfsdk:"api_token"` + Endpoint types.String `tfsdk:"endpoint"` +} + +func (p *ExampleCloudProvider) Schema(ctx context.Context, req provider.SchemaRequest, resp *provider.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "api_token": schema.StringAttribute{ + Optional: true, + }, + "endpoint": schema.StringAttribute{ + Optional: true, + }, + }, + } +} + +func (p *ExampleCloudProvider) Configure(ctx context.Context, req provider.ConfigureRequest, resp *provider.ConfigureResponse) { + // Check environment variables + apiToken := os.Getenv("EXAMPLECLOUD_API_TOKEN") + endpoint := os.Getenv("EXAMPLECLOUD_ENDPOINT") + + var data ExampleCloudProviderModel + + // Read configuration data into model + resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) + + // Check configuration data, which should take precedence over + // environment variable data, if found. + if data.ApiToken.ValueString() != "" { + apiToken = data.ApiToken.ValueString() + } + + if data.Endpoint.ValueString() != "" { + endpoint = data.Endpoint.ValueString() + } + + if apiToken == "" { + resp.Diagnostics.AddError( + "Missing API Token Configuration", + "While configuring the provider, the API token was not found in "+ + "the EXAMPLECLOUD_API_TOKEN environment variable or provider "+ + "configuration block api_token attribute.", + ) + // Not returning early allows the logic to collect all errors. + } + + if endpoint == "" { + resp.Diagnostics.AddError( + "Missing Endpoint Configuration", + "While configuring the provider, the endpoint was not found in "+ + "the EXAMPLECLOUD_ENDPOINT environment variable or provider "+ + "configuration block endpoint attribute.", + ) + // Not returning early allows the logic to collect all errors. + } + + // Create data/clients and persist to resp.DataSourceData, resp.ResourceData, + // and resp.EphemeralResourceData as appropriate. +} +``` + +#### Unknown Values + +Not all values are guaranteed to be +[known](/terraform/plugin/framework/types#unknown) when `Configure` is called. +For example, if a practitioner interpolates a resource's unknown value into the block, +that value may show up as unknown depending on how the graph executes: + +```hcl +resource "random_string" "example" {} + +provider "examplecloud" { + api_token = random_string.example.result + endpoint = "https://example.com/" +} +``` + +In the example above, `random_string.example.result` is a read-only field on +`random_string.example` that won't be set until after `random_string.example` has been +applied. So the `Configure` method for the provider may report that the value +is unknown. You can choose how your provider handles this. If +some resources or data sources can be used without knowing that value, it may +be worthwhile to [emit a warning](/terraform/plugin/framework/diagnostics) and +check whether the value is set in resources and data sources before attempting +to use it. If resources and data sources can't provide any functionality +without knowing that value, it's often better to [return an +error](/terraform/plugin/framework/diagnostics), which will halt the apply. + +### Resources + +The [`provider.Provider` interface `Resources` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider#Provider.Resources) returns a slice of [resources](/terraform/plugin/framework/resources). Each element in the slice is a function to create a new `resource.Resource` so data is not inadvertently shared across multiple, disjointed resource instance operations unless explicitly coded. Information such as the resource type name is managed by the `resource.Resource` implementation. + +The [`provider.Provider` interface `Resources` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider#Provider.Resources) is called during execution of the [`terraform validate`](/terraform/cli/commands/validate), [`terraform plan`](/terraform/cli/commands/plan) and [`terraform apply`](/terraform/cli/commands/apply) commands when the [`ValidateResourceConfig`](/terraform/plugin/framework/internals/rpcs#validateconfig-rpcs), [`ReadResource`](/terraform/plugin/framework/internals/rpcs#read-rpcs), [`PlanResourceChange`](/terraform/plugin/framework/internals/rpcs#planresourcechange-rpc) and [`ApplyResourceChange`](/terraform/plugin/framework/internals/rpcs#applyresourcechange-rpc) RPCs are sent. + +In this example, the provider implements a single resource: + +```go +// With the provider.Provider implementation +func (p *ExampleCloudProvider) Resources(_ context.Context) []func() resource.Resource { + return []func() resource.Resource{ + NewThingResource, + } +} + +// With the resource.Resource implementation +func NewThingResource() resource.Resource { + return &ThingResource{} +} + +type ThingResource struct {} +``` + +Use Go slice techniques to include large numbers of resources outside the provider `Resources` method code. + +In this example, the provider codebase implements multiple "services" which group their own resources: + +```go +// With the provider.Provider implementation +func (p *ExampleCloudProvider) Resources(_ context.Context) []func() resource.Resource { + return []func() resource.Resource{ + servicex.Resources..., + servicey.Resources..., + } +} + +// With the servicex implementation +package servicex + +var Resources = []func() resource.Resource { + NewThingResource, + NewWidgetResource, +} + +func NewThingResource() resource.Resource { + return &ThingResource{} +} + +type ThingResource struct {} + +func NewWidgetResource() resource.Resource { + return &WidgetResource{} +} + +type WidgetResource struct {} +``` + +### DataSources + +The [`provider.Provider` interface `DataSources` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider#Provider.DataSources) returns a slice of [data sources](/terraform/plugin/framework/data-sources). Each element in the slice is a function to create a new `datasource.DataSource` so data is not inadvertently shared across multiple, disjointed datasource instance operations unless explicitly coded. Information such as the datasource type name is managed by the `datasource.DataSource` implementation. + +The [`provider.Provider` interface `DataSources` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider#Provider.DataSources) is called during execution of the [`terraform validate`](/terraform/cli/commands/validate), [`terraform plan`](/terraform/cli/commands/plan) and [`terraform apply`](/terraform/cli/commands/apply) commands when the [`ValidateDataResourceConfig`](/terraform/plugin/framework/internals/rpcs#validatedataresourceconfig-rpc) and [`ReadDataSource`](/terraform/plugin/framework/internals/rpcs#readdatasource-rpc) RPCs are sent. + +In this example, the provider implements a single data source: + +```go +// With the provider.Provider implementation +func (p *ExampleCloudProvider) DataSources(_ context.Context) []func() datasource.DataSource { + return []func() datasource.DataSource{ + NewThingDataSource, + } +} + +// With the datasource.DataSource implementation +func NewThingDataSource() datasource.DataSource { + return &ThingDataSource{} +} + +type ThingDataSource struct {} +``` + +Use Go slice techniques to include large numbers of data sources outside the provider `DataSources` method code. + +In this example, the provider codebase implements multiple "services" which group their own datasources: + +```go +// With the provider.Provider implementation +func (p *ExampleCloudProvider) DataSources(_ context.Context) []func() datasource.DataSource { + return []func() datasource.DataSource{ + servicex.DataSources..., + servicey.DataSources..., + } +} + +// With the servicex implementation +package servicex + +var DataSources = []func() datasource.DataSource { + NewThingDataSource, + NewWidgetDataSource, +} + +func NewThingDataSource() datasource.DataSource { + return &ThingDataSource{} +} + +type ThingDataSource struct {} + +func NewWidgetDataSource() datasource.DataSource { + return &WidgetDataSource{} +} + +type WidgetDataSource struct {} +``` diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/providers/validate-configuration.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/providers/validate-configuration.mdx new file mode 100644 index 0000000000..f59330f1c6 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/providers/validate-configuration.mdx @@ -0,0 +1,86 @@ +--- +page_title: Validate provider configuration +description: >- + Learn how to validate provider configurations with the Terraform plugin + framework. +--- + +# Validate provider configuration + +[Providers](/terraform/plugin/framework/providers) support validating an entire practitioner configuration in either declarative or imperative logic. Feedback, such as required syntax or acceptable combinations of values, is returned via [diagnostics](/terraform/plugin/framework/diagnostics). + +This page describes implementation details for validating entire provider configurations, typically referencing multiple attributes. Further documentation is available for other configuration validation concepts: + +- [Single attribute validation](/terraform/plugin/framework/validation#attribute-validation) is a schema-based mechanism for implementing attribute-specific validation logic. +- [Type validation](/terraform/plugin/framework/validation#type-validation) is a schema-based mechanism for implementing reusable validation logic for any attribute using the type. + +-> Configuration validation in Terraform occurs without provider configuration ("offline"), so therefore the provider `Configure` method will not have been called. To implement validation with a configured API client, use logic within the `Configure` method, which occurs during Terraform's planning phase. + +## ConfigValidators Method + +The [`provider.ProviderWithConfigValidators` interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider#ProviderWithConfigValidators) follows a similar pattern to attribute validation and allows for a more declarative approach. This enables consistent validation logic across multiple providers. Each validator intended for this interface must implement the [`provider.ConfigValidator` interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider#ConfigValidator). + +During execution of the [`terraform validate`](/terraform/cli/commands/validate), [`terraform plan`](/terraform/cli/commands/plan) and [`terraform apply`](/terraform/cli/commands/apply) commands, Terraform calls the provider [`ValidateProviderConfig`](/terraform/plugin/framework/internals/rpcs#validateproviderconfig-rpc) RPC, in which the framework calls the `ConfigValidators` method on providers that implement the [`provider.ProviderWithConfigValidators` interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider#ProviderWithConfigValidators). + +The [`terraform-plugin-framework-validators` Go module](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators) has a collection of common use case provider configuration validators in the [`providervalidator` package](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators/providervalidator). These use [path expressions](/terraform/plugin/framework/path-expressions) for matching attributes. + +This example will raise an error if a practitioner attempts to configure both `attribute_one` and `attribute_two`: + +```go +// Other methods to implement the provider.Provider interface are omitted for brevity +type ExampleCloudProvider struct {} + +func (p ExampleCloudProvider) ConfigValidators(ctx context.Context) []provider.ConfigValidator { + return []provider.ConfigValidator{ + providervalidator.Conflicting( + path.MatchRoot("attribute_one"), + path.MatchRoot("attribute_two"), + ), + } +} +``` + +## ValidateConfig Method + +The [`provider.ProviderWithValidateConfig` interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider#ProviderWithValidateConfig) is more imperative in design and is useful for validating unique functionality across multiple attributes that typically applies to a single provider. + +During execution of the [`terraform validate`](/terraform/cli/commands/validate), [`terraform plan`](/terraform/cli/commands/plan) and [`terraform apply`](/terraform/cli/commands/apply) commands, Terraform calls the provider [`ValidateProviderConfig`](/terraform/plugin/framework/internals/rpcs#validateproviderconfig-rpc) RPC, in which the framework calls the `ValidateConfig` method on providers that implement the [`provider.ProviderWithValidateConfig` interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider#ProviderWithValidateConfig). + +This example will raise a warning if a practitioner attempts to configure `attribute_one`, but not `attribute_two`: + +```go +// Other methods to implement the provider.Provider interface are omitted for brevity +type ExampleCloudProvider struct {} + +type ExampleCloudProviderModel struct { + AttributeOne types.String `tfsdk:"attribute_one"` + AttributeTwo types.String `tfsdk:"attribute_two"` +} + +func (p ExampleCloudProvider) ValidateConfig(ctx context.Context, req provider.ValidateConfigRequest, resp *provider.ValidateConfigResponse) { + var data ExampleCloudProviderModel + + resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + // If attribute_one is not configured, return without warning. + if data.AttributeOne.IsNull() || data.AttributeOne.IsUnknown() { + return + } + + // If attribute_two is not null, return without warning. + if !data.AttributeTwo.IsNull() { + return + } + + resp.Diagnostics.AddAttributeWarning( + path.Root("attribute_two"), + "Missing Attribute Configuration", + "Expected attribute_two to be configured with attribute_one. "+ + "The provider may return unexpected results.", + ) +} +``` diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/resources/configure.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/resources/configure.mdx new file mode 100644 index 0000000000..93dced950a --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/resources/configure.mdx @@ -0,0 +1,104 @@ +--- +page_title: Configure resources +description: >- + Learn how to implement resource configuration with provider or client data in + the Terraform plugin framework. +--- + +# Configure resources + +[Resources](/terraform/plugin/framework/resources) may require provider-level data or remote system clients to operate correctly. The framework supports the ability to configure this data and/or clients once within the provider, then pass that information to resources by adding the `Configure` method. + +## Prepare Provider Configure Method + +Implement the [`provider.ConfigureResponse.ResourceData` field](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider#ConfigureResponse.ResourceData) in the [`Provider` interface `Configure` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider#Provider.Configure). This value can be set to any type, whether an existing client or vendor SDK type, a provider-defined custom type, or the provider implementation itself. It is recommended to use pointer types so that resources can determine if this value was configured before attempting to use it. + +During execution of the [`terraform plan`](/terraform/cli/commands/plan) and [`terraform apply`](/terraform/cli/commands/apply) commands, Terraform calls the provider [`ConfigureProvider`](/terraform/plugin/framework/internals/rpcs#configureprovider-rpc) RPC, in which the framework calls the [`provider.Provider` interface `Configure` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider#Provider.Configure). + +In this example, the Go standard library [`net/http.Client`](https://pkg.go.dev/net/http#Client) is configured in the provider, and made available for resources: + +```go +// With the provider.Provider implementation +func (p *ExampleCloudProvider) Configure(ctx context.Context, req provider.ConfigureRequest, resp *provider.ConfigureResponse) { + resp.ResourceData = &http.Client{/* ... */} +} +``` + +In this example, the code defines an `ExampleClient` type that is made available for resources: + +```go +type ExampleClient struct { + /* ... */ +} + +// With the provider.Provider implementation +func (p *ExampleCloudProvider) Configure(ctx context.Context, req provider.ConfigureRequest, resp *provider.ConfigureResponse) { + resp.ResourceData = &ExampleClient{/* ... */} +} +``` + +In this example, the `ExampleCloudProvider` type itself is made available for resources: + +```go +// With the provider.Provider implementation +type ExampleCloudProvider struct { + /* ... */ +} + +func (p *ExampleCloudProvider) Configure(ctx context.Context, req provider.ConfigureRequest, resp *provider.ConfigureResponse) { + resp.ResourceData = p +} +``` + +## Define Resource Configure Method + +Implement the [`resource.ResourceWithConfigure` interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#ResourceWithConfigure) which receives the provider configured data from the [`Provider` interface `Configure` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider#Provider.Configure) and saves it into the [`resource.Resource` interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#Resource) implementation. + +The [`resource.ResourceWithConfigure` interface `Configure` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#ResourceWithConfigure.Configure) is called during execution of the [`terraform validate`](/terraform/cli/commands/validate), [`terraform plan`](/terraform/cli/commands/plan) and [`terraform apply`](/terraform/cli/commands/apply) commands when the [`ValidateResourceConfig`](/terraform/plugin/framework/internals/rpcs#validateresourceconfig-rpc) RPC is sent. Additionally, the [`resource.ResourceWithConfigure` interface `Configure` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#ResourceWithConfigure.Configure) is called during execution of the [`terraform plan`](/terraform/cli/commands/plan) and [`terraform apply`](/terraform/cli/commands/apply) commands when the [`ReadResource`](/terraform/plugin/framework/internals/rpcs#readresource-rpc) RPC is sent. + +-> Note that Terraform calling the [`ValidateResourceConfig`](/terraform/plugin/framework/internals/rpcs#validateresourceconfig-rpc) RPC would not call the [`ConfigureProvider`](/terraform/plugin/framework/internals/rpcs#configureprovider-rpc) RPC first, so implementations need to account for that situation. Configuration validation in Terraform occurs without provider configuration ("offline"). + +In this example, the provider configured the Go standard library [`net/http.Client`](https://pkg.go.dev/net/http#Client) which the resource uses during `Read`: + +```go +// With the resource.Resource implementation +type ThingResource struct { + client *http.Client +} + +func (r *ThingResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { + // Always perform a nil check when handling ProviderData because Terraform + // sets that data after it calls the ConfigureProvider RPC. + if req.ProviderData == nil { + return + } + + client, ok := req.ProviderData.(*http.Client) + + if !ok { + resp.Diagnostics.AddError( + "Unexpected Resource Configure Type", + fmt.Sprintf("Expected *http.Client, got: %T. Please report this issue to the provider developers.", req.ProviderData), + ) + + return + } + + r.client = client +} + +func (r *ThingResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + // Prevent panic if the provider has not been configured. + if r.client == nil { + resp.Diagnostics.AddError( + "Unconfigured HTTP Client", + "Expected configured HTTP client. Please report this issue to the provider developers.", + ) + + return + } + + httpResp, err := r.client.Get("https://example.com") + /* ... */ +} +``` diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/resources/create.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/resources/create.mdx new file mode 100644 index 0000000000..ed387bd0c8 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/resources/create.mdx @@ -0,0 +1,176 @@ +--- +page_title: Create resources +description: >- + Learn how to implement resource creation in the Terraform plugin framework. +--- + +# Create resources + +Creation is part of the basic Terraform lifecycle for managing resources. During the [`terraform apply` command](/terraform/cli/commands/apply), Terraform calls the provider [`ApplyResourceChange`](/terraform/plugin/framework/internals/rpcs#applyresourcechange-rpc) RPC, in which the framework calls the [`resource.Resource` interface `Create` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#Resource.Create). The request contains Terraform configuration and plan data. The response expects the applied Terraform state data, including any computed values. The data is defined by the [schema](/terraform/plugin/framework/handling-data/schemas) of the resource. + +Other resource lifecycle implementations include: + +- [Read](/terraform/plugin/framework/resources/read) resources by receiving Terraform prior state data, performing read logic, and saving refreshed Terraform state data. +- [Update](/terraform/plugin/framework/resources/update) resources in-place by receiving Terraform prior state, configuration, and plan data, performing update logic, and saving updated Terraform state data. +- [Delete](/terraform/plugin/framework/resources/delete) resources by receiving Terraform prior state data and performing deletion logic. + +## Define Create Method + +Implement the `Create` method by: + +1. [Accessing the data](/terraform/plugin/framework/accessing-values) from the [`resource.CreateRequest` type](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#CreateRequest). Most use cases should access the plan data in the [`resource.CreateRequest.Plan` field](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#CreateRequest.Plan). +1. Performing logic or external calls to create and/or run the resource. +1. [Writing state data](/terraform/plugin/framework/writing-state) into the [`resource.CreateResponse.State` field](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#CreateResponse.State). + +If the logic needs to return [warning or error diagnostics](/terraform/plugin/framework/diagnostics), they can added into the [`resource.CreateResponse.Diagnostics` field](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#CreateResponse.Diagnostics). + +In this example, the resource is setup to accept a configuration value that is sent in a service API creation call: + +```go +// ThingResource defines the resource implementation. +// Some resource.Resource interface methods are omitted for brevity. +type ThingResource struct { + // client is configured via a Configure method, which is not shown in this + // example for brevity. Refer to the Configure Resources documentation for + // additional details for setting up resources with external clients. + client *http.Client +} + +// ThingResourceModel describes the Terraform resource data model to match the +// resource schema. +type ThingResourceModel struct { + Name types.String `tfsdk:"name"` + Id types.String `tfsdk:"id"` +} + +// ThingResourceAPIModel describes the API data model. +type ThingResourceAPIModel struct { + Name string `json:"name"` + Id string `json:"id"` +} + +func (r ThingResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "name": schema.StringAttribute{ + MarkdownDescription: "Name of the thing to be saved in the service.", + Required: true, + }, + "id": schema.StringAttribute{ + Computed: true, + MarkdownDescription: "Service generated identifier for the thing.", + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + }, + MarkdownDescription: "Manages a thing.", + } +} + +func (r ThingResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var data ThingResourceModel + + // Read Terraform plan data into the model + resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + // Convert from Terraform data model into API data model + createReq := ThingResourceAPIModel{ + Name: data.Name.ValueString(), + } + + httpReqBody, err := json.Marshal(createReq) + + if err != nil { + resp.Diagnostics.AddError( + "Unable to Create Resource", + "An unexpected error occurred while creating the resource create request. "+ + "Please report this issue to the provider developers.\n\n"+ + "JSON Error: "+err.Error(), + ) + + return + } + + // Create HTTP request + httpReq := http.NewRequestWithContext( + ctx, + http.MethodPost, + "http://example.com/things", + bytes.NewBuffer(httpReqBody), + ) + + // Send HTTP request + httpResp, err := d.client.Do(httpReq) + defer httpResp.Body.Close() + + if err != nil { + resp.Diagnostics.AddError( + "Unable to Create Resource", + "An unexpected error occurred while attempting to create the resource. "+ + "Please retry the operation or report this issue to the provider developers.\n\n"+ + "HTTP Error: "+err.Error(), + ) + + return + } + + // Return error if the HTTP status code is not 200 OK + if httpResp.StatusCode != http.StatusOK { + resp.Diagnostics.AddError( + "Unable to Create Resource", + "An unexpected error occurred while attempting to create the resource. "+ + "Please retry the operation or report this issue to the provider developers.\n\n"+ + "HTTP Status: "+httpResp.Status, + ) + + return + } + + var createResp ThingResourceAPIModel + + err := json.NewDecoder(httpResp.Body).Decode(&createResp) + + if err != nil { + resp.Diagnostics.AddError( + "Unable to Create Resource", + "An unexpected error occurred while parsing the resource creation response. "+ + "Please report this issue to the provider developers.\n\n"+ + "JSON Error: "+err.Error(), + ) + + return + } + + // Convert from the API data model to the Terraform data model + // and set any unknown attribute values. + data.Id = types.StringValue(createResp.Id) + + // Save data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} +``` + +## Resource Identity + +Managed resources that support identity also need to set the identity data during `Create`, see the ["Identity" page](/terraform/plugin/framework/resources/identity#writing-identity) for more information. + +## Caveats + +Note these caveats when implementing the `Create` method: + +* An error is returned if the response state contains unknown values. Set all attributes to either null or known values in the response. +* An error is returned if the response state has the `RemoveResource()` method called. This method is not valid during creation. +* An error is returned unless every null or known value in the request plan is saved exactly as-is into the response state. Only unknown plan values can be modified. +* Any response errors will cause Terraform to mark the resource as tainted for recreation on the next Terraform plan. + +## Recommendations + +Note these recommendations when implementing the `Create` method: + +* Get request data from the Terraform plan data over configuration data as the schema or resource may include [plan modification](/terraform/plugin/framework/resources/plan-modification) logic which sets plan values. +* Return errors that signify there is an existing resource. Terraform practitioners expect to be notified if an existing resource needs to be imported into Terraform rather than created. This prevents situations where multiple Terraform configurations unexpectedly manage the same underlying resource. diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/resources/default.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/resources/default.mdx new file mode 100644 index 0000000000..5254d5c5b2 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/resources/default.mdx @@ -0,0 +1,104 @@ +--- +page_title: Default values +description: >- + Learn how to set default values for resource attributes with the Terraform + plugin framework. +--- + +# Default values + +After [validation](/terraform/plugin/framework/validation) and before applying configuration changes, Terraform generates a plan that describes the expected values and behaviors of those changes. Resources can then tailor the plan to set default values on computed resource attributes that are null in the configuration. + +A Default can _only_ be added to a resource schema attribute. + +## When is a Default set? + +A Default is set during the [planning process](/terraform/plugin/framework/resources/plan-modification#plan-modification-process), immediately prior to the framework marking computed attributes that are null in the configuration as unknown in the plan. + +## Attribute Default + +You can supply the attribute type `Default` field with a default for that attribute. For example: + +```go +// Typically within the schema.Schema returned by Schema() for a resource. +schema.StringAttribute{ + // ... other Attribute configuration ... + + Default: stringdefault.StaticString("str"), +} + +schema.SetAttribute{ + // ... other Attribute configuration ... + + Default: setdefault.StaticValue( + types.SetValueMust( + types.StringType, + []attr.Value{ + types.StringValue("str"), + }, + ), + ), +}, +``` + +If defined, a default is applied to the current attribute providing that the attribute is null in the configuration. If any nested attributes define a default, then those are applied afterwards. Any default that returns an error will prevent Terraform from applying further defaults of that attribute as well as any nested attribute defaults. + +### Common Use Case Attribute Defaults + +The framework implements static value defaults in the typed packages under `resource/schema/`: + +| Schema Type | Built-In Default Functions | +|---|---| +| [`schema.BoolAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema#BoolAttribute) | [`resource/schema/booldefault` package](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault) | +| [`schema.DynamicAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema#DynamicAttribute) | [`resource/schema/dynamicdefault` package](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/dynamicdefault) | +| [`schema.Float32Attribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema#Float32Attribute) | [`resource/schema/float32default` package](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/float32default) | +| [`schema.Float64Attribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema#Float64Attribute) | [`resource/schema/float64default` package](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/float64default) | +| [`schema.Int32Attribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema#Int32Attribute) | [`resource/schema/int32default` package](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/int32default) | +| [`schema.Int64Attribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema#Int64Attribute) | [`resource/schema/int64default` package](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/int64default) | +| [`schema.ListAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema#ListAttribute) / [`schema.ListNestedAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema#ListNestedAttribute) | [`resource/schema/listdefault` package](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/listdefault) | +| [`schema.MapAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema#MapAttribute) / [`schema.MapNestedAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema#MapNestedAttribute) | [`resource/schema/mapdefault` package](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/mapdefault) | +| [`schema.NumberAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema#NumberAttribute) | [`resource/schema/numberdefault` package](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/numberdefault) | +| [`schema.ObjectAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema#ObjectAttribute) / [`schema.SingleNestedAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema#SingleNestedAttribute) | [`resource/schema/objectdefault` package](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/objectdefault) | +| [`schema.SetAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema#SetAttribute) / [`schema.SetNestedAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema#SetNestedAttribute) | [`resource/schema/setdefault` package](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/setdefault) | +| [`schema.StringAttribute`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema#StringAttribute) | [`resource/schema/stringdefault` package](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault) | + +### Custom Default Implementations + +To create an attribute default, you must implement the one of the [`resource/schema/defaults` package](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/defaults) interfaces. For example: + +```go +// timeDefaultValue is a default that sets the value for a types.StringType +// attribute to the current time when it is not configured. The attribute +// must be marked as Optional and Computed. When setting the state during +// the resource Create, Read, or Update methods, this value must also be +// included or the Terraform CLI will generate an error. +type timeDefaultValue struct { + time time.Time +} + +// Description returns a plain text description of the default's behavior, suitable for a practitioner to understand its impact. +func (d timeDefaultValue) Description(ctx context.Context) string { + return fmt.Sprintf("If value is not configured, defaults to a string representation of the current time") +} + +// MarkdownDescription returns a markdown formatted description of the default's behavior, suitable for a practitioner to understand its impact. +func (d timeDefaultValue) MarkdownDescription(ctx context.Context) string { + return fmt.Sprintf("If value is not configured, defaults to a string representation of the current time") +} + +// DefaultString runs the logic of the default. Access to the path is available in `req`, while +// `resp` contains fields for updating the planned value, and returning diagnostics. +func (d timeDefaultValue) DefaultString(_ context.Context, req defaults.StringRequest, resp *defaults.StringResponse) { + resp.PlanValue = types.StringValue(d.time.Format(time.RFC3339)) +} +``` + +Optionally, you may also want to create a helper function to instantiate the default. For example: + +```go +func timeDefault(t time.Time) defaults.String { + return timeDefaultValue{ + time: t, + } +} +``` \ No newline at end of file diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/resources/delete.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/resources/delete.mdx new file mode 100644 index 0000000000..37c706e7fe --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/resources/delete.mdx @@ -0,0 +1,149 @@ +--- +page_title: Delete resources +description: >- + Learn how to implement resource deletion in the Terraform plugin framework. +--- + +# Delete resources + +Deletion is part of the basic Terraform lifecycle for managing resources. During the [`terraform apply` command](/terraform/cli/commands/apply), Terraform calls the provider [`ApplyResourceChange`](/terraform/plugin/framework/internals/rpcs#applyresourcechange-rpc) RPC, in which the framework calls the [`resource.Resource` interface `Delete` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#Resource.Delete). The request contains Terraform prior state data. The response is only for returning diagnostics. The data is defined by the [schema](/terraform/plugin/framework/schemas) of the resource. + +Terraform 1.3 and later enables deletion planning, which resources can implement to return warning and error diagnostics. For additional information, refer to the [resource plan modification documentation](/terraform/plugin/framework/resources/plan-modification#resource-destroy-plan-diagnostics). + +Other resource lifecycle implementations include: + +- [Create](/terraform/plugin/framework/resources/create) resources by receiving Terraform configuration and plan data, performing creation logic, and saving Terraform state data. +- [Read](/terraform/plugin/framework/resources/read) resources by receiving Terraform prior state data, performing read logic, and saving refreshed Terraform state data. +- [Update](/terraform/plugin/framework/resources/update) resources in-place by receiving Terraform prior state, configuration, and plan data, performing update logic, and saving updated Terraform state data. + +## Define Delete Method + +Implement the `Delete` method by: + +1. [Accessing prior state data](/terraform/plugin/framework/accessing-values) from the [`resource.DeleteRequest.State` field](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#DeleteRequest.State). +1. Performing logic or external calls to destroy the resource. + +If the logic needs to return [warning or error diagnostics](/terraform/plugin/framework/diagnostics), they can added into the [`resource.DeleteResponse.Diagnostics` field](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#DeleteResponse.Diagnostics). + +In this example, the `Delete` function makes a HTTP call and returns successfully if the status code was 200 OK or 404 Not Found: + +```go +// ThingResource defines the resource implementation. +// Some resource.Resource interface methods are omitted for brevity. +type ThingResource struct { + // client is configured via a Configure method, which is not shown in this + // example for brevity. Refer to the Configure Resources documentation for + // additional details for setting up resources with external clients. + client *http.Client +} + +// ThingResourceModel describes the Terraform resource data model to match the +// resource schema. +type ThingResourceModel struct { + Name types.String `tfsdk:"name"` + Id types.String `tfsdk:"id"` +} + +// ThingResourceAPIModel describes the API data model. +type ThingResourceAPIModel struct { + Name string `json:"name"` + Id string `json:"id"` +} + +func (r ThingResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "name": schema.StringAttribute{ + MarkdownDescription: "Name of the thing to be saved in the service.", + Required: true, + }, + "id": schema.StringAttribute{ + Computed: true, + MarkdownDescription: "Service generated identifier for the thing.", + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + }, + MarkdownDescription: "Manages a thing.", + } +} + +func (r ThingResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var data ThingResourceModel + + // Read Terraform prior state data into the model + resp.Diagnostics.Append(req.State.Get(ctx, &data)...) + + // Convert from Terraform data model into API data model + readReq := ThingResourceAPIModel{ + Id: data.Id.ValueString(), + Name: data.Name.ValueString(), + } + + httpReqBody, err := json.Marshal(readReq) + + if err != nil { + resp.Diagnostics.AddError( + "Unable to Delete Resource", + "An unexpected error occurred while creating the resource d request. "+ + "Please report this issue to the provider developers.\n\n"+ + "JSON Error: "+err.Error(), + ) + + return + } + + // Create HTTP request + httpReq := http.NewRequestWithContext( + ctx, + http.MethodDelete, + "http://example.com/things", + bytes.NewBuffer(httpReqBody), + ) + + // Send HTTP request + httpResp, err := d.client.Do(httpReq) + defer httpResp.Body.Close() + + if err != nil { + resp.Diagnostics.AddError( + "Unable to Delete Resource", + "An unexpected error occurred while attempting to delete the resource. "+ + "Please retry the operation or report this issue to the provider developers.\n\n"+ + "HTTP Error: "+err.Error(), + ) + + return + } + + // Return error if the HTTP status code is not 200 OK or 404 Not Found + if httpResp.StatusCode != http.StatusNotFound && httpResp.StatusCode != http.StatusOK { + resp.Diagnostics.AddError( + "Unable to Delete Resource", + "An unexpected error occurred while attempting to delete the resource. "+ + "Please retry the operation or report this issue to the provider developers.\n\n"+ + "HTTP Status: "+httpResp.Status, + ) + + return + } + + // If the logic reaches here, it implicitly succeeded and will remove + // the resource from state if there are no other errors. +} +``` + +## Caveats + +Note these caveats when implementing the `Delete` method: + +* An error is returned if the response state is set to anything other than null. +* Any response errors will cause Terraform to keep the resource under management. + +## Recommendations + +Note these recommendations when implementing the `Delete` method: + +* Ignore errors that signify the resource is no longer existent. +* Skip calling the response state `RemoveResource()` method. The framework automatically handles this logic with the response state if there are no error diagnostics. diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/resources/identity-upgrade.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/resources/identity-upgrade.mdx new file mode 100644 index 0000000000..a1141b5a50 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/resources/identity-upgrade.mdx @@ -0,0 +1,147 @@ +--- +page_title: Identity Upgrade +description: >- + Learn how to implement upgrading identity data when provider schema changes from + one version of your Terraform framework provider to another. +--- + +# Identity Upgrade + +An identity schema captures the structure and types of a [managed resource identity](/terraform/plugin/framework/resources/identity). Any identity data that does not conform to the resource identity schema will generate errors or may not be persisted properly. Over time, it may be necessary for identities to make breaking changes to their schemas, such as changing an attribute type. Terraform supports versioning of these identity schemas and the current version is saved into the Terraform state. When the provider advertises a newer identity schema version, Terraform will call back to the provider to attempt to upgrade from the saved schema version to the one advertised. This operation is performed prior to planning, but with a configured provider. + +## Identity Upgrade Process + +1. When generating a plan, Terraform CLI will request the current resource identity schema, which contains a version. +1. If Terraform CLI detects that an existing identity with its saved version does not match the current version, Terraform CLI will request an identity upgrade from the provider with the prior identity version and expecting the identity to match the current version. +1. The framework will check the resource to see if it defines identity upgrade support: + * If no identity upgrade support is defined, an error diagnostic is returned. + * If identity upgrade support is defined, but not for the requested prior identity version, an error diagnostic is returned. + * If identity upgrade support is defined and has an implementation for the requested prior identity version, the provider defined implementation is executed. + +## Adding Identity Upgrade Support + +Ensure the [`identityschema.Schema` type `Version` field](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/identityschema#Schema.Version) field for the [`resource.Resource`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#Resource) is greater than `0`, then implement the `resource.ResourceWithUpgradeIdentity` interface for the [`resource.Resource`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#Resource). Conventionally the version is incremented by `1` for each upgrade identity. + +This example shows a `Resource` with the necessary `UpgradeIdentity` method to implement the `ResourceWithUpgradeIdentity` interface: + +```go +// Other Resource methods are omitted in this example +var _ resource.Resource = &ThingResource{} +var _ resource.ResourceWithUpgradeIdentity = &ThingResource{} + +type ThingResource struct{/* ... */} + +func (r *ThingResource) IdentitySchema(_ context.Context, _ resource.IdentitySchemaRequest, resp *resource.IdentitySchemaResponse) { + resp.IdentitySchema = identityschema.Schema{ + // ... other fields ... + + // This example conventionally declares that the resource has prior + // identity versions of 0 and 1, while the current version is 2. + Version: 2, + } +} + +func (r *ThingResource) UpgradeIdentity(ctx context.Context) map[int64]resource.IdentityUpgrader { + return map[int64]resource.IdentityUpgrader{ + // Identity upgrade implementation from 0 (prior identity version) to 1 (Schema.Version) + 0: { + // Optionally, the PriorSchema field can be defined. + IdentityUpgrader: func(ctx context.Context, req resource.UpgradeIdentityRequest, resp *resource.UpgradeIdentityResponse) { /* ... */ }, + }, + // Identity upgrade implementation from 1 (prior identity version) to 2 (Schema.Version) + 1: { + // Optionally, the PriorSchema field can be defined. + IdentityUpgrader: func(ctx context.Context, req resource.UpgradeIdentityRequest, resp *resource.UpgradeIdentityResponse) { /* ... */ }, + }, + } +} +``` + +Each `resource.IdentityUpgrader` implementation is expected to wholly upgrade the resource identity from the prior version to the current version. The framework does not iterate through intermediate version implementations as incrementing versions by 1 is only conventional and not required. + +All identity data must be populated in the [`resource.UpgradeIdentityResponse`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#UpgradeIdentityResponse). The framework does not copy any prior identity data from the `resource.UpgradeIdentityRequest`. + +The recommended approach for implementation is defining the prior schema matching the resource identity, which allows for prior identity access similar to other parts of the framework. It is possible to access the request data using lower level data handlers, but the response must be set with a [framework type](/terraform/plugin/framework/handling-data/types). + +### Implementing IdentityUpgrader + +Implement the `IdentityUpgrader` type `PriorSchema` field to enable the framework to populate the `resource.UpgradeIdentityRequest` type `Identity` field for the provider defined upgrade identity logic. Access the request `Identity` using methods such as `Get()` or `GetAttribute()`. Write the `resource.UpgradeIdentityResponse` type `Identity` field using methods such as `Set()` or `SetAttribute()`. + +This example shows a resource that changes the type for two attributes, using the `PriorSchema` approach: + +```go +// Other Resource methods are omitted in this example +var _ resource.Resource = &ThingResource{} +var _ resource.ResourceWithUpgradeIdentity = &ThingResource{} + +type ThingResource struct{/* ... */} + +type ThingResourceModelV0 struct { + Id string `tfsdk:"id"` + OldBoolAttribute bool `tfsdk:"old_bool_attribute"` +} + +type ThingResourceModelV1 struct { + Id string `tfsdk:"id"` + NewStringAttribute string `tfsdk:"new_string_attribute"` +} + +func (r *ThingResource) IdentitySchema(_ context.Context, _ resource.IdentitySchemaRequest, resp *resource.IdentitySchemaResponse) { + resp.IdentitySchema = identityschema.Schema{ + Attributes: map[string]identityschema.Attribute{ + "id": identityschema.StringAttribute{ + RequiredForImport: true, + }, + "new_string_attribute": identityschema.StringAttribute{ + // As compared to prior identityschema.BoolAttribute below + OptionalForImport: true, + }, + }, + // The resource has a prior identity version of 0, which had the attribute + // types of types.BoolType as shown below. + Version: 1, + } +} + +func (r *ThingResource) UpgradeIdentity(ctx context.Context) map[int64]resource.IdentityUpgrader { + return map[int64]resource.IdentityUpgrader{ + // Identity upgrade implementation from 0 (prior identity version) to 1 (identityschema.Version) + 0: { + PriorSchema: &identityschema.Schema{ + Attributes: map[string]identityschema.Attribute{ + "id": identityschema.StringAttribute{ + RequiredForImport: true, + }, + "old_bool_attribute": identityschema.BoolAttribute{ + // As compared to current identityschema.StringAttribute above + OptionalForImport: true, + }, + }, + }, + IdentityUpgrader: func(ctx context.Context, req resource.UpgradeIdentityRequest, resp *resource.UpgradeIdentityResponse) { + var priorIdentityData ThingResourceModelV0 + + resp.Diagnostics.Append(req.Identity.Get(ctx, &priorIdentityData)...) + + if resp.Diagnostics.HasError() { + return + } + + upgradedIdentityData := ThingResourceModelV1{ + Id: priorIdentityData.Id, + NewStringAttribute: fmt.Sprintf("%t", priorIdentityData.OldBoolAttribute), + } + + resp.Diagnostics.Append(resp.Identity.Set(ctx, upgradedIdentityData)...) + }, + }, + } +} +``` + +## Caveats + +Note these caveats when implementing the `UpgradeIdentity` method: + +* An error is returned if the response identity contains unknown values. Set all attributes to either null or known values in the response. +* Any response errors will cause Terraform to keep the prior resource identity. diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/resources/identity.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/resources/identity.mdx new file mode 100644 index 0000000000..c78cccd029 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/resources/identity.mdx @@ -0,0 +1,257 @@ +--- +page_title: Identify resources +description: >- + Learn how to implement resource identity in the Terraform plugin framework. +--- + +# Resource Identity + +A resource identity is a data object, determined by the provider, that is stored alongside the resource state to uniquely identify a remote object. Resource identity is supported in Terraform 1.12 and later. + +A resource identity should have the following properties: +- The resource identity must correspond to at most one remote object per provider, across all instances of that provider. +- Given a resource identity (during `import`), the provider must be able to determine whether the corresponding remote object exists, and if so, return the resource state. +- The identity data for a remote object must not change during its lifecycle from creation to deletion, or until the provider [upgrades](/terraform/plugin/framework/resources/identity-upgrade) the resource identity schema. + + + +## Schema + +To define the identity object for a managed resource, a resource identity [schema](/terraform/plugin/framework/handling-data/schemas) is provided that consists of a map of attribute names and associated behaviors. + +The resource identity schema is similar to the resource state schema, but with the following differences: +- The identity schema only supports primitive types and list types, which are represented by the following attributes: + +| Attribute | ElementType | +|------------------------------------------------------------------------------------|--------------------------------------------------------------------------------| +| [`BoolAttribute`](/terraform/plugin/framework/handling-data/attributes/bool) | N/A | +| [`Float32Attribute`](/terraform/plugin/framework/handling-data/attributes/float32) | N/A | +| [`Float64Attribute`](/terraform/plugin/framework/handling-data/attributes/float64) | N/A | +| [`Int32Attribute`](/terraform/plugin/framework/handling-data/attributes/int32) | N/A | +| [`Int64Attribute`](/terraform/plugin/framework/handling-data/attributes/int64) | N/A | +| [`NumberAttribute`](/terraform/plugin/framework/handling-data/attributes/number) | N/A | +| [`StringAttribute`](/terraform/plugin/framework/handling-data/attributes/string) | N/A | +| [`ListAttribute`](/terraform/plugin/framework/handling-data/attributes/list) | [`types.BoolType`](/terraform/plugin/framework/handling-data/types/bool) | +| [`ListAttribute`](/terraform/plugin/framework/handling-data/attributes/list) | [`types.Float32Type`](/terraform/plugin/framework/handling-data/types/float32) | +| [`ListAttribute`](/terraform/plugin/framework/handling-data/attributes/list) | [`types.Float64Type`](/terraform/plugin/framework/handling-data/types/float64) | +| [`ListAttribute`](/terraform/plugin/framework/handling-data/attributes/list) | [`types.Int32Type`](/terraform/plugin/framework/handling-data/types/int32) | +| [`ListAttribute`](/terraform/plugin/framework/handling-data/attributes/list) | [`types.Int64Type`](/terraform/plugin/framework/handling-data/types/int64) | +| [`ListAttribute`](/terraform/plugin/framework/handling-data/attributes/list) | [`types.NumberType`](/terraform/plugin/framework/handling-data/types/number) | +| [`ListAttribute`](/terraform/plugin/framework/handling-data/attributes/list) | [`types.StringType`](/terraform/plugin/framework/handling-data/types/string) | + +- Unlike the resource state schema, the resource identity schema does not support behaviors such as `Required` and `Computed`. All identity data is set by the provider, so the entire object is treated as `Computed`. Two behaviors are allowed for each identity schema to assist with importing a resource by identity, `RequiredForImport` and `OptionalForImport`. + - `RequiredForImport` only: A practitioner must configure the attribute to a known value (not `null`) during import, otherwise Terraform automatically raises an error diagnostic for the missing value. + - `OptionalForImport` only: A practitioner must configure the value to a known value or `null` during import. + +It is expected that exactly one of `RequiredForImport` or `OptionalForImport` is set to true. Regardless of which option is chosen, the provider can decide exactly what data is stored in the identity during import, similar to `Computed` attributes in resource state. + +## Defining Identity + +The identity schema can be set in a new `IdentitySchema` method, which is defined in the `resource.ResourceWithIdentity` interface: + +```go +var _ resource.ResourceWithIdentity = ThingResource{} // new interface + +func (r ThingResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = "examplecloud_thing" +} + +func (r ThingResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + // .. resource schema for examplecloud_thing + } +} + +// Struct model for identity data handling +type ThingResourceIdentityModel struct { + ID types.String `tfsdk:"id"` + Region types.String `tfsdk:"region"` +} + +func (r ThingResource) IdentitySchema(_ context.Context, _ resource.IdentitySchemaRequest, resp *resource.IdentitySchemaResponse) { + resp.IdentitySchema = identityschema.Schema{ + Attributes: map[string]identityschema.Attribute{ + "id": identityschema.StringAttribute{ + RequiredForImport: true, // must be set during import by the practitioner + }, + "region": identityschema.StringAttribute{ + OptionalForImport: true, // can be defaulted by the provider configuration + }, + }, + } +} +``` + +## Handling Identity Data + +Identity data, similar to resource state data, can be set or retrieved during the resource [`Create`](/terraform/plugin/framework/resources/create), +[`Read`](/terraform/plugin/framework/resources/read), [`Update`](/terraform/plugin/framework/resources/update), [`Delete`](/terraform/plugin/framework/resources/delete) +and [`ImportState`](/terraform/plugin/framework/resources/import) methods. Unlike resource state data, identity data is expected to be immutable after it is set during +[`Create`](/terraform/plugin/framework/resources/create), so typically the only locations a provider should need to write identity data is during [`Create`](/terraform/plugin/framework/resources/create) and +[`Read`](/terraform/plugin/framework/resources/read). + +[`Read`](/terraform/plugin/framework/resources/read) should return identity data so that the managed resource can support [importing](/terraform/plugin/framework/resources/import), +especially if not all of the identity attributes are provided by the practitioner during import (like provider configuration values and remote API data). + +### Writing Identity + +Typically, identity data should be set during [`Create`](/terraform/plugin/framework/resources/create) and [`Read`](/terraform/plugin/framework/resources/read). +The same data model for [writing data](/terraform/plugin/framework/handling-data/writing-state) to state is used for identity, for example: + +```go +// .. rest of resource implementation + +type ThingResourceModel struct { + // state data model +} + +// identity data model +type ThingResourceIdentityModel struct { + ID types.String `tfsdk:"id"` + Region types.String `tfsdk:"region"` +} + +func (r ThingResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + // Read plan data + + // Call remote API to create resource (i.e. apiResp) + + // Set data returned by API in state + data.ID = types.StringValue(apiResp.ID) + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) + + // Set data returned by API in identity + identity := ThingResourceIdentityModel{ + ID: types.StringValue(apiResp.ID), + Region: types.StringValue(apiResp.Region), + } + resp.Diagnostics.Append(resp.Identity.Set(ctx, identity)...) +} +``` + +### Reading Identity + +The same data model for [reading data](/terraform/plugin/framework/handling-data/accessing-values) from state is used for identity, for example: + +```go +// .. rest of resource implementation + +type ThingResourceModel struct { + // state data model +} + +// identity data model +type ThingResourceIdentityModel struct { + ID types.String `tfsdk:"id"` + Region types.String `tfsdk:"region"` +} + +func (r ThingResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + // Read state data + var stateData ThingResourceModel + resp.Diagnostics.Append(req.State.Get(ctx, &stateData)...) + if resp.Diagnostics.HasError() { + return + } + + // Read identity data + var identityData ThingResourceIdentityModel + resp.Diagnostics.Append(req.Identity.Get(ctx, &identityData)...) + if resp.Diagnostics.HasError() { + return + } + + // Call remote API to read resource + + // Set data returned by API in state + // Set data returned by API in identity +} +``` + +## Importing by Identity + +Managed resources that [define an identity](/terraform/plugin/framework/resources/identity#defining-identity) can be imported by either the `id` or the resource identity. +The user must set either `id` or `identity` and not both. Supplying both or none will result in a validation error. For example, the identity presented in the +["Define Identity"](/terraform/plugin/framework/resources/identity#defining-identity) section, can be imported via the following methods: + +- `terraform import` CLI command with ID string +```bash +terraform import examplecloud_thing.test id-123 +``` +- `import` block with `id` argument +```terraform +import { + to = examplecloud_thing.test + id = "id-123" +} +``` +- `import` block with `identity` argument +```terraform +import { + to = examplecloud_thing.test + identity = { + id = "id-123" # required for import + region = "us-east-1" # optional for import + } +} +``` + + +If identity data is present in the request, the provider is expected to ignore anything in the [`resource.ImportStateRequest.ID` field](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#ImportStateRequest) (which Core will set to `""`). To maintain compatibility with the `terraform import` CLI command and the `import` block with `id` field, providers must continue to support importing via import ID if the identity data is not present. + +For resources that only need to support Terraform v1.12+, providers may choose not to support an import ID at all. In this case, if the user supplies an import ID (via the `terraform import` CLI command or in an `import` block), Terraform will send an import request to the provider including a non-empty [`resource.ImportStateRequest.ID` field](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#ImportStateRequest), and the provider can choose to return an error with the [`resource.ImportStateResponse.Diagnostics` field](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#ImportStateResponse) saying that it is not supported. + +An example [`ImportState`](/terraform/plugin/framework/resources/import) implementation that accounts for both importing by `id` and importing by `identity`: + +```go +func (r ThingResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + // If importing by ID, we just set the ID field to state, allowing the read to fill in the rest of the data. + if req.ID != "" { + resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("id"), req.ID)...) + return + } + + // Otherwise, we're importing by identity. We can either let identity passthrough + // to Read, or we can read the identity and use it to set data to state to be used in Read. + + var identityData ThingResourceIdentityModel + resp.Diagnostics.Append(req.Identity.Get(ctx, &identityData)...) + if resp.Diagnostics.HasError() { + return + } + + resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("id"), identityData.ID)...) +} +``` + +If the identity is a single attribute that is passed through to a single attribute in state, the `resource.ImportStatePassthroughWithIdentity` +helper method can be used, which will set the same state attribute with either identity data or the ID string field: + +```go +func (r ThingResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + resource.ImportStatePassthroughWithIdentity(ctx, path.Root("id"), path.Root("id"), req, resp) +} +``` + +## Mutable Identities + +By default, if identity data unexpectedly changes during the resource's lifecycle, an error will be raised by the framework: +```bash +Error: Unexpected Identity Change + +During the operation, the Terraform Provider unexpectedly returned a +different identity then the previously stored one. +``` + +If the remote object has an identity that can be changed without being destroyed/re-created, this validation +can be disabled by setting the [`resource.ResourceBehavior`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#ResourceBehavior) +`MutableIdentity` field to `true`, which is set in the `Metadata` method on a resource: + +```go +func (r ThingResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = "examplecloud_thing" + resp.ResourceBehavior = resource.ResourceBehavior{ + MutableIdentity: true, + } +} +``` \ No newline at end of file diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/resources/import.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/resources/import.mdx new file mode 100644 index 0000000000..58c380e139 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/resources/import.mdx @@ -0,0 +1,105 @@ +--- +page_title: Resource import +description: >- + Learn how to support resource import using the Terraform plugin framework. +--- + +# Resource import + +Practitioners can use the [`terraform import` command](/terraform/cli/commands/import) to let Terraform begin managing existing infrastructure resources. Resources can implement the `ImportState` method, which must either specify enough Terraform state for the `Read` method to refresh [`resource.Resource`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#Resource) or return an error. + +## Import by ID + +The [`resource.ResourceWithImportState` interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#ResourceWithImportState) on the [`resource.Resource` interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#Resource) implementation will enable practitioner support for importing an existing resource. + +Implement the `ImportState` method by: + +1. Accessing the import identifier from the [`resource.ImportStateRequest.ID` field](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#ImportStateRequest) +1. [Writing state data](/terraform/plugin/framework/writing-state) into the [`resource.ImportStateResponse.State` field](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#ImportStateResponse.State). + +In this example, the resource state has the `id` attribute set to the value passed into the [`terraform import` command](/terraform/cli/commands/import) using the [`resource.ImportStatePassthroughID` function](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#ImportStatePassthroughID): + +```go +func (r *ThingResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp) +} +``` + +### Multiple Attributes + +When the `Read` method requires multiple attributes to refresh, you must write custom logic in the `ImportState` method. + +Implement the `ImportState` method by: + +1. Accessing the import identifier from the [`resource.ImportStateRequest.ID` field](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#ImportStateRequest) +1. Performing the custom logic. +1. [Writing state data](/terraform/plugin/framework/writing-state) into the [`resource.ImportStateResponse.State` field](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#ImportStateResponse.State). + +The `terraform import` command will need to accept the multiple attribute values as a single import identifier string. A typical convention is to use a separator character, such as a comma (`,`), between the values. The `ImportState` method will then need to parse the import identifier string into the multiple separate values and save them appropriately into the Terraform state. + +In this example, the resource requires two attributes to refresh state and accepts them as an import identifier of `attr_one,attr_two`: + +```go +func (r *ThingResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "attr_one": schema.StringAttribute{ + Required: true, + }, + "attr_two": schema.StringAttribute{ + Required: true, + }, + /* ... */ + }, + } +} + +func (r *ThingResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var attrOne, attrTwo string + + resp.Diagnostics.Append(req.State.GetAttribute(ctx, path.Root("attr_one"), &attrOne)...) + resp.Diagnostics.Append(req.State.GetAttribute(ctx, path.Root("attr_two"), &attrTwo)...) + + if resp.Diagnostics.HasError() { + return + } + + // API call using attrOne and attrTwo +} + +func (r *ThingResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + idParts := strings.Split(req.ID, ",") + + if len(idParts) != 2 || idParts[0] == "" || idParts[1] == "" { + resp.Diagnostics.AddError( + "Unexpected Import Identifier", + fmt.Sprintf("Expected import identifier with format: attr_one,attr_two. Got: %q", req.ID), + ) + return + } + + resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("attr_one"), idParts[0])...) + resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("attr_two"), idParts[1])...) +} +``` + +## Import by Identity + +Managed resources that support identity can be imported by either the `id` or the resource identity. See the ["Identity" page](/terraform/plugin/framework/resources/identity#importing-by-identity) for more information. + + +## Not Implemented + +If the resource does not support `terraform import`, skip the `ImportState` method implementation. + +When a practitioner runs `terraform import`, Terraform CLI will return: + +```console +$ terraform import example_resource.example some-identifier +example_resource.example: Importing from ID "some-identifier"... +╷ +│ Error: Resource Import Not Implemented +│ +│ This resource does not support import. Please contact the provider developer for additional information. +╵ +``` diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/resources/index.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/resources/index.mdx new file mode 100644 index 0000000000..6aa9000c80 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/resources/index.mdx @@ -0,0 +1,105 @@ +--- +page_title: Resources +description: >- + Learn how to build resources in the Terraform plugin framework. Resources + allow Terraform to manage infrastructure objects. +--- + +# Resources + +[Resources](/terraform/language/resources) are an abstraction that allow Terraform to manage infrastructure objects, such as a compute instance, an access policy, or disk. Terraform assumes that every resource: + +- operates as a pure key/value store, with values getting returned exactly as they were written. +- needs only one API call to update or return its state. +- can be be created, read, updated, and deleted. + +This page describes the initial implementation details required for supporting a resource within the provider. Resource lifecycle management functionality is also required: + +- [Create](/terraform/plugin/framework/resources/create) resources by receiving Terraform configuration and plan data, performing creation logic, and saving Terraform state data. +- [Read](/terraform/plugin/framework/resources/read) resources by receiving Terraform prior state data, performing read logic, and saving refreshed Terraform state data. +- [Update](/terraform/plugin/framework/resources/update) resources in-place by receiving Terraform prior state, configuration, and plan data, performing update logic, and saving updated Terraform state data. +- [Delete](/terraform/plugin/framework/resources/delete) resources by receiving Terraform prior state data and performing deletion logic. + +Further documentation is available for deeper resource concepts: + +- [Configure](/terraform/plugin/framework/resources/configure) resources with provider-level data types or clients. +- [Default](/terraform/plugin/framework/resources/default) for specifying a default value for an attribute that is null within the configuration. +- [Import state](/terraform/plugin/framework/resources/import) so practitioners can bring existing resources under Terraform lifecycle management. +- [Manage private state](/terraform/plugin/framework/resources/private-state) to store additional data in resource state that is not shown in plans. +- [Modify plans](/terraform/plugin/framework/resources/plan-modification) to enrich the output for expected resource behaviors during changes, or marking a resource for replacement if an in-place update cannot occur. +- [Upgrade state](/terraform/plugin/framework/resources/state-upgrade) to transparently update state data outside plans. +- [Validate](/terraform/plugin/framework/resources/validate-configuration) practitioner configuration against acceptable values. +- [Timeouts](/terraform/plugin/framework/resources/timeouts) in practitioner configuration for use in resource create, read, update and delete functions. +- [Write-only Arguments](/terraform/plugin/framework/resources/write-only-arguments) are special types of attributes that can accept [ephemeral values](/terraform/language/resources/ephemeral) and are not persisted in the Terraform plan or state artifacts. Write-only arguments are supported in Terraform 1.11 and later. + +## Define Resource Type + +Implement the [`resource.Resource` interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#Resource). Ensure the [Add Resource To Provider](#add-resource-to-provider) documentation is followed so the resource becomes part of the provider implementation, and therefore available to practitioners. + +### Metadata Method + +The [`resource.Resource` interface `Metadata` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#Resource.Metadata) defines the resource name as it would appear in Terraform configurations. This name should include the provider type prefix, an underscore, then the resource specific name. For example, a provider named `examplecloud` and a resource that reads "thing" resources would be named `examplecloud_thing`. + +In this example, the resource name in an `examplecloud` provider that reads "thing" resources is hardcoded to `examplecloud_thing`: + +```go +// With the resource.Resource implementation +func (r *ThingResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = "examplecloud_thing" +} +``` + +To simplify resource implementations, the [`provider.MetadataResponse.TypeName` field](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider#MetadataResponse.TypeName) from the [`provider.Provider` interface `Metadata` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider#Provider.Metadata) can set the provider name so it is available in the [`resource.MetadataRequest.ProviderTypeName` field](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#MetadataRequest.ProviderTypeName). + +In this example, the provider defines the `examplecloud` name for itself, and the data source is named `examplecloud_thing`: + +```go +// With the provider.Provider implementation +func (p *ExampleCloudProvider) Metadata(ctx context.Context, req provider.MetadataRequest, resp *provider.MetadataResponse) { + resp.TypeName = "examplecloud" +} + +// With the resource.Resource implementation +func (d *ThingDataSource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_thing" +} +``` + +### Schema Method + +The [`resource.Resource` interface `Schema` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#Resource.Schema) defines a [schema](/terraform/plugin/framework/schemas) describing what data is available in the resource's configuration, plan, and state. + +## Add Resource to Provider + +Resources become available to practitioners when they are included in the [provider](/terraform/plugin/framework/providers) implementation via the [`provider.Provider` interface `Resources` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/provider#Provider.Resources). + +In this example, the `ThingResource` type, which implements the `resource.Resource` interface, is added to the provider implementation: + +```go +// With the provider.Provider implementation +func (p *ExampleCloudProvider) Resources(_ context.Context) []func() resource.Resource { + return []func() resource.Resource{ + func() resource.Resource { + return &ThingResource{}, + }, + } +} +``` + +To simplify provider implementations, a named function can be created with the resource implementation. + +In this example, the `ThingResource` code includes an additional `NewThingResource` function, which simplifies the provider implementation: + +```go +// With the provider.Provider implementation +func (p *ExampleCloudProvider) Resources(_ context.Context) []func() resource.Resource { + return []func() resource.Resource{ + NewThingResource, + } +} + +// With the resource.Resource implementation +func NewThingResource() resource.Resource { + return &ThingResource{} +} +``` diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/resources/plan-modification.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/resources/plan-modification.mdx new file mode 100644 index 0000000000..bcab40d8d1 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/resources/plan-modification.mdx @@ -0,0 +1,206 @@ +--- +page_title: Plan modification +description: >- + Learn how to modify plan values and behaviors with the Terraform plugin + framework. +--- + +# Plan modification + +After [validation](/terraform/plugin/framework/validation) and before applying configuration changes, Terraform generates a plan that describes the expected values and behaviors of those changes. Resources can then tailor the plan to match the expected end state, prevent errant in-place updates, or return any [diagnostics](/terraform/plugin/framework/diagnostics). + +Terraform and the framework support multiple types of plan modification on resources: + +- Adjusting unknown attribute values, such as providing a known remote default value when a configuration is not present. +- Marking resources that should be replaced, such as when an in-place update is not supported for a change. +- Returning warning or error diagnostics on planned resource creation, update, or deletion. + +Plan modification can be added on resource schema attributes or an entire resource. Use resource-based plan modification if access to the [configured resource](/terraform/plugin/framework/resources/configure) is necessary. + +## Plan Modification Process + +When the provider receives a request to generate the plan for a resource change via the framework, the following occurs: + +1. Set any attributes with a null configuration value to the [default value](/terraform/plugin/framework/resources/default). +1. If the plan differs from the current resource state, the framework marks computed attributes that are null in the configuration as unknown in the plan. This is intended to prevent unexpected Terraform errors. Providers can later enter any values that may be known. +1. Run attribute plan modifiers. +1. Run resource plan modifiers. + +When the `Resource` interface `Update` method runs to apply a change, all attribute state values must match their associated planned values or Terraform will generate a `Provider produced inconsistent result` error. You can mark values as [unknown](/terraform/plugin/framework/types#unknown) in the plan if the full expected value is not known. + +Refer to the [Resource Instance Change Lifecycle document](https://github.com/hashicorp/terraform/blob/main/docs/resource-instance-change-lifecycle.md) for more details about the concepts and processes relevant to the plan and apply workflows. + +During the [`terraform plan`](/terraform/cli/commands/plan) and [`terraform apply`](/terraform/cli/commands/apply) commands, Terraform calls the provider [`PlanResourceChange`](/terraform/plugin/framework/internals/rpcs#planresourcechange-rpc) RPC, in which the framework calls the [`resource.Resource` interface `Schema` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#Resource.Schema) attribute plan modifiers and the `ModifyPlan` method on resources that implement the [`resource.ResourceWithModifyPlan` interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#ResourceWithModifyPlan). + +## Attribute Plan Modification + +You can supply the attribute type `PlanModifiers` field with a list of plan modifiers for that attribute. For example: + +```go +// Typically within the schema.Schema returned by Schema() for a resource. +schema.StringAttribute{ + // ... other Attribute configuration ... + + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, +} +``` + +If defined, plan modifiers are applied to the current attribute. If any nested attributes define plan modifiers, then those are applied afterwards. Any plan modifiers that return an error will prevent Terraform from applying further modifiers of that attribute as well as any nested attribute plan modifiers. + +### Common Use Case Attribute Plan Modifiers + +The framework implements some common use case modifiers in the typed packages under `resource/schema/`, such as `resource/schema/stringplanmodifier`: + +- `RequiresReplace()`: If the value of the attribute changes, in-place update is not possible and instead the resource should be replaced for the change to occur. Refer to the Go documentation for full details on its behavior. +- `RequiresReplaceIf()`: Similar to `resource.RequiresReplace()`, however it also accepts provider-defined conditional logic. Refer to the Go documentation for full details on its behavior. +- `RequiresReplaceIfConfigured()`: Similar to `resource.RequiresReplace()`, however it also will only trigger if the practitioner has configured a value. Refer to the Go documentation for full details on its behavior. +- `UseStateForUnknown()`: Copies the prior state value, if not null. This is useful for reducing `(known after apply)` plan outputs for computed attributes which are known to not change over time. + +### Creating Attribute Plan Modifiers + +To create an attribute plan modifier, you must implement the one of the [`planmodifier` package](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier) interfaces. For example: + +```go +// useStateForUnknownModifier implements the plan modifier. +type useStateForUnknownModifier struct{} + +// Description returns a human-readable description of the plan modifier. +func (m useStateForUnknownModifier) Description(_ context.Context) string { + return "Once set, the value of this attribute in state will not change." +} + +// MarkdownDescription returns a markdown description of the plan modifier. +func (m useStateForUnknownModifier) MarkdownDescription(_ context.Context) string { + return "Once set, the value of this attribute in state will not change." +} + +// PlanModifyBool implements the plan modification logic. +func (m useStateForUnknownModifier) PlanModifyBool(_ context.Context, req planmodifier.BoolRequest, resp *planmodifier.BoolResponse) { + // Do nothing if there is no state value. + if req.StateValue.IsNull() { + return + } + + // Do nothing if there is a known planned value. + if !req.PlanValue.IsUnknown() { + return + } + + // Do nothing if there is an unknown configuration value, otherwise interpolation gets messed up. + if req.ConfigValue.IsUnknown() { + return + } + + resp.PlanValue = req.StateValue +} +``` + +Optionally, you may also want to create a helper function to instantiate the plan modifier. For example: + +```go +// UseStateForUnknown returns a plan modifier that copies a known prior state +// value into the planned value. Use this when it is known that an unconfigured +// value will remain the same after a resource update. +// +// To prevent Terraform errors, the framework automatically sets unconfigured +// and Computed attributes to an unknown value "(known after apply)" on update. +// Using this plan modifier will instead display the prior state value in the +// plan, unless a prior plan modifier adjusts the value. +func UseStateForUnknown() planmodifier.Bool { + return useStateForUnknownModifier{} +} +``` + +### Caveats + +#### Terraform Data Consistency Rules + +Terraform core [implements data consistency rules](https://github.com/hashicorp/terraform/blob/main/docs/resource-instance-change-lifecycle.md) between configuration, plan, and state data. For example, if an attribute value is configured, it is never valid to change that value in the plan except being set to null on resource destroy. The framework does not raise its own targeted errors in many situations, so it is the responsibility of the developer to account for these rules when implementing plan modification logic. + +#### Prior State Under Lists and Sets + +Attribute plan modifiers under the following must take special consideration if they rely on prior state data: + +- List nested attributes +- List nested blocks +- Set nested attributes +- Set nested blocks + +These data structures are implemented based on array indexing, which the framework always sends the exact representation given across the protocol. If list/set elements are rearranged or removed, Terraform nor the framework performs any re-alignment of prior state for those elements. + +In this example, potentially unexpected prior state may be given to attribute plan modifier request: + +- A list nested attribute with two elements in configuration is saved into state +- The configuration for the first element is removed +- The list nested attribute with now one element still receives the prior state of the first element + +#### Checking Resource Change Operations + +Plan modifiers execute on all resource change operations: creation, update, and destroy. If the plan modification logic is sensitive to these details, check the request data to determine the current operation. + +Implement the following to check whether the resource is being created: + +```go +func (m ExampleModifier) PlanModifyString(_ context.Context, req planmodifier.StringRequest, resp *planmodifier.StringResponse) { + // Check if the resource is being created. + if req.State.Raw.IsNull() { + // ... + } + + // ... +} +``` + +Implement the following to check whether the resource is being destroyed: + +```go +func (m ExampleModifier) PlanModifyString(_ context.Context, req planmodifier.StringRequest, resp *planmodifier.StringResponse) { + // Check if the resource is being destroyed. + if req.Plan.Raw.IsNull() { + // ... + } + + // ... +} +``` + +## Resource Plan Modification + +Resources also support plan modification across all attributes. This is helpful when working with logic that applies to the resource as a whole, or in Terraform 1.3 and later, to return diagnostics during resource destruction. Implement the [`resource.ResourceWithModifyPlan` interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#ResourceWithModifyPlan) to support resource-level plan modification. For example: + +```go +// Ensure the Resource satisfies the resource.ResourceWithModifyPlan interface. +// Other methods to implement the resource.Resource interface are omitted for brevity +var _ resource.ResourceWithModifyPlan = ThingResource{} + +type ThingResource struct {} + +func (r ThingResource) ModifyPlan(ctx context.Context, req resource.ModifyPlanRequest, resp *resource.ModifyPlanResponse) { + // Fill in logic. +} +``` + +### Resource Destroy Plan Diagnostics + +-> Support for handling resource destruction during planning is available in Terraform 1.3 and later. + +Implement the `ModifyPlan` method by checking if the [`resource.ModifyPlanRequest` type `Plan` field](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#ModifyPlanRequest.Plan) is a `null` value: + +```go +func (r ThingResource) ModifyPlan(ctx context.Context, req resource.ModifyPlanRequest, resp *resource.ModifyPlanResponse) { + // If the entire plan is null, the resource is planned for destruction. + if req.Plan.Raw.IsNull() { + // Return an example warning diagnostic to practitioners. + resp.Diagnostics.AddWarning( + "Resource Destruction Considerations", + "Applying this resource destruction will only remove the resource from the Terraform state "+ + "and will not call the deletion API due to API limitations. Manually use the web "+ + "interface to fully destroy this resource.", + ) + } +} +``` + +Ensure the response plan remains entirely `null` when the request plan is entirely `null`. diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/resources/private-state.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/resources/private-state.mdx new file mode 100644 index 0000000000..244b1a9b91 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/resources/private-state.mdx @@ -0,0 +1,95 @@ +--- +page_title: Private state management +description: >- + Learn how to manage private state data in the Terraform plugin framework. + Private state is provider-only data storage for resources. +--- + +# Private state management + +Resource private state is provider maintained data that is stored in Terraform state alongside the schema-defined data. Private state is never accessed or exposed by Terraform plans, however providers can use this data storage for advanced use cases. + +## Usage + +Example uses in the framework include: + +* Storing and retrieving values that are not important to show to practitioners, but are required for API calls, such as ETags. +* Resource timeout functionality. + +## Concepts + +Private state data is byte data stored in the Terraform state and is intended for provider usage only (i.e., it is only used by the Framework and provider code). Providers have the ability to save this data during create, import, planning, read, and update operations and the ability to read this data during delete, planning, read, and update operations. + +## Accessing Private State Data + +Private state data can be read from a [privatestate.ProviderData](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/internal/privatestate#ProviderData) type in the `Private` field present in the _request_ that is passed into: + +| Resource Operation | Private State Data | +| --- | --- | +| Delete | [resource.DeleteRequest.Private](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#DeleteRequest.Private) | +| Plan Modification ([resource.ResourceWithModifyPlan](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#ResourceWithModifyPlan)) | [resource.ModifyPlanRequest.Private](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#ModifyPlanRequest.Private) | +| Plan Modification (`planmodifier` package interfaces) | Request type `Private` fields | +| Read | [resource.ReadRequest.Private](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#ReadRequest.Private) | +| Update | [resource.UpdateRequest.Private](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#UpdateRequest.Private) + +Private state data can be saved to a [privatestate.ProviderData](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/internal/privatestate#ProviderData) type in the `Private` field present in the _response_ that is returned from: + +| Resource Operation | Private State Data | +| --- | --- | +| Create | [resource.CreateResponse.Private](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#CreateResponse.Private) | +| Import | [resource.ImportStateResponse.Private](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#ImportStateResponse.Private) | +| Plan Modification ([resource.ResourceWithModifyPlan](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#ResourceWithModifyPlan)) | [resource.ModifyPlanResponse.Private](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#ModifyPlanResponse.Private) | +| Plan Modification (`planmodifier` package interfaces) | Response type `Private` fields | +| Read | [resource.ReadResponse.Private](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#ReadResponse.Private) | +| Update | [resource.UpdateResponse.Private](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#UpdateResponse.Private) + +### Reading Private State Data + +Private state data can be read using the [GetKey](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/internal/privatestate#ProviderData.GetKey) +function. For example: + +```go +func (r *resourceExample) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + + value, diags := req.Private.GetKey(ctx, "key") + + resp.Diagnostics.Append(diags...) + + if value != nil { + // value will be []byte. + ... + } +} +``` + +If the key supplied is [reserved](#reserved-keys) for framework usage, an error diagnostic will be returned. + +If the key is valid but no private state data is found, nil is returned. + +### Saving Private State Data + +Private state data can be saved using the [SetKey](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/internal/privatestate#ProviderData.SetKey) +function. For example: + +```go +func (r *resourceExample) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + + value := []byte(`{"valid": "json", "utf8": "safe"}`) + + diags := resp.Private.SetKey(ctx, "key", value) + + resp.Diagnostics.Append(diags...) +} +``` + +If the key supplied is [reserved](#reserved-keys) for framework usage, an error diagnostic will be returned. + +If the value is not valid JSON and UTF-8 safe, an error diagnostic will be returned. + +To remove a key and its associated value, use `nil` or a zero-length value such as `[]byte{}`. + +### Reserved Keys + +Keys supplied to [GetKey](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/internal/privatestate#ProviderData.GetKey) and [SetKey](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/internal/privatestate#ProviderData.SetKey) are validated using [ValidateProviderDataKey](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/internal/privatestate#ValidateProviderDataKey). + +Keys using a period ('.') as a prefix cannot be used for provider private state data as they are reserved for framework usage. diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/resources/read.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/resources/read.mdx new file mode 100644 index 0000000000..efdd282621 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/resources/read.mdx @@ -0,0 +1,167 @@ +--- +page_title: Read resources +description: >- + Learn how to implement resource read in the Terraform plugin framework. +--- + +# Read resources + +Read (refresh) is part of the basic Terraform lifecycle for managing resources. During the [`terraform apply`](/terraform/cli/commands/apply), [`terraform plan`](/terraform/cli/commands/plan), and [`terraform refresh`](/terraform/cli/commands/refresh) commands, Terraform calls the provider [`ReadResource`](/terraform/plugin/framework/internals/rpcs#readresource-rpc) RPC, in which the framework calls the [`resource.Resource` interface `Read` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#Resource.Read). The `Read` method is also executed after [resource import](/terraform/plugin/framework/resources/import). The request contains Terraform prior state data. The response contains the refreshed state data. The data is defined by the [schema](/terraform/plugin/framework/schemas) of the resource. + +Other resource lifecycle implementations include: + +- [Create](/terraform/plugin/framework/resources/create) resources by receiving Terraform configuration and plan data, performing creation logic, and saving Terraform state data. +- [Update](/terraform/plugin/framework/resources/update) resources in-place by receiving Terraform prior state, configuration, and plan data, performing update logic, and saving updated Terraform state data. +- [Delete](/terraform/plugin/framework/resources/delete) resources by receiving Terraform prior state data and performing deletion logic. + +## Define Read Method + +Implement the `Read` method by: + +1. [Accessing prior state data](/terraform/plugin/framework/accessing-values) from the [`resource.ReadRequest.State` field](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#ReadRequest.State). +1. Retriving updated resource state, such as remote system information. +1. [Writing state data](/terraform/plugin/framework/writing-state) into the [`resource.ReadResponse.State` field](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#ReadResponse.State). + +If the logic needs to return [warning or error diagnostics](/terraform/plugin/framework/diagnostics), they can added into the [`resource.ReadResponse.Diagnostics` field](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#ReadResponse.Diagnostics). + +In this example, the `Read` function makes a HTTP call and refreshes the state data if the status code was 200 OK or removes the resource if 404 Not Found: + +```go +// ThingResource defines the resource implementation. +// Some resource.Resource interface methods are omitted for brevity. +type ThingResource struct { + // client is configured via a Configure method, which is not shown in this + // example for brevity. Refer to the Configure Resources documentation for + // additional details for setting up resources with external clients. + client *http.Client +} + +// ThingResourceModel describes the Terraform resource data model to match the +// resource schema. +type ThingResourceModel struct { + Name types.String `tfsdk:"name"` + Id types.String `tfsdk:"id"` +} + +// ThingResourceAPIModel describes the API data model. +type ThingResourceAPIModel struct { + Name string `json:"name"` + Id string `json:"id"` +} + +func (r ThingResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "name": schema.StringAttribute{ + MarkdownDescription: "Name of the thing to be saved in the service.", + Required: true, + }, + "id": schema.StringAttribute{ + Computed: true, + MarkdownDescription: "Service generated identifier for the thing.", + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + }, + MarkdownDescription: "Manages a thing.", + } +} + +func (r *ThingResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var data ThingResourceModel + + // Read Terraform prior state data into the model + resp.Diagnostics.Append(req.State.Get(ctx, &data)...) + + // Convert from Terraform data model into API data model + readReq := ThingResourceAPIModel{ + Id: data.Id.ValueString(), + Name: data.Name.ValueString(), + } + + httpReqBody, err := json.Marshal(readReq) + + if err != nil { + resp.Diagnostics.AddError( + "Unable to Refresh Resource", + "An unexpected error occurred while creating the resource read request. "+ + "Please report this issue to the provider developers.\n\n"+ + "JSON Error: "+err.Error(), + ) + + return + } + + // Create HTTP request + httpReq := http.NewRequestWithContext( + ctx, + http.MethodPut, + "http://example.com/things", + bytes.NewBuffer(httpReqBody), + ) + + httpResp, err := d.client.Do(httpReq) + defer httpResp.Body.Close() + + if err != nil { + resp.Diagnostics.AddError( + "Unable to Refresh Resource", + "An unexpected error occurred while attempting to refresh resource state. "+ + "Please retry the operation or report this issue to the provider developers.\n\n"+ + "HTTP Error: "+err.Error(), + ) + + return + } + + // Treat HTTP 404 Not Found status as a signal to recreate resource + // and return early + if httpResp.StatusCode == http.StatusNotFound { + resp.State.RemoveResource(ctx) + + return + } + + var readResp ThingResourceAPIModel + + err := json.NewDecoder(httpResp.Body).Decode(&readResp) + + if err != nil { + resp.Diagnostics.AddError( + "Unable to Refresh Resource", + "An unexpected error occurred while parsing the resource read response. "+ + "Please report this issue to the provider developers.\n\n"+ + "JSON Error: "+err.Error(), + ) + + return + } + + // Convert from the API data model to the Terraform data model + // and refresh any attribute values. + data.Name = types.StringValue(readResp.Name) + + // Save updated data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} +``` + +## Resource Identity + +Managed resources that support identity also need to set the identity data during `Read`, see the ["Identity" page](/terraform/plugin/framework/resources/identity#writing-identity) for more information. + +## Caveats + +Note these caveats when implementing the `Read` method: + +* An error is returned if the response state contains unknown values. Set all attributes to either null or known values in the response. +* Any response errors will cause Terraform to keep the prior resource state. + +## Recommendations + +Note these recommendations when implementing the `Read` method: + +* Ignore returning errors that signify the resource is no longer existent, call the response state `RemoveResource()` method, and return early. The next Terraform plan will recreate the resource. +* Refresh all possible values. This will ensure Terraform shows configuration drift and reduces import logic. +* Preserve the prior state value if the updated value is semantically equal. For example, JSON strings that have inconsequential object property reordering or whitespace differences. This prevents Terraform from showing extraneous drift in plans. diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/resources/state-move.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/resources/state-move.mdx new file mode 100644 index 0000000000..c97dc57cd7 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/resources/state-move.mdx @@ -0,0 +1,267 @@ +--- +page_title: State move +description: >- + Learn how to implement moving state data across managed resource types using + the Terraform plugin framework. +--- + +# State move + + + +State move across managed resource types is supported in Terraform 1.8 and later. + + + +Terraform is designed with each managed resource type being distinguished from all other types. To prevent data loss or unexpected data issues, Terraform will raise an error when practitioners attempt to refactor existing resource usage across resource types via the [`moved` configuration block](/terraform/language/modules/develop/refactoring) since data compatibility is not guaranteed. Provider developers can opt into explicitly enabling Terraform to allow these refactoring operations for a target resource type based on source resource type criteria. This criteria can include the source provider address, resource type name, and schema version. + +## Use Cases + +Example use cases include: + +* Renaming a resource type, such as API service name changes or for Terraform resource naming consistency. +* Splitting a resource type, into separate resource types for practitioner ease, such as a compute resource into Linux and Windows variants. +* Handing a resource type with API versioning quirks, such as multiple resource types representing the same real world resources with partially different configuration data/concepts. + +## Concepts + +A managed resource type has an associated [state](/terraform/language/state), which captures the structure and types of data for the resource type. Enabling state move support requires the provider to handle data transformation logic which takes in source resource type state as an input and outputs the equivalent target resource type state. + +When a plan is generated with a `moved` configuration block, Terraform will send a request to the provider with all the source resource state information (provider address, resource type, schema version) and target resource type. The framework will check the target resource to see if it defines state move support. + +The framework implementation does the following: + +* If no state move support is defined for the resource, an error diagnostic is returned. +* If state move support is defined for the resource, each provider defined implementation is called until one responds with error diagnostics or state data. +* If all implementations return without error diagnostics and state data, an error diagnostic is returned. + +## Implementation + +Implement the [`resource.ResourceWithMoveState` interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#ResourceWithMoveState) for the [`resource.Resource`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#Resource). That interface requires the `MoveState` method, which enables individual source resource criteria and logic for each source resource type to support. + +This example shows a `Resource` with the necessary `MoveState` method to implement the `ResourceWithMoveState` interface: + +```go +// Other Resource methods are omitted in this example +var _ resource.ResourceWithMoveState = &TargetResource{} + +type TargetResource struct{/* ... */} + +func (r *TargetResource) MoveState(ctx context.Context) []resource.StateMover { + return []resource.StateMover{ + { + // Optionally, the SourceSchema field can be defined. + StateMover: func(ctx context.Context, req resource.MoveStateRequest, resp *resource.MoveStateResponse) { /* ... */ }, + }, + // ... potentially more StateMover for each compatible source ... + } +} +``` + +Each [`resource.StateMover`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#StateMover) implementation is expected to: + +* Check the [`resource.MoveStateRequest`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#MoveStateRequest) for whether this implementation matches a known source resource. It is always recommended to check the `SourceTypeName`, `SourceSchemaVersion`, and `SourceProviderAddress` (without the hostname, unless needed for disambiguation). +* If not matching, return early without diagnostics or setting state data in the [`resource.MoveStateResponse`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#MoveStateResponse). The framework will try the next implementation. +* If matching, wholly set the resource state from the source state. All state data must be populated in the [`resource.MoveStateResponse`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#MoveStateResponse). The framework does not copy any source state data from the [`resource.MoveStateRequest`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#MoveStateRequest). + +There are two approaches to implementing the provider logic for state moves in `StateMover`. The recommended approach is defining the source schema matching the source resource state, which allows for source state access similar to other parts of the framework. The second, more advanced, approach is accessing the source resource state using lower level data handlers. + +### StateMover With SourceSchema + +Implement the [`StateMover` type `SourceSchema` field](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#StateMover.SourceSchema) to enable the framework to populate the [`resource.MoveStateRequest` type `SourceState` field](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#MoveStateRequest.SourceState) for the provider defined state move logic. Access the request `SourceState` using methods such as [`Get()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/tfsdk#State.Get) or [`GetAttribute()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/tfsdk#State.GetAttribute). Write the [`resource.MoveStateResponse` type `TargetState` field](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#MoveStateResponse.TargetState) using methods such as [`Set()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/tfsdk#State.Set) or [`SetAttribute()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/tfsdk#State.SetAttribute). + +This example shows a target resource that supports moving state from a source resource, using the `SourceSchema` approach: + +```go +// Other Resource methods are omitted in this example +var _ resource.Resource = &TargetResource{} +var _ resource.ResourceWithMoveState = &TargetResource{} + +type TargetResource struct{/* ... */} + +type TargetResourceModel struct { + Id types.String `tfsdk:"id"` + TargetAttribute types.Bool `tfsdk:"target_attribute"` +} + +type SourceResourceModel struct { + Id types.String `tfsdk:"id"` + SourceAttribute types.Bool `tfsdk:"source_attribute"` +} + +func (r *TargetResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ /* ... */ }, + "target_attribute": schema.BoolAttribute{ /* ... */ }, + }, + } +} + +func (r *TargetResource) MoveState(ctx context.Context) []resource.StateMover { + return []resource.StateMover{ + { + SourceSchema: &schema.Schema{ + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{}, + "source_attribute": schema.BoolAttribute{}, + }, + }, + StateMover: func(ctx context.Context, req resource.MoveStateRequest, resp *resource.MoveStateResponse) { + // Always verify the expected source before working with the data. + if req.SourceTypeName != "examplecloud_source" { + return + } + + if req.SourceSchemaVersion != 0 { + return + } + + // This only checks the provider address namespace and type + // since practitioners may use differing hostnames for the same + // provider, such as a network mirror. If necessary though, the + // hostname can be used for disambiguation. + if !strings.HasSuffix(req.SourceProviderAddress, "examplecorp/examplecloud") { + return + } + + var sourceStateData SourceResourceModel + + resp.Diagnostics.Append(req.SourceState.Get(ctx, &sourceStateData)...) + + if resp.Diagnostics.HasError() { + return + } + + targetStateData := TargetResourceModel{ + Id: sourceStateData.Id, + TargetAttribute: sourceStateData.SourceAttribute, + } + + resp.Diagnostics.Append(resp.TargetState.Set(ctx, targetStateData)...) + }, + }, + } +} +``` + +### StateMover Without SourceSchema + +Read source state data from the [`resource.MoveStateRequest` type `SourceRawState` field](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#MoveStateRequest.SourceRawState). Write the [`resource.MoveStateResponse` type `TargetState` field](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#MoveStateResponse.TargetState) using methods such as [`Set()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/tfsdk#State.Set) or [`SetAttribute()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/tfsdk#State.SetAttribute). + +This example shows a target resource that supports moving state from a source resource, using the `SourceRawState` approach for the request: + +```go +// Other Resource methods are omitted in this example +var _ resource.Resource = &TargetResource{} +var _ resource.ResourceWithMoveState = &TargetResource{} + +type TargetResource struct{/* ... */} + +type TargetResourceModel struct { + Id types.String `tfsdk:"id"` + TargetAttribute types.Bool `tfsdk:"target_attribute"` +} + +func (r *TargetResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ /* ... */ }, + "target_attribute": schema.BoolAttribute{ /* ... */ }, + }, + } +} + +func (r *TargetResource) MoveState(ctx context.Context) []resource.StateMover { + return []resource.StateMover{ + { + StateMover: func(ctx context.Context, req resource.MoveStateRequest, resp *resource.MoveStateResponse) { + // Always verify the expected source before working with the data. + if req.SourceTypeName != "examplecloud_source" { + return + } + + if req.SourceSchemaVersion != 0 { + return + } + + // This only checks the provider address namespace and type + // since practitioners may use differing hostnames for the same + // provider, such as a network mirror. If necessary though, the + // hostname can be used for disambiguation. + if !strings.HasSuffix(req.SourceProviderAddress, "examplecorp/examplecloud") { + return + } + + // Refer also to the RawState type JSON field which can be used + // with json.Unmarshal() + rawStateValue, err := req.SourceRawState.Unmarshal(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "id": tftypes.String, + "source_attribute": tftypes.Bool, + }, + }) + + if err != nil { + resp.Diagnostics.AddError( + "Unable to Unmarshal Source State", + err.Error(), + ) + + return + } + + var rawState map[string]tftypes.Value + + if err := rawStateValue.As(&rawState); err != nil { + resp.Diagnostics.AddError( + "Unable to Convert Source State", + err.Error(), + ) + + return + } + + var id *string + + if err := rawState["id"].As(&id); err != nil { + resp.Diagnostics.AddAttributeError( + path.Root("id"), + "Unable to Convert Source State", + err.Error(), + ) + + return + } + + var sourceAttribute *bool + + if err := rawState["source_attribute"].As(&sourceAttribute); err != nil { + resp.Diagnostics.AddAttributeError( + path.Root("source_attribute"), + "Unable to Convert Source State", + err.Error(), + ) + + return + } + + targetStateData := TargetResourceModel{ + Id: types.StringPointerValue(id), + TargetAttribute: types.BoolPointerValue(sourceAttribute), + } + + resp.Diagnostics.Append(resp.TargetState.Set(ctx, targetStateData)...) + }, + }, + } +} +``` + +## Caveats + +Note these caveats when implementing the `MoveState` method: + +* The `SourceState` will not always be `nil` if the schema does not match the source state. Always verify the implementation matches other request fields (`SourceTypeName`, etc.) beforehand. +* An error is returned if the response state contains unknown values. Set all attributes to either null or known values in the response. +* Any response errors will cause Terraform to keep the source resource state. diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/resources/state-upgrade.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/resources/state-upgrade.mdx new file mode 100644 index 0000000000..4d762564df --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/resources/state-upgrade.mdx @@ -0,0 +1,294 @@ +--- +page_title: State upgrade +description: >- + Learn how to implement upgrading state data when provider schema changes from + one version of your Terraform framework provider to another. +--- + +# State upgrade + +A resource schema captures the structure and types of the resource [state](/terraform/language/state). Any state data that does not conform to the resource schema will generate errors or may not be persisted properly. Over time, it may be necessary for resources to make breaking changes to their schemas, such as changing an attribute type. Terraform supports versioning of these resource schemas and the current version is saved into the Terraform state. When the provider advertises a newer schema version, Terraform will call back to the provider to attempt to upgrade from the saved schema version to the one advertised. This operation is performed prior to planning, but with a configured provider. + +-> Some versions of Terraform CLI will also request state upgrades even when the current schema version matches the state version. The framework will automatically handle this request. + +## State Upgrade Process + +1. When generating a plan, Terraform CLI will request the current resource schema, which contains a version. +1. If Terraform CLI detects that an existing state with its saved version does not match the current version, Terraform CLI will request a state upgrade from the provider with the prior state version and expecting the state to match the current version. +1. The framework will check the resource to see if it defines state upgrade support: + * If no state upgrade support is defined, an error diagnostic is returned. + * If state upgrade support is defined, but not for the requested prior state version, an error diagnostic is returned. + * If state upgrade support is defined and has an implementation for the requested prior state version, the provider defined implementation is executed. + +## Implementing State Upgrade Support + +Ensure the [`schema.Schema` type `Version` field](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource/schema#Schema.Version) for the [`resource.Resource`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#Resource) is greater than `0`, then implement the [`resource.ResourceWithStateUpgrade` interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#ResourceWithStateUpgrade) for the [`resource.Resource`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#Resource). Conventionally the version is incremented by `1` for each state upgrade. + +This example shows a `Resource` with the necessary `StateUpgrade` method to implement the `ResourceWithStateUpgrade` interface: + +```go +// Other Resource methods are omitted in this example +var _ resource.Resource = &ThingResource{} +var _ resource.ResourceWithUpgradeState = &ThingResource{} + +type ThingResource struct{/* ... */} + +func (r *ThingResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + // ... other fields ... + + // This example conventionally declares that the resource has prior + // state versions of 0 and 1, while the current version is 2. + Version: 2, + } +} + +func (r *ThingResource) UpgradeState(ctx context.Context) map[int64]resource.StateUpgrader { + return map[int64]resource.StateUpgrader{ + // State upgrade implementation from 0 (prior state version) to 1 (Schema.Version) + 0: { + // Optionally, the PriorSchema field can be defined. + StateUpgrader: func(ctx context.Context, req resource.UpgradeStateRequest, resp *resource.UpgradeStateResponse) { /* ... */ }, + }, + // State upgrade implementation from 1 (prior state version) to 2 (Schema.Version) + 1: { + // Optionally, the PriorSchema field can be defined. + StateUpgrader: func(ctx context.Context, req resource.UpgradeStateRequest, resp *resource.UpgradeStateResponse) { /* ... */ }, + }, + } +} +``` + +Each [`resource.StateUpgrader`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#StateUpgrader) implementation is expected to wholly upgrade the resource state from the prior version to the current version. The framework does not iterate through intermediate version implementations as incrementing versions by 1 is only conventional and not required. + +All state data must be populated in the [`resource.UpgradeStateResponse`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#UpgradeStateResponse). The framework does not copy any prior state data from the [`resource.UpgradeStateRequest`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#UpgradeStateRequest). + +There are two approaches to implementing the provider logic for state upgrades in `StateUpgrader`. The recommended approach is defining the prior schema matching the resource state, which allows for prior state access similar to other parts of the framework. The second, more advanced, approach is accessing the prior resource state using lower level data handlers. + +### StateUpgrader With PriorSchema + +Implement the [`StateUpgrader` type `PriorSchema` field](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#StateUpgrader.PriorSchema) to enable the framework to populate the [`resource.UpgradeStateRequest` type `State` field](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#UpgradeStateRequest.State) for the provider defined state upgrade logic. Access the request `State` using methods such as [`Get()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/tfsdk#State.Get) or [`GetAttribute()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/tfsdk#State.GetAttribute). Write the [`resource.UpgradeStateResponse` type `State` field](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#UpgradeStateResponse.State) using methods such as [`Set()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/tfsdk#State.Set) or [`SetAttribute()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/tfsdk#State.SetAttribute). + +This example shows a resource that changes the type for two attributes, using the `PriorSchema` approach: + +```go +// Other Resource methods are omitted in this example +var _ resource.Resource = &ThingResource{} +var _ resource.ResourceWithUpgradeState = &ThingResource{} + +type ThingResource struct{/* ... */} + +type ThingResourceModelV0 struct { + Id string `tfsdk:"id"` + OptionalAttribute *bool `tfsdk:"optional_attribute"` + RequiredAttribute bool `tfsdk:"required_attribute"` +} + +type ThingResourceModelV1 struct { + Id string `tfsdk:"id"` + OptionalAttribute *string `tfsdk:"optional_attribute"` + RequiredAttribute string `tfsdk:"required_attribute"` +} + +func (r *ThingResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Computed: true, + }, + "optional_attribute": schema.StringAttribute{ + // As compared to prior schema.BoolAttribute below + Optional: true, + }, + "required_attribute": schema.StringAttribute{ + // As compared to prior schema.BoolAttribute below + Required: true, + }, + }, + // The resource has a prior state version of 0, which had the attribute + // types of types.BoolType as shown below. + Version: 1, + } +} + +func (r *ThingResource) UpgradeState(ctx context.Context) map[int64]resource.StateUpgrader { + return map[int64]resource.StateUpgrader{ + // State upgrade implementation from 0 (prior state version) to 1 (Schema.Version) + 0: { + PriorSchema: &schema.Schema{ + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Computed: true, + }, + "optional_attribute": schema.BoolAttribute{ + // As compared to current schema.StringAttribute above + Optional: true, + }, + "required_attribute": schema.BoolAttribute{ + // As compared to current schema.StringAttribute above + Required: true, + }, + }, + }, + StateUpgrader: func(ctx context.Context, req resource.UpgradeStateRequest, resp *resource.UpgradeStateResponse) { + var priorStateData ThingResourceModelV0 + + resp.Diagnostics.Append(req.State.Get(ctx, &priorStateData)...) + + if resp.Diagnostics.HasError() { + return + } + + upgradedStateData := ThingResourceModelV1{ + Id: priorStateData.Id, + RequiredAttribute: fmt.Sprintf("%t", priorStateData.RequiredAttribute), + } + + if priorStateData.OptionalAttribute != nil { + v := fmt.Sprintf("%t", *priorStateData.OptionalAttribute) + upgradedStateData.OptionalAttribute = &v + } + + resp.Diagnostics.Append(resp.State.Set(ctx, upgradedStateData)...) + }, + }, + } +} +``` + +### StateUpgrader Without PriorSchema + +Read prior state data from the [`resource.UpgradeStateRequest` type `RawState` field](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#UpgradeStateRequest.RawState). Write the [`resource.UpgradeStateResponse` type `State` field](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#UpgradeStateResponse.State) using methods such as [`Set()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/tfsdk#State.Set) or [`SetAttribute()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/tfsdk#State.SetAttribute), or for more advanced use cases, write the [`resource.UpgradeStateResponse` type `DynamicValue` field](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#UpgradeStateResponse.DynamicValue). + +This example shows a resource that changes the type for two attributes, using the `RawState` approach for the request and `DynamicValue` approach for the response: + +```go +// Other Resource methods are omitted in this example +var _ resource.Resource = &ThingResource{} +var _ resource.ResourceWithUpgradeState = &ThingResource{} + +var ThingResourceTftypesDataV0 = tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "id": tftypes.String, + "optional_attribute": tftypes.Bool, + "required_attribute": tftypes.Bool, + }, +} + +var ThingResourceTftypesDataV1 = tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "id": tftypes.String, + "optional_attribute": tftypes.String, + "required_attribute": tftypes.String, + }, +} + +type ThingResource struct{/* ... */} + +func (r *ThingResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Computed: true, + }, + "optional_attribute": schema.StringAttribute{ + // As compared to prior schema.BoolAttribute below + Optional: true, + }, + "required_attribute": schema.StringAttribute{ + // As compared to prior schema.BoolAttribute below + Required: true, + }, + }, + // The resource has a prior state version of 0, which had the attribute + // types of types.BoolType as shown below. + Version: 1, + } +} + +func (r *ThingResource) UpgradeState(ctx context.Context) map[int64]resource.StateUpgrader { + return map[int64]resource.StateUpgrader{ + // State upgrade implementation from 0 (prior state version) to 1 (Schema.Version) + 0: { + StateUpgrader: func(ctx context.Context, req resource.UpgradeStateRequest, resp *resource.UpgradeStateResponse) { + // Refer also to the RawState type JSON field which can be used + // with json.Unmarshal() + rawStateValue, err := req.RawState.Unmarshal(ThingResourceTftypesDataV0) + + if err != nil { + resp.Diagnostics.AddError( + "Unable to Unmarshal Prior State", + err.Error(), + ) + return + } + + var rawState map[string]tftypes.Value + + if err := rawStateValue.As(&rawState); err != nil { + resp.Diagnostics.AddError( + "Unable to Convert Prior State", + err.Error(), + ) + return + } + + var optionalAttributeString *string + + if !rawState["optional_attribute"].IsNull() { + var optionalAttribute bool + + if err := rawState["optional_attribute"].As(&optionalAttribute); err != nil { + resp.Diagnostics.AddAttributeError( + path.Root("optional_attribute"), + "Unable to Convert Prior State", + err.Error(), + ) + return + } + + v := fmt.Sprintf("%t", optionalAttribute) + optionalAttributeString = &v + } + + var requiredAttribute bool + + if err := rawState["required_attribute"].As(&requiredAttribute); err != nil { + resp.Diagnostics.AddAttributeError( + path.Root("required_attribute"), + "Unable to Convert Prior State", + err.Error(), + ) + return + } + + dynamicValue, err := tfprotov6.NewDynamicValue( + ThingResourceTftypesDataV1, + tftypes.NewValue(ThingResourceTftypesDataV1, map[string]tftypes.Value{ + "id": rawState["id"], + "optional_attribute": tftypes.NewValue(tftypes.String, optionalAttributeString), + "required_attribute": tftypes.NewValue(tftypes.String, fmt.Sprintf("%t", requiredAttribute)), + }), + ) + + if err != nil { + resp.Diagnostics.AddError( + "Unable to Convert Upgraded State", + err.Error(), + ) + return + } + + resp.DynamicValue = &dynamicValue + }, + }, + } +} +``` + +## Caveats + +Note these caveats when implementing the `UpgradeState` method: + +* An error is returned if the response state contains unknown values. Set all attributes to either null or known values in the response. +* Any response errors will cause Terraform to keep the prior resource state. diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/resources/timeouts.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/resources/timeouts.mdx new file mode 100644 index 0000000000..db14bae0c2 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/resources/timeouts.mdx @@ -0,0 +1,138 @@ +--- +page_title: Timeouts +description: >- + Learn how to implement timeouts with the Terraform plugin framework. +--- + +# Timeouts + +The reality of cloud infrastructure is that it typically takes time to perform operations such as booting operating systems, discovering services, and replicating state across network edges. As the provider developer you should take known delays in resource APIs into account in the CRUD functions of the resource. Terraform supports configurable timeouts to assist in these situations. + +The Framework can be used in conjunction with the [terraform-plugin-framework-timeouts](https://github.com/hashicorp/terraform-plugin-framework-timeouts) module in order to allow defining timeouts in configuration and have them be available in CRUD functions. + +## Specifying Timeouts in Configuration + +Timeouts can be defined using either nested blocks or nested attributes. + +If you are writing a new provider using [terraform-plugin-framework](https://github.com/hashicorp/terraform-plugin-framework) +then we recommend using nested attributes. + +If you are [migrating a provider from SDKv2 to the Framework](/terraform/plugin/framework/migrating) and +you are already using timeouts you can either continue to use block syntax, or switch to using nested attributes. +However, switching to using nested attributes will require that practitioners that are using your provider update their +Terraform configuration. + +#### Block + +If your configuration is using a nested block to define timeouts, such as the following: + +```hcl +resource "timeouts_example" "example" { + /* ... */ + + timeouts { + create = "60m" + } +} +``` + +Import the [timeouts module](https://github.com/hashicorp/terraform-plugin-framework-timeouts). + +```go +import ( + /* ... */ + "github.com/hashicorp/terraform-plugin-framework-timeouts/resource/timeouts" +) +```` + +You can use this module to mutate the `schema.Schema` as follows: + +```go +func (t *exampleResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + /* ... */ + + Blocks: map[string]schema.Block{ + "timeouts": timeouts.Block(ctx, timeouts.Opts{ + Create: true, + }), + }, +``` + +#### Attribute + +If your configuration is using nested attributes to define timeouts, such as the following: + +```hcl +resource "timeouts_example" "example" { + /* ... */ + + timeouts = { + create = "60m" + } +} +``` + +You can use this module to mutate the `schema.Schema` as follows: + +```go +func (t *exampleResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + /* ... */ + "timeouts": timeouts.Attributes(ctx, timeouts.Opts{ + Create: true, + }), + }, +``` + +## Updating Models + +In functions in which the config, state or plan is being unmarshalled, for instance, the `Create` function, the model +will need to be updated. + +```go +func (e *exampleResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var data exampleResourceData + + diags := req.Plan.Get(ctx, &data) + resp.Diagnostics.Append(diags...) +``` + +Modify the `exampleResourceData` model to include a field for timeouts using a [`timeouts.Value`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-timeouts/resource/timeouts#Value) type. + +```go +type exampleResourceData struct { + /* ... */ + Timeouts timeouts.Value `tfsdk:"timeouts"` +``` + +## Accessing Timeouts in CRUD Functions + +Once the model has been populated with the config, state or plan the duration of the timeout can be accessed by calling +the appropriate helper function (e.g., [`timeouts.Create`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-timeouts/resource/timeouts#Value.Create)) and then used to configure timeout behaviour, for instance: + +```go +func (e *exampleResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var data exampleResourceData + + diags := req.Plan.Get(ctx, &data) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + createTimeout, diags := data.Timeouts.Create(ctx, 20*time.Minute) + + resp.Diagnostics.Append(diags...) + + if resp.Diagnostics.HasError() { + return + } + + ctx, cancel := context.WithTimeout(ctx, createTimeout) + defer cancel() + + /* ... */ +} +``` diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/resources/update.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/resources/update.mdx new file mode 100644 index 0000000000..a892b4161f --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/resources/update.mdx @@ -0,0 +1,211 @@ +--- +page_title: Update resources +description: >- + Learn how to implement in-place updating of resources in the Terraform + plugin framework. +--- + +# Update resources + +In-place update is part of the basic Terraform lifecycle for managing resources. During the [`terraform apply` command](/terraform/cli/commands/apply), Terraform calls the provider [`ApplyResourceChange`](/terraform/plugin/framework/internals/rpcs#applyresourcechange-rpc) RPC, in which the framework calls the [`resource.Resource` interface `Update` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#Resource.Update). The request contains Terraform prior state, configuration, and plan data. The response contains updated state data. The data is defined by the [schema](/terraform/plugin/framework/schemas) of the resource. + +To ensure that Terraform plans replacement of a resource, rather than perform an in-place update, use the [`resource.RequiresReplace()` attribute plan modifier](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#RequiresReplace) in the schema or implement [resource plan modification](/terraform/plugin/framework/resources/plan-modification). + +Other resource lifecycle implementations include: + +- [Create](/terraform/plugin/framework/resources/create) resources by receiving Terraform configuration and plan data, performing creation logic, and saving Terraform state data. +- [Read](/terraform/plugin/framework/resources/read) resources by receiving Terraform prior state data, performing read logic, and saving refreshed Terraform state data. +- [Delete](/terraform/plugin/framework/resources/delete) resources by receiving Terraform prior state data and performing deletion logic. + +## Define Update Method + +Implement the `Update` method by: + +1. [Accessing the data](/terraform/plugin/framework/accessing-values) from the [`resource.UpdateRequest` type](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#UpdateRequest). Most use cases should access the plan data in the [`resource.UpdateRequest.Plan` field](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#UpdateRequest.Plan). +1. Performing logic or external calls to modify the resource. +1. [Writing state data](/terraform/plugin/framework/writing-state) into the [`resource.UpdateResponse.State` field](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#UpdateResponse.State). + +If the logic needs to return [warning or error diagnostics](/terraform/plugin/framework/diagnostics), they can added into the [`resource.UpdateResponse.Diagnostics` field](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#UpdateResponse.Diagnostics). + +In this example, the `Update` function makes a HTTP call and returns successfully if the status code was 200 OK: + +```go +// ThingResource defines the resource implementation. +// Some resource.Resource interface methods are omitted for brevity. +type ThingResource struct { + // client is configured via a Configure method, which is not shown in this + // example for brevity. Refer to the Configure Resources documentation for + // additional details for setting up resources with external clients. + client *http.Client +} + +// ThingResourceModel describes the Terraform resource data model to match the +// resource schema. +type ThingResourceModel struct { + Name types.String `tfsdk:"name"` + Id types.String `tfsdk:"id"` +} + +// ThingResourceAPIModel describes the API data model. +type ThingResourceAPIModel struct { + Name string `json:"name"` + Id string `json:"id"` +} + +func (r ThingResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "name": schema.StringAttribute{ + MarkdownDescription: "Name of the thing to be saved in the service.", + Required: true, + }, + "id": schema.StringAttribute{ + Computed: true, + MarkdownDescription: "Service generated identifier for the thing.", + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + }, + MarkdownDescription: "Manages a thing.", + } +} + +func (r ThingResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var data ThingResourceModel + + // Read Terraform plan data into the model + resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) + + // Convert from Terraform data model into API data model + updateReq := ThingResourceAPIModel{ + Id: data.Id.ValueString(), + Name: data.Name.ValueString(), + } + + httpReqBody, err := json.Marshal(updateReq) + + if err != nil { + resp.Diagnostics.AddError( + "Unable to Update Resource", + "An unexpected error occurred while creating the resource update request. "+ + "Please report this issue to the provider developers.\n\n"+ + "JSON Error: "+err.Error(), + ) + + return + } + + // Create HTTP request + httpReq := http.NewRequestWithContext( + ctx, + http.MethodPut, + "http://example.com/things", + bytes.NewBuffer(httpReqBody), + ) + + // Send HTTP request + httpResp, err := d.client.Do(httpReq) + defer httpResp.Body.Close() + + if err != nil { + resp.Diagnostics.AddError( + "Unable to Update Resource", + "An unexpected error occurred while attempting to update the resource. "+ + "Please retry the operation or report this issue to the provider developers.\n\n"+ + "HTTP Error: "+err.Error(), + ) + + return + } + + // Return error if the HTTP status code is not 200 OK + if httpResp.StatusCode != http.StatusOK { + resp.Diagnostics.AddError( + "Unable to Update Resource", + "An unexpected error occurred while attempting to update the resource. "+ + "Please retry the operation or report this issue to the provider developers.\n\n"+ + "HTTP Status: "+httpResp.Status, + ) + + return + } + + // Save updated data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} +``` + +## Caveats + +Note these caveats when implementing the `Update` method: + +* An error is returned if the response state is not set when `Update` is called by the framework. If the resource does not support modification and should always be recreated on configuration value updates, the `Update` logic can be left empty and ensure all configurable schema attributes implement the [`resource.RequiresReplace()` attribute plan modifier](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#RequiresReplace). +* An error is returned if the response state contains unknown values. Set all attributes to either null or known values in the response. +* An error is returned if the response state has the `RemoveResource()` method called. This method is not valid during update. Return an error if the resource is no longer exists. +* An error is returned unless every null or known value in the request plan is saved exactly as-is into the response state. Only unknown plan values can be modified. + +## Recommendations + +Note these recommendations when implementing the `Update` method: + +* Get request data from the Terraform plan data over configuration data as the schema or resource may include [plan modification](/terraform/plugin/framework/resources/plan-modification) logic which sets plan values. +* Only successfully modified parts of the resource should be return updated data in the state response. +* Use the [`resource.UseStateForUnknown()` attribute plan modifier](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#UseStateForUnknown) for `Computed` attributes that are known to not change during resource updates. This will enhance the Terraform plan to not show `=> (known after apply)` differences. + +## Additional Use Cases + +This section highlights implementation details for specific use cases. + +### Detect Specific Attribute Changes + +Certain update APIs require that only value changes are sent in the update request or require individual update API calls. Compare the attribute plan value to the attribute prior state value to determine individual attribute changes. + +In this example, the entire plan and prior state are fetched then the attribute values are compared: + +```go +type ThingResourceModel struct { + Name types.String `tfsdk:"name"` + // ... other attributes ... +} + +func (r ThingResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var plan, state ThingResourceModel + + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + + if resp.Diagnostics.HasError() { + return + } + + // Compare name attribute value between plan and prior state + if !plan.Name.Equal(state.Name) { + // name attribute was changed + } + + // ... further logic ... +} +``` + +In this example, the attribute is individually fetched from the plan and prior state then the values are compared: + +```go +func (r ThingResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var namePlan, nameState types.String + + resp.Diagnostics.Append(req.Plan.GetAttribute(ctx, path.Root("name"), &namePlan)...) + resp.Diagnostics.Append(req.State.GetAttribute(ctx, path.Root("name"), &nameState)...) + + if resp.Diagnostics.HasError() { + return + } + + // Compare name attribute value between plan and prior state + if !namePlan.Equal(nameState) { + // name attribute was changed + } + + // ... further logic ... +} +``` diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/resources/validate-configuration.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/resources/validate-configuration.mdx new file mode 100644 index 0000000000..4a1d936613 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/resources/validate-configuration.mdx @@ -0,0 +1,86 @@ +--- +page_title: Validate resource configuration +description: >- + Learn how to validate resource configuration with the Terraform plugin + framework. +--- + +# Validate configuration + +[Resources](/terraform/plugin/framework/resources) support validating an entire practitioner configuration in either declarative or imperative logic. Feedback, such as required syntax or acceptable combinations of values, is returned via [diagnostics](/terraform/plugin/framework/diagnostics). + +This page describes implementation details for validating entire resource configurations, typically referencing multiple attributes. Further documentation is available for other configuration validation concepts: + +- [Single attribute validation](/terraform/plugin/framework/validation#attribute-validation) is a schema-based mechanism for implementing attribute-specific validation logic. +- [Type validation](/terraform/plugin/framework/validation#type-validation) is a schema-based mechanism for implementing reusable validation logic for any attribute using the type. + +-> Configuration validation in Terraform occurs without provider configuration ("offline"), so therefore the resource `Configure` method will not have been called. To implement validation with a configured API client, use [plan modification](/terraform/plugin/framework/resources/plan-modification#resource-plan-modification) instead, which occurs during Terraform's planning phase. + +## ConfigValidators Method + +The [`resource.ResourceWithConfigValidators` interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#ResourceWithConfigValidators) follows a similar pattern to attribute validation and allows for a more declarative approach. This enables consistent validation logic across multiple resources. Each validator intended for this interface must implement the [`resource.ConfigValidator` interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#ConfigValidator). + +During execution of the [`terraform validate`](/terraform/cli/commands/validate), [`terraform plan`](/terraform/cli/commands/plan) and [`terraform apply`](/terraform/cli/commands/apply) commands, Terraform calls the provider [`ValidateResourceConfig`](/terraform/plugin/framework/internals/rpcs#validateresourceconfig-rpc) RPC, in which the framework calls the `ConfigValidators` method on resources that implement the [`resource.ResourceWithConfigValidators` interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#ResourceWithConfigValidators). + +The [`terraform-plugin-framework-validators` Go module](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators) has a collection of common use case resource configuration validators in the [`resourcevalidator` package](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators/resourcevalidator). These use [path expressions](/terraform/plugin/framework/path-expressions) for matching attributes. + +This example will raise an error if a practitioner attempts to configure both `attribute_one` and `attribute_two`: + +```go +// Other methods to implement the resource.Resource interface are omitted for brevity +type ThingResource struct {} + +func (r ThingResource) ConfigValidators(ctx context.Context) []resource.ConfigValidator { + return []resource.ConfigValidator{ + resourcevalidator.Conflicting( + path.MatchRoot("attribute_one"), + path.MatchRoot("attribute_two"), + ), + } +} +``` + +## ValidateConfig Method + +The [`resource.ResourceWithValidateConfig` interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#ResourceWithValidateConfig) is more imperative in design and is useful for validating unique functionality across multiple attributes that typically applies to a single resource. + +During execution of the [`terraform validate`](/terraform/cli/commands/validate), [`terraform plan`](/terraform/cli/commands/plan) and [`terraform apply`](/terraform/cli/commands/apply) commands, Terraform calls the provider [`ValidateResourceConfig`](/terraform/plugin/framework/internals/rpcs#validateresourceconfig-rpc) RPC, in which the framework calls the `ValidateConfig` method on providers that implement the [`resource.ResourceWithValidateConfig` interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/resource#ResourceWithValidateConfig). + +This example will raise a warning if a practitioner attempts to configure `attribute_one`, but not `attribute_two`: + +```go +// Other methods to implement the resource.Resource interface are omitted for brevity +type ThingResource struct {} + +type ThingResourceModel struct { + AttributeOne types.String `tfsdk:"attribute_one"` + AttributeTwo types.String `tfsdk:"attribute_two"` +} + +func (r ThingResource) ValidateConfig(ctx context.Context, req resource.ValidateConfigRequest, resp *resource.ValidateConfigResponse) { + var data ThingResourceModel + + resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + // If attribute_one is not configured, return without warning. + if data.AttributeOne.IsNull() || data.AttributeOne.IsUnknown() { + return + } + + // If attribute_two is not null, return without warning. + if !data.AttributeTwo.IsNull() { + return + } + + resp.Diagnostics.AddAttributeWarning( + path.Root("attribute_two"), + "Missing Attribute Configuration", + "Expected attribute_two to be configured with attribute_one. "+ + "The resource may return unexpected results.", + ) +} +``` diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/resources/write-only-arguments.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/resources/write-only-arguments.mdx new file mode 100644 index 0000000000..ee61e2b99e --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/resources/write-only-arguments.mdx @@ -0,0 +1,114 @@ +--- +page_title: 'Plugin Development - Framework: Write-only Arguments' +description: >- + How to implement write-only arguments with the provider development framework. +--- + +# Write-only Arguments + +Write-only arguments are managed resource attributes that are configured by practitioners but are not persisted to the Terraform plan or state artifacts. Write-only arguments are supported in Terraform 1.11 and later. +Write-only arguments should be used to handle secret values that do not need to be persisted in Terraform state, such as passwords, API keys, etc. +The provider is expected to be the terminal point for an ephemeral value, +which should either use the value by making the appropriate change to the API or ignore the value. Write-only arguments can accept [ephemeral values](/terraform/language/resources/ephemeral) and are not required to be consistent between plan and apply operations. + +## General Concepts + +The following are high level differences between `Required`/`Optional` arguments and write-only arguments: + +- Write-only arguments can accept ephemeral and non-ephemeral values. + +- Write-only arguments cannot be used with set attributes, set nested attributes, and set nested blocks. + +- Write-only argument values are only available in the configuration. The prior state, planned state, and final state values for +write-only arguments should always be `null`. + - Provider developers do not need to explicitly set write-only argument values to `null` after using them as the plugin framework will handle the nullification of write-only arguments for all RPCs. + +- Any value that is set for a write-only argument in the state or plan (during [Plan Modification](/terraform/plugin/framework/resources/plan-modification)) by the provider will be reverted to `null` by plugin framework before the RPC response is sent to Terraform. + +- Write-only argument values cannot produce a Terraform plan difference. + - This is because the prior state value for a write-only argument will always be `null` and the planned/final state value will also be `null`, therefore, it cannot produce a diff on its own. + - The one exception to this case is if the write-only argument is added to `requires_replace` during Plan Modification (i.e., using the [`RequiresReplace()`](/terraform/plugin/framework/resources/plan-modification#requiresreplace) plan modifier), in that case, the write-only argument will always cause a diff/trigger a resource recreation. + +- Since write-only arguments can accept ephemeral values, write-only argument configuration values are not expected to be consistent between plan and apply. + +## Schema + +An attribute can be made write-only by setting the `WriteOnly` field to `true` in the schema. Attributes with `WriteOnly` set to `true` must also have +one of `Required` or `Optional` set to `true`. If a list nested, map nested, or single nested attribute has `WriteOnly` set to `true`, all child attributes must also have `WriteOnly` set to `true`. +A set nested block cannot have any child attributes with `WriteOnly` set to `true`. `Computed` cannot be set to true for write-only arguments. + +**Schema example:** + +```go +"password_wo": schema.StringAttribute{ + Required: true, + WriteOnly: true, +}, +``` + +## Retrieving Write-only Values + +Write-only argument values should be retrieved from the configuration instead of the plan. Refer to [accessing values](/terraform/plugin/framework/handling-data/accessing-values) for more details on +retrieving values from configuration. + +## PreferWriteOnlyAttribute Validators + +The `PreferWriteOnlyAttribute()` validators available in the [`terraform-plugin-framework-validators` Go module](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators) +can be used when you have a write-only version of an existing attribute, and you want to encourage practitioners to use the write-only version whenever possible. + +The validator returns a warning if the Terraform client is 1.11 or above and the value of the existing attribute is non-null. + +`PreferWriteOnlyAttribute()` is available as a resource-level validator in the [`resourcevalidator` package](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators/resourcevalidator) or +as an attribute-level validator in the `[type]validator` packages (i.e., [`stringvalidator` package](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator)) + +Usage: + +```go +// Resource-level validator +// Used inside a resource.Resource type ConfigValidators method + _ = []resource.ConfigValidator{ + // Throws a warning diagnostic encouraging practitioners to use + // password_wo if password has a known value + resourcevalidator.PreferWriteOnlyAttribute( + path.MatchRoot("password"), + path.MatchRoot("password_wo"), + ), + } + +// Attribute-level validator +// Used within a Schema method of a Resource + _ = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "password": schema.StringAttribute{ + Optional: true, + Validators: []validator.String{ + // Throws a warning diagnostic encouraging practitioners to use + // password_wo if password has a known value. + stringvalidator.PreferWriteOnlyAttribute( + path.MatchRoot("password_wo"), + ), + }, + }, + "password_wo": schema.StringAttribute{ + WriteOnly: true, + Optional: true, + }, + }, + } +``` + +```hcl +resource "example_db_instance" "ex" { + username = "foo" + password = "bar" # returns a warning encouraging practitioners to use `password_wo` instead. +} +``` + +## Best Practices + +Since write-only arguments have no prior values, user intent or value changes cannot be determined with a write-only argument alone. To determine when to use/not use a write-only argument value in your provider, we recommend one of the following: + +- Pair write-only arguments with a configuration attribute (required or optional) to “trigger” the use of the write-only argument + - For example, a `password_wo` write-only argument can be paired with a configured `password_wo_version` attribute. When the `password_wo_version` is modified, the provider will send the `password_wo` value to the API. +- Use a keepers attribute (which is used in the [Random Provider](https://registry.terraform.io/providers/hashicorp/random/latest/docs#resource-keepers)) that will take in arbitrary key-pair values. Whenever there is a change to the `keepers` attribute, the provider will use the write-only argument value. +- Use the resource's [private state](/terraform/plugin/framework/resources/private-state) to store secure hashes of write-only argument values, the provider will then use the hash to determine if a write-only argument value has changed in later Terraform runs. \ No newline at end of file diff --git a/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/validation.mdx b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/validation.mdx new file mode 100644 index 0000000000..fedfbafb98 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/docs/plugin/framework/validation.mdx @@ -0,0 +1,518 @@ +--- +page_title: Validation +description: >- + Learn how to validate configuration values using the Terraform plugin framework. +--- + +# Validation + +The framework can return [diagnostics](/terraform/plugin/framework/diagnostics) feedback for values in provider, resource, and data source configurations or [errors](/terraform/plugin/framework/functions/errors) feedback for values in function parameters. This allows you to write validations that give users feedback about required syntax, types, and acceptable values. + +This page describes single attribute, parameter, and type validation concepts that can be used in any data source schema, provider schema, resource schema, or function definition. Further documentation is available for other configuration validation concepts: + +- [Data source validation](/terraform/plugin/framework/data-sources/validate-configuration) for multiple attributes declaratively or imperatively. +- [Provider validation](/terraform/plugin/framework/providers/validate-configuration) for multiple attributes declaratively or imperatively. +- [Resource validation](/terraform/plugin/framework/resources/validate-configuration) for multiple attributes declaratively or imperatively. +- [Ephemeral Resource validation](/terraform/plugin/framework/ephemeral-resources/validate-configuration) for multiple attributes declaratively or imperatively. + +-> **Note:** When implementing validation logic, configuration values may be [unknown](/terraform/plugin/framework/types#unknown) based on the source of the value. Implementations must account for this case, typically by returning early without returning new diagnostics. + +During execution of the [`terraform validate`](/terraform/cli/commands/validate), [`terraform plan`](/terraform/cli/commands/plan), [`terraform apply`](/terraform/cli/commands/apply) and [`terraform destroy`](/terraform/cli/commands/destroy) commands, Terraform calls the provider [`ValidateProviderConfig`](/terraform/plugin/framework/internals/rpcs#validateproviderconfig-rpc), [`ValidateResourceConfig`](/terraform/plugin/framework/internals/rpcs#validateresourceconfig-rpc), [`ValidateDataResourceConfig`](/terraform/plugin/framework/internals/rpcs#validatedataresourceconfig-rpc), and `ValidateEphemeralResourceConfig` RPCs. + +## Default Terraform CLI Validation + +The [Terraform configuration language](/terraform/language) is declarative and an implementation of [HashiCorp Configuration Language](https://github.com/hashicorp/hcl) (HCL). The Terraform CLI is responsible for reading and parsing configurations for validity, based on Terraform's concepts such as `resource` blocks and associated syntax. The Terraform CLI automatically handles basic validation of value type and behavior information based on the provider, resource, or data source schema. For example, the Terraform CLI returns an error when a string value is given where a list value is expected and also when a required attribute is missing from a configuration. + +Terraform CLI syntax and basic schema checks occur during the [`terraform apply`](/terraform/cli/commands/apply), [`terraform destroy`](/terraform/cli/commands/destroy), [`terraform plan`](/terraform/cli/commands/plan), and [`terraform validate`](/terraform/cli/commands/validate) commands. Any additional validation you define with the framework occurs directly after these checks are complete. + +## Attribute Validation + +You can introduce validation on attributes using the generic framework-defined types such as [`types.String`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#String). To do this, supply the `Validators` field with a list of validations, and the framework will return diagnostics from all validators. For example: + +```go +// Typically within the schema.Schema returned by Schema() for a provider, +// resource, or data source. +schema.StringAttribute{ + // ... other Attribute configuration ... + + Validators: []validator.String{ + // These are example validators from terraform-plugin-framework-validators + stringvalidator.LengthBetween(10, 256), + stringvalidator.RegexMatches( + regexp.MustCompile(`^[a-z0-9]+$`), + "must contain only lowercase alphanumeric characters", + ), + }, +} +``` + +All validators in the slice will always be run, regardless of whether previous validators returned an error or not. + +### Common Use Case Attribute Validators + +You can implement attribute validators from the [terraform-plugin-framework-validators Go module](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-validators), which contains validation handling for many common use cases such as string contents and integer ranges. + +### Creating Attribute Validators + +If there is not an attribute validator in `terraform-plugin-framework-validators` that meets a specific use case, a provider-defined attribute validator can be created. + +To create an attribute validator, you must implement at least one of the [`validator` package](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/schema/validator) interfaces. For example: + +```go +type stringLengthBetweenValidator struct { + Max int + Min int +} + +// Description returns a plain text description of the validator's behavior, suitable for a practitioner to understand its impact. +func (v stringLengthBetweenValidator) Description(ctx context.Context) string { + return fmt.Sprintf("string length must be between %d and %d", v.Min, v.Max) +} + +// MarkdownDescription returns a markdown formatted description of the validator's behavior, suitable for a practitioner to understand its impact. +func (v stringLengthBetweenValidator) MarkdownDescription(ctx context.Context) string { + return fmt.Sprintf("string length must be between `%d` and `%d`", v.Min, v.Max) +} + +// Validate runs the main validation logic of the validator, reading configuration data out of `req` and updating `resp` with diagnostics. +func (v stringLengthBetweenValidator) ValidateString(ctx context.Context, req validator.StringRequest, resp *validator.StringResponse) { + // If the value is unknown or null, there is nothing to validate. + if req.ConfigValue.IsUnknown() || req.ConfigValue.IsNull() { + return + } + + strLen := len(req.ConfigValue.ValueString()) + + if strLen < v.Min || strLen > v.Max { + resp.Diagnostics.AddAttributeError( + req.AttributePath, + "Invalid String Length", + fmt.Sprintf("String length must be between %d and %d, got: %d.", v.Min, v.Max, strLen), + ) + + return + } +} +``` + +Optionally and depending on the complexity, it may be desirable to also create a helper function to instantiate the validator. For example: + +```go +func stringLengthBetween(minLength int, maxLength int) stringLengthBetweenValidator { + return stringLengthBetweenValidator{ + Max: maxLength, + Min: minLength, + } +} +``` + +#### Path Based Attribute Validators + +Attribute validators that need to accept [paths](/terraform/plugin/framework/paths) to reference other attribute data should instead prefer [path expressions](/terraform/plugin/framework/path-expressions). This allows consumers to use either absolute paths starting at the root of a [schema](/terraform/plugin/framework/schemas), or relative paths based on the current attribute path where the validator is called. + +Path expressions may represent one or more actual paths in the data. To find those paths, the process is called path matching. Depending on the actual data, a path match may return a parent path for null or unknown values, since any underlying paths of those null or unknown values would also represent the same value. This framework behavior is used to prevent false positives of returning no paths for null or unknown values. + +The general structure for working with path expressions in an attribute validator is: + +- Merge the given path expression(s) with the current attribute path expression, such as calling the request type `PathExpression` field [`MergeExpressions()` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/path#Expression.MergeExpressions). +- Loop through each merged path expression to get the matching paths within the data, such as calling the request type `Config` field [`PathMatches()` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/tfsdk#Config.PathMatches). +- Loop through each matched path to get the generic `attr.Value` value, such as calling the request type `Config` field [`GetAttribute()` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/tfsdk#Config.GetAttribute). +- Perform null and unknown value checks on the `attr.Value`, such as the [`IsNull()` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/attr#Value.IsNull) and [`IsUnknown()` method](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/attr#Value.IsUnknown). +- If the `attr.Value` is not null and not unknown, then use [`tfsdk.ValueAs()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/tfsdk#ValueAs) using the expected value implementation as the target. + +The following example shows a generic path based attribute validator that returns an error if `types.Int64` values at the given path expressions are less than the current attribute `types.Int64` value. + +```go +// Ensure our implementation satisfies the validator.Int64 interface. +var _ validator.Int64 = &int64IsGreaterThanValidator{} + +// int64IsGreaterThanValidator is the underlying type implementing Int64IsGreaterThan. +type int64IsGreaterThanValidator struct { + expressions path.Expressions +} + +// Description returns a plaintext string describing the validator. +func (v int64IsGreaterThanValidator) Description(_ context.Context) string { + return fmt.Sprintf("If configured, must be greater than %s attributes", v.expressions) +} + +// MarkdownDescription returns a Markdown formatted string describing the validator. +func (v int64IsGreaterThanValidator) MarkdownDescription(ctx context.Context) string { + return v.Description(ctx) +} + +// Validate performs the validation logic for the validator. +func (v int64IsGreaterThanValidator) ValidateInt64(ctx context.Context, req validator.Int64Request, resp *validator.Int64Response) { + // If the current attribute configuration is null or unknown, there + // cannot be any value comparisons, so exit early without error. + if req.ConfigValue.IsNull() || req.ConfigValue.IsUnknown() { + return + } + + // Combine the given path expressions with the current attribute path + // expression. This call automatically handles relative and absolute + // expressions. + expressions := req.AttributePathExpression.MergeExpressions(v.expressions...) + + // For each expression, find matching paths. + for _, expression := range expressions { + // Find paths matching the expression in the configuration data. + matchedPaths, diags := req.Config.PathMatches(ctx, expression) + + resp.Diagnostics.Append(diags...) + + // Collect all errors + if diags.HasError() { + continue + } + + // For each matched path, get the data and compare. + for _, matchedPath := range matchedPaths { + // Fetch the generic attr.Value at the given path. This ensures any + // potential parent value of a different type, which can be a null + // or unknown value, can be safely checked without raising a type + // conversion error. + var matchedPathValue attr.Value + + diags := req.Config.GetAttribute(ctx, matchedPath, &matchedPathValue) + + resp.Diagnostics.Append(diags...) + + // Collect all errors + if diags.HasError() { + continue + } + + // If the matched path value is null or unknown, we cannot compare + // values, so continue to other matched paths. + if matchedPathValue.IsNull() || matchedPathValue.IsUnknown() { + continue + } + + // Now that we know the matched path value is not null or unknown, + // it is safe to attempt converting it to the intended attr.Value + // implementation, in this case a types.Int64 value. + var matchedPathConfig types.Int64 + + diags = tfsdk.ValueAs(ctx, matchedPathValue, &matchedPathConfig) + + resp.Diagnostics.Append(diags...) + + // If the matched path value was not able to be converted from + // attr.Value to the intended types.Int64 implementation, it most + // likely means that the path expression was not pointing at a + // types.Int64Type attribute. Collect the error and continue to + // other matched paths. + if diags.HasError() { + continue + } + + if matchedPathConfig.ValueInt64() >= attributeConfig.ValueInt64() { + resp.Diagnostics.AddAttributeError( + matchedPath, + "Invalid Attribute Value", + fmt.Sprintf("Must be less than %s value: %d", req.AttributePath, attributeConfig.ValueInt64()), + ) + } + } + } +} + +// Int64IsGreaterThan checks that any Int64 values in the paths described by the +// path.Expression are less than the current attribute value. +func Int64IsGreaterThan(expressions ...path.Expression) validator.Int64 { + return &int64IsGreaterThanValidator{ + expressions: expressions, + } +} +``` + +## Parameter Validation + +You can introduce validation on function parameters using the generic framework-defined types such as [`types.String`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/types#String). To do this, supply the `Validators` field with a list of validations, and the framework will return errors from all validators. For example: + +```go +// Typically within the function.Definition for a function. +function.StringParameter{ + // ... other Parameter configuration ... + + Validators: []function.StringParameterValidator{ + stringvalidator.LengthBetween(10, 256), + }, +}, +``` + +All validators in the slice will always be run, regardless of whether previous validators returned an error or not. + +### Creating Parameter Validators + +To create a parameter validator, you must implement at least one of the [`function` package](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/function) `ParameterValidator` interfaces. For example: + +```go +// Ensure the implementation satisfies the expected interfaces +var ( + _ function.StringParameterValidator = stringLengthBetweenValidator{} +) + +type stringLengthBetweenValidator struct { + Max int + Min int +} + +// Validate runs the main validation logic of the validator, reading configuration data out of `req` and updating `resp` with diagnostics. +func (v stringLengthBetweenValidator) ValidateParameterString(ctx context.Context, req validator.StringParameterValidatorRequest, resp *validator.StringParameterValidatorResponse) { + // If the value is unknown or null, there is nothing to validate. + if req.Value.IsUnknown() || req.Value.IsNull() { + return + } + + strLen := len(req.Value.ValueString()) + + if strLen < v.Min || strLen > v.Max { + resp.Error = function.NewArgumentFuncError( + req.ArgumentPosition, + fmt.Sprintf("Invalid String Length: String length must be between %d and %d, got: %d.", v.Min, v.Max, strLen), + ) + + return + } +} +``` + +Optionally and depending on the complexity, it may be desirable to also create a helper function to instantiate the validator. For example: + +```go +func stringLengthBetween(minLength int, maxLength int) stringLengthBetweenValidator { + return stringLengthBetweenValidator{ + Max: maxLength, + Min: minLength, + } +} +``` + +A single validator type can be used as both an attribute validator and a parameter validator, as long as the validator implements the appropriate interfaces. For example: + +```go +var ( + _ validator.String = stringLengthBetweenValidator{} + _ function.StringParameterValidator = stringLengthBetweenValidator{} + +) +type stringLengthBetweenValidator struct { + Max int + Min int +} + +// Description returns a plain text description of the attribute validator's behavior, suitable for a practitioner to understand its impact. +func (v stringLengthBetweenValidator) Description(ctx context.Context) string { + return fmt.Sprintf("string length must be between %d and %d", v.Min, v.Max) +} + +// MarkdownDescription returns a markdown formatted description of the attribute validator's behavior, suitable for a practitioner to understand its impact. +func (v stringLengthBetweenValidator) MarkdownDescription(ctx context.Context) string { + return fmt.Sprintf("string length must be between `%d` and `%d`", v.Min, v.Max) +} + +// Validate runs the main validation logic of the attribute validator, reading configuration data out of `req` and updating `resp` with diagnostics. +func (v stringLengthBetweenValidator) ValidateString(ctx context.Context, req validator.StringRequest, resp *validator.StringResponse) { + // If the value is unknown or null, there is nothing to validate. + if req.ConfigValue.IsUnknown() || req.ConfigValue.IsNull() { + return + } + + strLen := len(req.ConfigValue.ValueString()) + + if strLen < v.Min || strLen > v.Max { + resp.Diagnostics.AddAttributeError( + req.AttributePath, + "Invalid String Length", + fmt.Sprintf("String length must be between %d and %d, got: %d.", v.Min, v.Max, strLen), + ) + + return + } +} + +// Validate runs the main validation logic of the parameter validator, reading configuration data out of `req` and updating `resp` with diagnostics. +func (v stringLengthBetweenValidator) ValidateParameterString(ctx context.Context, req validator.StringParameterValidatorRequest, resp *validator.StringParameterValidatorResponse) { + // If the value is unknown or null, there is nothing to validate. + if req.Value.IsUnknown() || req.Value.IsNull() { + return + } + + strLen := len(req.Value.ValueString()) + + if strLen < v.Min || strLen > v.Max { + resp.Error = function.NewArgumentFuncError( + req.ArgumentPosition, + fmt.Sprintf("Invalid String Length: String length must be between %d and %d, got: %d.", v.Min, v.Max, strLen), + ) + + return + } +} +``` + +## Value Validation + +Validation of custom value types can be used for both attribute values and provider-defined function parameter values. This can be useful if you have consistent validation rules for a specific value type across multiple attributes or function parameters. + +When you implement validation on a custom value type associated with a schema attribute, you do not need to declare the same validation on the attribute, but you can supply additional validations in that manner. For example: + +```go +// Typically within the schema.Schema returned by Schema() for a provider, +// resource, or data source. +schema.StringAttribute{ + // ... other Attribute configuration ... + + // This is an example type with a corresponding custom value type + // which implements its own validation + CustomType: computeInstanceIdentifierType, + + // This is optional, example validation that is checked in addition + // to any validation performed by the custom value type + Validators: []validator.String{ + stringvalidator.LengthBetween(10, 256), + }, +} +``` + +### Defining Value Validation + +To support validation for a custom value type, you must implement [`xattr.ValidateableAttribute` interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/attr/xattr#ValidateableAttribute) for attribute validation, or [`function.ValidateableParameter` interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/function#ValidateableParameter) for provider-defined function parameter validation. + +Both interfaces can be implemented if the same custom value type is used for both attributes and function parameters, for example: + +```go +// Ensure the implementation satisfies the expected interfaces +var ( + _ basetypes.StringValuable = computeInstanceIdentifierValue{} + _ xattr.ValidateableAttribute = computeInstanceIdentifierValue{} + _ function.ValidateableParameter = computeInstanceIdentifierValue{} +) + +// Other methods to implement the attr.Value interface are omitted for brevity +type computeInstanceIdentifierValue struct { + basetypes.StringValue +} + +// Implementation of the xattr.ValidateableAttribute interface +func (v computeInstanceIdentifierValue) ValidateAttribute(ctx context.Context, req xattr.ValidateAttributeRequest, resp *xattr.ValidateAttributeResponse) { + if v.IsNull() || v.IsUnknown() { + return + } + + if !v.isValid(v.ValueString()) { + resp.Diagnostics.AddAttributeError( + req.Path, + "Compute Instance Type Validation Error", + fmt.Sprintf("Missing `instance-` prefix, got: %s", v.ValueString()), + ) + + return + } +} + +// Implementation of the function.ValidateableParameter interface +func (v computeInstanceIdentifierValue) ValidateParameter(ctx context.Context, req function.ValidateParameterRequest, resp *function.ValidateParameterResponse) { + if v.IsNull() || v.IsUnknown() { + return + } + + if !v.isValid(v.ValueString()) { + resp.Error = function.NewArgumentFuncError( + req.Position, + fmt.Sprintf("Compute Instance Type Validation Error: Missing `instance-` prefix, got: %s", v.ValueString()), + ) + + return + } +} + +func (v computeInstanceIdentifierValue) isValid(in string) bool { + return strings.HasPrefix(in, "instance-") +} +``` + +## Type Validation + + + +`Value` validation should be used in preference to `Type` validation. Refer to [Value Validation](#value-validation) for more information. + + + +You may want to create a custom type to simplify schemas if your provider contains common attribute values with consistent validation rules. When you implement validation on a type, you do not need to declare the same validation on the attribute, but you can supply additional validations in that manner. For example: + +```go +// Typically within the schema.Schema returned by Schema() for a provider, +// resource, or data source. +schema.StringAttribute{ + // ... other Attribute configuration ... + + // This is an example type which implements its own validation + CustomType: computeInstanceIdentifierType, + + // This is optional, example validation that is checked in addition + // to any validation performed by the type + Validators: []validator.String{ + stringvalidator.LengthBetween(10, 256), + }, +} +``` + +### Defining Type Validation + + + +The [`xattr.TypeWithValidate` interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/attr/xattr#TypeWithValidate) has been deprecated. Refer to [Defining Value Validation](#defining-value-validation) for more information about using [`xattr.ValidateableAttribute` interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/attr/xattr#ValidateableAttribute), and [`function.ValidateableParameter` interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/function#ValidateableParameter) instead. + + + +To support validation within a type, you must implement the [`xattr.TypeWithValidate` interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/attr/xattr#TypeWithValidate). For example: + +```go +// Ensure type satisfies xattr.TypeWithValidate interface +var _ xattr.TypeWithValidate = computeInstanceIdentifierType{} + +// Other methods to implement the attr.Type interface are omitted for brevity +type computeInstanceIdentifierType struct {} + +func (t computeInstanceIdentifierType) Validate(ctx context.Context, tfValue tftypes.Value, path path.Path) diag.Diagnostics { + var diags diag.Diagnostics + + if !tfValue.Type().Equal(tftypes.String) { + diags.AddAttributeError( + path, + "Compute Instance Type Validation Error", + fmt.Sprintf("Expected String value, received %T with value: %v", tfValue, tfValue), + ) + return diags + } + + if !tfValue.IsKnown() || tfValue.IsNull() { + return diags + } + + var value string + err := tfValue.As(&value) + + if err != nil { + diags.AddAttributeError( + path, + "Compute Instance Type Validation Error", + fmt.Sprintf("Cannot convert value to string: %s", err), + ) + return diags + } + + if !strings.HasPrefix(value, "instance-") { + diags.AddAttributeError( + path, + "Compute Instance Type Validation Error", + fmt.Sprintf("Missing `instance-` prefix, got: %s", value), + ) + return diags + } +} +``` diff --git a/content/terraform-plugin-framework/v1.15.x/img/.gitkeep b/content/terraform-plugin-framework/v1.15.x/img/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/content/terraform-plugin-framework/v1.15.x/img/README.md b/content/terraform-plugin-framework/v1.15.x/img/README.md new file mode 100644 index 0000000000..66e6d2d633 --- /dev/null +++ b/content/terraform-plugin-framework/v1.15.x/img/README.md @@ -0,0 +1,25 @@ +# Image Generation + +The images are exported as PNG from Whimsical. + +`Export` => `Size 2x` => `Include Background`. + +| Image | Whimsical | +|------------------------------------------|---------------------------------------------------------------------------------------------| +| apply-resource-change-detail.png | https://whimsical.com/applyresourcechange-FK9oXfU3ugzRyj4YNWuHZU | +| apply-resource-change-overview.png | https://whimsical.com/applyresourcechange-FK9oXfU3ugzRyj4YNWuHZU | +| configure-provider-detail.png | https://whimsical.com/configureprovider-5kUd5iMxCRQ1hVs4QiE9B8 | +| configure-provider-overview.png | https://whimsical.com/configureprovider-5kUd5iMxCRQ1hVs4QiE9B8 | +| get-provider-schema-detail.png | https://whimsical.com/getproviderschema-5dwaqjyiFoUv9DPTk9RYy1 | +| get-provider-schema-overview.png | https://whimsical.com/getproviderschema-5dwaqjyiFoUv9DPTk9RYy1 | +| plan-resource-change-detail.png | https://whimsical.com/planresourcechange-S5G9LdER9CuqRCjBmqDG5m | +| plan-resource-change-overview.png | https://whimsical.com/planresourcechange-S5G9LdER9CuqRCjBmqDG5m | +| read-data-source-detail.png | https://whimsical.com/read-resource-datasource-UGcyGChMxm8w12dh5emiD8 | +| read-overview.png | https://whimsical.com/read-resource-datasource-UGcyGChMxm8w12dh5emiD8 | +| read-resource-detail.png | https://whimsical.com/read-resource-datasource-UGcyGChMxm8w12dh5emiD8 | +| validate-config-overview.png | https://whimsical.com/validate-provider-resource-dataresource-config-E51CLC4wycgKoZfsTjZuVA | +| validate-data-resource-config-detail.png | https://whimsical.com/validate-provider-resource-dataresource-config-E51CLC4wycgKoZfsTjZuVA | +| validate-provider-config-detail.png | https://whimsical.com/validate-provider-resource-dataresource-config-E51CLC4wycgKoZfsTjZuVA | +| validate-resource-config-detail.png | https://whimsical.com/validate-provider-resource-dataresource-config-E51CLC4wycgKoZfsTjZuVA | + + diff --git a/content/terraform-plugin-framework/v1.15.x/img/apply-resource-change-detail.png b/content/terraform-plugin-framework/v1.15.x/img/apply-resource-change-detail.png new file mode 100644 index 0000000000..0aec591001 Binary files /dev/null and b/content/terraform-plugin-framework/v1.15.x/img/apply-resource-change-detail.png differ diff --git a/content/terraform-plugin-framework/v1.15.x/img/apply-resource-change-overview.png b/content/terraform-plugin-framework/v1.15.x/img/apply-resource-change-overview.png new file mode 100644 index 0000000000..7b22b5d260 Binary files /dev/null and b/content/terraform-plugin-framework/v1.15.x/img/apply-resource-change-overview.png differ diff --git a/content/terraform-plugin-framework/v1.15.x/img/configure-provider-detail.png b/content/terraform-plugin-framework/v1.15.x/img/configure-provider-detail.png new file mode 100644 index 0000000000..d17fa9aeaa Binary files /dev/null and b/content/terraform-plugin-framework/v1.15.x/img/configure-provider-detail.png differ diff --git a/content/terraform-plugin-framework/v1.15.x/img/configure-provider-overview.png b/content/terraform-plugin-framework/v1.15.x/img/configure-provider-overview.png new file mode 100644 index 0000000000..653b3db45d Binary files /dev/null and b/content/terraform-plugin-framework/v1.15.x/img/configure-provider-overview.png differ diff --git a/content/terraform-plugin-framework/v1.15.x/img/get-provider-schema-detail.png b/content/terraform-plugin-framework/v1.15.x/img/get-provider-schema-detail.png new file mode 100644 index 0000000000..1958583ea5 Binary files /dev/null and b/content/terraform-plugin-framework/v1.15.x/img/get-provider-schema-detail.png differ diff --git a/content/terraform-plugin-framework/v1.15.x/img/get-provider-schema-overview.png b/content/terraform-plugin-framework/v1.15.x/img/get-provider-schema-overview.png new file mode 100644 index 0000000000..d87f34d449 Binary files /dev/null and b/content/terraform-plugin-framework/v1.15.x/img/get-provider-schema-overview.png differ diff --git a/content/terraform-plugin-framework/v1.15.x/img/plan-resource-change-detail.png b/content/terraform-plugin-framework/v1.15.x/img/plan-resource-change-detail.png new file mode 100644 index 0000000000..7402d85d2a Binary files /dev/null and b/content/terraform-plugin-framework/v1.15.x/img/plan-resource-change-detail.png differ diff --git a/content/terraform-plugin-framework/v1.15.x/img/plan-resource-change-overview.png b/content/terraform-plugin-framework/v1.15.x/img/plan-resource-change-overview.png new file mode 100644 index 0000000000..cd21fc053e Binary files /dev/null and b/content/terraform-plugin-framework/v1.15.x/img/plan-resource-change-overview.png differ diff --git a/content/terraform-plugin-framework/v1.15.x/img/read-data-source-detail.png b/content/terraform-plugin-framework/v1.15.x/img/read-data-source-detail.png new file mode 100644 index 0000000000..f74b9bda65 Binary files /dev/null and b/content/terraform-plugin-framework/v1.15.x/img/read-data-source-detail.png differ diff --git a/content/terraform-plugin-framework/v1.15.x/img/read-overview.png b/content/terraform-plugin-framework/v1.15.x/img/read-overview.png new file mode 100644 index 0000000000..edaa30bf7f Binary files /dev/null and b/content/terraform-plugin-framework/v1.15.x/img/read-overview.png differ diff --git a/content/terraform-plugin-framework/v1.15.x/img/read-resource-detail.png b/content/terraform-plugin-framework/v1.15.x/img/read-resource-detail.png new file mode 100644 index 0000000000..a43a0cf613 Binary files /dev/null and b/content/terraform-plugin-framework/v1.15.x/img/read-resource-detail.png differ diff --git a/content/terraform-plugin-framework/v1.15.x/img/validate-config-overview.png b/content/terraform-plugin-framework/v1.15.x/img/validate-config-overview.png new file mode 100644 index 0000000000..db1e368569 Binary files /dev/null and b/content/terraform-plugin-framework/v1.15.x/img/validate-config-overview.png differ diff --git a/content/terraform-plugin-framework/v1.15.x/img/validate-data-resource-config-detail.png b/content/terraform-plugin-framework/v1.15.x/img/validate-data-resource-config-detail.png new file mode 100644 index 0000000000..78191ad32a Binary files /dev/null and b/content/terraform-plugin-framework/v1.15.x/img/validate-data-resource-config-detail.png differ diff --git a/content/terraform-plugin-framework/v1.15.x/img/validate-provider-config-detail.png b/content/terraform-plugin-framework/v1.15.x/img/validate-provider-config-detail.png new file mode 100644 index 0000000000..7c02dc2a9a Binary files /dev/null and b/content/terraform-plugin-framework/v1.15.x/img/validate-provider-config-detail.png differ diff --git a/content/terraform-plugin-framework/v1.15.x/img/validate-resource-config-detail.png b/content/terraform-plugin-framework/v1.15.x/img/validate-resource-config-detail.png new file mode 100644 index 0000000000..b6217cecd7 Binary files /dev/null and b/content/terraform-plugin-framework/v1.15.x/img/validate-resource-config-detail.png differ diff --git a/content/terraform-plugin-mux/v0.19.x/data/plugin-mux-nav-data.json b/content/terraform-plugin-mux/v0.19.x/data/plugin-mux-nav-data.json new file mode 100644 index 0000000000..ce4b003fec --- /dev/null +++ b/content/terraform-plugin-mux/v0.19.x/data/plugin-mux-nav-data.json @@ -0,0 +1,25 @@ +[ + { + "heading": "Combining and Translating" + }, + { + "title": "Overview", + "path": "" + }, + { + "title": "Combining Protocol v5 Providers", + "path": "combining-protocol-version-5-providers" + }, + { + "title": "Combining Protocol v6 Providers", + "path": "combining-protocol-version-6-providers" + }, + { + "title": "Translating Protocol v5 to v6", + "path": "translating-protocol-version-5-to-6" + }, + { + "title": "Translating Protocol v6 to v5", + "path": "translating-protocol-version-6-to-5" + } +] diff --git a/content/terraform-plugin-mux/v0.19.x/docs/plugin/mux/combining-protocol-version-5-providers.mdx b/content/terraform-plugin-mux/v0.19.x/docs/plugin/mux/combining-protocol-version-5-providers.mdx new file mode 100644 index 0000000000..28397885b7 --- /dev/null +++ b/content/terraform-plugin-mux/v0.19.x/docs/plugin/mux/combining-protocol-version-5-providers.mdx @@ -0,0 +1,139 @@ +--- +page_title: 'Plugin Development - Combining Protocol Version 5 Providers' +description: >- + Use the tf5muxserver package in terraform-plugin-mux to combine protocol version 5 providers. +--- + +# Combining Protocol Version 5 Providers + +The [`tf5muxserver`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-mux/tf5muxserver) package enables combining any number of [protocol version 5 provider servers](/terraform/plugin/how-terraform-works#protocol-version-5) into a single server. + +Use this package to: + +* Support multiple development teams across separate codebases. +* Support multiple provider SDK implementations. For example, you could downgrade an existing [terraform-plugin-framework](/terraform/plugin/framework) provider to protocol version 5 and then combine it with one or more providers built with [terraform-plugin-sdk/v2](/terraform/plugin/sdkv2). + +## Compatibility + +Protocol version 5 provider servers are compatible with Terraform CLI 0.12 and later. + +The following provider server implementations are supported: + +* [terraform-plugin-sdk/v2](/terraform/plugin/sdkv2): A higher-level SDK that makes Terraform provider development easier by abstracting implementation details. +* [terraform-plugin-go tf5server](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-go/tfprotov5/tf5server): A lower-level SDK to develop Terraform providers for more advanced use cases. +* [tf6to5server](/terraform/plugin/mux/translating-protocol-version-6-to-5): A package to translate protocol version 6 providers into protocol version 5. + +## Requirements + +To be combined, provider servers must meet the following requirements: + +* All provider schemas (except block `MinItems`/`MaxItems`) must match. +* All provider meta schemas must match. +* Only one provider may implement each resource and data source. + +If publishing to the [Terraform Registry](/terraform/registry), set `metadata.protocol_versions` to `["5.0"]` in the [Terraform Registry manifest file](/terraform/registry/providers/publishing#terraform-registry-manifest-file). + +## Code Implementation + +Use the [`tf5muxserver.NewMuxServer()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-mux/tf5muxserver#NewMuxServer) function to create a combined provider server. Most providers implement this method in the provider `main()` function of the root directory `main.go` file or within a shared internal package to enable [testing for the combined provider](#testing-implementation). You can use [`tf5server.WithManagedDebug()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-go/tfprotov5/tf5server#WithManagedDebug) to enable debugger support. + +The following example implements `tf5muxserver.NewMuxServer()` in a `main()` function. + +```go +package main + +import ( + "context" + "flag" + "log" + + "github.com/example/terraform-provider-example/internal/provider1" + "github.com/example/terraform-provider-example/internal/provider2" + "github.com/example/terraform-provider-example/internal/framework_provider" + "github.com/hashicorp/terraform-plugin-framework/providerserver" + "github.com/hashicorp/terraform-plugin-go/tfprotov5" + "github.com/hashicorp/terraform-plugin-go/tfprotov5/tf5server" + "github.com/hashicorp/terraform-plugin-mux/tf5muxserver" +) + +var ( + // Version can be updated by goreleaser on release + version string = "dev" +) + +func main() { + debugFlag := flag.Bool("debug", false, "Start provider in debug mode.") + flag.Parse() + + ctx := context.Background() + providers := []func() tfprotov5.ProviderServer{ + // Example terraform-plugin-sdk/v2 providers + provider1.New(version)().GRPCProvider, + provider2.New(version)().GRPCProvider, + + // Example terraform-plugin-framework provider + providerserver.NewProtocol5( + framework_provider.New(version)(), + ), + } + + muxServer, err := tf5muxserver.NewMuxServer(ctx, providers...) + + if err != nil { + log.Fatal(err) + } + + var serveOpts []tf5server.ServeOpt + + if *debugFlag { + serveOpts = append(serveOpts, tf5server.WithManagedDebug()) + } + + err = tf5server.Serve( + "registry.terraform.io/example/example", + muxServer.ProviderServer, + serveOpts..., + ) + + if err != nil { + log.Fatal(err) + } +} +``` + +## Testing Implementation + +You can test individual providers using the same [acceptance tests](/terraform/plugin/testing/acceptance-tests) as before. Set the `ProtoV5ProviderFactories` field of `TestCase` with an instance of the mux server, instead of declaring the provider with other `TestCase` fields such as `ProviderFactories`. + +The following example uses the acceptance testing framework to create a test for two providers. + +```go +resource.Test(t, resource.TestCase{ + // ... other TestCase fields ... + ProtoV5ProviderFactories: map[string]func() (tfprotov5.ProviderServer, error) { + "example": func() (tfprotov5.ProviderServer, error) { + ctx := context.Background() + providers := []func() tfprotov5.ProviderServer{ + // Example terraform-plugin-sdk/v2 providers + provider1.New(version)().GRPCProvider, + provider2.New(version)().GRPCProvider, + + // Example terraform-plugin-framework provider + providerserver.NewProtocol5( + framework_provider.New(version)(), + ), + } + + muxServer, err := tf5muxserver.NewMuxServer(ctx, providers...) + + if err != nil { + return nil, err + } + + return muxServer.ProviderServer(), nil + }, + }, +}) +``` + +Refer to the [acceptance tests](/terraform/plugin/testing/acceptance-tests) documentation for more details. diff --git a/content/terraform-plugin-mux/v0.19.x/docs/plugin/mux/combining-protocol-version-6-providers.mdx b/content/terraform-plugin-mux/v0.19.x/docs/plugin/mux/combining-protocol-version-6-providers.mdx new file mode 100644 index 0000000000..9b5dcc7ddd --- /dev/null +++ b/content/terraform-plugin-mux/v0.19.x/docs/plugin/mux/combining-protocol-version-6-providers.mdx @@ -0,0 +1,128 @@ +--- +page_title: 'Plugin Development - Combining Protocol Version 6 Providers' +description: >- + Use the tf6muxserver package in terraform-plugin-mux to combine protocol version 6 providers. +--- + +# Combining Protocol Version 6 Providers + +The [`tf6muxserver`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-mux/tf6muxserver) package enables combining any number of [protocol version 6 provider servers](/terraform/plugin/how-terraform-works#protocol-version-6) into a single server. + +Use this package to: + +* Support multiple development teams across separate codebases. +* Support multiple provider SDK implementations. For example, you could upgrade an existing [terraform-plugin-sdk/v2](/terraform/plugin/sdkv2) provider to protocol version 6 and then combine it with one or more providers built with [terraform-plugin-framework](/terraform/plugin/framework). + +## Compatibility + +Protocol version 6 provider servers are compatible with Terraform CLI 1.0 and later. + +The following provider server implementations are supported: + +* [terraform-plugin-framework](/terraform/plugin/framework): A higher-level SDK that makes Terraform provider development easier by abstracting implementation details. +* [terraform-plugin-go tf6server](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-go/tfprotov6/tf6server): A lower-level SDK to develop Terraform providers for more advanced use cases. +* [tf5to6server](/terraform/plugin/mux/translating-protocol-version-5-to-6): A package to translate protocol version 5 providers into protocol version 6. + +## Requirements + +To be combined, provider servers must meet the following requirements: + +* All provider schemas must match. +* All provider meta schemas must match. +* Only one provider may implement each resource and data source. + +If publishing to the [Terraform Registry](/terraform/registry), set `metadata.protocol_versions` to `["6.0"]` in the [Terraform Registry manifest file](/terraform/registry/providers/publishing#terraform-registry-manifest-file). + +## Code Implementation + +Use the [`tf6muxserver.NewMuxServer()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-mux/tf6muxserver#NewMuxServer) function to create a combined provider server. Most providers implement this in the provider `main()` function of the root directory `main.go` file or within a shared internal package to enable [testing for the combined provider](#testing-implementation). You can use [`tf6server.WithManagedDebug()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-go/tfprotov6/tf6server#WithManagedDebug) to enable debugger support. + +The following example implements `tf6muxserver.NewMuxServer()` in a `main()` function. + +```go +package main + +import ( + "context" + "flag" + "log" + + "github.com/example/terraform-provider-example/internal/provider1" + "github.com/example/terraform-provider-example/internal/provider2" + "github.com/hashicorp/terraform-plugin-framework/tfsdk" + "github.com/hashicorp/terraform-plugin-go/tfprotov6" + "github.com/hashicorp/terraform-plugin-go/tfprotov6/tf6server" + "github.com/hashicorp/terraform-plugin-mux/tf6muxserver" +) + +var ( + // Version can be updated by goreleaser on release + version string = "dev" +) + +func main() { + debugFlag := flag.Bool("debug", false, "Start provider in debug mode.") + flag.Parse() + + ctx := context.Background() + providers := []func() tfprotov6.ProviderServer{ + // Example terraform-plugin-framework providers + providerserver.NewProtocol6(provider1.New("test")()) + providerserver.NewProtocol6(provider2.New("test")()) + } + + muxServer, err := tf6muxserver.NewMuxServer(ctx, providers...) + + if err != nil { + log.Fatal(err) + } + + var serveOpts []tf6server.ServeOpt + + if *debugFlag { + serveOpts = append(serveOpts, tf6server.WithManagedDebug()) + } + + err = tf6server.Serve( + "registry.terraform.io/example/example", + muxServer.ProviderServer, + serveOpts..., + ) + + if err != nil { + log.Fatal(err) + } +} +``` + +## Testing Implementation + +You can test individual providers using the same [acceptance tests](/terraform/plugin/testing/acceptance-tests) as before. Set the `ProtoV6ProviderFactories` field of `TestCase` with an instance of the mux server, instead of declaring the provider with other `TestCase` fields such as `ProviderFactories`. + +The following example uses the acceptance testing framework to create a test for two providers. + +```go +resource.Test(t, resource.TestCase{ + // ... other TestCase fields ... + ProtoV6ProviderFactories: map[string]func() (tfprotov6.ProviderServer, error) { + "example": func() (tfprotov6.ProviderServer, error) { + ctx := context.Background() + providers := []func() tfprotov6.ProviderServer{ + // Example terraform-plugin-framework providers + providerserver.NewProtocol6(provider1.New("test")()) + providerserver.NewProtocol6(provider2.New("test")()) + } + + muxServer, err := tf6muxserver.NewMuxServer(ctx, providers...) + + if err != nil { + return nil, err + } + + return muxServer.ProviderServer(), nil + }, + }, +}) +``` + +Refer to the [acceptance tests](/terraform/plugin/testing/acceptance-tests) documentation for more details. diff --git a/content/terraform-plugin-mux/v0.19.x/docs/plugin/mux/index.mdx b/content/terraform-plugin-mux/v0.19.x/docs/plugin/mux/index.mdx new file mode 100644 index 0000000000..4baffa45cf --- /dev/null +++ b/content/terraform-plugin-mux/v0.19.x/docs/plugin/mux/index.mdx @@ -0,0 +1,34 @@ +--- +page_title: "Plugin Development - Combining and Translating Providers: Overview" +description: >- + Use terraform-plugin-mux to combine and translate providers. It minimizes code changes by wrapping existing provider servers. +--- + +# Combining and Translating Providers + +The [terraform-plugin-mux](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-mux) Go module is a collection of Go packages for combining (multiplexing) and translating provider servers. It helps minimize provider code changes by wrapping existing provider servers. This functionality is based on the [Terraform Plugin Protocol](/terraform/plugin/how-terraform-works#terraform-plugin-protocol) and [`terraform-plugin-go`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-go) provider servers. + +## Use Cases + +The `terraform-plugin-mux` Go module enables flexibility when you develop and maintain Terraform providers. It is especially useful when you need to upgrade an existing provider to use the plugin framework SDK or the latest version of the plugin protocol. We recommend using `terraform-plugin-mux` for the following use cases: + +- Combine providers to reduce maintenance burden while still supporting varying Terraform requirements or multiple provider SDK implementations. +- Migrate resources and data sources from [terraform-plugin-sdk/v2](/terraform/plugin/sdkv2) to [terraform-plugin-framework](/terraform/plugin/framework) over time. +- Develop with [terraform-plugin-sdk/v2](/terraform/plugin/sdkv2), but require Terraform CLI 1.0 or later. +- Develop with [terraform-plugin-framework](/terraform/plugin/framework), but support Terraform CLI 0.12 or later. + +## Combine Protocol 6 Providers + +Use the [tf6muxserver](/terraform/plugin/mux/combining-protocol-version-6-providers) package to combine any number of [protocol version 6 provider servers](/terraform/plugin/how-terraform-works#protocol-version-6) into a single server. + +## Combine Protocol 5 Providers + +Use the [tf5muxserver](/terraform/plugin/mux/combining-protocol-version-5-providers) package to combine any number of [protocol version 5 provider servers](/terraform/plugin/how-terraform-works#protocol-version-5) into a single server. + +## Translate Protocol 5 Providers to Protocol 6 + +Use the [tf5to6server](/terraform/plugin/mux/translating-protocol-version-5-to-6) package to translate a [protocol version 5 provider server](/terraform/plugin/how-terraform-works#protocol-version-5) into a [protocol version 6 provider server](/terraform/plugin/how-terraform-works#protocol-version-6). + +## Translate Protocol 6 Providers to Protocol 5 + +Use the [tf6to5server](/terraform/plugin/mux/translating-protocol-version-6-to-5) package to translate a [protocol version 6 provider server](/terraform/plugin/how-terraform-works#protocol-version-6) into a [protocol version 5 provider server](/terraform/plugin/how-terraform-works#protocol-version-5). diff --git a/content/terraform-plugin-mux/v0.19.x/docs/plugin/mux/translating-protocol-version-5-to-6.mdx b/content/terraform-plugin-mux/v0.19.x/docs/plugin/mux/translating-protocol-version-5-to-6.mdx new file mode 100644 index 0000000000..f61761d430 --- /dev/null +++ b/content/terraform-plugin-mux/v0.19.x/docs/plugin/mux/translating-protocol-version-5-to-6.mdx @@ -0,0 +1,92 @@ +--- +page_title: 'Plugin Development - Translating Protocol Version 5 to 6' +description: >- + Use the tf5to6server package in terraform-plugin-mux to translate protocol version 5 providers to protocol version 6. +--- + +# Translating Protocol Version 5 to 6 + +The [`tf5to6server`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-mux/tf5to6server) package enables translating a [protocol version 5](/terraform/plugin/how-terraform-works#protocol-version-5) provider server into a [protocol version 6](/terraform/plugin/how-terraform-works#protocol-version-6) provider server. + +Use this package to: + +* Migrate individual resources and data sources from [terraform-plugin-sdk/v2](/terraform/plugin/sdkv2) to [terraform-plugin-framework](/terraform/plugin/framework) over time, while using protocol version 6 features in terraform-plugin-framework. +* Develop with [terraform-plugin-sdk/v2](/terraform/plugin/sdkv2), but require Terraform CLI 1.0 or later. + +## Compatibility + +Protocol version 6 provider servers are compatible with Terraform CLI 1.0 and later. Terraform CLI 1.1.5 and later is required to upgrade [terraform-plugin-sdk/v2](/terraform/plugin/sdkv2). + +The following provider server implementations are supported: + +* [terraform-plugin-sdk/v2](/terraform/plugin/sdkv2): A higher-level SDK that makes Terraform provider development easier by abstracting implementation details. +* [terraform-plugin-go tf5server](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-go/tfprotov5/tf5server): A lower-level SDK to develop Terraform providers for more advanced use cases. + +## Requirements + +Upgrading provider servers from protocol version 5 to protocol version 6 has no provider code requirements. + +If publishing to the [Terraform Registry](/terraform/registry), set `metadata.protocol_versions` to `["6.0"]` in the [Terraform Registry manifest file](/terraform/registry/providers/publishing#terraform-registry-manifest-file). + +## Code Implementation + +Use the [`tf5to6server.UpgradeServer()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-mux/tf5to6server#UpgradeServer) function to wrap a provider server. For most providers, this is either in the provider `main()` function of the root directory `main.go` file or where [`tf6muxserver`](/terraform/plugin/mux/combining-protocol-version-6-providers) is implemented in the codebase. + +The following example upgrades a terraform-plugin-sdk/v2 provider. + +```go +upgradedSdkProvider, err := tf5to6server.UpgradeServer( + context.Background(), + sdkprovider.New(version)().GRPCProvider, +) +``` + +The following example uses [`tf6server`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-go/tfprotov6/tf6server) to serve the upgraded provider directly. + +```go +err = tf6server.Serve( + "registry.terraform.io/example/example", + func() tfprotov6.ProviderServer { + return upgradedSdkProvider + }, +) +``` + +The following example uses [`tf6muxserver`](/terraform/plugin/mux/combining-protocol-version-6-providers) to serve the upgraded provider while it is combined with others. + +```go +providers := []func() tfprotov6.ProviderServer{ + func() tfprotov6.ProviderServer { + return upgradedSdkProvider + }, + + // Example terraform-plugin-framework provider + providerserver.NewProtocol6(frameworkprovider.New(version)()) +} + +muxServer, err := tf6muxserver.NewMuxServer(ctx, providers...) +``` + +Refer to the [`tf6muxserver`](/terraform/plugin/mux/combining-protocol-version-6-providers) documentation for more details about how to serve the combined provider. + +## Testing Implementation + +You can test the original provider using the same [acceptance tests](/terraform/plugin/testing/acceptance-tests) as before. Set the `ProtoV6ProviderFactories` field of `TestCase` with an instance of the upgraded server, instead of declaring the provider with other `TestCase` fields such as `ProviderFactories`. + +The following example creates a test for a combined provider. + +```go +resource.Test(t, resource.TestCase{ + // ... other TestCase fields ... + ProtoV6ProviderFactories: map[string]func() (tfprotov6.ProviderServer, error) { + "example": func() (tfprotov6.ProviderServer, error) { + return tf5to6server.UpgradeServer( + context.Background(), + sdkprovider.New("test")().GRPCProvider, + ) + }, + }, +}) +``` + +Refer to the [acceptance tests](/terraform/plugin/testing/acceptance-tests) documentation for more details. diff --git a/content/terraform-plugin-mux/v0.19.x/docs/plugin/mux/translating-protocol-version-6-to-5.mdx b/content/terraform-plugin-mux/v0.19.x/docs/plugin/mux/translating-protocol-version-6-to-5.mdx new file mode 100644 index 0000000000..7fd0188dae --- /dev/null +++ b/content/terraform-plugin-mux/v0.19.x/docs/plugin/mux/translating-protocol-version-6-to-5.mdx @@ -0,0 +1,92 @@ +--- +page_title: 'Plugin Development - Translating Protocol Version 6 to 5' +description: >- + Use the tf6to5server package in terraform-plugin-mux to translate protocol version 6 providers to protocol version 5. +--- + +# Translating Protocol Version 6 to 5 + +The [`tf6to5server`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-mux/tf6to5server) package enables translating a [protocol version 6](/terraform/plugin/how-terraform-works#protocol-version-6) provider server into a [protocol version 5](/terraform/plugin/how-terraform-works#protocol-version-5) provider server. + +Use this package to: + +* Migrate individual resources and data sources from [terraform-plugin-sdk/v2](/terraform/plugin/sdkv2) to [terraform-plugin-framework](/terraform/plugin/framework) over time, while maintaining Terraform CLI 0.12 and later compatibility. +* Develop with [terraform-plugin-framework](/terraform/plugin/framework), but support Terraform CLI 0.12 and later. + +## Compatibility + +Protocol version 5 provider servers are compatible with Terraform CLI 0.12 and later. + +The following provider server implementations are supported: + +* [terraform-plugin-framework](/terraform/plugin/framework): A higher-level SDK that makes Terraform provider development easier by abstracting implementation details. +* [terraform-plugin-go tf6server](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-go/tfprotov6/tf6server): A lower-level SDK to develop Terraform providers for more advanced use cases. + +## Requirements + +Downgrading provider servers from protocol version 6 to protocol version 5 will return an error if there are protocol version 6 features that are in use that are not available in protocol version 5. Refer to the [protocol version 6](/terraform/plugin/how-terraform-works#protocol-version-6) documentation for more information. + +If publishing to the [Terraform Registry](/terraform/registry), set `metadata.protocol_versions` to `["5.0"]` in the [Terraform Registry manifest file](/terraform/registry/providers/publishing#terraform-registry-manifest-file). + +## Code Implementation + +Use the [`tf6to5server.DowngradeServer()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-mux/tf6to5server#DowngradeServer) function to wrap a provider server. For most providers, this is in the provider `main()` function of the root directory `main.go` file or where [`tf5muxserver`](/terraform/plugin/mux/combining-protocol-version-5-providers) is implemented in the codebase, if applicable. + +The following example downgrades a `terraform-plugin-framework` provider. + +```go +downgradedFrameworkProvider, err := tf6to5server.DowngradeServer( + context.Background(), + providerserver.NewProtocol6(frameworkprovider.New(version)()), +) +``` + +The following example uses [`tf5server`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-go/tfprotov5/tf5server) to serve the downgraded provider directly. + +```go +err = tf5server.Serve( + "registry.terraform.io/example/example", + func() tfprotov5.ProviderServer { + return downgradedFrameworkProvider + }, +) +``` + +The following example uses [`tf5muxserver`](/terraform/plugin/mux/combining-protocol-version-5-providers) to serve the downgraded provider while it is combined with other providers. + +```go +providers := []func() tfprotov5.ProviderServer{ + func() tfprotov5.ProviderServer { + return downgradedFrameworkProvider + }, + + // Example terraform-plugin-sdk/v2 provider + sdkprovider.New(version)().GRPCProvider, +} + +muxServer, err := tf5muxserver.NewMuxServer(ctx, providers...) +``` + +Refer to the [`tf5muxserver`](/terraform/plugin/mux/combining-protocol-version-5-providers) documentation for more details about how to serve the combined provider. + +## Testing Implementation + +You can test the original provider using the same [acceptance tests](/terraform/plugin/testing/acceptance-tests) as before. Set the `ProtoV5ProviderFactories` field of `TestCase` with an instance of the upgraded server, instead of declaring the provider with other `TestCase` fields such as `ProviderFactories`. + +The following example creates a test case for a downgraded provider. + +```go +resource.Test(t, resource.TestCase{ + // ... other TestCase fields ... + ProtoV5ProviderFactories: map[string]func() (tfprotov5.ProviderServer, error) { + "example": func() (tfprotov5.ProviderServer, error) { + return tf6to5server.DowngradeServer( + context.Background(), + providerserver.NewProtocol6(frameworkprovider.New("test")()), + ) + }, + }, +}) +``` + +Refer to the [acceptance tests](/terraform/plugin/sdkv2/testing/acceptance-tests) documentation for more details. diff --git a/content/terraform-plugin-mux/v0.19.x/img/.gitkeep b/content/terraform-plugin-mux/v0.19.x/img/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/content/terraform-plugin-testing/v1.13.x/data/plugin-testing-nav-data.json b/content/terraform-plugin-testing/v1.13.x/data/plugin-testing-nav-data.json new file mode 100644 index 0000000000..0cb8e53de8 --- /dev/null +++ b/content/terraform-plugin-testing/v1.13.x/data/plugin-testing-nav-data.json @@ -0,0 +1,192 @@ +[ + { + "heading": "Testing" + }, + { + "title": "Overview", + "path": "" + }, + { + "title": "Acceptance Testing", + "routes": [ + { + "title": "Overview", + "path": "acceptance-tests" + }, + { + "title": "Test Cases", + "path": "acceptance-tests/testcase" + }, + { + "title": "Test Steps", + "routes": [{ + "title": "Overview", + "path": "acceptance-tests/teststep" + }, { + "title": "Import mode", + "path": "acceptance-tests/import-mode" + }] + }, + { + "title": "Terraform Version Checks", + "path": "acceptance-tests/tfversion-checks" + }, + { + "title": "Plan Checks", + "routes": [ + { + "title": "Overview", + "path": "acceptance-tests/plan-checks" + }, + { + "title": "Resource Plan Checks", + "path": "acceptance-tests/plan-checks/resource" + }, + { + "title": "Output Plan Checks", + "path": "acceptance-tests/plan-checks/output" + }, + { + "title": "Custom Plan Checks", + "path": "acceptance-tests/plan-checks/custom" + } + ] + }, + { + "title": "State Checks", + "routes": [ + { + "title": "Overview", + "path": "acceptance-tests/state-checks" + }, + { + "title": "Resource State Checks", + "path": "acceptance-tests/state-checks/resource" + }, + { + "title": "Output State Checks", + "path": "acceptance-tests/state-checks/output" + }, + { + "title": "Custom State Checks", + "path": "acceptance-tests/state-checks/custom" + } + ] + }, + { + "title": "Known Value Checks", + "routes": [ + { + "title": "Overview", + "path": "acceptance-tests/known-value-checks" + }, + { + "title": "Bool", + "path": "acceptance-tests/known-value-checks/bool" + }, + { + "title": "Custom", + "path": "acceptance-tests/known-value-checks/custom" + }, + { + "title": "Float32", + "path": "acceptance-tests/known-value-checks/float32" + }, + { + "title": "Float64", + "path": "acceptance-tests/known-value-checks/float64" + }, + { + "title": "Int32", + "path": "acceptance-tests/known-value-checks/int32" + }, + { + "title": "Int64", + "path": "acceptance-tests/known-value-checks/int64" + }, + { + "title": "List", + "path": "acceptance-tests/known-value-checks/list" + }, + { + "title": "Map", + "path": "acceptance-tests/known-value-checks/map" + }, + { + "title": "NotNull", + "path": "acceptance-tests/known-value-checks/not-null" + }, + { + "title": "Null", + "path": "acceptance-tests/known-value-checks/null" + }, + { + "title": "Number", + "path": "acceptance-tests/known-value-checks/number" + }, + { + "title": "Object", + "path": "acceptance-tests/known-value-checks/object" + }, + { + "title": "Set", + "path": "acceptance-tests/known-value-checks/set" + }, + { + "title": "String", + "path": "acceptance-tests/known-value-checks/string" + }, + { + "title": "Tuple", + "path": "acceptance-tests/known-value-checks/tuple" + } + ] + }, + { + "title": "Value Comparers", + "routes": [ + { + "title": "Overview", + "path": "acceptance-tests/value-comparers" + } + ] + }, + { + "title": "Sweepers", + "path": "acceptance-tests/sweepers" + }, + { + "title": "Terraform JSON Paths", + "path": "acceptance-tests/tfjson-paths" + }, + { + "title": "Terraform Configuration", + "path": "acceptance-tests/configuration" + }, + { + "title": "Ephemeral Resources", + "path": "acceptance-tests/ephemeral-resources" + }, + { + "title": "Environment Variables", + "path": "acceptance-tests/environment-variables" + }, + { + "title": "Continuous Integration", + "path": "acceptance-tests/continuous-integration" + } + ] + }, + { + "title": "Testing Patterns", + "path": "testing-patterns" + }, + { + "title": "Unit Testing", + "path": "unit-testing" + }, + { + "title": "Migrating from SDK", + "path": "migrating" + } +] diff --git a/content/terraform-plugin-testing/v1.13.x/docs/plugin/testing/acceptance-tests/configuration.mdx b/content/terraform-plugin-testing/v1.13.x/docs/plugin/testing/acceptance-tests/configuration.mdx new file mode 100644 index 0000000000..f18c2a9401 --- /dev/null +++ b/content/terraform-plugin-testing/v1.13.x/docs/plugin/testing/acceptance-tests/configuration.mdx @@ -0,0 +1,277 @@ +--- +page_title: 'Plugin Development - Acceptance Testing: Terraform Configuration' +description: >- + Terraform Configuration specifies the configuration to be used during an acceptance test at the TestStep level. Terraform variables define the values to be used + in conjunction with Terraform configuration. +--- + +# Terraform Configuration + +The configuration used during the execution of an acceptance test can be specified at the [TestStep](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/helper/resource#TestStep) level by populating one of the following mutually exclusive fields: + +* [TestStep.Config](#teststep-config) +* [TestStep.ConfigDirectory](#teststep-configdirectory) +* [TestStep.ConfigFile](#teststep-configfile) + +Terraform configuration can be used in conjunction with Terraform variables defined via [TestStep.ConfigVariables](/terraform/plugin/testing/acceptance-tests/configuration#teststep-configvariables). + +## TestStep Config + +The `TestStep.Config` field accepts a string containing valid Terraform configuration. + +In the following example, the `Config` field specifies a resource which is used in combination with `ExternalProviders` to specify the version and source for the provider: + +```go +func TestAccResourcePassword_UpgradeFromVersion3_2_0(t *testing.T) { + resource.Test(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ExternalProviders: map[string]resource.ExternalProvider{ + "random": { + VersionConstraint: "3.2.0", + Source: "hashicorp/random", + }, + }, + Config: `resource "random_password" "min" { + length = 12 + override_special = "!#@" + min_lower = 2 + min_upper = 3 + min_special = 1 + min_numeric = 4 + }`, + +``` + +## TestStep ConfigDirectory + +The `TestStep.ConfigDirectory` field accepts a [TestStepConfigFunc](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/config#TestStepConfigFunc) which is a function that accepts a [TestStepConfigRequest](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/config#TestStepConfigRequest) and returns a string containing a path to a directory containing Terraform configuration files. The path can be a relative or absolute path. + +There are helper methods available for generating a [TestStepConfigFunc](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/config#TestStepConfigFunc) including: + +* [StaticDirectory(directory string)](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/config#StaticDirectory) +* [TestNameDirectory()](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/config#TestNameDirectory) +* [TestStepDirectory()](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/config#TestStepDirectory) + +~> **Note**: `TestStep.ExternalProviders` cannot be specified when using ConfigDirectory. It is expected that [required_providers](/terraform/language/providers/requirements#requiring-providers) are defined within the configuration files. + +Custom functions can be written and used in the `TestStep.ConfigDirectory` field as long as the function is a [TestStepConfigFunc](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/config#TestStepConfigFunc) type. + +### StaticDirectory + +The [StaticDirectory(directory string)](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/config#StaticDirectory) function accepts a string specifying a path to a directory containing Terraform configuration. + +For example: + +```go +func Test_ConfigDirectory_StaticDirectory(t *testing.T) { + t.Parallel() + + Test(t, TestCase{ + Steps: []TestStep{ + { + ConfigDirectory: config.StaticDirectory(`testdata/directory_containing_config`), + /* ... */ + }, + }, + }) +} +``` + +In this instance, the testing configuration is expected to be in the `testdata/directory_containing_config` directory relative to the file containing the `Test_ConfigDirectory_StaticDirectory` test. + +### TestNameDirectory + +The [TestNameDirectory()](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/config#TestNameDirectory) function will use the name of the executing test to specify a path to a directory containing Terraform configuration. + +For example: + +```go +func Test_ConfigDirectory_TestNameDirectory(t *testing.T) { + t.Parallel() + + Test(t, TestCase{ + Steps: []TestStep{ + { + ConfigDirectory: config.TestNameDirectory(), + /* ... */ + }, + }, + }) +} +``` + +In this instance, the testing configuration is expected to be in the `testdata/Test_ConfigDirectory_TestNameDirectory` directory relative to the file containing the `Test_ConfigDirectory_TestNameDirectory` test. + +### TestStepDirectory + +The [TestStepDirectory()](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/config#TestStepDirectory) function will use the name of the executing test and the current test step number to specify a path to a directory containing Terraform configuration. + +For example: + +```go +func Test_ConfigDirectory_TestStepDirectory(t *testing.T) { + t.Parallel() + + Test(t, TestCase{ + Steps: []TestStep{ + { + ConfigDirectory: config.TestStepDirectory(), + /* ... */ + }, + }, + }) +} +``` + +In this instance, because this is the first test step in the test, the testing configuration is expected to be in the `testdata/Test_ConfigDirectory_TestStepDirectory/1` directory relative to the file containing the `Test_ConfigDirectory_TestStepDirectory` test. + +## TestStep ConfigFile + +The `TestStep.ConfigFile` field accepts a [TestStepConfigFunc](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/config#TestStepConfigFunc) which is a function that accepts a [TestStepConfigRequest](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/config#TestStepConfigRequest) and returns a string containing a path to a file containing Terraform configuration. The path can be a relative or absolute path. + +There are helper methods available for generating a [TestStepConfigFunc](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/config#TestStepConfigFunc) including: + +* [StaticFile(file string)](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/config#StaticFile) +* [TestNameFile(file string)](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/config#TestNameFile) +* [TestStepFile(file string)](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/config#TestStepFile) + +~> **Note**: `TestStep.ExternalProviders` cannot be specified when using `ConfigFile`. It is expected that [required_providers](/terraform/language/providers/requirements#requiring-providers) are defined within the configuration file. + +Custom functions can be written and used in the `TestStep.ConfigFile` field as long as the function is a [TestStepConfigFunc](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/config#TestStepConfigFunc) type. + +### StaticFile + +The [StaticFile(file string)](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/config#Staticfile) function accepts a string specifying a path to a file containing Terraform configuration. + +For example: + +```go +func Test_ConfigFile_StaticFile(t *testing.T) { + t.Parallel() + + Test(t, TestCase{ + Steps: []TestStep{ + { + ConfigFile: config.StaticFile(`testdata/directory_containing_config/main.tf`), + /* ... */ + }, + }, + }) +} +``` + +In this instance, the testing configuration is expected to be in the `testdata/directory_containing_config/main.tf` file relative to the file containing the `Test_ConfigFile_StaticFile` test. + +### TestNameFile + +The [TestNameFile(file string)](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/config#TestNameFile) function will use the name of the executing test to specify a path to a file containing Terraform configuration. + +For example: + +```go +func Test_ConfigFile_TestNameFile(t *testing.T) { + t.Parallel() + + Test(t, TestCase{ + Steps: []TestStep{ + { + ConfigFile: config.TestNameFile("main.tf"), + /* ... */ + }, + }, + }) +} +``` + +In this instance, the testing configuration is expected to be in the `testdata/Test_ConfigFile_TestNameFile` directory relative to the file containing the `Test_ConfigFile_TestNameFile` test. + +### TestStepFile + +The [TestStepFile(file string)](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/config#TestStepFile) function will use the name of the executing test and the current test step number to specify a path to a file containing Terraform configuration. + +For example: + +```go +func Test_ConfigFile_TestStepFile(t *testing.T) { + t.Parallel() + + Test(t, TestCase{ + Steps: []TestStep{ + { + ConfigFile: config.TestStepFile("main.tf"), + /* ... */ + }, + }, + }) +} +``` + +In this instance, because this is the first test step in the test, the testing configuration is expected to be in the `testdata/Test_ConfigFile_TestStepFile/1/main.tf` file relative to the file containing the `Test_ConfigDirectory_TestNameDirectory` test. + +## TestStep ConfigVariables + +[Terraform input variables](https://developer.hashicorp.com/terraform/language/values/variables) allow customization of a Terraform configuration without altering the configuration itself. + +The `TestStep.ConfigVariables` field accepts a [Variables](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/config#Variables) type which is a key-value map of string to [Variable](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/config#Variable). + +The following functions return types implementing [Variable](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/config#Variable) that correlate with the [Terraform type constraints](https://developer.hashicorp.com/terraform/language/values/variables#type-constraints): + +* [BoolVariable(value bool)](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/config#BoolVariable) +* [FloatVariable[T constraints.Float](value T)](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/config#FloatVariable) +* [IntegerVariable[T constraints.Integer](value T)](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/config#IntegerVariable) +* [func ListVariable(value ...Variable)](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/config#ListVariable) +* [MapVariable(value map[string]Variable)](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/config#MapVariable) +* [ObjectVariable(value map[string]Variable)](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/config#ObjectVariable) +* [SetVariable(value ...Variable)](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/config#SetVariable) +* [StringVariable(value string)](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/config#StringVariable) +* [TupleVariable(value ...Variable)](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/config#TupleVariable) + +The following example shows the usage of `TestStep.ConfigVariables` in conjunction with `TestStep.ConfigFile`: + +```go +func Test_ConfigFile_TestNameFile(t *testing.T) { + t.Parallel() + + Test(t, TestCase{ + Steps: []TestStep{ + { + ConfigFile: config.TestNameFile("random.tf"), + ConfigVariables: config.Variables{ + "length": config.IntegerVariable(8), + "numeric": config.BoolVariable(false), + }, + /* ... */ + }, + }, + }) +} +``` + +The configuration would be expected to be in the `testdata/Test_ConfigFile_TestNameFile/random.tf` file, for example: + +```terraform +terraform { + required_providers { + random = { + source = "registry.terraform.io/hashicorp/random" + version = "3.5.1" + } + } +} + +provider "random" {} + +resource "random_password" "test" { + length = var.length + numeric = var.numeric +} + +variable "length" { + type = number +} + +variable "numeric" { + type = bool +} +``` \ No newline at end of file diff --git a/content/terraform-plugin-testing/v1.13.x/docs/plugin/testing/acceptance-tests/continuous-integration.mdx b/content/terraform-plugin-testing/v1.13.x/docs/plugin/testing/acceptance-tests/continuous-integration.mdx new file mode 100644 index 0000000000..290f70226b --- /dev/null +++ b/content/terraform-plugin-testing/v1.13.x/docs/plugin/testing/acceptance-tests/continuous-integration.mdx @@ -0,0 +1,149 @@ +--- +page_title: 'Plugin Development - Acceptance Testing: Continuous Integration' +description: +--- + +# Acceptance Tests: Continuous Integration + +## GitHub Actions Workflow + +If using [GitHub](https://github.com/), run acceptance testing via [GitHub +Actions](https://github.com/features/actions). Other continuous integration +runners are also supported. + +Ensure the [GitHub Organization settings for GitHub +Actions](https://docs.github.com/en/organizations/managing-organization-settings/disabling-or-limiting-github-actions-for-your-organization) +and [GitHub Repository settings for GitHub +Actions](https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/enabling-features-for-your-repository/managing-github-actions-settings-for-a-repository) +allows running workflows and allows the `actions/checkout`, `actions/setup-go`, +and `hashicorp/setup-terraform` actions. + +Create a [GitHub Actions +workflow](https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions) +file, such as `.github/workflows/test.yaml`, that does the following: + +- Runs when pull requests are submitted or on [other + events](https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows) +as appropriate. +- Uses [`actions/checkout`](https://github.com/actions/checkout) to checkout + the provider codebase. +- Uses [`actions/setup-go`](https://github.com/actions/setup-go) to install Go. +- Uses + [`hashicorp/setup-terraform`](https://github.com/hashicorp/setup-terraform) +to install Terraform CLI. +- Runs the `go test` command with the appropriate environment variables and + flags. + +Use the +[`matrix`](https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstrategymatrix) +strategy for more advanced configuration, such as running acceptance testing +against multiple Terraform CLI versions. + +The following example workflow runs acceptance testing for the provider using +the latest patch versions of the Go version in the `go.mod` file and Terraform +CLI 1.11: + +```yaml +--- +name: Terraform Provider Tests + +on: + pull_request: + paths: + - '.github/workflows/test.yaml' + - '**.go' + +permissions: + # Permission for checking out code + contents: read + +jobs: + acceptance: + name: Acceptance Tests + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-go@v4 + with: + go-version-file: 'go.mod' + - uses: hashicorp/setup-terraform@v2 + with: + terraform_version: '1.11.*' + terraform_wrapper: false + - run: go test -v -cover ./... + env: TF_ACC: '1'r + + unit: + name: Unit Tests + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-go@v4 + with: + go-version-file: 'go.mod' + - run: go test -v -cover ./... ``` +``` + +The following example workflow runs acceptance testing for the provider using +the latest patch versions of Go version in the `go.mod` file and Terraform CLI +0.12 through 1.11: + +```yaml +name: Terraform Provider Tests + +on: + pull_request: + paths: + - '.github/workflows/test.yaml' + - '**.go' + +permissions: + # Permission for checking out code + contents: read + +jobs: + acceptance: + name: Acceptance Tests (Terraform ${{ matrix.terraform-version }}) + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + terraform-version: + - '0.12.*' + - '0.13.*' + - '0.14.*' + - '0.15.*' + - '1.0.*' + - '1.1.*' + - '1.2.*' + - '1.3.*' + - '1.4.*' + - '1.5.*' + - '1.6.*' + - '1.7.*' + - '1.8.*' + - '1.9.*' + - '1.10.*' + - '1.11.*' + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-go@v4 + with: + go-version-file: 'go.mod' + - uses: hashicorp/setup-terraform@v2 + with: + terraform_version: ${{ matrix.terraform-version }} + terraform_wrapper: false + - run: go test -v -cover ./... + env: + TF_ACC: '1' + unit: + name: Unit Tests + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-go@v4 + with: + go-version-file: 'go.mod' + - run: go test -v -cover ./... +``` diff --git a/content/terraform-plugin-testing/v1.13.x/docs/plugin/testing/acceptance-tests/environment-variables.mdx b/content/terraform-plugin-testing/v1.13.x/docs/plugin/testing/acceptance-tests/environment-variables.mdx new file mode 100644 index 0000000000..23eee51940 --- /dev/null +++ b/content/terraform-plugin-testing/v1.13.x/docs/plugin/testing/acceptance-tests/environment-variables.mdx @@ -0,0 +1,55 @@ +--- +page_title: 'Plugin Development - Acceptance Testing: Environment Variables' +description: |- + Reference for environment variables that control aspects of acceptance test + execution. +--- + +# Acceptance Testing: Environment Variables + +A number of environment variables are available to control aspects of +acceptance test execution. + +| Environment Variable Name | Default | Description | +|------------------------------|-------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `TF_ACC` | N/A | Set to any value to enable acceptance testing via the [`helper/resource.ParallelTest()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/helper/resource#ParallelTest) and [`helper/resource.Test()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/helper/resource#Test) functions. | +| `TF_ACC_PROVIDER_HOST`: | `registry.terraform.io` | Set the hostname of the provider under test, such as `example.com` in the `example.com/myorg/myprovider` provider source address. This is only required if any [`TestStep.Config`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/helper/resource#TestStep.Config) specifies a provider source address, such as in the [`terraform` configuration block `required_providers` attribute](/terraform/language/settings#specifying-provider-requirements). | +| `TF_ACC_PROVIDER_NAMESPACE` | `hashicorp` | Set the namespace of the provider under test, such as `myorg` in the `registry.terraform.io/myorg/myprovider` provider source address. This is only required if any [`TestStep.Config`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/helper/resource#TestStep.Config) specifies a provider source address, such as in the [`terraform` configuration block `required_providers` attribute](/terraform/language/settings#specifying-provider-requirements). | +| `TF_ACC_STATE_LINEAGE` | N/A | Set to `1` to enable state lineage debug logs, which are normally suppressed during acceptance testing. | +| `TF_ACC_TEMP_DIR` | Operating system specific via [`os.TempDir()`](https://pkg.go.dev/os#TempDir) | Set a temporary directory used for testing files and installing Terraform CLI, if installation is required. | +| `TF_ACC_TERRAFORM_PATH` | N/A | Set the path to a Terraform CLI binary on the local filesystem to be used during testing. It must be executable. If not found and `TF_ACC_TERRAFORM_VERSION` is not set, an error is returned. | +| `TF_ACC_TERRAFORM_VERSION` | N/A | Set the exact version of Terraform CLI to automatically install into `TF_ACC_TEMP_DIR`. For example, `1.1.6` or `v1.0.11`. | +| `TF_ACC_PERSIST_WORKING_DIR` | N/A | Set to any value to enable persisting the working directory and the files generated during execution of each `TestStep`. The location of each directory is written to the test output for each `TestStep` when the `go test -v` (verbose) flag is provided. | + +### Logging Environment Variables + +A number of environment variables available to control logging aspects during acceptance test execution. Some of these modify or replace the production behaviors defined in [managing provider log output](/terraform/plugin/log/managing) and [debugging Terraform](/terraform/internals/debugging). + +#### Logging Levels + +| Environment Variable Name | Default | Description | +|---------------------------|--------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `TF_ACC_LOG` | N/A | Set the `TF_LOG` environment variable used by Terraform CLI while testing. If set, overrides `TF_LOG_CORE`. Use `TF_LOG_CORE` and `TF_LOG_PROVIDER` to configure separate levels for Terraform CLI logging. | +| `TF_LOG` | N/A | Set the log level for the Go standard library `log` package. If set to any level, sets the `TRACE` log level for any SDK and provider logs written by [`terraform-plugin-log`](/terraform/plugin/log/writing). Use the `TF_LOG_SDK*` and `TF_LOG_PROVIDER_*` environment variables described in [managing log output](/terraform/plugin/log/managing) to decrease or disable SDK and provider logs written by [`terraform-plugin-log`](/terraform/plugin/log/writing). Use `TF_ACC_LOG`, `TF_LOG_CORE`, or `TF_LOG_PROVIDER` environment variables to set the logging levels used by Terraform CLI while testing. | +| `TF_LOG_CORE` | `TF_ACC_LOG` | Set the `TF_LOG_CORE` environment variable used by Terraform CLI logging of graph operations and other core functionality while testing. If `TF_ACC_LOG` is set, this setting has no effect. Use `TF_LOG_PROVIDER` to configure a separate level for Terraform CLI logging of external providers while testing (e.g. defined by the `TestCase` or `TestStep` type `ExternalProviders` field). | +| `TF_LOG_PROVIDER` | `TF_ACC_LOG` | Set the `TF_LOG_PROVIDER` environment variable used by Terraform CLI logging of external providers while testing (e.g. defined by the `TestCase` or `TestStep` type `ExternalProviders` field). If set, overrides `TF_ACC_LOG`. Use `TF_LOG_CORE` to configure a separate level for Terraform CLI logging of graph operations and other core functionality while testing. | + +#### Logging Output + +By default, there is no logging output when running the `go test` command. Use one of the below environment variables to output logs to the local filesystem or use the `go test` command `-v` (verbose) flag to view logging without writing file(s). + +| Environment Variable Name | Default | Description | +|---------------------------|---------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `TF_ACC_LOG_PATH` | N/A | Set a file path for all logs during testing. Use `TF_LOG_PATH_MASK` to configure individual log files per test. | +| `TF_LOG_PATH_MASK` | N/A | Set a file path containing the string `%s`, which is replaced with the test name, to write a separate log file per test. Use `TF_ACC_LOG_PATH` to configure a single log file for all tests. | + +The logs associated with each test can output across incorrect files as each +new test starts if the provider is using the Go standard library [`log` +package](https://pkg.go.dev/log) for logging, acceptance testing that uses +[`helper/resource.ParallelTest()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/helper/resource#ParallelTest), +and `TF_LOG_PATH_MASK`. To resolve this issue, choose one of the following +approaches: + +* Use [`terraform-plugin-log`](/terraform/plugin/log/writing) based logging. Each logger will be correctly associated with each test name output. +* Wrap testing execution so that each test is individually executed with `go test`. Since each `go test` process will have its own `log` package output handling,logging will be correctly associated with each test name output. +* Replace [`helper/resource.ParallelTest()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/helper/resource#ParallelTest) with [`helper/resource.Test()`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/helper/resource#Test) and ensure [`(*testing.T).Parallel()`](https://pkg.go.dev/testing#T.Parallel) is not called in tests. This serializes all testing so each test will be associated with each test name output. diff --git a/content/terraform-plugin-testing/v1.13.x/docs/plugin/testing/acceptance-tests/ephemeral-resources.mdx b/content/terraform-plugin-testing/v1.13.x/docs/plugin/testing/acceptance-tests/ephemeral-resources.mdx new file mode 100644 index 0000000000..36c6fea291 --- /dev/null +++ b/content/terraform-plugin-testing/v1.13.x/docs/plugin/testing/acceptance-tests/ephemeral-resources.mdx @@ -0,0 +1,271 @@ +--- +page_title: 'Plugin Development - Acceptance Testing: Ephemeral Resources' +description: >- + Guidance on how to test ephemeral resources and data. +--- + + + +Ephemeral resource support is in technical preview and offered without compatibility promises until Terraform 1.10 is generally available. + + + +# Ephemeral Resources + +[Ephemeral Resources](/terraform/language/v1.10.x/resources/ephemeral) are an abstraction that allows Terraform to reference external data, similar to [data sources](/terraform/language/data-sources), without persisting that data to plan or state artifacts. The `terraform-plugin-testing` module exclusively uses Terraform plan and state artifacts for it's assertion-based test checks, like [plan checks](/terraform/plugin/testing/acceptance-tests/plan-checks) or [state checks](/terraform/plugin/testing/acceptance-tests/state-checks), which means that ephemeral resource data cannot be asserted using these methods alone. + +The following is a test for a hypothetical `examplecloud_secret` ephemeral resource which is referenced by a provider configuration that has a single managed resource. For this test to pass, the ephemeral `examplecloud_secret` resource must return valid data, specifically a kerberos `username`, `password`, and `realm`, which are used to configure the `dns` provider and create a DNS record via the `dns_a_record_set` managed resource. + +```go +func TestExampleCloudSecret_DnsKerberos(t *testing.T) { + resource.UnitTest(t, resource.TestCase{ + // Ephemeral resources are only available in 1.10 and later + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(tfversion.Version1_10_0), + }, + ExternalProviders: map[string]resource.ExternalProvider{ + "dns": { + Source: "hashicorp/dns", + }, + }, + ProtoV5ProviderFactories: map[string]func() (tfprotov5.ProviderServer, error){ + "examplecloud": providerserver.NewProtocol5WithError(New()), + }, + Steps: []resource.TestStep{ + { + Config: ` + # Retrieves a secret containing user kerberos configuration + ephemeral "examplecloud_secret" "krb" { + name = "example_kerberos_user" + } + + # Ephemeral data can be referenced in provider configuration + provider "dns" { + update { + server = "ns.example.com" + gssapi { + realm = ephemeral.examplecloud_secret.krb.secret_data.realm + username = ephemeral.examplecloud_secret.krb.secret_data.username + password = ephemeral.examplecloud_secret.krb.secret_data.password + } + } + } + + # If we can create this DNS record successfully, then the ephemeral resource returned valid data. + resource "dns_a_record_set" "record_set" { + zone = "example.com." + addresses = [ + "192.168.0.1", + "192.168.0.2", + "192.168.0.3", + ] + } + `, + }, + }, + }) +} +``` + +See the Terraform [ephemeral documentation](http://localhost:3000/terraform/language/v1.10.x/resources/ephemeral#referencing-ephemeral-resources) for more details on where ephemeral data can be referenced in configurations. + +## Testing ephemeral data with `echo` provider + +Test assertions on [result data](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/ephemeral#OpenResponse.Result) returned by an ephemeral resource during [`Open`](/terraform/plugin/framework/ephemeral-resources/open) can be arranged using the `echoprovider` package. + +This package contains a [Protocol V6 Terraform Provider](/terraform/plugin/terraform-plugin-protocol#protocol-version-6) named `echo`, with a single managed resource also named `echo`. Using the `echo` provider configuration and an instance of the managed resource, ephemeral data can be "echoed" from the provider configuration into Terraform state, where it can be referenced in test assertions with [state checks](/terraform/plugin/testing/acceptance-tests/state-checks). For example: + +```terraform +ephemeral "examplecloud_secret" "krb" { + name = "example_kerberos_user" +} + +provider "echo" { + # Provide the ephemeral data we want to run test assertions against + data = ephemeral.examplecloud_secret.krb.secret_data +} + +# The ephemeral data will be echoed into state +resource "echo" "test_krb" {} +``` + + + +This provider is designed specifically to be used as a utility for acceptance testing ephemeral data and is only available via the `terraform-plugin-testing` Go module. + + + +### Using `echo` provider in acceptance tests + +First, we include the `echo` provider using the [`echoprovider.NewProviderServer`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/echoprovider#NewProviderServer) function in the `(TestCase).ProtoV6ProviderFactories` property: + +```go +import ( + // .. other imports + + "github.com/hashicorp/terraform-plugin-testing/echoprovider" +) + +func TestExampleCloudSecret(t *testing.T) { + resource.UnitTest(t, resource.TestCase{ + // Ephemeral resources are only available in 1.10 and later + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(tfversion.Version1_10_0), + }, + // Include the provider we want to test: `examplecloud` + ProtoV5ProviderFactories: map[string]func() (tfprotov5.ProviderServer, error){ + "examplecloud": providerserver.NewProtocol5WithError(New()), + }, + // Include `echo` as a v6 provider from `terraform-plugin-testing` + ProtoV6ProviderFactories: map[string]func() (tfprotov6.ProviderServer, error){ + "echo": echoprovider.NewProviderServer(), + }, + Steps: []resource.TestStep{ + // .. test step configurations can now use the `echo` and `examplecloud` providers + }, + }) +} +``` + +After including both providers, our test step `Config` references the ephemeral data from `examplecloud_secret` in the `echo` provider configuration `data` attribute: + +```go +func TestExampleCloudSecret(t *testing.T) { + resource.UnitTest(t, resource.TestCase{ + // .. test case setup + + Steps: []resource.TestStep{ + { + Config: ` + ephemeral "examplecloud_secret" "krb" { + name = "example_kerberos_user" + } + + provider "echo" { + data = ephemeral.examplecloud_secret.krb.secret_data + } + + resource "echo" "test_krb" {} + `, + }, + }, + }) +} +``` + +The `echo.test_krb` managed resource has a single computed `data` attribute, which will contain the provider configuration `data` results. This data is then used in assertions with the [state check](/terraform/plugin/testing/acceptance-tests/state-checks) functionality: + +```go +func TestExampleCloudSecret(t *testing.T) { + resource.UnitTest(t, resource.TestCase{ + // .. test case setup + + Steps: []resource.TestStep{ + { + Config: ` + ephemeral "examplecloud_secret" "krb" { + name = "example_kerberos_user" + } + + provider "echo" { + data = ephemeral.examplecloud_secret.krb.secret_data + } + + resource "echo" "test_krb" {} + `, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue("echo.test_krb", tfjsonpath.New("data").AtMapKey("realm"), knownvalue.StringExact("EXAMPLE.COM")), + statecheck.ExpectKnownValue("echo.test_krb", tfjsonpath.New("data").AtMapKey("username"), knownvalue.StringExact("john-doe")), + statecheck.ExpectKnownValue("echo.test_krb", tfjsonpath.New("data").AtMapKey("password"), knownvalue.StringRegexp(regexp.MustCompile(`^.{12}$`))), + }, + }, + }, + }) +} +``` + +`data` is a `dynamic` attribute, so whatever [type](/terraform/language/expressions/types) you pass in will be directly reflected in the managed resource `data` attribute. In the config above, we reference an object (`secret_data`) from the ephemeral resource instance, so the resulting type of `echo.test_krb.data` is also an `object`. + +You can also reference the entire ephemeral resource instance for assertions, rather than specific attributes: + +```go +func TestExampleCloudSecret(t *testing.T) { + resource.UnitTest(t, resource.TestCase{ + // .. test case setup + + Steps: []resource.TestStep{ + { + Config: ` + ephemeral "examplecloud_secret" "krb" { + name = "example_kerberos_user" + } + + provider "echo" { + data = ephemeral.examplecloud_secret.krb + } + + resource "echo" "test_krb" {} + `, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue("echo.test_krb", tfjsonpath.New("data").AtMapKey("name"), knownvalue.StringExact("example_kerberos_user")), + }, + }, + }, + }) +} +``` + +### Caveats with `echo` provider + +Since data produced by an ephemeral resource is allowed to change between plan/apply operations, the `echo` resource has special handling to allow this data to be used in the `terraform-plugin-testing` Go module without producing confusing error messages: + +* During plan, if the `echo` resource is being created, the `data` attribute will always be marked as unknown. +* During plan, if the `echo` resource already exists and is not being destroyed, prior state will always be fully preserved regardless of changes to the provider configuration. This essentially means an instance of the `echo` resource is immutable. +* During refresh, the prior state of the `echo` resource is always returned, regardless of changes to the provider configuration. + +Due to this special handling, if multiple test steps are required for testing data, provider developers should create new instances of `echo` for each new test step, for example: + +```go +func TestExampleCloudSecret(t *testing.T) { + resource.UnitTest(t, resource.TestCase{ + // .. test case setup + + Steps: []resource.TestStep{ + { + Config: ` + ephemeral "examplecloud_secret" "krb" { + name = "user_one" + } + + provider "echo" { + data = ephemeral.examplecloud_secret.krb + } + + # First test object -> 1 + resource "echo" "test_krb_one" {} + `, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue("echo.test_krb_one", tfjsonpath.New("data").AtMapKey("name"), knownvalue.StringExact("user_one")), + }, + }, + { + Config: ` + ephemeral "examplecloud_secret" "krb" { + name = "user_two" + } + + provider "echo" { + data = ephemeral.examplecloud_secret.krb + } + + # New test object -> 2 + resource "echo" "test_krb_two" {} + `, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue("echo.test_krb_two", tfjsonpath.New("data").AtMapKey("name"), knownvalue.StringExact("user_two")), + }, + }, + }, + }) +} +``` \ No newline at end of file diff --git a/content/terraform-plugin-testing/v1.13.x/docs/plugin/testing/acceptance-tests/import-mode.mdx b/content/terraform-plugin-testing/v1.13.x/docs/plugin/testing/acceptance-tests/import-mode.mdx new file mode 100644 index 0000000000..a3622b1722 --- /dev/null +++ b/content/terraform-plugin-testing/v1.13.x/docs/plugin/testing/acceptance-tests/import-mode.mdx @@ -0,0 +1,94 @@ +--- +page_title: 'Plugin Development - Acceptance Testing: Import mode' +description: |- + _Import_ mode is used for testing resource functionality to import existing + infrastructure into a Terraform statefile, using real Terraform import logic. +--- + +# Acceptance Tests: Import mode + +_Import_ is the workflow that brings existing infrastructure into Terraform, +without altering the infrastructure itself. For reference information about the +_Import_ workflow: [Terraform +CLI](https://developer.hashicorp.com/terraform/cli/import), +[Framework](https://developer.hashicorp.com/terraform/plugin/framework/resources/import), +[SDKv2](https://developer.hashicorp.com/terraform/plugin/sdkv2/resources/import). + +In provider acceptance tests, _Import_ mode is used for testing resource +functionality to import existing infrastructure into a Terraform statefile, +using real Terraform import functionality. + +At its core, _Import_ mode runs the _Import_ workflow and checks: does a +resource added by _Import_ match a resource added by _Lifecycle_ methods (i.e. +_Create_)? + +In common testing terminology, _Import_ mode uses a resource added to Terraform +by _Create_ as the _expected result_. + +_Import_ mode uses a resource added to Terraform by _Import_ as the _actual +result_. + +_Import_ mode runs a deep comparison of the two data structures. The test step +passes only if the two data structures match. + +## Examples + +### Testing the `terraform import` workflow + +```go +func TestImportCommand(t *testing.T) { + r.ParallelTest(t, r.TestCase{ + ProtoV5ProviderFactories: providerFactories, + Steps: []r.TestStep{ + { + Config: `resource "examplecloud_thing" "test" {}`, + }, + { + ImportState: true, + ResourceName: "examplecloud_thing.test", + ImportStateVerify: true, + }, + }, + }) +} +``` + +### Testing the plannable import workflow + +```go +func TestImportBlockWithID(t *testing.T) { + r.ParallelTest(t, r.TestCase{ + ProtoV5ProviderFactories: providerFactories, + Steps: []r.TestStep{ + { + Config: `resource "examplecloud_thing" "test" {}`, + }, + { + ImportState: true, + ImportStateKind: resource.ImportBlockWithID, + ResourceName: "examplecloud_thing.test", + }, + }, + }) +} +``` + +#### Testing the plannable import workflow using a managed resource identity + +```go +func TestImportBlockWithResourceIdentity(t *testing.T) { + r.ParallelTest(t, r.TestCase{ + ProtoV5ProviderFactories: providerFactories, + Steps: []r.TestStep{ + { + Config: `resource "examplecloud_thing" "test" {}`, + }, + { + ImportState: true, + ImportStateKind: resource.ImportBlockWithResourceIdentity, + ResourceName: "examplecloud_thing.test", + }, + }, + }) +} +``` diff --git a/content/terraform-plugin-testing/v1.13.x/docs/plugin/testing/acceptance-tests/index.mdx b/content/terraform-plugin-testing/v1.13.x/docs/plugin/testing/acceptance-tests/index.mdx new file mode 100644 index 0000000000..9cea93edb8 --- /dev/null +++ b/content/terraform-plugin-testing/v1.13.x/docs/plugin/testing/acceptance-tests/index.mdx @@ -0,0 +1,168 @@ +--- +page_title: Plugin Development - Acceptance Testing +description: |- + Terraform includes a framework for constructing acceptance tests that + imitate applying one or more configuration files. +--- + +# Acceptance Tests + +Acceptance tests for Terraform providers are a feature of the +[`terraform-plugin-testing`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing) +framework. The testing framework uses standard Go features such as the `go +test` command. It runs a local Terraform binary to perform real plan, apply, +refresh, and destroy operations, and enables developers to make assertions +about what happens during those actions. + +When testing the happy path, developers can make assertions about the data +stored in state after successfully provisioning a resource. When testing +unhappy paths, developers are able to make assertions about errors returned by +Terraform, non-empty plans, and the actual cloud resources created by +Terraform. + +~> **Note**: Acceptance tests can create and destroy real infrastructure +resources; as a result, they can incur expenses and consume quotas. The testing +framework provides a way to check that all resources created by tests are +destroyed. Refer to the [requirements and +recommendations](#requirements-and-recommendations) section. + +## Test files + +Terraform follows many of the Go programming language conventions with regards +to testing. Tests are placed in a file that matches the file under test, with +an added `_test.go` suffix. Here's an example file structure: + +``` +terraform-plugin-example/ +├── provider.go +├── provider_test.go +├── example/ +│ ├── resource_example_compute.go +│ ├── resource_example_compute_test.go +``` + +To create an acceptance test in the example `resource_example_compute_test.go` +file, add a test function with name that begins with `TestAcc`. + +``` +import "testing" + +func TestAccExampleComputeResource(*testing.T) { +} +``` + +## Requirements and recommendations + +Acceptance tests have the following requirements: + +- **[Go](https://go.dev/)**: The most recent stable version. +- **Terraform CLI**: Version 0.12.26 or later. +- **Provider Access**: Network or system access to the provider and any + resources being tested. +- **Provider Credentials**: Authorized credentials to the provider and any + resources being tested. +- **`TF_ACC` environment variable**: Set to any value. Prevents developers from + incurring unintended charges when running other Go tests. + +We also recommend the following when running acceptance tests: + +- **A separate Account**: Use a separate provider account or namespace for + acceptance testing. This prevents Terraform from unexpectedly modifying or +destroying infrastructure due to code or testing issues. +- **Previous Terraform CLI Installation**: Install Terraform CLI either into + the operating system `PATH` or use the `TF_ACC_TERRAFORM_PATH` environment +variable prior to running acceptance tests. Otherwise, the testing framework +will download and install the latest Terraform CLI version into a temporary +directory for every test invocation. Refer to the [Terraform CLI Installation +Behaviors](#terraform-cli-installation-behaviors) section for details. + +Each provider may have additional requirements and setup recommendations. Refer +to the provider's codebase for more details. + +## Running Acceptance Tests + +Use the [`go test`](https://pkg.go.dev/cmd/go/internal/test) command to run +acceptance tests. You can run the acceptance tests on any environment capable +of running `go test`, such as a local workstation [command +line](#command-line-workflow) or a [continuous integration +runner](/terraform/plugin/testing/acceptance-tests/continuous-integration), +such as GitHub Actions. + +### Command Line Workflow + +This example will execute all available acceptance tests in a provider +codebase: + +```shell +TF_ACC=1 go test -v ./... +``` + +#### Makefiles + +test to make sure the gnu domain is skipped + +For convenience, provider codebases can place common tasks in a +[Makefile](https://www.gnu.org/software/make/manual/make.html#Makefiles). + +This example defines a `testacc` target, which sets `TF_ACC` and the verbose +(`-v`) flag. + +```make +testacc: + TF_ACC=1 go test -v ./... +``` + +To run acceptance tests: + +```shell +make testacc +``` + +-> **Note**: Refer to the [Environment +Variables](/terraform/plugin/testing/acceptance-tests/environment-variables) +section for more details about behaviors and valid configurations. + +### Terraform CLI Installation Behaviors + +The testing framework implements the following Terraform CLI discovery and +installation behaviors: + +- If the `TF_ACC_TERRAFORM_PATH` environment variable is set, the framework + will use that Terraform CLI binary if it exists and is executable. If the +framework cannot find the binary or it is not executable, the framework returns +an error unless the `TF_ACC_TERRAFORM_VERSION` environment variable is also +set. +- If the `TF_ACC_TERRAFORM_VERSION` environment variable is set, the framework + will install and use that Terraform CLI version. +- If both the `TF_ACC_TERRAFORM_PATH` and `TF_ACC_TERRAFORM_VERSION` + environment variables are unset, the framework will search for the Terraform +CLI binary based on the operating system `PATH`. If the framework cannot find +the specified binary, it installs the latest available Terraform CLI binary. + +## Troubleshooting + +This section lists common errors encountered during testing. + +### Unrecognized remote plugin message + +``` +terraform failed: exit status 1 + + stderr: + + Error: Failed to instantiate provider "random" to obtain schema: +Unrecognized remote plugin message: --- FAIL: TestAccResourceID (4.28s) + + This usually means that the plugin is either invalid or simply needs to +be recompiled to support the latest protocol. +``` + +This error indicates that the provider server could not connect to Terraform +Core. Verify that the output of `terraform version` is v0.12.26 or above. + +## Next Steps + +The next step is to create _Test Cases_ using Terraform's testing +framework.build and verify real infrastructure. + +Proceed to [_Test Cases_](/terraform/plugin/testing/acceptance-tests/testcase). diff --git a/content/terraform-plugin-testing/v1.13.x/docs/plugin/testing/acceptance-tests/known-value-checks/bool.mdx b/content/terraform-plugin-testing/v1.13.x/docs/plugin/testing/acceptance-tests/known-value-checks/bool.mdx new file mode 100644 index 0000000000..51c08f5ed8 --- /dev/null +++ b/content/terraform-plugin-testing/v1.13.x/docs/plugin/testing/acceptance-tests/known-value-checks/bool.mdx @@ -0,0 +1,77 @@ +--- +page_title: 'Plugin Development - Acceptance Testing: Known Values' +description: >- + Bool Value Checks for use with Plan and State Checks. +--- + +# Bool Known Value Checks + +The known value checks that are available for bool values are: + +* [Bool](/terraform/plugin/testing/acceptance-tests/known-value-checks/bool#bool-check) +* [BoolFunc](/terraform/plugin/testing/acceptance-tests/known-value-checks/bool#boolfunc-check) + +## `Bool` Check + +The [Bool](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#Bool) check tests that a resource attribute, or output value has an exactly matching bool value. + +Example usage of [Bool](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#Bool) in an [ExpectKnownValue](/terraform/plugin/testing/acceptance-tests/plan-checks/resource) plan check. + +```go +func TestExpectKnownValue_CheckPlan_Bool(t *testing.T) { + t.Parallel() + + resource.Test(t, resource.TestCase{ + // Provider definition omitted. + Steps: []resource.TestStep{ + { + // Example resource containing a computed boolean attribute named "computed_attribute" + Config: `resource "test_resource" "one" {}`, + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectKnownValue( + "test_resource.one", + tfjsonpath.New("computed_attribute"), + knownvalue.Bool(true), + ), + }, + }, + }, + }, + }) +} +``` + +## `BoolFunc` Check + +The [BoolFunc](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#BoolFunc) check allows defining a custom function to validate whether the bool value of a resource attribute or output satisfies specific conditions. + +Example usage of [BoolFunc](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#BoolFunc) in an [ExpectKnownValue](/terraform/plugin/testing/acceptance-tests/state-checks/resource) state check. + +```go +func TestExpectKnownValue_CheckState_BoolFunc(t *testing.T) { + t.Parallel() + + resource.Test(t, resource.TestCase{ + // Provider definition omitted. + Steps: []resource.TestStep{ + { + // Example resource containing a boolean attribute named "configurable_attribute" + Config: `resource "test_resource" "one" {}`, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue( + "test_resource.one", + tfjsonpath.New("configurable_attribute"), + knownvalue.BoolFunc(func(v bool) error { + if !v { + return fmt.Errorf("expected true, got %t", v) + } + return nil + }), + ), + }, + }, + }, + }) +} +``` \ No newline at end of file diff --git a/content/terraform-plugin-testing/v1.13.x/docs/plugin/testing/acceptance-tests/known-value-checks/custom.mdx b/content/terraform-plugin-testing/v1.13.x/docs/plugin/testing/acceptance-tests/known-value-checks/custom.mdx new file mode 100644 index 0000000000..ef92d4e806 --- /dev/null +++ b/content/terraform-plugin-testing/v1.13.x/docs/plugin/testing/acceptance-tests/known-value-checks/custom.mdx @@ -0,0 +1,76 @@ +--- +page_title: 'Plugin Development - Acceptance Testing: Known Values' +description: >- + Custom Value Checks for use with Plan Checks. +--- + +# Custom Known Value Checks + +Custom known value checks can be created by implementing the [knownvalue.Check](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#Check) interface. + +```go +type Check interface { + CheckValue(value any) error + String() string +} +``` + +For example, a `StringContains` implementation could look as follows: + +```go +var _ knownvalue.Check = stringContains{} + +type stringContains struct { + value string +} + +func (v stringContains) CheckValue(other any) error { + otherVal, ok := other.(string) + + if !ok { + return fmt.Errorf("expected string value for StringContains check, got: %T", other) + } + + if !strings.Contains(otherVal, v.value) { + return fmt.Errorf("expected string %q to contain %q for StringContains check", otherVal, v.value) + } + + return nil +} + +func (v stringContains) String() string { + return v.value +} + +func StringContains(value string) stringContains { + return stringContains{ + value: value, + } +} +``` + +## `CheckValue` Method Implementation + +The `other` parameter passed to the `CheckValue` method is one of the following types: + +* bool +* map[string]any +* []any +* string + +-> **Note:** Numerical values will be of type `json.Number`, with an underlying type of `string`. + +Refer to the following built-in known value checks for implementations that handle the different types that can be passed to the `CheckValue` method in the `other` parameter: + +* [Bool](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#Bool) +* [Float32Exact](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#Float32Exact) +* [Float64Exact](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#Float64Exact) +* [Int32Exact](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#Int32Exact) +* [Int64Exact](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#Int64Exact) +* [ListExact](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#ListExact) +* [MapExact](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#MapExact) +* [NumberExact](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#NumberExact) +* [ObjectExact](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#ObjectExact) +* [SetExact](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#SetExact) +* [StringExact](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#StringExact) +* [TupleExact](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#TupleExact) diff --git a/content/terraform-plugin-testing/v1.13.x/docs/plugin/testing/acceptance-tests/known-value-checks/float32.mdx b/content/terraform-plugin-testing/v1.13.x/docs/plugin/testing/acceptance-tests/known-value-checks/float32.mdx new file mode 100644 index 0000000000..613e69a217 --- /dev/null +++ b/content/terraform-plugin-testing/v1.13.x/docs/plugin/testing/acceptance-tests/known-value-checks/float32.mdx @@ -0,0 +1,77 @@ +--- +page_title: 'Plugin Development - Acceptance Testing: Known Values' +description: >- + Float32 Value Checks for use with Plan and State Checks. +--- + +# Float32 Known Value Checks + +The known value checks that are available for float32 values are: + +* [Float32Exact](/terraform/plugin/testing/acceptance-tests/known-value-checks/float32#float32exact-check) +* [Float32Func](/terraform/plugin/testing/acceptance-tests/known-value-checks/float32#float32func-check) + +## `Float32Exact` Check + +The [Float32Exact](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#Float32Exact) check tests that a resource attribute, or output value has an exactly matching float32 value. + +Example usage of [Float32Exact](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#Float32Exact) in an [ExpectKnownValue](/terraform/plugin/testing/acceptance-tests/plan-checks/resource) plan check. + +```go +func TestExpectKnownValue_CheckPlan_Float32(t *testing.T) { + t.Parallel() + + resource.Test(t, resource.TestCase{ + // Provider definition omitted. + Steps: []resource.TestStep{ + { + // Example resource containing a computed float32 attribute named "computed_attribute" + Config: `resource "test_resource" "one" {}`, + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectKnownValue( + "test_resource.one", + tfjsonpath.New("computed_attribute"), + knownvalue.Float32Exact(1.23), + ), + }, + }, + }, + }, + }) +} +``` + +## `Float32Func` Check + +The [Float32Func](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#Float32Func) check allows defining a custom function to validate whether the float32 value of a resource attribute or output satisfies specific conditions. + +Example usage of [Float32Func](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#Float32Func) in an [ExpectKnownValue](/terraform/plugin/testing/acceptance-tests/state-checks/resource) state check. + +```go +func TestExpectKnownValue_CheckState_Float32Func(t *testing.T) { + t.Parallel() + + resource.Test(t, resource.TestCase{ + // Provider definition omitted. + Steps: []resource.TestStep{ + { + // Example resource containing a float32 attribute named "configurable_attribute" + Config: `resource "test_resource" "one" {}`, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue( + "test_resource.one", + tfjsonpath.New("configurable_attribute"), + knownvalue.Float32Func(func(v float32) error { + if v > 1.0 && v < 5.0 { + return fmt.Errorf("value must be between 1.0 and 5.0") + } + return nil + }), + ), + }, + }, + }, + }) +} +``` \ No newline at end of file diff --git a/content/terraform-plugin-testing/v1.13.x/docs/plugin/testing/acceptance-tests/known-value-checks/float64.mdx b/content/terraform-plugin-testing/v1.13.x/docs/plugin/testing/acceptance-tests/known-value-checks/float64.mdx new file mode 100644 index 0000000000..04294ebb9f --- /dev/null +++ b/content/terraform-plugin-testing/v1.13.x/docs/plugin/testing/acceptance-tests/known-value-checks/float64.mdx @@ -0,0 +1,77 @@ +--- +page_title: 'Plugin Development - Acceptance Testing: Known Values' +description: >- + Float64 Value Checks for use with Plan and State Checks. +--- + +# Float64 Known Value Checks + +The known value checks that are available for float64 values are: + +* [Float64Exact](/terraform/plugin/testing/acceptance-tests/known-value-checks/float64#float64exact-check) +* [Float64Func](/terraform/plugin/testing/acceptance-tests/known-value-checks/float64#float64func-check) + +## `Float64Exact` Check + +The [Float64Exact](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#Float64Exact) check tests that a resource attribute, or output value has an exactly matching float64 value. + +Example usage of [Float64Exact](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#Float64Exact) in an [ExpectKnownValue](/terraform/plugin/testing/acceptance-tests/plan-checks/resource) plan check. + +```go +func TestExpectKnownValue_CheckPlan_Float64(t *testing.T) { + t.Parallel() + + resource.Test(t, resource.TestCase{ + // Provider definition omitted. + Steps: []resource.TestStep{ + { + // Example resource containing a computed float64 attribute named "computed_attribute" + Config: `resource "test_resource" "one" {}`, + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectKnownValue( + "test_resource.one", + tfjsonpath.New("computed_attribute"), + knownvalue.Float64Exact(1.23), + ), + }, + }, + }, + }, + }) +} +``` + +## `Float64Func` Check + +The [Float64Func](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#Float64Func) check allows defining a custom function to validate whether the float64 value of a resource attribute or output satisfies specific conditions. + +Example usage of [Float64Func](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#Float64Func) in an [ExpectKnownValue](/terraform/plugin/testing/acceptance-tests/state-checks/resource) state check. + +```go +func TestExpectKnownValue_CheckState_Float64Func(t *testing.T) { + t.Parallel() + + resource.Test(t, resource.TestCase{ + // Provider definition omitted. + Steps: []resource.TestStep{ + { + // Example resource containing a float64 attribute named "configurable_attribute" + Config: `resource "test_resource" "one" {}`, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue( + "test_resource.one", + tfjsonpath.New("configurable_attribute"), + knownvalue.Float64Func(func(v float64) error { + if v > 1.0 && v < 5.0 { + return fmt.Errorf("value must be between 1.0 and 5.0") + } + return nil + }), + ), + }, + }, + }, + }) +} +``` \ No newline at end of file diff --git a/content/terraform-plugin-testing/v1.13.x/docs/plugin/testing/acceptance-tests/known-value-checks/index.mdx b/content/terraform-plugin-testing/v1.13.x/docs/plugin/testing/acceptance-tests/known-value-checks/index.mdx new file mode 100644 index 0000000000..569f6b5b6b --- /dev/null +++ b/content/terraform-plugin-testing/v1.13.x/docs/plugin/testing/acceptance-tests/known-value-checks/index.mdx @@ -0,0 +1,56 @@ +--- +page_title: 'Plugin Development - Acceptance Testing: Known Values' +description: >- + How to use known values in the testing module. + Known values define an expected type, and value for a resource attribute, or output value in a Terraform plan or state for use in Plan Checks or State Checks. +--- + +# Known Value Checks + +Known Value Checks are for use in conjunction with [Plan Checks](/terraform/plugin/testing/acceptance-tests/plan-checks), and [State Checks](/terraform/plugin/testing/acceptance-tests/state-checks) which leverage the [terraform-json](https://pkg.go.dev/github.com/hashicorp/terraform-json) representation of a Terraform plan. + +## Usage + +Example uses in the testing module include: + +- **Plan Checks**: The [`ExpectKnownValue()`](/terraform/plugin/testing/acceptance-tests/plan-checks/resource#expectknownvalue-plan-check), [`ExpectKnownOutputValue()`](/terraform/plugin/testing/acceptance-tests/plan-checks/output#expectknownoutputvalue-plan-check) and [`ExpectKnownOutputValueAtPath()`](/terraform/plugin/testing/acceptance-tests/plan-checks/output#expectknownoutputvalueatpath-plan-check) [built-in plan checks](/terraform/plugin/testing/acceptance-tests/plan-checks) use known value checks for asserting whether a specific resource attribute, or output value has a particular type, and value. +- **State Checks**: The [`ExpectKnownValue()`](/terraform/plugin/testing/acceptance-tests/state-checks/resource#expectknownvalue-state-check), [`ExpectKnownOutputValue()`](/terraform/plugin/testing/acceptance-tests/state-checks/output#expectknownoutputvalue-state-check) and [`ExpectKnownOutputValueAtPath()`](/terraform/plugin/testing/acceptance-tests/state-checks/output#expectknownoutputvalueatpath-state-check) [built-in state checks](/terraform/plugin/testing/acceptance-tests/state-checks) use known value checks for asserting whether a specific resource attribute, or output value has a particular type, and value. + +## Using a Known Value Check + +The known value check types are implemented within the `terraform-plugin-testing` module in the [`knownvalue` package](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue). Known value checks are instantiated by calling the relevant constructor function. + +```go +knownvalue.Bool(true) +``` + +For known value checks that represent collections, or objects, nesting of known value checks can be used to define a "composite" known value check for use in asserting against a resource attribute, or output value that contains other values. + +```go +knownvalue.ListExact([]knownvalue.Check{ + knownvalue.StringExact("value1"), + knownvalue.StringExact("value2"), +}) +``` + +## Known Value Check Types + +The following table shows the correspondence between [knownvalue.Check](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#Check) types, and attributes. + +| Known Value Check Type | Framework Attribute Type | SDKv2 Attribute Type | +|------------------------------------------------------------------------------------------------------|---------------------------|----------------------| +| [Bool Known Value Checks](/terraform/plugin/testing/acceptance-tests/known-value-checks/bool) | `schema.BoolAttribute` | `schema.TypeBool` | +| [Float32 Known Value Checks](/terraform/plugin/testing/acceptance-tests/known-value-checks/float32) | `schema.Float32Attribute` | `schema.TypeFloat` | +| [Float64 Known Value Checks](/terraform/plugin/testing/acceptance-tests/known-value-checks/float64) | `schema.Float64Attribute` | `schema.TypeFloat` | +| [Int32 Known Value Checks](/terraform/plugin/testing/acceptance-tests/known-value-checks/int32) | `schema.Int32Attribute` | `schema.TypeInt` | +| [Int64 Known Value Checks](/terraform/plugin/testing/acceptance-tests/known-value-checks/int64) | `schema.Int64Attribute` | `schema.TypeInt` | +| [List Known Value Checks](/terraform/plugin/testing/acceptance-tests/known-value-checks/list) | `schema.ListAttribute` | `schema.TypeList` | +| [Map Known Value Checks](/terraform/plugin/testing/acceptance-tests/known-value-checks/map) | `schema.MapAttribute` | `schema.TypeMap` | +| [NotNull Known Value Checks](/terraform/plugin/testing/acceptance-tests/known-value-checks/not-null) | All | All | +| [Null Known Value Checks](/terraform/plugin/testing/acceptance-tests/known-value-checks/null) | All | All | +| [Number Known Value Checks](/terraform/plugin/testing/acceptance-tests/known-value-checks/number) | `schema.NumberAttribute` | N/A | +| [Object Known Value Checks](/terraform/plugin/testing/acceptance-tests/known-value-checks/object) | `schema.ObjectAttribute` | N/A | +| [Set Known Value Checks](/terraform/plugin/testing/acceptance-tests/known-value-checks/set) | `schema.SetAttribute` | `schema.TypeSet` | +| [String Known Value Checks](/terraform/plugin/testing/acceptance-tests/known-value-checks/string) | `schema.StringAttribute` | `schema.TypeString` | +| [Tuple Known Value Checks](/terraform/plugin/testing/acceptance-tests/known-value-checks/tuple) | `schema.DynamicAttribute` | N/A | + diff --git a/content/terraform-plugin-testing/v1.13.x/docs/plugin/testing/acceptance-tests/known-value-checks/int32.mdx b/content/terraform-plugin-testing/v1.13.x/docs/plugin/testing/acceptance-tests/known-value-checks/int32.mdx new file mode 100644 index 0000000000..254b28aed6 --- /dev/null +++ b/content/terraform-plugin-testing/v1.13.x/docs/plugin/testing/acceptance-tests/known-value-checks/int32.mdx @@ -0,0 +1,77 @@ +--- +page_title: 'Plugin Development - Acceptance Testing: Known Values' +description: >- + Int32 Value Checks for use with Plan and State Checks. +--- + +# Int32 Known Value Checks + +The known value checks that are available for int32 values are: + +* [Int32Exact](/terraform/plugin/testing/acceptance-tests/known-value-checks/int32#int32exact-check) +* [Int32Func](/terraform/plugin/testing/acceptance-tests/known-value-checks/int32#int32func-check) + +## `Int32Exact` Check + +The [Int32Exact](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#Int32Exact) check tests that a resource attribute, or output value has an exactly matching int32 value. + +Example usage of [Int32Exact](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#Int32Exact) in an [ExpectKnownValue](/terraform/plugin/testing/acceptance-tests/plan-checks/resource) plan check. + +```go +func TestExpectKnownValue_CheckPlan_Int32(t *testing.T) { + t.Parallel() + + resource.Test(t, resource.TestCase{ + // Provider definition omitted. + Steps: []resource.TestStep{ + { + // Example resource containing a computed int32 attribute named "computed_attribute" + Config: `resource "test_resource" "one" {}`, + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectKnownValue( + "test_resource.one", + tfjsonpath.New("computed_attribute"), + knownvalue.Int32Exact(123), + ), + }, + }, + }, + }, + }) +} +``` + +## `Int32Func` Check + +The [Int32Func](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#Int32Func) check allows defining a custom function to validate whether the int32 value of a resource attribute or output satisfies specific conditions. + +Example usage of [Int32Func](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#Int32Func) in an [ExpectKnownValue](/terraform/plugin/testing/acceptance-tests/state-checks/resource) state check. + +```go +func TestExpectKnownValue_CheckState_Int32Func(t *testing.T) { + t.Parallel() + + resource.Test(t, resource.TestCase{ + // Provider definition omitted. + Steps: []resource.TestStep{ + { + // Example resource containing an int32 attribute named "configurable_attribute" + Config: `resource "test_resource" "one" {}`, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue( + "test_resource.one", + tfjsonpath.New("configurable_attribute"), + knownvalue.Int32Func(func(v int32) error { + if v > 1 && v < 12 { + return fmt.Errorf("value must be between 1 and 12") + } + return nil + }), + ), + }, + }, + }, + }) +} +``` \ No newline at end of file diff --git a/content/terraform-plugin-testing/v1.13.x/docs/plugin/testing/acceptance-tests/known-value-checks/int64.mdx b/content/terraform-plugin-testing/v1.13.x/docs/plugin/testing/acceptance-tests/known-value-checks/int64.mdx new file mode 100644 index 0000000000..cb43441318 --- /dev/null +++ b/content/terraform-plugin-testing/v1.13.x/docs/plugin/testing/acceptance-tests/known-value-checks/int64.mdx @@ -0,0 +1,77 @@ +--- +page_title: 'Plugin Development - Acceptance Testing: Known Values' +description: >- + Int64 Value Checks for use with Plan and State Checks. +--- + +# Int64 Known Value Checks + +The known value checks that are available for int64 values are: + +* [Int64Exact](/terraform/plugin/testing/acceptance-tests/known-value-checks/int64#int64exact-check) +* [Int64Func](/terraform/plugin/testing/acceptance-tests/known-value-checks/int64#int64func-check) + +## `Int64Exact` Check + +The [Int64Exact](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#Int64Exact) check tests that a resource attribute, or output value has an exactly matching int64 value. + +Example usage of [Int64Exact](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#Int64Exact) in an [ExpectKnownValue](/terraform/plugin/testing/acceptance-tests/plan-checks/resource) plan check. + +```go +func TestExpectKnownValue_CheckPlan_Int64(t *testing.T) { + t.Parallel() + + resource.Test(t, resource.TestCase{ + // Provider definition omitted. + Steps: []resource.TestStep{ + { + // Example resource containing a computed int64 attribute named "computed_attribute" + Config: `resource "test_resource" "one" {}`, + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectKnownValue( + "test_resource.one", + tfjsonpath.New("computed_attribute"), + knownvalue.Int64Exact(123), + ), + }, + }, + }, + }, + }) +} +``` + +## `Int64Func` Check + +The [Int64Func](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#Int64Func) check allows defining a custom function to validate whether the int64 value of a resource attribute or output satisfies specific conditions. + +Example usage of [Int64Func](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#Int64Func) in an [ExpectKnownValue](/terraform/plugin/testing/acceptance-tests/state-checks/resource) state check. + +```go +func TestExpectKnownValue_CheckState_Int64Func(t *testing.T) { + t.Parallel() + + resource.Test(t, resource.TestCase{ + // Provider definition omitted. + Steps: []resource.TestStep{ + { + // Example resource containing an int64 attribute named "configurable_attribute" + Config: `resource "test_resource" "one" {}`, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue( + "test_resource.one", + tfjsonpath.New("configurable_attribute"), + knownvalue.Int64Func(func(v int64) error { + if v > 1 && v < 12 { + return fmt.Errorf("value must be between 1 and 12") + } + return nil + }), + ), + }, + }, + }, + }) +} +``` \ No newline at end of file diff --git a/content/terraform-plugin-testing/v1.13.x/docs/plugin/testing/acceptance-tests/known-value-checks/list.mdx b/content/terraform-plugin-testing/v1.13.x/docs/plugin/testing/acceptance-tests/known-value-checks/list.mdx new file mode 100644 index 0000000000..92963b91e4 --- /dev/null +++ b/content/terraform-plugin-testing/v1.13.x/docs/plugin/testing/acceptance-tests/known-value-checks/list.mdx @@ -0,0 +1,111 @@ +--- +page_title: 'Plugin Development - Acceptance Testing: Known Values' +description: >- + List Value Checks for use with Plan Checks. +--- + +# List Known Value Checks + +The known value checks that are available for list values are: + +* [ListExact](/terraform/plugin/testing/acceptance-tests/known-value-checks/list#listexact-check) +* [ListPartial](/terraform/plugin/testing/acceptance-tests/known-value-checks/list#listpartial-check) +* [ListSizeExact](/terraform/plugin/testing/acceptance-tests/known-value-checks/list#listsizeexact-check) + +## `ListExact` Check + +The [ListExact](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#ListExact) check tests that a resource attribute, or output value has an order-dependent, matching collection of element values. + +Example usage of [ListExact](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#ListExact) in an [ExpectKnownValue](/terraform/plugin/testing/acceptance-tests/plan-checks/resource) plan check. + +```go +func TestExpectKnownValue_CheckPlan_List(t *testing.T) { + t.Parallel() + + resource.Test(t, resource.TestCase{ + // Provider definition omitted. + Steps: []resource.TestStep{ + { + // Example resource containing a computed list attribute named "computed_attribute" + Config: `resource "test_resource" "one" {}`, + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectKnownValue( + "test_resource.one", + tfjsonpath.New("computed_attribute"), + knownvalue.ListExact([]knownvalue.Check{ + knownvalue.StringExact("value1"), + knownvalue.StringExact("value2"), + }), + ), + }, + }, + }, + }, + }) +} +``` + +## `ListPartial` Check + +The [ListPartial](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#ListPartial) check tests that a resource attribute, or output value has matching element values for the specified collection indices. + +Example usage of [ListPartial](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#ListPartial) in an [ExpectKnownValue](/terraform/plugin/testing/acceptance-tests/plan-checks/resource) plan check. In this example, only the first element within the list, the element defined at index `0`, is checked. + +```go +func TestExpectKnownValue_CheckPlan_ListPartial(t *testing.T) { + t.Parallel() + + resource.Test(t, resource.TestCase{ + // Provider definition omitted. + Steps: []resource.TestStep{ + { + // Example resource containing a computed list attribute named "computed_attribute" + Config: `resource "test_resource" "one" {}`, + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectKnownValue( + "test_resource.one", + tfjsonpath.New("computed_attribute"), + knownvalue.ListPartial(map[int]knownvalue.Check{ + 0: knownvalue.StringExact("value1"), + }), + ), + }, + }, + }, + }, + }) +} +``` + +## `ListSizeExact` Check + +The [ListSizeExact](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#ListSizeExact) check tests that a resource attribute, or output value contains the specified number of elements. + +Example usage of [ListSizeExact](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#ListSizeExact) in an [ExpectKnownValue](/terraform/plugin/testing/acceptance-tests/plan-checks/resource) plan check. + +```go +func TestExpectKnownValue_CheckPlan_ListElements(t *testing.T) { + t.Parallel() + + resource.Test(t, resource.TestCase{ + // Provider definition omitted. + Steps: []resource.TestStep{ + { + // Example resource containing a computed list attribute named "computed_attribute" + Config: `resource "test_resource" "one" {}`, + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectKnownValue( + "test_resource.one", + tfjsonpath.New("computed_attribute"), + knownvalue.ListSizeExact(2), + ), + }, + }, + }, + }, + }) +} +``` diff --git a/content/terraform-plugin-testing/v1.13.x/docs/plugin/testing/acceptance-tests/known-value-checks/map.mdx b/content/terraform-plugin-testing/v1.13.x/docs/plugin/testing/acceptance-tests/known-value-checks/map.mdx new file mode 100644 index 0000000000..9685249c4b --- /dev/null +++ b/content/terraform-plugin-testing/v1.13.x/docs/plugin/testing/acceptance-tests/known-value-checks/map.mdx @@ -0,0 +1,113 @@ +--- +page_title: 'Plugin Development - Acceptance Testing: Known Values' +description: >- + Map Value Checks for use with Plan Checks. +--- + +# Map Known Value Checks + +The known value checks that are available for map values are: + +* [MapExact](/terraform/plugin/testing/acceptance-tests/known-value-checks/map#mapexact-check) +* [MapPartial](/terraform/plugin/testing/acceptance-tests/known-value-checks/map#mappartial-check) +* [MapSizeExact](/terraform/plugin/testing/acceptance-tests/known-value-checks/map#mapsizeexact-check) + +## `MapExact` Check + +The [MapExact](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#MapExact) check tests that a resource attribute, or output value has a key-specified, matching collection of element values. + +Example usage of [MapExact](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#MapExact) in an [ExpectKnownValue](/terraform/plugin/testing/acceptance-tests/plan-checks/resource) plan check. + +```go +func TestExpectKnownValue_CheckPlan_Map(t *testing.T) { + t.Parallel() + + resource.Test(t, resource.TestCase{ + // Provider definition omitted. + Steps: []resource.TestStep{ + { + // Example resource containing a computed map attribute named "computed_attribute" + Config: `resource "test_resource" "one" {}`, + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectKnownValue( + "test_resource.one", + tfjsonpath.New("computed_attribute"), + knownvalue.MapExact(map[string]knownvalue.Check{ + "key1": knownvalue.StringExact("value1"), + "key2": knownvalue.StringExact("value2"), + }), + ), + }, + }, + }, + }, + }) +} +``` + +## `MapPartial` Check + +The [MapPartial](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#MapPartial) check tests that a resource attribute, or output value has matching element values for the specified keys. + +Example usage of [MapPartial](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#MapPartial) in an [ExpectKnownValue](/terraform/plugin/testing/acceptance-tests/plan-checks/resource) plan check. + +In this example, only the element associated with `key1` within the map is checked. + +```go +func TestExpectKnownValue_CheckPlan_MapPartial(t *testing.T) { + t.Parallel() + + resource.Test(t, resource.TestCase{ + // Provider definition omitted. + Steps: []resource.TestStep{ + { + // Example resource containing a computed map attribute named "computed_attribute" + Config: `resource "test_resource" "one" {}`, + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectKnownValue( + "test_resource.one", + tfjsonpath.New("computed_attribute"), + knownvalue.MapPartial(map[string]knownvalue.Check{ + "key1": knownvalue.StringExact("value1"), + }), + ), + }, + }, + }, + }, + }) +} +``` + +## `MapSizeExact` Check + +The [MapSizeExact](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#MapSizeExact) check tests that a resource attribute, or output value contains the specified number of elements. + +Example usage of [MapSizeExact](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#MapSizeExact) in an [ExpectKnownValue](/terraform/plugin/testing/acceptance-tests/plan-checks/resource) plan check. + +```go +func TestExpectKnownValue_CheckPlan_MapElements(t *testing.T) { + t.Parallel() + + resource.Test(t, resource.TestCase{ + // Provider definition omitted. + Steps: []resource.TestStep{ + { + // Example resource containing a computed map attribute named "computed_attribute" + Config: `resource "test_resource" "one" {}`, + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectKnownValue( + "test_resource.one", + tfjsonpath.New("computed_attribute"), + knownvalue.MapSizeExact(2), + ), + }, + }, + }, + }, + }) +} +``` \ No newline at end of file diff --git a/content/terraform-plugin-testing/v1.13.x/docs/plugin/testing/acceptance-tests/known-value-checks/not-null.mdx b/content/terraform-plugin-testing/v1.13.x/docs/plugin/testing/acceptance-tests/known-value-checks/not-null.mdx new file mode 100644 index 0000000000..b6e39030f3 --- /dev/null +++ b/content/terraform-plugin-testing/v1.13.x/docs/plugin/testing/acceptance-tests/known-value-checks/not-null.mdx @@ -0,0 +1,40 @@ +--- +page_title: 'Plugin Development - Acceptance Testing: Known Values' +description: >- + NotNull Value Checks for use with Plan Checks or State Checks. +--- + +# NotNull Known Value Checks + +The known value checks that are available for values that are not null are: + +* [NotNull](/terraform/plugin/testing/acceptance-tests/known-value-checks/null#notnull-check) + +## `NotNull` Check + +The [NotNull](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#NotNull) check tests that a resource attribute, or output value is not null (i.e., any known value). + +Example usage of [NotNull](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#NotNull) in an [ExpectKnownValue](/terraform/plugin/testing/acceptance-tests/state-checks/resource) state check. + +```go +func TestExpectKnownValue_CheckState_AttributeValueNull(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + // Provider definition omitted. + Steps: []r.TestStep{ + { + // Example resource containing a computed attribute named "computed_attribute" + Config: `resource "test_resource" "one" {}`, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue( + "test_resource.one", + tfjsonpath.New("computed_attribute"), + knownvalue.NotNull(), + ), + }, + }, + }, + }) +} +``` diff --git a/content/terraform-plugin-testing/v1.13.x/docs/plugin/testing/acceptance-tests/known-value-checks/null.mdx b/content/terraform-plugin-testing/v1.13.x/docs/plugin/testing/acceptance-tests/known-value-checks/null.mdx new file mode 100644 index 0000000000..8cd4bee006 --- /dev/null +++ b/content/terraform-plugin-testing/v1.13.x/docs/plugin/testing/acceptance-tests/known-value-checks/null.mdx @@ -0,0 +1,40 @@ +--- +page_title: 'Plugin Development - Acceptance Testing: Known Values' +description: >- + Null Value Checks for use with Plan Checks or State Checks. +--- + +# Null Known Value Checks + +The known value checks that are available for null values are: + +* [Null](/terraform/plugin/testing/acceptance-tests/known-value-checks/null#null-check) + +## `Null` Check + +The [Null](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#Null) check tests that a resource attribute, or output value has an exactly matching null value. + +Example usage of [Null](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#Null) in an [ExpectKnownValue](/terraform/plugin/testing/acceptance-tests/state-checks/resource) state check. + +```go +func TestExpectKnownValue_CheckState_AttributeValueNull(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + // Provider definition omitted. + Steps: []r.TestStep{ + { + // Example resource containing a computed attribute named "computed_attribute" + Config: `resource "test_resource" "one" {}`, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue( + "test_resource.one", + tfjsonpath.New("computed_attribute"), + knownvalue.Null(), + ), + }, + }, + }, + }) +} +``` diff --git a/content/terraform-plugin-testing/v1.13.x/docs/plugin/testing/acceptance-tests/known-value-checks/number.mdx b/content/terraform-plugin-testing/v1.13.x/docs/plugin/testing/acceptance-tests/known-value-checks/number.mdx new file mode 100644 index 0000000000..220cd8f5d6 --- /dev/null +++ b/content/terraform-plugin-testing/v1.13.x/docs/plugin/testing/acceptance-tests/known-value-checks/number.mdx @@ -0,0 +1,83 @@ +--- +page_title: 'Plugin Development - Acceptance Testing: Known Values' +description: >- + Number Value Checks for use with Plan and State Checks. +--- + +# Number Known Value Checks + +The known value checks that are available for number values are: + +* [NumberExact](/terraform/plugin/testing/acceptance-tests/known-value-checks/number#numberexact-check) +* [NumberFunc](/terraform/plugin/testing/acceptance-tests/known-value-checks/number#numberfunc-check) + +## `NumberExact` Check + +The [NumberExact](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#NumberExact) check tests that a resource attribute, or output value has an exactly matching number value. + +Example usage of [NumberExact](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#NumberExact) in an [ExpectKnownValue](/terraform/plugin/testing/acceptance-tests/plan-checks/resource) plan check. + +```go +func TestExpectKnownValue_CheckPlan_Number(t *testing.T) { + t.Parallel() + + num, _, err := big.ParseFloat("1.797693134862315797693134862315797693134862315", 10, 512, big.ToNearestEven) + + if err != nil { + t.Errorf("%s", err) + } + + resource.Test(t, resource.TestCase{ + // Provider definition omitted. + Steps: []resource.TestStep{ + { + // Example resource containing a computed number attribute named "computed_attribute" + Config: `resource "test_resource" "one" {}`, + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectKnownValue( + "test_resource.one", + tfjsonpath.New("computed_attribute"), + knownvalue.NumberExact(num), + ), + }, + }, + }, + }, + }) +} +``` + +## `NumberFunc` Check + +The [NumberFunc](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#NumberFunc) check allows defining a custom function to validate whether the number value of a resource attribute or output satisfies specific conditions. + +Example usage of [NumberFunc](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#NumberFunc) in an [ExpectKnownValue](/terraform/plugin/testing/acceptance-tests/state-checks/resource) state check. + +```go +func TestExpectKnownValue_CheckState_NumberFunc(t *testing.T) { + t.Parallel() + + resource.Test(t, resource.TestCase{ + // Provider definition omitted. + Steps: []resource.TestStep{ + { + // Example resource containing a number attribute named "configurable_attribute" + Config: `resource "test_resource" "one" {}`, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue( + "test_resource.one", + tfjsonpath.New("configurable_attribute"), + knownvalue.NumberFunc(func(v *big.Float) error { + if err := testConfigurableAttribute(v); err != nil { + return fmt.Errorf("attribute validation failed: %w", err) + } + return nil + }), + ), + }, + }, + }, + }) +} +``` \ No newline at end of file diff --git a/content/terraform-plugin-testing/v1.13.x/docs/plugin/testing/acceptance-tests/known-value-checks/object.mdx b/content/terraform-plugin-testing/v1.13.x/docs/plugin/testing/acceptance-tests/known-value-checks/object.mdx new file mode 100644 index 0000000000..68569dcdee --- /dev/null +++ b/content/terraform-plugin-testing/v1.13.x/docs/plugin/testing/acceptance-tests/known-value-checks/object.mdx @@ -0,0 +1,81 @@ +--- +page_title: 'Plugin Development - Acceptance Testing: Known Values' +description: >- + Object Value Checks for use with Plan Checks. +--- + +# Object Known Value Checks + +The known value checks that are available for object values are: + +* [ObjectExact](/terraform/plugin/testing/acceptance-tests/known-value-checks/object#objectexact-check) +* [ObjectPartial](/terraform/plugin/testing/acceptance-tests/known-value-checks/object#objectpartial-check) + +## `ObjectExact` Check + +The [ObjectExact](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#ObjectExact) check tests that a resource attribute, or output value has a matching collection of attribute name, and attribute values. + +Example usage of [ObjectExact](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#ObjectExact) in an [ExpectKnownValue](/terraform/plugin/testing/acceptance-tests/plan-checks/resource) plan check. + +```go +func TestExpectKnownValue_CheckPlan_Object(t *testing.T) { + t.Parallel() + + resource.Test(t, resource.TestCase{ + // Provider definition omitted. + Steps: []resource.TestStep{ + { + // Example resource containing a computed object attribute named "computed_attribute" + Config: `resource "test_resource" "one" {}`, + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectKnownValue( + "test_resource.one", + tfjsonpath.New("computed_attribute"), + knownvalue.ObjectExact(map[string]knownvalue.Check{ + "attr1": knownvalue.StringExact("value1"), + "attr2": knownvalue.StringExact("value2"), + }), + ), + }, + }, + }, + }, + }) +} +``` + +## `ObjectPartial` Check + +The [ObjectPartial](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#ObjectPartial) check tests that a resource attribute, or output value has matching attribute values for the specified attribute names. + +Example usage of [ObjectPartial](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#ObjectPartial) in an [ExpectKnownValue](/terraform/plugin/testing/acceptance-tests/plan-checks/resource) plan check. + +In this example, only the attribute value associated with the attribute name `attr1` within the object is checked. + +```go +func TestExpectKnownValue_CheckPlan_ObjectPartial(t *testing.T) { + t.Parallel() + + resource.Test(t, resource.TestCase{ + // Provider definition omitted. + Steps: []resource.TestStep{ + { + // Example resource containing a computed object attribute named "computed_attribute" + Config: `resource "test_resource" "one" {}`, + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectKnownValue( + "test_resource.one", + tfjsonpath.New("computed_attribute"), + knownvalue.ObjectPartial(map[string]knownvalue.Check{ + "attr1": knownvalue.StringExact("value1"), + }), + ), + }, + }, + }, + }, + }) +} +``` diff --git a/content/terraform-plugin-testing/v1.13.x/docs/plugin/testing/acceptance-tests/known-value-checks/set.mdx b/content/terraform-plugin-testing/v1.13.x/docs/plugin/testing/acceptance-tests/known-value-checks/set.mdx new file mode 100644 index 0000000000..48d9069269 --- /dev/null +++ b/content/terraform-plugin-testing/v1.13.x/docs/plugin/testing/acceptance-tests/known-value-checks/set.mdx @@ -0,0 +1,111 @@ +--- +page_title: 'Plugin Development - Acceptance Testing: Known Values' +description: >- + Set Value Checks for use with Plan Checks. +--- + +# Set Known Value Checks + +The known value checks that are available for set values are: + +* [SetExact](/terraform/plugin/testing/acceptance-tests/known-value-checks/set#setexact-check) +* [SetPartial](/terraform/plugin/testing/acceptance-tests/known-value-checks/set#setpartial-check) +* [SetSizeExact](/terraform/plugin/testing/acceptance-tests/known-value-checks/set#setsizeexact-check) + +## `SetExact` Check + +The [SetExact](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#SetExact) check tests that a resource attribute, or output value has an order-independent, matching collection of element values. + +Example usage of [SetExact](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#SetExact) in an [ExpectKnownValue](/terraform/plugin/testing/acceptance-tests/plan-checks/resource) plan check. + +```go +func TestExpectKnownValue_CheckPlan_Set(t *testing.T) { + t.Parallel() + + resource.Test(t, resource.TestCase{ + // Provider definition omitted. + Steps: []resource.TestStep{ + { + // Example resource containing a computed set attribute named "computed_attribute" + Config: `resource "test_resource" "one" {}`, + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectKnownValue( + "test_resource.one", + tfjsonpath.New("computed_attribute"), + knownvalue.SetExact([]knownvalue.Check{ + knownvalue.StringExact("value2"), + knownvalue.StringExact("value1"), + }), + ), + }, + }, + }, + }, + }) +} +``` + +## `SetPartial` Check + +The [SetPartial](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#SetPartial) check tests that a resource attribute, or output value contains matching element values. + +Example usage of [SetPartial](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#SetPartial) in an [ExpectKnownValue](/terraform/plugin/testing/acceptance-tests/plan-checks/resource) plan check. In this example, only the one element within the set is checked. + +```go +func TestExpectKnownValue_CheckPlan_SetPartial(t *testing.T) { + t.Parallel() + + resource.Test(t, resource.TestCase{ + // Provider definition omitted. + Steps: []resource.TestStep{ + { + // Example resource containing a computed set attribute named "computed_attribute" + Config: `resource "test_resource" "one" {}`, + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectKnownValue( + "test_resource.one", + tfjsonpath.New("computed_attribute"), + knownvalue.SetPartial([]knownvalue.Check{ + knownvalue.StringExact("value2"), + }), + ), + }, + }, + }, + }, + }) +} +``` + +## `SetSizeExact` Check + +The [SetSizeExact](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#SetSizeExact) check tests that a resource attribute, or output value contains the specified number of elements. + +Example usage of [SetSizeExact](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#SetSizeExact) in an [ExpectKnownValue](/terraform/plugin/testing/acceptance-tests/plan-checks/resource) plan check. + +```go +func TestExpectKnownValue_CheckPlan_SetElements(t *testing.T) { + t.Parallel() + + resource.Test(t, resource.TestCase{ + // Provider definition omitted. + Steps: []resource.TestStep{ + { + // Example resource containing a computed set attribute named "computed_attribute" + Config: `resource "test_resource" "one" {}`, + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectKnownValue( + "test_resource.one", + tfjsonpath.New("computed_attribute"), + knownvalue.SetSizeExact(2), + ), + }, + }, + }, + }, + }) +} +``` diff --git a/content/terraform-plugin-testing/v1.13.x/docs/plugin/testing/acceptance-tests/known-value-checks/string.mdx b/content/terraform-plugin-testing/v1.13.x/docs/plugin/testing/acceptance-tests/known-value-checks/string.mdx new file mode 100644 index 0000000000..2cbc7fe944 --- /dev/null +++ b/content/terraform-plugin-testing/v1.13.x/docs/plugin/testing/acceptance-tests/known-value-checks/string.mdx @@ -0,0 +1,107 @@ +--- +page_title: 'Plugin Development - Acceptance Testing: Known Values' +description: >- + String Value Checks for use with Plan and State Checks. +--- + +# String Known Value Checks + +The known value checks that are available for string values are: + +* [StringExact](/terraform/plugin/testing/acceptance-tests/known-value-checks/string#stringexact-check) +* [StringRegexp](/terraform/plugin/testing/acceptance-tests/known-value-checks/string#stringregexp-check) +* [StringFunc](/terraform/plugin/testing/acceptance-tests/known-value-checks/string#stringfunc-check) + +## `StringExact` Check + +The [StringExact](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#StringExact) check tests that a resource attribute, or output value has an exactly matching string value. + +Example usage of [StringExact](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#StringExact) in an [ExpectKnownValue](/terraform/plugin/testing/acceptance-tests/plan-checks/resource) plan check. + +```go +func TestExpectKnownValue_CheckPlan_StringExact(t *testing.T) { + t.Parallel() + + resource.Test(t, resource.TestCase{ + // Provider definition omitted. + Steps: []resource.TestStep{ + { + // Example resource containing a computed string attribute named "computed_attribute" + Config: `resource "test_resource" "one" {}`, + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectKnownValue( + "test_resource.one", + tfjsonpath.New("computed_attribute"), + knownvalue.StringExact("str")), + }, + }, + }, + }, + }) +} +``` + +## `StringRegexp` Check + +The [StringRegexp](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#StringRegexp) check tests that a resource attribute, or output value has a string value which matches the supplied regular expression. + +Example usage of [StringRegexp](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#StringRegexp) in an [ExpectKnownValue](/terraform/plugin/testing/acceptance-tests/plan-checks/resource) plan check. + +```go +func TestExpectKnownValue_CheckPlan_StringRegexp(t *testing.T) { + t.Parallel() + + resource.Test(t, resource.TestCase{ + // Provider definition omitted. + Steps: []resource.TestStep{ + { + // Example resource containing a computed string attribute named "computed_attribute" + Config: `resource "test_resource" "one" {}`, + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectKnownValue( + "test_resource.one", + tfjsonpath.New("computed_attribute"), + knownvalue.StringRegexp(regexp.MustCompile("str"))), + }, + }, + }, + }, + }) +} +``` + +## `StringFunc` Check + +The [StringFunc](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#StringFunc) check allows defining a custom function to validate whether the string value of a resource attribute or output satisfies specific conditions. + +Example usage of [StringFunc](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#StringFunc) in an [ExpectKnownValue](/terraform/plugin/testing/acceptance-tests/state-checks/resource) state check. + +```go +func TestExpectKnownValue_CheckState_StringFunc(t *testing.T) { + t.Parallel() + + resource.Test(t, resource.TestCase{ + // Provider definition omitted. + Steps: []resource.TestStep{ + { + // Example resource containing a string attribute named "configurable_attribute" + Config: `resource "test_resource" "one" {}`, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue( + "test_resource.one", + tfjsonpath.New("configurable_attribute"), + knownvalue.StringFunc(func(v string) error { + if !strings.HasPrefix(v, "str") { + return fmt.Errorf("value must start with 'str'") + } + return nil + }), + ), + }, + }, + }, + }) +} +``` \ No newline at end of file diff --git a/content/terraform-plugin-testing/v1.13.x/docs/plugin/testing/acceptance-tests/known-value-checks/tuple.mdx b/content/terraform-plugin-testing/v1.13.x/docs/plugin/testing/acceptance-tests/known-value-checks/tuple.mdx new file mode 100644 index 0000000000..95129a23a9 --- /dev/null +++ b/content/terraform-plugin-testing/v1.13.x/docs/plugin/testing/acceptance-tests/known-value-checks/tuple.mdx @@ -0,0 +1,123 @@ +--- +page_title: 'Plugin Development - Acceptance Testing: Known Values' +description: >- + Tuple Value Checks for use with Plan Checks. +--- + +# Tuple Known Value Checks + + + +Provider developers will only encounter tuples when testing [dynamic data values](/terraform/plugin/framework/handling-data/dynamic-data). + + + +The known value checks that are available for tuple values are: + +* [TupleExact](/terraform/plugin/testing/acceptance-tests/known-value-checks/tuple#tupleexact-check) +* [TuplePartial](/terraform/plugin/testing/acceptance-tests/known-value-checks/tuple#tuplepartial-check) +* [TupleSizeExact](/terraform/plugin/testing/acceptance-tests/known-value-checks/tuple#tuplesizeexact-check) + +## `TupleExact` Check + +The [TupleExact](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#TupleExact) check tests that a resource attribute, or output value has an order-dependent, matching collection of element values. + +Example usage of [TupleExact](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#TupleExact) in an [ExpectKnownValue](/terraform/plugin/testing/acceptance-tests/plan-checks/resource) plan check. + +```go +func TestExpectKnownValue_CheckPlan_Tuple(t *testing.T) { + t.Parallel() + + resource.Test(t, resource.TestCase{ + // Provider definition omitted. + Steps: []resource.TestStep{ + { + // Example resource containing a required dynamic attribute named "example_attribute" + Config: `resource "test_resource" "one" { + example_attribute = [true, "hello world"] + }`, + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectKnownValue( + "test_resource.one", + tfjsonpath.New("example_attribute"), + knownvalue.TupleExact([]knownvalue.Check{ + knownvalue.Bool(true), + knownvalue.StringExact("hello world"), + }), + ), + }, + }, + }, + }, + }) +} +``` + +## `TuplePartial` Check + +The [TuplePartial](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#TuplePartial) check tests that a resource attribute, or output value has matching element values for the specified collection indices. + +Example usage of [TuplePartial](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#TuplePartial) in an [ExpectKnownValue](/terraform/plugin/testing/acceptance-tests/plan-checks/resource) plan check. In this example, only the second element within the tuple, the element defined at index `1`, is checked. + +```go +func TestExpectKnownValue_CheckPlan_TuplePartial(t *testing.T) { + t.Parallel() + + resource.Test(t, resource.TestCase{ + // Provider definition omitted. + Steps: []resource.TestStep{ + { + // Example resource containing a required dynamic attribute named "example_attribute" + Config: `resource "test_resource" "one" { + example_attribute = [true, "hello world"] + }`, + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectKnownValue( + "test_resource.one", + tfjsonpath.New("example_attribute"), + knownvalue.TuplePartial(map[int]knownvalue.Check{ + 1: knownvalue.StringExact("hello world"), + }), + ), + }, + }, + }, + }, + }) +} +``` + +## `TupleSizeExact` Check + +The [TupleSizeExact](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#TupleSizeExact) check tests that a resource attribute, or output value contains the specified number of elements. + +Example usage of [TupleSizeExact](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#TupleSizeExact) in an [ExpectKnownValue](/terraform/plugin/testing/acceptance-tests/plan-checks/resource) plan check. + +```go +func TestExpectKnownValue_CheckPlan_TupleElements(t *testing.T) { + t.Parallel() + + resource.Test(t, resource.TestCase{ + // Provider definition omitted. + Steps: []resource.TestStep{ + { + // Example resource containing a required dynamic attribute named "example_attribute" + Config: `resource "test_resource" "one" { + example_attribute = [true, "hello world"] + }`, + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectKnownValue( + "test_resource.one", + tfjsonpath.New("example_attribute"), + knownvalue.TupleSizeExact(2), + ), + }, + }, + }, + }, + }) +} +``` diff --git a/content/terraform-plugin-testing/v1.13.x/docs/plugin/testing/acceptance-tests/plan-checks/custom.mdx b/content/terraform-plugin-testing/v1.13.x/docs/plugin/testing/acceptance-tests/plan-checks/custom.mdx new file mode 100644 index 0000000000..5e3672a515 --- /dev/null +++ b/content/terraform-plugin-testing/v1.13.x/docs/plugin/testing/acceptance-tests/plan-checks/custom.mdx @@ -0,0 +1,87 @@ +--- +page_title: 'Plugin Development - Acceptance Testing: Plan Checks' +description: >- + Plan Checks are test assertions that can inspect a plan at different phases in a TestStep. Custom Plan Checks can be implemented. +--- + +# Custom Plan Checks + +The package [`plancheck`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/plancheck) also provides the [`PlanCheck`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/plancheck#PlanCheck) interface, which can be implemented for a custom plan check. + +The [`plancheck.CheckPlanRequest`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/plancheck#CheckPlanRequest) contains the current plan file, parsed by the [terraform-json package](https://pkg.go.dev/github.com/hashicorp/terraform-json#Plan). + +Here is an example implementation of a plan check that asserts that every resource change is a no-op, aka, an empty plan: +```go +package example_test + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-testing/plancheck" +) + +var _ plancheck.PlanCheck = expectEmptyPlan{} + +type expectEmptyPlan struct{} + +func (e expectEmptyPlan) CheckPlan(ctx context.Context, req plancheck.CheckPlanRequest, resp *plancheck.CheckPlanResponse) { + var result error + + for _, rc := range req.Plan.ResourceChanges { + if !rc.Change.Actions.NoOp() { + result = errors.Join(result, fmt.Errorf("expected empty plan, but %s has planned action(s): %v", rc.Address, rc.Change.Actions)) + } + } + + resp.Error = result +} + +func ExpectEmptyPlan() plancheck.PlanCheck { + return expectEmptyPlan{} +} +``` + +And example usage: +```go +package example_test + +import ( + "context" + "errors" + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/plancheck" +) + +func Test_CustomPlanCheck_ExpectEmptyPlan(t *testing.T) { + t.Parallel() + + resource.Test(t, resource.TestCase{ + ExternalProviders: map[string]resource.ExternalProvider{ + "random": { + Source: "registry.terraform.io/hashicorp/random", + }, + }, + Steps: []resource.TestStep{ + { + Config: `resource "random_string" "one" { + length = 16 + }`, + }, + { + Config: `resource "random_string" "one" { + length = 16 + }`, + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + ExpectEmptyPlan(), + }, + }, + }, + }, + }) +} +``` diff --git a/content/terraform-plugin-testing/v1.13.x/docs/plugin/testing/acceptance-tests/plan-checks/index.mdx b/content/terraform-plugin-testing/v1.13.x/docs/plugin/testing/acceptance-tests/plan-checks/index.mdx new file mode 100644 index 0000000000..9c1960b547 --- /dev/null +++ b/content/terraform-plugin-testing/v1.13.x/docs/plugin/testing/acceptance-tests/plan-checks/index.mdx @@ -0,0 +1,128 @@ +--- +page_title: 'Plugin Development - Acceptance Testing: Plan Checks' +description: >- + Plan Checks are test assertions that can inspect a plan at different phases in a TestStep. The testing module + provides built-in Plan Checks for common use-cases, and custom Plan Checks can also be implemented. +--- + +# Plan Checks + +During the **Lifecycle (config)** and **Refresh** [modes](/terraform/plugin/testing/acceptance-tests/teststep#test-modes) of a `TestStep`, the testing framework will run `terraform plan` before and after certain operations. For example, the **Lifecycle (config)** mode will run a plan before the `terraform apply` phase, as well as a plan before and after the `terraform refresh` phase. + +These `terraform plan` operations results in a [plan file](/terraform/cli/commands/plan#out-filename) and can be represented by this [JSON format](/terraform/internals/json-format#plan-representation). + +A **plan check** is a test assertion that inspects the plan file at a specific phase during the current testing mode. Multiple plan checks can be run at each defined phase, all assertion errors returned are aggregated, reported as a test failure, and all test cleanup logic is executed. + +- Available plan phases for **Lifecycle (config)** mode are defined in the [`TestStep.ConfigPlanChecks`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/helper/resource#TestStep) struct +- Available plan phases for **Refresh** mode are defined in the [`TestStep.RefreshPlanChecks`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/helper/resource#TestStep) struct +- **Import** mode currently does not run any plan operations, and therefore does not support plan checks. + +Refer to: + +- [General Plan Checks](#general-plan-checks) for built-in general purpose plan checks. +- [Resource Plan Checks](/terraform/plugin/testing/acceptance-tests/plan-checks/resource) for built-in managed resource and data source plan checks. +- [Output Plan Checks](/terraform/plugin/testing/acceptance-tests/plan-checks/output) for built-in output-related plan checks. +- [Custom Plan Checks](/terraform/plugin/testing/acceptance-tests/plan-checks/custom) for defining bespoke plan checks. + +## General Plan Checks + +The `terraform-plugin-testing` module provides a package [`plancheck`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/plancheck) with built-in general plan checks for common use-cases: + +| Check | Description | +|--------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------| +| [`ExpectEmptyPlan`](/terraform/plugin/testing/acceptance-tests/plan-checks#expectemptyplan-plan-check) | Asserts the entire plan has no operations for apply. | +| [`ExpectNonEmptyPlan`](/terraform/plugin/testing/acceptance-tests/plan-checks#expectnonemptyplan-plan-check) | Asserts the entire plan contains at least one operation for apply. | + +## `ExpectEmptyPlan` Plan Check + +One of the built-in plan checks, [`plancheck.ExpectEmptyPlan`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/plancheck#ExpectEmptyPlan), is useful for determining a plan is a no-op prior to, for instance, the `terraform apply` phase. + +Given the following example with the [random provider](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/string), we have written a test that asserts that there are no planned changes: + +```go +package example_test + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/plancheck" +) + +func Test_Random_EmptyPlan(t *testing.T) { + t.Parallel() + + resource.Test(t, resource.TestCase{ + ExternalProviders: map[string]resource.ExternalProvider{ + "random": { + Source: "registry.terraform.io/hashicorp/random", + }, + }, + Steps: []resource.TestStep{ + { + Config: `resource "random_string" "one" { + length = 16 + }`, + }, + { + Config: `resource "random_string" "one" { + length = 16 + }`, + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectEmptyPlan(), + }, + }, + }, + }, + }) +} +``` + +## `ExpectNonEmptyPlan` Plan Check + +One of the built-in plan checks, [`plancheck.ExpectNonEmptyPlan`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/plancheck#ExpectNonEmptyPlan), is useful for determining whether a plan contains changes prior to, for instance, the `terraform apply` phase. + +The following example, which uses the built-in [terraform_data resource](https://developer.hashicorp.com/terraform/language/resources/terraform-data), asserts that there are planned changes: + +```go +package example_test + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/plancheck" + "github.com/hashicorp/terraform-plugin-testing/tfversion" +) + +func Test_ExpectNonEmptyPlan_ResourceChanges(t *testing.T) { + t.Parallel() + + resource.UnitTest(t, resource.TestCase{ + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(tfversion.Version1_4_0), + }, + ExternalProviders: map[string]resource.ExternalProvider{ + "terraform": {Source: "terraform.io/builtin/terraform"}, + }, + Steps: []resource.TestStep{ + { + Config: `resource "terraform_data" "one" { + triggers_replace = ["original"] + }`, + }, + { + Config: `resource "terraform_data" "one" { + triggers_replace = ["new"] + }`, + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectNonEmptyPlan(), + }, + }, + }, + }, + }) +} +``` \ No newline at end of file diff --git a/content/terraform-plugin-testing/v1.13.x/docs/plugin/testing/acceptance-tests/plan-checks/output.mdx b/content/terraform-plugin-testing/v1.13.x/docs/plugin/testing/acceptance-tests/plan-checks/output.mdx new file mode 100644 index 0000000000..3f0b97bf72 --- /dev/null +++ b/content/terraform-plugin-testing/v1.13.x/docs/plugin/testing/acceptance-tests/plan-checks/output.mdx @@ -0,0 +1,309 @@ +--- +page_title: 'Plugin Development - Acceptance Testing: Plan Checks' +description: >- + Plan Checks are test assertions that can inspect a plan at different phases in a TestStep. The testing module + provides built-in Output Value Plan Checks for common use-cases. +--- + +# Output Plan Checks + +The `terraform-plugin-testing` module provides a package [`plancheck`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/plancheck) with built-in output value plan checks for common use-cases: + +| Check | Description | +|-------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------| +| [`ExpectKnownOutputValue`](/terraform/plugin/testing/acceptance-tests/plan-checks/output#expectknownoutputvalue-plan-check) | Asserts the output at the specified address has the specified type, and value. | +| [`ExpectKnownOutputValueAtPath`](/terraform/plugin/testing/acceptance-tests/plan-checks/output#expectknownoutputvalueatpath-plan-check) | Asserts the output at the specified address, and path has the specified type, and value. | +| [`ExpectNullOutputValue`](/terraform/plugin/testing/acceptance-tests/plan-checks/output#expectnulloutputvalue-plan-check) | Asserts the output at the specified address has a null value. | +| [`ExpectNullOutputValueAtPath`](/terraform/plugin/testing/acceptance-tests/plan-checks/output#expectnulloutputvalueatpath-plan-check) | Asserts the output at the specified address, and path has a null value. | +| [`ExpectUnknownOutputValue`](/terraform/plugin/testing/acceptance-tests/plan-checks/output#expectunknownoutputvalue-plan-check) | Asserts the output at the specified address has an unknown value. | +| [`ExpectUnknownOutputValueAtPath`](/terraform/plugin/testing/acceptance-tests/plan-checks/output#expectknownoutputvalueatpath-plan-check) | Asserts the output at the specified address, and path has an unknown value. | + +## `ExpectKnownOutputValue` Plan Check + +The [`plancheck.ExpectKnownOutputValue(address, value)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/plancheck#ExpectKnownOutputValue) plan check verifies that a specific output value has a known type, and value. + +Refer to [Known Value Checks](/terraform/plugin/testing/acceptance-tests/known-value-checks) for details, and examples of the available [knownvalue.Check](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#Check) types that can be used with the `ExpectKnownOutputValue` plan check. + +```go +package example_test + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/knownvalue" + "github.com/hashicorp/terraform-plugin-testing/plancheck" + "github.com/hashicorp/terraform-plugin-testing/tfversion" +) + +func TestExpectKnownOutputValue_CheckPlan_Bool(t *testing.T) { + t.Parallel() + + resource.UnitTest(t, resource.TestCase{ + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(tfversion.Version1_8_0), + }, + // Provider definition omitted. + Steps: []resource.TestStep{ + { + Config: ` + output "test" { + value = provider::example::bool(true) + }`, + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectKnownOutputValue( + "test", + knownvalue.Bool(true), + ), + }, + }, + }, + }, + }) +} +``` + +## `ExpectKnownOutputValueAtPath` Plan Check + +The [`plancheck.ExpectKnownOutputValueAtPath(address, path, value)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/plancheck#ExpectKnownOutputValueAtPath) plan check verifies that a specific output value at a defined path has a known type, and value. + +~> **Note**: Prior to Terraform v1.3.0 a planned output is marked as fully unknown if any attribute is unknown. + +Refer to [Known Value Checks](/terraform/plugin/testing/acceptance-tests/known-value-checks) for details, and examples of the available [knownvalue.Check](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#Check) types that can be used with the `ExpectKnownOutputValueAtPath` plan check. + +```go +package example_test + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/knownvalue" + "github.com/hashicorp/terraform-plugin-testing/plancheck" +) + +func TestExpectKnownOutputValue_CheckPlan_Bool(t *testing.T) { + t.Parallel() + + resource.Test(t, resource.TestCase{ + // Provider definition omitted. + Steps: []resource.TestStep{ + { + // Example resource containing a computed boolean attribute named "computed_attribute" + Config: `resource "test_resource" "one" {} + + // Generally, it is not necessary to use an output to test a resource attribute, + // the resource attribute should be tested directly instead. This is only shown as + // an example. + // + // ConfigPlanChecks: resource.ConfigPlanChecks{ + // PreApply: []plancheck.PlanCheck{ + // plancheck.ExpectKnownValue( + // "test_resource.one", + // tfjsonpath.New("computed_attribute"), + // knownvalue.Bool(true), + // ), + // }, + // }, + // + // This is only shown as an example. + output test { + value = test_resource.one + }`, + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectKnownOutputValueAtPath( + "test", + tfjsonpath.New("computed_attribute"), + knownvalue.Bool(true), + ), + }, + }, + }, + }, + }) +} +``` + +## `ExpectNullOutputValue` Plan Check + +~> **Note**: `ExpectNullOutputValue` is deprecated. Use [`ExpectKnownOutputValue`](/terraform/plugin/testing/acceptance-tests/plan-checks/output#expectknownoutputvalue-plan-check) with [`knownvalue.Null()`](/terraform/plugin/testing/acceptance-tests/known-value-checks/null) instead. + +The built-in [`plancheck.ExpectNullOutputValue(address)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/plancheck#ExpectNullOutputValue) plan check determines whether an output at the specified address has a null value. + +```go +package example_test + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/plancheck" +) + +func Test_ExpectNullOutputValue_StringAttribute_NullConfig(t *testing.T) { + t.Parallel() + + resource.Test(t, resource.TestCase{ + // Provider definition omitted. + Steps: []resource.TestStep{ + { + Config: `resource "test_resource" "test" { + string_attribute = null + } + + output "string_attribute" { + value = test_resource.test.string_attribute + } + `, + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectNullOutputValue("string_attribute"), + }, + }, + }, + }, + }) +} +``` + +## `ExpectNullOutputValueAtPath` Plan Check + +~> **Note**: `ExpectNullOutputValueAtPath` is deprecated. Use [`ExpectKnownOutputValueAtPath`](/terraform/plugin/testing/acceptance-tests/plan-checks/output#expectknownoutputvalueatpath-plan-check) with [`knownvalue.Null()`](/terraform/plugin/testing/acceptance-tests/known-value-checks/null) instead. + +The built-in [`plancheck.ExpectNullOutputValueAtPath(address, path)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/plancheck#ExpectNullOutputValueAtPath) plan check determines whether an output at the specified address, and path has a null value. | + +```go +package example_test + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/plancheck" + "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" + "github.com/hashicorp/terraform-plugin-testing/tfversion" +) + +func Test_ExpectNullOutputValueAtPath_StringAttribute_NullConfig(t *testing.T) { + t.Parallel() + + resource.Test(t, resource.TestCase{ + // Prior to Terraform v1.3.0 a planned output is marked as fully unknown + // if any attribute is unknown. The id attribute within the test provider + // is unknown. + // Reference: https://github.com/hashicorp/terraform/blob/v1.3/CHANGELOG.md#130-september-21-2022 + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(tfversion.Version1_3_0), + }, + // Provider definition omitted. + Steps: []resource.TestStep{ + { + Config: `resource "test_resource" "test" { + string_attribute = null + } + + output "resource" { + value = test_resource.test + } + `, + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectNullOutputValueAtPath("resource", tfjsonpath.New("string_attribute")), + }, + }, + }, + }, + }) +} +``` + +## `ExpectUnknownOutputValue` Plan Check + +One of the built-in plan checks, [`plancheck.ExpectUnknownOutputValue`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/plancheck#ExpectUnknownOutputValue), determines whether an output value is unknown, for example, prior to the `terraform apply` phase. + +The following uses the [time_offset](https://registry.terraform.io/providers/hashicorp/time/latest/docs/resources/offset) resource from the [time provider](https://registry.terraform.io/providers/hashicorp/time/latest), to illustrate usage of the [`plancheck.ExpectUnknownOutputValue`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/plancheck#ExpectUnknownOutputValue), and verifies that `day` is unknown. + +```go +package example_test + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/plancheck" +) + +func Test_Time_Unknown(t *testing.T) { + t.Parallel() + + resource.Test(t, resource.TestCase{ + ExternalProviders: map[string]resource.ExternalProvider{ + "time": { + Source: "registry.terraform.io/hashicorp/time", + }, + }, + Steps: []resource.TestStep{ + { + Config: `resource "time_offset" "one" { + offset_days = 1 + } + + output day { + value = time_offset.one.day + }`, + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectUnknownOutputValue("day"), + }, + }, + }, + }, + }) +} +``` + +## `ExpectUnknownOutputValueAtPath` Plan Check + +Output values can contain objects or collections as well as primitive (e.g., string) values. Output value plan checks provide two forms for the plan checks, for example `ExpectUnknownOutputValue()`, and `ExpectUnknownOutputValueAtPath()`. The `Expect<...>OutputValueAtPath()` form is used to access a value contained within an object or collection, as illustrated in the following example. + +```go +package example_test + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/plancheck" + "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" +) + +func Test_Time_Unknown(t *testing.T) { + t.Parallel() + + resource.Test(t, resource.TestCase{ + ExternalProviders: map[string]resource.ExternalProvider{ + "time": { + Source: "registry.terraform.io/hashicorp/time", + }, + }, + Steps: []resource.TestStep{ + { + Config: `resource "time_offset" "one" { + offset_days = 1 + } + + output time_offset_one { + value = time_offset.one + }`, + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectUnknownOutputValueAtPath("time_offset_one", tfjsonpath.New("day")), + }, + }, + }, + }, + }) +} +``` diff --git a/content/terraform-plugin-testing/v1.13.x/docs/plugin/testing/acceptance-tests/plan-checks/resource.mdx b/content/terraform-plugin-testing/v1.13.x/docs/plugin/testing/acceptance-tests/plan-checks/resource.mdx new file mode 100644 index 0000000000..b96ae68bb9 --- /dev/null +++ b/content/terraform-plugin-testing/v1.13.x/docs/plugin/testing/acceptance-tests/plan-checks/resource.mdx @@ -0,0 +1,286 @@ +--- +page_title: 'Plugin Development - Acceptance Testing: Plan Checks' +description: >- + Plan Checks are test assertions that can inspect a plan at different phases in a TestStep. The testing module + provides built-in Managed Resource and Data Source Plan Checks for common use-cases. +--- + +# Resource Plan Checks + +The `terraform-plugin-testing` module provides a package [`plancheck`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/plancheck) with built-in managed resource, and data source plan checks for common use-cases: + +| Check | Description | +|---------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------| +| [`ExpectKnownValue`](/terraform/plugin/testing/acceptance-tests/plan-checks/resource#expectknownvalue-plan-check) | Asserts the specified attribute at the given managed resource, or data source, has the specified type, and value. | +| [`ExpectResourceAction`](/terraform/plugin/testing/acceptance-tests/plan-checks/resource#expectresourceaction-plan-check) | Asserts the given managed resource, or data source, has the specified operation for apply. | +| [`ExpectSensitiveValue`](/terraform/plugin/testing/acceptance-tests/plan-checks/resource#expectsensitivevalue-plan-check) | Asserts the specified attribute at the given managed resource, or data source, has a sensitive value. | +| [`ExpectUnknownValue`](/terraform/plugin/testing/acceptance-tests/plan-checks/resource#expectunknownvalue-plan-check) | Asserts the specified attribute at the given managed resource, or data source, has an unknown value. | + +## `ExpectKnownValue` Plan Check + +The [`plancheck.ExpectKnownValue(address, path, value)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/plancheck#ExpectKnownValue) plan check provides a basis for asserting that a specific resource attribute has a known type, and value. + +Refer to [Known Value Checks](/terraform/plugin/testing/acceptance-tests/known-value-checks) for details, and examples of the available [knownvalue.Check](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#Check) types that can be used with the `ExpectKnownValue` plan check. + +```go +package example_test + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/knownvalue" + "github.com/hashicorp/terraform-plugin-testing/plancheck" + "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" +) + +func TestExpectKnownValue_CheckPlan_String(t *testing.T) { + t.Parallel() + + resource.Test(t, resource.TestCase{ + // Provider definition omitted. + Steps: []resource.TestStep{ + { + // Example resource containing a computed string attribute named "computed_attribute" + Config: `resource "test_resource" "one" {}`, + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectKnownValue( + "test_resource.one", + tfjsonpath.New("computed_attribute"), + knownvalue.StringExact("str")), + }, + }, + }, + }, + }) +} +``` + +## `ExpectResourceAction` Plan Check + +One of the built-in plan checks, [`plancheck.ExpectResourceAction`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/plancheck#ExpectResourceAction), is useful for determining the exact action type a resource will under-go during, say, the `terraform apply` phase. + +Given the following example with the [random provider](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/string), we have written a test that asserts that `random_string.one` will be destroyed and re-created when the `length` attribute is changed: + +```go +package example_test + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/plancheck" +) + +func Test_Random_ForcesRecreate(t *testing.T) { + t.Parallel() + + resource.Test(t, resource.TestCase{ + ExternalProviders: map[string]resource.ExternalProvider{ + "random": { + Source: "registry.terraform.io/hashicorp/random", + }, + }, + Steps: []resource.TestStep{ + { + Config: `resource "random_string" "one" { + length = 16 + }`, + }, + { + Config: `resource "random_string" "one" { + length = 15 + }`, + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectResourceAction("random_string.one", plancheck.ResourceActionDestroyBeforeCreate), + }, + }, + }, + }, + }) +} +``` + +Another example with the [time provider](https://registry.terraform.io/providers/hashicorp/time/latest/docs/resources/offset) asserts that `time_offset.one` will be updated in-place when the `offset_days` attribute is changed: + +```go +package example_test + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/plancheck" +) + +func Test_Time_UpdateInPlace(t *testing.T) { + t.Parallel() + + resource.Test(t, resource.TestCase{ + ExternalProviders: map[string]resource.ExternalProvider{ + "time": { + Source: "registry.terraform.io/hashicorp/time", + }, + }, + Steps: []resource.TestStep{ + { + Config: `resource "time_offset" "one" { + offset_days = 1 + }`, + }, + { + Config: `resource "time_offset" "one" { + offset_days = 2 + }`, + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectResourceAction("time_offset.one", plancheck.ResourceActionUpdate), + }, + }, + }, + }, + }) +} +``` + +Multiple plan checks can be combined if you want to assert multiple resource actions: + +```go +package example_test + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/plancheck" +) + +func Test_Time_UpdateInPlace_and_NoOp(t *testing.T) { + t.Parallel() + + resource.Test(t, resource.TestCase{ + ExternalProviders: map[string]resource.ExternalProvider{ + "time": { + Source: "registry.terraform.io/hashicorp/time", + }, + }, + Steps: []resource.TestStep{ + { + Config: `resource "time_offset" "one" { + offset_days = 1 + } + resource "time_offset" "two" { + offset_days = 1 + }`, + }, + { + Config: `resource "time_offset" "one" { + offset_days = 2 + } + resource "time_offset" "two" { + offset_days = 1 + }`, + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectResourceAction("time_offset.one", plancheck.ResourceActionUpdate), + plancheck.ExpectResourceAction("time_offset.two", plancheck.ResourceActionNoop), + }, + }, + }, + }, + }) +} +``` + +## `ExpectSensitiveValue` Plan Check + +The built-in [`plancheck.ExpectSensitiveValue(address, path)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/plancheck#ExpectSensitiveValue) plan check is used to determine whether the specified attribute at the given managed resource, or data source, has a sensitive value. + +```go +package example_test + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/plancheck" + "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" + "github.com/hashicorp/terraform-plugin-testing/tfversion" +) + +func Test_ExpectSensitiveValue_SensitiveStringAttribute(t *testing.T) { + t.Parallel() + + resource.UnitTest(t, resource.TestCase{ + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(tfversion.Version1_0_0), // Change.AfterSensitive + }, + // Provider definition omitted. + Steps: []resource.TestStep{ + { + Config: ` + resource "test_resource" "one" { + sensitive_string_attribute = "test" + } + `, + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectSensitiveValue("test_resource.one", + tfjsonpath.New("sensitive_string_attribute")), + }, + }, + }, + }, + }) +} +``` + +## `ExpectUnknownValue` Plan Check + +The built-in [`plancheck.ExpectUnknownValue(address, path)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/plancheck#ExpectUnknownValue) plan check is used to determine whether the specified attribute at the given managed resource, or data source, has an unknown value. + +```go +package example_test + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/plancheck" + "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" +) + +func Test_ExpectUnknownValue_StringAttribute(t *testing.T) { + t.Parallel() + + resource.Test(t, resource.TestCase{ + ExternalProviders: map[string]resource.ExternalProvider{ + "time": { + Source: "registry.terraform.io/hashicorp/time", + }, + }, + // Provider definition for `test` omitted. + Steps: []resource.TestStep{ + { + Config: `resource "time_static" "one" {} + + resource "test_resource" "two" { + string_attribute = time_static.one.rfc3339 + } + `, + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectUnknownValue("test_resource.two", tfjsonpath.New("string_attribute")), + }, + }, + }, + }, + }) +} +``` \ No newline at end of file diff --git a/content/terraform-plugin-testing/v1.13.x/docs/plugin/testing/acceptance-tests/state-checks/custom.mdx b/content/terraform-plugin-testing/v1.13.x/docs/plugin/testing/acceptance-tests/state-checks/custom.mdx new file mode 100644 index 0000000000..46cd3877ee --- /dev/null +++ b/content/terraform-plugin-testing/v1.13.x/docs/plugin/testing/acceptance-tests/state-checks/custom.mdx @@ -0,0 +1,122 @@ +--- +page_title: 'Plugin Development - Acceptance Testing: State Checks' +description: >- + State Checks are test assertions that can inspect state during a TestStep. Custom State Checks can be implemented. +--- + +# Custom State Checks + +The package [`statecheck`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/statecheck) also provides the [`StateCheck`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/statecheck#StateCheck) interface, which can be implemented for a custom state check. + +The [`statecheck.CheckStateRequest`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/statecheck#CheckStateRequest) contains the current state file, parsed by the [terraform-json package](https://pkg.go.dev/github.com/hashicorp/terraform-json#State). + +Here is an example implementation of a state check that asserts that a specific resource attribute has a known type and value: + +```go +package example_test + +import ( + "context" + "fmt" + + tfjson "github.com/hashicorp/terraform-json" + + "github.com/hashicorp/terraform-plugin-testing/knownvalue" + "github.com/hashicorp/terraform-plugin-testing/statecheck" + "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" +) + +var _ statecheck.StateCheck = expectKnownValue{} + +type expectKnownValue struct { + resourceAddress string + attributePath tfjsonpath.Path + knownValue knownvalue.Check +} + +func (e expectKnownValue) CheckState(ctx context.Context, req statecheck.CheckStateRequest, resp *statecheck.CheckStateResponse) { + var resource *tfjson.StateResource + + if req.State == nil { + resp.Error = fmt.Errorf("state is nil") + } + + if req.State.Values == nil { + resp.Error = fmt.Errorf("state does not contain any state values") + } + + if req.State.Values.RootModule == nil { + resp.Error = fmt.Errorf("state does not contain a root module") + } + + for _, r := range req.State.Values.RootModule.Resources { + if e.resourceAddress == r.Address { + resource = r + + break + } + } + + if resource == nil { + resp.Error = fmt.Errorf("%s - Resource not found in state", e.resourceAddress) + + return + } + + result, err := tfjsonpath.Traverse(resource.AttributeValues, e.attributePath) + + if err != nil { + resp.Error = err + + return + } + + if err := e.knownValue.CheckValue(result); err != nil { + resp.Error = err + } +} + +func ExpectKnownValue(resourceAddress string, attributePath tfjsonpath.Path, knownValue knownvalue.Check) statecheck.StateCheck { + return expectKnownValue{ + resourceAddress: resourceAddress, + attributePath: attributePath, + knownValue: knownValue, + } +} +``` + +And example usage: + +```go +package example_test + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/knownvalue" + "github.com/hashicorp/terraform-plugin-testing/statecheck" + "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" +) + +func TestExpectKnownValue_CheckState_Bool(t *testing.T) { + t.Parallel() + + resource.Test(t, resource.TestCase{ + // Provider definition omitted. + Steps: []resource.TestStep{ + { + // Example resource containing a computed boolean attribute named "computed_attribute" + Config: `resource "test_resource" "one" {}`, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue( + "test_resource.one", + tfjsonpath.New("computed_attribute"), + knownvalue.Bool(true), + ), + }, + }, + }, + }) +} +``` diff --git a/content/terraform-plugin-testing/v1.13.x/docs/plugin/testing/acceptance-tests/state-checks/index.mdx b/content/terraform-plugin-testing/v1.13.x/docs/plugin/testing/acceptance-tests/state-checks/index.mdx new file mode 100644 index 0000000000..36304e3880 --- /dev/null +++ b/content/terraform-plugin-testing/v1.13.x/docs/plugin/testing/acceptance-tests/state-checks/index.mdx @@ -0,0 +1,20 @@ +--- +page_title: 'Plugin Development - Acceptance Testing: State Checks' +description: >- + State Checks are test assertions that can inspect state during a TestStep. The testing module + provides built-in State Checks for common use-cases, and custom State Checks can also be implemented. +--- + +# State Checks + +During the **Lifecycle (config)** [mode](/terraform/plugin/testing/acceptance-tests/teststep#test-modes) of a `TestStep`, the testing framework will run `terraform apply`. + +The execution of `terraform apply` results in a [state file](/terraform/language/state), and can be represented by this [JSON format](/terraform/internals/json-format#state-representation). + +A **state check** is a test assertion that inspects the state file. Multiple state checks can be run, all assertion errors returned are aggregated, reported as a test failure, and all test cleanup logic is executed. + +Refer to: + +- [Resource State Checks](/terraform/plugin/testing/acceptance-tests/state-checks/resource) for built-in managed resource and data source state checks. +- [Output State Checks](/terraform/plugin/testing/acceptance-tests/state-checks/output) for built-in output-related state checks. +- [Custom State Checks](/terraform/plugin/testing/acceptance-tests/state-checks/custom) for defining bespoke state checks. diff --git a/content/terraform-plugin-testing/v1.13.x/docs/plugin/testing/acceptance-tests/state-checks/output.mdx b/content/terraform-plugin-testing/v1.13.x/docs/plugin/testing/acceptance-tests/state-checks/output.mdx new file mode 100644 index 0000000000..434e7486b9 --- /dev/null +++ b/content/terraform-plugin-testing/v1.13.x/docs/plugin/testing/acceptance-tests/state-checks/output.mdx @@ -0,0 +1,119 @@ +--- +page_title: 'Plugin Development - Acceptance Testing: State Checks' +description: >- + State Checks are test assertions that can inspect state during a TestStep. The testing module + provides built-in Output Value State Checks for common use-cases. +--- + +# Output State Checks + +The `terraform-plugin-testing` module provides a package [`statecheck`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/statecheck) with built-in output value state checks for common use-cases: + +| Check | Description | +|-------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------| +| [`ExpectKnownOutputValue`](/terraform/plugin/testing/acceptance-tests/state-checks/output#expectknownoutputvalue-state-check) | Asserts the output at the specified address has the specified type, and value. | +| [`ExpectKnownOutputValueAtPath`](/terraform/plugin/testing/acceptance-tests/state-checks/output#expectknownoutputvalueatpath-state-check) | Asserts the output at the specified address, and path has the specified type, and value. | + +## `ExpectKnownOutputValue` State Check + +The [`statecheck.ExpectKnownOutputValue(address, value)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/statecheck#ExpectKnownOutputValue) state check verifies that a specific output value has a known type, and value. + +Refer to [Known Value Checks](/terraform/plugin/testing/acceptance-tests/known-value-checks) for details, and examples of the available [knownvalue.Check](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#Check) types that can be used with the `ExpectKnownOutputValue` state check. + +```go +package example_test + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/knownvalue" + "github.com/hashicorp/terraform-plugin-testing/statecheck" + "github.com/hashicorp/terraform-plugin-testing/tfversion" +) + +func TestExpectKnownOutputValue_CheckState_Bool(t *testing.T) { + t.Parallel() + + resource.UnitTest(t, resource.TestCase{ + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(tfversion.Version1_8_0), + }, + // Provider definition omitted. + Steps: []resource.TestStep{ + { + Config: ` + output "test" { + value = provider::example::bool(true) + }`, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownOutputValue( + "test", + knownvalue.Bool(true), + ), + }, + }, + }, + }) +} +``` + +## `ExpectKnownOutputValueAtPath` State Check + +The [`statecheck.ExpectKnownOutputValueAtPath(address, path, value)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/statecheck#ExpectKnownOutputValueAtPath) state check verifies that a specific output value at a defined path has a known type, and value. + +Refer to [Known Value Checks](/terraform/plugin/testing/acceptance-tests/known-value-checks) for details, and examples of the available [knownvalue.Check](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#Check) types that can be used with the `ExpectKnownOutputValueAtPath` state check. + +```go +package example_test + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/knownvalue" + "github.com/hashicorp/terraform-plugin-testing/statecheck" + "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" +) + +func TestExpectKnownOutputValueAtPath_CheckState_Bool(t *testing.T) { + t.Parallel() + + resource.Test(t, resource.TestCase{ + // Provider definition omitted. + Steps: []resource.TestStep{ + { + // Example resource containing a computed boolean attribute named "computed_attribute" + Config: `resource "test_resource" "one" {} + + // Generally, it is not necessary to use an output to test a resource attribute, + // the resource attribute should be tested directly instead. This is only shown as + // an example. + // Generally, it is not necessary to use an output to test a resource attribute, + // the resource attribute should be tested directly instead, by inspecting the + // value of the resource attribute. For instance: + // + // ConfigStateChecks: []statecheck.StateCheck{ + // statecheck.ExpectKnownValue( + // "test_resource.one", + // tfjsonpath.New("computed_attribute"), + // knownvalue.Bool(true), + // ), + // }, + // + // This is only shown as an example. + output test_resource_one_output { + value = test_resource.one + }`, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownOutputValueAtPath( + "test_resource_one_output", + tfjsonpath.New("computed_attribute"), + knownvalue.Bool(true), + ), + }, + }, + }, + }) +} +``` \ No newline at end of file diff --git a/content/terraform-plugin-testing/v1.13.x/docs/plugin/testing/acceptance-tests/state-checks/resource.mdx b/content/terraform-plugin-testing/v1.13.x/docs/plugin/testing/acceptance-tests/state-checks/resource.mdx new file mode 100644 index 0000000000..d11db8f63f --- /dev/null +++ b/content/terraform-plugin-testing/v1.13.x/docs/plugin/testing/acceptance-tests/state-checks/resource.mdx @@ -0,0 +1,310 @@ +--- +page_title: 'Plugin Development - Acceptance Testing: State Checks' +description: >- + State Checks are test assertions that can inspect state during a TestStep. The testing module + provides built-in Managed Resource and Data Source State Checks for common use-cases. +--- + +# Resource State Checks + +The `terraform-plugin-testing` module provides a package [`statecheck`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/statecheck) with built-in managed resource, and data source state checks for common use-cases: + +| Check | Description | +|---------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| [`CompareValue`](/terraform/plugin/testing/acceptance-tests/state-checks/resource#comparevalue-state-check) | Compares sequential values of the specified attribute at the given managed resource, or data source, using the supplied [value comparer](/terraform/plugin/testing/acceptance-tests/value-comparers). | +| [`CompareValueCollection`](/terraform/plugin/testing/acceptance-tests/state-checks/resource#comparevaluecollection-state-check) | Compares each item in the specified collection (e.g., list, set) attribute, with the second specified attribute at the given managed resources, or data sources, using the supplied [value comparer](/terraform/plugin/testing/acceptance-tests/value-comparers). | +| [`CompareValuePairs`](/terraform/plugin/testing/acceptance-tests/state-checks/resource#comparevaluecollection-state-check) | Compares the specified attributes at the given managed resources, or data sources, using the supplied [value comparer](/terraform/plugin/testing/acceptance-tests/value-comparers). | +| [`ExpectKnownValue`](/terraform/plugin/testing/acceptance-tests/state-checks/resource#expectknownvalue-state-check) | Asserts the specified attribute at the given managed resource, or data source, has the specified type, and value. | +| [`ExpectSensitiveValue`](/terraform/plugin/testing/acceptance-tests/state-checks/resource#expectsensitivevalue-state-check) | Asserts the specified attribute at the given managed resource, or data source, has a sensitive value. | + +## `CompareValue` State Check + +The intended usage of [`statecheck.CompareValue(comparer)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/statecheck#CompareValue) state check is to retrieve a specific resource attribute value from state during sequential test steps, and to compare these values using the supplied value comparer. + +Refer to [Value Comparers](/terraform/plugin/testing/acceptance-tests/value-comparers) for details, and examples of the available [compare.ValueComparer](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/compare#ValueComparer) types that can be used with the `CompareValue` state check. + +```go +package example_test + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-testing/compare" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/statecheck" + "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" +) + +func TestCompareValue_CheckState_ValuesSame(t *testing.T) { + t.Parallel() + + compareValuesSame := statecheck.CompareValue(compare.ValuesSame()) + + resource.Test(t, resource.TestCase{ + // Provider definition omitted. + Steps: []resource.TestStep{ + { + // Example resource containing a computed attribute named "computed_attribute" + Config: `resource "test_resource" "one" {}`, + ConfigStateChecks: []statecheck.StateCheck{ + compareValuesSame.AddStateValue( + "test_resource.one", + tfjsonpath.New("computed_attribute"), + ), + }, + }, + { + // Example resource containing a computed attribute named "computed_attribute" + Config: `resource "test_resource" "one" {}`, + ConfigStateChecks: []statecheck.StateCheck{ + compareValuesSame.AddStateValue( + "test_resource.one", + tfjsonpath.New("computed_attribute"), + ), + }, + }, + }, + }) +} +``` + +## `CompareValueCollection` State Check + +The [`statecheck.CompareValueCollection(resourceAddressOne, collectionPath, resourceAddressTwo, attributePath, comparer)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/statecheck#CompareValueCollection) state check retrieves a specific collection (e.g., list, set) resource attribute, and a second resource attribute from state, and compares each of the items in the collection with the second attribute using the supplied value comparer. + +Refer to [Value Comparers](/terraform/plugin/testing/acceptance-tests/value-comparers) for details, and examples of the available [compare.ValueComparer](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/compare#ValueComparer) types that can be used with the `CompareValueCollection` state check. + +The following example illustrates how a `CompareValue` state check can be used to determine whether an attribute value appears in a collection attribute. Note that this is for illustrative purposes only, `CompareValue` should only be used for checking computed values. + +```go +package example_test + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-testing/compare" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/statecheck" + "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" +) + +func TestCompareValueCollection_CheckState_ValuesSame(t *testing.T) { + t.Parallel() + + resource.Test(t, resource.TestCase{ + // Provider definition omitted. + Steps: []resource.TestStep{ + { + // The following is for illustrative purposes. State checking + // should only be used for computed attributes + Config: `resource "test_resource" "one" { + string_attribute = "str" + } + + resource "test_resource" "two" { + list_attribute = [ + "str2", + "str", + ] + } + `, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.CompareValueCollection( + "test_resource.two", + []tfjsonpath.Path{ + tfjsonpath.New("list_attribute"), + }, + "test_resource.one", + tfjsonpath.New("string_attribute"), + compare.ValuesSame(), + ), + }, + }, + }, + }) +} +``` + +The following example illustrates how a `CompareValue` state check can be used to determine whether an object attribute value appears in a collection (e.g., list) attribute containing objects. Note that this is for illustrative purposes only, `CompareValue` should only be used for checking computed values. + + +```go +package example_test + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-testing/compare" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/statecheck" + "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" +) + +func TestCompareValueCollection_CheckState_ValuesSame(t *testing.T) { + resource.Test(t, resource.TestCase{ + // Provider definition omitted. + Steps: []resource.TestStep{ + { + // The following is for illustrative purposes. State checking + // should only be used for computed attributes + Config: `resource "test_resource" "one" { + list_nested_attribute = [ + { + a = false + b = "two" + }, + { + a = true + b = "four" + } + ] + single_nested_attribute = { + a = true + b = "four" + } + }`, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.CompareValueCollection( + "test_resource.one", + []tfjsonpath.Path{ + tfjsonpath.New("list_nested_attribute"), + tfjsonpath.New("b"), + }, + "test_resource.one", + tfjsonpath.New("single_nested_attribute").AtMapKey("b"), + compare.ValuesSame(), + ), + }, + }, + }, + }) +} +``` + +## `CompareValuePairs` State Check + +The [`statecheck.CompareValuePairs(resourceAddressOne, attributePathOne, resourceAddressTwo, attributePathTwo, comparer)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/statecheck#CompareValuePairs) state check provides a basis for retrieving a pair of attribute values, and comparing them using the supplied value comparer. + +Refer to [Value Comparers](/terraform/plugin/testing/acceptance-tests/value-comparers) for details, and examples of the available [compare.ValueComparer](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/compare#ValueComparer) types that can be used with the `CompareValuePairs` state check. + +```go +package statecheck_test + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-testing/compare" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/statecheck" + "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" +) + +func TestCompareValuePairs_CheckState_ValuesSame(t *testing.T) { + t.Parallel() + + resource.Test(t, resource.TestCase{ + // Provider definition omitted. + Steps: []resource.TestStep{ + { + // Example resource containing a computed attribute named "computed_attribute" + Config: `resource "test_resource" "one" {} + + resource "test_resource" "two" {} + `, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.CompareValuePairs( + "test_resource.one", + tfjsonpath.New("computed_attribute"), + "test_resource.two", + tfjsonpath.New("computed_attribute"), + compare.ValuesSame(), + ), + }, + }, + }, + }) +} +``` + +## `ExpectKnownValue` State Check + +The [`statecheck.ExpectKnownValue(address, path, value)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/statecheck#ExpectKnownValue) state check provides a basis for asserting that a specific resource attribute has a known type, and value. + +Refer to [Known Value Checks](/terraform/plugin/testing/acceptance-tests/known-value-checks) for details, and examples of the available [knownvalue.Check](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#Check) types that can be used with the `ExpectKnownValue` state check. + +```go +package example_test + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/knownvalue" + "github.com/hashicorp/terraform-plugin-testing/statecheck" + "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" +) + +func TestExpectKnownValue_CheckState_Bool(t *testing.T) { + t.Parallel() + + resource.Test(t, resource.TestCase{ + // Provider definition omitted. + Steps: []resource.TestStep{ + { + // Example resource containing a computed boolean attribute named "computed_attribute" + Config: `resource "test_resource" "one" {}`, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue( + "test_resource.one", + tfjsonpath.New("computed_attribute"), + knownvalue.Bool(true), + ), + }, + }, + }, + }) +} +``` + +## `ExpectSensitiveValue` State Check + +The [`statecheck.ExpectSensitiveValue(address, path)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/statecheck#ExpectSensitiveValue) state check provides a basis for asserting that a specific resource attribute is marked as sensitive. + +-> **Note:** In this example, a [TerraformVersionCheck](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/tfversion#TerraformVersionCheck) is being used to prevent execution of this test prior to Terraform version `1.4.6` (refer to the release notes for Terraform [v1.4.6](https://github.com/hashicorp/terraform/releases/tag/v1.4.6)). + +```go +package example_test + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/statecheck" + "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" + "github.com/hashicorp/terraform-plugin-testing/tfversion" +) + +func Test_ExpectSensitiveValue_SensitiveStringAttribute(t *testing.T) { + t.Parallel() + + resource.UnitTest(t, resource.TestCase{ + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(tfversion.Version1_4_6), // StateResource.SensitiveValues + }, + // Provider definition omitted. + Steps: []resource.TestStep{ + { + Config: ` + resource "test_resource" "one" { + sensitive_string_attribute = "test" + } + `, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectSensitiveValue("test_resource.one", + tfjsonpath.New("sensitive_string_attribute")), + }, + }, + }, + }) +} +``` diff --git a/content/terraform-plugin-testing/v1.13.x/docs/plugin/testing/acceptance-tests/sweepers.mdx b/content/terraform-plugin-testing/v1.13.x/docs/plugin/testing/acceptance-tests/sweepers.mdx new file mode 100644 index 0000000000..2fff9834f8 --- /dev/null +++ b/content/terraform-plugin-testing/v1.13.x/docs/plugin/testing/acceptance-tests/sweepers.mdx @@ -0,0 +1,121 @@ +--- +page_title: 'Plugin Development - Acceptance Testing: Sweepers' +description: >- + Acceptance tests provision and verify real infrastructure with Terraform's + testing framework. Sweepers clean up leftover infrastructure. +--- + +# Sweepers + +Acceptance tests in Terraform provision and verify real infrastructure using [Terraform's testing framework](/terraform/plugin/testing/acceptance-tests). Ideally all infrastructure created is then destroyed within the lifecycle of a test, however the reality is that there are several situations that can arise where resources created during a test are “leaked”. Leaked test resources are resources created by Terraform during a test, but Terraform either failed to destroy them as part of the test, or the test falsely reported all resources were destroyed after completing the test. Common causes are intermittent errors or failures in vendor APIs, or developer error in the resource code or test. + +To address the possibility of leaked resources, Terraform provides a mechanism called sweepers to cleanup leftover infrastructure. We will add a file to our folder structure that will invoke the sweeper helper. + +``` +terraform-plugin-example/ +├── provider.go +├── provider_test.go +├── example/ +│ ├── example_sweeper_test.go +│ ├── resource_example_compute.go +│ ├── resource_example_compute_test.go +``` + +**`example_sweeper_test.go`** + +```go +package example + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" +) + +func TestMain(m *testing.M) { + resource.TestMain(m) +} + +// sharedClientForRegion returns a common provider client configured for the specified region +func sharedClientForRegion(region string) (any, error) { + ... + return client, nil +} +``` + +`resource.TestMain` is responsible for parsing the special test flags and invoking the sweepers. Sweepers should be added within the acceptance test file of a resource. + +**`resource_example_compute_test.go`** + +```go +package example + +import ( + "log" + "strings" + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" +) + +func init() { + resource.AddTestSweepers("example_compute", &resource.Sweeper{ + Name: "example_compute", + F: func (region string) error { + client, err := sharedClientForRegion(region) + if err != nil { + return fmt.Errorf("Error getting client: %s", err) + } + conn := client.(*ExampleClient) + + instances, err := conn.DescribeComputeInstances() + if err != nil { + return fmt.Errorf("Error getting instances: %s", err) + } + for _, instance := range instances { + if strings.HasPrefix(instance.Name, "test-acc") { + err := conn.DestroyInstance(instance.ID) + + if err != nil { + log.Printf("Error destroying %s during sweep: %s", instance.Name, err) + } + } + } + return nil + }, + }) +} +``` + +This example demonstrates adding a sweeper, it is important to note that the string passed to `resource.AddTestSweepers` is added to a map, this name must therefore be unique. Also note there needs to be a way of identifying resources created by Terraform during acceptance tests, a common practice is to prefix all resource names created during acceptance tests with `"test-acc"` or something similar. + +For more complex leaks, sweepers can also specify a list of sweepers that need to be run prior to the one being defined. + +**`resource_example_compute_disk_test.go`** + +```go +package example + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" +) + +func init() { + resource.AddTestSweepers("example_compute_disk", &resource.Sweeper{ + Name: "example_compute_disk", + Dependencies: []string{"example_compute"} + ... + }) +} +``` + +The sweepers can be invoked with the common make target `sweep`: + +``` +$ make sweep +WARNING: This will destroy infrastructure. Use only in development accounts. +go test ... +... +``` diff --git a/content/terraform-plugin-testing/v1.13.x/docs/plugin/testing/acceptance-tests/testcase.mdx b/content/terraform-plugin-testing/v1.13.x/docs/plugin/testing/acceptance-tests/testcase.mdx new file mode 100644 index 0000000000..0ecdcce202 --- /dev/null +++ b/content/terraform-plugin-testing/v1.13.x/docs/plugin/testing/acceptance-tests/testcase.mdx @@ -0,0 +1,352 @@ +--- +page_title: 'Plugin Development - Acceptance Testing: TestCase' +description: |- + Acceptance tests are expressed in terms of Test Cases. Each Test Case + creates a set of resources then verifies the new infrastructure. +--- + +# Acceptance Tests: TestCases + +Acceptance tests are expressed in terms of **Test Cases**, each using one or +more Terraform configurations designed to create a set of resources under test, +and then verify the actual infrastructure created. Terraform's `resource` +package offers a method `Test()`, accepting two parameters and acting as the +entry point to Terraform's acceptance test framework. The first parameter is the +standard [\*testing.T struct from Golang's Testing package][3], and the second is +[TestCase][1], a Go struct that developers use to setup the acceptance tests. + +Here's an example acceptance test. Here the Provider is named `Example`, and the +Resource under test is `Widget`. The parts of this test are explained below the +example. + +```go +package example + +// example.Widget represents a concrete Go type that represents an API resource +func TestAccExampleWidget_basic(t *testing.T) { + var widgetBefore, widgetAfter example.Widget + rName := acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckExampleResourceDestroy, + Steps: []resource.TestStep{ + { + Config: testAccExampleResource(rName), + ConfigStateChecks: []statecheck.StateCheck{ + stateCheckExampleResourceExists("example_widget.foo", &widgetBefore), + }, + }, + { + Config: testAccExampleResource_removedPolicy(rName), + ConfigStateChecks: []statecheck.StateCheck{ + stateCheckExampleResourceExists("example_widget.foo", &widgetAfter), + }, + }, + }, + }) +} +``` + +## Creating Acceptance Tests Functions + +Terraform acceptance tests are declared with the naming pattern `TestAccXxx` +and with the standard Go test function signature of `func TestAccXxx(*testing.T)`. +Using the above test as an example: + +```go +// File: example/widget_test.go +package example + +func TestAccExampleWidget_basic(t *testing.T) { + // ... +} +``` + +Inside this function we invoke `resource.Test()` with the `*testing.T` input and +a new testcase object: + +```go +// File: example/widget_test.go +package example + +func TestAccExampleWidget_basic(t *testing.T) { + resource.Test(t, resource.TestCase{ + // ... + }) +} +``` + +The majority of acceptance tests will only invoke `resource.Test()` and exit. If +at any point this method encounters an error, either in executing the provided +Terraform configurations or subsequent developer defined checks, `Test()` will +invoke the `t.Error` method of Go's standard testing framework and the test will +fail. A failed test will not halt or otherwise interrupt any other tests +currently running. + +## TestCase Reference API + +`TestCase` offers several fields for developers to add to customize and validate +each test, defined below. The source for `TestCase` can be viewed [here on +godoc.org](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/helper/resource#TestCase) + +### IsUnitTest + +**Type:** [bool](https://pkg.go.dev/builtin#bool) + +**Default:** `false` + +**Required:** no + +**IsUnitTest** allows a test to run regardless of the TF_ACC environment +variable. This should be used with care - only for fast tests on local resources +(e.g. remote state with a local backend) but can be used to increase confidence +in correct operation of Terraform without waiting for a full acceptance test +run. + +### PreCheck + +**Type:** `function` + +**Default:** `nil` + +**Required:** no + +**PreCheck** if non-nil, will be called before any test steps are executed. It +is commonly used to verify that required values exist for testing, such as +environment variables containing test keys that are used to configure the +Provider or Resource under test. + +**Example usage:** + +```go +// File: example/widget_test.go +package example + +func TestAccExampleWidget_basic(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + // ... + }) +} + + +// testAccPreCheck validates the necessary test API keys exist +// in the testing environment +func testAccPreCheck(t *testing.T) { + if v := os.Getenv("EXAMPLE_KEY"); v == "" { + t.Fatal("EXAMPLE_KEY must be set for acceptance tests") + } + if v := os.Getenv("EXAMPLE_SECRET"); v == "" { + t.Fatal("EXAMPLE_SECRET must be set for acceptance tests") + } +} +``` + +### TerraformVersionChecks + +**Type:** [`[]tfversion.TerraformVersionCheck`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/tfversion#TerraformVersionCheck) + +**Default:** `nil` + +**Required:** no + +**TerraformVersionChecks** if non-nil, will be called after any defined PreChecks +but before any test steps are executed. The [Terraform Version Checks](/terraform/plugin/testing/acceptance-tests/tfversion-checks) +are generic checks that check logic against the Terraform CLI version and can +immediately pass or fail a test before any test steps are executed. + +The [`tfversion`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/tfversion) package provides built-in checks for common scenarios. + +**Example usage:** + +```go +// File: example/widget_test.go +package example + +func TestAccExampleWidget_basic(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(tfversion.Version1_1_0), // built-in check from tfversion package + }, + // ... + }) +} + +``` + +### Providers + +**Type:** [`map[string]*schema.Provider`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema#Provider) + +**Required:** Yes + +**Providers** is a map of `*schema.Provider` values with `string` +keys, representing the Providers that will be under test. Only the Providers +included in this map will be loaded during the test, so any Provider included in +a configuration file for testing must be represented in this map or the test +will fail during initialization. + +This map is most commonly constructed once in a common `init()` method of the +Provider's main test file, and includes an object of the current Provider type. + +**Example usage:** (note the different files `widget_test.go` and `provider_test.go`) + +```go +// File: example/widget_test.go +package example + +func TestAccExampleWidget_basic(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(tfversion.Version1_1_0), + }, + Providers: testAccProviders, + // ... + }) +} + +// File: example/provider_test.go +package example + +var testAccProviders map[string]*schema.Provider +var testAccProvider *schema.Provider + +func init() { + testAccProvider = Provider() + testAccProviders = map[string]*schema.Provider{ + "example": testAccProvider, + } +} +``` + +### CheckDestroy + +**Type:** [TestCheckFunc](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/helper/resource#TestCheckFunc) + +**Default:** `nil` + +**Required:** no + +**CheckDestroy** is called after all test steps have been run and Terraform +has run `destroy` on the remaining state. This allows developers to ensure any +resource created is truly destroyed. This method receives the last known +Terraform state as input, and commonly uses infrastructure SDKs to query APIs +directly to verify the expected objects are no longer found, and should return +an error if any resources remain. + +**Example usage:** + +```go +// File: example/widget_test.go +package example + +func TestAccExampleWidget_basic(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(tfversion.Version1_1_0), + }, + Providers: testAccProviders, + CheckDestroy: testAccCheckExampleResourceDestroy, + // ... + }) +} + +// testAccCheckExampleResourceDestroy verifies the Widget +// has been destroyed +func testAccCheckExampleResourceDestroy(s *terraform.State) error { + // retrieve the connection established in Provider configuration + conn := testAccProvider.Meta().(*ExampleClient) + + // loop through the resources in state, verifying each widget + // is destroyed + for _, rs := range s.RootModule().Resources { + if rs.Type != "example_widget" { + continue + } + + // Retrieve our widget by referencing it's state ID for API lookup + request := &example.DescribeWidgets{ + IDs: []string{rs.Primary.ID}, + } + + response, err := conn.DescribeWidgets(request) + if err == nil { + if len(response.Widgets) > 0 && *response.Widgets[0].ID == rs.Primary.ID { + return fmt.Errorf("Widget (%s) still exists.", rs.Primary.ID) + } + + return nil + } + + // If the error is equivalent to 404 not found, the widget is destroyed. + // Otherwise return the error + if !strings.Contains(err.Error(), "Widget not found") { + return err + } + } + + return nil +} +``` + +### Steps + +**Type:** [`[]TestStep`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/helper/resource#TestStep) + +**Required:** yes + +**TestStep** is a single apply sequence of a test, done within the context of a +state. Multiple `TestStep`s can be sequenced in a Test to allow testing +potentially complex update logic and usage. Basic tests typically contain one to +two steps, to verify the resource can be created and subsequently updated, +depending on the properties of the resource. In general, simply create/destroy +tests will only need one step. + +`TestStep`s are covered in detail in [the next section, `TestSteps`](/terraform/plugin/testing/acceptance-tests/teststep). + +**Example usage:** + +```go +// File: example/widget_test.go +package example + +func TestAccExampleWidget_basic(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(tfversion.Version1_1_0), + }, + Providers: testAccProviders, + CheckDestroy: testAccCheckExampleResourceDestroy, + Steps: []resource.TestStep{ + { + Config: testAccExampleResource(rName), + ConfigStateChecks: []statecheck.StateCheck{ + stateCheckExampleResourceExists("example_widget.foo", &widgetBefore), + }, + }, + { + Config: testAccExampleResource_removedPolicy(rName), + ConfigStateChecks: []statecheck.StateCheck{ + stateCheckExampleResourceExists("example_widget.foo", &widgetAfter), + }, + }, + }, + }) +} +``` + +## Next Steps + +Proceed to define [`TestSteps`][2]. + +[1]: https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/helper/resource#TestCase + +[2]: /terraform/plugin/testing/acceptance-tests/teststep + +[3]: https://pkg.go.dev/testing#T diff --git a/content/terraform-plugin-testing/v1.13.x/docs/plugin/testing/acceptance-tests/teststep.mdx b/content/terraform-plugin-testing/v1.13.x/docs/plugin/testing/acceptance-tests/teststep.mdx new file mode 100644 index 0000000000..192406852b --- /dev/null +++ b/content/terraform-plugin-testing/v1.13.x/docs/plugin/testing/acceptance-tests/teststep.mdx @@ -0,0 +1,375 @@ +--- +page_title: 'Plugin Development - Acceptance Testing: TestStep' +description: |- + TestSteps represent the application of an actual Terraform configuration + file to a given state. +--- + +# Acceptance Tests: TestSteps + +`TestStep`s represent the application of an actual Terraform configuration file +to a given state. Each step requires a configuration as input and provides +developers several means of validating the behavior of the specific resource +under test. + +## Test Modes + +Terraform's test framework facilitates three distinct modes of acceptance tests, +_Lifecycle (config)_, _Import_ and _Refresh_. + +### Lifecycle (config) mode + +_Lifecycle (config)_ mode is the most common mode. It tests plugins by +providing one or more configuration files with the same logic as would be used +when running `terraform apply`. Configuration is supplied by specifying +[TestStep.Config](/terraform/plugin/testing/acceptance-tests/configuration#teststep-config), +[TestStep.ConfigDirectory](/terraform/plugin/testing/acceptance-tests/configuration#teststep-configdirectory), or +[TestStep.ConfigFile](/terraform/plugin/testing/acceptance-tests/configuration#teststep-configfile). +Variables for use with configuration are defined by specifying +[TestStep.ConfigVariables](/terraform/plugin/testing/acceptance-tests/configuration#teststep-configvariables). + +-> **Note:** To define a _Lifecycle (config)_ mode test step, use one of the +`Config`, `ConfigFile`, or `ConfigDirectory` fields. + +```go +steps := []TestStep{ + { + Config: `resource "random_string" { length = 12 }`, + }, +} +``` + +### Import mode + +[_Import_ mode](/terraform/plugin/testing/acceptance-tests/import-mode) +exercises provider logic for importing existing infrastructure resources into a +Terraform statefile, using real Terraform import functionality. + +-> **Note:** To define an _Import_ mode test step, set the `ImportState` field to `true`. + +The recommended use of _Import_ mode is to run it after a _Lifecycle (config)_ +mode test step. The testing framework uses the configuration and state of the +_Lifecycle (config)_ mode test step as known good values for any `_Import_` +mode test steps that follow it. As a result, _Import_ mode blocks tend to be +concise and idiomatic. + +```go +steps := []TestStep{ + { + Config: `resource "random_string" "puzzle" { length = 12 }`, + }, + { + ImportState: true, + ImportStateKind: resource.ImportBlockWithID, + ResourceName: `random_string.puzzle`, + }, +} +``` + + +### Refresh mode + +_Refresh_ mode runs `terraform refresh` to refresh the test case's Terraform state. + +-> **Note:** To define a _Refresh_ mode test step, set the `RefreshState` field to `true`. + +```go +steps := []TestStep{ + { + Config: `resource "random_string" "puzzle" { length = 12 }`, + }, + { + RefreshState: true, + }, +} +``` + +## Steps + +`Steps` is a field within +[TestCase](/terraform/plugin/testing/acceptance-tests/testcase), the struct used +to construct acceptance tests. Each step represents a full `terraform apply` of +a given configuration language, followed by zero or more checks (defined later) +to verify the application. Each `Step` is applied in order, and require its own +configuration and optional check functions. + +Below is a code example of a lifecycle test that provides two `TestStep` structs: + +```go +package example + +// example.Widget represents a concrete Go type that represents an API resource +func TestAccExampleWidget_basic(t *testing.T) { + var widgetBefore, widgetAfter example.Widget + rName := acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckExampleResourceDestroy, + Steps: []resource.TestStep{ + { + Config: testAccExampleResource(rName), + ConfigStateChecks: []statecheck.StateCheck{ + stateCheckExampleResourceExists("example_widget.foo", &widgetBefore), + }, + }, + { + Config: testAccExampleResource_removedPolicy(rName), + ConfigStateChecks: []statecheck.StateCheck{ + stateCheckExampleResourceExists("example_widget.foo", &widgetAfter), + }, + }, + }, + }) +} +``` + +In the above example each `TestCase` invokes a function to retrieve it's desired +configuration, based on a randomized name provided, however an in-line string or +constant string would work as well, so long as they contain valid Terraform +configuration for the plugin or resource under test. This pattern of first +applying and checking a basic configuration, followed by applying a modified +configuration with updated or additional checks is a common pattern used to test +update functionality. + +## Plan Checks + +Before and after the configuration for a `TestStep` is applied, Terraform's +testing framework provides developers an opportunity to make test assertions +against `terraform plan` results via the plan file. This is provided via [Plan +Checks](/terraform/plugin/testing/acceptance-tests/plan-checks), which provide +both built-in plan checks and an interface to implement custom plan checks. + +## State Checks + +After the configuration for a `TestStep` is applied, Terraform's testing +framework provides developers an opportunity to check the results by providing +one or more [state check +implementations](/terraform/plugin/testing/acceptance-tests/state-checks). +While possible to only supply a single state check, it is recommended you use +multiple state checks to validate specific information about the results of the +`terraform apply` ran in each `TestStep`. + +Refer to the [State Checks](/terraform/plugin/testing/acceptance-tests/state-checks) +section for more information about the built-in state checks for resources, +data sources, output values, and how to write custom state checks. + +### Legacy Check function + + + +Use the new `ConfigStateChecks` attribute and [State Check implementations](/terraform/plugin/testing/acceptance-tests/state-checks) +instead of the `Check` function. + + + +The `Check` function is used to check results of a Terraform operation. The `Check` +attribute of `TestStep` is singular, so in order to include multiple checks +developers should use either `ComposeTestCheckFunc` or +`ComposeAggregateTestCheckFunc` (defined below) to group multiple check +functions, defined below: + +#### ComposeTestCheckFunc + +ComposeTestCheckFunc lets you compose multiple TestCheckFunc functions into a +single check. As a user testing their provider, this lets you decompose your +checks into smaller pieces more easily, with individual methods for checking +specific attributes. Each check is ran in the order provided, and on failure the +entire `TestCase` is stopped, and Terraform attempts to destroy any resources +created. + +Example: + +```go +Steps: []resource.TestStep{ + { + Config: testAccExampleResource(rName), + Check: resource.ComposeTestCheckFunc( + // if testAccCheckExampleResourceExists fails to find the resource, + // the parent TestStep and TestCase fail + testAccCheckExampleResourceExists("example_widget.foo", &widgetBefore), + resource.TestCheckResourceAttr("example_widget.foo", "size", "expected size"), + ), + }, +}, +``` + +#### ComposeAggregateTestCheckFunc + +ComposeAggregateTestCheckFunc lets you compose multiple TestCheckFunc functions +into a single check. It's purpose and usage is identical to +ComposeTestCheckFunc, however each check is ran in order even if a previous +check failed, collecting the errors returned from any checks and returning a +single aggregate error. The entire `TestCase` is still stopped, and Terraform +attempts to destroy any resources created. + +Example: + +```go +Steps: []resource.TestStep{ + { + Config: testAccExampleResource(rName), + Check: resource.ComposeAggregateTestCheckFunc( + // if testAccCheckExampleResourceExists fails to find the resource, + // the following TestCheckResourceAttr is still run, with any errors aggregated + testAccCheckExampleResourceExists("example_widget.foo", &widgetBefore), + resource.TestCheckResourceAttr("example_widget.foo", "active", "true"), + ), + }, +}, +``` + +#### Built-in check functions + +Terraform has several TestCheckFunc functions built in for developers to use for +common checks, such as verifying the status and value of a specific attribute in +the resulting state. Developers are encouraged to use as many as reasonable to +verify the behavior of the plugin/resource, and should combine them with the +above mentioned `ComposeTestCheckFunc` or `ComposeAggregateTestCheckFunc` +functions. + +Most builtin functions accept `name`, `key`, and/or `value` fields, derived from +the typical Terraform configuration stanzas: + +```hcl +resource "example_widget" "foo" { + active = true +} +``` + +Here the `name` represents the resource name in state (`example_widget.foo`), +the `key` represents the attribute to check (`active`), and `value` represents +the desired value to check against (`true`). In this case, an equality check +would be: + +```go +resource.TestCheckResourceAttr("example_widget.foo", "active", "true"), +``` + +The full list of functions can be seen in the [`helper/resource` package](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/helper/resource). Names for these begin with `TestCheck...` and `TestMatch...`. The most common checks for non-`TypeSet` attributes are below. + +| Function | Purpose | +| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------- | +| [`TestCheckResourceAttr(name string, key string, value string)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/helper/resource#TestCheckResourceAttr) | Value equality checks | +| [`TestMatchResourceAttr(name string, key string, regex *regexp.Regexp)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/helper/resource#TestMatchResourceAttr) | | +| Value regular expression checks | | +| [`TestCheckResourceAttrPair(nameFirst string, keyFirst string, nameSecond string, keySecond string)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/helper/resource#TestCheckResourceAttrPair) | Value equality across two attributes (usually in different resources) | +| [`TestCheckResourceAttrSet(name string, key string)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/helper/resource#TestCheckResourceAttrSet) | Passes if any value was set | +| [`TestCheckNoResourceAttr(name string, key string)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/helper/resource#TestCheckNoResourceAttr) | Passes if no value was set | + +For `TypeSet` attributes, there are some additional functions that accept a `*` placeholder in attribute keys for indexing into the set. + +| Function | Purpose | +| ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------- | +| [`TestCheckTypeSetElemAttr(name string, key string, value string)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/helper/resource#TestCheckTypeSetElemAttr) | Value is contained in set | +| [`TestCheckTypeSetElemAttrPair(nameFirst string, keyFirst string, nameSecond string, keySecond string)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/helper/resource#TestCheckTypeSetElemAttrPair) | Value is contained in set from another attribute (usually in different resources) | +| [`TestCheckTypeSetElemNestedAttrs(name string, key string, values map[string]string)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/helper/resource#TestCheckTypeSetElemNestedAttrs) | Map of values is contained in set (usually checking multiple attributes of a block) | + +All of these functions also accept the below syntax in attribute keys to enable additional behaviors. + +| Syntax | Purpose | Example | +| ----------- | --------------------------------- | ------------------------------------------------------------------------------- | +| `.{NUMBER}` | List index | `TestCheckResourceAttr("example_widget.foo", "some_block.0", "first value")` | +| `.{KEY}` | Map key | `TestCheckResourceAttr("example_widget.foo", "some_map.some_key", "map value")` | +| `.#` | Number of elements in list or set | `TestCheckResourceAttr("example_widget.foo", "some_list.#", "2")` | +| `.%` | Number of keys in map | `TestCheckResourceAttr("example_widget.foo", "some_map.%", "2")` | + +### Custom check functions + +The `Check` field of `TestStep` accepts any function of type +[TestCheckFunc](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/helper/resource#TestCheckFunc). +Developers are free to write their own `check` functions to create customized +validation functions for their plugin. Any function that matches the +`TestCheckFunc` function signature of `func(*terraform.State) error` can be used +individually, or with other `TestCheckFunc` functions with one of the above +Aggregate functions. + +It's common to write custom `TestCheckFunc` functions to validate resources were +created correctly by using SDKs directly to verify identity and properties of +resources. These functions can retrieve information by SDKs and provide the +results to other `TestCheckFunc` methods. The below example uses +`ComposeTestCheckFunc` to group a set of `TestCheckFunc` functions together. The +first function `testAccCheckExampleWidgetExists` uses the `Example` service SDK +directly, and queries it for the ID of the widget we have in state. Once found, +the result is stored into the `widget` struct declared at the beginning of the +test function. The next check function `testAccCheckExampleWidgetAttributes` +receives the updated `widget` and checks its attributes. The final check +`TestCheckResourceAttr` verifies that the same value is stored in state. + +```go +func TestAccExampleWidget_basic(t *testing.T) { + var widget example.WidgetDescription + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckExampleWidgetDestroy, + Steps: []resource.TestStep{ + { + Config: testAccExampleWidgetConfig, + Check: resource.ComposeTestCheckFunc( + testAccCheckExampleWidgetExists("example_widget.bar", &widget), + testAccCheckExampleWidgetAttributes(&widget), + resource.TestCheckResourceAttr("example_widget.bar", "active", "true"), + ), + }, + }, + }) +} + +// testAccCheckExampleWidgetAttributes verifies attributes are set correctly by +// Terraform +func testAccCheckExampleWidgetAttributes(widget *example.WidgetDescription) resource.TestCheckFunc { + return func(s *terraform.State) error { + if *widget.active != true { + return fmt.Errorf("widget is not active") + } + + return nil + } +} + +// testAccCheckExampleWidgetExists uses the Example SDK directly to retrieve +// the Widget description, and stores it in the provided +// *example.WidgetDescription +func testAccCheckExampleWidgetExists(resourceName string, widget *example.WidgetDescription) resource.TestCheckFunc { + return func(s *terraform.State) error { + // retrieve the resource by name from state + rs, ok := s.RootModule().Resources[resourceName] + if !ok { + return fmt.Errorf("Not found: %s", resourceName) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("Widget ID is not set") + } + + // retrieve the client from the test provider + client := testAccProvider.Meta().(*ExampleClient) + + response, err := client.DescribeWidgets(&example.DescribeWidgetsInput{ + WidgetIDs: []string{rs.Primary.ID}, + }) + + if err != nil { + return err + } + + // we expect only a single widget by this ID. If we find zero, or many, + // then we consider this an error + if len(response.WidgetDescriptions) != 1 || + *response.WidgetDescriptions[0].WidgetID != rs.Primary.ID { + return fmt.Errorf("Widget not found") + } + + // store the resulting widget in the *example.WidgetDescription pointer + *widget = *response.WidgetDescriptions[0] + return nil + } +} +``` + +## Sweepers + +Acceptance Testing is an essential approach to validating the implementation of a Terraform Provider. Using actual APIs to provision resources for testing can leave behind real infrastructure that costs money between tests. The reasons for these leaks can vary, regardless Terraform provides a mechanism known as [Sweepers](/terraform/plugin/testing/acceptance-tests/sweepers) to help keep the testing account clean. diff --git a/content/terraform-plugin-testing/v1.13.x/docs/plugin/testing/acceptance-tests/tfjson-paths.mdx b/content/terraform-plugin-testing/v1.13.x/docs/plugin/testing/acceptance-tests/tfjson-paths.mdx new file mode 100644 index 0000000000..a40e90f52b --- /dev/null +++ b/content/terraform-plugin-testing/v1.13.x/docs/plugin/testing/acceptance-tests/tfjson-paths.mdx @@ -0,0 +1,692 @@ +--- +page_title: 'Plugin Development - Acceptance Testing: Terraform JSON Paths' +description: >- + How to implement attribute paths in the testing module. + Attribute paths represent the location of an attribute within Terraform JSON data. +--- + +# Terraform JSON Paths + +An exact location within Terraform JSON data is referred to as a Terraform JSON or tfjson path. + +## Usage + +Example uses in the testing module include: + +- The `ExpectUnknownValue()` and `ExpectSensitiveValue()` [built-in plan checks](/terraform/plugin/testing/acceptance-tests/plan-checks#built-in-plan-checks) for specifying an attribute to make the check assertion against. + +## Concepts + +Terraform JSON Paths are designed around the underlying Go types corresponding to the Terraform JSON implementation of a schema and schema-based data. The [terraform-json](https://pkg.go.dev/github.com/hashicorp/terraform-json) library serves as the de-facto documentation for Terraform JSON data. Paths are always absolute and start from the root, or top level, of a JSON object. + +Given the tree structure of JSON objects, descriptions of paths and their steps borrow certain hierarchy terminology such as parent and child. A parent path describes a path without one or more of the final steps of a given path, or put differently, a partial path closer to the root of the object. A child path describes a path with one or more additional steps beyond a given path, or put differently, a path containing the given path but further from the root of the object. + +## Building Paths + +The `terraform-plugin-testing` module implementation for tfjson paths is in the [`tfjsonpath` package](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/tfjsonpath), with the [`tfjsonpath.Path` type](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/tfjsonpath#Path) being the main provider developer interaction point. Call the [`tfjsonpath.New()` function](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/tfjsonpath#New) with a property name at the root of the object to begin a path. + +Given the following JSON object + +```json +{ + "first_name": "John", + "last_name": "Doe", + "age": 18, + "street_address": "123 Terraform Dr.", + "phone_numbers": [ + { "mobile": "111-111-1111" }, + { "home": "222-222-2222" } + ] +} +``` + +The call to `tfjsonpath.New()` which matches the location of `first_name` string value is: + +```go +tfjsonpath.New("first_name") +``` + +Once a `tfjsonpath.Path` is started, it supports a builder pattern, which allows for chaining method calls to construct a full path. + +The path which matches the location of the string value `"222-222-222"` is: + +```go +tfjsonpath.New("phone_numbers").AtSliceIndex(1).AtMapKey("home") +``` + +The most common usage of `tfjsonpath.Path` is to specify an attribute within Terraform JSON data. When used in this way, the root of the JSON object is the same as the root of a schema. +The follow sections show how to build attribute paths for [primitive attributes](#building-attribute-paths), [aggregate attributes](#building-aggregate-type-attribute-paths), [nested attributes](#building-nested-attribute-paths), and [blocks](#building-block-paths). + +### Building Attribute Paths + +The following table shows the different [`tfjsonpath.Path` type](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/tfjsonpath#Path) methods associated with building paths for attribute implementations. Attribute types that cannot be traversed further are shown with N/A (not applicable). + +| Framework Attribute Type | SDKv2 Attribute Type | Child Path Method | +|---------------------------|----------------------|-------------------| +| `schema.BoolAttribute` | `schema.TypeBool` | N/A | +| `schema.Float32Attribute` | `schema.TypeFloat` | N/A | +| `schema.Float64Attribute` | `schema.TypeFloat` | N/A | +| `schema.Int32Attribute` | `schema.TypeInt` | N/A | +| `schema.Int64Attribute` | `schema.TypeInt` | N/A | +| `schema.ListAttribute` | `schema.TypeList` | `AtSliceIndex()` | +| `schema.MapAttribute` | `schema.TypeMap` | `AtMapKey()` | +| `schema.NumberAttribute` | N/A | N/A | +| `schema.ObjectAttribute` | N/A | `AtMapKey()` | +| `schema.SetAttribute` | `schema.TypeSet` | `AtSliceIndex()` | +| `schema.StringAttribute` | `schema.TypeString` | N/A | + + +Given this example schema with a root attribute named `example_root_attribute`: + +```go +//Terraform Plugin Framework +schema.Schema{ + Attributes: map[string]schema.Attribute{ + "example_root_attribute": schema.StringAttribute{ + Required: true, + }, + }, +} + +//Terraform Plugin SDKv2 +Schema: map[string]*schema.Schema{ + "example_root_attribute": { + Type: schema.TypeString, + Required: true, + }, +}, +``` + +And the following Terraform JSON object representation of the state: +```json +{ + "example_root_attribute": "example-value" +} +``` + +The call to `tfjsonpath.New()` which matches the location of `example_root_attribute` string value is: + +```go +tfjsonpath.New("example_root_attribute") +``` + +For blocks, the beginning of a path is similarly defined. + +Given this example schema with a root block named `example_root_block`: + +```go +//Terraform Plugin Framework +schema.Schema{ + Blocks: map[string]schema.Block{ + "example_root_block": schema.ListNestedBlock{ + NestedObject: schema.NestedBlockObject{/* ... */}, + }, + }, +} + +//Terraform Plugin SDKv2 +Schema: map[string]*schema.Schema{ + "example_root_block": { + Type: schema.TypeList, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{/* ... */}, + }, + }, +}, +``` + +And the following Terraform JSON object representation of the state: +```json +{ + "example_root_block": [ + {} + ] +} +``` + +The call to `tfjsonpath.New()` which matches the location of `example_root_block` slice value is: + +```go +tfjsonpath.New("example_root_block") +``` + +### Building Aggregate Type Attribute Paths + +Given following schema example: + +```go +//Terraform Plugin Framework +schema.Schema{ + Attributes: map[string]schema.Attribute{ + "root_map_attribute": schema.MapAttribute{ + ElementType: types.StringType, + Required: true, + }, + "root_list_attribute": schema.ListAttribute{ + ElementType: types.StringType, + Required: true, + }, + "root_set_attribute": schema.SetAttribute{ + ElementType: types.StringType, + Required: true, + }, + }, +} + +//Terraform Plugin SDKv2 +Schema: map[string]*schema.Schema{ + "root_map_attribute": { + Type: schema.TypeMap, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + Required: true, + }, + "root_list_attribute": { + Type: schema.TypeList, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + Required: true, + }, + "root_set_attribute": { + Type: schema.TypeSet, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + Required: true, + }, +}, +``` + +And the following Terraform JSON object representation of the state: +```json +{ + "root_map_attribute": { + "example-key": "map-value" + }, + "root_list_attribute": [ + "list-value1", + "list-value2" + ], + "root_set_attribute": [ + "set-value1", + "set-value2" + ] +} +``` + +The path which matches the string value associated with the map key `example-key` of the `root_map_attribute` attribute is: + +```go +tfjsonpath.New("root_map_attribute").AtMapKey("example-key") +``` + +The path which matches the string value `list-value1` in the `root_list_attribute` attribute is: + +```go +tfjsonpath.New("root_list_attribute").AtSliceIndex(0) +``` + +The path which matches the string value `set-value2` in the `root_set_attribute` attribute is: + +```go +tfjsonpath.New("root_set_attribute").AtSliceIndex(1) +``` + +Note that because Sets are unordered in Terraform, the ordering of Set elements in the Terraform JSON data is not guaranteed to be the same as the ordering in the configuration. + +### Building Nested Attribute Paths + +The following table shows the different [`tfjsonpath.Path` type](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/tfjsonpath#Path) methods associated with building paths for nested attributes. + +| Nested Attribute Type | Child Path Method(s) | +|--------------------------------|-----------------------------| +| `schema.ListNestedAttribute` | `AtSliceIndex().AtMapKey()` | +| `schema.MapNestedAttribute` | `AtMapKey().AtMapKey()` | +| `schema.SetNestedAttribute` | `AtSliceIndex().AtMapKey()` | +| `schema.SingleNestedAttribute` | `AtMapKey()` | + +Nested attributes eventually follow the same path rules as attributes at child paths, which follow the methods shown in the [Building Attribute Paths section](#building-attribute-paths). + +#### Building List Nested Attributes Paths + +An attribute that implements `schema.ListNestedAttribute` conceptually is a slice containing a map with attribute names as keys. + +Given the following schema example: + +```go +schema.Schema{ + Attributes: map[string]schema.Attribute{ + "root_list_attribute": schema.ListNestedAttribute{ + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "nested_string_attribute": schema.StringAttribute{ + Required: true, + }, + }, + }, + Required: true, + }, + }, +} +``` + +And the following Terraform JSON object representation of the state: +```json +{ + "root_list_attribute": [ + { + "nested_string_attribute": "value" + } + ] +} +``` + +The path which matches the slice associated with the `root_list_attribute` attribute is: + +```go +tfjsonpath.New("root_list_attribute") +``` + +The path which matches the first map in the slice associated with the `root_list_attribute` attribute is: + +```go +tfjsonpath.New("root_list_attribute").AtSliceIndex(0) +``` + +The path which matches the `nested_string_attribute` map key in the first map in the slice associated with `root_list_attribute` attribute is: + +```go +tfjsonpath.New("root_list_attribute").AtSliceIndex(0).AtMapKey("nested_string_attribute") +``` + +#### Building Map Nested Attributes Paths + +An attribute that implements `schema.MapNestedAttribute` conceptually is a map containing values of maps with attribute names as keys. + +Given the following schema example: + +```go +schema.Schema{ + Attributes: map[string]schema.Attribute{ + "root_map_attribute": schema.MapNestedAttribute{ + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "nested_string_attribute": schema.StringAttribute{ + Required: true, + }, + }, + }, + Required: true, + }, + }, +} +``` + +And the following Terraform JSON object representation of the state: +```json +{ + "root_map_attribute": { + "example-key" : { + "nested_string_attribute": "value" + } + } +} +``` + +The path which matches the map associated with the `root_map_attribute` attribute is: + +```go +tfjsonpath.New("root_map_attribute") +``` + +The path which matches the `"example-key"` object in the map associated with the `root_map_attribute` attribute is: + +```go +tfjsonpath.New("root_map_attribute").AtMapKey("example-key") +``` + +The path which matches the `nested_string_attribute` string value in a `"example-key"` object in the map associated with `root_map_attribute` attribute is: + +```go +tfjsonpath.New("root_map_attribute").AtMapKey("example-key").AtMapKey("nested_string_attribute") +``` + +#### Building Set Nested Attributes Paths + +An attribute that implements `schema.SetNestedAttribute` conceptually is a slice containing maps with attribute names as keys. + +Given the following schema example: + +```go +schema.Schema{ + Attributes: map[string]schema.Attribute{ + "root_set_attribute": schema.SetNestedAttribute{ + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "nested_string_attribute": schema.StringAttribute{ + Required: true, + }, + }, + }, + Required: true, + }, + }, +} +``` + +And the following Terraform JSON object representation of the state: +```json +{ + "root_set_attribute": [ + { + "nested_string_attribute": "value" + } + ] +} +``` + +The path which matches the set associated with the `root_set_attribute` attribute is: + +```go +tfjsonpath.New("root_set_attribute") +``` + +The path which matches the first map in the slice associated with the `root_set_attribute` attribute is: + +```go +tfjsonpath.New("root_set_attribute").AtSliceIndex(0) +``` + +Note that because Sets are unordered in Terraform, the ordering of Set elements in the Terraform JSON data is not guaranteed to be the same as the ordering in the configuration. + +The path which matches the `nested_string_attribute` map key in the first map in the slice associated with `root_set_attribute` attribute is: + +```go +tfjsonpath.New("root_set_attribute").AtSliceIndex(0).AtMapKey("nested_string_attribute") +``` + +#### Building Single Nested Attributes Paths + +An attribute that implements `schema.SingleNestedAttribute` conceptually is a map with attribute names as keys. + +Given the following schema example: + +```go +schema.Schema{ + Attributes: map[string]schema.Attribute{ + "root_grouped_attributes": schema.SingleNestedAttribute{ + Attributes: map[string]schema.Attribute{ + "nested_string_attribute": schema.StringAttribute{ + Required: true, + }, + }, + Required: true, + }, + }, +} +``` + +And the following Terraform JSON object representation of the state: +```json +{ + "root_grouped_attributes": { + "nested_string_attribute": "value" + } +} +``` + +The path which matches the map associated with the `root_grouped_attributes` attribute is: + +```go +tfjsonpath.New("root_grouped_attributes") +``` + +The path which matches the `nested_string_attribute` string value in the map associated with the `root_grouped_attributes` attribute is: + +```go +tfjsonpath.New("root_grouped_attributes").AtMapKey("nested_string_attribute") +``` + +### Building Block Paths + +The following table shows the different [`tfjsonpath.Path` type](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/tfjsonpath#Path) methods associated with building paths for blocks. + +| Block Type | Child Path Method(s) | +|---------------------|-----------------------------| +| `ListNestedBlock` | `AtSliceIndex().AtMapKey()` | +| `SetNestedBlock` | `AtSliceIndex().AtMapKey()` | +| `SingleNestedBlock` | `AtMapKey()` | + +Blocks can implement nested blocks. Paths can continue to be built using the associated method with each level of the block type. + +Blocks eventually follow the same path rules as attributes at child paths, which follow the methods shown in the [Building Attribute Paths section](#building-attribute-paths). Blocks cannot contain nested attributes. + +#### Building List Block Paths + +A `ListNestedBlock` conceptually is a slice containing maps with attribute or block names as keys. + +Given following schema example: + +```go +//Terraform Plugin Framework +schema.Schema{ + Blocks: map[string]schema.Block{ + "root_list_block": schema.ListNestedBlock{ + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "block_string_attribute": schema.StringAttribute{ + Required: true, + }, + }, + Blocks: map[string]schema.Block{ + "nested_list_block": schema.ListNestedBlock{ + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "nested_block_string_attribute": schema.StringAttribute{ + Required: true, + }, + }, + }, + }, + }, + }, + }, + }, +} + +//Terraform Plugin SDKv2 +Schema: map[string]*schema.Schema{ + "root_list_block": { + Type: schema.TypeList, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "block_string_attribute": { + Type: schema.TypeString, + Required: true, + }, + "nested_list_block": { + Type: schema.TypeList, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "nested_block_string_attribute": { + Type: schema.TypeString, + Required: true, + }, + }, + }, + }, + }, + }, + }, +}, +``` + +And the following Terraform JSON object representation of the state: +```json +{ + "root_list_block": [ + { + "block_string_attribute": "value1", + "nested_list_block": [ + {"nested_block_string_attribute": "value2"} + ] + } + ] +} +``` + +The path which matches the slice associated with the `root_list_block` block is: + +```go +tfjsonpath.New("root_list_block") +``` + +The path which matches the first map in the slice associated with the `root_list_block` block is: + +```go +tfjsonpath.New("root_list_block").AtSliceIndex(0) +``` + +The path which matches the `block_string_attribute` string value in the first map in the slice associated with `root_list_block` block is: + +```go +tfjsonpath.New("root_list_block").AtSliceIndex(0).AtMapKey("block_string_attribute") +``` + +The path which matches the `nested_list_block` slice in the first object in the slice associated with `root_list_block` block is: + +```go +tfjsonpath.New("root_list_block").AtSliceIndex(0).AtMapKey("nested_list_block") +``` + +The path which matches the `nested_block_string_attribute` string value in the first map in the slice associated with the `nested_list_block` slice in the first map in the slice associated with `root_list_block` block is: + +```go +tfjsonpath.New("root_list_block").AtSliceIndex(0).AtMapKey("nested_list_block").AtSliceIndex(0).AtMapKey("nested_block_string_attribute") +``` + +#### Building Set Block Paths + +A `SetNestedBlock` conceptually is a slice containing maps with attribute or block names as keys. + +Given following schema example: + +```go +//Terraform Plugin Framework +schema.Schema{ + Blocks: map[string]schema.Block{ + "root_set_block": schema.SetNestedBlock{ + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "block_string_attribute": schema.StringAttribute{ + Optional: true, + }, + }, + }, + }, + }, +} + +//Terraform Plugin SDKv2 +Schema: map[string]*schema.Schema{ + "root_set_block": { + Type: schema.TypeSet, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "block_string_attribute": { + Type: schema.TypeString, + Optional: true, + }, + }, + }, + }, +}, +``` + +And the following Terraform JSON object representation of the state: +```json +{ + "root_set_block": [ + { + "block_string_attribute": "value1" + } + ] +} +``` + +The path which matches the slice associated with the `root_set_block` block is: + +```go +tfjsonpath.New("root_set_block") +``` + + +The path which matches the first map in the slice associated with the `root_set_block` block is: + +```go +tfjsonpath.New("root_set_block").AtSliceIndex(0) +``` + +Note that because sets are unordered in Terraform, the ordering of set elements in the Terraform JSON data is not guaranteed to be the same as the ordering in the configuration. + +The path which matches the `block_string_attribute` string value in the first map in the slice associated with `root_set_block` block is: + +```go +tfjsonpath.New("root_set_block").AtSliceIndex(0).AtMapKey("block_string_attribute") +```` + +#### Building Single Block Paths + +A `SingleNestedBlock` conceptually is a map with attribute or block names as keys. + +Given following schema example: + +```go +//Terraform Plugin Framework +schema.Schema{ + Blocks: map[string]schema.Block{ + "root_single_block": schema.SingleNestedBlock{ + Attributes: map[string]schema.Attribute{ + "block_string_attribute": schema.StringAttribute{ + Required: true, + }, + }, + Blocks: map[string]schema.Block{ + "nested_single_block": schema.SingleNestedBlock{ + Attributes: map[string]schema.Attribute{ + "nested_block_string_attribute": schema.StringAttribute{ + Required: true, + }, + }, + }, + }, + }, + }, +} +``` + +The path which matches the map associated with the `root_single_block` block is: + +```go +tfjsonpath.New("root_single_block") +``` + +The path which matches the `block_string_attribute` string value in the map associated with `root_single_block` block is: + +```go +tfjsonpath.New("root_single_block").AtMapKey("block_string_attribute") +``` + +The path which matches the `nested_single_block` map in the map associated with `root_single_block` block is: + +```go +tfjsonpath.New("root_single_block").AtMapKey("nested_single_block") +``` + +The path which matches the `nested_block_string_attribute` string value in the map associated with the `nested_single_block` in the map associated with `root_single_block` block is: + +```go +tfjsonpath.New("root_single_block").AtMapKey("nested_single_block").AtMapKey("nested_block_string_attribute") +``` diff --git a/content/terraform-plugin-testing/v1.13.x/docs/plugin/testing/acceptance-tests/tfversion-checks.mdx b/content/terraform-plugin-testing/v1.13.x/docs/plugin/testing/acceptance-tests/tfversion-checks.mdx new file mode 100644 index 0000000000..3c8efd4f03 --- /dev/null +++ b/content/terraform-plugin-testing/v1.13.x/docs/plugin/testing/acceptance-tests/tfversion-checks.mdx @@ -0,0 +1,267 @@ +--- +page_title: 'Plugin Development - Acceptance Testing: Terraform Version Checks' +description: >- + Terraform Version Checks are generic checks defined at the TestCase level that check logic against the Terraform CLI version. The testing module + provides built-in Version Checks for common use-cases, but custom Version Checks can also be implemented. +--- + +# Terraform Version Checks + +**Terraform Version Checks** are generic checks defined at the TestCase level that check logic against the Terraform CLI version. The checks are executed at the beginning of the TestCase before any TestStep is executed. + +The Terraform CLI version is determined by the binary selected by the [`TF_ACC_TERRAFORM_PATH`](/terraform/plugin/testing/acceptance-tests#environment-variables) environment variable value, installed by the [`TF_ACC_TERRAFORM_VERSION`](/terraform/plugin/testing/acceptance-tests#environment-variables) value, or already existing based on the `PATH` environment variable. + +A **version check** will either return an error and fail the associated test, return a skip message and pass the associated test immediately by skipping, or it will return nothing and allow the associated test to run. + +Terraform CLI prerelease versions include a `-alphaYYYYMMDD`, `-beta#`, or `rc#` (release candidate) suffix for a minor version with `0` patch version. For example, `1.8.0-rc1`. Prereleases of Terraform are considered semantically equivalent to the associated minor version since prereleases are when any new features are introduced. + +## Built-in Version Checks and Variables + +The `terraform-plugin-testing` module provides a package [`tfversion`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/tfversion) with built-in version checks for common use-cases. There are three types of version checks: Skip Checks, Require Checks, and Collection Checks. + + + +Built-in version checks handle prereleases of a minor version as semantically equivalent to given minor versions. For example, if the test includes `tfversion.SkipBelow(tfversion.Version1_8_0)` and the running Terraform CLI version is `1.8.0-rc1`, the test will run, not skip. This is intended to enable prerelease testing of new features. + + + +### Version Variables + +The built-in checks in the `tfversion` package typically require the use of the [`github.com/hashicorp/go-version`](https://pkg.go.dev/github.com/hashicorp/go-version) module [`version.Version`](https://pkg.go.dev/github.com/hashicorp/go-version#Version) type. To simplify provider testing implementations, the `tfversion` package provides [built-in variables](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/tfversion#pkg-variables) for common use case versions, such as each released minor and major Terraform version. These follow the pattern of `Version{MAJOR}_{MINOR}_{PATCH}` with the major, minor, and patch version numbers, such as `Version1_2_0`. + +### Skip Version Checks + +Skip Version Checks will pass the associated test by skipping and provide a skip message if the detected Terraform CLI version satisfies the specified check criteria. + +| Check | Description | +|---------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------| +| [`tfversion.SkipAbove(maximumVersion)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/tfversion#SkipAbove) | Skips the test if the Terraform CLI version is exclusively above the given maximum. | +| [`tfversion.SkipBelow(minimumVersion)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/tfversion#SkipBelow) | Skips the test if the Terraform CLI version is exclusively below the given minimum. | +| [`tfversion.SkipBetween(minimumVersion, maximumVersion)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/tfversion#SkipBetween) | Skips the test if the Terraform CLI version is between the given minimum (inclusive) and maximum (exclusive). | +| [`tfversion.SkipIf(version)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/tfversion#SkipIf) | Skips the test if the Terraform CLI version matches the given version. | + +#### Example using `tfversion.SkipBetween` + +The built-in [`tfversion.SkipBetween`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/tfversion#SkipBetween) version check is useful for skipping all patch versions associated with a minor version. + +In the following example, we have written a test that skips all Terraform CLI patch versions associated with 0.14.0: + +```go +package example_test + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-go/tfprotov6" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/tfversion" +) + +func Test_Skip_TF14(t *testing.T) { + t.Parallel() + + resource.UnitTest(t, resource.TestCase{ + ProtoV6ProviderFactories: map[string]func() (tfprotov6.ProviderServer, error){ + "test": func() (tfprotov6.ProviderServer, error) { + return nil, nil + }, + }, + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBetween(tfversion.Version0_14_0, tfversion.Version0_15_0), + }, + Steps: []resource.TestStep{ + { + Config: `//example test config`, + }, + }, + }) +} +``` + +### Require Version Checks + +Require Version Checks will raise an error and fail the associated test if the detected Terraform CLI version does not satisfy the specified check requirements. + +| Check | Description | +|---------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------| +| [`tfversion.RequireAbove(minimumVersion)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/tfversion#RequireAbove) | Fails the test if the Terraform CLI version is exclusively below the given minimum. | +| [`tfversion.RequireBelow(maximumVersion)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/tfversion#RequireBelow) | Fails the test if the Terraform CLI version is inclusively above the given maximum. | +| [`tfversion.RequireBetween(minimumVersion, maximumVersion)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/tfversion#RequireBetween) | Fails the test if the Terraform CLI version is outside the given minimum (exclusive) and maximum (inclusive). | +| [`tfversion.RequireNot(version)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/tfversion#RequireNot) | Fails the test if the Terraform CLI version matches the given version. | + + +#### Example using `tfversion.RequireAbove` + +The built-in [`tfversion.RequireAbove`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/tfversion#RequireAbove) version check is useful for required tests that may use features only available in newer versions of the Terraform ClI. + +In the following example, the test Terraform configuration uses the `nullable` argument for an input variable, a feature that is only available in Terraform CLI versions `1.3.0` and above. The version check will fail the test with a specific error if the detected version is below `1.3.0`. + +```go +package example_test + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-go/tfprotov6" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/tfversion" +) + +func Test_Require_TF1_3(t *testing.T) { + t.Parallel() + + resource.UnitTest(t, resource.TestCase{ + ProtoV6ProviderFactories: map[string]func() (tfprotov6.ProviderServer, error){ + "test": func() (tfprotov6.ProviderServer, error) { + return nil, nil + }, + }, + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.RequireAbove(tfversion.Version1_3_0), + }, + Steps: []resource.TestStep{ + { + Config: `variable "a" { + nullable = true + default = "hello" + }`, + }, + }, + }) +} +``` + +### Collection Version Checks + +Collection Version Checks operate on multiple version checks and can be used to create more complex checks. + +[`tfversion.Any(TerraformVersionChecks...)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/tfversion#Any) will run the associated test by returning a nil error and empty skip message +if any of the given version sub-checks return a nil error and empty skip message. If none of the sub-checks return a nil error and empty skip message, then the check will return all sub-check errors and fail the associated test. +Otherwise, if none of the sub-checks return a non-nil error, the check will pass the associated test by skipping and return all sub-check skip messages. + +[`tfversion.All(TerraformVersionChecks...)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/tfversion#All) will either fail or skip the associated test if any of the given sub-checks return a non-nil error or non-empty skip message. The check will return the +first non-nil error or non-empty skip message from the given sub-checks in the order that they are given. Otherwise, if all sub-checks return a nil error and empty skip message, then the check will return a nil error and empty skip message and run the associated test. This check should only be +used in conjunction with `tfversion.Any()` as the behavior provided by this check is applied to the `TerraformVersionChecks` field by default. + +#### Example using `tfversion.Any` + +In the following example, the test will only run if either the Terraform CLI version is above `1.2.0` or if it's below `1.0.0` but not version `0.15.0`, otherwise an error will be returned. + +```go +package example_test + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-go/tfprotov6" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/tfversion" +) + +func Test_Any(t *testing.T) { + t.Parallel() + + resource.UnitTest(t, resource.TestCase{ + ProtoV6ProviderFactories: map[string]func() (tfprotov6.ProviderServer, error){ + "test": func() (tfprotov6.ProviderServer, error) { //nolint:unparam // required signature + return nil, nil + }, + }, + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.Any( + tfversion.All( + tfversion.RequireNot(tfversion.Version0_15_0), + tfversion.RequireBelow(tfversion.Version1_0_0), + ), + tfversion.RequireAbove(tfversion.Version1_2_0), + ), + }, + Steps: []resource.TestStep{ + { + Config: `//example test config`, + }, + }, + }) +} +``` + + +## Custom Version Checks + +The package [`tfversion`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/tfversion) also provides the [`TerraformVersionCheck`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/tfversion#TerraformVersionCheck) interface, which can be implemented for a custom version check. + +The [`tfversion.CheckTerraformVersionRequest`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/tfversion#CheckTerraformVersionRequest) has a `TerraformVersion` field of type [`*version.Version`](https://pkg.go.dev/github.com/hashicorp/go-version#Version) which contains the version of the Terraform CLI binary running the test. + +The [`tfversion.CheckTerraformVersionResponse`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/tfversion#CheckTerraformVersionResponse) has an `Error` field and a `Skip` field. The behavior of the version check depends on which field is populated. Populating the `Error` field will fail the associated test with the given error. +Populating the `Skip` field will pass the associated test by skipping the test with the given skip message. Only one of these fields should be populated. + +Here is an example implementation of a version check returns an error if the detected Terraform CLI version matches the given version: + +```go +package example_test + +import ( + "context" + "fmt" + + "github.com/hashicorp/go-version" +) + +// Ensure implementation satisfies the tfversion.TerraformVersionCheck interface. +var _ tfversion.TerraformVersionCheck = requireNotCheck{} + +// RequireNot will fail the test if the given version matches. +func RequireNot(v *version.Version) tfversion.TerraformVersionCheck { + return requireNotCheck{ + version: v, + } +} + +type requireNotCheck struct { + version *version.Version +} + +func (s requireNotCheck) CheckTerraformVersion(ctx context.Context, req tfversion.CheckTerraformVersionRequest, resp *tfversion.CheckTerraformVersionResponse) { + if req.TerraformVersion.Equal(s.version) { + resp.Error = fmt.Errorf("unexpected Terraform CLI version: %s", s.version) + } +} +``` + +And example usage: + +```go +package example_test + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-go/tfprotov6" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/tfversion" +) + +func Test_RequireNot(t *testing.T) { + t.Parallel() + + resource.UnitTest(t, resource.TestCase{ + ProtoV6ProviderFactories: map[string]func() (tfprotov6.ProviderServer, error){ + "test": func() (tfprotov6.ProviderServer, error) { + return nil, nil + }, + }, + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.RequireNot(tfversion.Version0_13_0), + }, + Steps: []resource.TestStep{ + { + Config: `//example test config`, + }, + }, + }) +} +``` diff --git a/content/terraform-plugin-testing/v1.13.x/docs/plugin/testing/acceptance-tests/value-comparers/index.mdx b/content/terraform-plugin-testing/v1.13.x/docs/plugin/testing/acceptance-tests/value-comparers/index.mdx new file mode 100644 index 0000000000..3ef5e19d6a --- /dev/null +++ b/content/terraform-plugin-testing/v1.13.x/docs/plugin/testing/acceptance-tests/value-comparers/index.mdx @@ -0,0 +1,183 @@ +--- +page_title: 'Plugin Development - Acceptance Testing: Value Comparers' +description: >- + How to use value comparers in the testing module. + Value comparers define a comparison for a resource attribute, or output value for use in State Checks. +--- + +# Value Comparers + + + +Value Comparers are for use in conjunction with [State Checks](/terraform/plugin/testing/acceptance-tests/state-checks), which leverage the [terraform-json](https://pkg.go.dev/github.com/hashicorp/terraform-json) representation of Terraform state. + + + +Value comparers can be used to assert a resource or data source attribute value across multiple [Test Steps](/terraform/plugin/testing/acceptance-tests/teststep), like asserting that a randomly generated resource attribute doesn't change after multiple apply steps. This is done by creating the value comparer, typically before the test case is defined, using the relevant constructor function: +```go +func TestExample(t *testing.T) { + // Create the value comparer so we can add state values to it during the test steps + compareValuesDiffer := statecheck.CompareValue(compare.ValuesDiffer()) + + resource.Test(t, resource.TestCase{ + // Provider definition omitted. + Steps: []resource.TestStep{ + // .. test steps omitted + }, + }) +} +``` + +Once the value comparer is created, state values can be added in `TestStep.ConfigStateChecks`: +```go +func TestExample(t *testing.T) { + // Create the value comparer so we can add state values to it during the test steps + compareValuesDiffer := statecheck.CompareValue(compare.ValuesDiffer()) + + resource.Test(t, resource.TestCase{ + // Provider definition omitted. + Steps: []resource.TestStep{ + { + Config: `resource "test_resource" "one" {}`, + ConfigStateChecks: []statecheck.StateCheck{ + // Add the current state value of "computed_attribute" to the value comparer. + // Since there are no other state values at this point, no assertion is made. + compareValuesDiffer.AddStateValue( + "test_resource.one", + tfjsonpath.New("computed_attribute"), + ), + }, + }, + { + Config: `resource "test_resource" "one" {}`, + ConfigStateChecks: []statecheck.StateCheck{ + // Add the current state value of "computed_attribute" to the value comparer. + // Since there is an existing state value in the value comparer at this point, + // if the two values are equal, the test will produce an error. + compareValuesDiffer.AddStateValue( + "test_resource.one", + tfjsonpath.New("computed_attribute"), + ), + }, + }, + }, + }) +} +``` + +The value comparer implementation (defined by the [`ValueComparer` interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/compare#ValueComparer)) determines what assertion occurs when a state value is added. The built-in value comparers are: +- [`CompareValue()`](/terraform/plugin/testing/acceptance-tests/state-checks/resource#comparevalue-state-check) +- [`CompareValueCollection()`](/terraform/plugin/testing/acceptance-tests/state-checks/resource#comparevaluecollection-state-check) +- [`CompareValuePairs()`](/terraform/plugin/testing/acceptance-tests/state-checks/resource#comparevaluepairs-state-check) + +## Values Differ + +The [ValuesDiffer](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/compare#ValuesDiffer) value comparer verifies that each value in the sequence of values supplied to the `CompareValues()` method differs from the preceding value. + +Example usage of [ValuesDiffer](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/compare#ValuesDiffer) in a [CompareValue](/terraform/plugin/testing/acceptance-tests/state-checks/resource#comparevalue-state-check) state check. + +```go +package example_test + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + + "github.com/hashicorp/terraform-plugin-testing/compare" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/statecheck" + "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" +) + +func TestCompareValue_CheckState_ValuesDiffer(t *testing.T) { + t.Parallel() + + compareValuesDiffer := statecheck.CompareValue(compare.ValuesDiffer()) + + resource.Test(t, resource.TestCase{ + // Provider definition omitted. + Steps: []resource.TestStep{ + { + Config: `resource "test_resource" "one" {}`, + ConfigStateChecks: []statecheck.StateCheck{ + // Add the current state value of "computed_attribute" to the value comparer. + // Since there are no other state values at this point, no assertion is made. + compareValuesDiffer.AddStateValue( + "test_resource.one", + tfjsonpath.New("computed_attribute"), + ), + }, + }, + { + // Add the current state value of "computed_attribute" to the value comparer. + // Since there is an existing state value in the value comparer at this point, + // if the two values are equal, the test will produce an error. + Config: `resource "test_resource" "one" {}`, + ConfigStateChecks: []statecheck.StateCheck{ + compareValuesDiffer.AddStateValue( + "test_resource.one", + tfjsonpath.New("computed_attribute"), + ), + }, + }, + }, + }) +} +``` + +## Values Same + +The [ValuesSame](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/compare#ValuesSame) value comparer verifies that each value in the sequence of values supplied to the `CompareValues()` method is the same as the preceding value. + +Example usage of [ValuesSame](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/compare#ValuesSame) in a [CompareValue](/terraform/plugin/testing/acceptance-tests/state-checks/resource#comparevalue-state-check) state check. + +```go +package example_test + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + + "github.com/hashicorp/terraform-plugin-testing/compare" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/statecheck" + "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" +) + +func TestCompareValue_CheckState_ValuesSame(t *testing.T) { + t.Parallel() + + compareValuesSame := statecheck.CompareValue(compare.ValuesSame()) + + resource.Test(t, resource.TestCase{ + // Provider definition omitted. + Steps: []resource.TestStep{ + { + Config: `resource "test_resource" "one" {}`, + ConfigStateChecks: []statecheck.StateCheck{ + // Add the current state value of "computed_attribute" to the value comparer. + // Since there are no other state values at this point, no assertion is made. + compareValuesSame.AddStateValue( + "test_resource.one", + tfjsonpath.New("computed_attribute"), + ), + }, + }, + { + Config: `resource "test_resource" "one" {}`, + ConfigStateChecks: []statecheck.StateCheck{ + // Add the current state value of "computed_attribute" to the value comparer. + // Since there is an existing state value in the value comparer at this point, + // if the two values are not equal, the test will produce an error. + compareValuesSame.AddStateValue( + "test_resource.one", + tfjsonpath.New("computed_attribute"), + ), + }, + }, + }, + }) +} +``` diff --git a/content/terraform-plugin-testing/v1.13.x/docs/plugin/testing/index.mdx b/content/terraform-plugin-testing/v1.13.x/docs/plugin/testing/index.mdx new file mode 100644 index 0000000000..2ff339fb32 --- /dev/null +++ b/content/terraform-plugin-testing/v1.13.x/docs/plugin/testing/index.mdx @@ -0,0 +1,55 @@ +--- +page_title: Plugin Development - Testing +description: |- + Write acceptance tests and unit tests with the terraform-plugin-testing Go + module. +--- + +# Plugin Testing + +The testing framework for Terraform providers,`terraform-plugin-testing`, +uses standard Go features such as the `go test` command and the standard +library `testing` package. In addition to this documentation, refer to the +[generated Go +documentation](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing). + +This section introduces strategies for testing provider functionality with +acceptance tests and unit tests. + +## Acceptance Tests + +Acceptance tests for Terraform providers are a feature of the +[`terraform-plugin-testing`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing) +framework. The testing framework uses standard Go features such as the `go +test` command. It runs a local Terraform binary to perform real plan, apply, +refresh, and destroy operations, and enables developers to make assertions +about what happens during those actions. + +Refer to [Acceptance Testing](/terraform/plugin/testing/acceptance-tests) to learn more. + +## Unit Tests + +Testing plugin code in small, isolated units is distinct from Acceptance Tests, +and does not require network connections. Unit tests are commonly used for +testing helper methods that expand or flatten API response data into data +structures for storage into state by Terraform. This section covers the +specifics of writing Unit Tests for Terraform Plugin code. + +For a given plugin, Unit Tests can be run from the root of the project by using +a common make task: + +```shell +$ make test +``` + +Refer to [Unit Testing](/terraform/plugin/testing/unit-testing) to learn more. + +## Testing Patterns + +Terraform developers are encouraged to write acceptance tests that create real +resource to verify the behavior of plugins, ensuring a reliable and safe way to +manage infrastructure. In [Testing +Patterns](/terraform/plugin/testing/testing-patterns) we cover some basic +acceptance tests that almost all resources should have to validate not only the +functionality of the resource, but that the resource behaves as Terraform would +expect. diff --git a/content/terraform-plugin-testing/v1.13.x/docs/plugin/testing/migrating.mdx b/content/terraform-plugin-testing/v1.13.x/docs/plugin/testing/migrating.mdx new file mode 100644 index 0000000000..51de606104 --- /dev/null +++ b/content/terraform-plugin-testing/v1.13.x/docs/plugin/testing/migrating.mdx @@ -0,0 +1,133 @@ +--- +page_title: 'Plugin Development: Migrating testing from SDKv2 to the testing module' +description: >- + Migrate your provider's acceptance testing dependencies from SDKv2 to the testing module. +--- + +# Overview + +This guide helps you migrate a Terraform provider's acceptance testing dependencies from SDKv2 to the plugin testing module. We recommend migrating to terraform-plugin-testing to take advantage of new features of the testing module and to avoid importing the SDKv2 for providers that are built on the plugin Framework. + +This guide provides information and examples for most common use cases, but it does not discuss every nuance of migration. You can ask additional migration questions in the [HashiCorp Discuss forum](https://discuss.hashicorp.com/c/terraform-providers/tf-plugin-sdk/43). To request additions or updates to this guide, submit issues or pull requests to the [`terraform-plugin-testing` repository](https://github.com/hashicorp/terraform-plugin-testing). + +## Migration steps + +Take the following steps when you migrate a provider's acceptance tests from SDKv2 to the testing module. + +Change all instances of the following Go import statements in `*_test.go` files: + +| Original Import | Migrated Import | +|---|---| +| `github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest` | `github.com/hashicorp/terraform-plugin-testing/helper/acctest` | +| `github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource` | `github.com/hashicorp/terraform-plugin-testing/helper/resource` | +| `github.com/hashicorp/terraform-plugin-sdk/v2/terraform` | `github.com/hashicorp/terraform-plugin-testing/terraform` | + +If the provider implements terraform-plugin-sdk based state migration unit testing with `github.com/hashicorp/terraform-plugin-sdk/v2/terraform.InstanceState`, this must remain with the original import since it is testing terraform-plugin-sdk functionality. + +Verify if the `TestStep` type `PlanOnly` field is enabled in any tests where the final `TestStep` is intentionally changing the provider setup to ensure schema changes (e.g. state upgrades or SDK to framework migrations) cause no plan differences. In those tests, replace `PlanOnly` with `ConfigPlanChecks` containing a `PreApply` check of `plancheck.ExpectEmptyPlan()` instead: + +```go +resource.Test(t, resource.TestCase{ + // ... + Steps: []resource.TestStep{ + { /* ... */ }, + { + // ... + // The below replacing PlanOnly: true + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectEmptyPlan(), + }, + }, + }, + }, +}) +``` + +Change all instances of the following in **non-test** `*.go` files: + +| Original Reference | Migrated Reference | +|---|---| +| `github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource.NonRetryableError` | `github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry.NonRetryableError` | +| `github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource.NotFoundError` | `github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry.NotFoundError` | +| `github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource.PrefixedUniqueId` | `github.com/hashicorp/terraform-plugin-sdk/v2/helper/id.PrefixedUniqueId` | +| `github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource.Retry` | `github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry.Retry` | +| `github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource.RetryableError` | `github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry.RetryableError` | +| `github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource.RetryContext` | `github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry.RetryContext` | +| `github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource.RetryError` | `github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry.RetryError` | +| `github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource.RetryFunc` | `github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry.RetryFunc` | +| `github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource.StateChangeConf` | `github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry.StateChangeConf` | +| `github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource.StateRefreshFunc` | `github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry.StateRefreshFunc` | +| `github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource.TimeoutError` | `github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry.TimeoutError` | +| `github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource.UnexpectedStateError` | `github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry.UnexpectedStateError` | +| `github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource.UniqueId` | `github.com/hashicorp/terraform-plugin-sdk/v2/helper/id.UniqueId` | +| `github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource.UniqueIdPrefix` | `github.com/hashicorp/terraform-plugin-sdk/v2/helper/id.UniqueIdPrefix` | +| `github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource.UniqueIDSuffixLength` | `github.com/hashicorp/terraform-plugin-sdk/v2/helper/id.UniqueIDSuffixLength` | + +Get and download the latest version of terraform-plugin-testing: + +```shell +$ go get github.com/hashicorp/terraform-plugin-testing@latest +``` + +Clean up `go.mod`: + +```shell +$ go mod tidy +``` + +Verify that the tests are working as expected. + +## Troubleshooting + +### flag redefined Panic + +This panic occurs when your provider code imports both the `github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource` and `github.com/hashicorp/terraform-plugin-testing/helper/resource` packages because they contain a duplicate `TestMain` function: + +```text +panic: XXX flag redefined: sweep + +goroutine 1 [running]: +flag.(*FlagSet).Var(0x14000030240, {0x10132b6d8, 0x140002219c0}, {0x10103ad88, 0x5}, {0x10105d47b, 0x29}) + /usr/local/go/src/flag/flag.go:982 +0x2a4 +flag.(*FlagSet).StringVar(...) + /usr/local/go/src/flag/flag.go:847 +flag.(*FlagSet).String(0x1400031fb98?, {0x10103ad88, 0x5}, {0x0, 0x0}, {0x10105d47b, 0x29}) + /usr/local/go/src/flag/flag.go:860 +0x98 +flag.String(...) + /usr/local/go/src/flag/flag.go:867 +github.com/hashicorp/terraform-plugin-testing/helper/resource.init() + /XXX/go/pkg/mod/github.com/hashicorp/terraform-plugin-testing@v1.1.0/helper/resource/testing.go:53 +0x44 +``` + +Remove imports of `github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource` to resolve the issue. terraform-plugin-sdk version 2.26.0 introduced separate packages, [`github.com/hashicorp/terraform-plugin-sdk/v2/helper/id`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-sdk/v2/helper/id) and [`github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry), which contain all non-testing functionality. + +### Failed to marshal state to json + +This error can occur when your testing includes `PlanOnly: true` in final `TestStep` that is intentionally changing the provider setup to ensure schema changes (e.g. state upgrades or SDK to framework migrations) cause no plan differences: + +```text +Failed to marshal state to json: schema version 0 for examplecloud_thing.test in state does not match version 1 from the provider +# or in the case of removed attributes between provider versions: +Failed to marshal state to json: unsupported attribute +``` + +In those tests, replace `PlanOnly` with `ConfigPlanChecks` containing a `PreApply` check of `plancheck.ExpectEmptyPlan()` instead: + +```go +resource.Test(t, resource.TestCase{ + // ... + Steps: []resource.TestStep{ + { /* ... at least one prior step ... */ }, + { + // ... + // Replacing PlanOnly: true + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectEmptyPlan(), + }, + }, + }, + }, +}) +``` diff --git a/content/terraform-plugin-testing/v1.13.x/docs/plugin/testing/testing-patterns.mdx b/content/terraform-plugin-testing/v1.13.x/docs/plugin/testing/testing-patterns.mdx new file mode 100644 index 0000000000..ab2bb4c781 --- /dev/null +++ b/content/terraform-plugin-testing/v1.13.x/docs/plugin/testing/testing-patterns.mdx @@ -0,0 +1,392 @@ +--- +page_title: Plugin Development - Testing Patterns +description: |- + Testing Patterns covers essential acceptance test patterns to implement for + Terraform resources. +--- + +# Testing Patterns + +In [Testing Terraform Plugins][1] we introduce Terraform's Testing Framework, +providing reference for its functionality and introducing the basic parts of +writing acceptance tests. In this section we'll cover some test patterns that +are common and considered a best practice to have when developing and verifying +your Terraform plugins. At time of writing these guides are particular to +Terraform Resources, but other testing best practices may be added later. + +## Table of Contents + +- [Built-in Patterns](#built-in-patterns) +- [Basic test to verify attributes](#basic-test-to-verify-attributes) +- [Update test verify configuration changes](#update-test-verify-configuration-changes) +- [Import mode: verify that import and create produce the same state](#import-mode-verify-that-import-and-create-produce-the-same-state) +- [Expecting errors or non-empty plans](#expecting-errors-or-non-empty-plans) +- [Regression tests](#regression-tests) + +## Built-in Patterns + +Acceptance tests use [TestCases][2] to construct scenarios that can be evaluated +with Terraform's lifecycle of plan, apply, refresh, and destroy. The test +framework has some behaviors built in that provide very basic workflow assurance +tests, such as verifying configurations apply with no diff generated by the next +plan. + +Each TestCase will run any [PreCheck][3] function provided before running the +test, and then any [CheckDestroy][4] functions after the test concludes. These +functions allow developers to verify the state of the resource and test before +and after it runs. + +When a test is ran, Terraform runs plan, apply, refresh, and then final plan for +each [TestStep][5] in the TestCase. If the last plan results in a non-empty +plan, Terraform will exit with an error. This enables developers to ensure that +configurations apply cleanly. In the case of introducing regression tests or +otherwise testing specific error behavior, TestStep offers a boolean field +[ExpectNonEmptyPlan][6] as well [ExpectError][7] regex field to specify ways the +test framework can handle expected failures. If these properties are omitted and +either a non-empty plan occurs or an error encountered, Terraform will fail the +test. + +After all TestSteps have been ran, Terraform then runs destroy, and ends by +running any CheckDestroy function provided. + +[Back to top](#table-of-contents) + +## Basic test to verify attributes + +The most basic resource acceptance test should use what is likely to be a common +configuration for the resource under test, and verify that Terraform can +correctly create the resource, and that resources attributes are what Terraform +expects them to be. At a high level, the first basic test for a resource should +establish the following: + +- Terraform can plan and apply a common resource configuration without error. +- Verify the expected attributes are saved to state, and contain the values + expected. +- Verify the values in the remote API/Service for the resource match + what is stored in state. +- Verify that a subsequent terraform plan does not produce + a diff/change. + +The first and last item are provided by the test framework as described above in +**Built-in Patterns**. The middle items are implemented by composing a series of +State Check implementations as described in [Acceptance Tests: State Checks][8]. + +To verify attributes are saved to the state file correctly, use a combination of +the built-in [`statecheck`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/statecheck) +implementations provided by the testing framework. See [Resource State Checks][9] to see available +state checks for managed resource and data source attributes. + +Checking the values in a remote API generally consists of two parts: a function +to verify the corresponding object exists remotely, and a state check implementation to +verify the values of the object. By separating the check used to verify the +object exists into its own function, developers are free to re-use it for all +TestSteps as a means of retrieving it's values, and can provide [custom state checks][10] +functions per TestStep to verify remote values or scenarios specific to that TestStep. + +Here's an example test, with in-line comments to demonstrate the key parts of a +basic test. + +```go +package example + +// example.Widget represents a concrete Go type that represents an API resource +func TestAccExampleWidget_basic(t *testing.T) { + var widget example.Widget + + // generate a random name for each widget test run, to avoid + // collisions from multiple concurrent tests. + // the acctest package includes many helpers such as RandStringFromCharSet + // See https://pkg.go.dev/github.com/hashicorp/terraform-plugin-sdk/helper/acctest + rName := acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckExampleResourceDestroy, + Steps: []resource.TestStep{ + { + // use a dynamic configuration with the random name from above + Config: testAccExampleResource(rName), + // compose a basic test, checking both remote and local values + ConfigStateChecks: []statecheck.StateCheck{ + // custom state check - query the API to retrieve the widget object + stateCheckExampleResourceExists("example_widget.foo", &widget), + // custom state check - verify remote values + stateCheckExampleWidgetValues(widget, rName), + // built-in state checks - verify local (state) values + statecheck.ExpectKnownValue("example_widget.foo", tfjsonpath.New("active"), knownvalue.Bool(true)), + statecheck.ExpectKnownValue("example_widget.foo", tfjsonpath.New("name"), knownvalue.StringExact(rName)), + }, + }, + }, + }) +} + +// testAccExampleResource returns an configuration for an Example Widget with the provided name +func testAccExampleResource(name string) string { + return fmt.Sprintf(` +resource "example_widget" "foo" { + active = true + name = "%s" +}`, name) +} +``` + +This example covers all the items needed for a basic test, and will be +referenced or added to in the other test cases to come. + +[Back to top](#table-of-contents) + +## Update test verify configuration changes + +A basic test covers a simple configuration that should apply successfully and +with no follow up differences in state. To verify a resource correctly applies +updates, the second most common test found is an extension of the basic test, +that simply applies another `TestStep` with a modified version of the original +configuration. + +Below is an example test, copied and modified from the basic test. Here we +preserve the `TestStep` from the basic test, but we add an additional +`TestStep`, changing the configuration and rechecking the values, with a +different configuration function `testAccExampleResourceUpdated` and state check +implementation `stateCheckExampleWidgetValuesUpdated` for verifying the values. + +```go +func TestAccExampleWidget_update(t *testing.T) { + var widget example.Widget + rName := acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckExampleResourceDestroy, + Steps: []resource.TestStep{ + { + // use a dynamic configuration with the random name from above + Config: testAccExampleResource(rName), + ConfigStateChecks: []statecheck.StateCheck{ + stateCheckExampleResourceExists("example_widget.foo", &widget), + stateCheckExampleWidgetValues(widget, rName), + statecheck.ExpectKnownValue("example_widget.foo", tfjsonpath.New("active"), knownvalue.Bool(true)), + statecheck.ExpectKnownValue("example_widget.foo", tfjsonpath.New("name"), knownvalue.StringExact(rName)), + }, + }, + { + // use a dynamic configuration with the random name from above + Config: testAccExampleResourceUpdated(rName), + ConfigStateChecks: []statecheck.StateCheck{ + stateCheckExampleResourceExists("example_widget.foo", &widget), + stateCheckExampleWidgetValuesUpdated(widget, rName), + statecheck.ExpectKnownValue("example_widget.foo", tfjsonpath.New("active"), knownvalue.Bool(false)), + statecheck.ExpectKnownValue("example_widget.foo", tfjsonpath.New("name"), knownvalue.StringExact(rName)), + }, + }, + }, + }) +} + +// testAccExampleResource returns an configuration for an Example Widget with the provided name +func testAccExampleResourceUpdated(name string) string { + return fmt.Sprintf(` +resource "example_widget" "foo" { + active = false + name = "%s" +}`, name) +} +``` + +It's common for resources to just have the above update test, as it is a +superset of the basic test. So long as the basics are covered, combining the two +tests is sufficient as opposed to having two separate tests. + +[Back to top](#table-of-contents) + +## Import mode: verify that import and create produce the same state + +To exercise a provider's import behaviors, the test author adds a test step in import mode. +In this example, `ImportState: true` indicates an import mode test step. + +```go +func TestAccExampleWidget_update(t *testing.T) { + var widget example.Widget + rName := acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckExampleResourceDestroy, + Steps: []resource.TestStep{ + { + // Test Step 1: config mode + Config: testAccExampleResource(rName), + ConfigStateChecks: []statecheck.StateCheck{ + stateCheckExampleResourceExists("example_widget.foo", &widget), + stateCheckExampleWidgetValues(widget, rName), + statecheck.ExpectKnownValue("example_widget.foo", tfjsonpath.New("active"), knownvalue.Bool(true)), + statecheck.ExpectKnownValue("example_widget.foo", tfjsonpath.New("name"), knownvalue.StringExact(rName)), + }, + }, + { + // Test Step 2: config mode + Config: testAccExampleResourceUpdated(rName), + ConfigStateChecks: []statecheck.StateCheck{ + stateCheckExampleResourceExists("example_widget.foo", &widget), + stateCheckExampleWidgetValuesUpdated(widget, rName), + statecheck.ExpectKnownValue("example_widget.foo", tfjsonpath.New("active"), knownvalue.Bool(false)), + statecheck.ExpectKnownValue("example_widget.foo", tfjsonpath.New("name"), knownvalue.StringExact(rName)), + }, + }, + { + // Test Step 3: import mode + ImportState: true, + ImportStateKind: resource.ImportBlockWithID, + ResourceName: "example_widget.foo", + }, + }, + }) +} +``` +In this example, "Test Step 3" is a very concise import mode test step because it does not need a completely different Terraform configration. It simply reuses artifacts from its surrounding `TestCase`, in a "convention over configuration" style. As a detailed example: + +* After "Test Step 2" has run, the test case has two useful artifacts: (1) a Terraform configuration and (2) a statefile. These two artifacts act as inputs to "Test Step 3." +* "Test Step 3" uses the Terraform configuration input as a base configuration for verifying import behavior. The test step generates a new Terraform configuration: the base configuration plus a generated `import` block. + * This is a testing convenience; the test author can override this via their choice of `step.Config`, `step.ConfigFile`, or `step.ConfigDirectory` +* "Test Step 3" generates a plan from this Terraform configuration. It uses the statefile input as a "known good" reference for the generated plan. + * In other words, The statefile acts as a "golden file" reference. In import mode, the planned values are expected to match the state values in the "golden file" and the test will report an error if the values do not match. + +[Back to top](#table-of-contents) + +## Expecting errors or non-empty plans + +The number of acceptance tests for a given resource typically start small with +the basic and update scenarios covered. Other tests should be added to +demonstrate common expected configurations or behavior scenarios for a given +resource, such as typical updates or changes to configuration, or exercising +logic that uses polling for updates such as an autoscaling group adding or +draining instances. + +It is possible for scenarios to exist where a valid configuration (no errors +during `plan`) would result in a non-empty `plan` after successfully running +`terraform apply`. This is typically due to a valid but otherwise +misconfiguration of the resource, and is generally undesirable. Occasionally it +is useful to intentionally create this scenario in an early `TestStep` in order +to demonstrate correcting the state with proper configuration in a follow-up +`TestStep`. Normally a `TestStep` that results in a non-empty plan would fail +the test after apply, however developers can use the `ExpectNonEmptyPlan` +attribute to prevent failure and allow the `TestCase` to continue: + +```go +func TestAccExampleWidget_expectPlan(t *testing.T) { + var widget example.Widget + rName := acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckExampleResourceDestroy, + Steps: []resource.TestStep{ + { + // use an incomplete configuration that we expect + // to result in a non-empty plan after apply + Config: testAccExampleResourceIncomplete(rName), + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue("example_widget.foo", tfjsonpath.New("name"), knownvalue.StringExact(rName)), + }, + ExpectNonEmptyPlan: true, + }, + { + // apply the complete configuration + Config: testAccExampleResourceComplete(rName), + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue("example_widget.foo", tfjsonpath.New("name"), knownvalue.StringExact(rName)), + }, + }, + }, + }) +} +``` + +In addition to `ExpectNonEmptyPlan`, `TestStep` also exposes an `ExpectError` +hook, allowing developers to test configuration that they expect to produce an +error, such as configuration that fails schema validators: + +```go +func TestAccExampleWidget_expectError(t *testing.T) { + var widget example.Widget + rName := acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckExampleResourceDestroy, + Steps: []resource.TestStep{ + { + // use a configuration that we expect to fail a validator + // on the resource Name attribute, which only allows alphanumeric + // characters + Config: testAccExampleResourceError(rName + "*$%%^"), + // No check function is given because we expect this configuration + // to fail before any infrastructure is created + ExpectError: regexp.MustCompile("Widget names may only contain alphanumeric characters"), + }, + }, + }) +} +``` + +`ExpectError` expects a valid regular expression, and the error message must +match in order to consider the error as expected and allow the test to pass. If +the regular expression does not match, the `TestStep` fails explaining that the +configuration did not produce the error expected. + +[Back to top](#table-of-contents) + +## Regression tests + +As resources are put into use, issues can arise as bugs that need to be fixed +and released in a new version. Developers are encouraged to introduce regression +tests that demonstrate not only any bugs reported, but that code modified to +address any bug is verified as fixing the issues. These regression tests should +be named and documented appropriately to identify the issue(s) they demonstrate +fixes for. When possible the documentation for a regression test should include +a link to the original bug report. + +An ideal bug fix would include at least 2 commits to source control: + +A single commit introducing the regression test, verifying the issue(s) 1 or +more commits that modify code to fix the issue(s) + +This allows other developers to independently verify that a regression test +indeed reproduces the issue by checking out the source at that commit first, and +then advancing the revisions to evaluate the fix. + +[Back to top](#table-of-contents) + +# Conclusion + +Terraform's [Testing Framework][1] allows for powerful, iterative acceptance +tests that enable developers to fully test the behavior of Terraform plugins. By +following the above best practices, developers can ensure their plugin behaves +correctly across the most common use cases and everyday operations users will +have using their plugins, and ensure that Terraform remains a world-class tool +for safely managing infrastructure. + +[1]: /terraform/plugin/testing + +[2]: /terraform/plugin/testing/acceptance-tests/testcase + +[3]: /terraform/plugin/testing/acceptance-tests/testcase#precheck + +[4]: /terraform/plugin/testing/acceptance-tests/testcase#checkdestroy + +[5]: /terraform/plugin/testing/acceptance-tests/teststep + +[6]: https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/helper/resource#TestStep.ExpectNonEmptyPlan + +[7]: https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/helper/resource#TestStep.ExpectError + +[8]: /terraform/plugin/testing/acceptance-tests/state-checks + +[9]: /terraform/plugin/testing/acceptance-tests/state-checks/resource + +[10]: /terraform/plugin/testing/acceptance-tests/state-checks/custom \ No newline at end of file diff --git a/content/terraform-plugin-testing/v1.13.x/docs/plugin/testing/unit-testing.mdx b/content/terraform-plugin-testing/v1.13.x/docs/plugin/testing/unit-testing.mdx new file mode 100644 index 0000000000..c8b8690c11 --- /dev/null +++ b/content/terraform-plugin-testing/v1.13.x/docs/plugin/testing/unit-testing.mdx @@ -0,0 +1,93 @@ +--- +page_title: Plugin Development - Unit Testing +description: |- + Unit tests are commonly used for testing helper methods that expand or + flatten API responses into data structures that Terraform stores as state. +--- + +# Unit Testing + +Testing plugin code in small, isolated units is distinct from Acceptance Tests, +and does not require network connections. Unit tests are commonly used for +testing helper methods that expand or flatten API responses into data structures +for storage into state by Terraform. This section covers the specifics of +writing Unit Tests for Terraform Plugin code. + +The procedure for writing unit tests for Terraform follows the same setup and +conventions of writing any Go unit tests. We recommend naming tests to follow +the same convention as our acceptance tests, `Test_`. For more +information on Go tests, see the [official Golang docs on testing](https://pkg.go.dev/testing). + +Below is an example unit test used in flattening AWS security group rules, +demonstrating a typical `flattener` type method that's commonly used to convert +structures returned from APIs into data structures used by Terraform in saving +to state. This example is truncated for brevity, but you can see the full test in the +[aws/structure_test.go in the Terraform AWS Provider +repository on GitHub](https://github.com/hashicorp/terraform-provider-aws/blob/f22ae122d8407672bd38951f80a2813b8b9af683/aws/structure_test.go#L930-L1027) + +```go +func TestFlattenSecurityGroups(t *testing.T) { + cases := []struct { + ownerId *string + pairs []*ec2.UserIdGroupPair + expected []*GroupIdentifier + }{ + // simple, no user id included + { + ownerId: aws.String("user1234"), + pairs: []*ec2.UserIdGroupPair{ + &ec2.UserIdGroupPair{ + GroupId: aws.String("sg-12345"), + }, + }, + expected: []*GroupIdentifier{ + &GroupIdentifier{ + GroupId: aws.String("sg-12345"), + }, + }, + }, + // include the owner id, but keep it consitent with the same account. Tests + // EC2 classic situation + { + ownerId: aws.String("user1234"), + pairs: []*ec2.UserIdGroupPair{ + &ec2.UserIdGroupPair{ + GroupId: aws.String("sg-12345"), + UserId: aws.String("user1234"), + }, + }, + expected: []*GroupIdentifier{ + &GroupIdentifier{ + GroupId: aws.String("sg-12345"), + }, + }, + }, + + // include the owner id, but from a different account. This is reflects + // EC2 Classic when referring to groups by name + { + ownerId: aws.String("user1234"), + pairs: []*ec2.UserIdGroupPair{ + &ec2.UserIdGroupPair{ + GroupId: aws.String("sg-12345"), + GroupName: aws.String("somegroup"), // GroupName is only included in Classic + UserId: aws.String("user4321"), + }, + }, + expected: []*GroupIdentifier{ + &GroupIdentifier{ + GroupId: aws.String("sg-12345"), + GroupName: aws.String("user4321/somegroup"), + }, + }, + }, + } + + for _, c := range cases { + out := flattenSecurityGroups(c.pairs, c.ownerId) + if !reflect.DeepEqual(out, c.expected) { + t.Fatalf("Error matching output and expected: %#v vs %#v", out, c.expected) + } + } +} +``` diff --git a/content/terraform-plugin-testing/v1.13.x/img/.gitkeep b/content/terraform-plugin-testing/v1.13.x/img/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/package.json b/package.json index 0041c25946..e5883c190b 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,7 @@ "scripts": { "dev": "npm run watch-content & dotenvx run -- next dev", "prebuild": "node ./scripts/prebuild.mjs", + "prebuild-only-version-metadata": "node ./scripts/prebuild.mjs --only-version-metadata", "build": "next build", "start": "next start", "test": "vitest", diff --git a/scripts/compare-api-responses/index.mjs b/scripts/compare-api-responses/index.mjs index 9cb56ebe66..fa6376b269 100644 --- a/scripts/compare-api-responses/index.mjs +++ b/scripts/compare-api-responses/index.mjs @@ -45,7 +45,7 @@ program .option('-p, --product ', 'Product') .option( '-a, --api ', - 'API type: "content", "nav-data", "version-metadata", or "content-versions"', + 'API type: "content", "nav-data", "version-metadata", "content-versions", or "all-docs-paths"', 'version-metadata', ) .option('-d, --drop-keys ', 'Result keys to drop', (value) => { @@ -334,6 +334,104 @@ if (options.api === 'content-versions') { saveTestOutputIfSelected(outputString, newApiURL) } +if (options.api === 'all-docs-paths') { + const allProducts = [ + 'boundary', + 'consul', + 'hcp-docs', + 'nomad', + 'packer', + 'ptfe-releases', + 'sentinel', + 'terraform', + 'terraform-cdk', + 'terraform-docs-agents', + 'terraform-docs-common', + 'terraform-plugin-framework', + 'terraform-plugin-log', + 'terraform-plugin-mux', + 'terraform-plugin-sdk', + 'terraform-plugin-testing', + 'vagrant', + 'vault', + 'waypoint', + ] + let newApiURL + let oldApiURL + if (options.product) { + newApiURL = `${options.newApiUrl}/api/all-docs-paths?products=${options.product}` + + const contentAPIFilter = allProducts.map((repo) => { + if (repo !== options.product) { + return `filterOut=${repo}` + } + }) + const contentAPIFilterString = contentAPIFilter.join('&') + + oldApiURL = `${options.oldApiUrl}/api/all-docs-paths?${contentAPIFilterString}` + } else { + newApiURL = `${options.newApiUrl}/api/all-docs-paths` + oldApiURL = `${options.oldApiUrl}/api/all-docs-paths` + } + + let newApiResponse, oldApiResponse + try { + newApiResponse = await fetch(newApiURL) + oldApiResponse = await fetch(oldApiURL) + + if (!newApiResponse.ok) { + console.log( + `Error fetching API response:\n${newApiURL}\n${newApiResponse.statusText}`, + ) + } + if (!oldApiResponse.ok) { + console.log( + `Error fetching API response:\n${oldApiURL}\n${oldApiResponse.statusText}`, + ) + } + } catch (error) { + console.log(`Error fetching API response\n${error}`) + } + + let newApiData, oldApiData + try { + newApiData = await newApiResponse.json() + oldApiData = await oldApiResponse.json() + } catch (error) { + console.log(`Error decoding JSON\n${error}`) + } + + // Filter out just the paths to compare and sort them alphabetically + const newDataToCompare = newApiData.result + .map((item) => { + return item.path + }) + .sort() + const oldDataToCompare = oldApiData.result + .map((item) => { + return item.path + }) + .sort() + + const diffOptions = { + contextLines: 1, + expand: false, + } + + const difference = diff(oldDataToCompare, newDataToCompare, diffOptions) + + const outputString = `Testing API URL:\n${newApiURL}` + console.log(outputString) + + if (difference.includes('Compared values have no visual difference.')) { + console.log('✅ No visual difference found.\n') + } else { + console.log(`${difference}\n`) + } + + saveTestOutputIfSelected(outputString, newApiURL) +} + if ( options.api === 'content' || options.api === 'nav-data' || diff --git a/scripts/prebuild.mjs b/scripts/prebuild.mjs index 52779abe47..250064607b 100644 --- a/scripts/prebuild.mjs +++ b/scripts/prebuild.mjs @@ -36,6 +36,11 @@ async function main() { const versionMetadataJson = JSON.stringify(versionMetadata, null, 2) fs.writeFileSync(VERSION_METADATA_FILE, versionMetadataJson) + if (process.argv.includes('--only-version-metadata')) { + console.log('Only generating version metadata, skipping other steps.') + return + } + const docsPathsAllVersions = await gatherAllVersionsDocsPaths(versionMetadata) const docsPathsAllVersionsJson = JSON.stringify(docsPathsAllVersions, null, 2) fs.writeFileSync(DOCS_PATHS_ALL_VERSIONS_FILE, docsPathsAllVersionsJson)