mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-16 17:46:45 +00:00
feat(editor): Allow users to update verified nodes from the node settings panel (#16447)
Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com> Co-authored-by: Michael Kret <michael.k@radency.com>
This commit is contained in:
@@ -80,4 +80,44 @@ describe('CommunityPackagesController', () => {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('updatePackage', () => {
|
||||
it('should use the version from the request body when updating a package', async () => {
|
||||
const req = mock<NodeRequest.Update>({
|
||||
body: {
|
||||
name: 'n8n-nodes-test',
|
||||
version: '2.0.0',
|
||||
checksum: 'a893hfdsy7399',
|
||||
},
|
||||
user: { id: 'user1' },
|
||||
});
|
||||
|
||||
const previouslyInstalledPackage = mock<InstalledPackages>({
|
||||
installedNodes: [{ type: 'testNode', latestVersion: 1, name: 'testNode' }],
|
||||
installedVersion: '1.0.0',
|
||||
authorName: 'Author',
|
||||
authorEmail: 'author@example.com',
|
||||
});
|
||||
const newInstalledPackage = mock<InstalledPackages>({
|
||||
installedNodes: [{ type: 'testNode', latestVersion: 1, name: 'testNode' }],
|
||||
installedVersion: '2.0.0',
|
||||
authorName: 'Author',
|
||||
authorEmail: 'author@example.com',
|
||||
});
|
||||
|
||||
communityPackagesService.findInstalledPackage.mockResolvedValue(previouslyInstalledPackage);
|
||||
communityPackagesService.updatePackage.mockResolvedValue(newInstalledPackage);
|
||||
|
||||
const result = await controller.updatePackage(req);
|
||||
|
||||
expect(communityPackagesService.updatePackage).toHaveBeenCalledWith(
|
||||
'n8n-nodes-test',
|
||||
previouslyInstalledPackage,
|
||||
'2.0.0',
|
||||
'a893hfdsy7399',
|
||||
);
|
||||
|
||||
expect(result).toBe(newInstalledPackage);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -247,7 +247,7 @@ export class CommunityPackagesController {
|
||||
@Patch('/')
|
||||
@GlobalScope('communityPackage:update')
|
||||
async updatePackage(req: NodeRequest.Update) {
|
||||
const { name } = req.body;
|
||||
const { name, version, checksum } = req.body;
|
||||
|
||||
if (!name) {
|
||||
throw new BadRequestError(PACKAGE_NAME_NOT_PROVIDED);
|
||||
@@ -264,6 +264,8 @@ export class CommunityPackagesController {
|
||||
const newInstalledPackage = await this.communityPackagesService.updatePackage(
|
||||
this.communityPackagesService.parseNpmPackageName(name).packageName,
|
||||
previouslyInstalledPackage,
|
||||
version,
|
||||
checksum,
|
||||
);
|
||||
|
||||
// broadcast to connected frontends that node list has been updated
|
||||
|
||||
@@ -202,7 +202,11 @@ export declare namespace AnnotationTagsRequest {
|
||||
export declare namespace NodeRequest {
|
||||
type GetAll = AuthenticatedRequest;
|
||||
|
||||
type Post = AuthenticatedRequest<{}, {}, { name?: string; verify?: boolean; version?: string }>;
|
||||
type Post = AuthenticatedRequest<
|
||||
{},
|
||||
{},
|
||||
{ name?: string; verify?: boolean; version?: string; checksum?: string }
|
||||
>;
|
||||
|
||||
type Delete = AuthenticatedRequest<{}, {}, {}, { name: string }>;
|
||||
|
||||
|
||||
@@ -349,8 +349,10 @@ export class CommunityPackagesService {
|
||||
async updatePackage(
|
||||
packageName: string,
|
||||
installedPackage: InstalledPackages,
|
||||
version?: string,
|
||||
checksum?: string,
|
||||
): Promise<InstalledPackages> {
|
||||
return await this.installOrUpdatePackage(packageName, { installedPackage });
|
||||
return await this.installOrUpdatePackage(packageName, { installedPackage, version, checksum });
|
||||
}
|
||||
|
||||
async removePackage(packageName: string, installedPackage: InstalledPackages): Promise<void> {
|
||||
@@ -376,9 +378,7 @@ export class CommunityPackagesService {
|
||||
);
|
||||
}
|
||||
|
||||
private checkInstallPermissions(isUpdate: boolean, checksumProvided: boolean) {
|
||||
if (isUpdate) return;
|
||||
|
||||
private checkInstallPermissions(checksumProvided: boolean) {
|
||||
if (!this.globalConfig.nodes.communityPackages.unverifiedEnabled && !checksumProvided) {
|
||||
throw new UnexpectedError('Installation of unverified community packages is forbidden!');
|
||||
}
|
||||
@@ -386,15 +386,17 @@ export class CommunityPackagesService {
|
||||
|
||||
private async installOrUpdatePackage(
|
||||
packageName: string,
|
||||
options: { version?: string; checksum?: string } | { installedPackage: InstalledPackages },
|
||||
options:
|
||||
| { version?: string; checksum?: string }
|
||||
| { installedPackage: InstalledPackages; version?: string; checksum?: string } = {},
|
||||
) {
|
||||
const isUpdate = 'installedPackage' in options;
|
||||
const packageVersion = isUpdate || !options.version ? 'latest' : options.version;
|
||||
const packageVersion = !options.version ? 'latest' : options.version;
|
||||
|
||||
const shouldValidateChecksum = 'checksum' in options && Boolean(options.checksum);
|
||||
this.checkInstallPermissions(isUpdate, shouldValidateChecksum);
|
||||
this.checkInstallPermissions(shouldValidateChecksum);
|
||||
|
||||
if (!isUpdate && options.checksum) {
|
||||
if (options.checksum) {
|
||||
await verifyIntegrity(packageName, packageVersion, this.getNpmRegistry(), options.checksum);
|
||||
}
|
||||
|
||||
|
||||
@@ -1911,6 +1911,7 @@
|
||||
"settings.communityNodes.confirmModal.uninstall.buttonLoadingLabel": "Uninstalling",
|
||||
"settings.communityNodes.confirmModal.update.title": "Update community node package?",
|
||||
"settings.communityNodes.confirmModal.update.message": "You are about to update {packageName} to version {version}",
|
||||
"settings.communityNodes.confirmModal.update.warning": "This version has not been verified by n8n and may contain breaking changes or bugs.",
|
||||
"settings.communityNodes.confirmModal.update.description": "We recommend you deactivate workflows that use any of the package's nodes and reactivate them once the update is completed",
|
||||
"settings.communityNodes.confirmModal.update.buttonLabel": "Update package",
|
||||
"settings.communityNodes.confirmModal.update.buttonLoadingLabel": "Updating...",
|
||||
|
||||
@@ -27,8 +27,14 @@ export async function uninstallPackage(context: IRestApiContext, name: string):
|
||||
export async function updatePackage(
|
||||
context: IRestApiContext,
|
||||
name: string,
|
||||
version?: string,
|
||||
checksum?: string,
|
||||
): Promise<PublicInstalledPackage> {
|
||||
return await makeRestApiRequest(context, 'PATCH', '/community-packages', { name });
|
||||
return await makeRestApiRequest(context, 'PATCH', '/community-packages', {
|
||||
name,
|
||||
version,
|
||||
checksum,
|
||||
});
|
||||
}
|
||||
|
||||
export async function getAvailableCommunityPackageCount(): Promise<number> {
|
||||
|
||||
@@ -46,6 +46,7 @@
|
||||
"@n8n/utils": "workspace:*",
|
||||
"@replit/codemirror-indentation-markers": "^6.5.3",
|
||||
"@sentry/vue": "catalog:frontend",
|
||||
"@types/semver": "^7.7.0",
|
||||
"@typescript/vfs": "^1.6.0",
|
||||
"@vue-flow/background": "^1.3.2",
|
||||
"@vue-flow/controls": "^1.1.2",
|
||||
@@ -79,6 +80,7 @@
|
||||
"pinia": "catalog:frontend",
|
||||
"prettier": "^3.3.3",
|
||||
"qrcode.vue": "^3.3.4",
|
||||
"semver": "^7.5.4",
|
||||
"stream-browserify": "^3.0.0",
|
||||
"timeago.js": "^4.0.2",
|
||||
"typescript": "catalog:",
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
import { useNodeTypesStore } from '@/stores/nodeTypes.store';
|
||||
import { createTestingPinia } from '@pinia/testing';
|
||||
import { setActivePinia } from 'pinia';
|
||||
import CommunityPackageCard from './CommunityPackageCard.vue';
|
||||
import { createComponentRenderer } from '@/__tests__/render';
|
||||
|
||||
const communityPackage = {
|
||||
packageName: 'n8n-nodes-test',
|
||||
installedVersion: '1.0.0',
|
||||
installedNodes: [{ name: 'TestNode' }],
|
||||
};
|
||||
|
||||
const renderComponent = createComponentRenderer(CommunityPackageCard);
|
||||
|
||||
const flushPromises = async () => await new Promise(setImmediate);
|
||||
|
||||
describe('CommunityPackageCard', () => {
|
||||
let nodeTypesStore: ReturnType<typeof useNodeTypesStore>;
|
||||
|
||||
beforeEach(() => {
|
||||
const pinia = createTestingPinia();
|
||||
setActivePinia(pinia);
|
||||
|
||||
nodeTypesStore = useNodeTypesStore();
|
||||
});
|
||||
|
||||
it('should call nodeTypesStore methods and update latestVerifiedVersion when packageName changes', async () => {
|
||||
Object.defineProperty(nodeTypesStore, 'visibleNodeTypes', {
|
||||
get: () => [{ name: 'n8n-nodes-test' }],
|
||||
});
|
||||
nodeTypesStore.loadNodeTypesIfNotLoaded = vi.fn().mockResolvedValue(undefined);
|
||||
nodeTypesStore.getCommunityNodeAttributes = vi.fn().mockResolvedValue({ npmVersion: '2.0.0' });
|
||||
|
||||
renderComponent({
|
||||
props: {
|
||||
communityPackage,
|
||||
},
|
||||
});
|
||||
|
||||
await flushPromises();
|
||||
|
||||
expect(nodeTypesStore.loadNodeTypesIfNotLoaded).toHaveBeenCalled();
|
||||
expect(nodeTypesStore.getCommunityNodeAttributes).toHaveBeenCalledWith('n8n-nodes-test');
|
||||
});
|
||||
});
|
||||
@@ -6,6 +6,9 @@ import { useI18n } from '@n8n/i18n';
|
||||
import { useTelemetry } from '@/composables/useTelemetry';
|
||||
import { useSettingsStore } from '@/stores/settings.store';
|
||||
import type { UserAction } from '@n8n/design-system';
|
||||
import { useNodeTypesStore } from '@/stores/nodeTypes.store';
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import semver from 'semver';
|
||||
|
||||
interface Props {
|
||||
communityPackage?: PublicInstalledPackage | null;
|
||||
@@ -21,7 +24,23 @@ const { openCommunityPackageUpdateConfirmModal, openCommunityPackageUninstallCon
|
||||
useUIStore();
|
||||
const i18n = useI18n();
|
||||
const telemetry = useTelemetry();
|
||||
|
||||
const settingsStore = useSettingsStore();
|
||||
const nodeTypesStore = useNodeTypesStore();
|
||||
|
||||
const latestVerifiedVersion = ref<string>();
|
||||
const currVersion = computed(() => props.communityPackage?.installedVersion || '');
|
||||
|
||||
const hasUnverifiedPackagesUpdate = computed(() => {
|
||||
return settingsStore.isUnverifiedPackagesEnabled && props.communityPackage?.updateAvailable;
|
||||
});
|
||||
|
||||
const hasVerifiedPackageUpdate = computed(() => {
|
||||
const canUpdate =
|
||||
latestVerifiedVersion.value && semver.gt(latestVerifiedVersion.value || '', currVersion.value);
|
||||
|
||||
return settingsStore.isCommunityNodesFeatureEnabled && canUpdate;
|
||||
});
|
||||
|
||||
const packageActions: Array<UserAction<IUser>> = [
|
||||
{
|
||||
@@ -57,6 +76,24 @@ function onUpdateClick() {
|
||||
if (!props.communityPackage) return;
|
||||
openCommunityPackageUpdateConfirmModal(props.communityPackage.packageName);
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.communityPackage?.packageName,
|
||||
async (packageName) => {
|
||||
if (packageName) {
|
||||
await nodeTypesStore.loadNodeTypesIfNotLoaded();
|
||||
const nodeType = nodeTypesStore.visibleNodeTypes.find((node) =>
|
||||
node.name.includes(packageName),
|
||||
);
|
||||
|
||||
const attributes = await nodeTypesStore.getCommunityNodeAttributes(nodeType?.name || '');
|
||||
if (attributes?.npmVersion) {
|
||||
latestVerifiedVersion.value = attributes.npmVersion;
|
||||
}
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -99,7 +136,7 @@ function onUpdateClick() {
|
||||
<n8n-icon icon="exclamation-triangle" color="danger" size="large" />
|
||||
</n8n-tooltip>
|
||||
<n8n-tooltip
|
||||
v-else-if="settingsStore.isUnverifiedPackagesEnabled && communityPackage.updateAvailable"
|
||||
v-else-if="hasUnverifiedPackagesUpdate || hasVerifiedPackageUpdate"
|
||||
placement="top"
|
||||
>
|
||||
<template #content>
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
import { useNodeTypesStore } from '@/stores/nodeTypes.store';
|
||||
import { createComponentRenderer } from '@/__tests__/render';
|
||||
import CommunityPackageManageConfirmModal from './CommunityPackageManageConfirmModal.vue';
|
||||
import { cleanupAppModals, createAppModals, SETTINGS_STORE_DEFAULT_STATE } from '@/__tests__/utils';
|
||||
import { useSettingsStore } from '@/stores/settings.store';
|
||||
import { defaultSettings } from '@/__tests__/defaults';
|
||||
import { mockNodeTypeDescription } from '@/__tests__/mocks';
|
||||
import { createTestingPinia } from '@pinia/testing';
|
||||
import { STORES } from '@n8n/stores';
|
||||
import { COMMUNITY_PACKAGE_CONFIRM_MODAL_KEY } from '@/constants';
|
||||
|
||||
const renderComponent = createComponentRenderer(CommunityPackageManageConfirmModal, {
|
||||
data() {
|
||||
return {
|
||||
packageName: 'n8n-nodes-hello',
|
||||
};
|
||||
},
|
||||
pinia: createTestingPinia({
|
||||
initialState: {
|
||||
[STORES.UI]: {
|
||||
modalsById: {
|
||||
[COMMUNITY_PACKAGE_CONFIRM_MODAL_KEY]: { open: true },
|
||||
},
|
||||
},
|
||||
[STORES.COMMUNITY_NODES]: {
|
||||
installedPackages: {
|
||||
'n8n-nodes-test': {
|
||||
packageName: 'n8n-nodes-test',
|
||||
installedVersion: '1.0.0',
|
||||
updateAvailable: '2.0.0',
|
||||
},
|
||||
},
|
||||
},
|
||||
[STORES.NODE_TYPES]: {
|
||||
nodeTypes: {
|
||||
['n8n-nodes-test.test']: {
|
||||
1: mockNodeTypeDescription({
|
||||
name: 'n8n-nodes-test.test',
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
[STORES.SETTINGS]: {
|
||||
...SETTINGS_STORE_DEFAULT_STATE,
|
||||
settings: {
|
||||
...SETTINGS_STORE_DEFAULT_STATE.settings,
|
||||
communityNodesEnabled: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
const flushPromises = async () => await new Promise(setImmediate);
|
||||
|
||||
describe('CommunityPackageManageConfirmModal', () => {
|
||||
let nodeTypesStore: ReturnType<typeof useNodeTypesStore>;
|
||||
|
||||
beforeEach(() => {
|
||||
createAppModals();
|
||||
|
||||
nodeTypesStore = useNodeTypesStore();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
cleanupAppModals();
|
||||
});
|
||||
|
||||
it('should call nodeTypesStore methods and update latestVerifiedVersion on mount', async () => {
|
||||
nodeTypesStore.loadNodeTypesIfNotLoaded = vi.fn().mockResolvedValue(undefined);
|
||||
nodeTypesStore.getCommunityNodeAttributes = vi.fn().mockResolvedValue({ npmVersion: '2.0.0' });
|
||||
|
||||
renderComponent({
|
||||
props: {
|
||||
modalName: 'test-modal',
|
||||
activePackageName: 'n8n-nodes-test',
|
||||
mode: 'update',
|
||||
},
|
||||
});
|
||||
|
||||
await flushPromises();
|
||||
|
||||
expect(nodeTypesStore.loadNodeTypesIfNotLoaded).toHaveBeenCalled();
|
||||
expect(nodeTypesStore.getCommunityNodeAttributes).toHaveBeenCalledWith('n8n-nodes-test.test');
|
||||
});
|
||||
|
||||
it('should call nodeTypesStore methods and update latestVerifiedVersion on mount', async () => {
|
||||
useSettingsStore().setSettings({ ...defaultSettings, communityNodesEnabled: true });
|
||||
|
||||
nodeTypesStore.loadNodeTypesIfNotLoaded = vi.fn().mockResolvedValue(undefined);
|
||||
nodeTypesStore.getCommunityNodeAttributes = vi.fn().mockResolvedValue({ npmVersion: '1.5.0' });
|
||||
|
||||
const { getByTestId } = renderComponent({
|
||||
props: {
|
||||
modalName: 'test-modal',
|
||||
activePackageName: 'n8n-nodes-test',
|
||||
mode: 'update',
|
||||
},
|
||||
});
|
||||
|
||||
await flushPromises();
|
||||
|
||||
const testId = getByTestId('communityPackageManageConfirmModal-warning');
|
||||
expect(testId).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -6,8 +6,11 @@ import { useCommunityNodesStore } from '@/stores/communityNodes.store';
|
||||
import { createEventBus } from '@n8n/utils/event-bus';
|
||||
import { useI18n } from '@n8n/i18n';
|
||||
import { useTelemetry } from '@/composables/useTelemetry';
|
||||
import { computed, ref } from 'vue';
|
||||
import { computed, onMounted, ref } from 'vue';
|
||||
import { useNodeTypesStore } from '@/stores/nodeTypes.store';
|
||||
import type { CommunityNodeType } from '@n8n/api-types';
|
||||
import { useSettingsStore } from '@/stores/settings.store';
|
||||
import semver from 'semver';
|
||||
|
||||
export type CommunityPackageManageMode = 'uninstall' | 'update' | 'view-documentation';
|
||||
|
||||
@@ -20,6 +23,8 @@ interface Props {
|
||||
const props = defineProps<Props>();
|
||||
|
||||
const communityNodesStore = useCommunityNodesStore();
|
||||
const nodeTypesStore = useNodeTypesStore();
|
||||
const settingsStore = useSettingsStore();
|
||||
|
||||
const modalBus = createEventBus();
|
||||
|
||||
@@ -29,9 +34,24 @@ const telemetry = useTelemetry();
|
||||
|
||||
const loading = ref(false);
|
||||
|
||||
const activePackage = computed(
|
||||
const isUsingVerifiedAndUnverifiedPackages =
|
||||
settingsStore.isCommunityNodesFeatureEnabled && settingsStore.isUnverifiedPackagesEnabled;
|
||||
const isUsingVerifiedPackagesOnly =
|
||||
settingsStore.isCommunityNodesFeatureEnabled && !settingsStore.isUnverifiedPackagesEnabled;
|
||||
|
||||
const communityStorePackage = computed(
|
||||
() => communityNodesStore.installedPackages[props.activePackageName],
|
||||
);
|
||||
const updateVersion = computed(() => {
|
||||
return settingsStore.isUnverifiedPackagesEnabled
|
||||
? communityStorePackage.value.updateAvailable
|
||||
: nodeTypeStorePackage.value?.npmVersion;
|
||||
});
|
||||
const nodeTypeStorePackage = ref<CommunityNodeType>();
|
||||
|
||||
const isLatestPackageVerified = ref<boolean>(true);
|
||||
|
||||
const packageVersion = ref<string>(communityStorePackage.value.updateAvailable ?? '');
|
||||
|
||||
const getModalContent = computed(() => {
|
||||
if (props.mode === COMMUNITY_PACKAGE_MANAGE_ACTIONS.UNINSTALL) {
|
||||
@@ -55,10 +75,11 @@ const getModalContent = computed(() => {
|
||||
},
|
||||
}),
|
||||
description: i18n.baseText('settings.communityNodes.confirmModal.update.description'),
|
||||
warning: i18n.baseText('settings.communityNodes.confirmModal.update.warning'),
|
||||
message: i18n.baseText('settings.communityNodes.confirmModal.update.message', {
|
||||
interpolate: {
|
||||
packageName: props.activePackageName,
|
||||
version: activePackage.value.updateAvailable ?? '',
|
||||
version: packageVersion.value,
|
||||
},
|
||||
}),
|
||||
buttonLabel: i18n.baseText('settings.communityNodes.confirmModal.update.buttonLabel'),
|
||||
@@ -83,11 +104,11 @@ const onConfirmButtonClick = async () => {
|
||||
const onUninstall = async () => {
|
||||
try {
|
||||
telemetry.track('user started cnr package deletion', {
|
||||
package_name: activePackage.value.packageName,
|
||||
package_node_names: activePackage.value.installedNodes.map((node) => node.name),
|
||||
package_version: activePackage.value.installedVersion,
|
||||
package_author: activePackage.value.authorName,
|
||||
package_author_email: activePackage.value.authorEmail,
|
||||
package_name: communityStorePackage.value.packageName,
|
||||
package_node_names: communityStorePackage.value.installedNodes.map((node) => node.name),
|
||||
package_version: communityStorePackage.value.installedVersion,
|
||||
package_author: communityStorePackage.value.authorName,
|
||||
package_author_email: communityStorePackage.value.authorEmail,
|
||||
});
|
||||
loading.value = true;
|
||||
await communityNodesStore.uninstallPackage(props.activePackageName);
|
||||
@@ -107,23 +128,34 @@ const onUninstall = async () => {
|
||||
const onUpdate = async () => {
|
||||
try {
|
||||
telemetry.track('user started cnr package update', {
|
||||
package_name: activePackage.value.packageName,
|
||||
package_node_names: activePackage.value.installedNodes.map((node) => node.name),
|
||||
package_version_current: activePackage.value.installedVersion,
|
||||
package_version_new: activePackage.value.updateAvailable,
|
||||
package_author: activePackage.value.authorName,
|
||||
package_author_email: activePackage.value.authorEmail,
|
||||
package_name: communityStorePackage.value.packageName,
|
||||
package_node_names: communityStorePackage.value.installedNodes.map((node) => node.name),
|
||||
package_version_current: communityStorePackage.value.installedVersion,
|
||||
package_version_new: communityStorePackage.value.updateAvailable,
|
||||
package_author: communityStorePackage.value.authorName,
|
||||
package_author_email: communityStorePackage.value.authorEmail,
|
||||
});
|
||||
loading.value = true;
|
||||
const updatedVersion = activePackage.value.updateAvailable;
|
||||
await communityNodesStore.updatePackage(props.activePackageName);
|
||||
|
||||
if (settingsStore.isUnverifiedPackagesEnabled) {
|
||||
await communityNodesStore.updatePackage(props.activePackageName);
|
||||
} else if (settingsStore.isCommunityNodesFeatureEnabled) {
|
||||
await communityNodesStore.updatePackage(
|
||||
props.activePackageName,
|
||||
updateVersion.value,
|
||||
nodeTypeStorePackage.value?.checksum,
|
||||
);
|
||||
} else {
|
||||
throw new Error('Community nodes feature is not correctly enabled.');
|
||||
}
|
||||
|
||||
await useNodeTypesStore().getNodeTypes();
|
||||
toast.showMessage({
|
||||
title: i18n.baseText('settings.communityNodes.messages.update.success.title'),
|
||||
message: i18n.baseText('settings.communityNodes.messages.update.success.message', {
|
||||
interpolate: {
|
||||
packageName: props.activePackageName,
|
||||
version: updatedVersion ?? '',
|
||||
version: updateVersion.value ?? '',
|
||||
},
|
||||
}),
|
||||
type: 'success',
|
||||
@@ -135,6 +167,47 @@ const onUpdate = async () => {
|
||||
modalBus.emit('close');
|
||||
}
|
||||
};
|
||||
|
||||
async function fetchPackageInfo(packageName: string) {
|
||||
await nodeTypesStore.loadNodeTypesIfNotLoaded();
|
||||
const nodeType = nodeTypesStore.visibleNodeTypes.find((nodeType) =>
|
||||
nodeType.name.includes(packageName),
|
||||
);
|
||||
|
||||
if (nodeType) {
|
||||
const communityNodeAttributes = await nodeTypesStore.getCommunityNodeAttributes(nodeType?.name);
|
||||
|
||||
nodeTypeStorePackage.value = communityNodeAttributes ?? undefined;
|
||||
}
|
||||
}
|
||||
|
||||
function setIsVerifiedLatestPackage() {
|
||||
if (
|
||||
isUsingVerifiedAndUnverifiedPackages &&
|
||||
nodeTypeStorePackage.value?.npmVersion &&
|
||||
communityStorePackage.value.updateAvailable
|
||||
) {
|
||||
isLatestPackageVerified.value = semver.eq(
|
||||
nodeTypeStorePackage.value.npmVersion,
|
||||
communityStorePackage.value.updateAvailable,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function setPackageVersion() {
|
||||
if (isUsingVerifiedPackagesOnly) {
|
||||
packageVersion.value = nodeTypeStorePackage.value?.npmVersion ?? packageVersion.value;
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
if (props.activePackageName) {
|
||||
await fetchPackageInfo(props.activePackageName);
|
||||
}
|
||||
|
||||
setIsVerifiedLatestPackage();
|
||||
setPackageVersion();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -156,6 +229,11 @@ const onUpdate = async () => {
|
||||
<n8n-info-tip theme="info" type="note" :bold="false">
|
||||
<span v-text="getModalContent.description"></span>
|
||||
</n8n-info-tip>
|
||||
<n8n-notice
|
||||
data-test-id="communityPackageManageConfirmModal-warning"
|
||||
v-if="!isLatestPackageVerified"
|
||||
:content="getModalContent.warning"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<template #footer>
|
||||
@@ -175,6 +253,7 @@ const onUpdate = async () => {
|
||||
.descriptionContainer {
|
||||
display: flex;
|
||||
margin: var(--spacing-s) 0;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.descriptionIcon {
|
||||
|
||||
@@ -81,11 +81,17 @@ export const useCommunityNodesStore = defineStore(STORES.COMMUNITY_NODES, () =>
|
||||
installedPackages.value[newPackage.packageName] = newPackage;
|
||||
};
|
||||
|
||||
const updatePackage = async (packageName: string): Promise<void> => {
|
||||
const updatePackage = async (
|
||||
packageName: string,
|
||||
version?: string,
|
||||
checksum?: string,
|
||||
): Promise<void> => {
|
||||
const packageToUpdate = installedPackages.value[packageName];
|
||||
const updatedPackage = await communityNodesApi.updatePackage(
|
||||
rootStore.restApiContext,
|
||||
packageToUpdate.packageName,
|
||||
version,
|
||||
checksum,
|
||||
);
|
||||
updatePackageObject(updatedPackage);
|
||||
};
|
||||
@@ -99,5 +105,6 @@ export const useCommunityNodesStore = defineStore(STORES.COMMUNITY_NODES, () =>
|
||||
installPackage,
|
||||
uninstallPackage,
|
||||
updatePackage,
|
||||
setInstalledPackages,
|
||||
};
|
||||
});
|
||||
|
||||
20
pnpm-lock.yaml
generated
20
pnpm-lock.yaml
generated
@@ -2261,6 +2261,9 @@ importers:
|
||||
'@sentry/vue':
|
||||
specifier: catalog:frontend
|
||||
version: 8.33.1(vue@3.5.13(typescript@5.8.3))
|
||||
'@types/semver':
|
||||
specifier: ^7.7.0
|
||||
version: 7.7.0
|
||||
'@typescript/vfs':
|
||||
specifier: ^1.6.0
|
||||
version: 1.6.0(typescript@5.8.3)
|
||||
@@ -2360,6 +2363,9 @@ importers:
|
||||
qrcode.vue:
|
||||
specifier: ^3.3.4
|
||||
version: 3.3.4(vue@3.5.13(typescript@5.8.3))
|
||||
semver:
|
||||
specifier: ^7.5.4
|
||||
version: 7.7.2
|
||||
stream-browserify:
|
||||
specifier: ^3.0.0
|
||||
version: 3.0.0
|
||||
@@ -6986,8 +6992,8 @@ packages:
|
||||
'@types/scheduler@0.26.0':
|
||||
resolution: {integrity: sha512-WFHp9YUJQ6CKshqoC37iOlHnQSmxNc795UhB26CyBBttrN9svdIrUjl/NjnNmfcwtncN0h/0PPAFWv9ovP8mLA==}
|
||||
|
||||
'@types/semver@7.5.0':
|
||||
resolution: {integrity: sha512-G8hZ6XJiHnuhQKR7ZmysCeJWE08o8T0AXtk5darsCaTVsYZhhgUrq53jizaR2FvsoeCwJhlmwTjkXBY5Pn/ZHw==}
|
||||
'@types/semver@7.7.0':
|
||||
resolution: {integrity: sha512-k107IF4+Xr7UHjwDc7Cfd6PRQfbdkiRabXGRjo07b4WyPahFBZCZ1sE+BNxYIJPPg73UkfOsVOLwqVc/6ETrIA==}
|
||||
|
||||
'@types/send@0.17.4':
|
||||
resolution: {integrity: sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==}
|
||||
@@ -20621,7 +20627,7 @@ snapshots:
|
||||
|
||||
'@types/scheduler@0.26.0': {}
|
||||
|
||||
'@types/semver@7.5.0': {}
|
||||
'@types/semver@7.7.0': {}
|
||||
|
||||
'@types/send@0.17.4':
|
||||
dependencies:
|
||||
@@ -20844,7 +20850,7 @@ snapshots:
|
||||
globby: 11.1.0
|
||||
is-glob: 4.0.3
|
||||
minimatch: 9.0.3
|
||||
semver: 7.6.0
|
||||
semver: 7.7.2
|
||||
ts-api-utils: 1.4.3(typescript@5.8.3)
|
||||
optionalDependencies:
|
||||
typescript: 5.8.3
|
||||
@@ -20871,7 +20877,7 @@ snapshots:
|
||||
dependencies:
|
||||
'@eslint-community/eslint-utils': 4.7.0(eslint@9.29.0(jiti@1.21.0))
|
||||
'@types/json-schema': 7.0.15
|
||||
'@types/semver': 7.5.0
|
||||
'@types/semver': 7.7.0
|
||||
'@typescript-eslint/scope-manager': 6.21.0
|
||||
'@typescript-eslint/types': 6.21.0
|
||||
'@typescript-eslint/typescript-estree': 6.21.0(typescript@5.8.3)
|
||||
@@ -27065,7 +27071,7 @@ snapshots:
|
||||
ignore-by-default: 1.0.1
|
||||
minimatch: 3.1.2
|
||||
pstree.remy: 1.1.8
|
||||
semver: 7.6.0
|
||||
semver: 7.7.2
|
||||
simple-update-notifier: 2.0.0
|
||||
supports-color: 5.5.0
|
||||
touch: 3.1.0
|
||||
@@ -29456,7 +29462,7 @@ snapshots:
|
||||
json5: 2.2.3
|
||||
lodash.memoize: 4.1.2
|
||||
make-error: 1.3.6
|
||||
semver: 7.6.0
|
||||
semver: 7.7.2
|
||||
typescript: 5.8.3
|
||||
yargs-parser: 21.1.1
|
||||
optionalDependencies:
|
||||
|
||||
Reference in New Issue
Block a user