@gadgetinc/ggt 0.1.16 → 0.1.18
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/lib/commands/help.d.ts +2 -2
- package/lib/commands/help.js +2 -1
- package/lib/commands/help.js.map +1 -1
- package/lib/commands/list.d.ts +1 -0
- package/lib/commands/list.js +15 -6
- package/lib/commands/list.js.map +1 -1
- package/lib/commands/sync.d.ts +88 -6
- package/lib/commands/sync.js +196 -122
- package/lib/commands/sync.js.map +1 -1
- package/lib/utils/base-command.d.ts +16 -10
- package/lib/utils/base-command.js +27 -14
- package/lib/utils/base-command.js.map +1 -1
- package/lib/utils/client.d.ts +6 -0
- package/lib/utils/client.js +6 -0
- package/lib/utils/client.js.map +1 -1
- package/lib/utils/context.d.ts +3 -2
- package/lib/utils/context.js +3 -2
- package/lib/utils/context.js.map +1 -1
- package/lib/utils/fs-utils.d.ts +2 -2
- package/lib/utils/fs-utils.js +3 -3
- package/lib/utils/fs-utils.js.map +1 -1
- package/lib/utils/promise.d.ts +35 -0
- package/lib/utils/promise.js +114 -0
- package/lib/utils/promise.js.map +1 -0
- package/npm-shrinkwrap.json +17320 -11197
- package/oclif.manifest.json +1 -1
- package/package.json +23 -23
package/lib/commands/sync.js
CHANGED
|
@@ -12,7 +12,6 @@ const fs_extra_1 = tslib_1.__importDefault(require("fs-extra"));
|
|
|
12
12
|
const inquirer_1 = require("inquirer");
|
|
13
13
|
const lodash_1 = require("lodash");
|
|
14
14
|
const lodash_2 = require("lodash");
|
|
15
|
-
const lodash_3 = require("lodash");
|
|
16
15
|
const normalize_path_1 = tslib_1.__importDefault(require("normalize-path"));
|
|
17
16
|
const p_map_1 = tslib_1.__importDefault(require("p-map"));
|
|
18
17
|
const p_queue_1 = tslib_1.__importDefault(require("p-queue"));
|
|
@@ -27,6 +26,7 @@ const errors_1 = require("../utils/errors");
|
|
|
27
26
|
const flags_1 = require("../utils/flags");
|
|
28
27
|
const fs_utils_1 = require("../utils/fs-utils");
|
|
29
28
|
const graphql_1 = require("../__generated__/graphql");
|
|
29
|
+
const promise_1 = require("../utils/promise");
|
|
30
30
|
class Sync extends base_command_1.BaseCommand {
|
|
31
31
|
constructor() {
|
|
32
32
|
super(...arguments);
|
|
@@ -36,96 +36,151 @@ class Sync extends base_command_1.BaseCommand {
|
|
|
36
36
|
writable: true,
|
|
37
37
|
value: true
|
|
38
38
|
});
|
|
39
|
+
/**
|
|
40
|
+
* The current status of the sync process.
|
|
41
|
+
*/
|
|
39
42
|
Object.defineProperty(this, "status", {
|
|
40
43
|
enumerable: true,
|
|
41
44
|
configurable: true,
|
|
42
45
|
writable: true,
|
|
43
46
|
value: SyncStatus.STARTING
|
|
44
47
|
});
|
|
48
|
+
/**
|
|
49
|
+
* The absolute path to the directory to sync files to.
|
|
50
|
+
*/
|
|
45
51
|
Object.defineProperty(this, "dir", {
|
|
46
52
|
enumerable: true,
|
|
47
53
|
configurable: true,
|
|
48
54
|
writable: true,
|
|
49
55
|
value: void 0
|
|
50
56
|
});
|
|
51
|
-
|
|
57
|
+
/**
|
|
58
|
+
* A list of filepaths that have changed because of a remote file-sync event. This is used to avoid sending files that
|
|
59
|
+
* we recently received from a remote file-sync event.
|
|
60
|
+
*/
|
|
61
|
+
Object.defineProperty(this, "recentRemoteChanges", {
|
|
52
62
|
enumerable: true,
|
|
53
63
|
configurable: true,
|
|
54
64
|
writable: true,
|
|
55
65
|
value: new Set()
|
|
56
66
|
});
|
|
67
|
+
/**
|
|
68
|
+
* A FIFO async callback queue that ensures we process file-sync events in the order they occurred.
|
|
69
|
+
*/
|
|
57
70
|
Object.defineProperty(this, "queue", {
|
|
58
71
|
enumerable: true,
|
|
59
72
|
configurable: true,
|
|
60
73
|
writable: true,
|
|
61
74
|
value: new p_queue_1.default({ concurrency: 1 })
|
|
62
75
|
});
|
|
76
|
+
/**
|
|
77
|
+
* A GraphQL client connected to the app's /edit/api/graphql-ws endpoint
|
|
78
|
+
*/
|
|
63
79
|
Object.defineProperty(this, "client", {
|
|
64
80
|
enumerable: true,
|
|
65
81
|
configurable: true,
|
|
66
82
|
writable: true,
|
|
67
83
|
value: void 0
|
|
68
84
|
});
|
|
85
|
+
/**
|
|
86
|
+
* Loads the .ignore file and provides methods for checking if a file should be ignored.
|
|
87
|
+
*/
|
|
69
88
|
Object.defineProperty(this, "ignorer", {
|
|
70
89
|
enumerable: true,
|
|
71
90
|
configurable: true,
|
|
72
91
|
writable: true,
|
|
73
92
|
value: void 0
|
|
74
93
|
});
|
|
94
|
+
/**
|
|
95
|
+
* Watches the local filesystem for changes.
|
|
96
|
+
*/
|
|
75
97
|
Object.defineProperty(this, "watcher", {
|
|
76
98
|
enumerable: true,
|
|
77
99
|
configurable: true,
|
|
78
100
|
writable: true,
|
|
79
101
|
value: void 0
|
|
80
102
|
});
|
|
103
|
+
/**
|
|
104
|
+
* Holds information about the state of the local filesystem. It's persisted to `.gadget/sync.json`.
|
|
105
|
+
*/
|
|
81
106
|
Object.defineProperty(this, "metadata", {
|
|
82
107
|
enumerable: true,
|
|
83
108
|
configurable: true,
|
|
84
109
|
writable: true,
|
|
85
110
|
value: {
|
|
111
|
+
/**
|
|
112
|
+
* The app this filesystem is synced to.
|
|
113
|
+
*/
|
|
86
114
|
app: "",
|
|
115
|
+
/**
|
|
116
|
+
* The last filesVersion that was successfully written to the filesystem. This is used to determine if the remote
|
|
117
|
+
* filesystem is ahead of the local one.
|
|
118
|
+
*/
|
|
87
119
|
filesVersion: "0",
|
|
120
|
+
/**
|
|
121
|
+
* The largest mtime that was seen on the local filesystem before `ggt sync` stopped. This is used to determine if
|
|
122
|
+
* the local filesystem has changed since the last sync.
|
|
123
|
+
*
|
|
124
|
+
* Note: This does not include the mtime of files that are ignored.
|
|
125
|
+
*/
|
|
88
126
|
mtime: 0,
|
|
89
127
|
}
|
|
90
128
|
});
|
|
129
|
+
/**
|
|
130
|
+
* A debounced function that enqueue's local file changes to be sent to Gadget.
|
|
131
|
+
*/
|
|
91
132
|
Object.defineProperty(this, "publish", {
|
|
92
133
|
enumerable: true,
|
|
93
134
|
configurable: true,
|
|
94
135
|
writable: true,
|
|
95
136
|
value: void 0
|
|
96
137
|
});
|
|
138
|
+
/**
|
|
139
|
+
* Gracefully stops the sync.
|
|
140
|
+
*/
|
|
97
141
|
Object.defineProperty(this, "stop", {
|
|
98
142
|
enumerable: true,
|
|
99
143
|
configurable: true,
|
|
100
144
|
writable: true,
|
|
101
145
|
value: void 0
|
|
102
146
|
});
|
|
103
|
-
Object.defineProperty(this, "finished", {
|
|
104
|
-
enumerable: true,
|
|
105
|
-
configurable: true,
|
|
106
|
-
writable: true,
|
|
107
|
-
value: void 0
|
|
108
|
-
});
|
|
109
|
-
Object.defineProperty(this, "markFinished", {
|
|
110
|
-
enumerable: true,
|
|
111
|
-
configurable: true,
|
|
112
|
-
writable: true,
|
|
113
|
-
value: void 0
|
|
114
|
-
});
|
|
115
147
|
}
|
|
148
|
+
/**
|
|
149
|
+
* Turns an absolute filepath into a relative one from {@linkcode dir}.
|
|
150
|
+
*/
|
|
116
151
|
relative(to) {
|
|
117
152
|
return path_1.default.relative(this.dir, to);
|
|
118
153
|
}
|
|
154
|
+
/**
|
|
155
|
+
* Combines path segments into an absolute filepath that starts at {@linkcode dir}.
|
|
156
|
+
*/
|
|
119
157
|
absolute(...pathSegments) {
|
|
120
158
|
return path_1.default.resolve(this.dir, ...pathSegments);
|
|
121
159
|
}
|
|
122
|
-
|
|
160
|
+
/**
|
|
161
|
+
* Similar to {@linkcode relative} in that it turns a filepath into a relative one from {@linkcode dir}. However, it
|
|
162
|
+
* also changes any slashes to be posix/unix-like forward slashes, condenses repeated slashes into a single slash, and
|
|
163
|
+
* adds a trailing slash if the filepath is a directory.
|
|
164
|
+
*
|
|
165
|
+
* This is used when sending file-sync events to Gadget to ensure that the paths are consistent across platforms.
|
|
166
|
+
*
|
|
167
|
+
* @see https://www.npmjs.com/package/normalize-path
|
|
168
|
+
*/
|
|
169
|
+
normalize(filepath, isDirectory) {
|
|
123
170
|
return (0, normalize_path_1.default)(path_1.default.isAbsolute(filepath) ? this.relative(filepath) : filepath) + (isDirectory ? "/" : "");
|
|
124
171
|
}
|
|
172
|
+
/**
|
|
173
|
+
* Pretty-prints changed and deleted filepaths to the console.
|
|
174
|
+
*
|
|
175
|
+
* @param prefix The prefix to print before each line.
|
|
176
|
+
* @param changed The normalized paths that have changed.
|
|
177
|
+
* @param deleted The normalized paths that have been deleted.
|
|
178
|
+
* @param options.limit The maximum number of lines to print. Defaults to 10. If debug is enabled, this is ignored.
|
|
179
|
+
*/
|
|
125
180
|
logPaths(prefix, changed, deleted, { limit = 10 } = {}) {
|
|
126
181
|
const lines = (0, lodash_1.sortBy)([
|
|
127
|
-
...changed.map((
|
|
128
|
-
...deleted.map((
|
|
182
|
+
...changed.map((normalizedPath) => (0, chalk_1.default) `{green ${prefix}} ${normalizedPath} {gray (changed)}`),
|
|
183
|
+
...deleted.map((normalizedPath) => (0, chalk_1.default) `{red ${prefix}} ${normalizedPath} {gray (deleted)}`),
|
|
129
184
|
], (line) => line.slice(line.indexOf(" ") + 1));
|
|
130
185
|
let logged = 0;
|
|
131
186
|
for (const line of lines) {
|
|
@@ -139,9 +194,16 @@ class Sync extends base_command_1.BaseCommand {
|
|
|
139
194
|
this.log((0, chalk_1.default) `{gray ${(0, pluralize_1.default)("file", lines.length, true)} in total. ${changed.length} changed, ${deleted.length} deleted.}`);
|
|
140
195
|
this.log();
|
|
141
196
|
}
|
|
197
|
+
/**
|
|
198
|
+
* Initializes the sync process.
|
|
199
|
+
* - Ensures the directory exists.
|
|
200
|
+
* - Ensures the directory is empty or contains a `.gadget/sync.json` file.
|
|
201
|
+
* - Ensures an app is selected and that it matches the app the directory was previously synced to.
|
|
202
|
+
* - Ensures yarn v1 is installed.
|
|
203
|
+
* - Prompts the user how to resolve conflicts if the local filesystem has changed since the last sync.
|
|
204
|
+
*/
|
|
142
205
|
async init() {
|
|
143
206
|
await super.init();
|
|
144
|
-
(0, assert_1.default)((0, lodash_2.isString)(this.args["directory"]));
|
|
145
207
|
this.dir =
|
|
146
208
|
this.config.windows && this.args["directory"].startsWith("~/")
|
|
147
209
|
? path_1.default.join(this.config.home, this.args["directory"].slice(2))
|
|
@@ -182,7 +244,7 @@ class Sync extends base_command_1.BaseCommand {
|
|
|
182
244
|
await context_1.context.setApp(this.metadata.app);
|
|
183
245
|
this.client = new client_1.Client();
|
|
184
246
|
// local files/folders that should never be published
|
|
185
|
-
this.ignorer = new fs_utils_1.
|
|
247
|
+
this.ignorer = new fs_utils_1.FSIgnorer(this.dir, ["node_modules", ".gadget", ".git"]);
|
|
186
248
|
this.watcher = new chokidar_1.FSWatcher({
|
|
187
249
|
ignored: (filepath) => this.ignorer.ignores(filepath),
|
|
188
250
|
// don't emit an event for every watched file on boot
|
|
@@ -201,14 +263,14 @@ class Sync extends base_command_1.BaseCommand {
|
|
|
201
263
|
const hasRemoteChanges = BigInt(remoteFilesVersion) > BigInt(this.metadata.filesVersion);
|
|
202
264
|
const getChangedFiles = async () => {
|
|
203
265
|
const files = new Map();
|
|
204
|
-
for await (const
|
|
205
|
-
const stats = await fs_extra_1.default.stat(
|
|
266
|
+
for await (const absolutePath of (0, fs_utils_1.walkDir)(this.dir, { ignorer: this.ignorer })) {
|
|
267
|
+
const stats = await fs_extra_1.default.stat(absolutePath);
|
|
206
268
|
if (stats.mtime.getTime() > this.metadata.mtime) {
|
|
207
|
-
files.set(this.
|
|
269
|
+
files.set(this.normalize(absolutePath, stats.isDirectory()), stats);
|
|
208
270
|
}
|
|
209
271
|
}
|
|
210
|
-
//
|
|
211
|
-
files.delete(
|
|
272
|
+
// never include the root directory
|
|
273
|
+
files.delete("/");
|
|
212
274
|
return files;
|
|
213
275
|
};
|
|
214
276
|
const changedFiles = await getChangedFiles();
|
|
@@ -232,21 +294,22 @@ class Sync extends base_command_1.BaseCommand {
|
|
|
232
294
|
case Action.MERGE: {
|
|
233
295
|
// get all the changed files again in case more changed
|
|
234
296
|
const files = await getChangedFiles();
|
|
235
|
-
//
|
|
297
|
+
// We purposefully don't set the returned remoteFilesVersion here because we haven't received the remote changes
|
|
298
|
+
// yet. This will cause us to receive the local files that we just published + the remote files that were
|
|
299
|
+
// changed since the last sync.
|
|
236
300
|
await this.client.queryUnwrap({
|
|
237
301
|
query: exports.PUBLISH_FILE_SYNC_EVENTS_MUTATION,
|
|
238
302
|
variables: {
|
|
239
303
|
input: {
|
|
240
304
|
expectedRemoteFilesVersion: remoteFilesVersion,
|
|
241
|
-
changed: await (0, p_map_1.default)(files, async ([
|
|
305
|
+
changed: await (0, p_map_1.default)(files, async ([normalizedPath, stats]) => {
|
|
242
306
|
if (stats.mtime.getTime() > this.metadata.mtime) {
|
|
243
307
|
this.metadata.mtime = stats.mtime.getTime();
|
|
244
308
|
}
|
|
245
|
-
const isDirectory = stats.isDirectory();
|
|
246
309
|
return {
|
|
247
|
-
path:
|
|
310
|
+
path: normalizedPath,
|
|
248
311
|
mode: stats.mode,
|
|
249
|
-
content: isDirectory ? "" : await fs_extra_1.default.readFile(
|
|
312
|
+
content: stats.isDirectory() ? "" : await fs_extra_1.default.readFile(this.absolute(normalizedPath), "base64"),
|
|
250
313
|
encoding: graphql_1.FileSyncEncoding.Base64,
|
|
251
314
|
};
|
|
252
315
|
}),
|
|
@@ -257,7 +320,9 @@ class Sync extends base_command_1.BaseCommand {
|
|
|
257
320
|
break;
|
|
258
321
|
}
|
|
259
322
|
case Action.RESET: {
|
|
260
|
-
|
|
323
|
+
// delete all the local files that have changed since the last sync and set the files version to 0 so we receive
|
|
324
|
+
// all the remote files again, including any files that we just deleted that still exist
|
|
325
|
+
await (0, p_map_1.default)(changedFiles.keys(), (normalizedPath) => fs_extra_1.default.remove(this.absolute(normalizedPath)));
|
|
261
326
|
this.metadata.filesVersion = "0";
|
|
262
327
|
break;
|
|
263
328
|
}
|
|
@@ -267,11 +332,12 @@ class Sync extends base_command_1.BaseCommand {
|
|
|
267
332
|
}
|
|
268
333
|
this.debug("started");
|
|
269
334
|
}
|
|
335
|
+
/**
|
|
336
|
+
* Runs the sync process until it is stopped or an error occurs.
|
|
337
|
+
*/
|
|
270
338
|
async run() {
|
|
271
339
|
let error;
|
|
272
|
-
|
|
273
|
-
this.markFinished = resolve;
|
|
274
|
-
});
|
|
340
|
+
const stopped = new promise_1.PromiseSignal();
|
|
275
341
|
this.stop = async (e) => {
|
|
276
342
|
if (this.status != SyncStatus.RUNNING)
|
|
277
343
|
return;
|
|
@@ -289,7 +355,7 @@ class Sync extends base_command_1.BaseCommand {
|
|
|
289
355
|
await Promise.allSettled([this.watcher.close(), this.client.dispose()]);
|
|
290
356
|
this.debug("stopped");
|
|
291
357
|
this.status = SyncStatus.STOPPED;
|
|
292
|
-
|
|
358
|
+
stopped.resolve();
|
|
293
359
|
}
|
|
294
360
|
};
|
|
295
361
|
for (const signal of ["SIGINT", "SIGTERM"]) {
|
|
@@ -313,13 +379,14 @@ class Sync extends base_command_1.BaseCommand {
|
|
|
313
379
|
variables: () => ({ localFilesVersion: this.metadata.filesVersion }),
|
|
314
380
|
}, {
|
|
315
381
|
error: (error) => void this.stop(error),
|
|
316
|
-
next: ({ remoteFileSyncEvents
|
|
317
|
-
const
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
382
|
+
next: ({ remoteFileSyncEvents }) => {
|
|
383
|
+
const remoteFilesVersion = remoteFileSyncEvents.remoteFilesVersion;
|
|
384
|
+
// we always ignore .gadget/ files so that we don't publish them (they're managed by gadget), but we still want to receive them
|
|
385
|
+
const filter = (event) => event.path.startsWith(".gadget/") || !this.ignorer.ignores(event.path);
|
|
386
|
+
const changed = remoteFileSyncEvents.changed.filter(filter);
|
|
387
|
+
const deleted = remoteFileSyncEvents.deleted.filter(filter);
|
|
388
|
+
this._enqueue(async () => {
|
|
389
|
+
if (!changed.length && !deleted.length) {
|
|
323
390
|
if (BigInt(remoteFilesVersion) > BigInt(this.metadata.filesVersion)) {
|
|
324
391
|
// we still need to update filesVersion, otherwise our expectedFilesVersion will be behind the next time we publish
|
|
325
392
|
this.debug("updated local files version from %s to %s", this.metadata.filesVersion, remoteFilesVersion);
|
|
@@ -328,131 +395,130 @@ class Sync extends base_command_1.BaseCommand {
|
|
|
328
395
|
return;
|
|
329
396
|
}
|
|
330
397
|
this.log((0, chalk_1.default) `Received {gray ${(0, format_1.default)(new Date(), "pp")}}`);
|
|
331
|
-
this.logPaths("←", changed.map((x) => x.path)
|
|
332
|
-
|
|
333
|
-
|
|
398
|
+
this.logPaths("←", changed.map((x) => x.path), deleted.map((x) => x.path));
|
|
399
|
+
// we need to processed deleted files first as we may delete an empty directory after a file has been put
|
|
400
|
+
// into it. if processed out of order the new file is deleted as well
|
|
401
|
+
await (0, p_map_1.default)(deleted, async (file) => {
|
|
402
|
+
this.recentRemoteChanges.add(file.path);
|
|
403
|
+
await fs_extra_1.default.remove(this.absolute(file.path));
|
|
404
|
+
});
|
|
405
|
+
await (0, p_map_1.default)(changed, async (file) => {
|
|
406
|
+
this.recentRemoteChanges.add(file.path);
|
|
407
|
+
const absolutePath = this.absolute(file.path);
|
|
408
|
+
if (file.path.endsWith("/")) {
|
|
409
|
+
await fs_extra_1.default.ensureDir(absolutePath, { mode: 0o755 });
|
|
334
410
|
return;
|
|
335
411
|
}
|
|
336
|
-
|
|
337
|
-
this.
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
}
|
|
346
|
-
else {
|
|
347
|
-
await fs_extra_1.default.ensureDir(filepath, { mode: 0o755 });
|
|
348
|
-
}
|
|
349
|
-
if (filepath == this.absolute("yarn.lock")) {
|
|
350
|
-
await (0, execa_1.default)("yarn", ["install"], { cwd: this.dir }).catch((err) => {
|
|
351
|
-
this.debug("yarn install failed");
|
|
352
|
-
this.debug(err.message);
|
|
353
|
-
});
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
else {
|
|
357
|
-
await fs_extra_1.default.remove(filepath);
|
|
412
|
+
// we need to add the parent directory to recentRemoteChanges so that we don't try to publish it
|
|
413
|
+
this.recentRemoteChanges.add(path_1.default.dirname(file.path) + "/");
|
|
414
|
+
await fs_extra_1.default.ensureDir(path_1.default.dirname(absolutePath), { mode: 0o755 });
|
|
415
|
+
await fs_extra_1.default.writeFile(absolutePath, Buffer.from(file.content, file.encoding), { mode: file.mode });
|
|
416
|
+
if (absolutePath == this.absolute("yarn.lock")) {
|
|
417
|
+
await (0, execa_1.default)("yarn", ["install"], { cwd: this.dir }).catch((err) => {
|
|
418
|
+
this.debug("yarn install failed");
|
|
419
|
+
this.debug(err.message);
|
|
420
|
+
});
|
|
358
421
|
}
|
|
359
|
-
if (
|
|
422
|
+
if (absolutePath == this.ignorer.filepath) {
|
|
360
423
|
this.ignorer.reload();
|
|
361
424
|
}
|
|
362
|
-
}
|
|
363
|
-
// we need to processed deleted files first as we may delete an empty directory once a file has been put into it. if processed out of order the new file is deleted as well
|
|
364
|
-
await handleFiles(deleted);
|
|
365
|
-
await handleFiles(changed);
|
|
425
|
+
});
|
|
366
426
|
this.debug("updated local files version from %s to %s", this.metadata.filesVersion, remoteFilesVersion);
|
|
367
427
|
this.metadata.filesVersion = remoteFilesVersion;
|
|
368
|
-
|
|
369
|
-
.
|
|
428
|
+
// always remove the root directory from recentRemoteChanges
|
|
429
|
+
this.recentRemoteChanges.delete("./");
|
|
430
|
+
// remove any files in recentRemoteChanges that are ignored (e.g. .gadget/ files)
|
|
431
|
+
for (const filepath of this.recentRemoteChanges) {
|
|
432
|
+
if (this.ignorer.ignores(filepath)) {
|
|
433
|
+
this.recentRemoteChanges.delete(filepath);
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
});
|
|
370
437
|
},
|
|
371
438
|
});
|
|
372
439
|
const localFilesBuffer = new Map();
|
|
373
|
-
this.publish = (0,
|
|
440
|
+
this.publish = (0, lodash_2.debounce)(() => {
|
|
374
441
|
const localFiles = new Map(localFilesBuffer.entries());
|
|
375
442
|
localFilesBuffer.clear();
|
|
376
|
-
|
|
377
|
-
.add(async () => {
|
|
443
|
+
this._enqueue(async () => {
|
|
378
444
|
const changed = [];
|
|
379
445
|
const deleted = [];
|
|
380
|
-
await (0, p_map_1.default)(localFiles, async ([
|
|
446
|
+
await (0, p_map_1.default)(localFiles, async ([normalizedPath, file]) => {
|
|
381
447
|
if ("isDeleted" in file) {
|
|
382
|
-
deleted.push({ path:
|
|
448
|
+
deleted.push({ path: normalizedPath });
|
|
449
|
+
return;
|
|
383
450
|
}
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
});
|
|
392
|
-
}
|
|
393
|
-
catch (error) {
|
|
394
|
-
// A file could have been changed and then deleted before we process the change event, so the readFile above will raise
|
|
395
|
-
// an ENOENT. This is normal operation, so just ignore this event.
|
|
396
|
-
(0, fs_utils_1.ignoreEnoent)(error);
|
|
397
|
-
}
|
|
451
|
+
try {
|
|
452
|
+
changed.push({
|
|
453
|
+
path: normalizedPath,
|
|
454
|
+
mode: file.mode,
|
|
455
|
+
content: file.isDirectory ? "" : await fs_extra_1.default.readFile(this.absolute(normalizedPath), "base64"),
|
|
456
|
+
encoding: graphql_1.FileSyncEncoding.Base64,
|
|
457
|
+
});
|
|
398
458
|
}
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
variables: { input: { expectedRemoteFilesVersion: this.metadata.filesVersion, changed, deleted } },
|
|
404
|
-
});
|
|
405
|
-
this.log((0, chalk_1.default) `Sent {gray ${(0, format_1.default)(new Date(), "pp")}}`);
|
|
406
|
-
this.logPaths("→", changed.map((x) => x.path), deleted.map((x) => x.path));
|
|
407
|
-
const { remoteFilesVersion } = data.publishFileSyncEvents;
|
|
408
|
-
this.debug("remote files version after publishing %s", remoteFilesVersion);
|
|
409
|
-
if (BigInt(remoteFilesVersion) > BigInt(this.metadata.filesVersion)) {
|
|
410
|
-
this.debug("updated local files version from %s to %s", this.metadata.filesVersion, remoteFilesVersion);
|
|
411
|
-
this.metadata.filesVersion = remoteFilesVersion;
|
|
459
|
+
catch (error) {
|
|
460
|
+
// A file could have been changed and then deleted before we process the change event, so the readFile
|
|
461
|
+
// above will raise an ENOENT. This is normal operation, so just ignore this event.
|
|
462
|
+
(0, fs_utils_1.ignoreEnoent)(error);
|
|
412
463
|
}
|
|
464
|
+
});
|
|
465
|
+
if (!changed.length && !deleted.length) {
|
|
466
|
+
return;
|
|
467
|
+
}
|
|
468
|
+
const data = await this.client.queryUnwrap({
|
|
469
|
+
query: exports.PUBLISH_FILE_SYNC_EVENTS_MUTATION,
|
|
470
|
+
variables: { input: { expectedRemoteFilesVersion: this.metadata.filesVersion, changed, deleted } },
|
|
471
|
+
});
|
|
472
|
+
this.log((0, chalk_1.default) `Sent {gray ${(0, format_1.default)(new Date(), "pp")}}`);
|
|
473
|
+
this.logPaths("→", changed.map((x) => x.path), deleted.map((x) => x.path));
|
|
474
|
+
const { remoteFilesVersion } = data.publishFileSyncEvents;
|
|
475
|
+
this.debug("remote files version after publishing %s", remoteFilesVersion);
|
|
476
|
+
if (BigInt(remoteFilesVersion) > BigInt(this.metadata.filesVersion)) {
|
|
477
|
+
this.debug("updated local files version from %s to %s", this.metadata.filesVersion, remoteFilesVersion);
|
|
478
|
+
this.metadata.filesVersion = remoteFilesVersion;
|
|
413
479
|
}
|
|
414
|
-
})
|
|
415
|
-
.catch(this.stop);
|
|
480
|
+
});
|
|
416
481
|
}, this.flags["file-push-delay"]);
|
|
417
482
|
this.watcher
|
|
418
483
|
.add(`${this.dir}/**/*`)
|
|
419
484
|
.on("error", (error) => void this.stop(error))
|
|
420
|
-
.on("all", (event,
|
|
421
|
-
const
|
|
485
|
+
.on("all", (event, absolutePath, stats) => {
|
|
486
|
+
const normalizedPath = this.normalize(absolutePath, event == "addDir" || event == "unlinkDir");
|
|
422
487
|
if (stats?.isSymbolicLink?.()) {
|
|
423
|
-
this.debug("skipping event caused by symlink %s",
|
|
488
|
+
this.debug("skipping event caused by symlink %s", normalizedPath);
|
|
424
489
|
return;
|
|
425
490
|
}
|
|
426
|
-
if (
|
|
491
|
+
if (absolutePath == this.ignorer.filepath) {
|
|
427
492
|
this.ignorer.reload();
|
|
428
493
|
}
|
|
429
|
-
else if (this.ignorer.ignores(
|
|
430
|
-
this.debug("skipping event caused by ignored file %s",
|
|
494
|
+
else if (this.ignorer.ignores(absolutePath)) {
|
|
495
|
+
this.debug("skipping event caused by ignored file %s", normalizedPath);
|
|
431
496
|
return;
|
|
432
497
|
}
|
|
433
|
-
// we only update the mtime if the file is not ignored, because if we restart and the mtime is set to an ignored
|
|
434
|
-
// be greater than the mtime of all non ignored files and we'll think that local files have
|
|
498
|
+
// we only update the mtime if the file is not ignored, because if we restart and the mtime is set to an ignored
|
|
499
|
+
// file, then it could be greater than the mtime of all non ignored files and we'll think that local files have
|
|
500
|
+
// changed when only an ignored one has
|
|
435
501
|
if (stats && stats.mtime.getTime() > this.metadata.mtime) {
|
|
436
502
|
this.metadata.mtime = stats.mtime.getTime();
|
|
437
503
|
}
|
|
438
|
-
if (this.
|
|
439
|
-
this.debug("skipping event caused by recent write %s",
|
|
504
|
+
if (this.recentRemoteChanges.delete(normalizedPath)) {
|
|
505
|
+
this.debug("skipping event caused by recent write %s", normalizedPath);
|
|
440
506
|
return;
|
|
441
507
|
}
|
|
442
|
-
this.debug("file changed %s",
|
|
508
|
+
this.debug("file changed %s", normalizedPath, event);
|
|
443
509
|
switch (event) {
|
|
444
510
|
case "add":
|
|
445
511
|
case "change":
|
|
446
512
|
(0, assert_1.default)(stats, "missing stats on add/change event");
|
|
447
|
-
localFilesBuffer.set(
|
|
513
|
+
localFilesBuffer.set(normalizedPath, { mode: stats.mode, isDirectory: false });
|
|
448
514
|
break;
|
|
449
515
|
case "addDir":
|
|
450
516
|
(0, assert_1.default)(stats, "missing stats on addDir event");
|
|
451
|
-
localFilesBuffer.set(
|
|
517
|
+
localFilesBuffer.set(normalizedPath, { mode: stats.mode, isDirectory: true });
|
|
452
518
|
break;
|
|
453
519
|
case "unlinkDir":
|
|
454
520
|
case "unlink":
|
|
455
|
-
localFilesBuffer.set(
|
|
521
|
+
localFilesBuffer.set(normalizedPath, { isDeleted: true, isDirectory: event === "unlinkDir" });
|
|
456
522
|
break;
|
|
457
523
|
}
|
|
458
524
|
this.publish();
|
|
@@ -479,7 +545,7 @@ class Sync extends base_command_1.BaseCommand {
|
|
|
479
545
|
Watching for file changes... {gray Press Ctrl+C to stop}
|
|
480
546
|
`));
|
|
481
547
|
this.log();
|
|
482
|
-
await
|
|
548
|
+
await stopped;
|
|
483
549
|
if (error) {
|
|
484
550
|
this.notify({ subtitle: "Uh oh!", message: "An error occurred while syncing files" });
|
|
485
551
|
throw error;
|
|
@@ -488,6 +554,14 @@ class Sync extends base_command_1.BaseCommand {
|
|
|
488
554
|
this.log("Goodbye!");
|
|
489
555
|
}
|
|
490
556
|
}
|
|
557
|
+
/**
|
|
558
|
+
* Enqueues a function that handles file-sync events onto the {@linkcode queue}.
|
|
559
|
+
*
|
|
560
|
+
* @param fn The function to enqueue.
|
|
561
|
+
*/
|
|
562
|
+
_enqueue(fn) {
|
|
563
|
+
void this.queue.add(fn).catch(this.stop);
|
|
564
|
+
}
|
|
491
565
|
}
|
|
492
566
|
Object.defineProperty(Sync, "priority", {
|
|
493
567
|
enumerable: true,
|