@gadgetinc/ggt 0.3.3 → 0.4.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/README.md +139 -76
- package/bin/dev.js +4 -7
- package/lib/__generated__/graphql.js.map +1 -1
- package/lib/commands/deploy.js +232 -0
- package/lib/commands/deploy.js.map +1 -0
- package/lib/commands/list.js +20 -16
- package/lib/commands/list.js.map +1 -1
- package/lib/commands/login.js +22 -20
- package/lib/commands/login.js.map +1 -1
- package/lib/commands/logout.js +13 -9
- package/lib/commands/logout.js.map +1 -1
- package/lib/commands/root.js +89 -56
- package/lib/commands/root.js.map +1 -1
- package/lib/commands/sync.js +253 -496
- package/lib/commands/sync.js.map +1 -1
- package/lib/commands/version.js +21 -0
- package/lib/commands/version.js.map +1 -0
- package/lib/commands/whoami.js +15 -11
- package/lib/commands/whoami.js.map +1 -1
- package/lib/main.js +4 -10
- package/lib/main.js.map +1 -1
- package/lib/services/{app.js → app/app.js} +8 -3
- package/lib/services/app/app.js.map +1 -0
- package/lib/services/app/arg.js +28 -0
- package/lib/services/app/arg.js.map +1 -0
- package/lib/services/app/edit-graphql.js +389 -0
- package/lib/services/app/edit-graphql.js.map +1 -0
- package/lib/services/command/arg.js +53 -0
- package/lib/services/command/arg.js.map +1 -0
- package/lib/services/command/command.js +27 -0
- package/lib/services/command/command.js.map +1 -0
- package/lib/services/command/context.js +60 -0
- package/lib/services/command/context.js.map +1 -0
- package/lib/services/{config.js → config/config.js} +29 -31
- package/lib/services/config/config.js.map +1 -0
- package/lib/services/config/env.js +22 -0
- package/lib/services/config/env.js.map +1 -0
- package/lib/services/config/package-json.js +9 -0
- package/lib/services/config/package-json.js.map +1 -0
- package/lib/services/filesync/changes.js +97 -0
- package/lib/services/filesync/changes.js.map +1 -0
- package/lib/services/filesync/conflicts.js +137 -0
- package/lib/services/filesync/conflicts.js.map +1 -0
- package/lib/services/filesync/directory.js +253 -0
- package/lib/services/filesync/directory.js.map +1 -0
- package/lib/services/filesync/error.js +67 -0
- package/lib/services/filesync/error.js.map +1 -0
- package/lib/services/filesync/file.js +3 -0
- package/lib/services/filesync/file.js.map +1 -0
- package/lib/services/filesync/filesync.js +673 -0
- package/lib/services/filesync/filesync.js.map +1 -0
- package/lib/services/filesync/hashes.js +150 -0
- package/lib/services/filesync/hashes.js.map +1 -0
- package/lib/services/http/auth.js +41 -0
- package/lib/services/http/auth.js.map +1 -0
- package/lib/services/http/http.js +64 -0
- package/lib/services/http/http.js.map +1 -0
- package/lib/services/output/log/field.js +3 -0
- package/lib/services/output/log/field.js.map +1 -0
- package/lib/services/output/log/format/format.js +8 -0
- package/lib/services/output/log/format/format.js.map +1 -0
- package/lib/services/output/log/format/json.js +45 -0
- package/lib/services/output/log/format/json.js.map +1 -0
- package/lib/services/output/log/format/pretty.js +147 -0
- package/lib/services/output/log/format/pretty.js.map +1 -0
- package/lib/services/output/log/level.js +41 -0
- package/lib/services/output/log/level.js.map +1 -0
- package/lib/services/output/log/logger.js +40 -0
- package/lib/services/output/log/logger.js.map +1 -0
- package/lib/services/output/log/printer.js +120 -0
- package/lib/services/output/log/printer.js.map +1 -0
- package/lib/services/output/log/structured.js +52 -0
- package/lib/services/output/log/structured.js.map +1 -0
- package/lib/services/{notify.js → output/notify.js} +7 -6
- package/lib/services/output/notify.js.map +1 -0
- package/lib/services/output/prompt.js +52 -0
- package/lib/services/output/prompt.js.map +1 -0
- package/lib/services/output/report.js +162 -0
- package/lib/services/output/report.js.map +1 -0
- package/lib/services/output/sprint.js +21 -0
- package/lib/services/output/sprint.js.map +1 -0
- package/lib/services/output/stream.js +54 -0
- package/lib/services/output/stream.js.map +1 -0
- package/lib/services/{version.js → output/update.js} +24 -16
- package/lib/services/output/update.js.map +1 -0
- package/lib/services/user/session.js +50 -0
- package/lib/services/user/session.js.map +1 -0
- package/lib/services/{user.js → user/user.js} +23 -14
- package/lib/services/user/user.js.map +1 -0
- package/lib/services/util/boolean.js +15 -0
- package/lib/services/util/boolean.js.map +1 -0
- package/lib/services/util/collection.js +38 -0
- package/lib/services/util/collection.js.map +1 -0
- package/lib/services/util/function.js +97 -0
- package/lib/services/util/function.js.map +1 -0
- package/lib/services/{is.js → util/is.js} +7 -0
- package/lib/services/util/is.js.map +1 -0
- package/lib/services/util/number.js +27 -0
- package/lib/services/util/number.js.map +1 -0
- package/lib/services/util/object.js +101 -0
- package/lib/services/util/object.js.map +1 -0
- package/lib/services/util/paths.js +36 -0
- package/lib/services/util/paths.js.map +1 -0
- package/lib/services/{promise.js → util/promise.js} +5 -7
- package/lib/services/util/promise.js.map +1 -0
- package/npm-shrinkwrap.json +2143 -1304
- package/package.json +50 -42
- package/lib/commands/index.js +0 -9
- package/lib/commands/index.js.map +0 -1
- package/lib/services/app.js.map +0 -1
- package/lib/services/args.js +0 -28
- package/lib/services/args.js.map +0 -1
- package/lib/services/collections.js +0 -17
- package/lib/services/collections.js.map +0 -1
- package/lib/services/config.js.map +0 -1
- package/lib/services/debounce.js +0 -21
- package/lib/services/debounce.js.map +0 -1
- package/lib/services/defaults.js +0 -8
- package/lib/services/defaults.js.map +0 -1
- package/lib/services/edit-graphql.js +0 -202
- package/lib/services/edit-graphql.js.map +0 -1
- package/lib/services/errors.js +0 -277
- package/lib/services/errors.js.map +0 -1
- package/lib/services/filesync.js +0 -404
- package/lib/services/filesync.js.map +0 -1
- package/lib/services/fs.js +0 -35
- package/lib/services/fs.js.map +0 -1
- package/lib/services/http.js +0 -53
- package/lib/services/http.js.map +0 -1
- package/lib/services/is.js.map +0 -1
- package/lib/services/log.js +0 -45
- package/lib/services/log.js.map +0 -1
- package/lib/services/noop.js +0 -4
- package/lib/services/noop.js.map +0 -1
- package/lib/services/notify.js.map +0 -1
- package/lib/services/output.js +0 -74
- package/lib/services/output.js.map +0 -1
- package/lib/services/promise.js.map +0 -1
- package/lib/services/prompt.js +0 -22
- package/lib/services/prompt.js.map +0 -1
- package/lib/services/session.js +0 -31
- package/lib/services/session.js.map +0 -1
- package/lib/services/sleep.js +0 -21
- package/lib/services/sleep.js.map +0 -1
- package/lib/services/timeout.js +0 -8
- package/lib/services/timeout.js.map +0 -1
- package/lib/services/user.js.map +0 -1
- package/lib/services/version.js.map +0 -1
package/lib/commands/sync.js
CHANGED
|
@@ -1,556 +1,313 @@
|
|
|
1
|
-
import { _ as _define_property } from "@swc/helpers/_/_define_property";
|
|
2
|
-
import arg from "arg";
|
|
3
1
|
import dayjs from "dayjs";
|
|
4
2
|
import { execa } from "execa";
|
|
5
|
-
import fs from "fs-extra";
|
|
6
3
|
import ms from "ms";
|
|
7
4
|
import path from "node:path";
|
|
8
|
-
import
|
|
9
|
-
import PQueue from "p-queue";
|
|
10
|
-
import FSWatcher from "watcher";
|
|
5
|
+
import Watcher from "watcher";
|
|
11
6
|
import which from "which";
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
21
|
-
import {
|
|
22
|
-
import {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
import { PromiseSignal } from "../services/promise.js";
|
|
26
|
-
import { select } from "../services/prompt.js";
|
|
27
|
-
import { getUserOrLogin } from "../services/user.js";
|
|
28
|
-
export const usage = sprint`
|
|
29
|
-
Sync your Gadget application's source code to and from
|
|
30
|
-
your local filesystem.
|
|
7
|
+
import { AppArg } from "../services/app/arg.js";
|
|
8
|
+
import { config } from "../services/config/config.js";
|
|
9
|
+
import { Changes } from "../services/filesync/changes.js";
|
|
10
|
+
import { YarnNotFoundError } from "../services/filesync/error.js";
|
|
11
|
+
import { ConflictPreferenceArg, FileSync } from "../services/filesync/filesync.js";
|
|
12
|
+
import { notify } from "../services/output/notify.js";
|
|
13
|
+
import { reportErrorAndExit } from "../services/output/report.js";
|
|
14
|
+
import { sprint } from "../services/output/sprint.js";
|
|
15
|
+
import { getUserOrLogin } from "../services/user/user.js";
|
|
16
|
+
import { debounce } from "../services/util/function.js";
|
|
17
|
+
import { isAbortError } from "../services/util/is.js";
|
|
18
|
+
export const usage = ()=>sprint`
|
|
19
|
+
Sync your Gadget environment's source code with your local filesystem.
|
|
31
20
|
|
|
32
21
|
{bold USAGE}
|
|
33
|
-
|
|
22
|
+
ggt sync [DIRECTORY]
|
|
34
23
|
|
|
35
24
|
{bold ARGUMENTS}
|
|
36
|
-
DIRECTORY
|
|
37
|
-
|
|
38
|
-
If the directory doesn't exist, it will be created.}
|
|
25
|
+
DIRECTORY The directory to sync files to (default: ".")
|
|
39
26
|
|
|
40
27
|
{bold FLAGS}
|
|
41
|
-
-a, --app=<name>
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
your remote ones.}
|
|
28
|
+
-a, --app=<name> The Gadget application to sync files to
|
|
29
|
+
--prefer=<filesystem> Prefer "local" or "gadget" conflicting changes
|
|
30
|
+
--once Sync once and exit
|
|
31
|
+
--force Sync regardless of local filesystem state
|
|
46
32
|
|
|
47
33
|
{bold DESCRIPTION}
|
|
48
|
-
Sync
|
|
49
|
-
code
|
|
34
|
+
Sync allows you to synchronize your Gadget application's source
|
|
35
|
+
code with your local filesystem.
|
|
50
36
|
|
|
51
37
|
While ggt sync is running, local file changes are immediately
|
|
52
|
-
reflected within Gadget, while files that are changed
|
|
38
|
+
reflected within Gadget, while files that are changed in Gadget are
|
|
53
39
|
immediately saved to your local filesystem.
|
|
54
40
|
|
|
55
|
-
|
|
56
|
-
•
|
|
57
|
-
• Storing
|
|
41
|
+
Ideal for:
|
|
42
|
+
• Local development with editors like VSCode
|
|
43
|
+
• Storing source code in a Git repository like GitHub
|
|
58
44
|
|
|
59
|
-
Sync
|
|
60
|
-
|
|
61
|
-
sent to Gadget when syncing. The format of this file is identical
|
|
62
|
-
to the one used by Git {dim (https://git-scm.com/docs/gitignore)}.
|
|
45
|
+
Sync looks for a ".ignore" file to exclude certain files/directories
|
|
46
|
+
from being synced. The format is identical to Git's.
|
|
63
47
|
|
|
64
|
-
|
|
48
|
+
These files are always ignored:
|
|
65
49
|
• .DS_Store
|
|
66
50
|
• .gadget
|
|
67
51
|
• .git
|
|
68
52
|
• node_modules
|
|
69
53
|
|
|
70
54
|
Note:
|
|
71
|
-
•
|
|
72
|
-
|
|
73
|
-
• Gadget
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
• Deleting all your files
|
|
78
|
-
• Moving all your files to a different directory
|
|
55
|
+
• Sync only works with your development environment
|
|
56
|
+
• Avoid deleting/moving all your files while sync is running
|
|
57
|
+
• Gadget only supports Yarn v1 for dependency installation
|
|
58
|
+
|
|
59
|
+
{bold EXAMPLE}
|
|
60
|
+
$ ggt sync ~/gadget/example --app example
|
|
79
61
|
|
|
80
|
-
|
|
81
|
-
|
|
62
|
+
App example
|
|
63
|
+
Editor https://example.gadget.app/edit
|
|
64
|
+
Playground https://example.gadget.app/api/graphql/playground
|
|
65
|
+
Docs https://docs.gadget.dev/api/example
|
|
82
66
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
67
|
+
Endpoints
|
|
68
|
+
• https://example.gadget.app
|
|
69
|
+
• https://example--development.gadget.app
|
|
70
|
+
|
|
71
|
+
Watching for file changes... {gray Press Ctrl+C to stop}
|
|
87
72
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
• https://my-app--development.gadget.app
|
|
73
|
+
→ Sent {gray 09:06:25 AM}
|
|
74
|
+
{greenBright routes/GET-hello.js + created}
|
|
91
75
|
|
|
92
|
-
|
|
76
|
+
→ Sent {gray 09:06:49 AM}
|
|
77
|
+
{blueBright routes/GET-hello.js ± updated}
|
|
93
78
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
{green ←} user/signUp/signIn.js {dim (changed)}
|
|
97
|
-
{dim 2 files in total. 2 changed, 0 deleted.}
|
|
79
|
+
← Received {gray 09:06:54 AM}
|
|
80
|
+
{blueBright routes/GET-hello.js ± updated}
|
|
98
81
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
82
|
+
← Received {gray 09:06:56 AM}
|
|
83
|
+
{redBright routes/GET-hello.js - deleted}
|
|
84
|
+
^C Stopping... {gray press Ctrl+C again to force}
|
|
102
85
|
|
|
103
|
-
|
|
104
|
-
Goodbye!
|
|
86
|
+
Goodbye!
|
|
105
87
|
`;
|
|
106
|
-
export
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
SyncStatus[SyncStatus["STOPPED"] = 3] = "STOPPED";
|
|
112
|
-
})(SyncStatus || (SyncStatus = {}));
|
|
113
|
-
export var Action;
|
|
114
|
-
(function(Action) {
|
|
115
|
-
Action["CANCEL"] = "Cancel (Ctrl+C)";
|
|
116
|
-
Action["MERGE"] = "Merge local files with remote ones";
|
|
117
|
-
Action["RESET"] = "Reset local files to remote ones";
|
|
118
|
-
})(Action || (Action = {}));
|
|
119
|
-
const argSpec = {
|
|
120
|
-
"-a": "--app",
|
|
121
|
-
"--app": AppArg,
|
|
88
|
+
export const args = {
|
|
89
|
+
"--app": {
|
|
90
|
+
type: AppArg,
|
|
91
|
+
alias: "-a"
|
|
92
|
+
},
|
|
122
93
|
"--force": Boolean,
|
|
123
|
-
"--
|
|
124
|
-
"--
|
|
125
|
-
"--file-
|
|
126
|
-
|
|
127
|
-
|
|
94
|
+
"--once": Boolean,
|
|
95
|
+
"--prefer": ConflictPreferenceArg,
|
|
96
|
+
"--file-push-delay": {
|
|
97
|
+
type: Number,
|
|
98
|
+
default: ms("100ms")
|
|
99
|
+
},
|
|
100
|
+
"--file-watch-debounce": {
|
|
101
|
+
type: Number,
|
|
102
|
+
default: ms("300ms")
|
|
103
|
+
},
|
|
104
|
+
"--file-watch-poll-interval": {
|
|
105
|
+
type: Number,
|
|
106
|
+
default: ms("3s")
|
|
107
|
+
},
|
|
108
|
+
"--file-watch-poll-timeout": {
|
|
109
|
+
type: Number,
|
|
110
|
+
default: ms("20s")
|
|
111
|
+
},
|
|
112
|
+
"--file-watch-rename-timeout": {
|
|
113
|
+
type: Number,
|
|
114
|
+
default: ms("1.25s")
|
|
115
|
+
}
|
|
128
116
|
};
|
|
129
|
-
|
|
117
|
+
/**
|
|
118
|
+
* Runs the sync process until it is stopped or an error occurs.
|
|
119
|
+
*/ export const command = async (ctx)=>{
|
|
120
|
+
const filesync = await FileSync.init({
|
|
121
|
+
user: await getUserOrLogin(),
|
|
122
|
+
dir: ctx.args._[0],
|
|
123
|
+
app: ctx.args["--app"],
|
|
124
|
+
force: ctx.args["--force"]
|
|
125
|
+
});
|
|
126
|
+
await filesync.sync({
|
|
127
|
+
preference: ctx.args["--prefer"]
|
|
128
|
+
});
|
|
129
|
+
if (ctx.args["--once"]) {
|
|
130
|
+
ctx.log.println("Done!");
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
if (!which.sync("yarn", {
|
|
134
|
+
nothrow: true
|
|
135
|
+
})) {
|
|
136
|
+
throw new YarnNotFoundError();
|
|
137
|
+
}
|
|
130
138
|
/**
|
|
131
|
-
*
|
|
132
|
-
*
|
|
133
|
-
*
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
"--file-push-delay": 100,
|
|
142
|
-
"--file-watch-debounce": 300,
|
|
143
|
-
"--file-watch-poll-interval": 3_000,
|
|
144
|
-
"--file-watch-poll-timeout": 20_000,
|
|
145
|
-
"--file-watch-rename-timeout": 1_250
|
|
146
|
-
});
|
|
147
|
-
if (!which.sync("yarn", {
|
|
148
|
-
nothrow: true
|
|
149
|
-
})) {
|
|
150
|
-
throw new YarnNotFoundError();
|
|
139
|
+
* A list of filepaths that have changed because we (this ggt process)
|
|
140
|
+
* modified them. This is used to avoid reacting to filesystem events
|
|
141
|
+
* that we caused, which would cause an infinite loop.
|
|
142
|
+
*/ const recentWritesToLocalFilesystem = new Map();
|
|
143
|
+
const clearRecentWritesInterval = setInterval(()=>{
|
|
144
|
+
for (const [path, timestamp] of recentWritesToLocalFilesystem){
|
|
145
|
+
if (dayjs().isAfter(timestamp + ms("5s"))) {
|
|
146
|
+
// this change should have been seen by now
|
|
147
|
+
recentWritesToLocalFilesystem.delete(path);
|
|
148
|
+
}
|
|
151
149
|
}
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
150
|
+
}, ms("1s")).unref();
|
|
151
|
+
/**
|
|
152
|
+
* Subscribe to file changes on Gadget and apply them to the local
|
|
153
|
+
* filesystem.
|
|
154
|
+
*/ const unsubscribeFromGadgetChanges = filesync.subscribeToGadgetChanges({
|
|
155
|
+
onError: (error)=>ctx.abort(error),
|
|
156
|
+
beforeChanges: ({ changed, deleted })=>{
|
|
157
|
+
// add all the files and directories we're about to touch to
|
|
158
|
+
// recentWritesToLocalFilesystem so that we don't send them back
|
|
159
|
+
// to Gadget
|
|
160
|
+
for (const filepath of [
|
|
161
|
+
...changed,
|
|
162
|
+
...deleted
|
|
163
|
+
]){
|
|
164
|
+
recentWritesToLocalFilesystem.set(filepath, Date.now());
|
|
165
|
+
let dir = path.dirname(filepath);
|
|
166
|
+
while(dir !== "."){
|
|
167
|
+
recentWritesToLocalFilesystem.set(dir + "/", Date.now());
|
|
168
|
+
dir = path.dirname(dir);
|
|
171
169
|
}
|
|
172
170
|
}
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
printPaths("-", Array.from(changedFiles.keys()), [], {
|
|
188
|
-
limit: changedFiles.size
|
|
189
|
-
});
|
|
190
|
-
println();
|
|
171
|
+
},
|
|
172
|
+
afterChanges: async ({ changes })=>{
|
|
173
|
+
if (changes.has("yarn.lock")) {
|
|
174
|
+
await execa("yarn", [
|
|
175
|
+
"install",
|
|
176
|
+
"--check-files"
|
|
177
|
+
], {
|
|
178
|
+
cwd: filesync.directory.path
|
|
179
|
+
}).catch((error)=>{
|
|
180
|
+
ctx.log.error("yarn install failed", {
|
|
181
|
+
error
|
|
182
|
+
});
|
|
183
|
+
});
|
|
184
|
+
}
|
|
191
185
|
}
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
186
|
+
});
|
|
187
|
+
/**
|
|
188
|
+
* A buffer of local file changes to send to Gadget.
|
|
189
|
+
*/ const localChangesBuffer = new Changes();
|
|
190
|
+
/**
|
|
191
|
+
* A debounced function that sends the local file changes to Gadget.
|
|
192
|
+
*/ const sendChangesToGadget = debounce(ctx.args["--file-push-delay"], ()=>{
|
|
193
|
+
const changes = new Changes(localChangesBuffer.entries());
|
|
194
|
+
localChangesBuffer.clear();
|
|
195
|
+
filesync.sendChangesToGadget({
|
|
196
|
+
changes
|
|
197
|
+
}).catch((error)=>ctx.abort(error));
|
|
198
|
+
});
|
|
199
|
+
ctx.log.debug("watching", {
|
|
200
|
+
path: filesync.directory.path
|
|
201
|
+
});
|
|
202
|
+
/**
|
|
203
|
+
* Watches the local filesystem for changes.
|
|
204
|
+
*/ const fileWatcher = new Watcher(filesync.directory.path, {
|
|
205
|
+
// don't emit an event for every watched file on boot
|
|
206
|
+
ignoreInitial: true,
|
|
207
|
+
// don't emit changes to .gadget/ files because they're readonly (Gadget manages them)
|
|
208
|
+
ignore: (path)=>filesync.directory.relative(path).startsWith(".gadget") || filesync.directory.ignores(path),
|
|
209
|
+
renameDetection: true,
|
|
210
|
+
recursive: true,
|
|
211
|
+
debounce: ctx.args["--file-watch-debounce"],
|
|
212
|
+
pollingInterval: ctx.args["--file-watch-poll-interval"],
|
|
213
|
+
pollingTimeout: ctx.args["--file-watch-poll-timeout"],
|
|
214
|
+
renameTimeout: ctx.args["--file-watch-rename-timeout"]
|
|
215
|
+
}, (event, absolutePath, renamedPath)=>{
|
|
216
|
+
const filepath = event === "rename" || event === "renameDir" ? renamedPath : absolutePath;
|
|
217
|
+
const isDirectory = event === "renameDir" || event === "addDir" || event === "unlinkDir";
|
|
218
|
+
const normalizedPath = filesync.directory.normalize(filepath, isDirectory);
|
|
219
|
+
ctx.log.trace("file event", {
|
|
220
|
+
event,
|
|
221
|
+
isDirectory,
|
|
222
|
+
path: normalizedPath
|
|
223
|
+
});
|
|
224
|
+
if (filepath === filesync.directory.absolute(".ignore")) {
|
|
225
|
+
filesync.directory.loadIgnoreFile().catch((error)=>ctx.abort(error));
|
|
226
|
+
} else if (filesync.directory.ignores(filepath)) {
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
if (recentWritesToLocalFilesystem.delete(normalizedPath)) {
|
|
230
|
+
ctx.log.trace("ignoring event because we caused it", {
|
|
231
|
+
event,
|
|
232
|
+
path: normalizedPath
|
|
201
233
|
});
|
|
234
|
+
return;
|
|
202
235
|
}
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
236
|
+
switch(event){
|
|
237
|
+
case "add":
|
|
238
|
+
case "addDir":
|
|
239
|
+
localChangesBuffer.set(normalizedPath, {
|
|
240
|
+
type: "create"
|
|
241
|
+
});
|
|
242
|
+
break;
|
|
243
|
+
case "rename":
|
|
244
|
+
case "renameDir":
|
|
207
245
|
{
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
changed: Array.from(changedFiles.keys())
|
|
213
|
-
});
|
|
214
|
-
// We purposefully don't write the returned files version here
|
|
215
|
-
// because we haven't received its associated files yet. This
|
|
216
|
-
// will cause us to receive the remote files that have changed
|
|
217
|
-
// since the last sync (+ the local files that we just
|
|
218
|
-
// published)
|
|
219
|
-
await this.graphql.query({
|
|
220
|
-
query: PUBLISH_FILE_SYNC_EVENTS_MUTATION,
|
|
221
|
-
variables: {
|
|
222
|
-
input: {
|
|
223
|
-
expectedRemoteFilesVersion: remoteFilesVersion,
|
|
224
|
-
changed: await pMap(changedFiles, async ([normalizedPath, stats])=>({
|
|
225
|
-
path: normalizedPath,
|
|
226
|
-
mode: stats.mode,
|
|
227
|
-
content: stats.isDirectory() ? "" : await fs.readFile(this.filesync.absolute(normalizedPath), "base64"),
|
|
228
|
-
encoding: FileSyncEncoding.Base64
|
|
229
|
-
})),
|
|
230
|
-
deleted: []
|
|
231
|
-
}
|
|
232
|
-
}
|
|
246
|
+
const oldNormalizedPath = filesync.directory.normalize(absolutePath, isDirectory);
|
|
247
|
+
localChangesBuffer.set(normalizedPath, {
|
|
248
|
+
type: "create",
|
|
249
|
+
oldPath: oldNormalizedPath
|
|
233
250
|
});
|
|
234
251
|
break;
|
|
235
252
|
}
|
|
236
|
-
case "
|
|
253
|
+
case "change":
|
|
237
254
|
{
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
hasRemoteChanges,
|
|
241
|
-
hasLocalChanges,
|
|
242
|
-
changed: Array.from(changedFiles.keys())
|
|
255
|
+
localChangesBuffer.set(normalizedPath, {
|
|
256
|
+
type: "update"
|
|
243
257
|
});
|
|
244
|
-
// delete all the local files that have changed since the last
|
|
245
|
-
// sync and set the files version to 0 so we receive all the
|
|
246
|
-
// remote files again, including any files that we just deleted
|
|
247
|
-
// that still exist
|
|
248
|
-
await this.filesync.write(0n, [], changedFiles.keys(), true);
|
|
249
258
|
break;
|
|
250
259
|
}
|
|
251
|
-
case "
|
|
260
|
+
case "unlink":
|
|
261
|
+
case "unlinkDir":
|
|
252
262
|
{
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
/**
|
|
258
|
-
* Runs the sync process until it is stopped or an error occurs.
|
|
259
|
-
*/ async run() {
|
|
260
|
-
let error;
|
|
261
|
-
const stopped = new PromiseSignal();
|
|
262
|
-
const recentRemoteChangesInterval = setInterval(()=>{
|
|
263
|
-
for (const [path, timestamp] of this.recentRemoteChanges){
|
|
264
|
-
if (dayjs().isAfter(timestamp + ms("5s"))) {
|
|
265
|
-
// this change should have been seen by now, so remove it
|
|
266
|
-
this.recentRemoteChanges.delete(path);
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
}, ms("1s")).unref();
|
|
270
|
-
this.stop = async (e)=>{
|
|
271
|
-
if (this.status !== 1) {
|
|
272
|
-
return;
|
|
273
|
-
}
|
|
274
|
-
this.status = 2;
|
|
275
|
-
error = e;
|
|
276
|
-
this.log.info("stopping", {
|
|
277
|
-
error
|
|
278
|
-
});
|
|
279
|
-
try {
|
|
280
|
-
clearInterval(recentRemoteChangesInterval);
|
|
281
|
-
unsubscribe();
|
|
282
|
-
this.watcher.removeAllListeners();
|
|
283
|
-
this.publish.flush();
|
|
284
|
-
await this.queue.onIdle();
|
|
285
|
-
} finally{
|
|
286
|
-
await Promise.allSettled([
|
|
287
|
-
this.watcher.close(),
|
|
288
|
-
this.graphql.dispose()
|
|
289
|
-
]);
|
|
290
|
-
this.status = 3;
|
|
291
|
-
stopped.resolve();
|
|
292
|
-
this.log.info("stopped");
|
|
293
|
-
}
|
|
294
|
-
};
|
|
295
|
-
for (const signal of [
|
|
296
|
-
"SIGINT",
|
|
297
|
-
"SIGTERM"
|
|
298
|
-
]){
|
|
299
|
-
process.on(signal, ()=>{
|
|
300
|
-
if (this.status !== 1) {
|
|
301
|
-
return;
|
|
302
|
-
}
|
|
303
|
-
println` Stopping... {gray (press Ctrl+C again to force)}`;
|
|
304
|
-
void this.stop();
|
|
305
|
-
// When ggt is run via npx, and the user presses Ctrl+C, npx sends SIGINT twice in quick succession. In order to prevent the second
|
|
306
|
-
// SIGINT from triggering the force exit listener, we wait a bit before registering it. This is a bit of a hack, but it works.
|
|
307
|
-
setTimeout(()=>{
|
|
308
|
-
process.once(signal, ()=>{
|
|
309
|
-
println(" Exiting immediately. Note that files may not have finished syncing.");
|
|
310
|
-
process.exit(1);
|
|
263
|
+
localChangesBuffer.set(normalizedPath, {
|
|
264
|
+
type: "delete"
|
|
311
265
|
});
|
|
312
|
-
|
|
313
|
-
});
|
|
314
|
-
}
|
|
315
|
-
const unsubscribe = this.graphql.subscribe({
|
|
316
|
-
query: REMOTE_FILE_SYNC_EVENTS_SUBSCRIPTION,
|
|
317
|
-
variables: ()=>({
|
|
318
|
-
localFilesVersion: String(this.filesync.filesVersion)
|
|
319
|
-
})
|
|
320
|
-
}, {
|
|
321
|
-
error: (error)=>void this.stop(error),
|
|
322
|
-
next: ({ remoteFileSyncEvents })=>{
|
|
323
|
-
const remoteFilesVersion = remoteFileSyncEvents.remoteFilesVersion;
|
|
324
|
-
// we always ignore .gadget/ files so that we don't publish them (they're managed by gadget), but we still want to receive them
|
|
325
|
-
const filterIgnored = (event)=>event.path.startsWith(".gadget/") || !this.filesync.ignores(event.path);
|
|
326
|
-
const changed = remoteFileSyncEvents.changed.filter(filterIgnored);
|
|
327
|
-
const deleted = remoteFileSyncEvents.deleted.filter(filterIgnored);
|
|
328
|
-
this.log.info("received files", {
|
|
329
|
-
remoteFilesVersion,
|
|
330
|
-
changed: changed.map((x)=>x.path),
|
|
331
|
-
deleted: deleted.map((x)=>x.path)
|
|
332
|
-
});
|
|
333
|
-
this._enqueue(async ()=>{
|
|
334
|
-
// add all the non-ignored files and directories we're about
|
|
335
|
-
// to touch to recentRemoteChanges so that we don't send
|
|
336
|
-
// them back
|
|
337
|
-
for (const file of [
|
|
338
|
-
...changed,
|
|
339
|
-
...deleted
|
|
340
|
-
].filter((file)=>!this.filesync.ignores(file.path))){
|
|
341
|
-
this.recentRemoteChanges.set(file.path, Date.now());
|
|
342
|
-
let dir = path.dirname(file.path);
|
|
343
|
-
while(dir !== "."){
|
|
344
|
-
this.recentRemoteChanges.set(dir + "/", Date.now());
|
|
345
|
-
dir = path.dirname(dir);
|
|
346
|
-
}
|
|
347
|
-
}
|
|
348
|
-
if (changed.length > 0 || deleted.length > 0) {
|
|
349
|
-
println`Received {gray ${dayjs().format("hh:mm:ss A")}}`;
|
|
350
|
-
printPaths("←", changed.map((x)=>x.path), deleted.map((x)=>x.path));
|
|
351
|
-
}
|
|
352
|
-
await this.filesync.write(remoteFilesVersion, changed, deleted.map((x)=>x.path));
|
|
353
|
-
if (changed.some((x)=>x.path === "yarn.lock")) {
|
|
354
|
-
await execa("yarn", [
|
|
355
|
-
"install"
|
|
356
|
-
], {
|
|
357
|
-
cwd: this.filesync.dir
|
|
358
|
-
}).catch(noop);
|
|
359
|
-
}
|
|
360
|
-
});
|
|
361
|
-
}
|
|
362
|
-
});
|
|
363
|
-
const localFilesBuffer = new Map();
|
|
364
|
-
this.publish = debounce(this.args["--file-push-delay"], ()=>{
|
|
365
|
-
const localFiles = new Map(localFilesBuffer.entries());
|
|
366
|
-
localFilesBuffer.clear();
|
|
367
|
-
this._enqueue(async ()=>{
|
|
368
|
-
const changed = [];
|
|
369
|
-
const deleted = [];
|
|
370
|
-
await pMap(localFiles, async ([normalizedPath, file])=>{
|
|
371
|
-
if ("isDeleted" in file) {
|
|
372
|
-
deleted.push({
|
|
373
|
-
path: normalizedPath
|
|
374
|
-
});
|
|
375
|
-
return;
|
|
376
|
-
}
|
|
377
|
-
try {
|
|
378
|
-
changed.push({
|
|
379
|
-
path: normalizedPath,
|
|
380
|
-
oldPath: "oldPath" in file ? file.oldPath : undefined,
|
|
381
|
-
mode: file.mode,
|
|
382
|
-
content: file.isDirectory ? "" : await fs.readFile(this.filesync.absolute(normalizedPath), FileSyncEncoding.Base64),
|
|
383
|
-
encoding: FileSyncEncoding.Base64
|
|
384
|
-
});
|
|
385
|
-
} catch (error) {
|
|
386
|
-
// A file could have been changed and then deleted before we process the change event, so the readFile
|
|
387
|
-
// above will raise an ENOENT. This is normal operation, so just ignore this event.
|
|
388
|
-
swallowEnoent(error);
|
|
389
|
-
}
|
|
390
|
-
});
|
|
391
|
-
if (changed.length === 0 && deleted.length === 0) {
|
|
392
|
-
return;
|
|
266
|
+
break;
|
|
393
267
|
}
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
changed,
|
|
400
|
-
deleted
|
|
401
|
-
}
|
|
402
|
-
}
|
|
403
|
-
});
|
|
404
|
-
await this.filesync.write(publishFileSyncEvents.remoteFilesVersion, [], []);
|
|
405
|
-
println`Sent {gray ${dayjs().format("hh:mm:ss A")}}`;
|
|
406
|
-
printPaths("→", changed.map((x)=>x.path), deleted.map((x)=>x.path));
|
|
407
|
-
});
|
|
408
|
-
});
|
|
409
|
-
this.watcher = new FSWatcher(this.filesync.dir, {
|
|
410
|
-
// don't emit an event for every watched file on boot
|
|
411
|
-
ignoreInitial: true,
|
|
412
|
-
ignore: (path)=>this.filesync.ignores(path),
|
|
413
|
-
renameDetection: true,
|
|
414
|
-
recursive: true,
|
|
415
|
-
debounce: this.args["--file-watch-debounce"],
|
|
416
|
-
pollingInterval: this.args["--file-watch-poll-interval"],
|
|
417
|
-
pollingTimeout: this.args["--file-watch-poll-timeout"],
|
|
418
|
-
renameTimeout: this.args["--file-watch-rename-timeout"]
|
|
419
|
-
});
|
|
420
|
-
this.watcher.once("error", (error)=>void this.stop(error));
|
|
421
|
-
this.watcher.on("all", (event, absolutePath, renamedPath)=>{
|
|
422
|
-
const filepath = event === "rename" || event === "renameDir" ? renamedPath : absolutePath;
|
|
423
|
-
const isDirectory = event === "renameDir" || event === "addDir" || event === "unlinkDir";
|
|
424
|
-
const normalizedPath = this.filesync.normalize(filepath, isDirectory);
|
|
425
|
-
this.log.debug("file event", {
|
|
426
|
-
event,
|
|
427
|
-
path: normalizedPath,
|
|
428
|
-
isDirectory,
|
|
429
|
-
recentRemoteChanges: Array.from(this.recentRemoteChanges.keys())
|
|
430
|
-
});
|
|
431
|
-
if (filepath === this.filesync.absolute(".ignore")) {
|
|
432
|
-
this.filesync.reloadIgnorePaths();
|
|
433
|
-
} else if (this.filesync.ignores(filepath)) {
|
|
434
|
-
return;
|
|
435
|
-
}
|
|
436
|
-
if (this.recentRemoteChanges.delete(normalizedPath)) {
|
|
437
|
-
return;
|
|
438
|
-
}
|
|
439
|
-
switch(event){
|
|
440
|
-
case "add":
|
|
441
|
-
case "addDir":
|
|
442
|
-
case "change":
|
|
443
|
-
{
|
|
444
|
-
const stats = fs.statSync(filepath);
|
|
445
|
-
localFilesBuffer.set(normalizedPath, {
|
|
446
|
-
mode: stats.mode,
|
|
447
|
-
isDirectory
|
|
448
|
-
});
|
|
449
|
-
break;
|
|
450
|
-
}
|
|
451
|
-
case "unlink":
|
|
452
|
-
case "unlinkDir":
|
|
453
|
-
{
|
|
454
|
-
localFilesBuffer.set(normalizedPath, {
|
|
455
|
-
isDeleted: true,
|
|
456
|
-
isDirectory
|
|
457
|
-
});
|
|
458
|
-
break;
|
|
459
|
-
}
|
|
460
|
-
case "rename":
|
|
461
|
-
case "renameDir":
|
|
462
|
-
{
|
|
463
|
-
const stats = fs.statSync(filepath);
|
|
464
|
-
localFilesBuffer.set(normalizedPath, {
|
|
465
|
-
oldPath: this.filesync.normalize(absolutePath, isDirectory),
|
|
466
|
-
newPath: normalizedPath,
|
|
467
|
-
isDirectory,
|
|
468
|
-
mode: stats.mode
|
|
469
|
-
});
|
|
470
|
-
break;
|
|
471
|
-
}
|
|
472
|
-
}
|
|
473
|
-
this.publish();
|
|
474
|
-
});
|
|
475
|
-
this.status = 1;
|
|
476
|
-
println();
|
|
477
|
-
println`
|
|
478
|
-
{bold ggt v${config.version}}
|
|
268
|
+
}
|
|
269
|
+
sendChangesToGadget();
|
|
270
|
+
}).once("error", (error)=>ctx.abort(error));
|
|
271
|
+
ctx.log.printlns`
|
|
272
|
+
ggt v${config.version}
|
|
479
273
|
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
274
|
+
App ${filesync.app.slug}
|
|
275
|
+
Editor https://${filesync.app.slug}.gadget.app/edit
|
|
276
|
+
Playground https://${filesync.app.slug}.gadget.app/api/graphql/playground
|
|
277
|
+
Docs https://docs.gadget.dev/api/${filesync.app.slug}
|
|
484
278
|
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
279
|
+
Endpoints ${filesync.app.hasSplitEnvironments ? `
|
|
280
|
+
• https://${filesync.app.primaryDomain}
|
|
281
|
+
• https://${filesync.app.slug}--development.gadget.app` : `
|
|
282
|
+
• https://${filesync.app.primaryDomain}`}
|
|
489
283
|
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
284
|
+
Watching for file changes... {gray Press Ctrl+C to stop}
|
|
285
|
+
`;
|
|
286
|
+
ctx.onAbort(async (reason)=>{
|
|
287
|
+
ctx.log.info("stopping", {
|
|
288
|
+
reason
|
|
289
|
+
});
|
|
290
|
+
unsubscribeFromGadgetChanges();
|
|
291
|
+
fileWatcher.close();
|
|
292
|
+
clearInterval(clearRecentWritesInterval);
|
|
293
|
+
sendChangesToGadget.flush();
|
|
294
|
+
try {
|
|
295
|
+
await filesync.idle();
|
|
296
|
+
} catch (error) {
|
|
297
|
+
ctx.log.error("error while waiting for idle", {
|
|
298
|
+
error
|
|
498
299
|
});
|
|
499
|
-
throw error;
|
|
500
|
-
} else {
|
|
501
|
-
println("Goodbye!");
|
|
502
300
|
}
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
* The current status of the sync process.
|
|
515
|
-
*/ _define_property(this, "status", 0);
|
|
516
|
-
/**
|
|
517
|
-
* A list of filepaths that have changed because of a remote file-sync
|
|
518
|
-
* event. This is used to avoid sending files that we recently
|
|
519
|
-
* received from a remote file-sync event.
|
|
520
|
-
*/ _define_property(this, "recentRemoteChanges", new Map());
|
|
521
|
-
/**
|
|
522
|
-
* A FIFO async callback queue that ensures we process file-sync events in the order they occurred.
|
|
523
|
-
*/ _define_property(this, "queue", new PQueue({
|
|
524
|
-
concurrency: 1
|
|
525
|
-
}));
|
|
526
|
-
/**
|
|
527
|
-
* A GraphQL client connected to the app's /edit/api/graphql-ws endpoint
|
|
528
|
-
*/ _define_property(this, "graphql", void 0);
|
|
529
|
-
/**
|
|
530
|
-
* Watches the local filesystem for changes.
|
|
531
|
-
*/ _define_property(this, "watcher", void 0);
|
|
532
|
-
/**
|
|
533
|
-
* Handles writing files to the local filesystem.
|
|
534
|
-
*/ _define_property(this, "filesync", void 0);
|
|
535
|
-
/**
|
|
536
|
-
* A debounced function that enqueue's local file changes to be sent to Gadget.
|
|
537
|
-
*/ _define_property(this, "publish", void 0);
|
|
538
|
-
/**
|
|
539
|
-
* Gracefully stops the sync.
|
|
540
|
-
*/ _define_property(this, "stop", void 0);
|
|
541
|
-
/**
|
|
542
|
-
* A logger for the sync command.
|
|
543
|
-
*/ _define_property(this, "log", createLogger("sync", ()=>{
|
|
544
|
-
return {
|
|
545
|
-
app: this.filesync.app.slug,
|
|
546
|
-
filesVersion: String(this.filesync.filesVersion),
|
|
547
|
-
mtime: this.filesync.mtime
|
|
548
|
-
};
|
|
549
|
-
}));
|
|
550
|
-
}
|
|
551
|
-
}
|
|
552
|
-
const sync = new Sync();
|
|
553
|
-
export const init = sync.init.bind(sync);
|
|
554
|
-
export const run = sync.run.bind(sync);
|
|
301
|
+
if (isAbortError(reason)) {
|
|
302
|
+
ctx.log.printlns("Goodbye!");
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
notify({
|
|
306
|
+
subtitle: "Uh oh!",
|
|
307
|
+
message: "An error occurred while syncing files"
|
|
308
|
+
});
|
|
309
|
+
await reportErrorAndExit(reason);
|
|
310
|
+
});
|
|
311
|
+
};
|
|
555
312
|
|
|
556
313
|
//# sourceMappingURL=sync.js.map
|