@gadgetinc/ggt 0.4.0 → 0.4.2
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 -59
- package/lib/commands/deploy.js +7 -2
- package/lib/commands/deploy.js.map +1 -1
- package/lib/commands/root.js +0 -1
- package/lib/commands/root.js.map +1 -1
- package/lib/services/command/command.js +0 -1
- package/lib/services/command/command.js.map +1 -1
- package/lib/services/filesync/filesync.js +1 -3
- package/lib/services/filesync/filesync.js.map +1 -1
- package/lib/services/util/object.js +3 -0
- package/lib/services/util/object.js.map +1 -1
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -29,7 +29,6 @@
|
|
|
29
29
|
- [Usage](#usage)
|
|
30
30
|
- [Commands](#commands)
|
|
31
31
|
- [`ggt sync`](#ggt-sync)
|
|
32
|
-
- [`ggt deploy`](#ggt-deploy)
|
|
33
32
|
- [`ggt list`](#ggt-list)
|
|
34
33
|
- [`ggt login`](#ggt-login)
|
|
35
34
|
- [`ggt logout`](#ggt-logout)
|
|
@@ -62,7 +61,6 @@ USAGE
|
|
|
62
61
|
|
|
63
62
|
COMMANDS
|
|
64
63
|
sync Sync your Gadget application's source code
|
|
65
|
-
deploy Deploy your app to production
|
|
66
64
|
list List your apps
|
|
67
65
|
login Log in to your account
|
|
68
66
|
logout Log out of your account
|
|
@@ -152,62 +150,6 @@ EXAMPLE
|
|
|
152
150
|
Goodbye!
|
|
153
151
|
```
|
|
154
152
|
|
|
155
|
-
### `ggt deploy`
|
|
156
|
-
|
|
157
|
-
```
|
|
158
|
-
Deploy your Gadget application's development source code to production.
|
|
159
|
-
|
|
160
|
-
USAGE
|
|
161
|
-
ggt deploy [DIRECTORY] [--app=<name>]
|
|
162
|
-
|
|
163
|
-
ARGUMENTS
|
|
164
|
-
DIRECTORY The directory to sync files to and deploy (default: ".")
|
|
165
|
-
|
|
166
|
-
FLAGS
|
|
167
|
-
-a, --app=<name> The Gadget application to deploy
|
|
168
|
-
--force Deploy the Gadget application regardless of any issues it may have
|
|
169
|
-
|
|
170
|
-
DESCRIPTION
|
|
171
|
-
Deploy allows you to deploy your current Gadget application in development to production.
|
|
172
|
-
|
|
173
|
-
It detects if local files are up to date with remote and if the Gadget application
|
|
174
|
-
is in a deployable state. If there are any issues, it will display them and ask if
|
|
175
|
-
you would like to deploy anyways.
|
|
176
|
-
|
|
177
|
-
Note:
|
|
178
|
-
• If local files are not up to date or have not recently been synced with remote ones,
|
|
179
|
-
you will be prompted to run a one-time sync to ensure the files remain consistent with
|
|
180
|
-
what is on the remote.
|
|
181
|
-
• You may wish to keep ggt sync running in the background before trying to run ggt deploy
|
|
182
|
-
|
|
183
|
-
EXAMPLE
|
|
184
|
-
$ ggt deploy ~/gadget/example --app example
|
|
185
|
-
|
|
186
|
-
App example
|
|
187
|
-
Editor https://example.gadget.app/edit
|
|
188
|
-
Playground https://example.gadget.app/api/graphql/playground
|
|
189
|
-
Docs https://docs.gadget.dev/api/example
|
|
190
|
-
|
|
191
|
-
Endpoints
|
|
192
|
-
• https://example.gadget.app
|
|
193
|
-
• https://example--development.gadget.app
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
Building frontend assets ...
|
|
197
|
-
✔ DONE
|
|
198
|
-
|
|
199
|
-
Setting up database ...
|
|
200
|
-
✔ DONE
|
|
201
|
-
|
|
202
|
-
Copying development ...
|
|
203
|
-
✔ DONE
|
|
204
|
-
|
|
205
|
-
Restarting app ...
|
|
206
|
-
✔ DONE
|
|
207
|
-
|
|
208
|
-
Deploy completed. Good bye!
|
|
209
|
-
```
|
|
210
|
-
|
|
211
153
|
### `ggt list`
|
|
212
154
|
|
|
213
155
|
```
|
|
@@ -278,5 +220,5 @@ USAGE
|
|
|
278
220
|
|
|
279
221
|
EXAMPLES
|
|
280
222
|
$ ggt version
|
|
281
|
-
0.4.
|
|
223
|
+
0.4.2
|
|
282
224
|
```
|
package/lib/commands/deploy.js
CHANGED
|
@@ -7,7 +7,7 @@ import { isEqualHashes } from "../services/filesync/hashes.js";
|
|
|
7
7
|
import { select } from "../services/output/prompt.js";
|
|
8
8
|
import { sprint } from "../services/output/sprint.js";
|
|
9
9
|
import { getUserOrLogin } from "../services/user/user.js";
|
|
10
|
-
import { isGraphQLErrors } from "../services/util/is.js";
|
|
10
|
+
import { isCloseEvent, isGraphQLErrors } from "../services/util/is.js";
|
|
11
11
|
export const usage = ()=>sprint`
|
|
12
12
|
Deploy your Gadget application's development source code to production.
|
|
13
13
|
|
|
@@ -153,7 +153,10 @@ var AppDeploymentSteps;
|
|
|
153
153
|
force: ctx.args["--force"]
|
|
154
154
|
}),
|
|
155
155
|
onError: (error)=>{
|
|
156
|
-
if (
|
|
156
|
+
if (isCloseEvent(error.cause)) {
|
|
157
|
+
spinner.fail("Failed");
|
|
158
|
+
log.printlns(error.message);
|
|
159
|
+
} else if (isGraphQLErrors(error.cause)) {
|
|
157
160
|
const message = error.cause[0]?.message;
|
|
158
161
|
if (message && message.includes("GGT_PAYMENT_REQUIRED")) {
|
|
159
162
|
log.println("Production environment limit reached. Upgrade your plan to deploy");
|
|
@@ -164,6 +167,8 @@ var AppDeploymentSteps;
|
|
|
164
167
|
log.error("failed to deploy", {
|
|
165
168
|
error
|
|
166
169
|
});
|
|
170
|
+
unsubscribe();
|
|
171
|
+
return;
|
|
167
172
|
},
|
|
168
173
|
onData: async ({ publishStatus })=>{
|
|
169
174
|
const { progress, issues } = publishStatus ?? {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/commands/deploy.ts"],"sourcesContent":["import chalk from \"chalk\";\nimport ora from \"ora\";\nimport { AppArg } from \"../services/app/arg.js\";\nimport { REMOTE_SERVER_CONTRACT_STATUS_SUBSCRIPTION } from \"../services/app/edit-graphql.js\";\nimport type { ArgsSpec } from \"../services/command/arg.js\";\nimport { type Command, type Usage } from \"../services/command/command.js\";\nimport { FileSync } from \"../services/filesync/filesync.js\";\nimport { isEqualHashes } from \"../services/filesync/hashes.js\";\nimport { select } from \"../services/output/prompt.js\";\nimport { sprint } from \"../services/output/sprint.js\";\nimport { getUserOrLogin } from \"../services/user/user.js\";\nimport { isGraphQLErrors } from \"../services/util/is.js\";\n\nexport const usage: Usage = () => sprint`\n Deploy your Gadget application's development source code to production.\n\n {bold USAGE}\n ggt deploy [DIRECTORY] [--app=<name>]\n\n {bold ARGUMENTS}\n DIRECTORY The directory to sync files to and deploy (default: \".\")\n\n {bold FLAGS}\n -a, --app=<name> The Gadget application to deploy\n --force Deploy the Gadget application regardless of any issues it may have\n\n {bold DESCRIPTION}\n Deploy allows you to deploy your current Gadget application in development to production.\n \n It detects if local files are up to date with remote and if the Gadget application \n is in a deployable state. If there are any issues, it will display them and ask if \n you would like to deploy anyways. \n \n Note:\n • If local files are not up to date or have not recently been synced with remote ones,\n you will be prompted to run a one-time sync to ensure the files remain consistent with \n what is on the remote. \n • You may wish to keep ggt sync running in the background before trying to run ggt deploy\n \n {bold EXAMPLE} \n $ ggt deploy ~/gadget/example --app example\n \n App example\n Editor https://example.gadget.app/edit\n Playground https://example.gadget.app/api/graphql/playground\n Docs https://docs.gadget.dev/api/example\n\n Endpoints\n • https://example.gadget.app\n • https://example--development.gadget.app\n \n \n Building frontend assets ...\n ✔ DONE\n \n Setting up database ...\n ✔ DONE\n \n Copying development ...\n ✔ DONE\n \n Restarting app ...\n ✔ DONE\n \n Deploy completed. Good bye!\n`;\n\nexport const args = {\n \"--app\": {\n type: AppArg,\n alias: \"-a\",\n },\n \"--force\": Boolean,\n} satisfies ArgsSpec;\n\nexport enum Action {\n DEPLOY_ANYWAYS = \"Deploy anyways\",\n SYNC_ONCE = \"Sync once\",\n CANCEL = \"Cancel (Ctrl+C)\",\n}\n\nconst AppDeploymentStepsToAppDeployState = (step: string | undefined): string => {\n switch (step) {\n case \"NOT_STARTED\":\n return \"Deploy not started\";\n case \"STARTING\":\n case \"BUILDING_ASSETS\":\n case \"UPLOADING_ASSETS\":\n return \"Building frontend assets\";\n case \"CONVERGING_STORAGE\":\n return \"Setting up database\";\n case \"PUBLISHING_TREE\":\n return \"Copying development\";\n case \"RELOADING_SANDBOX\":\n return \"Restarting app\";\n case \"COMPLETED\":\n return \"Deploy completed\";\n default:\n return \"Unknown step\";\n }\n};\n\nenum AppDeploymentSteps {\n NOT_STARTED = \"NOT_STARTED\",\n STARTING = \"STARTING\",\n BUILDING_ASSETS = \"BUILDING_ASSETS\",\n UPLOADING_ASSETS = \"UPLOADING_ASSETS\",\n CONVERGING_STORAGE = \"CONVERGING_STORAGE\",\n PUBLISHING_TREE = \"PUBLISHING_TREE\",\n RELOADING_SANDBOX = \"RELOADING_SANDBOX\",\n COMPLETED = \"COMPLETED\",\n}\n\n/**\n * Runs the deploy process.\n */\n\nexport const command = (async (ctx, firstRun = true) => {\n const spinner = ora();\n let prevProgress: string | undefined = AppDeploymentStepsToAppDeployState(\"NOT_STARTED\");\n let action: Action;\n\n const filesync = await FileSync.init({\n user: await getUserOrLogin(),\n dir: ctx.args._[0],\n app: ctx.args[\"--app\"],\n });\n\n const log = filesync.log.extend(\"deploy\");\n\n if (firstRun) {\n log.printlns`App: ${filesync.app.slug}`;\n }\n\n const { localHashes, gadgetHashes } = await filesync._getHashes();\n\n const upToDate = isEqualHashes(localHashes, gadgetHashes);\n\n if (!upToDate) {\n log.printlns`\n Local files have diverged from remote. Run a sync once to converge your files or keep {italic ggt sync} running in the background.\n `;\n\n action = await select({\n message: \"How would you like to proceed?\",\n choices: [Action.CANCEL, Action.SYNC_ONCE],\n });\n\n switch (action) {\n case Action.SYNC_ONCE: {\n await filesync.sync();\n\n break;\n }\n case Action.CANCEL: {\n process.exit(0);\n }\n }\n }\n\n // subscribes to the graphql subscription that will listen and send back the server contract status\n const unsubscribe = filesync.editGraphQL.subscribe({\n query: REMOTE_SERVER_CONTRACT_STATUS_SUBSCRIPTION,\n variables: () => ({ localFilesVersion: String(filesync.filesVersion), force: ctx.args[\"--force\"] }),\n onError: (error) => {\n if (isGraphQLErrors(error.cause)) {\n const message = error.cause[0]?.message;\n if (message && message.includes(\"GGT_PAYMENT_REQUIRED\")) {\n log.println(\"Production environment limit reached. Upgrade your plan to deploy\");\n } else {\n log.println(`${message}`);\n }\n }\n log.error(\"failed to deploy\", { error });\n },\n onData: async ({ publishStatus }): Promise<void> => {\n const { progress, issues } = publishStatus ?? {};\n\n const hasIssues = issues?.length;\n\n if (firstRun && hasIssues) {\n log.printlns`{underline Issues detected}`;\n\n for (const issue of issues) {\n const message = issue.message.replace(/\"/g, \"\");\n const nodeType = issue.node?.type;\n const nodeName = issue.node?.name;\n const nodeParent = issue.node?.parentApiIdentifier;\n\n log.printlns(\n `\n • ${message} \n ${nodeType ? `${nodeType}: ${chalk.cyan(nodeName)}` : \"\"} ${\n nodeParent ? `ParentResource: ${chalk.cyan(nodeParent)}` : \"\"\n }\n `.trim(),\n );\n }\n\n if (!ctx.args[\"--force\"]) {\n unsubscribe();\n\n action = await select({\n message: \"Detected some issues with your app. How would you like to proceed?\",\n choices: [Action.CANCEL, Action.DEPLOY_ANYWAYS],\n });\n\n switch (action) {\n case Action.DEPLOY_ANYWAYS: {\n ctx.args[\"--force\"] = true;\n await command(ctx, false);\n break;\n }\n case Action.CANCEL: {\n process.exit(0);\n }\n }\n }\n\n firstRun = false;\n } else {\n if (progress === AppDeploymentSteps.COMPLETED) {\n spinner.succeed(\"DONE\");\n log.printlns(\"Deploy completed. Good bye!\");\n unsubscribe();\n return;\n }\n\n const currentProgress = AppDeploymentStepsToAppDeployState(progress);\n\n if (progress && currentProgress !== prevProgress) {\n if ((progress as AppDeploymentSteps) !== AppDeploymentSteps.STARTING) {\n spinner.succeed(\"DONE\");\n }\n\n prevProgress = currentProgress;\n log.printlns(`${currentProgress} ...`);\n spinner.start(\"Working ...\");\n }\n }\n },\n });\n}) satisfies Command<typeof args>;\n"],"names":["chalk","ora","AppArg","REMOTE_SERVER_CONTRACT_STATUS_SUBSCRIPTION","FileSync","isEqualHashes","select","sprint","getUserOrLogin","isGraphQLErrors","usage","args","type","alias","Boolean","Action","AppDeploymentStepsToAppDeployState","step","AppDeploymentSteps","command","ctx","firstRun","spinner","prevProgress","action","filesync","init","user","dir","_","app","log","extend","printlns","slug","localHashes","gadgetHashes","_getHashes","upToDate","message","choices","sync","process","exit","unsubscribe","editGraphQL","subscribe","query","variables","localFilesVersion","String","filesVersion","force","onError","error","cause","includes","println","onData","publishStatus","progress","issues","hasIssues","length","issue","replace","nodeType","node","nodeName","name","nodeParent","parentApiIdentifier","cyan","trim","succeed","currentProgress","start"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../src/commands/deploy.ts"],"sourcesContent":["import chalk from \"chalk\";\nimport ora from \"ora\";\nimport { AppArg } from \"../services/app/arg.js\";\nimport { REMOTE_SERVER_CONTRACT_STATUS_SUBSCRIPTION } from \"../services/app/edit-graphql.js\";\nimport type { ArgsSpec } from \"../services/command/arg.js\";\nimport { type Command, type Usage } from \"../services/command/command.js\";\nimport { FileSync } from \"../services/filesync/filesync.js\";\nimport { isEqualHashes } from \"../services/filesync/hashes.js\";\nimport { select } from \"../services/output/prompt.js\";\nimport { sprint } from \"../services/output/sprint.js\";\nimport { getUserOrLogin } from \"../services/user/user.js\";\nimport { isCloseEvent, isGraphQLErrors } from \"../services/util/is.js\";\n\nexport const usage: Usage = () => sprint`\n Deploy your Gadget application's development source code to production.\n\n {bold USAGE}\n ggt deploy [DIRECTORY] [--app=<name>]\n\n {bold ARGUMENTS}\n DIRECTORY The directory to sync files to and deploy (default: \".\")\n\n {bold FLAGS}\n -a, --app=<name> The Gadget application to deploy\n --force Deploy the Gadget application regardless of any issues it may have\n\n {bold DESCRIPTION}\n Deploy allows you to deploy your current Gadget application in development to production.\n \n It detects if local files are up to date with remote and if the Gadget application \n is in a deployable state. If there are any issues, it will display them and ask if \n you would like to deploy anyways. \n \n Note:\n • If local files are not up to date or have not recently been synced with remote ones,\n you will be prompted to run a one-time sync to ensure the files remain consistent with \n what is on the remote. \n • You may wish to keep ggt sync running in the background before trying to run ggt deploy\n \n {bold EXAMPLE} \n $ ggt deploy ~/gadget/example --app example\n \n App example\n Editor https://example.gadget.app/edit\n Playground https://example.gadget.app/api/graphql/playground\n Docs https://docs.gadget.dev/api/example\n\n Endpoints\n • https://example.gadget.app\n • https://example--development.gadget.app\n \n \n Building frontend assets ...\n ✔ DONE\n \n Setting up database ...\n ✔ DONE\n \n Copying development ...\n ✔ DONE\n \n Restarting app ...\n ✔ DONE\n \n Deploy completed. Good bye!\n`;\n\nexport const args = {\n \"--app\": {\n type: AppArg,\n alias: \"-a\",\n },\n \"--force\": Boolean,\n} satisfies ArgsSpec;\n\nexport enum Action {\n DEPLOY_ANYWAYS = \"Deploy anyways\",\n SYNC_ONCE = \"Sync once\",\n CANCEL = \"Cancel (Ctrl+C)\",\n}\n\nconst AppDeploymentStepsToAppDeployState = (step: string | undefined): string => {\n switch (step) {\n case \"NOT_STARTED\":\n return \"Deploy not started\";\n case \"STARTING\":\n case \"BUILDING_ASSETS\":\n case \"UPLOADING_ASSETS\":\n return \"Building frontend assets\";\n case \"CONVERGING_STORAGE\":\n return \"Setting up database\";\n case \"PUBLISHING_TREE\":\n return \"Copying development\";\n case \"RELOADING_SANDBOX\":\n return \"Restarting app\";\n case \"COMPLETED\":\n return \"Deploy completed\";\n default:\n return \"Unknown step\";\n }\n};\n\nenum AppDeploymentSteps {\n NOT_STARTED = \"NOT_STARTED\",\n STARTING = \"STARTING\",\n BUILDING_ASSETS = \"BUILDING_ASSETS\",\n UPLOADING_ASSETS = \"UPLOADING_ASSETS\",\n CONVERGING_STORAGE = \"CONVERGING_STORAGE\",\n PUBLISHING_TREE = \"PUBLISHING_TREE\",\n RELOADING_SANDBOX = \"RELOADING_SANDBOX\",\n COMPLETED = \"COMPLETED\",\n}\n\n/**\n * Runs the deploy process.\n */\n\nexport const command = (async (ctx, firstRun = true) => {\n const spinner = ora();\n let prevProgress: string | undefined = AppDeploymentStepsToAppDeployState(\"NOT_STARTED\");\n let action: Action;\n\n const filesync = await FileSync.init({\n user: await getUserOrLogin(),\n dir: ctx.args._[0],\n app: ctx.args[\"--app\"],\n });\n\n const log = filesync.log.extend(\"deploy\");\n\n if (firstRun) {\n log.printlns`App: ${filesync.app.slug}`;\n }\n\n const { localHashes, gadgetHashes } = await filesync._getHashes();\n\n const upToDate = isEqualHashes(localHashes, gadgetHashes);\n\n if (!upToDate) {\n log.printlns`\n Local files have diverged from remote. Run a sync once to converge your files or keep {italic ggt sync} running in the background.\n `;\n\n action = await select({\n message: \"How would you like to proceed?\",\n choices: [Action.CANCEL, Action.SYNC_ONCE],\n });\n\n switch (action) {\n case Action.SYNC_ONCE: {\n await filesync.sync();\n\n break;\n }\n case Action.CANCEL: {\n process.exit(0);\n }\n }\n }\n\n // subscribes to the graphql subscription that will listen and send back the server contract status\n const unsubscribe = filesync.editGraphQL.subscribe({\n query: REMOTE_SERVER_CONTRACT_STATUS_SUBSCRIPTION,\n variables: () => ({ localFilesVersion: String(filesync.filesVersion), force: ctx.args[\"--force\"] }),\n onError: (error) => {\n if (isCloseEvent(error.cause)) {\n spinner.fail(\"Failed\");\n log.printlns(error.message);\n } else if (isGraphQLErrors(error.cause)) {\n const message = error.cause[0]?.message;\n if (message && message.includes(\"GGT_PAYMENT_REQUIRED\")) {\n log.println(\"Production environment limit reached. Upgrade your plan to deploy\");\n } else {\n log.println(`${message}`);\n }\n }\n log.error(\"failed to deploy\", { error });\n unsubscribe();\n return;\n },\n onData: async ({ publishStatus }): Promise<void> => {\n const { progress, issues } = publishStatus ?? {};\n\n const hasIssues = issues?.length;\n\n if (firstRun && hasIssues) {\n log.printlns`{underline Issues detected}`;\n\n for (const issue of issues) {\n const message = issue.message.replace(/\"/g, \"\");\n const nodeType = issue.node?.type;\n const nodeName = issue.node?.name;\n const nodeParent = issue.node?.parentApiIdentifier;\n\n log.printlns(\n `\n • ${message} \n ${nodeType ? `${nodeType}: ${chalk.cyan(nodeName)}` : \"\"} ${\n nodeParent ? `ParentResource: ${chalk.cyan(nodeParent)}` : \"\"\n }\n `.trim(),\n );\n }\n\n if (!ctx.args[\"--force\"]) {\n unsubscribe();\n\n action = await select({\n message: \"Detected some issues with your app. How would you like to proceed?\",\n choices: [Action.CANCEL, Action.DEPLOY_ANYWAYS],\n });\n\n switch (action) {\n case Action.DEPLOY_ANYWAYS: {\n ctx.args[\"--force\"] = true;\n await command(ctx, false);\n break;\n }\n case Action.CANCEL: {\n process.exit(0);\n }\n }\n }\n\n firstRun = false;\n } else {\n if (progress === AppDeploymentSteps.COMPLETED) {\n spinner.succeed(\"DONE\");\n log.printlns(\"Deploy completed. Good bye!\");\n unsubscribe();\n return;\n }\n\n const currentProgress = AppDeploymentStepsToAppDeployState(progress);\n\n if (progress && currentProgress !== prevProgress) {\n if ((progress as AppDeploymentSteps) !== AppDeploymentSteps.STARTING) {\n spinner.succeed(\"DONE\");\n }\n\n prevProgress = currentProgress;\n log.printlns(`${currentProgress} ...`);\n spinner.start(\"Working ...\");\n }\n }\n },\n });\n}) satisfies Command<typeof args>;\n"],"names":["chalk","ora","AppArg","REMOTE_SERVER_CONTRACT_STATUS_SUBSCRIPTION","FileSync","isEqualHashes","select","sprint","getUserOrLogin","isCloseEvent","isGraphQLErrors","usage","args","type","alias","Boolean","Action","AppDeploymentStepsToAppDeployState","step","AppDeploymentSteps","command","ctx","firstRun","spinner","prevProgress","action","filesync","init","user","dir","_","app","log","extend","printlns","slug","localHashes","gadgetHashes","_getHashes","upToDate","message","choices","sync","process","exit","unsubscribe","editGraphQL","subscribe","query","variables","localFilesVersion","String","filesVersion","force","onError","error","cause","fail","includes","println","onData","publishStatus","progress","issues","hasIssues","length","issue","replace","nodeType","node","nodeName","name","nodeParent","parentApiIdentifier","cyan","trim","succeed","currentProgress","start"],"mappings":"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"}
|
package/lib/commands/root.js
CHANGED
package/lib/commands/root.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/commands/root.ts"],"sourcesContent":["import arg from \"arg\";\nimport ms from \"ms\";\nimport type { ArgsSpec } from \"../services/command/arg.js\";\nimport { AvailableCommands, importCommand, isAvailableCommand, type Usage } from \"../services/command/command.js\";\nimport { Context } from \"../services/command/context.js\";\nimport { verbosityToLevel } from \"../services/output/log/level.js\";\nimport { createLogger } from \"../services/output/log/logger.js\";\nimport { reportErrorAndExit } from \"../services/output/report.js\";\nimport { sprint } from \"../services/output/sprint.js\";\nimport { warnIfUpdateAvailable } from \"../services/output/update.js\";\nimport { sortBySimilar } from \"../services/util/collection.js\";\nimport { isNil } from \"../services/util/is.js\";\n\nconst log = createLogger({ name: \"root\" });\n\nexport const rootUsage: Usage = () => sprint`\n The command-line interface for Gadget\n\n {bold USAGE}\n ggt [COMMAND]\n\n {bold COMMANDS}\n sync Sync your Gadget application's source code\n
|
|
1
|
+
{"version":3,"sources":["../../src/commands/root.ts"],"sourcesContent":["import arg from \"arg\";\nimport ms from \"ms\";\nimport type { ArgsSpec } from \"../services/command/arg.js\";\nimport { AvailableCommands, importCommand, isAvailableCommand, type Usage } from \"../services/command/command.js\";\nimport { Context } from \"../services/command/context.js\";\nimport { verbosityToLevel } from \"../services/output/log/level.js\";\nimport { createLogger } from \"../services/output/log/logger.js\";\nimport { reportErrorAndExit } from \"../services/output/report.js\";\nimport { sprint } from \"../services/output/sprint.js\";\nimport { warnIfUpdateAvailable } from \"../services/output/update.js\";\nimport { sortBySimilar } from \"../services/util/collection.js\";\nimport { isNil } from \"../services/util/is.js\";\n\nconst log = createLogger({ name: \"root\" });\n\nexport const rootUsage: Usage = () => sprint`\n The command-line interface for Gadget\n\n {bold USAGE}\n ggt [COMMAND]\n\n {bold COMMANDS}\n sync Sync your Gadget application's source code\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 version Print the version of ggt\n\n {bold FLAGS}\n -h, --help Print command's usage\n -v, --verbose Print verbose output\n --json Print output as JSON\n\n For more information on a specific command, use 'ggt [COMMAND] --help'\n`;\n\nexport const rootArgs = {\n \"--help\": {\n type: Boolean,\n alias: \"-h\",\n },\n \"--verbose\": {\n type: arg.COUNT,\n alias: [\"-v\", \"--debug\"],\n },\n \"--json\": {\n type: Boolean,\n },\n} satisfies ArgsSpec;\n\nexport const command = async (): Promise<void> => {\n const ctx = new Context(rootArgs, { argv: process.argv.slice(2), permissive: true });\n\n await warnIfUpdateAvailable();\n\n if (ctx.args[\"--json\"]) {\n process.env[\"GGT_LOG_FORMAT\"] = \"json\";\n }\n\n if (ctx.args[\"--verbose\"]) {\n process.env[\"GGT_LOG_LEVEL\"] = verbosityToLevel(ctx.args[\"--verbose\"]).toString();\n }\n\n const cmd = ctx.args._.shift();\n if (isNil(cmd)) {\n log.println(rootUsage());\n process.exit(0);\n }\n\n if (!isAvailableCommand(cmd)) {\n const [closest] = sortBySimilar(cmd, AvailableCommands);\n log.println`\n Unknown command {yellow ${cmd}}\n\n Did you mean {blueBright ${closest}}?\n\n Run {gray ggt --help} for usage\n `;\n process.exit(1);\n }\n\n const { usage, command, args } = await importCommand(cmd);\n\n if (ctx.args[\"--help\"]) {\n log.println(usage());\n process.exit(0);\n }\n\n try {\n await command(ctx.extend({ args, logName: cmd }));\n } catch (error) {\n await reportErrorAndExit(error);\n }\n\n for (const signal of [\"SIGINT\", \"SIGTERM\"] as const) {\n process.once(signal, () => {\n log.trace(\"received signal\", { signal });\n log.println` Stopping... {gray Press Ctrl+C again to force}`;\n ctx.abort();\n\n // when ggt is run via npx, and the user presses ctrl+c, npx\n // sends sigint twice in quick succession. in order to prevent\n // the second sigint from triggering the force exit listener,\n // we wait a bit before registering it\n setTimeout(() => {\n process.once(signal, () => {\n log.println(\" Exiting immediately\");\n process.exit(1);\n });\n }, ms(\"100ms\")).unref();\n });\n }\n};\n"],"names":["arg","ms","AvailableCommands","importCommand","isAvailableCommand","Context","verbosityToLevel","createLogger","reportErrorAndExit","sprint","warnIfUpdateAvailable","sortBySimilar","isNil","log","name","rootUsage","rootArgs","type","Boolean","alias","COUNT","command","ctx","argv","process","slice","permissive","args","env","toString","cmd","_","shift","println","exit","closest","usage","extend","logName","error","signal","once","trace","abort","setTimeout","unref"],"mappings":"AAAA,OAAOA,SAAS,MAAM;AACtB,OAAOC,QAAQ,KAAK;AAEpB,SAASC,iBAAiB,EAAEC,aAAa,EAAEC,kBAAkB,QAAoB,iCAAiC;AAClH,SAASC,OAAO,QAAQ,iCAAiC;AACzD,SAASC,gBAAgB,QAAQ,kCAAkC;AACnE,SAASC,YAAY,QAAQ,mCAAmC;AAChE,SAASC,kBAAkB,QAAQ,+BAA+B;AAClE,SAASC,MAAM,QAAQ,+BAA+B;AACtD,SAASC,qBAAqB,QAAQ,+BAA+B;AACrE,SAASC,aAAa,QAAQ,iCAAiC;AAC/D,SAASC,KAAK,QAAQ,yBAAyB;AAE/C,MAAMC,MAAMN,aAAa;IAAEO,MAAM;AAAO;AAExC,OAAO,MAAMC,YAAmB,IAAMN,MAAM,CAAC;;;;;;;;;;;;;;;;;;;;AAoB7C,CAAC,CAAC;AAEF,OAAO,MAAMO,WAAW;IACtB,UAAU;QACRC,MAAMC;QACNC,OAAO;IACT;IACA,aAAa;QACXF,MAAMjB,IAAIoB,KAAK;QACfD,OAAO;YAAC;YAAM;SAAU;IAC1B;IACA,UAAU;QACRF,MAAMC;IACR;AACF,EAAqB;AAErB,OAAO,MAAMG,UAAU;IACrB,MAAMC,MAAM,IAAIjB,QAAQW,UAAU;QAAEO,MAAMC,QAAQD,IAAI,CAACE,KAAK,CAAC;QAAIC,YAAY;IAAK;IAElF,MAAMhB;IAEN,IAAIY,IAAIK,IAAI,CAAC,SAAS,EAAE;QACtBH,QAAQI,GAAG,CAAC,iBAAiB,GAAG;IAClC;IAEA,IAAIN,IAAIK,IAAI,CAAC,YAAY,EAAE;QACzBH,QAAQI,GAAG,CAAC,gBAAgB,GAAGtB,iBAAiBgB,IAAIK,IAAI,CAAC,YAAY,EAAEE,QAAQ;IACjF;IAEA,MAAMC,MAAMR,IAAIK,IAAI,CAACI,CAAC,CAACC,KAAK;IAC5B,IAAIpB,MAAMkB,MAAM;QACdjB,IAAIoB,OAAO,CAAClB;QACZS,QAAQU,IAAI,CAAC;IACf;IAEA,IAAI,CAAC9B,mBAAmB0B,MAAM;QAC5B,MAAM,CAACK,QAAQ,GAAGxB,cAAcmB,KAAK5B;QACrCW,IAAIoB,OAAO,CAAC;8BACc,EAAEH,IAAI;;+BAEL,EAAEK,QAAQ;;;IAGrC,CAAC;QACDX,QAAQU,IAAI,CAAC;IACf;IAEA,MAAM,EAAEE,KAAK,EAAEf,OAAO,EAAEM,IAAI,EAAE,GAAG,MAAMxB,cAAc2B;IAErD,IAAIR,IAAIK,IAAI,CAAC,SAAS,EAAE;QACtBd,IAAIoB,OAAO,CAACG;QACZZ,QAAQU,IAAI,CAAC;IACf;IAEA,IAAI;QACF,MAAMb,QAAQC,IAAIe,MAAM,CAAC;YAAEV;YAAMW,SAASR;QAAI;IAChD,EAAE,OAAOS,OAAO;QACd,MAAM/B,mBAAmB+B;IAC3B;IAEA,KAAK,MAAMC,UAAU;QAAC;QAAU;KAAU,CAAW;QACnDhB,QAAQiB,IAAI,CAACD,QAAQ;YACnB3B,IAAI6B,KAAK,CAAC,mBAAmB;gBAAEF;YAAO;YACtC3B,IAAIoB,OAAO,CAAC,+CAA+C,CAAC;YAC5DX,IAAIqB,KAAK;YAET,4DAA4D;YAC5D,8DAA8D;YAC9D,6DAA6D;YAC7D,sCAAsC;YACtCC,WAAW;gBACTpB,QAAQiB,IAAI,CAACD,QAAQ;oBACnB3B,IAAIoB,OAAO,CAAC;oBACZT,QAAQU,IAAI,CAAC;gBACf;YACF,GAAGjC,GAAG,UAAU4C,KAAK;QACvB;IACF;AACF,EAAE"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/services/command/command.ts"],"sourcesContent":["/* eslint-disable @typescript-eslint/consistent-type-imports */\nimport assert from \"node:assert\";\nimport { pathToFileURL } from \"node:url\";\nimport type { Promisable } from \"type-fest\";\nimport type { rootArgs } from \"../../commands/root.js\";\nimport { config } from \"../config/config.js\";\nimport { relativeToThisFile } from \"../util/paths.js\";\nimport type { ArgsSpec } from \"./arg.js\";\nimport type { Context } from \"./context.js\";\n\nexport const AvailableCommands = [\"sync\", \"
|
|
1
|
+
{"version":3,"sources":["../../../src/services/command/command.ts"],"sourcesContent":["/* eslint-disable @typescript-eslint/consistent-type-imports */\nimport assert from \"node:assert\";\nimport { pathToFileURL } from \"node:url\";\nimport type { Promisable } from \"type-fest\";\nimport type { rootArgs } from \"../../commands/root.js\";\nimport { config } from \"../config/config.js\";\nimport { relativeToThisFile } from \"../util/paths.js\";\nimport type { ArgsSpec } from \"./arg.js\";\nimport type { Context } from \"./context.js\";\n\nexport const AvailableCommands = [\"sync\", \"list\", \"login\", \"logout\", \"whoami\", \"version\"] as const;\n\nexport type AvailableCommand = (typeof AvailableCommands)[number];\n\nexport const isAvailableCommand = (command: string): command is AvailableCommand => {\n return AvailableCommands.includes(command as AvailableCommand);\n};\n\nexport const importCommand = async (cmd: AvailableCommand): Promise<CommandSpec> => {\n assert(isAvailableCommand(cmd), `invalid command: ${cmd}`);\n let commandPath = relativeToThisFile(`../../commands/${cmd}.js`);\n if (config.windows) {\n // https://github.com/nodejs/node/issues/31710\n commandPath = pathToFileURL(commandPath).toString();\n }\n return (await import(commandPath)) as CommandSpec;\n};\n\nexport type CommandSpec<Args extends ArgsSpec = ArgsSpec, ParentArgsSpec extends ArgsSpec = typeof rootArgs> = {\n args?: Args;\n usage: () => string;\n command: (ctx: Context<Args, ParentArgsSpec>) => Promisable<void>;\n};\n\nexport type Command<Spec extends ArgsSpec = ArgsSpec, ParentSpec extends ArgsSpec = typeof rootArgs> = CommandSpec<\n Spec,\n ParentSpec\n>[\"command\"];\n\nexport type Usage = CommandSpec[\"usage\"];\n"],"names":["assert","pathToFileURL","config","relativeToThisFile","AvailableCommands","isAvailableCommand","command","includes","importCommand","cmd","commandPath","windows","toString"],"mappings":"AAAA,6DAA6D,GAC7D,OAAOA,YAAY,cAAc;AACjC,SAASC,aAAa,QAAQ,WAAW;AAGzC,SAASC,MAAM,QAAQ,sBAAsB;AAC7C,SAASC,kBAAkB,QAAQ,mBAAmB;AAItD,OAAO,MAAMC,oBAAoB;IAAC;IAAQ;IAAQ;IAAS;IAAU;IAAU;CAAU,CAAU;AAInG,OAAO,MAAMC,qBAAqB,CAACC;IACjC,OAAOF,kBAAkBG,QAAQ,CAACD;AACpC,EAAE;AAEF,OAAO,MAAME,gBAAgB,OAAOC;IAClCT,OAAOK,mBAAmBI,MAAM,CAAC,iBAAiB,EAAEA,IAAI,CAAC;IACzD,IAAIC,cAAcP,mBAAmB,CAAC,eAAe,EAAEM,IAAI,GAAG,CAAC;IAC/D,IAAIP,OAAOS,OAAO,EAAE;QAClB,8CAA8C;QAC9CD,cAAcT,cAAcS,aAAaE,QAAQ;IACnD;IACA,OAAQ,MAAM,MAAM,CAACF;AACvB,EAAE"}
|
|
@@ -645,9 +645,7 @@ export class FileSync {
|
|
|
645
645
|
}
|
|
646
646
|
};
|
|
647
647
|
export const assertAllGadgetFiles = ({ gadgetChanges })=>{
|
|
648
|
-
assert(gadgetChanges.created().length > 0, "expected gadgetChanges to have
|
|
649
|
-
assert(gadgetChanges.deleted().length === 0, "expected gadgetChanges to not have deleted files");
|
|
650
|
-
assert(gadgetChanges.updated().length === 0, "expected gadgetChanges to not have updated files");
|
|
648
|
+
assert(gadgetChanges.created().length > 0 || gadgetChanges.deleted().length > 0 || gadgetChanges.updated().length > 0, "expected gadgetChanges to have changes");
|
|
651
649
|
const allGadgetFiles = Array.from(gadgetChanges.keys()).every((path)=>path.startsWith(".gadget/"));
|
|
652
650
|
assert(allGadgetFiles, "expected all gadgetChanges to be .gadget/ files");
|
|
653
651
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/services/filesync/filesync.ts"],"sourcesContent":["import dayjs from \"dayjs\";\nimport { findUp } from \"find-up\";\nimport fs from \"fs-extra\";\nimport ms from \"ms\";\nimport assert from \"node:assert\";\nimport path from \"node:path\";\nimport process from \"node:process\";\nimport pMap from \"p-map\";\nimport PQueue from \"p-queue\";\nimport pRetry from \"p-retry\";\nimport type { Promisable } from \"type-fest\";\nimport { z } from \"zod\";\nimport { FileSyncEncoding, type FileSyncChangedEventInput, type FileSyncDeletedEventInput } from \"../../__generated__/graphql.js\";\nimport type { App } from \"../app/app.js\";\nimport { getApps } from \"../app/app.js\";\nimport {\n EditGraphQL,\n FILE_SYNC_COMPARISON_HASHES_QUERY,\n FILE_SYNC_FILES_QUERY,\n FILE_SYNC_HASHES_QUERY,\n PUBLISH_FILE_SYNC_EVENTS_MUTATION,\n REMOTE_FILE_SYNC_EVENTS_SUBSCRIPTION,\n} from \"../app/edit-graphql.js\";\nimport { ArgError } from \"../command/arg.js\";\nimport { config, homePath } from \"../config/config.js\";\nimport { createLogger } from \"../output/log/logger.js\";\nimport { select } from \"../output/prompt.js\";\nimport { sprint } from \"../output/sprint.js\";\nimport type { User } from \"../user/user.js\";\nimport { sortBySimilar } from \"../util/collection.js\";\nimport { noop } from \"../util/function.js\";\nimport { Changes, printChanges } from \"./changes.js\";\nimport { getConflicts, printConflicts, withoutConflictingChanges } from \"./conflicts.js\";\nimport { Directory, supportsPermissions, swallowEnoent, type Hashes } from \"./directory.js\";\nimport { InvalidSyncFileError, TooManySyncAttemptsError } from \"./error.js\";\nimport type { File } from \"./file.js\";\nimport { getChanges, isEqualHashes, type ChangesWithHash } from \"./hashes.js\";\n\nexport class FileSync {\n readonly editGraphQL: EditGraphQL;\n\n readonly log = createLogger({ name: \"filesync\", fields: () => ({ state: this._state }) });\n\n /**\n * A FIFO async callback queue that ensures we process filesync events\n * in the order we receive them.\n */\n private _queue = new PQueue({ concurrency: 1 });\n\n private constructor(\n /**\n * The directory that is being synced to.\n */\n readonly directory: Directory,\n\n /**\n * Whether the directory was empty or non-existent when we started.\n */\n readonly wasEmptyOrNonExistent: boolean,\n\n /**\n * The Gadget application that is being synced to.\n */\n readonly app: App,\n\n /**\n * The state of the filesystem.\n *\n * This is persisted to `.gadget/sync.json` within the {@linkcode directory}.\n */\n private _state: { app: string; filesVersion: string; mtime: number },\n ) {\n this.editGraphQL = new EditGraphQL(this.app);\n }\n\n /**\n * The last filesVersion that was written to the filesystem.\n *\n * This determines if the filesystem in Gadget is ahead of the\n * filesystem on the local machine.\n */\n get filesVersion(): bigint {\n return BigInt(this._state.filesVersion);\n }\n\n /**\n * The largest mtime that was seen on the filesystem.\n *\n * This is used to determine if any files have changed since the last\n * sync. This does not include the mtime of files that are ignored.\n */\n get mtime(): number {\n return this._state.mtime;\n }\n\n /**\n * Initializes a {@linkcode FileSync} instance.\n * - Ensures the directory exists.\n * - Ensures the directory is empty or contains a `.gadget/sync.json` file (unless `options.force` is `true`)\n * - Ensures an app is specified (either via `options.app` or by prompting the user)\n * - Ensures the specified app matches the app the directory was previously synced to (unless `options.force` is `true`)\n */\n static async init(options: { user: User; dir?: string; app?: string; force?: boolean }): Promise<FileSync> {\n const apps = await getApps(options.user);\n if (apps.length === 0) {\n throw new ArgError(\n sprint`\n You (${options.user.email}) don't have have any Gadget applications.\n\n Visit https://gadget.new to create one!\n `,\n );\n }\n\n let dir = options.dir;\n if (!dir) {\n // the user didn't specify a directory\n const filepath = await findUp(\".gadget/sync.json\");\n if (filepath) {\n // we found a .gadget/sync.json file, use its parent directory\n dir = path.join(filepath, \"../..\");\n } else {\n // we didn't find a .gadget/sync.json file, use the current directory\n dir = process.cwd();\n }\n }\n\n if (config.windows && dir.startsWith(\"~/\")) {\n // `~` doesn't expand to the home directory on Windows\n dir = homePath(dir.slice(2));\n }\n\n // ensure the root directory is an absolute path and exists\n const wasEmptyOrNonExistent = await isEmptyOrNonExistentDir(dir);\n await fs.ensureDir((dir = path.resolve(dir)));\n\n // try to load the .gadget/sync.json file\n const state = await fs\n .readJson(path.join(dir, \".gadget/sync.json\"))\n .then((json) =>\n z\n .object({\n app: z.string(),\n filesVersion: z.string(),\n mtime: z.number(),\n })\n .parse(json),\n )\n .catch(noop);\n\n let appSlug = options.app || state?.app;\n if (!appSlug) {\n // the user didn't specify an app, suggest some apps that they can sync to\n appSlug = await select({\n message: \"Select the app to sync to\",\n choices: apps.map((x) => x.slug),\n });\n }\n\n // try to find the appSlug in their list of apps\n const app = apps.find((app) => app.slug === appSlug);\n if (!app) {\n // the specified appSlug doesn't exist in their list of apps,\n // either they misspelled it or they don't have access to it\n // anymore, suggest some apps that are similar to the one they\n // specified\n const similarAppSlugs = sortBySimilar(\n appSlug,\n apps.map((app) => app.slug),\n ).slice(0, 5);\n\n throw new ArgError(\n sprint`\n Unknown application:\n\n ${appSlug}\n\n Did you mean one of these?\n\n\n `.concat(` • ${similarAppSlugs.join(\"\\n • \")}`),\n );\n }\n\n const directory = await Directory.init(dir);\n\n if (!state) {\n // the .gadget/sync.json file didn't exist or contained invalid json\n if (wasEmptyOrNonExistent || options.force) {\n // the directory was empty or the user passed --force\n // either way, create a fresh .gadget/sync.json file\n return new FileSync(directory, wasEmptyOrNonExistent, app, { app: app.slug, filesVersion: \"0\", mtime: 0 });\n }\n\n // the directory isn't empty and the user didn't pass --force\n throw new InvalidSyncFileError(dir, app.slug);\n }\n\n // the .gadget/sync.json file exists\n if (state.app === app.slug) {\n // the .gadget/sync.json file is for the same app that the user specified\n return new FileSync(directory, wasEmptyOrNonExistent, app, state);\n }\n\n // the .gadget/sync.json file is for a different app\n if (options.force) {\n // the user passed --force, so use the app they specified and overwrite everything\n return new FileSync(directory, wasEmptyOrNonExistent, app, { app: app.slug, filesVersion: \"0\", mtime: 0 });\n }\n\n // the user didn't pass --force, so throw an error\n throw new ArgError(sprint`\n You were about to sync the following app to the following directory:\n\n {dim ${app.slug}} → {dim ${dir}}\n\n However, that directory has already been synced with this app:\n\n {dim ${state.app}}\n\n If you're sure that you want to sync:\n\n {dim ${app.slug}} → {dim ${dir}}\n\n Then run {dim ggt sync} again with the {dim --force} flag.\n `);\n }\n\n /**\n * Waits for all pending and ongoing filesync operations to complete.\n */\n async idle(): Promise<void> {\n await this._queue.onIdle();\n }\n\n /**\n * Sends file changes to the Gadget.\n *\n * @param changes - The changes to send.\n * @returns A promise that resolves when the changes have been sent.\n */\n async sendChangesToGadget({ changes }: { changes: Changes }): Promise<void> {\n await this._enqueue(() => this._sendChangesToGadget({ changes }));\n }\n\n /**\n * Subscribes to file changes on Gadget and executes the provided\n * callbacks before and after the changes occur.\n *\n * @returns A function that unsubscribes from changes on Gadget.\n */\n subscribeToGadgetChanges({\n beforeChanges,\n afterChanges,\n onError,\n }: {\n beforeChanges: (data: { changed: string[]; deleted: string[] }) => Promisable<void>;\n afterChanges: (data: { changes: Changes }) => Promisable<void>;\n onError: (error: unknown) => void;\n }): () => void {\n return this.editGraphQL.subscribe({\n query: REMOTE_FILE_SYNC_EVENTS_SUBSCRIPTION,\n // the reason this is a function rather than a static value is\n // so that it will be re-evaluated if the connection is lost and\n // then re-established. this ensures that we send our current\n // filesVersion rather than the one that was sent when we first\n // subscribed\n variables: () => ({ localFilesVersion: String(this.filesVersion) }),\n onError,\n onData: ({ remoteFileSyncEvents: { changed, deleted, remoteFilesVersion } }) => {\n this._enqueue(async () => {\n if (BigInt(remoteFilesVersion) < this.filesVersion) {\n this.log.warn(\"skipping received changes because files version is outdated\", { filesVersion: remoteFilesVersion });\n return;\n }\n\n this.log.debug(\"received files\", {\n remoteFilesVersion: remoteFilesVersion,\n changed: changed.map((change) => change.path),\n deleted: deleted.map((change) => change.path),\n });\n\n const filterIgnoredFiles = (file: { path: string }): boolean => {\n const ignored = this.directory.ignores(file.path);\n if (ignored) {\n this.log.warn(\"skipping received change because file is ignored\", { path: file.path });\n }\n return !ignored;\n };\n\n changed = changed.filter(filterIgnoredFiles);\n deleted = deleted.filter(filterIgnoredFiles);\n\n if (changed.length === 0 && deleted.length === 0) {\n await this._save(remoteFilesVersion);\n return;\n }\n\n await beforeChanges({\n changed: changed.map((file) => file.path),\n deleted: deleted.map((file) => file.path),\n });\n\n const changes = await this._writeToLocalFilesystem({\n filesVersion: remoteFilesVersion,\n files: changed,\n delete: deleted.map((file) => file.path),\n });\n\n if (changes.size > 0) {\n printChanges({\n message: sprint`← Received {gray ${dayjs().format(\"hh:mm:ss A\")}}`,\n changes,\n tense: \"past\",\n limit: 10,\n });\n }\n\n await afterChanges({ changes });\n }).catch(onError);\n },\n });\n }\n\n /**\n * Ensures the local filesystem is in sync with Gadget's filesystem.\n * - All non-conflicting changes are automatically merged.\n * - Conflicts are resolved by prompting the user to either keep their\n * local changes or keep Gadget's changes.\n * - This function will not return until the filesystem is in sync.\n */\n async sync({ attempt = 0, preference }: { attempt?: number; preference?: ConflictPreference } = {}): Promise<void> {\n if (attempt > 10) {\n throw new TooManySyncAttemptsError(attempt);\n }\n\n const { filesVersionHashes, localHashes, gadgetHashes, gadgetFilesVersion } = await this._getHashes();\n this.log.debug(\"syncing\", { filesVersionHashes, localHashes, gadgetHashes, gadgetFilesVersion });\n\n if (isEqualHashes(localHashes, gadgetHashes)) {\n this.log.info(\"filesystem is in sync\");\n await this._save(gadgetFilesVersion);\n return;\n }\n\n let localChanges = getChanges({ from: filesVersionHashes, to: localHashes, existing: gadgetHashes, ignore: [\".gadget/\"] });\n let gadgetChanges = getChanges({ from: filesVersionHashes, to: gadgetHashes, existing: localHashes });\n\n if (localChanges.size === 0 && gadgetChanges.size === 0) {\n // the local filesystem is missing .gadget/ files\n gadgetChanges = getChanges({ from: localHashes, to: gadgetHashes });\n assertAllGadgetFiles({ gadgetChanges });\n }\n\n const conflicts = getConflicts({ localChanges, gadgetChanges });\n if (conflicts.size > 0) {\n this.log.debug(\"conflicts detected\", { conflicts });\n\n if (!preference) {\n printConflicts({\n message: sprint`{bold You have conflicting changes with Gadget}`,\n conflicts,\n });\n\n preference = await select({\n message: \"How would you like to resolve these conflicts?\",\n choices: Object.values(ConflictPreference),\n });\n }\n\n switch (preference) {\n case ConflictPreference.CANCEL: {\n process.exit(0);\n break;\n }\n case ConflictPreference.LOCAL: {\n gadgetChanges = withoutConflictingChanges({ conflicts, changes: gadgetChanges });\n break;\n }\n case ConflictPreference.GADGET: {\n localChanges = withoutConflictingChanges({ conflicts, changes: localChanges });\n break;\n }\n }\n }\n\n assert(localChanges.size > 0 || gadgetChanges.size > 0, \"there must be changes if hashes don't match\");\n\n if (gadgetChanges.size > 0) {\n await this._getChangesFromGadget({ changes: gadgetChanges, filesVersion: gadgetFilesVersion });\n }\n\n if (localChanges.size > 0) {\n await this._sendChangesToGadget({ changes: localChanges, expectedFilesVersion: gadgetFilesVersion });\n }\n\n // recursively call this function until we're in sync\n return this.sync({ attempt: ++attempt, preference });\n }\n\n async _getHashes(): Promise<{\n gadgetFilesVersion: bigint;\n filesVersionHashes: Hashes;\n localHashes: Hashes;\n gadgetHashes: Hashes;\n }> {\n const [localHashes, { filesVersionHashes, gadgetHashes, gadgetFilesVersion }] = await Promise.all([\n // get the hashes of our local files\n this.directory.hashes(),\n // get the hashes of our local filesVersion and the latest filesVersion\n (async () => {\n let gadgetFilesVersion: bigint;\n let gadgetHashes: Hashes;\n let filesVersionHashes: Hashes;\n\n if (this.filesVersion === 0n) {\n // this is the first time we're syncing, so just get the\n // hashes of the latest filesVersion\n const { fileSyncHashes } = await this.editGraphQL.query({ query: FILE_SYNC_HASHES_QUERY });\n gadgetFilesVersion = BigInt(fileSyncHashes.filesVersion);\n gadgetHashes = fileSyncHashes.hashes;\n filesVersionHashes = {};\n } else {\n // this isn't the first time we're syncing, so get the hashes\n // of the files at our local filesVersion and the latest\n // filesVersion\n const { fileSyncComparisonHashes } = await this.editGraphQL.query({\n query: FILE_SYNC_COMPARISON_HASHES_QUERY,\n variables: { filesVersion: String(this.filesVersion) },\n });\n gadgetFilesVersion = BigInt(fileSyncComparisonHashes.latestFilesVersionHashes.filesVersion);\n gadgetHashes = fileSyncComparisonHashes.latestFilesVersionHashes.hashes;\n filesVersionHashes = fileSyncComparisonHashes.filesVersionHashes.hashes;\n }\n\n return { filesVersionHashes, gadgetHashes, gadgetFilesVersion };\n })(),\n ]);\n\n return { filesVersionHashes, localHashes, gadgetHashes, gadgetFilesVersion };\n }\n\n private async _getChangesFromGadget({\n filesVersion,\n changes,\n }: {\n filesVersion: bigint;\n changes: Changes | ChangesWithHash;\n }): Promise<void> {\n this.log.debug(\"getting changes from gadget\", { filesVersion, changes });\n const created = changes.created();\n const updated = changes.updated();\n\n let files: File[] = [];\n if (created.length > 0 || updated.length > 0) {\n const { fileSyncFiles } = await this.editGraphQL.query({\n query: FILE_SYNC_FILES_QUERY,\n variables: {\n paths: [...created, ...updated],\n filesVersion: String(filesVersion),\n encoding: FileSyncEncoding.Base64,\n },\n });\n\n files = fileSyncFiles.files;\n }\n\n await this._writeToLocalFilesystem({\n filesVersion,\n files,\n delete: changes.deleted(),\n });\n\n printChanges({\n changes,\n tense: \"past\",\n message: sprint`← Received {gray ${dayjs().format(\"hh:mm:ss A\")}}`,\n });\n }\n\n private async _sendChangesToGadget({\n expectedFilesVersion = this.filesVersion,\n changes,\n printLimit,\n }: {\n expectedFilesVersion?: bigint;\n changes: Changes;\n printLimit?: number;\n }): Promise<void> {\n this.log.debug(\"sending changes to gadget\", { expectedFilesVersion, changes });\n const changed: FileSyncChangedEventInput[] = [];\n const deleted: FileSyncDeletedEventInput[] = [];\n\n await pMap(changes, async ([normalizedPath, change]) => {\n if (change.type === \"delete\") {\n deleted.push({ path: normalizedPath });\n return;\n }\n\n const absolutePath = this.directory.absolute(normalizedPath);\n\n let stats;\n try {\n stats = await fs.stat(absolutePath);\n } catch (error) {\n swallowEnoent(error);\n this.log.debug(\"skipping change because file doesn't exist\", { path: normalizedPath });\n return;\n }\n\n let content = \"\";\n if (stats.isFile()) {\n content = await fs.readFile(absolutePath, FileSyncEncoding.Base64);\n }\n\n let oldPath;\n if (change.type === \"create\" && change.oldPath) {\n oldPath = change.oldPath;\n }\n\n changed.push({\n content,\n oldPath,\n path: normalizedPath,\n mode: stats.mode,\n encoding: FileSyncEncoding.Base64,\n });\n });\n\n if (changed.length === 0 && deleted.length === 0) {\n this.log.debug(\"skipping send because there are no changes\");\n return;\n }\n\n const {\n publishFileSyncEvents: { remoteFilesVersion },\n } = await this.editGraphQL.query({\n query: PUBLISH_FILE_SYNC_EVENTS_MUTATION,\n variables: {\n input: {\n expectedRemoteFilesVersion: String(expectedFilesVersion),\n changed,\n deleted,\n },\n },\n });\n\n await this._save(remoteFilesVersion);\n\n printChanges({\n changes,\n tense: \"past\",\n message: sprint`→ Sent {gray ${dayjs().format(\"hh:mm:ss A\")}}`,\n limit: printLimit,\n });\n }\n\n private async _writeToLocalFilesystem(options: { filesVersion: bigint | string; files: File[]; delete: string[] }): Promise<Changes> {\n const filesVersion = BigInt(options.filesVersion);\n assert(filesVersion >= this.filesVersion, \"filesVersion must be greater than or equal to current filesVersion\");\n\n this.log.debug(\"writing to local filesystem\", {\n filesVersion,\n files: options.files.map((file) => file.path),\n delete: options.delete,\n });\n\n const created: string[] = [];\n const updated: string[] = [];\n\n await pMap(options.delete, async (filepath) => {\n const currentPath = this.directory.absolute(filepath);\n const backupPath = this.directory.absolute(\".gadget/backup\", this.directory.relative(filepath));\n\n // rather than `rm -rf`ing files, we move them to\n // `.gadget/backup/` so that users can recover them if something\n // goes wrong. We've seen a lot of EBUSY/EINVAL errors when moving\n // files so we retry a few times.\n await pRetry(\n async () => {\n try {\n // remove the current backup file in case it exists and is a\n // different type (file vs directory)\n await fs.remove(backupPath);\n await fs.move(currentPath, backupPath);\n } catch (error) {\n // replicate the behavior of `rm -rf` and ignore ENOENT\n swallowEnoent(error);\n }\n },\n {\n retries: 2,\n minTimeout: ms(\"100ms\"),\n onFailedAttempt: (error) => {\n this.log.warn(\"failed to move file to backup\", { error, currentPath, backupPath });\n },\n },\n );\n });\n\n await pMap(options.files, async (file) => {\n const absolutePath = this.directory.absolute(file.path);\n if (await fs.pathExists(absolutePath)) {\n updated.push(file.path);\n } else {\n created.push(file.path);\n }\n\n if (file.path.endsWith(\"/\")) {\n await fs.ensureDir(absolutePath);\n } else {\n await fs.outputFile(absolutePath, Buffer.from(file.content, file.encoding));\n }\n\n if (supportsPermissions) {\n // the os's default umask makes setting the mode during creation\n // not work, so an additional fs.chmod call is necessary to\n // ensure the file has the correct mode\n await fs.chmod(absolutePath, file.mode & 0o777);\n }\n\n if (absolutePath === this.directory.absolute(\".ignore\")) {\n await this.directory.loadIgnoreFile();\n }\n });\n\n await this._save(String(filesVersion));\n\n return new Changes([\n ...created.map((path) => [path, { type: \"create\" }] as const),\n ...updated.map((path) => [path, { type: \"update\" }] as const),\n ...options.delete.map((path) => [path, { type: \"delete\" }] as const),\n ]);\n }\n\n /**\n * Updates {@linkcode _state} and saves it to `.gadget/sync.json`.\n */\n private async _save(filesVersion: string | bigint): Promise<void> {\n this._state = { ...this._state, mtime: Date.now() + 1, filesVersion: String(filesVersion) };\n this.log.debug(\"saving state\", { state: this._state });\n await fs.outputJSON(this.directory.absolute(\".gadget/sync.json\"), this._state, { spaces: 2 });\n }\n\n /**\n * Enqueues a function that handles filesync events onto the {@linkcode _queue}.\n */\n private _enqueue<T>(fn: () => Promise<T>): Promise<T> {\n return this._queue.add(fn) as Promise<T>;\n }\n}\n\n/**\n * Checks if a directory is empty or non-existent.\n *\n * @param dir - The directory path to check.\n * @returns A Promise that resolves to a boolean indicating whether the directory is empty or non-existent.\n */\nexport const isEmptyOrNonExistentDir = async (dir: string): Promise<boolean> => {\n try {\n for await (const _ of await fs.opendir(dir, { bufferSize: 1 })) {\n return false;\n }\n return true;\n } catch (error) {\n swallowEnoent(error);\n return true;\n }\n};\n\nexport const assertAllGadgetFiles = ({ gadgetChanges }: { gadgetChanges: Changes }): void => {\n assert(gadgetChanges.created().length > 0, \"expected gadgetChanges to have created files\");\n assert(gadgetChanges.deleted().length === 0, \"expected gadgetChanges to not have deleted files\");\n assert(gadgetChanges.updated().length === 0, \"expected gadgetChanges to not have updated files\");\n\n const allGadgetFiles = Array.from(gadgetChanges.keys()).every((path) => path.startsWith(\".gadget/\"));\n assert(allGadgetFiles, \"expected all gadgetChanges to be .gadget/ files\");\n};\n\nexport const ConflictPreference = Object.freeze({\n CANCEL: \"Cancel (Ctrl+C)\",\n LOCAL: \"Keep my conflicting changes\",\n GADGET: \"Keep Gadget's conflicting changes\",\n});\n\nexport type ConflictPreference = (typeof ConflictPreference)[keyof typeof ConflictPreference];\n\nexport const ConflictPreferenceArg = (value: string, name: string): ConflictPreference => {\n if ([\"local\", \"gadget\"].includes(value)) {\n return ConflictPreference[value.toUpperCase() as keyof typeof ConflictPreference];\n }\n\n throw new ArgError(sprint`\n ${name} must be {bold local} or {bold gadget}\n\n {bold EXAMPLES:}\n ${name} local\n ${name} gadget\n `);\n};\n"],"names":["dayjs","findUp","fs","ms","assert","path","process","pMap","PQueue","pRetry","z","FileSyncEncoding","getApps","EditGraphQL","FILE_SYNC_COMPARISON_HASHES_QUERY","FILE_SYNC_FILES_QUERY","FILE_SYNC_HASHES_QUERY","PUBLISH_FILE_SYNC_EVENTS_MUTATION","REMOTE_FILE_SYNC_EVENTS_SUBSCRIPTION","ArgError","config","homePath","createLogger","select","sprint","sortBySimilar","noop","Changes","printChanges","getConflicts","printConflicts","withoutConflictingChanges","Directory","supportsPermissions","swallowEnoent","InvalidSyncFileError","TooManySyncAttemptsError","getChanges","isEqualHashes","FileSync","filesVersion","BigInt","_state","mtime","init","options","apps","user","length","email","dir","filepath","join","cwd","windows","startsWith","slice","wasEmptyOrNonExistent","isEmptyOrNonExistentDir","ensureDir","resolve","state","readJson","then","json","object","app","string","number","parse","catch","appSlug","message","choices","map","x","slug","find","similarAppSlugs","concat","directory","force","idle","_queue","onIdle","sendChangesToGadget","changes","_enqueue","_sendChangesToGadget","subscribeToGadgetChanges","beforeChanges","afterChanges","onError","editGraphQL","subscribe","query","variables","localFilesVersion","String","onData","remoteFileSyncEvents","changed","deleted","remoteFilesVersion","log","warn","debug","change","filterIgnoredFiles","file","ignored","ignores","filter","_save","_writeToLocalFilesystem","files","delete","size","format","tense","limit","sync","attempt","preference","filesVersionHashes","localHashes","gadgetHashes","gadgetFilesVersion","_getHashes","info","localChanges","from","to","existing","ignore","gadgetChanges","assertAllGadgetFiles","conflicts","Object","values","ConflictPreference","CANCEL","exit","LOCAL","GADGET","_getChangesFromGadget","expectedFilesVersion","Promise","all","hashes","fileSyncHashes","fileSyncComparisonHashes","latestFilesVersionHashes","created","updated","fileSyncFiles","paths","encoding","Base64","printLimit","normalizedPath","type","push","absolutePath","absolute","stats","stat","error","content","isFile","readFile","oldPath","mode","publishFileSyncEvents","input","expectedRemoteFilesVersion","currentPath","backupPath","relative","remove","move","retries","minTimeout","onFailedAttempt","pathExists","endsWith","outputFile","Buffer","chmod","loadIgnoreFile","Date","now","outputJSON","spaces","fn","add","name","fields","concurrency","_","opendir","bufferSize","allGadgetFiles","Array","keys","every","freeze","ConflictPreferenceArg","value","includes","toUpperCase"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../src/services/filesync/filesync.ts"],"sourcesContent":["import dayjs from \"dayjs\";\nimport { findUp } from \"find-up\";\nimport fs from \"fs-extra\";\nimport ms from \"ms\";\nimport assert from \"node:assert\";\nimport path from \"node:path\";\nimport process from \"node:process\";\nimport pMap from \"p-map\";\nimport PQueue from \"p-queue\";\nimport pRetry from \"p-retry\";\nimport type { Promisable } from \"type-fest\";\nimport { z } from \"zod\";\nimport { FileSyncEncoding, type FileSyncChangedEventInput, type FileSyncDeletedEventInput } from \"../../__generated__/graphql.js\";\nimport type { App } from \"../app/app.js\";\nimport { getApps } from \"../app/app.js\";\nimport {\n EditGraphQL,\n FILE_SYNC_COMPARISON_HASHES_QUERY,\n FILE_SYNC_FILES_QUERY,\n FILE_SYNC_HASHES_QUERY,\n PUBLISH_FILE_SYNC_EVENTS_MUTATION,\n REMOTE_FILE_SYNC_EVENTS_SUBSCRIPTION,\n} from \"../app/edit-graphql.js\";\nimport { ArgError } from \"../command/arg.js\";\nimport { config, homePath } from \"../config/config.js\";\nimport { createLogger } from \"../output/log/logger.js\";\nimport { select } from \"../output/prompt.js\";\nimport { sprint } from \"../output/sprint.js\";\nimport type { User } from \"../user/user.js\";\nimport { sortBySimilar } from \"../util/collection.js\";\nimport { noop } from \"../util/function.js\";\nimport { Changes, printChanges } from \"./changes.js\";\nimport { getConflicts, printConflicts, withoutConflictingChanges } from \"./conflicts.js\";\nimport { Directory, supportsPermissions, swallowEnoent, type Hashes } from \"./directory.js\";\nimport { InvalidSyncFileError, TooManySyncAttemptsError } from \"./error.js\";\nimport type { File } from \"./file.js\";\nimport { getChanges, isEqualHashes, type ChangesWithHash } from \"./hashes.js\";\n\nexport class FileSync {\n readonly editGraphQL: EditGraphQL;\n\n readonly log = createLogger({ name: \"filesync\", fields: () => ({ state: this._state }) });\n\n /**\n * A FIFO async callback queue that ensures we process filesync events\n * in the order we receive them.\n */\n private _queue = new PQueue({ concurrency: 1 });\n\n private constructor(\n /**\n * The directory that is being synced to.\n */\n readonly directory: Directory,\n\n /**\n * Whether the directory was empty or non-existent when we started.\n */\n readonly wasEmptyOrNonExistent: boolean,\n\n /**\n * The Gadget application that is being synced to.\n */\n readonly app: App,\n\n /**\n * The state of the filesystem.\n *\n * This is persisted to `.gadget/sync.json` within the {@linkcode directory}.\n */\n private _state: { app: string; filesVersion: string; mtime: number },\n ) {\n this.editGraphQL = new EditGraphQL(this.app);\n }\n\n /**\n * The last filesVersion that was written to the filesystem.\n *\n * This determines if the filesystem in Gadget is ahead of the\n * filesystem on the local machine.\n */\n get filesVersion(): bigint {\n return BigInt(this._state.filesVersion);\n }\n\n /**\n * The largest mtime that was seen on the filesystem.\n *\n * This is used to determine if any files have changed since the last\n * sync. This does not include the mtime of files that are ignored.\n */\n get mtime(): number {\n return this._state.mtime;\n }\n\n /**\n * Initializes a {@linkcode FileSync} instance.\n * - Ensures the directory exists.\n * - Ensures the directory is empty or contains a `.gadget/sync.json` file (unless `options.force` is `true`)\n * - Ensures an app is specified (either via `options.app` or by prompting the user)\n * - Ensures the specified app matches the app the directory was previously synced to (unless `options.force` is `true`)\n */\n static async init(options: { user: User; dir?: string; app?: string; force?: boolean }): Promise<FileSync> {\n const apps = await getApps(options.user);\n if (apps.length === 0) {\n throw new ArgError(\n sprint`\n You (${options.user.email}) don't have have any Gadget applications.\n\n Visit https://gadget.new to create one!\n `,\n );\n }\n\n let dir = options.dir;\n if (!dir) {\n // the user didn't specify a directory\n const filepath = await findUp(\".gadget/sync.json\");\n if (filepath) {\n // we found a .gadget/sync.json file, use its parent directory\n dir = path.join(filepath, \"../..\");\n } else {\n // we didn't find a .gadget/sync.json file, use the current directory\n dir = process.cwd();\n }\n }\n\n if (config.windows && dir.startsWith(\"~/\")) {\n // `~` doesn't expand to the home directory on Windows\n dir = homePath(dir.slice(2));\n }\n\n // ensure the root directory is an absolute path and exists\n const wasEmptyOrNonExistent = await isEmptyOrNonExistentDir(dir);\n await fs.ensureDir((dir = path.resolve(dir)));\n\n // try to load the .gadget/sync.json file\n const state = await fs\n .readJson(path.join(dir, \".gadget/sync.json\"))\n .then((json) =>\n z\n .object({\n app: z.string(),\n filesVersion: z.string(),\n mtime: z.number(),\n })\n .parse(json),\n )\n .catch(noop);\n\n let appSlug = options.app || state?.app;\n if (!appSlug) {\n // the user didn't specify an app, suggest some apps that they can sync to\n appSlug = await select({\n message: \"Select the app to sync to\",\n choices: apps.map((x) => x.slug),\n });\n }\n\n // try to find the appSlug in their list of apps\n const app = apps.find((app) => app.slug === appSlug);\n if (!app) {\n // the specified appSlug doesn't exist in their list of apps,\n // either they misspelled it or they don't have access to it\n // anymore, suggest some apps that are similar to the one they\n // specified\n const similarAppSlugs = sortBySimilar(\n appSlug,\n apps.map((app) => app.slug),\n ).slice(0, 5);\n\n throw new ArgError(\n sprint`\n Unknown application:\n\n ${appSlug}\n\n Did you mean one of these?\n\n\n `.concat(` • ${similarAppSlugs.join(\"\\n • \")}`),\n );\n }\n\n const directory = await Directory.init(dir);\n\n if (!state) {\n // the .gadget/sync.json file didn't exist or contained invalid json\n if (wasEmptyOrNonExistent || options.force) {\n // the directory was empty or the user passed --force\n // either way, create a fresh .gadget/sync.json file\n return new FileSync(directory, wasEmptyOrNonExistent, app, { app: app.slug, filesVersion: \"0\", mtime: 0 });\n }\n\n // the directory isn't empty and the user didn't pass --force\n throw new InvalidSyncFileError(dir, app.slug);\n }\n\n // the .gadget/sync.json file exists\n if (state.app === app.slug) {\n // the .gadget/sync.json file is for the same app that the user specified\n return new FileSync(directory, wasEmptyOrNonExistent, app, state);\n }\n\n // the .gadget/sync.json file is for a different app\n if (options.force) {\n // the user passed --force, so use the app they specified and overwrite everything\n return new FileSync(directory, wasEmptyOrNonExistent, app, { app: app.slug, filesVersion: \"0\", mtime: 0 });\n }\n\n // the user didn't pass --force, so throw an error\n throw new ArgError(sprint`\n You were about to sync the following app to the following directory:\n\n {dim ${app.slug}} → {dim ${dir}}\n\n However, that directory has already been synced with this app:\n\n {dim ${state.app}}\n\n If you're sure that you want to sync:\n\n {dim ${app.slug}} → {dim ${dir}}\n\n Then run {dim ggt sync} again with the {dim --force} flag.\n `);\n }\n\n /**\n * Waits for all pending and ongoing filesync operations to complete.\n */\n async idle(): Promise<void> {\n await this._queue.onIdle();\n }\n\n /**\n * Sends file changes to the Gadget.\n *\n * @param changes - The changes to send.\n * @returns A promise that resolves when the changes have been sent.\n */\n async sendChangesToGadget({ changes }: { changes: Changes }): Promise<void> {\n await this._enqueue(() => this._sendChangesToGadget({ changes }));\n }\n\n /**\n * Subscribes to file changes on Gadget and executes the provided\n * callbacks before and after the changes occur.\n *\n * @returns A function that unsubscribes from changes on Gadget.\n */\n subscribeToGadgetChanges({\n beforeChanges,\n afterChanges,\n onError,\n }: {\n beforeChanges: (data: { changed: string[]; deleted: string[] }) => Promisable<void>;\n afterChanges: (data: { changes: Changes }) => Promisable<void>;\n onError: (error: unknown) => void;\n }): () => void {\n return this.editGraphQL.subscribe({\n query: REMOTE_FILE_SYNC_EVENTS_SUBSCRIPTION,\n // the reason this is a function rather than a static value is\n // so that it will be re-evaluated if the connection is lost and\n // then re-established. this ensures that we send our current\n // filesVersion rather than the one that was sent when we first\n // subscribed\n variables: () => ({ localFilesVersion: String(this.filesVersion) }),\n onError,\n onData: ({ remoteFileSyncEvents: { changed, deleted, remoteFilesVersion } }) => {\n this._enqueue(async () => {\n if (BigInt(remoteFilesVersion) < this.filesVersion) {\n this.log.warn(\"skipping received changes because files version is outdated\", { filesVersion: remoteFilesVersion });\n return;\n }\n\n this.log.debug(\"received files\", {\n remoteFilesVersion: remoteFilesVersion,\n changed: changed.map((change) => change.path),\n deleted: deleted.map((change) => change.path),\n });\n\n const filterIgnoredFiles = (file: { path: string }): boolean => {\n const ignored = this.directory.ignores(file.path);\n if (ignored) {\n this.log.warn(\"skipping received change because file is ignored\", { path: file.path });\n }\n return !ignored;\n };\n\n changed = changed.filter(filterIgnoredFiles);\n deleted = deleted.filter(filterIgnoredFiles);\n\n if (changed.length === 0 && deleted.length === 0) {\n await this._save(remoteFilesVersion);\n return;\n }\n\n await beforeChanges({\n changed: changed.map((file) => file.path),\n deleted: deleted.map((file) => file.path),\n });\n\n const changes = await this._writeToLocalFilesystem({\n filesVersion: remoteFilesVersion,\n files: changed,\n delete: deleted.map((file) => file.path),\n });\n\n if (changes.size > 0) {\n printChanges({\n message: sprint`← Received {gray ${dayjs().format(\"hh:mm:ss A\")}}`,\n changes,\n tense: \"past\",\n limit: 10,\n });\n }\n\n await afterChanges({ changes });\n }).catch(onError);\n },\n });\n }\n\n /**\n * Ensures the local filesystem is in sync with Gadget's filesystem.\n * - All non-conflicting changes are automatically merged.\n * - Conflicts are resolved by prompting the user to either keep their\n * local changes or keep Gadget's changes.\n * - This function will not return until the filesystem is in sync.\n */\n async sync({ attempt = 0, preference }: { attempt?: number; preference?: ConflictPreference } = {}): Promise<void> {\n if (attempt > 10) {\n throw new TooManySyncAttemptsError(attempt);\n }\n\n const { filesVersionHashes, localHashes, gadgetHashes, gadgetFilesVersion } = await this._getHashes();\n this.log.debug(\"syncing\", { filesVersionHashes, localHashes, gadgetHashes, gadgetFilesVersion });\n\n if (isEqualHashes(localHashes, gadgetHashes)) {\n this.log.info(\"filesystem is in sync\");\n await this._save(gadgetFilesVersion);\n return;\n }\n\n let localChanges = getChanges({ from: filesVersionHashes, to: localHashes, existing: gadgetHashes, ignore: [\".gadget/\"] });\n let gadgetChanges = getChanges({ from: filesVersionHashes, to: gadgetHashes, existing: localHashes });\n\n if (localChanges.size === 0 && gadgetChanges.size === 0) {\n // the local filesystem is missing .gadget/ files\n gadgetChanges = getChanges({ from: localHashes, to: gadgetHashes });\n assertAllGadgetFiles({ gadgetChanges });\n }\n\n const conflicts = getConflicts({ localChanges, gadgetChanges });\n if (conflicts.size > 0) {\n this.log.debug(\"conflicts detected\", { conflicts });\n\n if (!preference) {\n printConflicts({\n message: sprint`{bold You have conflicting changes with Gadget}`,\n conflicts,\n });\n\n preference = await select({\n message: \"How would you like to resolve these conflicts?\",\n choices: Object.values(ConflictPreference),\n });\n }\n\n switch (preference) {\n case ConflictPreference.CANCEL: {\n process.exit(0);\n break;\n }\n case ConflictPreference.LOCAL: {\n gadgetChanges = withoutConflictingChanges({ conflicts, changes: gadgetChanges });\n break;\n }\n case ConflictPreference.GADGET: {\n localChanges = withoutConflictingChanges({ conflicts, changes: localChanges });\n break;\n }\n }\n }\n\n assert(localChanges.size > 0 || gadgetChanges.size > 0, \"there must be changes if hashes don't match\");\n\n if (gadgetChanges.size > 0) {\n await this._getChangesFromGadget({ changes: gadgetChanges, filesVersion: gadgetFilesVersion });\n }\n\n if (localChanges.size > 0) {\n await this._sendChangesToGadget({ changes: localChanges, expectedFilesVersion: gadgetFilesVersion });\n }\n\n // recursively call this function until we're in sync\n return this.sync({ attempt: ++attempt, preference });\n }\n\n async _getHashes(): Promise<{\n gadgetFilesVersion: bigint;\n filesVersionHashes: Hashes;\n localHashes: Hashes;\n gadgetHashes: Hashes;\n }> {\n const [localHashes, { filesVersionHashes, gadgetHashes, gadgetFilesVersion }] = await Promise.all([\n // get the hashes of our local files\n this.directory.hashes(),\n // get the hashes of our local filesVersion and the latest filesVersion\n (async () => {\n let gadgetFilesVersion: bigint;\n let gadgetHashes: Hashes;\n let filesVersionHashes: Hashes;\n\n if (this.filesVersion === 0n) {\n // this is the first time we're syncing, so just get the\n // hashes of the latest filesVersion\n const { fileSyncHashes } = await this.editGraphQL.query({ query: FILE_SYNC_HASHES_QUERY });\n gadgetFilesVersion = BigInt(fileSyncHashes.filesVersion);\n gadgetHashes = fileSyncHashes.hashes;\n filesVersionHashes = {};\n } else {\n // this isn't the first time we're syncing, so get the hashes\n // of the files at our local filesVersion and the latest\n // filesVersion\n const { fileSyncComparisonHashes } = await this.editGraphQL.query({\n query: FILE_SYNC_COMPARISON_HASHES_QUERY,\n variables: { filesVersion: String(this.filesVersion) },\n });\n gadgetFilesVersion = BigInt(fileSyncComparisonHashes.latestFilesVersionHashes.filesVersion);\n gadgetHashes = fileSyncComparisonHashes.latestFilesVersionHashes.hashes;\n filesVersionHashes = fileSyncComparisonHashes.filesVersionHashes.hashes;\n }\n\n return { filesVersionHashes, gadgetHashes, gadgetFilesVersion };\n })(),\n ]);\n\n return { filesVersionHashes, localHashes, gadgetHashes, gadgetFilesVersion };\n }\n\n private async _getChangesFromGadget({\n filesVersion,\n changes,\n }: {\n filesVersion: bigint;\n changes: Changes | ChangesWithHash;\n }): Promise<void> {\n this.log.debug(\"getting changes from gadget\", { filesVersion, changes });\n const created = changes.created();\n const updated = changes.updated();\n\n let files: File[] = [];\n if (created.length > 0 || updated.length > 0) {\n const { fileSyncFiles } = await this.editGraphQL.query({\n query: FILE_SYNC_FILES_QUERY,\n variables: {\n paths: [...created, ...updated],\n filesVersion: String(filesVersion),\n encoding: FileSyncEncoding.Base64,\n },\n });\n\n files = fileSyncFiles.files;\n }\n\n await this._writeToLocalFilesystem({\n filesVersion,\n files,\n delete: changes.deleted(),\n });\n\n printChanges({\n changes,\n tense: \"past\",\n message: sprint`← Received {gray ${dayjs().format(\"hh:mm:ss A\")}}`,\n });\n }\n\n private async _sendChangesToGadget({\n expectedFilesVersion = this.filesVersion,\n changes,\n printLimit,\n }: {\n expectedFilesVersion?: bigint;\n changes: Changes;\n printLimit?: number;\n }): Promise<void> {\n this.log.debug(\"sending changes to gadget\", { expectedFilesVersion, changes });\n const changed: FileSyncChangedEventInput[] = [];\n const deleted: FileSyncDeletedEventInput[] = [];\n\n await pMap(changes, async ([normalizedPath, change]) => {\n if (change.type === \"delete\") {\n deleted.push({ path: normalizedPath });\n return;\n }\n\n const absolutePath = this.directory.absolute(normalizedPath);\n\n let stats;\n try {\n stats = await fs.stat(absolutePath);\n } catch (error) {\n swallowEnoent(error);\n this.log.debug(\"skipping change because file doesn't exist\", { path: normalizedPath });\n return;\n }\n\n let content = \"\";\n if (stats.isFile()) {\n content = await fs.readFile(absolutePath, FileSyncEncoding.Base64);\n }\n\n let oldPath;\n if (change.type === \"create\" && change.oldPath) {\n oldPath = change.oldPath;\n }\n\n changed.push({\n content,\n oldPath,\n path: normalizedPath,\n mode: stats.mode,\n encoding: FileSyncEncoding.Base64,\n });\n });\n\n if (changed.length === 0 && deleted.length === 0) {\n this.log.debug(\"skipping send because there are no changes\");\n return;\n }\n\n const {\n publishFileSyncEvents: { remoteFilesVersion },\n } = await this.editGraphQL.query({\n query: PUBLISH_FILE_SYNC_EVENTS_MUTATION,\n variables: {\n input: {\n expectedRemoteFilesVersion: String(expectedFilesVersion),\n changed,\n deleted,\n },\n },\n });\n\n await this._save(remoteFilesVersion);\n\n printChanges({\n changes,\n tense: \"past\",\n message: sprint`→ Sent {gray ${dayjs().format(\"hh:mm:ss A\")}}`,\n limit: printLimit,\n });\n }\n\n private async _writeToLocalFilesystem(options: { filesVersion: bigint | string; files: File[]; delete: string[] }): Promise<Changes> {\n const filesVersion = BigInt(options.filesVersion);\n assert(filesVersion >= this.filesVersion, \"filesVersion must be greater than or equal to current filesVersion\");\n\n this.log.debug(\"writing to local filesystem\", {\n filesVersion,\n files: options.files.map((file) => file.path),\n delete: options.delete,\n });\n\n const created: string[] = [];\n const updated: string[] = [];\n\n await pMap(options.delete, async (filepath) => {\n const currentPath = this.directory.absolute(filepath);\n const backupPath = this.directory.absolute(\".gadget/backup\", this.directory.relative(filepath));\n\n // rather than `rm -rf`ing files, we move them to\n // `.gadget/backup/` so that users can recover them if something\n // goes wrong. We've seen a lot of EBUSY/EINVAL errors when moving\n // files so we retry a few times.\n await pRetry(\n async () => {\n try {\n // remove the current backup file in case it exists and is a\n // different type (file vs directory)\n await fs.remove(backupPath);\n await fs.move(currentPath, backupPath);\n } catch (error) {\n // replicate the behavior of `rm -rf` and ignore ENOENT\n swallowEnoent(error);\n }\n },\n {\n retries: 2,\n minTimeout: ms(\"100ms\"),\n onFailedAttempt: (error) => {\n this.log.warn(\"failed to move file to backup\", { error, currentPath, backupPath });\n },\n },\n );\n });\n\n await pMap(options.files, async (file) => {\n const absolutePath = this.directory.absolute(file.path);\n if (await fs.pathExists(absolutePath)) {\n updated.push(file.path);\n } else {\n created.push(file.path);\n }\n\n if (file.path.endsWith(\"/\")) {\n await fs.ensureDir(absolutePath);\n } else {\n await fs.outputFile(absolutePath, Buffer.from(file.content, file.encoding));\n }\n\n if (supportsPermissions) {\n // the os's default umask makes setting the mode during creation\n // not work, so an additional fs.chmod call is necessary to\n // ensure the file has the correct mode\n await fs.chmod(absolutePath, file.mode & 0o777);\n }\n\n if (absolutePath === this.directory.absolute(\".ignore\")) {\n await this.directory.loadIgnoreFile();\n }\n });\n\n await this._save(String(filesVersion));\n\n return new Changes([\n ...created.map((path) => [path, { type: \"create\" }] as const),\n ...updated.map((path) => [path, { type: \"update\" }] as const),\n ...options.delete.map((path) => [path, { type: \"delete\" }] as const),\n ]);\n }\n\n /**\n * Updates {@linkcode _state} and saves it to `.gadget/sync.json`.\n */\n private async _save(filesVersion: string | bigint): Promise<void> {\n this._state = { ...this._state, mtime: Date.now() + 1, filesVersion: String(filesVersion) };\n this.log.debug(\"saving state\", { state: this._state });\n await fs.outputJSON(this.directory.absolute(\".gadget/sync.json\"), this._state, { spaces: 2 });\n }\n\n /**\n * Enqueues a function that handles filesync events onto the {@linkcode _queue}.\n */\n private _enqueue<T>(fn: () => Promise<T>): Promise<T> {\n return this._queue.add(fn) as Promise<T>;\n }\n}\n\n/**\n * Checks if a directory is empty or non-existent.\n *\n * @param dir - The directory path to check.\n * @returns A Promise that resolves to a boolean indicating whether the directory is empty or non-existent.\n */\nexport const isEmptyOrNonExistentDir = async (dir: string): Promise<boolean> => {\n try {\n for await (const _ of await fs.opendir(dir, { bufferSize: 1 })) {\n return false;\n }\n return true;\n } catch (error) {\n swallowEnoent(error);\n return true;\n }\n};\n\nexport const assertAllGadgetFiles = ({ gadgetChanges }: { gadgetChanges: Changes }): void => {\n assert(\n gadgetChanges.created().length > 0 || gadgetChanges.deleted().length > 0 || gadgetChanges.updated().length > 0,\n \"expected gadgetChanges to have changes\",\n );\n\n const allGadgetFiles = Array.from(gadgetChanges.keys()).every((path) => path.startsWith(\".gadget/\"));\n assert(allGadgetFiles, \"expected all gadgetChanges to be .gadget/ files\");\n};\n\nexport const ConflictPreference = Object.freeze({\n CANCEL: \"Cancel (Ctrl+C)\",\n LOCAL: \"Keep my conflicting changes\",\n GADGET: \"Keep Gadget's conflicting changes\",\n});\n\nexport type ConflictPreference = (typeof ConflictPreference)[keyof typeof ConflictPreference];\n\nexport const ConflictPreferenceArg = (value: string, name: string): ConflictPreference => {\n if ([\"local\", \"gadget\"].includes(value)) {\n return ConflictPreference[value.toUpperCase() as keyof typeof ConflictPreference];\n }\n\n throw new ArgError(sprint`\n ${name} must be {bold local} or {bold gadget}\n\n {bold EXAMPLES:}\n ${name} local\n ${name} gadget\n `);\n};\n"],"names":["dayjs","findUp","fs","ms","assert","path","process","pMap","PQueue","pRetry","z","FileSyncEncoding","getApps","EditGraphQL","FILE_SYNC_COMPARISON_HASHES_QUERY","FILE_SYNC_FILES_QUERY","FILE_SYNC_HASHES_QUERY","PUBLISH_FILE_SYNC_EVENTS_MUTATION","REMOTE_FILE_SYNC_EVENTS_SUBSCRIPTION","ArgError","config","homePath","createLogger","select","sprint","sortBySimilar","noop","Changes","printChanges","getConflicts","printConflicts","withoutConflictingChanges","Directory","supportsPermissions","swallowEnoent","InvalidSyncFileError","TooManySyncAttemptsError","getChanges","isEqualHashes","FileSync","filesVersion","BigInt","_state","mtime","init","options","apps","user","length","email","dir","filepath","join","cwd","windows","startsWith","slice","wasEmptyOrNonExistent","isEmptyOrNonExistentDir","ensureDir","resolve","state","readJson","then","json","object","app","string","number","parse","catch","appSlug","message","choices","map","x","slug","find","similarAppSlugs","concat","directory","force","idle","_queue","onIdle","sendChangesToGadget","changes","_enqueue","_sendChangesToGadget","subscribeToGadgetChanges","beforeChanges","afterChanges","onError","editGraphQL","subscribe","query","variables","localFilesVersion","String","onData","remoteFileSyncEvents","changed","deleted","remoteFilesVersion","log","warn","debug","change","filterIgnoredFiles","file","ignored","ignores","filter","_save","_writeToLocalFilesystem","files","delete","size","format","tense","limit","sync","attempt","preference","filesVersionHashes","localHashes","gadgetHashes","gadgetFilesVersion","_getHashes","info","localChanges","from","to","existing","ignore","gadgetChanges","assertAllGadgetFiles","conflicts","Object","values","ConflictPreference","CANCEL","exit","LOCAL","GADGET","_getChangesFromGadget","expectedFilesVersion","Promise","all","hashes","fileSyncHashes","fileSyncComparisonHashes","latestFilesVersionHashes","created","updated","fileSyncFiles","paths","encoding","Base64","printLimit","normalizedPath","type","push","absolutePath","absolute","stats","stat","error","content","isFile","readFile","oldPath","mode","publishFileSyncEvents","input","expectedRemoteFilesVersion","currentPath","backupPath","relative","remove","move","retries","minTimeout","onFailedAttempt","pathExists","endsWith","outputFile","Buffer","chmod","loadIgnoreFile","Date","now","outputJSON","spaces","fn","add","name","fields","concurrency","_","opendir","bufferSize","allGadgetFiles","Array","keys","every","freeze","ConflictPreferenceArg","value","includes","toUpperCase"],"mappings":"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"}
|
|
@@ -75,6 +75,9 @@ import { workspaceRoot } from "./paths.js";
|
|
|
75
75
|
* Wraps `serialize-error` with some handy stuff, like special support
|
|
76
76
|
* for Got HTTP errors
|
|
77
77
|
*/ export const serializeError = (error)=>{
|
|
78
|
+
if (Array.isArray(error) && error.length === 1) {
|
|
79
|
+
error = error[0];
|
|
80
|
+
}
|
|
78
81
|
let serialized = baseSerializeError(Array.isArray(error) ? new AggregateError(error) : error);
|
|
79
82
|
if (typeof serialized == "string") {
|
|
80
83
|
serialized = {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/services/util/object.ts"],"sourcesContent":["import cleanStack from \"clean-stack\";\nimport { RequestError } from \"got\";\nimport { inspect } from \"node:util\";\nimport { serializeError as baseSerializeError, type ErrorObject } from \"serialize-error\";\nimport type { Simplify } from \"type-fest\";\nimport { workspaceRoot } from \"./paths.js\";\n\n/**\n * Returns a new object with the properties of the input object merged\n * with the properties of the defaults object. If a property exists in\n * both objects, the property of the input object will be used.\n *\n * @param input - The input object to merge with the defaults object.\n * @param defaults - The defaults object to merge with the input object.\n * @returns A new object with the properties of the input object merged\n * with the properties of the defaults object.\n */\nexport const defaults = <Input extends Record<string, unknown>, Defaults extends Partial<Input>>(\n input: Input | null | undefined,\n defaults: Defaults,\n): Simplify<Defaults & Input> => {\n const result = { ...input };\n for (const [key, defaultValue] of Object.entries(defaults)) {\n if (!result[key]) {\n result[key] = defaultValue;\n }\n }\n return result as Simplify<Defaults & Input>;\n};\n\n/**\n * Creates a new object with only the specified properties of the\n * original object.\n *\n * @param object - The original object to pick properties from.\n * @param keys - The keys of the properties to pick.\n * @returns A new object with only the specified properties of the\n * original object.\n */\nexport const pick = <T extends Record<string, unknown>, K extends keyof T>(object: T, keys: readonly K[]): Pick<T, K> => {\n const result = {} as Pick<T, K>;\n for (const key of keys) {\n result[key] = object[key];\n }\n return result;\n};\n\n/**\n * Returns a new object with the specified keys omitted.\n *\n * @param record The input object.\n * @param keys The keys to omit.\n * @returns A new object with the specified keys omitted.\n */\nexport const omit = <T extends Record<string, unknown>, K extends keyof T>(record: T, keys: readonly K[]): Omit<T, K> => {\n const result = { ...record };\n for (const key of keys) {\n // eslint-disable-next-line @typescript-eslint/no-dynamic-delete\n delete result[key];\n }\n return result;\n};\n\n/**\n * Maps the values of an object to a new set of values using the\n * provided function.\n *\n * @param obj The input object to map.\n * @param fn The function to apply to each value in the input object.\n * @returns A new object with the same keys as the input object, but\n * with the values mapped to new values using the provided function.\n */\nexport const mapValues = <Key extends string | number | symbol, Value, MappedValue>(\n obj: Record<Key, Value>,\n fn: (value: Value) => MappedValue,\n): Record<Key, MappedValue> => {\n const result = {} as Record<Key, MappedValue>;\n for (const [key, value] of Object.entries(obj)) {\n result[key as Key] = fn(value as Value);\n }\n return result;\n};\n\n/**\n * Universal Error object to json blob serializer.\n *\n * Wraps `serialize-error` with some handy stuff, like special support\n * for Got HTTP errors\n */\nexport const serializeError = (error: unknown): ErrorObject => {\n let serialized = baseSerializeError(Array.isArray(error) ? new AggregateError(error) : error);\n if (typeof serialized == \"string\") {\n serialized = { message: serialized };\n }\n\n if (serialized.stack) {\n serialized.stack = cleanStack(serialized.stack, { pretty: true, basePath: workspaceRoot }).replaceAll(/file:\\/\\/\\//g, \"\");\n }\n\n if (error instanceof RequestError) {\n serialized[\"timings\"] = undefined;\n serialized[\"options\"] = {\n method: error.options.method,\n url: error.options.url instanceof URL ? error.options.url.toJSON() : error.options.url,\n };\n serialized[\"responseBody\"] = inspect(error.response?.body);\n }\n\n return serialized;\n};\n"],"names":["cleanStack","RequestError","inspect","serializeError","baseSerializeError","workspaceRoot","defaults","input","result","key","defaultValue","Object","entries","pick","object","keys","omit","record","mapValues","obj","fn","value","error","
|
|
1
|
+
{"version":3,"sources":["../../../src/services/util/object.ts"],"sourcesContent":["import cleanStack from \"clean-stack\";\nimport { RequestError } from \"got\";\nimport { inspect } from \"node:util\";\nimport { serializeError as baseSerializeError, type ErrorObject } from \"serialize-error\";\nimport type { Simplify } from \"type-fest\";\nimport { workspaceRoot } from \"./paths.js\";\n\n/**\n * Returns a new object with the properties of the input object merged\n * with the properties of the defaults object. If a property exists in\n * both objects, the property of the input object will be used.\n *\n * @param input - The input object to merge with the defaults object.\n * @param defaults - The defaults object to merge with the input object.\n * @returns A new object with the properties of the input object merged\n * with the properties of the defaults object.\n */\nexport const defaults = <Input extends Record<string, unknown>, Defaults extends Partial<Input>>(\n input: Input | null | undefined,\n defaults: Defaults,\n): Simplify<Defaults & Input> => {\n const result = { ...input };\n for (const [key, defaultValue] of Object.entries(defaults)) {\n if (!result[key]) {\n result[key] = defaultValue;\n }\n }\n return result as Simplify<Defaults & Input>;\n};\n\n/**\n * Creates a new object with only the specified properties of the\n * original object.\n *\n * @param object - The original object to pick properties from.\n * @param keys - The keys of the properties to pick.\n * @returns A new object with only the specified properties of the\n * original object.\n */\nexport const pick = <T extends Record<string, unknown>, K extends keyof T>(object: T, keys: readonly K[]): Pick<T, K> => {\n const result = {} as Pick<T, K>;\n for (const key of keys) {\n result[key] = object[key];\n }\n return result;\n};\n\n/**\n * Returns a new object with the specified keys omitted.\n *\n * @param record The input object.\n * @param keys The keys to omit.\n * @returns A new object with the specified keys omitted.\n */\nexport const omit = <T extends Record<string, unknown>, K extends keyof T>(record: T, keys: readonly K[]): Omit<T, K> => {\n const result = { ...record };\n for (const key of keys) {\n // eslint-disable-next-line @typescript-eslint/no-dynamic-delete\n delete result[key];\n }\n return result;\n};\n\n/**\n * Maps the values of an object to a new set of values using the\n * provided function.\n *\n * @param obj The input object to map.\n * @param fn The function to apply to each value in the input object.\n * @returns A new object with the same keys as the input object, but\n * with the values mapped to new values using the provided function.\n */\nexport const mapValues = <Key extends string | number | symbol, Value, MappedValue>(\n obj: Record<Key, Value>,\n fn: (value: Value) => MappedValue,\n): Record<Key, MappedValue> => {\n const result = {} as Record<Key, MappedValue>;\n for (const [key, value] of Object.entries(obj)) {\n result[key as Key] = fn(value as Value);\n }\n return result;\n};\n\n/**\n * Universal Error object to json blob serializer.\n *\n * Wraps `serialize-error` with some handy stuff, like special support\n * for Got HTTP errors\n */\nexport const serializeError = (error: unknown): ErrorObject => {\n if (Array.isArray(error) && error.length === 1) {\n error = error[0];\n }\n\n let serialized = baseSerializeError(Array.isArray(error) ? new AggregateError(error) : error);\n if (typeof serialized == \"string\") {\n serialized = { message: serialized };\n }\n\n if (serialized.stack) {\n serialized.stack = cleanStack(serialized.stack, { pretty: true, basePath: workspaceRoot }).replaceAll(/file:\\/\\/\\//g, \"\");\n }\n\n if (error instanceof RequestError) {\n serialized[\"timings\"] = undefined;\n serialized[\"options\"] = {\n method: error.options.method,\n url: error.options.url instanceof URL ? error.options.url.toJSON() : error.options.url,\n };\n serialized[\"responseBody\"] = inspect(error.response?.body);\n }\n\n return serialized;\n};\n"],"names":["cleanStack","RequestError","inspect","serializeError","baseSerializeError","workspaceRoot","defaults","input","result","key","defaultValue","Object","entries","pick","object","keys","omit","record","mapValues","obj","fn","value","error","Array","isArray","length","serialized","AggregateError","message","stack","pretty","basePath","replaceAll","undefined","method","options","url","URL","toJSON","response","body"],"mappings":"AAAA,OAAOA,gBAAgB,cAAc;AACrC,SAASC,YAAY,QAAQ,MAAM;AACnC,SAASC,OAAO,QAAQ,YAAY;AACpC,SAASC,kBAAkBC,kBAAkB,QAA0B,kBAAkB;AAEzF,SAASC,aAAa,QAAQ,aAAa;AAE3C;;;;;;;;;CASC,GACD,OAAO,MAAMC,WAAW,CACtBC,OACAD;IAEA,MAAME,SAAS;QAAE,GAAGD,KAAK;IAAC;IAC1B,KAAK,MAAM,CAACE,KAAKC,aAAa,IAAIC,OAAOC,OAAO,CAACN,UAAW;QAC1D,IAAI,CAACE,MAAM,CAACC,IAAI,EAAE;YAChBD,MAAM,CAACC,IAAI,GAAGC;QAChB;IACF;IACA,OAAOF;AACT,EAAE;AAEF;;;;;;;;CAQC,GACD,OAAO,MAAMK,OAAO,CAAuDC,QAAWC;IACpF,MAAMP,SAAS,CAAC;IAChB,KAAK,MAAMC,OAAOM,KAAM;QACtBP,MAAM,CAACC,IAAI,GAAGK,MAAM,CAACL,IAAI;IAC3B;IACA,OAAOD;AACT,EAAE;AAEF;;;;;;CAMC,GACD,OAAO,MAAMQ,OAAO,CAAuDC,QAAWF;IACpF,MAAMP,SAAS;QAAE,GAAGS,MAAM;IAAC;IAC3B,KAAK,MAAMR,OAAOM,KAAM;QACtB,gEAAgE;QAChE,OAAOP,MAAM,CAACC,IAAI;IACpB;IACA,OAAOD;AACT,EAAE;AAEF;;;;;;;;CAQC,GACD,OAAO,MAAMU,YAAY,CACvBC,KACAC;IAEA,MAAMZ,SAAS,CAAC;IAChB,KAAK,MAAM,CAACC,KAAKY,MAAM,IAAIV,OAAOC,OAAO,CAACO,KAAM;QAC9CX,MAAM,CAACC,IAAW,GAAGW,GAAGC;IAC1B;IACA,OAAOb;AACT,EAAE;AAEF;;;;;CAKC,GACD,OAAO,MAAML,iBAAiB,CAACmB;IAC7B,IAAIC,MAAMC,OAAO,CAACF,UAAUA,MAAMG,MAAM,KAAK,GAAG;QAC9CH,QAAQA,KAAK,CAAC,EAAE;IAClB;IAEA,IAAII,aAAatB,mBAAmBmB,MAAMC,OAAO,CAACF,SAAS,IAAIK,eAAeL,SAASA;IACvF,IAAI,OAAOI,cAAc,UAAU;QACjCA,aAAa;YAAEE,SAASF;QAAW;IACrC;IAEA,IAAIA,WAAWG,KAAK,EAAE;QACpBH,WAAWG,KAAK,GAAG7B,WAAW0B,WAAWG,KAAK,EAAE;YAAEC,QAAQ;YAAMC,UAAU1B;QAAc,GAAG2B,UAAU,CAAC,gBAAgB;IACxH;IAEA,IAAIV,iBAAiBrB,cAAc;QACjCyB,UAAU,CAAC,UAAU,GAAGO;QACxBP,UAAU,CAAC,UAAU,GAAG;YACtBQ,QAAQZ,MAAMa,OAAO,CAACD,MAAM;YAC5BE,KAAKd,MAAMa,OAAO,CAACC,GAAG,YAAYC,MAAMf,MAAMa,OAAO,CAACC,GAAG,CAACE,MAAM,KAAKhB,MAAMa,OAAO,CAACC,GAAG;QACxF;QACAV,UAAU,CAAC,eAAe,GAAGxB,QAAQoB,MAAMiB,QAAQ,EAAEC;IACvD;IAEA,OAAOd;AACT,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.2",
|
|
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.2",
|
|
10
10
|
"license": "MIT",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"@sentry/node": "^7.88.0",
|