@gadgetinc/ggt 0.1.15 → 0.1.17

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 CHANGED
@@ -129,7 +129,7 @@ EXAMPLES
129
129
  Goodbye!
130
130
  ```
131
131
 
132
- _See code: [src/commands/sync.ts](https://github.com/gadget-inc/ggt/blob/v0.1.15/src/commands/sync.ts)_
132
+ _See code: [src/commands/sync.ts](https://github.com/gadget-inc/ggt/blob/v0.1.17/src/commands/sync.ts)_
133
133
 
134
134
  ### `ggt help [COMMAND]`
135
135
 
@@ -143,7 +143,7 @@ ARGUMENTS
143
143
  COMMAND The command to show help for.
144
144
  ```
145
145
 
146
- _See code: [src/commands/help.ts](https://github.com/gadget-inc/ggt/blob/v0.1.15/src/commands/help.ts)_
146
+ _See code: [src/commands/help.ts](https://github.com/gadget-inc/ggt/blob/v0.1.17/src/commands/help.ts)_
147
147
 
148
148
  ### `ggt list`
149
149
 
@@ -170,7 +170,7 @@ EXAMPLES
170
170
  $ ggt list --sort=slug
171
171
  ```
172
172
 
173
- _See code: [src/commands/list.ts](https://github.com/gadget-inc/ggt/blob/v0.1.15/src/commands/list.ts)_
173
+ _See code: [src/commands/list.ts](https://github.com/gadget-inc/ggt/blob/v0.1.17/src/commands/list.ts)_
174
174
 
175
175
  ### `ggt login`
176
176
 
@@ -189,7 +189,7 @@ EXAMPLES
189
189
  Hello, Jane Doe (jane@example.com)
190
190
  ```
191
191
 
192
- _See code: [src/commands/login.ts](https://github.com/gadget-inc/ggt/blob/v0.1.15/src/commands/login.ts)_
192
+ _See code: [src/commands/login.ts](https://github.com/gadget-inc/ggt/blob/v0.1.17/src/commands/login.ts)_
193
193
 
194
194
  ### `ggt logout`
195
195
 
@@ -204,7 +204,7 @@ EXAMPLES
204
204
  Goodbye
205
205
  ```
206
206
 
207
- _See code: [src/commands/logout.ts](https://github.com/gadget-inc/ggt/blob/v0.1.15/src/commands/logout.ts)_
207
+ _See code: [src/commands/logout.ts](https://github.com/gadget-inc/ggt/blob/v0.1.17/src/commands/logout.ts)_
208
208
 
209
209
  ### `ggt whoami`
210
210
 
@@ -219,7 +219,7 @@ EXAMPLES
219
219
  You are logged in as Jane Doe (jane@example.com)
220
220
  ```
221
221
 
222
- _See code: [src/commands/whoami.ts](https://github.com/gadget-inc/ggt/blob/v0.1.15/src/commands/whoami.ts)_
222
+ _See code: [src/commands/whoami.ts](https://github.com/gadget-inc/ggt/blob/v0.1.17/src/commands/whoami.ts)_
223
223
 
224
224
  <!-- commandsstop -->
225
225
 
@@ -1,10 +1,10 @@
1
- import { Command } from "@oclif/core";
1
+ import { BaseCommand } from "../utils/base-command";
2
2
  /**
3
3
  * Copied from @oclif/plugin-help. Uses our own {@link Help} template class instead of the one from @oclif/core.
4
4
  *
5
5
  * @see https://github.com/oclif/plugin-help/blob/67b580570257b45e92d3a04d50bf2a432c59afe3/src/commands/help.ts
6
6
  */
7
- export default class HelpCommand extends Command {
7
+ export default class HelpCommand extends BaseCommand<typeof HelpCommand> {
8
8
  static strict: boolean;
9
9
  static summary: string;
10
10
  static args: {
@@ -3,12 +3,13 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const tslib_1 = require("tslib");
4
4
  const core_1 = require("@oclif/core");
5
5
  const help_1 = tslib_1.__importDefault(require("../utils/help"));
6
+ const base_command_1 = require("../utils/base-command");
6
7
  /**
7
8
  * Copied from @oclif/plugin-help. Uses our own {@link Help} template class instead of the one from @oclif/core.
8
9
  *
9
10
  * @see https://github.com/oclif/plugin-help/blob/67b580570257b45e92d3a04d50bf2a432c59afe3/src/commands/help.ts
10
11
  */
11
- class HelpCommand extends core_1.Command {
12
+ class HelpCommand extends base_command_1.BaseCommand {
12
13
  async run() {
13
14
  const { argv } = await this.parse();
14
15
  const help = new help_1.default(this.config, { all: true });
@@ -1 +1 @@
1
- {"version":3,"file":"help.js","sourceRoot":"/","sources":["commands/help.ts"],"names":[],"mappings":";;;AAAA,sCAA4C;AAC5C,iEAAiC;AAEjC;;;;GAIG;AACH,MAAqB,WAAY,SAAQ,cAAO;IAY9C,KAAK,CAAC,GAAG;QACP,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,IAAI,CAAC,KAAK,EAAE,CAAC;QACpC,MAAM,IAAI,GAAG,IAAI,cAAI,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC;QAClD,MAAM,IAAI,CAAC,QAAQ,CAAC,IAAgB,CAAC,CAAC;IACxC,CAAC;;AAfe;;;;WAAS,KAAK;GAAC;AAEf;;;;WAAU,uBAAuB;GAAC;AAElC;;;;WAAO;QACrB,OAAO,EAAE,WAAI,CAAC,MAAM,CAAC;YACnB,QAAQ,EAAE,KAAK;YACf,WAAW,EAAE,+BAA+B;SAC7C,CAAC;KACH;GAAC;kBAViB,WAAW","sourcesContent":["import { Args, Command } from \"@oclif/core\";\nimport Help from \"../utils/help\";\n\n/**\n * Copied from @oclif/plugin-help. Uses our own {@link Help} template class instead of the one from @oclif/core.\n *\n * @see https://github.com/oclif/plugin-help/blob/67b580570257b45e92d3a04d50bf2a432c59afe3/src/commands/help.ts\n */\nexport default class HelpCommand extends Command {\n static override strict = false;\n\n static override summary = \"Display help for ggt.\";\n\n static override args = {\n command: Args.string({\n required: false,\n description: \"The command to show help for.\",\n }),\n };\n\n async run(): Promise<void> {\n const { argv } = await this.parse();\n const help = new Help(this.config, { all: true });\n await help.showHelp(argv as string[]);\n }\n}\n"]}
1
+ {"version":3,"file":"help.js","sourceRoot":"/","sources":["commands/help.ts"],"names":[],"mappings":";;;AAAA,sCAAmC;AACnC,iEAAiC;AACjC,wDAAoD;AAEpD;;;;GAIG;AACH,MAAqB,WAAY,SAAQ,0BAA+B;IAYtE,KAAK,CAAC,GAAG;QACP,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,IAAI,CAAC,KAAK,EAAE,CAAC;QACpC,MAAM,IAAI,GAAG,IAAI,cAAI,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC;QAClD,MAAM,IAAI,CAAC,QAAQ,CAAC,IAAgB,CAAC,CAAC;IACxC,CAAC;;AAfe;;;;WAAS,KAAK;GAAC;AAEf;;;;WAAU,uBAAuB;GAAC;AAElC;;;;WAAO;QACrB,OAAO,EAAE,WAAI,CAAC,MAAM,CAAC;YACnB,QAAQ,EAAE,KAAK;YACf,WAAW,EAAE,+BAA+B;SAC7C,CAAC;KACH;GAAC;kBAViB,WAAW","sourcesContent":["import { Args } from \"@oclif/core\";\nimport Help from \"../utils/help\";\nimport { BaseCommand } from \"../utils/base-command\";\n\n/**\n * Copied from @oclif/plugin-help. Uses our own {@link Help} template class instead of the one from @oclif/core.\n *\n * @see https://github.com/oclif/plugin-help/blob/67b580570257b45e92d3a04d50bf2a432c59afe3/src/commands/help.ts\n */\nexport default class HelpCommand extends BaseCommand<typeof HelpCommand> {\n static override strict = false;\n\n static override summary = \"Display help for ggt.\";\n\n static override args = {\n command: Args.string({\n required: false,\n description: \"The command to show help for.\",\n }),\n };\n\n async run(): Promise<void> {\n const { argv } = await this.parse();\n const help = new Help(this.config, { all: true });\n await help.showHelp(argv as string[]);\n }\n}\n"]}
@@ -13,5 +13,6 @@ export default class List extends BaseCommand<typeof List> {
13
13
  'no-truncate': import("@oclif/core/lib/interfaces").Flag<boolean>;
14
14
  'no-header': import("@oclif/core/lib/interfaces").Flag<boolean>;
15
15
  };
16
+ requireUser: boolean;
16
17
  run(): Promise<void>;
17
18
  }
@@ -7,15 +7,24 @@ const ts_dedent_1 = tslib_1.__importDefault(require("ts-dedent"));
7
7
  const base_command_1 = require("../utils/base-command");
8
8
  const context_1 = require("../utils/context");
9
9
  class List extends base_command_1.BaseCommand {
10
+ constructor() {
11
+ super(...arguments);
12
+ Object.defineProperty(this, "requireUser", {
13
+ enumerable: true,
14
+ configurable: true,
15
+ writable: true,
16
+ value: true
17
+ });
18
+ }
10
19
  async run() {
11
20
  const { flags } = await this.parse(List);
12
- const user = await context_1.context.getUser();
13
- if (!user) {
14
- this.error("You are not logged in -- no apps available", { exit: 1 });
15
- }
16
21
  const apps = await context_1.context.getAvailableApps();
17
- if (apps.length === 0 && !flags.csv) {
18
- this.log("No apps found");
22
+ if (!apps.length) {
23
+ this.log((0, ts_dedent_1.default) `
24
+ It doesn't look like you have any applications.
25
+
26
+ Visit https://gadget.new to create one!
27
+ `);
19
28
  return;
20
29
  }
21
30
  core_1.ux.table(apps, {
@@ -1 +1 @@
1
- {"version":3,"file":"list.js","sourceRoot":"/","sources":["commands/list.ts"],"names":[],"mappings":";;;AAAA,0DAA0B;AAC1B,sCAAiC;AACjC,kEAA+B;AAC/B,wDAAoD;AAEpD,8CAA2C;AAE3C,MAAqB,IAAK,SAAQ,0BAAwB;IAiBxD,KAAK,CAAC,GAAG;QACP,MAAM,EAAE,KAAK,EAAE,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QACzC,MAAM,IAAI,GAAG,MAAM,iBAAO,CAAC,OAAO,EAAE,CAAC;QACrC,IAAI,CAAC,IAAI,EAAE;YACT,IAAI,CAAC,KAAK,CAAC,4CAA4C,EAAE,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC,CAAC;SACvE;QACD,MAAM,IAAI,GAAG,MAAM,iBAAO,CAAC,gBAAgB,EAAE,CAAC;QAE9C,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,EAAE;YACnC,IAAI,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC;YAC1B,OAAO;SACR;QAED,SAAE,CAAC,KAAK,CACN,IAAkD,EAClD;YACE,EAAE,EAAE;gBACF,MAAM,EAAE,IAAI;gBACZ,QAAQ,EAAE,IAAI;aACf;YACD,IAAI,EAAE;gBACJ,MAAM,EAAE,MAAM;aACf;YACD,aAAa,EAAE;gBACb,MAAM,EAAE,QAAQ;aACjB;SACF,EACD;YACE,SAAS,EAAE,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC;YAC9B,GAAG,KAAK,EAAE,eAAe;SAC1B,CACF,CAAC;IACJ,CAAC;;AAhDe;;;;WAAU,0DAA0D;GAAC;AAErE;;;;WAAQ,MAAM;GAAC;AAEf;;;;WAAW;QACzB,IAAA,mBAAM,EAAC,IAAA,eAAK,EAAA;;;;KAIX,CAAC;KACH;GAAC;AAEc;;;;WAAQ;QACtB,GAAG,SAAE,CAAC,KAAK,CAAC,KAAK,EAAE;KACpB;GAAC;kBAfiB,IAAI","sourcesContent":["import chalk from \"chalk\";\nimport { ux } from \"@oclif/core\";\nimport dedent from \"ts-dedent\";\nimport { BaseCommand } from \"../utils/base-command\";\nimport type { App } from \"../utils/context\";\nimport { context } from \"../utils/context\";\n\nexport default class List extends BaseCommand<typeof List> {\n static override summary = \"List the apps available to the currently logged in user.\";\n\n static override usage = \"list\";\n\n static override examples = [\n dedent(chalk`\n {gray $ ggt list}\n {gray $ ggt list --extended}\n {gray $ ggt list --sort=slug}\n `),\n ];\n\n static override flags = {\n ...ux.table.flags(),\n };\n\n async run(): Promise<void> {\n const { flags } = await this.parse(List);\n const user = await context.getUser();\n if (!user) {\n this.error(\"You are not logged in -- no apps available\", { exit: 1 });\n }\n const apps = await context.getAvailableApps();\n\n if (apps.length === 0 && !flags.csv) {\n this.log(\"No apps found\");\n return;\n }\n\n ux.table<App & Record<string, never>>(\n apps as unknown as (App & Record<string, never>)[],\n {\n id: {\n header: \"ID\",\n extended: true,\n },\n slug: {\n header: \"Slug\",\n },\n primaryDomain: {\n header: \"Domain\",\n },\n },\n {\n printLine: this.log.bind(this),\n ...flags, // parsed flags\n }\n );\n }\n}\n"]}
1
+ {"version":3,"file":"list.js","sourceRoot":"/","sources":["commands/list.ts"],"names":[],"mappings":";;;AAAA,0DAA0B;AAC1B,sCAAiC;AACjC,kEAA+B;AAC/B,wDAAoD;AAEpD,8CAA2C;AAE3C,MAAqB,IAAK,SAAQ,0BAAwB;IAA1D;;QAiBW;;;;mBAAc,IAAI;WAAC;IAmC9B,CAAC;IAjCC,KAAK,CAAC,GAAG;QACP,MAAM,EAAE,KAAK,EAAE,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAEzC,MAAM,IAAI,GAAG,MAAM,iBAAO,CAAC,gBAAgB,EAAE,CAAC;QAC9C,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE;YAChB,IAAI,CAAC,GAAG,CAAC,IAAA,mBAAM,EAAA;;;;OAId,CAAC,CAAC;YACH,OAAO;SACR;QAED,SAAE,CAAC,KAAK,CACN,IAAkD,EAClD;YACE,EAAE,EAAE;gBACF,MAAM,EAAE,IAAI;gBACZ,QAAQ,EAAE,IAAI;aACf;YACD,IAAI,EAAE;gBACJ,MAAM,EAAE,MAAM;aACf;YACD,aAAa,EAAE;gBACb,MAAM,EAAE,QAAQ;aACjB;SACF,EACD;YACE,SAAS,EAAE,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC;YAC9B,GAAG,KAAK,EAAE,eAAe;SAC1B,CACF,CAAC;IACJ,CAAC;;AAlDe;;;;WAAU,0DAA0D;EAA7D,CAA8D;AAErE;;;;WAAQ,MAAM;EAAT,CAAU;AAEf;;;;WAAW;QACzB,IAAA,mBAAM,EAAC,IAAA,eAAK,EAAA;;;;KAIX,CAAC;KACH;EANuB,CAMtB;AAEc;;;;WAAQ;QACtB,GAAG,SAAE,CAAC,KAAK,CAAC,KAAK,EAAE;KACpB;EAFoB,CAEnB;kBAfiB,IAAI","sourcesContent":["import chalk from \"chalk\";\nimport { ux } from \"@oclif/core\";\nimport dedent from \"ts-dedent\";\nimport { BaseCommand } from \"../utils/base-command\";\nimport type { App } from \"../utils/context\";\nimport { context } from \"../utils/context\";\n\nexport default class List extends BaseCommand<typeof List> {\n static override summary = \"List the apps available to the currently logged in user.\";\n\n static override usage = \"list\";\n\n static override examples = [\n dedent(chalk`\n {gray $ ggt list}\n {gray $ ggt list --extended}\n {gray $ ggt list --sort=slug}\n `),\n ];\n\n static override flags = {\n ...ux.table.flags(),\n };\n\n override requireUser = true;\n\n async run(): Promise<void> {\n const { flags } = await this.parse(List);\n\n const apps = await context.getAvailableApps();\n if (!apps.length) {\n this.log(dedent`\n It doesn't look like you have any applications.\n\n Visit https://gadget.new to create one!\n `);\n return;\n }\n\n ux.table<App & Record<string, never>>(\n apps as unknown as (App & Record<string, never>)[],\n {\n id: {\n header: \"ID\",\n extended: true,\n },\n slug: {\n header: \"Slug\",\n },\n primaryDomain: {\n header: \"Domain\",\n },\n },\n {\n printLine: this.log.bind(this),\n ...flags, // parsed flags\n }\n );\n }\n}\n"]}
@@ -4,7 +4,7 @@ import PQueue from "p-queue";
4
4
  import { BaseCommand } from "../utils/base-command";
5
5
  import type { Query } from "../utils/client";
6
6
  import { Client } from "../utils/client";
7
- import { Ignorer } from "../utils/fs-utils";
7
+ import { FSIgnorer } from "../utils/fs-utils";
8
8
  import type { PublishFileSyncEventsMutation, PublishFileSyncEventsMutationVariables, RemoteFilesVersionQuery, RemoteFilesVersionQueryVariables, RemoteFileSyncEventsSubscription, RemoteFileSyncEventsSubscriptionVariables } from "../__generated__/graphql";
9
9
  export default class Sync extends BaseCommand<typeof Sync> {
10
10
  static priority: number;
@@ -23,29 +23,104 @@ export default class Sync extends BaseCommand<typeof Sync> {
23
23
  };
24
24
  static examples: string[];
25
25
  requireUser: boolean;
26
+ /**
27
+ * The current status of the sync process.
28
+ */
26
29
  status: SyncStatus;
30
+ /**
31
+ * The absolute path to the directory to sync files to.
32
+ */
27
33
  dir: string;
28
- recentWrites: Set<unknown>;
34
+ /**
35
+ * A list of filepaths that have changed because of a remote file-sync event. This is used to avoid sending files that
36
+ * we recently received from a remote file-sync event.
37
+ */
38
+ recentRemoteChanges: Set<unknown>;
39
+ /**
40
+ * A FIFO async callback queue that ensures we process file-sync events in the order they occurred.
41
+ */
29
42
  queue: PQueue<import("p-queue/dist/priority-queue").default, import("p-queue").DefaultAddOptions>;
43
+ /**
44
+ * A GraphQL client connected to the app's /edit/api/graphql-ws endpoint
45
+ */
30
46
  client: Client;
31
- ignorer: Ignorer;
47
+ /**
48
+ * Loads the .ignore file and provides methods for checking if a file should be ignored.
49
+ */
50
+ ignorer: FSIgnorer;
51
+ /**
52
+ * Watches the local filesystem for changes.
53
+ */
32
54
  watcher: FSWatcher;
55
+ /**
56
+ * Holds information about the state of the local filesystem. It's persisted to `.gadget/sync.json`.
57
+ */
33
58
  metadata: {
59
+ /**
60
+ * The app this filesystem is synced to.
61
+ */
34
62
  app: string;
63
+ /**
64
+ * The last filesVersion that was successfully written to the filesystem. This is used to determine if the remote
65
+ * filesystem is ahead of the local one.
66
+ */
35
67
  filesVersion: string;
68
+ /**
69
+ * The largest mtime that was seen on the local filesystem before `ggt sync` stopped. This is used to determine if
70
+ * the local filesystem has changed since the last sync.
71
+ *
72
+ * Note: This does not include the mtime of files that are ignored.
73
+ */
36
74
  mtime: number;
37
75
  };
76
+ /**
77
+ * A debounced function that enqueue's local file changes to be sent to Gadget.
78
+ */
38
79
  publish: DebouncedFunc<() => void>;
80
+ /**
81
+ * Gracefully stops the sync.
82
+ */
39
83
  stop: (error?: unknown) => Promise<void>;
40
- finished: Promise<void>;
41
- markFinished: () => void;
84
+ /**
85
+ * Turns an absolute filepath into a relative one from {@linkcode dir}.
86
+ */
42
87
  relative(to: string): string;
88
+ /**
89
+ * Combines path segments into an absolute filepath that starts at {@linkcode dir}.
90
+ */
43
91
  absolute(...pathSegments: string[]): string;
92
+ /**
93
+ * Similar to {@linkcode relative} in that it turns a filepath into a relative one from {@linkcode dir}. However, it
94
+ * also changes any slashes to be posix/unix-like forward slashes, condenses repeat slashes into a single slash.
95
+ *
96
+ * This is used when sending file-sync events to Gadget to ensure that the paths are consistent across platforms.
97
+ *
98
+ * @see https://www.npmjs.com/package/normalize-path
99
+ */
44
100
  normalize(filepath: string, isDirectory?: boolean): string;
101
+ /**
102
+ * Pretty-prints changed and deleted filepaths to the console.
103
+ *
104
+ * @param prefix The prefix to print before each line.
105
+ * @param changed The filepaths that have changed.
106
+ * @param deleted The filepaths that have been deleted.
107
+ * @param options.limit The maximum number of lines to print. Defaults to 10. If debug is enabled, this is ignored.
108
+ */
45
109
  logPaths(prefix: string, changed: string[], deleted: string[], { limit }?: {
46
110
  limit?: number | undefined;
47
111
  }): void;
112
+ /**
113
+ * Initializes the sync process.
114
+ * - Ensures the directory exists.
115
+ * - Ensures the directory is empty or contains a `.gadget/sync.json` file.
116
+ * - Ensures an app is selected and that it matches the app the directory was previously synced to.
117
+ * - Ensures yarn v1 is installed.
118
+ * - Prompts the user how to resolve conflicts if the local filesystem has changed since the last sync.
119
+ */
48
120
  init(): Promise<void>;
121
+ /**
122
+ * Runs the sync process until it is stopped or an error occurs.
123
+ */
49
124
  run(): Promise<void>;
50
125
  }
51
126
  export declare enum SyncStatus {
@@ -12,7 +12,6 @@ const fs_extra_1 = tslib_1.__importDefault(require("fs-extra"));
12
12
  const inquirer_1 = require("inquirer");
13
13
  const lodash_1 = require("lodash");
14
14
  const lodash_2 = require("lodash");
15
- const lodash_3 = require("lodash");
16
15
  const normalize_path_1 = tslib_1.__importDefault(require("normalize-path"));
17
16
  const p_map_1 = tslib_1.__importDefault(require("p-map"));
18
17
  const p_queue_1 = tslib_1.__importDefault(require("p-queue"));
@@ -27,6 +26,7 @@ const errors_1 = require("../utils/errors");
27
26
  const flags_1 = require("../utils/flags");
28
27
  const fs_utils_1 = require("../utils/fs-utils");
29
28
  const graphql_1 = require("../__generated__/graphql");
29
+ const promise_1 = require("../utils/promise");
30
30
  class Sync extends base_command_1.BaseCommand {
31
31
  constructor() {
32
32
  super(...arguments);
@@ -36,92 +36,146 @@ class Sync extends base_command_1.BaseCommand {
36
36
  writable: true,
37
37
  value: true
38
38
  });
39
+ /**
40
+ * The current status of the sync process.
41
+ */
39
42
  Object.defineProperty(this, "status", {
40
43
  enumerable: true,
41
44
  configurable: true,
42
45
  writable: true,
43
46
  value: SyncStatus.STARTING
44
47
  });
48
+ /**
49
+ * The absolute path to the directory to sync files to.
50
+ */
45
51
  Object.defineProperty(this, "dir", {
46
52
  enumerable: true,
47
53
  configurable: true,
48
54
  writable: true,
49
55
  value: void 0
50
56
  });
51
- Object.defineProperty(this, "recentWrites", {
57
+ /**
58
+ * A list of filepaths that have changed because of a remote file-sync event. This is used to avoid sending files that
59
+ * we recently received from a remote file-sync event.
60
+ */
61
+ Object.defineProperty(this, "recentRemoteChanges", {
52
62
  enumerable: true,
53
63
  configurable: true,
54
64
  writable: true,
55
65
  value: new Set()
56
66
  });
67
+ /**
68
+ * A FIFO async callback queue that ensures we process file-sync events in the order they occurred.
69
+ */
57
70
  Object.defineProperty(this, "queue", {
58
71
  enumerable: true,
59
72
  configurable: true,
60
73
  writable: true,
61
74
  value: new p_queue_1.default({ concurrency: 1 })
62
75
  });
76
+ /**
77
+ * A GraphQL client connected to the app's /edit/api/graphql-ws endpoint
78
+ */
63
79
  Object.defineProperty(this, "client", {
64
80
  enumerable: true,
65
81
  configurable: true,
66
82
  writable: true,
67
83
  value: void 0
68
84
  });
85
+ /**
86
+ * Loads the .ignore file and provides methods for checking if a file should be ignored.
87
+ */
69
88
  Object.defineProperty(this, "ignorer", {
70
89
  enumerable: true,
71
90
  configurable: true,
72
91
  writable: true,
73
92
  value: void 0
74
93
  });
94
+ /**
95
+ * Watches the local filesystem for changes.
96
+ */
75
97
  Object.defineProperty(this, "watcher", {
76
98
  enumerable: true,
77
99
  configurable: true,
78
100
  writable: true,
79
101
  value: void 0
80
102
  });
103
+ /**
104
+ * Holds information about the state of the local filesystem. It's persisted to `.gadget/sync.json`.
105
+ */
81
106
  Object.defineProperty(this, "metadata", {
82
107
  enumerable: true,
83
108
  configurable: true,
84
109
  writable: true,
85
110
  value: {
111
+ /**
112
+ * The app this filesystem is synced to.
113
+ */
86
114
  app: "",
115
+ /**
116
+ * The last filesVersion that was successfully written to the filesystem. This is used to determine if the remote
117
+ * filesystem is ahead of the local one.
118
+ */
87
119
  filesVersion: "0",
120
+ /**
121
+ * The largest mtime that was seen on the local filesystem before `ggt sync` stopped. This is used to determine if
122
+ * the local filesystem has changed since the last sync.
123
+ *
124
+ * Note: This does not include the mtime of files that are ignored.
125
+ */
88
126
  mtime: 0,
89
127
  }
90
128
  });
129
+ /**
130
+ * A debounced function that enqueue's local file changes to be sent to Gadget.
131
+ */
91
132
  Object.defineProperty(this, "publish", {
92
133
  enumerable: true,
93
134
  configurable: true,
94
135
  writable: true,
95
136
  value: void 0
96
137
  });
138
+ /**
139
+ * Gracefully stops the sync.
140
+ */
97
141
  Object.defineProperty(this, "stop", {
98
142
  enumerable: true,
99
143
  configurable: true,
100
144
  writable: true,
101
145
  value: void 0
102
146
  });
103
- Object.defineProperty(this, "finished", {
104
- enumerable: true,
105
- configurable: true,
106
- writable: true,
107
- value: void 0
108
- });
109
- Object.defineProperty(this, "markFinished", {
110
- enumerable: true,
111
- configurable: true,
112
- writable: true,
113
- value: void 0
114
- });
115
147
  }
148
+ /**
149
+ * Turns an absolute filepath into a relative one from {@linkcode dir}.
150
+ */
116
151
  relative(to) {
117
152
  return path_1.default.relative(this.dir, to);
118
153
  }
154
+ /**
155
+ * Combines path segments into an absolute filepath that starts at {@linkcode dir}.
156
+ */
119
157
  absolute(...pathSegments) {
120
158
  return path_1.default.resolve(this.dir, ...pathSegments);
121
159
  }
160
+ /**
161
+ * Similar to {@linkcode relative} in that it turns a filepath into a relative one from {@linkcode dir}. However, it
162
+ * also changes any slashes to be posix/unix-like forward slashes, condenses repeat slashes into a single slash.
163
+ *
164
+ * This is used when sending file-sync events to Gadget to ensure that the paths are consistent across platforms.
165
+ *
166
+ * @see https://www.npmjs.com/package/normalize-path
167
+ */
122
168
  normalize(filepath, isDirectory = false) {
123
- return (0, normalize_path_1.default)(`${path_1.default.isAbsolute(filepath) ? this.relative(filepath) : filepath}${isDirectory ? "/" : ""}`, false);
169
+ return (0, normalize_path_1.default)(path_1.default.isAbsolute(filepath) ? this.relative(filepath) : filepath) + (isDirectory ? "/" : "");
124
170
  }
171
+ /**
172
+ * Pretty-prints changed and deleted filepaths to the console.
173
+ *
174
+ * @param prefix The prefix to print before each line.
175
+ * @param changed The filepaths that have changed.
176
+ * @param deleted The filepaths that have been deleted.
177
+ * @param options.limit The maximum number of lines to print. Defaults to 10. If debug is enabled, this is ignored.
178
+ */
125
179
  logPaths(prefix, changed, deleted, { limit = 10 } = {}) {
126
180
  const lines = (0, lodash_1.sortBy)([
127
181
  ...changed.map((filepath) => (0, chalk_1.default) `{green ${prefix}} ${this.normalize(filepath)} {gray (changed)}`),
@@ -139,9 +193,16 @@ class Sync extends base_command_1.BaseCommand {
139
193
  this.log((0, chalk_1.default) `{gray ${(0, pluralize_1.default)("file", lines.length, true)} in total. ${changed.length} changed, ${deleted.length} deleted.}`);
140
194
  this.log();
141
195
  }
196
+ /**
197
+ * Initializes the sync process.
198
+ * - Ensures the directory exists.
199
+ * - Ensures the directory is empty or contains a `.gadget/sync.json` file.
200
+ * - Ensures an app is selected and that it matches the app the directory was previously synced to.
201
+ * - Ensures yarn v1 is installed.
202
+ * - Prompts the user how to resolve conflicts if the local filesystem has changed since the last sync.
203
+ */
142
204
  async init() {
143
205
  await super.init();
144
- (0, assert_1.default)((0, lodash_2.isString)(this.args["directory"]));
145
206
  this.dir =
146
207
  this.config.windows && this.args["directory"].startsWith("~/")
147
208
  ? path_1.default.join(this.config.home, this.args["directory"].slice(2))
@@ -182,7 +243,7 @@ class Sync extends base_command_1.BaseCommand {
182
243
  await context_1.context.setApp(this.metadata.app);
183
244
  this.client = new client_1.Client();
184
245
  // local files/folders that should never be published
185
- this.ignorer = new fs_utils_1.Ignorer(this.dir, ["node_modules", ".gadget", ".git"]);
246
+ this.ignorer = new fs_utils_1.FSIgnorer(this.dir, ["node_modules", ".gadget", ".git"]);
186
247
  this.watcher = new chokidar_1.FSWatcher({
187
248
  ignored: (filepath) => this.ignorer.ignores(filepath),
188
249
  // don't emit an event for every watched file on boot
@@ -207,6 +268,8 @@ class Sync extends base_command_1.BaseCommand {
207
268
  files.set(this.absolute(filepath), stats);
208
269
  }
209
270
  }
271
+ // never include the root directory
272
+ files.delete(this.dir);
210
273
  return files;
211
274
  };
212
275
  const changedFiles = await getChangedFiles();
@@ -230,7 +293,9 @@ class Sync extends base_command_1.BaseCommand {
230
293
  case Action.MERGE: {
231
294
  // get all the changed files again in case more changed
232
295
  const files = await getChangedFiles();
233
- // we purposefully don't set the returned remoteFilesVersion here because we haven't processed the in-between versions yet
296
+ // We purposefully don't set the returned remoteFilesVersion here because we haven't received the remote changes
297
+ // yet. This will cause us to receive the local files that we just published + the remote files that were
298
+ // changed since the last sync.
234
299
  await this.client.queryUnwrap({
235
300
  query: exports.PUBLISH_FILE_SYNC_EVENTS_MUTATION,
236
301
  variables: {
@@ -240,10 +305,11 @@ class Sync extends base_command_1.BaseCommand {
240
305
  if (stats.mtime.getTime() > this.metadata.mtime) {
241
306
  this.metadata.mtime = stats.mtime.getTime();
242
307
  }
308
+ const isDirectory = stats.isDirectory();
243
309
  return {
244
- path: this.normalize(filepath),
310
+ path: this.normalize(filepath, isDirectory),
245
311
  mode: stats.mode,
246
- content: await fs_extra_1.default.readFile(filepath, "base64"),
312
+ content: isDirectory ? "" : await fs_extra_1.default.readFile(filepath, "base64"),
247
313
  encoding: graphql_1.FileSyncEncoding.Base64,
248
314
  };
249
315
  }),
@@ -254,6 +320,8 @@ class Sync extends base_command_1.BaseCommand {
254
320
  break;
255
321
  }
256
322
  case Action.RESET: {
323
+ // delete all the local files that have changed since the last sync and set the files version to 0 so we receive
324
+ // all the remote files again, including any files that we just deleted that still exist
257
325
  await (0, p_map_1.default)(changedFiles, ([filepath]) => fs_extra_1.default.remove(filepath));
258
326
  this.metadata.filesVersion = "0";
259
327
  break;
@@ -264,11 +332,12 @@ class Sync extends base_command_1.BaseCommand {
264
332
  }
265
333
  this.debug("started");
266
334
  }
335
+ /**
336
+ * Runs the sync process until it is stopped or an error occurs.
337
+ */
267
338
  async run() {
268
339
  let error;
269
- this.finished = new Promise((resolve) => {
270
- this.markFinished = resolve;
271
- });
340
+ const stopped = new promise_1.PromiseSignal();
272
341
  this.stop = async (e) => {
273
342
  if (this.status != SyncStatus.RUNNING)
274
343
  return;
@@ -286,7 +355,7 @@ class Sync extends base_command_1.BaseCommand {
286
355
  await Promise.allSettled([this.watcher.close(), this.client.dispose()]);
287
356
  this.debug("stopped");
288
357
  this.status = SyncStatus.STOPPED;
289
- this.markFinished();
358
+ stopped.resolve();
290
359
  }
291
360
  };
292
361
  for (const signal of ["SIGINT", "SIGTERM"]) {
@@ -331,10 +400,10 @@ class Sync extends base_command_1.BaseCommand {
331
400
  return;
332
401
  }
333
402
  const filepath = this.absolute(file.path);
334
- this.recentWrites.add(filepath);
403
+ this.recentRemoteChanges.add(filepath);
335
404
  if ("content" in file) {
336
405
  if (!file.path.endsWith("/")) {
337
- this.recentWrites.add(path_1.default.dirname(filepath));
406
+ this.recentRemoteChanges.add(path_1.default.dirname(filepath));
338
407
  await fs_extra_1.default.ensureDir(path_1.default.dirname(filepath), { mode: 0o755 });
339
408
  await fs_extra_1.default.writeFile(filepath, Buffer.from(file.content, file.encoding ?? graphql_1.FileSyncEncoding.Utf8), {
340
409
  mode: file.mode,
@@ -367,7 +436,7 @@ class Sync extends base_command_1.BaseCommand {
367
436
  },
368
437
  });
369
438
  const localFilesBuffer = new Map();
370
- this.publish = (0, lodash_3.debounce)(() => {
439
+ this.publish = (0, lodash_2.debounce)(() => {
371
440
  const localFiles = new Map(localFilesBuffer.entries());
372
441
  localFilesBuffer.clear();
373
442
  void this.queue
@@ -388,8 +457,8 @@ class Sync extends base_command_1.BaseCommand {
388
457
  });
389
458
  }
390
459
  catch (error) {
391
- // A file could have been changed and then deleted before we process the change event, so the readFile above will raise
392
- // an ENOENT. This is normal operation, so just ignore this event.
460
+ // A file could have been changed and then deleted before we process the change event, so the readFile
461
+ // above will raise an ENOENT. This is normal operation, so just ignore this event.
393
462
  (0, fs_utils_1.ignoreEnoent)(error);
394
463
  }
395
464
  }
@@ -427,12 +496,13 @@ class Sync extends base_command_1.BaseCommand {
427
496
  this.debug("skipping event caused by ignored file %s", relativePath);
428
497
  return;
429
498
  }
430
- // we only update the mtime if the file is not ignored, because if we restart and the mtime is set to an ignored file, then it could
431
- // be greater than the mtime of all non ignored files and we'll think that local files have changed when only an ignored one has
499
+ // we only update the mtime if the file is not ignored, because if we restart and the mtime is set to an ignored
500
+ // file, then it could be greater than the mtime of all non ignored files and we'll think that local files have
501
+ // changed when only an ignored one has
432
502
  if (stats && stats.mtime.getTime() > this.metadata.mtime) {
433
503
  this.metadata.mtime = stats.mtime.getTime();
434
504
  }
435
- if (this.recentWrites.delete(filepath)) {
505
+ if (this.recentRemoteChanges.delete(filepath)) {
436
506
  this.debug("skipping event caused by recent write %s", relativePath);
437
507
  return;
438
508
  }
@@ -476,7 +546,7 @@ class Sync extends base_command_1.BaseCommand {
476
546
  Watching for file changes... {gray Press Ctrl+C to stop}
477
547
  `));
478
548
  this.log();
479
- await this.finished;
549
+ await stopped;
480
550
  if (error) {
481
551
  this.notify({ subtitle: "Uh oh!", message: "An error occurred while syncing files" });
482
552
  throw error;