Compare commits

...

32 commits

Author SHA1 Message Date
Michael Kret 27305001ff
Merge 4e6e035e5d into fdef6c9f0d 2024-09-19 15:31:56 +01:00
कारतोफ्फेलस्क्रिप्ट™ fdef6c9f0d
ci: Remove eslint-plugin-prettier again (no-changelog) (#10876) 2024-09-19 16:29:04 +02:00
Michael Kret 4e6e035e5d Merge branch 'master' of https://github.com/n8n-io/n8n into node-1714-show-result-of-waiting-execution-on-canvas-after-execution 2024-09-19 17:06:26 +03:00
Michael Kret 0572977364 review update 2024-09-19 17:06:20 +03:00
Tomi Turtiainen 8fb31e8459
fix(benchmark): Simplify binary data scenario setup and use larger binary file (#10879) 2024-09-19 16:21:55 +03:00
Michael Kret 548907e529 Merge branch 'master' of https://github.com/n8n-io/n8n into node-1714-show-result-of-waiting-execution-on-canvas-after-execution 2024-09-19 15:29:49 +03:00
Michael Kret 0a17008995 Merge branch 'master' of https://github.com/n8n-io/n8n into node-1714-show-result-of-waiting-execution-on-canvas-after-execution 2024-09-18 16:45:31 +03:00
Michael Kret 3ed91d959b fix unsafe access to executionStatus 2024-09-18 16:45:20 +03:00
Michael Kret 91632c0f9c Merge branch 'master' of https://github.com/n8n-io/n8n into node-1714-show-result-of-waiting-execution-on-canvas-after-execution 2024-09-18 14:41:18 +03:00
Michael Kret 3ea7cc5673 form triger url fix, remove redundant logic 2024-09-18 14:41:13 +03:00
Michael Kret 40b98d7fbf Merge branch 'master' of https://github.com/n8n-io/n8n into node-1714-show-result-of-waiting-execution-on-canvas-after-execution 2024-09-18 11:25:11 +03:00
Michael Kret 178c8f12e1 cleanup 2024-09-18 10:39:01 +03:00
Michael Kret c126b3b829 Merge branch 'master' of https://github.com/n8n-io/n8n into node-1714-show-result-of-waiting-execution-on-canvas-after-execution 2024-09-18 09:27:49 +03:00
Michael Kret d7a60611e2 setWorkflowExecutionRunData fix 2024-09-18 09:27:34 +03:00
Michael Kret 41512376a0 Merge branch 'master' of https://github.com/n8n-io/n8n into node-1714-show-result-of-waiting-execution-on-canvas-after-execution 2024-09-17 15:49:43 +03:00
Michael Kret 639290b937 Merge branch 'master' of https://github.com/n8n-io/n8n into node-1714-show-result-of-waiting-execution-on-canvas-after-execution 2024-09-17 14:37:52 +03:00
Michael Kret ddc36989a0 tests, review update 2024-09-17 14:37:47 +03:00
Michael Kret 6bde28302c update v2 canvas 2024-09-17 10:01:52 +03:00
Michael Kret eff418d466 update new canvas 2024-09-17 09:53:20 +03:00
Michael Kret 0c1d2e9a88 delay update, form waiting hint update 2024-09-17 09:12:13 +03:00
Michael Kret a924763596 displayForm tests update 2024-09-17 07:23:45 +03:00
Michael Kret 50450c3afb clean up, test step support 2024-09-17 06:58:54 +03:00
Michael Kret b37bf9d3c9 Merge branch 'master' of https://github.com/n8n-io/n8n into node-1714-show-result-of-waiting-execution-on-canvas-after-execution 2024-09-17 05:16:37 +03:00
Michael Kret 5cfee2b280 Merge branch 'master' of https://github.com/n8n-io/n8n into node-1714-show-result-of-waiting-execution-on-canvas-after-execution 2024-09-16 15:46:52 +03:00
Michael Kret 9883a7a53f use local storage to redirect to next waiting form 2024-09-16 15:46:48 +03:00
Michael Kret 73d383d53d Merge branch 'master' of https://github.com/n8n-io/n8n into node-1714-show-result-of-waiting-execution-on-canvas-after-execution 2024-09-16 14:16:30 +03:00
Michael Kret 9d72ea5068 refactoring, replaced while with setInterval 2024-09-16 14:16:26 +03:00
Michael Kret 6cd183e59d Merge branch 'master' of https://github.com/n8n-io/n8n into node-1714-show-result-of-waiting-execution-on-canvas-after-execution 2024-09-16 10:53:08 +03:00
Michael Kret 139d481aac waiting webhoo fix 2024-09-16 10:53:04 +03:00
Michael Kret d34cabddd3 refactoring 2024-09-16 08:01:33 +03:00
Michael Kret 9da74b5b3b Merge branch 'master' of https://github.com/n8n-io/n8n into node-1714-show-result-of-waiting-execution-on-canvas-after-execution 2024-09-14 08:58:49 +03:00
Michael Kret 726cd86378 fix, WIP 2024-09-14 08:58:45 +03:00
32 changed files with 358 additions and 215 deletions

View file

@ -72,7 +72,6 @@
"chokidar": "3.5.2",
"esbuild": "^0.20.2",
"formidable": "3.5.1",
"prettier": "^3.2.5",
"pug": "^3.0.3",
"semver": "^7.5.4",
"tslib": "^2.6.2",

View file

@ -3,8 +3,9 @@ import { check } from 'k6';
const apiBaseUrl = __ENV.API_BASE_URL;
const file = open(__ENV.SCRIPT_FILE_PATH, 'b');
const filename = String(__ENV.SCRIPT_FILE_PATH).split('/').pop();
// This creates a 2MB file (16 * 128 * 1024 = 2 * 1024 * 1024 = 2MB)
const file = Array.from({ length: 128 * 1024 }, () => Math.random().toString().slice(2)).join('');
const filename = 'test.bin';
export default function () {
const data = {

View file

@ -77,7 +77,6 @@ export function handleSummary(data) {
env: {
API_BASE_URL: this.opts.n8nApiBaseUrl,
K6_CLOUD_TOKEN: this.opts.k6ApiToken,
SCRIPT_FILE_PATH: augmentedTestScriptPath,
},
stdio: 'inherit',
})`${k6ExecutablePath} run ${flattedFlags} ${augmentedTestScriptPath}`;

View file

@ -25,7 +25,6 @@ module.exports = {
},
{
files: ['**/*.vue'],
plugins: isCI ? [] : ['eslint-plugin-prettier'],
rules: {
'vue/no-deprecated-slot-attribute': 'error',
'vue/no-deprecated-slot-scope-attribute': 'error',
@ -68,14 +67,6 @@ module.exports = {
],
'vue/no-v-html': 'error',
...(isCI
? {}
: {
'prettier/prettier': ['error', { endOfLine: 'auto' }],
'arrow-body-style': 'off',
'prefer-arrow-callback': 'off',
}),
// TODO: remove these
'vue/no-mutating-props': 'warn',
'vue/no-side-effects-in-computed-properties': 'warn',

View file

@ -6,7 +6,6 @@
"@types/eslint": "^8.56.5",
"@typescript-eslint/eslint-plugin": "^7.2.0",
"@typescript-eslint/parser": "^7.2.0",
"@vue/eslint-config-prettier": "^9.0.0",
"@vue/eslint-config-typescript": "^13.0.0",
"eslint": "^8.57.0",
"eslint-config-airbnb-typescript": "^18.0.0",
@ -15,7 +14,6 @@
"eslint-plugin-import": "^2.29.1",
"eslint-plugin-lodash": "^7.4.0",
"eslint-plugin-n8n-local-rules": "^1.0.0",
"eslint-plugin-prettier": "^5.1.3",
"eslint-plugin-unicorn": "^51.0.1",
"eslint-plugin-unused-imports": "^3.1.0",
"eslint-plugin-vue": "^9.23.0",

View file

@ -735,6 +735,14 @@
}
return;
}).then(() => {
window.addEventListener('storage', function(event) {
if (event.key === 'n8n_redirect_to_next_form_test_page' && event.newValue) {
const newUrl = event.newValue;
localStorage.removeItem('n8n_redirect_to_next_form_test_page');
window.location.replace(newUrl);
}
});
})
.catch(function (error) {
console.error('Error:', error);

View file

@ -63,7 +63,7 @@
"n8n-design-system": "workspace:*",
"n8n-workflow": "workspace:*",
"pinia": "^2.1.6",
"prettier": "^3.1.0",
"prettier": "^3.3.3",
"qrcode.vue": "^3.3.4",
"stream-browserify": "^3.0.0",
"timeago.js": "^4.0.2",

View file

@ -77,7 +77,7 @@ const onDrag = (event: MouseEvent) => {
if (!isDragging.value && draggingElement.value) {
isDragging.value = true;
const data = props.targetDataKey ? draggingElement.value.dataset.value : props.data ?? '';
const data = props.targetDataKey ? draggingElement.value.dataset.value : (props.data ?? '');
ndvStore.draggableStartDragging({
type: props.type,

View file

@ -417,7 +417,7 @@ export default defineComponent({
type="secondary"
hide-icon
:transparent="true"
:node-name="isActiveNodeConfig ? rootNode : currentNodeName ?? ''"
:node-name="isActiveNodeConfig ? rootNode : (currentNodeName ?? '')"
:label="$locale.baseText('ndv.input.noOutputData.executePrevious')"
telemetry-source="inputs"
data-test-id="execute-previous-node"

View file

@ -7,6 +7,7 @@ import {
NODE_INSERT_SPACER_BETWEEN_INPUT_GROUPS,
SIMULATE_NODE_TYPE,
SIMULATE_TRIGGER_NODE_TYPE,
WAIT_NODE_TYPE,
WAIT_TIME_UNLIMITED,
} from '@/constants';
import type {
@ -268,7 +269,7 @@ const nodeClass = computed(() => {
const nodeExecutionStatus = computed(() => {
const nodeExecutionRunData = workflowsStore.getWorkflowRunData?.[props.name];
if (nodeExecutionRunData) {
return nodeExecutionRunData.filter(Boolean)[0].executionStatus ?? '';
return nodeExecutionRunData.filter(Boolean)[0]?.executionStatus ?? '';
}
return '';
});
@ -320,9 +321,21 @@ const nodeTitle = computed(() => {
const waiting = computed(() => {
const workflowExecution = workflowsStore.getWorkflowExecution as ExecutionSummary;
if (workflowExecution?.waitTill) {
if (workflowExecution?.waitTill && !workflowExecution?.finished) {
const lastNodeExecuted = get(workflowExecution, 'data.resultData.lastNodeExecuted');
if (props.name === lastNodeExecuted) {
const node = props.workflow.getNode(lastNodeExecuted);
if (
node &&
node.type === WAIT_NODE_TYPE &&
['webhook', 'form'].includes(node.parameters.resume as string)
) {
const event =
node.parameters.resume === 'webhook'
? i18n.baseText('node.theNodeIsWaitingWebhookCall')
: i18n.baseText('node.theNodeIsWaitingFormCall');
return event;
}
const waitDate = new Date(workflowExecution.waitTill);
if (waitDate.toISOString() === WAIT_TIME_UNLIMITED) {
return i18n.baseText('node.theNodeIsWaitingIndefinitelyForAnIncomingWebhookCall');

View file

@ -56,7 +56,7 @@ defineOptions({
const lastPopupCountUpdate = ref(0);
const router = useRouter();
const { runWorkflow, stopCurrentExecution } = useRunWorkflow({ router });
const { runWorkflowResolvePending, stopCurrentExecution } = useRunWorkflow({ router });
const workflowsStore = useWorkflowsStore();
const externalHooks = useExternalHooks();
@ -264,7 +264,7 @@ async function onClick() {
telemetry.track('User clicked execute node button', telemetryPayload);
await externalHooks.run('nodeExecuteButton.onClick', telemetryPayload);
await runWorkflow({
await runWorkflowResolvePending({
destinationNode: props.nodeName,
source: 'RunData.ExecuteNodeButton',
});

View file

@ -210,6 +210,16 @@ const canPinData = computed(() => {
// Methods
const waitingNodeMessage = (resume: string) => {
if (resume === 'form') {
return i18n.baseText('ndv.output.waitNodeWaitingForFormSubmission');
}
if (resume === 'webhook') {
return i18n.baseText('ndv.output.waitNodeWaitingForWebhook');
}
return i18n.baseText('ndv.output.waitNodeWaiting');
};
const insertTestData = () => {
if (!runDataRef.value) return;
@ -348,6 +358,13 @@ const activatePane = () => {
</n8n-text>
</template>
<template #node-waiting>
<n8n-text :bold="true" color="text-dark" size="large">Waiting for input</n8n-text>
<n8n-text>
{{ waitingNodeMessage(node?.parameters?.resume as string) }}
</n8n-text>
</template>
<template #no-output-data>
<n8n-text :bold="true" color="text-dark" size="large">{{
$locale.baseText('ndv.output.noOutputData.title')

View file

@ -26,7 +26,7 @@ const emit = defineEmits<{
projectRemoved: [value: ProjectSharingData];
}>();
const selectedProject = ref(Array.isArray(model.value) ? '' : model.value?.id ?? '');
const selectedProject = ref(Array.isArray(model.value) ? '' : (model.value?.id ?? ''));
const filter = ref('');
const selectPlaceholder = computed(
() => props.placeholder ?? locale.baseText('projects.sharing.select.placeholder'),

View file

@ -41,7 +41,7 @@ export default defineComponent({
const projectId = props.projectId ?? inferProjectIdFromRoute(route);
const resourceType = props.resourceType ?? inferResourceTypeFromRoute(route);
const resourceId = resourceType
? props.resourceId ?? inferResourceIdFromRoute(route)
? (props.resourceId ?? inferResourceIdFromRoute(route))
: undefined;
return rbacStore.hasScope(

View file

@ -40,6 +40,7 @@ import {
MAX_DISPLAY_ITEMS_AUTO_ALL,
TEST_PIN_DATA,
HTML_NODE_TYPE,
WAIT_NODE_TYPE,
} from '@/constants';
import BinaryDataDisplay from '@/components/BinaryDataDisplay.vue';
@ -218,6 +219,14 @@ export default defineComponent({
isReadOnlyRoute() {
return this.$route?.meta?.readOnlyCanvas === true;
},
isWaitNodeWaiting() {
return (
this.hasNodeRun &&
this.workflowExecution?.status === 'waiting' &&
this.node?.type === WAIT_NODE_TYPE &&
this.workflowExecution?.data?.resultData?.lastNodeExecuted === this.node?.name
);
},
activeNode(): INodeUi | null {
return this.ndvStore.activeNode;
},
@ -1501,6 +1510,10 @@ export default defineComponent({
</n8n-text>
</div>
<div v-else-if="isWaitNodeWaiting" :class="$style.center">
<slot name="node-waiting">xxx</slot>
</div>
<div v-else-if="hasNodeRun && !inputData.length && !search" :class="$style.center">
<slot name="no-output-data">xxx</slot>
</div>

View file

@ -25,7 +25,7 @@ const i18n = useI18n();
const saveButtonLabel = computed(() => {
return props.isSaving
? props.savingLabel ?? i18n.baseText('saveButton.saving')
? (props.savingLabel ?? i18n.baseText('saveButton.saving'))
: i18n.baseText('saveButton.save');
});

View file

@ -87,7 +87,7 @@ const valueToDisplay = computed<NodeParameterValue>(() => {
}
if (isListMode.value) {
return props.modelValue ? props.modelValue.cachedResultName ?? props.modelValue.value : '';
return props.modelValue ? (props.modelValue.cachedResultName ?? props.modelValue.value) : '';
}
return props.modelValue ? props.modelValue.value : '';

View file

@ -37,7 +37,7 @@ const plusLineSize = computed(
small: 46,
medium: 66,
large: 80,
})[renderOptions.value.outputs?.labelSize ?? runData.value ? 'large' : 'small'],
})[(renderOptions.value.outputs?.labelSize ?? runData.value) ? 'large' : 'small'],
);
function onMouseEnter() {

View file

@ -39,7 +39,7 @@ const router = useRouter();
const temporaryExecution = computed<ExecutionSummary | undefined>(() =>
props.executions.find((execution) => execution.id === props.execution?.id)
? undefined
: props.execution ?? undefined,
: (props.execution ?? undefined),
);
const hidePreview = computed(() => {

View file

@ -6,7 +6,7 @@ import { ExpressionError, type IPinData, type IRunData, type Workflow } from 'n8
import { useRootStore } from '@/stores/root.store';
import { useRunWorkflow } from '@/composables/useRunWorkflow';
import type { IStartRunData, IWorkflowData } from '@/Interface';
import type { IExecutionResponse, IStartRunData, IWorkflowData } from '@/Interface';
import { useWorkflowsStore } from '@/stores/workflows.store';
import { useUIStore } from '@/stores/ui.store';
import { useWorkflowHelpers } from '@/composables/useWorkflowHelpers';
@ -22,6 +22,7 @@ vi.mock('@/stores/workflows.store', () => ({
executionWaitingForWebhook: false,
getCurrentWorkflow: vi.fn().mockReturnValue({ id: '123' }),
getNodeByName: vi.fn(),
getExecution: vi.fn(),
}),
}));
@ -306,4 +307,101 @@ describe('useRunWorkflow({ router })', () => {
expect(result.runData).toEqual(undefined);
});
});
describe('useRunWorkflow({ router }) - runWorkflowResolvePending', () => {
let uiStore: ReturnType<typeof useUIStore>;
let workflowsStore: ReturnType<typeof useWorkflowsStore>;
let router: ReturnType<typeof useRouter>;
beforeAll(() => {
const pinia = createTestingPinia({ stubActions: false });
setActivePinia(pinia);
rootStore = useRootStore();
uiStore = useUIStore();
workflowsStore = useWorkflowsStore();
router = useRouter();
workflowHelpers = useWorkflowHelpers({ router });
});
beforeEach(() => {
uiStore.activeActions = [];
vi.mocked(workflowsStore).runWorkflow.mockReset();
});
it('should resolve when runWorkflow finished', async () => {
const { runWorkflowResolvePending } = useRunWorkflow({ router });
const mockExecutionResponse = { executionId: '123' };
vi.mocked(workflowsStore).runWorkflow.mockResolvedValue(mockExecutionResponse);
vi.mocked(workflowsStore).allNodes = [];
vi.mocked(workflowsStore).getExecution.mockResolvedValue({
finished: true,
} as unknown as IExecutionResponse);
vi.mocked(workflowsStore).workflowExecutionData = {
id: '123',
} as unknown as IExecutionResponse;
const result = await runWorkflowResolvePending({});
expect(result).toEqual(mockExecutionResponse);
});
it('should return when workflowExecutionData is null', async () => {
const { runWorkflowResolvePending } = useRunWorkflow({ router });
const mockExecutionResponse = { executionId: '123' };
vi.mocked(workflowsStore).runWorkflow.mockResolvedValue(mockExecutionResponse);
vi.mocked(workflowsStore).allNodes = [];
vi.mocked(workflowsStore).getExecution.mockResolvedValue({
finished: true,
} as unknown as IExecutionResponse);
vi.mocked(workflowsStore).workflowExecutionData = null;
const result = await runWorkflowResolvePending({});
expect(result).toEqual(mockExecutionResponse);
});
it('should handle workflow execution error properly', async () => {
const { runWorkflowResolvePending } = useRunWorkflow({ router });
const mockExecutionResponse = { executionId: '123' };
vi.mocked(workflowsStore).runWorkflow.mockResolvedValue(mockExecutionResponse);
vi.mocked(workflowsStore).allNodes = [];
vi.mocked(workflowsStore).getExecution.mockResolvedValue({
finished: false,
status: 'error',
} as unknown as IExecutionResponse);
await runWorkflowResolvePending({});
expect(workflowsStore.setWorkflowExecutionData).toHaveBeenCalled();
expect(workflowsStore.workflowExecutionData).toBe(null);
});
it('should retry execution when waiting for webhook and eventually resolve', async () => {
const { runWorkflowResolvePending } = useRunWorkflow({ router });
const mockExecutionResponse = { waitingForWebhook: true };
vi.mocked(workflowsStore)
.runWorkflow.mockResolvedValueOnce(mockExecutionResponse)
.mockResolvedValueOnce({
executionId: '123',
});
vi.mocked(workflowsStore).allNodes = [];
vi.mocked(workflowsStore)
.getExecution.mockResolvedValueOnce({ status: 'waiting' } as unknown as IExecutionResponse)
.mockResolvedValueOnce({ status: 'waiting' } as unknown as IExecutionResponse)
.mockResolvedValueOnce({ finished: true } as unknown as IExecutionResponse);
const result = await runWorkflowResolvePending({});
expect(result).toEqual({
executionId: '123',
});
expect(workflowsStore.getExecution).toHaveBeenCalledTimes(4);
expect(workflowsStore.getExecution).toHaveBeenNthCalledWith(4, '123');
});
});
});

View file

@ -231,7 +231,8 @@ export function useCanvasMapping({
const nodeExecutionStatusById = computed(() =>
nodes.value.reduce<Record<string, ExecutionStatus>>((acc, node) => {
acc[node.id] =
workflowsStore.getWorkflowRunData?.[node.name]?.filter(Boolean)[0].executionStatus ?? 'new';
workflowsStore.getWorkflowRunData?.[node.name]?.filter(Boolean)[0]?.executionStatus ??
'new';
return acc;
}, {}),
);
@ -325,7 +326,8 @@ export function useCanvasMapping({
if (workflowExecution && lastNodeExecuted && isExecutionSummary(workflowExecution)) {
if (
node.name === workflowExecution.data?.resultData?.lastNodeExecuted &&
workflowExecution.waitTill
workflowExecution?.waitTill &&
!workflowExecution?.finished
) {
const waitDate = new Date(workflowExecution.waitTill);

View file

@ -325,13 +325,6 @@ export function usePushConnection({ router }: { router: ReturnType<typeof useRou
// Workflow did start but had been put to wait
titleChange.titleSet(workflow.name as string, 'IDLE');
toast.showToast({
title: 'Workflow started waiting',
message: `${action} <a href="https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.wait/" target="_blank">More info</a>`,
type: 'success',
duration: 0,
dangerouslyUseHTMLString: true,
});
} else if (runDataExecuted.finished !== true) {
titleChange.titleSet(workflow.name as string, 'ERROR');

View file

@ -4,6 +4,7 @@ import type {
IStartRunData,
IWorkflowDb,
} from '@/Interface';
import type {
IRunData,
IRunExecutionData,
@ -13,19 +14,25 @@ import type {
StartNodeData,
IRun,
INode,
IDataObject,
} from 'n8n-workflow';
import { NodeConnectionType } from 'n8n-workflow';
import { useToast } from '@/composables/useToast';
import { useNodeHelpers } from '@/composables/useNodeHelpers';
import { CHAT_TRIGGER_NODE_TYPE, WORKFLOW_LM_CHAT_MODAL_KEY } from '@/constants';
import {
CHAT_TRIGGER_NODE_TYPE,
FORM_TRIGGER_NODE_TYPE,
WAIT_NODE_TYPE,
WORKFLOW_LM_CHAT_MODAL_KEY,
} from '@/constants';
import { useTitleChange } from '@/composables/useTitleChange';
import { useRootStore } from '@/stores/root.store';
import { useUIStore } from '@/stores/ui.store';
import { useNodeTypesStore } from '@/stores/nodeTypes.store';
import { useWorkflowsStore } from '@/stores/workflows.store';
import { displayForm } from '@/utils/executionUtils';
import { displayForm, openPopUpWindow } from '@/utils/executionUtils';
import { useExternalHooks } from '@/composables/useExternalHooks';
import { useWorkflowHelpers } from '@/composables/useWorkflowHelpers';
import type { useRouter } from 'vue-router';
@ -36,6 +43,8 @@ import { useExecutionsStore } from '@/stores/executions.store';
import type { PushPayload } from '@n8n/api-types';
import { useLocalStorage } from '@vueuse/core';
const FORM_RELOAD = 'n8n_redirect_to_next_form_test_page';
export function useRunWorkflow(useRunWorkflowOpts: { router: ReturnType<typeof useRouter> }) {
const nodeHelpers = useNodeHelpers();
const workflowHelpers = useWorkflowHelpers({ router: useRunWorkflowOpts.router });
@ -45,7 +54,6 @@ export function useRunWorkflow(useRunWorkflowOpts: { router: ReturnType<typeof u
const rootStore = useRootStore();
const uiStore = useUIStore();
const nodeTypesStore = useNodeTypesStore();
const workflowsStore = useWorkflowsStore();
const executionsStore = useExecutionsStore();
@ -265,42 +273,23 @@ export function useRunWorkflow(useRunWorkflowOpts: { router: ReturnType<typeof u
const getTestUrl = (() => {
return (node: INode) => {
const nodeType = nodeTypesStore.getNodeType(node.type, node.typeVersion);
if (nodeType?.webhooks?.length) {
return workflowHelpers.getWebhookUrl(nodeType.webhooks[0], node, 'test');
}
return '';
};
})();
const shouldShowForm = (() => {
return (node: INode) => {
const workflowTriggerNodes = workflow
.getTriggerNodes()
.map((triggerNode) => triggerNode.name);
const showForm =
options.destinationNode === node.name ||
directParentNodes.includes(node.name) ||
workflowTriggerNodes.some((triggerNode) =>
workflowsStore.isNodeInOutgoingNodeConnections(triggerNode, node.name),
);
return showForm;
return `${rootStore.formTestUrl}/${node.parameters.path}`;
};
})();
try {
displayForm({
nodes: workflowData.nodes,
runData: workflowsStore.getWorkflowExecution?.data?.resultData?.runData,
destinationNode: options.destinationNode,
pinData,
directParentNodes,
formWaitingUrl: rootStore.formWaitingUrl,
executionId: runWorkflowApiResponse.executionId,
source: options.source,
getTestUrl,
shouldShowForm,
});
} catch (error) {
console.log(error);
}
await useExternalHooks().run('workflowRun.runWorkflow', {
nodeName: options.destinationNode,
@ -315,6 +304,114 @@ export function useRunWorkflow(useRunWorkflowOpts: { router: ReturnType<typeof u
}
}
function getFormResumeUrl(node: INode, executionId: string) {
const { webhookSuffix } = (node.parameters.options ?? {}) as IDataObject;
const suffix = webhookSuffix && typeof webhookSuffix !== 'object' ? `/${webhookSuffix}` : '';
const testUrl = `${rootStore.formWaitingUrl}/${executionId}${suffix}`;
return testUrl;
}
async function runWorkflowResolvePending(options: {
destinationNode?: string;
triggerNode?: string;
nodeData?: ITaskData;
source?: string;
}): Promise<IExecutionPushResponse | undefined> {
let runWorkflowApiResponse = await runWorkflow(options);
let { executionId } = runWorkflowApiResponse || {};
const MAX_DELAY = 3000;
let delay = 300;
const waitForWebhook = async (): Promise<string> => {
return await new Promise<string>((resolve) => {
const interval = setInterval(async () => {
await useExternalHooks().run('workflowRun.runWorkflow', {
nodeName: options.destinationNode,
source: options.source,
});
if (workflowsStore.activeExecutionId) {
executionId = workflowsStore.activeExecutionId;
runWorkflowApiResponse = { executionId };
clearInterval(interval);
resolve(executionId);
}
delay = Math.min(delay * 1.1, MAX_DELAY);
}, delay);
});
};
if (!executionId) executionId = await waitForWebhook();
let isFormShown =
!options.destinationNode &&
workflowsStore.allNodes.some((node) => node.type === FORM_TRIGGER_NODE_TYPE);
const resolveWaitingNodesData = async (): Promise<void> => {
return await new Promise<void>((resolve) => {
const interval = setInterval(async () => {
const execution = await workflowsStore.getExecution((executionId as string) || '');
localStorage.removeItem(FORM_RELOAD);
if (!execution || workflowsStore.workflowExecutionData === null) {
clearInterval(interval);
resolve();
return;
}
if (execution.finished || ['error', 'canceled', 'crashed'].includes(execution.status)) {
clearInterval(interval);
resolve();
return;
}
if (execution.status === 'waiting' && execution.data?.waitTill) {
workflowsStore.setWorkflowExecutionRunData(execution.data);
const { lastNodeExecuted } = execution.data?.resultData || {};
const waitingNode = execution.workflowData.nodes.find((node) => {
return node.name === lastNodeExecuted;
});
if (
waitingNode &&
waitingNode.type === WAIT_NODE_TYPE &&
waitingNode.parameters.resume === 'form'
) {
const testUrl = getFormResumeUrl(waitingNode, executionId as string);
if (isFormShown) {
localStorage.setItem(FORM_RELOAD, testUrl);
} else {
isFormShown = true;
openPopUpWindow(testUrl);
}
}
}
delay = Math.min(delay * 1.1, MAX_DELAY);
}, delay);
});
};
await resolveWaitingNodesData();
const execution = await workflowsStore.getExecution((executionId as string) || '');
if (execution) {
workflowsStore.setWorkflowExecutionData(execution);
}
await useExternalHooks().run('workflowRun.runWorkflow', {
nodeName: options.destinationNode,
source: options.source,
});
return runWorkflowApiResponse;
}
function consolidateRunDataAndStartNodes(
directParentNodes: string[],
runData: IRunData | null,
@ -435,6 +532,7 @@ export function useRunWorkflow(useRunWorkflowOpts: { router: ReturnType<typeof u
return {
consolidateRunDataAndStartNodes,
runWorkflow,
runWorkflowResolvePending,
runWorkflowApi,
stopCurrentExecution,
stopWaitingForWebhook,

View file

@ -955,6 +955,9 @@
"ndv.output.run": "Run",
"ndv.output.runNodeHint": "Execute this node to view data",
"ndv.output.runNodeHintSubNode": "Output will appear here once the parent node is run",
"ndv.output.waitNodeWaitingForWebhook": "Execution will continue when webhook is received",
"ndv.output.waitNodeWaitingForFormSubmission": "Execution will continue when form is submitted",
"ndv.output.waitNodeWaiting": "Execution will continue when wait time is over",
"ndv.output.insertTestData": "set mock data",
"ndv.output.staleDataWarning.regular": "Node parameters have changed.<br>Test node again to refresh output.",
"ndv.output.staleDataWarning.pinData": "Node parameter changes will not affect pinned output data.",
@ -1001,6 +1004,8 @@
"node.nodeIsExecuting": "Node is executing",
"node.nodeIsWaitingTill": "Node is waiting until {date} {time}",
"node.theNodeIsWaitingIndefinitelyForAnIncomingWebhookCall": "The node is waiting for an incoming webhook call (indefinitely)",
"node.theNodeIsWaitingWebhookCall": "The node is waiting for an incoming webhook call",
"node.theNodeIsWaitingFormCall": "The node is waiting for an form submission",
"node.waitingForYouToCreateAnEventIn": "Waiting for you to create an event in {nodeType}",
"node.discovery.pinData.canvas": "You can pin this output instead of waiting for a test event. Open node to do so.",
"node.discovery.pinData.ndv": "You can pin this output instead of waiting for a test event.",

View file

@ -44,7 +44,9 @@ export const useRootStore = defineStore(STORES.ROOT, () => {
const formTestUrl = computed(() => `${state.value.urlBaseEditor}${state.value.endpointFormTest}`);
const formWaitingUrl = computed(() => `${state.value.baseUrl}${state.value.endpointFormWaiting}`);
const formWaitingUrl = computed(
() => `${state.value.urlBaseEditor}${state.value.endpointFormWaiting}`,
);
const webhookUrl = computed(() => `${state.value.urlBaseWebhook}${state.value.endpointWebhook}`);

View file

@ -15,7 +15,6 @@ vi.mock('../executionUtils', async () => {
describe('displayForm', () => {
const getTestUrlMock = vi.fn();
const shouldShowFormMock = vi.fn();
beforeEach(() => {
vi.clearAllMocks();
@ -50,11 +49,8 @@ describe('displayForm', () => {
pinData,
destinationNode: undefined,
directParentNodes: [],
formWaitingUrl: 'http://example.com',
executionId: undefined,
source: undefined,
getTestUrl: getTestUrlMock,
shouldShowForm: shouldShowFormMock,
});
expect(openPopUpWindow).not.toHaveBeenCalled();
@ -86,11 +82,8 @@ describe('displayForm', () => {
pinData: {},
destinationNode: 'Node3',
directParentNodes: ['Node4'],
formWaitingUrl: 'http://example.com',
executionId: '12345',
source: undefined,
getTestUrl: getTestUrlMock,
shouldShowForm: shouldShowFormMock,
});
expect(openPopUpWindow).not.toHaveBeenCalled();
@ -116,11 +109,8 @@ describe('displayForm', () => {
pinData: {},
destinationNode: undefined,
directParentNodes: [],
formWaitingUrl: 'http://example.com',
executionId: undefined,
source: 'RunData.ManualChatMessage',
getTestUrl: getTestUrlMock,
shouldShowForm: shouldShowFormMock,
});
expect(openPopUpWindow).not.toHaveBeenCalled();

View file

@ -1,7 +1,7 @@
import type { ExecutionStatus, IDataObject, INode, IPinData, IRunData } from 'n8n-workflow';
import type { ExecutionFilterType, ExecutionsQueryFilter } from '@/Interface';
import { isEmpty } from '@/utils/typesUtils';
import { FORM_TRIGGER_NODE_TYPE, WAIT_NODE_TYPE } from '../constants';
import { FORM_TRIGGER_NODE_TYPE } from '../constants';
export function getDefaultExecutionFilters(): ExecutionFilterType {
return {
@ -76,15 +76,15 @@ export const openPopUpWindow = (
const windowWidth = window.innerWidth;
const smallScreen = windowWidth <= 800;
if (options?.alwaysInNewTab || smallScreen) {
window.open(url, '_blank');
return window.open(url, '_blank');
} else {
const height = options?.width || 700;
const width = options?.height || window.innerHeight - 50;
const left = (window.innerWidth - height) / 2;
const top = 50;
const features = `width=${height},height=${width},left=${left},top=${top},resizable=yes,scrollbars=yes`;
window.open(url, '_blank', features);
const windowName = `form-waiting-since-${Date.now()}`;
return window.open(url, windowName, features);
}
};
@ -94,56 +94,30 @@ export function displayForm({
pinData,
destinationNode,
directParentNodes,
formWaitingUrl,
executionId,
source,
getTestUrl,
shouldShowForm,
}: {
nodes: INode[];
runData: IRunData | undefined;
pinData: IPinData;
destinationNode: string | undefined;
directParentNodes: string[];
formWaitingUrl: string;
executionId: string | undefined;
source: string | undefined;
getTestUrl: (node: INode) => string;
shouldShowForm: (node: INode) => boolean;
}) {
for (const node of nodes) {
const hasNodeRun = runData && runData?.hasOwnProperty(node.name);
if (hasNodeRun || pinData[node.name]) continue;
if (![FORM_TRIGGER_NODE_TYPE, WAIT_NODE_TYPE].includes(node.type)) {
continue;
}
if (![FORM_TRIGGER_NODE_TYPE].includes(node.type)) continue;
if (
destinationNode &&
destinationNode !== node.name &&
!directParentNodes.includes(node.name)
) {
if (destinationNode && destinationNode !== node.name && !directParentNodes.includes(node.name))
continue;
}
if (node.name === destinationNode || !node.disabled) {
let testUrl = '';
if (node.type === FORM_TRIGGER_NODE_TYPE) {
testUrl = getTestUrl(node);
}
if (node.type === WAIT_NODE_TYPE && node.parameters.resume === 'form' && executionId) {
if (!shouldShowForm(node)) continue;
const { webhookSuffix } = (node.parameters.options ?? {}) as IDataObject;
const suffix =
webhookSuffix && typeof webhookSuffix !== 'object' ? `/${webhookSuffix}` : '';
testUrl = `${formWaitingUrl}/${executionId}${suffix}`;
}
if (node.type === FORM_TRIGGER_NODE_TYPE) testUrl = getTestUrl(node);
if (testUrl && source !== 'RunData.ManualChatMessage') openPopUpWindow(testUrl);
}
}

View file

@ -153,7 +153,8 @@ const { addBeforeUnloadEventBindings, removeBeforeUnloadEventBindings } = useBef
route,
});
const { registerCustomAction, unregisterCustomAction } = useGlobalLinkActions();
const { runWorkflow, stopCurrentExecution, stopWaitingForWebhook } = useRunWorkflow({ router });
const { runWorkflow, runWorkflowResolvePending, stopCurrentExecution, stopWaitingForWebhook } =
useRunWorkflow({ router });
const {
updateNodePosition,
updateNodesPosition,
@ -935,7 +936,7 @@ const workflowPermissions = computed(() => {
const projectPermissions = computed(() => {
const project = route.query?.projectId
? projectsStore.myProjects.find((p) => p.id === route.query.projectId)
: projectsStore.currentProject ?? projectsStore.personalProject;
: (projectsStore.currentProject ?? projectsStore.personalProject);
return getResourcePermissions(project?.scopes);
});
@ -980,7 +981,11 @@ const workflowExecutionData = computed(() => workflowsStore.workflowExecutionDat
async function onRunWorkflow() {
trackRunWorkflow();
if (isExecutionPreview.value) {
await runWorkflow({});
} else {
await runWorkflowResolvePending({});
}
}
function trackRunWorkflow() {
@ -1005,7 +1010,12 @@ async function onRunWorkflowToNode(id: string) {
if (!node) return;
trackRunWorkflowToNode(node);
if (isExecutionPreview.value) {
await runWorkflow({ destinationNode: node.name, source: 'Node.executeNode' });
} else {
await runWorkflowResolvePending({ destinationNode: node.name, source: 'Node.executeNode' });
}
}
function trackRunWorkflowToNode(node: INodeUi) {

View file

@ -234,7 +234,9 @@ export default defineComponent({
const { callDebounced } = useDebounce();
const canvasPanning = useCanvasPanning(nodeViewRootRef, { onMouseMoveEnd });
const workflowHelpers = useWorkflowHelpers({ router });
const { runWorkflow, stopCurrentExecution } = useRunWorkflow({ router });
const { runWorkflow, stopCurrentExecution, runWorkflowResolvePending } = useRunWorkflow({
router,
});
const { addBeforeUnloadEventBindings, removeBeforeUnloadEventBindings } = useBeforeUnload({
route,
});
@ -254,6 +256,7 @@ export default defineComponent({
onMouseMoveEnd,
workflowHelpers,
runWorkflow,
runWorkflowResolvePending,
stopCurrentExecution,
callDebounced,
...useCanvasMouseSelect(),
@ -507,7 +510,7 @@ export default defineComponent({
projectPermissions() {
const project = this.$route.query?.projectId
? this.projectsStore.myProjects.find((p) => p.id === this.$route.query.projectId)
: this.projectsStore.currentProject ?? this.projectsStore.personalProject;
: (this.projectsStore.currentProject ?? this.projectsStore.personalProject);
return getResourcePermissions(project?.scopes);
},
},
@ -847,7 +850,12 @@ export default defineComponent({
};
this.$telemetry.track('User clicked execute node button', telemetryPayload);
void this.externalHooks.run('nodeView.onRunNode', telemetryPayload);
if (this.isExecutionPreview) {
void this.runWorkflow({ destinationNode: nodeName, source });
} else {
void this.runWorkflowResolvePending({ destinationNode: nodeName, source });
}
},
async onOpenChat() {
const telemetryPayload = {
@ -857,6 +865,7 @@ export default defineComponent({
void this.externalHooks.run('nodeView.onOpenChat', telemetryPayload);
this.uiStore.openModal(WORKFLOW_LM_CHAT_MODAL_KEY);
},
async onRunWorkflow() {
void this.workflowHelpers.getWorkflowDataToSave().then((workflowData) => {
const telemetryPayload = {
@ -873,7 +882,12 @@ export default defineComponent({
void this.externalHooks.run('nodeView.onRunWorkflow', telemetryPayload);
});
if (this.isExecutionPreview) {
await this.runWorkflow({});
} else {
await this.runWorkflowResolvePending({});
}
this.refreshEndpointsErrorsState();
},
resetEndpointsErrors() {

View file

@ -124,7 +124,7 @@ function resetNewVariablesList() {
const resourceToEnvironmentVariable = (data: IResource): EnvironmentVariable => ({
id: data.id,
key: data.name,
value: 'value' in data ? data.value ?? '' : '',
value: 'value' in data ? (data.value ?? '') : '',
});
const environmentVariableToResource = (data: EnvironmentVariable): IResource => ({

View file

@ -238,14 +238,6 @@ export class Wait extends Webhook {
inputs: [NodeConnectionType.Main],
outputs: [NodeConnectionType.Main],
credentials: credentialsProperty(this.authPropertyName),
hints: [
{
message:
"When testing your workflow using the Editor UI, you can't see the rest of the execution following the Wait node. To inspect the execution results, enable Save Manual Executions in your Workflow settings so you can review the execution results there.",
location: 'outputPane',
whenToDisplay: 'beforeExecution',
},
],
webhooks: [
{
...defaultWebhookDescription,

View file

@ -88,7 +88,6 @@ overrides:
chokidar: 3.5.2
esbuild: ^0.20.2
formidable: 3.5.1
prettier: ^3.2.5
pug: ^3.0.3
semver: ^7.5.4
tslib: ^2.6.2
@ -623,9 +622,6 @@ importers:
'@typescript-eslint/parser':
specifier: ^7.2.0
version: 7.2.0(eslint@8.57.0)(typescript@5.6.2)
'@vue/eslint-config-prettier':
specifier: ^9.0.0
version: 9.0.0(@types/eslint@8.56.5)(eslint@8.57.0)(prettier@3.2.5)
'@vue/eslint-config-typescript':
specifier: ^13.0.0
version: 13.0.0(eslint-plugin-vue@9.23.0(eslint@8.57.0))(eslint@8.57.0)(typescript@5.6.2)
@ -650,9 +646,6 @@ importers:
eslint-plugin-n8n-local-rules:
specifier: ^1.0.0
version: 1.0.0
eslint-plugin-prettier:
specifier: ^5.1.3
version: 5.1.3(@types/eslint@8.56.5)(eslint-config-prettier@9.1.0(eslint@8.57.0))(eslint@8.57.0)(prettier@3.2.5)
eslint-plugin-unicorn:
specifier: ^51.0.1
version: 51.0.1(eslint@8.57.0)
@ -1392,8 +1385,8 @@ importers:
specifier: ^2.1.6
version: 2.1.6(typescript@5.6.2)(vue@3.4.21(typescript@5.6.2))
prettier:
specifier: ^3.2.5
version: 3.2.5
specifier: ^3.3.3
version: 3.3.3
qrcode.vue:
specifier: ^3.3.4
version: 3.3.4(vue@3.4.21(typescript@5.6.2))
@ -3819,10 +3812,6 @@ packages:
resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==}
engines: {node: '>=14'}
'@pkgr/core@0.1.1':
resolution: {integrity: sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==}
engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0}
'@protobufjs/aspromise@1.1.2':
resolution: {integrity: sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==}
@ -5435,12 +5424,6 @@ packages:
'@vue/devtools-api@6.5.0':
resolution: {integrity: sha512-o9KfBeaBmCKl10usN4crU53fYtC1r7jJwdGKjPT24t348rHxgfpZ0xL3Xm/gLUYnc0oTp8LAmrxOeLyu6tbk2Q==}
'@vue/eslint-config-prettier@9.0.0':
resolution: {integrity: sha512-z1ZIAAUS9pKzo/ANEfd2sO+v2IUalz7cM/cTLOZ7vRFOPk5/xuRKQteOu1DErFLAh/lYGXMVZ0IfYKlyInuDVg==}
peerDependencies:
eslint: '>= 8.0.0'
prettier: ^3.2.5
'@vue/eslint-config-typescript@13.0.0':
resolution: {integrity: sha512-MHh9SncG/sfqjVqjcuFLOLD6Ed4dRAis4HNt0dXASeAuLqIAx4YMB1/m2o4pUKK1vCt8fUvYG8KKX2Ot3BVZTg==}
engines: {node: ^18.18.0 || >=20.0.0}
@ -7171,20 +7154,6 @@ packages:
resolution: {integrity: sha512-Qj8S+YgymYkt/5Fr1buwOTjl0jAERJBp3MA5V8M6NR1HYfErKazVjpOPEy5+04c0vAQZO1mPLGAzanxqqNUIng==}
engines: {node: '>=20.15', pnpm: '>=9.6'}
eslint-plugin-prettier@5.1.3:
resolution: {integrity: sha512-C9GCVAs4Eq7ZC/XFQHITLiHJxQngdtraXaM+LoUFoFp/lHNl2Zn8f3WQbe9HvTBBQ9YnKFB0/2Ajdqwo5D1EAw==}
engines: {node: ^14.18.0 || >=16.0.0}
peerDependencies:
'@types/eslint': '>=8.0.0'
eslint: '>=8.0.0'
eslint-config-prettier: '*'
prettier: ^3.2.5
peerDependenciesMeta:
'@types/eslint':
optional: true
eslint-config-prettier:
optional: true
eslint-plugin-unicorn@51.0.1:
resolution: {integrity: sha512-MuR/+9VuB0fydoI0nIn2RDA5WISRn4AsJyNSaNKLVwie9/ONvQhxOBbkfSICBPnzKrB77Fh6CZZXjgTt/4Latw==}
engines: {node: '>=16'}
@ -7388,9 +7357,6 @@ packages:
fast-deep-equal@3.1.3:
resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
fast-diff@1.2.0:
resolution: {integrity: sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==}
fast-glob@3.2.12:
resolution: {integrity: sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==}
engines: {node: '>=8.6.0'}
@ -10440,12 +10406,8 @@ packages:
pretender@3.4.7:
resolution: {integrity: sha512-jkPAvt1BfRi0RKamweJdEcnjkeu7Es8yix3bJ+KgBC5VpG/Ln4JE3hYN6vJym4qprm8Xo5adhWpm3HCoft1dOw==}
prettier-linter-helpers@1.0.0:
resolution: {integrity: sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==}
engines: {node: '>=6.0.0'}
prettier@3.2.5:
resolution: {integrity: sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==}
prettier@3.3.3:
resolution: {integrity: sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==}
engines: {node: '>=14'}
hasBin: true
@ -11516,10 +11478,6 @@ packages:
symbol-tree@3.2.4:
resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==}
synckit@0.8.8:
resolution: {integrity: sha512-HwOKAP7Wc5aRGYdKH+dw0PRRpbO841v2DENBtjnR5HFWoiNByAl7vrx3p0G/rCyYXQsrxqtX48TImFtPcIHSpQ==}
engines: {node: ^14.18.0 || >=16.0.0}
syslog-client@1.1.1:
resolution: {integrity: sha512-c3qKw8JzCuHt0mwrzKQr8eqOc3RB28HgOpFuwGMO3GLscVpfR+0ECevWLZq/yIJTbx3WTb3QXBFVpTFtKAPDrw==}
@ -15578,8 +15536,6 @@ snapshots:
'@pkgjs/parseargs@0.11.0':
optional: true
'@pkgr/core@0.1.1': {}
'@protobufjs/aspromise@1.1.2': {}
'@protobufjs/base64@1.1.2': {}
@ -17814,15 +17770,6 @@ snapshots:
'@vue/devtools-api@6.5.0': {}
'@vue/eslint-config-prettier@9.0.0(@types/eslint@8.56.5)(eslint@8.57.0)(prettier@3.2.5)':
dependencies:
eslint: 8.57.0
eslint-config-prettier: 9.1.0(eslint@8.57.0)
eslint-plugin-prettier: 5.1.3(@types/eslint@8.56.5)(eslint-config-prettier@9.1.0(eslint@8.57.0))(eslint@8.57.0)(prettier@3.2.5)
prettier: 3.2.5
transitivePeerDependencies:
- '@types/eslint'
'@vue/eslint-config-typescript@13.0.0(eslint-plugin-vue@9.23.0(eslint@8.57.0))(eslint@8.57.0)(typescript@5.6.2)':
dependencies:
'@typescript-eslint/eslint-plugin': 7.2.0(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.6.2))(eslint@8.57.0)(typescript@5.6.2)
@ -19909,16 +19856,6 @@ snapshots:
- supports-color
- typescript
eslint-plugin-prettier@5.1.3(@types/eslint@8.56.5)(eslint-config-prettier@9.1.0(eslint@8.57.0))(eslint@8.57.0)(prettier@3.2.5):
dependencies:
eslint: 8.57.0
prettier: 3.2.5
prettier-linter-helpers: 1.0.0
synckit: 0.8.8
optionalDependencies:
'@types/eslint': 8.56.5
eslint-config-prettier: 9.1.0(eslint@8.57.0)
eslint-plugin-unicorn@51.0.1(eslint@8.57.0):
dependencies:
'@babel/helper-validator-identifier': 7.22.20
@ -20233,8 +20170,6 @@ snapshots:
fast-deep-equal@3.1.3: {}
fast-diff@1.2.0: {}
fast-glob@3.2.12:
dependencies:
'@nodelib/fs.stat': 2.0.5
@ -23882,11 +23817,7 @@ snapshots:
fake-xml-http-request: 2.1.2
route-recognizer: 0.3.4
prettier-linter-helpers@1.0.0:
dependencies:
fast-diff: 1.2.0
prettier@3.2.5: {}
prettier@3.3.3: {}
pretty-bytes@5.6.0: {}
@ -25252,11 +25183,6 @@ snapshots:
symbol-tree@3.2.4: {}
synckit@0.8.8:
dependencies:
'@pkgr/core': 0.1.1
tslib: 2.6.2
syslog-client@1.1.1: {}
tailwindcss@3.4.3(ts-node@10.9.2(@types/node@18.16.16)(typescript@5.6.2)):