dorky 4.1.2 → 4.1.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/bin/index.js +56 -36
- package/bin/mcp.js +79 -60
- package/package.json +1 -1
package/bin/index.js
CHANGED
|
@@ -23,6 +23,23 @@ const SCOPES = ['https://www.googleapis.com/auth/drive'];
|
|
|
23
23
|
// Helpers
|
|
24
24
|
const readJson = (p) => existsSync(p) ? JSON.parse(readFileSync(p)) : {};
|
|
25
25
|
const writeJson = (p, d) => writeFileSync(p, JSON.stringify(d, null, 2));
|
|
26
|
+
const toPosix = (p) => p ? p.replace(/\\/g, '/') : p;
|
|
27
|
+
const normalizeKeys = (obj) => {
|
|
28
|
+
if (!obj) return {};
|
|
29
|
+
const out = {};
|
|
30
|
+
for (const k of Object.keys(obj)) out[toPosix(k)] = obj[k];
|
|
31
|
+
return out;
|
|
32
|
+
};
|
|
33
|
+
const readMetadata = () => {
|
|
34
|
+
const meta = readJson(METADATA_PATH);
|
|
35
|
+
meta["stage-1-files"] = normalizeKeys(meta["stage-1-files"]);
|
|
36
|
+
meta["uploaded-files"] = normalizeKeys(meta["uploaded-files"]);
|
|
37
|
+
return meta;
|
|
38
|
+
};
|
|
39
|
+
const readHistory = () => {
|
|
40
|
+
const history = existsSync(HISTORY_PATH) ? JSON.parse(readFileSync(HISTORY_PATH)) : [];
|
|
41
|
+
return history.map(e => ({ ...e, files: normalizeKeys(e.files) }));
|
|
42
|
+
};
|
|
26
43
|
|
|
27
44
|
const checkDorkyProject = () => {
|
|
28
45
|
if (!existsSync(DORKY_DIR) && !existsSync(".dorkyignore")) {
|
|
@@ -96,7 +113,7 @@ async function authorizeGoogleDriveClient(forceReauth = false) {
|
|
|
96
113
|
}
|
|
97
114
|
|
|
98
115
|
async function init(storage) {
|
|
99
|
-
if (existsSync(
|
|
116
|
+
if (existsSync(CREDENTIALS_PATH)) return console.log(chalk.yellow("⚠ Dorky is already initialized."));
|
|
100
117
|
if (!["aws", "google-drive"].includes(storage)) return console.log(chalk.red("✖ Invalid storage. Use 'aws' or 'google-drive'."));
|
|
101
118
|
|
|
102
119
|
let credentials = {};
|
|
@@ -107,14 +124,15 @@ async function init(storage) {
|
|
|
107
124
|
}
|
|
108
125
|
credentials = { storage: "aws", accessKey: process.env.AWS_ACCESS_KEY, secretKey: process.env.AWS_SECRET_KEY, awsRegion: process.env.AWS_REGION, bucket: process.env.BUCKET_NAME };
|
|
109
126
|
} else {
|
|
127
|
+
if (!existsSync(DORKY_DIR)) mkdirSync(DORKY_DIR);
|
|
110
128
|
const client = await authorizeGoogleDriveClient(true);
|
|
111
129
|
credentials = { storage: "google-drive", ...client.credentials };
|
|
112
130
|
}
|
|
113
131
|
|
|
114
|
-
mkdirSync(DORKY_DIR);
|
|
115
|
-
writeJson(METADATA_PATH, { "stage-1-files": {}, "uploaded-files": {} });
|
|
116
|
-
writeJson(HISTORY_PATH, []);
|
|
117
|
-
writeFileSync(".dorkyignore", "");
|
|
132
|
+
if (!existsSync(DORKY_DIR)) mkdirSync(DORKY_DIR);
|
|
133
|
+
if (!existsSync(METADATA_PATH)) writeJson(METADATA_PATH, { "stage-1-files": {}, "uploaded-files": {} });
|
|
134
|
+
if (!existsSync(HISTORY_PATH)) writeJson(HISTORY_PATH, []);
|
|
135
|
+
if (!existsSync(".dorkyignore")) writeFileSync(".dorkyignore", "");
|
|
118
136
|
writeJson(CREDENTIALS_PATH, credentials);
|
|
119
137
|
console.log(chalk.green("✔ Dorky project initialized successfully."));
|
|
120
138
|
updateGitIgnore();
|
|
@@ -122,7 +140,7 @@ async function init(storage) {
|
|
|
122
140
|
|
|
123
141
|
async function list(type) {
|
|
124
142
|
checkDorkyProject();
|
|
125
|
-
const meta =
|
|
143
|
+
const meta = readMetadata();
|
|
126
144
|
if (type === "remote") {
|
|
127
145
|
if (!await checkCredentials()) return;
|
|
128
146
|
const creds = readJson(CREDENTIALS_PATH);
|
|
@@ -156,7 +174,7 @@ async function list(type) {
|
|
|
156
174
|
const files = await glob("**/*", { dot: true, ignore: [...exclusions.map(e => `**/${e}/**`), ...exclusions, ".dorky/**", ".dorkyignore", ".git/**", "node_modules/**"] });
|
|
157
175
|
|
|
158
176
|
files.forEach(f => {
|
|
159
|
-
const rel = path.relative(process.cwd(), f);
|
|
177
|
+
const rel = toPosix(path.relative(process.cwd(), f));
|
|
160
178
|
if (rel.includes('.env') || rel.includes('.config')) console.log(chalk.yellow(` ⚠ ${rel} (Potential sensitive file)`));
|
|
161
179
|
else console.log(chalk.gray(` ${rel}`));
|
|
162
180
|
});
|
|
@@ -167,14 +185,15 @@ async function list(type) {
|
|
|
167
185
|
|
|
168
186
|
function add(files) {
|
|
169
187
|
checkDorkyProject();
|
|
170
|
-
const meta =
|
|
188
|
+
const meta = readMetadata();
|
|
171
189
|
const added = [];
|
|
172
190
|
files.forEach(f => {
|
|
173
191
|
if (!existsSync(f)) return console.log(chalk.red(`✖ File not found: ${f}`));
|
|
174
192
|
const hash = md5(readFileSync(f));
|
|
175
|
-
|
|
176
|
-
meta["stage-1-files"][
|
|
177
|
-
|
|
193
|
+
const key = toPosix(f);
|
|
194
|
+
if (meta["stage-1-files"][key]?.hash === hash) return console.log(chalk.gray(`• ${key} (unchanged)`));
|
|
195
|
+
meta["stage-1-files"][key] = { "mime-type": mimeTypes.lookup(f) || "application/octet-stream", hash };
|
|
196
|
+
added.push(key);
|
|
178
197
|
});
|
|
179
198
|
writeJson(METADATA_PATH, meta);
|
|
180
199
|
added.forEach(f => console.log(chalk.green(`✔ Staged: ${f}`)));
|
|
@@ -182,10 +201,11 @@ function add(files) {
|
|
|
182
201
|
|
|
183
202
|
function rm(files) {
|
|
184
203
|
checkDorkyProject();
|
|
185
|
-
const meta =
|
|
204
|
+
const meta = readMetadata();
|
|
186
205
|
const removed = files.filter(f => {
|
|
187
|
-
|
|
188
|
-
|
|
206
|
+
const key = toPosix(f);
|
|
207
|
+
if (!meta["stage-1-files"][key]) return false;
|
|
208
|
+
delete meta["stage-1-files"][key];
|
|
189
209
|
return true;
|
|
190
210
|
});
|
|
191
211
|
writeJson(METADATA_PATH, meta);
|
|
@@ -268,7 +288,7 @@ async function runDrive(fn) {
|
|
|
268
288
|
async function push() {
|
|
269
289
|
checkDorkyProject();
|
|
270
290
|
if (!await checkCredentials()) return;
|
|
271
|
-
const meta =
|
|
291
|
+
const meta = readMetadata();
|
|
272
292
|
const filesToUpload = Object.keys(meta["stage-1-files"])
|
|
273
293
|
.filter(f => !meta["uploaded-files"][f] || meta["stage-1-files"][f].hash !== meta["uploaded-files"][f].hash)
|
|
274
294
|
.map(f => ({ name: f, ...meta["stage-1-files"][f] }));
|
|
@@ -280,7 +300,7 @@ async function push() {
|
|
|
280
300
|
|
|
281
301
|
const commitFiles = { ...meta["stage-1-files"] };
|
|
282
302
|
const commitId = md5(JSON.stringify(commitFiles)).slice(0, 8);
|
|
283
|
-
const history =
|
|
303
|
+
const history = readHistory();
|
|
284
304
|
if (history.length > 0 && history[history.length - 1].id === commitId) return console.log(chalk.yellow("ℹ Already on the latest commit. Nothing to push."));
|
|
285
305
|
|
|
286
306
|
const creds = readJson(CREDENTIALS_PATH);
|
|
@@ -288,14 +308,14 @@ async function push() {
|
|
|
288
308
|
await runS3(creds, async (s3, bucket) => {
|
|
289
309
|
if (filesToUpload.length > 0) {
|
|
290
310
|
await Promise.all(filesToUpload.map(async f => {
|
|
291
|
-
const key = path.join(path.basename(process.cwd()), f.name);
|
|
311
|
+
const key = path.posix.join(path.basename(process.cwd()), f.name);
|
|
292
312
|
await s3.send(new PutObjectCommand({ Bucket: bucket, Key: key, Body: readFileSync(f.name) }));
|
|
293
313
|
console.log(chalk.green(`✔ Uploaded: ${f.name}`));
|
|
294
314
|
}));
|
|
295
315
|
}
|
|
296
316
|
if (filesToDelete.length > 0) {
|
|
297
317
|
await Promise.all(filesToDelete.map(async f => {
|
|
298
|
-
const key = path.join(path.basename(process.cwd()), f);
|
|
318
|
+
const key = path.posix.join(path.basename(process.cwd()), f);
|
|
299
319
|
await s3.send(new DeleteObjectCommand({ Bucket: bucket, Key: key }));
|
|
300
320
|
console.log(chalk.yellow(`✔ Deleted remote: ${f}`));
|
|
301
321
|
}));
|
|
@@ -306,9 +326,9 @@ async function push() {
|
|
|
306
326
|
if (filesToUpload.length > 0) {
|
|
307
327
|
for (const f of filesToUpload) {
|
|
308
328
|
const root = path.basename(process.cwd());
|
|
309
|
-
const parentId = await getFolderId(path.dirname(path.join(root, f.name)), drive);
|
|
329
|
+
const parentId = await getFolderId(path.posix.dirname(path.posix.join(root, f.name)), drive);
|
|
310
330
|
await drive.files.create({
|
|
311
|
-
requestBody: { name: path.basename(f.name), parents: [parentId] },
|
|
331
|
+
requestBody: { name: path.posix.basename(f.name), parents: [parentId] },
|
|
312
332
|
media: { mimeType: f["mime-type"], body: createReadStream(f.name) }
|
|
313
333
|
});
|
|
314
334
|
console.log(chalk.green(`✔ Uploaded: ${f.name}`));
|
|
@@ -317,10 +337,10 @@ async function push() {
|
|
|
317
337
|
if (filesToDelete.length > 0) {
|
|
318
338
|
const root = path.basename(process.cwd());
|
|
319
339
|
for (const f of filesToDelete) {
|
|
320
|
-
const parentId = await getFolderId(path.dirname(path.join(root, f)), drive, false);
|
|
340
|
+
const parentId = await getFolderId(path.posix.dirname(path.posix.join(root, f)), drive, false);
|
|
321
341
|
if (parentId) {
|
|
322
342
|
const res = await drive.files.list({
|
|
323
|
-
q: `name='${path.basename(f)}' and '${parentId}' in parents and trashed=false`,
|
|
343
|
+
q: `name='${path.posix.basename(f)}' and '${parentId}' in parents and trashed=false`,
|
|
324
344
|
fields: 'files(id)'
|
|
325
345
|
});
|
|
326
346
|
if (res.data.files[0]) {
|
|
@@ -340,20 +360,20 @@ async function push() {
|
|
|
340
360
|
writeJson(HISTORY_PATH, history);
|
|
341
361
|
|
|
342
362
|
const root = path.basename(process.cwd());
|
|
343
|
-
const historyPrefix = path.join(root, ".dorky-history", commitId);
|
|
363
|
+
const historyPrefix = path.posix.join(root, ".dorky-history", commitId);
|
|
344
364
|
if (creds.storage === "aws") {
|
|
345
365
|
await runS3(creds, async (s3, bucket) => {
|
|
346
366
|
await Promise.all(Object.keys(commitFiles).map(async f => {
|
|
347
|
-
const key = path.join(historyPrefix, f);
|
|
367
|
+
const key = path.posix.join(historyPrefix, f);
|
|
348
368
|
await s3.send(new PutObjectCommand({ Bucket: bucket, Key: key, Body: readFileSync(f) }));
|
|
349
369
|
}));
|
|
350
370
|
});
|
|
351
371
|
} else if (creds.storage === "google-drive") {
|
|
352
372
|
await runDrive(async (drive) => {
|
|
353
373
|
for (const f of Object.keys(commitFiles)) {
|
|
354
|
-
const parentId = await getFolderId(path.join(root, ".dorky-history", commitId, path.dirname(f)), drive);
|
|
374
|
+
const parentId = await getFolderId(path.posix.join(root, ".dorky-history", commitId, path.posix.dirname(f)), drive);
|
|
355
375
|
await drive.files.create({
|
|
356
|
-
requestBody: { name: path.basename(f), parents: [parentId] },
|
|
376
|
+
requestBody: { name: path.posix.basename(f), parents: [parentId] },
|
|
357
377
|
media: { mimeType: commitFiles[f]["mime-type"], body: createReadStream(f) }
|
|
358
378
|
});
|
|
359
379
|
}
|
|
@@ -365,14 +385,14 @@ async function push() {
|
|
|
365
385
|
async function pull() {
|
|
366
386
|
checkDorkyProject();
|
|
367
387
|
if (!await checkCredentials()) return;
|
|
368
|
-
const meta =
|
|
388
|
+
const meta = readMetadata();
|
|
369
389
|
const files = meta["uploaded-files"];
|
|
370
390
|
const creds = readJson(CREDENTIALS_PATH);
|
|
371
391
|
|
|
372
392
|
if (creds.storage === "aws") {
|
|
373
393
|
await runS3(creds, async (s3, bucket) => {
|
|
374
394
|
await Promise.all(Object.keys(files).map(async f => {
|
|
375
|
-
const key = path.join(path.basename(process.cwd()), f);
|
|
395
|
+
const key = path.posix.join(path.basename(process.cwd()), f);
|
|
376
396
|
const { Body } = await s3.send(new GetObjectCommand({ Bucket: bucket, Key: key }));
|
|
377
397
|
const dir = path.dirname(f);
|
|
378
398
|
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
@@ -384,7 +404,7 @@ async function pull() {
|
|
|
384
404
|
await runDrive(async (drive) => {
|
|
385
405
|
const fileList = Object.keys(files).map(k => ({ name: k, ...files[k] }));
|
|
386
406
|
await Promise.all(fileList.map(async f => {
|
|
387
|
-
const res = await drive.files.list({ q: `name='${path.basename(f.name)}' and mimeType!='application/vnd.google-apps.folder'`, fields: 'files(id)' });
|
|
407
|
+
const res = await drive.files.list({ q: `name='${path.posix.basename(f.name)}' and mimeType!='application/vnd.google-apps.folder'`, fields: 'files(id)' });
|
|
388
408
|
if (!res.data.files[0]) return console.log(chalk.red(`✖ Missing remote file: ${f.name}`));
|
|
389
409
|
const data = await drive.files.get({ fileId: res.data.files[0].id, alt: 'media' });
|
|
390
410
|
if (!existsSync(path.dirname(f.name))) mkdirSync(path.dirname(f.name), { recursive: true });
|
|
@@ -397,7 +417,7 @@ async function pull() {
|
|
|
397
417
|
|
|
398
418
|
function log() {
|
|
399
419
|
checkDorkyProject();
|
|
400
|
-
const history =
|
|
420
|
+
const history = readHistory();
|
|
401
421
|
if (!history.length) return console.log(chalk.yellow("ℹ No history found. Push some files first."));
|
|
402
422
|
console.log(chalk.blue.bold("\n📜 Push History:\n"));
|
|
403
423
|
[...history].reverse().forEach((entry, i) => {
|
|
@@ -415,7 +435,7 @@ async function checkout(commitId) {
|
|
|
415
435
|
checkDorkyProject();
|
|
416
436
|
if (!await checkCredentials()) return;
|
|
417
437
|
|
|
418
|
-
const history =
|
|
438
|
+
const history = readHistory();
|
|
419
439
|
const entry = history.find(e => e.id === commitId || e.id.startsWith(commitId));
|
|
420
440
|
if (!entry) return console.log(chalk.red(`✖ Commit not found: ${commitId}. Run --log to see available commits.`));
|
|
421
441
|
|
|
@@ -423,12 +443,12 @@ async function checkout(commitId) {
|
|
|
423
443
|
|
|
424
444
|
const creds = readJson(CREDENTIALS_PATH);
|
|
425
445
|
const root = path.basename(process.cwd());
|
|
426
|
-
const historyPrefix = path.join(root, ".dorky-history", entry.id);
|
|
446
|
+
const historyPrefix = path.posix.join(root, ".dorky-history", entry.id);
|
|
427
447
|
|
|
428
448
|
if (creds.storage === "aws") {
|
|
429
449
|
await runS3(creds, async (s3, bucket) => {
|
|
430
450
|
await Promise.all(Object.keys(entry.files).map(async f => {
|
|
431
|
-
const key = path.join(historyPrefix, f);
|
|
451
|
+
const key = path.posix.join(historyPrefix, f);
|
|
432
452
|
const { Body } = await s3.send(new GetObjectCommand({ Bucket: bucket, Key: key }));
|
|
433
453
|
if (!existsSync(path.dirname(f))) mkdirSync(path.dirname(f), { recursive: true });
|
|
434
454
|
writeFileSync(f, await Body.transformToString());
|
|
@@ -438,10 +458,10 @@ async function checkout(commitId) {
|
|
|
438
458
|
} else if (creds.storage === "google-drive") {
|
|
439
459
|
await runDrive(async (drive) => {
|
|
440
460
|
for (const f of Object.keys(entry.files)) {
|
|
441
|
-
const parentId = await getFolderId(path.join(root, ".dorky-history", entry.id, path.dirname(f)), drive, false);
|
|
461
|
+
const parentId = await getFolderId(path.posix.join(root, ".dorky-history", entry.id, path.posix.dirname(f)), drive, false);
|
|
442
462
|
if (!parentId) { console.log(chalk.red(`✖ Remote history folder missing for: ${f}`)); continue; }
|
|
443
463
|
const res = await drive.files.list({
|
|
444
|
-
q: `name='${path.basename(f)}' and '${parentId}' in parents and trashed=false`,
|
|
464
|
+
q: `name='${path.posix.basename(f)}' and '${parentId}' in parents and trashed=false`,
|
|
445
465
|
fields: 'files(id)'
|
|
446
466
|
});
|
|
447
467
|
if (!res.data.files[0]) { console.log(chalk.red(`✖ Missing remote history file: ${f}`)); continue; }
|
|
@@ -453,7 +473,7 @@ async function checkout(commitId) {
|
|
|
453
473
|
});
|
|
454
474
|
}
|
|
455
475
|
|
|
456
|
-
const meta =
|
|
476
|
+
const meta = readMetadata();
|
|
457
477
|
meta["stage-1-files"] = { ...entry.files };
|
|
458
478
|
writeJson(METADATA_PATH, meta);
|
|
459
479
|
console.log(chalk.cyan(`\nℹ Staged state restored to commit ${entry.id}. Run --push to publish this state.`));
|
package/bin/mcp.js
CHANGED
|
@@ -24,6 +24,23 @@ const SCOPES = ["https://www.googleapis.com/auth/drive"];
|
|
|
24
24
|
// Helpers
|
|
25
25
|
const readJson = (p) => existsSync(p) ? JSON.parse(readFileSync(p)) : {};
|
|
26
26
|
const writeJson = (p, d) => writeFileSync(p, JSON.stringify(d, null, 2));
|
|
27
|
+
const toPosix = (p) => p ? p.replace(/\\/g, '/') : p;
|
|
28
|
+
const normalizeKeys = (obj) => {
|
|
29
|
+
if (!obj) return {};
|
|
30
|
+
const out = {};
|
|
31
|
+
for (const k of Object.keys(obj)) out[toPosix(k)] = obj[k];
|
|
32
|
+
return out;
|
|
33
|
+
};
|
|
34
|
+
const readMetadata = () => {
|
|
35
|
+
const meta = readJson(METADATA_PATH);
|
|
36
|
+
meta["stage-1-files"] = normalizeKeys(meta["stage-1-files"]);
|
|
37
|
+
meta["uploaded-files"] = normalizeKeys(meta["uploaded-files"]);
|
|
38
|
+
return meta;
|
|
39
|
+
};
|
|
40
|
+
const readHistory = () => {
|
|
41
|
+
const history = existsSync(HISTORY_PATH) ? JSON.parse(readFileSync(HISTORY_PATH)) : [];
|
|
42
|
+
return history.map(e => ({ ...e, files: normalizeKeys(e.files) }));
|
|
43
|
+
};
|
|
27
44
|
|
|
28
45
|
const checkDorkyProject = () => {
|
|
29
46
|
if (!existsSync(DORKY_DIR) && !existsSync(".dorkyignore")) {
|
|
@@ -68,7 +85,7 @@ async function authorizeGoogleDriveClient(forceReauth = false) {
|
|
|
68
85
|
}
|
|
69
86
|
|
|
70
87
|
async function init(storage) {
|
|
71
|
-
if (existsSync(
|
|
88
|
+
if (existsSync(CREDENTIALS_PATH)) return "Dorky is already initialized.";
|
|
72
89
|
if (!["aws", "google-drive"].includes(storage)) return "Invalid storage. Use 'aws' or 'google-drive'.";
|
|
73
90
|
|
|
74
91
|
let credentials = {};
|
|
@@ -78,14 +95,15 @@ async function init(storage) {
|
|
|
78
95
|
}
|
|
79
96
|
credentials = { storage: "aws", accessKey: process.env.AWS_ACCESS_KEY, secretKey: process.env.AWS_SECRET_KEY, awsRegion: process.env.AWS_REGION, bucket: process.env.BUCKET_NAME };
|
|
80
97
|
} else {
|
|
98
|
+
if (!existsSync(DORKY_DIR)) mkdirSync(DORKY_DIR);
|
|
81
99
|
const client = await authorizeGoogleDriveClient(true);
|
|
82
100
|
credentials = { storage: "google-drive", ...client.credentials };
|
|
83
101
|
}
|
|
84
102
|
|
|
85
|
-
mkdirSync(DORKY_DIR);
|
|
86
|
-
writeJson(METADATA_PATH, { "stage-1-files": {}, "uploaded-files": {} });
|
|
87
|
-
writeJson(HISTORY_PATH, []);
|
|
88
|
-
writeFileSync(".dorkyignore", "");
|
|
103
|
+
if (!existsSync(DORKY_DIR)) mkdirSync(DORKY_DIR);
|
|
104
|
+
if (!existsSync(METADATA_PATH)) writeJson(METADATA_PATH, { "stage-1-files": {}, "uploaded-files": {} });
|
|
105
|
+
if (!existsSync(HISTORY_PATH)) writeJson(HISTORY_PATH, []);
|
|
106
|
+
if (!existsSync(".dorkyignore")) writeFileSync(".dorkyignore", "");
|
|
89
107
|
writeJson(CREDENTIALS_PATH, credentials);
|
|
90
108
|
updateGitIgnore();
|
|
91
109
|
return "Dorky project initialized successfully.";
|
|
@@ -93,7 +111,7 @@ async function init(storage) {
|
|
|
93
111
|
|
|
94
112
|
async function list(type) {
|
|
95
113
|
checkDorkyProject();
|
|
96
|
-
const meta =
|
|
114
|
+
const meta = readMetadata();
|
|
97
115
|
const lines = [];
|
|
98
116
|
|
|
99
117
|
if (type === "remote") {
|
|
@@ -129,7 +147,7 @@ async function list(type) {
|
|
|
129
147
|
const files = await glob("**/*", { dot: true, ignore: [...exclusions.map(e => `**/${e}/**`), ...exclusions, ".dorky/**", ".dorkyignore", ".git/**", "node_modules/**"] });
|
|
130
148
|
|
|
131
149
|
files.forEach(f => {
|
|
132
|
-
const rel = path.relative(process.cwd(), f);
|
|
150
|
+
const rel = toPosix(path.relative(process.cwd(), f));
|
|
133
151
|
if (rel.includes(".env") || rel.includes(".config")) lines.push(` ${rel} (Potential sensitive file)`);
|
|
134
152
|
else lines.push(` ${rel}`);
|
|
135
153
|
});
|
|
@@ -142,14 +160,15 @@ async function list(type) {
|
|
|
142
160
|
|
|
143
161
|
function add(files) {
|
|
144
162
|
checkDorkyProject();
|
|
145
|
-
const meta =
|
|
163
|
+
const meta = readMetadata();
|
|
146
164
|
const results = [];
|
|
147
165
|
files.forEach(f => {
|
|
148
166
|
if (!existsSync(f)) { results.push(`File not found: ${f}`); return; }
|
|
149
167
|
const hash = md5(readFileSync(f));
|
|
150
|
-
|
|
151
|
-
meta["stage-1-files"][
|
|
152
|
-
|
|
168
|
+
const key = toPosix(f);
|
|
169
|
+
if (meta["stage-1-files"][key]?.hash === hash) { results.push(`${key} (unchanged)`); return; }
|
|
170
|
+
meta["stage-1-files"][key] = { "mime-type": mimeTypes.lookup(f) || "application/octet-stream", hash };
|
|
171
|
+
results.push(`Staged: ${key}`);
|
|
153
172
|
});
|
|
154
173
|
writeJson(METADATA_PATH, meta);
|
|
155
174
|
return results.join("\n");
|
|
@@ -157,10 +176,11 @@ function add(files) {
|
|
|
157
176
|
|
|
158
177
|
function rm(files) {
|
|
159
178
|
checkDorkyProject();
|
|
160
|
-
const meta =
|
|
179
|
+
const meta = readMetadata();
|
|
161
180
|
const removed = files.filter(f => {
|
|
162
|
-
|
|
163
|
-
|
|
181
|
+
const key = toPosix(f);
|
|
182
|
+
if (!meta["stage-1-files"][key]) return false;
|
|
183
|
+
delete meta["stage-1-files"][key];
|
|
164
184
|
return true;
|
|
165
185
|
});
|
|
166
186
|
writeJson(METADATA_PATH, meta);
|
|
@@ -235,7 +255,7 @@ async function runDrive(fn) {
|
|
|
235
255
|
async function push() {
|
|
236
256
|
checkDorkyProject();
|
|
237
257
|
if (!await checkCredentials()) return "Credentials not found. Please run init first.";
|
|
238
|
-
const meta =
|
|
258
|
+
const meta = readMetadata();
|
|
239
259
|
const filesToUpload = Object.keys(meta["stage-1-files"])
|
|
240
260
|
.filter(f => !meta["uploaded-files"][f] || meta["stage-1-files"][f].hash !== meta["uploaded-files"][f].hash)
|
|
241
261
|
.map(f => ({ name: f, ...meta["stage-1-files"][f] }));
|
|
@@ -243,6 +263,11 @@ async function push() {
|
|
|
243
263
|
|
|
244
264
|
if (filesToUpload.length === 0 && filesToDelete.length === 0) return "Nothing to push.";
|
|
245
265
|
|
|
266
|
+
const commitFiles = { ...meta["stage-1-files"] };
|
|
267
|
+
const commitId = md5(JSON.stringify(commitFiles)).slice(0, 8);
|
|
268
|
+
const history = readHistory();
|
|
269
|
+
if (history.length > 0 && history[history.length - 1].id === commitId) return "Already on the latest commit. Nothing to push.";
|
|
270
|
+
|
|
246
271
|
const creds = readJson(CREDENTIALS_PATH);
|
|
247
272
|
const results = [];
|
|
248
273
|
|
|
@@ -250,14 +275,14 @@ async function push() {
|
|
|
250
275
|
await runS3(creds, async (s3, bucket) => {
|
|
251
276
|
if (filesToUpload.length > 0) {
|
|
252
277
|
await Promise.all(filesToUpload.map(async f => {
|
|
253
|
-
const key = path.join(path.basename(process.cwd()), f.name);
|
|
278
|
+
const key = path.posix.join(path.basename(process.cwd()), f.name);
|
|
254
279
|
await s3.send(new PutObjectCommand({ Bucket: bucket, Key: key, Body: readFileSync(f.name) }));
|
|
255
280
|
results.push(`Uploaded: ${f.name}`);
|
|
256
281
|
}));
|
|
257
282
|
}
|
|
258
283
|
if (filesToDelete.length > 0) {
|
|
259
284
|
await Promise.all(filesToDelete.map(async f => {
|
|
260
|
-
const key = path.join(path.basename(process.cwd()), f);
|
|
285
|
+
const key = path.posix.join(path.basename(process.cwd()), f);
|
|
261
286
|
await s3.send(new DeleteObjectCommand({ Bucket: bucket, Key: key }));
|
|
262
287
|
results.push(`Deleted remote: ${f}`);
|
|
263
288
|
}));
|
|
@@ -268,9 +293,9 @@ async function push() {
|
|
|
268
293
|
if (filesToUpload.length > 0) {
|
|
269
294
|
for (const f of filesToUpload) {
|
|
270
295
|
const root = path.basename(process.cwd());
|
|
271
|
-
const parentId = await getFolderId(path.dirname(path.join(root, f.name)), drive);
|
|
296
|
+
const parentId = await getFolderId(path.posix.dirname(path.posix.join(root, f.name)), drive);
|
|
272
297
|
await drive.files.create({
|
|
273
|
-
requestBody: { name: path.basename(f.name), parents: [parentId] },
|
|
298
|
+
requestBody: { name: path.posix.basename(f.name), parents: [parentId] },
|
|
274
299
|
media: { mimeType: f["mime-type"], body: createReadStream(f.name) }
|
|
275
300
|
});
|
|
276
301
|
results.push(`Uploaded: ${f.name}`);
|
|
@@ -279,9 +304,9 @@ async function push() {
|
|
|
279
304
|
if (filesToDelete.length > 0) {
|
|
280
305
|
const root = path.basename(process.cwd());
|
|
281
306
|
for (const f of filesToDelete) {
|
|
282
|
-
const parentId = await getFolderId(path.dirname(path.join(root, f)), drive, false);
|
|
307
|
+
const parentId = await getFolderId(path.posix.dirname(path.posix.join(root, f)), drive, false);
|
|
283
308
|
if (parentId) {
|
|
284
|
-
const res = await drive.files.list({ q: `name='${path.basename(f)}' and '${parentId}' in parents and trashed=false`, fields: "files(id)" });
|
|
309
|
+
const res = await drive.files.list({ q: `name='${path.posix.basename(f)}' and '${parentId}' in parents and trashed=false`, fields: "files(id)" });
|
|
285
310
|
if (res.data.files[0]) {
|
|
286
311
|
await drive.files.delete({ fileId: res.data.files[0].id });
|
|
287
312
|
results.push(`Deleted remote: ${f}`);
|
|
@@ -295,35 +320,30 @@ async function push() {
|
|
|
295
320
|
meta["uploaded-files"] = { ...meta["stage-1-files"] };
|
|
296
321
|
writeJson(METADATA_PATH, meta);
|
|
297
322
|
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
const history = existsSync(HISTORY_PATH) ? JSON.parse(readFileSync(HISTORY_PATH)) : [];
|
|
301
|
-
if (!history.find(e => e.id === commitId)) {
|
|
302
|
-
history.push({ id: commitId, timestamp: new Date().toISOString(), files: commitFiles });
|
|
303
|
-
writeJson(HISTORY_PATH, history);
|
|
323
|
+
history.push({ id: commitId, timestamp: new Date().toISOString(), files: commitFiles });
|
|
324
|
+
writeJson(HISTORY_PATH, history);
|
|
304
325
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
}
|
|
325
|
-
results.push(`History commit saved: ${commitId}`);
|
|
326
|
+
const root = path.basename(process.cwd());
|
|
327
|
+
const historyPrefix = path.posix.join(root, ".dorky-history", commitId);
|
|
328
|
+
if (creds.storage === "aws") {
|
|
329
|
+
await runS3(creds, async (s3, bucket) => {
|
|
330
|
+
await Promise.all(Object.keys(commitFiles).map(async f => {
|
|
331
|
+
const key = path.posix.join(historyPrefix, f);
|
|
332
|
+
await s3.send(new PutObjectCommand({ Bucket: bucket, Key: key, Body: readFileSync(f) }));
|
|
333
|
+
}));
|
|
334
|
+
});
|
|
335
|
+
} else if (creds.storage === "google-drive") {
|
|
336
|
+
await runDrive(async (drive) => {
|
|
337
|
+
for (const f of Object.keys(commitFiles)) {
|
|
338
|
+
const parentId = await getFolderId(path.posix.join(root, ".dorky-history", commitId, path.posix.dirname(f)), drive);
|
|
339
|
+
await drive.files.create({
|
|
340
|
+
requestBody: { name: path.posix.basename(f), parents: [parentId] },
|
|
341
|
+
media: { mimeType: commitFiles[f]["mime-type"], body: createReadStream(f) }
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
});
|
|
326
345
|
}
|
|
346
|
+
results.push(`History commit saved: ${commitId}`);
|
|
327
347
|
|
|
328
348
|
return results.join("\n");
|
|
329
349
|
}
|
|
@@ -331,7 +351,7 @@ async function push() {
|
|
|
331
351
|
async function pull() {
|
|
332
352
|
checkDorkyProject();
|
|
333
353
|
if (!await checkCredentials()) return "Credentials not found. Please run init first.";
|
|
334
|
-
const meta =
|
|
354
|
+
const meta = readMetadata();
|
|
335
355
|
const files = meta["uploaded-files"];
|
|
336
356
|
const creds = readJson(CREDENTIALS_PATH);
|
|
337
357
|
const results = [];
|
|
@@ -339,7 +359,7 @@ async function pull() {
|
|
|
339
359
|
if (creds.storage === "aws") {
|
|
340
360
|
await runS3(creds, async (s3, bucket) => {
|
|
341
361
|
await Promise.all(Object.keys(files).map(async f => {
|
|
342
|
-
const key = path.join(path.basename(process.cwd()), f);
|
|
362
|
+
const key = path.posix.join(path.basename(process.cwd()), f);
|
|
343
363
|
const { Body } = await s3.send(new GetObjectCommand({ Bucket: bucket, Key: key }));
|
|
344
364
|
const dir = path.dirname(f);
|
|
345
365
|
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
@@ -351,7 +371,7 @@ async function pull() {
|
|
|
351
371
|
await runDrive(async (drive) => {
|
|
352
372
|
const fileList = Object.keys(files).map(k => ({ name: k, ...files[k] }));
|
|
353
373
|
await Promise.all(fileList.map(async f => {
|
|
354
|
-
const res = await drive.files.list({ q: `name='${path.basename(f.name)}' and mimeType!='application/vnd.google-apps.folder'`, fields: "files(id)" });
|
|
374
|
+
const res = await drive.files.list({ q: `name='${path.posix.basename(f.name)}' and mimeType!='application/vnd.google-apps.folder'`, fields: "files(id)" });
|
|
355
375
|
if (!res.data.files[0]) { results.push(`Missing remote file: ${f.name}`); return; }
|
|
356
376
|
const data = await drive.files.get({ fileId: res.data.files[0].id, alt: "media" });
|
|
357
377
|
if (!existsSync(path.dirname(f.name))) mkdirSync(path.dirname(f.name), { recursive: true });
|
|
@@ -366,7 +386,7 @@ async function pull() {
|
|
|
366
386
|
|
|
367
387
|
function log() {
|
|
368
388
|
checkDorkyProject();
|
|
369
|
-
const history =
|
|
389
|
+
const history = readHistory();
|
|
370
390
|
if (!history.length) return "No history found. Push some files first.";
|
|
371
391
|
|
|
372
392
|
const lines = ["Push History:"];
|
|
@@ -386,19 +406,19 @@ async function checkout(commitId) {
|
|
|
386
406
|
checkDorkyProject();
|
|
387
407
|
if (!await checkCredentials()) return "Credentials not found. Please run init first.";
|
|
388
408
|
|
|
389
|
-
const history =
|
|
409
|
+
const history = readHistory();
|
|
390
410
|
const entry = history.find(e => e.id === commitId || e.id.startsWith(commitId));
|
|
391
411
|
if (!entry) return `Commit not found: ${commitId}. Run log to see available commits.`;
|
|
392
412
|
|
|
393
413
|
const creds = readJson(CREDENTIALS_PATH);
|
|
394
414
|
const root = path.basename(process.cwd());
|
|
395
|
-
const historyPrefix = path.join(root, ".dorky-history", entry.id);
|
|
415
|
+
const historyPrefix = path.posix.join(root, ".dorky-history", entry.id);
|
|
396
416
|
const results = [`Checking out commit ${entry.id} (${new Date(entry.timestamp).toLocaleString()}):`];
|
|
397
417
|
|
|
398
418
|
if (creds.storage === "aws") {
|
|
399
419
|
await runS3(creds, async (s3, bucket) => {
|
|
400
420
|
await Promise.all(Object.keys(entry.files).map(async f => {
|
|
401
|
-
const key = path.join(historyPrefix, f);
|
|
421
|
+
const key = path.posix.join(historyPrefix, f);
|
|
402
422
|
const { Body } = await s3.send(new GetObjectCommand({ Bucket: bucket, Key: key }));
|
|
403
423
|
if (!existsSync(path.dirname(f))) mkdirSync(path.dirname(f), { recursive: true });
|
|
404
424
|
writeFileSync(f, await Body.transformToString());
|
|
@@ -408,9 +428,9 @@ async function checkout(commitId) {
|
|
|
408
428
|
} else if (creds.storage === "google-drive") {
|
|
409
429
|
await runDrive(async (drive) => {
|
|
410
430
|
for (const f of Object.keys(entry.files)) {
|
|
411
|
-
const parentId = await getFolderId(path.join(root, ".dorky-history", entry.id, path.dirname(f)), drive, false);
|
|
431
|
+
const parentId = await getFolderId(path.posix.join(root, ".dorky-history", entry.id, path.posix.dirname(f)), drive, false);
|
|
412
432
|
if (!parentId) { results.push(`Remote history folder missing for: ${f}`); continue; }
|
|
413
|
-
const res = await drive.files.list({ q: `name='${path.basename(f)}' and '${parentId}' in parents and trashed=false`, fields: "files(id)" });
|
|
433
|
+
const res = await drive.files.list({ q: `name='${path.posix.basename(f)}' and '${parentId}' in parents and trashed=false`, fields: "files(id)" });
|
|
414
434
|
if (!res.data.files[0]) { results.push(`Missing remote history file: ${f}`); continue; }
|
|
415
435
|
const data = await drive.files.get({ fileId: res.data.files[0].id, alt: "media" });
|
|
416
436
|
if (!existsSync(path.dirname(f))) mkdirSync(path.dirname(f), { recursive: true });
|
|
@@ -420,11 +440,10 @@ async function checkout(commitId) {
|
|
|
420
440
|
});
|
|
421
441
|
}
|
|
422
442
|
|
|
423
|
-
const meta =
|
|
443
|
+
const meta = readMetadata();
|
|
424
444
|
meta["stage-1-files"] = { ...entry.files };
|
|
425
|
-
meta["uploaded-files"] = { ...entry.files };
|
|
426
445
|
writeJson(METADATA_PATH, meta);
|
|
427
|
-
results.push(`Staged
|
|
446
|
+
results.push(`Staged state restored to commit ${entry.id}. Run push to publish this state.`);
|
|
428
447
|
return results.join("\n");
|
|
429
448
|
}
|
|
430
449
|
|