@simplysm/storage 13.0.99 → 14.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/clients/ftp-storage-client.d.ts +18 -18
- package/dist/clients/ftp-storage-client.d.ts.map +1 -1
- package/dist/clients/ftp-storage-client.js +127 -116
- package/dist/clients/ftp-storage-client.js.map +1 -6
- package/dist/clients/sftp-storage-client.d.ts +14 -14
- package/dist/clients/sftp-storage-client.d.ts.map +1 -1
- package/dist/clients/sftp-storage-client.js +132 -116
- package/dist/clients/sftp-storage-client.js.map +1 -6
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -6
- package/dist/storage-factory.d.ts +5 -5
- package/dist/storage-factory.js +34 -30
- package/dist/storage-factory.js.map +1 -6
- package/dist/types/storage-conn-config.js +2 -1
- package/dist/types/storage-conn-config.js.map +1 -6
- package/dist/types/storage-type.js +2 -1
- package/dist/types/storage-type.js.map +1 -6
- package/dist/types/storage.js +2 -1
- package/dist/types/storage.js.map +1 -6
- package/package.json +6 -7
- package/src/clients/ftp-storage-client.ts +22 -22
- package/src/clients/sftp-storage-client.ts +25 -25
- package/src/index.ts +3 -3
- package/src/storage-factory.ts +6 -6
- package/README.md +0 -142
- package/tests/ftp-storage-client.spec.ts +0 -261
- package/tests/sftp-storage-client.spec.ts +0 -253
- package/tests/storage-factory.spec.ts +0 -172
|
@@ -1,129 +1,145 @@
|
|
|
1
1
|
import { SdError } from "@simplysm/core-common";
|
|
2
2
|
import SftpClient from "ssh2-sftp-client";
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
3
|
+
/**
|
|
4
|
+
* SFTP 프로토콜을 사용하는 스토리지 클라이언트.
|
|
5
|
+
*
|
|
6
|
+
* @remarks
|
|
7
|
+
* 직접 사용하기보다 {@link StorageFactory.connect} 사용을 권장합니다.
|
|
8
|
+
*/
|
|
9
|
+
export class SftpStorageClient {
|
|
10
|
+
_client;
|
|
11
|
+
/**
|
|
12
|
+
* SFTP 서버에 연결합니다.
|
|
13
|
+
*
|
|
14
|
+
* @remarks
|
|
15
|
+
* - 사용 후 {@link close}로 연결을 종료해야 합니다.
|
|
16
|
+
* - 동일 인스턴스에서 여러 번 호출하지 마세요 (연결 누수).
|
|
17
|
+
* - 자동 연결/종료 관리를 위해 {@link StorageFactory.connect} 사용을 권장합니다.
|
|
18
|
+
*/
|
|
19
|
+
async connect(config) {
|
|
20
|
+
if (this._client !== undefined) {
|
|
21
|
+
throw new SdError("SFTP 서버에 이미 연결되어 있습니다. 먼저 close()를 호출해 주세요.");
|
|
22
|
+
}
|
|
23
|
+
const client = new SftpClient();
|
|
24
|
+
try {
|
|
25
|
+
if (config.password != null) {
|
|
26
|
+
await client.connect({
|
|
27
|
+
host: config.host,
|
|
28
|
+
port: config.port,
|
|
29
|
+
username: config.user,
|
|
30
|
+
password: config.password,
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
// SSH agent + 키 파일로 인증
|
|
35
|
+
const fsP = await import("fs/promises");
|
|
36
|
+
const os = await import("os");
|
|
37
|
+
const pathMod = await import("path");
|
|
38
|
+
const keyPath = pathMod.join(os.homedir(), ".ssh", "id_ed25519");
|
|
39
|
+
const baseOptions = {
|
|
40
|
+
host: config.host,
|
|
41
|
+
port: config.port,
|
|
42
|
+
username: config.user,
|
|
43
|
+
...(process.env["SSH_AUTH_SOCK"] != null ? { agent: process.env["SSH_AUTH_SOCK"] } : {}),
|
|
44
|
+
};
|
|
45
|
+
try {
|
|
46
|
+
await client.connect({
|
|
47
|
+
...baseOptions,
|
|
48
|
+
privateKey: await fsP.readFile(keyPath),
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
// privateKey 파싱 실패 (암호화된 키 등) -> agent만으로 재시도
|
|
53
|
+
await client.connect(baseOptions);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
this._client = client;
|
|
57
|
+
}
|
|
58
|
+
catch (err) {
|
|
59
|
+
await client.end();
|
|
60
|
+
throw err;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
_requireClient() {
|
|
64
|
+
if (this._client === undefined) {
|
|
65
|
+
throw new SdError("SFTP 서버에 연결되어 있지 않습니다.");
|
|
66
|
+
}
|
|
67
|
+
return this._client;
|
|
68
|
+
}
|
|
69
|
+
/** 디렉토리를 생성합니다. 부모 디렉토리가 없으면 함께 생성합니다. */
|
|
70
|
+
async mkdir(dirPath) {
|
|
71
|
+
await this._requireClient().mkdir(dirPath, true);
|
|
16
72
|
}
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
const os = await import("os");
|
|
29
|
-
const pathMod = await import("path");
|
|
30
|
-
const keyPath = pathMod.join(os.homedir(), ".ssh", "id_ed25519");
|
|
31
|
-
const baseOptions = {
|
|
32
|
-
host: config.host,
|
|
33
|
-
port: config.port,
|
|
34
|
-
username: config.user,
|
|
35
|
-
...process.env["SSH_AUTH_SOCK"] != null ? { agent: process.env["SSH_AUTH_SOCK"] } : {}
|
|
36
|
-
};
|
|
73
|
+
async rename(fromPath, toPath) {
|
|
74
|
+
await this._requireClient().rename(fromPath, toPath);
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* 파일 또는 디렉토리의 존재 여부를 확인합니다.
|
|
78
|
+
*
|
|
79
|
+
* @remarks
|
|
80
|
+
* 부모 디렉토리가 존재하지 않아도 false를 반환합니다.
|
|
81
|
+
* 네트워크 오류, 권한 오류 등 모든 예외에 대해 false를 반환합니다.
|
|
82
|
+
*/
|
|
83
|
+
async exists(filePath) {
|
|
37
84
|
try {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
}
|
|
43
|
-
|
|
85
|
+
// ssh2-sftp-client의 exists()는 false | 'd' | '-' | 'l'을 반환합니다.
|
|
86
|
+
// false: 존재하지 않음, 'd': 디렉토리, '-': 파일, 'l': 심볼릭 링크
|
|
87
|
+
const result = await this._requireClient().exists(filePath);
|
|
88
|
+
return typeof result === "string";
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
return false;
|
|
44
92
|
}
|
|
45
|
-
}
|
|
46
|
-
this._client = client;
|
|
47
|
-
} catch (err) {
|
|
48
|
-
await client.end();
|
|
49
|
-
throw err;
|
|
50
93
|
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
94
|
+
async list(dirPath) {
|
|
95
|
+
const list = await this._requireClient().list(dirPath);
|
|
96
|
+
return list.map((item) => ({
|
|
97
|
+
name: item.name,
|
|
98
|
+
isFile: item.type === "-",
|
|
99
|
+
}));
|
|
55
100
|
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
* @remarks
|
|
69
|
-
* Returns false even if the parent directory does not exist.
|
|
70
|
-
* Returns false for all exceptions including network errors and permission errors.
|
|
71
|
-
*/
|
|
72
|
-
async exists(filePath) {
|
|
73
|
-
try {
|
|
74
|
-
const result = await this._requireClient().exists(filePath);
|
|
75
|
-
return typeof result === "string";
|
|
76
|
-
} catch {
|
|
77
|
-
return false;
|
|
101
|
+
async readFile(filePath) {
|
|
102
|
+
// ssh2-sftp-client의 get()은 dst가 제공되지 않으면 Buffer를 반환합니다.
|
|
103
|
+
// 타입 정의(string | WritableStream | Buffer)와 달리 실제로는 Buffer만 반환됩니다.
|
|
104
|
+
const result = (await this._requireClient().get(filePath));
|
|
105
|
+
if (result instanceof Uint8Array) {
|
|
106
|
+
return result;
|
|
107
|
+
}
|
|
108
|
+
// 타입 정의상 string도 가능하므로 방어 코드
|
|
109
|
+
if (typeof result === "string") {
|
|
110
|
+
return new TextEncoder().encode(result);
|
|
111
|
+
}
|
|
112
|
+
throw new SdError("예상하지 못한 응답 타입입니다.");
|
|
78
113
|
}
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
const list = await this._requireClient().list(dirPath);
|
|
82
|
-
return list.map((item) => ({
|
|
83
|
-
name: item.name,
|
|
84
|
-
isFile: item.type === "-"
|
|
85
|
-
}));
|
|
86
|
-
}
|
|
87
|
-
async readFile(filePath) {
|
|
88
|
-
const result = await this._requireClient().get(filePath);
|
|
89
|
-
if (result instanceof Uint8Array) {
|
|
90
|
-
return result;
|
|
114
|
+
async remove(filePath) {
|
|
115
|
+
await this._requireClient().delete(filePath);
|
|
91
116
|
}
|
|
92
|
-
|
|
93
|
-
|
|
117
|
+
/** 로컬 파일 경로 또는 바이트 데이터를 원격 경로에 업로드합니다. */
|
|
118
|
+
async put(localPathOrBuffer, storageFilePath) {
|
|
119
|
+
if (typeof localPathOrBuffer === "string") {
|
|
120
|
+
await this._requireClient().fastPut(localPathOrBuffer, storageFilePath);
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
// eslint-disable-next-line no-restricted-globals -- ssh2-sftp-client library requirement
|
|
124
|
+
await this._requireClient().put(Buffer.from(localPathOrBuffer), storageFilePath);
|
|
125
|
+
}
|
|
94
126
|
}
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
async remove(filePath) {
|
|
98
|
-
await this._requireClient().delete(filePath);
|
|
99
|
-
}
|
|
100
|
-
/** Upload a local file path or byte data to the remote path. */
|
|
101
|
-
async put(localPathOrBuffer, storageFilePath) {
|
|
102
|
-
if (typeof localPathOrBuffer === "string") {
|
|
103
|
-
await this._requireClient().fastPut(localPathOrBuffer, storageFilePath);
|
|
104
|
-
} else {
|
|
105
|
-
await this._requireClient().put(Buffer.from(localPathOrBuffer), storageFilePath);
|
|
127
|
+
async uploadDir(fromPath, toPath) {
|
|
128
|
+
await this._requireClient().uploadDir(fromPath, toPath);
|
|
106
129
|
}
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
return;
|
|
130
|
+
/**
|
|
131
|
+
* 연결을 종료합니다.
|
|
132
|
+
*
|
|
133
|
+
* @remarks
|
|
134
|
+
* 이미 종료된 상태에서 호출해도 안전합니다 (오류 미발생).
|
|
135
|
+
* 종료 후 동일 인스턴스에서 {@link connect}를 다시 호출하여 재연결할 수 있습니다.
|
|
136
|
+
*/
|
|
137
|
+
async close() {
|
|
138
|
+
if (this._client === undefined) {
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
await this._client.end();
|
|
142
|
+
this._client = undefined;
|
|
121
143
|
}
|
|
122
|
-
await this._client.end();
|
|
123
|
-
this._client = void 0;
|
|
124
|
-
}
|
|
125
144
|
}
|
|
126
|
-
|
|
127
|
-
SftpStorageClient
|
|
128
|
-
};
|
|
129
|
-
//# sourceMappingURL=sftp-storage-client.js.map
|
|
145
|
+
//# sourceMappingURL=sftp-storage-client.js.map
|
|
@@ -1,6 +1 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"sources": ["../../src/clients/sftp-storage-client.ts"],
|
|
4
|
-
"mappings": "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",
|
|
5
|
-
"names": []
|
|
6
|
-
}
|
|
1
|
+
{"version":3,"file":"sftp-storage-client.js","sourceRoot":"","sources":["..\\..\\src\\clients\\sftp-storage-client.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
|
+
// 타입
|
|
1
2
|
export * from "./types/storage-conn-config.js";
|
|
2
3
|
export * from "./types/storage.js";
|
|
3
4
|
export * from "./types/storage-type.js";
|
|
5
|
+
// 클라이언트
|
|
4
6
|
export * from "./clients/ftp-storage-client.js";
|
|
5
7
|
export * from "./clients/sftp-storage-client.js";
|
|
8
|
+
// 팩토리
|
|
6
9
|
export * from "./storage-factory.js";
|
|
7
|
-
//# sourceMappingURL=index.js.map
|
|
10
|
+
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1,6 +1 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"sources": ["../src/index.ts"],
|
|
4
|
-
"mappings": "AACA,cAAc;AACd,cAAc;AACd,cAAc;AAGd,cAAc;AACd,cAAc;AAGd,cAAc;",
|
|
5
|
-
"names": []
|
|
6
|
-
}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["..\\src\\index.ts"],"names":[],"mappings":"AAAA,KAAK;AACL,cAAc,6BAA6B,CAAC;AAC5C,cAAc,iBAAiB,CAAC;AAChC,cAAc,sBAAsB,CAAC;AAErC,QAAQ;AACR,cAAc,8BAA8B,CAAC;AAC7C,cAAc,+BAA+B,CAAC;AAE9C,MAAM;AACN,cAAc,mBAAmB,CAAC"}
|
|
@@ -2,17 +2,17 @@ import type { StorageConnConfig } from "./types/storage-conn-config";
|
|
|
2
2
|
import type { StorageClient } from "./types/storage";
|
|
3
3
|
import type { StorageProtocol } from "./types/storage-type";
|
|
4
4
|
/**
|
|
5
|
-
*
|
|
5
|
+
* 스토리지 클라이언트 팩토리
|
|
6
6
|
*
|
|
7
|
-
*
|
|
7
|
+
* FTP, FTPS, SFTP 스토리지 연결을 생성하고 관리합니다.
|
|
8
8
|
*/
|
|
9
9
|
export declare class StorageFactory {
|
|
10
10
|
/**
|
|
11
|
-
*
|
|
11
|
+
* 스토리지에 연결하고, 콜백을 실행한 후, 자동으로 연결을 종료합니다.
|
|
12
12
|
*
|
|
13
13
|
* @remarks
|
|
14
|
-
*
|
|
15
|
-
*
|
|
14
|
+
* 콜백 패턴으로 연결/종료를 자동 관리하므로 직접 클라이언트를 사용하는 것보다 권장됩니다.
|
|
15
|
+
* 콜백에서 예외가 발생해도 연결은 자동으로 종료됩니다.
|
|
16
16
|
*/
|
|
17
17
|
static connect<R>(type: StorageProtocol, config: StorageConnConfig, fn: (storage: StorageClient) => R | Promise<R>): Promise<R>;
|
|
18
18
|
private static _createClient;
|
package/dist/storage-factory.js
CHANGED
|
@@ -1,35 +1,39 @@
|
|
|
1
1
|
import { FtpStorageClient } from "./clients/ftp-storage-client.js";
|
|
2
2
|
import { SftpStorageClient } from "./clients/sftp-storage-client.js";
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
3
|
+
/**
|
|
4
|
+
* 스토리지 클라이언트 팩토리
|
|
5
|
+
*
|
|
6
|
+
* FTP, FTPS, SFTP 스토리지 연결을 생성하고 관리합니다.
|
|
7
|
+
*/
|
|
8
|
+
export class StorageFactory {
|
|
9
|
+
/**
|
|
10
|
+
* 스토리지에 연결하고, 콜백을 실행한 후, 자동으로 연결을 종료합니다.
|
|
11
|
+
*
|
|
12
|
+
* @remarks
|
|
13
|
+
* 콜백 패턴으로 연결/종료를 자동 관리하므로 직접 클라이언트를 사용하는 것보다 권장됩니다.
|
|
14
|
+
* 콜백에서 예외가 발생해도 연결은 자동으로 종료됩니다.
|
|
15
|
+
*/
|
|
16
|
+
static async connect(type, config, fn) {
|
|
17
|
+
const client = StorageFactory._createClient(type);
|
|
18
|
+
await client.connect(config);
|
|
19
|
+
try {
|
|
20
|
+
return await fn(client);
|
|
21
|
+
}
|
|
22
|
+
finally {
|
|
23
|
+
await client.close().catch(() => {
|
|
24
|
+
// 이미 종료된 경우 무시
|
|
25
|
+
});
|
|
26
|
+
}
|
|
19
27
|
}
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
28
|
+
static _createClient(type) {
|
|
29
|
+
switch (type) {
|
|
30
|
+
case "sftp":
|
|
31
|
+
return new SftpStorageClient();
|
|
32
|
+
case "ftps":
|
|
33
|
+
return new FtpStorageClient(true);
|
|
34
|
+
case "ftp":
|
|
35
|
+
return new FtpStorageClient(false);
|
|
36
|
+
}
|
|
29
37
|
}
|
|
30
|
-
}
|
|
31
38
|
}
|
|
32
|
-
|
|
33
|
-
StorageFactory
|
|
34
|
-
};
|
|
35
|
-
//# sourceMappingURL=storage-factory.js.map
|
|
39
|
+
//# sourceMappingURL=storage-factory.js.map
|
|
@@ -1,6 +1 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"sources": ["../src/storage-factory.ts"],
|
|
4
|
-
"mappings": "AAGA,SAAS,wBAAwB;AACjC,SAAS,yBAAyB;AAO3B,MAAM,eAAe;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQ1B,aAAa,QACX,MACA,QACA,IACY;AACZ,UAAM,SAAS,eAAe,cAAc,IAAI;AAEhD,UAAM,OAAO,QAAQ,MAAM;AAC3B,QAAI;AACF,aAAO,MAAM,GAAG,MAAM;AAAA,IACxB,UAAE;AACA,YAAM,OAAO,MAAM,EAAE,MAAM,MAAM;AAAA,MAEjC,CAAC;AAAA,IACH;AAAA,EACF;AAAA,EAEA,OAAe,cAAc,MAAsC;AACjE,YAAQ,MAAM;AAAA,MACZ,KAAK;AACH,eAAO,IAAI,kBAAkB;AAAA,MAC/B,KAAK;AACH,eAAO,IAAI,iBAAiB,IAAI;AAAA,MAClC,KAAK;AACH,eAAO,IAAI,iBAAiB,KAAK;AAAA,IACrC;AAAA,EACF;AACF;",
|
|
5
|
-
"names": []
|
|
6
|
-
}
|
|
1
|
+
{"version":3,"file":"storage-factory.js","sourceRoot":"","sources":["..\\src\\storage-factory.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AAChE,OAAO,EAAE,iBAAiB,EAAE,MAAM,+BAA+B,CAAC;AAElE;;;;GAIG;AACH,MAAM,OAAO,cAAc;IACzB;;;;;;OAMG;IACH,MAAM,CAAC,KAAK,CAAC,OAAO,CAClB,IAAqB,EACrB,MAAyB,EACzB,EAA8C;QAE9C,MAAM,MAAM,GAAG,cAAc,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC;QAElD,MAAM,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;QAC7B,IAAI,CAAC;YACH,OAAO,MAAM,EAAE,CAAC,MAAM,CAAC,CAAC;QAC1B,CAAC;gBAAS,CAAC;YACT,MAAM,MAAM,CAAC,KAAK,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE;gBAC9B,eAAe;YACjB,CAAC,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAEO,MAAM,CAAC,aAAa,CAAC,IAAqB;QAChD,QAAQ,IAAI,EAAE,CAAC;YACb,KAAK,MAAM;gBACT,OAAO,IAAI,iBAAiB,EAAE,CAAC;YACjC,KAAK,MAAM;gBACT,OAAO,IAAI,gBAAgB,CAAC,IAAI,CAAC,CAAC;YACpC,KAAK,KAAK;gBACR,OAAO,IAAI,gBAAgB,CAAC,KAAK,CAAC,CAAC;QACvC,CAAC;IACH,CAAC;CACF"}
|
|
@@ -1 +1,2 @@
|
|
|
1
|
-
|
|
1
|
+
export {};
|
|
2
|
+
//# sourceMappingURL=storage-conn-config.js.map
|
|
@@ -1 +1,2 @@
|
|
|
1
|
-
|
|
1
|
+
export {};
|
|
2
|
+
//# sourceMappingURL=storage-type.js.map
|
package/dist/types/storage.js
CHANGED
|
@@ -1 +1,2 @@
|
|
|
1
|
-
|
|
1
|
+
export {};
|
|
2
|
+
//# sourceMappingURL=storage.js.map
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@simplysm/storage",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "
|
|
5
|
-
"author": "
|
|
3
|
+
"version": "14.0.1",
|
|
4
|
+
"description": "심플리즘 패키지 - 저장소 (node)",
|
|
5
|
+
"author": "심플리즘",
|
|
6
6
|
"license": "Apache-2.0",
|
|
7
7
|
"repository": {
|
|
8
8
|
"type": "git",
|
|
@@ -14,14 +14,13 @@
|
|
|
14
14
|
"types": "./dist/index.d.ts",
|
|
15
15
|
"files": [
|
|
16
16
|
"dist",
|
|
17
|
-
"src"
|
|
18
|
-
"tests"
|
|
17
|
+
"src"
|
|
19
18
|
],
|
|
20
19
|
"sideEffects": false,
|
|
21
20
|
"dependencies": {
|
|
22
21
|
"basic-ftp": "^5.2.0",
|
|
23
|
-
"ssh2-sftp-client": "^12.1.
|
|
24
|
-
"@simplysm/core-common": "
|
|
22
|
+
"ssh2-sftp-client": "^12.1.1",
|
|
23
|
+
"@simplysm/core-common": "14.0.1"
|
|
25
24
|
},
|
|
26
25
|
"devDependencies": {
|
|
27
26
|
"@types/ssh2-sftp-client": "^9.0.6"
|
|
@@ -6,11 +6,11 @@ import type { StorageClient, FileInfo } from "../types/storage";
|
|
|
6
6
|
import type { StorageConnConfig } from "../types/storage-conn-config";
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
|
-
*
|
|
9
|
+
* FTP/FTPS 프로토콜을 사용하는 스토리지 클라이언트.
|
|
10
10
|
*
|
|
11
11
|
* @remarks
|
|
12
|
-
*
|
|
13
|
-
*
|
|
12
|
+
* `secure` 생성자 매개변수로 FTPS 사용 여부를 설정합니다.
|
|
13
|
+
* 직접 사용하기보다 {@link StorageFactory.connect} 사용을 권장합니다.
|
|
14
14
|
*/
|
|
15
15
|
export class FtpStorageClient implements StorageClient {
|
|
16
16
|
private _client: ftp.Client | undefined;
|
|
@@ -18,16 +18,16 @@ export class FtpStorageClient implements StorageClient {
|
|
|
18
18
|
constructor(private readonly _secure: boolean = false) {}
|
|
19
19
|
|
|
20
20
|
/**
|
|
21
|
-
*
|
|
21
|
+
* FTP 서버에 연결합니다.
|
|
22
22
|
*
|
|
23
23
|
* @remarks
|
|
24
|
-
* -
|
|
25
|
-
* -
|
|
26
|
-
* -
|
|
24
|
+
* - 사용 후 {@link close}로 연결을 종료해야 합니다.
|
|
25
|
+
* - 동일 인스턴스에서 여러 번 호출하지 마세요 (연결 누수).
|
|
26
|
+
* - 자동 연결/종료 관리를 위해 {@link StorageFactory.connect} 사용을 권장합니다.
|
|
27
27
|
*/
|
|
28
28
|
async connect(config: StorageConnConfig): Promise<void> {
|
|
29
29
|
if (this._client !== undefined) {
|
|
30
|
-
throw new SdError("FTP
|
|
30
|
+
throw new SdError("FTP 서버에 이미 연결되어 있습니다. 먼저 close()를 호출해 주세요.");
|
|
31
31
|
}
|
|
32
32
|
const client = new ftp.Client();
|
|
33
33
|
try {
|
|
@@ -47,12 +47,12 @@ export class FtpStorageClient implements StorageClient {
|
|
|
47
47
|
|
|
48
48
|
private _requireClient(): ftp.Client {
|
|
49
49
|
if (this._client === undefined) {
|
|
50
|
-
throw new SdError("
|
|
50
|
+
throw new SdError("FTP 서버에 연결되어 있지 않습니다.");
|
|
51
51
|
}
|
|
52
52
|
return this._client;
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
-
/**
|
|
55
|
+
/** 디렉토리를 생성합니다. 부모 디렉토리가 없으면 함께 생성합니다. */
|
|
56
56
|
async mkdir(dirPath: string): Promise<void> {
|
|
57
57
|
await this._requireClient().ensureDir(dirPath);
|
|
58
58
|
}
|
|
@@ -78,24 +78,24 @@ export class FtpStorageClient implements StorageClient {
|
|
|
78
78
|
}
|
|
79
79
|
|
|
80
80
|
/**
|
|
81
|
-
*
|
|
81
|
+
* 파일 또는 디렉토리의 존재 여부를 확인합니다.
|
|
82
82
|
*
|
|
83
83
|
* @remarks
|
|
84
|
-
*
|
|
85
|
-
*
|
|
84
|
+
* 파일의 경우 size() 명령으로 O(1) 성능을 제공합니다.
|
|
85
|
+
* 디렉토리의 경우 부모 디렉토리 목록을 조회하므로 항목이 많으면 성능이 저하될 수 있습니다.
|
|
86
86
|
*
|
|
87
|
-
*
|
|
87
|
+
* 슬래시가 없는 경로(예: `file.txt`)는 루트 디렉토리(`/`)에서 검색합니다.
|
|
88
88
|
*
|
|
89
|
-
*
|
|
90
|
-
*
|
|
89
|
+
* 부모 디렉토리가 존재하지 않아도 false를 반환합니다.
|
|
90
|
+
* 네트워크 오류, 권한 오류 등 모든 예외에 대해 false를 반환합니다.
|
|
91
91
|
*/
|
|
92
92
|
async exists(filePath: string): Promise<boolean> {
|
|
93
93
|
try {
|
|
94
|
-
//
|
|
94
|
+
// size()로 파일 존재 여부 빠른 확인 (O(1))
|
|
95
95
|
await this._requireClient().size(filePath);
|
|
96
96
|
return true;
|
|
97
97
|
} catch {
|
|
98
|
-
//
|
|
98
|
+
// size() 실패 시 디렉토리일 수 있으므로 list()로 확인
|
|
99
99
|
try {
|
|
100
100
|
const lastSlash = filePath.lastIndexOf("/");
|
|
101
101
|
const dirPath = lastSlash > 0 ? filePath.substring(0, lastSlash) : "/";
|
|
@@ -112,7 +112,7 @@ export class FtpStorageClient implements StorageClient {
|
|
|
112
112
|
await this._requireClient().remove(filePath);
|
|
113
113
|
}
|
|
114
114
|
|
|
115
|
-
/**
|
|
115
|
+
/** 로컬 파일 경로 또는 바이트 데이터를 원격 경로에 업로드합니다. */
|
|
116
116
|
async put(localPathOrBuffer: string | Bytes, storageFilePath: string): Promise<void> {
|
|
117
117
|
let param: string | Readable;
|
|
118
118
|
if (typeof localPathOrBuffer === "string") {
|
|
@@ -128,11 +128,11 @@ export class FtpStorageClient implements StorageClient {
|
|
|
128
128
|
}
|
|
129
129
|
|
|
130
130
|
/**
|
|
131
|
-
*
|
|
131
|
+
* 연결을 종료합니다.
|
|
132
132
|
*
|
|
133
133
|
* @remarks
|
|
134
|
-
*
|
|
135
|
-
*
|
|
134
|
+
* 이미 종료된 상태에서 호출해도 안전합니다 (오류 미발생).
|
|
135
|
+
* 종료 후 동일 인스턴스에서 {@link connect}를 다시 호출하여 재연결할 수 있습니다.
|
|
136
136
|
*/
|
|
137
137
|
close(): Promise<void> {
|
|
138
138
|
if (this._client === undefined) {
|