@salesforce/source-tracking 1.1.7 → 1.3.1

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/CHANGELOG.md CHANGED
@@ -2,6 +2,29 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
4
4
 
5
+ ### [1.3.1](https://github.com/forcedotcom/source-tracking/compare/v1.3.0...v1.3.1) (2022-03-25)
6
+
7
+ ### Bug Fixes
8
+
9
+ - tracking really large repos in chunks, lower limit for windows ([0cb2ce5](https://github.com/forcedotcom/source-tracking/commit/0cb2ce5f3b65b8be9f4e4210aba010e919f692a3))
10
+
11
+ ## [1.3.0](https://github.com/forcedotcom/source-tracking/compare/v1.2.0...v1.3.0) (2022-03-25)
12
+
13
+ ### Features
14
+
15
+ - gracful-fs for EMFILE: too many open files ([1573828](https://github.com/forcedotcom/source-tracking/commit/1573828f5b3cf5f4f8b2023ff31b5764214d4b06))
16
+
17
+ ## [1.2.0](https://github.com/forcedotcom/source-tracking/compare/v1.1.7...v1.2.0) (2022-03-23)
18
+
19
+ ### Features
20
+
21
+ - let isogit deal with ignore files ([#135](https://github.com/forcedotcom/source-tracking/issues/135)) ([1ddb2cd](https://github.com/forcedotcom/source-tracking/commit/1ddb2cdb8f23688f7bb4876a893097a85581f4c1))
22
+
23
+ ### Bug Fixes
24
+
25
+ - ga tracking commands compatibility ([3a31a0d](https://github.com/forcedotcom/source-tracking/commit/3a31a0de448993c643ea4661a7a37772e46e8f51))
26
+ - support pkgDir with ./foo ([3b46454](https://github.com/forcedotcom/source-tracking/commit/3b46454b3e57f653cbe80c66fbfee1cac121c2a8))
27
+
5
28
  ### [1.1.7](https://github.com/forcedotcom/source-tracking/compare/v1.1.6...v1.1.7) (2022-03-16)
6
29
 
7
30
  ### Bug Fixes
@@ -57,13 +57,18 @@ const throwIfInvalid = ({ org, projectPath, toValidate, command, }) => {
57
57
  throw new core_1.SfdxError(`${messages.getMessage('sourceTrackingFileVersionMismatch', ['new/beta'])}\n\nTry this:\n${messages.getMessage('useOtherVersion', ['new/beta', (0, exports.replaceRenamedCommands)(command.replace(':legacy', ''))])}.\n${messages.getMessage('clearSuggestion', [
58
58
  'new/beta',
59
59
  (0, exports.replaceRenamedCommands)('sfdx force:source:tracking:clear'),
60
+ (0, exports.replaceRenamedCommands)('sfdx force:source:tracking:reset', true),
60
61
  ])}.`, 'SourceTrackingFileVersionMismatch');
61
62
  }
62
63
  // We expected it to be the plugin-source version but it is using the old tracking files
63
64
  if (toValidate === 'plugin-source') {
64
65
  throw new core_1.SfdxError(messages.getMessage('sourceTrackingFileVersionMismatch', ['old/legacy']), 'SourceTrackingFileVersionMismatch', [
65
66
  messages.getMessage('useOtherVersion', ['old/legacy', (0, exports.replaceRenamedCommands)(command, true)]),
66
- messages.getMessage('clearSuggestion', ['old/legacy', 'sfdx force:source:tracking:clear']),
67
+ messages.getMessage('clearSuggestion', [
68
+ 'old/legacy',
69
+ 'sfdx force:source:legacy:tracking:clear',
70
+ 'sfdx force:source:tracking:reset',
71
+ ]),
67
72
  ]);
68
73
  }
69
74
  };
@@ -82,10 +87,10 @@ const replaceRenamedCommands = (input, reverse = false) => {
82
87
  };
83
88
  exports.replaceRenamedCommands = replaceRenamedCommands;
84
89
  const renames = new Map([
85
- ['force:source:status', 'force:source:beta:status'],
86
- ['force:source:push', 'force:source:beta:push'],
87
- ['force:source:pull', 'force:source:beta:pull'],
88
- ['force:source:tracking:reset', 'force:source:beta:tracking:reset'],
89
- ['force:source:tracking:clear', 'force:source:beta:tracking:clear'],
90
+ ['force:source:legacy:status', 'force:source:status'],
91
+ ['force:source:legacy:push', 'force:source:push'],
92
+ ['force:source:legacy:pull', 'force:source:pull'],
93
+ ['force:source:legacy:tracking:reset', 'force:source:tracking:reset'],
94
+ ['force:source:legacy:tracking:clear', 'force:source:tracking:clear'],
90
95
  ]);
91
96
  //# sourceMappingURL=compatibility.js.map
@@ -8,3 +8,4 @@ export declare const isBundle: (cmp: SourceComponent) => boolean;
8
8
  * ex: '/foo/bar-extra/baz'.startsWith('foo/bar') would be true, but this function understands that they are not in the same folder
9
9
  */
10
10
  export declare const pathIsInFolder: (filePath: string, folder: string) => boolean;
11
+ export declare const chunkArray: <T>(arr: T[], size: number) => T[][];
@@ -6,7 +6,7 @@
6
6
  * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
7
7
  */
8
8
  Object.defineProperty(exports, "__esModule", { value: true });
9
- exports.pathIsInFolder = exports.isBundle = exports.getKeyFromObject = exports.getMetadataKey = void 0;
9
+ exports.chunkArray = exports.pathIsInFolder = exports.isBundle = exports.getKeyFromObject = exports.getMetadataKey = void 0;
10
10
  const path_1 = require("path");
11
11
  const ts_types_1 = require("@salesforce/ts-types");
12
12
  const getMetadataKey = (metadataType, metadataName) => {
@@ -37,4 +37,7 @@ exports.pathIsInFolder = pathIsInFolder;
37
37
  const nonEmptyStringFilter = (value) => {
38
38
  return (0, ts_types_1.isString)(value) && value.length > 0;
39
39
  };
40
+ // adapted for TS from https://github.com/30-seconds/30-seconds-of-code/blob/master/snippets/chunk.md
41
+ const chunkArray = (arr, size) => Array.from({ length: Math.ceil(arr.length / size) }, (v, i) => arr.slice(i * size, i * size + size));
42
+ exports.chunkArray = chunkArray;
40
43
  //# sourceMappingURL=functions.js.map
@@ -18,7 +18,9 @@ export declare class ShadowRepo {
18
18
  private packageDirs;
19
19
  private status;
20
20
  private logger;
21
- private gitIgnoreLocations;
21
+ private isWindows;
22
+ /** do not try to add more than this many files at a time through isogit. You'll hit EMFILE: too many open files even with graceful-fs */
23
+ private maxFileAdd;
22
24
  private constructor();
23
25
  static getInstance(options: ShadowRepoOptions): Promise<ShadowRepo>;
24
26
  init(): Promise<void>;
@@ -76,8 +78,5 @@ export declare class ShadowRepo {
76
78
  * @returns sha (string)
77
79
  */
78
80
  commitChanges({ deployedFiles, deletedFiles, message, needsUpdatedStatus, }?: CommitRequest): Promise<string>;
79
- private locateIgnoreFiles;
80
- private stashIgnoreFile;
81
- private unStashIgnoreFile;
82
81
  }
83
82
  export {};
@@ -5,20 +5,15 @@
5
5
  * Licensed under the BSD 3-Clause license.
6
6
  * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
7
7
  */
8
- /* eslint-disable no-console */
9
8
  Object.defineProperty(exports, "__esModule", { value: true });
10
9
  exports.ShadowRepo = void 0;
11
10
  const path = require("path");
12
11
  const os = require("os");
13
- const fs = require("fs");
12
+ const fs = require("graceful-fs");
14
13
  const core_1 = require("@salesforce/core");
15
14
  const git = require("isomorphic-git");
16
15
  const functions_1 = require("./functions");
17
- const gitIgnoreFileName = '.gitignore';
18
- const stashedGitIgnoreFileName = '.BAK.gitignore';
19
- /**
20
- * returns the full path to where we store the shadow repo
21
- */
16
+ /** returns the full path to where we store the shadow repo */
22
17
  const getGitDir = (orgId, projectPath) => {
23
18
  return path.join(projectPath, '.sfdx', 'orgs', orgId, 'localSourceTracking');
24
19
  };
@@ -30,10 +25,11 @@ const HEAD = 1;
30
25
  const WORKDIR = 2;
31
26
  class ShadowRepo {
32
27
  constructor(options) {
33
- this.gitIgnoreLocations = [];
34
28
  this.gitDir = getGitDir(options.orgId, options.projectPath);
35
29
  this.projectPath = options.projectPath;
36
30
  this.packageDirs = options.packageDirs;
31
+ this.isWindows = os.type() === 'Windows_NT';
32
+ this.maxFileAdd = this.isWindows ? 8000 : 15000;
37
33
  }
38
34
  // think of singleton behavior but unique to the projectPath
39
35
  static async getInstance(options) {
@@ -51,7 +47,6 @@ class ShadowRepo {
51
47
  this.logger.debug('initializing git repo');
52
48
  await this.gitInit();
53
49
  }
54
- await this.locateIgnoreFiles();
55
50
  }
56
51
  /**
57
52
  * Initialize a new source tracking shadow repo. Think of git init
@@ -86,37 +81,32 @@ class ShadowRepo {
86
81
  */
87
82
  async getStatus(noCache = false) {
88
83
  if (!this.status || noCache) {
89
- try {
90
- // only ask about OS once but use twice
91
- const isWindows = os.type() === 'Windows_NT';
92
- await this.stashIgnoreFile();
93
- const filepaths = isWindows
94
- ? // iso-git uses posix paths, but packageDirs has already normalized them so we need to convert if windows
95
- this.packageDirs.map((dir) => dir.path.split(path.sep).join(path.posix.sep))
96
- : this.packageDirs.map((dir) => dir.path);
97
- // status hasn't been initalized yet
98
- this.status = await git.statusMatrix({
99
- fs,
100
- dir: this.projectPath,
101
- gitdir: this.gitDir,
102
- filepaths,
103
- filter: (f) =>
104
- // no hidden files
105
- !f.includes(`${path.sep}.`) &&
106
- // no lwc tests
107
- !f.includes('__tests__') &&
108
- // no gitignore files
109
- ![gitIgnoreFileName, stashedGitIgnoreFileName].includes(path.basename(f)) &&
110
- // isogit uses `startsWith` for filepaths so it's possible to get a false positive
111
- filepaths.some((pkgDir) => (0, functions_1.pathIsInFolder)(f, pkgDir)),
112
- });
113
- // isomorphic-git stores things in unix-style tree. Convert to windows-style if necessary
114
- if (isWindows) {
115
- this.status = this.status.map((row) => [path.normalize(row[FILE]), row[HEAD], row[WORKDIR], row[3]]);
116
- }
117
- }
118
- finally {
119
- await this.unStashIgnoreFile();
84
+ // iso-git uses relative, posix paths
85
+ // but packageDirs has already resolved / normalized them
86
+ // so we need to make them project-relative again and convert if windows
87
+ const filepaths = this.packageDirs
88
+ .map((dir) => path.relative(this.projectPath, dir.fullPath))
89
+ .map((p) => (this.isWindows ? p.split(path.sep).join(path.posix.sep) : p));
90
+ // status hasn't been initalized yet
91
+ this.status = await git.statusMatrix({
92
+ fs,
93
+ dir: this.projectPath,
94
+ gitdir: this.gitDir,
95
+ filepaths,
96
+ ignored: true,
97
+ filter: (f) =>
98
+ // no hidden files
99
+ !f.includes(`${path.sep}.`) &&
100
+ // no lwc tests
101
+ !f.includes('__tests__') &&
102
+ // no gitignore files
103
+ !f.endsWith('.gitignore') &&
104
+ // isogit uses `startsWith` for filepaths so it's possible to get a false positive
105
+ filepaths.some((pkgDir) => (0, functions_1.pathIsInFolder)(f, pkgDir)),
106
+ });
107
+ // isomorphic-git stores things in unix-style tree. Convert to windows-style if necessary
108
+ if (this.isWindows) {
109
+ this.status = this.status.map((row) => [path.normalize(row[FILE]), row[HEAD], row[WORKDIR], row[3]]);
120
110
  }
121
111
  }
122
112
  return this.status;
@@ -180,56 +170,49 @@ class ShadowRepo {
180
170
  // this is valid, might not be an error
181
171
  return 'no files to commit';
182
172
  }
183
- await this.stashIgnoreFile();
184
173
  // these are stored in posix/style/path format. We have to convert inbound stuff from windows
185
174
  if (os.type() === 'Windows_NT') {
186
175
  deployedFiles = deployedFiles.map((filepath) => path.normalize(filepath).split(path.sep).join(path.posix.sep));
187
176
  deletedFiles = deletedFiles.map((filepath) => path.normalize(filepath).split(path.sep).join(path.posix.sep));
188
177
  }
189
- try {
190
- if (deployedFiles.length) {
191
- await git.add({ fs, dir: this.projectPath, gitdir: this.gitDir, filepath: [...new Set(deployedFiles)] });
192
- }
193
- for (const filepath of [...new Set(deletedFiles)]) {
194
- await git.remove({ fs, dir: this.projectPath, gitdir: this.gitDir, filepath });
195
- }
196
- const sha = await git.commit({
197
- fs,
198
- dir: this.projectPath,
199
- gitdir: this.gitDir,
200
- message,
201
- author: { name: 'sfdx source tracking' },
202
- });
203
- // status changed as a result of the commit. This prevents users from having to run getStatus(true) to avoid cache
204
- if (needsUpdatedStatus) {
205
- await this.getStatus(true);
178
+ if (deployedFiles.length) {
179
+ const chunks = (0, functions_1.chunkArray)([...new Set(deployedFiles)], this.maxFileAdd);
180
+ for (const chunk of chunks) {
181
+ try {
182
+ await git.add({
183
+ fs,
184
+ dir: this.projectPath,
185
+ gitdir: this.gitDir,
186
+ filepath: chunk,
187
+ force: true,
188
+ });
189
+ }
190
+ catch (e) {
191
+ if (e instanceof git.Errors.MultipleGitError) {
192
+ this.logger.error('multiple errors on git.add', e.errors.slice(0, 5));
193
+ const error = new core_1.SfdxError(e.message, e.name, [], 1);
194
+ error.setData(e.errors);
195
+ throw error;
196
+ }
197
+ throw e;
198
+ }
206
199
  }
207
- return sha;
208
200
  }
209
- finally {
210
- await this.unStashIgnoreFile();
201
+ for (const filepath of [...new Set(deletedFiles)]) {
202
+ await git.remove({ fs, dir: this.projectPath, gitdir: this.gitDir, filepath });
211
203
  }
212
- }
213
- async locateIgnoreFiles() {
214
- // set the gitIgnoreLocations so we only have to do it once
215
- this.gitIgnoreLocations = (await git.walk({
204
+ const sha = await git.commit({
216
205
  fs,
217
206
  dir: this.projectPath,
218
207
  gitdir: this.gitDir,
219
- trees: [git.WORKDIR()],
220
- // eslint-disable-next-line @typescript-eslint/require-await
221
- map: async (filepath) => filepath,
222
- }))
223
- .filter((filepath) => filepath.includes(gitIgnoreFileName))
224
- .map((ignoreFile) => path.join(this.projectPath, ignoreFile));
225
- }
226
- async stashIgnoreFile() {
227
- // allSettled allows them to fail (example, the file wasn't where it was expected).
228
- await Promise.allSettled(this.gitIgnoreLocations.map((originalLocation) => fs.promises.rename(originalLocation, originalLocation.replace(gitIgnoreFileName, stashedGitIgnoreFileName))));
229
- }
230
- async unStashIgnoreFile() {
231
- // allSettled allows them to fail (example, the file wasn't where it was expected).
232
- await Promise.allSettled(this.gitIgnoreLocations.map((originalLocation) => fs.promises.rename(originalLocation.replace(gitIgnoreFileName, stashedGitIgnoreFileName), originalLocation)));
208
+ message,
209
+ author: { name: 'sfdx source tracking' },
210
+ });
211
+ // status changed as a result of the commit. This prevents users from having to run getStatus(true) to avoid cache
212
+ if (needsUpdatedStatus) {
213
+ await this.getStatus(true);
214
+ }
215
+ return sha;
233
216
  }
234
217
  }
235
218
  exports.ShadowRepo = ShadowRepo;
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "sourceTrackingFileVersionMismatch": "This project uses the %s version of source tracking files.",
3
- "clearSuggestion": "Clear the %s version of the tracking files by running '%s'",
3
+ "clearSuggestion": "Push/Pull any local or remote changes. Then, clear the %s version of the tracking files by running '%s' followed by '%s'.",
4
4
  "useOtherVersion": "Use the %s version of the command, '%s' with your existing tracking files."
5
5
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@salesforce/source-tracking",
3
3
  "description": "API for tracking local and remote Salesforce metadata changes",
4
- "version": "1.1.7",
4
+ "version": "1.3.1",
5
5
  "author": "Salesforce",
6
6
  "license": "BSD-3-Clause",
7
7
  "main": "lib/index.js",
@@ -46,7 +46,8 @@
46
46
  "@salesforce/core": "^2.33.1",
47
47
  "@salesforce/kit": "^1.5.17",
48
48
  "@salesforce/source-deploy-retrieve": "^5.9.4",
49
- "isomorphic-git": "1.14.0",
49
+ "graceful-fs": "^4.2.9",
50
+ "isomorphic-git": "1.17.0",
50
51
  "ts-retry-promise": "^0.6.0"
51
52
  },
52
53
  "devDependencies": {