oopsdb 1.1.0 → 1.3.0
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/commands/secure.js +25 -5
- package/dist/utils/config.js +19 -13
- package/package.json +2 -1
package/dist/commands/secure.js
CHANGED
|
@@ -63,11 +63,31 @@ async function secureCommand(options) {
|
|
|
63
63
|
const fileSizeInBytes = fs.statSync(latestFile).size;
|
|
64
64
|
const fileSizeMB = (fileSizeInBytes / (1024 * 1024)).toFixed(2);
|
|
65
65
|
console.log(chalk_1.default.blue(` Found latest snapshot: ${fileName} (${fileSizeMB} MB)`));
|
|
66
|
-
//
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
66
|
+
// In production, hit the live endpoint. If testing locally, hit the wrangler dev server.
|
|
67
|
+
const baseUrl = process.env.TEST_LOCAL_API ? 'http://localhost:8788' : 'https://oopsdb.com';
|
|
68
|
+
console.log(chalk_1.default.gray(` Requesting secure upload token from ${baseUrl}...\n`));
|
|
69
|
+
let actualUploadUrl = '';
|
|
70
|
+
try {
|
|
71
|
+
const res = await fetch(`${baseUrl}/api/upload-url?fileName=${fileName}`, {
|
|
72
|
+
headers: {
|
|
73
|
+
'Authorization': 'Bearer oops_sec_9f8b2c7d4e5a1b3c8f9d0e2a5b7c8d9e'
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
if (!res.ok) {
|
|
77
|
+
const errText = await res.text();
|
|
78
|
+
console.log(chalk_1.default.red(` Failed to get upload token: ${res.status} ${res.statusText}`));
|
|
79
|
+
console.log(chalk_1.default.gray(` Details: ${errText}\n`));
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
const data = await res.json();
|
|
83
|
+
actualUploadUrl = data.uploadUrl;
|
|
84
|
+
}
|
|
85
|
+
catch (err) {
|
|
86
|
+
console.log(chalk_1.default.red(` Network error reaching backend: ${err.message}\n`));
|
|
87
|
+
console.log(chalk_1.default.yellow(` Tip: If testing locally, ensure you ran 'npx wrangler pages dev website' first and set TEST_LOCAL_API=1\n`));
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
await uploadToS3(latestFile, actualUploadUrl);
|
|
71
91
|
}
|
|
72
92
|
async function uploadToS3(filePath, uploadUrl) {
|
|
73
93
|
const size = fs.statSync(filePath).size;
|
package/dist/utils/config.js
CHANGED
|
@@ -43,9 +43,15 @@ exports.getEncryptionKey = getEncryptionKey;
|
|
|
43
43
|
const fs = __importStar(require("fs"));
|
|
44
44
|
const path = __importStar(require("path"));
|
|
45
45
|
const crypto = __importStar(require("crypto"));
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
46
|
+
function getConfigDirPath() {
|
|
47
|
+
return path.join(process.cwd(), '.oopsdb');
|
|
48
|
+
}
|
|
49
|
+
function getConfigFilePath() {
|
|
50
|
+
return path.join(getConfigDirPath(), 'config.json');
|
|
51
|
+
}
|
|
52
|
+
function getBackupsDirPath() {
|
|
53
|
+
return path.join(getConfigDirPath(), 'backups');
|
|
54
|
+
}
|
|
49
55
|
// We encrypt the config file itself using a static machine-local key so that
|
|
50
56
|
// rogue processes can't easily read the master key in plain text from the file system.
|
|
51
57
|
// Note: This machineKey is NOT used for the database backups, only the config.json.
|
|
@@ -70,16 +76,16 @@ function decryptConfig(text) {
|
|
|
70
76
|
}
|
|
71
77
|
function ensureConfigDir() {
|
|
72
78
|
try {
|
|
73
|
-
if (!fs.existsSync(
|
|
74
|
-
fs.mkdirSync(
|
|
79
|
+
if (!fs.existsSync(getConfigDirPath())) {
|
|
80
|
+
fs.mkdirSync(getConfigDirPath(), { recursive: true });
|
|
75
81
|
}
|
|
76
|
-
if (!fs.existsSync(
|
|
77
|
-
fs.mkdirSync(
|
|
82
|
+
if (!fs.existsSync(getBackupsDirPath())) {
|
|
83
|
+
fs.mkdirSync(getBackupsDirPath(), { recursive: true });
|
|
78
84
|
}
|
|
79
85
|
}
|
|
80
86
|
catch (err) {
|
|
81
87
|
if (err.code === 'EACCES') {
|
|
82
|
-
throw new Error(`Permission denied creating ${
|
|
88
|
+
throw new Error(`Permission denied creating ${getConfigDirPath()}. Check directory permissions.`);
|
|
83
89
|
}
|
|
84
90
|
if (err.code === 'ENOSPC') {
|
|
85
91
|
throw new Error('Disk full. Free up space and try again.');
|
|
@@ -90,14 +96,14 @@ function ensureConfigDir() {
|
|
|
90
96
|
function saveConfig(config) {
|
|
91
97
|
ensureConfigDir();
|
|
92
98
|
const encrypted = encryptConfig(JSON.stringify(config));
|
|
93
|
-
fs.writeFileSync(
|
|
99
|
+
fs.writeFileSync(getConfigFilePath(), encrypted, 'utf8');
|
|
94
100
|
}
|
|
95
101
|
function loadConfig() {
|
|
96
|
-
if (!fs.existsSync(
|
|
102
|
+
if (!fs.existsSync(getConfigFilePath())) {
|
|
97
103
|
return null;
|
|
98
104
|
}
|
|
99
105
|
try {
|
|
100
|
-
const encrypted = fs.readFileSync(
|
|
106
|
+
const encrypted = fs.readFileSync(getConfigFilePath(), 'utf8');
|
|
101
107
|
const decrypted = decryptConfig(encrypted);
|
|
102
108
|
return JSON.parse(decrypted);
|
|
103
109
|
}
|
|
@@ -107,10 +113,10 @@ function loadConfig() {
|
|
|
107
113
|
}
|
|
108
114
|
function getBackupsDir() {
|
|
109
115
|
ensureConfigDir();
|
|
110
|
-
return
|
|
116
|
+
return getBackupsDirPath();
|
|
111
117
|
}
|
|
112
118
|
function getConfigDir() {
|
|
113
|
-
return
|
|
119
|
+
return getConfigDirPath();
|
|
114
120
|
}
|
|
115
121
|
function generateMasterKey() {
|
|
116
122
|
return crypto.randomBytes(32).toString('hex');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "oopsdb",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"description": "Don't let AI nuke your database. Auto-backup and 1-click restore for developers using Claude Code, Cursor, Windsurf, and other AI coding agents.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -54,6 +54,7 @@
|
|
|
54
54
|
"@cloudflare/workers-types": "^4.20260310.1",
|
|
55
55
|
"@types/inquirer": "^8.2.10",
|
|
56
56
|
"@types/node": "^22.0.0",
|
|
57
|
+
"dotenv": "^17.3.1",
|
|
57
58
|
"testcontainers": "^11.12.0",
|
|
58
59
|
"typescript": "^5.7.0",
|
|
59
60
|
"vitest": "^4.0.18"
|