@gadgetinc/ggt 0.4.3 → 0.4.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +20 -14
- package/lib/commands/deploy.js +2 -4
- package/lib/commands/deploy.js.map +1 -1
- package/lib/commands/login.js +1 -1
- package/lib/commands/login.js.map +1 -1
- package/lib/commands/logout.js +1 -1
- package/lib/commands/logout.js.map +1 -1
- package/lib/commands/version.js +1 -1
- package/lib/commands/version.js.map +1 -1
- package/lib/commands/whoami.js +1 -1
- package/lib/commands/whoami.js.map +1 -1
- package/lib/services/filesync/directory.js +2 -1
- package/lib/services/filesync/directory.js.map +1 -1
- package/lib/services/filesync/filesync.js +35 -33
- package/lib/services/filesync/filesync.js.map +1 -1
- package/lib/services/filesync/hashes.js +2 -2
- package/lib/services/filesync/hashes.js.map +1 -1
- package/lib/services/output/log/format/pretty.js +4 -1
- package/lib/services/output/log/format/pretty.js.map +1 -1
- package/npm-shrinkwrap.json +946 -205
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
<a href="https://github.com/gadget-inc/ggt/actions/workflows/ci.yml?query=branch%3Amain">
|
|
9
9
|
<img alt="ci workflow status" src="https://img.shields.io/github/actions/workflow/status/gadget-inc/ggt/ci.yml?branch=main&label=ci">
|
|
10
10
|
</a>
|
|
11
|
-
<a href="https://www.npmjs.com/package
|
|
12
|
-
<img alt="npm version" src="https://img.shields.io/npm/v
|
|
11
|
+
<a href="https://www.npmjs.com/package/ggt">
|
|
12
|
+
<img alt="npm version" src="https://img.shields.io/npm/v/ggt">
|
|
13
13
|
</a>
|
|
14
14
|
<a href="https://discord.gg/nAfNKMdwKh">
|
|
15
15
|
<img alt="discord chat" src="https://img.shields.io/discord/836317518595096598">
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
Run the following to sync a `my-app.gadget.app` application to the `~/gadget/my-app` on your local machine:
|
|
45
45
|
|
|
46
46
|
```sh
|
|
47
|
-
npx
|
|
47
|
+
npx ggt@latest sync ~/gadget/my-app --app=my-app
|
|
48
48
|
```
|
|
49
49
|
|
|
50
50
|
With this running in the background, your local `~/gadget/my-app` folder will become two-way synced with your application's filesystem in Gadget's cloud. Changes you make locally will be immediately reflected by your application's API and actions if you re-run them.
|
|
@@ -79,7 +79,8 @@ For more information on a specific command, use 'ggt [COMMAND] --help'
|
|
|
79
79
|
|
|
80
80
|
### `ggt sync`
|
|
81
81
|
|
|
82
|
-
```
|
|
82
|
+
```sh-session
|
|
83
|
+
$ ggt sync --help
|
|
83
84
|
Sync your Gadget environment's source code with your local filesystem.
|
|
84
85
|
|
|
85
86
|
USAGE
|
|
@@ -152,7 +153,8 @@ EXAMPLE
|
|
|
152
153
|
|
|
153
154
|
### `ggt list`
|
|
154
155
|
|
|
155
|
-
```
|
|
156
|
+
```sh-session
|
|
157
|
+
$ ggt list --help
|
|
156
158
|
List the apps available to the currently logged in user.
|
|
157
159
|
|
|
158
160
|
USAGE
|
|
@@ -169,13 +171,14 @@ EXAMPLE
|
|
|
169
171
|
|
|
170
172
|
### `ggt login`
|
|
171
173
|
|
|
172
|
-
```
|
|
174
|
+
```sh-session
|
|
175
|
+
$ ggt login --help
|
|
173
176
|
Log in to your account.
|
|
174
177
|
|
|
175
178
|
USAGE
|
|
176
179
|
ggt login
|
|
177
180
|
|
|
178
|
-
|
|
181
|
+
EXAMPLE
|
|
179
182
|
$ ggt login
|
|
180
183
|
We've opened Gadget's login page using your default browser.
|
|
181
184
|
|
|
@@ -186,39 +189,42 @@ EXAMPLES
|
|
|
186
189
|
|
|
187
190
|
### `ggt logout`
|
|
188
191
|
|
|
189
|
-
```
|
|
192
|
+
```sh-session
|
|
193
|
+
$ ggt logout --help
|
|
190
194
|
Log out of your account.
|
|
191
195
|
|
|
192
196
|
USAGE
|
|
193
197
|
ggt logout
|
|
194
198
|
|
|
195
|
-
|
|
199
|
+
EXAMPLE
|
|
196
200
|
$ ggt logout
|
|
197
201
|
Goodbye
|
|
198
202
|
```
|
|
199
203
|
|
|
200
204
|
### `ggt whoami`
|
|
201
205
|
|
|
202
|
-
```
|
|
206
|
+
```sh-session
|
|
207
|
+
$ ggt whoami --help
|
|
203
208
|
Show the name and email address of the currently logged in user
|
|
204
209
|
|
|
205
210
|
USAGE
|
|
206
211
|
ggt whoami
|
|
207
212
|
|
|
208
|
-
|
|
213
|
+
EXAMPLE
|
|
209
214
|
$ ggt whoami
|
|
210
215
|
You are logged in as Jane Doe (jane@example.com)
|
|
211
216
|
```
|
|
212
217
|
|
|
213
218
|
### `ggt version`
|
|
214
219
|
|
|
215
|
-
```
|
|
220
|
+
```sh-session
|
|
221
|
+
$ ggt version --help
|
|
216
222
|
Print the version of ggt
|
|
217
223
|
|
|
218
224
|
USAGE
|
|
219
225
|
ggt version
|
|
220
226
|
|
|
221
|
-
|
|
227
|
+
EXAMPLE
|
|
222
228
|
$ ggt version
|
|
223
|
-
0.4.
|
|
229
|
+
0.4.5
|
|
224
230
|
```
|
package/lib/commands/deploy.js
CHANGED
|
@@ -3,7 +3,6 @@ import ora from "ora";
|
|
|
3
3
|
import { AppArg } from "../services/app/arg.js";
|
|
4
4
|
import { REMOTE_SERVER_CONTRACT_STATUS_SUBSCRIPTION } from "../services/app/edit-graphql.js";
|
|
5
5
|
import { FileSync } from "../services/filesync/filesync.js";
|
|
6
|
-
import { isEqualHashes } from "../services/filesync/hashes.js";
|
|
7
6
|
import { select } from "../services/output/prompt.js";
|
|
8
7
|
import { sprint } from "../services/output/sprint.js";
|
|
9
8
|
import { getUserOrLogin } from "../services/user/user.js";
|
|
@@ -120,9 +119,8 @@ var AppDeploymentSteps;
|
|
|
120
119
|
if (firstRun) {
|
|
121
120
|
log.printlns`App: ${filesync.app.slug}`;
|
|
122
121
|
}
|
|
123
|
-
const {
|
|
124
|
-
|
|
125
|
-
if (!upToDate) {
|
|
122
|
+
const { inSync } = await filesync.hashes();
|
|
123
|
+
if (!inSync) {
|
|
126
124
|
log.printlns`
|
|
127
125
|
Local files have diverged from remote. Run a sync once to converge your files or keep {italic ggt sync} running in the background.
|
|
128
126
|
`;
|
|
@@ -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 { 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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"}
|
|
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 { 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 { inSync } = await filesync.hashes();\n if (!inSync) {\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","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","inSync","hashes","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/login.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/commands/login.ts"],"sourcesContent":["import getPort from \"get-port\";\nimport assert from \"node:assert\";\nimport http, { type Server } from \"node:http\";\nimport open from \"open\";\nimport type { Command, Usage } from \"../services/command/command.js\";\nimport { config } from \"../services/config/config.js\";\nimport { createLogger } from \"../services/output/log/logger.js\";\nimport { sprint } from \"../services/output/sprint.js\";\nimport { writeSession } from \"../services/user/session.js\";\nimport { getUser } from \"../services/user/user.js\";\n\nconst log = createLogger({ name: \"login\" });\n\nexport const usage: Usage = () => sprint`\n Log in to your account.\n\n {bold USAGE}\n ggt login\n\n {bold
|
|
1
|
+
{"version":3,"sources":["../../src/commands/login.ts"],"sourcesContent":["import getPort from \"get-port\";\nimport assert from \"node:assert\";\nimport http, { type Server } from \"node:http\";\nimport open from \"open\";\nimport type { Command, Usage } from \"../services/command/command.js\";\nimport { config } from \"../services/config/config.js\";\nimport { createLogger } from \"../services/output/log/logger.js\";\nimport { sprint } from \"../services/output/sprint.js\";\nimport { writeSession } from \"../services/user/session.js\";\nimport { getUser } from \"../services/user/user.js\";\n\nconst log = createLogger({ name: \"login\" });\n\nexport const usage: Usage = () => sprint`\n Log in to your account.\n\n {bold USAGE}\n ggt login\n\n {bold EXAMPLE}\n $ ggt login\n We've opened Gadget's login page using your default browser.\n\n Please log in and then return to this terminal.\n\n Hello, Jane Doe (jane@example.com)\n`;\n\nexport const login = async (): Promise<void> => {\n let server: Server | undefined;\n\n try {\n const port = await getPort();\n const receiveSession = new Promise<void>((resolve, reject) => {\n // eslint-disable-next-line @typescript-eslint/no-misused-promises\n server = http.createServer(async (req, res) => {\n const landingPage = new URL(`https://${config.domains.services}/auth/cli`);\n\n try {\n assert(req.url, \"missing url\");\n const session = new URL(req.url, `http://localhost:${port}`).searchParams.get(\"session\");\n assert(session, \"missing session\");\n\n writeSession(session);\n\n const user = await getUser();\n assert(user, \"missing user after successful login\");\n\n if (user.name) {\n log.printlns`Hello, ${user.name} {gray (${user.email})}`;\n } else {\n log.printlns`Hello, ${user.email}`;\n }\n\n landingPage.searchParams.set(\"success\", \"true\");\n resolve();\n } catch (error) {\n writeSession(undefined);\n landingPage.searchParams.set(\"success\", \"false\");\n reject(error);\n } finally {\n res.writeHead(303, { Location: landingPage.toString() });\n res.end();\n }\n });\n\n log.info(\"starting login server\", { port });\n server.listen(port);\n });\n\n // open the login page in the user's default browser have it\n // redirect to the cli callback route. The cli callback route will\n // send the session to the server we just started.\n const url = new URL(`https://${config.domains.services}/auth/login`);\n url.searchParams.set(\"returnTo\", `https://${config.domains.services}/auth/cli/callback?port=${port}`);\n\n try {\n await open(url.toString());\n log.printlns`\n We've opened Gadget's login page using your default browser.\n\n Please log in and then return to this terminal.\n `;\n } catch (error) {\n log.error(\"failed to open browser\", { error });\n log.printlns`\n Please open the following URL in your browser and log in:\n\n {gray ${url.toString()}}\n\n Once logged in, return to this terminal.\n `;\n }\n\n await receiveSession;\n } finally {\n server?.close();\n }\n};\n\nexport const command: Command = login;\n"],"names":["getPort","assert","http","open","config","createLogger","sprint","writeSession","getUser","log","name","usage","login","server","port","receiveSession","Promise","resolve","reject","createServer","req","res","landingPage","URL","domains","services","url","session","searchParams","get","user","printlns","email","set","error","undefined","writeHead","Location","toString","end","info","listen","close","command"],"mappings":"AAAA,OAAOA,aAAa,WAAW;AAC/B,OAAOC,YAAY,cAAc;AACjC,OAAOC,UAA2B,YAAY;AAC9C,OAAOC,UAAU,OAAO;AAExB,SAASC,MAAM,QAAQ,+BAA+B;AACtD,SAASC,YAAY,QAAQ,mCAAmC;AAChE,SAASC,MAAM,QAAQ,+BAA+B;AACtD,SAASC,YAAY,QAAQ,8BAA8B;AAC3D,SAASC,OAAO,QAAQ,2BAA2B;AAEnD,MAAMC,MAAMJ,aAAa;IAAEK,MAAM;AAAQ;AAEzC,OAAO,MAAMC,QAAe,IAAML,MAAM,CAAC;;;;;;;;;;;;;AAazC,CAAC,CAAC;AAEF,OAAO,MAAMM,QAAQ;IACnB,IAAIC;IAEJ,IAAI;QACF,MAAMC,OAAO,MAAMd;QACnB,MAAMe,iBAAiB,IAAIC,QAAc,CAACC,SAASC;YACjD,kEAAkE;YAClEL,SAASX,KAAKiB,YAAY,CAAC,OAAOC,KAAKC;gBACrC,MAAMC,cAAc,IAAIC,IAAI,CAAC,QAAQ,EAAEnB,OAAOoB,OAAO,CAACC,QAAQ,CAAC,SAAS,CAAC;gBAEzE,IAAI;oBACFxB,OAAOmB,IAAIM,GAAG,EAAE;oBAChB,MAAMC,UAAU,IAAIJ,IAAIH,IAAIM,GAAG,EAAE,CAAC,iBAAiB,EAAEZ,KAAK,CAAC,EAAEc,YAAY,CAACC,GAAG,CAAC;oBAC9E5B,OAAO0B,SAAS;oBAEhBpB,aAAaoB;oBAEb,MAAMG,OAAO,MAAMtB;oBACnBP,OAAO6B,MAAM;oBAEb,IAAIA,KAAKpB,IAAI,EAAE;wBACbD,IAAIsB,QAAQ,CAAC,OAAO,EAAED,KAAKpB,IAAI,CAAC,QAAQ,EAAEoB,KAAKE,KAAK,CAAC,EAAE,CAAC;oBAC1D,OAAO;wBACLvB,IAAIsB,QAAQ,CAAC,OAAO,EAAED,KAAKE,KAAK,CAAC,CAAC;oBACpC;oBAEAV,YAAYM,YAAY,CAACK,GAAG,CAAC,WAAW;oBACxChB;gBACF,EAAE,OAAOiB,OAAO;oBACd3B,aAAa4B;oBACbb,YAAYM,YAAY,CAACK,GAAG,CAAC,WAAW;oBACxCf,OAAOgB;gBACT,SAAU;oBACRb,IAAIe,SAAS,CAAC,KAAK;wBAAEC,UAAUf,YAAYgB,QAAQ;oBAAG;oBACtDjB,IAAIkB,GAAG;gBACT;YACF;YAEA9B,IAAI+B,IAAI,CAAC,yBAAyB;gBAAE1B;YAAK;YACzCD,OAAO4B,MAAM,CAAC3B;QAChB;QAEA,4DAA4D;QAC5D,kEAAkE;QAClE,kDAAkD;QAClD,MAAMY,MAAM,IAAIH,IAAI,CAAC,QAAQ,EAAEnB,OAAOoB,OAAO,CAACC,QAAQ,CAAC,WAAW,CAAC;QACnEC,IAAIE,YAAY,CAACK,GAAG,CAAC,YAAY,CAAC,QAAQ,EAAE7B,OAAOoB,OAAO,CAACC,QAAQ,CAAC,wBAAwB,EAAEX,KAAK,CAAC;QAEpG,IAAI;YACF,MAAMX,KAAKuB,IAAIY,QAAQ;YACvB7B,IAAIsB,QAAQ,CAAC;;;;IAIf,CAAC;QACD,EAAE,OAAOG,OAAO;YACdzB,IAAIyB,KAAK,CAAC,0BAA0B;gBAAEA;YAAM;YAC5CzB,IAAIsB,QAAQ,CAAC;;;gBAGH,EAAEL,IAAIY,QAAQ,GAAG;;;MAG3B,CAAC;QACH;QAEA,MAAMvB;IACR,SAAU;QACRF,QAAQ6B;IACV;AACF,EAAE;AAEF,OAAO,MAAMC,UAAmB/B,MAAM"}
|
package/lib/commands/logout.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/commands/logout.ts"],"sourcesContent":["import type { Command, Usage } from \"../services/command/command.js\";\nimport { createLogger } from \"../services/output/log/logger.js\";\nimport { sprint } from \"../services/output/sprint.js\";\nimport { readSession, writeSession } from \"../services/user/session.js\";\n\nconst log = createLogger({ name: \"logout\" });\n\nexport const usage: Usage = () => sprint`\n Log out of your account.\n\n {bold USAGE}\n ggt logout\n\n {bold
|
|
1
|
+
{"version":3,"sources":["../../src/commands/logout.ts"],"sourcesContent":["import type { Command, Usage } from \"../services/command/command.js\";\nimport { createLogger } from \"../services/output/log/logger.js\";\nimport { sprint } from \"../services/output/sprint.js\";\nimport { readSession, writeSession } from \"../services/user/session.js\";\n\nconst log = createLogger({ name: \"logout\" });\n\nexport const usage: Usage = () => sprint`\n Log out of your account.\n\n {bold USAGE}\n ggt logout\n\n {bold EXAMPLE}\n $ ggt logout\n Goodbye\n`;\n\nexport const command: Command = () => {\n const token = readSession();\n if (token) {\n writeSession(undefined);\n log.println(\"Goodbye\");\n } else {\n log.println(\"You are not logged in\");\n }\n};\n"],"names":["createLogger","sprint","readSession","writeSession","log","name","usage","command","token","undefined","println"],"mappings":"AACA,SAASA,YAAY,QAAQ,mCAAmC;AAChE,SAASC,MAAM,QAAQ,+BAA+B;AACtD,SAASC,WAAW,EAAEC,YAAY,QAAQ,8BAA8B;AAExE,MAAMC,MAAMJ,aAAa;IAAEK,MAAM;AAAS;AAE1C,OAAO,MAAMC,QAAe,IAAML,MAAM,CAAC;;;;;;;;;AASzC,CAAC,CAAC;AAEF,OAAO,MAAMM,UAAmB;IAC9B,MAAMC,QAAQN;IACd,IAAIM,OAAO;QACTL,aAAaM;QACbL,IAAIM,OAAO,CAAC;IACd,OAAO;QACLN,IAAIM,OAAO,CAAC;IACd;AACF,EAAE"}
|
package/lib/commands/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/commands/version.ts"],"sourcesContent":["import type { Command, Usage } from \"../services/command/command.js\";\nimport { config } from \"../services/config/config.js\";\nimport { createLogger } from \"../services/output/log/logger.js\";\nimport { sprint } from \"../services/output/sprint.js\";\n\nconst log = createLogger({ name: \"version\" });\n\nexport const usage: Usage = () => sprint`\n Print the version of ggt\n\n {bold USAGE}\n ggt version\n\n {bold
|
|
1
|
+
{"version":3,"sources":["../../src/commands/version.ts"],"sourcesContent":["import type { Command, Usage } from \"../services/command/command.js\";\nimport { config } from \"../services/config/config.js\";\nimport { createLogger } from \"../services/output/log/logger.js\";\nimport { sprint } from \"../services/output/sprint.js\";\n\nconst log = createLogger({ name: \"version\" });\n\nexport const usage: Usage = () => sprint`\n Print the version of ggt\n\n {bold USAGE}\n ggt version\n\n {bold EXAMPLE}\n $ ggt version\n ${config.version}\n`;\n\nexport const command: Command = () => {\n log.println(config.version);\n};\n"],"names":["config","createLogger","sprint","log","name","usage","version","command","println"],"mappings":"AACA,SAASA,MAAM,QAAQ,+BAA+B;AACtD,SAASC,YAAY,QAAQ,mCAAmC;AAChE,SAASC,MAAM,QAAQ,+BAA+B;AAEtD,MAAMC,MAAMF,aAAa;IAAEG,MAAM;AAAU;AAE3C,OAAO,MAAMC,QAAe,IAAMH,MAAM,CAAC;;;;;;;;MAQnC,EAAEF,OAAOM,OAAO,CAAC;AACvB,CAAC,CAAC;AAEF,OAAO,MAAMC,UAAmB;IAC9BJ,IAAIK,OAAO,CAACR,OAAOM,OAAO;AAC5B,EAAE"}
|
package/lib/commands/whoami.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/commands/whoami.ts"],"sourcesContent":["import type { Command, Usage } from \"../services/command/command.js\";\nimport { createLogger } from \"../services/output/log/logger.js\";\nimport { sprint } from \"../services/output/sprint.js\";\nimport { getUser } from \"../services/user/user.js\";\n\nconst log = createLogger({ name: \"whoami\" });\n\nexport const usage: Usage = () => sprint`\n Show the name and email address of the currently logged in user\n\n {bold USAGE}\n ggt whoami\n\n {bold
|
|
1
|
+
{"version":3,"sources":["../../src/commands/whoami.ts"],"sourcesContent":["import type { Command, Usage } from \"../services/command/command.js\";\nimport { createLogger } from \"../services/output/log/logger.js\";\nimport { sprint } from \"../services/output/sprint.js\";\nimport { getUser } from \"../services/user/user.js\";\n\nconst log = createLogger({ name: \"whoami\" });\n\nexport const usage: Usage = () => sprint`\n Show the name and email address of the currently logged in user\n\n {bold USAGE}\n ggt whoami\n\n {bold EXAMPLE}\n $ ggt whoami\n You are logged in as Jane Doe (jane@example.com)\n`;\n\nexport const command: Command = async () => {\n const user = await getUser();\n if (!user) {\n log.println`You are not logged in`;\n return;\n }\n\n if (user.name) {\n log.println`You are logged in as ${user.name} {gray (${user.email})}`;\n } else {\n log.println`You are logged in as ${user.email}`;\n }\n};\n"],"names":["createLogger","sprint","getUser","log","name","usage","command","user","println","email"],"mappings":"AACA,SAASA,YAAY,QAAQ,mCAAmC;AAChE,SAASC,MAAM,QAAQ,+BAA+B;AACtD,SAASC,OAAO,QAAQ,2BAA2B;AAEnD,MAAMC,MAAMH,aAAa;IAAEI,MAAM;AAAS;AAE1C,OAAO,MAAMC,QAAe,IAAMJ,MAAM,CAAC;;;;;;;;;AASzC,CAAC,CAAC;AAEF,OAAO,MAAMK,UAAmB;IAC9B,MAAMC,OAAO,MAAML;IACnB,IAAI,CAACK,MAAM;QACTJ,IAAIK,OAAO,CAAC,qBAAqB,CAAC;QAClC;IACF;IAEA,IAAID,KAAKH,IAAI,EAAE;QACbD,IAAIK,OAAO,CAAC,qBAAqB,EAAED,KAAKH,IAAI,CAAC,QAAQ,EAAEG,KAAKE,KAAK,CAAC,EAAE,CAAC;IACvE,OAAO;QACLN,IAAIK,OAAO,CAAC,qBAAqB,EAAED,KAAKE,KAAK,CAAC,CAAC;IACjD;AACF,EAAE"}
|
|
@@ -25,7 +25,8 @@ import normalizePath from "normalize-path";
|
|
|
25
25
|
* NOTE: This is the _only_ thing that is allowed to be different between gadget and ggt.
|
|
26
26
|
*/ export const HASHING_IGNORE_PATHS = [
|
|
27
27
|
".gadget/sync.json",
|
|
28
|
-
".gadget/backup"
|
|
28
|
+
".gadget/backup",
|
|
29
|
+
"yarn-error.log"
|
|
29
30
|
];
|
|
30
31
|
/**
|
|
31
32
|
* Represents a directory that is being synced.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/services/filesync/directory.ts"],"sourcesContent":["/**\n * DO NOT MODIFY\n *\n * Everything in this file also exists in ggt to ensure that this logic\n * is the same between the two projects.\n */\nimport fs from \"fs-extra\";\nimport type { Ignore } from \"ignore\";\nimport ignore from \"ignore\";\nimport assert from \"node:assert\";\nimport { createHash } from \"node:crypto\";\nimport path from \"node:path\";\nimport { Transform } from \"node:stream\";\nimport { pipeline } from \"node:stream/promises\";\nimport normalizePath from \"normalize-path\";\n\n/**\n * Paths that are always ignored, regardless of the contents of the `.ignore` file.\n */\nexport const ALWAYS_IGNORE_PATHS = [\".DS_Store\", \"node_modules\", \".git\"] as const;\n\n/**\n * Paths that are ignored when hashing the directory.\n *\n * NOTE: This is the _only_ thing that is allowed to be different between gadget and ggt.\n */\nexport const HASHING_IGNORE_PATHS = [\".gadget/sync.json\", \".gadget/backup\"] as const;\n\n/**\n * Represents a directory that is being synced.\n */\nexport class Directory {\n /**\n * A gitignore-style file parser used to determine which files to\n * ignore while syncing.\n *\n * @see https://www.npmjs.com/package/ignore\n */\n private _ignorer!: Ignore;\n\n /**\n * Whether the directory is currently being hashed.\n */\n private _isHashing = false;\n\n private constructor(\n /**\n * An absolute path to the directory that is being synced.\n */\n readonly path: string,\n ) {}\n\n /**\n * Initializes a directory to be synced.\n *\n * If the directory does not exist, it is created.\n *\n * @param dir - The directory to initialize.\n * @returns A Promise that resolves to a Directory instance.\n */\n static async init(dir: string): Promise<Directory> {\n const directory = new Directory(dir);\n await directory.loadIgnoreFile();\n return directory;\n }\n\n /**\n * Returns the relative path from this directory to the specified path.\n *\n * @param to - The path to which the relative path is calculated.\n * @returns The relative path from this directory to the specified path.\n */\n relative(to: string): string {\n if (!path.isAbsolute(to)) {\n // the filepath is already relative\n return to;\n }\n\n return path.relative(this.path, to);\n }\n\n /**\n * Returns the absolute path by resolving the given path segments\n * relative to the directory path.\n *\n * @param pathSegments The path segments to resolve.\n * @returns The absolute path.\n */\n absolute(...pathSegments: string[]): string {\n const result = path.resolve(this.path, ...pathSegments);\n assert(result.startsWith(this.path), `expected ${result} to be within ${this.path}`);\n return result;\n }\n\n /**\n * Similar to {@linkcode relative} in that it converts an absolute\n * path into a relative one from {@linkcode path}. However, it also\n * changes any slashes to be posix/unix-like forward slashes,\n * condenses repeated slashes into a single slash, and adds a trailing\n * slash if the path is a directory.\n *\n * This is used when sending files to Gadget to ensure that the paths\n * are consistent across platforms.\n *\n * @see https://www.npmjs.com/package/normalize-path\n */\n normalize(filepath: string, isDirectory: boolean): string {\n if (path.isAbsolute(filepath)) {\n filepath = this.relative(filepath);\n }\n\n // true = trim trailing slashes\n filepath = normalizePath(filepath, true);\n\n if (isDirectory) {\n filepath += \"/\";\n }\n\n return filepath;\n }\n\n /**\n * Loads the `.ignore` file in the directory. If the file does not\n * exist, it is silently ignored.\n */\n async loadIgnoreFile(): Promise<void> {\n this._ignorer = ignore.default();\n this._ignorer.add(ALWAYS_IGNORE_PATHS);\n\n try {\n const content = await fs.readFile(this.absolute(\".ignore\"), \"utf8\");\n this._ignorer.add(content);\n } catch (error) {\n swallowEnoent(error);\n }\n }\n\n /**\n * Determines if a file should be ignored based on its filepath.\n *\n * @param filepath - The filepath of the file to check.\n * @returns True if the file should be ignored, false otherwise.\n */\n ignores(filepath: string): boolean {\n filepath = this.relative(filepath);\n if (filepath === \"\") {\n // don't ignore the root dir\n return false;\n }\n\n if (filepath.startsWith(\"..\")) {\n // anything above the root dir is ignored\n return true;\n }\n\n // false = don't trim trailing slashes\n filepath = normalizePath(filepath, false);\n if (this._isHashing && HASHING_IGNORE_PATHS.some((ignored) => filepath.startsWith(ignored))) {\n // special case for hashing\n return true;\n }\n\n return this._ignorer.ignores(filepath);\n }\n\n /**\n * Recursively walks through the directory and yields all non-ignored\n * files and directories within it.\n *\n * @yields - The normalized path of each file and directory.\n */\n async *walk({ dir = this.path } = {}): AsyncGenerator<string> {\n // don't yield the root directory\n if (dir !== this.path) {\n yield this.normalize(dir, true);\n }\n\n for await (const entry of await fs.opendir(dir)) {\n const filepath = path.join(dir, entry.name);\n if (this.ignores(filepath)) {\n continue;\n }\n\n if (entry.isDirectory()) {\n yield* this.walk({ dir: filepath });\n } else if (entry.isFile()) {\n yield this.normalize(filepath, false);\n }\n }\n }\n\n /**\n * Calculates the hash of each file and directory and returns an\n * object containing the hashes keyed by the normalized file path.\n *\n * @returns A Promise that resolves to an object containing the hashes\n * of each file.\n */\n async hashes(): Promise<Hashes> {\n try {\n this._isHashing = true;\n const files = {} as Hashes;\n\n for await (const normalizedPath of this.walk()) {\n const absolutePath = this.absolute(normalizedPath);\n files[normalizedPath] = await hash(absolutePath);\n }\n\n return files;\n } finally {\n this._isHashing = false;\n }\n }\n}\n\n/**\n * Key/value pairs where the key is the normalized path and the value is\n * the result of {@linkcode hash} for that path.\n */\nexport type Hashes = Record<string, Hash>;\n\nexport type Hash = {\n /**\n * The SHA-1 hash of the file or directory.\n *\n * If the path points to a directory, the hash is calculated based on\n * the directory's basename. If the path points to a file, the hash is\n * calculated based on the file's basename and contents.\n */\n sha1: string;\n\n /**\n * The Unix-style file permissions of the file or directory, or\n * undefined if the platform that generated this hash doesn't support\n * them.\n *\n * @example 0o644\n * @see supportsPermissions\n */\n permissions?: number;\n};\n\n/**\n * Whether the current platform supports Unix-style file permissions.\n *\n * Windows doesn't support Unix-style file permissions and all file\n * permissions retrieved via `node:fs` on Windows are translated to 666\n * or 444.\n */\nexport const supportsPermissions = process.platform === \"linux\" || process.platform === \"darwin\";\n\n/**\n * Calculates the {@linkcode Hash} of the file or directory at the\n * specified absolute path.\n *\n * @param absolutePath The absolute path to the file or directory.\n * @returns A Promise that resolves to the {@linkcode Hash} of the file\n * or directory.\n */\nconst hash = async (absolutePath: string): Promise<Hash> => {\n const sha1 = createHash(\"sha1\");\n sha1.update(path.basename(absolutePath));\n\n const stats = await fs.stat(absolutePath);\n\n let permissions;\n if (supportsPermissions) {\n // strip everything but the permissions\n permissions = stats.mode & 0o777;\n }\n\n if (stats.isDirectory()) {\n return { sha1: sha1.digest(\"hex\"), permissions };\n }\n\n // windows uses CRLF line endings whereas unix uses LF line endings so\n // we always strip out CR bytes (0x0d) when hashing files. this does\n // make us blind to files that only differ by CR bytes, but that's a\n // tradeoff we're willing to make.\n const removeCR = new Transform({\n transform(chunk: Buffer, _encoding, callback) {\n if (!chunk.includes(0x0d)) {\n callback(undefined, chunk);\n return;\n }\n\n const filteredChunk = Buffer.alloc(chunk.length);\n let i = 0;\n for (const byte of chunk) {\n if (byte !== 0x0d) {\n filteredChunk[i++] = byte;\n }\n }\n\n callback(undefined, filteredChunk.slice(0, i));\n },\n });\n\n await pipeline(fs.createReadStream(absolutePath), removeCR, sha1);\n\n return { sha1: sha1.digest(\"hex\"), permissions };\n};\n\n/**\n * Swallows ENOENT errors and throws any other errors.\n *\n * @param error - The error to handle.\n * @throws The original error if it is not an ENOENT error.\n */\nexport const swallowEnoent = (error: unknown): void => {\n if (error && typeof error === \"object\" && \"code\" in error && error.code === \"ENOENT\") {\n return;\n }\n throw error;\n};\n"],"names":["fs","ignore","assert","createHash","path","Transform","pipeline","normalizePath","ALWAYS_IGNORE_PATHS","HASHING_IGNORE_PATHS","Directory","init","dir","directory","loadIgnoreFile","relative","to","isAbsolute","absolute","pathSegments","result","resolve","startsWith","normalize","filepath","isDirectory","_ignorer","default","add","content","readFile","error","swallowEnoent","ignores","_isHashing","some","ignored","walk","entry","opendir","join","name","isFile","hashes","files","normalizedPath","absolutePath","hash","supportsPermissions","process","platform","sha1","update","basename","stats","stat","permissions","mode","digest","removeCR","transform","chunk","_encoding","callback","includes","undefined","filteredChunk","Buffer","alloc","length","i","byte","slice","createReadStream","code"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../src/services/filesync/directory.ts"],"sourcesContent":["/**\n * DO NOT MODIFY\n *\n * Everything in this file also exists in ggt to ensure that this logic\n * is the same between the two projects.\n */\nimport fs from \"fs-extra\";\nimport type { Ignore } from \"ignore\";\nimport ignore from \"ignore\";\nimport assert from \"node:assert\";\nimport { createHash } from \"node:crypto\";\nimport path from \"node:path\";\nimport { Transform } from \"node:stream\";\nimport { pipeline } from \"node:stream/promises\";\nimport normalizePath from \"normalize-path\";\n\n/**\n * Paths that are always ignored, regardless of the contents of the `.ignore` file.\n */\nexport const ALWAYS_IGNORE_PATHS = [\".DS_Store\", \"node_modules\", \".git\"] as const;\n\n/**\n * Paths that are ignored when hashing the directory.\n *\n * NOTE: This is the _only_ thing that is allowed to be different between gadget and ggt.\n */\nexport const HASHING_IGNORE_PATHS = [\".gadget/sync.json\", \".gadget/backup\", \"yarn-error.log\"] as const;\n\n/**\n * Represents a directory that is being synced.\n */\nexport class Directory {\n /**\n * A gitignore-style file parser used to determine which files to\n * ignore while syncing.\n *\n * @see https://www.npmjs.com/package/ignore\n */\n private _ignorer!: Ignore;\n\n /**\n * Whether the directory is currently being hashed.\n */\n private _isHashing = false;\n\n private constructor(\n /**\n * An absolute path to the directory that is being synced.\n */\n readonly path: string,\n ) {}\n\n /**\n * Initializes a directory to be synced.\n *\n * If the directory does not exist, it is created.\n *\n * @param dir - The directory to initialize.\n * @returns A Promise that resolves to a Directory instance.\n */\n static async init(dir: string): Promise<Directory> {\n const directory = new Directory(dir);\n await directory.loadIgnoreFile();\n return directory;\n }\n\n /**\n * Returns the relative path from this directory to the specified path.\n *\n * @param to - The path to which the relative path is calculated.\n * @returns The relative path from this directory to the specified path.\n */\n relative(to: string): string {\n if (!path.isAbsolute(to)) {\n // the filepath is already relative\n return to;\n }\n\n return path.relative(this.path, to);\n }\n\n /**\n * Returns the absolute path by resolving the given path segments\n * relative to the directory path.\n *\n * @param pathSegments The path segments to resolve.\n * @returns The absolute path.\n */\n absolute(...pathSegments: string[]): string {\n const result = path.resolve(this.path, ...pathSegments);\n assert(result.startsWith(this.path), `expected ${result} to be within ${this.path}`);\n return result;\n }\n\n /**\n * Similar to {@linkcode relative} in that it converts an absolute\n * path into a relative one from {@linkcode path}. However, it also\n * changes any slashes to be posix/unix-like forward slashes,\n * condenses repeated slashes into a single slash, and adds a trailing\n * slash if the path is a directory.\n *\n * This is used when sending files to Gadget to ensure that the paths\n * are consistent across platforms.\n *\n * @see https://www.npmjs.com/package/normalize-path\n */\n normalize(filepath: string, isDirectory: boolean): string {\n if (path.isAbsolute(filepath)) {\n filepath = this.relative(filepath);\n }\n\n // true = trim trailing slashes\n filepath = normalizePath(filepath, true);\n\n if (isDirectory) {\n filepath += \"/\";\n }\n\n return filepath;\n }\n\n /**\n * Loads the `.ignore` file in the directory. If the file does not\n * exist, it is silently ignored.\n */\n async loadIgnoreFile(): Promise<void> {\n this._ignorer = ignore.default();\n this._ignorer.add(ALWAYS_IGNORE_PATHS);\n\n try {\n const content = await fs.readFile(this.absolute(\".ignore\"), \"utf8\");\n this._ignorer.add(content);\n } catch (error) {\n swallowEnoent(error);\n }\n }\n\n /**\n * Determines if a file should be ignored based on its filepath.\n *\n * @param filepath - The filepath of the file to check.\n * @returns True if the file should be ignored, false otherwise.\n */\n ignores(filepath: string): boolean {\n filepath = this.relative(filepath);\n if (filepath === \"\") {\n // don't ignore the root dir\n return false;\n }\n\n if (filepath.startsWith(\"..\")) {\n // anything above the root dir is ignored\n return true;\n }\n\n // false = don't trim trailing slashes\n filepath = normalizePath(filepath, false);\n if (this._isHashing && HASHING_IGNORE_PATHS.some((ignored) => filepath.startsWith(ignored))) {\n // special case for hashing\n return true;\n }\n\n return this._ignorer.ignores(filepath);\n }\n\n /**\n * Recursively walks through the directory and yields all non-ignored\n * files and directories within it.\n *\n * @yields - The normalized path of each file and directory.\n */\n async *walk({ dir = this.path } = {}): AsyncGenerator<string> {\n // don't yield the root directory\n if (dir !== this.path) {\n yield this.normalize(dir, true);\n }\n\n for await (const entry of await fs.opendir(dir)) {\n const filepath = path.join(dir, entry.name);\n if (this.ignores(filepath)) {\n continue;\n }\n\n if (entry.isDirectory()) {\n yield* this.walk({ dir: filepath });\n } else if (entry.isFile()) {\n yield this.normalize(filepath, false);\n }\n }\n }\n\n /**\n * Calculates the hash of each file and directory and returns an\n * object containing the hashes keyed by the normalized file path.\n *\n * @returns A Promise that resolves to an object containing the hashes\n * of each file.\n */\n async hashes(): Promise<Hashes> {\n try {\n this._isHashing = true;\n const files = {} as Hashes;\n\n for await (const normalizedPath of this.walk()) {\n const absolutePath = this.absolute(normalizedPath);\n files[normalizedPath] = await hash(absolutePath);\n }\n\n return files;\n } finally {\n this._isHashing = false;\n }\n }\n}\n\n/**\n * Key/value pairs where the key is the normalized path and the value is\n * the result of {@linkcode hash} for that path.\n */\nexport type Hashes = Record<string, Hash>;\n\nexport type Hash = {\n /**\n * The SHA-1 hash of the file or directory.\n *\n * If the path points to a directory, the hash is calculated based on\n * the directory's basename. If the path points to a file, the hash is\n * calculated based on the file's basename and contents.\n */\n sha1: string;\n\n /**\n * The Unix-style file permissions of the file or directory, or\n * undefined if the platform that generated this hash doesn't support\n * them.\n *\n * @example 0o644\n * @see supportsPermissions\n */\n permissions?: number;\n};\n\n/**\n * Whether the current platform supports Unix-style file permissions.\n *\n * Windows doesn't support Unix-style file permissions and all file\n * permissions retrieved via `node:fs` on Windows are translated to 666\n * or 444.\n */\nexport const supportsPermissions = process.platform === \"linux\" || process.platform === \"darwin\";\n\n/**\n * Calculates the {@linkcode Hash} of the file or directory at the\n * specified absolute path.\n *\n * @param absolutePath The absolute path to the file or directory.\n * @returns A Promise that resolves to the {@linkcode Hash} of the file\n * or directory.\n */\nconst hash = async (absolutePath: string): Promise<Hash> => {\n const sha1 = createHash(\"sha1\");\n sha1.update(path.basename(absolutePath));\n\n const stats = await fs.stat(absolutePath);\n\n let permissions;\n if (supportsPermissions) {\n // strip everything but the permissions\n permissions = stats.mode & 0o777;\n }\n\n if (stats.isDirectory()) {\n return { sha1: sha1.digest(\"hex\"), permissions };\n }\n\n // windows uses CRLF line endings whereas unix uses LF line endings so\n // we always strip out CR bytes (0x0d) when hashing files. this does\n // make us blind to files that only differ by CR bytes, but that's a\n // tradeoff we're willing to make.\n const removeCR = new Transform({\n transform(chunk: Buffer, _encoding, callback) {\n if (!chunk.includes(0x0d)) {\n callback(undefined, chunk);\n return;\n }\n\n const filteredChunk = Buffer.alloc(chunk.length);\n let i = 0;\n for (const byte of chunk) {\n if (byte !== 0x0d) {\n filteredChunk[i++] = byte;\n }\n }\n\n callback(undefined, filteredChunk.slice(0, i));\n },\n });\n\n await pipeline(fs.createReadStream(absolutePath), removeCR, sha1);\n\n return { sha1: sha1.digest(\"hex\"), permissions };\n};\n\n/**\n * Swallows ENOENT errors and throws any other errors.\n *\n * @param error - The error to handle.\n * @throws The original error if it is not an ENOENT error.\n */\nexport const swallowEnoent = (error: unknown): void => {\n if (error && typeof error === \"object\" && \"code\" in error && error.code === \"ENOENT\") {\n return;\n }\n throw error;\n};\n"],"names":["fs","ignore","assert","createHash","path","Transform","pipeline","normalizePath","ALWAYS_IGNORE_PATHS","HASHING_IGNORE_PATHS","Directory","init","dir","directory","loadIgnoreFile","relative","to","isAbsolute","absolute","pathSegments","result","resolve","startsWith","normalize","filepath","isDirectory","_ignorer","default","add","content","readFile","error","swallowEnoent","ignores","_isHashing","some","ignored","walk","entry","opendir","join","name","isFile","hashes","files","normalizedPath","absolutePath","hash","supportsPermissions","process","platform","sha1","update","basename","stats","stat","permissions","mode","digest","removeCR","transform","chunk","_encoding","callback","includes","undefined","filteredChunk","Buffer","alloc","length","i","byte","slice","createReadStream","code"],"mappings":"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"}
|
|
@@ -25,6 +25,9 @@ import { getConflicts, printConflicts, withoutConflictingChanges } from "./confl
|
|
|
25
25
|
import { Directory, supportsPermissions, swallowEnoent } from "./directory.js";
|
|
26
26
|
import { InvalidSyncFileError, TooManySyncAttemptsError } from "./error.js";
|
|
27
27
|
import { getChanges, isEqualHashes } from "./hashes.js";
|
|
28
|
+
const log = createLogger({
|
|
29
|
+
name: "filesync"
|
|
30
|
+
});
|
|
28
31
|
export class FileSync {
|
|
29
32
|
/**
|
|
30
33
|
* The last filesVersion that was written to the filesystem.
|
|
@@ -157,7 +160,7 @@ export class FileSync {
|
|
|
157
160
|
/**
|
|
158
161
|
* Waits for all pending and ongoing filesync operations to complete.
|
|
159
162
|
*/ async idle() {
|
|
160
|
-
await this.
|
|
163
|
+
await this._syncOperations.onIdle();
|
|
161
164
|
}
|
|
162
165
|
/**
|
|
163
166
|
* Sends file changes to the Gadget.
|
|
@@ -165,7 +168,7 @@ export class FileSync {
|
|
|
165
168
|
* @param changes - The changes to send.
|
|
166
169
|
* @returns A promise that resolves when the changes have been sent.
|
|
167
170
|
*/ async sendChangesToGadget({ changes }) {
|
|
168
|
-
await this.
|
|
171
|
+
await this._syncOperations.add(()=>this._sendChangesToGadget({
|
|
169
172
|
changes
|
|
170
173
|
}));
|
|
171
174
|
}
|
|
@@ -187,7 +190,7 @@ export class FileSync {
|
|
|
187
190
|
}),
|
|
188
191
|
onError,
|
|
189
192
|
onData: ({ remoteFileSyncEvents: { changed, deleted, remoteFilesVersion } })=>{
|
|
190
|
-
this.
|
|
193
|
+
this._syncOperations.add(async ()=>{
|
|
191
194
|
if (BigInt(remoteFilesVersion) < this.filesVersion) {
|
|
192
195
|
this.log.warn("skipping received changes because files version is outdated", {
|
|
193
196
|
filesVersion: remoteFilesVersion
|
|
@@ -244,22 +247,33 @@ export class FileSync {
|
|
|
244
247
|
* - Conflicts are resolved by prompting the user to either keep their
|
|
245
248
|
* local changes or keep Gadget's changes.
|
|
246
249
|
* - This function will not return until the filesystem is in sync.
|
|
247
|
-
*/ async sync({
|
|
248
|
-
|
|
249
|
-
|
|
250
|
+
*/ async sync({ preference, maxAttempts = 10 } = {}) {
|
|
251
|
+
this._syncOperations.pause();
|
|
252
|
+
try {
|
|
253
|
+
for(let attempt = 0; attempt < maxAttempts; attempt++){
|
|
254
|
+
const { inSync, ...hashes } = await this.hashes();
|
|
255
|
+
if (inSync) {
|
|
256
|
+
this.log.info("filesystem is in sync");
|
|
257
|
+
await this._save(hashes.gadgetFilesVersion);
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
await this._sync({
|
|
261
|
+
preference,
|
|
262
|
+
...hashes
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
} finally{
|
|
266
|
+
this._syncOperations.start();
|
|
250
267
|
}
|
|
251
|
-
|
|
268
|
+
throw new TooManySyncAttemptsError(maxAttempts);
|
|
269
|
+
}
|
|
270
|
+
async _sync({ preference, filesVersionHashes, localHashes, gadgetHashes, gadgetFilesVersion }) {
|
|
252
271
|
this.log.debug("syncing", {
|
|
253
272
|
filesVersionHashes,
|
|
254
273
|
localHashes,
|
|
255
274
|
gadgetHashes,
|
|
256
275
|
gadgetFilesVersion
|
|
257
276
|
});
|
|
258
|
-
if (isEqualHashes(localHashes, gadgetHashes)) {
|
|
259
|
-
this.log.info("filesystem is in sync");
|
|
260
|
-
await this._save(gadgetFilesVersion);
|
|
261
|
-
return;
|
|
262
|
-
}
|
|
263
277
|
let localChanges = getChanges({
|
|
264
278
|
from: filesVersionHashes,
|
|
265
279
|
to: localHashes,
|
|
@@ -283,6 +297,7 @@ export class FileSync {
|
|
|
283
297
|
gadgetChanges
|
|
284
298
|
});
|
|
285
299
|
}
|
|
300
|
+
assert(localChanges.size > 0 || gadgetChanges.size > 0, "there must be changes if hashes don't match");
|
|
286
301
|
const conflicts = getConflicts({
|
|
287
302
|
localChanges,
|
|
288
303
|
gadgetChanges
|
|
@@ -325,7 +340,6 @@ export class FileSync {
|
|
|
325
340
|
}
|
|
326
341
|
}
|
|
327
342
|
}
|
|
328
|
-
assert(localChanges.size > 0 || gadgetChanges.size > 0, "there must be changes if hashes don't match");
|
|
329
343
|
if (gadgetChanges.size > 0) {
|
|
330
344
|
await this._getChangesFromGadget({
|
|
331
345
|
changes: gadgetChanges,
|
|
@@ -338,13 +352,8 @@ export class FileSync {
|
|
|
338
352
|
expectedFilesVersion: gadgetFilesVersion
|
|
339
353
|
});
|
|
340
354
|
}
|
|
341
|
-
// recursively call this function until we're in sync
|
|
342
|
-
return this.sync({
|
|
343
|
-
attempt: ++attempt,
|
|
344
|
-
preference
|
|
345
|
-
});
|
|
346
355
|
}
|
|
347
|
-
async
|
|
356
|
+
async hashes() {
|
|
348
357
|
const [localHashes, { filesVersionHashes, gadgetHashes, gadgetFilesVersion }] = await Promise.all([
|
|
349
358
|
// get the hashes of our local files
|
|
350
359
|
this.directory.hashes(),
|
|
@@ -387,7 +396,8 @@ export class FileSync {
|
|
|
387
396
|
filesVersionHashes,
|
|
388
397
|
localHashes,
|
|
389
398
|
gadgetHashes,
|
|
390
|
-
gadgetFilesVersion
|
|
399
|
+
gadgetFilesVersion,
|
|
400
|
+
inSync: isEqualHashes(localHashes, gadgetHashes)
|
|
391
401
|
};
|
|
392
402
|
}
|
|
393
403
|
async _getChangesFromGadget({ filesVersion, changes }) {
|
|
@@ -584,11 +594,6 @@ export class FileSync {
|
|
|
584
594
|
spaces: 2
|
|
585
595
|
});
|
|
586
596
|
}
|
|
587
|
-
/**
|
|
588
|
-
* Enqueues a function that handles filesync events onto the {@linkcode _queue}.
|
|
589
|
-
*/ _enqueue(fn) {
|
|
590
|
-
return this._queue.add(fn);
|
|
591
|
-
}
|
|
592
597
|
constructor(/**
|
|
593
598
|
* The directory that is being synced to.
|
|
594
599
|
*/ directory, /**
|
|
@@ -609,18 +614,15 @@ export class FileSync {
|
|
|
609
614
|
/**
|
|
610
615
|
* A FIFO async callback queue that ensures we process filesync events
|
|
611
616
|
* in the order we receive them.
|
|
612
|
-
*/ _define_property(this, "
|
|
617
|
+
*/ _define_property(this, "_syncOperations", void 0);
|
|
613
618
|
this.directory = directory;
|
|
614
619
|
this.wasEmptyOrNonExistent = wasEmptyOrNonExistent;
|
|
615
620
|
this.app = app;
|
|
616
621
|
this._state = _state;
|
|
617
|
-
this.log =
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
})
|
|
622
|
-
});
|
|
623
|
-
this._queue = new PQueue({
|
|
622
|
+
this.log = log.extend("filesync", ()=>({
|
|
623
|
+
state: this._state
|
|
624
|
+
}));
|
|
625
|
+
this._syncOperations = new PQueue({
|
|
624
626
|
concurrency: 1
|
|
625
627
|
});
|
|
626
628
|
this.editGraphQL = new EditGraphQL(this.app);
|