create-entity-server 0.5.2 → 0.5.4
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/package.json +1 -1
- package/template/configs/auth/csrf.json +12 -0
- package/template/entities/System/Auth/account.json +1 -1
- package/template/entities/System/Auth/account_audit.json +2 -6
- package/template/entities/System/Auth/account_device.json +1 -1
- package/template/entities/System/Auth/account_login_log.json +1 -1
- package/template/entities/System/Auth/anon_device.json +1 -1
- package/template/entities/System/Auth/api_keys.json +1 -2
- package/template/entities/System/Auth/license.json +1 -1
- package/template/entities/System/Auth/rbac_roles.json +2 -4
- package/template/entities/System/Backup/backup_log.json +3 -12
- package/template/entities/System/Email/smtp_log.json +2 -8
- package/template/entities/System/Email/smtp_msg.json +2 -6
- package/template/entities/System/Storage/file_backup_log.json +2 -7
- package/template/entities/System/Storage/file_download_log.json +1 -1
- package/template/entities/System/Storage/file_meta.json +3 -14
- package/template/entities/System/system_audit_log.json +1 -1
- package/template/entities/company.json +1 -1
- package/template/entities/goods.json +2 -6
- package/template/entities/todo.json +2 -5
- package/template/samples/react/src/example.tsx +2 -2
- package/template/scripts/run.sh +2 -13
- package/template/scripts/service-install.sh +2 -2
- package/template/scripts/service-remove.sh +2 -2
- package/template/scripts/update-server.sh +19 -5
package/package.json
CHANGED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_comment": "브라우저 직접 통신용 CSRF 설정 예시. access/refresh 는 HttpOnly 쿠키, CSRF 는 double-submit cookie + header 로 사용합니다.",
|
|
3
|
+
"enabled": true,
|
|
4
|
+
"header_name": "X-CSRF-Token",
|
|
5
|
+
"cookie_name": "_csrf",
|
|
6
|
+
"cookie_path": "/",
|
|
7
|
+
"cookie_max_age_sec": 86400,
|
|
8
|
+
"same_site": "Lax",
|
|
9
|
+
"issue_on_health": true,
|
|
10
|
+
"protect_methods": ["POST", "PUT", "PATCH", "DELETE"],
|
|
11
|
+
"ignore_paths": ["/v1/health", "/v1/alimtalk/webhook/"]
|
|
12
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "account_audit",
|
|
3
|
-
"description": "account 감사 로그. account.json 훅(after_insert/after_update/after_delete)에 의해 자동 기록됩니다.
|
|
3
|
+
"description": "account 감사 로그. account.json 훅(after_insert/after_update/after_delete)에 의해 자동 기록됩니다.",
|
|
4
4
|
"hard_delete": true,
|
|
5
5
|
"read_only": true,
|
|
6
6
|
"compress": true,
|
|
@@ -14,11 +14,7 @@
|
|
|
14
14
|
"action": {
|
|
15
15
|
"index": true,
|
|
16
16
|
"comment": "작업 유형 (after_insert → INSERT, after_update → UPDATE, after_delete → DELETE)",
|
|
17
|
-
"type": [
|
|
18
|
-
"INSERT",
|
|
19
|
-
"UPDATE",
|
|
20
|
-
"DELETE"
|
|
21
|
-
],
|
|
17
|
+
"type": ["INSERT", "UPDATE", "DELETE"],
|
|
22
18
|
"required": true
|
|
23
19
|
},
|
|
24
20
|
"email": {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "account_device",
|
|
3
|
-
"description": "회원 계정 기기 정보 및 푸시 토큰 관리. account_seq로 계정과 연결되며 회원 전용으로 사용합니다.
|
|
3
|
+
"description": "회원 계정 기기 정보 및 푸시 토큰 관리. account_seq로 계정과 연결되며 회원 전용으로 사용합니다.",
|
|
4
4
|
"license_scope": false,
|
|
5
5
|
"fields": {
|
|
6
6
|
"account_seq": {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "anon_device",
|
|
3
|
-
"description": "비회원 디바이스 식별 및 사용 제한 집계. device_id 쿠키 값 기반으로 추적하며 익명 사용자 전용으로 사용합니다.
|
|
3
|
+
"description": "비회원 디바이스 식별 및 사용 제한 집계. device_id 쿠키 값 기반으로 추적하며 익명 사용자 전용으로 사용합니다.",
|
|
4
4
|
"license_scope": false,
|
|
5
5
|
"fields": {
|
|
6
6
|
"id": {
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "api_keys",
|
|
3
|
-
"description": "API 키 및 HMAC 시크릿 관리. account_seq는 JWT 인증 시에만 FK.
|
|
3
|
+
"description": "API 키 및 HMAC 시크릿 관리. account_seq는 JWT 인증 시에만 FK.",
|
|
4
4
|
"license_scope": false,
|
|
5
5
|
"fields": {
|
|
6
6
|
"key_value": {
|
|
7
|
-
"index": true,
|
|
8
7
|
"comment": "API 키 Hash 값",
|
|
9
8
|
"required": true,
|
|
10
9
|
"unique": true,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rbac_roles",
|
|
3
|
-
"description": "역할 기반 접근 제어 역할 정의.
|
|
3
|
+
"description": "역할 기반 접근 제어 역할 정의.",
|
|
4
4
|
"license_scope": false,
|
|
5
5
|
"hard_delete": true,
|
|
6
6
|
"fields": {
|
|
@@ -18,9 +18,7 @@
|
|
|
18
18
|
{
|
|
19
19
|
"name": "admin",
|
|
20
20
|
"description": "전체 권한 (모든 엔티티 CRUD + 관리)",
|
|
21
|
-
"permissions": [
|
|
22
|
-
"*"
|
|
23
|
-
]
|
|
21
|
+
"permissions": ["*"]
|
|
24
22
|
},
|
|
25
23
|
{
|
|
26
24
|
"name": "editor",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "backup_log",
|
|
3
|
-
"description": "백업 작업 이력.
|
|
3
|
+
"description": "백업 작업 이력.",
|
|
4
4
|
"license_scope": false,
|
|
5
5
|
"hard_delete": true,
|
|
6
6
|
"history": false,
|
|
@@ -8,11 +8,7 @@
|
|
|
8
8
|
"fields": {
|
|
9
9
|
"backup_type": {
|
|
10
10
|
"index": true,
|
|
11
|
-
"type": [
|
|
12
|
-
"data",
|
|
13
|
-
"file",
|
|
14
|
-
"full"
|
|
15
|
-
],
|
|
11
|
+
"type": ["data", "file", "full"],
|
|
16
12
|
"required": true
|
|
17
13
|
},
|
|
18
14
|
"finished_time": {
|
|
@@ -23,12 +19,7 @@
|
|
|
23
19
|
},
|
|
24
20
|
"status": {
|
|
25
21
|
"index": true,
|
|
26
|
-
"type": [
|
|
27
|
-
"running",
|
|
28
|
-
"completed",
|
|
29
|
-
"partial",
|
|
30
|
-
"failed"
|
|
31
|
-
],
|
|
22
|
+
"type": ["running", "completed", "partial", "failed"],
|
|
32
23
|
"required": true
|
|
33
24
|
},
|
|
34
25
|
"session_id": {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "smtp_log",
|
|
3
|
-
"description": "SMTP 이메일 발송 이력. 발송 결과 추적 및 재시도 관리용.
|
|
3
|
+
"description": "SMTP 이메일 발송 이력. 발송 결과 추적 및 재시도 관리용.",
|
|
4
4
|
"license_scope": false,
|
|
5
5
|
"hard_delete": true,
|
|
6
6
|
"history": false,
|
|
@@ -29,13 +29,7 @@
|
|
|
29
29
|
},
|
|
30
30
|
"status": {
|
|
31
31
|
"index": true,
|
|
32
|
-
"type": [
|
|
33
|
-
"pending",
|
|
34
|
-
"processing",
|
|
35
|
-
"sent",
|
|
36
|
-
"failed",
|
|
37
|
-
"expired"
|
|
38
|
-
],
|
|
32
|
+
"type": ["pending", "processing", "sent", "failed", "expired"],
|
|
39
33
|
"default": "pending",
|
|
40
34
|
"required": true
|
|
41
35
|
},
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "smtp_msg",
|
|
3
|
-
"description": "SMTP 이메일 트리거 엔티티. insert 시 smtp hook으로 비동기 발송.
|
|
3
|
+
"description": "SMTP 이메일 트리거 엔티티. insert 시 smtp hook으로 비동기 발송.",
|
|
4
4
|
"hard_delete": true,
|
|
5
5
|
"history": false,
|
|
6
6
|
"fields": {
|
|
@@ -11,11 +11,7 @@
|
|
|
11
11
|
},
|
|
12
12
|
"status": {
|
|
13
13
|
"index": true,
|
|
14
|
-
"type": [
|
|
15
|
-
"queued",
|
|
16
|
-
"sent",
|
|
17
|
-
"failed"
|
|
18
|
-
],
|
|
14
|
+
"type": ["queued", "sent", "failed"],
|
|
19
15
|
"default": "queued",
|
|
20
16
|
"required": true
|
|
21
17
|
},
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "file_backup_log",
|
|
3
|
-
"description": "파일 백업 작업 로그. 스토리지 간 백업 동기화 이력을 기록합니다. 서버 내부 백업 고루틴이 자동 기록.
|
|
3
|
+
"description": "파일 백업 작업 로그. 스토리지 간 백업 동기화 이력을 기록합니다. 서버 내부 백업 고루틴이 자동 기록.",
|
|
4
4
|
"license_scope": false,
|
|
5
5
|
"history": false,
|
|
6
6
|
"hard_delete": true,
|
|
@@ -14,12 +14,7 @@
|
|
|
14
14
|
"status": {
|
|
15
15
|
"index": true,
|
|
16
16
|
"comment": "작업 상태",
|
|
17
|
-
"type": [
|
|
18
|
-
"running",
|
|
19
|
-
"completed",
|
|
20
|
-
"partial",
|
|
21
|
-
"failed"
|
|
22
|
-
],
|
|
17
|
+
"type": ["running", "completed", "partial", "failed"],
|
|
23
18
|
"default": "running",
|
|
24
19
|
"required": true
|
|
25
20
|
},
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "file_download_log",
|
|
3
|
-
"description": "파일 다운로드 이력. 누가 어떤 파일을 언제 다운로드했는지 기록합니다. 감사(audit) 및 통계 목적.
|
|
3
|
+
"description": "파일 다운로드 이력. 누가 어떤 파일을 언제 다운로드했는지 기록합니다. 감사(audit) 및 통계 목적.",
|
|
4
4
|
"hard_delete": true,
|
|
5
5
|
"history": false,
|
|
6
6
|
"compress": true,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "file_meta",
|
|
3
|
-
"description": "파일 메타데이터. 업로드된 파일의 저장 위치·크기·해시·상태를 관리합니다. 파일 핸들러가 자동 기록하며 API를 통한 직접 수정은 허용되지 않습니다.
|
|
3
|
+
"description": "파일 메타데이터. 업로드된 파일의 저장 위치·크기·해시·상태를 관리합니다. 파일 핸들러가 자동 기록하며 API를 통한 직접 수정은 허용되지 않습니다.",
|
|
4
4
|
"history": false,
|
|
5
5
|
"hard_delete": true,
|
|
6
6
|
"read_only": true,
|
|
@@ -8,13 +8,7 @@
|
|
|
8
8
|
"backup_status": {
|
|
9
9
|
"index": true,
|
|
10
10
|
"comment": "백업 동기화 상태",
|
|
11
|
-
"type": [
|
|
12
|
-
"none",
|
|
13
|
-
"pending",
|
|
14
|
-
"synced",
|
|
15
|
-
"failed",
|
|
16
|
-
"skipped"
|
|
17
|
-
],
|
|
11
|
+
"type": ["none", "pending", "synced", "failed", "skipped"],
|
|
18
12
|
"default": "none"
|
|
19
13
|
},
|
|
20
14
|
"size": {
|
|
@@ -25,12 +19,7 @@
|
|
|
25
19
|
"status": {
|
|
26
20
|
"index": true,
|
|
27
21
|
"comment": "파일 상태",
|
|
28
|
-
"type": [
|
|
29
|
-
"active",
|
|
30
|
-
"pending",
|
|
31
|
-
"orphan",
|
|
32
|
-
"deleted"
|
|
33
|
-
],
|
|
22
|
+
"type": ["active", "pending", "orphan", "deleted"],
|
|
34
23
|
"default": "pending",
|
|
35
24
|
"required": true
|
|
36
25
|
},
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "system_audit_log",
|
|
3
|
-
"description": "시스템 감사 로그. 서버 레벨에서 자동 기록되며 API를 통한 직접 수정은 허용되지 않습니다. JWT 인증 시에만 account_seq 기록.
|
|
3
|
+
"description": "시스템 감사 로그. 서버 레벨에서 자동 기록되며 API를 통한 직접 수정은 허용되지 않습니다. JWT 인증 시에만 account_seq 기록.",
|
|
4
4
|
"license_scope": false,
|
|
5
5
|
"hard_delete": true,
|
|
6
6
|
"history_ttl": 0,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "goods",
|
|
3
|
-
"description": "goods Entity.
|
|
3
|
+
"description": "goods Entity.",
|
|
4
4
|
"fields": {
|
|
5
5
|
"name": {
|
|
6
6
|
"index": true,
|
|
@@ -11,11 +11,7 @@
|
|
|
11
11
|
"category": {
|
|
12
12
|
"index": true,
|
|
13
13
|
"comment": "카테고리",
|
|
14
|
-
"type": [
|
|
15
|
-
"전자제품",
|
|
16
|
-
"가구",
|
|
17
|
-
"생활용품"
|
|
18
|
-
]
|
|
14
|
+
"type": ["전자제품", "가구", "생활용품"]
|
|
19
15
|
},
|
|
20
16
|
"price": {
|
|
21
17
|
"index": true,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "todo",
|
|
3
|
-
"description": "todo Entity.
|
|
3
|
+
"description": "todo Entity.",
|
|
4
4
|
"fields": {
|
|
5
5
|
"title": {
|
|
6
6
|
"index": true,
|
|
@@ -9,10 +9,7 @@
|
|
|
9
9
|
"status": {
|
|
10
10
|
"index": true,
|
|
11
11
|
"comment": "상태",
|
|
12
|
-
"type": [
|
|
13
|
-
"pending",
|
|
14
|
-
"done"
|
|
15
|
-
]
|
|
12
|
+
"type": ["pending", "done"]
|
|
16
13
|
}
|
|
17
14
|
}
|
|
18
15
|
}
|
|
@@ -10,9 +10,9 @@
|
|
|
10
10
|
// @ts-ignore
|
|
11
11
|
import { useEffect, useState } from "react";
|
|
12
12
|
// @ts-ignore
|
|
13
|
-
import { useEntityServer } from "entity-
|
|
13
|
+
import { useEntityServer } from "entity-client/react";
|
|
14
14
|
// @ts-ignore
|
|
15
|
-
import type { EntityListResult } from "entity-
|
|
15
|
+
import type { EntityListResult } from "entity-client";
|
|
16
16
|
|
|
17
17
|
interface Product {
|
|
18
18
|
seq: number;
|
package/template/scripts/run.sh
CHANGED
|
@@ -213,23 +213,10 @@ stop_pid_with_confirm() {
|
|
|
213
213
|
echo "Running process ($reason):"
|
|
214
214
|
echo " PID USER ELAPSED COMMAND"
|
|
215
215
|
echo " $proc_info"
|
|
216
|
-
echo ""
|
|
217
|
-
read -r -p "Stop this process? [y/N]: " input
|
|
218
216
|
else
|
|
219
217
|
echo "실행 중인 프로세스($reason):"
|
|
220
218
|
echo " PID USER 실행시간 COMMAND"
|
|
221
219
|
echo " $proc_info"
|
|
222
|
-
echo ""
|
|
223
|
-
read -r -p "이 프로세스를 중지할까요? [y/N]: " input
|
|
224
|
-
fi
|
|
225
|
-
input=$(echo "$input" | tr '[:upper:]' '[:lower:]')
|
|
226
|
-
if [ "$input" != "y" ] && [ "$input" != "yes" ] && [ "$input" != "ㅛ" ]; then
|
|
227
|
-
if [ "$LANGUAGE" = "en" ]; then
|
|
228
|
-
echo "Canceled."
|
|
229
|
-
else
|
|
230
|
-
echo "취소되었습니다."
|
|
231
|
-
fi
|
|
232
|
-
return 0
|
|
233
220
|
fi
|
|
234
221
|
|
|
235
222
|
kill "$pid" 2>/dev/null
|
|
@@ -435,8 +422,10 @@ fi
|
|
|
435
422
|
if [ ! -f "$SERVER_BIN" ]; then
|
|
436
423
|
if [ "$LANGUAGE" = "en" ]; then
|
|
437
424
|
echo "❌ entity-server binary not found (bin/entity-server or ./entity-server)"
|
|
425
|
+
echo " Run ./scripts/update-server.sh to download the latest binary."
|
|
438
426
|
else
|
|
439
427
|
echo "❌ entity-server 바이너리 파일이 없습니다 (bin/entity-server 또는 ./entity-server)"
|
|
428
|
+
echo " ./scripts/update-server.sh 를 실행하여 바이너리를 다운로드하세요."
|
|
440
429
|
fi
|
|
441
430
|
exit 1
|
|
442
431
|
fi
|
|
@@ -2,14 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
# Register entity-server as a systemd service using the current project path.
|
|
4
4
|
|
|
5
|
-
set -
|
|
5
|
+
set -euo pipefail
|
|
6
6
|
|
|
7
7
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
8
8
|
PROJECT_ROOT="$(dirname "$SCRIPT_DIR")"
|
|
9
9
|
|
|
10
10
|
# Load language from .env
|
|
11
11
|
if [ -f "$PROJECT_ROOT/.env" ]; then
|
|
12
|
-
LANGUAGE=$(
|
|
12
|
+
LANGUAGE=$(sed -n 's/^LANGUAGE=//p' "$PROJECT_ROOT/.env" | tail -n 1)
|
|
13
13
|
fi
|
|
14
14
|
LANGUAGE=${LANGUAGE:-ko}
|
|
15
15
|
|
|
@@ -2,14 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
# Remove entity-server systemd service.
|
|
4
4
|
|
|
5
|
-
set -
|
|
5
|
+
set -euo pipefail
|
|
6
6
|
|
|
7
7
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
8
8
|
PROJECT_ROOT="$(dirname "$SCRIPT_DIR")"
|
|
9
9
|
|
|
10
10
|
# Load language from .env
|
|
11
11
|
if [ -f "$PROJECT_ROOT/.env" ]; then
|
|
12
|
-
LANGUAGE=$(
|
|
12
|
+
LANGUAGE=$(sed -n 's/^LANGUAGE=//p' "$PROJECT_ROOT/.env" | tail -n 1)
|
|
13
13
|
fi
|
|
14
14
|
LANGUAGE=${LANGUAGE:-ko}
|
|
15
15
|
|
|
@@ -76,6 +76,7 @@ ARCH="$(uname -m)"
|
|
|
76
76
|
case "$OS" in
|
|
77
77
|
Linux) PLATFORM="linux" ;;
|
|
78
78
|
Darwin) PLATFORM="darwin" ;;
|
|
79
|
+
MINGW*|MSYS*|CYGWIN*) PLATFORM="windows" ;;
|
|
79
80
|
*)
|
|
80
81
|
echo "❌ 지원하지 않는 OS: $OS"
|
|
81
82
|
echo " Windows 는 scripts\\update-server.ps1 을 사용하세요."
|
|
@@ -92,15 +93,28 @@ case "$ARCH" in
|
|
|
92
93
|
;;
|
|
93
94
|
esac
|
|
94
95
|
|
|
96
|
+
BIN_EXT=""
|
|
97
|
+
INSTALL_BIN_EXT=""
|
|
98
|
+
if [ "$PLATFORM" = "windows" ]; then
|
|
99
|
+
BIN_EXT=".exe"
|
|
100
|
+
INSTALL_BIN_EXT=".exe"
|
|
101
|
+
fi
|
|
102
|
+
|
|
95
103
|
# ── 현재 버전 확인 ────────────────────────────────────────────────────────────
|
|
96
104
|
|
|
97
105
|
_current_ver() {
|
|
98
|
-
local bin="$PROJECT_ROOT/bin/entity-server"
|
|
106
|
+
local bin="$PROJECT_ROOT/bin/entity-server${INSTALL_BIN_EXT}"
|
|
99
107
|
local ver=""
|
|
100
|
-
if [ ! -
|
|
108
|
+
if [ ! -f "$bin" ] && [ -f "$PROJECT_ROOT/bin/entity-server" ]; then
|
|
109
|
+
bin="$PROJECT_ROOT/bin/entity-server"
|
|
110
|
+
fi
|
|
111
|
+
if [ ! -f "$bin" ] && [ -f "$PROJECT_ROOT/entity-server${INSTALL_BIN_EXT}" ]; then
|
|
112
|
+
bin="$PROJECT_ROOT/entity-server${INSTALL_BIN_EXT}"
|
|
113
|
+
fi
|
|
114
|
+
if [ ! -f "$bin" ] && [ -f "$PROJECT_ROOT/entity-server" ]; then
|
|
101
115
|
bin="$PROJECT_ROOT/entity-server"
|
|
102
116
|
fi
|
|
103
|
-
if [ -
|
|
117
|
+
if [ -f "$bin" ]; then
|
|
104
118
|
ver=$("$bin" --version 2>/dev/null | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1 || true)
|
|
105
119
|
if [ -z "$ver" ]; then
|
|
106
120
|
ver=$("$bin" version 2>/dev/null | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1 || true)
|
|
@@ -229,9 +243,9 @@ _install() {
|
|
|
229
243
|
mkdir -p "$PROJECT_ROOT/bin"
|
|
230
244
|
|
|
231
245
|
for BIN in "${BINARIES[@]}"; do
|
|
232
|
-
local file="${BIN}-${PLATFORM}-${ARCH_TAG}"
|
|
246
|
+
local file="${BIN}-${PLATFORM}-${ARCH_TAG}${BIN_EXT}"
|
|
233
247
|
local url="https://github.com/${REPO}/releases/download/v${target_ver}/${file}"
|
|
234
|
-
local dest="$PROJECT_ROOT/bin/$BIN"
|
|
248
|
+
local dest="$PROJECT_ROOT/bin/${BIN}${INSTALL_BIN_EXT}"
|
|
235
249
|
|
|
236
250
|
printf " ↓ %-32s" "$file"
|
|
237
251
|
if _download "$url" "$dest" 2>/dev/null; then
|