dorky 3.0.0 → 3.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/package.json +1 -1
- package/extension/dorky-extension/.vscode/extensions.json +0 -8
- package/extension/dorky-extension/.vscode/launch.json +0 -17
- package/extension/dorky-extension/.vscode-test.mjs +0 -5
- package/extension/dorky-extension/.vscodeignore +0 -10
- package/extension/dorky-extension/CHANGELOG.md +0 -9
- package/extension/dorky-extension/README.md +0 -106
- package/extension/dorky-extension/eslint.config.mjs +0 -25
- package/extension/dorky-extension/extension.js +0 -587
- package/extension/dorky-extension/jsconfig.json +0 -13
- package/extension/dorky-extension/media/logo.svg +0 -6
- package/extension/dorky-extension/package-lock.json +0 -8404
- package/extension/dorky-extension/package.json +0 -151
|
@@ -1,587 +0,0 @@
|
|
|
1
|
-
const vscode = require('vscode');
|
|
2
|
-
const { existsSync, mkdirSync, writeFileSync, readFileSync, createReadStream, unlinkSync, rmSync } = require('fs');
|
|
3
|
-
const path = require('path');
|
|
4
|
-
const mimeTypes = require('mime-types');
|
|
5
|
-
const md5 = require('md5');
|
|
6
|
-
const { GetObjectCommand, PutObjectCommand, ListObjectsV2Command, DeleteObjectCommand, DeleteObjectsCommand, S3Client } = require('@aws-sdk/client-s3');
|
|
7
|
-
const { authenticate } = require('@google-cloud/local-auth');
|
|
8
|
-
const { google } = require('googleapis');
|
|
9
|
-
|
|
10
|
-
const DORKY_DIR = '.dorky';
|
|
11
|
-
const METADATA_PATH = path.join(DORKY_DIR, 'metadata.json');
|
|
12
|
-
const CREDENTIALS_PATH = path.join(DORKY_DIR, 'credentials.json');
|
|
13
|
-
const SCOPES = ['https://www.googleapis.com/auth/drive'];
|
|
14
|
-
|
|
15
|
-
let outputChannel;
|
|
16
|
-
let filesProvider;
|
|
17
|
-
|
|
18
|
-
// --- Helpers ---
|
|
19
|
-
|
|
20
|
-
function getRoot() {
|
|
21
|
-
const folders = vscode.workspace.workspaceFolders;
|
|
22
|
-
if (!folders?.length) {
|
|
23
|
-
vscode.window.showErrorMessage('No workspace folder open.');
|
|
24
|
-
return null;
|
|
25
|
-
}
|
|
26
|
-
return folders[0].uri.fsPath;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
const readJson = (p) => existsSync(p) ? JSON.parse(readFileSync(p)) : {};
|
|
30
|
-
const writeJson = (p, d) => writeFileSync(p, JSON.stringify(d, null, 2));
|
|
31
|
-
|
|
32
|
-
function log(msg) {
|
|
33
|
-
outputChannel.appendLine(msg);
|
|
34
|
-
outputChannel.show(true);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function checkDorkyProject(root) {
|
|
38
|
-
if (!existsSync(path.join(root, DORKY_DIR)) && !existsSync(path.join(root, '.dorkyignore'))) {
|
|
39
|
-
vscode.window.showErrorMessage('Not a dorky project. Use the sidebar to initialize.');
|
|
40
|
-
return false;
|
|
41
|
-
}
|
|
42
|
-
return true;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
function updateDorkyContext(root) {
|
|
46
|
-
vscode.commands.executeCommand('setContext', 'dorky.initialized', existsSync(path.join(root, DORKY_DIR)));
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
function updateGitIgnore(root) {
|
|
50
|
-
const gitignorePath = path.join(root, '.gitignore');
|
|
51
|
-
let content = existsSync(gitignorePath) ? readFileSync(gitignorePath).toString() : '';
|
|
52
|
-
if (!content.includes(CREDENTIALS_PATH)) {
|
|
53
|
-
writeFileSync(gitignorePath, content + '\n' + CREDENTIALS_PATH + '\n');
|
|
54
|
-
log('ℹ Updated .gitignore to secure credentials.');
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
// --- Google Drive Auth ---
|
|
59
|
-
|
|
60
|
-
async function authorizeGoogleDriveClient(root, forceReauth = false) {
|
|
61
|
-
const credPath = path.join(root, CREDENTIALS_PATH);
|
|
62
|
-
const gdCredPath = path.join(root, 'google-drive-credentials.json');
|
|
63
|
-
|
|
64
|
-
if (!existsSync(gdCredPath)) {
|
|
65
|
-
vscode.window.showErrorMessage('google-drive-credentials.json not found in workspace root.');
|
|
66
|
-
return null;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
if (!forceReauth && existsSync(credPath)) {
|
|
70
|
-
const saved = readJson(credPath);
|
|
71
|
-
if (saved.storage === 'google-drive' && saved.expiry_date) {
|
|
72
|
-
const keys = readJson(gdCredPath);
|
|
73
|
-
const key = keys.installed || keys.web;
|
|
74
|
-
const client = new google.auth.OAuth2(key.client_id, key.client_secret, key.redirect_uris[0]);
|
|
75
|
-
client.setCredentials(saved);
|
|
76
|
-
if (Date.now() >= saved.expiry_date - 300000) {
|
|
77
|
-
try {
|
|
78
|
-
const { credentials } = await client.refreshAccessToken();
|
|
79
|
-
writeJson(credPath, { storage: 'google-drive', ...credentials });
|
|
80
|
-
client.setCredentials(credentials);
|
|
81
|
-
} catch {
|
|
82
|
-
return authorizeGoogleDriveClient(root, true);
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
return client;
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
const client = await authenticate({ scopes: SCOPES, keyfilePath: gdCredPath });
|
|
90
|
-
if (client?.credentials && existsSync(path.dirname(credPath))) {
|
|
91
|
-
writeJson(credPath, { storage: 'google-drive', ...client.credentials });
|
|
92
|
-
}
|
|
93
|
-
return client;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
// --- AWS ---
|
|
97
|
-
|
|
98
|
-
const getS3 = (c) => new S3Client({
|
|
99
|
-
credentials: { accessKeyId: c.accessKey, secretAccessKey: c.secretKey },
|
|
100
|
-
region: c.awsRegion
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
async function runS3(creds, fn) {
|
|
104
|
-
try {
|
|
105
|
-
await fn(getS3(creds), creds.bucket);
|
|
106
|
-
} catch (err) {
|
|
107
|
-
if (['InvalidAccessKeyId', 'SignatureDoesNotMatch'].includes(err.name) || err.$metadata?.httpStatusCode === 403) {
|
|
108
|
-
log('✖ AWS authentication failed.');
|
|
109
|
-
vscode.window.showErrorMessage('AWS authentication failed. Check credentials in .dorky/credentials.json');
|
|
110
|
-
} else {
|
|
111
|
-
throw err;
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
async function runDrive(root, fn) {
|
|
117
|
-
let client = await authorizeGoogleDriveClient(root);
|
|
118
|
-
if (!client) return;
|
|
119
|
-
let drive = google.drive({ version: 'v3', auth: client });
|
|
120
|
-
try {
|
|
121
|
-
await fn(drive);
|
|
122
|
-
} catch (err) {
|
|
123
|
-
if (err.code === 401 || err.message?.includes('invalid_grant')) {
|
|
124
|
-
log('Drive auth failed. Re-authenticating...');
|
|
125
|
-
const credPath = path.join(root, CREDENTIALS_PATH);
|
|
126
|
-
if (existsSync(credPath)) unlinkSync(credPath);
|
|
127
|
-
client = await authorizeGoogleDriveClient(root, true);
|
|
128
|
-
if (!client) return;
|
|
129
|
-
drive = google.drive({ version: 'v3', auth: client });
|
|
130
|
-
await fn(drive);
|
|
131
|
-
} else {
|
|
132
|
-
throw err;
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
async function checkCredentials(root) {
|
|
138
|
-
if (existsSync(path.join(root, CREDENTIALS_PATH))) return true;
|
|
139
|
-
vscode.window.showErrorMessage('Credentials not found. Initialize the project first.');
|
|
140
|
-
return false;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
async function getFolderId(pathStr, drive, create = true) {
|
|
144
|
-
let parentId = 'root';
|
|
145
|
-
if (!pathStr || pathStr === '.') return parentId;
|
|
146
|
-
for (const folder of pathStr.split('/')) {
|
|
147
|
-
if (!folder) continue;
|
|
148
|
-
const res = await drive.files.list({
|
|
149
|
-
q: `name='${folder}' and mimeType='application/vnd.google-apps.folder' and '${parentId}' in parents and trashed=false`,
|
|
150
|
-
fields: 'files(id)'
|
|
151
|
-
});
|
|
152
|
-
if (res.data.files[0]) {
|
|
153
|
-
parentId = res.data.files[0].id;
|
|
154
|
-
} else if (create) {
|
|
155
|
-
parentId = (await drive.files.create({
|
|
156
|
-
requestBody: { name: folder, mimeType: 'application/vnd.google-apps.folder', parents: [parentId] },
|
|
157
|
-
fields: 'id'
|
|
158
|
-
})).data.id;
|
|
159
|
-
} else {
|
|
160
|
-
return null;
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
return parentId;
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
// --- Tree View ---
|
|
167
|
-
|
|
168
|
-
class DorkyItem extends vscode.TreeItem {
|
|
169
|
-
constructor(label, collapsibleState, contextValue, description, iconId) {
|
|
170
|
-
super(label, collapsibleState);
|
|
171
|
-
this.contextValue = contextValue;
|
|
172
|
-
this.description = description;
|
|
173
|
-
if (iconId) this.iconPath = new vscode.ThemeIcon(iconId);
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
class DorkyFilesProvider {
|
|
178
|
-
constructor() {
|
|
179
|
-
this._onDidChangeTreeData = new vscode.EventEmitter();
|
|
180
|
-
this.onDidChangeTreeData = this._onDidChangeTreeData.event;
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
refresh() {
|
|
184
|
-
this._onDidChangeTreeData.fire();
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
getTreeItem(element) {
|
|
188
|
-
return element;
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
getChildren(element) {
|
|
192
|
-
const root = getRoot();
|
|
193
|
-
if (!root || !existsSync(path.join(root, DORKY_DIR))) return [];
|
|
194
|
-
|
|
195
|
-
if (!element) {
|
|
196
|
-
const creds = readJson(path.join(root, CREDENTIALS_PATH));
|
|
197
|
-
const meta = readJson(path.join(root, METADATA_PATH));
|
|
198
|
-
const staged = Object.keys(meta['stage-1-files'] || {});
|
|
199
|
-
const uploaded = Object.keys(meta['uploaded-files'] || {});
|
|
200
|
-
|
|
201
|
-
const statusItem = new DorkyItem(
|
|
202
|
-
creds.storage || 'unknown',
|
|
203
|
-
vscode.TreeItemCollapsibleState.None,
|
|
204
|
-
'status',
|
|
205
|
-
'storage',
|
|
206
|
-
creds.storage === 'aws' ? 'cloud' : 'globe'
|
|
207
|
-
);
|
|
208
|
-
|
|
209
|
-
const stagedSection = new DorkyItem(
|
|
210
|
-
'Staged',
|
|
211
|
-
vscode.TreeItemCollapsibleState.Expanded,
|
|
212
|
-
'section-staged',
|
|
213
|
-
`${staged.length} file(s)`,
|
|
214
|
-
'git-add'
|
|
215
|
-
);
|
|
216
|
-
stagedSection.files = staged;
|
|
217
|
-
|
|
218
|
-
const uploadedSection = new DorkyItem(
|
|
219
|
-
'Uploaded',
|
|
220
|
-
vscode.TreeItemCollapsibleState.Expanded,
|
|
221
|
-
'section-uploaded',
|
|
222
|
-
`${uploaded.length} file(s)`,
|
|
223
|
-
'cloud'
|
|
224
|
-
);
|
|
225
|
-
uploadedSection.files = uploaded;
|
|
226
|
-
|
|
227
|
-
return [statusItem, stagedSection, uploadedSection];
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
if (element.contextValue === 'section-staged') {
|
|
231
|
-
return (element.files || []).map(f => {
|
|
232
|
-
const item = new DorkyItem(f, vscode.TreeItemCollapsibleState.None, 'stagedFile', undefined, 'file');
|
|
233
|
-
item.filePath = f;
|
|
234
|
-
return item;
|
|
235
|
-
});
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
if (element.contextValue === 'section-uploaded') {
|
|
239
|
-
return (element.files || []).map(f =>
|
|
240
|
-
new DorkyItem(f, vscode.TreeItemCollapsibleState.None, 'uploadedFile', undefined, 'file-symlink-file')
|
|
241
|
-
);
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
return [];
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
// --- Commands ---
|
|
249
|
-
|
|
250
|
-
async function initCommand(root) {
|
|
251
|
-
if (existsSync(path.join(root, DORKY_DIR))) {
|
|
252
|
-
vscode.window.showWarningMessage('Dorky is already initialized.');
|
|
253
|
-
return;
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
const storage = await vscode.window.showQuickPick(['aws', 'google-drive'], {
|
|
257
|
-
placeHolder: 'Select storage backend'
|
|
258
|
-
});
|
|
259
|
-
if (!storage) return;
|
|
260
|
-
|
|
261
|
-
let credentials = {};
|
|
262
|
-
if (storage === 'aws') {
|
|
263
|
-
const accessKey = await vscode.window.showInputBox({ prompt: 'AWS Access Key ID', ignoreFocusOut: true });
|
|
264
|
-
if (!accessKey) return;
|
|
265
|
-
const secretKey = await vscode.window.showInputBox({ prompt: 'AWS Secret Access Key', ignoreFocusOut: true, password: true });
|
|
266
|
-
if (!secretKey) return;
|
|
267
|
-
const region = await vscode.window.showInputBox({ prompt: 'AWS Region (e.g. us-east-1)', ignoreFocusOut: true });
|
|
268
|
-
if (!region) return;
|
|
269
|
-
const bucket = await vscode.window.showInputBox({ prompt: 'S3 Bucket Name', ignoreFocusOut: true });
|
|
270
|
-
if (!bucket) return;
|
|
271
|
-
credentials = { storage: 'aws', accessKey, secretKey, awsRegion: region, bucket };
|
|
272
|
-
} else {
|
|
273
|
-
const client = await authorizeGoogleDriveClient(root, true);
|
|
274
|
-
if (!client) return;
|
|
275
|
-
credentials = { storage: 'google-drive', ...client.credentials };
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
mkdirSync(path.join(root, DORKY_DIR));
|
|
279
|
-
writeJson(path.join(root, METADATA_PATH), { 'stage-1-files': {}, 'uploaded-files': {} });
|
|
280
|
-
writeFileSync(path.join(root, '.dorkyignore'), '');
|
|
281
|
-
writeJson(path.join(root, CREDENTIALS_PATH), credentials);
|
|
282
|
-
updateGitIgnore(root);
|
|
283
|
-
updateDorkyContext(root);
|
|
284
|
-
filesProvider.refresh();
|
|
285
|
-
log('✔ Dorky project initialized successfully.');
|
|
286
|
-
vscode.window.showInformationMessage('Dorky initialized successfully.');
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
async function addCommand(root) {
|
|
290
|
-
if (!checkDorkyProject(root)) return;
|
|
291
|
-
|
|
292
|
-
const uris = await vscode.window.showOpenDialog({
|
|
293
|
-
canSelectMany: true,
|
|
294
|
-
defaultUri: vscode.Uri.file(root),
|
|
295
|
-
openLabel: 'Stage Files'
|
|
296
|
-
});
|
|
297
|
-
if (!uris || uris.length === 0) return;
|
|
298
|
-
|
|
299
|
-
const metaPath = path.join(root, METADATA_PATH);
|
|
300
|
-
const meta = readJson(metaPath);
|
|
301
|
-
const added = [];
|
|
302
|
-
|
|
303
|
-
uris.forEach(uri => {
|
|
304
|
-
const absPath = uri.fsPath;
|
|
305
|
-
const relPath = path.relative(root, absPath);
|
|
306
|
-
if (!existsSync(absPath)) { log(`✖ File not found: ${relPath}`); return; }
|
|
307
|
-
const hash = md5(readFileSync(absPath));
|
|
308
|
-
if (meta['stage-1-files'][relPath]?.hash === hash) { log(`• ${relPath} (unchanged)`); return; }
|
|
309
|
-
meta['stage-1-files'][relPath] = { 'mime-type': mimeTypes.lookup(relPath) || 'application/octet-stream', hash };
|
|
310
|
-
added.push(relPath);
|
|
311
|
-
});
|
|
312
|
-
|
|
313
|
-
writeJson(metaPath, meta);
|
|
314
|
-
added.forEach(f => log(`✔ Staged: ${f}`));
|
|
315
|
-
if (added.length > 0) {
|
|
316
|
-
filesProvider.refresh();
|
|
317
|
-
vscode.window.showInformationMessage(`Staged ${added.length} file(s).`);
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
async function rmFileCommand(item) {
|
|
322
|
-
const root = getRoot();
|
|
323
|
-
if (!root || !checkDorkyProject(root)) return;
|
|
324
|
-
|
|
325
|
-
const metaPath = path.join(root, METADATA_PATH);
|
|
326
|
-
const meta = readJson(metaPath);
|
|
327
|
-
let filesToRemove = [];
|
|
328
|
-
|
|
329
|
-
if (item?.filePath) {
|
|
330
|
-
filesToRemove = [item.filePath];
|
|
331
|
-
} else {
|
|
332
|
-
const staged = Object.keys(meta['stage-1-files'] || {});
|
|
333
|
-
if (staged.length === 0) { vscode.window.showInformationMessage('No staged files to remove.'); return; }
|
|
334
|
-
const selected = await vscode.window.showQuickPick(staged, { canPickMany: true, placeHolder: 'Select files to unstage' });
|
|
335
|
-
if (!selected?.length) return;
|
|
336
|
-
filesToRemove = selected;
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
filesToRemove.forEach(f => {
|
|
340
|
-
delete meta['stage-1-files'][f];
|
|
341
|
-
log(`✔ Unstaged: ${f}`);
|
|
342
|
-
});
|
|
343
|
-
|
|
344
|
-
writeJson(metaPath, meta);
|
|
345
|
-
filesProvider.refresh();
|
|
346
|
-
vscode.window.showInformationMessage(`Unstaged ${filesToRemove.length} file(s).`);
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
async function pushCommand(root) {
|
|
350
|
-
if (!checkDorkyProject(root)) return;
|
|
351
|
-
if (!await checkCredentials(root)) return;
|
|
352
|
-
|
|
353
|
-
const metaPath = path.join(root, METADATA_PATH);
|
|
354
|
-
const meta = readJson(metaPath);
|
|
355
|
-
const filesToUpload = Object.keys(meta['stage-1-files'])
|
|
356
|
-
.filter(f => !meta['uploaded-files'][f] || meta['stage-1-files'][f].hash !== meta['uploaded-files'][f].hash)
|
|
357
|
-
.map(f => ({ name: f, ...meta['stage-1-files'][f] }));
|
|
358
|
-
const filesToDelete = Object.keys(meta['uploaded-files']).filter(f => !meta['stage-1-files'][f]);
|
|
359
|
-
|
|
360
|
-
if (filesToUpload.length === 0 && filesToDelete.length === 0) {
|
|
361
|
-
log('ℹ Nothing to push.');
|
|
362
|
-
vscode.window.showInformationMessage('Nothing to push.');
|
|
363
|
-
return;
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
const creds = readJson(path.join(root, CREDENTIALS_PATH));
|
|
367
|
-
const projectName = path.basename(root);
|
|
368
|
-
|
|
369
|
-
await vscode.window.withProgress({
|
|
370
|
-
location: vscode.ProgressLocation.Notification,
|
|
371
|
-
title: 'Dorky: Pushing files...',
|
|
372
|
-
cancellable: false
|
|
373
|
-
}, async () => {
|
|
374
|
-
if (creds.storage === 'aws') {
|
|
375
|
-
await runS3(creds, async (s3, bucket) => {
|
|
376
|
-
await Promise.all(filesToUpload.map(async f => {
|
|
377
|
-
const key = path.join(projectName, f.name).replace(/\\/g, '/');
|
|
378
|
-
await s3.send(new PutObjectCommand({ Bucket: bucket, Key: key, Body: readFileSync(path.join(root, f.name)) }));
|
|
379
|
-
log(`✔ Uploaded: ${f.name}`);
|
|
380
|
-
}));
|
|
381
|
-
await Promise.all(filesToDelete.map(async f => {
|
|
382
|
-
const key = path.join(projectName, f).replace(/\\/g, '/');
|
|
383
|
-
await s3.send(new DeleteObjectCommand({ Bucket: bucket, Key: key }));
|
|
384
|
-
log(`✔ Deleted remote: ${f}`);
|
|
385
|
-
}));
|
|
386
|
-
});
|
|
387
|
-
} else if (creds.storage === 'google-drive') {
|
|
388
|
-
await runDrive(root, async (drive) => {
|
|
389
|
-
for (const f of filesToUpload) {
|
|
390
|
-
const parentId = await getFolderId(path.dirname(path.join(projectName, f.name)), drive);
|
|
391
|
-
await drive.files.create({
|
|
392
|
-
requestBody: { name: path.basename(f.name), parents: [parentId] },
|
|
393
|
-
media: { mimeType: f['mime-type'], body: createReadStream(path.join(root, f.name)) }
|
|
394
|
-
});
|
|
395
|
-
log(`✔ Uploaded: ${f.name}`);
|
|
396
|
-
}
|
|
397
|
-
for (const f of filesToDelete) {
|
|
398
|
-
const parentId = await getFolderId(path.dirname(path.join(projectName, f)), drive, false);
|
|
399
|
-
if (parentId) {
|
|
400
|
-
const res = await drive.files.list({
|
|
401
|
-
q: `name='${path.basename(f)}' and '${parentId}' in parents and trashed=false`,
|
|
402
|
-
fields: 'files(id)'
|
|
403
|
-
});
|
|
404
|
-
if (res.data.files[0]) {
|
|
405
|
-
await drive.files.delete({ fileId: res.data.files[0].id });
|
|
406
|
-
log(`✔ Deleted remote: ${f}`);
|
|
407
|
-
}
|
|
408
|
-
}
|
|
409
|
-
}
|
|
410
|
-
});
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
meta['uploaded-files'] = { ...meta['stage-1-files'] };
|
|
414
|
-
writeJson(metaPath, meta);
|
|
415
|
-
filesProvider.refresh();
|
|
416
|
-
vscode.window.showInformationMessage('Push complete.');
|
|
417
|
-
});
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
async function pullCommand(root) {
|
|
421
|
-
if (!checkDorkyProject(root)) return;
|
|
422
|
-
if (!await checkCredentials(root)) return;
|
|
423
|
-
|
|
424
|
-
const metaPath = path.join(root, METADATA_PATH);
|
|
425
|
-
const meta = readJson(metaPath);
|
|
426
|
-
const files = meta['uploaded-files'];
|
|
427
|
-
const creds = readJson(path.join(root, CREDENTIALS_PATH));
|
|
428
|
-
const projectName = path.basename(root);
|
|
429
|
-
|
|
430
|
-
if (Object.keys(files).length === 0) {
|
|
431
|
-
log('ℹ Nothing to pull.');
|
|
432
|
-
vscode.window.showInformationMessage('Nothing to pull.');
|
|
433
|
-
return;
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
await vscode.window.withProgress({
|
|
437
|
-
location: vscode.ProgressLocation.Notification,
|
|
438
|
-
title: 'Dorky: Pulling files...',
|
|
439
|
-
cancellable: false
|
|
440
|
-
}, async () => {
|
|
441
|
-
if (creds.storage === 'aws') {
|
|
442
|
-
await runS3(creds, async (s3, bucket) => {
|
|
443
|
-
await Promise.all(Object.keys(files).map(async f => {
|
|
444
|
-
const key = path.join(projectName, f).replace(/\\/g, '/');
|
|
445
|
-
const { Body } = await s3.send(new GetObjectCommand({ Bucket: bucket, Key: key }));
|
|
446
|
-
const dir = path.dirname(path.join(root, f));
|
|
447
|
-
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
448
|
-
writeFileSync(path.join(root, f), await Body.transformToString());
|
|
449
|
-
log(`✔ Downloaded: ${f}`);
|
|
450
|
-
}));
|
|
451
|
-
});
|
|
452
|
-
} else if (creds.storage === 'google-drive') {
|
|
453
|
-
await runDrive(root, async (drive) => {
|
|
454
|
-
const fileList = Object.keys(files).map(k => ({ name: k, ...files[k] }));
|
|
455
|
-
await Promise.all(fileList.map(async f => {
|
|
456
|
-
const res = await drive.files.list({
|
|
457
|
-
q: `name='${path.basename(f.name)}' and mimeType!='application/vnd.google-apps.folder'`,
|
|
458
|
-
fields: 'files(id)'
|
|
459
|
-
});
|
|
460
|
-
if (!res.data.files[0]) { log(`✖ Missing remote file: ${f.name}`); return; }
|
|
461
|
-
const data = await drive.files.get({ fileId: res.data.files[0].id, alt: 'media' });
|
|
462
|
-
const dir = path.dirname(path.join(root, f.name));
|
|
463
|
-
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
464
|
-
writeFileSync(path.join(root, f.name), await data.data.text());
|
|
465
|
-
log(`✔ Downloaded: ${f.name}`);
|
|
466
|
-
}));
|
|
467
|
-
});
|
|
468
|
-
}
|
|
469
|
-
filesProvider.refresh();
|
|
470
|
-
vscode.window.showInformationMessage('Pull complete.');
|
|
471
|
-
});
|
|
472
|
-
}
|
|
473
|
-
|
|
474
|
-
async function listRemoteCommand(root) {
|
|
475
|
-
if (!checkDorkyProject(root)) return;
|
|
476
|
-
if (!await checkCredentials(root)) return;
|
|
477
|
-
|
|
478
|
-
const creds = readJson(path.join(root, CREDENTIALS_PATH));
|
|
479
|
-
const projectName = path.basename(root);
|
|
480
|
-
log('\n☁ Remote Files:');
|
|
481
|
-
|
|
482
|
-
if (creds.storage === 'aws') {
|
|
483
|
-
await runS3(creds, async (s3, bucket) => {
|
|
484
|
-
const data = await s3.send(new ListObjectsV2Command({ Bucket: bucket, Prefix: projectName + '/' }));
|
|
485
|
-
if (!data.Contents?.length) { log('ℹ No remote files found.'); return; }
|
|
486
|
-
data.Contents.forEach(o => log(` ${o.Key.replace(projectName + '/', '')}`));
|
|
487
|
-
});
|
|
488
|
-
} else {
|
|
489
|
-
await runDrive(root, async (drive) => {
|
|
490
|
-
const q = `name='${projectName}' and mimeType='application/vnd.google-apps.folder' and 'root' in parents and trashed=false`;
|
|
491
|
-
const { data: { files: [folder] } } = await drive.files.list({ q, fields: 'files(id)' });
|
|
492
|
-
if (!folder) { log('ℹ Remote folder not found.'); return; }
|
|
493
|
-
const walk = async (pid, p = '') => {
|
|
494
|
-
const { data: { files } } = await drive.files.list({
|
|
495
|
-
q: `'${pid}' in parents and trashed=false`,
|
|
496
|
-
fields: 'files(id, name, mimeType)'
|
|
497
|
-
});
|
|
498
|
-
for (const f of files) {
|
|
499
|
-
if (f.mimeType === 'application/vnd.google-apps.folder') await walk(f.id, path.join(p, f.name));
|
|
500
|
-
else log(` ${path.join(p, f.name)}`);
|
|
501
|
-
}
|
|
502
|
-
};
|
|
503
|
-
await walk(folder.id);
|
|
504
|
-
});
|
|
505
|
-
}
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
async function destroyCommand(root) {
|
|
509
|
-
if (!checkDorkyProject(root)) return;
|
|
510
|
-
|
|
511
|
-
const confirm = await vscode.window.showWarningMessage(
|
|
512
|
-
'This will delete all remote files and local dorky configuration. This cannot be undone.',
|
|
513
|
-
{ modal: true },
|
|
514
|
-
'Yes, Destroy'
|
|
515
|
-
);
|
|
516
|
-
if (confirm !== 'Yes, Destroy') return;
|
|
517
|
-
|
|
518
|
-
if (!await checkCredentials(root)) return;
|
|
519
|
-
|
|
520
|
-
const creds = readJson(path.join(root, CREDENTIALS_PATH));
|
|
521
|
-
const projectName = path.basename(root);
|
|
522
|
-
|
|
523
|
-
if (creds.storage === 'aws') {
|
|
524
|
-
await runS3(creds, async (s3, bucket) => {
|
|
525
|
-
const data = await s3.send(new ListObjectsV2Command({ Bucket: bucket, Prefix: projectName + '/' }));
|
|
526
|
-
if (data.Contents?.length > 0) {
|
|
527
|
-
await s3.send(new DeleteObjectsCommand({
|
|
528
|
-
Bucket: bucket,
|
|
529
|
-
Delete: { Objects: data.Contents.map(o => ({ Key: o.Key })) }
|
|
530
|
-
}));
|
|
531
|
-
log('✖ Remote files deleted.');
|
|
532
|
-
}
|
|
533
|
-
});
|
|
534
|
-
} else if (creds.storage === 'google-drive') {
|
|
535
|
-
await runDrive(root, async (drive) => {
|
|
536
|
-
const q = `name='${projectName}' and mimeType='application/vnd.google-apps.folder' and 'root' in parents and trashed=false`;
|
|
537
|
-
const { data: { files: [folder] } } = await drive.files.list({ q, fields: 'files(id)' });
|
|
538
|
-
if (folder) {
|
|
539
|
-
await drive.files.delete({ fileId: folder.id });
|
|
540
|
-
log('✖ Remote folder deleted.');
|
|
541
|
-
}
|
|
542
|
-
});
|
|
543
|
-
}
|
|
544
|
-
|
|
545
|
-
const dorkyDir = path.join(root, DORKY_DIR);
|
|
546
|
-
const dorkyIgnore = path.join(root, '.dorkyignore');
|
|
547
|
-
if (existsSync(dorkyDir)) rmSync(dorkyDir, { recursive: true, force: true });
|
|
548
|
-
if (existsSync(dorkyIgnore)) unlinkSync(dorkyIgnore);
|
|
549
|
-
|
|
550
|
-
updateDorkyContext(root);
|
|
551
|
-
filesProvider.refresh();
|
|
552
|
-
log('✖ Project destroyed.');
|
|
553
|
-
vscode.window.showInformationMessage('Dorky project destroyed.');
|
|
554
|
-
}
|
|
555
|
-
|
|
556
|
-
// --- Activate ---
|
|
557
|
-
|
|
558
|
-
function activate(context) {
|
|
559
|
-
outputChannel = vscode.window.createOutputChannel('Dorky');
|
|
560
|
-
filesProvider = new DorkyFilesProvider();
|
|
561
|
-
|
|
562
|
-
const root = getRoot();
|
|
563
|
-
if (root) updateDorkyContext(root);
|
|
564
|
-
|
|
565
|
-
vscode.window.registerTreeDataProvider('dorky.filesView', filesProvider);
|
|
566
|
-
|
|
567
|
-
const commands = [
|
|
568
|
-
['dorky-extension.init', async () => { const r = getRoot(); if (r) await initCommand(r); }],
|
|
569
|
-
['dorky-extension.add', async () => { const r = getRoot(); if (r) await addCommand(r); }],
|
|
570
|
-
['dorky-extension.rmFile', async (item) => await rmFileCommand(item)],
|
|
571
|
-
['dorky-extension.push', async () => { const r = getRoot(); if (r) await pushCommand(r); }],
|
|
572
|
-
['dorky-extension.pull', async () => { const r = getRoot(); if (r) await pullCommand(r); }],
|
|
573
|
-
['dorky-extension.listRemote', async () => { const r = getRoot(); if (r) await listRemoteCommand(r); }],
|
|
574
|
-
['dorky-extension.refresh', () => { const r = getRoot(); if (r) { updateDorkyContext(r); filesProvider.refresh(); } }],
|
|
575
|
-
['dorky-extension.destroy', async () => { const r = getRoot(); if (r) await destroyCommand(r); }],
|
|
576
|
-
];
|
|
577
|
-
|
|
578
|
-
for (const [id, handler] of commands) {
|
|
579
|
-
context.subscriptions.push(vscode.commands.registerCommand(id, handler));
|
|
580
|
-
}
|
|
581
|
-
|
|
582
|
-
context.subscriptions.push(outputChannel);
|
|
583
|
-
}
|
|
584
|
-
|
|
585
|
-
function deactivate() {}
|
|
586
|
-
|
|
587
|
-
module.exports = { activate, deactivate };
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
<?xml version="1.0" encoding="utf-8"?>
|
|
2
|
-
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
|
3
|
-
<svg fill="#000000" width="800px" height="800px" viewBox="0 0 32 32" version="1.1" xmlns="http://www.w3.org/2000/svg">
|
|
4
|
-
<title>lion</title>
|
|
5
|
-
<path d="M24.162 12.116l0-0c-0.414-1.43-1.488-2.364-3.546-3.307 0.733-3.675-1.030-5.775-4.376-7.727l0.268 2.698c-2.369-1.583-5.763-3.216-8.804-3.074-1.014 0.047-2.060 0.206-3.159 0.527l3.723 3.911c-2.034 0.012-5.435 0.296-7.315 1.693l2.012 3.271-2.294 1.191v4.224l2.479 1.4-2.479 2.135v2.882c1.78-0.145 4.183 0.115 6.33-0.603-1.518 1.78-2.002 4.331-3.811 6.558 2.419-0.092 4.831-0.406 7.408-1.805l-0.602 4.776c4.108-1.53 8.165-5.386 7.817-8.677l0.058-0.275c0.239 0.142 0.491 0.269 0.755 0.379l7.263 2.574c0.397-0.859 0.828-1.723 1.284-2.586-0.286 0.111-0.597 0.173-0.922 0.173-1.405 0-2.544-1.139-2.544-2.544s1.139-2.544 2.544-2.544c1.252 0 2.291 0.904 2.504 2.094 0.758-1.281 1.561-2.552 2.391-3.8l-6.986-3.544z"></path>
|
|
6
|
-
</svg>
|