@gadgetinc/ggt 0.4.9 → 1.0.0
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 +165 -93
- package/lib/__generated__/graphql.js +66 -1
- package/lib/__generated__/graphql.js.map +1 -1
- package/lib/commands/deploy.js +329 -184
- package/lib/commands/deploy.js.map +1 -1
- package/lib/commands/dev.js +445 -0
- package/lib/commands/dev.js.map +1 -0
- package/lib/commands/list.js +27 -19
- package/lib/commands/list.js.map +1 -1
- package/lib/commands/login.js +15 -11
- package/lib/commands/login.js.map +1 -1
- package/lib/commands/logout.js +5 -5
- package/lib/commands/logout.js.map +1 -1
- package/lib/commands/open.js +200 -0
- package/lib/commands/open.js.map +1 -0
- package/lib/commands/pull.js +128 -0
- package/lib/commands/pull.js.map +1 -0
- package/lib/commands/push.js +126 -0
- package/lib/commands/push.js.map +1 -0
- package/lib/commands/root.js +46 -28
- package/lib/commands/root.js.map +1 -1
- package/lib/commands/status.js +61 -0
- package/lib/commands/status.js.map +1 -0
- package/lib/commands/version.js +6 -6
- package/lib/commands/version.js.map +1 -1
- package/lib/commands/whoami.js +6 -6
- package/lib/commands/whoami.js.map +1 -1
- package/lib/ggt.js +33 -8
- package/lib/ggt.js.map +1 -1
- package/lib/main.js +5 -0
- package/lib/main.js.map +1 -0
- package/lib/services/app/api/api.js +191 -0
- package/lib/services/app/api/api.js.map +1 -0
- package/lib/services/app/api/operation.js +12 -0
- package/lib/services/app/api/operation.js.map +1 -0
- package/lib/services/app/app.js +44 -10
- package/lib/services/app/app.js.map +1 -1
- package/lib/services/app/{edit/client.js → client.js} +29 -19
- package/lib/services/app/client.js.map +1 -0
- package/lib/services/app/edit/edit.js +67 -31
- package/lib/services/app/edit/edit.js.map +1 -1
- package/lib/services/app/edit/operation.js +19 -3
- package/lib/services/app/edit/operation.js.map +1 -1
- package/lib/services/app/{edit/error.js → error.js} +6 -6
- package/lib/services/app/error.js.map +1 -0
- package/lib/services/command/arg.js +4 -4
- package/lib/services/command/arg.js.map +1 -1
- package/lib/services/command/command.js +9 -7
- package/lib/services/command/command.js.map +1 -1
- package/lib/services/command/context.js +82 -20
- package/lib/services/command/context.js.map +1 -1
- package/lib/services/config/config.js +4 -7
- package/lib/services/config/config.js.map +1 -1
- package/lib/services/config/env.js +1 -1
- package/lib/services/config/env.js.map +1 -1
- package/lib/services/filesync/changes.js +76 -37
- package/lib/services/filesync/changes.js.map +1 -1
- package/lib/services/filesync/conflicts.js +10 -9
- package/lib/services/filesync/conflicts.js.map +1 -1
- package/lib/services/filesync/directory.js +16 -1
- package/lib/services/filesync/directory.js.map +1 -1
- package/lib/services/filesync/error.js +96 -27
- package/lib/services/filesync/error.js.map +1 -1
- package/lib/services/filesync/filesync.js +448 -472
- package/lib/services/filesync/filesync.js.map +1 -1
- package/lib/services/filesync/hashes.js +8 -5
- package/lib/services/filesync/hashes.js.map +1 -1
- package/lib/services/filesync/strategy.js +59 -0
- package/lib/services/filesync/strategy.js.map +1 -0
- package/lib/services/filesync/sync-json.js +475 -0
- package/lib/services/filesync/sync-json.js.map +1 -0
- package/lib/services/http/auth.js +30 -1
- package/lib/services/http/auth.js.map +1 -1
- package/lib/services/http/http.js +5 -0
- package/lib/services/http/http.js.map +1 -1
- package/lib/services/output/confirm.js +149 -0
- package/lib/services/output/confirm.js.map +1 -0
- package/lib/services/output/footer.js +22 -0
- package/lib/services/output/footer.js.map +1 -0
- package/lib/services/output/log/format/pretty.js +2 -1
- package/lib/services/output/log/format/pretty.js.map +1 -1
- package/lib/services/output/log/logger.js +13 -5
- package/lib/services/output/log/logger.js.map +1 -1
- package/lib/services/output/log/structured.js +2 -2
- package/lib/services/output/log/structured.js.map +1 -1
- package/lib/services/output/output.js +197 -0
- package/lib/services/output/output.js.map +1 -0
- package/lib/services/output/print.js +31 -0
- package/lib/services/output/print.js.map +1 -0
- package/lib/services/output/problems.js +84 -0
- package/lib/services/output/problems.js.map +1 -0
- package/lib/services/output/prompt.js +173 -40
- package/lib/services/output/prompt.js.map +1 -1
- package/lib/services/output/report.js +63 -19
- package/lib/services/output/report.js.map +1 -1
- package/lib/services/output/select.js +198 -0
- package/lib/services/output/select.js.map +1 -0
- package/lib/services/output/spinner.js +141 -0
- package/lib/services/output/spinner.js.map +1 -0
- package/lib/services/output/sprint.js +38 -15
- package/lib/services/output/sprint.js.map +1 -1
- package/lib/services/output/symbols.js +23 -0
- package/lib/services/output/symbols.js.map +1 -0
- package/lib/services/output/table.js +98 -0
- package/lib/services/output/table.js.map +1 -0
- package/lib/services/output/timestamp.js +12 -0
- package/lib/services/output/timestamp.js.map +1 -0
- package/lib/services/output/update.js +29 -9
- package/lib/services/output/update.js.map +1 -1
- package/lib/services/user/session.js +4 -0
- package/lib/services/user/session.js.map +1 -1
- package/lib/services/user/user.js +15 -10
- package/lib/services/user/user.js.map +1 -1
- package/lib/services/util/assert.js +11 -0
- package/lib/services/util/assert.js.map +1 -0
- package/lib/services/util/boolean.js +2 -2
- package/lib/services/util/boolean.js.map +1 -1
- package/lib/services/util/function.js +45 -7
- package/lib/services/util/function.js.map +1 -1
- package/lib/services/util/is.js +23 -2
- package/lib/services/util/is.js.map +1 -1
- package/lib/services/util/json.js +16 -13
- package/lib/services/util/json.js.map +1 -1
- package/lib/services/util/object.js +2 -2
- package/lib/services/util/object.js.map +1 -1
- package/lib/services/util/promise.js +5 -2
- package/lib/services/util/promise.js.map +1 -1
- package/lib/services/util/types.js.map +1 -1
- package/npm-shrinkwrap.json +3436 -2833
- package/package.json +47 -40
- package/bin/dev.cmd +0 -3
- package/bin/dev.js +0 -14
- package/bin/run.cmd +0 -3
- package/bin/run.js +0 -5
- package/lib/commands/sync.js +0 -284
- package/lib/commands/sync.js.map +0 -1
- package/lib/services/app/edit/client.js.map +0 -1
- package/lib/services/app/edit/error.js.map +0 -1
- package/lib/services/output/log/printer.js +0 -120
- package/lib/services/output/log/printer.js.map +0 -1
- package/lib/services/output/stream.js +0 -54
- package/lib/services/output/stream.js.map +0 -1
|
@@ -0,0 +1,445 @@
|
|
|
1
|
+
import dayjs from "dayjs";
|
|
2
|
+
import ms from "ms";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import Watcher from "watcher";
|
|
5
|
+
import which from "which";
|
|
6
|
+
import { Changes } from "../services/filesync/changes.js";
|
|
7
|
+
import { YarnNotFoundError } from "../services/filesync/error.js";
|
|
8
|
+
import { FileSync } from "../services/filesync/filesync.js";
|
|
9
|
+
import { FileSyncStrategy, MergeConflictPreferenceArg } from "../services/filesync/strategy.js";
|
|
10
|
+
import { SyncJson, SyncJsonArgs, loadSyncJsonDirectory } from "../services/filesync/sync-json.js";
|
|
11
|
+
import { footer } from "../services/output/footer.js";
|
|
12
|
+
import { notify } from "../services/output/notify.js";
|
|
13
|
+
import { println } from "../services/output/print.js";
|
|
14
|
+
import { reportErrorAndExit } from "../services/output/report.js";
|
|
15
|
+
import { select } from "../services/output/select.js";
|
|
16
|
+
import { spin } from "../services/output/spinner.js";
|
|
17
|
+
import { sprint } from "../services/output/sprint.js";
|
|
18
|
+
import { symbol } from "../services/output/symbols.js";
|
|
19
|
+
import { unreachable } from "../services/util/assert.js";
|
|
20
|
+
import { debounceAsync } from "../services/util/function.js";
|
|
21
|
+
import { isAbortError } from "../services/util/is.js";
|
|
22
|
+
import { delay } from "../services/util/promise.js";
|
|
23
|
+
export const args = {
|
|
24
|
+
...SyncJsonArgs,
|
|
25
|
+
"--prefer": MergeConflictPreferenceArg,
|
|
26
|
+
"--file-push-delay": {
|
|
27
|
+
type: Number,
|
|
28
|
+
default: ms("100ms")
|
|
29
|
+
},
|
|
30
|
+
"--file-watch-debounce": {
|
|
31
|
+
type: Number,
|
|
32
|
+
default: ms("300ms")
|
|
33
|
+
},
|
|
34
|
+
"--file-watch-poll-interval": {
|
|
35
|
+
type: Number,
|
|
36
|
+
default: ms("3s")
|
|
37
|
+
},
|
|
38
|
+
"--file-watch-poll-timeout": {
|
|
39
|
+
type: Number,
|
|
40
|
+
default: ms("20s")
|
|
41
|
+
},
|
|
42
|
+
"--file-watch-rename-timeout": {
|
|
43
|
+
type: Number,
|
|
44
|
+
default: ms("1.25s")
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
export const usage = (ctx)=>{
|
|
48
|
+
if (ctx.args["-h"]) {
|
|
49
|
+
return sprint`
|
|
50
|
+
Develop your app by synchronizing your local files with your
|
|
51
|
+
environment's files, in real-time. Changes are tracked from
|
|
52
|
+
the last "ggt dev", "ggt push", or "ggt pull" run locally.
|
|
53
|
+
|
|
54
|
+
{bold USAGE}
|
|
55
|
+
ggt dev [DIRECTORY]
|
|
56
|
+
|
|
57
|
+
{bold EXAMPLES}
|
|
58
|
+
$ ggt dev
|
|
59
|
+
$ ggt dev ~/gadget/example
|
|
60
|
+
$ ggt dev ~/gadget/example
|
|
61
|
+
$ ggt dev ~/gadget/example --app=example
|
|
62
|
+
$ ggt dev ~/gadget/example --app=example --env=development --prefer=local
|
|
63
|
+
|
|
64
|
+
{bold ARGUMENTS}
|
|
65
|
+
DIRECTORY The directory to synchronize files to (default: ".")
|
|
66
|
+
|
|
67
|
+
{bold FLAGS}
|
|
68
|
+
-a, --app=<name> The application to synchronize files with
|
|
69
|
+
-e, --env=<name> The environment to synchronize files with
|
|
70
|
+
--prefer=<filesystem> Prefer "local" or "environment" conflicting changes
|
|
71
|
+
|
|
72
|
+
Run "ggt dev --help" for more information.
|
|
73
|
+
`;
|
|
74
|
+
}
|
|
75
|
+
return sprint`
|
|
76
|
+
Develop your app by synchronizing your local files with your
|
|
77
|
+
environment's files, in real-time. Changes are tracked from
|
|
78
|
+
the last "ggt dev", "ggt push", or "ggt pull" run locally.
|
|
79
|
+
|
|
80
|
+
While "ggt dev" is running, changes on your local filesystem are
|
|
81
|
+
immediately pushed to your environment, while file changes on
|
|
82
|
+
your environment are immediately pulled to your local filesystem.
|
|
83
|
+
|
|
84
|
+
If conflicting changes are detected, and "--prefer" is not passed,
|
|
85
|
+
you will be prompted to choose which changes to keep before
|
|
86
|
+
"ggt dev" resumes.
|
|
87
|
+
|
|
88
|
+
"ggt dev" looks for an ".ignore" file to exclude files and
|
|
89
|
+
directories from being pushed or pulled. The format is identical
|
|
90
|
+
to Git's.
|
|
91
|
+
|
|
92
|
+
The following files and directories are always ignored:
|
|
93
|
+
• .DS_Store
|
|
94
|
+
• .gadget
|
|
95
|
+
• .git
|
|
96
|
+
• node_modules
|
|
97
|
+
|
|
98
|
+
Note:
|
|
99
|
+
• "ggt dev" only works with development environments
|
|
100
|
+
• "ggt dev" only supports "yarn" v1 for installing dependencies
|
|
101
|
+
• Avoid deleting or moving all of your files while "ggt dev" is running
|
|
102
|
+
|
|
103
|
+
{bold USAGE}
|
|
104
|
+
|
|
105
|
+
ggt dev [DIRECTORY] [--app=<name>] [--env=<name>] [--prefer=<filesystem>]
|
|
106
|
+
[--allow-unknown-directory] [--allow-different-app]
|
|
107
|
+
|
|
108
|
+
{bold EXAMPLES}
|
|
109
|
+
|
|
110
|
+
$ ggt dev
|
|
111
|
+
$ ggt dev ~/gadget/example
|
|
112
|
+
$ ggt dev ~/gadget/example
|
|
113
|
+
$ ggt dev ~/gadget/example --app=example
|
|
114
|
+
$ ggt dev ~/gadget/example --app=example --env=development --prefer=local
|
|
115
|
+
|
|
116
|
+
{bold ARGUMENTS}
|
|
117
|
+
|
|
118
|
+
DIRECTORY
|
|
119
|
+
The path to the directory to synchronize files to.
|
|
120
|
+
The directory will be created if it does not exist.
|
|
121
|
+
|
|
122
|
+
Defaults to the current working directory. (default: ".")
|
|
123
|
+
|
|
124
|
+
{bold FLAGS}
|
|
125
|
+
|
|
126
|
+
-a, --app, --application=<name>
|
|
127
|
+
The application to synchronize files with.
|
|
128
|
+
|
|
129
|
+
Defaults to the application within the ".gadget/sync.json"
|
|
130
|
+
file in the chosen directory or any parent directories.
|
|
131
|
+
|
|
132
|
+
-e, --env, --environment=<name>
|
|
133
|
+
The development environment to synchronize files with.
|
|
134
|
+
|
|
135
|
+
Defaults to the environment within the ".gadget/sync.json"
|
|
136
|
+
file in the chosen directory or any parent directories.
|
|
137
|
+
|
|
138
|
+
--prefer=<filesystem>
|
|
139
|
+
Which filesystem's changes to automatically keep when
|
|
140
|
+
conflicting changes are detected.
|
|
141
|
+
|
|
142
|
+
Must be one of "local" or "environment".
|
|
143
|
+
|
|
144
|
+
If not provided, "ggt dev" will pause when conflicting changes
|
|
145
|
+
are detected and you will be prompted to choose which changes to
|
|
146
|
+
keep before "ggt dev" resumes.
|
|
147
|
+
|
|
148
|
+
--allow-unknown-directory
|
|
149
|
+
Allows "ggt dev" to continue when the chosen directory, nor
|
|
150
|
+
any parent directories, contain a ".gadget/sync.json" file
|
|
151
|
+
within it.
|
|
152
|
+
|
|
153
|
+
Defaults to false.
|
|
154
|
+
|
|
155
|
+
--allow-different-app
|
|
156
|
+
Allows "ggt dev" to continue with a different "--app" than the
|
|
157
|
+
one found within the ".gadget/sync.json" file.
|
|
158
|
+
|
|
159
|
+
Defaults to false.
|
|
160
|
+
|
|
161
|
+
Run "ggt dev -h" for less information.
|
|
162
|
+
`;
|
|
163
|
+
};
|
|
164
|
+
export const command = async (ctx)=>{
|
|
165
|
+
if (!await which("yarn", {
|
|
166
|
+
nothrow: true
|
|
167
|
+
})) {
|
|
168
|
+
throw new YarnNotFoundError();
|
|
169
|
+
}
|
|
170
|
+
const directory = await loadSyncJsonDirectory(ctx.args._[0] || process.cwd());
|
|
171
|
+
const syncJson = await SyncJson.loadOrInit(ctx, {
|
|
172
|
+
directory
|
|
173
|
+
});
|
|
174
|
+
footer({
|
|
175
|
+
ensureEmptyLineAbove: true
|
|
176
|
+
})(syncJson.sprint());
|
|
177
|
+
const filesync = new FileSync(syncJson);
|
|
178
|
+
const hashes = await filesync.hashes(ctx);
|
|
179
|
+
if (!hashes.inSync) {
|
|
180
|
+
// our local files don't match our environment's files
|
|
181
|
+
if (!syncJson.previousEnvironment || hashes.localChangesToPush.size === 0 && hashes.onlyDotGadgetFilesChanged) {
|
|
182
|
+
// one of the following is true:
|
|
183
|
+
// - we're developing on this environment for the first time
|
|
184
|
+
// - we're developing on the same environment as last time
|
|
185
|
+
// - we're developing on a different environment, but only .gadget/ files have changed
|
|
186
|
+
// merge the changes (if any) and continue
|
|
187
|
+
await filesync.merge(ctx, {
|
|
188
|
+
hashes,
|
|
189
|
+
printLocalChangesOptions: {
|
|
190
|
+
limit: 5
|
|
191
|
+
},
|
|
192
|
+
printEnvironmentChangesOptions: {
|
|
193
|
+
limit: 5
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
} else {
|
|
197
|
+
// we're switching environment's and files outside of .gadget/
|
|
198
|
+
// have changed, so ask the user what to do
|
|
199
|
+
await filesync.print(ctx, {
|
|
200
|
+
hashes
|
|
201
|
+
});
|
|
202
|
+
const choices = Object.values(FileSyncStrategy);
|
|
203
|
+
const strategy = await select({
|
|
204
|
+
ensureEmptyLineAbove: true,
|
|
205
|
+
choices: hashes.bothChanged ? choices : choices.filter((choice)=>choice !== FileSyncStrategy.MERGE),
|
|
206
|
+
formatChoice: (choice)=>{
|
|
207
|
+
switch(choice){
|
|
208
|
+
case FileSyncStrategy.CANCEL:
|
|
209
|
+
return sprint`Cancel (Ctrl+C)`;
|
|
210
|
+
case FileSyncStrategy.MERGE:
|
|
211
|
+
return sprint`Merge local and environment's changes`;
|
|
212
|
+
case FileSyncStrategy.PUSH:
|
|
213
|
+
switch(true){
|
|
214
|
+
case hashes.bothChanged:
|
|
215
|
+
return sprint`Push local changes and {underline discard environment's} changes`;
|
|
216
|
+
case hashes.localChanges.size > 0:
|
|
217
|
+
return sprint`Push local changes`;
|
|
218
|
+
case hashes.environmentChanges.size > 0:
|
|
219
|
+
return sprint`Discard environment's changes`;
|
|
220
|
+
default:
|
|
221
|
+
return unreachable("no changes to push or discard");
|
|
222
|
+
}
|
|
223
|
+
case FileSyncStrategy.PULL:
|
|
224
|
+
switch(true){
|
|
225
|
+
case hashes.bothChanged:
|
|
226
|
+
return sprint`Pull environment's changes and {underline discard local} changes`;
|
|
227
|
+
case hashes.localChanges.size > 0:
|
|
228
|
+
return sprint`Discard local changes`;
|
|
229
|
+
case hashes.environmentChanges.size > 0:
|
|
230
|
+
return sprint`Pull environment's changes`;
|
|
231
|
+
default:
|
|
232
|
+
return unreachable("no changes to pull or discard");
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
})`
|
|
237
|
+
{bold What do you want to do?}
|
|
238
|
+
`;
|
|
239
|
+
switch(strategy){
|
|
240
|
+
case FileSyncStrategy.CANCEL:
|
|
241
|
+
process.exit(0);
|
|
242
|
+
break;
|
|
243
|
+
case FileSyncStrategy.MERGE:
|
|
244
|
+
await filesync.merge(ctx, {
|
|
245
|
+
hashes
|
|
246
|
+
});
|
|
247
|
+
break;
|
|
248
|
+
case FileSyncStrategy.PUSH:
|
|
249
|
+
await filesync.push(ctx, {
|
|
250
|
+
hashes,
|
|
251
|
+
force: true
|
|
252
|
+
});
|
|
253
|
+
break;
|
|
254
|
+
case FileSyncStrategy.PULL:
|
|
255
|
+
await filesync.pull(ctx, {
|
|
256
|
+
hashes,
|
|
257
|
+
force: true
|
|
258
|
+
});
|
|
259
|
+
break;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* A list of filepaths that have changed because we (this ggt process)
|
|
265
|
+
* modified them. This is used to avoid reacting to filesystem events
|
|
266
|
+
* that we caused, which would cause an infinite loop.
|
|
267
|
+
*/ const recentWritesToLocalFilesystem = new Map();
|
|
268
|
+
const clearRecentWritesInterval = setInterval(()=>{
|
|
269
|
+
for (const [path, timestamp] of recentWritesToLocalFilesystem){
|
|
270
|
+
if (dayjs().isAfter(timestamp + ms("5s"))) {
|
|
271
|
+
// this change should have been seen by now
|
|
272
|
+
recentWritesToLocalFilesystem.delete(path);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
}, ms("1s")).unref();
|
|
276
|
+
/**
|
|
277
|
+
* Subscribe to file changes on Gadget and apply them to the local
|
|
278
|
+
* filesystem.
|
|
279
|
+
*/ const filesyncSubscription = filesync.subscribeToEnvironmentChanges(ctx, {
|
|
280
|
+
onError: (error)=>ctx.abort(error),
|
|
281
|
+
beforeChanges: ({ changed, deleted })=>{
|
|
282
|
+
// add all the files and directories we're about to touch to
|
|
283
|
+
// recentWritesToLocalFilesystem so that we don't send them back
|
|
284
|
+
// to Gadget
|
|
285
|
+
for (const filepath of [
|
|
286
|
+
...changed,
|
|
287
|
+
...deleted
|
|
288
|
+
]){
|
|
289
|
+
recentWritesToLocalFilesystem.set(filepath, Date.now());
|
|
290
|
+
let dir = path.dirname(filepath);
|
|
291
|
+
while(dir !== "."){
|
|
292
|
+
recentWritesToLocalFilesystem.set(dir + "/", Date.now());
|
|
293
|
+
dir = path.dirname(dir);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
});
|
|
298
|
+
/**
|
|
299
|
+
* A buffer of local file changes to send to Gadget.
|
|
300
|
+
*/ const localChangesBuffer = new Changes();
|
|
301
|
+
/**
|
|
302
|
+
* A debounced function that sends the local file changes to Gadget.
|
|
303
|
+
*/ const mergeChangesWithEnvironment = debounceAsync(ctx.args["--file-push-delay"], async ()=>{
|
|
304
|
+
try {
|
|
305
|
+
const lastGitBranch = syncJson.gitBranch;
|
|
306
|
+
await syncJson.loadGitBranch();
|
|
307
|
+
if (lastGitBranch !== syncJson.gitBranch) {
|
|
308
|
+
println({
|
|
309
|
+
ensureEmptyLineAbove: true
|
|
310
|
+
})`
|
|
311
|
+
Your git branch changed.
|
|
312
|
+
|
|
313
|
+
${lastGitBranch} → ${syncJson.gitBranch}
|
|
314
|
+
`;
|
|
315
|
+
// we need all the changes to be sent in a single batch, so wait
|
|
316
|
+
// a bit in case there are changes the watcher hasn't seen yet
|
|
317
|
+
const spinner = spin({
|
|
318
|
+
ensureEmptyLineAbove: true
|
|
319
|
+
})("Waiting for file changes to settle.");
|
|
320
|
+
await delay("3s"); // this time was chosen arbitrarily
|
|
321
|
+
spinner.succeed();
|
|
322
|
+
}
|
|
323
|
+
const changes = new Changes(localChangesBuffer.entries());
|
|
324
|
+
localChangesBuffer.clear();
|
|
325
|
+
await filesync.mergeChangesWithEnvironment(ctx, {
|
|
326
|
+
changes
|
|
327
|
+
});
|
|
328
|
+
} catch (error) {
|
|
329
|
+
ctx.log.error("error sending changes to gadget", {
|
|
330
|
+
error
|
|
331
|
+
});
|
|
332
|
+
ctx.abort(error);
|
|
333
|
+
}
|
|
334
|
+
});
|
|
335
|
+
ctx.log.debug("watching", {
|
|
336
|
+
path: syncJson.directory.path
|
|
337
|
+
});
|
|
338
|
+
/**
|
|
339
|
+
* Watches the local filesystem for changes.
|
|
340
|
+
*/ const fileWatcher = new Watcher(syncJson.directory.path, {
|
|
341
|
+
// don't emit an event for every watched file when we start watching
|
|
342
|
+
ignoreInitial: true,
|
|
343
|
+
// watch everything
|
|
344
|
+
recursive: true,
|
|
345
|
+
// don't emit changes to .gadget/ files because they're readonly (Gadget manages them)
|
|
346
|
+
ignore: (path)=>syncJson.directory.relative(path).startsWith(".gadget") || syncJson.directory.ignores(path),
|
|
347
|
+
// emit rename/renameDir events
|
|
348
|
+
renameDetection: true,
|
|
349
|
+
// how long to wait for an add event to be followed by an unlink
|
|
350
|
+
// event, and vice versa (i.e. a rename event)
|
|
351
|
+
renameTimeout: ctx.args["--file-watch-rename-timeout"],
|
|
352
|
+
// how long to wait before emitting a change event (helps avoid duplicate events)
|
|
353
|
+
debounce: ctx.args["--file-watch-debounce"]
|
|
354
|
+
}, (event, absolutePath, renamedPath)=>{
|
|
355
|
+
const filepath = event === "rename" || event === "renameDir" ? renamedPath : absolutePath;
|
|
356
|
+
const isDirectory = event === "renameDir" || event === "addDir" || event === "unlinkDir";
|
|
357
|
+
const normalizedPath = syncJson.directory.normalize(filepath, isDirectory);
|
|
358
|
+
ctx.log.trace("file event", {
|
|
359
|
+
event,
|
|
360
|
+
isDirectory,
|
|
361
|
+
path: normalizedPath
|
|
362
|
+
});
|
|
363
|
+
if (filepath === syncJson.directory.absolute(".ignore")) {
|
|
364
|
+
syncJson.directory.loadIgnoreFile().catch((error)=>ctx.abort(error));
|
|
365
|
+
} else if (syncJson.directory.ignores(filepath)) {
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
if (recentWritesToLocalFilesystem.delete(normalizedPath)) {
|
|
369
|
+
ctx.log.trace("ignoring event because we caused it", {
|
|
370
|
+
event,
|
|
371
|
+
path: normalizedPath
|
|
372
|
+
});
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
375
|
+
switch(event){
|
|
376
|
+
case "add":
|
|
377
|
+
case "addDir":
|
|
378
|
+
localChangesBuffer.set(normalizedPath, {
|
|
379
|
+
type: "create"
|
|
380
|
+
});
|
|
381
|
+
break;
|
|
382
|
+
case "rename":
|
|
383
|
+
case "renameDir":
|
|
384
|
+
{
|
|
385
|
+
const oldNormalizedPath = syncJson.directory.normalize(absolutePath, isDirectory);
|
|
386
|
+
localChangesBuffer.set(normalizedPath, {
|
|
387
|
+
type: "create",
|
|
388
|
+
oldPath: oldNormalizedPath
|
|
389
|
+
});
|
|
390
|
+
break;
|
|
391
|
+
}
|
|
392
|
+
case "change":
|
|
393
|
+
{
|
|
394
|
+
localChangesBuffer.set(normalizedPath, {
|
|
395
|
+
type: "update"
|
|
396
|
+
});
|
|
397
|
+
break;
|
|
398
|
+
}
|
|
399
|
+
case "unlink":
|
|
400
|
+
case "unlinkDir":
|
|
401
|
+
{
|
|
402
|
+
localChangesBuffer.set(normalizedPath, {
|
|
403
|
+
type: "delete"
|
|
404
|
+
});
|
|
405
|
+
break;
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
mergeChangesWithEnvironment();
|
|
409
|
+
}).once("error", (error)=>ctx.abort(error));
|
|
410
|
+
ctx.onAbort(async (reason)=>{
|
|
411
|
+
ctx.log.info("stopping", {
|
|
412
|
+
reason
|
|
413
|
+
});
|
|
414
|
+
filesyncSubscription.unsubscribe();
|
|
415
|
+
fileWatcher.close();
|
|
416
|
+
clearInterval(clearRecentWritesInterval);
|
|
417
|
+
await mergeChangesWithEnvironment.flush();
|
|
418
|
+
try {
|
|
419
|
+
await filesync.idle();
|
|
420
|
+
} catch (error) {
|
|
421
|
+
ctx.log.error("error while waiting for idle", {
|
|
422
|
+
error
|
|
423
|
+
});
|
|
424
|
+
}
|
|
425
|
+
if (isAbortError(reason)) {
|
|
426
|
+
return;
|
|
427
|
+
}
|
|
428
|
+
notify(ctx, {
|
|
429
|
+
subtitle: "Uh oh!",
|
|
430
|
+
message: "An error occurred while syncing files"
|
|
431
|
+
});
|
|
432
|
+
await reportErrorAndExit(ctx, reason);
|
|
433
|
+
});
|
|
434
|
+
footer({
|
|
435
|
+
ensureEmptyLineAbove: true
|
|
436
|
+
})`
|
|
437
|
+
${syncJson.sprint({
|
|
438
|
+
indent: 4
|
|
439
|
+
})}
|
|
440
|
+
|
|
441
|
+
Waiting for file changes${symbol.ellipsis} {gray Press Ctrl+C to stop}
|
|
442
|
+
`;
|
|
443
|
+
};
|
|
444
|
+
|
|
445
|
+
//# sourceMappingURL=dev.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/commands/dev.ts"],"sourcesContent":["import dayjs from \"dayjs\";\nimport ms from \"ms\";\nimport path from \"node:path\";\nimport Watcher from \"watcher\";\nimport which from \"which\";\nimport type { ArgsDefinition } from \"../services/command/arg.js\";\nimport type { Command, Usage } from \"../services/command/command.js\";\nimport type { Context } from \"../services/command/context.js\";\nimport { Changes } from \"../services/filesync/changes.js\";\nimport { YarnNotFoundError } from \"../services/filesync/error.js\";\nimport { FileSync } from \"../services/filesync/filesync.js\";\nimport { FileSyncStrategy, MergeConflictPreferenceArg } from \"../services/filesync/strategy.js\";\nimport { SyncJson, SyncJsonArgs, loadSyncJsonDirectory } from \"../services/filesync/sync-json.js\";\nimport { footer } from \"../services/output/footer.js\";\nimport { notify } from \"../services/output/notify.js\";\nimport { println } from \"../services/output/print.js\";\nimport { reportErrorAndExit } from \"../services/output/report.js\";\nimport { select } from \"../services/output/select.js\";\nimport { spin } from \"../services/output/spinner.js\";\nimport { sprint } from \"../services/output/sprint.js\";\nimport { symbol } from \"../services/output/symbols.js\";\nimport { unreachable } from \"../services/util/assert.js\";\nimport { debounceAsync } from \"../services/util/function.js\";\nimport { isAbortError } from \"../services/util/is.js\";\nimport { delay } from \"../services/util/promise.js\";\nimport type { PullArgs } from \"./pull.js\";\nimport type { PushArgs } from \"./push.js\";\n\nexport type DevArgs = typeof args;\n\nexport const args = {\n ...SyncJsonArgs,\n \"--prefer\": MergeConflictPreferenceArg,\n \"--file-push-delay\": { type: Number, default: ms(\"100ms\") },\n \"--file-watch-debounce\": { type: Number, default: ms(\"300ms\") },\n \"--file-watch-poll-interval\": { type: Number, default: ms(\"3s\") },\n \"--file-watch-poll-timeout\": { type: Number, default: ms(\"20s\") },\n \"--file-watch-rename-timeout\": { type: Number, default: ms(\"1.25s\") },\n} satisfies ArgsDefinition;\n\nexport const usage: Usage = (ctx) => {\n if (ctx.args[\"-h\"]) {\n return sprint`\n Develop your app by synchronizing your local files with your\n environment's files, in real-time. Changes are tracked from\n the last \"ggt dev\", \"ggt push\", or \"ggt pull\" run locally.\n\n {bold USAGE}\n ggt dev [DIRECTORY]\n\n {bold EXAMPLES}\n $ ggt dev\n $ ggt dev ~/gadget/example\n $ ggt dev ~/gadget/example\n $ ggt dev ~/gadget/example --app=example\n $ ggt dev ~/gadget/example --app=example --env=development --prefer=local\n\n {bold ARGUMENTS}\n DIRECTORY The directory to synchronize files to (default: \".\")\n\n {bold FLAGS}\n -a, --app=<name> The application to synchronize files with\n -e, --env=<name> The environment to synchronize files with\n --prefer=<filesystem> Prefer \"local\" or \"environment\" conflicting changes\n\n Run \"ggt dev --help\" for more information.\n `;\n }\n\n return sprint`\n Develop your app by synchronizing your local files with your\n environment's files, in real-time. Changes are tracked from\n the last \"ggt dev\", \"ggt push\", or \"ggt pull\" run locally.\n\n While \"ggt dev\" is running, changes on your local filesystem are\n immediately pushed to your environment, while file changes on\n your environment are immediately pulled to your local filesystem.\n\n If conflicting changes are detected, and \"--prefer\" is not passed,\n you will be prompted to choose which changes to keep before\n \"ggt dev\" resumes.\n\n \"ggt dev\" looks for an \".ignore\" file to exclude files and\n directories from being pushed or pulled. The format is identical\n to Git's.\n\n The following files and directories are always ignored:\n • .DS_Store\n • .gadget\n • .git\n • node_modules\n\n Note:\n • \"ggt dev\" only works with development environments\n • \"ggt dev\" only supports \"yarn\" v1 for installing dependencies\n • Avoid deleting or moving all of your files while \"ggt dev\" is running\n\n {bold USAGE}\n\n ggt dev [DIRECTORY] [--app=<name>] [--env=<name>] [--prefer=<filesystem>]\n [--allow-unknown-directory] [--allow-different-app]\n\n {bold EXAMPLES}\n\n $ ggt dev\n $ ggt dev ~/gadget/example\n $ ggt dev ~/gadget/example\n $ ggt dev ~/gadget/example --app=example\n $ ggt dev ~/gadget/example --app=example --env=development --prefer=local\n\n {bold ARGUMENTS}\n\n DIRECTORY\n The path to the directory to synchronize files to.\n The directory will be created if it does not exist.\n\n Defaults to the current working directory. (default: \".\")\n\n {bold FLAGS}\n\n -a, --app, --application=<name>\n The application to synchronize files with.\n\n Defaults to the application within the \".gadget/sync.json\"\n file in the chosen directory or any parent directories.\n\n -e, --env, --environment=<name>\n The development environment to synchronize files with.\n\n Defaults to the environment within the \".gadget/sync.json\"\n file in the chosen directory or any parent directories.\n\n --prefer=<filesystem>\n Which filesystem's changes to automatically keep when\n conflicting changes are detected.\n\n Must be one of \"local\" or \"environment\".\n\n If not provided, \"ggt dev\" will pause when conflicting changes\n are detected and you will be prompted to choose which changes to\n keep before \"ggt dev\" resumes.\n\n --allow-unknown-directory\n Allows \"ggt dev\" to continue when the chosen directory, nor\n any parent directories, contain a \".gadget/sync.json\" file\n within it.\n\n Defaults to false.\n\n --allow-different-app\n Allows \"ggt dev\" to continue with a different \"--app\" than the\n one found within the \".gadget/sync.json\" file.\n\n Defaults to false.\n\n Run \"ggt dev -h\" for less information.\n `;\n};\n\nexport const command: Command<DevArgs> = async (ctx) => {\n if (!(await which(\"yarn\", { nothrow: true }))) {\n throw new YarnNotFoundError();\n }\n\n const directory = await loadSyncJsonDirectory(ctx.args._[0] || process.cwd());\n const syncJson = await SyncJson.loadOrInit(ctx, { directory });\n footer({ ensureEmptyLineAbove: true })(syncJson.sprint());\n\n const filesync = new FileSync(syncJson);\n const hashes = await filesync.hashes(ctx);\n\n if (!hashes.inSync) {\n // our local files don't match our environment's files\n if (!syncJson.previousEnvironment || (hashes.localChangesToPush.size === 0 && hashes.onlyDotGadgetFilesChanged)) {\n // one of the following is true:\n // - we're developing on this environment for the first time\n // - we're developing on the same environment as last time\n // - we're developing on a different environment, but only .gadget/ files have changed\n // merge the changes (if any) and continue\n await filesync.merge(ctx, {\n hashes,\n printLocalChangesOptions: {\n limit: 5,\n },\n printEnvironmentChangesOptions: {\n limit: 5,\n },\n });\n } else {\n // we're switching environment's and files outside of .gadget/\n // have changed, so ask the user what to do\n await filesync.print(ctx, { hashes });\n const choices = Object.values(FileSyncStrategy);\n\n const strategy = await select({\n ensureEmptyLineAbove: true,\n choices: hashes.bothChanged ? choices : choices.filter((choice) => choice !== FileSyncStrategy.MERGE),\n formatChoice: (choice) => {\n switch (choice) {\n case FileSyncStrategy.CANCEL:\n return sprint`Cancel (Ctrl+C)`;\n case FileSyncStrategy.MERGE:\n return sprint`Merge local and environment's changes`;\n case FileSyncStrategy.PUSH:\n switch (true) {\n case hashes.bothChanged:\n return sprint`Push local changes and {underline discard environment's} changes`;\n case hashes.localChanges.size > 0:\n return sprint`Push local changes`;\n case hashes.environmentChanges.size > 0:\n return sprint`Discard environment's changes`;\n default:\n return unreachable(\"no changes to push or discard\");\n }\n case FileSyncStrategy.PULL:\n switch (true) {\n case hashes.bothChanged:\n return sprint`Pull environment's changes and {underline discard local} changes`;\n case hashes.localChanges.size > 0:\n return sprint`Discard local changes`;\n case hashes.environmentChanges.size > 0:\n return sprint`Pull environment's changes`;\n default:\n return unreachable(\"no changes to pull or discard\");\n }\n }\n },\n })`\n {bold What do you want to do?}\n `;\n\n switch (strategy) {\n case FileSyncStrategy.CANCEL:\n process.exit(0);\n break;\n case FileSyncStrategy.MERGE:\n await filesync.merge(ctx, { hashes });\n break;\n case FileSyncStrategy.PUSH:\n await filesync.push(ctx as unknown as Context<PushArgs>, { hashes, force: true });\n break;\n case FileSyncStrategy.PULL:\n await filesync.pull(ctx as unknown as Context<PullArgs>, { hashes, force: true });\n break;\n }\n }\n }\n\n /**\n * A list of filepaths that have changed because we (this ggt process)\n * modified them. This is used to avoid reacting to filesystem events\n * that we caused, which would cause an infinite loop.\n */\n const recentWritesToLocalFilesystem = new Map<string, number>();\n\n const clearRecentWritesInterval = setInterval(() => {\n for (const [path, timestamp] of recentWritesToLocalFilesystem) {\n if (dayjs().isAfter(timestamp + ms(\"5s\"))) {\n // this change should have been seen by now\n recentWritesToLocalFilesystem.delete(path);\n }\n }\n }, ms(\"1s\")).unref();\n\n /**\n * Subscribe to file changes on Gadget and apply them to the local\n * filesystem.\n */\n const filesyncSubscription = filesync.subscribeToEnvironmentChanges(ctx, {\n onError: (error) => ctx.abort(error),\n beforeChanges: ({ changed, deleted }) => {\n // add all the files and directories we're about to touch to\n // recentWritesToLocalFilesystem so that we don't send them back\n // to Gadget\n for (const filepath of [...changed, ...deleted]) {\n recentWritesToLocalFilesystem.set(filepath, Date.now());\n\n let dir = path.dirname(filepath);\n while (dir !== \".\") {\n recentWritesToLocalFilesystem.set(dir + \"/\", Date.now());\n dir = path.dirname(dir);\n }\n }\n },\n });\n\n /**\n * A buffer of local file changes to send to Gadget.\n */\n const localChangesBuffer = new Changes();\n\n /**\n * A debounced function that sends the local file changes to Gadget.\n */\n const mergeChangesWithEnvironment = debounceAsync(ctx.args[\"--file-push-delay\"], async (): Promise<void> => {\n try {\n const lastGitBranch = syncJson.gitBranch;\n await syncJson.loadGitBranch();\n\n if (lastGitBranch !== syncJson.gitBranch) {\n println({ ensureEmptyLineAbove: true })`\n Your git branch changed.\n\n ${lastGitBranch} → ${syncJson.gitBranch}\n `;\n\n // we need all the changes to be sent in a single batch, so wait\n // a bit in case there are changes the watcher hasn't seen yet\n const spinner = spin({ ensureEmptyLineAbove: true })(\"Waiting for file changes to settle.\");\n await delay(\"3s\"); // this time was chosen arbitrarily\n spinner.succeed();\n }\n\n const changes = new Changes(localChangesBuffer.entries());\n localChangesBuffer.clear();\n\n await filesync.mergeChangesWithEnvironment(ctx, { changes });\n } catch (error) {\n ctx.log.error(\"error sending changes to gadget\", { error });\n ctx.abort(error);\n }\n });\n\n ctx.log.debug(\"watching\", { path: syncJson.directory.path });\n\n /**\n * Watches the local filesystem for changes.\n */\n const fileWatcher = new Watcher(\n syncJson.directory.path,\n {\n // don't emit an event for every watched file when we start watching\n ignoreInitial: true,\n // watch everything\n recursive: true,\n // don't emit changes to .gadget/ files because they're readonly (Gadget manages them)\n ignore: (path: string) => syncJson.directory.relative(path).startsWith(\".gadget\") || syncJson.directory.ignores(path),\n // emit rename/renameDir events\n renameDetection: true,\n // how long to wait for an add event to be followed by an unlink\n // event, and vice versa (i.e. a rename event)\n renameTimeout: ctx.args[\"--file-watch-rename-timeout\"],\n // how long to wait before emitting a change event (helps avoid duplicate events)\n debounce: ctx.args[\"--file-watch-debounce\"],\n },\n (event: string, absolutePath: string, renamedPath: string) => {\n const filepath = event === \"rename\" || event === \"renameDir\" ? renamedPath : absolutePath;\n const isDirectory = event === \"renameDir\" || event === \"addDir\" || event === \"unlinkDir\";\n const normalizedPath = syncJson.directory.normalize(filepath, isDirectory);\n\n ctx.log.trace(\"file event\", { event, isDirectory, path: normalizedPath });\n\n if (filepath === syncJson.directory.absolute(\".ignore\")) {\n syncJson.directory.loadIgnoreFile().catch((error) => ctx.abort(error));\n } else if (syncJson.directory.ignores(filepath)) {\n return;\n }\n\n if (recentWritesToLocalFilesystem.delete(normalizedPath)) {\n ctx.log.trace(\"ignoring event because we caused it\", { event, path: normalizedPath });\n return;\n }\n\n switch (event) {\n case \"add\":\n case \"addDir\":\n localChangesBuffer.set(normalizedPath, { type: \"create\" });\n break;\n case \"rename\":\n case \"renameDir\": {\n const oldNormalizedPath = syncJson.directory.normalize(absolutePath, isDirectory);\n localChangesBuffer.set(normalizedPath, { type: \"create\", oldPath: oldNormalizedPath });\n break;\n }\n case \"change\": {\n localChangesBuffer.set(normalizedPath, { type: \"update\" });\n break;\n }\n case \"unlink\":\n case \"unlinkDir\": {\n localChangesBuffer.set(normalizedPath, { type: \"delete\" });\n break;\n }\n }\n\n mergeChangesWithEnvironment();\n },\n ).once(\"error\", (error) => ctx.abort(error));\n\n ctx.onAbort(async (reason) => {\n ctx.log.info(\"stopping\", { reason });\n\n filesyncSubscription.unsubscribe();\n fileWatcher.close();\n clearInterval(clearRecentWritesInterval);\n await mergeChangesWithEnvironment.flush();\n\n try {\n await filesync.idle();\n } catch (error) {\n ctx.log.error(\"error while waiting for idle\", { error });\n }\n\n if (isAbortError(reason)) {\n return;\n }\n\n notify(ctx, { subtitle: \"Uh oh!\", message: \"An error occurred while syncing files\" });\n await reportErrorAndExit(ctx, reason);\n });\n\n footer({ ensureEmptyLineAbove: true })`\n${syncJson.sprint({ indent: 4 })}\n\n Waiting for file changes${symbol.ellipsis} {gray Press Ctrl+C to stop}\n `;\n};\n"],"names":["dayjs","ms","path","Watcher","which","Changes","YarnNotFoundError","FileSync","FileSyncStrategy","MergeConflictPreferenceArg","SyncJson","SyncJsonArgs","loadSyncJsonDirectory","footer","notify","println","reportErrorAndExit","select","spin","sprint","symbol","unreachable","debounceAsync","isAbortError","delay","args","type","Number","default","usage","ctx","command","nothrow","directory","_","process","cwd","syncJson","loadOrInit","ensureEmptyLineAbove","filesync","hashes","inSync","previousEnvironment","localChangesToPush","size","onlyDotGadgetFilesChanged","merge","printLocalChangesOptions","limit","printEnvironmentChangesOptions","print","choices","Object","values","strategy","bothChanged","filter","choice","MERGE","formatChoice","CANCEL","PUSH","localChanges","environmentChanges","PULL","exit","push","force","pull","recentWritesToLocalFilesystem","Map","clearRecentWritesInterval","setInterval","timestamp","isAfter","delete","unref","filesyncSubscription","subscribeToEnvironmentChanges","onError","error","abort","beforeChanges","changed","deleted","filepath","set","Date","now","dir","dirname","localChangesBuffer","mergeChangesWithEnvironment","lastGitBranch","gitBranch","loadGitBranch","spinner","succeed","changes","entries","clear","log","debug","fileWatcher","ignoreInitial","recursive","ignore","relative","startsWith","ignores","renameDetection","renameTimeout","debounce","event","absolutePath","renamedPath","isDirectory","normalizedPath","normalize","trace","absolute","loadIgnoreFile","catch","oldNormalizedPath","oldPath","once","onAbort","reason","info","unsubscribe","close","clearInterval","flush","idle","subtitle","message","indent","ellipsis"],"mappings":"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"}
|
package/lib/commands/list.js
CHANGED
|
@@ -1,41 +1,49 @@
|
|
|
1
1
|
import { getApps } from "../services/app/app.js";
|
|
2
|
-
import {
|
|
2
|
+
import { output } from "../services/output/output.js";
|
|
3
|
+
import { println } from "../services/output/print.js";
|
|
4
|
+
import { sprint, sprintln } from "../services/output/sprint.js";
|
|
5
|
+
import { printTable } from "../services/output/table.js";
|
|
3
6
|
import { getUserOrLogin } from "../services/user/user.js";
|
|
4
7
|
export const usage = ()=>sprint`
|
|
5
|
-
List
|
|
8
|
+
List your available applications.
|
|
6
9
|
|
|
7
10
|
{bold USAGE}
|
|
8
11
|
ggt list
|
|
9
12
|
|
|
10
|
-
{bold
|
|
13
|
+
{bold EXAMPLES}
|
|
11
14
|
$ ggt list
|
|
12
|
-
Slug Domain
|
|
13
|
-
─────── ──────────────────
|
|
14
|
-
my-app my-app.gadget.app
|
|
15
|
-
example example.gadget.app
|
|
16
|
-
test test.gadget.app
|
|
17
15
|
`;
|
|
18
16
|
export const command = async (ctx)=>{
|
|
19
17
|
await getUserOrLogin(ctx);
|
|
20
18
|
const apps = await getApps(ctx);
|
|
21
19
|
if (apps.length === 0) {
|
|
22
|
-
|
|
20
|
+
println`
|
|
23
21
|
It doesn't look like you have any applications.
|
|
24
22
|
|
|
25
23
|
Visit https://gadget.new to create one!
|
|
26
24
|
`;
|
|
27
25
|
return;
|
|
28
26
|
}
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
27
|
+
if (output.isInteractive) {
|
|
28
|
+
printTable({
|
|
29
|
+
json: apps,
|
|
30
|
+
headers: [
|
|
31
|
+
"Name",
|
|
32
|
+
"Domain"
|
|
33
|
+
],
|
|
34
|
+
rows: apps.map((app)=>[
|
|
35
|
+
app.slug,
|
|
36
|
+
app.primaryDomain
|
|
37
|
+
])
|
|
38
|
+
});
|
|
39
|
+
} else {
|
|
40
|
+
let simpleOutput = "";
|
|
41
|
+
for (const app of apps){
|
|
42
|
+
simpleOutput += sprintln`${app.slug}\t${app.primaryDomain}`;
|
|
43
|
+
}
|
|
44
|
+
println({
|
|
45
|
+
json: apps
|
|
46
|
+
})(simpleOutput);
|
|
39
47
|
}
|
|
40
48
|
};
|
|
41
49
|
|
package/lib/commands/list.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/commands/list.ts"],"sourcesContent":["import { getApps } from \"../services/app/app.js\";\nimport type { Command, Usage } from \"../services/command/command.js\";\nimport { sprint } from \"../services/output/sprint.js\";\nimport { getUserOrLogin } from \"../services/user/user.js\";\n\nexport const usage: Usage = () => sprint`\n List
|
|
1
|
+
{"version":3,"sources":["../../src/commands/list.ts"],"sourcesContent":["import { getApps } from \"../services/app/app.js\";\nimport type { Command, Usage } from \"../services/command/command.js\";\nimport { output } from \"../services/output/output.js\";\nimport { println } from \"../services/output/print.js\";\nimport { sprint, sprintln } from \"../services/output/sprint.js\";\nimport { printTable } from \"../services/output/table.js\";\nimport { getUserOrLogin } from \"../services/user/user.js\";\n\nexport const usage: Usage = () => sprint`\n List your available applications.\n\n {bold USAGE}\n ggt list\n\n {bold EXAMPLES}\n $ ggt list\n`;\n\nexport const command: Command = async (ctx) => {\n await getUserOrLogin(ctx);\n\n const apps = await getApps(ctx);\n if (apps.length === 0) {\n println`\n It doesn't look like you have any applications.\n\n Visit https://gadget.new to create one!\n `;\n return;\n }\n\n if (output.isInteractive) {\n printTable({\n json: apps,\n headers: [\"Name\", \"Domain\"],\n rows: apps.map((app) => [app.slug, app.primaryDomain]),\n });\n } else {\n let simpleOutput = \"\";\n for (const app of apps) {\n simpleOutput += sprintln`${app.slug}\\t${app.primaryDomain}`;\n }\n\n println({ json: apps })(simpleOutput);\n }\n};\n"],"names":["getApps","output","println","sprint","sprintln","printTable","getUserOrLogin","usage","command","ctx","apps","length","isInteractive","json","headers","rows","map","app","slug","primaryDomain","simpleOutput"],"mappings":"AAAA,SAASA,OAAO,QAAQ,yBAAyB;AAEjD,SAASC,MAAM,QAAQ,+BAA+B;AACtD,SAASC,OAAO,QAAQ,8BAA8B;AACtD,SAASC,MAAM,EAAEC,QAAQ,QAAQ,+BAA+B;AAChE,SAASC,UAAU,QAAQ,8BAA8B;AACzD,SAASC,cAAc,QAAQ,2BAA2B;AAE1D,OAAO,MAAMC,QAAe,IAAMJ,MAAM,CAAC;;;;;;;;AAQzC,CAAC,CAAC;AAEF,OAAO,MAAMK,UAAmB,OAAOC;IACrC,MAAMH,eAAeG;IAErB,MAAMC,OAAO,MAAMV,QAAQS;IAC3B,IAAIC,KAAKC,MAAM,KAAK,GAAG;QACrBT,OAAO,CAAC;;;;IAIR,CAAC;QACD;IACF;IAEA,IAAID,OAAOW,aAAa,EAAE;QACxBP,WAAW;YACTQ,MAAMH;YACNI,SAAS;gBAAC;gBAAQ;aAAS;YAC3BC,MAAML,KAAKM,GAAG,CAAC,CAACC,MAAQ;oBAACA,IAAIC,IAAI;oBAAED,IAAIE,aAAa;iBAAC;QACvD;IACF,OAAO;QACL,IAAIC,eAAe;QACnB,KAAK,MAAMH,OAAOP,KAAM;YACtBU,gBAAgBhB,QAAQ,CAAC,EAAEa,IAAIC,IAAI,CAAC,EAAE,EAAED,IAAIE,aAAa,CAAC,CAAC;QAC7D;QAEAjB,QAAQ;YAAEW,MAAMH;QAAK,GAAGU;IAC1B;AACF,EAAE"}
|
package/lib/commands/login.js
CHANGED
|
@@ -3,6 +3,7 @@ import assert from "node:assert";
|
|
|
3
3
|
import http from "node:http";
|
|
4
4
|
import open from "open";
|
|
5
5
|
import { config } from "../services/config/config.js";
|
|
6
|
+
import { println } from "../services/output/print.js";
|
|
6
7
|
import { sprint } from "../services/output/sprint.js";
|
|
7
8
|
import { writeSession } from "../services/user/session.js";
|
|
8
9
|
import { getUser } from "../services/user/user.js";
|
|
@@ -12,13 +13,8 @@ export const usage = ()=>sprint`
|
|
|
12
13
|
{bold USAGE}
|
|
13
14
|
ggt login
|
|
14
15
|
|
|
15
|
-
{bold
|
|
16
|
+
{bold EXAMPLES}
|
|
16
17
|
$ ggt login
|
|
17
|
-
We've opened Gadget's login page using your default browser.
|
|
18
|
-
|
|
19
|
-
Please log in and then return to this terminal.
|
|
20
|
-
|
|
21
|
-
Hello, Jane Doe (jane@example.com)
|
|
22
18
|
`;
|
|
23
19
|
export const login = async (ctx)=>{
|
|
24
20
|
let server;
|
|
@@ -36,9 +32,13 @@ export const login = async (ctx)=>{
|
|
|
36
32
|
const user = await getUser(ctx);
|
|
37
33
|
assert(user, "missing user after successful login");
|
|
38
34
|
if (user.name) {
|
|
39
|
-
|
|
35
|
+
println({
|
|
36
|
+
ensureEmptyLineAbove: true
|
|
37
|
+
})`Hello, ${user.name} {gray (${user.email})}`;
|
|
40
38
|
} else {
|
|
41
|
-
|
|
39
|
+
println({
|
|
40
|
+
ensureEmptyLineAbove: true
|
|
41
|
+
})`Hello, ${user.email}`;
|
|
42
42
|
}
|
|
43
43
|
landingPage.searchParams.set("success", "true");
|
|
44
44
|
resolve();
|
|
@@ -65,16 +65,20 @@ export const login = async (ctx)=>{
|
|
|
65
65
|
url.searchParams.set("returnTo", `https://${config.domains.services}/auth/cli/callback?port=${port}`);
|
|
66
66
|
try {
|
|
67
67
|
await open(url.toString());
|
|
68
|
-
|
|
68
|
+
println({
|
|
69
|
+
ensureEmptyLineAbove: true
|
|
70
|
+
})`
|
|
69
71
|
We've opened Gadget's login page using your default browser.
|
|
70
72
|
|
|
71
73
|
Please log in and then return to this terminal.
|
|
72
|
-
|
|
74
|
+
`;
|
|
73
75
|
} catch (error) {
|
|
74
76
|
ctx.log.error("failed to open browser", {
|
|
75
77
|
error
|
|
76
78
|
});
|
|
77
|
-
|
|
79
|
+
println({
|
|
80
|
+
ensureEmptyLineAbove: true
|
|
81
|
+
})`
|
|
78
82
|
Please open the following URL in your browser and log in:
|
|
79
83
|
|
|
80
84
|
{gray ${url.toString()}}
|