mirror of
https://github.com/hashicorp/vault.git
synced 2026-06-09 00:33:28 -04:00
* adds playwright * adds playwright auth setup and kv tests * removes generated gh action for playwright * removes testem ignore paths * consolidates kv e2e workflows into single test * adds missing ids to key shares and threshold inputs * updates ariaLabel arg to attribute in enabled and disabled plugin card components * adds script to start vault with config for playwright tests * updates playwright setup to initialize and unseal vault and create user for testing rather than using root token * adds policies for e2e tests * updates e2e init setup to use web repl for creating token * moves kv e2e test under superuser directory * updates playwright config to create projects for multiple user types Co-authored-by: Jordan Reimer <zofskeez@gmail.com>
This commit is contained in:
parent
8f6253cc0b
commit
763be2684d
13 changed files with 397 additions and 28 deletions
7
ui/.gitignore
vendored
7
ui/.gitignore
vendored
|
|
@ -43,4 +43,9 @@ package-lock.json
|
|||
vendor/jsondiffpatch.umd.js
|
||||
vendor/htmlformatter.umd.js
|
||||
|
||||
|
||||
# Playwright
|
||||
/test-results/
|
||||
/playwright-report/
|
||||
/blob-report/
|
||||
/playwright/.cache/
|
||||
/playwright/.auth/
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
@level="base"
|
||||
@background="neutral-secondary"
|
||||
@hasBorder={{false}}
|
||||
@ariaLabel="{{@type.displayName}} - disabled engine type"
|
||||
aria-label="{{@type.displayName}} - disabled engine type"
|
||||
tabindex="0"
|
||||
{{on "click" (fn @handleDisabledPluginClick @type)}}
|
||||
{{on "keydown" (fn @handleDisabledPluginKeyDown @type)}}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@
|
|||
@level={{if isDisabled "base" "mid"}}
|
||||
@background={{if isDisabled "neutral-secondary" "neutral-primary"}}
|
||||
@hasBorder={{true}}
|
||||
@ariaLabel="{{@type.displayName}} - {{if isDisabled 'disabled' 'enabled'}} engine type"
|
||||
aria-label="{{@type.displayName}} - {{if isDisabled 'disabled' 'enabled'}} engine type"
|
||||
tabindex="0"
|
||||
{{on "click" this.handleSelection}}
|
||||
{{on "keydown" this.handleKeyDown}}
|
||||
|
|
|
|||
|
|
@ -132,7 +132,7 @@
|
|||
</label>
|
||||
<div class="control">
|
||||
<Input
|
||||
aria-label="Key shares"
|
||||
id="key-shares"
|
||||
data-test-key-shares="true"
|
||||
class="input"
|
||||
autocomplete="off"
|
||||
|
|
@ -155,7 +155,7 @@
|
|||
</label>
|
||||
<div class="control">
|
||||
<Input
|
||||
aria-label="Key threshold"
|
||||
id="key-threshold"
|
||||
data-test-key-threshold="true"
|
||||
class="input"
|
||||
autocomplete="off"
|
||||
|
|
|
|||
2
ui/e2e/.gitignore
vendored
Normal file
2
ui/e2e/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
!*.hcl
|
||||
/tmp
|
||||
69
ui/e2e/init.setup.ts
Normal file
69
ui/e2e/init.setup.ts
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
/**
|
||||
* Copyright IBM Corp. 2016, 2025
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
import { test as base } from '@playwright/test';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { USER_POLICY_MAP } from './policies';
|
||||
|
||||
export type UserSetupOptions = {
|
||||
userType: string;
|
||||
};
|
||||
|
||||
// use superuser as the default policy if not provided in the config for a project
|
||||
export const setup = base.extend<UserSetupOptions>({
|
||||
userType: 'superuser',
|
||||
});
|
||||
|
||||
// setup will run once before all tests
|
||||
setup('initialize vault and setup user for testing', async ({ page, userType }) => {
|
||||
// on fresh app load navigating to the root will land us on the initialize page
|
||||
await page.goto('./');
|
||||
// initialize vault
|
||||
await page.getByRole('spinbutton', { name: 'Key shares' }).fill('1');
|
||||
await page.getByRole('spinbutton', { name: 'Key threshold' }).fill('1');
|
||||
await page.getByRole('button', { name: 'Initialize' }).click();
|
||||
// listen for download event so we can get the unseal key and root token
|
||||
const downloadPromise = page.waitForEvent('download');
|
||||
await page.getByRole('button', { name: 'Download keys' }).click();
|
||||
const download = await downloadPromise;
|
||||
const keysPath = path.join(__dirname, `/tmp/${userType}-keys.json`);
|
||||
await download.saveAs(keysPath);
|
||||
const { keys, root_token } = JSON.parse(fs.readFileSync(keysPath, 'utf-8'));
|
||||
// unseal vault
|
||||
await page.getByRole('link', { name: 'Continue to Unseal' }).click();
|
||||
await page.getByRole('textbox', { name: 'Unseal Key Portion' }).fill(keys[0]);
|
||||
await page.getByRole('button', { name: 'Unseal' }).click();
|
||||
// use the root token to login
|
||||
await page.getByRole('textbox', { name: 'Token' }).fill(root_token);
|
||||
await page.getByRole('button', { name: 'Sign in' }).click();
|
||||
// create a policy for a specific user persona
|
||||
// defaults to superuser but should be passed in via the project config in playwright.config.ts
|
||||
await page.getByRole('link', { name: 'Access', exact: true }).click();
|
||||
await page.getByRole('link', { name: 'Create ACL policy' }).click();
|
||||
await page.getByRole('textbox', { name: 'Policy name' }).fill(userType);
|
||||
await page.getByRole('radio', { name: 'Code editor' }).check();
|
||||
await page.getByRole('textbox', { name: 'Policy editor' }).fill(USER_POLICY_MAP[userType]);
|
||||
await page.getByRole('button', { name: 'Create policy' }).click();
|
||||
// there is no UI workflow for creating tokens with specific policies
|
||||
// generate a token using the web REPL and assign the new policy to it
|
||||
await page.getByRole('button', { name: 'Console toggle' }).click();
|
||||
await page
|
||||
.getByRole('textbox', { name: 'web R.E.P.L.' })
|
||||
.fill(`write -field=client_token auth/token/create policies=${userType} ttl=1d`);
|
||||
await page.getByRole('textbox', { name: 'web R.E.P.L.' }).press('Enter');
|
||||
const newToken = await page.locator('.console-ui-output pre').innerText();
|
||||
await page.getByRole('button', { name: 'Console toggle' }).click();
|
||||
// log out with the root token and log in with the new token/policy
|
||||
await page.getByRole('button', { name: 'User menu' }).click();
|
||||
await page.getByRole('link', { name: 'Log out' }).click();
|
||||
await page.getByRole('textbox', { name: 'Token' }).fill(newToken);
|
||||
await page.getByRole('button', { name: 'Sign in' }).click();
|
||||
// wait for the dashboard to load to ensure login was successful
|
||||
await page.waitForURL('**/dashboard');
|
||||
// save the authenticated state to file
|
||||
// subsequent tests can then reuse this session data
|
||||
await page.context().storageState({ path: path.join(__dirname, `/tmp/${userType}-session.json`) });
|
||||
});
|
||||
15
ui/e2e/policies/index.ts
Normal file
15
ui/e2e/policies/index.ts
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
/**
|
||||
* Copyright IBM Corp. 2016, 2025
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
const readFile = (filePath: string) => {
|
||||
return fs.readFileSync(path.join(__dirname, filePath), 'utf-8');
|
||||
};
|
||||
|
||||
export const USER_POLICY_MAP = {
|
||||
superuser: readFile('./superuser.hcl'),
|
||||
};
|
||||
6
ui/e2e/policies/superuser.hcl
Normal file
6
ui/e2e/policies/superuser.hcl
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
# Copyright IBM Corp. 2016, 2025
|
||||
# SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
path "*" {
|
||||
capabilities = ["create", "read", "update", "delete", "list", "sudo"]
|
||||
}
|
||||
120
ui/e2e/tests/superuser/kv.spec.ts
Normal file
120
ui/e2e/tests/superuser/kv.spec.ts
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
/**
|
||||
* Copyright IBM Corp. 2016, 2025
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
test('kvv2 workflow', async ({ page }) => {
|
||||
await page.goto('dashboard');
|
||||
// enable kv secrets engine
|
||||
await page.getByRole('link', { name: 'Secrets Engines' }).click();
|
||||
await page.getByRole('link', { name: 'Enable new engine' }).click();
|
||||
await page.locator('div').filter({ hasText: 'KV' }).nth(4).click();
|
||||
await page.getByRole('textbox', { name: 'Path' }).click();
|
||||
await page.getByRole('textbox', { name: 'Path' }).fill('kv-test');
|
||||
await page.getByRole('button', { name: 'Enable engine' }).click();
|
||||
// once enabled it should navigate to the secrets engine overview page
|
||||
await expect(page.locator('section')).toContainText('kv-test version 2');
|
||||
await expect(page.locator('section')).toContainText(
|
||||
'No secrets yet When created, secrets will be listed here. Create a secret to get started.'
|
||||
);
|
||||
// verify that the kv engine appears in the list view
|
||||
await page.getByRole('link', { name: 'Secrets Engines' }).click();
|
||||
await page.getByRole('link', { name: 'kv-test/' }).click();
|
||||
// create a secret
|
||||
await page.getByRole('link', { name: 'Create secret' }).click();
|
||||
await page.getByRole('textbox', { name: 'Path for this secret' }).fill('foo');
|
||||
await page.getByRole('textbox', { name: 'key' }).fill('bar');
|
||||
await page.getByRole('textbox', { name: 'bar' }).fill('baz');
|
||||
await page.getByRole('button', { name: 'Save' }).click();
|
||||
// it should navigate to the overview page for the new secret
|
||||
await expect(page.locator('section')).toContainText('foo');
|
||||
await expect(page.locator('section')).toContainText(
|
||||
'Current version Create new The current version of this secret. 1'
|
||||
);
|
||||
// verify secret details
|
||||
await page.getByRole('link', { name: 'Secret', exact: true }).click();
|
||||
await expect(page.locator('section')).toContainText('bar');
|
||||
await page.getByRole('button', { name: 'show value' }).click();
|
||||
await expect(page.locator('pre')).toContainText('baz');
|
||||
await page.locator('label').click();
|
||||
await expect(page.getByRole('code')).toContainText('{ "bar": "baz" }');
|
||||
// create metadata for the secret
|
||||
await page.getByRole('link', { name: 'Metadata', exact: true }).click();
|
||||
await expect(page.locator('#app-main-content')).toContainText(
|
||||
'No custom metadata This data is version-agnostic and is usually used to describe the secret being stored. Add metadata'
|
||||
);
|
||||
await page.getByRole('link', { name: 'Edit metadata' }).click();
|
||||
await page.getByRole('textbox', { name: 'key' }).fill('meta');
|
||||
await page.getByRole('textbox', { name: 'value' }).fill('data');
|
||||
await page.getByRole('button', { name: 'Update' }).click();
|
||||
await expect(page.locator('#app-main-content')).toContainText('meta data');
|
||||
// create new version
|
||||
await page.getByRole('link', { name: 'Version History' }).click();
|
||||
await expect(page.locator('section')).toContainText('Version 1');
|
||||
await expect(page.locator('section')).toContainText('Current');
|
||||
await page.getByRole('button', { name: 'Manage version' }).click();
|
||||
await page.getByRole('link', { name: 'Create new version from 1', exact: true }).click();
|
||||
await page.getByRole('textbox', { name: 'key' }).first().fill('bar-v2');
|
||||
await page.getByRole('textbox', { name: 'bar-v2' }).fill('baz-v2');
|
||||
await page.getByRole('button', { name: 'Save' }).click();
|
||||
await expect(page.locator('section')).toContainText(
|
||||
'Current version Create new The current version of this secret. 2'
|
||||
);
|
||||
await page.getByRole('link', { name: 'Version History' }).click();
|
||||
await expect(page.locator('section')).toContainText('Version 2');
|
||||
await expect(page.locator('section')).toContainText('Current');
|
||||
await page.getByRole('link', { name: 'Version diff' }).click();
|
||||
await expect(page.locator('section')).toContainText('bar"baz"bar-v2"baz-v2"');
|
||||
// delete version 2
|
||||
await page.goto('secrets-engines/kv-test/kv/foo');
|
||||
await page.getByRole('link', { name: 'Secret', exact: true }).click();
|
||||
await page.getByRole('button', { name: 'Delete' }).click();
|
||||
await page.getByRole('radio', { name: 'Delete this version This' }).check();
|
||||
await page.getByRole('button', { name: 'Confirm' }).click();
|
||||
await expect(page.locator('section')).toContainText(
|
||||
'Current version Deleted Create new The current version of this secret was deleted'
|
||||
);
|
||||
await page.getByRole('link', { name: 'Secret', exact: true }).click();
|
||||
await expect(page.locator('section')).toContainText(
|
||||
'Version 2 of this secret has been deleted This version has been deleted but can be undeleted. View other versions of this secret by clicking the Version History tab above. KV v2 API docs'
|
||||
);
|
||||
// undelete version
|
||||
await page.getByRole('button', { name: 'Undelete' }).click();
|
||||
await expect(page.locator('section')).toContainText(
|
||||
'Current version Create new The current version of this secret. 2'
|
||||
);
|
||||
// delete latest version
|
||||
await page.getByRole('link', { name: 'Secret', exact: true }).click();
|
||||
await page.getByRole('button', { name: 'Version' }).click();
|
||||
await page.getByRole('link', { name: 'Version 1' }).click();
|
||||
await page.getByRole('button', { name: 'Delete' }).click();
|
||||
await page.getByRole('radio', { name: 'Delete latest version This' }).check();
|
||||
await page.getByRole('button', { name: 'Confirm' }).click();
|
||||
await expect(page.locator('section')).toContainText(
|
||||
'Current version Deleted Create new The current version of this secret was deleted'
|
||||
);
|
||||
// destroy version 2
|
||||
await page.getByRole('link', { name: 'Secret', exact: true }).click();
|
||||
await page.getByRole('button', { name: 'Destroy' }).click();
|
||||
await page.getByRole('button', { name: 'Confirm' }).click();
|
||||
await expect(page.locator('section')).toContainText(
|
||||
'Current version Destroyed Create new The current version of this secret has been permanently deleted and cannot be restored. 2'
|
||||
);
|
||||
await page.getByRole('link', { name: 'Secret', exact: true }).click();
|
||||
await expect(page.locator('section')).toContainText(
|
||||
'Version 2 of this secret has been permanently destroyed A version that has been permanently deleted cannot be restored. You can view other versions of this secret in the Version History tab above. KV v2 API docs'
|
||||
);
|
||||
// destroy version 1
|
||||
await page.getByRole('button', { name: 'Version' }).click();
|
||||
await page.getByRole('link', { name: 'Version 1' }).click();
|
||||
await page.getByRole('button', { name: 'Destroy' }).click();
|
||||
await page.getByRole('button', { name: 'Confirm' }).click();
|
||||
await page.getByRole('link', { name: 'Secret', exact: true }).click();
|
||||
await page.getByRole('button', { name: 'Version' }).click();
|
||||
await page.getByRole('link', { name: 'Version 1' }).click();
|
||||
await expect(page.locator('section')).toContainText(
|
||||
'Version 1 of this secret has been permanently destroyed A version that has been permanently deleted cannot be restored. You can view other versions of this secret in the Version History tab above. KV v2 API docs'
|
||||
);
|
||||
});
|
||||
15
ui/e2e/vault-config.json
Normal file
15
ui/e2e/vault-config.json
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"ui": true,
|
||||
"disable_mlock": true,
|
||||
|
||||
"storage": {
|
||||
"inmem": {}
|
||||
},
|
||||
|
||||
"listener": {
|
||||
"tcp": {
|
||||
"address": "127.0.0.1:8204",
|
||||
"tls_disable": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -42,7 +42,8 @@
|
|||
"test:server": "node scripts/start-vault.js --server",
|
||||
"test:dev": "node scripts/start-vault.js",
|
||||
"vault": "VAULT_REDIRECT_ADDR=http://127.0.0.1:8200 vault server -log-level=error -dev -dev-root-token-id=root -dev-ha -dev-transactional",
|
||||
"vault:cluster": "VAULT_REDIRECT_ADDR=http://127.0.0.1:8202 vault server -log-level=error -dev -dev-root-token-id=root -dev-listen-address=127.0.0.1:8202 -dev-ha -dev-transactional"
|
||||
"vault:cluster": "VAULT_REDIRECT_ADDR=http://127.0.0.1:8202 vault server -log-level=error -dev -dev-root-token-id=root -dev-listen-address=127.0.0.1:8202 -dev-ha -dev-transactional",
|
||||
"vault:e2e": "vault server"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/cli": "~7.27.0",
|
||||
|
|
@ -67,9 +68,11 @@
|
|||
"@glint/template": "^1.7.3",
|
||||
"@icholy/duration": "~5.1.0",
|
||||
"@lineal-viz/lineal": "~0.5.1",
|
||||
"@playwright/test": "^1.58.0",
|
||||
"@tsconfig/ember": "~2.0.0",
|
||||
"@types/d3-array": "~3.2.1",
|
||||
"@types/ember-data": "~4.4.16",
|
||||
"@types/node": "^25.1.0",
|
||||
"@types/qunit": "~2.19.12",
|
||||
"@types/rsvp": "~4.0.9",
|
||||
"@types/shell-quote": "~1.7.5",
|
||||
|
|
|
|||
93
ui/playwright.config.ts
Normal file
93
ui/playwright.config.ts
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
/**
|
||||
* Copyright IBM Corp. 2016, 2025
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
import { defineConfig, devices } from '@playwright/test';
|
||||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
import { USER_POLICY_MAP } from './e2e/policies';
|
||||
|
||||
import type { UserSetupOptions } from './e2e/init.setup';
|
||||
|
||||
const userTypes = Object.keys(USER_POLICY_MAP);
|
||||
|
||||
// start at port 8204 and increment for each project to allow them to run concurrently
|
||||
const getURL = (increment: number, server = false) => {
|
||||
const port = `820${4 + increment}`;
|
||||
return server ? `127.0.0.1:${port}` : `http://localhost:${port}/ui/vault/`;
|
||||
};
|
||||
|
||||
// create tmp dir if it doesn't exist for storing session, keys and vault config files
|
||||
const tmpDir = path.join(__dirname, '/e2e/tmp');
|
||||
fs.mkdirSync(tmpDir, { recursive: true });
|
||||
|
||||
/**
|
||||
* See https://playwright.dev/docs/test-configuration.
|
||||
*/
|
||||
export default defineConfig<UserSetupOptions>({
|
||||
testDir: './e2e',
|
||||
// opt out of parallel execution with a test file - by default tests will run in the order they are defined
|
||||
fullyParallel: false,
|
||||
// fail the build on CI if you accidentally left test.only in the source code.
|
||||
forbidOnly: !!process.env.CI,
|
||||
// retry on CI only
|
||||
retries: process.env.CI ? 2 : 0,
|
||||
// use a worker for each project so they run concurrently
|
||||
workers: userTypes.length,
|
||||
// reporter to use. See https://playwright.dev/docs/test-reporters
|
||||
reporter: 'html',
|
||||
// shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions.
|
||||
use: {
|
||||
// collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer
|
||||
trace: 'on-first-retry',
|
||||
},
|
||||
projects: [
|
||||
// create setup project for each user type
|
||||
...userTypes.map((userType, index) => ({
|
||||
name: `setup:${userType}`,
|
||||
testMatch: /init\.setup\.ts/,
|
||||
use: {
|
||||
userType,
|
||||
baseURL: getURL(index),
|
||||
},
|
||||
})),
|
||||
// create browser projects for each user type
|
||||
...userTypes.map((userType, index) => {
|
||||
const sessionFile = path.join(tmpDir, `${userType}-session.json`);
|
||||
return {
|
||||
name: `chrome:${userType}`,
|
||||
dependencies: [`setup:${userType}`],
|
||||
workers: 1,
|
||||
// only run tests for this user type
|
||||
testDir: `./e2e/tests/${userType}`,
|
||||
use: {
|
||||
...devices['Desktop Chrome'],
|
||||
// only use if file has already been created by the setup project
|
||||
storageState: fs.existsSync(sessionFile) ? sessionFile : undefined,
|
||||
// start at port 8204 and increment for each project to allow them to run concurrently without conflicts
|
||||
baseURL: getURL(index),
|
||||
},
|
||||
};
|
||||
}),
|
||||
],
|
||||
webServer: [
|
||||
// start a vault server for each project on a different port to allow them to run concurrently
|
||||
...userTypes.map((userType, index) => {
|
||||
// read base config file
|
||||
const config = JSON.parse(fs.readFileSync(path.join(__dirname, '/e2e/vault-config.json'), 'utf-8'));
|
||||
// set the listener address with correct port for this project
|
||||
config.listener.tcp.address = getURL(index, true);
|
||||
// write the config to a new file for this project
|
||||
const configPath = path.join(tmpDir, `${userType}-vault-config.json`);
|
||||
fs.writeFileSync(configPath, JSON.stringify(config));
|
||||
|
||||
return {
|
||||
// start vault server (not dev) with inmem storage
|
||||
command: `pnpm run vault:e2e -config=${configPath}`,
|
||||
url: getURL(index),
|
||||
reuseExistingServer: false,
|
||||
};
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
|
@ -126,6 +126,9 @@ importers:
|
|||
'@lineal-viz/lineal':
|
||||
specifier: ~0.5.1
|
||||
version: 0.5.1(@babel/core@7.26.10)(@glint/template@1.7.3)(ember-source@5.8.0(@babel/core@7.26.10)(@glimmer/component@1.1.2(@babel/core@7.26.10))(@glint/template@1.7.3)(rsvp@4.8.5)(webpack@5.94.0))
|
||||
'@playwright/test':
|
||||
specifier: ^1.58.0
|
||||
version: 1.58.0
|
||||
'@tsconfig/ember':
|
||||
specifier: ~2.0.0
|
||||
version: 2.0.0
|
||||
|
|
@ -135,6 +138,9 @@ importers:
|
|||
'@types/ember-data':
|
||||
specifier: ~4.4.16
|
||||
version: 4.4.16(@babel/core@7.26.10)
|
||||
'@types/node':
|
||||
specifier: ^25.1.0
|
||||
version: 25.1.0
|
||||
'@types/qunit':
|
||||
specifier: ~2.19.12
|
||||
version: 2.19.12
|
||||
|
|
@ -1745,6 +1751,11 @@ packages:
|
|||
resolution: {integrity: sha512-ROFF39F6ZrnzSUEmQQZUar0Jt4xVoP9WnDRdWwF4NNcXs3xBTLgBUDoOwW141y1jP+S8nahIbdxbFC7IShw9Iw==}
|
||||
engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0}
|
||||
|
||||
'@playwright/test@1.58.0':
|
||||
resolution: {integrity: sha512-fWza+Lpbj6SkQKCrU6si4iu+fD2dD3gxNHFhUPxsfXBPhnv3rRSQVd0NtBUT9Z/RhF/boCBcuUaMUSTRTopjZg==}
|
||||
engines: {node: '>=18'}
|
||||
hasBin: true
|
||||
|
||||
'@pnpm/constants@7.1.1':
|
||||
resolution: {integrity: sha512-31pZqMtjwV+Vaq7MaPrT1EoDFSYwye3dp6BiHIGRJmVThCQwySRKM7hCvqqI94epNkqFAAYoWrNynWoRYosGdw==}
|
||||
engines: {node: '>=16.14'}
|
||||
|
|
@ -2045,8 +2056,8 @@ packages:
|
|||
'@types/minimatch@5.1.2':
|
||||
resolution: {integrity: sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==}
|
||||
|
||||
'@types/node@22.15.21':
|
||||
resolution: {integrity: sha512-EV/37Td6c+MgKAbkcLG6vqZ2zEYHD7bvSrzqqs2RIhbA6w3x+Dqz8MZM3sP6kGTeLrdoOgKZe+Xja7tUB2DNkQ==}
|
||||
'@types/node@25.1.0':
|
||||
resolution: {integrity: sha512-t7frlewr6+cbx+9Ohpl0NOTKXZNV9xHRmNOvql47BFJKcEG1CxtxlPEEe+gR9uhVWM4DwhnvTF110mIL4yP9RA==}
|
||||
|
||||
'@types/prettier@2.7.3':
|
||||
resolution: {integrity: sha512-+68kP9yzs4LMp7VNh8gdzMSPZFL44MLGqiHWvttYJe+6qnuVr4Ek9wSBQoveqY/r+LwjCcU29kNVkidwim+kYA==}
|
||||
|
|
@ -4891,6 +4902,11 @@ packages:
|
|||
fs.realpath@1.0.0:
|
||||
resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==}
|
||||
|
||||
fsevents@2.3.2:
|
||||
resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==}
|
||||
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
|
||||
os: [darwin]
|
||||
|
||||
fsevents@2.3.3:
|
||||
resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
|
||||
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
|
||||
|
|
@ -6583,6 +6599,16 @@ packages:
|
|||
resolution: {integrity: sha512-cjJP/mYuGyMrjJ49jI04khId5Oufd3nFTUYBzQTIIVNI7/oAWdwXEfpwTF8HELFV/gz+WGYUBHCe3KHWD8rYvg==}
|
||||
engines: {node: '>=6.0.0'}
|
||||
|
||||
playwright-core@1.58.0:
|
||||
resolution: {integrity: sha512-aaoB1RWrdNi3//rOeKuMiS65UCcgOVljU46At6eFcOFPFHWtd2weHRRow6z/n+Lec0Lvu0k9ZPKJSjPugikirw==}
|
||||
engines: {node: '>=18'}
|
||||
hasBin: true
|
||||
|
||||
playwright@1.58.0:
|
||||
resolution: {integrity: sha512-2SVA0sbPktiIY/MCOPX8e86ehA/e+tDNq+e5Y8qjKYti2Z/JG7xnronT/TXTIkKbYGWlCbuucZ6dziEgkoEjQQ==}
|
||||
engines: {node: '>=18'}
|
||||
hasBin: true
|
||||
|
||||
portfinder@1.0.37:
|
||||
resolution: {integrity: sha512-yuGIEjDAYnnOex9ddMnKZEMFE0CcGo6zbfzDklkmT1m5z734ss6JMzN9rNB3+RR7iS+F10D4/BVIaXOyh8PQKw==}
|
||||
engines: {node: '>= 10.12'}
|
||||
|
|
@ -7825,8 +7851,8 @@ packages:
|
|||
underscore@1.13.7:
|
||||
resolution: {integrity: sha512-GMXzWtsc57XAtguZgaQViUOzs0KTkk8ojr3/xAxXLITqf/3EMwxC0inyETfDFjH/Krbhuep0HNbbjI9i/q3F3g==}
|
||||
|
||||
undici-types@6.21.0:
|
||||
resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==}
|
||||
undici-types@7.16.0:
|
||||
resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==}
|
||||
|
||||
unicode-canonical-property-names-ecmascript@2.0.1:
|
||||
resolution: {integrity: sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==}
|
||||
|
|
@ -10811,6 +10837,10 @@ snapshots:
|
|||
|
||||
'@pkgr/core@0.2.4': {}
|
||||
|
||||
'@playwright/test@1.58.0':
|
||||
dependencies:
|
||||
playwright: 1.58.0
|
||||
|
||||
'@pnpm/constants@7.1.1': {}
|
||||
|
||||
'@pnpm/error@5.0.3':
|
||||
|
|
@ -10892,7 +10922,7 @@ snapshots:
|
|||
'@types/body-parser@1.19.5':
|
||||
dependencies:
|
||||
'@types/connect': 3.4.38
|
||||
'@types/node': 22.15.21
|
||||
'@types/node': 25.1.0
|
||||
|
||||
'@types/chai-as-promised@7.1.8':
|
||||
dependencies:
|
||||
|
|
@ -10906,11 +10936,11 @@ snapshots:
|
|||
|
||||
'@types/connect@3.4.38':
|
||||
dependencies:
|
||||
'@types/node': 22.15.21
|
||||
'@types/node': 25.1.0
|
||||
|
||||
'@types/cors@2.8.18':
|
||||
dependencies:
|
||||
'@types/node': 22.15.21
|
||||
'@types/node': 25.1.0
|
||||
|
||||
'@types/d3-array@3.2.1': {}
|
||||
|
||||
|
|
@ -11184,7 +11214,7 @@ snapshots:
|
|||
|
||||
'@types/express-serve-static-core@4.19.6':
|
||||
dependencies:
|
||||
'@types/node': 22.15.21
|
||||
'@types/node': 25.1.0
|
||||
'@types/qs': 6.14.0
|
||||
'@types/range-parser': 1.2.7
|
||||
'@types/send': 0.17.4
|
||||
|
|
@ -11198,27 +11228,27 @@ snapshots:
|
|||
|
||||
'@types/fs-extra@5.1.0':
|
||||
dependencies:
|
||||
'@types/node': 22.15.21
|
||||
'@types/node': 25.1.0
|
||||
|
||||
'@types/fs-extra@8.1.5':
|
||||
dependencies:
|
||||
'@types/node': 22.15.21
|
||||
'@types/node': 25.1.0
|
||||
|
||||
'@types/fs-extra@9.0.13':
|
||||
dependencies:
|
||||
'@types/node': 22.15.21
|
||||
'@types/node': 25.1.0
|
||||
|
||||
'@types/geojson@7946.0.16': {}
|
||||
|
||||
'@types/glob@7.2.0':
|
||||
dependencies:
|
||||
'@types/minimatch': 5.1.2
|
||||
'@types/node': 22.15.21
|
||||
'@types/node': 25.1.0
|
||||
|
||||
'@types/glob@8.1.0':
|
||||
dependencies:
|
||||
'@types/minimatch': 5.1.2
|
||||
'@types/node': 22.15.21
|
||||
'@types/node': 25.1.0
|
||||
|
||||
'@types/http-errors@2.0.4': {}
|
||||
|
||||
|
|
@ -11247,9 +11277,9 @@ snapshots:
|
|||
|
||||
'@types/minimatch@5.1.2': {}
|
||||
|
||||
'@types/node@22.15.21':
|
||||
'@types/node@25.1.0':
|
||||
dependencies:
|
||||
undici-types: 6.21.0
|
||||
undici-types: 7.16.0
|
||||
|
||||
'@types/prettier@2.7.3': {}
|
||||
|
||||
|
|
@ -11262,7 +11292,7 @@ snapshots:
|
|||
'@types/rimraf@2.0.5':
|
||||
dependencies:
|
||||
'@types/glob': 8.1.0
|
||||
'@types/node': 22.15.21
|
||||
'@types/node': 25.1.0
|
||||
|
||||
'@types/rsvp@4.0.9': {}
|
||||
|
||||
|
|
@ -11271,12 +11301,12 @@ snapshots:
|
|||
'@types/send@0.17.4':
|
||||
dependencies:
|
||||
'@types/mime': 1.3.5
|
||||
'@types/node': 22.15.21
|
||||
'@types/node': 25.1.0
|
||||
|
||||
'@types/serve-static@1.15.7':
|
||||
dependencies:
|
||||
'@types/http-errors': 2.0.4
|
||||
'@types/node': 22.15.21
|
||||
'@types/node': 25.1.0
|
||||
'@types/send': 0.17.4
|
||||
|
||||
'@types/shell-quote@1.7.5': {}
|
||||
|
|
@ -12380,7 +12410,7 @@ snapshots:
|
|||
|
||||
broccoli-rollup@4.0.0:
|
||||
dependencies:
|
||||
'@types/node': 22.15.21
|
||||
'@types/node': 25.1.0
|
||||
broccoli-plugin: 2.1.0
|
||||
fs-tree-diff: 2.0.1
|
||||
heimdalljs: 0.2.6
|
||||
|
|
@ -14624,7 +14654,7 @@ snapshots:
|
|||
engine.io@6.6.4:
|
||||
dependencies:
|
||||
'@types/cors': 2.8.18
|
||||
'@types/node': 22.15.21
|
||||
'@types/node': 25.1.0
|
||||
accepts: 1.3.8
|
||||
base64id: 2.0.0
|
||||
cookie: 0.7.2
|
||||
|
|
@ -15414,6 +15444,9 @@ snapshots:
|
|||
|
||||
fs.realpath@1.0.0: {}
|
||||
|
||||
fsevents@2.3.2:
|
||||
optional: true
|
||||
|
||||
fsevents@2.3.3:
|
||||
optional: true
|
||||
|
||||
|
|
@ -16150,7 +16183,7 @@ snapshots:
|
|||
|
||||
jest-worker@27.5.1:
|
||||
dependencies:
|
||||
'@types/node': 22.15.21
|
||||
'@types/node': 25.1.0
|
||||
merge-stream: 2.0.0
|
||||
supports-color: 8.1.1
|
||||
|
||||
|
|
@ -17211,6 +17244,14 @@ snapshots:
|
|||
bytestreamjs: 1.1.3
|
||||
pvutils: 1.1.3
|
||||
|
||||
playwright-core@1.58.0: {}
|
||||
|
||||
playwright@1.58.0:
|
||||
dependencies:
|
||||
playwright-core: 1.58.0
|
||||
optionalDependencies:
|
||||
fsevents: 2.3.2
|
||||
|
||||
portfinder@1.0.37:
|
||||
dependencies:
|
||||
async: 2.6.4
|
||||
|
|
@ -18714,7 +18755,7 @@ snapshots:
|
|||
|
||||
underscore@1.13.7: {}
|
||||
|
||||
undici-types@6.21.0: {}
|
||||
undici-types@7.16.0: {}
|
||||
|
||||
unicode-canonical-property-names-ecmascript@2.0.1: {}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue