@lage-run/hasher 1.2.0 → 1.2.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.json CHANGED
@@ -2,82 +2,16 @@
2
2
  "name": "@lage-run/hasher",
3
3
  "entries": [
4
4
  {
5
- "date": "Tue, 25 Jun 2024 18:10:41 GMT",
6
- "version": "1.2.0",
7
- "tag": "@lage-run/hasher_v1.2.0",
8
- "comments": {
9
- "minor": [
10
- {
11
- "author": "kchau@microsoft.com",
12
- "package": "@lage-run/hasher",
13
- "commit": "e294f805313ceb5b66640f8aaff8647bdb588511",
14
- "comment": "moving back to using globby instead for speed & accuracy reasons"
15
- },
16
- {
17
- "author": "beachball",
18
- "package": "@lage-run/hasher",
19
- "comment": "Bump @lage-run/globby to v14.0.2",
20
- "commit": "not available"
21
- }
22
- ]
23
- }
24
- },
25
- {
26
- "date": "Mon, 10 Jun 2024 23:50:39 GMT",
27
- "version": "1.1.2",
28
- "tag": "@lage-run/hasher_v1.1.2",
5
+ "date": "Tue, 25 Jun 2024 22:03:26 GMT",
6
+ "version": "1.2.1",
7
+ "tag": "@lage-run/hasher_v1.2.1",
29
8
  "comments": {
30
9
  "patch": [
31
10
  {
32
11
  "author": "kchau@microsoft.com_msteamsmdb",
33
12
  "package": "@lage-run/hasher",
34
- "commit": "0894d97ebfedc339b77161c40fa8643d93e4486d",
35
- "comment": "matching correct glob and exclude patterns"
36
- }
37
- ]
38
- }
39
- },
40
- {
41
- "date": "Sun, 05 May 2024 22:55:45 GMT",
42
- "version": "1.1.1",
43
- "tag": "@lage-run/hasher_v1.1.1",
44
- "comments": {
45
- "patch": [
46
- {
47
- "author": "kchau@microsoft.com",
48
- "package": "@lage-run/hasher",
49
- "commit": "1e36de04ab83fc0cde38062fc1543e4b12902166",
50
- "comment": "fixing hashing issues related to rust panic"
51
- }
52
- ]
53
- }
54
- },
55
- {
56
- "date": "Wed, 17 Apr 2024 23:20:47 GMT",
57
- "version": "1.1.0",
58
- "tag": "@lage-run/hasher_v1.1.0",
59
- "comments": {
60
- "none": [
61
- {
62
- "author": "elcraig@microsoft.com",
63
- "package": "@lage-run/hasher",
64
- "commit": "fb4fcb8419cc778210104d7d04102fc95df13d5b",
65
- "comment": "Update formatting"
66
- }
67
- ]
68
- }
69
- },
70
- {
71
- "date": "Fri, 15 Mar 2024 04:35:11 GMT",
72
- "version": "1.1.0",
73
- "tag": "@lage-run/hasher_v1.1.0",
74
- "comments": {
75
- "minor": [
76
- {
77
- "author": "kchau@microsoft.com",
78
- "package": "@lage-run/hasher",
79
- "commit": "71283d4af8888454c953e5228c97bfbb471c15ba",
80
- "comment": "perf optimizations"
13
+ "commit": "440bbd19c317982a187383a5a35befbea71f22c5",
14
+ "comment": "reverting all the hasher changes"
81
15
  }
82
16
  ]
83
17
  }
package/CHANGELOG.md CHANGED
@@ -1,41 +1,16 @@
1
1
  # Change Log - @lage-run/hasher
2
2
 
3
- This log was last generated on Tue, 25 Jun 2024 18:10:41 GMT and should not be manually modified.
3
+ This log was last generated on Tue, 25 Jun 2024 22:03:26 GMT and should not be manually modified.
4
4
 
5
5
  <!-- Start content -->
6
6
 
7
- ## 1.2.0
7
+ ## 1.2.1
8
8
 
9
- Tue, 25 Jun 2024 18:10:41 GMT
10
-
11
- ### Minor changes
12
-
13
- - moving back to using globby instead for speed & accuracy reasons (kchau@microsoft.com)
14
- - Bump @lage-run/globby to v14.0.2
15
-
16
- ## 1.1.2
17
-
18
- Mon, 10 Jun 2024 23:50:39 GMT
9
+ Tue, 25 Jun 2024 22:03:26 GMT
19
10
 
20
11
  ### Patches
21
12
 
22
- - matching correct glob and exclude patterns (kchau@microsoft.com_msteamsmdb)
23
-
24
- ## 1.1.1
25
-
26
- Sun, 05 May 2024 22:55:45 GMT
27
-
28
- ### Patches
29
-
30
- - fixing hashing issues related to rust panic (kchau@microsoft.com)
31
-
32
- ## 1.1.0
33
-
34
- Fri, 15 Mar 2024 04:35:11 GMT
35
-
36
- ### Minor changes
37
-
38
- - perf optimizations (kchau@microsoft.com)
13
+ - reverting all the hasher changes (kchau@microsoft.com_msteamsmdb)
39
14
 
40
15
  ## 1.0.7
41
16
 
@@ -1,16 +1,20 @@
1
- export interface LocalPackageTreeOptions {
1
+ import { type PackageInfos } from "workspace-tools";
2
+ export interface PackageTreeOptions {
2
3
  root: string;
4
+ packageInfos: PackageInfos;
3
5
  includeUntracked: boolean;
4
6
  }
5
7
  /**
6
- * Keeps a data structure to quickly find all files in a package.
8
+ * Package Tree keeps a data structure to quickly find all files in a package.
9
+ *
10
+ * TODO: add a watcher to make sure the tree is up to date during a "watched" run.
7
11
  */
8
12
  export declare class PackageTree {
9
13
  #private;
10
14
  private options;
11
- constructor(options: LocalPackageTreeOptions);
15
+ constructor(options: PackageTreeOptions);
12
16
  reset(): void;
13
17
  initialize(): Promise<void>;
14
- findFilesInPath(packagePath: string, patterns: string[]): Promise<string[]>;
15
- cleanup(): void;
18
+ addToPackageTree(filePaths: string[]): Promise<void>;
19
+ getPackageFiles(packageName: string, patterns: string[]): string[];
16
20
  }
@@ -8,8 +8,10 @@ Object.defineProperty(exports, "PackageTree", {
8
8
  return PackageTree;
9
9
  }
10
10
  });
11
+ const _execa = /*#__PURE__*/ _interop_require_default(require("execa"));
11
12
  const _path = /*#__PURE__*/ _interop_require_default(require("path"));
12
- const _globby = require("@lage-run/globby");
13
+ const _fs = /*#__PURE__*/ _interop_require_default(require("fs"));
14
+ const _micromatch = /*#__PURE__*/ _interop_require_default(require("micromatch"));
13
15
  function _check_private_redeclaration(obj, privateCollection) {
14
16
  if (privateCollection.has(obj)) {
15
17
  throw new TypeError("Cannot initialize the same private elements twice on an object");
@@ -50,16 +52,6 @@ function _class_private_field_set(receiver, privateMap, value) {
50
52
  _class_apply_descriptor_set(receiver, descriptor, value);
51
53
  return value;
52
54
  }
53
- function _class_private_method_get(receiver, privateSet, fn) {
54
- if (!privateSet.has(receiver)) {
55
- throw new TypeError("attempted to get private field on non-instance");
56
- }
57
- return fn;
58
- }
59
- function _class_private_method_init(obj, privateSet) {
60
- _check_private_redeclaration(obj, privateSet);
61
- privateSet.add(obj);
62
- }
63
55
  function _define_property(obj, key, value) {
64
56
  if (key in obj) {
65
57
  Object.defineProperty(obj, key, {
@@ -78,43 +70,110 @@ function _interop_require_default(obj) {
78
70
  default: obj
79
71
  };
80
72
  }
81
- var _memoizedPackageFiles = /*#__PURE__*/ new WeakMap(), _findFilesFromGitTree = /*#__PURE__*/ new WeakSet();
73
+ var _tree = /*#__PURE__*/ new WeakMap(), _packageFiles = /*#__PURE__*/ new WeakMap(), _memoizedPackageFiles = /*#__PURE__*/ new WeakMap();
82
74
  class PackageTree {
83
75
  reset() {
76
+ _class_private_field_set(this, _tree, {});
77
+ _class_private_field_set(this, _packageFiles, {});
84
78
  _class_private_field_set(this, _memoizedPackageFiles, {});
85
79
  }
86
80
  async initialize() {
81
+ const { root , includeUntracked , packageInfos } = this.options;
87
82
  this.reset();
83
+ // Generate path tree of all packages in workspace (scale: ~2000 * ~3)
84
+ for (const info of Object.values(packageInfos)){
85
+ const packagePath = _path.default.dirname(info.packageJsonPath);
86
+ const pathParts = _path.default.relative(root, packagePath).split(/[\\/]/);
87
+ let currentNode = _class_private_field_get(this, _tree);
88
+ for (const part of pathParts){
89
+ currentNode[part] = currentNode[part] || {};
90
+ currentNode = currentNode[part];
91
+ }
92
+ }
93
+ // Get all files in the workspace (scale: ~2000) according to git
94
+ const lsFilesResults = await (0, _execa.default)("git", [
95
+ "ls-files",
96
+ "-z"
97
+ ], {
98
+ cwd: root
99
+ });
100
+ if (lsFilesResults.exitCode === 0) {
101
+ const files = lsFilesResults.stdout.split("\0").filter((f)=>Boolean(f) && _fs.default.existsSync(_path.default.join(root, f)));
102
+ this.addToPackageTree(files);
103
+ }
104
+ if (includeUntracked) {
105
+ // Also get all untracked files in the workspace according to git
106
+ const lsOtherResults = await (0, _execa.default)("git", [
107
+ "ls-files",
108
+ "-o",
109
+ "--exclude-standard"
110
+ ], {
111
+ cwd: root
112
+ });
113
+ if (lsOtherResults.exitCode === 0) {
114
+ const files = lsOtherResults.stdout.split("\0").filter(Boolean);
115
+ this.addToPackageTree(files);
116
+ }
117
+ }
118
+ }
119
+ async addToPackageTree(filePaths) {
120
+ // key: path/to/package (packageRoot), value: array of a tuple of [file, hash]
121
+ const packageFiles = _class_private_field_get(this, _packageFiles);
122
+ for (const entry of filePaths){
123
+ const pathParts = entry.split(/[\\/]/);
124
+ let node = _class_private_field_get(this, _tree);
125
+ const packagePathParts = [];
126
+ for (const part of pathParts){
127
+ if (node[part]) {
128
+ node = node[part];
129
+ packagePathParts.push(part);
130
+ } else {
131
+ break;
132
+ }
133
+ }
134
+ const packageRoot = packagePathParts.join("/");
135
+ packageFiles[packageRoot] = packageFiles[packageRoot] || [];
136
+ packageFiles[packageRoot].push(entry);
137
+ }
88
138
  }
89
- async findFilesInPath(packagePath, patterns) {
90
- const key = `${packagePath}\0${patterns.join("\0")}`;
139
+ getPackageFiles(packageName, patterns) {
140
+ const { root , packageInfos } = this.options;
141
+ const packagePath = _path.default.relative(root, _path.default.dirname(packageInfos[packageName].packageJsonPath)).replace(/\\/g, "/");
142
+ const packageFiles = _class_private_field_get(this, _packageFiles)[packagePath];
143
+ if (!packageFiles) {
144
+ return [];
145
+ }
146
+ const key = `${packageName}\0${patterns.join("\0")}`;
91
147
  if (!_class_private_field_get(this, _memoizedPackageFiles)[key]) {
92
- const files = await _class_private_method_get(this, _findFilesFromGitTree, findFilesFromGitTree).call(this, packagePath, patterns);
93
- _class_private_field_get(this, _memoizedPackageFiles)[key] = files;
148
+ const packagePatterns = patterns.map((pattern)=>{
149
+ if (pattern.startsWith("!")) {
150
+ return `!${_path.default.join(packagePath, pattern.slice(1)).replace(/\\/g, "/")}`;
151
+ }
152
+ return _path.default.join(packagePath, pattern).replace(/\\/g, "/");
153
+ });
154
+ _class_private_field_get(this, _memoizedPackageFiles)[key] = (0, _micromatch.default)(packageFiles, packagePatterns, {
155
+ dot: true
156
+ });
94
157
  }
95
158
  return _class_private_field_get(this, _memoizedPackageFiles)[key];
96
159
  }
97
- cleanup() {}
98
160
  constructor(options){
99
- _class_private_method_init(this, _findFilesFromGitTree);
100
161
  _define_property(this, "options", void 0);
162
+ _class_private_field_init(this, _tree, {
163
+ writable: true,
164
+ value: void 0
165
+ });
166
+ _class_private_field_init(this, _packageFiles, {
167
+ writable: true,
168
+ value: void 0
169
+ });
101
170
  _class_private_field_init(this, _memoizedPackageFiles, {
102
171
  writable: true,
103
172
  value: void 0
104
173
  });
105
174
  this.options = options;
175
+ _class_private_field_set(this, _tree, {});
176
+ _class_private_field_set(this, _packageFiles, {});
106
177
  _class_private_field_set(this, _memoizedPackageFiles, {});
107
178
  }
108
179
  }
109
- async function findFilesFromGitTree(packagePath, patterns) {
110
- const cwd = _path.default.isAbsolute(packagePath) ? packagePath : _path.default.join(this.options.root, packagePath);
111
- return (0, _globby.globby)(patterns, {
112
- cwd,
113
- onlyFiles: true,
114
- ignore: [
115
- ".git"
116
- ],
117
- gitignore: true,
118
- absolute: true
119
- }) ?? [];
120
- }
@@ -32,7 +32,7 @@ export declare class TargetHasher {
32
32
  private options;
33
33
  logger: Logger | undefined;
34
34
  fileHasher: FileHasher;
35
- packageTree: PackageTree;
35
+ packageTree: PackageTree | undefined;
36
36
  initializedPromise: Promise<unknown> | undefined;
37
37
  packageInfos: PackageInfos;
38
38
  workspaceInfo: WorkspaceInfo | undefined;
@@ -9,6 +9,7 @@ Object.defineProperty(exports, "TargetHasher", {
9
9
  }
10
10
  });
11
11
  const _globhasher = require("glob-hasher");
12
+ const _fastglob = /*#__PURE__*/ _interop_require_default(require("fast-glob"));
12
13
  const _fs = /*#__PURE__*/ _interop_require_default(require("fs"));
13
14
  const _path = /*#__PURE__*/ _interop_require_default(require("path"));
14
15
  const _workspacetools = require("workspace-tools");
@@ -108,25 +109,26 @@ class TargetHasher {
108
109
  await this.initializedPromise;
109
110
  return;
110
111
  }
111
- this.workspaceInfo = (0, _workspacetools.getWorkspaces)(root);
112
- this.packageInfos = this.getPackageInfos(this.workspaceInfo);
113
- this.dependencyMap = (0, _workspacetools.createDependencyMap)(this.packageInfos, {
114
- withDevDependencies: true,
115
- withPeerDependencies: false
116
- });
117
- const finderInitPromise = this.packageTree.initialize();
118
- this.initializedPromise = finderInitPromise.then(()=>{
119
- return Promise.all([
120
- this.fileHasher.readManifest().then(()=>environmentGlob.length > 0 ? this.packageTree.findFilesInPath(root, environmentGlob) : []).then((files)=>{
121
- if (this.logger !== undefined) {
122
- this.logger.verbose(`Environment glob files:`);
123
- this.logger.silly(" " + files.join("\n "));
124
- }
125
- return this.fileHasher.hash(files);
126
- }).then((hash)=>this.globalInputsHash = hash),
127
- (0, _workspacetools.parseLockFile)(root).then((lockInfo)=>this.lockInfo = lockInfo)
128
- ]);
129
- });
112
+ this.initializedPromise = Promise.all([
113
+ this.fileHasher.readManifest().then(()=>(0, _fastglob.default)(environmentGlob, {
114
+ cwd: root
115
+ })).then((files)=>this.fileHasher.hash(files)).then((hash)=>this.globalInputsHash = hash),
116
+ (0, _workspacetools.getWorkspacesAsync)(root).then((workspaceInfo)=>this.workspaceInfo = workspaceInfo).then(()=>{
117
+ this.packageInfos = this.getPackageInfos(this.workspaceInfo);
118
+ this.dependencyMap = (0, _workspacetools.createDependencyMap)(this.packageInfos, {
119
+ withDevDependencies: true,
120
+ withPeerDependencies: false
121
+ });
122
+ this.packageTree = new _PackageTree.PackageTree({
123
+ root,
124
+ packageInfos: this.packageInfos,
125
+ // TODO: (optimization) false if process.env.TF_BUILD || process.env.CI
126
+ includeUntracked: true
127
+ });
128
+ return this.packageTree.initialize();
129
+ }),
130
+ (0, _workspacetools.parseLockFile)(root).then((lockInfo)=>this.lockInfo = lockInfo)
131
+ ]);
130
132
  await this.initializedPromise;
131
133
  if (this.logger !== undefined) {
132
134
  const globalInputsHash = (0, _hashStrings.hashStrings)(Object.values(this.globalInputsHash ?? {}));
@@ -143,11 +145,13 @@ class TargetHasher {
143
145
  if (!target.inputs) {
144
146
  throw new Error("Root-level targets must have `inputs` defined if it has cache enabled.");
145
147
  }
146
- const files = await this.packageTree.findFilesInPath(root, target.inputs);
148
+ const files = await (0, _fastglob.default)(target.inputs, {
149
+ cwd: root
150
+ });
147
151
  const fileFashes = (0, _globhasher.hash)(files, {
148
152
  cwd: root
149
153
  }) ?? {};
150
- const hashes = Object.values(fileFashes).filter((hash)=>hash !== undefined && hash !== null);
154
+ const hashes = Object.values(fileFashes);
151
155
  return (0, _hashStrings.hashStrings)(hashes);
152
156
  }
153
157
  // 1. add hash of target's inputs
@@ -171,9 +175,7 @@ class TargetHasher {
171
175
  const packagePatterns = this.expandInputPatterns(inputs, target);
172
176
  const files = [];
173
177
  for (const [pkg, patterns] of Object.entries(packagePatterns)){
174
- const { root } = this.options;
175
- const packagePath = _path.default.relative(root, _path.default.dirname(this.packageInfos[pkg].packageJsonPath)).replace(/\\/g, "/");
176
- const packageFiles = await this.packageTree.findFilesInPath(packagePath, patterns);
178
+ const packageFiles = this.packageTree.getPackageFiles(pkg, patterns);
177
179
  files.push(...packageFiles);
178
180
  }
179
181
  const fileHashes = this.fileHasher.hash(files) ?? {}; // this list is sorted by file name
@@ -195,8 +197,7 @@ class TargetHasher {
195
197
  return hashString;
196
198
  }
197
199
  async cleanup() {
198
- this.packageTree.cleanup();
199
- this.fileHasher.writeManifest();
200
+ await this.fileHasher.writeManifest();
200
201
  }
201
202
  constructor(options){
202
203
  _define_property(this, "options", void 0);
@@ -222,9 +223,5 @@ class TargetHasher {
222
223
  this.fileHasher = new _FileHasher.FileHasher({
223
224
  root
224
225
  });
225
- this.packageTree = new _PackageTree.PackageTree({
226
- root,
227
- includeUntracked: true
228
- });
229
226
  }
230
227
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lage-run/hasher",
3
- "version": "1.2.0",
3
+ "version": "1.2.1",
4
4
  "description": "Hasher for Lage Targets",
5
5
  "repository": {
6
6
  "url": "https://github.com/microsoft/lage"
@@ -16,12 +16,13 @@
16
16
  },
17
17
  "dependencies": {
18
18
  "@lage-run/target-graph": "^0.8.9",
19
- "@lage-run/globby": "^14.0.2",
20
19
  "@lage-run/logger": "^1.3.0",
21
20
  "execa": "5.1.1",
22
21
  "workspace-tools": "0.36.4",
22
+ "fast-glob": "3.3.1",
23
23
  "glob-hasher": "^1.4.2",
24
- "graceful-fs": "4.2.11"
24
+ "graceful-fs": "4.2.11",
25
+ "micromatch": "4.0.5"
25
26
  },
26
27
  "devDependencies": {
27
28
  "@lage-run/monorepo-fixture": "*",
@@ -1 +0,0 @@
1
- export {};
@@ -1,93 +0,0 @@
1
- // test out the findFilesInPath method of the PackageTree
2
- "use strict";
3
- Object.defineProperty(exports, "__esModule", {
4
- value: true
5
- });
6
- const _monorepofixture = require("@lage-run/monorepo-fixture");
7
- const _PackageTree = require("../PackageTree");
8
- const _path = /*#__PURE__*/ _interop_require_default(require("path"));
9
- function _interop_require_default(obj) {
10
- return obj && obj.__esModule ? obj : {
11
- default: obj
12
- };
13
- }
14
- // given various patterns that exercises the globby functionality with extgob, etc.
15
- describe("PackageTree", ()=>{
16
- async function setupFixture(fixture = "monorepo") {
17
- const monorepo = new _monorepofixture.Monorepo(fixture);
18
- await monorepo.init(_path.default.join(fixturesPath, fixture));
19
- return monorepo;
20
- }
21
- const fixturesPath = _path.default.join(__dirname, "../__fixtures__");
22
- it("should find all files in a package using globby basic pattern", async ()=>{
23
- const monorepo = await setupFixture("basic");
24
- const packagePath = monorepo.root;
25
- const patterns = [
26
- "**/*"
27
- ];
28
- const packageTree = new _PackageTree.PackageTree({
29
- root: packagePath,
30
- includeUntracked: true
31
- });
32
- await packageTree.initialize();
33
- const files = await packageTree.findFilesInPath(packagePath, patterns);
34
- expect(serializeFiles(files, packagePath)).toMatchInlineSnapshot(`
35
- [
36
- "node_modules/package-2/package.json",
37
- "package.json",
38
- "src/index.ts",
39
- "yarn.lock",
40
- ]
41
- `);
42
- monorepo.cleanup();
43
- });
44
- it("should find all files in a package using globby with extglob", async ()=>{
45
- const monorepo = await setupFixture("basic");
46
- const packagePath = monorepo.root;
47
- const patterns = [
48
- "**/*.+(json|ts)",
49
- "!yarn.lock"
50
- ];
51
- const packageTree = new _PackageTree.PackageTree({
52
- root: packagePath,
53
- includeUntracked: true
54
- });
55
- await packageTree.initialize();
56
- const files = await packageTree.findFilesInPath(packagePath, patterns);
57
- expect(serializeFiles(files, packagePath)).toMatchInlineSnapshot(`
58
- [
59
- "node_modules/package-2/package.json",
60
- "package.json",
61
- "src/index.ts",
62
- ]
63
- `);
64
- monorepo.cleanup();
65
- });
66
- it("should find all files in a package using globby with extglob of sub-exclusion pattern", async ()=>{
67
- const monorepo = await setupFixture("monorepo-with-deps");
68
- const packagePath = monorepo.root;
69
- const patterns = [
70
- "**/!(package.json)"
71
- ];
72
- const packageTree = new _PackageTree.PackageTree({
73
- root: packagePath,
74
- includeUntracked: true
75
- });
76
- await packageTree.initialize();
77
- const files = await packageTree.findFilesInPath(packagePath, patterns);
78
- expect(serializeFiles(files, packagePath)).toMatchInlineSnapshot(`
79
- [
80
- "node_modules/.yarn-integrity",
81
- "node_modules/package-a/src/index.ts",
82
- "node_modules/package-b/src/index.ts",
83
- "packages/package-a/src/index.ts",
84
- "packages/package-b/src/index.ts",
85
- "yarn.lock",
86
- ]
87
- `);
88
- monorepo.cleanup();
89
- });
90
- function serializeFiles(files, packagePath) {
91
- return files.map((p)=>_path.default.relative(packagePath, p)).map((p)=>p.replace(/\\/g, "/")).sort();
92
- }
93
- });