fix: update vite because of vulnerability (#3283)

### **PR Type**
Bug fix, Enhancement


___

### **Description**
- Update Vite to address security vulnerability

- Upgrade dependencies in Vue examples

- Add 'type: module' to Vue quickstart package

- Update resolutions for Vite versions


___



### **Changes walkthrough** 📝
<table><thead><tr><th></th><th align="left">Relevant
files</th></tr></thead><tbody><tr><td><strong>Documentation</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>stale-horses-run.md</strong><dd><code>Add changeset for
Vite vulnerability fix</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; </dd></summary>
<hr>

.changeset/stale-horses-run.md

<li>Add new changeset file<br> <li> List affected packages for patch
update<br> <li> Describe fix for Vite vulnerability


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3283/files#diff-79e66d2654e3803067439855123d20d162193a019ecf68b5b45ee1d0e344949d">+13/-0</a>&nbsp;
&nbsp; </td>

</tr>
</table></td></tr><tr><td><strong>Dependencies</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>package.json</strong><dd><code>Update sass dependency
in Vue Apollo example</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

examples/vue-apollo/package.json

- Update sass dependency from 1.32.0 to 1.86.1


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3283/files#diff-fc4298d3512fdd9a3d871f9f182fe871c8beccd1580f864a271ddfb32005feef">+1/-1</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>
</table></td></tr><tr><td><strong>Enhancement</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>package.json</strong><dd><code>Update package
configuration and dependencies in Vue quickstart</code></dd></summary>
<hr>

examples/vue-quickstart/package.json

<li>Add "type": "module" to package.json<br> <li> Update @unocss/reset
from 0.33.5 to 66.1.0-beta.8<br> <li> Update unocss from 0.33.5 to
66.1.0-beta.8


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3283/files#diff-85166d1137e29a5275f991e1e94a0c9d5b83ac7504463ba76f9187b2b750c895">+3/-2</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>
</table></td></tr><tr><td><strong>Bug fix</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>package.json</strong><dd><code>Add Vite version
resolutions to address vulnerabilities</code>&nbsp; &nbsp;
</dd></summary>
<hr>

package.json

- Add resolutions for Vite versions 5.4.16 and 6.2.4


</details>


  </td>
<td><a
href="https://github.com/nhost/nhost/pull/3283/files#diff-7ae45ad102eab3b6d7e7896acd08c427a9b25b346470d7bc6507b6481575d519">+3/-1</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>
</table></td></tr></tr></tbody></table>

___

> <details> <summary> Need help?</summary><li>Type <code>/help how to
...</code> in the comments thread for any questions about PR-Agent
usage.</li><li>Check out the <a
href="https://qodo-merge-docs.qodo.ai/usage-guide/">documentation</a>
for more information.</li></details>
This commit is contained in:
robertkasza
2025-04-02 17:23:26 +02:00
committed by GitHub
parent 0420e4fda4
commit a1333df2a1
12 changed files with 1031 additions and 729 deletions

View File

@@ -0,0 +1,13 @@
---
'@nhost-examples/vue-quickstart': patch
'@nhost-examples/vue-apollo': patch
'@nhost/dashboard': patch
'@nhost-examples/codegen-react-apollo': patch
'@nhost-examples/codegen-react-query': patch
'@nhost-examples/codegen-react-urql': patch
'@nhost-examples/sveltekit': patch
'@nhost-examples/react-apollo': patch
'@nhost-examples/react-gqty': patch
---
fix: update vite because of vulnerability

View File

@@ -4,7 +4,13 @@ import {
mockMatchMediaValue,
} from '@/tests/mocks';
import tokenQuery from '@/tests/msw/mocks/rest/tokenQuery';
import { mockPointerEvent, render, screen, waitFor } from '@/tests/testUtils';
import {
clickOnElement,
mockPointerEvent,
render,
screen,
waitFor,
} from '@/tests/testUtils';
import { setupServer } from 'msw/node';
import { vi } from 'vitest';
@@ -89,8 +95,6 @@ describe('ImportBackupContent', () => {
server.use(getOrganization);
server.use(getProjectsQuery);
const user = userEvent.setup();
render(<TestComponent />);
expect(
await screen.getByText(
@@ -100,7 +104,7 @@ describe('ImportBackupContent', () => {
const projectComboBox = await screen.findByRole('combobox');
await user.click(projectComboBox);
await clickOnElement(projectComboBox);
// check for only projects from the same region are listed
expect(screen.getByRole('option', { name: /pitr14/i })).toBeInTheDocument();
expect(
@@ -147,13 +151,15 @@ describe('ImportBackupContent', () => {
const projectComboBox = await screen.findByRole('combobox');
await user.click(projectComboBox);
await clickOnElement(projectComboBox);
await user.click(
screen.getByRole('option', {
name: 'pitr14 (us-east-1)',
}),
);
await waitFor(async () => {
await user.click(
screen.getByRole('option', {
name: 'pitr14 (us-east-1)',
}),
);
});
expect(
await screen.getByText('Import backup from pitr14 (us-east-1)'),
@@ -162,8 +168,7 @@ describe('ImportBackupContent', () => {
const startImportButton = await screen.getByRole('button', {
name: 'Start import',
});
await user.click(startImportButton);
await clickOnElement(startImportButton);
await waitFor(async () =>
expect(
@@ -175,7 +180,7 @@ describe('ImportBackupContent', () => {
name: /UTC/i,
});
await user.click(dateTimePickerButton);
await clickOnElement(dateTimePickerButton);
await waitFor(async () =>
expect(
@@ -183,18 +188,19 @@ describe('ImportBackupContent', () => {
).toBeInTheDocument(),
);
await user.click(await screen.getByText('13'));
await clickOnElement(await screen.getByText('13'));
const hoursInput = await screen.getByLabelText('Hours');
await user.type(hoursInput, '18');
await waitFor(async () => {
await user.type(hoursInput, '18');
});
const updatedDateTimeButton = await screen.getByRole('button', {
name: /UTC/i,
});
expect(updatedDateTimeButton).toHaveTextContent(
'13 Mar 2025, 18:00:05 (UTC+02:00)',
);
await user.click(await screen.getByRole('button', { name: 'Select' }));
await clickOnElement(await screen.getByRole('button', { name: 'Select' }));
await waitFor(async () =>
expect(
@@ -208,11 +214,11 @@ describe('ImportBackupContent', () => {
// check checkboxes
await user.click(
await clickOnElement(
await screen.getByLabelText(/I understand that restoring this backup/),
);
await user.click(
await clickOnElement(
await screen.getByLabelText(/I understand this cannot be undone/),
);
@@ -222,7 +228,7 @@ describe('ImportBackupContent', () => {
).not.toBeDisabled(),
);
await user.click(
await clickOnElement(
await screen.getByRole('button', { name: 'Import backup' }),
);
@@ -234,21 +240,19 @@ describe('ImportBackupContent', () => {
mocks.restoreApplicationDatabase.mock.calls[0][0].recoveryTarget,
).toBe('2025-03-13T16:00:05.000Z');
});
// TODO
test('Pitr is not enabled on project', async () => {
server.use(getOrganization);
server.use(getProjectsQuery);
server.use(getPiTRNotEnabledPostgresSettings);
const user = userEvent.setup();
render(<TestComponent />);
const projectComboBox = await screen.findByRole('combobox');
await user.click(projectComboBox);
await clickOnElement(projectComboBox);
await user.click(
await clickOnElement(
screen.getByRole('option', {
name: 'pitr-not-enabled-usa (us-east-1)',
}),

View File

@@ -8,9 +8,12 @@ import {
import updateConfigMutation from '@/tests/msw/mocks/graphql/updateConfigMutation';
import tokenQuery from '@/tests/msw/mocks/rest/tokenQuery';
import {
clickOnElement,
fireEvent,
render,
screen,
userClearElement,
userType,
waitFor,
waitForElementToBeRemoved,
within,
@@ -19,7 +22,6 @@ import {
RESOURCE_MEMORY_MULTIPLIER,
RESOURCE_VCPU_MULTIPLIER,
} from '@/utils/constants/common';
import userEvent from '@testing-library/user-event';
import { setupServer } from 'msw/node';
import { expect, test, vi } from 'vitest';
import ResourcesForm from './ResourcesForm';
@@ -66,13 +68,12 @@ test('should show an empty state message that the feature must be enabled if no
test('should show the sliders if the switch is enabled', async () => {
server.use(resourcesUnavailableQuery);
const user = userEvent.setup();
render(<ResourcesForm />);
expect(await screen.findByText(/enable this feature/i)).toBeInTheDocument();
await user.click(screen.getByRole('checkbox'));
await clickOnElement(screen.getByRole('checkbox'));
expect(screen.queryByText(/enable this feature/i)).not.toBeInTheDocument();
expect(screen.getAllByRole('slider')).toHaveLength(9);
@@ -133,7 +134,6 @@ test('should update the price when the top slider is changed', async () => {
});
test('should show a validation error when the form is submitted when not everything is allocated', async () => {
const user = userEvent.setup();
render(<ResourcesForm />);
expect(
@@ -149,7 +149,7 @@ test('should show a validation error when the form is submitted when not everyth
9 * RESOURCE_VCPU_MULTIPLIER,
);
await user.click(screen.getByRole('button', { name: /save/i }));
await clickOnElement(screen.getByRole('button', { name: /save/i }));
expect(
screen.getByText(/you have 1 vcpus and 2048 mib of memory unused./i),
@@ -160,8 +160,6 @@ test('should show a validation error when the form is submitted when not everyth
test('should show a confirmation dialog when the form is submitted', async () => {
server.use(updateConfigMutation);
const user = userEvent.setup();
render(<ResourcesForm />);
expect(
@@ -209,7 +207,7 @@ test('should show a confirmation dialog when the form is submitted', async () =>
5 * RESOURCE_MEMORY_MULTIPLIER,
);
await user.click(screen.getByRole('button', { name: /save/i }));
await clickOnElement(screen.getByRole('button', { name: /save/i }));
expect(await screen.findByRole('dialog')).toBeInTheDocument();
expect(
@@ -239,7 +237,7 @@ test('should show a confirmation dialog when the form is submitted', async () =>
// and we need to return the updated values
server.use(resourcesUpdatedQuery);
await user.click(screen.getByRole('button', { name: /confirm/i }));
await clickOnElement(screen.getByRole('button', { name: /confirm/i }));
await waitForElementToBeRemoved(() => screen.queryByRole('dialog'));
@@ -254,19 +252,19 @@ test('should show a confirmation dialog when the form is submitted', async () =>
});
test('should display a red button when custom resources are disabled', async () => {
const user = userEvent.setup();
render(<ResourcesForm />);
expect(
await screen.findByRole('slider', { name: /total available vcpu/i }),
).toBeInTheDocument();
await user.click(screen.getAllByRole('checkbox')[0]);
await clickOnElement(screen.getAllByRole('checkbox')[0]);
await waitFor(() => {});
expect(screen.getByText(/enable this feature/i)).toBeInTheDocument();
await user.click(screen.getByRole('button', { name: /save/i }));
await clickOnElement(screen.getByRole('button', { name: /save/i }));
expect(await screen.findByRole('dialog')).toBeInTheDocument();
@@ -274,30 +272,28 @@ test('should display a red button when custom resources are disabled', async ()
screen.getByRole('heading', { name: /disable dedicated resources/i }),
).toBeInTheDocument();
expect(screen.getByRole('button', { name: /confirm/i })).toHaveStyle({
'background-color': '#f13154',
'background-color': '#D32F2F',
});
});
test('should hide the pricing information when custom resource allocation is disabled', async () => {
server.use(updateConfigMutation);
const user = userEvent.setup();
render(<ResourcesForm />);
expect(
await screen.findByRole('slider', { name: /total available vcpu/i }),
).toBeInTheDocument();
await user.click(screen.getAllByRole('checkbox')[0]);
await clickOnElement(screen.getAllByRole('checkbox')[0]);
await user.click(screen.getByRole('button', { name: /save/i }));
await clickOnElement(screen.getByRole('button', { name: /save/i }));
expect(await screen.findByRole('dialog')).toBeInTheDocument();
server.use(resourcesUnavailableQuery);
await user.click(screen.getByRole('button', { name: /confirm/i }));
await clickOnElement(screen.getByRole('button', { name: /confirm/i }));
await waitForElementToBeRemoved(() => screen.queryByRole('dialog'));
@@ -326,8 +322,6 @@ test('should show a warning message when resources are overallocated', async ()
});
test('should change pricing based on selected replicas', async () => {
const user = userEvent.setup();
render(<ResourcesForm />);
expect(
@@ -340,9 +334,9 @@ test('should change pricing based on selected replicas', async () => {
const hasuraReplicasInput = screen.getAllByPlaceholderText('Replicas')[0];
await user.click(hasuraReplicasInput);
await user.clear(hasuraReplicasInput);
await user.type(hasuraReplicasInput, '2');
await clickOnElement(hasuraReplicasInput);
await userClearElement(hasuraReplicasInput);
await userType(hasuraReplicasInput, '2');
await new Promise((resolve) => {
setTimeout(resolve, 1000);
@@ -354,9 +348,9 @@ test('should change pricing based on selected replicas', async () => {
),
);
await user.click(hasuraReplicasInput);
await user.clear(hasuraReplicasInput);
await user.type(hasuraReplicasInput, '1');
await clickOnElement(hasuraReplicasInput);
await userClearElement(hasuraReplicasInput);
await userType(hasuraReplicasInput, '1');
await waitFor(() => {
expect(screen.getByText(/approximate cost:/i)).toHaveTextContent(
@@ -365,58 +359,7 @@ test('should change pricing based on selected replicas', async () => {
});
});
test('should validate if vCPU and Memory match the 1:2 ratio if more than 1 replica is selected', async () => {
const user = userEvent.setup();
render(<ResourcesForm />);
expect(
await screen.findByRole('slider', { name: /total available vcpu/i }),
).toBeInTheDocument();
changeSliderValue(
screen.getByRole('slider', {
name: /total available vcpu/i,
}),
20 * RESOURCE_VCPU_MULTIPLIER,
);
const storageReplicasInput = screen.getAllByPlaceholderText('Replicas')[2];
await user.click(storageReplicasInput);
await user.clear(storageReplicasInput);
await user.type(storageReplicasInput, '2');
changeSliderValue(
screen.getByRole('slider', { name: /storage vcpu/i }),
1 * RESOURCE_VCPU_MULTIPLIER,
);
changeSliderValue(
screen.getByRole('slider', { name: /storage memory/i }),
6 * RESOURCE_MEMORY_MULTIPLIER,
);
await user.click(screen.getByRole('button', { name: /save/i }));
expect(screen.getByText(/invalid configuration/i)).toBeInTheDocument();
expect(
screen.getByText(
/please check the form for errors and the allocation for each service and try again\./i,
),
).toBeInTheDocument();
await waitFor(() => {
const validationErrorMessage = screen.getByText(
/vCPU and Memory for this service must follow a 1:2 ratio when more than one replica is selected or when the autoscaler is activated\./i,
);
expect(validationErrorMessage).toBeInTheDocument();
expect(validationErrorMessage).toHaveStyle({ color: '#f13154' });
});
});
test('should take replicas into account when confirming the resources', async () => {
const user = userEvent.setup();
render(<ResourcesForm />);
expect(
@@ -441,9 +384,9 @@ test('should take replicas into account when confirming the resources', async ()
);
const hasuraReplicasInput = screen.getAllByPlaceholderText('Replicas')[0];
await user.click(hasuraReplicasInput);
await user.clear(hasuraReplicasInput);
await user.type(hasuraReplicasInput, '3');
await clickOnElement(hasuraReplicasInput);
await userClearElement(hasuraReplicasInput);
await userType(hasuraReplicasInput, '3');
changeSliderValue(
screen.getByRole('slider', { name: /hasura graphql vcpu/i }),
@@ -456,9 +399,9 @@ test('should take replicas into account when confirming the resources', async ()
const authReplicasInput = screen.getAllByPlaceholderText('Replicas')[1];
// setting up auth
await user.click(authReplicasInput);
await user.clear(authReplicasInput);
await user.type(authReplicasInput, '2');
await clickOnElement(authReplicasInput);
await userClearElement(authReplicasInput);
await userType(authReplicasInput, '2');
changeSliderValue(
screen.getByRole('slider', { name: /auth vcpu/i }),
@@ -471,9 +414,9 @@ test('should take replicas into account when confirming the resources', async ()
const storageReplicasInput = screen.getAllByPlaceholderText('Replicas')[2];
// setting up storage
await user.click(storageReplicasInput);
await user.clear(storageReplicasInput);
await user.type(storageReplicasInput, '4');
await clickOnElement(storageReplicasInput);
await userClearElement(storageReplicasInput);
await userType(storageReplicasInput, '4');
changeSliderValue(
screen.getByRole('slider', { name: /storage vcpu/i }),
@@ -484,7 +427,7 @@ test('should take replicas into account when confirming the resources', async ()
5 * RESOURCE_MEMORY_MULTIPLIER,
);
await user.click(screen.getByRole('button', { name: /save/i }));
await clickOnElement(screen.getByRole('button', { name: /save/i }));
expect(await screen.findByRole('dialog')).toBeInTheDocument();
@@ -515,3 +458,50 @@ test('should take replicas into account when confirming the resources', async ()
expect(within(dialog).getByText(/\$0.0270\/min/i)).toBeInTheDocument();
expect(within(dialog).getByText(/\$1125\.00\/mo/i)).toBeInTheDocument();
});
test('should validate if vCPU and Memory match the 1:2 ratio if more than 1 replica is selected', async () => {
render(<ResourcesForm />);
expect(
await screen.findByRole('slider', { name: /total available vcpu/i }),
).toBeInTheDocument();
changeSliderValue(
screen.getByRole('slider', {
name: /total available vcpu/i,
}),
20 * RESOURCE_VCPU_MULTIPLIER,
);
const storageReplicasInput = screen.getAllByPlaceholderText('Replicas')[2];
await clickOnElement(storageReplicasInput);
await userClearElement(storageReplicasInput);
await userType(storageReplicasInput, '2');
changeSliderValue(
screen.getByRole('slider', { name: /storage vcpu/i }),
1 * RESOURCE_VCPU_MULTIPLIER,
);
changeSliderValue(
screen.getByRole('slider', { name: /storage memory/i }),
6 * RESOURCE_MEMORY_MULTIPLIER,
);
await clickOnElement(screen.getByRole('button', { name: /save/i }));
expect(screen.getByText(/invalid configuration/i)).toBeInTheDocument();
expect(
screen.getByText(
/please check the form for errors and the allocation for each service and try again\./i,
),
).toBeInTheDocument();
await waitFor(() => {
const validationErrorMessage = screen.getByText(
/vCPU and Memory for this service must follow a 1:2 ratio when more than one replica is selected or when the autoscaler is activated\./i,
);
expect(validationErrorMessage).toBeInTheDocument();
expect(validationErrorMessage).toHaveStyle({ color: '#D32F2F' });
});
});

View File

@@ -20,7 +20,9 @@ import type {
import {
render as rtlRender,
waitForElementToBeRemoved as rtlWaitForElementToBeRemoved,
waitFor,
} from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { RouterContext } from 'next/dist/shared/lib/router-context.shared-runtime';
import type { PropsWithChildren, ReactElement } from 'react';
import { Toaster } from 'react-hot-toast';
@@ -160,5 +162,26 @@ export const mockPointerEvent = () => {
window.HTMLElement.prototype.hasPointerCapture = vi.fn();
};
export async function clickOnElement(element: Element) {
const user = userEvent.setup();
await waitFor(async () => {
await user.click(element);
});
}
export async function userType(element: Element, value: string, options?: any) {
const user = userEvent.setup();
await waitFor(async () => {
await user.type(element, value, options);
});
}
export async function userClearElement(element: Element) {
const user = userEvent.setup();
await waitFor(async () => {
await user.clear(element);
});
}
export * from '@testing-library/react';
export { render, waitForElementToBeRemoved };

View File

@@ -12,5 +12,8 @@ export default defineConfig({
globals: true,
setupFiles: 'src/setupTests.ts',
include: ['src/**/*.(spec|test).{js,jsx,ts,tsx}'],
deps: {
inline: ['clsx'],
},
},
});

View File

@@ -31,9 +31,11 @@
},
"devDependencies": {
"@next/bundle-analyzer": "^12.3.4",
"@next/eslint-plugin-next": "14.2.26",
"@types/node": "^16.18.93",
"@types/react": "^18.2.73",
"@xstate/inspect": "^0.6.5",
"eslint": "8.57.0",
"eslint-config-next": "12.0.10",
"typescript": "^4.9.5",
"ws": "^8.16.0",

View File

@@ -35,7 +35,7 @@
"@types/webfontloader": "^1.6.38",
"@vitejs/plugin-vue": "^4.6.2",
"@xstate/inspect": "^0.6.5",
"sass": "1.32.0",
"sass": "1.86.1",
"typescript": "4.9.4",
"vite": "^5.4.15",
"vue-tsc": "^0.38.9"

View File

@@ -0,0 +1,13 @@
{
"root": true,
"extends": ["../../config/.eslintrc.vue.js", "@antfu"],
"rules": {
"@typescript-eslint/comma-dangle": "off",
"curly": "off",
"quote-props": "off",
"vue/html-self-closing": "off",
"vue/singleline-html-element-content-newline": "off",
"eol-last": "off",
"eslint-comments/no-unlimited-disable": "off"
}
}

View File

@@ -1,13 +0,0 @@
module.exports = {
root: true,
extends: ['../../config/.eslintrc.vue.js', '@antfu'],
rules: {
'@typescript-eslint/comma-dangle': 'off',
curly: 'off',
'quote-props': 'off',
'vue/html-self-closing': 'off',
'vue/singleline-html-element-content-newline': 'off',
'eol-last': 'off',
'eslint-comments/no-unlimited-disable': 'off'
}
}

View File

@@ -1,5 +1,6 @@
{
"name": "@nhost-examples/vue-quickstart",
"type": "module",
"version": "0.4.0",
"private": true,
"scripts": {
@@ -25,13 +26,13 @@
"@antfu/eslint-config": "^0.23.1",
"@iconify-json/carbon": "^1.1.31",
"@types/node": "^16.18.93",
"@unocss/reset": "^0.33.5",
"@unocss/reset": "66.0.0",
"@vitejs/plugin-vue": "^4.6.2",
"@vue/test-utils": "^2.4.5",
"eslint": "^8.57.0",
"jsdom": "^19.0.0",
"typescript": "^4.9.5",
"unocss": "^0.33.5",
"unocss": "66.0.0",
"unplugin-auto-import": "^0.17.5",
"unplugin-vue-components": "^0.26.0",
"vite": "^5.4.15",

View File

@@ -173,7 +173,9 @@
"cross-spawn": "^7.0.5",
"cookie@<0.7.0": ">=0.7.0",
"prismjs@<1.30.0": ">=1.30.0",
"axios@<1.8.2": ">=1.8.2"
"axios@<1.8.2": ">=1.8.2",
"vite@>=5.0.0 <5.4.16": ">=5.4.16",
"vite@>=6.2.0 <6.2.4": ">=6.2.4"
}
}
}

1462
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff