@gadgetinc/ggt 0.2.1 → 0.2.2
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/README.md +6 -6
- package/bin/dev.js +2 -2
- package/bin/run.js +1 -1
- package/lib/commands/help.js +2 -2
- package/lib/commands/help.js.map +1 -1
- package/lib/commands/list.js +3 -3
- package/lib/commands/list.js.map +1 -1
- package/lib/commands/login.js +2 -2
- package/lib/commands/login.js.map +1 -1
- package/lib/commands/logout.js +2 -2
- package/lib/commands/logout.js.map +1 -1
- package/lib/commands/sync.js +233 -88
- package/lib/commands/sync.js.map +1 -1
- package/lib/commands/whoami.js +2 -2
- package/lib/commands/whoami.js.map +1 -1
- package/lib/{utils → services}/base-command.js +23 -8
- package/lib/services/base-command.js.map +1 -0
- package/lib/{utils → services}/client.js +70 -21
- package/lib/services/client.js.map +1 -0
- package/lib/{utils → services}/context.js +42 -27
- package/lib/services/context.js.map +1 -0
- package/lib/{utils → services}/errors.js +8 -8
- package/lib/services/errors.js.map +1 -0
- package/lib/{utils → services}/flags.js +4 -3
- package/lib/services/flags.js.map +1 -0
- package/lib/{utils → services}/fs-utils.js +2 -2
- package/lib/services/fs-utils.js.map +1 -0
- package/lib/{utils → services}/help.js +1 -1
- package/lib/services/help.js.map +1 -0
- package/lib/services/promise.js.map +1 -0
- package/lib/{utils → services}/sleep.js +6 -2
- package/lib/services/sleep.js.map +1 -0
- package/npm-shrinkwrap.json +2188 -2630
- package/oclif.manifest.json +47 -7
- package/package.json +29 -28
- package/lib/utils/base-command.js.map +0 -1
- package/lib/utils/client.js.map +0 -1
- package/lib/utils/context.js.map +0 -1
- package/lib/utils/errors.js.map +0 -1
- package/lib/utils/flags.js.map +0 -1
- package/lib/utils/fs-utils.js.map +0 -1
- package/lib/utils/help.js.map +0 -1
- package/lib/utils/promise.js.map +0 -1
- package/lib/utils/sleep.js.map +0 -1
- /package/lib/{utils → services}/promise.js +0 -0
package/lib/commands/sync.js
CHANGED
|
@@ -5,7 +5,6 @@ import { _ as _define_property } from "@swc/helpers/_/_define_property";
|
|
|
5
5
|
import { Args, Flags } from "@oclif/core";
|
|
6
6
|
import assert from "assert";
|
|
7
7
|
import chalkTemplate from "chalk-template";
|
|
8
|
-
import { default as FSWatcher } from "watcher";
|
|
9
8
|
import { format } from "date-fns";
|
|
10
9
|
import { execa } from "execa";
|
|
11
10
|
import fs from "fs-extra";
|
|
@@ -17,24 +16,17 @@ import PQueue from "p-queue";
|
|
|
17
16
|
import path from "path";
|
|
18
17
|
import pluralize from "pluralize";
|
|
19
18
|
import { dedent } from "ts-dedent";
|
|
19
|
+
import FSWatcher from "watcher";
|
|
20
20
|
import which from "which";
|
|
21
21
|
import { FileSyncEncoding } from "../__generated__/graphql.js";
|
|
22
|
-
import { BaseCommand } from "../
|
|
23
|
-
import { Client } from "../
|
|
24
|
-
import { context } from "../
|
|
25
|
-
import { InvalidSyncAppFlagError, InvalidSyncFileError, YarnNotFoundError } from "../
|
|
26
|
-
import { app } from "../
|
|
27
|
-
import { FSIgnorer, ignoreEnoent, isEmptyDir, walkDir } from "../
|
|
28
|
-
import { PromiseSignal } from "../
|
|
22
|
+
import { BaseCommand } from "../services/base-command.js";
|
|
23
|
+
import { Client } from "../services/client.js";
|
|
24
|
+
import { context } from "../services/context.js";
|
|
25
|
+
import { InvalidSyncAppFlagError, InvalidSyncFileError, YarnNotFoundError } from "../services/errors.js";
|
|
26
|
+
import { app } from "../services/flags.js";
|
|
27
|
+
import { FSIgnorer, ignoreEnoent, isEmptyDir, walkDir } from "../services/fs-utils.js";
|
|
28
|
+
import { PromiseSignal } from "../services/promise.js";
|
|
29
29
|
class Sync extends BaseCommand {
|
|
30
|
-
static fileStats(path) {
|
|
31
|
-
try {
|
|
32
|
-
return fs.statSync(path);
|
|
33
|
-
} catch (error) {
|
|
34
|
-
ignoreEnoent(error);
|
|
35
|
-
return undefined;
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
30
|
/**
|
|
39
31
|
* Turns an absolute filepath into a relative one from {@linkcode dir}.
|
|
40
32
|
*/ relative(to) {
|
|
@@ -77,8 +69,8 @@ class Sync extends BaseCommand {
|
|
|
77
69
|
* @param options.limit The maximum number of lines to print. Defaults to 10. If debug is enabled, this is ignored.
|
|
78
70
|
*/ logPaths(prefix, changed, deleted, { limit = 10 } = {}) {
|
|
79
71
|
const lines = _.sortBy([
|
|
80
|
-
...
|
|
81
|
-
...
|
|
72
|
+
..._.map(changed, (normalizedPath)=>chalkTemplate`{green ${prefix}} ${normalizedPath} {gray (changed)}`),
|
|
73
|
+
..._.map(deleted, (normalizedPath)=>chalkTemplate`{red ${prefix}} ${normalizedPath} {gray (deleted)}`)
|
|
82
74
|
], (line)=>line.slice(line.indexOf(" ") + 1));
|
|
83
75
|
let logged = 0;
|
|
84
76
|
for (const line of lines){
|
|
@@ -100,16 +92,21 @@ class Sync extends BaseCommand {
|
|
|
100
92
|
* - Prompts the user how to resolve conflicts if the local filesystem has changed since the last sync.
|
|
101
93
|
*/ async init() {
|
|
102
94
|
await super.init();
|
|
103
|
-
|
|
104
|
-
this.dir = this.config.windows && this.args["directory"].startsWith("~/") ? path.join(this.config.home, this.args["directory"].slice(2)) : path.resolve(this.args["directory"]);
|
|
95
|
+
this.dir = this.config.windows && _.startsWith(this.args.directory, "~/") ? path.join(this.config.home, this.args.directory.slice(2)) : path.resolve(this.args.directory);
|
|
105
96
|
const getApp = async ()=>{
|
|
106
|
-
if (this.flags.app)
|
|
107
|
-
|
|
97
|
+
if (this.flags.app) {
|
|
98
|
+
return this.flags.app;
|
|
99
|
+
}
|
|
100
|
+
// this.state can be undefined if the user is running `ggt sync` for the first time
|
|
101
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
102
|
+
if (this.state?.app) {
|
|
103
|
+
return this.state.app;
|
|
104
|
+
}
|
|
108
105
|
const selected = await inquirer.prompt({
|
|
109
106
|
type: "list",
|
|
110
107
|
name: "app",
|
|
111
108
|
message: "Please select the app to sync to.",
|
|
112
|
-
choices: await context.getAvailableApps().then((apps)=>
|
|
109
|
+
choices: await context.getAvailableApps().then((apps)=>_.map(apps, "slug"))
|
|
113
110
|
});
|
|
114
111
|
return selected.app;
|
|
115
112
|
};
|
|
@@ -142,7 +139,6 @@ class Sync extends BaseCommand {
|
|
|
142
139
|
".gadget",
|
|
143
140
|
".git"
|
|
144
141
|
]);
|
|
145
|
-
this.debug("starting");
|
|
146
142
|
if (!which.sync("yarn", {
|
|
147
143
|
nothrow: true
|
|
148
144
|
})) {
|
|
@@ -167,7 +163,7 @@ class Sync extends BaseCommand {
|
|
|
167
163
|
files.delete("/");
|
|
168
164
|
return files;
|
|
169
165
|
};
|
|
170
|
-
|
|
166
|
+
let changedFiles = await getChangedFiles();
|
|
171
167
|
const hasLocalChanges = changedFiles.size > 0;
|
|
172
168
|
if (hasLocalChanges) {
|
|
173
169
|
this.log("Local files have changed since you last synced");
|
|
@@ -176,11 +172,16 @@ class Sync extends BaseCommand {
|
|
|
176
172
|
});
|
|
177
173
|
this.log();
|
|
178
174
|
}
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
175
|
+
context.addBreadcrumb({
|
|
176
|
+
category: "sync",
|
|
177
|
+
message: "Initializing",
|
|
178
|
+
data: {
|
|
179
|
+
state: this.state,
|
|
180
|
+
remoteFilesVersion,
|
|
181
|
+
hasRemoteChanges,
|
|
182
|
+
hasLocalChanges,
|
|
183
|
+
changed: Array.from(changedFiles.keys())
|
|
184
|
+
}
|
|
184
185
|
});
|
|
185
186
|
let action;
|
|
186
187
|
if (hasLocalChanges) {
|
|
@@ -195,11 +196,20 @@ class Sync extends BaseCommand {
|
|
|
195
196
|
message: hasRemoteChanges ? "Remote files have also changed. How would you like to proceed?" : "How would you like to proceed?"
|
|
196
197
|
}));
|
|
197
198
|
}
|
|
199
|
+
// get all the changed files again in case more changed
|
|
200
|
+
changedFiles = await getChangedFiles();
|
|
198
201
|
switch(action){
|
|
199
202
|
case Action.MERGE:
|
|
200
203
|
{
|
|
201
|
-
|
|
202
|
-
|
|
204
|
+
context.addBreadcrumb({
|
|
205
|
+
category: "sync",
|
|
206
|
+
message: "Merging local changes",
|
|
207
|
+
data: {
|
|
208
|
+
state: this.state,
|
|
209
|
+
remoteFilesVersion,
|
|
210
|
+
changed: Array.from(changedFiles.keys())
|
|
211
|
+
}
|
|
212
|
+
});
|
|
203
213
|
// We purposefully don't set the returned remoteFilesVersion here because we haven't received the remote changes
|
|
204
214
|
// yet. This will cause us to receive the local files that we just published + the remote files that were
|
|
205
215
|
// changed since the last sync.
|
|
@@ -208,7 +218,7 @@ class Sync extends BaseCommand {
|
|
|
208
218
|
variables: {
|
|
209
219
|
input: {
|
|
210
220
|
expectedRemoteFilesVersion: remoteFilesVersion,
|
|
211
|
-
changed: await pMap(
|
|
221
|
+
changed: await pMap(changedFiles, async ([normalizedPath, stats])=>{
|
|
212
222
|
if (stats.mtime.getTime() > this.state.mtime) {
|
|
213
223
|
this.state.mtime = stats.mtime.getTime();
|
|
214
224
|
}
|
|
@@ -227,6 +237,15 @@ class Sync extends BaseCommand {
|
|
|
227
237
|
}
|
|
228
238
|
case Action.RESET:
|
|
229
239
|
{
|
|
240
|
+
context.addBreadcrumb({
|
|
241
|
+
category: "sync",
|
|
242
|
+
message: "Resetting local changes",
|
|
243
|
+
data: {
|
|
244
|
+
state: this.state,
|
|
245
|
+
remoteFilesVersion,
|
|
246
|
+
changed: Array.from(changedFiles.keys())
|
|
247
|
+
}
|
|
248
|
+
});
|
|
230
249
|
// delete all the local files that have changed since the last sync and set the files version to 0 so we receive
|
|
231
250
|
// all the remote files again, including any files that we just deleted that still exist
|
|
232
251
|
await pMap(changedFiles.keys(), (normalizedPath)=>this.softDelete(normalizedPath));
|
|
@@ -238,27 +257,32 @@ class Sync extends BaseCommand {
|
|
|
238
257
|
process.exit(0);
|
|
239
258
|
}
|
|
240
259
|
}
|
|
241
|
-
|
|
260
|
+
context.addBreadcrumb({
|
|
261
|
+
category: "sync",
|
|
262
|
+
message: "Initialized",
|
|
263
|
+
data: {
|
|
264
|
+
state: this.state
|
|
265
|
+
}
|
|
266
|
+
});
|
|
242
267
|
}
|
|
243
268
|
/**
|
|
244
269
|
* Runs the sync process until it is stopped or an error occurs.
|
|
245
270
|
*/ async run() {
|
|
246
271
|
let error;
|
|
247
272
|
const stopped = new PromiseSignal();
|
|
248
|
-
this.watcher = new FSWatcher(this.dir, {
|
|
249
|
-
ignore: (filepath)=>{
|
|
250
|
-
return this.ignorer.ignores(filepath);
|
|
251
|
-
},
|
|
252
|
-
// don't emit an event for every watched file on boot
|
|
253
|
-
ignoreInitial: true,
|
|
254
|
-
renameDetection: true,
|
|
255
|
-
recursive: true
|
|
256
|
-
});
|
|
257
273
|
this.stop = async (e)=>{
|
|
258
274
|
if (this.status != SyncStatus.RUNNING) return;
|
|
259
|
-
error = e;
|
|
260
|
-
this.debug("stopping");
|
|
261
275
|
this.status = SyncStatus.STOPPING;
|
|
276
|
+
error = e;
|
|
277
|
+
context.addBreadcrumb({
|
|
278
|
+
category: "sync",
|
|
279
|
+
message: "Stopping",
|
|
280
|
+
level: error ? "error" : undefined,
|
|
281
|
+
data: {
|
|
282
|
+
state: this.state,
|
|
283
|
+
error
|
|
284
|
+
}
|
|
285
|
+
});
|
|
262
286
|
try {
|
|
263
287
|
unsubscribe();
|
|
264
288
|
this.watcher.removeAllListeners();
|
|
@@ -270,9 +294,15 @@ class Sync extends BaseCommand {
|
|
|
270
294
|
this.watcher.close(),
|
|
271
295
|
this.client.dispose()
|
|
272
296
|
]);
|
|
273
|
-
this.debug("stopped");
|
|
274
297
|
this.status = SyncStatus.STOPPED;
|
|
275
298
|
stopped.resolve();
|
|
299
|
+
context.addBreadcrumb({
|
|
300
|
+
category: "sync",
|
|
301
|
+
message: "Stopped",
|
|
302
|
+
data: {
|
|
303
|
+
state: this.state
|
|
304
|
+
}
|
|
305
|
+
});
|
|
276
306
|
}
|
|
277
307
|
};
|
|
278
308
|
for (const signal of [
|
|
@@ -301,22 +331,49 @@ class Sync extends BaseCommand {
|
|
|
301
331
|
}, {
|
|
302
332
|
error: (error)=>void this.stop(error),
|
|
303
333
|
next: ({ remoteFileSyncEvents })=>{
|
|
334
|
+
context.addBreadcrumb({
|
|
335
|
+
category: "sync",
|
|
336
|
+
message: "Received file sync events",
|
|
337
|
+
data: {
|
|
338
|
+
state: this.state,
|
|
339
|
+
remoteFilesVersion: remoteFileSyncEvents.remoteFilesVersion,
|
|
340
|
+
changed: _.map(remoteFileSyncEvents.changed, "path"),
|
|
341
|
+
deleted: _.map(remoteFileSyncEvents.deleted, "path")
|
|
342
|
+
}
|
|
343
|
+
});
|
|
304
344
|
const remoteFilesVersion = remoteFileSyncEvents.remoteFilesVersion;
|
|
305
345
|
// we always ignore .gadget/ files so that we don't publish them (they're managed by gadget), but we still want to receive them
|
|
306
|
-
const filter = (event)=>event.path
|
|
307
|
-
const changed = remoteFileSyncEvents.changed
|
|
308
|
-
const deleted = remoteFileSyncEvents.deleted
|
|
346
|
+
const filter = (event)=>_.startsWith(event.path, ".gadget/") || !this.ignorer.ignores(event.path);
|
|
347
|
+
const changed = _.filter(remoteFileSyncEvents.changed, filter);
|
|
348
|
+
const deleted = _.filter(remoteFileSyncEvents.deleted, filter);
|
|
309
349
|
this._enqueue(async ()=>{
|
|
350
|
+
context.addBreadcrumb({
|
|
351
|
+
category: "sync",
|
|
352
|
+
message: "Processing received file sync events",
|
|
353
|
+
data: {
|
|
354
|
+
state: this.state,
|
|
355
|
+
remoteFilesVersion: remoteFileSyncEvents.remoteFilesVersion,
|
|
356
|
+
changed: _.map(remoteFileSyncEvents.changed, "path"),
|
|
357
|
+
deleted: _.map(remoteFileSyncEvents.deleted, "path")
|
|
358
|
+
}
|
|
359
|
+
});
|
|
310
360
|
if (!changed.length && !deleted.length) {
|
|
311
361
|
if (BigInt(remoteFilesVersion) > this.state.filesVersion) {
|
|
312
362
|
// we still need to update filesVersion, otherwise our expectedFilesVersion will be behind the next time we publish
|
|
313
|
-
this.debug("updated local files version from %s to %s", this.state.filesVersion, remoteFilesVersion);
|
|
314
363
|
this.state.filesVersion = remoteFilesVersion;
|
|
364
|
+
context.addBreadcrumb({
|
|
365
|
+
category: "sync",
|
|
366
|
+
message: "Received empty file sync events",
|
|
367
|
+
data: {
|
|
368
|
+
state: this.state,
|
|
369
|
+
remoteFilesVersion: remoteFileSyncEvents.remoteFilesVersion
|
|
370
|
+
}
|
|
371
|
+
});
|
|
315
372
|
}
|
|
316
373
|
return;
|
|
317
374
|
}
|
|
318
375
|
this.log(chalkTemplate`Received {gray ${format(new Date(), "pp")}}`);
|
|
319
|
-
this.logPaths("←",
|
|
376
|
+
this.logPaths("←", _.map(changed, "path"), _.map(deleted, "path"));
|
|
320
377
|
// we need to processed deleted files first as we may delete an empty directory after a file has been put
|
|
321
378
|
// into it. if processed out of order the new file will be deleted as well
|
|
322
379
|
await pMap(deleted, async (file)=>{
|
|
@@ -326,14 +383,16 @@ class Sync extends BaseCommand {
|
|
|
326
383
|
await pMap(changed, async (file)=>{
|
|
327
384
|
this.recentRemoteChanges.add(file.path);
|
|
328
385
|
const absolutePath = this.absolute(file.path);
|
|
329
|
-
if (file.path
|
|
386
|
+
if (_.endsWith(file.path, "/")) {
|
|
330
387
|
await fs.ensureDir(absolutePath, {
|
|
331
388
|
mode: 0o755
|
|
332
389
|
});
|
|
333
390
|
return;
|
|
334
391
|
}
|
|
335
|
-
// we need to add
|
|
336
|
-
|
|
392
|
+
// we need to add all parent directories to recentRemoteChanges so that we don't re-publish them
|
|
393
|
+
for (const dir of _.split(path.dirname(file.path), "/")){
|
|
394
|
+
this.recentRemoteChanges.add(dir + "/");
|
|
395
|
+
}
|
|
337
396
|
await fs.ensureDir(path.dirname(absolutePath), {
|
|
338
397
|
mode: 0o755
|
|
339
398
|
});
|
|
@@ -345,16 +404,22 @@ class Sync extends BaseCommand {
|
|
|
345
404
|
"install"
|
|
346
405
|
], {
|
|
347
406
|
cwd: this.dir
|
|
348
|
-
}).catch((
|
|
349
|
-
|
|
350
|
-
|
|
407
|
+
}).catch((error)=>{
|
|
408
|
+
context.addBreadcrumb({
|
|
409
|
+
category: "sync",
|
|
410
|
+
message: "Yarn install failed",
|
|
411
|
+
level: "error",
|
|
412
|
+
data: {
|
|
413
|
+
state: this.state,
|
|
414
|
+
error
|
|
415
|
+
}
|
|
416
|
+
});
|
|
351
417
|
});
|
|
352
418
|
}
|
|
353
419
|
if (absolutePath == this.ignorer.filepath) {
|
|
354
420
|
this.ignorer.reload();
|
|
355
421
|
}
|
|
356
422
|
});
|
|
357
|
-
this.debug("updated local files version from %s to %s", this.state.filesVersion, remoteFilesVersion);
|
|
358
423
|
this.state.filesVersion = remoteFilesVersion;
|
|
359
424
|
// always remove the root directory from recentRemoteChanges
|
|
360
425
|
this.recentRemoteChanges.delete("./");
|
|
@@ -364,6 +429,17 @@ class Sync extends BaseCommand {
|
|
|
364
429
|
this.recentRemoteChanges.delete(filepath);
|
|
365
430
|
}
|
|
366
431
|
}
|
|
432
|
+
context.addBreadcrumb({
|
|
433
|
+
category: "sync",
|
|
434
|
+
message: "Processed received file sync events",
|
|
435
|
+
data: {
|
|
436
|
+
state: this.state,
|
|
437
|
+
remoteFilesVersion: remoteFileSyncEvents.remoteFilesVersion,
|
|
438
|
+
changed: _.map(remoteFileSyncEvents.changed, "path"),
|
|
439
|
+
deleted: _.map(remoteFileSyncEvents.deleted, "path"),
|
|
440
|
+
recentRemoteChanges: Array.from(this.recentRemoteChanges.keys())
|
|
441
|
+
}
|
|
442
|
+
});
|
|
367
443
|
});
|
|
368
444
|
}
|
|
369
445
|
});
|
|
@@ -372,6 +448,14 @@ class Sync extends BaseCommand {
|
|
|
372
448
|
const localFiles = new Map(localFilesBuffer.entries());
|
|
373
449
|
localFilesBuffer.clear();
|
|
374
450
|
this._enqueue(async ()=>{
|
|
451
|
+
context.addBreadcrumb({
|
|
452
|
+
category: "sync",
|
|
453
|
+
message: "Publishing file sync events",
|
|
454
|
+
data: {
|
|
455
|
+
state: this.state,
|
|
456
|
+
localFiles: Array.from(localFiles.keys())
|
|
457
|
+
}
|
|
458
|
+
});
|
|
375
459
|
const changed = [];
|
|
376
460
|
const deleted = [];
|
|
377
461
|
await pMap(localFiles, async ([normalizedPath, file])=>{
|
|
@@ -381,16 +465,13 @@ class Sync extends BaseCommand {
|
|
|
381
465
|
});
|
|
382
466
|
return;
|
|
383
467
|
}
|
|
384
|
-
const isRename = "oldPath" in file;
|
|
385
468
|
try {
|
|
386
469
|
changed.push({
|
|
387
470
|
path: normalizedPath,
|
|
471
|
+
oldPath: "oldPath" in file ? file.oldPath : undefined,
|
|
388
472
|
mode: file.mode,
|
|
389
|
-
content: file.isDirectory ? "" : await fs.readFile(this.absolute(normalizedPath),
|
|
390
|
-
encoding: FileSyncEncoding.Base64
|
|
391
|
-
...isRename ? {
|
|
392
|
-
oldPath: file.oldPath
|
|
393
|
-
} : undefined
|
|
473
|
+
content: file.isDirectory ? "" : await fs.readFile(this.absolute(normalizedPath), FileSyncEncoding.Base64),
|
|
474
|
+
encoding: FileSyncEncoding.Base64
|
|
394
475
|
});
|
|
395
476
|
} catch (error) {
|
|
396
477
|
// A file could have been changed and then deleted before we process the change event, so the readFile
|
|
@@ -401,7 +482,7 @@ class Sync extends BaseCommand {
|
|
|
401
482
|
if (!changed.length && !deleted.length) {
|
|
402
483
|
return;
|
|
403
484
|
}
|
|
404
|
-
const
|
|
485
|
+
const { publishFileSyncEvents } = await this.client.queryUnwrap({
|
|
405
486
|
query: PUBLISH_FILE_SYNC_EVENTS_MUTATION,
|
|
406
487
|
variables: {
|
|
407
488
|
input: {
|
|
@@ -411,32 +492,52 @@ class Sync extends BaseCommand {
|
|
|
411
492
|
}
|
|
412
493
|
}
|
|
413
494
|
});
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
495
|
+
context.addBreadcrumb({
|
|
496
|
+
category: "sync",
|
|
497
|
+
message: "Published file sync events",
|
|
498
|
+
data: {
|
|
499
|
+
state: this.state,
|
|
500
|
+
remoteFilesVersion: publishFileSyncEvents.remoteFilesVersion,
|
|
501
|
+
changed: _.map(changed, "path"),
|
|
502
|
+
deleted: _.map(deleted, "path")
|
|
503
|
+
}
|
|
504
|
+
});
|
|
505
|
+
if (BigInt(publishFileSyncEvents.remoteFilesVersion) > this.state.filesVersion) {
|
|
506
|
+
this.state.filesVersion = publishFileSyncEvents.remoteFilesVersion;
|
|
421
507
|
}
|
|
508
|
+
this.log(chalkTemplate`Sent {gray ${format(new Date(), "pp")}}`);
|
|
509
|
+
this.logPaths("→", _.map(changed, "path"), _.map(deleted, "path"));
|
|
422
510
|
});
|
|
423
511
|
}, this.flags["file-push-delay"]);
|
|
424
|
-
this.
|
|
425
|
-
|
|
512
|
+
this.watcher = new FSWatcher(this.dir, {
|
|
513
|
+
// paths that we never want to publish
|
|
514
|
+
ignore: /(\.gadget|\.git|node_modules)/,
|
|
515
|
+
// don't emit an event for every watched file on boot
|
|
516
|
+
ignoreInitial: true,
|
|
517
|
+
renameDetection: true,
|
|
518
|
+
recursive: true,
|
|
519
|
+
debounce: this.flags["file-watch-debounce"],
|
|
520
|
+
pollingInterval: this.flags["file-watch-poll-interval"],
|
|
521
|
+
pollingTimeout: this.flags["file-watch-poll-timeout"],
|
|
522
|
+
renameTimeout: this.flags["file-watch-rename-timeout"]
|
|
523
|
+
});
|
|
524
|
+
this.watcher.once("error", (error)=>void this.stop(error));
|
|
525
|
+
this.watcher.on("all", (event, absolutePath, renamedPath)=>{
|
|
526
|
+
const filepath = event === "rename" || event === "renameDir" ? renamedPath : absolutePath;
|
|
426
527
|
const isDirectory = event === "renameDir" || event === "addDir" || event === "unlinkDir";
|
|
427
|
-
const normalizedPath = this.normalize(
|
|
428
|
-
|
|
429
|
-
// this shouldn't ever be the case using watcher since it doesn't support symlinks but we'll keep it here just in case we ever switch to another lib that does support it
|
|
430
|
-
if (stats?.isSymbolicLink?.()) {
|
|
431
|
-
this.debug("skipping event caused by symlink %s", normalizedPath);
|
|
432
|
-
return;
|
|
433
|
-
}
|
|
434
|
-
if (filePath == this.ignorer.filepath) {
|
|
528
|
+
const normalizedPath = this.normalize(filepath, isDirectory);
|
|
529
|
+
if (filepath == this.ignorer.filepath) {
|
|
435
530
|
this.ignorer.reload();
|
|
436
|
-
} else if (this.ignorer.ignores(
|
|
531
|
+
} else if (this.ignorer.ignores(filepath)) {
|
|
437
532
|
this.debug("skipping event caused by ignored file %s", normalizedPath);
|
|
438
533
|
return;
|
|
439
534
|
}
|
|
535
|
+
let stats;
|
|
536
|
+
try {
|
|
537
|
+
stats = fs.statSync(filepath);
|
|
538
|
+
} catch (error) {
|
|
539
|
+
ignoreEnoent(error);
|
|
540
|
+
}
|
|
440
541
|
// we only update the mtime if the file is not ignored, because if we restart and the mtime is set to an ignored
|
|
441
542
|
// file, then it could be greater than the mtime of all non ignored files and we'll think that local files have
|
|
442
543
|
// changed when only an ignored one has
|
|
@@ -447,7 +548,7 @@ class Sync extends BaseCommand {
|
|
|
447
548
|
this.debug("skipping event caused by recent write %s", normalizedPath);
|
|
448
549
|
return;
|
|
449
550
|
}
|
|
450
|
-
this.debug("
|
|
551
|
+
this.debug("%s %s", event, normalizedPath);
|
|
451
552
|
switch(event){
|
|
452
553
|
case "add":
|
|
453
554
|
case "change":
|
|
@@ -515,10 +616,6 @@ class Sync extends BaseCommand {
|
|
|
515
616
|
this.log("Goodbye!");
|
|
516
617
|
}
|
|
517
618
|
}
|
|
518
|
-
on(eventName, listener) {
|
|
519
|
-
this.watcher.on(eventName, listener);
|
|
520
|
-
return this;
|
|
521
|
-
}
|
|
522
619
|
/**
|
|
523
620
|
* Enqueues a function that handles file-sync events onto the {@linkcode queue}.
|
|
524
621
|
*
|
|
@@ -612,6 +709,35 @@ _define_property(Sync, "flags", {
|
|
|
612
709
|
helpValue: "ms",
|
|
613
710
|
default: 100,
|
|
614
711
|
hidden: true
|
|
712
|
+
}),
|
|
713
|
+
// The following flags are passed to FSWatcher (https://github.com/fabiospampinato/watcher)
|
|
714
|
+
"file-watch-debounce": Flags.integer({
|
|
715
|
+
summary: "Amount of milliseconds to debounce file changed events",
|
|
716
|
+
helpGroup: "file",
|
|
717
|
+
helpValue: "ms",
|
|
718
|
+
default: 300,
|
|
719
|
+
hidden: true
|
|
720
|
+
}),
|
|
721
|
+
"file-watch-poll-interval": Flags.integer({
|
|
722
|
+
summary: "Polling is used as a last resort measure when watching non-existent paths inside non-existent directories, this controls how often polling is performed, in milliseconds. You can set it to a lower value to make the app detect events much more quickly, but don't set it too low if you are watching many paths that require polling as polling is expensive.",
|
|
723
|
+
helpGroup: "file",
|
|
724
|
+
helpValue: "ms",
|
|
725
|
+
default: 3_000,
|
|
726
|
+
hidden: true
|
|
727
|
+
}),
|
|
728
|
+
"file-watch-poll-timeout": Flags.integer({
|
|
729
|
+
summary: "Sometimes polling will fail, for example if there are too many file descriptors currently open, usually eventually polling will succeed after a few tries though, this controls the amount of milliseconds the library should keep retrying for.",
|
|
730
|
+
helpGroup: "file",
|
|
731
|
+
helpValue: "ms",
|
|
732
|
+
default: 20_000,
|
|
733
|
+
hidden: true
|
|
734
|
+
}),
|
|
735
|
+
"file-watch-rename-timeout": Flags.integer({
|
|
736
|
+
summary: "Amount of milliseconds to wait for a potential rename/renameDir event to be detected. The higher this value is the more reliably renames will be detected, but don't set this too high, or the emission of some events could be delayed by that amount. The higher this value is the longer the library will take to emit add/addDir/unlink/unlinkDir events.",
|
|
737
|
+
helpGroup: "file",
|
|
738
|
+
helpValue: "ms",
|
|
739
|
+
default: 1_250,
|
|
740
|
+
hidden: true
|
|
615
741
|
})
|
|
616
742
|
});
|
|
617
743
|
_define_property(Sync, "examples", [
|
|
@@ -700,6 +826,13 @@ var /**
|
|
|
700
826
|
* @returns The SyncFile instance.
|
|
701
827
|
*/ static load(rootDir) {
|
|
702
828
|
const state = fs.readJsonSync(path.join(rootDir, ".gadget/sync.json"));
|
|
829
|
+
context.addBreadcrumb({
|
|
830
|
+
category: "sync",
|
|
831
|
+
message: "Loaded sync state",
|
|
832
|
+
data: {
|
|
833
|
+
state
|
|
834
|
+
}
|
|
835
|
+
});
|
|
703
836
|
assert(_.isString(state.app), "missing or invalid app");
|
|
704
837
|
assert(_.isString(state.filesVersion), "missing or invalid filesVersion");
|
|
705
838
|
assert(_.isNumber(state.mtime), "missing or invalid mtime");
|
|
@@ -714,6 +847,11 @@ var /**
|
|
|
714
847
|
*/ flush() {
|
|
715
848
|
_class_private_field_get(this, _save).flush();
|
|
716
849
|
}
|
|
850
|
+
/**
|
|
851
|
+
* @returns The JSON representation of this instance.
|
|
852
|
+
*/ toJSON() {
|
|
853
|
+
return this._inner;
|
|
854
|
+
}
|
|
717
855
|
constructor(_rootDir, inner){
|
|
718
856
|
_define_property(this, "_rootDir", void 0);
|
|
719
857
|
_define_property(this, "_inner", void 0);
|
|
@@ -726,6 +864,13 @@ var /**
|
|
|
726
864
|
fs.outputJSONSync(path.join(this._rootDir, ".gadget/sync.json"), this._inner, {
|
|
727
865
|
spaces: 2
|
|
728
866
|
});
|
|
867
|
+
context.addBreadcrumb({
|
|
868
|
+
category: "sync",
|
|
869
|
+
message: "Saved sync state",
|
|
870
|
+
data: {
|
|
871
|
+
state: this._inner
|
|
872
|
+
}
|
|
873
|
+
});
|
|
729
874
|
}, 100));
|
|
730
875
|
this._inner = inner;
|
|
731
876
|
}
|