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.
Files changed (3) hide show
  1. package/bin/index.js +56 -36
  2. package/bin/mcp.js +79 -60
  3. 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(DORKY_DIR)) return console.log(chalk.yellow("⚠ Dorky is already initialized."));
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 = readJson(METADATA_PATH);
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 = readJson(METADATA_PATH);
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
- if (meta["stage-1-files"][f]?.hash === hash) return console.log(chalk.gray(`• ${f} (unchanged)`));
176
- meta["stage-1-files"][f] = { "mime-type": mimeTypes.lookup(f) || "application/octet-stream", hash };
177
- added.push(f);
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 = readJson(METADATA_PATH);
204
+ const meta = readMetadata();
186
205
  const removed = files.filter(f => {
187
- if (!meta["stage-1-files"][f]) return false;
188
- delete meta["stage-1-files"][f];
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 = readJson(METADATA_PATH);
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 = existsSync(HISTORY_PATH) ? JSON.parse(readFileSync(HISTORY_PATH)) : [];
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 = readJson(METADATA_PATH);
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 = existsSync(HISTORY_PATH) ? JSON.parse(readFileSync(HISTORY_PATH)) : [];
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 = existsSync(HISTORY_PATH) ? JSON.parse(readFileSync(HISTORY_PATH)) : [];
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 = readJson(METADATA_PATH);
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(DORKY_DIR)) return "Dorky is already initialized.";
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 = readJson(METADATA_PATH);
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 = readJson(METADATA_PATH);
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
- if (meta["stage-1-files"][f]?.hash === hash) { results.push(`${f} (unchanged)`); return; }
151
- meta["stage-1-files"][f] = { "mime-type": mimeTypes.lookup(f) || "application/octet-stream", hash };
152
- results.push(`Staged: ${f}`);
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 = readJson(METADATA_PATH);
179
+ const meta = readMetadata();
161
180
  const removed = files.filter(f => {
162
- if (!meta["stage-1-files"][f]) return false;
163
- delete meta["stage-1-files"][f];
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 = readJson(METADATA_PATH);
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
- const commitFiles = { ...meta["stage-1-files"] };
299
- const commitId = md5(JSON.stringify(commitFiles)).slice(0, 8);
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
- const root = path.basename(process.cwd());
306
- const historyPrefix = path.join(root, ".dorky-history", commitId);
307
- if (creds.storage === "aws") {
308
- await runS3(creds, async (s3, bucket) => {
309
- await Promise.all(Object.keys(commitFiles).map(async f => {
310
- const key = path.join(historyPrefix, f);
311
- await s3.send(new PutObjectCommand({ Bucket: bucket, Key: key, Body: readFileSync(f) }));
312
- }));
313
- });
314
- } else if (creds.storage === "google-drive") {
315
- await runDrive(async (drive) => {
316
- for (const f of Object.keys(commitFiles)) {
317
- const parentId = await getFolderId(path.join(root, ".dorky-history", commitId, path.dirname(f)), drive);
318
- await drive.files.create({
319
- requestBody: { name: path.basename(f), parents: [parentId] },
320
- media: { mimeType: commitFiles[f]["mime-type"], body: createReadStream(f) }
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 = readJson(METADATA_PATH);
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 = existsSync(HISTORY_PATH) ? JSON.parse(readFileSync(HISTORY_PATH)) : [];
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 = existsSync(HISTORY_PATH) ? JSON.parse(readFileSync(HISTORY_PATH)) : [];
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 = readJson(METADATA_PATH);
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 and uploaded state restored to commit ${entry.id}.`);
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
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dorky",
3
- "version": "4.1.2",
3
+ "version": "4.1.4",
4
4
  "description": "DevOps Records Keeper.",
5
5
  "bin": {
6
6
  "dorky": "bin/index.js",