@gadgetinc/ggt 0.4.7 → 0.4.9
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 +2 -2
- package/bin/dev.js +2 -1
- package/bin/run.js +3 -1
- package/lib/commands/deploy.js +11 -15
- package/lib/commands/deploy.js.map +1 -1
- package/lib/commands/list.js +7 -11
- package/lib/commands/list.js.map +1 -1
- package/lib/commands/login.js +8 -12
- package/lib/commands/login.js.map +1 -1
- package/lib/commands/logout.js +3 -7
- package/lib/commands/logout.js.map +1 -1
- package/lib/commands/root.js +17 -44
- package/lib/commands/root.js.map +1 -1
- package/lib/commands/sync.js +10 -29
- package/lib/commands/sync.js.map +1 -1
- package/lib/commands/version.js +2 -6
- package/lib/commands/version.js.map +1 -1
- package/lib/commands/whoami.js +5 -9
- package/lib/commands/whoami.js.map +1 -1
- package/lib/ggt.js +40 -0
- package/lib/ggt.js.map +1 -0
- package/lib/services/app/app.js +8 -6
- package/lib/services/app/app.js.map +1 -1
- package/lib/services/app/edit/client.js +176 -0
- package/lib/services/app/edit/client.js.map +1 -0
- package/lib/services/app/edit/edit.js +155 -0
- package/lib/services/app/edit/edit.js.map +1 -0
- package/lib/services/app/edit/error.js +65 -0
- package/lib/services/app/edit/error.js.map +1 -0
- package/lib/services/app/edit/operation.js +87 -0
- package/lib/services/app/edit/operation.js.map +1 -0
- package/lib/services/command/arg.js +5 -5
- package/lib/services/command/arg.js.map +1 -1
- package/lib/services/command/command.js +21 -7
- package/lib/services/command/command.js.map +1 -1
- package/lib/services/command/context.js +108 -56
- package/lib/services/command/context.js.map +1 -1
- package/lib/services/filesync/changes.js +7 -9
- package/lib/services/filesync/changes.js.map +1 -1
- package/lib/services/filesync/conflicts.js +11 -9
- package/lib/services/filesync/conflicts.js.map +1 -1
- package/lib/services/filesync/directory.js +2 -2
- package/lib/services/filesync/directory.js.map +1 -1
- package/lib/services/filesync/filesync.js +73 -43
- package/lib/services/filesync/filesync.js.map +1 -1
- package/lib/services/filesync/hashes.js +18 -15
- package/lib/services/filesync/hashes.js.map +1 -1
- package/lib/services/http/auth.js +4 -4
- package/lib/services/http/auth.js.map +1 -1
- package/lib/services/http/http.js +83 -23
- package/lib/services/http/http.js.map +1 -1
- package/lib/services/output/log/logger.js +11 -6
- package/lib/services/output/log/logger.js.map +1 -1
- package/lib/services/output/log/printer.js.map +1 -1
- package/lib/services/output/log/structured.js +2 -2
- package/lib/services/output/log/structured.js.map +1 -1
- package/lib/services/output/notify.js +3 -7
- package/lib/services/output/notify.js.map +1 -1
- package/lib/services/output/prompt.js +11 -11
- package/lib/services/output/prompt.js.map +1 -1
- package/lib/services/output/report.js +51 -67
- package/lib/services/output/report.js.map +1 -1
- package/lib/services/output/update.js +9 -10
- package/lib/services/output/update.js.map +1 -1
- package/lib/services/user/user.js +19 -25
- package/lib/services/user/user.js.map +1 -1
- package/lib/services/util/collection.js +1 -1
- package/lib/services/util/collection.js.map +1 -1
- package/lib/services/util/function.js +29 -11
- package/lib/services/util/function.js.map +1 -1
- package/lib/services/util/number.js +3 -3
- package/lib/services/util/number.js.map +1 -1
- package/lib/services/util/object.js +4 -4
- package/lib/services/util/object.js.map +1 -1
- package/lib/services/util/paths.js +4 -4
- package/lib/services/util/paths.js.map +1 -1
- package/lib/services/util/types.js +3 -1
- package/lib/services/util/types.js.map +1 -1
- package/npm-shrinkwrap.json +579 -411
- package/package.json +15 -14
- package/lib/main.js +0 -8
- package/lib/main.js.map +0 -1
- package/lib/services/app/edit-graphql.js +0 -392
- package/lib/services/app/edit-graphql.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/services/filesync/changes.ts"],"sourcesContent":["import chalk from \"chalk\";\nimport pluralize from \"pluralize\";\nimport {
|
|
1
|
+
{"version":3,"sources":["../../../src/services/filesync/changes.ts"],"sourcesContent":["import chalk from \"chalk\";\nimport pluralize from \"pluralize\";\nimport type { Context } from \"../command/context.js\";\nimport { config } from \"../config/config.js\";\nimport { Level } from \"../output/log/level.js\";\nimport type { PrintTableOptions } from \"../output/log/printer.js\";\nimport { sprint } from \"../output/sprint.js\";\nimport { isNever, isString } from \"../util/is.js\";\n\nexport type Create = { type: \"create\"; oldPath?: string };\nexport type Update = { type: \"update\" };\nexport type Delete = { type: \"delete\" };\nexport type Change = Create | Update | Delete;\n\nexport class Changes extends Map<string, Change> {\n created(): string[] {\n return Array.from(this.entries())\n .filter(([, change]) => change.type === \"create\")\n .map(([path]) => path);\n }\n\n updated(): string[] {\n return Array.from(this.entries())\n .filter(([, change]) => change.type === \"update\")\n .map(([path]) => path);\n }\n\n deleted(): string[] {\n return Array.from(this.entries())\n .filter(([, change]) => change.type === \"delete\")\n .map(([path]) => path);\n }\n}\n\n/**\n * Prints the changes to the console.\n *\n * @param ctx - The current context.\n * @param options - The options to use.\n * @param options.changes - The changes to print.\n * @param options.tense - The tense to use for the change type.\n * @param options.limit - The maximum number of changes to print.\n */\nexport const printChanges = (\n ctx: Context,\n {\n changes,\n tense,\n limit = Infinity,\n ...tableOptions\n }: {\n changes: Changes;\n tense: \"past\" | \"present\";\n limit?: number;\n } & Partial<PrintTableOptions>,\n): void => {\n if (config.logLevel <= Level.TRACE) {\n // print all changes when tracing\n limit = Infinity;\n }\n\n const renamed = chalk.yellowBright((tense === \"past\" ? \"renamed\" : \"rename\") + \" →\");\n const created = chalk.greenBright((tense === \"past\" ? \"created\" : \"create\") + \" +\");\n const updated = chalk.blueBright((tense === \"past\" ? \"updated\" : \"update\") + \" ±\");\n const deleted = chalk.redBright((tense === \"past\" ? \"deleted\" : \"delete\") + \" -\");\n\n const rows = Array.from(changes.entries())\n .sort((a, b) => a[0].localeCompare(b[0]))\n .slice(0, limit)\n .map(([path, change]) => {\n switch (true) {\n case change.type === \"create\" && isString(change.oldPath):\n return [chalk.yellowBright(change.oldPath), renamed, chalk.yellowBright(path)];\n case change.type === \"create\":\n return [chalk.greenBright(path), created];\n case change.type === \"update\":\n return [chalk.blueBright(path), updated];\n case change.type === \"delete\":\n return [chalk.redBright(path), deleted];\n default:\n return isNever(change);\n }\n });\n\n if (changes.size > limit) {\n rows.push([sprint`{gray … ${changes.size - limit} more}`, \"\"]);\n }\n\n let footer: string | undefined;\n if (changes.size >= 10) {\n tableOptions.spaceY = 1;\n\n footer = sprint`${pluralize(\"change\", changes.size, true)} in total. `;\n\n const breakdown = [];\n\n const created = changes.created();\n if (created.length > 0) {\n breakdown.push(sprint`{greenBright ${pluralize(\"create\", created.length, true)}}`);\n }\n\n const updated = changes.updated();\n if (updated.length > 0) {\n breakdown.push(sprint`{blueBright ${pluralize(\"update\", updated.length, true)}}`);\n }\n\n const deleted = changes.deleted();\n if (deleted.length > 0) {\n breakdown.push(sprint`{redBright ${pluralize(\"delete\", deleted.length, true)}}`);\n }\n\n footer += breakdown.join(\", \");\n footer += \".\";\n }\n\n ctx.log.printTable({ rows, footer, ...tableOptions });\n};\n"],"names":["chalk","pluralize","config","Level","sprint","isNever","isString","Changes","Map","created","Array","from","entries","filter","change","type","map","path","updated","deleted","printChanges","ctx","changes","tense","limit","Infinity","tableOptions","logLevel","TRACE","renamed","yellowBright","greenBright","blueBright","redBright","rows","sort","a","b","localeCompare","slice","oldPath","size","push","footer","spaceY","breakdown","length","join","log","printTable"],"mappings":"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"}
|
|
@@ -1,9 +1,5 @@
|
|
|
1
1
|
import chalk from "chalk";
|
|
2
|
-
import { createLogger } from "../output/log/logger.js";
|
|
3
2
|
import { ChangesWithHash, isEqualHash } from "./hashes.js";
|
|
4
|
-
const log = createLogger({
|
|
5
|
-
name: "conflicts"
|
|
6
|
-
});
|
|
7
3
|
/**
|
|
8
4
|
* A map of conflicting changes made between the user's local filesystem
|
|
9
5
|
* and Gadget's filesystem where the key is the path of the conflicting
|
|
@@ -46,9 +42,10 @@ const log = createLogger({
|
|
|
46
42
|
* Returns a new `Changes` object that contains only the changes that do
|
|
47
43
|
* not have conflicts.
|
|
48
44
|
*
|
|
49
|
-
* @param
|
|
50
|
-
* @param
|
|
51
|
-
* @
|
|
45
|
+
* @param options - The options to use.
|
|
46
|
+
* @param options.conflicts - The conflicts to check against.
|
|
47
|
+
* @param options.changes - The changes to filter.
|
|
48
|
+
* @returns A new {@linkcode Changes} object without conflicts.
|
|
52
49
|
*/ export const withoutConflictingChanges = ({ conflicts, changes })=>{
|
|
53
50
|
const changesWithoutConflicts = new ChangesWithHash(changes);
|
|
54
51
|
for (const [filepath] of changesWithoutConflicts){
|
|
@@ -60,11 +57,16 @@ const log = createLogger({
|
|
|
60
57
|
};
|
|
61
58
|
/**
|
|
62
59
|
* Prints a table of conflicts between local changes and gadget changes.
|
|
63
|
-
|
|
60
|
+
*
|
|
61
|
+
* @param ctx - The current context.
|
|
62
|
+
* @param options - The options to use.
|
|
63
|
+
* @param options.message - The message to print above the table.
|
|
64
|
+
* @param options.conflicts - The conflicts to print.
|
|
65
|
+
*/ export const printConflicts = (ctx, { message, conflicts })=>{
|
|
64
66
|
const created = chalk.greenBright("+ created");
|
|
65
67
|
const updated = chalk.blueBright("± updated");
|
|
66
68
|
const deleted = chalk.redBright("- deleted");
|
|
67
|
-
log.printTable({
|
|
69
|
+
ctx.log.printTable({
|
|
68
70
|
message,
|
|
69
71
|
colAligns: [
|
|
70
72
|
"left",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/services/filesync/conflicts.ts"],"sourcesContent":["import chalk from \"chalk\";\nimport {
|
|
1
|
+
{"version":3,"sources":["../../../src/services/filesync/conflicts.ts"],"sourcesContent":["import chalk from \"chalk\";\nimport type { Context } from \"../command/context.js\";\nimport { ChangesWithHash, isEqualHash, type ChangeWithHash } from \"./hashes.js\";\n\n/**\n * A map of conflicting changes made between the user's local filesystem\n * and Gadget's filesystem where the key is the path of the conflicting\n * file and the value is an object containing the conflicting changes.\n */\nexport class Conflicts extends Map<string, { localChange: ChangeWithHash; gadgetChange: ChangeWithHash }> {}\n\n/**\n * Returns the conflicting changes between the user's local filesystem\n * and Gadget's filesystem.\n */\nexport const getConflicts = ({\n localChanges,\n gadgetChanges,\n}: {\n localChanges: ChangesWithHash;\n gadgetChanges: ChangesWithHash;\n}): Conflicts => {\n const conflicts = new Conflicts();\n\n for (const [filepath, localChange] of localChanges) {\n const gadgetChange = gadgetChanges.get(filepath);\n if (!gadgetChange) {\n // gadget doesn't have this change, so there's no conflict\n continue;\n }\n\n if (localChange.type === \"delete\" && gadgetChange.type === \"delete\") {\n // local and gadget both deleted the same file\n continue;\n }\n\n if (\n \"targetHash\" in localChange &&\n \"targetHash\" in gadgetChange &&\n isEqualHash(filepath, localChange.targetHash, gadgetChange.targetHash)\n ) {\n // local and gadget both created/updated the same file with the same content\n continue;\n }\n\n // local and gadget both updated the same file with different\n // content or one updated and the other deleted\n conflicts.set(filepath, { localChange, gadgetChange });\n }\n\n // ignore .gadget/ file conflicts and always use gadget's version\n // since gadget is the source of truth for .gadget/ files\n for (const filepath of conflicts.keys()) {\n if (filepath.startsWith(\".gadget/\")) {\n conflicts.delete(filepath);\n }\n }\n\n return conflicts;\n};\n\n/**\n * Returns a new `Changes` object that contains only the changes that do\n * not have conflicts.\n *\n * @param options - The options to use.\n * @param options.conflicts - The conflicts to check against.\n * @param options.changes - The changes to filter.\n * @returns A new {@linkcode Changes} object without conflicts.\n */\nexport const withoutConflictingChanges = ({ conflicts, changes }: { conflicts: Conflicts; changes: ChangesWithHash }): ChangesWithHash => {\n const changesWithoutConflicts = new ChangesWithHash(changes);\n\n for (const [filepath] of changesWithoutConflicts) {\n if (conflicts.has(filepath)) {\n changesWithoutConflicts.delete(filepath);\n }\n }\n\n return changesWithoutConflicts;\n};\n\n/**\n * Prints a table of conflicts between local changes and gadget changes.\n *\n * @param ctx - The current context.\n * @param options - The options to use.\n * @param options.message - The message to print above the table.\n * @param options.conflicts - The conflicts to print.\n */\nexport const printConflicts = (ctx: Context, { message, conflicts }: { message: string; conflicts: Conflicts }): void => {\n const created = chalk.greenBright(\"+ created\");\n const updated = chalk.blueBright(\"± updated\");\n const deleted = chalk.redBright(\"- deleted\");\n\n ctx.log.printTable({\n message,\n colAligns: [\"left\", \"center\", \"center\"],\n headers: [\"\", \"You\", \"Gadget\"],\n spaceY: 1,\n rows: Array.from(conflicts.entries())\n .sort((a, b) => a[0].localeCompare(b[0]))\n .map(([path, { localChange, gadgetChange }]) => {\n switch (true) {\n case localChange.type === \"create\" && gadgetChange.type === \"create\":\n return [path, created, created];\n case localChange.type === \"create\" && gadgetChange.type === \"update\":\n return [path, created, updated];\n case localChange.type === \"create\" && gadgetChange.type === \"delete\":\n return [path, created, deleted];\n case localChange.type === \"update\" && gadgetChange.type === \"create\":\n return [path, updated, created];\n case localChange.type === \"update\" && gadgetChange.type === \"update\":\n return [path, updated, updated];\n case localChange.type === \"update\" && gadgetChange.type === \"delete\":\n return [path, updated, deleted];\n case localChange.type === \"delete\" && gadgetChange.type === \"create\":\n return [path, deleted, created];\n case localChange.type === \"delete\" && gadgetChange.type === \"update\":\n return [path, deleted, updated];\n default:\n throw new Error(`Unexpected conflict: ${localChange.type} vs ${gadgetChange.type}`);\n }\n }),\n });\n};\n"],"names":["chalk","ChangesWithHash","isEqualHash","Conflicts","Map","getConflicts","localChanges","gadgetChanges","conflicts","filepath","localChange","gadgetChange","get","type","targetHash","set","keys","startsWith","delete","withoutConflictingChanges","changes","changesWithoutConflicts","has","printConflicts","ctx","message","created","greenBright","updated","blueBright","deleted","redBright","log","printTable","colAligns","headers","spaceY","rows","Array","from","entries","sort","a","b","localeCompare","map","path","Error"],"mappings":"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"}
|
|
@@ -59,7 +59,7 @@ import normalizePath from "normalize-path";
|
|
|
59
59
|
* Returns the absolute path by resolving the given path segments
|
|
60
60
|
* relative to the directory path.
|
|
61
61
|
*
|
|
62
|
-
* @param pathSegments The path segments to resolve.
|
|
62
|
+
* @param pathSegments - The path segments to resolve.
|
|
63
63
|
* @returns The absolute path.
|
|
64
64
|
*/ absolute(...pathSegments) {
|
|
65
65
|
const result = path.resolve(this.path, ...pathSegments);
|
|
@@ -195,7 +195,7 @@ import normalizePath from "normalize-path";
|
|
|
195
195
|
* Calculates the {@linkcode Hash} of the file or directory at the
|
|
196
196
|
* specified absolute path.
|
|
197
197
|
*
|
|
198
|
-
* @param absolutePath The absolute path to the file or directory.
|
|
198
|
+
* @param absolutePath - The absolute path to the file or directory.
|
|
199
199
|
* @returns A Promise that resolves to the {@linkcode Hash} of the file
|
|
200
200
|
* or directory.
|
|
201
201
|
*/ const hash = async (absolutePath)=>{
|
|
@@ -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\", \"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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"}
|
|
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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"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { _ as _define_property } from "@swc/helpers/_/_define_property";
|
|
2
2
|
import dayjs from "dayjs";
|
|
3
|
+
import { execa } from "execa";
|
|
3
4
|
import { findUp } from "find-up";
|
|
4
5
|
import fs from "fs-extra";
|
|
5
6
|
import ms from "ms";
|
|
@@ -13,11 +14,14 @@ import { z } from "zod";
|
|
|
13
14
|
import { FileSyncEncoding } from "../../__generated__/graphql.js";
|
|
14
15
|
import { getApps } from "../app/app.js";
|
|
15
16
|
import { AppArg } from "../app/arg.js";
|
|
16
|
-
import {
|
|
17
|
+
import { Edit } from "../app/edit/edit.js";
|
|
18
|
+
import { EditError } from "../app/edit/error.js";
|
|
19
|
+
import { FILE_SYNC_COMPARISON_HASHES_QUERY, FILE_SYNC_FILES_QUERY, FILE_SYNC_HASHES_QUERY, PUBLISH_FILE_SYNC_EVENTS_MUTATION, REMOTE_FILE_SYNC_EVENTS_SUBSCRIPTION } from "../app/edit/operation.js";
|
|
17
20
|
import { ArgError } from "../command/arg.js";
|
|
18
21
|
import { config, homePath } from "../config/config.js";
|
|
19
22
|
import { select } from "../output/prompt.js";
|
|
20
23
|
import { sprint } from "../output/sprint.js";
|
|
24
|
+
import { getUserOrLogin } from "../user/user.js";
|
|
21
25
|
import { sortBySimilar } from "../util/collection.js";
|
|
22
26
|
import { noop } from "../util/function.js";
|
|
23
27
|
import { isGraphQLErrors, isGraphQLResult, isObject, isString } from "../util/is.js";
|
|
@@ -33,7 +37,7 @@ export class FileSync {
|
|
|
33
37
|
* This determines if the filesystem in Gadget is ahead of the
|
|
34
38
|
* filesystem on the local machine.
|
|
35
39
|
*/ get filesVersion() {
|
|
36
|
-
return BigInt(this.
|
|
40
|
+
return BigInt(this._syncJson.filesVersion);
|
|
37
41
|
}
|
|
38
42
|
/**
|
|
39
43
|
* The largest mtime that was seen on the filesystem.
|
|
@@ -41,7 +45,7 @@ export class FileSync {
|
|
|
41
45
|
* This is used to determine if any files have changed since the last
|
|
42
46
|
* sync. This does not include the mtime of files that are ignored.
|
|
43
47
|
*/ get mtime() {
|
|
44
|
-
return this.
|
|
48
|
+
return this._syncJson.mtime;
|
|
45
49
|
}
|
|
46
50
|
/**
|
|
47
51
|
* Initializes a {@linkcode FileSync} instance.
|
|
@@ -49,11 +53,12 @@ export class FileSync {
|
|
|
49
53
|
* - Ensures the directory is empty or contains a `.gadget/sync.json` file (unless `options.force` is `true`)
|
|
50
54
|
* - Ensures an app is specified (either via `options.app` or by prompting the user)
|
|
51
55
|
* - Ensures the specified app matches the app the directory was previously synced to (unless `options.force` is `true`)
|
|
52
|
-
*/ static async init(
|
|
53
|
-
ctx = ctx.
|
|
56
|
+
*/ static async init(ctx) {
|
|
57
|
+
ctx = ctx.child({
|
|
54
58
|
name: "filesync"
|
|
55
59
|
});
|
|
56
|
-
const
|
|
60
|
+
const user = await getUserOrLogin(ctx);
|
|
61
|
+
const apps = await getApps(ctx);
|
|
57
62
|
if (apps.length === 0) {
|
|
58
63
|
throw new ArgError(sprint`
|
|
59
64
|
You (${user.email}) don't have have any Gadget applications.
|
|
@@ -89,7 +94,7 @@ export class FileSync {
|
|
|
89
94
|
let appSlug = ctx.args["--app"] || state?.app;
|
|
90
95
|
if (!appSlug) {
|
|
91
96
|
// the user didn't specify an app, suggest some apps that they can sync to
|
|
92
|
-
appSlug = await select({
|
|
97
|
+
appSlug = await select(ctx, {
|
|
93
98
|
message: "Select the app to sync to",
|
|
94
99
|
choices: apps.map((x)=>x.slug)
|
|
95
100
|
});
|
|
@@ -112,6 +117,7 @@ export class FileSync {
|
|
|
112
117
|
|
|
113
118
|
`.concat(` • ${similarAppSlugs.join("\n • ")}`));
|
|
114
119
|
}
|
|
120
|
+
ctx.app = app;
|
|
115
121
|
const directory = await Directory.init(dir);
|
|
116
122
|
if (!state) {
|
|
117
123
|
// the .gadget/sync.json file didn't exist or contained invalid json
|
|
@@ -166,7 +172,8 @@ export class FileSync {
|
|
|
166
172
|
/**
|
|
167
173
|
* Sends file changes to the Gadget.
|
|
168
174
|
*
|
|
169
|
-
* @param
|
|
175
|
+
* @param options - The options to use.
|
|
176
|
+
* @param options.changes - The changes to send.
|
|
170
177
|
* @returns A promise that resolves when the changes have been sent.
|
|
171
178
|
*/ async sendChangesToGadget({ changes }) {
|
|
172
179
|
await this._syncOperations.add(async ()=>{
|
|
@@ -187,10 +194,14 @@ export class FileSync {
|
|
|
187
194
|
* Subscribes to file changes on Gadget and executes the provided
|
|
188
195
|
* callbacks before and after the changes occur.
|
|
189
196
|
*
|
|
197
|
+
* @param options - The options to use.
|
|
198
|
+
* @param options.beforeChanges - A callback that is called before the changes occur.
|
|
199
|
+
* @param options.afterChanges - A callback that is called after the changes occur.
|
|
200
|
+
* @param options.onError - A callback that is called if an error occurs.
|
|
190
201
|
* @returns A function that unsubscribes from changes on Gadget.
|
|
191
|
-
*/ subscribeToGadgetChanges({ beforeChanges, afterChanges, onError }) {
|
|
192
|
-
return this.
|
|
193
|
-
|
|
202
|
+
*/ subscribeToGadgetChanges({ beforeChanges = noop, afterChanges = noop, onError }) {
|
|
203
|
+
return this.edit.subscribe({
|
|
204
|
+
subscription: REMOTE_FILE_SYNC_EVENTS_SUBSCRIPTION,
|
|
194
205
|
// the reason this is a function rather than a static value is
|
|
195
206
|
// so that it will be re-evaluated if the connection is lost and
|
|
196
207
|
// then re-established. this ensures that we send our current
|
|
@@ -209,7 +220,7 @@ export class FileSync {
|
|
|
209
220
|
return;
|
|
210
221
|
}
|
|
211
222
|
this.ctx.log.debug("received files", {
|
|
212
|
-
remoteFilesVersion
|
|
223
|
+
remoteFilesVersion,
|
|
213
224
|
changed: changed.map((change)=>change.path),
|
|
214
225
|
deleted: deleted.map((change)=>change.path)
|
|
215
226
|
});
|
|
@@ -238,7 +249,7 @@ export class FileSync {
|
|
|
238
249
|
delete: deleted.map((file)=>file.path)
|
|
239
250
|
});
|
|
240
251
|
if (changes.size > 0) {
|
|
241
|
-
printChanges({
|
|
252
|
+
printChanges(this.ctx, {
|
|
242
253
|
message: sprint`← Received {gray ${dayjs().format("hh:mm:ss A")}}`,
|
|
243
254
|
changes,
|
|
244
255
|
tense: "past",
|
|
@@ -255,8 +266,7 @@ export class FileSync {
|
|
|
255
266
|
/**
|
|
256
267
|
* Ensures the local filesystem is in sync with Gadget's filesystem.
|
|
257
268
|
* - All non-conflicting changes are automatically merged.
|
|
258
|
-
* - Conflicts are resolved by prompting the user to either keep their
|
|
259
|
-
* local changes or keep Gadget's changes.
|
|
269
|
+
* - Conflicts are resolved by prompting the user to either keep their local changes or keep Gadget's changes.
|
|
260
270
|
* - This function will not return until the filesystem is in sync.
|
|
261
271
|
*/ async sync({ maxAttempts = 10 } = {}) {
|
|
262
272
|
let attempt = 0;
|
|
@@ -289,7 +299,7 @@ export class FileSync {
|
|
|
289
299
|
}
|
|
290
300
|
}
|
|
291
301
|
async _sync({ filesVersionHashes, localHashes, gadgetHashes, gadgetFilesVersion }) {
|
|
292
|
-
let localChanges = getChanges({
|
|
302
|
+
let localChanges = getChanges(this.ctx, {
|
|
293
303
|
from: filesVersionHashes,
|
|
294
304
|
to: localHashes,
|
|
295
305
|
existing: gadgetHashes,
|
|
@@ -297,14 +307,14 @@ export class FileSync {
|
|
|
297
307
|
".gadget/"
|
|
298
308
|
]
|
|
299
309
|
});
|
|
300
|
-
let gadgetChanges = getChanges({
|
|
310
|
+
let gadgetChanges = getChanges(this.ctx, {
|
|
301
311
|
from: filesVersionHashes,
|
|
302
312
|
to: gadgetHashes,
|
|
303
313
|
existing: localHashes
|
|
304
314
|
});
|
|
305
315
|
if (localChanges.size === 0 && gadgetChanges.size === 0) {
|
|
306
316
|
// the local filesystem is missing .gadget/ files
|
|
307
|
-
gadgetChanges = getChanges({
|
|
317
|
+
gadgetChanges = getChanges(this.ctx, {
|
|
308
318
|
from: localHashes,
|
|
309
319
|
to: gadgetHashes
|
|
310
320
|
});
|
|
@@ -323,11 +333,11 @@ export class FileSync {
|
|
|
323
333
|
});
|
|
324
334
|
let preference = this.ctx.args["--prefer"];
|
|
325
335
|
if (!preference) {
|
|
326
|
-
printConflicts({
|
|
336
|
+
printConflicts(this.ctx, {
|
|
327
337
|
message: sprint`{bold You have conflicting changes with Gadget}`,
|
|
328
338
|
conflicts
|
|
329
339
|
});
|
|
330
|
-
preference = await select({
|
|
340
|
+
preference = await select(this.ctx, {
|
|
331
341
|
message: "How would you like to resolve these conflicts?",
|
|
332
342
|
choices: Object.values(ConflictPreference)
|
|
333
343
|
});
|
|
@@ -381,7 +391,7 @@ export class FileSync {
|
|
|
381
391
|
if (this.filesVersion === 0n) {
|
|
382
392
|
// this is the first time we're syncing, so just get the
|
|
383
393
|
// hashes of the latest filesVersion
|
|
384
|
-
const { fileSyncHashes } = await this.
|
|
394
|
+
const { fileSyncHashes } = await this.edit.query({
|
|
385
395
|
query: FILE_SYNC_HASHES_QUERY
|
|
386
396
|
});
|
|
387
397
|
gadgetFilesVersion = BigInt(fileSyncHashes.filesVersion);
|
|
@@ -391,7 +401,7 @@ export class FileSync {
|
|
|
391
401
|
// this isn't the first time we're syncing, so get the hashes
|
|
392
402
|
// of the files at our local filesVersion and the latest
|
|
393
403
|
// filesVersion
|
|
394
|
-
const { fileSyncComparisonHashes } = await this.
|
|
404
|
+
const { fileSyncComparisonHashes } = await this.edit.query({
|
|
395
405
|
query: FILE_SYNC_COMPARISON_HASHES_QUERY,
|
|
396
406
|
variables: {
|
|
397
407
|
filesVersion: String(this.filesVersion)
|
|
@@ -413,7 +423,7 @@ export class FileSync {
|
|
|
413
423
|
localHashes,
|
|
414
424
|
gadgetHashes,
|
|
415
425
|
gadgetFilesVersion,
|
|
416
|
-
inSync: isEqualHashes(localHashes, gadgetHashes)
|
|
426
|
+
inSync: isEqualHashes(this.ctx, localHashes, gadgetHashes)
|
|
417
427
|
};
|
|
418
428
|
}
|
|
419
429
|
async _getChangesFromGadget({ filesVersion, changes }) {
|
|
@@ -425,7 +435,7 @@ export class FileSync {
|
|
|
425
435
|
const updated = changes.updated();
|
|
426
436
|
let files = [];
|
|
427
437
|
if (created.length > 0 || updated.length > 0) {
|
|
428
|
-
const { fileSyncFiles } = await this.
|
|
438
|
+
const { fileSyncFiles } = await this.edit.query({
|
|
429
439
|
query: FILE_SYNC_FILES_QUERY,
|
|
430
440
|
variables: {
|
|
431
441
|
paths: [
|
|
@@ -443,7 +453,7 @@ export class FileSync {
|
|
|
443
453
|
files,
|
|
444
454
|
delete: changes.deleted()
|
|
445
455
|
});
|
|
446
|
-
printChanges({
|
|
456
|
+
printChanges(this.ctx, {
|
|
447
457
|
changes,
|
|
448
458
|
tense: "past",
|
|
449
459
|
message: sprint`← Received {gray ${dayjs().format("hh:mm:ss A")}}`
|
|
@@ -494,8 +504,8 @@ export class FileSync {
|
|
|
494
504
|
this.ctx.log.debug("skipping send because there are no changes");
|
|
495
505
|
return;
|
|
496
506
|
}
|
|
497
|
-
const { publishFileSyncEvents: { remoteFilesVersion } } = await this.
|
|
498
|
-
|
|
507
|
+
const { publishFileSyncEvents: { remoteFilesVersion } } = await this.edit.mutate({
|
|
508
|
+
mutation: PUBLISH_FILE_SYNC_EVENTS_MUTATION,
|
|
499
509
|
variables: {
|
|
500
510
|
input: {
|
|
501
511
|
expectedRemoteFilesVersion: String(expectedFilesVersion),
|
|
@@ -520,7 +530,7 @@ export class FileSync {
|
|
|
520
530
|
}
|
|
521
531
|
}
|
|
522
532
|
});
|
|
523
|
-
printChanges({
|
|
533
|
+
printChanges(this.ctx, {
|
|
524
534
|
changes,
|
|
525
535
|
tense: "past",
|
|
526
536
|
message: sprint`→ Sent {gray ${dayjs().format("hh:mm:ss A")}}`,
|
|
@@ -561,7 +571,9 @@ export class FileSync {
|
|
|
561
571
|
swallowEnoent(error);
|
|
562
572
|
}
|
|
563
573
|
}, {
|
|
564
|
-
|
|
574
|
+
// windows tends to run into these issues way more often than
|
|
575
|
+
// mac/linux, so we retry more times
|
|
576
|
+
retries: config.windows ? 4 : 2,
|
|
565
577
|
minTimeout: ms("100ms"),
|
|
566
578
|
onFailedAttempt: (error)=>{
|
|
567
579
|
this.ctx.log.warn("failed to move file to backup", {
|
|
@@ -595,7 +607,7 @@ export class FileSync {
|
|
|
595
607
|
}
|
|
596
608
|
});
|
|
597
609
|
await this._save(String(filesVersion));
|
|
598
|
-
|
|
610
|
+
const changes = new Changes([
|
|
599
611
|
...created.map((path)=>[
|
|
600
612
|
path,
|
|
601
613
|
{
|
|
@@ -615,19 +627,29 @@ export class FileSync {
|
|
|
615
627
|
}
|
|
616
628
|
])
|
|
617
629
|
]);
|
|
630
|
+
if (changes.has("yarn.lock")) {
|
|
631
|
+
this.ctx.log.info("running yarn install --check-files");
|
|
632
|
+
await execa("yarn", [
|
|
633
|
+
"install",
|
|
634
|
+
"--check-files"
|
|
635
|
+
], {
|
|
636
|
+
cwd: this.directory.path
|
|
637
|
+
}).then(()=>this.ctx.log.info("yarn install complete")).catch((error)=>this.ctx.log.error("yarn install failed", {
|
|
638
|
+
error
|
|
639
|
+
}));
|
|
640
|
+
}
|
|
641
|
+
return changes;
|
|
618
642
|
}
|
|
619
643
|
/**
|
|
620
|
-
* Updates {@linkcode
|
|
644
|
+
* Updates {@linkcode _syncJson} and saves it to `.gadget/sync.json`.
|
|
621
645
|
*/ async _save(filesVersion) {
|
|
622
|
-
this.
|
|
623
|
-
...this.
|
|
646
|
+
this._syncJson = {
|
|
647
|
+
...this._syncJson,
|
|
624
648
|
mtime: Date.now() + 1,
|
|
625
649
|
filesVersion: String(filesVersion)
|
|
626
650
|
};
|
|
627
|
-
this.ctx.log.debug("saving
|
|
628
|
-
|
|
629
|
-
});
|
|
630
|
-
await fs.outputJSON(this.directory.absolute(".gadget/sync.json"), this._state, {
|
|
651
|
+
this.ctx.log.debug("saving .gadget/sync.json");
|
|
652
|
+
await fs.outputJSON(this.directory.absolute(".gadget/sync.json"), this._syncJson, {
|
|
631
653
|
spaces: 2
|
|
632
654
|
});
|
|
633
655
|
}
|
|
@@ -642,12 +664,12 @@ export class FileSync {
|
|
|
642
664
|
* The state of the filesystem.
|
|
643
665
|
*
|
|
644
666
|
* This is persisted to `.gadget/sync.json` within the {@linkcode directory}.
|
|
645
|
-
*/
|
|
667
|
+
*/ _syncJson){
|
|
646
668
|
_define_property(this, "ctx", void 0);
|
|
647
669
|
_define_property(this, "directory", void 0);
|
|
648
670
|
_define_property(this, "app", void 0);
|
|
649
|
-
_define_property(this, "
|
|
650
|
-
_define_property(this, "
|
|
671
|
+
_define_property(this, "_syncJson", void 0);
|
|
672
|
+
_define_property(this, "edit", void 0);
|
|
651
673
|
/**
|
|
652
674
|
* A FIFO async callback queue that ensures we process filesync events
|
|
653
675
|
* in the order we receive them.
|
|
@@ -655,11 +677,19 @@ export class FileSync {
|
|
|
655
677
|
this.ctx = ctx;
|
|
656
678
|
this.directory = directory;
|
|
657
679
|
this.app = app;
|
|
658
|
-
this.
|
|
680
|
+
this._syncJson = _syncJson;
|
|
659
681
|
this._syncOperations = new PQueue({
|
|
660
682
|
concurrency: 1
|
|
661
683
|
});
|
|
662
|
-
this.
|
|
684
|
+
this.ctx = ctx.child({
|
|
685
|
+
fields: ()=>({
|
|
686
|
+
filesync: {
|
|
687
|
+
directory: this.directory.path,
|
|
688
|
+
filesVersion: this.filesVersion
|
|
689
|
+
}
|
|
690
|
+
})
|
|
691
|
+
});
|
|
692
|
+
this.edit = new Edit(this.ctx);
|
|
663
693
|
}
|
|
664
694
|
}
|
|
665
695
|
/**
|
|
@@ -714,7 +744,7 @@ export const FileSyncArgs = {
|
|
|
714
744
|
"--force": Boolean
|
|
715
745
|
};
|
|
716
746
|
export const isFilesVersionMismatchError = (error)=>{
|
|
717
|
-
if (error instanceof
|
|
747
|
+
if (error instanceof EditError) {
|
|
718
748
|
error = error.cause;
|
|
719
749
|
}
|
|
720
750
|
if (isGraphQLResult(error)) {
|