@vibedhost/cli 1.0.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/index.js +913 -0
- package/dist/utils.js +336 -0
- package/package.json +33 -0
- package/src/index.ts +1028 -0
- package/src/utils.ts +365 -0
- package/tsconfig.json +15 -0
package/src/utils.ts
ADDED
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import os from "os";
|
|
4
|
+
import zlib from "zlib";
|
|
5
|
+
import { exec } from "child_process";
|
|
6
|
+
|
|
7
|
+
export const API_BASE_URL = process.env.VIBED_API_URL || "https://www.vibedhost.com";
|
|
8
|
+
export const GLOBAL_CONFIG_DIR = path.join(os.homedir(), ".vibed");
|
|
9
|
+
export const GLOBAL_CONFIG_FILE = path.join(GLOBAL_CONFIG_DIR, "config.json");
|
|
10
|
+
export const PROJECT_CONFIG_DIR = ".vibed";
|
|
11
|
+
export const PROJECT_CONFIG_FILE = path.join(".vibed", "config.json");
|
|
12
|
+
|
|
13
|
+
export const MAX_PAYLOAD_BYTES = 50 * 1024 * 1024; // 50MB
|
|
14
|
+
|
|
15
|
+
const DEFAULT_IGNORED_DIRS = [
|
|
16
|
+
"node_modules",
|
|
17
|
+
".git",
|
|
18
|
+
".next",
|
|
19
|
+
"dist",
|
|
20
|
+
"build",
|
|
21
|
+
".output",
|
|
22
|
+
".venv",
|
|
23
|
+
"venv",
|
|
24
|
+
"env",
|
|
25
|
+
"__pycache__",
|
|
26
|
+
".turbo",
|
|
27
|
+
".cache",
|
|
28
|
+
".local",
|
|
29
|
+
".vibed",
|
|
30
|
+
".aws",
|
|
31
|
+
".ssh"
|
|
32
|
+
];
|
|
33
|
+
|
|
34
|
+
const DEFAULT_IGNORED_EXACT_FILES = [
|
|
35
|
+
".ds_store",
|
|
36
|
+
"thumbs.db",
|
|
37
|
+
".npmrc",
|
|
38
|
+
".git-credentials",
|
|
39
|
+
"credentials.json",
|
|
40
|
+
"service-account.json",
|
|
41
|
+
"id_rsa",
|
|
42
|
+
"id_ed25519"
|
|
43
|
+
];
|
|
44
|
+
|
|
45
|
+
const IGNORED_EXTENSIONS_REGEX =
|
|
46
|
+
/\.(mp4|mov|mkv|avi|iso|tar\.gz|zip|7z|rar|dmg|exe|dll|so|pem|key|pfx|p12|kdbx)$/i;
|
|
47
|
+
|
|
48
|
+
export interface GlobalConfig {
|
|
49
|
+
token: string;
|
|
50
|
+
email?: string;
|
|
51
|
+
name?: string;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface ProjectConfig {
|
|
55
|
+
workspaceId: string;
|
|
56
|
+
appName: string;
|
|
57
|
+
domain?: string;
|
|
58
|
+
lastDeployHash?: string;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
import crypto from "crypto";
|
|
62
|
+
|
|
63
|
+
export function computeBufferSha256(buf: Buffer): string {
|
|
64
|
+
return crypto.createHash("sha256").update(buf).digest("hex");
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function readGlobalConfig(): GlobalConfig | null {
|
|
68
|
+
try {
|
|
69
|
+
if (!fs.existsSync(GLOBAL_CONFIG_FILE)) return null;
|
|
70
|
+
const raw = fs.readFileSync(GLOBAL_CONFIG_FILE, "utf-8");
|
|
71
|
+
return JSON.parse(raw);
|
|
72
|
+
} catch {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function writeGlobalConfig(config: GlobalConfig): void {
|
|
78
|
+
try {
|
|
79
|
+
if (!fs.existsSync(GLOBAL_CONFIG_DIR)) {
|
|
80
|
+
fs.mkdirSync(GLOBAL_CONFIG_DIR, { recursive: true, mode: 0o700 });
|
|
81
|
+
}
|
|
82
|
+
fs.writeFileSync(GLOBAL_CONFIG_FILE, JSON.stringify(config, null, 2), { mode: 0o600 });
|
|
83
|
+
} catch (err: any) {
|
|
84
|
+
console.error(`Failed to write local configuration: ${err.message}`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function clearGlobalConfig(): void {
|
|
89
|
+
try {
|
|
90
|
+
if (fs.existsSync(GLOBAL_CONFIG_FILE)) {
|
|
91
|
+
fs.unlinkSync(GLOBAL_CONFIG_FILE);
|
|
92
|
+
}
|
|
93
|
+
} catch {}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function readProjectConfig(targetDir: string = process.cwd()): ProjectConfig | null {
|
|
97
|
+
try {
|
|
98
|
+
const configPath = path.join(targetDir, PROJECT_CONFIG_FILE);
|
|
99
|
+
if (!fs.existsSync(configPath)) return null;
|
|
100
|
+
const raw = fs.readFileSync(configPath, "utf-8");
|
|
101
|
+
return JSON.parse(raw);
|
|
102
|
+
} catch {
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export function writeProjectConfig(config: ProjectConfig, targetDir: string = process.cwd()): void {
|
|
108
|
+
try {
|
|
109
|
+
const dirPath = path.join(targetDir, PROJECT_CONFIG_DIR);
|
|
110
|
+
if (!fs.existsSync(dirPath)) {
|
|
111
|
+
fs.mkdirSync(dirPath, { recursive: true });
|
|
112
|
+
}
|
|
113
|
+
const configPath = path.join(targetDir, PROJECT_CONFIG_FILE);
|
|
114
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
115
|
+
} catch {}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export function openBrowser(url: string): void {
|
|
119
|
+
try {
|
|
120
|
+
const parsed = new URL(url);
|
|
121
|
+
if (parsed.protocol !== "https:" && parsed.protocol !== "http:") {
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
// Reject shell control characters
|
|
125
|
+
if (/[\r\n"';`$&|<>]/.test(url)) {
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
} catch {
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const platform = process.platform;
|
|
133
|
+
let command = "";
|
|
134
|
+
if (platform === "darwin") {
|
|
135
|
+
command = `open "${url}"`;
|
|
136
|
+
} else if (platform === "win32") {
|
|
137
|
+
command = `start "" "${url}"`;
|
|
138
|
+
} else {
|
|
139
|
+
command = `xdg-open "${url}"`;
|
|
140
|
+
}
|
|
141
|
+
exec(command, () => {});
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export interface FileEntry {
|
|
145
|
+
relativePath: string;
|
|
146
|
+
absolutePath: string;
|
|
147
|
+
size: number;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export function scanDirectory(
|
|
151
|
+
rootDir: string,
|
|
152
|
+
currentDir: string = rootDir,
|
|
153
|
+
collected: FileEntry[] = [],
|
|
154
|
+
bloatFound: Record<string, number> = {}
|
|
155
|
+
): { files: FileEntry[]; bloatReport: Record<string, number> } {
|
|
156
|
+
const items = fs.readdirSync(currentDir, { withFileTypes: true });
|
|
157
|
+
let realRootDir = rootDir;
|
|
158
|
+
try {
|
|
159
|
+
realRootDir = fs.realpathSync(rootDir);
|
|
160
|
+
} catch {}
|
|
161
|
+
|
|
162
|
+
for (const item of items) {
|
|
163
|
+
const itemName = item.name;
|
|
164
|
+
const fullPath = path.join(currentDir, itemName);
|
|
165
|
+
const relPath = path.relative(rootDir, fullPath).replace(/\\/g, "/");
|
|
166
|
+
|
|
167
|
+
// Symlink Boundary Guard: Ensure symlinks do not escape project root
|
|
168
|
+
try {
|
|
169
|
+
const lstat = fs.lstatSync(fullPath);
|
|
170
|
+
if (lstat.isSymbolicLink()) {
|
|
171
|
+
const realPath = fs.realpathSync(fullPath);
|
|
172
|
+
if (!realPath.startsWith(realRootDir)) {
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
} catch {
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (item.isDirectory()) {
|
|
181
|
+
if (DEFAULT_IGNORED_DIRS.includes(itemName) || itemName.startsWith(".")) {
|
|
182
|
+
// Track directory size for diagnostic reporting
|
|
183
|
+
try {
|
|
184
|
+
const dirStat = getDirectorySize(fullPath);
|
|
185
|
+
bloatFound[itemName] = (bloatFound[itemName] || 0) + dirStat;
|
|
186
|
+
} catch {}
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
scanDirectory(rootDir, fullPath, collected, bloatFound);
|
|
190
|
+
} else if (item.isFile()) {
|
|
191
|
+
const lowerName = itemName.toLowerCase();
|
|
192
|
+
if (
|
|
193
|
+
DEFAULT_IGNORED_EXACT_FILES.includes(lowerName) ||
|
|
194
|
+
lowerName.startsWith(".env") ||
|
|
195
|
+
IGNORED_EXTENSIONS_REGEX.test(lowerName)
|
|
196
|
+
) {
|
|
197
|
+
continue;
|
|
198
|
+
}
|
|
199
|
+
try {
|
|
200
|
+
const stat = fs.statSync(fullPath);
|
|
201
|
+
collected.push({
|
|
202
|
+
relativePath: relPath,
|
|
203
|
+
absolutePath: fullPath,
|
|
204
|
+
size: stat.size
|
|
205
|
+
});
|
|
206
|
+
} catch {}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
return { files: collected, bloatReport: bloatFound };
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function getDirectorySize(dirPath: string): number {
|
|
214
|
+
let total = 0;
|
|
215
|
+
try {
|
|
216
|
+
const items = fs.readdirSync(dirPath, { withFileTypes: true });
|
|
217
|
+
for (const item of items) {
|
|
218
|
+
const p = path.join(dirPath, item.name);
|
|
219
|
+
if (item.isDirectory()) {
|
|
220
|
+
total += getDirectorySize(p);
|
|
221
|
+
} else if (item.isFile()) {
|
|
222
|
+
total += fs.statSync(p).size;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
} catch {}
|
|
226
|
+
return total;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Pure zero-dependency in-memory PKZip generator.
|
|
231
|
+
*/
|
|
232
|
+
export function createZipBuffer(files: FileEntry[]): Buffer {
|
|
233
|
+
const localHeaders: Buffer[] = [];
|
|
234
|
+
const centralRecords: Buffer[] = [];
|
|
235
|
+
let offset = 0;
|
|
236
|
+
|
|
237
|
+
for (const file of files) {
|
|
238
|
+
const fileContent = fs.readFileSync(file.absolutePath);
|
|
239
|
+
const uncompressedSize = fileContent.length;
|
|
240
|
+
const crc = computeCrc32(fileContent);
|
|
241
|
+
|
|
242
|
+
const compressedContent = zlib.deflateRawSync(fileContent);
|
|
243
|
+
const compressedSize = compressedContent.length;
|
|
244
|
+
|
|
245
|
+
const pathBuffer = Buffer.from(file.relativePath, "utf8");
|
|
246
|
+
const pathLength = pathBuffer.length;
|
|
247
|
+
|
|
248
|
+
// 1. Local File Header (30 Bytes)
|
|
249
|
+
const localHeader = Buffer.alloc(30 + pathLength);
|
|
250
|
+
localHeader.writeUInt32LE(0x04034b50, 0); // Signature
|
|
251
|
+
localHeader.writeUInt16LE(20, 4); // Version needed
|
|
252
|
+
localHeader.writeUInt16LE(0, 6); // Flags
|
|
253
|
+
localHeader.writeUInt16LE(8, 8); // Compression (Deflate)
|
|
254
|
+
localHeader.writeUInt16LE(0, 10); // Mod time
|
|
255
|
+
localHeader.writeUInt16LE(0, 12); // Mod date
|
|
256
|
+
localHeader.writeUInt32LE(crc, 14); // CRC-32
|
|
257
|
+
localHeader.writeUInt32LE(compressedSize, 18);
|
|
258
|
+
localHeader.writeUInt32LE(uncompressedSize, 22);
|
|
259
|
+
localHeader.writeUInt16LE(pathLength, 26);
|
|
260
|
+
localHeader.writeUInt16LE(0, 28); // Extra field length
|
|
261
|
+
pathBuffer.copy(localHeader, 30);
|
|
262
|
+
|
|
263
|
+
localHeaders.push(localHeader, compressedContent);
|
|
264
|
+
|
|
265
|
+
// 2. Central Directory Record (46 Bytes)
|
|
266
|
+
const centralRecord = Buffer.alloc(46 + pathLength);
|
|
267
|
+
centralRecord.writeUInt32LE(0x02014b50, 0); // Signature
|
|
268
|
+
centralRecord.writeUInt16LE(20, 4); // Version made by
|
|
269
|
+
centralRecord.writeUInt16LE(20, 6); // Version needed
|
|
270
|
+
centralRecord.writeUInt16LE(0, 8); // Flags
|
|
271
|
+
centralRecord.writeUInt16LE(8, 10); // Compression
|
|
272
|
+
centralRecord.writeUInt16LE(0, 12); // Mod time
|
|
273
|
+
centralRecord.writeUInt16LE(0, 14); // Mod date
|
|
274
|
+
centralRecord.writeUInt32LE(crc, 16); // CRC-32
|
|
275
|
+
centralRecord.writeUInt32LE(compressedSize, 20);
|
|
276
|
+
centralRecord.writeUInt32LE(uncompressedSize, 24);
|
|
277
|
+
centralRecord.writeUInt16LE(pathLength, 28);
|
|
278
|
+
centralRecord.writeUInt16LE(0, 30); // Extra field length
|
|
279
|
+
centralRecord.writeUInt16LE(0, 32); // Comment length
|
|
280
|
+
centralRecord.writeUInt16LE(0, 34); // Disk start
|
|
281
|
+
centralRecord.writeUInt16LE(0, 36); // Internal attr
|
|
282
|
+
centralRecord.writeUInt32LE(0, 38); // External attr
|
|
283
|
+
centralRecord.writeUInt32LE(offset, 42); // Relative offset of local header
|
|
284
|
+
pathBuffer.copy(centralRecord, 46);
|
|
285
|
+
|
|
286
|
+
centralRecords.push(centralRecord);
|
|
287
|
+
|
|
288
|
+
offset += localHeader.length + compressedContent.length;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
const centralDirBuffer = Buffer.concat(centralRecords);
|
|
292
|
+
const centralDirSize = centralDirBuffer.length;
|
|
293
|
+
const centralDirOffset = offset;
|
|
294
|
+
const totalEntries = files.length;
|
|
295
|
+
|
|
296
|
+
// 3. End of Central Directory Record (22 Bytes)
|
|
297
|
+
const eocd = Buffer.alloc(22);
|
|
298
|
+
eocd.writeUInt32LE(0x06054b50, 0); // Signature
|
|
299
|
+
eocd.writeUInt16LE(0, 4); // Disk number
|
|
300
|
+
eocd.writeUInt16LE(0, 6); // Disk with CD
|
|
301
|
+
eocd.writeUInt16LE(totalEntries, 8);
|
|
302
|
+
eocd.writeUInt16LE(totalEntries, 10);
|
|
303
|
+
eocd.writeUInt32LE(centralDirSize, 12);
|
|
304
|
+
eocd.writeUInt32LE(centralDirOffset, 16);
|
|
305
|
+
eocd.writeUInt16LE(0, 20); // Comment length
|
|
306
|
+
|
|
307
|
+
return Buffer.concat([...localHeaders, centralDirBuffer, eocd]);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
const CRC_TABLE = new Uint32Array(256);
|
|
311
|
+
for (let i = 0; i < 256; i++) {
|
|
312
|
+
let c = i;
|
|
313
|
+
for (let j = 0; j < 8; j++) {
|
|
314
|
+
c = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1;
|
|
315
|
+
}
|
|
316
|
+
CRC_TABLE[i] = c;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
function computeCrc32(buf: Buffer): number {
|
|
320
|
+
let crc = 0xffffffff;
|
|
321
|
+
for (let i = 0; i < buf.length; i++) {
|
|
322
|
+
crc = CRC_TABLE[(crc ^ buf[i]) & 0xff] ^ (crc >>> 8);
|
|
323
|
+
}
|
|
324
|
+
return (crc ^ 0xffffffff) >>> 0;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
import readline from "readline";
|
|
328
|
+
|
|
329
|
+
export function askQuestion(query: string, defaultValue: string = ""): Promise<string> {
|
|
330
|
+
return new Promise((resolve) => {
|
|
331
|
+
const rl = readline.createInterface({
|
|
332
|
+
input: process.stdin,
|
|
333
|
+
output: process.stdout,
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
const promptText = defaultValue ? `${query} [${defaultValue}]: ` : `${query}: `;
|
|
337
|
+
rl.question(promptText, (answer) => {
|
|
338
|
+
rl.close();
|
|
339
|
+
const trimmed = answer.trim();
|
|
340
|
+
resolve(trimmed || defaultValue);
|
|
341
|
+
});
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
export function inferAppName(targetDir: string): string {
|
|
346
|
+
try {
|
|
347
|
+
const pkgPath = path.join(targetDir, "package.json");
|
|
348
|
+
if (fs.existsSync(pkgPath)) {
|
|
349
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
|
|
350
|
+
if (pkg.name) {
|
|
351
|
+
const clean = String(pkg.name)
|
|
352
|
+
.replace(/^@[^/]+\//, "")
|
|
353
|
+
.toLowerCase()
|
|
354
|
+
.replace(/[^a-z0-9-]/g, "-")
|
|
355
|
+
.replace(/(^-|-$)+/g, "");
|
|
356
|
+
if (clean) return clean;
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
} catch {}
|
|
360
|
+
return path
|
|
361
|
+
.basename(targetDir)
|
|
362
|
+
.toLowerCase()
|
|
363
|
+
.replace(/[^a-z0-9-]/g, "-")
|
|
364
|
+
.replace(/(^-|-$)+/g, "");
|
|
365
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "CommonJS",
|
|
5
|
+
"moduleResolution": "Node",
|
|
6
|
+
"outDir": "./dist",
|
|
7
|
+
"rootDir": "./src",
|
|
8
|
+
"strict": true,
|
|
9
|
+
"esModuleInterop": true,
|
|
10
|
+
"skipLibCheck": true,
|
|
11
|
+
"forceConsistentCasingInFileNames": true,
|
|
12
|
+
"declaration": false
|
|
13
|
+
},
|
|
14
|
+
"include": ["src/**/*"]
|
|
15
|
+
}
|