@lage-run/hasher 1.0.7 → 1.1.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,7 +2,52 @@
2
2
  "name": "@lage-run/hasher",
3
3
  "entries": [
4
4
  {
5
- "date": "Thu, 21 Dec 2023 09:48:33 GMT",
5
+ "date": "Sun, 05 May 2024 22:55:31 GMT",
6
+ "version": "1.1.1",
7
+ "tag": "@lage-run/hasher_v1.1.1",
8
+ "comments": {
9
+ "patch": [
10
+ {
11
+ "author": "kchau@microsoft.com",
12
+ "package": "@lage-run/hasher",
13
+ "commit": "1e36de04ab83fc0cde38062fc1543e4b12902166",
14
+ "comment": "fixing hashing issues related to rust panic"
15
+ }
16
+ ]
17
+ }
18
+ },
19
+ {
20
+ "date": "Wed, 17 Apr 2024 23:20:47 GMT",
21
+ "version": "1.1.0",
22
+ "tag": "@lage-run/hasher_v1.1.0",
23
+ "comments": {
24
+ "none": [
25
+ {
26
+ "author": "elcraig@microsoft.com",
27
+ "package": "@lage-run/hasher",
28
+ "commit": "fb4fcb8419cc778210104d7d04102fc95df13d5b",
29
+ "comment": "Update formatting"
30
+ }
31
+ ]
32
+ }
33
+ },
34
+ {
35
+ "date": "Fri, 15 Mar 2024 04:35:11 GMT",
36
+ "version": "1.1.0",
37
+ "tag": "@lage-run/hasher_v1.1.0",
38
+ "comments": {
39
+ "minor": [
40
+ {
41
+ "author": "kchau@microsoft.com",
42
+ "package": "@lage-run/hasher",
43
+ "commit": "71283d4af8888454c953e5228c97bfbb471c15ba",
44
+ "comment": "perf optimizations"
45
+ }
46
+ ]
47
+ }
48
+ },
49
+ {
50
+ "date": "Thu, 21 Dec 2023 09:49:09 GMT",
6
51
  "version": "1.0.7",
7
52
  "tag": "@lage-run/hasher_v1.0.7",
8
53
  "comments": {
package/CHANGELOG.md CHANGED
@@ -1,12 +1,28 @@
1
1
  # Change Log - @lage-run/hasher
2
2
 
3
- This log was last generated on Thu, 21 Dec 2023 09:48:33 GMT and should not be manually modified.
3
+ This log was last generated on Sun, 05 May 2024 22:55:31 GMT and should not be manually modified.
4
4
 
5
5
  <!-- Start content -->
6
6
 
7
+ ## 1.1.1
8
+
9
+ Sun, 05 May 2024 22:55:31 GMT
10
+
11
+ ### Patches
12
+
13
+ - fixing hashing issues related to rust panic (kchau@microsoft.com)
14
+
15
+ ## 1.1.0
16
+
17
+ Fri, 15 Mar 2024 04:35:11 GMT
18
+
19
+ ### Minor changes
20
+
21
+ - perf optimizations (kchau@microsoft.com)
22
+
7
23
  ## 1.0.7
8
24
 
9
- Thu, 21 Dec 2023 09:48:33 GMT
25
+ Thu, 21 Dec 2023 09:49:09 GMT
10
26
 
11
27
  ### Patches
12
28
 
package/README.md CHANGED
@@ -1,2 +1,3 @@
1
1
  # @lage-run/hasher
2
- This package takes code from both backfill-hasher & package-dep-hash and strips out the extraneous dependencies: backfill-logger and @rushstack/node-core-lib. This is done so that `lage` can become a pure ES Module package. It also allows us to control how the hashing works should lage gains the ability to customize the `inputs` in the future.
2
+
3
+ This package takes code from both backfill-hasher & package-dep-hash and strips out the extraneous dependencies: backfill-logger and @rushstack/node-core-lib. This is done so that `lage` can become a pure ES Module package. It also allows us to control how the hashing works should lage gains the ability to customize the `inputs` in the future.
package/lib/FileHasher.js CHANGED
@@ -106,12 +106,13 @@ class FileHasher {
106
106
  input: inputStream,
107
107
  crlfDelay: Infinity
108
108
  });
109
+ let info = [];
109
110
  rl.on("line", (line)=>{
110
- const [relativePath, mtimeStr, sizeStr, hash] = line.split("\0");
111
- _class_private_field_get(this, _store)[relativePath] = {
112
- mtime: BigInt(mtimeStr),
113
- size: parseInt(sizeStr),
114
- hash
111
+ info = line.split("\0");
112
+ _class_private_field_get(this, _store)[info[0]] = {
113
+ mtime: BigInt(info[1]),
114
+ size: parseInt(info[2]),
115
+ hash: info[3]
115
116
  };
116
117
  });
117
118
  inputStream.on("end", ()=>{
@@ -124,11 +125,10 @@ class FileHasher {
124
125
  _gracefulfs.default.mkdirSync(_path.default.dirname(_class_private_field_get(this, _manifestFile)), {
125
126
  recursive: true
126
127
  });
127
- const outputStream = _gracefulfs.default.createWriteStream(_class_private_field_get(this, _manifestFile), "utf-8");
128
- for (const [relativePath, info] of Object.entries(_class_private_field_get(this, _store))){
129
- outputStream.write(`${relativePath}\0${info.mtime.toString()}\0${info.size.toString()}\0${info.hash}\n`);
130
- }
131
- outputStream.end();
128
+ const outputLines = Object.entries(_class_private_field_get(this, _store)).map(([relativePath, info])=>{
129
+ return `${relativePath}\0${info.mtime.toString()}\0${info.size.toString()}\0${info.hash}`;
130
+ });
131
+ _gracefulfs.default.writeFileSync(_class_private_field_get(this, _manifestFile), outputLines.join("\n"), "utf-8");
132
132
  }
133
133
  hash(files) {
134
134
  const hashes = {};
@@ -156,9 +156,9 @@ class FileHasher {
156
156
  _class_private_field_get(this, _store)[file] = {
157
157
  mtime: stat.mtimeMs,
158
158
  size: Number(stat.size),
159
- hash
159
+ hash: hash ?? ""
160
160
  };
161
- hashes[file] = hash;
161
+ hashes[file] = hash ?? "";
162
162
  }
163
163
  return hashes;
164
164
  }
@@ -1,20 +1,16 @@
1
- import { type PackageInfos } from "workspace-tools";
2
- export interface PackageTreeOptions {
1
+ export interface LocalPackageTreeOptions {
3
2
  root: string;
4
- packageInfos: PackageInfos;
5
3
  includeUntracked: boolean;
6
4
  }
7
5
  /**
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.
6
+ * Keeps a data structure to quickly find all files in a package.
11
7
  */
12
8
  export declare class PackageTree {
13
9
  #private;
14
10
  private options;
15
- constructor(options: PackageTreeOptions);
11
+ constructor(options: LocalPackageTreeOptions);
16
12
  reset(): void;
17
13
  initialize(): Promise<void>;
18
- addToPackageTree(filePaths: string[]): Promise<void>;
19
- getPackageFiles(packageName: string, patterns: string[]): string[];
14
+ findFilesInPath(packagePath: string, patterns: string[]): Promise<string[]>;
15
+ cleanup(): void;
20
16
  }
@@ -11,7 +11,6 @@ Object.defineProperty(exports, "PackageTree", {
11
11
  const _execa = /*#__PURE__*/ _interop_require_default(require("execa"));
12
12
  const _path = /*#__PURE__*/ _interop_require_default(require("path"));
13
13
  const _fs = /*#__PURE__*/ _interop_require_default(require("fs"));
14
- const _micromatch = /*#__PURE__*/ _interop_require_default(require("micromatch"));
15
14
  function _check_private_redeclaration(obj, privateCollection) {
16
15
  if (privateCollection.has(obj)) {
17
16
  throw new TypeError("Cannot initialize the same private elements twice on an object");
@@ -52,6 +51,16 @@ function _class_private_field_set(receiver, privateMap, value) {
52
51
  _class_apply_descriptor_set(receiver, descriptor, value);
53
52
  return value;
54
53
  }
54
+ function _class_private_method_get(receiver, privateSet, fn) {
55
+ if (!privateSet.has(receiver)) {
56
+ throw new TypeError("attempted to get private field on non-instance");
57
+ }
58
+ return fn;
59
+ }
60
+ function _class_private_method_init(obj, privateSet) {
61
+ _check_private_redeclaration(obj, privateSet);
62
+ privateSet.add(obj);
63
+ }
55
64
  function _define_property(obj, key, value) {
56
65
  if (key in obj) {
57
66
  Object.defineProperty(obj, key, {
@@ -70,7 +79,7 @@ function _interop_require_default(obj) {
70
79
  default: obj
71
80
  };
72
81
  }
73
- var _tree = /*#__PURE__*/ new WeakMap(), _packageFiles = /*#__PURE__*/ new WeakMap(), _memoizedPackageFiles = /*#__PURE__*/ new WeakMap();
82
+ var _tree = /*#__PURE__*/ new WeakMap(), _packageFiles = /*#__PURE__*/ new WeakMap(), _memoizedPackageFiles = /*#__PURE__*/ new WeakMap(), _findFilesFromGitTree = /*#__PURE__*/ new WeakSet();
74
83
  class PackageTree {
75
84
  reset() {
76
85
  _class_private_field_set(this, _tree, {});
@@ -78,86 +87,19 @@ class PackageTree {
78
87
  _class_private_field_set(this, _memoizedPackageFiles, {});
79
88
  }
80
89
  async initialize() {
81
- const { root , includeUntracked , packageInfos } = this.options;
82
90
  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
91
  }
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
- }
138
- }
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")}`;
92
+ async findFilesInPath(packagePath, patterns) {
93
+ const key = `${packagePath}\0${patterns.join("\0")}`;
147
94
  if (!_class_private_field_get(this, _memoizedPackageFiles)[key]) {
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
- });
95
+ const files = (await _class_private_method_get(this, _findFilesFromGitTree, findFilesFromGitTree).call(this, packagePath, patterns)).map((f)=>_path.default.join(packagePath, f));
96
+ _class_private_field_get(this, _memoizedPackageFiles)[key] = files;
157
97
  }
158
98
  return _class_private_field_get(this, _memoizedPackageFiles)[key];
159
99
  }
100
+ cleanup() {}
160
101
  constructor(options){
102
+ _class_private_method_init(this, _findFilesFromGitTree);
161
103
  _define_property(this, "options", void 0);
162
104
  _class_private_field_init(this, _tree, {
163
105
  writable: true,
@@ -177,3 +119,40 @@ class PackageTree {
177
119
  _class_private_field_set(this, _memoizedPackageFiles, {});
178
120
  }
179
121
  }
122
+ async function findFilesFromGitTree(packagePath, patterns) {
123
+ const { includeUntracked } = this.options;
124
+ const cwd = _path.default.isAbsolute(packagePath) ? packagePath : _path.default.join(this.options.root, packagePath);
125
+ const trackedPromise = (0, _execa.default)("git", [
126
+ "ls-files",
127
+ "-z",
128
+ ...patterns.filter((p)=>!p.startsWith("!")),
129
+ ...patterns.filter((p)=>p.startsWith("!")).map((p)=>`:!:${p.slice(1)}`)
130
+ ], {
131
+ cwd
132
+ }).then((lsFilesResults)=>{
133
+ if (lsFilesResults.exitCode === 0) {
134
+ return lsFilesResults.stdout.split("\0").filter((f)=>Boolean(f) && _fs.default.existsSync(_path.default.join(cwd, f)));
135
+ }
136
+ return [];
137
+ });
138
+ const untrackedPromise = includeUntracked ? (0, _execa.default)("git", [
139
+ "ls-files",
140
+ "-z",
141
+ "-o",
142
+ "--exclude-standard",
143
+ ...patterns.filter((p)=>!p.startsWith("!")),
144
+ ...patterns.filter((p)=>p.startsWith("!")).map((p)=>`:!:${p.slice(1)}`)
145
+ ], {
146
+ cwd
147
+ }).then((lsOtherResults)=>{
148
+ if (lsOtherResults.exitCode === 0) {
149
+ return lsOtherResults.stdout.split("\0").filter((f)=>Boolean(f));
150
+ }
151
+ return [];
152
+ }) : Promise.resolve([]);
153
+ const [trackedFiles, untrackedFiles] = await Promise.all([
154
+ trackedPromise,
155
+ untrackedPromise
156
+ ]);
157
+ return trackedFiles.concat(untrackedFiles);
158
+ }
@@ -32,7 +32,7 @@ export declare class TargetHasher {
32
32
  private options;
33
33
  logger: Logger | undefined;
34
34
  fileHasher: FileHasher;
35
- packageTree: PackageTree | undefined;
35
+ packageTree: PackageTree;
36
36
  initializedPromise: Promise<unknown> | undefined;
37
37
  packageInfos: PackageInfos;
38
38
  workspaceInfo: WorkspaceInfo | undefined;
@@ -9,7 +9,6 @@ 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"));
13
12
  const _fs = /*#__PURE__*/ _interop_require_default(require("fs"));
14
13
  const _path = /*#__PURE__*/ _interop_require_default(require("path"));
15
14
  const _workspacetools = require("workspace-tools");
@@ -109,26 +108,19 @@ class TargetHasher {
109
108
  await this.initializedPromise;
110
109
  return;
111
110
  }
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
- ]);
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)=>this.fileHasher.hash(files)).then((hash)=>this.globalInputsHash = hash),
121
+ (0, _workspacetools.parseLockFile)(root).then((lockInfo)=>this.lockInfo = lockInfo)
122
+ ]);
123
+ });
132
124
  await this.initializedPromise;
133
125
  if (this.logger !== undefined) {
134
126
  const globalInputsHash = (0, _hashStrings.hashStrings)(Object.values(this.globalInputsHash ?? {}));
@@ -145,13 +137,11 @@ class TargetHasher {
145
137
  if (!target.inputs) {
146
138
  throw new Error("Root-level targets must have `inputs` defined if it has cache enabled.");
147
139
  }
148
- const files = await (0, _fastglob.default)(target.inputs, {
149
- cwd: root
150
- });
140
+ const files = await this.packageTree.findFilesInPath(root, target.inputs);
151
141
  const fileFashes = (0, _globhasher.hash)(files, {
152
142
  cwd: root
153
143
  }) ?? {};
154
- const hashes = Object.values(fileFashes);
144
+ const hashes = Object.values(fileFashes).filter((hash)=>hash !== undefined && hash !== null);
155
145
  return (0, _hashStrings.hashStrings)(hashes);
156
146
  }
157
147
  // 1. add hash of target's inputs
@@ -175,7 +165,9 @@ class TargetHasher {
175
165
  const packagePatterns = this.expandInputPatterns(inputs, target);
176
166
  const files = [];
177
167
  for (const [pkg, patterns] of Object.entries(packagePatterns)){
178
- const packageFiles = this.packageTree.getPackageFiles(pkg, patterns);
168
+ const { root } = this.options;
169
+ const packagePath = _path.default.relative(root, _path.default.dirname(this.packageInfos[pkg].packageJsonPath)).replace(/\\/g, "/");
170
+ const packageFiles = await this.packageTree.findFilesInPath(packagePath, patterns);
179
171
  files.push(...packageFiles);
180
172
  }
181
173
  const fileHashes = this.fileHasher.hash(files) ?? {}; // this list is sorted by file name
@@ -197,7 +189,8 @@ class TargetHasher {
197
189
  return hashString;
198
190
  }
199
191
  async cleanup() {
200
- await this.fileHasher.writeManifest();
192
+ this.packageTree.cleanup();
193
+ this.fileHasher.writeManifest();
201
194
  }
202
195
  constructor(options){
203
196
  _define_property(this, "options", void 0);
@@ -223,5 +216,9 @@ class TargetHasher {
223
216
  this.fileHasher = new _FileHasher.FileHasher({
224
217
  root
225
218
  });
219
+ this.packageTree = new _PackageTree.PackageTree({
220
+ root,
221
+ includeUntracked: true
222
+ });
226
223
  }
227
224
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lage-run/hasher",
3
- "version": "1.0.7",
3
+ "version": "1.1.1",
4
4
  "description": "Hasher for Lage Targets",
5
5
  "repository": {
6
6
  "url": "https://github.com/microsoft/lage"
@@ -19,10 +19,8 @@
19
19
  "@lage-run/logger": "^1.3.0",
20
20
  "execa": "5.1.1",
21
21
  "workspace-tools": "0.36.4",
22
- "fast-glob": "3.3.1",
23
- "glob-hasher": "^1.3.0",
24
- "graceful-fs": "4.2.11",
25
- "micromatch": "4.0.5"
22
+ "glob-hasher": "^1.4.2",
23
+ "graceful-fs": "4.2.11"
26
24
  },
27
25
  "devDependencies": {
28
26
  "@lage-run/monorepo-fixture": "*",