@gadgetinc/ggt 0.3.1 → 0.3.3
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 +1 -1
- package/lib/commands/list.js +11 -8
- package/lib/commands/list.js.map +1 -1
- package/lib/commands/login.js +15 -2
- package/lib/commands/login.js.map +1 -1
- package/lib/commands/root.js +3 -3
- package/lib/commands/root.js.map +1 -1
- package/lib/commands/sync.js +38 -38
- package/lib/commands/sync.js.map +1 -1
- package/lib/services/app.js +1 -2
- package/lib/services/app.js.map +1 -1
- package/lib/services/args.js +3 -3
- package/lib/services/args.js.map +1 -1
- package/lib/services/collections.js +17 -0
- package/lib/services/collections.js.map +1 -0
- package/lib/services/config.js +5 -6
- package/lib/services/config.js.map +1 -1
- package/lib/services/debounce.js +21 -0
- package/lib/services/debounce.js.map +1 -0
- package/lib/services/defaults.js +8 -0
- package/lib/services/defaults.js.map +1 -0
- package/lib/services/edit-graphql.js +20 -11
- package/lib/services/edit-graphql.js.map +1 -1
- package/lib/services/errors.js +37 -34
- package/lib/services/errors.js.map +1 -1
- package/lib/services/filesync.js +33 -33
- package/lib/services/filesync.js.map +1 -1
- package/lib/services/{fs-utils.js → fs.js} +17 -15
- package/lib/services/fs.js.map +1 -0
- package/lib/services/is.js +39 -0
- package/lib/services/is.js.map +1 -0
- package/lib/services/log.js +5 -5
- package/lib/services/log.js.map +1 -1
- package/lib/services/noop.js +4 -0
- package/lib/services/noop.js.map +1 -0
- package/lib/services/output.js +21 -6
- package/lib/services/output.js.map +1 -1
- package/lib/services/promise.js +5 -3
- package/lib/services/promise.js.map +1 -1
- package/lib/services/prompt.js +22 -0
- package/lib/services/prompt.js.map +1 -0
- package/lib/services/session.js +6 -2
- package/lib/services/session.js.map +1 -1
- package/lib/services/sleep.js +8 -6
- package/lib/services/sleep.js.map +1 -1
- package/lib/services/user.js +4 -6
- package/lib/services/user.js.map +1 -1
- package/lib/services/version.js +2 -2
- package/lib/services/version.js.map +1 -1
- package/npm-shrinkwrap.json +503 -473
- package/package.json +9 -11
- package/lib/services/fs-utils.js.map +0 -1
package/README.md
CHANGED
package/lib/commands/list.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import _ from "lodash";
|
|
2
1
|
import { getApps } from "../services/app.js";
|
|
3
2
|
import { println, sprint } from "../services/output.js";
|
|
4
3
|
import { getUserOrLogin } from "../services/user.js";
|
|
@@ -19,7 +18,7 @@ export const usage = sprint`
|
|
|
19
18
|
export const run = async ()=>{
|
|
20
19
|
const user = await getUserOrLogin();
|
|
21
20
|
const apps = await getApps(user);
|
|
22
|
-
if (
|
|
21
|
+
if (apps.length === 0) {
|
|
23
22
|
println`
|
|
24
23
|
It doesn't look like you have any applications.
|
|
25
24
|
|
|
@@ -27,12 +26,16 @@ export const run = async ()=>{
|
|
|
27
26
|
`;
|
|
28
27
|
return;
|
|
29
28
|
}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
29
|
+
let longestSlug = 0;
|
|
30
|
+
let longestDomain = 0;
|
|
31
|
+
for (const app of apps){
|
|
32
|
+
longestSlug = Math.max(longestSlug, app.slug.length);
|
|
33
|
+
longestDomain = Math.max(longestDomain, app.primaryDomain.length);
|
|
34
|
+
}
|
|
35
|
+
println`{bold Slug}${" ".repeat(longestSlug - 4)} {bold Domain}`;
|
|
36
|
+
println`${"─".repeat(Math.max(longestSlug, 4))} ${"─".repeat(Math.max(longestDomain, 6))}`;
|
|
37
|
+
for (const app of apps.sort((a, b)=>a.slug.localeCompare(b.slug))){
|
|
38
|
+
println`${app.slug}${" ".repeat(longestSlug - app.slug.length)} ${app.primaryDomain}`;
|
|
36
39
|
}
|
|
37
40
|
};
|
|
38
41
|
|
package/lib/commands/list.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/commands/list.ts"],"sourcesContent":["import
|
|
1
|
+
{"version":3,"sources":["../../src/commands/list.ts"],"sourcesContent":["import { getApps } from \"../services/app.js\";\nimport { println, sprint } from \"../services/output.js\";\nimport { getUserOrLogin } from \"../services/user.js\";\n\nexport const usage = sprint`\n List the apps available to the currently logged in user.\n\n {bold USAGE}\n $ ggt list\n\n {bold EXAMPLE}\n {gray $ ggt list}\n Slug Domain\n ─────── ──────────────────\n my-app my-app.gadget.app\n example example.gadget.app\n test test.gadget.app\n`;\n\nexport const run = async () => {\n const user = await getUserOrLogin();\n\n const apps = await getApps(user);\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 let longestSlug = 0;\n let longestDomain = 0;\n\n for (const app of apps) {\n longestSlug = Math.max(longestSlug, app.slug.length);\n longestDomain = Math.max(longestDomain, app.primaryDomain.length);\n }\n\n println`{bold Slug}${\" \".repeat(longestSlug - 4)} {bold Domain}`;\n println`${\"─\".repeat(Math.max(longestSlug, 4))} ${\"─\".repeat(Math.max(longestDomain, 6))}`;\n for (const app of apps.sort((a, b) => a.slug.localeCompare(b.slug))) {\n println`${app.slug}${\" \".repeat(longestSlug - app.slug.length)} ${app.primaryDomain}`;\n }\n};\n"],"names":["getApps","println","sprint","getUserOrLogin","usage","run","user","apps","length","longestSlug","longestDomain","app","Math","max","slug","primaryDomain","repeat","sort","a","b","localeCompare"],"mappings":"AAAA,SAASA,OAAO,QAAQ,qBAAqB;AAC7C,SAASC,OAAO,EAAEC,MAAM,QAAQ,wBAAwB;AACxD,SAASC,cAAc,QAAQ,sBAAsB;AAErD,OAAO,MAAMC,QAAQF,MAAM,CAAC;;;;;;;;;;;;;AAa5B,CAAC,CAAC;AAEF,OAAO,MAAMG,MAAM;IACjB,MAAMC,OAAO,MAAMH;IAEnB,MAAMI,OAAO,MAAMP,QAAQM;IAC3B,IAAIC,KAAKC,MAAM,KAAK,GAAG;QACrBP,OAAO,CAAC;;;;IAIR,CAAC;QACD;IACF;IAEA,IAAIQ,cAAc;IAClB,IAAIC,gBAAgB;IAEpB,KAAK,MAAMC,OAAOJ,KAAM;QACtBE,cAAcG,KAAKC,GAAG,CAACJ,aAAaE,IAAIG,IAAI,CAACN,MAAM;QACnDE,gBAAgBE,KAAKC,GAAG,CAACH,eAAeC,IAAII,aAAa,CAACP,MAAM;IAClE;IAEAP,OAAO,CAAC,WAAW,EAAE,IAAIe,MAAM,CAACP,cAAc,GAAG,cAAc,CAAC;IAChER,OAAO,CAAC,EAAE,IAAIe,MAAM,CAACJ,KAAKC,GAAG,CAACJ,aAAa,IAAI,CAAC,EAAE,IAAIO,MAAM,CAACJ,KAAKC,GAAG,CAACH,eAAe,IAAI,CAAC;IAC1F,KAAK,MAAMC,OAAOJ,KAAKU,IAAI,CAAC,CAACC,GAAGC,IAAMD,EAAEJ,IAAI,CAACM,aAAa,CAACD,EAAEL,IAAI,GAAI;QACnEb,OAAO,CAAC,EAAEU,IAAIG,IAAI,CAAC,EAAE,IAAIE,MAAM,CAACP,cAAcE,IAAIG,IAAI,CAACN,MAAM,EAAE,CAAC,EAAEG,IAAII,aAAa,CAAC,CAAC;IACvF;AACF,EAAE"}
|
package/lib/commands/login.js
CHANGED
|
@@ -66,12 +66,25 @@ export const run = async ()=>{
|
|
|
66
66
|
// send the session to the server we just started.
|
|
67
67
|
const url = new URL(`https://${config.domains.services}/auth/login`);
|
|
68
68
|
url.searchParams.set("returnTo", `https://${config.domains.services}/auth/cli/callback?port=${port}`);
|
|
69
|
-
|
|
70
|
-
|
|
69
|
+
try {
|
|
70
|
+
await open(url.toString());
|
|
71
|
+
println`
|
|
71
72
|
We've opened Gadget's login page using your default browser.
|
|
72
73
|
|
|
73
74
|
Please log in and then return to this terminal.\n
|
|
74
75
|
`;
|
|
76
|
+
} catch (error) {
|
|
77
|
+
log.error("failed to open browser", {
|
|
78
|
+
error
|
|
79
|
+
});
|
|
80
|
+
println`
|
|
81
|
+
Please open the following URL in your browser and log in:
|
|
82
|
+
|
|
83
|
+
{gray ${url.toString()}}
|
|
84
|
+
|
|
85
|
+
Once logged in, return to this terminal.\n
|
|
86
|
+
`;
|
|
87
|
+
}
|
|
75
88
|
await receiveSession;
|
|
76
89
|
} finally{
|
|
77
90
|
server?.close();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/commands/login.ts"],"sourcesContent":["import getPort from \"get-port\";\nimport assert from \"node:assert\";\nimport http, { type Server } from \"node:http\";\nimport open from \"open\";\nimport { config } from \"../services/config.js\";\nimport { createLogger } from \"../services/log.js\";\nimport { println, sprint } from \"../services/output.js\";\nimport { writeSession } from \"../services/session.js\";\nimport { getUser } from \"../services/user.js\";\n\nexport const usage = sprint`\n Log in to your account.\n\n {bold USAGE}\n $ ggt login\n\n {bold EXAMPLES}\n {gray $ ggt login}\n We've opened Gadget's login page using your default browser.\n\n Please log in and then return to this terminal.\n\n Hello, Jane Doe (jane@example.com)\n`;\n\nconst log = createLogger(\"login\");\n\nexport const run = async () => {\n let server: Server | undefined;\n\n try {\n const port = await getPort();\n const receiveSession = new Promise<void>((resolve, reject) => {\n // eslint-disable-next-line @typescript-eslint/no-misused-promises\n server = http.createServer(async (req, res) => {\n const landingPage = new URL(`https://${config.domains.services}/auth/cli`);\n\n try {\n assert(req.url, \"missing url\");\n const session = new URL(req.url, `http://localhost:${port}`).searchParams.get(\"session\");\n assert(session, \"missing session\");\n\n writeSession(session);\n\n const user = await getUser();\n assert(user, \"missing user after successful login\");\n\n if (user.name) {\n println`Hello, ${user.name} {gray (${user.email})}`;\n } else {\n println`Hello, ${user.email}`;\n }\n println();\n\n landingPage.searchParams.set(\"success\", \"true\");\n resolve();\n } catch (error) {\n writeSession(undefined);\n landingPage.searchParams.set(\"success\", \"false\");\n reject(error);\n } finally {\n res.writeHead(303, { Location: landingPage.toString() });\n res.end();\n }\n });\n\n log.info(\"starting login server\", { port });\n server.listen(port);\n });\n\n // open the login page in the user's default browser have it\n // redirect to the cli callback route. The cli callback route will\n // send the session to the server we just started.\n const url = new URL(`https://${config.domains.services}/auth/login`);\n url.searchParams.set(\"returnTo\", `https://${config.domains.services}/auth/cli/callback?port=${port}`);\n await open(url.toString());\n
|
|
1
|
+
{"version":3,"sources":["../../src/commands/login.ts"],"sourcesContent":["import getPort from \"get-port\";\nimport assert from \"node:assert\";\nimport http, { type Server } from \"node:http\";\nimport open from \"open\";\nimport { config } from \"../services/config.js\";\nimport { createLogger } from \"../services/log.js\";\nimport { println, sprint } from \"../services/output.js\";\nimport { writeSession } from \"../services/session.js\";\nimport { getUser } from \"../services/user.js\";\n\nexport const usage = sprint`\n Log in to your account.\n\n {bold USAGE}\n $ ggt login\n\n {bold EXAMPLES}\n {gray $ ggt login}\n We've opened Gadget's login page using your default browser.\n\n Please log in and then return to this terminal.\n\n Hello, Jane Doe (jane@example.com)\n`;\n\nconst log = createLogger(\"login\");\n\nexport const run = async () => {\n let server: Server | undefined;\n\n try {\n const port = await getPort();\n const receiveSession = new Promise<void>((resolve, reject) => {\n // eslint-disable-next-line @typescript-eslint/no-misused-promises\n server = http.createServer(async (req, res) => {\n const landingPage = new URL(`https://${config.domains.services}/auth/cli`);\n\n try {\n assert(req.url, \"missing url\");\n const session = new URL(req.url, `http://localhost:${port}`).searchParams.get(\"session\");\n assert(session, \"missing session\");\n\n writeSession(session);\n\n const user = await getUser();\n assert(user, \"missing user after successful login\");\n\n if (user.name) {\n println`Hello, ${user.name} {gray (${user.email})}`;\n } else {\n println`Hello, ${user.email}`;\n }\n println();\n\n landingPage.searchParams.set(\"success\", \"true\");\n resolve();\n } catch (error) {\n writeSession(undefined);\n landingPage.searchParams.set(\"success\", \"false\");\n reject(error);\n } finally {\n res.writeHead(303, { Location: landingPage.toString() });\n res.end();\n }\n });\n\n log.info(\"starting login server\", { port });\n server.listen(port);\n });\n\n // open the login page in the user's default browser have it\n // redirect to the cli callback route. The cli callback route will\n // send the session to the server we just started.\n const url = new URL(`https://${config.domains.services}/auth/login`);\n url.searchParams.set(\"returnTo\", `https://${config.domains.services}/auth/cli/callback?port=${port}`);\n\n try {\n await open(url.toString());\n println`\n We've opened Gadget's login page using your default browser.\n\n Please log in and then return to this terminal.\\n\n `;\n } catch (error) {\n log.error(\"failed to open browser\", { error });\n println`\n Please open the following URL in your browser and log in:\n\n {gray ${url.toString()}}\n\n Once logged in, return to this terminal.\\n\n `;\n }\n\n await receiveSession;\n } finally {\n server?.close();\n }\n};\n"],"names":["getPort","assert","http","open","config","createLogger","println","sprint","writeSession","getUser","usage","log","run","server","port","receiveSession","Promise","resolve","reject","createServer","req","res","landingPage","URL","domains","services","url","session","searchParams","get","user","name","email","set","error","undefined","writeHead","Location","toString","end","info","listen","close"],"mappings":"AAAA,OAAOA,aAAa,WAAW;AAC/B,OAAOC,YAAY,cAAc;AACjC,OAAOC,UAA2B,YAAY;AAC9C,OAAOC,UAAU,OAAO;AACxB,SAASC,MAAM,QAAQ,wBAAwB;AAC/C,SAASC,YAAY,QAAQ,qBAAqB;AAClD,SAASC,OAAO,EAAEC,MAAM,QAAQ,wBAAwB;AACxD,SAASC,YAAY,QAAQ,yBAAyB;AACtD,SAASC,OAAO,QAAQ,sBAAsB;AAE9C,OAAO,MAAMC,QAAQH,MAAM,CAAC;;;;;;;;;;;;;AAa5B,CAAC,CAAC;AAEF,MAAMI,MAAMN,aAAa;AAEzB,OAAO,MAAMO,MAAM;IACjB,IAAIC;IAEJ,IAAI;QACF,MAAMC,OAAO,MAAMd;QACnB,MAAMe,iBAAiB,IAAIC,QAAc,CAACC,SAASC;YACjD,kEAAkE;YAClEL,SAASX,KAAKiB,YAAY,CAAC,OAAOC,KAAKC;gBACrC,MAAMC,cAAc,IAAIC,IAAI,CAAC,QAAQ,EAAEnB,OAAOoB,OAAO,CAACC,QAAQ,CAAC,SAAS,CAAC;gBAEzE,IAAI;oBACFxB,OAAOmB,IAAIM,GAAG,EAAE;oBAChB,MAAMC,UAAU,IAAIJ,IAAIH,IAAIM,GAAG,EAAE,CAAC,iBAAiB,EAAEZ,KAAK,CAAC,EAAEc,YAAY,CAACC,GAAG,CAAC;oBAC9E5B,OAAO0B,SAAS;oBAEhBnB,aAAamB;oBAEb,MAAMG,OAAO,MAAMrB;oBACnBR,OAAO6B,MAAM;oBAEb,IAAIA,KAAKC,IAAI,EAAE;wBACbzB,OAAO,CAAC,OAAO,EAAEwB,KAAKC,IAAI,CAAC,QAAQ,EAAED,KAAKE,KAAK,CAAC,EAAE,CAAC;oBACrD,OAAO;wBACL1B,OAAO,CAAC,OAAO,EAAEwB,KAAKE,KAAK,CAAC,CAAC;oBAC/B;oBACA1B;oBAEAgB,YAAYM,YAAY,CAACK,GAAG,CAAC,WAAW;oBACxChB;gBACF,EAAE,OAAOiB,OAAO;oBACd1B,aAAa2B;oBACbb,YAAYM,YAAY,CAACK,GAAG,CAAC,WAAW;oBACxCf,OAAOgB;gBACT,SAAU;oBACRb,IAAIe,SAAS,CAAC,KAAK;wBAAEC,UAAUf,YAAYgB,QAAQ;oBAAG;oBACtDjB,IAAIkB,GAAG;gBACT;YACF;YAEA5B,IAAI6B,IAAI,CAAC,yBAAyB;gBAAE1B;YAAK;YACzCD,OAAO4B,MAAM,CAAC3B;QAChB;QAEA,4DAA4D;QAC5D,kEAAkE;QAClE,kDAAkD;QAClD,MAAMY,MAAM,IAAIH,IAAI,CAAC,QAAQ,EAAEnB,OAAOoB,OAAO,CAACC,QAAQ,CAAC,WAAW,CAAC;QACnEC,IAAIE,YAAY,CAACK,GAAG,CAAC,YAAY,CAAC,QAAQ,EAAE7B,OAAOoB,OAAO,CAACC,QAAQ,CAAC,wBAAwB,EAAEX,KAAK,CAAC;QAEpG,IAAI;YACF,MAAMX,KAAKuB,IAAIY,QAAQ;YACvBhC,OAAO,CAAC;;;;IAIV,CAAC;QACD,EAAE,OAAO4B,OAAO;YACdvB,IAAIuB,KAAK,CAAC,0BAA0B;gBAAEA;YAAM;YAC5C5B,OAAO,CAAC;;;gBAGE,EAAEoB,IAAIY,QAAQ,GAAG;;;MAG3B,CAAC;QACH;QAEA,MAAMvB;IACR,SAAU;QACRF,QAAQ6B;IACV;AACF,EAAE"}
|
package/lib/commands/root.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import arg from "arg";
|
|
2
2
|
import debug from "debug";
|
|
3
|
-
import _ from "lodash";
|
|
4
3
|
import { parseBoolean } from "../services/args.js";
|
|
5
4
|
import { config } from "../services/config.js";
|
|
6
5
|
import { CLIError } from "../services/errors.js";
|
|
6
|
+
import { isNil } from "../services/is.js";
|
|
7
7
|
import { println, sortByLevenshtein, sprint } from "../services/output.js";
|
|
8
8
|
import { warnIfUpdateAvailable } from "../services/version.js";
|
|
9
9
|
import { availableCommands } from "./index.js";
|
|
@@ -51,11 +51,11 @@ export const run = async ()=>{
|
|
|
51
51
|
process.exit(0);
|
|
52
52
|
}
|
|
53
53
|
const command = rootArgs._.shift();
|
|
54
|
-
if (
|
|
54
|
+
if (isNil(command)) {
|
|
55
55
|
println(usage);
|
|
56
56
|
process.exit(0);
|
|
57
57
|
}
|
|
58
|
-
if (!
|
|
58
|
+
if (!availableCommands.includes(command)) {
|
|
59
59
|
const [closest] = sortByLevenshtein(command, availableCommands);
|
|
60
60
|
println`
|
|
61
61
|
Unknown command {yellow ${command}}
|
package/lib/commands/root.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/commands/root.ts"],"sourcesContent":["import arg from \"arg\";\nimport debug from \"debug\";\nimport
|
|
1
|
+
{"version":3,"sources":["../../src/commands/root.ts"],"sourcesContent":["import arg from \"arg\";\nimport debug from \"debug\";\nimport { parseBoolean } from \"../services/args.js\";\nimport { config } from \"../services/config.js\";\nimport { CLIError } from \"../services/errors.js\";\nimport { isNil } from \"../services/is.js\";\nimport { println, sortByLevenshtein, sprint } from \"../services/output.js\";\nimport { warnIfUpdateAvailable } from \"../services/version.js\";\nimport { availableCommands, type Command } from \"./index.js\";\n\nexport const usage = sprint`\n The command-line interface for Gadget\n\n {bold VERSION}\n ${config.versionFull}\n\n {bold USAGE}\n $ ggt [COMMAND]\n\n {bold FLAGS}\n -h, --help {gray Print command's usage}\n -v, --version {gray Print version}\n --debug {gray Print debug output}\n\n {bold COMMANDS}\n sync Sync your Gadget application's source code to and\n from your local filesystem.\n list List your apps.\n login Log in to your account.\n logout Log out of your account.\n whoami Print the currently logged in account.\n`;\n\nexport const rootArgsSpec = {\n \"--help\": Boolean,\n \"-h\": \"--help\",\n \"--version\": Boolean,\n \"-v\": \"--version\",\n \"--debug\": Boolean,\n};\n\nexport type RootArgs = arg.Result<typeof rootArgsSpec>;\n\nexport const run = async () => {\n await warnIfUpdateAvailable();\n\n const rootArgs = arg(rootArgsSpec, {\n argv: process.argv.slice(2),\n permissive: true,\n stopAtPositional: false,\n });\n\n if (rootArgs[\"--debug\"] ?? parseBoolean(process.env[\"DEBUG\"])) {\n debug.enable(\"ggt:*\");\n }\n\n if (rootArgs[\"--version\"]) {\n println(config.version);\n process.exit(0);\n }\n\n const command = rootArgs._.shift() as (typeof availableCommands)[number] | undefined;\n if (isNil(command)) {\n println(usage);\n process.exit(0);\n }\n\n if (!availableCommands.includes(command)) {\n const [closest] = sortByLevenshtein(command, availableCommands);\n println`\n Unknown command {yellow ${command}}\n\n Did you mean {blueBright ${closest}}?\n\n Run {gray ggt --help} for usage\n `;\n process.exit(1);\n }\n\n const cmd = (await import(`./${command}.js`)) as Command;\n\n if (rootArgs[\"--help\"]) {\n println(cmd.usage);\n process.exit(0);\n }\n\n try {\n await cmd.init?.(rootArgs);\n await cmd.run(rootArgs);\n } catch (cause) {\n const error = CLIError.from(cause);\n println(error.render());\n await error.capture();\n process.exit(1);\n }\n};\n"],"names":["arg","debug","parseBoolean","config","CLIError","isNil","println","sortByLevenshtein","sprint","warnIfUpdateAvailable","availableCommands","usage","versionFull","rootArgsSpec","Boolean","run","rootArgs","argv","process","slice","permissive","stopAtPositional","env","enable","version","exit","command","_","shift","includes","closest","cmd","init","cause","error","from","render","capture"],"mappings":"AAAA,OAAOA,SAAS,MAAM;AACtB,OAAOC,WAAW,QAAQ;AAC1B,SAASC,YAAY,QAAQ,sBAAsB;AACnD,SAASC,MAAM,QAAQ,wBAAwB;AAC/C,SAASC,QAAQ,QAAQ,wBAAwB;AACjD,SAASC,KAAK,QAAQ,oBAAoB;AAC1C,SAASC,OAAO,EAAEC,iBAAiB,EAAEC,MAAM,QAAQ,wBAAwB;AAC3E,SAASC,qBAAqB,QAAQ,yBAAyB;AAC/D,SAASC,iBAAiB,QAAsB,aAAa;AAE7D,OAAO,MAAMC,QAAQH,MAAM,CAAC;;;;MAItB,EAAEL,OAAOS,WAAW,CAAC;;;;;;;;;;;;;;;;;AAiB3B,CAAC,CAAC;AAEF,OAAO,MAAMC,eAAe;IAC1B,UAAUC;IACV,MAAM;IACN,aAAaA;IACb,MAAM;IACN,WAAWA;AACb,EAAE;AAIF,OAAO,MAAMC,MAAM;IACjB,MAAMN;IAEN,MAAMO,WAAWhB,IAAIa,cAAc;QACjCI,MAAMC,QAAQD,IAAI,CAACE,KAAK,CAAC;QACzBC,YAAY;QACZC,kBAAkB;IACpB;IAEA,IAAIL,QAAQ,CAAC,UAAU,IAAId,aAAagB,QAAQI,GAAG,CAAC,QAAQ,GAAG;QAC7DrB,MAAMsB,MAAM,CAAC;IACf;IAEA,IAAIP,QAAQ,CAAC,YAAY,EAAE;QACzBV,QAAQH,OAAOqB,OAAO;QACtBN,QAAQO,IAAI,CAAC;IACf;IAEA,MAAMC,UAAUV,SAASW,CAAC,CAACC,KAAK;IAChC,IAAIvB,MAAMqB,UAAU;QAClBpB,QAAQK;QACRO,QAAQO,IAAI,CAAC;IACf;IAEA,IAAI,CAACf,kBAAkBmB,QAAQ,CAACH,UAAU;QACxC,MAAM,CAACI,QAAQ,GAAGvB,kBAAkBmB,SAAShB;QAC7CJ,OAAO,CAAC;8BACkB,EAAEoB,QAAQ;;+BAET,EAAEI,QAAQ;;;IAGrC,CAAC;QACDZ,QAAQO,IAAI,CAAC;IACf;IAEA,MAAMM,MAAO,MAAM,MAAM,CAAC,CAAC,EAAE,EAAEL,QAAQ,GAAG,CAAC;IAE3C,IAAIV,QAAQ,CAAC,SAAS,EAAE;QACtBV,QAAQyB,IAAIpB,KAAK;QACjBO,QAAQO,IAAI,CAAC;IACf;IAEA,IAAI;QACF,MAAMM,IAAIC,IAAI,GAAGhB;QACjB,MAAMe,IAAIhB,GAAG,CAACC;IAChB,EAAE,OAAOiB,OAAO;QACd,MAAMC,QAAQ9B,SAAS+B,IAAI,CAACF;QAC5B3B,QAAQ4B,MAAME,MAAM;QACpB,MAAMF,MAAMG,OAAO;QACnBnB,QAAQO,IAAI,CAAC;IACf;AACF,EAAE"}
|
package/lib/commands/sync.js
CHANGED
|
@@ -1,27 +1,29 @@
|
|
|
1
1
|
import { _ as _define_property } from "@swc/helpers/_/_define_property";
|
|
2
2
|
import arg from "arg";
|
|
3
|
-
import
|
|
3
|
+
import dayjs from "dayjs";
|
|
4
4
|
import { execa } from "execa";
|
|
5
5
|
import fs from "fs-extra";
|
|
6
|
-
import inquirer from "inquirer";
|
|
7
|
-
import _ from "lodash";
|
|
8
6
|
import ms from "ms";
|
|
7
|
+
import path from "node:path";
|
|
9
8
|
import pMap from "p-map";
|
|
10
9
|
import PQueue from "p-queue";
|
|
11
|
-
import path from "path";
|
|
12
10
|
import FSWatcher from "watcher";
|
|
13
11
|
import which from "which";
|
|
14
12
|
import { FileSyncEncoding } from "../__generated__/graphql.js";
|
|
15
13
|
import { AppArg } from "../services/args.js";
|
|
16
14
|
import { config } from "../services/config.js";
|
|
15
|
+
import { debounce } from "../services/debounce.js";
|
|
16
|
+
import { defaults } from "../services/defaults.js";
|
|
17
17
|
import { EditGraphQL } from "../services/edit-graphql.js";
|
|
18
18
|
import { YarnNotFoundError } from "../services/errors.js";
|
|
19
19
|
import { FileSync, PUBLISH_FILE_SYNC_EVENTS_MUTATION, REMOTE_FILES_VERSION_QUERY, REMOTE_FILE_SYNC_EVENTS_SUBSCRIPTION, printPaths } from "../services/filesync.js";
|
|
20
|
-
import { swallowEnoent } from "../services/fs
|
|
20
|
+
import { swallowEnoent } from "../services/fs.js";
|
|
21
21
|
import { createLogger } from "../services/log.js";
|
|
22
|
+
import { noop } from "../services/noop.js";
|
|
22
23
|
import { notify } from "../services/notify.js";
|
|
23
24
|
import { println, sprint } from "../services/output.js";
|
|
24
25
|
import { PromiseSignal } from "../services/promise.js";
|
|
26
|
+
import { select } from "../services/prompt.js";
|
|
25
27
|
import { getUserOrLogin } from "../services/user.js";
|
|
26
28
|
export const usage = sprint`
|
|
27
29
|
Sync your Gadget application's source code to and from
|
|
@@ -133,7 +135,7 @@ export class Sync {
|
|
|
133
135
|
* - Ensures yarn v1 is installed.
|
|
134
136
|
* - Prompts the user how to resolve conflicts if the local filesystem has changed since the last sync.
|
|
135
137
|
*/ async init(rootArgs) {
|
|
136
|
-
this.args =
|
|
138
|
+
this.args = defaults(arg(argSpec, {
|
|
137
139
|
argv: rootArgs._
|
|
138
140
|
}), {
|
|
139
141
|
"--file-push-delay": 100,
|
|
@@ -189,16 +191,14 @@ export class Sync {
|
|
|
189
191
|
}
|
|
190
192
|
let action;
|
|
191
193
|
if (hasLocalChanges) {
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
name: "action",
|
|
194
|
+
action = await select({
|
|
195
|
+
message: hasRemoteChanges ? "Remote files have also changed. How would you like to proceed?" : "How would you like to proceed?",
|
|
195
196
|
choices: [
|
|
196
197
|
"Cancel (Ctrl+C)",
|
|
197
198
|
"Merge local files with remote ones",
|
|
198
199
|
"Reset local files to remote ones"
|
|
199
|
-
]
|
|
200
|
-
|
|
201
|
-
}));
|
|
200
|
+
]
|
|
201
|
+
});
|
|
202
202
|
}
|
|
203
203
|
// get all the changed files again in case more changed
|
|
204
204
|
changedFiles = await getChangedFiles();
|
|
@@ -261,14 +261,16 @@ export class Sync {
|
|
|
261
261
|
const stopped = new PromiseSignal();
|
|
262
262
|
const recentRemoteChangesInterval = setInterval(()=>{
|
|
263
263
|
for (const [path, timestamp] of this.recentRemoteChanges){
|
|
264
|
-
if (
|
|
264
|
+
if (dayjs().isAfter(timestamp + ms("5s"))) {
|
|
265
265
|
// this change should have been seen by now, so remove it
|
|
266
266
|
this.recentRemoteChanges.delete(path);
|
|
267
267
|
}
|
|
268
268
|
}
|
|
269
269
|
}, ms("1s")).unref();
|
|
270
270
|
this.stop = async (e)=>{
|
|
271
|
-
if (this.status
|
|
271
|
+
if (this.status !== 1) {
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
272
274
|
this.status = 2;
|
|
273
275
|
error = e;
|
|
274
276
|
this.log.info("stopping", {
|
|
@@ -295,7 +297,9 @@ export class Sync {
|
|
|
295
297
|
"SIGTERM"
|
|
296
298
|
]){
|
|
297
299
|
process.on(signal, ()=>{
|
|
298
|
-
if (this.status
|
|
300
|
+
if (this.status !== 1) {
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
299
303
|
println` Stopping... {gray (press Ctrl+C again to force)}`;
|
|
300
304
|
void this.stop();
|
|
301
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
|
|
@@ -318,22 +322,22 @@ export class Sync {
|
|
|
318
322
|
next: ({ remoteFileSyncEvents })=>{
|
|
319
323
|
const remoteFilesVersion = remoteFileSyncEvents.remoteFilesVersion;
|
|
320
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
|
|
321
|
-
const
|
|
322
|
-
const changed =
|
|
323
|
-
const deleted =
|
|
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);
|
|
324
328
|
this.log.info("received files", {
|
|
325
329
|
remoteFilesVersion,
|
|
326
|
-
changed:
|
|
327
|
-
deleted:
|
|
330
|
+
changed: changed.map((x)=>x.path),
|
|
331
|
+
deleted: deleted.map((x)=>x.path)
|
|
328
332
|
});
|
|
329
333
|
this._enqueue(async ()=>{
|
|
330
334
|
// add all the non-ignored files and directories we're about
|
|
331
335
|
// to touch to recentRemoteChanges so that we don't send
|
|
332
336
|
// them back
|
|
333
|
-
for (const file of
|
|
337
|
+
for (const file of [
|
|
334
338
|
...changed,
|
|
335
339
|
...deleted
|
|
336
|
-
]
|
|
340
|
+
].filter((file)=>!this.filesync.ignores(file.path))){
|
|
337
341
|
this.recentRemoteChanges.set(file.path, Date.now());
|
|
338
342
|
let dir = path.dirname(file.path);
|
|
339
343
|
while(dir !== "."){
|
|
@@ -341,26 +345,23 @@ export class Sync {
|
|
|
341
345
|
dir = path.dirname(dir);
|
|
342
346
|
}
|
|
343
347
|
}
|
|
344
|
-
if (changed.length || deleted.length) {
|
|
345
|
-
println`Received {gray ${
|
|
346
|
-
printPaths("←",
|
|
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));
|
|
347
351
|
}
|
|
348
|
-
await this.filesync.write(remoteFilesVersion, changed,
|
|
349
|
-
if (
|
|
350
|
-
"path",
|
|
351
|
-
"yarn.lock"
|
|
352
|
-
])) {
|
|
352
|
+
await this.filesync.write(remoteFilesVersion, changed, deleted.map((x)=>x.path));
|
|
353
|
+
if (changed.some((x)=>x.path === "yarn.lock")) {
|
|
353
354
|
await execa("yarn", [
|
|
354
355
|
"install"
|
|
355
356
|
], {
|
|
356
357
|
cwd: this.filesync.dir
|
|
357
|
-
}).catch(
|
|
358
|
+
}).catch(noop);
|
|
358
359
|
}
|
|
359
360
|
});
|
|
360
361
|
}
|
|
361
362
|
});
|
|
362
363
|
const localFilesBuffer = new Map();
|
|
363
|
-
this.publish =
|
|
364
|
+
this.publish = debounce(this.args["--file-push-delay"], ()=>{
|
|
364
365
|
const localFiles = new Map(localFilesBuffer.entries());
|
|
365
366
|
localFilesBuffer.clear();
|
|
366
367
|
this._enqueue(async ()=>{
|
|
@@ -387,7 +388,7 @@ export class Sync {
|
|
|
387
388
|
swallowEnoent(error);
|
|
388
389
|
}
|
|
389
390
|
});
|
|
390
|
-
if (
|
|
391
|
+
if (changed.length === 0 && deleted.length === 0) {
|
|
391
392
|
return;
|
|
392
393
|
}
|
|
393
394
|
const { publishFileSyncEvents } = await this.graphql.query({
|
|
@@ -401,15 +402,14 @@ export class Sync {
|
|
|
401
402
|
}
|
|
402
403
|
});
|
|
403
404
|
await this.filesync.write(publishFileSyncEvents.remoteFilesVersion, [], []);
|
|
404
|
-
println`Sent {gray ${
|
|
405
|
-
printPaths("→",
|
|
405
|
+
println`Sent {gray ${dayjs().format("hh:mm:ss A")}}`;
|
|
406
|
+
printPaths("→", changed.map((x)=>x.path), deleted.map((x)=>x.path));
|
|
406
407
|
});
|
|
407
|
-
}
|
|
408
|
+
});
|
|
408
409
|
this.watcher = new FSWatcher(this.filesync.dir, {
|
|
409
|
-
// paths that we never want to publish
|
|
410
|
-
ignore: /(\.gadget|\.git|node_modules|\.DS_Store)/,
|
|
411
410
|
// don't emit an event for every watched file on boot
|
|
412
411
|
ignoreInitial: true,
|
|
412
|
+
ignore: (path)=>this.filesync.ignores(path),
|
|
413
413
|
renameDetection: true,
|
|
414
414
|
recursive: true,
|
|
415
415
|
debounce: this.args["--file-watch-debounce"],
|
package/lib/commands/sync.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/commands/sync.ts"],"sourcesContent":["import arg from \"arg\";\nimport { format as formatDate, isAfter } from \"date-fns\";\nimport { execa } from \"execa\";\nimport type { Stats } from \"fs-extra\";\nimport fs from \"fs-extra\";\nimport inquirer from \"inquirer\";\nimport _ from \"lodash\";\nimport ms from \"ms\";\nimport pMap from \"p-map\";\nimport PQueue from \"p-queue\";\nimport path from \"path\";\nimport FSWatcher from \"watcher\";\nimport which from \"which\";\nimport { FileSyncEncoding, type FileSyncChangedEventInput, type FileSyncDeletedEventInput } from \"../__generated__/graphql.js\";\nimport { AppArg } from \"../services/args.js\";\nimport { config } from \"../services/config.js\";\nimport { EditGraphQL } from \"../services/edit-graphql.js\";\nimport { YarnNotFoundError } from \"../services/errors.js\";\nimport {\n FileSync,\n PUBLISH_FILE_SYNC_EVENTS_MUTATION,\n REMOTE_FILES_VERSION_QUERY,\n REMOTE_FILE_SYNC_EVENTS_SUBSCRIPTION,\n printPaths,\n} from \"../services/filesync.js\";\nimport { swallowEnoent } from \"../services/fs-utils.js\";\nimport { createLogger } from \"../services/log.js\";\nimport { notify } from \"../services/notify.js\";\nimport { println, sprint } from \"../services/output.js\";\nimport { PromiseSignal } from \"../services/promise.js\";\nimport { getUserOrLogin } from \"../services/user.js\";\nimport { type RootArgs } from \"./root.js\";\n\nexport const usage = sprint`\n Sync your Gadget application's source code to and from\n your local filesystem.\n\n {bold USAGE}\n $ ggt sync [DIRECTORY] [--app <name>]\n\n {bold ARGUMENTS}\n DIRECTORY {dim [default: .] The directory to sync files to.\n\n If the directory doesn't exist, it will be created.}\n\n {bold FLAGS}\n -a, --app=<name> {dim The Gadget application to sync files to.}\n\n --force {dim Whether to sync even if we can't determine\n the state of your local files relative to\n your remote ones.}\n\n {bold DESCRIPTION}\n Sync provides the ability to sync your Gadget application's source\n code to and from your local filesystem.\n\n While ggt sync is running, local file changes are immediately\n reflected within Gadget, while files that are changed remotely are\n immediately saved to your local filesystem.\n\n Use cases for this include:\n • Developing locally with your own editor like VSCode\n • Storing your source code in a Git repository like GitHub\n\n Sync includes the concept of a {dim .ignore} file. This file may\n contain a list of files and directories that won't be received or\n sent to Gadget when syncing. The format of this file is identical\n to the one used by Git {dim (https://git-scm.com/docs/gitignore)}.\n\n The following files and directories are always ignored:\n • .DS_Store\n • .gadget\n • .git\n • node_modules\n\n Note:\n • If you have separate development and production environments,\n {dim ggt sync} will only sync with your development environment\n • Gadget applications only support installing dependencies\n with Yarn 1 {dim (https://classic.yarnpkg.com/lang/en/)}\n • Since file changes are immediately reflected in Gadget,\n avoid the following while {dim ggt sync} is running:\n • Deleting all your files\n • Moving all your files to a different directory\n\n {bold EXAMPLES}\n {dim $ ggt sync --app my-app ~/gadget/my-app}\n\n App my-app\n Editor https://my-app.gadget.app/edit\n Playground https://my-app.gadget.app/api/graphql/playground\n Docs https://docs.gadget.dev/api/my-app\n\n Endpoints\n • https://my-app.gadget.app\n • https://my-app--development.gadget.app\n\n Watching for file changes... {dim Press Ctrl+C to stop}\n\n Received {dim 12:00:00 PM}\n {green ←} routes/GET.js {dim (changed)}\n {green ←} user/signUp/signIn.js {dim (changed)}\n {dim 2 files in total. 2 changed, 0 deleted.}\n\n Sent {dim 12:00:03 PM}\n {green →} routes/GET.ts {dim (changed)}\n {dim 1 file in total. 1 changed, 0 deleted.}\n\n ^C Stopping... {dim (press Ctrl+C again to force)}\n Goodbye!\n`;\n\nexport enum SyncStatus {\n STARTING,\n RUNNING,\n STOPPING,\n STOPPED,\n}\n\nexport enum Action {\n CANCEL = \"Cancel (Ctrl+C)\",\n MERGE = \"Merge local files with remote ones\",\n RESET = \"Reset local files to remote ones\",\n}\n\nconst argSpec = {\n \"-a\": \"--app\",\n \"--app\": AppArg,\n \"--force\": Boolean,\n \"--file-push-delay\": Number,\n \"--file-watch-debounce\": Number,\n \"--file-watch-poll-interval\": Number,\n \"--file-watch-poll-timeout\": Number,\n \"--file-watch-rename-timeout\": Number,\n};\n\nexport class Sync {\n args!: arg.Result<typeof argSpec>;\n\n /**\n * The current status of the sync process.\n */\n status = SyncStatus.STARTING;\n\n /**\n * A list of filepaths that have changed because of a remote file-sync\n * event. This is used to avoid sending files that we recently\n * received from a remote file-sync event.\n */\n recentRemoteChanges = new Map<string, number>();\n\n /**\n * A FIFO async callback queue that ensures we process file-sync events in the order they occurred.\n */\n queue = new PQueue({ concurrency: 1 });\n\n /**\n * A GraphQL client connected to the app's /edit/api/graphql-ws endpoint\n */\n graphql!: EditGraphQL;\n\n /**\n * Watches the local filesystem for changes.\n */\n watcher!: FSWatcher;\n\n /**\n * Handles writing files to the local filesystem.\n */\n filesync!: FileSync;\n\n /**\n * A debounced function that enqueue's local file changes to be sent to Gadget.\n */\n publish!: _.DebouncedFunc<() => void>;\n\n /**\n * Gracefully stops the sync.\n */\n stop!: (error?: unknown) => Promise<void>;\n\n /**\n * A logger for the sync command.\n */\n log = createLogger(\"sync\", () => {\n return {\n app: this.filesync.app.slug,\n filesVersion: String(this.filesync.filesVersion),\n mtime: this.filesync.mtime,\n };\n });\n\n /**\n * Initializes the sync process.\n * - Ensures the directory exists.\n * - Ensures the directory is empty or contains a `.gadget/sync.json` file.\n * - Ensures an app is selected and that it matches the app the directory was previously synced to.\n * - Ensures yarn v1 is installed.\n * - Prompts the user how to resolve conflicts if the local filesystem has changed since the last sync.\n */\n async init(rootArgs: RootArgs): Promise<void> {\n this.args = _.defaults(arg(argSpec, { argv: rootArgs._ }), {\n \"--file-push-delay\": 100,\n \"--file-watch-debounce\": 300,\n \"--file-watch-poll-interval\": 3_000,\n \"--file-watch-poll-timeout\": 20_000,\n \"--file-watch-rename-timeout\": 1_250,\n });\n\n if (!which.sync(\"yarn\", { nothrow: true })) {\n throw new YarnNotFoundError();\n }\n\n const user = await getUserOrLogin();\n\n this.filesync = await FileSync.init(user, {\n dir: this.args._[0],\n app: this.args[\"--app\"],\n force: this.args[\"--force\"],\n extraIgnorePaths: [\".gadget\"],\n });\n\n this.graphql = new EditGraphQL(this.filesync.app);\n\n const { remoteFilesVersion } = await this.graphql.query({ query: REMOTE_FILES_VERSION_QUERY });\n const hasRemoteChanges = BigInt(remoteFilesVersion) > this.filesync.filesVersion;\n\n const getChangedFiles = async (): Promise<Map<string, Stats>> => {\n const files = new Map();\n for await (const [absolutePath, stats] of this.filesync.walkDir()) {\n if (stats.mtime.getTime() > this.filesync.mtime) {\n files.set(this.filesync.normalize(absolutePath, stats.isDirectory()), stats);\n }\n }\n\n // never include the root directory\n files.delete(\"/\");\n\n return files;\n };\n\n let changedFiles = await getChangedFiles();\n const hasLocalChanges = changedFiles.size > 0;\n if (hasLocalChanges) {\n this.log.info(\"local files have changed\", {\n remoteFilesVersion,\n hasRemoteChanges,\n hasLocalChanges,\n changed: Array.from(changedFiles.keys()),\n });\n\n println(\"Local files have changed since you last synced\");\n printPaths(\"-\", Array.from(changedFiles.keys()), [], { limit: changedFiles.size });\n println();\n }\n\n let action: Action | undefined;\n if (hasLocalChanges) {\n ({ action } = await inquirer.prompt({\n type: \"list\",\n name: \"action\",\n choices: [Action.CANCEL, Action.MERGE, Action.RESET],\n message: hasRemoteChanges ? \"Remote files have also changed. How would you like to proceed?\" : \"How would you like to proceed?\",\n }));\n }\n\n // get all the changed files again in case more changed\n changedFiles = await getChangedFiles();\n\n switch (action) {\n case Action.MERGE: {\n this.log.info(\"merging local changes\", {\n remoteFilesVersion,\n hasRemoteChanges,\n hasLocalChanges,\n changed: Array.from(changedFiles.keys()),\n });\n\n // We purposefully don't write the returned files version here\n // because we haven't received its associated files yet. This\n // will cause us to receive the remote files that have changed\n // since the last sync (+ the local files that we just\n // published)\n await this.graphql.query({\n query: PUBLISH_FILE_SYNC_EVENTS_MUTATION,\n variables: {\n input: {\n expectedRemoteFilesVersion: remoteFilesVersion,\n changed: await pMap(changedFiles, async ([normalizedPath, stats]) => ({\n path: normalizedPath,\n mode: stats.mode,\n content: stats.isDirectory() ? \"\" : await fs.readFile(this.filesync.absolute(normalizedPath), \"base64\"),\n encoding: FileSyncEncoding.Base64,\n })),\n deleted: [],\n },\n },\n });\n break;\n }\n case Action.RESET: {\n this.log.info(\"resetting local changes\", {\n remoteFilesVersion,\n hasRemoteChanges,\n hasLocalChanges,\n changed: Array.from(changedFiles.keys()),\n });\n\n // delete all the local files that have changed since the last\n // sync and set the files version to 0 so we receive all the\n // remote files again, including any files that we just deleted\n // that still exist\n await this.filesync.write(0n, [], changedFiles.keys(), true);\n break;\n }\n case Action.CANCEL: {\n process.exit(0);\n }\n }\n }\n\n /**\n * Runs the sync process until it is stopped or an error occurs.\n */\n async run(): Promise<void> {\n let error: unknown;\n const stopped = new PromiseSignal();\n\n const recentRemoteChangesInterval = setInterval(() => {\n for (const [path, timestamp] of this.recentRemoteChanges) {\n if (isAfter(Date.now(), timestamp + ms(\"5s\"))) {\n // this change should have been seen by now, so remove it\n this.recentRemoteChanges.delete(path);\n }\n }\n }, ms(\"1s\")).unref();\n\n this.stop = async (e?: unknown) => {\n if (this.status != SyncStatus.RUNNING) return;\n this.status = SyncStatus.STOPPING;\n error = e;\n\n this.log.info(\"stopping\", { error });\n\n try {\n clearInterval(recentRemoteChangesInterval);\n unsubscribe();\n this.watcher.removeAllListeners();\n this.publish.flush();\n await this.queue.onIdle();\n } finally {\n await Promise.allSettled([this.watcher.close(), this.graphql.dispose()]);\n\n this.status = SyncStatus.STOPPED;\n stopped.resolve();\n this.log.info(\"stopped\");\n }\n };\n\n for (const signal of [\"SIGINT\", \"SIGTERM\"] as const) {\n process.on(signal, () => {\n if (this.status != SyncStatus.RUNNING) return;\n\n println` Stopping... {gray (press Ctrl+C again to force)}`;\n void this.stop();\n\n // 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\n // SIGINT from triggering the force exit listener, we wait a bit before registering it. This is a bit of a hack, but it works.\n setTimeout(() => {\n process.once(signal, () => {\n println(\" Exiting immediately. Note that files may not have finished syncing.\");\n process.exit(1);\n });\n }, 100).unref();\n });\n }\n\n const unsubscribe = this.graphql.subscribe(\n {\n query: REMOTE_FILE_SYNC_EVENTS_SUBSCRIPTION,\n variables: () => ({ localFilesVersion: String(this.filesync.filesVersion) }),\n },\n {\n error: (error) => void this.stop(error),\n next: ({ remoteFileSyncEvents }) => {\n const remoteFilesVersion = remoteFileSyncEvents.remoteFilesVersion;\n\n // we always ignore .gadget/ files so that we don't publish them (they're managed by gadget), but we still want to receive them\n const filter = (event: { path: string }) => _.startsWith(event.path, \".gadget/\") || !this.filesync.ignores(event.path);\n const changed = _.filter(remoteFileSyncEvents.changed, filter);\n const deleted = _.filter(remoteFileSyncEvents.deleted, filter);\n\n this.log.info(\"received files\", {\n remoteFilesVersion,\n changed: _.map(changed, \"path\"),\n deleted: _.map(deleted, \"path\"),\n });\n\n this._enqueue(async () => {\n // add all the non-ignored files and directories we're about\n // to touch to recentRemoteChanges so that we don't send\n // them back\n for (const file of _.filter([...changed, ...deleted], (file) => !this.filesync.ignores(file.path))) {\n this.recentRemoteChanges.set(file.path, Date.now());\n\n let dir = path.dirname(file.path);\n while (dir !== \".\") {\n this.recentRemoteChanges.set(dir + \"/\", Date.now());\n dir = path.dirname(dir);\n }\n }\n\n if (changed.length || deleted.length) {\n println`Received {gray ${formatDate(new Date(), \"pp\")}}`;\n printPaths(\"←\", _.map(changed, \"path\"), _.map(deleted, \"path\"));\n }\n\n await this.filesync.write(remoteFilesVersion, changed, _.map(deleted, \"path\"));\n\n if (_.some(changed, [\"path\", \"yarn.lock\"])) {\n await execa(\"yarn\", [\"install\"], { cwd: this.filesync.dir }).catch(_.noop);\n }\n });\n },\n },\n );\n\n const localFilesBuffer = new Map<\n string,\n | { mode: number; isDirectory: boolean }\n | { isDeleted: true; isDirectory: boolean }\n | { mode: number; oldPath: string; newPath: string; isDirectory: boolean }\n >();\n\n this.publish = _.debounce(() => {\n const localFiles = new Map(localFilesBuffer.entries());\n localFilesBuffer.clear();\n\n this._enqueue(async () => {\n const changed: FileSyncChangedEventInput[] = [];\n const deleted: FileSyncDeletedEventInput[] = [];\n\n await pMap(localFiles, async ([normalizedPath, file]) => {\n if (\"isDeleted\" in file) {\n deleted.push({ path: normalizedPath });\n return;\n }\n\n try {\n changed.push({\n path: normalizedPath,\n oldPath: \"oldPath\" in file ? file.oldPath : undefined,\n mode: file.mode,\n content: file.isDirectory ? \"\" : await fs.readFile(this.filesync.absolute(normalizedPath), FileSyncEncoding.Base64),\n encoding: FileSyncEncoding.Base64,\n });\n } catch (error) {\n // A file could have been changed and then deleted before we process the change event, so the readFile\n // above will raise an ENOENT. This is normal operation, so just ignore this event.\n swallowEnoent(error);\n }\n });\n\n if (!changed.length && !deleted.length) {\n return;\n }\n\n const { publishFileSyncEvents } = await this.graphql.query({\n query: PUBLISH_FILE_SYNC_EVENTS_MUTATION,\n variables: { input: { expectedRemoteFilesVersion: String(this.filesync.filesVersion), changed, deleted } },\n });\n\n await this.filesync.write(publishFileSyncEvents.remoteFilesVersion, [], []);\n\n println`Sent {gray ${formatDate(new Date(), \"pp\")}}`;\n printPaths(\"→\", _.map(changed, \"path\"), _.map(deleted, \"path\"));\n });\n }, this.args[\"--file-push-delay\"]);\n\n this.watcher = new FSWatcher(this.filesync.dir, {\n // paths that we never want to publish\n ignore: /(\\.gadget|\\.git|node_modules|\\.DS_Store)/,\n // don't emit an event for every watched file on boot\n ignoreInitial: true,\n renameDetection: true,\n recursive: true,\n debounce: this.args[\"--file-watch-debounce\"],\n pollingInterval: this.args[\"--file-watch-poll-interval\"],\n pollingTimeout: this.args[\"--file-watch-poll-timeout\"],\n renameTimeout: this.args[\"--file-watch-rename-timeout\"],\n });\n\n this.watcher.once(\"error\", (error) => void this.stop(error));\n\n this.watcher.on(\"all\", (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 = this.filesync.normalize(filepath, isDirectory);\n\n this.log.debug(\"file event\", {\n event,\n path: normalizedPath,\n isDirectory,\n recentRemoteChanges: Array.from(this.recentRemoteChanges.keys()),\n });\n\n if (filepath === this.filesync.absolute(\".ignore\")) {\n this.filesync.reloadIgnorePaths();\n } else if (this.filesync.ignores(filepath)) {\n return;\n }\n\n if (this.recentRemoteChanges.delete(normalizedPath)) {\n return;\n }\n\n switch (event) {\n case \"add\":\n case \"addDir\":\n case \"change\": {\n const stats = fs.statSync(filepath);\n localFilesBuffer.set(normalizedPath, { mode: stats.mode, isDirectory });\n break;\n }\n case \"unlink\":\n case \"unlinkDir\": {\n localFilesBuffer.set(normalizedPath, { isDeleted: true, isDirectory });\n break;\n }\n case \"rename\":\n case \"renameDir\": {\n const stats = fs.statSync(filepath);\n localFilesBuffer.set(normalizedPath, {\n oldPath: this.filesync.normalize(absolutePath, isDirectory),\n newPath: normalizedPath,\n isDirectory,\n mode: stats.mode,\n });\n break;\n }\n }\n\n this.publish();\n });\n\n this.status = SyncStatus.RUNNING;\n\n println();\n println`\n {bold ggt v${config.version}}\n\n App ${this.filesync.app.slug}\n Editor https://${this.filesync.app.slug}.gadget.app/edit\n Playground https://${this.filesync.app.slug}.gadget.app/api/graphql/playground\n Docs https://docs.gadget.dev/api/${this.filesync.app.slug}\n\n {underline Endpoints} ${\n this.filesync.app.hasSplitEnvironments\n ? `\n • https://${this.filesync.app.primaryDomain}\n • https://${this.filesync.app.slug}--development.gadget.app`\n : `\n • https://${this.filesync.app.primaryDomain}`\n }\n\n Watching for file changes... {gray Press Ctrl+C to stop}\n `;\n println();\n\n await stopped;\n\n if (error) {\n notify({ subtitle: \"Uh oh!\", message: \"An error occurred while syncing files\" });\n throw error as Error;\n } else {\n println(\"Goodbye!\");\n }\n }\n\n /**\n * Enqueues a function that handles file-sync events onto the {@linkcode queue}.\n *\n * @param fn The function to enqueue.\n */\n private _enqueue(fn: () => Promise<unknown>): void {\n void this.queue.add(fn).catch(this.stop);\n }\n}\n\nconst sync = new Sync();\nexport const init = sync.init.bind(sync);\nexport const run = sync.run.bind(sync);\n"],"names":["arg","format","formatDate","isAfter","execa","fs","inquirer","_","ms","pMap","PQueue","path","FSWatcher","which","FileSyncEncoding","AppArg","config","EditGraphQL","YarnNotFoundError","FileSync","PUBLISH_FILE_SYNC_EVENTS_MUTATION","REMOTE_FILES_VERSION_QUERY","REMOTE_FILE_SYNC_EVENTS_SUBSCRIPTION","printPaths","swallowEnoent","createLogger","notify","println","sprint","PromiseSignal","getUserOrLogin","usage","SyncStatus","Action","argSpec","Boolean","Number","Sync","init","rootArgs","args","defaults","argv","sync","nothrow","user","filesync","dir","app","force","extraIgnorePaths","graphql","remoteFilesVersion","query","hasRemoteChanges","BigInt","filesVersion","getChangedFiles","files","Map","absolutePath","stats","walkDir","mtime","getTime","set","normalize","isDirectory","delete","changedFiles","hasLocalChanges","size","log","info","changed","Array","from","keys","limit","action","prompt","type","name","choices","message","variables","input","expectedRemoteFilesVersion","normalizedPath","mode","content","readFile","absolute","encoding","Base64","deleted","write","process","exit","run","error","stopped","recentRemoteChangesInterval","setInterval","timestamp","recentRemoteChanges","Date","now","unref","stop","e","status","clearInterval","unsubscribe","watcher","removeAllListeners","publish","flush","queue","onIdle","Promise","allSettled","close","dispose","resolve","signal","on","setTimeout","once","subscribe","localFilesVersion","String","next","remoteFileSyncEvents","filter","event","startsWith","ignores","map","_enqueue","file","dirname","length","some","cwd","catch","noop","localFilesBuffer","debounce","localFiles","entries","clear","push","oldPath","undefined","publishFileSyncEvents","ignore","ignoreInitial","renameDetection","recursive","pollingInterval","pollingTimeout","renameTimeout","renamedPath","filepath","debug","reloadIgnorePaths","statSync","isDeleted","newPath","version","slug","hasSplitEnvironments","primaryDomain","subtitle","fn","add","concurrency","bind"],"mappings":";AAAA,OAAOA,SAAS,MAAM;AACtB,SAASC,UAAUC,UAAU,EAAEC,OAAO,QAAQ,WAAW;AACzD,SAASC,KAAK,QAAQ,QAAQ;AAE9B,OAAOC,QAAQ,WAAW;AAC1B,OAAOC,cAAc,WAAW;AAChC,OAAOC,OAAO,SAAS;AACvB,OAAOC,QAAQ,KAAK;AACpB,OAAOC,UAAU,QAAQ;AACzB,OAAOC,YAAY,UAAU;AAC7B,OAAOC,UAAU,OAAO;AACxB,OAAOC,eAAe,UAAU;AAChC,OAAOC,WAAW,QAAQ;AAC1B,SAASC,gBAAgB,QAAwE,8BAA8B;AAC/H,SAASC,MAAM,QAAQ,sBAAsB;AAC7C,SAASC,MAAM,QAAQ,wBAAwB;AAC/C,SAASC,WAAW,QAAQ,8BAA8B;AAC1D,SAASC,iBAAiB,QAAQ,wBAAwB;AAC1D,SACEC,QAAQ,EACRC,iCAAiC,EACjCC,0BAA0B,EAC1BC,oCAAoC,EACpCC,UAAU,QACL,0BAA0B;AACjC,SAASC,aAAa,QAAQ,0BAA0B;AACxD,SAASC,YAAY,QAAQ,qBAAqB;AAClD,SAASC,MAAM,QAAQ,wBAAwB;AAC/C,SAASC,OAAO,EAAEC,MAAM,QAAQ,wBAAwB;AACxD,SAASC,aAAa,QAAQ,yBAAyB;AACvD,SAASC,cAAc,QAAQ,sBAAsB;AAGrD,OAAO,MAAMC,QAAQH,MAAM,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6E5B,CAAC,CAAC;;UAEUI;;;;;GAAAA,eAAAA;;UAOAC;;;;GAAAA,WAAAA;AAMZ,MAAMC,UAAU;IACd,MAAM;IACN,SAASnB;IACT,WAAWoB;IACX,qBAAqBC;IACrB,yBAAyBA;IACzB,8BAA8BA;IAC9B,6BAA6BA;IAC7B,+BAA+BA;AACjC;AAEA,OAAO,MAAMC;IAwDX;;;;;;;GAOC,GACD,MAAMC,KAAKC,QAAkB,EAAiB;QAC5C,IAAI,CAACC,IAAI,GAAGjC,EAAEkC,QAAQ,CAACzC,IAAIkC,SAAS;YAAEQ,MAAMH,SAAShC,CAAC;QAAC,IAAI;YACzD,qBAAqB;YACrB,yBAAyB;YACzB,8BAA8B;YAC9B,6BAA6B;YAC7B,+BAA+B;QACjC;QAEA,IAAI,CAACM,MAAM8B,IAAI,CAAC,QAAQ;YAAEC,SAAS;QAAK,IAAI;YAC1C,MAAM,IAAI1B;QACZ;QAEA,MAAM2B,OAAO,MAAMf;QAEnB,IAAI,CAACgB,QAAQ,GAAG,MAAM3B,SAASmB,IAAI,CAACO,MAAM;YACxCE,KAAK,IAAI,CAACP,IAAI,CAACjC,CAAC,CAAC,EAAE;YACnByC,KAAK,IAAI,CAACR,IAAI,CAAC,QAAQ;YACvBS,OAAO,IAAI,CAACT,IAAI,CAAC,UAAU;YAC3BU,kBAAkB;gBAAC;aAAU;QAC/B;QAEA,IAAI,CAACC,OAAO,GAAG,IAAIlC,YAAY,IAAI,CAAC6B,QAAQ,CAACE,GAAG;QAEhD,MAAM,EAAEI,kBAAkB,EAAE,GAAG,MAAM,IAAI,CAACD,OAAO,CAACE,KAAK,CAAC;YAAEA,OAAOhC;QAA2B;QAC5F,MAAMiC,mBAAmBC,OAAOH,sBAAsB,IAAI,CAACN,QAAQ,CAACU,YAAY;QAEhF,MAAMC,kBAAkB;YACtB,MAAMC,QAAQ,IAAIC;YAClB,WAAW,MAAM,CAACC,cAAcC,MAAM,IAAI,IAAI,CAACf,QAAQ,CAACgB,OAAO,GAAI;gBACjE,IAAID,MAAME,KAAK,CAACC,OAAO,KAAK,IAAI,CAAClB,QAAQ,CAACiB,KAAK,EAAE;oBAC/CL,MAAMO,GAAG,CAAC,IAAI,CAACnB,QAAQ,CAACoB,SAAS,CAACN,cAAcC,MAAMM,WAAW,KAAKN;gBACxE;YACF;YAEA,mCAAmC;YACnCH,MAAMU,MAAM,CAAC;YAEb,OAAOV;QACT;QAEA,IAAIW,eAAe,MAAMZ;QACzB,MAAMa,kBAAkBD,aAAaE,IAAI,GAAG;QAC5C,IAAID,iBAAiB;YACnB,IAAI,CAACE,GAAG,CAACC,IAAI,CAAC,4BAA4B;gBACxCrB;gBACAE;gBACAgB;gBACAI,SAASC,MAAMC,IAAI,CAACP,aAAaQ,IAAI;YACvC;YAEAlD,QAAQ;YACRJ,WAAW,KAAKoD,MAAMC,IAAI,CAACP,aAAaQ,IAAI,KAAK,EAAE,EAAE;gBAAEC,OAAOT,aAAaE,IAAI;YAAC;YAChF5C;QACF;QAEA,IAAIoD;QACJ,IAAIT,iBAAiB;YAClB,CAAA,EAAES,MAAM,EAAE,GAAG,MAAMzE,SAAS0E,MAAM,CAAC;gBAClCC,MAAM;gBACNC,MAAM;gBACNC,SAAS;;;;iBAA2C;gBACpDC,SAAS9B,mBAAmB,mEAAmE;YACjG,EAAC;QACH;QAEA,uDAAuD;QACvDe,eAAe,MAAMZ;QAErB,OAAQsB;YACN;gBAAmB;oBACjB,IAAI,CAACP,GAAG,CAACC,IAAI,CAAC,yBAAyB;wBACrCrB;wBACAE;wBACAgB;wBACAI,SAASC,MAAMC,IAAI,CAACP,aAAaQ,IAAI;oBACvC;oBAEA,8DAA8D;oBAC9D,6DAA6D;oBAC7D,8DAA8D;oBAC9D,sDAAsD;oBACtD,aAAa;oBACb,MAAM,IAAI,CAAC1B,OAAO,CAACE,KAAK,CAAC;wBACvBA,OAAOjC;wBACPiE,WAAW;4BACTC,OAAO;gCACLC,4BAA4BnC;gCAC5BsB,SAAS,MAAMjE,KAAK4D,cAAc,OAAO,CAACmB,gBAAgB3B,MAAM,GAAM,CAAA;wCACpElD,MAAM6E;wCACNC,MAAM5B,MAAM4B,IAAI;wCAChBC,SAAS7B,MAAMM,WAAW,KAAK,KAAK,MAAM9D,GAAGsF,QAAQ,CAAC,IAAI,CAAC7C,QAAQ,CAAC8C,QAAQ,CAACJ,iBAAiB;wCAC9FK,UAAU/E,iBAAiBgF,MAAM;oCACnC,CAAA;gCACAC,SAAS,EAAE;4BACb;wBACF;oBACF;oBACA;gBACF;YACA;gBAAmB;oBACjB,IAAI,CAACvB,GAAG,CAACC,IAAI,CAAC,2BAA2B;wBACvCrB;wBACAE;wBACAgB;wBACAI,SAASC,MAAMC,IAAI,CAACP,aAAaQ,IAAI;oBACvC;oBAEA,8DAA8D;oBAC9D,4DAA4D;oBAC5D,+DAA+D;oBAC/D,mBAAmB;oBACnB,MAAM,IAAI,CAAC/B,QAAQ,CAACkD,KAAK,CAAC,EAAE,EAAE,EAAE,EAAE3B,aAAaQ,IAAI,IAAI;oBACvD;gBACF;YACA;gBAAoB;oBAClBoB,QAAQC,IAAI,CAAC;gBACf;QACF;IACF;IAEA;;GAEC,GACD,MAAMC,MAAqB;QACzB,IAAIC;QACJ,MAAMC,UAAU,IAAIxE;QAEpB,MAAMyE,8BAA8BC,YAAY;YAC9C,KAAK,MAAM,CAAC5F,MAAM6F,UAAU,IAAI,IAAI,CAACC,mBAAmB,CAAE;gBACxD,IAAItG,QAAQuG,KAAKC,GAAG,IAAIH,YAAYhG,GAAG,QAAQ;oBAC7C,yDAAyD;oBACzD,IAAI,CAACiG,mBAAmB,CAACrC,MAAM,CAACzD;gBAClC;YACF;QACF,GAAGH,GAAG,OAAOoG,KAAK;QAElB,IAAI,CAACC,IAAI,GAAG,OAAOC;YACjB,IAAI,IAAI,CAACC,MAAM,OAAwB;YACvC,IAAI,CAACA,MAAM;YACXX,QAAQU;YAER,IAAI,CAACtC,GAAG,CAACC,IAAI,CAAC,YAAY;gBAAE2B;YAAM;YAElC,IAAI;gBACFY,cAAcV;gBACdW;gBACA,IAAI,CAACC,OAAO,CAACC,kBAAkB;gBAC/B,IAAI,CAACC,OAAO,CAACC,KAAK;gBAClB,MAAM,IAAI,CAACC,KAAK,CAACC,MAAM;YACzB,SAAU;gBACR,MAAMC,QAAQC,UAAU,CAAC;oBAAC,IAAI,CAACP,OAAO,CAACQ,KAAK;oBAAI,IAAI,CAACvE,OAAO,CAACwE,OAAO;iBAAG;gBAEvE,IAAI,CAACZ,MAAM;gBACXV,QAAQuB,OAAO;gBACf,IAAI,CAACpD,GAAG,CAACC,IAAI,CAAC;YAChB;QACF;QAEA,KAAK,MAAMoD,UAAU;YAAC;YAAU;SAAU,CAAW;YACnD5B,QAAQ6B,EAAE,CAACD,QAAQ;gBACjB,IAAI,IAAI,CAACd,MAAM,OAAwB;gBAEvCpF,OAAO,CAAC,iDAAiD,CAAC;gBAC1D,KAAK,IAAI,CAACkF,IAAI;gBAEd,mIAAmI;gBACnI,8HAA8H;gBAC9HkB,WAAW;oBACT9B,QAAQ+B,IAAI,CAACH,QAAQ;wBACnBlG,QAAQ;wBACRsE,QAAQC,IAAI,CAAC;oBACf;gBACF,GAAG,KAAKU,KAAK;YACf;QACF;QAEA,MAAMK,cAAc,IAAI,CAAC9D,OAAO,CAAC8E,SAAS,CACxC;YACE5E,OAAO/B;YACP+D,WAAW,IAAO,CAAA;oBAAE6C,mBAAmBC,OAAO,IAAI,CAACrF,QAAQ,CAACU,YAAY;gBAAE,CAAA;QAC5E,GACA;YACE4C,OAAO,CAACA,QAAU,KAAK,IAAI,CAACS,IAAI,CAACT;YACjCgC,MAAM,CAAC,EAAEC,oBAAoB,EAAE;gBAC7B,MAAMjF,qBAAqBiF,qBAAqBjF,kBAAkB;gBAElE,+HAA+H;gBAC/H,MAAMkF,SAAS,CAACC,QAA4BhI,EAAEiI,UAAU,CAACD,MAAM5H,IAAI,EAAE,eAAe,CAAC,IAAI,CAACmC,QAAQ,CAAC2F,OAAO,CAACF,MAAM5H,IAAI;gBACrH,MAAM+D,UAAUnE,EAAE+H,MAAM,CAACD,qBAAqB3D,OAAO,EAAE4D;gBACvD,MAAMvC,UAAUxF,EAAE+H,MAAM,CAACD,qBAAqBtC,OAAO,EAAEuC;gBAEvD,IAAI,CAAC9D,GAAG,CAACC,IAAI,CAAC,kBAAkB;oBAC9BrB;oBACAsB,SAASnE,EAAEmI,GAAG,CAAChE,SAAS;oBACxBqB,SAASxF,EAAEmI,GAAG,CAAC3C,SAAS;gBAC1B;gBAEA,IAAI,CAAC4C,QAAQ,CAAC;oBACZ,4DAA4D;oBAC5D,wDAAwD;oBACxD,YAAY;oBACZ,KAAK,MAAMC,QAAQrI,EAAE+H,MAAM,CAAC;2BAAI5D;2BAAYqB;qBAAQ,EAAE,CAAC6C,OAAS,CAAC,IAAI,CAAC9F,QAAQ,CAAC2F,OAAO,CAACG,KAAKjI,IAAI,GAAI;wBAClG,IAAI,CAAC8F,mBAAmB,CAACxC,GAAG,CAAC2E,KAAKjI,IAAI,EAAE+F,KAAKC,GAAG;wBAEhD,IAAI5D,MAAMpC,KAAKkI,OAAO,CAACD,KAAKjI,IAAI;wBAChC,MAAOoC,QAAQ,IAAK;4BAClB,IAAI,CAAC0D,mBAAmB,CAACxC,GAAG,CAAClB,MAAM,KAAK2D,KAAKC,GAAG;4BAChD5D,MAAMpC,KAAKkI,OAAO,CAAC9F;wBACrB;oBACF;oBAEA,IAAI2B,QAAQoE,MAAM,IAAI/C,QAAQ+C,MAAM,EAAE;wBACpCnH,OAAO,CAAC,eAAe,EAAEzB,WAAW,IAAIwG,QAAQ,MAAM,CAAC,CAAC;wBACxDnF,WAAW,KAAKhB,EAAEmI,GAAG,CAAChE,SAAS,SAASnE,EAAEmI,GAAG,CAAC3C,SAAS;oBACzD;oBAEA,MAAM,IAAI,CAACjD,QAAQ,CAACkD,KAAK,CAAC5C,oBAAoBsB,SAASnE,EAAEmI,GAAG,CAAC3C,SAAS;oBAEtE,IAAIxF,EAAEwI,IAAI,CAACrE,SAAS;wBAAC;wBAAQ;qBAAY,GAAG;wBAC1C,MAAMtE,MAAM,QAAQ;4BAAC;yBAAU,EAAE;4BAAE4I,KAAK,IAAI,CAAClG,QAAQ,CAACC,GAAG;wBAAC,GAAGkG,KAAK,CAAC1I,EAAE2I,IAAI;oBAC3E;gBACF;YACF;QACF;QAGF,MAAMC,mBAAmB,IAAIxF;QAO7B,IAAI,CAACyD,OAAO,GAAG7G,EAAE6I,QAAQ,CAAC;YACxB,MAAMC,aAAa,IAAI1F,IAAIwF,iBAAiBG,OAAO;YACnDH,iBAAiBI,KAAK;YAEtB,IAAI,CAACZ,QAAQ,CAAC;gBACZ,MAAMjE,UAAuC,EAAE;gBAC/C,MAAMqB,UAAuC,EAAE;gBAE/C,MAAMtF,KAAK4I,YAAY,OAAO,CAAC7D,gBAAgBoD,KAAK;oBAClD,IAAI,eAAeA,MAAM;wBACvB7C,QAAQyD,IAAI,CAAC;4BAAE7I,MAAM6E;wBAAe;wBACpC;oBACF;oBAEA,IAAI;wBACFd,QAAQ8E,IAAI,CAAC;4BACX7I,MAAM6E;4BACNiE,SAAS,aAAab,OAAOA,KAAKa,OAAO,GAAGC;4BAC5CjE,MAAMmD,KAAKnD,IAAI;4BACfC,SAASkD,KAAKzE,WAAW,GAAG,KAAK,MAAM9D,GAAGsF,QAAQ,CAAC,IAAI,CAAC7C,QAAQ,CAAC8C,QAAQ,CAACJ,iBAAiB1E,iBAAiBgF,MAAM;4BAClHD,UAAU/E,iBAAiBgF,MAAM;wBACnC;oBACF,EAAE,OAAOM,OAAO;wBACd,sGAAsG;wBACtG,mFAAmF;wBACnF5E,cAAc4E;oBAChB;gBACF;gBAEA,IAAI,CAAC1B,QAAQoE,MAAM,IAAI,CAAC/C,QAAQ+C,MAAM,EAAE;oBACtC;gBACF;gBAEA,MAAM,EAAEa,qBAAqB,EAAE,GAAG,MAAM,IAAI,CAACxG,OAAO,CAACE,KAAK,CAAC;oBACzDA,OAAOjC;oBACPiE,WAAW;wBAAEC,OAAO;4BAAEC,4BAA4B4C,OAAO,IAAI,CAACrF,QAAQ,CAACU,YAAY;4BAAGkB;4BAASqB;wBAAQ;oBAAE;gBAC3G;gBAEA,MAAM,IAAI,CAACjD,QAAQ,CAACkD,KAAK,CAAC2D,sBAAsBvG,kBAAkB,EAAE,EAAE,EAAE,EAAE;gBAE1EzB,OAAO,CAAC,WAAW,EAAEzB,WAAW,IAAIwG,QAAQ,MAAM,CAAC,CAAC;gBACpDnF,WAAW,KAAKhB,EAAEmI,GAAG,CAAChE,SAAS,SAASnE,EAAEmI,GAAG,CAAC3C,SAAS;YACzD;QACF,GAAG,IAAI,CAACvD,IAAI,CAAC,oBAAoB;QAEjC,IAAI,CAAC0E,OAAO,GAAG,IAAItG,UAAU,IAAI,CAACkC,QAAQ,CAACC,GAAG,EAAE;YAC9C,sCAAsC;YACtC6G,QAAQ;YACR,qDAAqD;YACrDC,eAAe;YACfC,iBAAiB;YACjBC,WAAW;YACXX,UAAU,IAAI,CAAC5G,IAAI,CAAC,wBAAwB;YAC5CwH,iBAAiB,IAAI,CAACxH,IAAI,CAAC,6BAA6B;YACxDyH,gBAAgB,IAAI,CAACzH,IAAI,CAAC,4BAA4B;YACtD0H,eAAe,IAAI,CAAC1H,IAAI,CAAC,8BAA8B;QACzD;QAEA,IAAI,CAAC0E,OAAO,CAACc,IAAI,CAAC,SAAS,CAAC5B,QAAU,KAAK,IAAI,CAACS,IAAI,CAACT;QAErD,IAAI,CAACc,OAAO,CAACY,EAAE,CAAC,OAAO,CAACS,OAAe3E,cAAsBuG;YAC3D,MAAMC,WAAW7B,UAAU,YAAYA,UAAU,cAAc4B,cAAcvG;YAC7E,MAAMO,cAAcoE,UAAU,eAAeA,UAAU,YAAYA,UAAU;YAC7E,MAAM/C,iBAAiB,IAAI,CAAC1C,QAAQ,CAACoB,SAAS,CAACkG,UAAUjG;YAEzD,IAAI,CAACK,GAAG,CAAC6F,KAAK,CAAC,cAAc;gBAC3B9B;gBACA5H,MAAM6E;gBACNrB;gBACAsC,qBAAqB9B,MAAMC,IAAI,CAAC,IAAI,CAAC6B,mBAAmB,CAAC5B,IAAI;YAC/D;YAEA,IAAIuF,aAAa,IAAI,CAACtH,QAAQ,CAAC8C,QAAQ,CAAC,YAAY;gBAClD,IAAI,CAAC9C,QAAQ,CAACwH,iBAAiB;YACjC,OAAO,IAAI,IAAI,CAACxH,QAAQ,CAAC2F,OAAO,CAAC2B,WAAW;gBAC1C;YACF;YAEA,IAAI,IAAI,CAAC3D,mBAAmB,CAACrC,MAAM,CAACoB,iBAAiB;gBACnD;YACF;YAEA,OAAQ+C;gBACN,KAAK;gBACL,KAAK;gBACL,KAAK;oBAAU;wBACb,MAAM1E,QAAQxD,GAAGkK,QAAQ,CAACH;wBAC1BjB,iBAAiBlF,GAAG,CAACuB,gBAAgB;4BAAEC,MAAM5B,MAAM4B,IAAI;4BAAEtB;wBAAY;wBACrE;oBACF;gBACA,KAAK;gBACL,KAAK;oBAAa;wBAChBgF,iBAAiBlF,GAAG,CAACuB,gBAAgB;4BAAEgF,WAAW;4BAAMrG;wBAAY;wBACpE;oBACF;gBACA,KAAK;gBACL,KAAK;oBAAa;wBAChB,MAAMN,QAAQxD,GAAGkK,QAAQ,CAACH;wBAC1BjB,iBAAiBlF,GAAG,CAACuB,gBAAgB;4BACnCiE,SAAS,IAAI,CAAC3G,QAAQ,CAACoB,SAAS,CAACN,cAAcO;4BAC/CsG,SAASjF;4BACTrB;4BACAsB,MAAM5B,MAAM4B,IAAI;wBAClB;wBACA;oBACF;YACF;YAEA,IAAI,CAAC2B,OAAO;QACd;QAEA,IAAI,CAACL,MAAM;QAEXpF;QACAA,OAAO,CAAC;iBACK,EAAEX,OAAO0J,OAAO,CAAC;;kBAEhB,EAAE,IAAI,CAAC5H,QAAQ,CAACE,GAAG,CAAC2H,IAAI,CAAC;0BACjB,EAAE,IAAI,CAAC7H,QAAQ,CAACE,GAAG,CAAC2H,IAAI,CAAC;0BACzB,EAAE,IAAI,CAAC7H,QAAQ,CAACE,GAAG,CAAC2H,IAAI,CAAC;8CACL,EAAE,IAAI,CAAC7H,QAAQ,CAACE,GAAG,CAAC2H,IAAI,CAAC;;4BAE3C,EACpB,IAAI,CAAC7H,QAAQ,CAACE,GAAG,CAAC4H,oBAAoB,GAClC,CAAC;kBACK,EAAE,IAAI,CAAC9H,QAAQ,CAACE,GAAG,CAAC6H,aAAa,CAAC;kBAClC,EAAE,IAAI,CAAC/H,QAAQ,CAACE,GAAG,CAAC2H,IAAI,CAAC,wBAAwB,CAAC,GACxD,CAAC;kBACK,EAAE,IAAI,CAAC7H,QAAQ,CAACE,GAAG,CAAC6H,aAAa,CAAC,CAAC,CAC9C;;;IAGH,CAAC;QACDlJ;QAEA,MAAM0E;QAEN,IAAID,OAAO;YACT1E,OAAO;gBAAEoJ,UAAU;gBAAU1F,SAAS;YAAwC;YAC9E,MAAMgB;QACR,OAAO;YACLzE,QAAQ;QACV;IACF;IAEA;;;;GAIC,GACD,AAAQgH,SAASoC,EAA0B,EAAQ;QACjD,KAAK,IAAI,CAACzD,KAAK,CAAC0D,GAAG,CAACD,IAAI9B,KAAK,CAAC,IAAI,CAACpC,IAAI;IACzC;;QAjcArE,uBAAAA,QAAAA,KAAAA;QAEA;;GAEC,GACDuE,uBAAAA;QAEA;;;;GAIC,GACDN,uBAAAA,uBAAsB,IAAI9C;QAE1B;;GAEC,GACD2D,uBAAAA,SAAQ,IAAI5G,OAAO;YAAEuK,aAAa;QAAE;QAEpC;;GAEC,GACD9H,uBAAAA,WAAAA,KAAAA;QAEA;;GAEC,GACD+D,uBAAAA,WAAAA,KAAAA;QAEA;;GAEC,GACDpE,uBAAAA,YAAAA,KAAAA;QAEA;;GAEC,GACDsE,uBAAAA,WAAAA,KAAAA;QAEA;;GAEC,GACDP,uBAAAA,QAAAA,KAAAA;QAEA;;GAEC,GACDrC,uBAAAA,OAAM/C,aAAa,QAAQ;YACzB,OAAO;gBACLuB,KAAK,IAAI,CAACF,QAAQ,CAACE,GAAG,CAAC2H,IAAI;gBAC3BnH,cAAc2E,OAAO,IAAI,CAACrF,QAAQ,CAACU,YAAY;gBAC/CO,OAAO,IAAI,CAACjB,QAAQ,CAACiB,KAAK;YAC5B;QACF;;AA6YF;AAEA,MAAMpB,OAAO,IAAIN;AACjB,OAAO,MAAMC,OAAOK,KAAKL,IAAI,CAAC4I,IAAI,CAACvI,MAAM;AACzC,OAAO,MAAMwD,MAAMxD,KAAKwD,GAAG,CAAC+E,IAAI,CAACvI,MAAM"}
|
|
1
|
+
{"version":3,"sources":["../../src/commands/sync.ts"],"sourcesContent":["import arg from \"arg\";\nimport dayjs from \"dayjs\";\nimport { execa } from \"execa\";\nimport type { Stats } from \"fs-extra\";\nimport fs from \"fs-extra\";\nimport ms from \"ms\";\nimport path from \"node:path\";\nimport pMap from \"p-map\";\nimport PQueue from \"p-queue\";\nimport type { SetRequired } from \"type-fest\";\nimport FSWatcher from \"watcher\";\nimport which from \"which\";\nimport { FileSyncEncoding, type FileSyncChangedEventInput, type FileSyncDeletedEventInput } from \"../__generated__/graphql.js\";\nimport { AppArg } from \"../services/args.js\";\nimport { config } from \"../services/config.js\";\nimport { debounce, type DebouncedFunc } from \"../services/debounce.js\";\nimport { defaults } from \"../services/defaults.js\";\nimport { EditGraphQL } from \"../services/edit-graphql.js\";\nimport { YarnNotFoundError } from \"../services/errors.js\";\nimport {\n FileSync,\n PUBLISH_FILE_SYNC_EVENTS_MUTATION,\n REMOTE_FILES_VERSION_QUERY,\n REMOTE_FILE_SYNC_EVENTS_SUBSCRIPTION,\n printPaths,\n} from \"../services/filesync.js\";\nimport { swallowEnoent } from \"../services/fs.js\";\nimport { createLogger } from \"../services/log.js\";\nimport { noop } from \"../services/noop.js\";\nimport { notify } from \"../services/notify.js\";\nimport { println, sprint } from \"../services/output.js\";\nimport { PromiseSignal } from \"../services/promise.js\";\nimport { select } from \"../services/prompt.js\";\nimport { getUserOrLogin } from \"../services/user.js\";\nimport { type RootArgs } from \"./root.js\";\n\nexport const usage = sprint`\n Sync your Gadget application's source code to and from\n your local filesystem.\n\n {bold USAGE}\n $ ggt sync [DIRECTORY] [--app <name>]\n\n {bold ARGUMENTS}\n DIRECTORY {dim [default: .] The directory to sync files to.\n\n If the directory doesn't exist, it will be created.}\n\n {bold FLAGS}\n -a, --app=<name> {dim The Gadget application to sync files to.}\n\n --force {dim Whether to sync even if we can't determine\n the state of your local files relative to\n your remote ones.}\n\n {bold DESCRIPTION}\n Sync provides the ability to sync your Gadget application's source\n code to and from your local filesystem.\n\n While ggt sync is running, local file changes are immediately\n reflected within Gadget, while files that are changed remotely are\n immediately saved to your local filesystem.\n\n Use cases for this include:\n • Developing locally with your own editor like VSCode\n • Storing your source code in a Git repository like GitHub\n\n Sync includes the concept of a {dim .ignore} file. This file may\n contain a list of files and directories that won't be received or\n sent to Gadget when syncing. The format of this file is identical\n to the one used by Git {dim (https://git-scm.com/docs/gitignore)}.\n\n The following files and directories are always ignored:\n • .DS_Store\n • .gadget\n • .git\n • node_modules\n\n Note:\n • If you have separate development and production environments,\n {dim ggt sync} will only sync with your development environment\n • Gadget applications only support installing dependencies\n with Yarn 1 {dim (https://classic.yarnpkg.com/lang/en/)}\n • Since file changes are immediately reflected in Gadget,\n avoid the following while {dim ggt sync} is running:\n • Deleting all your files\n • Moving all your files to a different directory\n\n {bold EXAMPLES}\n {dim $ ggt sync --app my-app ~/gadget/my-app}\n\n App my-app\n Editor https://my-app.gadget.app/edit\n Playground https://my-app.gadget.app/api/graphql/playground\n Docs https://docs.gadget.dev/api/my-app\n\n Endpoints\n • https://my-app.gadget.app\n • https://my-app--development.gadget.app\n\n Watching for file changes... {dim Press Ctrl+C to stop}\n\n Received {dim 12:00:00 PM}\n {green ←} routes/GET.js {dim (changed)}\n {green ←} user/signUp/signIn.js {dim (changed)}\n {dim 2 files in total. 2 changed, 0 deleted.}\n\n Sent {dim 12:00:03 PM}\n {green →} routes/GET.ts {dim (changed)}\n {dim 1 file in total. 1 changed, 0 deleted.}\n\n ^C Stopping... {dim (press Ctrl+C again to force)}\n Goodbye!\n`;\n\nexport enum SyncStatus {\n STARTING,\n RUNNING,\n STOPPING,\n STOPPED,\n}\n\nexport enum Action {\n CANCEL = \"Cancel (Ctrl+C)\",\n MERGE = \"Merge local files with remote ones\",\n RESET = \"Reset local files to remote ones\",\n}\n\nconst argSpec = {\n \"-a\": \"--app\",\n \"--app\": AppArg,\n \"--force\": Boolean,\n \"--file-push-delay\": Number,\n \"--file-watch-debounce\": Number,\n \"--file-watch-poll-interval\": Number,\n \"--file-watch-poll-timeout\": Number,\n \"--file-watch-rename-timeout\": Number,\n};\n\nexport class Sync {\n args!: SetRequired<arg.Result<typeof argSpec>, \"--file-push-delay\">;\n\n /**\n * The current status of the sync process.\n */\n status = SyncStatus.STARTING;\n\n /**\n * A list of filepaths that have changed because of a remote file-sync\n * event. This is used to avoid sending files that we recently\n * received from a remote file-sync event.\n */\n recentRemoteChanges = new Map<string, number>();\n\n /**\n * A FIFO async callback queue that ensures we process file-sync events in the order they occurred.\n */\n queue = new PQueue({ concurrency: 1 });\n\n /**\n * A GraphQL client connected to the app's /edit/api/graphql-ws endpoint\n */\n graphql!: EditGraphQL;\n\n /**\n * Watches the local filesystem for changes.\n */\n watcher!: FSWatcher;\n\n /**\n * Handles writing files to the local filesystem.\n */\n filesync!: FileSync;\n\n /**\n * A debounced function that enqueue's local file changes to be sent to Gadget.\n */\n publish!: DebouncedFunc<() => void>;\n\n /**\n * Gracefully stops the sync.\n */\n stop!: (error?: unknown) => Promise<void>;\n\n /**\n * A logger for the sync command.\n */\n log = createLogger(\"sync\", () => {\n return {\n app: this.filesync.app.slug,\n filesVersion: String(this.filesync.filesVersion),\n mtime: this.filesync.mtime,\n };\n });\n\n /**\n * Initializes the sync process.\n * - Ensures the directory exists.\n * - Ensures the directory is empty or contains a `.gadget/sync.json` file.\n * - Ensures an app is selected and that it matches the app the directory was previously synced to.\n * - Ensures yarn v1 is installed.\n * - Prompts the user how to resolve conflicts if the local filesystem has changed since the last sync.\n */\n async init(rootArgs: RootArgs): Promise<void> {\n this.args = defaults(arg(argSpec, { argv: rootArgs._ }), {\n \"--file-push-delay\": 100,\n \"--file-watch-debounce\": 300,\n \"--file-watch-poll-interval\": 3_000,\n \"--file-watch-poll-timeout\": 20_000,\n \"--file-watch-rename-timeout\": 1_250,\n });\n\n if (!which.sync(\"yarn\", { nothrow: true })) {\n throw new YarnNotFoundError();\n }\n\n const user = await getUserOrLogin();\n\n this.filesync = await FileSync.init(user, {\n dir: this.args._[0],\n app: this.args[\"--app\"],\n force: this.args[\"--force\"],\n extraIgnorePaths: [\".gadget\"],\n });\n\n this.graphql = new EditGraphQL(this.filesync.app);\n\n const { remoteFilesVersion } = await this.graphql.query({ query: REMOTE_FILES_VERSION_QUERY });\n const hasRemoteChanges = BigInt(remoteFilesVersion) > this.filesync.filesVersion;\n\n const getChangedFiles = async (): Promise<Map<string, Stats>> => {\n const files = new Map();\n for await (const [absolutePath, stats] of this.filesync.walkDir()) {\n if (stats.mtime.getTime() > this.filesync.mtime) {\n files.set(this.filesync.normalize(absolutePath, stats.isDirectory()), stats);\n }\n }\n\n // never include the root directory\n files.delete(\"/\");\n\n return files;\n };\n\n let changedFiles = await getChangedFiles();\n const hasLocalChanges = changedFiles.size > 0;\n if (hasLocalChanges) {\n this.log.info(\"local files have changed\", {\n remoteFilesVersion,\n hasRemoteChanges,\n hasLocalChanges,\n changed: Array.from(changedFiles.keys()),\n });\n\n println(\"Local files have changed since you last synced\");\n printPaths(\"-\", Array.from(changedFiles.keys()), [], { limit: changedFiles.size });\n println();\n }\n\n let action: Action | undefined;\n if (hasLocalChanges) {\n action = await select({\n message: hasRemoteChanges ? \"Remote files have also changed. How would you like to proceed?\" : \"How would you like to proceed?\",\n choices: [Action.CANCEL, Action.MERGE, Action.RESET],\n });\n }\n\n // get all the changed files again in case more changed\n changedFiles = await getChangedFiles();\n\n switch (action) {\n case Action.MERGE: {\n this.log.info(\"merging local changes\", {\n remoteFilesVersion,\n hasRemoteChanges,\n hasLocalChanges,\n changed: Array.from(changedFiles.keys()),\n });\n\n // We purposefully don't write the returned files version here\n // because we haven't received its associated files yet. This\n // will cause us to receive the remote files that have changed\n // since the last sync (+ the local files that we just\n // published)\n await this.graphql.query({\n query: PUBLISH_FILE_SYNC_EVENTS_MUTATION,\n variables: {\n input: {\n expectedRemoteFilesVersion: remoteFilesVersion,\n changed: await pMap(changedFiles, async ([normalizedPath, stats]) => ({\n path: normalizedPath,\n mode: stats.mode,\n content: stats.isDirectory() ? \"\" : await fs.readFile(this.filesync.absolute(normalizedPath), \"base64\"),\n encoding: FileSyncEncoding.Base64,\n })),\n deleted: [],\n },\n },\n });\n break;\n }\n case Action.RESET: {\n this.log.info(\"resetting local changes\", {\n remoteFilesVersion,\n hasRemoteChanges,\n hasLocalChanges,\n changed: Array.from(changedFiles.keys()),\n });\n\n // delete all the local files that have changed since the last\n // sync and set the files version to 0 so we receive all the\n // remote files again, including any files that we just deleted\n // that still exist\n await this.filesync.write(0n, [], changedFiles.keys(), true);\n break;\n }\n case Action.CANCEL: {\n process.exit(0);\n }\n }\n }\n\n /**\n * Runs the sync process until it is stopped or an error occurs.\n */\n async run(): Promise<void> {\n let error: unknown;\n const stopped = new PromiseSignal();\n\n const recentRemoteChangesInterval = setInterval(() => {\n for (const [path, timestamp] of this.recentRemoteChanges) {\n if (dayjs().isAfter(timestamp + ms(\"5s\"))) {\n // this change should have been seen by now, so remove it\n this.recentRemoteChanges.delete(path);\n }\n }\n }, ms(\"1s\")).unref();\n\n this.stop = async (e?: unknown) => {\n if (this.status !== SyncStatus.RUNNING) {\n return;\n }\n\n this.status = SyncStatus.STOPPING;\n error = e;\n\n this.log.info(\"stopping\", { error });\n\n try {\n clearInterval(recentRemoteChangesInterval);\n unsubscribe();\n this.watcher.removeAllListeners();\n this.publish.flush();\n await this.queue.onIdle();\n } finally {\n await Promise.allSettled([this.watcher.close(), this.graphql.dispose()]);\n\n this.status = SyncStatus.STOPPED;\n stopped.resolve();\n this.log.info(\"stopped\");\n }\n };\n\n for (const signal of [\"SIGINT\", \"SIGTERM\"] as const) {\n process.on(signal, () => {\n if (this.status !== SyncStatus.RUNNING) {\n return;\n }\n\n println` Stopping... {gray (press Ctrl+C again to force)}`;\n void this.stop();\n\n // 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\n // SIGINT from triggering the force exit listener, we wait a bit before registering it. This is a bit of a hack, but it works.\n setTimeout(() => {\n process.once(signal, () => {\n println(\" Exiting immediately. Note that files may not have finished syncing.\");\n process.exit(1);\n });\n }, 100).unref();\n });\n }\n\n const unsubscribe = this.graphql.subscribe(\n {\n query: REMOTE_FILE_SYNC_EVENTS_SUBSCRIPTION,\n variables: () => ({ localFilesVersion: String(this.filesync.filesVersion) }),\n },\n {\n error: (error) => void this.stop(error),\n next: ({ remoteFileSyncEvents }) => {\n const remoteFilesVersion = remoteFileSyncEvents.remoteFilesVersion;\n\n // we always ignore .gadget/ files so that we don't publish them (they're managed by gadget), but we still want to receive them\n const filterIgnored = (event: { path: string }) => event.path.startsWith(\".gadget/\") || !this.filesync.ignores(event.path);\n const changed = remoteFileSyncEvents.changed.filter(filterIgnored);\n const deleted = remoteFileSyncEvents.deleted.filter(filterIgnored);\n\n this.log.info(\"received files\", {\n remoteFilesVersion,\n changed: changed.map((x) => x.path),\n deleted: deleted.map((x) => x.path),\n });\n\n this._enqueue(async () => {\n // add all the non-ignored files and directories we're about\n // to touch to recentRemoteChanges so that we don't send\n // them back\n for (const file of [...changed, ...deleted].filter((file) => !this.filesync.ignores(file.path))) {\n this.recentRemoteChanges.set(file.path, Date.now());\n\n let dir = path.dirname(file.path);\n while (dir !== \".\") {\n this.recentRemoteChanges.set(dir + \"/\", Date.now());\n dir = path.dirname(dir);\n }\n }\n\n if (changed.length > 0 || deleted.length > 0) {\n println`Received {gray ${dayjs().format(\"hh:mm:ss A\")}}`;\n printPaths(\n \"←\",\n changed.map((x) => x.path),\n deleted.map((x) => x.path),\n );\n }\n\n await this.filesync.write(\n remoteFilesVersion,\n changed,\n deleted.map((x) => x.path),\n );\n\n if (changed.some((x) => x.path === \"yarn.lock\")) {\n await execa(\"yarn\", [\"install\"], { cwd: this.filesync.dir }).catch(noop);\n }\n });\n },\n },\n );\n\n const localFilesBuffer = new Map<\n string,\n | { mode: number; isDirectory: boolean }\n | { isDeleted: true; isDirectory: boolean }\n | { mode: number; oldPath: string; newPath: string; isDirectory: boolean }\n >();\n\n this.publish = debounce(this.args[\"--file-push-delay\"], () => {\n const localFiles = new Map(localFilesBuffer.entries());\n localFilesBuffer.clear();\n\n this._enqueue(async () => {\n const changed: FileSyncChangedEventInput[] = [];\n const deleted: FileSyncDeletedEventInput[] = [];\n\n await pMap(localFiles, async ([normalizedPath, file]) => {\n if (\"isDeleted\" in file) {\n deleted.push({ path: normalizedPath });\n return;\n }\n\n try {\n changed.push({\n path: normalizedPath,\n oldPath: \"oldPath\" in file ? file.oldPath : undefined,\n mode: file.mode,\n content: file.isDirectory ? \"\" : await fs.readFile(this.filesync.absolute(normalizedPath), FileSyncEncoding.Base64),\n encoding: FileSyncEncoding.Base64,\n });\n } catch (error) {\n // A file could have been changed and then deleted before we process the change event, so the readFile\n // above will raise an ENOENT. This is normal operation, so just ignore this event.\n swallowEnoent(error);\n }\n });\n\n if (changed.length === 0 && deleted.length === 0) {\n return;\n }\n\n const { publishFileSyncEvents } = await this.graphql.query({\n query: PUBLISH_FILE_SYNC_EVENTS_MUTATION,\n variables: { input: { expectedRemoteFilesVersion: String(this.filesync.filesVersion), changed, deleted } },\n });\n\n await this.filesync.write(publishFileSyncEvents.remoteFilesVersion, [], []);\n\n println`Sent {gray ${dayjs().format(\"hh:mm:ss A\")}}`;\n printPaths(\n \"→\",\n changed.map((x) => x.path),\n deleted.map((x) => x.path),\n );\n });\n });\n\n this.watcher = new FSWatcher(this.filesync.dir, {\n // don't emit an event for every watched file on boot\n ignoreInitial: true,\n ignore: (path: string) => this.filesync.ignores(path),\n renameDetection: true,\n recursive: true,\n debounce: this.args[\"--file-watch-debounce\"],\n pollingInterval: this.args[\"--file-watch-poll-interval\"],\n pollingTimeout: this.args[\"--file-watch-poll-timeout\"],\n renameTimeout: this.args[\"--file-watch-rename-timeout\"],\n });\n\n this.watcher.once(\"error\", (error) => void this.stop(error));\n\n this.watcher.on(\"all\", (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 = this.filesync.normalize(filepath, isDirectory);\n\n this.log.debug(\"file event\", {\n event,\n path: normalizedPath,\n isDirectory,\n recentRemoteChanges: Array.from(this.recentRemoteChanges.keys()),\n });\n\n if (filepath === this.filesync.absolute(\".ignore\")) {\n this.filesync.reloadIgnorePaths();\n } else if (this.filesync.ignores(filepath)) {\n return;\n }\n\n if (this.recentRemoteChanges.delete(normalizedPath)) {\n return;\n }\n\n switch (event) {\n case \"add\":\n case \"addDir\":\n case \"change\": {\n const stats = fs.statSync(filepath);\n localFilesBuffer.set(normalizedPath, { mode: stats.mode, isDirectory });\n break;\n }\n case \"unlink\":\n case \"unlinkDir\": {\n localFilesBuffer.set(normalizedPath, { isDeleted: true, isDirectory });\n break;\n }\n case \"rename\":\n case \"renameDir\": {\n const stats = fs.statSync(filepath);\n localFilesBuffer.set(normalizedPath, {\n oldPath: this.filesync.normalize(absolutePath, isDirectory),\n newPath: normalizedPath,\n isDirectory,\n mode: stats.mode,\n });\n break;\n }\n }\n\n this.publish();\n });\n\n this.status = SyncStatus.RUNNING;\n\n println();\n println`\n {bold ggt v${config.version}}\n\n App ${this.filesync.app.slug}\n Editor https://${this.filesync.app.slug}.gadget.app/edit\n Playground https://${this.filesync.app.slug}.gadget.app/api/graphql/playground\n Docs https://docs.gadget.dev/api/${this.filesync.app.slug}\n\n {underline Endpoints} ${\n this.filesync.app.hasSplitEnvironments\n ? `\n • https://${this.filesync.app.primaryDomain}\n • https://${this.filesync.app.slug}--development.gadget.app`\n : `\n • https://${this.filesync.app.primaryDomain}`\n }\n\n Watching for file changes... {gray Press Ctrl+C to stop}\n `;\n println();\n\n await stopped;\n\n if (error) {\n notify({ subtitle: \"Uh oh!\", message: \"An error occurred while syncing files\" });\n throw error as Error;\n } else {\n println(\"Goodbye!\");\n }\n }\n\n /**\n * Enqueues a function that handles file-sync events onto the {@linkcode queue}.\n *\n * @param fn The function to enqueue.\n */\n private _enqueue(fn: () => Promise<unknown>): void {\n void this.queue.add(fn).catch(this.stop);\n }\n}\n\nconst sync = new Sync();\nexport const init = sync.init.bind(sync);\nexport const run = sync.run.bind(sync);\n"],"names":["arg","dayjs","execa","fs","ms","path","pMap","PQueue","FSWatcher","which","FileSyncEncoding","AppArg","config","debounce","defaults","EditGraphQL","YarnNotFoundError","FileSync","PUBLISH_FILE_SYNC_EVENTS_MUTATION","REMOTE_FILES_VERSION_QUERY","REMOTE_FILE_SYNC_EVENTS_SUBSCRIPTION","printPaths","swallowEnoent","createLogger","noop","notify","println","sprint","PromiseSignal","select","getUserOrLogin","usage","SyncStatus","Action","argSpec","Boolean","Number","Sync","init","rootArgs","args","argv","_","sync","nothrow","user","filesync","dir","app","force","extraIgnorePaths","graphql","remoteFilesVersion","query","hasRemoteChanges","BigInt","filesVersion","getChangedFiles","files","Map","absolutePath","stats","walkDir","mtime","getTime","set","normalize","isDirectory","delete","changedFiles","hasLocalChanges","size","log","info","changed","Array","from","keys","limit","action","message","choices","variables","input","expectedRemoteFilesVersion","normalizedPath","mode","content","readFile","absolute","encoding","Base64","deleted","write","process","exit","run","error","stopped","recentRemoteChangesInterval","setInterval","timestamp","recentRemoteChanges","isAfter","unref","stop","e","status","clearInterval","unsubscribe","watcher","removeAllListeners","publish","flush","queue","onIdle","Promise","allSettled","close","dispose","resolve","signal","on","setTimeout","once","subscribe","localFilesVersion","String","next","remoteFileSyncEvents","filterIgnored","event","startsWith","ignores","filter","map","x","_enqueue","file","Date","now","dirname","length","format","some","cwd","catch","localFilesBuffer","localFiles","entries","clear","push","oldPath","undefined","publishFileSyncEvents","ignoreInitial","ignore","renameDetection","recursive","pollingInterval","pollingTimeout","renameTimeout","renamedPath","filepath","debug","reloadIgnorePaths","statSync","isDeleted","newPath","version","slug","hasSplitEnvironments","primaryDomain","subtitle","fn","add","concurrency","bind"],"mappings":"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"}
|
package/lib/services/app.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import _ from "lodash";
|
|
2
1
|
import { z } from "zod";
|
|
3
2
|
import { config } from "./config.js";
|
|
4
3
|
import { http, loadCookie } from "./http.js";
|
|
@@ -27,7 +26,7 @@ export const App = z.object({
|
|
|
27
26
|
responseType: "json",
|
|
28
27
|
resolveBodyOnly: true
|
|
29
28
|
});
|
|
30
|
-
return
|
|
29
|
+
return z.array(App).parse(json).map((app)=>({
|
|
31
30
|
...app,
|
|
32
31
|
user
|
|
33
32
|
}));
|
package/lib/services/app.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/services/app.ts"],"sourcesContent":["import
|
|
1
|
+
{"version":3,"sources":["../../src/services/app.ts"],"sourcesContent":["import { z } from \"zod\";\nimport { config } from \"./config.js\";\nimport { http, loadCookie } from \"./http.js\";\nimport type { User } from \"./user.js\";\n\nexport const App = z.object({\n id: z.union([z.string(), z.number(), z.bigint()]),\n slug: z.string(),\n primaryDomain: z.string(),\n hasSplitEnvironments: z.boolean(),\n});\n\nexport type App = z.infer<typeof App> & { user: User };\n\n/**\n * @returns The list of Gadget applications the current user has access to.\n */\nexport const getApps = async (user: User): Promise<App[]> => {\n const cookie = loadCookie();\n if (!cookie) {\n return [];\n }\n\n const json = await http({\n url: `https://${config.domains.services}/auth/api/apps`,\n headers: { cookie },\n responseType: \"json\",\n resolveBodyOnly: true,\n });\n\n return z\n .array(App)\n .parse(json)\n .map((app) => ({ ...app, user }));\n};\n"],"names":["z","config","http","loadCookie","App","object","id","union","string","number","bigint","slug","primaryDomain","hasSplitEnvironments","boolean","getApps","user","cookie","json","url","domains","services","headers","responseType","resolveBodyOnly","array","parse","map","app"],"mappings":"AAAA,SAASA,CAAC,QAAQ,MAAM;AACxB,SAASC,MAAM,QAAQ,cAAc;AACrC,SAASC,IAAI,EAAEC,UAAU,QAAQ,YAAY;AAG7C,OAAO,MAAMC,MAAMJ,EAAEK,MAAM,CAAC;IAC1BC,IAAIN,EAAEO,KAAK,CAAC;QAACP,EAAEQ,MAAM;QAAIR,EAAES,MAAM;QAAIT,EAAEU,MAAM;KAAG;IAChDC,MAAMX,EAAEQ,MAAM;IACdI,eAAeZ,EAAEQ,MAAM;IACvBK,sBAAsBb,EAAEc,OAAO;AACjC,GAAG;AAIH;;CAEC,GACD,OAAO,MAAMC,UAAU,OAAOC;IAC5B,MAAMC,SAASd;IACf,IAAI,CAACc,QAAQ;QACX,OAAO,EAAE;IACX;IAEA,MAAMC,OAAO,MAAMhB,KAAK;QACtBiB,KAAK,CAAC,QAAQ,EAAElB,OAAOmB,OAAO,CAACC,QAAQ,CAAC,cAAc,CAAC;QACvDC,SAAS;YAAEL;QAAO;QAClBM,cAAc;QACdC,iBAAiB;IACnB;IAEA,OAAOxB,EACJyB,KAAK,CAACrB,KACNsB,KAAK,CAACR,MACNS,GAAG,CAAC,CAACC,MAAS,CAAA;YAAE,GAAGA,GAAG;YAAEZ;QAAK,CAAA;AAClC,EAAE"}
|
package/lib/services/args.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import _ from "lodash";
|
|
2
1
|
import { ArgError } from "./errors.js";
|
|
3
2
|
import { sprint } from "./output.js";
|
|
4
3
|
export const parseBoolean = (value)=>{
|
|
5
|
-
|
|
4
|
+
value ??= "";
|
|
5
|
+
return [
|
|
6
6
|
"true",
|
|
7
7
|
"1"
|
|
8
|
-
]
|
|
8
|
+
].includes(value.trim().toLowerCase());
|
|
9
9
|
};
|
|
10
10
|
export const AppArg = (value, name)=>{
|
|
11
11
|
const slug = RegExp("^(https:\\/\\/)?(?<slug>[\\w-]+?)(--development)?(\\..*)?$").exec(value)?.groups?.["slug"];
|
package/lib/services/args.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/services/args.ts"],"sourcesContent":["import
|
|
1
|
+
{"version":3,"sources":["../../src/services/args.ts"],"sourcesContent":["import { ArgError } from \"./errors.js\";\nimport { sprint } from \"./output.js\";\n\nexport const parseBoolean = (value: string | null | undefined) => {\n value ??= \"\";\n return [\"true\", \"1\"].includes(value.trim().toLowerCase());\n};\n\nexport const AppArg = (value: string, name: string) => {\n const slug = /^(https:\\/\\/)?(?<slug>[\\w-]+?)(--development)?(\\..*)?$/.exec(value)?.groups?.[\"slug\"];\n if (slug) {\n return slug;\n }\n\n throw new ArgError(\n sprint`\n The ${name} option must be the application's slug or URL\n\n Examples:\n\n --${name} my-app\n --${name} my-app.gadget.app\n --${name} https://my-app.gadget.app\n --${name} https://my-app.gadget.app/edit\n --${name} https://my-app--development.gadget.app/edit\n `,\n );\n};\n"],"names":["ArgError","sprint","parseBoolean","value","includes","trim","toLowerCase","AppArg","name","slug","exec","groups"],"mappings":"AAAA,SAASA,QAAQ,QAAQ,cAAc;AACvC,SAASC,MAAM,QAAQ,cAAc;AAErC,OAAO,MAAMC,eAAe,CAACC;IAC3BA,UAAU;IACV,OAAO;QAAC;QAAQ;KAAI,CAACC,QAAQ,CAACD,MAAME,IAAI,GAAGC,WAAW;AACxD,EAAE;AAEF,OAAO,MAAMC,SAAS,CAACJ,OAAeK;IACpC,MAAMC,OAAO,qEAAyDC,IAAI,CAACP,QAAQQ,QAAQ,CAAC,OAAO;IACnG,IAAIF,MAAM;QACR,OAAOA;IACT;IAEA,MAAM,IAAIT,SACRC,MAAM,CAAC;UACD,EAAEO,KAAK;;;;UAIP,EAAEA,KAAK;UACP,EAAEA,KAAK;UACP,EAAEA,KAAK;UACP,EAAEA,KAAK;UACP,EAAEA,KAAK;IACb,CAAC;AAEL,EAAE"}
|