@gadgetinc/ggt 0.4.2 → 0.4.4
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 +20 -14
- package/lib/commands/login.js +1 -1
- package/lib/commands/login.js.map +1 -1
- package/lib/commands/logout.js +1 -1
- package/lib/commands/logout.js.map +1 -1
- package/lib/commands/version.js +1 -1
- package/lib/commands/version.js.map +1 -1
- package/lib/commands/whoami.js +1 -1
- package/lib/commands/whoami.js.map +1 -1
- package/lib/main.js +2 -0
- package/lib/main.js.map +1 -1
- package/lib/services/filesync/conflicts.js +1 -1
- package/lib/services/filesync/conflicts.js.map +1 -1
- package/lib/services/filesync/directory.js +2 -1
- package/lib/services/filesync/directory.js.map +1 -1
- package/lib/services/filesync/hashes.js +19 -5
- package/lib/services/filesync/hashes.js.map +1 -1
- package/lib/services/output/log/format/json.js +0 -8
- package/lib/services/output/log/format/json.js.map +1 -1
- package/lib/services/output/log/format/pretty.js +16 -9
- package/lib/services/output/log/format/pretty.js.map +1 -1
- package/lib/services/util/json.js +30 -0
- package/lib/services/util/json.js.map +1 -0
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
<a href="https://github.com/gadget-inc/ggt/actions/workflows/ci.yml?query=branch%3Amain">
|
|
9
9
|
<img alt="ci workflow status" src="https://img.shields.io/github/actions/workflow/status/gadget-inc/ggt/ci.yml?branch=main&label=ci">
|
|
10
10
|
</a>
|
|
11
|
-
<a href="https://www.npmjs.com/package
|
|
12
|
-
<img alt="npm version" src="https://img.shields.io/npm/v
|
|
11
|
+
<a href="https://www.npmjs.com/package/ggt">
|
|
12
|
+
<img alt="npm version" src="https://img.shields.io/npm/v/ggt">
|
|
13
13
|
</a>
|
|
14
14
|
<a href="https://discord.gg/nAfNKMdwKh">
|
|
15
15
|
<img alt="discord chat" src="https://img.shields.io/discord/836317518595096598">
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
Run the following to sync a `my-app.gadget.app` application to the `~/gadget/my-app` on your local machine:
|
|
45
45
|
|
|
46
46
|
```sh
|
|
47
|
-
npx
|
|
47
|
+
npx ggt@latest sync ~/gadget/my-app --app=my-app
|
|
48
48
|
```
|
|
49
49
|
|
|
50
50
|
With this running in the background, your local `~/gadget/my-app` folder will become two-way synced with your application's filesystem in Gadget's cloud. Changes you make locally will be immediately reflected by your application's API and actions if you re-run them.
|
|
@@ -79,7 +79,8 @@ For more information on a specific command, use 'ggt [COMMAND] --help'
|
|
|
79
79
|
|
|
80
80
|
### `ggt sync`
|
|
81
81
|
|
|
82
|
-
```
|
|
82
|
+
```sh-session
|
|
83
|
+
$ ggt sync --help
|
|
83
84
|
Sync your Gadget environment's source code with your local filesystem.
|
|
84
85
|
|
|
85
86
|
USAGE
|
|
@@ -152,7 +153,8 @@ EXAMPLE
|
|
|
152
153
|
|
|
153
154
|
### `ggt list`
|
|
154
155
|
|
|
155
|
-
```
|
|
156
|
+
```sh-session
|
|
157
|
+
$ ggt list --help
|
|
156
158
|
List the apps available to the currently logged in user.
|
|
157
159
|
|
|
158
160
|
USAGE
|
|
@@ -169,13 +171,14 @@ EXAMPLE
|
|
|
169
171
|
|
|
170
172
|
### `ggt login`
|
|
171
173
|
|
|
172
|
-
```
|
|
174
|
+
```sh-session
|
|
175
|
+
$ ggt login --help
|
|
173
176
|
Log in to your account.
|
|
174
177
|
|
|
175
178
|
USAGE
|
|
176
179
|
ggt login
|
|
177
180
|
|
|
178
|
-
|
|
181
|
+
EXAMPLE
|
|
179
182
|
$ ggt login
|
|
180
183
|
We've opened Gadget's login page using your default browser.
|
|
181
184
|
|
|
@@ -186,39 +189,42 @@ EXAMPLES
|
|
|
186
189
|
|
|
187
190
|
### `ggt logout`
|
|
188
191
|
|
|
189
|
-
```
|
|
192
|
+
```sh-session
|
|
193
|
+
$ ggt logout --help
|
|
190
194
|
Log out of your account.
|
|
191
195
|
|
|
192
196
|
USAGE
|
|
193
197
|
ggt logout
|
|
194
198
|
|
|
195
|
-
|
|
199
|
+
EXAMPLE
|
|
196
200
|
$ ggt logout
|
|
197
201
|
Goodbye
|
|
198
202
|
```
|
|
199
203
|
|
|
200
204
|
### `ggt whoami`
|
|
201
205
|
|
|
202
|
-
```
|
|
206
|
+
```sh-session
|
|
207
|
+
$ ggt whoami --help
|
|
203
208
|
Show the name and email address of the currently logged in user
|
|
204
209
|
|
|
205
210
|
USAGE
|
|
206
211
|
ggt whoami
|
|
207
212
|
|
|
208
|
-
|
|
213
|
+
EXAMPLE
|
|
209
214
|
$ ggt whoami
|
|
210
215
|
You are logged in as Jane Doe (jane@example.com)
|
|
211
216
|
```
|
|
212
217
|
|
|
213
218
|
### `ggt version`
|
|
214
219
|
|
|
215
|
-
```
|
|
220
|
+
```sh-session
|
|
221
|
+
$ ggt version --help
|
|
216
222
|
Print the version of ggt
|
|
217
223
|
|
|
218
224
|
USAGE
|
|
219
225
|
ggt version
|
|
220
226
|
|
|
221
|
-
|
|
227
|
+
EXAMPLE
|
|
222
228
|
$ ggt version
|
|
223
|
-
0.4.
|
|
229
|
+
0.4.4
|
|
224
230
|
```
|
package/lib/commands/login.js
CHANGED
|
@@ -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 type { Command, Usage } from \"../services/command/command.js\";\nimport { config } from \"../services/config/config.js\";\nimport { createLogger } from \"../services/output/log/logger.js\";\nimport { sprint } from \"../services/output/sprint.js\";\nimport { writeSession } from \"../services/user/session.js\";\nimport { getUser } from \"../services/user/user.js\";\n\nconst log = createLogger({ name: \"login\" });\n\nexport const usage: Usage = () => sprint`\n Log in to your account.\n\n {bold USAGE}\n ggt login\n\n {bold
|
|
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 type { Command, Usage } from \"../services/command/command.js\";\nimport { config } from \"../services/config/config.js\";\nimport { createLogger } from \"../services/output/log/logger.js\";\nimport { sprint } from \"../services/output/sprint.js\";\nimport { writeSession } from \"../services/user/session.js\";\nimport { getUser } from \"../services/user/user.js\";\n\nconst log = createLogger({ name: \"login\" });\n\nexport const usage: Usage = () => sprint`\n Log in to your account.\n\n {bold USAGE}\n ggt login\n\n {bold EXAMPLE}\n $ 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\nexport const login = async (): Promise<void> => {\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 log.printlns`Hello, ${user.name} {gray (${user.email})}`;\n } else {\n log.printlns`Hello, ${user.email}`;\n }\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 log.printlns`\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 } catch (error) {\n log.error(\"failed to open browser\", { error });\n log.printlns`\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 await receiveSession;\n } finally {\n server?.close();\n }\n};\n\nexport const command: Command = login;\n"],"names":["getPort","assert","http","open","config","createLogger","sprint","writeSession","getUser","log","name","usage","login","server","port","receiveSession","Promise","resolve","reject","createServer","req","res","landingPage","URL","domains","services","url","session","searchParams","get","user","printlns","email","set","error","undefined","writeHead","Location","toString","end","info","listen","close","command"],"mappings":"AAAA,OAAOA,aAAa,WAAW;AAC/B,OAAOC,YAAY,cAAc;AACjC,OAAOC,UAA2B,YAAY;AAC9C,OAAOC,UAAU,OAAO;AAExB,SAASC,MAAM,QAAQ,+BAA+B;AACtD,SAASC,YAAY,QAAQ,mCAAmC;AAChE,SAASC,MAAM,QAAQ,+BAA+B;AACtD,SAASC,YAAY,QAAQ,8BAA8B;AAC3D,SAASC,OAAO,QAAQ,2BAA2B;AAEnD,MAAMC,MAAMJ,aAAa;IAAEK,MAAM;AAAQ;AAEzC,OAAO,MAAMC,QAAe,IAAML,MAAM,CAAC;;;;;;;;;;;;;AAazC,CAAC,CAAC;AAEF,OAAO,MAAMM,QAAQ;IACnB,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;oBAEhBpB,aAAaoB;oBAEb,MAAMG,OAAO,MAAMtB;oBACnBP,OAAO6B,MAAM;oBAEb,IAAIA,KAAKpB,IAAI,EAAE;wBACbD,IAAIsB,QAAQ,CAAC,OAAO,EAAED,KAAKpB,IAAI,CAAC,QAAQ,EAAEoB,KAAKE,KAAK,CAAC,EAAE,CAAC;oBAC1D,OAAO;wBACLvB,IAAIsB,QAAQ,CAAC,OAAO,EAAED,KAAKE,KAAK,CAAC,CAAC;oBACpC;oBAEAV,YAAYM,YAAY,CAACK,GAAG,CAAC,WAAW;oBACxChB;gBACF,EAAE,OAAOiB,OAAO;oBACd3B,aAAa4B;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;YAEA9B,IAAI+B,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;YACvB7B,IAAIsB,QAAQ,CAAC;;;;IAIf,CAAC;QACD,EAAE,OAAOG,OAAO;YACdzB,IAAIyB,KAAK,CAAC,0BAA0B;gBAAEA;YAAM;YAC5CzB,IAAIsB,QAAQ,CAAC;;;gBAGH,EAAEL,IAAIY,QAAQ,GAAG;;;MAG3B,CAAC;QACH;QAEA,MAAMvB;IACR,SAAU;QACRF,QAAQ6B;IACV;AACF,EAAE;AAEF,OAAO,MAAMC,UAAmB/B,MAAM"}
|
package/lib/commands/logout.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/commands/logout.ts"],"sourcesContent":["import type { Command, Usage } from \"../services/command/command.js\";\nimport { createLogger } from \"../services/output/log/logger.js\";\nimport { sprint } from \"../services/output/sprint.js\";\nimport { readSession, writeSession } from \"../services/user/session.js\";\n\nconst log = createLogger({ name: \"logout\" });\n\nexport const usage: Usage = () => sprint`\n Log out of your account.\n\n {bold USAGE}\n ggt logout\n\n {bold
|
|
1
|
+
{"version":3,"sources":["../../src/commands/logout.ts"],"sourcesContent":["import type { Command, Usage } from \"../services/command/command.js\";\nimport { createLogger } from \"../services/output/log/logger.js\";\nimport { sprint } from \"../services/output/sprint.js\";\nimport { readSession, writeSession } from \"../services/user/session.js\";\n\nconst log = createLogger({ name: \"logout\" });\n\nexport const usage: Usage = () => sprint`\n Log out of your account.\n\n {bold USAGE}\n ggt logout\n\n {bold EXAMPLE}\n $ ggt logout\n Goodbye\n`;\n\nexport const command: Command = () => {\n const token = readSession();\n if (token) {\n writeSession(undefined);\n log.println(\"Goodbye\");\n } else {\n log.println(\"You are not logged in\");\n }\n};\n"],"names":["createLogger","sprint","readSession","writeSession","log","name","usage","command","token","undefined","println"],"mappings":"AACA,SAASA,YAAY,QAAQ,mCAAmC;AAChE,SAASC,MAAM,QAAQ,+BAA+B;AACtD,SAASC,WAAW,EAAEC,YAAY,QAAQ,8BAA8B;AAExE,MAAMC,MAAMJ,aAAa;IAAEK,MAAM;AAAS;AAE1C,OAAO,MAAMC,QAAe,IAAML,MAAM,CAAC;;;;;;;;;AASzC,CAAC,CAAC;AAEF,OAAO,MAAMM,UAAmB;IAC9B,MAAMC,QAAQN;IACd,IAAIM,OAAO;QACTL,aAAaM;QACbL,IAAIM,OAAO,CAAC;IACd,OAAO;QACLN,IAAIM,OAAO,CAAC;IACd;AACF,EAAE"}
|
package/lib/commands/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/commands/version.ts"],"sourcesContent":["import type { Command, Usage } from \"../services/command/command.js\";\nimport { config } from \"../services/config/config.js\";\nimport { createLogger } from \"../services/output/log/logger.js\";\nimport { sprint } from \"../services/output/sprint.js\";\n\nconst log = createLogger({ name: \"version\" });\n\nexport const usage: Usage = () => sprint`\n Print the version of ggt\n\n {bold USAGE}\n ggt version\n\n {bold
|
|
1
|
+
{"version":3,"sources":["../../src/commands/version.ts"],"sourcesContent":["import type { Command, Usage } from \"../services/command/command.js\";\nimport { config } from \"../services/config/config.js\";\nimport { createLogger } from \"../services/output/log/logger.js\";\nimport { sprint } from \"../services/output/sprint.js\";\n\nconst log = createLogger({ name: \"version\" });\n\nexport const usage: Usage = () => sprint`\n Print the version of ggt\n\n {bold USAGE}\n ggt version\n\n {bold EXAMPLE}\n $ ggt version\n ${config.version}\n`;\n\nexport const command: Command = () => {\n log.println(config.version);\n};\n"],"names":["config","createLogger","sprint","log","name","usage","version","command","println"],"mappings":"AACA,SAASA,MAAM,QAAQ,+BAA+B;AACtD,SAASC,YAAY,QAAQ,mCAAmC;AAChE,SAASC,MAAM,QAAQ,+BAA+B;AAEtD,MAAMC,MAAMF,aAAa;IAAEG,MAAM;AAAU;AAE3C,OAAO,MAAMC,QAAe,IAAMH,MAAM,CAAC;;;;;;;;MAQnC,EAAEF,OAAOM,OAAO,CAAC;AACvB,CAAC,CAAC;AAEF,OAAO,MAAMC,UAAmB;IAC9BJ,IAAIK,OAAO,CAACR,OAAOM,OAAO;AAC5B,EAAE"}
|
package/lib/commands/whoami.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/commands/whoami.ts"],"sourcesContent":["import type { Command, Usage } from \"../services/command/command.js\";\nimport { createLogger } from \"../services/output/log/logger.js\";\nimport { sprint } from \"../services/output/sprint.js\";\nimport { getUser } from \"../services/user/user.js\";\n\nconst log = createLogger({ name: \"whoami\" });\n\nexport const usage: Usage = () => sprint`\n Show the name and email address of the currently logged in user\n\n {bold USAGE}\n ggt whoami\n\n {bold
|
|
1
|
+
{"version":3,"sources":["../../src/commands/whoami.ts"],"sourcesContent":["import type { Command, Usage } from \"../services/command/command.js\";\nimport { createLogger } from \"../services/output/log/logger.js\";\nimport { sprint } from \"../services/output/sprint.js\";\nimport { getUser } from \"../services/user/user.js\";\n\nconst log = createLogger({ name: \"whoami\" });\n\nexport const usage: Usage = () => sprint`\n Show the name and email address of the currently logged in user\n\n {bold USAGE}\n ggt whoami\n\n {bold EXAMPLE}\n $ ggt whoami\n You are logged in as Jane Doe (jane@example.com)\n`;\n\nexport const command: Command = async () => {\n const user = await getUser();\n if (!user) {\n log.println`You are not logged in`;\n return;\n }\n\n if (user.name) {\n log.println`You are logged in as ${user.name} {gray (${user.email})}`;\n } else {\n log.println`You are logged in as ${user.email}`;\n }\n};\n"],"names":["createLogger","sprint","getUser","log","name","usage","command","user","println","email"],"mappings":"AACA,SAASA,YAAY,QAAQ,mCAAmC;AAChE,SAASC,MAAM,QAAQ,+BAA+B;AACtD,SAASC,OAAO,QAAQ,2BAA2B;AAEnD,MAAMC,MAAMH,aAAa;IAAEI,MAAM;AAAS;AAE1C,OAAO,MAAMC,QAAe,IAAMJ,MAAM,CAAC;;;;;;;;;AASzC,CAAC,CAAC;AAEF,OAAO,MAAMK,UAAmB;IAC9B,MAAMC,OAAO,MAAML;IACnB,IAAI,CAACK,MAAM;QACTJ,IAAIK,OAAO,CAAC,qBAAqB,CAAC;QAClC;IACF;IAEA,IAAID,KAAKH,IAAI,EAAE;QACbD,IAAIK,OAAO,CAAC,qBAAqB,EAAED,KAAKH,IAAI,CAAC,QAAQ,EAAEG,KAAKE,KAAK,CAAC,EAAE,CAAC;IACvE,OAAO;QACLN,IAAIK,OAAO,CAAC,qBAAqB,EAAED,KAAKE,KAAK,CAAC,CAAC;IACjD;AACF,EAAE"}
|
package/lib/main.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { command as root } from "./commands/root.js";
|
|
2
2
|
import { installErrorHandlers } from "./services/output/report.js";
|
|
3
|
+
import { installJsonExtensions } from "./services/util/json.js";
|
|
3
4
|
installErrorHandlers();
|
|
5
|
+
installJsonExtensions();
|
|
4
6
|
await root();
|
|
5
7
|
|
|
6
8
|
//# sourceMappingURL=main.js.map
|
package/lib/main.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/main.ts"],"sourcesContent":["import { command as root } from \"./commands/root.js\";\nimport { installErrorHandlers } from \"./services/output/report.js\";\n\ninstallErrorHandlers();\n\nawait root();\n"],"names":["command","root","installErrorHandlers"],"mappings":"AAAA,SAASA,WAAWC,IAAI,QAAQ,qBAAqB;AACrD,SAASC,oBAAoB,QAAQ,8BAA8B;
|
|
1
|
+
{"version":3,"sources":["../src/main.ts"],"sourcesContent":["import { command as root } from \"./commands/root.js\";\nimport { installErrorHandlers } from \"./services/output/report.js\";\nimport { installJsonExtensions } from \"./services/util/json.js\";\n\ninstallErrorHandlers();\ninstallJsonExtensions();\n\nawait root();\n"],"names":["command","root","installErrorHandlers","installJsonExtensions"],"mappings":"AAAA,SAASA,WAAWC,IAAI,QAAQ,qBAAqB;AACrD,SAASC,oBAAoB,QAAQ,8BAA8B;AACnE,SAASC,qBAAqB,QAAQ,0BAA0B;AAEhED;AACAC;AAEA,MAAMF"}
|
|
@@ -23,7 +23,7 @@ const log = createLogger({
|
|
|
23
23
|
if (localChange.type === "delete" && gadgetChange.type === "delete") {
|
|
24
24
|
continue;
|
|
25
25
|
}
|
|
26
|
-
if ("targetHash" in localChange && "targetHash" in gadgetChange && isEqualHash(localChange.targetHash, gadgetChange.targetHash)) {
|
|
26
|
+
if ("targetHash" in localChange && "targetHash" in gadgetChange && isEqualHash(filepath, localChange.targetHash, gadgetChange.targetHash)) {
|
|
27
27
|
continue;
|
|
28
28
|
}
|
|
29
29
|
// local and gadget both updated the same file with different
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/services/filesync/conflicts.ts"],"sourcesContent":["import chalk from \"chalk\";\nimport { createLogger } from \"../output/log/logger.js\";\nimport { ChangesWithHash, isEqualHash, type ChangeWithHash } from \"./hashes.js\";\n\nconst log = createLogger({ name: \"conflicts\" });\n\n/**\n * A map of conflicting changes made between the user's local filesystem\n * and Gadget's filesystem where the key is the path of the conflicting\n * file and the value is an object containing the conflicting changes.\n */\nexport class Conflicts extends Map<string, { localChange: ChangeWithHash; gadgetChange: ChangeWithHash }> {}\n\n/**\n * Returns the conflicting changes between the user's local filesystem\n * and Gadget's filesystem.\n */\nexport const getConflicts = ({\n localChanges,\n gadgetChanges,\n}: {\n localChanges: ChangesWithHash;\n gadgetChanges: ChangesWithHash;\n}): Conflicts => {\n const conflicts = new Conflicts();\n\n for (const [filepath, localChange] of localChanges) {\n const gadgetChange = gadgetChanges.get(filepath);\n if (!gadgetChange) {\n // gadget doesn't have this change, so there's no conflict\n continue;\n }\n\n if (localChange.type === \"delete\" && gadgetChange.type === \"delete\") {\n // local and gadget both deleted the same file\n continue;\n }\n\n if (\"targetHash\" in localChange
|
|
1
|
+
{"version":3,"sources":["../../../src/services/filesync/conflicts.ts"],"sourcesContent":["import chalk from \"chalk\";\nimport { createLogger } from \"../output/log/logger.js\";\nimport { ChangesWithHash, isEqualHash, type ChangeWithHash } from \"./hashes.js\";\n\nconst log = createLogger({ name: \"conflicts\" });\n\n/**\n * A map of conflicting changes made between the user's local filesystem\n * and Gadget's filesystem where the key is the path of the conflicting\n * file and the value is an object containing the conflicting changes.\n */\nexport class Conflicts extends Map<string, { localChange: ChangeWithHash; gadgetChange: ChangeWithHash }> {}\n\n/**\n * Returns the conflicting changes between the user's local filesystem\n * and Gadget's filesystem.\n */\nexport const getConflicts = ({\n localChanges,\n gadgetChanges,\n}: {\n localChanges: ChangesWithHash;\n gadgetChanges: ChangesWithHash;\n}): Conflicts => {\n const conflicts = new Conflicts();\n\n for (const [filepath, localChange] of localChanges) {\n const gadgetChange = gadgetChanges.get(filepath);\n if (!gadgetChange) {\n // gadget doesn't have this change, so there's no conflict\n continue;\n }\n\n if (localChange.type === \"delete\" && gadgetChange.type === \"delete\") {\n // local and gadget both deleted the same file\n continue;\n }\n\n if (\n \"targetHash\" in localChange &&\n \"targetHash\" in gadgetChange &&\n isEqualHash(filepath, localChange.targetHash, gadgetChange.targetHash)\n ) {\n // local and gadget both created/updated the same file with the same content\n continue;\n }\n\n // local and gadget both updated the same file with different\n // content or one updated and the other deleted\n conflicts.set(filepath, { localChange, gadgetChange });\n }\n\n // ignore .gadget/ file conflicts and always use gadget's version\n // since gadget is the source of truth for .gadget/ files\n for (const filepath of conflicts.keys()) {\n if (filepath.startsWith(\".gadget/\")) {\n conflicts.delete(filepath);\n }\n }\n\n return conflicts;\n};\n\n/**\n * Returns a new `Changes` object that contains only the changes that do\n * not have conflicts.\n *\n * @param conflicts - The conflicts to check against.\n * @param changes - The changes to filter.\n * @returns A new `Changes` object without conflicts.\n */\nexport const withoutConflictingChanges = ({ conflicts, changes }: { conflicts: Conflicts; changes: ChangesWithHash }): ChangesWithHash => {\n const changesWithoutConflicts = new ChangesWithHash(changes);\n\n for (const [filepath] of changesWithoutConflicts) {\n if (conflicts.has(filepath)) {\n changesWithoutConflicts.delete(filepath);\n }\n }\n\n return changesWithoutConflicts;\n};\n\n/**\n * Prints a table of conflicts between local changes and gadget changes.\n */\nexport const printConflicts = ({ message, conflicts }: { message: string; conflicts: Conflicts }): void => {\n const created = chalk.greenBright(\"+ created\");\n const updated = chalk.blueBright(\"± updated\");\n const deleted = chalk.redBright(\"- deleted\");\n\n log.printTable({\n message,\n colAligns: [\"left\", \"center\", \"center\"],\n headers: [\"\", \"You\", \"Gadget\"],\n spaceY: 1,\n rows: Array.from(conflicts.entries())\n .sort((a, b) => a[0].localeCompare(b[0]))\n .map(([path, { localChange, gadgetChange }]) => {\n switch (true) {\n case localChange.type === \"create\" && gadgetChange.type === \"create\":\n return [path, created, created];\n case localChange.type === \"create\" && gadgetChange.type === \"update\":\n return [path, created, updated];\n case localChange.type === \"create\" && gadgetChange.type === \"delete\":\n return [path, created, deleted];\n case localChange.type === \"update\" && gadgetChange.type === \"create\":\n return [path, updated, created];\n case localChange.type === \"update\" && gadgetChange.type === \"update\":\n return [path, updated, updated];\n case localChange.type === \"update\" && gadgetChange.type === \"delete\":\n return [path, updated, deleted];\n case localChange.type === \"delete\" && gadgetChange.type === \"create\":\n return [path, deleted, created];\n case localChange.type === \"delete\" && gadgetChange.type === \"update\":\n return [path, deleted, updated];\n default:\n throw new Error(`Unexpected conflict: ${localChange.type} vs ${gadgetChange.type}`);\n }\n }),\n });\n};\n"],"names":["chalk","createLogger","ChangesWithHash","isEqualHash","log","name","Conflicts","Map","getConflicts","localChanges","gadgetChanges","conflicts","filepath","localChange","gadgetChange","get","type","targetHash","set","keys","startsWith","delete","withoutConflictingChanges","changes","changesWithoutConflicts","has","printConflicts","message","created","greenBright","updated","blueBright","deleted","redBright","printTable","colAligns","headers","spaceY","rows","Array","from","entries","sort","a","b","localeCompare","map","path","Error"],"mappings":"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"}
|
|
@@ -25,7 +25,8 @@ import normalizePath from "normalize-path";
|
|
|
25
25
|
* NOTE: This is the _only_ thing that is allowed to be different between gadget and ggt.
|
|
26
26
|
*/ export const HASHING_IGNORE_PATHS = [
|
|
27
27
|
".gadget/sync.json",
|
|
28
|
-
".gadget/backup"
|
|
28
|
+
".gadget/backup",
|
|
29
|
+
"yarn-error.log"
|
|
29
30
|
];
|
|
30
31
|
/**
|
|
31
32
|
* Represents a directory that is being synced.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/services/filesync/directory.ts"],"sourcesContent":["/**\n * DO NOT MODIFY\n *\n * Everything in this file also exists in ggt to ensure that this logic\n * is the same between the two projects.\n */\nimport fs from \"fs-extra\";\nimport type { Ignore } from \"ignore\";\nimport ignore from \"ignore\";\nimport assert from \"node:assert\";\nimport { createHash } from \"node:crypto\";\nimport path from \"node:path\";\nimport { Transform } from \"node:stream\";\nimport { pipeline } from \"node:stream/promises\";\nimport normalizePath from \"normalize-path\";\n\n/**\n * Paths that are always ignored, regardless of the contents of the `.ignore` file.\n */\nexport const ALWAYS_IGNORE_PATHS = [\".DS_Store\", \"node_modules\", \".git\"] as const;\n\n/**\n * Paths that are ignored when hashing the directory.\n *\n * NOTE: This is the _only_ thing that is allowed to be different between gadget and ggt.\n */\nexport const HASHING_IGNORE_PATHS = [\".gadget/sync.json\", \".gadget/backup\"] as const;\n\n/**\n * Represents a directory that is being synced.\n */\nexport class Directory {\n /**\n * A gitignore-style file parser used to determine which files to\n * ignore while syncing.\n *\n * @see https://www.npmjs.com/package/ignore\n */\n private _ignorer!: Ignore;\n\n /**\n * Whether the directory is currently being hashed.\n */\n private _isHashing = false;\n\n private constructor(\n /**\n * An absolute path to the directory that is being synced.\n */\n readonly path: string,\n ) {}\n\n /**\n * Initializes a directory to be synced.\n *\n * If the directory does not exist, it is created.\n *\n * @param dir - The directory to initialize.\n * @returns A Promise that resolves to a Directory instance.\n */\n static async init(dir: string): Promise<Directory> {\n const directory = new Directory(dir);\n await directory.loadIgnoreFile();\n return directory;\n }\n\n /**\n * Returns the relative path from this directory to the specified path.\n *\n * @param to - The path to which the relative path is calculated.\n * @returns The relative path from this directory to the specified path.\n */\n relative(to: string): string {\n if (!path.isAbsolute(to)) {\n // the filepath is already relative\n return to;\n }\n\n return path.relative(this.path, to);\n }\n\n /**\n * Returns the absolute path by resolving the given path segments\n * relative to the directory path.\n *\n * @param pathSegments The path segments to resolve.\n * @returns The absolute path.\n */\n absolute(...pathSegments: string[]): string {\n const result = path.resolve(this.path, ...pathSegments);\n assert(result.startsWith(this.path), `expected ${result} to be within ${this.path}`);\n return result;\n }\n\n /**\n * Similar to {@linkcode relative} in that it converts an absolute\n * path into a relative one from {@linkcode path}. However, it also\n * changes any slashes to be posix/unix-like forward slashes,\n * condenses repeated slashes into a single slash, and adds a trailing\n * slash if the path is a directory.\n *\n * This is used when sending files to Gadget to ensure that the paths\n * are consistent across platforms.\n *\n * @see https://www.npmjs.com/package/normalize-path\n */\n normalize(filepath: string, isDirectory: boolean): string {\n if (path.isAbsolute(filepath)) {\n filepath = this.relative(filepath);\n }\n\n // true = trim trailing slashes\n filepath = normalizePath(filepath, true);\n\n if (isDirectory) {\n filepath += \"/\";\n }\n\n return filepath;\n }\n\n /**\n * Loads the `.ignore` file in the directory. If the file does not\n * exist, it is silently ignored.\n */\n async loadIgnoreFile(): Promise<void> {\n this._ignorer = ignore.default();\n this._ignorer.add(ALWAYS_IGNORE_PATHS);\n\n try {\n const content = await fs.readFile(this.absolute(\".ignore\"), \"utf8\");\n this._ignorer.add(content);\n } catch (error) {\n swallowEnoent(error);\n }\n }\n\n /**\n * Determines if a file should be ignored based on its filepath.\n *\n * @param filepath - The filepath of the file to check.\n * @returns True if the file should be ignored, false otherwise.\n */\n ignores(filepath: string): boolean {\n filepath = this.relative(filepath);\n if (filepath === \"\") {\n // don't ignore the root dir\n return false;\n }\n\n if (filepath.startsWith(\"..\")) {\n // anything above the root dir is ignored\n return true;\n }\n\n // false = don't trim trailing slashes\n filepath = normalizePath(filepath, false);\n if (this._isHashing && HASHING_IGNORE_PATHS.some((ignored) => filepath.startsWith(ignored))) {\n // special case for hashing\n return true;\n }\n\n return this._ignorer.ignores(filepath);\n }\n\n /**\n * Recursively walks through the directory and yields all non-ignored\n * files and directories within it.\n *\n * @yields - The normalized path of each file and directory.\n */\n async *walk({ dir = this.path } = {}): AsyncGenerator<string> {\n // don't yield the root directory\n if (dir !== this.path) {\n yield this.normalize(dir, true);\n }\n\n for await (const entry of await fs.opendir(dir)) {\n const filepath = path.join(dir, entry.name);\n if (this.ignores(filepath)) {\n continue;\n }\n\n if (entry.isDirectory()) {\n yield* this.walk({ dir: filepath });\n } else if (entry.isFile()) {\n yield this.normalize(filepath, false);\n }\n }\n }\n\n /**\n * Calculates the hash of each file and directory and returns an\n * object containing the hashes keyed by the normalized file path.\n *\n * @returns A Promise that resolves to an object containing the hashes\n * of each file.\n */\n async hashes(): Promise<Hashes> {\n try {\n this._isHashing = true;\n const files = {} as Hashes;\n\n for await (const normalizedPath of this.walk()) {\n const absolutePath = this.absolute(normalizedPath);\n files[normalizedPath] = await hash(absolutePath);\n }\n\n return files;\n } finally {\n this._isHashing = false;\n }\n }\n}\n\n/**\n * Key/value pairs where the key is the normalized path and the value is\n * the result of {@linkcode hash} for that path.\n */\nexport type Hashes = Record<string, Hash>;\n\nexport type Hash = {\n /**\n * The SHA-1 hash of the file or directory.\n *\n * If the path points to a directory, the hash is calculated based on\n * the directory's basename. If the path points to a file, the hash is\n * calculated based on the file's basename and contents.\n */\n sha1: string;\n\n /**\n * The Unix-style file permissions of the file or directory, or\n * undefined if the platform that generated this hash doesn't support\n * them.\n *\n * @example 0o644\n * @see supportsPermissions\n */\n permissions?: number;\n};\n\n/**\n * Whether the current platform supports Unix-style file permissions.\n *\n * Windows doesn't support Unix-style file permissions and all file\n * permissions retrieved via `node:fs` on Windows are translated to 666\n * or 444.\n */\nexport const supportsPermissions = process.platform === \"linux\" || process.platform === \"darwin\";\n\n/**\n * Calculates the {@linkcode Hash} of the file or directory at the\n * specified absolute path.\n *\n * @param absolutePath The absolute path to the file or directory.\n * @returns A Promise that resolves to the {@linkcode Hash} of the file\n * or directory.\n */\nconst hash = async (absolutePath: string): Promise<Hash> => {\n const sha1 = createHash(\"sha1\");\n sha1.update(path.basename(absolutePath));\n\n const stats = await fs.stat(absolutePath);\n\n let permissions;\n if (supportsPermissions) {\n // strip everything but the permissions\n permissions = stats.mode & 0o777;\n }\n\n if (stats.isDirectory()) {\n return { sha1: sha1.digest(\"hex\"), permissions };\n }\n\n // windows uses CRLF line endings whereas unix uses LF line endings so\n // we always strip out CR bytes (0x0d) when hashing files. this does\n // make us blind to files that only differ by CR bytes, but that's a\n // tradeoff we're willing to make.\n const removeCR = new Transform({\n transform(chunk: Buffer, _encoding, callback) {\n if (!chunk.includes(0x0d)) {\n callback(undefined, chunk);\n return;\n }\n\n const filteredChunk = Buffer.alloc(chunk.length);\n let i = 0;\n for (const byte of chunk) {\n if (byte !== 0x0d) {\n filteredChunk[i++] = byte;\n }\n }\n\n callback(undefined, filteredChunk.slice(0, i));\n },\n });\n\n await pipeline(fs.createReadStream(absolutePath), removeCR, sha1);\n\n return { sha1: sha1.digest(\"hex\"), permissions };\n};\n\n/**\n * Swallows ENOENT errors and throws any other errors.\n *\n * @param error - The error to handle.\n * @throws The original error if it is not an ENOENT error.\n */\nexport const swallowEnoent = (error: unknown): void => {\n if (error && typeof error === \"object\" && \"code\" in error && error.code === \"ENOENT\") {\n return;\n }\n throw error;\n};\n"],"names":["fs","ignore","assert","createHash","path","Transform","pipeline","normalizePath","ALWAYS_IGNORE_PATHS","HASHING_IGNORE_PATHS","Directory","init","dir","directory","loadIgnoreFile","relative","to","isAbsolute","absolute","pathSegments","result","resolve","startsWith","normalize","filepath","isDirectory","_ignorer","default","add","content","readFile","error","swallowEnoent","ignores","_isHashing","some","ignored","walk","entry","opendir","join","name","isFile","hashes","files","normalizedPath","absolutePath","hash","supportsPermissions","process","platform","sha1","update","basename","stats","stat","permissions","mode","digest","removeCR","transform","chunk","_encoding","callback","includes","undefined","filteredChunk","Buffer","alloc","length","i","byte","slice","createReadStream","code"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../src/services/filesync/directory.ts"],"sourcesContent":["/**\n * DO NOT MODIFY\n *\n * Everything in this file also exists in ggt to ensure that this logic\n * is the same between the two projects.\n */\nimport fs from \"fs-extra\";\nimport type { Ignore } from \"ignore\";\nimport ignore from \"ignore\";\nimport assert from \"node:assert\";\nimport { createHash } from \"node:crypto\";\nimport path from \"node:path\";\nimport { Transform } from \"node:stream\";\nimport { pipeline } from \"node:stream/promises\";\nimport normalizePath from \"normalize-path\";\n\n/**\n * Paths that are always ignored, regardless of the contents of the `.ignore` file.\n */\nexport const ALWAYS_IGNORE_PATHS = [\".DS_Store\", \"node_modules\", \".git\"] as const;\n\n/**\n * Paths that are ignored when hashing the directory.\n *\n * NOTE: This is the _only_ thing that is allowed to be different between gadget and ggt.\n */\nexport const HASHING_IGNORE_PATHS = [\".gadget/sync.json\", \".gadget/backup\", \"yarn-error.log\"] as const;\n\n/**\n * Represents a directory that is being synced.\n */\nexport class Directory {\n /**\n * A gitignore-style file parser used to determine which files to\n * ignore while syncing.\n *\n * @see https://www.npmjs.com/package/ignore\n */\n private _ignorer!: Ignore;\n\n /**\n * Whether the directory is currently being hashed.\n */\n private _isHashing = false;\n\n private constructor(\n /**\n * An absolute path to the directory that is being synced.\n */\n readonly path: string,\n ) {}\n\n /**\n * Initializes a directory to be synced.\n *\n * If the directory does not exist, it is created.\n *\n * @param dir - The directory to initialize.\n * @returns A Promise that resolves to a Directory instance.\n */\n static async init(dir: string): Promise<Directory> {\n const directory = new Directory(dir);\n await directory.loadIgnoreFile();\n return directory;\n }\n\n /**\n * Returns the relative path from this directory to the specified path.\n *\n * @param to - The path to which the relative path is calculated.\n * @returns The relative path from this directory to the specified path.\n */\n relative(to: string): string {\n if (!path.isAbsolute(to)) {\n // the filepath is already relative\n return to;\n }\n\n return path.relative(this.path, to);\n }\n\n /**\n * Returns the absolute path by resolving the given path segments\n * relative to the directory path.\n *\n * @param pathSegments The path segments to resolve.\n * @returns The absolute path.\n */\n absolute(...pathSegments: string[]): string {\n const result = path.resolve(this.path, ...pathSegments);\n assert(result.startsWith(this.path), `expected ${result} to be within ${this.path}`);\n return result;\n }\n\n /**\n * Similar to {@linkcode relative} in that it converts an absolute\n * path into a relative one from {@linkcode path}. However, it also\n * changes any slashes to be posix/unix-like forward slashes,\n * condenses repeated slashes into a single slash, and adds a trailing\n * slash if the path is a directory.\n *\n * This is used when sending files to Gadget to ensure that the paths\n * are consistent across platforms.\n *\n * @see https://www.npmjs.com/package/normalize-path\n */\n normalize(filepath: string, isDirectory: boolean): string {\n if (path.isAbsolute(filepath)) {\n filepath = this.relative(filepath);\n }\n\n // true = trim trailing slashes\n filepath = normalizePath(filepath, true);\n\n if (isDirectory) {\n filepath += \"/\";\n }\n\n return filepath;\n }\n\n /**\n * Loads the `.ignore` file in the directory. If the file does not\n * exist, it is silently ignored.\n */\n async loadIgnoreFile(): Promise<void> {\n this._ignorer = ignore.default();\n this._ignorer.add(ALWAYS_IGNORE_PATHS);\n\n try {\n const content = await fs.readFile(this.absolute(\".ignore\"), \"utf8\");\n this._ignorer.add(content);\n } catch (error) {\n swallowEnoent(error);\n }\n }\n\n /**\n * Determines if a file should be ignored based on its filepath.\n *\n * @param filepath - The filepath of the file to check.\n * @returns True if the file should be ignored, false otherwise.\n */\n ignores(filepath: string): boolean {\n filepath = this.relative(filepath);\n if (filepath === \"\") {\n // don't ignore the root dir\n return false;\n }\n\n if (filepath.startsWith(\"..\")) {\n // anything above the root dir is ignored\n return true;\n }\n\n // false = don't trim trailing slashes\n filepath = normalizePath(filepath, false);\n if (this._isHashing && HASHING_IGNORE_PATHS.some((ignored) => filepath.startsWith(ignored))) {\n // special case for hashing\n return true;\n }\n\n return this._ignorer.ignores(filepath);\n }\n\n /**\n * Recursively walks through the directory and yields all non-ignored\n * files and directories within it.\n *\n * @yields - The normalized path of each file and directory.\n */\n async *walk({ dir = this.path } = {}): AsyncGenerator<string> {\n // don't yield the root directory\n if (dir !== this.path) {\n yield this.normalize(dir, true);\n }\n\n for await (const entry of await fs.opendir(dir)) {\n const filepath = path.join(dir, entry.name);\n if (this.ignores(filepath)) {\n continue;\n }\n\n if (entry.isDirectory()) {\n yield* this.walk({ dir: filepath });\n } else if (entry.isFile()) {\n yield this.normalize(filepath, false);\n }\n }\n }\n\n /**\n * Calculates the hash of each file and directory and returns an\n * object containing the hashes keyed by the normalized file path.\n *\n * @returns A Promise that resolves to an object containing the hashes\n * of each file.\n */\n async hashes(): Promise<Hashes> {\n try {\n this._isHashing = true;\n const files = {} as Hashes;\n\n for await (const normalizedPath of this.walk()) {\n const absolutePath = this.absolute(normalizedPath);\n files[normalizedPath] = await hash(absolutePath);\n }\n\n return files;\n } finally {\n this._isHashing = false;\n }\n }\n}\n\n/**\n * Key/value pairs where the key is the normalized path and the value is\n * the result of {@linkcode hash} for that path.\n */\nexport type Hashes = Record<string, Hash>;\n\nexport type Hash = {\n /**\n * The SHA-1 hash of the file or directory.\n *\n * If the path points to a directory, the hash is calculated based on\n * the directory's basename. If the path points to a file, the hash is\n * calculated based on the file's basename and contents.\n */\n sha1: string;\n\n /**\n * The Unix-style file permissions of the file or directory, or\n * undefined if the platform that generated this hash doesn't support\n * them.\n *\n * @example 0o644\n * @see supportsPermissions\n */\n permissions?: number;\n};\n\n/**\n * Whether the current platform supports Unix-style file permissions.\n *\n * Windows doesn't support Unix-style file permissions and all file\n * permissions retrieved via `node:fs` on Windows are translated to 666\n * or 444.\n */\nexport const supportsPermissions = process.platform === \"linux\" || process.platform === \"darwin\";\n\n/**\n * Calculates the {@linkcode Hash} of the file or directory at the\n * specified absolute path.\n *\n * @param absolutePath The absolute path to the file or directory.\n * @returns A Promise that resolves to the {@linkcode Hash} of the file\n * or directory.\n */\nconst hash = async (absolutePath: string): Promise<Hash> => {\n const sha1 = createHash(\"sha1\");\n sha1.update(path.basename(absolutePath));\n\n const stats = await fs.stat(absolutePath);\n\n let permissions;\n if (supportsPermissions) {\n // strip everything but the permissions\n permissions = stats.mode & 0o777;\n }\n\n if (stats.isDirectory()) {\n return { sha1: sha1.digest(\"hex\"), permissions };\n }\n\n // windows uses CRLF line endings whereas unix uses LF line endings so\n // we always strip out CR bytes (0x0d) when hashing files. this does\n // make us blind to files that only differ by CR bytes, but that's a\n // tradeoff we're willing to make.\n const removeCR = new Transform({\n transform(chunk: Buffer, _encoding, callback) {\n if (!chunk.includes(0x0d)) {\n callback(undefined, chunk);\n return;\n }\n\n const filteredChunk = Buffer.alloc(chunk.length);\n let i = 0;\n for (const byte of chunk) {\n if (byte !== 0x0d) {\n filteredChunk[i++] = byte;\n }\n }\n\n callback(undefined, filteredChunk.slice(0, i));\n },\n });\n\n await pipeline(fs.createReadStream(absolutePath), removeCR, sha1);\n\n return { sha1: sha1.digest(\"hex\"), permissions };\n};\n\n/**\n * Swallows ENOENT errors and throws any other errors.\n *\n * @param error - The error to handle.\n * @throws The original error if it is not an ENOENT error.\n */\nexport const swallowEnoent = (error: unknown): void => {\n if (error && typeof error === \"object\" && \"code\" in error && error.code === \"ENOENT\") {\n return;\n }\n throw error;\n};\n"],"names":["fs","ignore","assert","createHash","path","Transform","pipeline","normalizePath","ALWAYS_IGNORE_PATHS","HASHING_IGNORE_PATHS","Directory","init","dir","directory","loadIgnoreFile","relative","to","isAbsolute","absolute","pathSegments","result","resolve","startsWith","normalize","filepath","isDirectory","_ignorer","default","add","content","readFile","error","swallowEnoent","ignores","_isHashing","some","ignored","walk","entry","opendir","join","name","isFile","hashes","files","normalizedPath","absolutePath","hash","supportsPermissions","process","platform","sha1","update","basename","stats","stat","permissions","mode","digest","removeCR","transform","chunk","_encoding","callback","includes","undefined","filteredChunk","Buffer","alloc","length","i","byte","slice","createReadStream","code"],"mappings":"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"}
|
|
@@ -46,7 +46,7 @@ export class ChangesWithHash extends Map {
|
|
|
46
46
|
sourceHash
|
|
47
47
|
});
|
|
48
48
|
}
|
|
49
|
-
} else if (!isEqualHash(sourceHash, targetHash)) {
|
|
49
|
+
} else if (!isEqualHash(sourcePath, sourceHash, targetHash)) {
|
|
50
50
|
// the file or directory exists in target, but has a different
|
|
51
51
|
// hash, so it's been updated
|
|
52
52
|
changes.set(sourcePath, {
|
|
@@ -100,7 +100,7 @@ export class ChangesWithHash extends Map {
|
|
|
100
100
|
});
|
|
101
101
|
continue;
|
|
102
102
|
}
|
|
103
|
-
if (change.type !== "delete" && existingHash && isEqualHash(change.targetHash, existingHash)) {
|
|
103
|
+
if (change.type !== "delete" && existingHash && isEqualHash(path, change.targetHash, existingHash)) {
|
|
104
104
|
// already created or updated
|
|
105
105
|
log.trace("already created or updated", {
|
|
106
106
|
path,
|
|
@@ -119,13 +119,27 @@ export class ChangesWithHash extends Map {
|
|
|
119
119
|
}
|
|
120
120
|
return necessaryChanges;
|
|
121
121
|
};
|
|
122
|
-
export const isEqualHash = (
|
|
123
|
-
|
|
122
|
+
export const isEqualHash = (path, aHash, bHash)=>{
|
|
123
|
+
if (aHash.sha1 !== bHash.sha1) {
|
|
124
|
+
// the contents are different
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
127
|
+
if (path.endsWith("/")) {
|
|
128
|
+
// it's a directory, so we don't care about permissions
|
|
129
|
+
return true;
|
|
130
|
+
}
|
|
131
|
+
if (isNil(aHash.permissions) || isNil(bHash.permissions)) {
|
|
132
|
+
// one of the filesystems doesn't support permissions, so ignore them
|
|
133
|
+
return true;
|
|
134
|
+
}
|
|
135
|
+
// the contents are the same, and both filesystems support permissions
|
|
136
|
+
// so ensure the permissions are also the same
|
|
137
|
+
return aHash.permissions === bHash.permissions;
|
|
124
138
|
};
|
|
125
139
|
export const isEqualHashes = (a, b)=>{
|
|
126
140
|
for (const [aPath, aHash] of Object.entries(a)){
|
|
127
141
|
const bHash = b[aPath];
|
|
128
|
-
if (!bHash || !isEqualHash(aHash, bHash)) {
|
|
142
|
+
if (!bHash || !isEqualHash(aPath, aHash, bHash)) {
|
|
129
143
|
log.trace("hashes are not equal", {
|
|
130
144
|
path: aPath,
|
|
131
145
|
aHash,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/services/filesync/hashes.ts"],"sourcesContent":["import assert from \"node:assert\";\nimport { createLogger } from \"../output/log/logger.js\";\nimport { isNil } from \"../util/is.js\";\nimport { type Create, type Delete, type Update } from \"./changes.js\";\nimport type { Hash, Hashes } from \"./directory.js\";\n\nconst log = createLogger({ name: \"hashes\" });\n\nexport type CreateWithHash = Create & { targetHash: Hash };\nexport type UpdateWithHash = Update & { sourceHash: Hash; targetHash: Hash };\nexport type DeleteWithHash = Delete & { sourceHash: Hash };\nexport type ChangeWithHash = CreateWithHash | UpdateWithHash | DeleteWithHash;\n\nexport class ChangesWithHash extends Map<string, ChangeWithHash> {\n created(): string[] {\n return Array.from(this.entries())\n .filter(([, change]) => change.type === \"create\")\n .map(([path]) => path);\n }\n\n updated(): string[] {\n return Array.from(this.entries())\n .filter(([, change]) => change.type === \"update\")\n .map(([path]) => path);\n }\n\n deleted(): string[] {\n return Array.from(this.entries())\n .filter(([, change]) => change.type === \"delete\")\n .map(([path]) => path);\n }\n}\n\n/**\n * Calculates the changes that were made to `from` to make it end up as `to`.\n *\n * If `existing` is provided, only the changes that are necessary to\n * apply to `existing` are returned.\n *\n * If `ignore` is provided, any changes that were made to a path that\n * starts with any of the `ignore` paths are skipped.\n */\nexport const getChanges = ({\n from: source,\n to: target,\n existing,\n ignore,\n}: {\n from: Hashes;\n to: Hashes;\n existing?: Hashes;\n ignore?: string[];\n}): ChangesWithHash => {\n const changes = new ChangesWithHash();\n const targetPaths = Object.keys(target);\n\n for (const [sourcePath, sourceHash] of Object.entries(source)) {\n if (ignore?.some((ignored) => sourcePath.startsWith(ignored))) {\n continue;\n }\n\n const targetHash = target[sourcePath];\n if (!targetHash) {\n if (!sourcePath.endsWith(\"/\") || !targetPaths.some((targetPath) => targetPath.startsWith(sourcePath))) {\n // sourcePath is a file and it doesn't exist in target OR\n // sourcePath is a directory and target doesn't have any\n // existing files inside it, therefor the sourcePath has been\n // deleted\n changes.set(sourcePath, { type: \"delete\", sourceHash });\n log.trace(\"file deleted\", { path: sourcePath, sourceHash });\n }\n } else if (!isEqualHash(sourceHash, targetHash)) {\n // the file or directory exists in target, but has a different\n // hash, so it's been updated\n changes.set(sourcePath, { type: \"update\", sourceHash, targetHash });\n log.trace(\"file updated\", { path: sourcePath, sourceHash, targetHash });\n }\n }\n\n for (const targetPath of targetPaths) {\n if (ignore?.some((ignored) => targetPath.startsWith(ignored))) {\n continue;\n }\n\n if (!source[targetPath]) {\n // the targetPath doesn't exist in source, so it's been created\n const targetHash = target[targetPath];\n assert(targetHash, \"targetHash should exist\");\n\n changes.set(targetPath, { type: \"create\", targetHash });\n log.trace(\"file created\", { path: targetPath, targetHash });\n }\n }\n\n if (!existing) {\n return changes;\n }\n\n return withoutUnnecessaryChanges({ changes, existing });\n};\n\n/**\n * Filters out changes that the existing filesystem already has.\n */\nexport const withoutUnnecessaryChanges = ({ changes, existing }: { changes: ChangesWithHash; existing: Hashes }): ChangesWithHash => {\n const necessaryChanges = new ChangesWithHash();\n\n for (const [path, change] of changes) {\n const existingHash = existing[path];\n if (change.type === \"delete\" && !existingHash) {\n // already deleted\n log.trace(\"already deleted\", { path });\n continue;\n }\n\n if (change.type !== \"delete\" && existingHash && isEqualHash(change.targetHash, existingHash)) {\n // already created or updated\n log.trace(\"already created or updated\", { path, existingHash, targetHash: change.targetHash });\n continue;\n }\n\n // we could do this:\n // if (change.type === \"update\" && !existingHash) {\n // change = { type: \"create\", targetHash: change.targetHash };\n // }\n // but, changing the type makes the output look confusing and it\n // doesn't change the outcome, so we just leave it as is\n\n necessaryChanges.set(path, change);\n }\n\n return necessaryChanges;\n};\n\nexport const isEqualHash = (
|
|
1
|
+
{"version":3,"sources":["../../../src/services/filesync/hashes.ts"],"sourcesContent":["import assert from \"node:assert\";\nimport { createLogger } from \"../output/log/logger.js\";\nimport { isNil } from \"../util/is.js\";\nimport { type Create, type Delete, type Update } from \"./changes.js\";\nimport type { Hash, Hashes } from \"./directory.js\";\n\nconst log = createLogger({ name: \"hashes\" });\n\nexport type CreateWithHash = Create & { targetHash: Hash };\nexport type UpdateWithHash = Update & { sourceHash: Hash; targetHash: Hash };\nexport type DeleteWithHash = Delete & { sourceHash: Hash };\nexport type ChangeWithHash = CreateWithHash | UpdateWithHash | DeleteWithHash;\n\nexport class ChangesWithHash extends Map<string, ChangeWithHash> {\n created(): string[] {\n return Array.from(this.entries())\n .filter(([, change]) => change.type === \"create\")\n .map(([path]) => path);\n }\n\n updated(): string[] {\n return Array.from(this.entries())\n .filter(([, change]) => change.type === \"update\")\n .map(([path]) => path);\n }\n\n deleted(): string[] {\n return Array.from(this.entries())\n .filter(([, change]) => change.type === \"delete\")\n .map(([path]) => path);\n }\n}\n\n/**\n * Calculates the changes that were made to `from` to make it end up as `to`.\n *\n * If `existing` is provided, only the changes that are necessary to\n * apply to `existing` are returned.\n *\n * If `ignore` is provided, any changes that were made to a path that\n * starts with any of the `ignore` paths are skipped.\n */\nexport const getChanges = ({\n from: source,\n to: target,\n existing,\n ignore,\n}: {\n from: Hashes;\n to: Hashes;\n existing?: Hashes;\n ignore?: string[];\n}): ChangesWithHash => {\n const changes = new ChangesWithHash();\n const targetPaths = Object.keys(target);\n\n for (const [sourcePath, sourceHash] of Object.entries(source)) {\n if (ignore?.some((ignored) => sourcePath.startsWith(ignored))) {\n continue;\n }\n\n const targetHash = target[sourcePath];\n if (!targetHash) {\n if (!sourcePath.endsWith(\"/\") || !targetPaths.some((targetPath) => targetPath.startsWith(sourcePath))) {\n // sourcePath is a file and it doesn't exist in target OR\n // sourcePath is a directory and target doesn't have any\n // existing files inside it, therefor the sourcePath has been\n // deleted\n changes.set(sourcePath, { type: \"delete\", sourceHash });\n log.trace(\"file deleted\", { path: sourcePath, sourceHash });\n }\n } else if (!isEqualHash(sourcePath, sourceHash, targetHash)) {\n // the file or directory exists in target, but has a different\n // hash, so it's been updated\n changes.set(sourcePath, { type: \"update\", sourceHash, targetHash });\n log.trace(\"file updated\", { path: sourcePath, sourceHash, targetHash });\n }\n }\n\n for (const targetPath of targetPaths) {\n if (ignore?.some((ignored) => targetPath.startsWith(ignored))) {\n continue;\n }\n\n if (!source[targetPath]) {\n // the targetPath doesn't exist in source, so it's been created\n const targetHash = target[targetPath];\n assert(targetHash, \"targetHash should exist\");\n\n changes.set(targetPath, { type: \"create\", targetHash });\n log.trace(\"file created\", { path: targetPath, targetHash });\n }\n }\n\n if (!existing) {\n return changes;\n }\n\n return withoutUnnecessaryChanges({ changes, existing });\n};\n\n/**\n * Filters out changes that the existing filesystem already has.\n */\nexport const withoutUnnecessaryChanges = ({ changes, existing }: { changes: ChangesWithHash; existing: Hashes }): ChangesWithHash => {\n const necessaryChanges = new ChangesWithHash();\n\n for (const [path, change] of changes) {\n const existingHash = existing[path];\n if (change.type === \"delete\" && !existingHash) {\n // already deleted\n log.trace(\"already deleted\", { path });\n continue;\n }\n\n if (change.type !== \"delete\" && existingHash && isEqualHash(path, change.targetHash, existingHash)) {\n // already created or updated\n log.trace(\"already created or updated\", { path, existingHash, targetHash: change.targetHash });\n continue;\n }\n\n // we could do this:\n // if (change.type === \"update\" && !existingHash) {\n // change = { type: \"create\", targetHash: change.targetHash };\n // }\n // but, changing the type makes the output look confusing and it\n // doesn't change the outcome, so we just leave it as is\n\n necessaryChanges.set(path, change);\n }\n\n return necessaryChanges;\n};\n\nexport const isEqualHash = (path: string, aHash: Hash, bHash: Hash): boolean => {\n if (aHash.sha1 !== bHash.sha1) {\n // the contents are different\n return false;\n }\n\n if (path.endsWith(\"/\")) {\n // it's a directory, so we don't care about permissions\n return true;\n }\n\n if (isNil(aHash.permissions) || isNil(bHash.permissions)) {\n // one of the filesystems doesn't support permissions, so ignore them\n return true;\n }\n\n // the contents are the same, and both filesystems support permissions\n // so ensure the permissions are also the same\n return aHash.permissions === bHash.permissions;\n};\n\nexport const isEqualHashes = (a: Hashes, b: Hashes): boolean => {\n for (const [aPath, aHash] of Object.entries(a)) {\n const bHash = b[aPath];\n if (!bHash || !isEqualHash(aPath, aHash, bHash)) {\n log.trace(\"hashes are not equal\", { path: aPath, aHash, bHash });\n return false;\n }\n }\n\n for (const bPath of Object.keys(b)) {\n if (!a[bPath]) {\n log.trace(\"hashes are not equal\", { path: bPath, aHash: undefined, bHash: b[bPath] });\n return false;\n }\n }\n\n return true;\n};\n"],"names":["assert","createLogger","isNil","log","name","ChangesWithHash","Map","created","Array","from","entries","filter","change","type","map","path","updated","deleted","getChanges","source","to","target","existing","ignore","changes","targetPaths","Object","keys","sourcePath","sourceHash","some","ignored","startsWith","targetHash","endsWith","targetPath","set","trace","isEqualHash","withoutUnnecessaryChanges","necessaryChanges","existingHash","aHash","bHash","sha1","permissions","isEqualHashes","a","b","aPath","bPath","undefined"],"mappings":"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"}
|
|
@@ -1,8 +1,5 @@
|
|
|
1
|
-
import assert from "node:assert";
|
|
2
1
|
import stripAnsi from "strip-ansi";
|
|
3
|
-
import { config } from "../../../config/config.js";
|
|
4
2
|
import { isObject, isString } from "../../../util/is.js";
|
|
5
|
-
import { Level } from "../level.js";
|
|
6
3
|
export const formatJson = (level, name, msg, fields)=>{
|
|
7
4
|
return JSON.stringify({
|
|
8
5
|
level,
|
|
@@ -23,11 +20,6 @@ const serializeValue = (value)=>{
|
|
|
23
20
|
value = Array.from(value);
|
|
24
21
|
}
|
|
25
22
|
if (Array.isArray(value)) {
|
|
26
|
-
if (value.length > 10 && config.logLevel > Level.TRACE) {
|
|
27
|
-
// truncate arrays to 10 elements when not tracing
|
|
28
|
-
value = value.slice(0, 10);
|
|
29
|
-
assert(Array.isArray(value));
|
|
30
|
-
}
|
|
31
23
|
return value.map(serializeValue);
|
|
32
24
|
}
|
|
33
25
|
if (value instanceof Map) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../src/services/output/log/format/json.ts"],"sourcesContent":["import
|
|
1
|
+
{"version":3,"sources":["../../../../../src/services/output/log/format/json.ts"],"sourcesContent":["import stripAnsi from \"strip-ansi\";\nimport { isObject, isString } from \"../../../util/is.js\";\nimport type { Formatter } from \"./format.js\";\n\nexport const formatJson: Formatter = (level, name, msg, fields) => {\n return JSON.stringify({ level, name, msg: stripAnsi(msg).trim(), fields: serializeFields(fields) }) + \"\\n\";\n};\n\nconst serializeFields = (fields: Record<string, unknown>): Record<string, unknown> => {\n const result = {} as Record<string, unknown>;\n for (const [key, value] of Object.entries(fields)) {\n result[key] = serializeValue(value);\n }\n return result;\n};\n\nconst serializeValue = (value: unknown): unknown => {\n if (value instanceof Set) {\n value = Array.from(value);\n }\n\n if (Array.isArray(value)) {\n return value.map(serializeValue);\n }\n\n if (value instanceof Map) {\n value = Object.fromEntries(value.entries());\n }\n\n if (isObject(value)) {\n return serializeFields(value as Record<string, unknown>);\n }\n\n if (isString(value)) {\n return stripAnsi(value).trim();\n }\n\n return value;\n};\n"],"names":["stripAnsi","isObject","isString","formatJson","level","name","msg","fields","JSON","stringify","trim","serializeFields","result","key","value","Object","entries","serializeValue","Set","Array","from","isArray","map","Map","fromEntries"],"mappings":"AAAA,OAAOA,eAAe,aAAa;AACnC,SAASC,QAAQ,EAAEC,QAAQ,QAAQ,sBAAsB;AAGzD,OAAO,MAAMC,aAAwB,CAACC,OAAOC,MAAMC,KAAKC;IACtD,OAAOC,KAAKC,SAAS,CAAC;QAAEL;QAAOC;QAAMC,KAAKN,UAAUM,KAAKI,IAAI;QAAIH,QAAQI,gBAAgBJ;IAAQ,KAAK;AACxG,EAAE;AAEF,MAAMI,kBAAkB,CAACJ;IACvB,MAAMK,SAAS,CAAC;IAChB,KAAK,MAAM,CAACC,KAAKC,MAAM,IAAIC,OAAOC,OAAO,CAACT,QAAS;QACjDK,MAAM,CAACC,IAAI,GAAGI,eAAeH;IAC/B;IACA,OAAOF;AACT;AAEA,MAAMK,iBAAiB,CAACH;IACtB,IAAIA,iBAAiBI,KAAK;QACxBJ,QAAQK,MAAMC,IAAI,CAACN;IACrB;IAEA,IAAIK,MAAME,OAAO,CAACP,QAAQ;QACxB,OAAOA,MAAMQ,GAAG,CAACL;IACnB;IAEA,IAAIH,iBAAiBS,KAAK;QACxBT,QAAQC,OAAOS,WAAW,CAACV,MAAME,OAAO;IAC1C;IAEA,IAAIf,SAASa,QAAQ;QACnB,OAAOH,gBAAgBG;IACzB;IAEA,IAAIZ,SAASY,QAAQ;QACnB,OAAOd,UAAUc,OAAOJ,IAAI;IAC9B;IAEA,OAAOI;AACT"}
|
|
@@ -78,17 +78,24 @@ const formatFields = (fields, indent = 2)=>{
|
|
|
78
78
|
buf.push(formatValue(" []", gray, indent));
|
|
79
79
|
continue;
|
|
80
80
|
}
|
|
81
|
-
if (value.length > 10 && config.logLevel > Level.TRACE) {
|
|
82
|
-
// truncate arrays to 10 elements when not tracing
|
|
83
|
-
value = value.slice(0, 10);
|
|
84
|
-
assert(Array.isArray(value));
|
|
85
|
-
}
|
|
86
|
-
value = Object.fromEntries(value.entries());
|
|
87
|
-
}
|
|
88
|
-
if (value instanceof Map) {
|
|
89
81
|
value = Object.fromEntries(value.entries());
|
|
90
82
|
}
|
|
91
|
-
if (isObject(value)) {
|
|
83
|
+
if (isObject(value) || value instanceof Map) {
|
|
84
|
+
const entries = Object.entries(value);
|
|
85
|
+
if (entries.length === 0) {
|
|
86
|
+
buf.push(formatValue(" {}", gray, indent));
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
if (entries.length > 10 && config.logLevel > Level.TRACE) {
|
|
90
|
+
// truncate objects to 10 keys when not tracing
|
|
91
|
+
value = Object.fromEntries([
|
|
92
|
+
...entries.slice(0, 10),
|
|
93
|
+
[
|
|
94
|
+
"…",
|
|
95
|
+
`${entries.length - 10} more`
|
|
96
|
+
]
|
|
97
|
+
]);
|
|
98
|
+
}
|
|
92
99
|
buf.push(formatFields(value, indent + 2));
|
|
93
100
|
continue;
|
|
94
101
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../src/services/output/log/format/pretty.ts"],"sourcesContent":["import chalk, { Chalk } from \"chalk\";\nimport dayjs from \"dayjs\";\nimport assert from \"node:assert\";\nimport { config } from \"../../../config/config.js\";\nimport { env } from \"../../../config/env.js\";\nimport { isObject } from \"../../../util/is.js\";\nimport { Level } from \"../level.js\";\nimport type { Formatter } from \"./format.js\";\n\nexport const formatPretty: Formatter = (level, name, msg, fields) => {\n return `${formatTimestamp()} ${formatLevel(level)} ${formatName(name)}:${formatMessage(msg)}${formatFields(fields)}${NEW_LINE}`;\n};\n\nconst color = new Chalk({\n // we always turn off colors in tests (FORCE_COLOR=0) so that we get\n // predictable output, but if we're running with logs enabled\n // (GGT_LOG_LEVEL=info), we still want to see colors in our logs\n level: env.testLike && config.logLevel < Level.PRINT ? 3 : chalk.level,\n});\n\nconst blue = color.hex(\"#86B5F7\");\nconst blueLight = color.hex(\"#B2D0FA\");\nconst gray = color.hex(\"#D6D6D6\");\nconst grayDark = color.hex(\"#C2C2C2\");\nconst green = color.hex(\"#9DE6A4\");\nconst greenLight = color.hex(\"#BEEEC3\");\nconst orange = color.hex(\"#EEAC78\");\nconst orangeLight = color.hex(\"#F4C7A4\");\nconst pink = color.hex(\"#FAACB5\");\nconst red = color.hex(\"#A64E4E\");\nconst white = color.hex(\"#FFFFFF\");\n\nconst EMPTY = \"\";\nconst SPACE = \" \";\nconst NEW_LINE = \"\\n\";\nconst COLON = \":\";\nconst QUOTE = \"'\";\n\nconst formatKey = (key: string, indent: number): string => {\n const color = key === \"error\" ? red : gray;\n\n const buf: string[] = [];\n buf.push(NEW_LINE);\n for (let i = 0; i < indent; i++) {\n buf.push(SPACE);\n }\n buf.push(color(key));\n buf.push(COLON);\n\n return buf.join(\"\");\n};\n\nconst formatValue = (value: string, color: (s: string) => string, indent: number): string => {\n const lines = value.split(NEW_LINE);\n if (lines.length === 0) {\n return EMPTY;\n }\n\n const buf: string[] = [];\n const firstLine = lines.shift();\n assert(firstLine);\n buf.push(color(firstLine));\n\n // color the rest of the lines\n for (const line of lines) {\n if (!line) {\n continue;\n }\n\n buf.push(NEW_LINE);\n for (let i = 0; i < indent; i++) {\n buf.push(SPACE);\n }\n\n buf.push(color(line));\n }\n\n return buf.join(EMPTY);\n};\n\nconst formatFields = (fields: Record<string, unknown>, indent = 2): string => {\n if (Object.keys(fields).length === 0) {\n return EMPTY;\n }\n\n const buf: string[] = [];\n for (let [key, value] of Object.entries(fields)) {\n buf.push(formatKey(key, indent));\n\n if (value instanceof Set) {\n value = Array.from(value);\n }\n\n if (Array.isArray(value)) {\n if (value.length === 0) {\n buf.push(formatValue(\" []\", gray, indent));\n continue;\n }\n\n
|
|
1
|
+
{"version":3,"sources":["../../../../../src/services/output/log/format/pretty.ts"],"sourcesContent":["import chalk, { Chalk } from \"chalk\";\nimport dayjs from \"dayjs\";\nimport assert from \"node:assert\";\nimport { config } from \"../../../config/config.js\";\nimport { env } from \"../../../config/env.js\";\nimport { isObject } from \"../../../util/is.js\";\nimport { Level } from \"../level.js\";\nimport type { Formatter } from \"./format.js\";\n\nexport const formatPretty: Formatter = (level, name, msg, fields) => {\n return `${formatTimestamp()} ${formatLevel(level)} ${formatName(name)}:${formatMessage(msg)}${formatFields(fields)}${NEW_LINE}`;\n};\n\nconst color = new Chalk({\n // we always turn off colors in tests (FORCE_COLOR=0) so that we get\n // predictable output, but if we're running with logs enabled\n // (GGT_LOG_LEVEL=info), we still want to see colors in our logs\n level: env.testLike && config.logLevel < Level.PRINT ? 3 : chalk.level,\n});\n\nconst blue = color.hex(\"#86B5F7\");\nconst blueLight = color.hex(\"#B2D0FA\");\nconst gray = color.hex(\"#D6D6D6\");\nconst grayDark = color.hex(\"#C2C2C2\");\nconst green = color.hex(\"#9DE6A4\");\nconst greenLight = color.hex(\"#BEEEC3\");\nconst orange = color.hex(\"#EEAC78\");\nconst orangeLight = color.hex(\"#F4C7A4\");\nconst pink = color.hex(\"#FAACB5\");\nconst red = color.hex(\"#A64E4E\");\nconst white = color.hex(\"#FFFFFF\");\n\nconst EMPTY = \"\";\nconst SPACE = \" \";\nconst NEW_LINE = \"\\n\";\nconst COLON = \":\";\nconst QUOTE = \"'\";\n\nconst formatKey = (key: string, indent: number): string => {\n const color = key === \"error\" ? red : gray;\n\n const buf: string[] = [];\n buf.push(NEW_LINE);\n for (let i = 0; i < indent; i++) {\n buf.push(SPACE);\n }\n buf.push(color(key));\n buf.push(COLON);\n\n return buf.join(\"\");\n};\n\nconst formatValue = (value: string, color: (s: string) => string, indent: number): string => {\n const lines = value.split(NEW_LINE);\n if (lines.length === 0) {\n return EMPTY;\n }\n\n const buf: string[] = [];\n const firstLine = lines.shift();\n assert(firstLine);\n buf.push(color(firstLine));\n\n // color the rest of the lines\n for (const line of lines) {\n if (!line) {\n continue;\n }\n\n buf.push(NEW_LINE);\n for (let i = 0; i < indent; i++) {\n buf.push(SPACE);\n }\n\n buf.push(color(line));\n }\n\n return buf.join(EMPTY);\n};\n\nconst formatFields = (fields: Record<string, unknown>, indent = 2): string => {\n if (Object.keys(fields).length === 0) {\n return EMPTY;\n }\n\n const buf: string[] = [];\n for (let [key, value] of Object.entries(fields)) {\n buf.push(formatKey(key, indent));\n\n if (value instanceof Set) {\n value = Array.from(value);\n }\n\n if (Array.isArray(value)) {\n if (value.length === 0) {\n buf.push(formatValue(\" []\", gray, indent));\n continue;\n }\n\n value = Object.fromEntries(value.entries());\n }\n\n if (isObject(value) || value instanceof Map) {\n const entries = Object.entries(value);\n if (entries.length === 0) {\n buf.push(formatValue(\" {}\", gray, indent));\n continue;\n }\n\n if (entries.length > 10 && config.logLevel > Level.TRACE) {\n // truncate objects to 10 keys when not tracing\n value = Object.fromEntries([...entries.slice(0, 10), [\"…\", `${entries.length - 10} more`]]);\n }\n\n buf.push(formatFields(value as Record<string, unknown>, indent + 2));\n continue;\n }\n\n buf.push(SPACE);\n\n switch (typeof value) {\n case \"string\":\n buf.push(formatValue(QUOTE + value.replaceAll(NEW_LINE, NEW_LINE + SPACE.repeat(indent + key.length)) + QUOTE, blueLight, indent));\n break;\n case \"number\":\n buf.push(formatValue(String(value), orangeLight, indent));\n break;\n case \"bigint\":\n buf.push(formatValue(String(value) + \"n\", orangeLight, indent));\n break;\n case \"boolean\":\n buf.push(formatValue(String(value), greenLight, indent));\n break;\n default:\n buf.push(formatValue(String(value), white, indent));\n break;\n }\n }\n\n return buf.join(EMPTY);\n};\n\nconst formatTimestamp = (): string => {\n const ts = dayjs().format(\"hh:mm:ss\");\n return grayDark(ts);\n};\n\nconst formatLevel = (level: Level): string => {\n switch (level) {\n case Level.PRINT:\n return gray(\"PRINT\");\n case Level.TRACE:\n return blue(\"TRACE\");\n case Level.DEBUG:\n return orange(\"DEBUG\");\n case Level.INFO:\n return green(\"INFO\");\n case Level.WARN:\n return pink(\"WARN\");\n case Level.ERROR:\n return red(\"ERROR\");\n // case \"fatal\":\n // return red(colors.bold(level));\n }\n};\n\nconst formatName = (name: string): string => {\n return white(name);\n};\n\nconst formatMessage = (msg: string): string => {\n const lines = msg.split(NEW_LINE);\n if (lines.length === 1) {\n return SPACE + white(msg);\n }\n return NEW_LINE + lines.map((line) => SPACE + SPACE + line).join(NEW_LINE);\n};\n"],"names":["chalk","Chalk","dayjs","assert","config","env","isObject","Level","formatPretty","level","name","msg","fields","formatTimestamp","formatLevel","formatName","formatMessage","formatFields","NEW_LINE","color","testLike","logLevel","PRINT","blue","hex","blueLight","gray","grayDark","green","greenLight","orange","orangeLight","pink","red","white","EMPTY","SPACE","COLON","QUOTE","formatKey","key","indent","buf","push","i","join","formatValue","value","lines","split","length","firstLine","shift","line","Object","keys","entries","Set","Array","from","isArray","fromEntries","Map","TRACE","slice","replaceAll","repeat","String","ts","format","DEBUG","INFO","WARN","ERROR","map"],"mappings":"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"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-unused-vars */ /* eslint-disable @typescript-eslint/no-unsafe-return */ /* eslint-disable @typescript-eslint/consistent-type-definitions */ export const installJsonExtensions = ()=>{
|
|
2
|
+
if (!Object.prototype.hasOwnProperty.call(BigInt, "toJSON")) {
|
|
3
|
+
BigInt.prototype.toJSON = function() {
|
|
4
|
+
return String(this);
|
|
5
|
+
};
|
|
6
|
+
}
|
|
7
|
+
if (!Object.prototype.hasOwnProperty.call(Map, "toJSON")) {
|
|
8
|
+
Map.prototype.toJSON = function() {
|
|
9
|
+
return Object.fromEntries(this);
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
if (!Object.prototype.hasOwnProperty.call(Set, "toJSON")) {
|
|
13
|
+
Set.prototype.toJSON = function() {
|
|
14
|
+
return Array.from(this);
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
export const uninstallJsonExtensions = ()=>{
|
|
19
|
+
if (Object.prototype.hasOwnProperty.call(BigInt, "toJSON")) {
|
|
20
|
+
delete BigInt.prototype.toJSON;
|
|
21
|
+
}
|
|
22
|
+
if (Object.prototype.hasOwnProperty.call(Map, "toJSON")) {
|
|
23
|
+
delete Map.prototype.toJSON;
|
|
24
|
+
}
|
|
25
|
+
if (Object.prototype.hasOwnProperty.call(Set, "toJSON")) {
|
|
26
|
+
delete Set.prototype.toJSON;
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
//# sourceMappingURL=json.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/services/util/json.ts"],"sourcesContent":["/* eslint-disable @typescript-eslint/no-unused-vars */\n/* eslint-disable @typescript-eslint/no-unsafe-return */\n/* eslint-disable @typescript-eslint/consistent-type-definitions */\n\ndeclare global {\n interface BigInt {\n toJSON?(): string;\n }\n interface Map<K, V> {\n toJSON?(): Record<string, V>;\n }\n interface Set<T> {\n toJSON?(): T[];\n }\n}\n\nexport const installJsonExtensions = (): void => {\n if (!Object.prototype.hasOwnProperty.call(BigInt, \"toJSON\")) {\n BigInt.prototype.toJSON = function () {\n return String(this);\n };\n }\n\n if (!Object.prototype.hasOwnProperty.call(Map, \"toJSON\")) {\n Map.prototype.toJSON = function () {\n return Object.fromEntries(this);\n };\n }\n\n if (!Object.prototype.hasOwnProperty.call(Set, \"toJSON\")) {\n Set.prototype.toJSON = function () {\n return Array.from(this);\n };\n }\n};\n\nexport const uninstallJsonExtensions = (): void => {\n if (Object.prototype.hasOwnProperty.call(BigInt, \"toJSON\")) {\n delete BigInt.prototype.toJSON;\n }\n\n if (Object.prototype.hasOwnProperty.call(Map, \"toJSON\")) {\n delete Map.prototype.toJSON;\n }\n\n if (Object.prototype.hasOwnProperty.call(Set, \"toJSON\")) {\n delete Set.prototype.toJSON;\n }\n};\n"],"names":["installJsonExtensions","Object","prototype","hasOwnProperty","call","BigInt","toJSON","String","Map","fromEntries","Set","Array","from","uninstallJsonExtensions"],"mappings":"AAAA,oDAAoD,GACpD,sDAAsD,GACtD,iEAAiE,GAcjE,OAAO,MAAMA,wBAAwB;IACnC,IAAI,CAACC,OAAOC,SAAS,CAACC,cAAc,CAACC,IAAI,CAACC,QAAQ,WAAW;QAC3DA,OAAOH,SAAS,CAACI,MAAM,GAAG;YACxB,OAAOC,OAAO,IAAI;QACpB;IACF;IAEA,IAAI,CAACN,OAAOC,SAAS,CAACC,cAAc,CAACC,IAAI,CAACI,KAAK,WAAW;QACxDA,IAAIN,SAAS,CAACI,MAAM,GAAG;YACrB,OAAOL,OAAOQ,WAAW,CAAC,IAAI;QAChC;IACF;IAEA,IAAI,CAACR,OAAOC,SAAS,CAACC,cAAc,CAACC,IAAI,CAACM,KAAK,WAAW;QACxDA,IAAIR,SAAS,CAACI,MAAM,GAAG;YACrB,OAAOK,MAAMC,IAAI,CAAC,IAAI;QACxB;IACF;AACF,EAAE;AAEF,OAAO,MAAMC,0BAA0B;IACrC,IAAIZ,OAAOC,SAAS,CAACC,cAAc,CAACC,IAAI,CAACC,QAAQ,WAAW;QAC1D,OAAOA,OAAOH,SAAS,CAACI,MAAM;IAChC;IAEA,IAAIL,OAAOC,SAAS,CAACC,cAAc,CAACC,IAAI,CAACI,KAAK,WAAW;QACvD,OAAOA,IAAIN,SAAS,CAACI,MAAM;IAC7B;IAEA,IAAIL,OAAOC,SAAS,CAACC,cAAc,CAACC,IAAI,CAACM,KAAK,WAAW;QACvD,OAAOA,IAAIR,SAAS,CAACI,MAAM;IAC7B;AACF,EAAE"}
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gadgetinc/ggt",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.4",
|
|
4
4
|
"lockfileVersion": 2,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@gadgetinc/ggt",
|
|
9
|
-
"version": "0.4.
|
|
9
|
+
"version": "0.4.4",
|
|
10
10
|
"license": "MIT",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"@sentry/node": "^7.88.0",
|