@heronlabs/terminal-cli 0.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 HeronLabs
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,207 @@
1
+ # ðŸŠķ terminal-cli — Heron CLI (`hcli`)
2
+
3
+ [![CI](https://github.com/heronlabs/terminal-cli/actions/workflows/ci-cli.yml/badge.svg)](https://github.com/heronlabs/terminal-cli/actions/workflows/ci-cli.yml)
4
+ [![TypeScript](https://img.shields.io/badge/TypeScript-5.8.x-blue.svg)](https://www.typescriptlang.org/)
5
+ [![NestJS](https://img.shields.io/badge/NestJS-11.x-e0234e.svg)](https://nestjs.com/)
6
+ [![Node.js](https://img.shields.io/badge/Node.js-22+-green.svg)](https://nodejs.org/)
7
+ [![License](https://img.shields.io/badge/License-MIT-yellow.svg)](./LICENSE)
8
+
9
+ Database backup & rollup CLI for PostgreSQL and MySQL. Dumps a database to a
10
+ gzipped SQL file, uploads it to S3 (or keeps it local), and restores it back on
11
+ demand. Built with NestJS + [nest-commander](https://nest-commander.jaymcdoniel.dev/),
12
+ AWS S3, and Luxon. Designed to run as a scheduled cron container on EasyPanel.
13
+
14
+ ## Table of Contents
15
+
16
+ - [Why This Project](#why-this-project)
17
+ - [Quick Start](#quick-start)
18
+ - [Commands](#commands)
19
+ - [Configuration](#configuration)
20
+ - [Architecture](#architecture)
21
+ - [Docker & Cron](#docker--cron)
22
+ - [Testing](#testing)
23
+ - [Contributing](#contributing)
24
+ - [License](#license)
25
+
26
+ ## Why This Project
27
+
28
+ Scheduled, type-safe database backups with a single binary:
29
+
30
+ - **One tool, two engines** — PostgreSQL (`pg_dump`/`psql`) and MySQL (`mysqldump`/`mysql`) behind one CLI.
31
+ - **S3 or local** — push backups to S3 by default, or keep them on disk with `--local` (handy for seeding).
32
+ - **Cron-ready containers** — purpose-built Dockerfiles run a backup on start and every 12 hours.
33
+ - **100% tested** — v8 coverage + Stryker mutation testing, both at 100% thresholds.
34
+
35
+ ## Quick Start
36
+
37
+ ### Prerequisites
38
+
39
+ | Requirement | Version | Purpose |
40
+ |-------------|---------|---------|
41
+ | Node.js | `>=22` | Runtime |
42
+ | pnpm | `>=10.29.3` | Package manager |
43
+ | `pg_dump` / `psql` | — | PostgreSQL backup/restore |
44
+ | `mysqldump` / `mysql` | — | MySQL backup/restore |
45
+
46
+ ### Installation
47
+
48
+ ```bash
49
+ git clone https://github.com/heronlabs/terminal-cli.git
50
+ cd terminal-cli
51
+ pnpm install
52
+ pnpm build # nest build → bin/
53
+ ```
54
+
55
+ After `pnpm build`, run the CLI via `node bin/src/main.js <command>` or link the
56
+ `hcli` bin (`pnpm link --global`).
57
+
58
+ ### Verify
59
+
60
+ ```bash
61
+ pnpm build # nest build → bin/
62
+ pnpm lint:check # gts + eslint
63
+ pnpm test:unit # vitest run (100% coverage)
64
+ pnpm test:mutation # stryker (100% break)
65
+ pnpm dep:cruise # architecture check
66
+ ```
67
+
68
+ ## Commands
69
+
70
+ | Command | Description |
71
+ |---|---|
72
+ | `hcli psql-backup` | Back up a PostgreSQL database (S3 by default) |
73
+ | `hcli psql-rollup --filename <file>` | Restore a PostgreSQL database from a backup |
74
+ | `hcli mysql-backup` | Back up a MySQL database (S3 by default) |
75
+ | `hcli mysql-rollup --filename <file>` | Restore a MySQL database from a backup |
76
+ | `hcli version` | Print the current version |
77
+
78
+ ### Flags
79
+
80
+ | Flag | Applies to | Meaning |
81
+ |---|---|---|
82
+ | `-f, --filename <name>` | all | Backup filename. Backup defaults to `<database>-<timestamp>.sql.gz`; rollup requires it. |
83
+ | `--local` | all | Read/write the backup on the local filesystem instead of S3. |
84
+
85
+ Examples:
86
+
87
+ ```bash
88
+ # Back up to S3 with an auto-generated, timestamped filename
89
+ hcli psql-backup
90
+
91
+ # Back up to a local file (no S3)
92
+ hcli psql-backup --local --filename seed.sql.gz
93
+
94
+ # Restore from a local file
95
+ hcli psql-rollup --local --filename seed.sql.gz
96
+
97
+ # Restore from S3
98
+ hcli mysql-rollup --filename mydb-2026-03-05T12-00-00Z.sql.gz
99
+ ```
100
+
101
+ ## Configuration
102
+
103
+ All configuration comes from environment variables (see [.env.example](./.env.example)).
104
+
105
+ | Variable | Required | Description |
106
+ |---|---|---|
107
+ | `DB_HOST` | ✅ | Database host |
108
+ | `DB_PORT` | ✅ | Database port |
109
+ | `DB_NAME` | ✅ | Database name |
110
+ | `DB_USER` | ✅ | Database user |
111
+ | `DB_PASSWORD` | ✅ | Database password |
112
+ | `AWS_S3_BUCKET_NAME` | for S3 | Destination bucket for remote backups |
113
+ | `AWS_REGION` | for S3 | AWS region |
114
+ | `AWS_ACCESS_KEY_ID` | for S3 | AWS credentials (or use an instance role) |
115
+ | `AWS_SECRET_ACCESS_KEY` | for S3 | AWS credentials (or use an instance role) |
116
+
117
+ Locally, `pnpm start -- <command>` loads variables from a `.env` file via `dotenv`.
118
+
119
+ ## Architecture
120
+
121
+ Hexagonal NestJS layering:
122
+
123
+ ```
124
+ src/
125
+ ├── application/ # CLI surface (nest-commander)
126
+ │ └── cli/
127
+ │ ├── cli-module.ts
128
+ │ └── commands/ # backup/, rollup/, version/ + per-command option types
129
+ ├── core/ # domain logic
130
+ │ ├── interfaces/ # BackupService / RollupService abstract base services
131
+ │ └── services/ # {mysql,psql}-{backup,rollup}-service
132
+ └── infrastructure/ # adapters
133
+ ├── environment/ # EnvironmentService (ConfigService wrapper)
134
+ ├── log/ # nestjs-pino logger module
135
+ └── storage/ # S3StorageService (AWS SDK v3)
136
+ ```
137
+
138
+ - **`application/`** wires commands into modules — the composition root.
139
+ - **`core/`** holds the backup/rollup flow. The abstract `BackupService`/`RollupService`
140
+ own the S3 upload/download + local-cleanup orchestration; engine subclasses implement
141
+ the `dump`/`restore` shell commands.
142
+ - **`infrastructure/`** adapters never import inward (enforced by `pnpm dep:cruise`).
143
+
144
+ ## Docker & Cron
145
+
146
+ Three Dockerfiles:
147
+
148
+ | File | Purpose |
149
+ |---|---|
150
+ | `Dockerfile` | General-purpose image with `hcli` on `PATH` and both DB clients |
151
+ | `cron-tab-postgres.dockerfile` | PostgreSQL cron container — backup on start + every 12h |
152
+ | `cron-tab-mysql.dockerfile` | MySQL cron container — backup on start + every 12h |
153
+
154
+ The cron images dump `printenv` to `/etc/environment` so `crond` inherits the
155
+ runtime variables injected by EasyPanel, run an immediate backup, then start
156
+ `crond` in the foreground.
157
+
158
+ Local stack for manual testing:
159
+
160
+ ```bash
161
+ docker compose up # spins up psql + mysql DBs and CLI containers
162
+ ```
163
+
164
+ ## Testing
165
+
166
+ | Detail | Value |
167
+ |---|---|
168
+ | Framework | Vitest 4.x (`vitest.config.ts`, SWC transform for decorators) |
169
+ | Test location | `tests/specs/` (mirrors `src/`) |
170
+ | Shared mocks | `tests/__mocks__/create-testing-module.ts` (moq.ts + vitest) |
171
+ | Coverage | v8, 100% lines/functions/branches/statements |
172
+ | Coverage excludes | `**/main.ts`, `**/*.d.ts`, `**/*factory.ts`, `**/types/` |
173
+ | Mutation | Stryker 9.x (`stryker.conf.json`), 100% break threshold |
174
+ | Mutation scope | `src/**/*.ts` excluding `main.ts`, `*.d.ts`, `*factory.ts`, `*-module.ts` |
175
+
176
+ ```bash
177
+ pnpm test:unit # vitest run with coverage
178
+ pnpm test:mutation # stryker mutation testing
179
+ ```
180
+
181
+ ## Contributing
182
+
183
+ ```bash
184
+ pnpm lint:check # check
185
+ pnpm lint:fix # auto-fix
186
+ pnpm build # verify compilation
187
+ pnpm test:unit # verify tests pass at 100% coverage
188
+ pnpm dep:cruise # verify architecture
189
+ ```
190
+
191
+ Conventional Commits:
192
+
193
+ ```
194
+ feat: add mongodb backup engine
195
+ fix: handle empty database name in psql dump
196
+ test: cover s3 download error path
197
+ ```
198
+
199
+ - All work branches from `main`; PRs target `main`. Never commit directly to `main`.
200
+
201
+ ## License
202
+
203
+ MIT — see [LICENSE](LICENSE).
204
+
205
+ ---
206
+
207
+ Built by [HeronLabs](https://github.com/heronlabs)
@@ -0,0 +1,34 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.CliModule = exports.cliModule = void 0;
10
+ const common_1 = require("@nestjs/common");
11
+ const core_module_1 = require("../../core/core-module");
12
+ const log_module_1 = require("../../infrastructure/log/log-module");
13
+ const mysql_backup_command_1 = require("./commands/backup/mysql-backup-command");
14
+ const psql_backup_command_1 = require("./commands/backup/psql-backup-command");
15
+ const mysql_rollup_command_1 = require("./commands/rollup/mysql-rollup-command");
16
+ const psql_rollup_command_1 = require("./commands/rollup/psql-rollup-command");
17
+ const version_command_1 = require("./commands/version/version-command");
18
+ exports.cliModule = {
19
+ imports: [log_module_1.LogModule, core_module_1.CoreModule],
20
+ providers: [
21
+ common_1.Logger,
22
+ version_command_1.VersionCommand,
23
+ psql_backup_command_1.PsqlBackupCommand,
24
+ psql_rollup_command_1.PsqlRollupCommand,
25
+ mysql_backup_command_1.MysqlBackupCommand,
26
+ mysql_rollup_command_1.MysqlRollupCommand,
27
+ ],
28
+ };
29
+ let CliModule = class CliModule {
30
+ };
31
+ exports.CliModule = CliModule;
32
+ exports.CliModule = CliModule = __decorate([
33
+ (0, common_1.Module)(exports.cliModule)
34
+ ], CliModule);
@@ -0,0 +1,57 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var __metadata = (this && this.__metadata) || function (k, v) {
9
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.MysqlBackupCommand = void 0;
13
+ const nest_commander_1 = require("nest-commander");
14
+ const mysql_backup_service_1 = require("../../../../core/services/mysql-backup-service");
15
+ const backup_options_1 = require("./types/backup-options");
16
+ let MysqlBackupCommand = class MysqlBackupCommand extends nest_commander_1.CommandRunner {
17
+ mysqlBackupService;
18
+ parseFilename(val) {
19
+ return val;
20
+ }
21
+ parseLocal() {
22
+ return true;
23
+ }
24
+ async run(_args, options) {
25
+ await this.mysqlBackupService.run(options?.[backup_options_1.BackupOptionsKeys.LOCAL] ?? false, options?.[backup_options_1.BackupOptionsKeys.FILENAME]);
26
+ }
27
+ constructor(mysqlBackupService) {
28
+ super();
29
+ this.mysqlBackupService = mysqlBackupService;
30
+ }
31
+ };
32
+ exports.MysqlBackupCommand = MysqlBackupCommand;
33
+ __decorate([
34
+ (0, nest_commander_1.Option)({
35
+ flags: `-f, --${backup_options_1.BackupOptionsKeys.FILENAME} <filename>`,
36
+ description: 'Output filename (e.g. seed.sql.gz). Defaults to "<database>-<timestamp>.sql.gz"',
37
+ }),
38
+ __metadata("design:type", Function),
39
+ __metadata("design:paramtypes", [String]),
40
+ __metadata("design:returntype", String)
41
+ ], MysqlBackupCommand.prototype, "parseFilename", null);
42
+ __decorate([
43
+ (0, nest_commander_1.Option)({
44
+ flags: `--${backup_options_1.BackupOptionsKeys.LOCAL}`,
45
+ description: 'Save backup to the local filesystem instead of S3 (default: S3)',
46
+ }),
47
+ __metadata("design:type", Function),
48
+ __metadata("design:paramtypes", []),
49
+ __metadata("design:returntype", Boolean)
50
+ ], MysqlBackupCommand.prototype, "parseLocal", null);
51
+ exports.MysqlBackupCommand = MysqlBackupCommand = __decorate([
52
+ (0, nest_commander_1.Command)({
53
+ name: 'mysql-backup',
54
+ description: 'Backup MySQL database',
55
+ }),
56
+ __metadata("design:paramtypes", [mysql_backup_service_1.MysqlBackupService])
57
+ ], MysqlBackupCommand);
@@ -0,0 +1,57 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var __metadata = (this && this.__metadata) || function (k, v) {
9
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.PsqlBackupCommand = void 0;
13
+ const nest_commander_1 = require("nest-commander");
14
+ const psql_backup_service_1 = require("../../../../core/services/psql-backup-service");
15
+ const backup_options_1 = require("./types/backup-options");
16
+ let PsqlBackupCommand = class PsqlBackupCommand extends nest_commander_1.CommandRunner {
17
+ psqlBackupService;
18
+ parseFilename(val) {
19
+ return val;
20
+ }
21
+ parseLocal() {
22
+ return true;
23
+ }
24
+ async run(_args, options) {
25
+ await this.psqlBackupService.run(options?.[backup_options_1.BackupOptionsKeys.LOCAL] ?? false, options?.[backup_options_1.BackupOptionsKeys.FILENAME]);
26
+ }
27
+ constructor(psqlBackupService) {
28
+ super();
29
+ this.psqlBackupService = psqlBackupService;
30
+ }
31
+ };
32
+ exports.PsqlBackupCommand = PsqlBackupCommand;
33
+ __decorate([
34
+ (0, nest_commander_1.Option)({
35
+ flags: `-f, --${backup_options_1.BackupOptionsKeys.FILENAME} <filename>`,
36
+ description: 'Output filename (e.g. seed.sql.gz). Defaults to "<database>-<timestamp>.sql.gz"',
37
+ }),
38
+ __metadata("design:type", Function),
39
+ __metadata("design:paramtypes", [String]),
40
+ __metadata("design:returntype", String)
41
+ ], PsqlBackupCommand.prototype, "parseFilename", null);
42
+ __decorate([
43
+ (0, nest_commander_1.Option)({
44
+ flags: `--${backup_options_1.BackupOptionsKeys.LOCAL}`,
45
+ description: 'Save backup to the local filesystem instead of S3 (default: S3)',
46
+ }),
47
+ __metadata("design:type", Function),
48
+ __metadata("design:paramtypes", []),
49
+ __metadata("design:returntype", Boolean)
50
+ ], PsqlBackupCommand.prototype, "parseLocal", null);
51
+ exports.PsqlBackupCommand = PsqlBackupCommand = __decorate([
52
+ (0, nest_commander_1.Command)({
53
+ name: 'psql-backup',
54
+ description: 'Backup PostgreSQL database',
55
+ }),
56
+ __metadata("design:paramtypes", [psql_backup_service_1.PsqlBackupService])
57
+ ], PsqlBackupCommand);
@@ -0,0 +1,8 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.BackupOptionsKeys = void 0;
4
+ var BackupOptionsKeys;
5
+ (function (BackupOptionsKeys) {
6
+ BackupOptionsKeys["FILENAME"] = "filename";
7
+ BackupOptionsKeys["LOCAL"] = "local";
8
+ })(BackupOptionsKeys || (exports.BackupOptionsKeys = BackupOptionsKeys = {}));
@@ -0,0 +1,58 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var __metadata = (this && this.__metadata) || function (k, v) {
9
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.MysqlRollupCommand = void 0;
13
+ const nest_commander_1 = require("nest-commander");
14
+ const mysql_rollup_service_1 = require("../../../../core/services/mysql-rollup-service");
15
+ const rollup_options_1 = require("./types/rollup-options");
16
+ let MysqlRollupCommand = class MysqlRollupCommand extends nest_commander_1.CommandRunner {
17
+ mysqlRollupService;
18
+ parseFilename(val) {
19
+ return val;
20
+ }
21
+ parseLocal() {
22
+ return true;
23
+ }
24
+ async run(_args, options) {
25
+ await this.mysqlRollupService.run(options[rollup_options_1.RollupOptionsKeys.FILENAME], options[rollup_options_1.RollupOptionsKeys.LOCAL] ?? false);
26
+ }
27
+ constructor(mysqlRollupService) {
28
+ super();
29
+ this.mysqlRollupService = mysqlRollupService;
30
+ }
31
+ };
32
+ exports.MysqlRollupCommand = MysqlRollupCommand;
33
+ __decorate([
34
+ (0, nest_commander_1.Option)({
35
+ flags: `-f, --${rollup_options_1.RollupOptionsKeys.FILENAME} <filename>`,
36
+ description: 'Backup filename to restore (e.g. mydb-2026-03-05T12-00-00Z.sql.gz)',
37
+ required: true,
38
+ }),
39
+ __metadata("design:type", Function),
40
+ __metadata("design:paramtypes", [String]),
41
+ __metadata("design:returntype", String)
42
+ ], MysqlRollupCommand.prototype, "parseFilename", null);
43
+ __decorate([
44
+ (0, nest_commander_1.Option)({
45
+ flags: `--${rollup_options_1.RollupOptionsKeys.LOCAL}`,
46
+ description: 'Read backup from the local filesystem instead of S3 (default: S3)',
47
+ }),
48
+ __metadata("design:type", Function),
49
+ __metadata("design:paramtypes", []),
50
+ __metadata("design:returntype", Boolean)
51
+ ], MysqlRollupCommand.prototype, "parseLocal", null);
52
+ exports.MysqlRollupCommand = MysqlRollupCommand = __decorate([
53
+ (0, nest_commander_1.Command)({
54
+ name: 'mysql-rollup',
55
+ description: 'Restore a MySQL database from a backup file (S3 by default, or local filesystem with --local)',
56
+ }),
57
+ __metadata("design:paramtypes", [mysql_rollup_service_1.MysqlRollupService])
58
+ ], MysqlRollupCommand);
@@ -0,0 +1,58 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var __metadata = (this && this.__metadata) || function (k, v) {
9
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.PsqlRollupCommand = void 0;
13
+ const nest_commander_1 = require("nest-commander");
14
+ const psql_rollup_service_1 = require("../../../../core/services/psql-rollup-service");
15
+ const rollup_options_1 = require("./types/rollup-options");
16
+ let PsqlRollupCommand = class PsqlRollupCommand extends nest_commander_1.CommandRunner {
17
+ psqlRollupService;
18
+ parseFilename(val) {
19
+ return val;
20
+ }
21
+ parseLocal() {
22
+ return true;
23
+ }
24
+ async run(_args, options) {
25
+ await this.psqlRollupService.run(options[rollup_options_1.RollupOptionsKeys.FILENAME], options[rollup_options_1.RollupOptionsKeys.LOCAL] ?? false);
26
+ }
27
+ constructor(psqlRollupService) {
28
+ super();
29
+ this.psqlRollupService = psqlRollupService;
30
+ }
31
+ };
32
+ exports.PsqlRollupCommand = PsqlRollupCommand;
33
+ __decorate([
34
+ (0, nest_commander_1.Option)({
35
+ flags: `-f, --${rollup_options_1.RollupOptionsKeys.FILENAME} <filename>`,
36
+ description: 'Backup filename to restore (e.g. mydb-2026-03-05T12-00-00Z.sql.gz)',
37
+ required: true,
38
+ }),
39
+ __metadata("design:type", Function),
40
+ __metadata("design:paramtypes", [String]),
41
+ __metadata("design:returntype", String)
42
+ ], PsqlRollupCommand.prototype, "parseFilename", null);
43
+ __decorate([
44
+ (0, nest_commander_1.Option)({
45
+ flags: `--${rollup_options_1.RollupOptionsKeys.LOCAL}`,
46
+ description: 'Read backup from the local filesystem instead of S3 (default: S3)',
47
+ }),
48
+ __metadata("design:type", Function),
49
+ __metadata("design:paramtypes", []),
50
+ __metadata("design:returntype", Boolean)
51
+ ], PsqlRollupCommand.prototype, "parseLocal", null);
52
+ exports.PsqlRollupCommand = PsqlRollupCommand = __decorate([
53
+ (0, nest_commander_1.Command)({
54
+ name: 'psql-rollup',
55
+ description: 'Restore a PostgreSQL database from a backup file (S3 by default, or local filesystem with --local)',
56
+ }),
57
+ __metadata("design:paramtypes", [psql_rollup_service_1.PsqlRollupService])
58
+ ], PsqlRollupCommand);
@@ -0,0 +1,8 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.RollupOptionsKeys = void 0;
4
+ var RollupOptionsKeys;
5
+ (function (RollupOptionsKeys) {
6
+ RollupOptionsKeys["FILENAME"] = "filename";
7
+ RollupOptionsKeys["LOCAL"] = "local";
8
+ })(RollupOptionsKeys || (exports.RollupOptionsKeys = RollupOptionsKeys = {}));
@@ -0,0 +1,33 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var __metadata = (this && this.__metadata) || function (k, v) {
9
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.VersionCommand = void 0;
13
+ const node_fs_1 = require("node:fs");
14
+ const node_path_1 = require("node:path");
15
+ const common_1 = require("@nestjs/common");
16
+ const nest_commander_1 = require("nest-commander");
17
+ let VersionCommand = class VersionCommand extends nest_commander_1.CommandRunner {
18
+ logger;
19
+ async run() {
20
+ const path = (0, node_path_1.join)(__dirname, '../../../../../../package.json');
21
+ const { version } = JSON.parse((0, node_fs_1.readFileSync)(path, 'utf-8'));
22
+ this.logger.log(`Current Version: ${version}`);
23
+ }
24
+ constructor(logger) {
25
+ super();
26
+ this.logger = logger;
27
+ }
28
+ };
29
+ exports.VersionCommand = VersionCommand;
30
+ exports.VersionCommand = VersionCommand = __decorate([
31
+ (0, nest_commander_1.Command)({ name: 'version', description: 'Print current version' }),
32
+ __metadata("design:paramtypes", [common_1.Logger])
33
+ ], VersionCommand);
@@ -0,0 +1,38 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.CoreModule = void 0;
10
+ const common_1 = require("@nestjs/common");
11
+ const environment_module_1 = require("../infrastructure/environment/environment-module");
12
+ const storage_module_1 = require("../infrastructure/storage/storage-module");
13
+ const mysql_backup_service_1 = require("./services/mysql-backup-service");
14
+ const mysql_rollup_service_1 = require("./services/mysql-rollup-service");
15
+ const psql_backup_service_1 = require("./services/psql-backup-service");
16
+ const psql_rollup_service_1 = require("./services/psql-rollup-service");
17
+ const coreModule = {
18
+ imports: [environment_module_1.EnvironmentModule, storage_module_1.StorageModule],
19
+ providers: [
20
+ common_1.Logger,
21
+ mysql_backup_service_1.MysqlBackupService,
22
+ mysql_rollup_service_1.MysqlRollupService,
23
+ psql_backup_service_1.PsqlBackupService,
24
+ psql_rollup_service_1.PsqlRollupService,
25
+ ],
26
+ exports: [
27
+ mysql_backup_service_1.MysqlBackupService,
28
+ mysql_rollup_service_1.MysqlRollupService,
29
+ psql_backup_service_1.PsqlBackupService,
30
+ psql_rollup_service_1.PsqlRollupService,
31
+ ],
32
+ };
33
+ let CoreModule = class CoreModule {
34
+ };
35
+ exports.CoreModule = CoreModule;
36
+ exports.CoreModule = CoreModule = __decorate([
37
+ (0, common_1.Module)(coreModule)
38
+ ], CoreModule);
@@ -0,0 +1,31 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.BackupService = void 0;
4
+ const fs_1 = require("fs");
5
+ class BackupService {
6
+ logger;
7
+ s3StorageService;
8
+ async run(local, filename) {
9
+ const result = this.dump(filename);
10
+ if (!result.ok) {
11
+ this.logger.error(result.error.message);
12
+ return;
13
+ }
14
+ if (local) {
15
+ return { ok: true };
16
+ }
17
+ const { error: uploadError } = await this.s3StorageService.upload(result.data.backupFileName);
18
+ if (uploadError) {
19
+ this.logger.error(uploadError.message);
20
+ return;
21
+ }
22
+ (0, fs_1.unlinkSync)(result.data.backupFileName);
23
+ this.logger.log('Deleted local backup file');
24
+ return { ok: true };
25
+ }
26
+ constructor(logger, s3StorageService) {
27
+ this.logger = logger;
28
+ this.s3StorageService = s3StorageService;
29
+ }
30
+ }
31
+ exports.BackupService = BackupService;
@@ -0,0 +1,32 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.RollupService = void 0;
4
+ const fs_1 = require("fs");
5
+ class RollupService {
6
+ logger;
7
+ s3StorageService;
8
+ async run(filename, local) {
9
+ if (!local) {
10
+ const { error: downloadError } = await this.s3StorageService.download(filename);
11
+ if (downloadError) {
12
+ this.logger.error(downloadError.message);
13
+ return;
14
+ }
15
+ }
16
+ const result = this.restore(filename);
17
+ if (!result.ok) {
18
+ this.logger.error(result.error.message);
19
+ return;
20
+ }
21
+ this.logger.log(`Restored ${result.data.backupFileName}`);
22
+ if (!local) {
23
+ (0, fs_1.unlinkSync)(filename);
24
+ this.logger.log('Deleted downloaded backup file');
25
+ }
26
+ }
27
+ constructor(logger, s3StorageService) {
28
+ this.logger = logger;
29
+ this.s3StorageService = s3StorageService;
30
+ }
31
+ }
32
+ exports.RollupService = RollupService;
@@ -0,0 +1,61 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var __metadata = (this && this.__metadata) || function (k, v) {
9
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.MysqlBackupService = void 0;
13
+ const common_1 = require("@nestjs/common");
14
+ const child_process_1 = require("child_process");
15
+ const luxon_1 = require("luxon");
16
+ const environment_service_1 = require("../../infrastructure/environment/services/environment-service");
17
+ const s3_storage_service_1 = require("../../infrastructure/storage/services/s3-storage-service");
18
+ const backup_service_1 = require("../interfaces/backup-service");
19
+ let MysqlBackupService = class MysqlBackupService extends backup_service_1.BackupService {
20
+ logger;
21
+ environmentService;
22
+ s3StorageService;
23
+ dump(filename) {
24
+ const { host, port, name, user, password } = this.environmentService.database;
25
+ const timestamp = luxon_1.DateTime.utc().toFormat("yyyy-MM-dd'T'HH-mm-ss'Z'");
26
+ const backupFileName = filename ?? `${name}-${timestamp}.sql.gz`;
27
+ try {
28
+ (0, child_process_1.execSync)('set -o pipefail; mariadb-dump -u "$DB_USER" -h "$DB_HOST" -P "$DB_PORT" "$DB_NAME" | gzip > "$BACKUP_FILE"', {
29
+ env: {
30
+ ...process.env,
31
+ DB_USER: user,
32
+ DB_HOST: host,
33
+ DB_PORT: port,
34
+ DB_NAME: name,
35
+ MYSQL_PWD: password,
36
+ BACKUP_FILE: backupFileName,
37
+ },
38
+ stdio: ['inherit', 'pipe', 'inherit'],
39
+ shell: '/bin/bash',
40
+ });
41
+ this.logger.log(`Backup MySQL database successfully! Filename: ${backupFileName}`);
42
+ return { ok: true, data: { backupFileName } };
43
+ }
44
+ catch {
45
+ return { ok: false, error: new Error('mariadb-dump failed') };
46
+ }
47
+ }
48
+ constructor(logger, environmentService, s3StorageService) {
49
+ super(logger, s3StorageService);
50
+ this.logger = logger;
51
+ this.environmentService = environmentService;
52
+ this.s3StorageService = s3StorageService;
53
+ }
54
+ };
55
+ exports.MysqlBackupService = MysqlBackupService;
56
+ exports.MysqlBackupService = MysqlBackupService = __decorate([
57
+ (0, common_1.Injectable)(),
58
+ __metadata("design:paramtypes", [common_1.Logger,
59
+ environment_service_1.EnvironmentService,
60
+ s3_storage_service_1.S3StorageService])
61
+ ], MysqlBackupService);
@@ -0,0 +1,57 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var __metadata = (this && this.__metadata) || function (k, v) {
9
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.MysqlRollupService = void 0;
13
+ const common_1 = require("@nestjs/common");
14
+ const child_process_1 = require("child_process");
15
+ const environment_service_1 = require("../../infrastructure/environment/services/environment-service");
16
+ const s3_storage_service_1 = require("../../infrastructure/storage/services/s3-storage-service");
17
+ const rollup_service_1 = require("../interfaces/rollup-service");
18
+ let MysqlRollupService = class MysqlRollupService extends rollup_service_1.RollupService {
19
+ logger;
20
+ environmentService;
21
+ s3StorageService;
22
+ restore(backupFileName) {
23
+ const { host, port, name, user, password } = this.environmentService.database;
24
+ try {
25
+ (0, child_process_1.execSync)('set -o pipefail; gunzip -c "$BACKUP_FILE" | mariadb -u "$DB_USER" -h "$DB_HOST" -P "$DB_PORT" "$DB_NAME"', {
26
+ env: {
27
+ ...process.env,
28
+ DB_USER: user,
29
+ DB_HOST: host,
30
+ DB_PORT: port,
31
+ DB_NAME: name,
32
+ MYSQL_PWD: password,
33
+ BACKUP_FILE: backupFileName,
34
+ },
35
+ stdio: ['inherit', 'pipe', 'inherit'],
36
+ shell: '/bin/bash',
37
+ });
38
+ return { ok: true, data: { backupFileName } };
39
+ }
40
+ catch {
41
+ return { ok: false, error: new Error('mariadb restore failed') };
42
+ }
43
+ }
44
+ constructor(logger, environmentService, s3StorageService) {
45
+ super(logger, s3StorageService);
46
+ this.logger = logger;
47
+ this.environmentService = environmentService;
48
+ this.s3StorageService = s3StorageService;
49
+ }
50
+ };
51
+ exports.MysqlRollupService = MysqlRollupService;
52
+ exports.MysqlRollupService = MysqlRollupService = __decorate([
53
+ (0, common_1.Injectable)(),
54
+ __metadata("design:paramtypes", [common_1.Logger,
55
+ environment_service_1.EnvironmentService,
56
+ s3_storage_service_1.S3StorageService])
57
+ ], MysqlRollupService);
@@ -0,0 +1,61 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var __metadata = (this && this.__metadata) || function (k, v) {
9
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.PsqlBackupService = void 0;
13
+ const common_1 = require("@nestjs/common");
14
+ const child_process_1 = require("child_process");
15
+ const luxon_1 = require("luxon");
16
+ const environment_service_1 = require("../../infrastructure/environment/services/environment-service");
17
+ const s3_storage_service_1 = require("../../infrastructure/storage/services/s3-storage-service");
18
+ const backup_service_1 = require("../interfaces/backup-service");
19
+ let PsqlBackupService = class PsqlBackupService extends backup_service_1.BackupService {
20
+ logger;
21
+ environmentService;
22
+ s3StorageService;
23
+ dump(filename) {
24
+ const { host, port, name, user, password } = this.environmentService.database;
25
+ const timestamp = luxon_1.DateTime.utc().toFormat("yyyy-MM-dd'T'HH-mm-ss'Z'");
26
+ const backupFileName = filename ?? `${name}-${timestamp}.sql.gz`;
27
+ try {
28
+ (0, child_process_1.execSync)('set -o pipefail; pg_dump | gzip > "$BACKUP_FILE"', {
29
+ env: {
30
+ ...process.env,
31
+ PGHOST: host,
32
+ PGPORT: port,
33
+ PGDATABASE: name,
34
+ PGUSER: user,
35
+ PGPASSWORD: password,
36
+ BACKUP_FILE: backupFileName,
37
+ },
38
+ stdio: ['inherit', 'pipe', 'inherit'],
39
+ shell: '/bin/bash',
40
+ });
41
+ this.logger.log(`Backup PostgreSQL database successfully! Filename: ${backupFileName}`);
42
+ return { ok: true, data: { backupFileName } };
43
+ }
44
+ catch {
45
+ return { ok: false, error: new Error('pg_dump failed') };
46
+ }
47
+ }
48
+ constructor(logger, environmentService, s3StorageService) {
49
+ super(logger, s3StorageService);
50
+ this.logger = logger;
51
+ this.environmentService = environmentService;
52
+ this.s3StorageService = s3StorageService;
53
+ }
54
+ };
55
+ exports.PsqlBackupService = PsqlBackupService;
56
+ exports.PsqlBackupService = PsqlBackupService = __decorate([
57
+ (0, common_1.Injectable)(),
58
+ __metadata("design:paramtypes", [common_1.Logger,
59
+ environment_service_1.EnvironmentService,
60
+ s3_storage_service_1.S3StorageService])
61
+ ], PsqlBackupService);
@@ -0,0 +1,57 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var __metadata = (this && this.__metadata) || function (k, v) {
9
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.PsqlRollupService = void 0;
13
+ const common_1 = require("@nestjs/common");
14
+ const child_process_1 = require("child_process");
15
+ const environment_service_1 = require("../../infrastructure/environment/services/environment-service");
16
+ const s3_storage_service_1 = require("../../infrastructure/storage/services/s3-storage-service");
17
+ const rollup_service_1 = require("../interfaces/rollup-service");
18
+ let PsqlRollupService = class PsqlRollupService extends rollup_service_1.RollupService {
19
+ logger;
20
+ environmentService;
21
+ s3StorageService;
22
+ restore(backupFileName) {
23
+ const { host, port, name, user, password } = this.environmentService.database;
24
+ try {
25
+ (0, child_process_1.execSync)('set -o pipefail; gunzip -c "$BACKUP_FILE" | psql', {
26
+ env: {
27
+ ...process.env,
28
+ PGHOST: host,
29
+ PGPORT: port,
30
+ PGDATABASE: name,
31
+ PGUSER: user,
32
+ PGPASSWORD: password,
33
+ BACKUP_FILE: backupFileName,
34
+ },
35
+ stdio: ['inherit', 'pipe', 'inherit'],
36
+ shell: '/bin/bash',
37
+ });
38
+ return { ok: true, data: { backupFileName } };
39
+ }
40
+ catch {
41
+ return { ok: false, error: new Error('psql restore failed') };
42
+ }
43
+ }
44
+ constructor(logger, environmentService, s3StorageService) {
45
+ super(logger, s3StorageService);
46
+ this.logger = logger;
47
+ this.environmentService = environmentService;
48
+ this.s3StorageService = s3StorageService;
49
+ }
50
+ };
51
+ exports.PsqlRollupService = PsqlRollupService;
52
+ exports.PsqlRollupService = PsqlRollupService = __decorate([
53
+ (0, common_1.Injectable)(),
54
+ __metadata("design:paramtypes", [common_1.Logger,
55
+ environment_service_1.EnvironmentService,
56
+ s3_storage_service_1.S3StorageService])
57
+ ], PsqlRollupService);
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.EnvironmentModule = exports.environmentModule = void 0;
10
+ const common_1 = require("@nestjs/common");
11
+ const config_1 = require("@nestjs/config");
12
+ const environment_service_1 = require("./services/environment-service");
13
+ exports.environmentModule = {
14
+ providers: [environment_service_1.EnvironmentService],
15
+ exports: [environment_service_1.EnvironmentService],
16
+ imports: [config_1.ConfigModule],
17
+ };
18
+ let EnvironmentModule = class EnvironmentModule {
19
+ };
20
+ exports.EnvironmentModule = EnvironmentModule;
21
+ exports.EnvironmentModule = EnvironmentModule = __decorate([
22
+ (0, common_1.Module)(exports.environmentModule)
23
+ ], EnvironmentModule);
@@ -0,0 +1,39 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var __metadata = (this && this.__metadata) || function (k, v) {
9
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.EnvironmentService = void 0;
13
+ const common_1 = require("@nestjs/common");
14
+ const config_1 = require("@nestjs/config");
15
+ let EnvironmentService = class EnvironmentService {
16
+ configService;
17
+ get database() {
18
+ return {
19
+ host: this.configService.getOrThrow('DB_HOST'),
20
+ port: this.configService.getOrThrow('DB_PORT'),
21
+ name: this.configService.getOrThrow('DB_NAME'),
22
+ user: this.configService.getOrThrow('DB_USER'),
23
+ password: this.configService.getOrThrow('DB_PASSWORD'),
24
+ };
25
+ }
26
+ get storage() {
27
+ return {
28
+ bucketName: this.configService.getOrThrow('AWS_S3_BUCKET_NAME'),
29
+ };
30
+ }
31
+ constructor(configService) {
32
+ this.configService = configService;
33
+ }
34
+ };
35
+ exports.EnvironmentService = EnvironmentService;
36
+ exports.EnvironmentService = EnvironmentService = __decorate([
37
+ (0, common_1.Injectable)(),
38
+ __metadata("design:paramtypes", [config_1.ConfigService])
39
+ ], EnvironmentService);
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.LogModule = void 0;
10
+ const common_1 = require("@nestjs/common");
11
+ const nestjs_pino_1 = require("nestjs-pino");
12
+ const logModule = {
13
+ imports: [nestjs_pino_1.LoggerModule.forRoot()],
14
+ };
15
+ let LogModule = class LogModule {
16
+ };
17
+ exports.LogModule = LogModule;
18
+ exports.LogModule = LogModule = __decorate([
19
+ (0, common_1.Module)(logModule),
20
+ (0, common_1.Global)()
21
+ ], LogModule);
@@ -0,0 +1,72 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var __metadata = (this && this.__metadata) || function (k, v) {
9
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.S3StorageService = void 0;
13
+ const client_s3_1 = require("@aws-sdk/client-s3");
14
+ const common_1 = require("@nestjs/common");
15
+ const fs_1 = require("fs");
16
+ const environment_service_1 = require("../../environment/services/environment-service");
17
+ let S3StorageService = class S3StorageService {
18
+ logger;
19
+ environmentService;
20
+ s3;
21
+ async upload(filePath, key) {
22
+ try {
23
+ const body = (0, fs_1.readFileSync)(filePath);
24
+ const command = new client_s3_1.PutObjectCommand({
25
+ Bucket: this.environmentService.storage.bucketName,
26
+ Key: key ?? filePath,
27
+ Body: body,
28
+ ContentType: 'application/octet-stream',
29
+ });
30
+ await this.s3.send(command);
31
+ this.logger.log('Uploaded file to S3');
32
+ return { ok: true };
33
+ }
34
+ catch (error) {
35
+ if (error instanceof Error) {
36
+ return { ok: false, error };
37
+ }
38
+ return { ok: false, error: new Error('Error uploading file to S3') };
39
+ }
40
+ }
41
+ async download(key) {
42
+ try {
43
+ const command = new client_s3_1.GetObjectCommand({
44
+ Bucket: this.environmentService.storage.bucketName,
45
+ Key: key,
46
+ });
47
+ const response = await this.s3.send(command);
48
+ const bytes = await response.Body.transformToByteArray();
49
+ (0, fs_1.writeFileSync)(key, bytes);
50
+ this.logger.log('Downloaded file from S3');
51
+ return { ok: true };
52
+ }
53
+ catch (error) {
54
+ if (error instanceof Error) {
55
+ return { ok: false, error };
56
+ }
57
+ return { ok: false, error: new Error('Error downloading file from S3') };
58
+ }
59
+ }
60
+ constructor(logger, environmentService, s3) {
61
+ this.logger = logger;
62
+ this.environmentService = environmentService;
63
+ this.s3 = s3;
64
+ }
65
+ };
66
+ exports.S3StorageService = S3StorageService;
67
+ exports.S3StorageService = S3StorageService = __decorate([
68
+ (0, common_1.Injectable)(),
69
+ __metadata("design:paramtypes", [common_1.Logger,
70
+ environment_service_1.EnvironmentService,
71
+ client_s3_1.S3Client])
72
+ ], S3StorageService);
@@ -0,0 +1,31 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.StorageModule = exports.storageModule = void 0;
10
+ const client_s3_1 = require("@aws-sdk/client-s3");
11
+ const common_1 = require("@nestjs/common");
12
+ const environment_module_1 = require("../environment/environment-module");
13
+ const s3_storage_service_1 = require("./services/s3-storage-service");
14
+ exports.storageModule = {
15
+ imports: [environment_module_1.EnvironmentModule],
16
+ providers: [
17
+ common_1.Logger,
18
+ s3_storage_service_1.S3StorageService,
19
+ {
20
+ provide: client_s3_1.S3Client,
21
+ useValue: new client_s3_1.S3Client({}),
22
+ },
23
+ ],
24
+ exports: [s3_storage_service_1.S3StorageService],
25
+ };
26
+ let StorageModule = class StorageModule {
27
+ };
28
+ exports.StorageModule = StorageModule;
29
+ exports.StorageModule = StorageModule = __decorate([
30
+ (0, common_1.Module)(exports.storageModule)
31
+ ], StorageModule);
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ require("reflect-metadata");
5
+ const nest_commander_1 = require("nest-commander");
6
+ const nestjs_pino_1 = require("nestjs-pino");
7
+ const cli_module_1 = require("./application/cli/cli-module");
8
+ const bootstrap = async () => {
9
+ const app = await nest_commander_1.CommandFactory.createWithoutRunning(cli_module_1.CliModule);
10
+ app.useLogger(app.get(nestjs_pino_1.Logger));
11
+ await nest_commander_1.CommandFactory.runApplication(app);
12
+ };
13
+ void bootstrap();
package/package.json ADDED
@@ -0,0 +1,96 @@
1
+ {
2
+ "author": "Heron Labs",
3
+ "bin": {
4
+ "hcli": "bin/src/main.js"
5
+ },
6
+ "bugs": {
7
+ "url": "https://github.com/heronlabs/terminal-cli/issues"
8
+ },
9
+ "dependencies": {
10
+ "@aws-sdk/client-s3": "^3.1045.0",
11
+ "@nestjs/common": "^11.1.18",
12
+ "@nestjs/config": "^4.0.4",
13
+ "@nestjs/core": "^11.1.18",
14
+ "luxon": "^3.7.2",
15
+ "nest-commander": "^3.20.1",
16
+ "nestjs-pino": "^4.6.1",
17
+ "pino-http": "^10.5.0",
18
+ "reflect-metadata": "^0.2.2",
19
+ "rxjs": "^7.8.2"
20
+ },
21
+ "description": "Heron CLI — database backup & rollup (PostgreSQL/MySQL → S3)",
22
+ "devDependencies": {
23
+ "@faker-js/faker": "^9.9.0",
24
+ "@nestjs/cli": "^11.0.21",
25
+ "@nestjs/testing": "^11.1.18",
26
+ "@stryker-mutator/core": "^9.6.1",
27
+ "@stryker-mutator/typescript-checker": "^9.6.1",
28
+ "@stryker-mutator/vitest-runner": "^9.6.1",
29
+ "@types/luxon": "^3.7.1",
30
+ "@types/node": "^22.15.30",
31
+ "@vitest/coverage-v8": "^4.1.6",
32
+ "dependency-cruiser": "^17.4.2",
33
+ "dotenv-cli": "^11.0.0",
34
+ "eslint": "^9.39.4",
35
+ "eslint-plugin-jsonc": "^3.1.2",
36
+ "eslint-plugin-simple-import-sort": "^12.1.1",
37
+ "eslint-plugin-yml": "^3.3.2",
38
+ "gts": "^7.0.0",
39
+ "jiti": "^2.7.0",
40
+ "moq.ts": "^10.0.8",
41
+ "nest-commander-testing": "^3.5.1",
42
+ "typescript": "~5.8.3",
43
+ "typescript-eslint": "^8.60.0",
44
+ "vitest": "^4.1.6"
45
+ },
46
+ "engineStrict": true,
47
+ "engines": {
48
+ "node": ">=22",
49
+ "pnpm": ">=10.29.3"
50
+ },
51
+ "files": [
52
+ "bin"
53
+ ],
54
+ "homepage": "https://github.com/heronlabs/terminal-cli#readme",
55
+ "keywords": [
56
+ "cli",
57
+ "backup",
58
+ "rollup",
59
+ "postgresql",
60
+ "mysql",
61
+ "s3",
62
+ "database",
63
+ "nestjs"
64
+ ],
65
+ "license": "MIT",
66
+ "name": "@heronlabs/terminal-cli",
67
+ "packageManager": "pnpm@10.33.2",
68
+ "pnpm": {
69
+ "onlyBuiltDependencies": [
70
+ "@nestjs/core",
71
+ "@swc/core"
72
+ ],
73
+ "overrides": {
74
+ "tmp@<0.2.6": ">=0.2.6"
75
+ }
76
+ },
77
+ "prettier": "gts/.prettierrc.json",
78
+ "publishConfig": {
79
+ "access": "public"
80
+ },
81
+ "repository": {
82
+ "type": "git",
83
+ "url": "git+https://github.com/heronlabs/terminal-cli.git"
84
+ },
85
+ "scripts": {
86
+ "build": "nest build --path tsconfig.bin.json",
87
+ "dep:cruise": "depcruise src --config .dependency-cruiser.cjs",
88
+ "lint:check": "gts lint && eslint \"**/*.json\" \"**/*.yml\" \"**/*.yaml\" --no-error-on-unmatched-pattern",
89
+ "lint:fix": "gts fix && eslint \"**/*.json\" \"**/*.yml\" \"**/*.yaml\" --fix --no-error-on-unmatched-pattern",
90
+ "prepublishOnly": "pnpm build",
91
+ "start": "dotenv -- node bin/src/main.js",
92
+ "test:mutation": "stryker run stryker.conf.json",
93
+ "test:unit": "vitest run"
94
+ },
95
+ "version": "0.0.1"
96
+ }