directus

Форк
0
123 строки · 3.8 Кб
1
import { useEnv } from '@directus/env';
2
import { ServiceUnavailableError } from '@directus/errors';
3
import { EXTENSION_PKG_KEY, ExtensionManifest } from '@directus/extensions';
4
import { download, type DownloadOptions } from '@directus/extensions-registry';
5
import DriverLocal from '@directus/storage-driver-local';
6
import { move, remove } from 'fs-extra';
7
import { mkdir, readFile, rm } from 'node:fs/promises';
8
import { Readable } from 'node:stream';
9
import Queue from 'p-queue';
10
import { join } from 'path';
11
import type { ReadableStream } from 'stream/web';
12
import { extract } from 'tar';
13
import { useLogger } from '../../../logger.js';
14
import { getStorage } from '../../../storage/index.js';
15
import { getExtensionsPath } from '../get-extensions-path.js';
16

17
const env = useEnv();
18

19
export class InstallationManager {
20
	extensionPath = getExtensionsPath();
21

22
	async install(versionId: string) {
23
		const logger = useLogger();
24
		const tempDir = join(env['TEMP_PATH'] as string, 'marketplace', versionId);
25
		const tmpStorage = new DriverLocal({ root: tempDir });
26

27
		try {
28
			await mkdir(tempDir, { recursive: true });
29

30
			const options: DownloadOptions = {};
31

32
			if (env['MARKETPLACE_REGISTRY'] && typeof env['MARKETPLACE_REGISTRY'] === 'string') {
33
				options.registry = env['MARKETPLACE_REGISTRY'];
34
			}
35

36
			const tarReadableStream = await download(versionId, env['MARKETPLACE_TRUST'] === 'sandbox', options);
37

38
			if (!tarReadableStream) {
39
				throw new Error(`No readable stream returned from download`);
40
			}
41

42
			const tarStream = Readable.fromWeb(tarReadableStream as ReadableStream);
43
			const tarPath = join(tempDir, `bin.tar.tgz`);
44
			await tmpStorage.write('bin.tar.tgz', tarStream);
45

46
			/**
47
			 * NPM modules that are packed are always tarballed in a folder called "package"
48
			 */
49
			const extractedPath = 'package';
50

51
			await extract({
52
				file: tarPath,
53
				cwd: tempDir,
54
			});
55

56
			const packageFile = JSON.parse(
57
				await readFile(join(tempDir, extractedPath, 'package.json'), { encoding: 'utf-8' }),
58
			);
59

60
			const extensionManifest = await ExtensionManifest.parseAsync(packageFile);
61

62
			if (!extensionManifest[EXTENSION_PKG_KEY]?.type) {
63
				throw new Error(`Extension type not found in package.json`);
64
			}
65

66
			if (env['EXTENSIONS_LOCATION']) {
67
				// Upload the extension into the configured extensions location
68
				const storage = await getStorage();
69
				const remoteDisk = storage.location(env['EXTENSIONS_LOCATION'] as string);
70

71
				const queue = new Queue({ concurrency: 1000 });
72

73
				for await (const filepath of tmpStorage.list(extractedPath)) {
74
					const readStream = await tmpStorage.read(filepath);
75

76
					const remotePath = join(
77
						env['EXTENSIONS_PATH'] as string,
78
						'.registry',
79
						versionId,
80
						filepath.substring(extractedPath.length),
81
					);
82

83
					queue.add(() => remoteDisk.write(remotePath, readStream));
84
				}
85

86
				await queue.onIdle();
87
			} else {
88
				// No custom location, so save to regular local extensions folder
89
				const dest = join(this.extensionPath, '.registry', versionId);
90
				await move(join(tempDir, extractedPath), dest, { overwrite: true });
91
			}
92
		} catch (err) {
93
			logger.warn(err);
94

95
			throw new ServiceUnavailableError(
96
				{ service: 'marketplace', reason: 'Could not download and extract the extension' },
97
				{ cause: err },
98
			);
99
		} finally {
100
			await rm(tempDir, { recursive: true });
101
		}
102
	}
103

104
	async uninstall(folder: string) {
105
		if (env['EXTENSIONS_LOCATION']) {
106
			const storage = await getStorage();
107
			const remoteDisk = storage.location(env['EXTENSIONS_LOCATION'] as string);
108

109
			const queue = new Queue({ concurrency: 1000 });
110

111
			const prefix = join(env['EXTENSIONS_PATH'] as string, '.registry', folder);
112

113
			for await (const filepath of remoteDisk.list(prefix)) {
114
				queue.add(() => remoteDisk.delete(filepath));
115
			}
116

117
			await queue.onIdle();
118
		} else {
119
			const path = join(this.extensionPath, '.registry', folder);
120
			await remove(path);
121
		}
122
	}
123
}
124

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.