@react-native-harness/cache 1.4.0 → 1.4.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/package.json +2 -2
- package/dist/__tests__/boundary.test.d.ts +0 -2
- package/dist/__tests__/boundary.test.d.ts.map +0 -1
- package/dist/__tests__/boundary.test.js +0 -77
- package/dist/__tests__/domains/metro.test.d.ts +0 -2
- package/dist/__tests__/domains/metro.test.d.ts.map +0 -1
- package/dist/__tests__/domains/metro.test.js +0 -234
- package/dist/__tests__/index.test.d.ts +0 -2
- package/dist/__tests__/index.test.d.ts.map +0 -1
- package/dist/__tests__/index.test.js +0 -40
- package/dist/__tests__/paths.test.d.ts +0 -2
- package/dist/__tests__/paths.test.d.ts.map +0 -1
- package/dist/__tests__/paths.test.js +0 -12
- package/dist/__tests__/plan.test.d.ts +0 -2
- package/dist/__tests__/plan.test.d.ts.map +0 -1
- package/dist/__tests__/plan.test.js +0 -197
- package/dist/__tests__/warmth.test.d.ts +0 -2
- package/dist/__tests__/warmth.test.d.ts.map +0 -1
- package/dist/__tests__/warmth.test.js +0 -36
- package/dist/tsconfig.lib.tsbuildinfo +0 -1
- package/src/__tests__/boundary.test.ts +0 -95
- package/src/__tests__/domains/metro.test.ts +0 -298
- package/src/__tests__/index.test.ts +0 -54
- package/src/__tests__/paths.test.ts +0 -18
- package/src/__tests__/plan.test.ts +0 -330
- package/src/__tests__/warmth.test.ts +0 -47
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@react-native-harness/cache",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.js",
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
},
|
|
17
17
|
"dependencies": {
|
|
18
18
|
"tslib": "^2.3.0",
|
|
19
|
-
"@react-native-harness/tools": "1.4.
|
|
19
|
+
"@react-native-harness/tools": "1.4.1"
|
|
20
20
|
},
|
|
21
21
|
"license": "MIT"
|
|
22
22
|
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"boundary.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/boundary.test.ts"],"names":[],"mappings":""}
|
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
import fs from 'node:fs';
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
import { fileURLToPath } from 'node:url';
|
|
4
|
-
import { describe, expect, it } from 'vitest';
|
|
5
|
-
// All `.harness/cache` layout knowledge must live in this package; other
|
|
6
|
-
// packages consume `HarnessCache.paths` instead of constructing cache paths
|
|
7
|
-
// themselves. This test scans the other packages' sources for violations.
|
|
8
|
-
//
|
|
9
|
-
// Allowlisted until phase 2 migrates them onto this package:
|
|
10
|
-
// - packages/tools/src/harness-artifacts.ts: getHarnessCacheRootPath, used by
|
|
11
|
-
// platform-ios for the XCTest agent cache.
|
|
12
|
-
const ALLOWLIST = new Set(['tools/src/harness-artifacts.ts']);
|
|
13
|
-
const findRepoRoot = (start) => {
|
|
14
|
-
let dir = start;
|
|
15
|
-
while (!fs.existsSync(path.join(dir, 'pnpm-workspace.yaml'))) {
|
|
16
|
-
const parent = path.dirname(dir);
|
|
17
|
-
if (parent === dir) {
|
|
18
|
-
throw new Error('Could not find the repository root');
|
|
19
|
-
}
|
|
20
|
-
dir = parent;
|
|
21
|
-
}
|
|
22
|
-
return dir;
|
|
23
|
-
};
|
|
24
|
-
const collectSourceFiles = (dir, files = []) => {
|
|
25
|
-
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
26
|
-
if (entry.isDirectory()) {
|
|
27
|
-
if (entry.name === 'node_modules' ||
|
|
28
|
-
entry.name === 'dist' ||
|
|
29
|
-
entry.name === '__tests__') {
|
|
30
|
-
continue;
|
|
31
|
-
}
|
|
32
|
-
collectSourceFiles(path.join(dir, entry.name), files);
|
|
33
|
-
}
|
|
34
|
-
else if (entry.name.endsWith('.ts') || entry.name.endsWith('.tsx')) {
|
|
35
|
-
files.push(path.join(dir, entry.name));
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
return files;
|
|
39
|
-
};
|
|
40
|
-
const constructsHarnessCachePath = (content) => {
|
|
41
|
-
if (content.includes('.harness/cache')) {
|
|
42
|
-
return true;
|
|
43
|
-
}
|
|
44
|
-
const hasHarnessDirLiteral = content.includes("'.harness'") || content.includes('".harness"');
|
|
45
|
-
// A quoted 'cache' followed by `,`, `)`, or `]` is a path segment being
|
|
46
|
-
// joined, as opposed to e.g. a property name in a type.
|
|
47
|
-
const hasCacheSegmentLiteral = /['"]cache['"]\s*[,)\]]/.test(content);
|
|
48
|
-
return hasHarnessDirLiteral && hasCacheSegmentLiteral;
|
|
49
|
-
};
|
|
50
|
-
describe('cache path boundary', () => {
|
|
51
|
-
it('no package outside @react-native-harness/cache constructs .harness cache paths', () => {
|
|
52
|
-
const repoRoot = findRepoRoot(path.dirname(fileURLToPath(import.meta.url)));
|
|
53
|
-
const packagesRoot = path.join(repoRoot, 'packages');
|
|
54
|
-
const violations = [];
|
|
55
|
-
for (const entry of fs.readdirSync(packagesRoot, {
|
|
56
|
-
withFileTypes: true,
|
|
57
|
-
})) {
|
|
58
|
-
if (!entry.isDirectory() || entry.name === 'cache') {
|
|
59
|
-
continue;
|
|
60
|
-
}
|
|
61
|
-
const srcDir = path.join(packagesRoot, entry.name, 'src');
|
|
62
|
-
if (!fs.existsSync(srcDir)) {
|
|
63
|
-
continue;
|
|
64
|
-
}
|
|
65
|
-
for (const file of collectSourceFiles(srcDir)) {
|
|
66
|
-
const relativePath = path.relative(packagesRoot, file);
|
|
67
|
-
if (ALLOWLIST.has(relativePath)) {
|
|
68
|
-
continue;
|
|
69
|
-
}
|
|
70
|
-
if (constructsHarnessCachePath(fs.readFileSync(file, 'utf8'))) {
|
|
71
|
-
violations.push(relativePath);
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
expect(violations).toEqual([]);
|
|
76
|
-
});
|
|
77
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"metro.test.d.ts","sourceRoot":"","sources":["../../../src/__tests__/domains/metro.test.ts"],"names":[],"mappings":""}
|
|
@@ -1,234 +0,0 @@
|
|
|
1
|
-
import fs from 'node:fs';
|
|
2
|
-
import os from 'node:os';
|
|
3
|
-
import path from 'node:path';
|
|
4
|
-
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
5
|
-
import { computeMetroStaticInputs } from '../../domains/metro.js';
|
|
6
|
-
describe('computeMetroStaticInputs', () => {
|
|
7
|
-
let repoRoot;
|
|
8
|
-
let projectRoot;
|
|
9
|
-
beforeEach(() => {
|
|
10
|
-
repoRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'harness-cache-metro-'));
|
|
11
|
-
projectRoot = path.join(repoRoot, 'apps', 'mobile');
|
|
12
|
-
fs.mkdirSync(projectRoot, { recursive: true });
|
|
13
|
-
});
|
|
14
|
-
afterEach(() => {
|
|
15
|
-
fs.rmSync(repoRoot, { recursive: true, force: true });
|
|
16
|
-
});
|
|
17
|
-
it('includes a lockfile at an ancestor directory between projectRoot and repoRoot', () => {
|
|
18
|
-
fs.writeFileSync(path.join(repoRoot, 'pnpm-lock.yaml'), 'lockfile-a');
|
|
19
|
-
const before = computeMetroStaticInputs({
|
|
20
|
-
projectRoot,
|
|
21
|
-
repoRoot,
|
|
22
|
-
bundlerMetroVersion: '1.0.0',
|
|
23
|
-
});
|
|
24
|
-
fs.writeFileSync(path.join(repoRoot, 'pnpm-lock.yaml'), 'lockfile-b');
|
|
25
|
-
const after = computeMetroStaticInputs({
|
|
26
|
-
projectRoot,
|
|
27
|
-
repoRoot,
|
|
28
|
-
bundlerMetroVersion: '1.0.0',
|
|
29
|
-
});
|
|
30
|
-
expect(before.lockfileHash).not.toBe(after.lockfileHash);
|
|
31
|
-
});
|
|
32
|
-
it('ignores a config file in a sibling directory not on the ancestor chain', () => {
|
|
33
|
-
const siblingDir = path.join(repoRoot, 'apps', 'other');
|
|
34
|
-
fs.mkdirSync(siblingDir, { recursive: true });
|
|
35
|
-
fs.writeFileSync(path.join(siblingDir, 'metro.config.js'), 'config-a');
|
|
36
|
-
const before = computeMetroStaticInputs({
|
|
37
|
-
projectRoot,
|
|
38
|
-
repoRoot,
|
|
39
|
-
bundlerMetroVersion: '1.0.0',
|
|
40
|
-
});
|
|
41
|
-
fs.writeFileSync(path.join(siblingDir, 'metro.config.js'), 'config-b');
|
|
42
|
-
const after = computeMetroStaticInputs({
|
|
43
|
-
projectRoot,
|
|
44
|
-
repoRoot,
|
|
45
|
-
bundlerMetroVersion: '1.0.0',
|
|
46
|
-
});
|
|
47
|
-
expect(before.lockfileHash).toBe(after.lockfileHash);
|
|
48
|
-
});
|
|
49
|
-
it('excludes a vendored lockfile under node_modules (regression pin)', () => {
|
|
50
|
-
// Pin for the old bug where a downward `hashFiles('**/pnpm-lock.yaml',
|
|
51
|
-
// ...)`-style glob would match a dependency's own lockfile under
|
|
52
|
-
// node_modules. Trivially true now that node_modules is never on the
|
|
53
|
-
// upward ancestor chain, but kept as an explicit regression test.
|
|
54
|
-
fs.writeFileSync(path.join(repoRoot, 'pnpm-lock.yaml'), 'lockfile-a');
|
|
55
|
-
const vendoredDir = path.join(projectRoot, 'node_modules', 'some-pkg');
|
|
56
|
-
fs.mkdirSync(vendoredDir, { recursive: true });
|
|
57
|
-
fs.writeFileSync(path.join(vendoredDir, 'package-lock.json'), 'vendored-lockfile');
|
|
58
|
-
const withVendored = computeMetroStaticInputs({
|
|
59
|
-
projectRoot,
|
|
60
|
-
repoRoot,
|
|
61
|
-
bundlerMetroVersion: '1.0.0',
|
|
62
|
-
});
|
|
63
|
-
fs.rmSync(path.join(projectRoot, 'node_modules'), {
|
|
64
|
-
recursive: true,
|
|
65
|
-
force: true,
|
|
66
|
-
});
|
|
67
|
-
const withoutVendored = computeMetroStaticInputs({
|
|
68
|
-
projectRoot,
|
|
69
|
-
repoRoot,
|
|
70
|
-
bundlerMetroVersion: '1.0.0',
|
|
71
|
-
});
|
|
72
|
-
expect(withVendored.lockfileHash).toBe(withoutVendored.lockfileHash);
|
|
73
|
-
});
|
|
74
|
-
it('is deterministic across independent calls given the same fixture tree', () => {
|
|
75
|
-
fs.writeFileSync(path.join(repoRoot, 'pnpm-lock.yaml'), 'lockfile-a');
|
|
76
|
-
fs.writeFileSync(path.join(projectRoot, 'metro.config.js'), 'config-a');
|
|
77
|
-
const first = computeMetroStaticInputs({
|
|
78
|
-
projectRoot,
|
|
79
|
-
repoRoot,
|
|
80
|
-
bundlerMetroVersion: '1.0.0',
|
|
81
|
-
});
|
|
82
|
-
const second = computeMetroStaticInputs({
|
|
83
|
-
projectRoot,
|
|
84
|
-
repoRoot,
|
|
85
|
-
bundlerMetroVersion: '1.0.0',
|
|
86
|
-
});
|
|
87
|
-
expect(first.lockfileHash).toBe(second.lockfileHash);
|
|
88
|
-
});
|
|
89
|
-
it('changes the hash when a matched file changes content', () => {
|
|
90
|
-
fs.writeFileSync(path.join(repoRoot, 'pnpm-lock.yaml'), 'lockfile-a');
|
|
91
|
-
const before = computeMetroStaticInputs({
|
|
92
|
-
projectRoot,
|
|
93
|
-
repoRoot,
|
|
94
|
-
bundlerMetroVersion: '1.0.0',
|
|
95
|
-
});
|
|
96
|
-
fs.writeFileSync(path.join(repoRoot, 'pnpm-lock.yaml'), 'lockfile-b');
|
|
97
|
-
const after = computeMetroStaticInputs({
|
|
98
|
-
projectRoot,
|
|
99
|
-
repoRoot,
|
|
100
|
-
bundlerMetroVersion: '1.0.0',
|
|
101
|
-
});
|
|
102
|
-
expect(before.lockfileHash).not.toBe(after.lockfileHash);
|
|
103
|
-
});
|
|
104
|
-
it('does not change the hash when an unrelated file changes content', () => {
|
|
105
|
-
fs.writeFileSync(path.join(repoRoot, 'pnpm-lock.yaml'), 'lockfile-a');
|
|
106
|
-
fs.writeFileSync(path.join(projectRoot, 'notes.txt'), 'v1');
|
|
107
|
-
const before = computeMetroStaticInputs({
|
|
108
|
-
projectRoot,
|
|
109
|
-
repoRoot,
|
|
110
|
-
bundlerMetroVersion: '1.0.0',
|
|
111
|
-
});
|
|
112
|
-
fs.writeFileSync(path.join(projectRoot, 'notes.txt'), 'v2');
|
|
113
|
-
const after = computeMetroStaticInputs({
|
|
114
|
-
projectRoot,
|
|
115
|
-
repoRoot,
|
|
116
|
-
bundlerMetroVersion: '1.0.0',
|
|
117
|
-
});
|
|
118
|
-
expect(before.lockfileHash).toBe(after.lockfileHash);
|
|
119
|
-
});
|
|
120
|
-
it.each([
|
|
121
|
-
['metro.config.mts', 'metro-mts-a', 'metro-mts-b'],
|
|
122
|
-
['metro.config.cts', 'metro-cts-a', 'metro-cts-b'],
|
|
123
|
-
['metro.config.json', 'metro-json-a', 'metro-json-b'],
|
|
124
|
-
['babel.config.cts', 'babel-cts-a', 'babel-cts-b'],
|
|
125
|
-
])('picks up %s as a candidate at projectRoot', (filename, before, after) => {
|
|
126
|
-
fs.writeFileSync(path.join(projectRoot, filename), before);
|
|
127
|
-
const beforeInputs = computeMetroStaticInputs({
|
|
128
|
-
projectRoot,
|
|
129
|
-
repoRoot,
|
|
130
|
-
bundlerMetroVersion: '1.0.0',
|
|
131
|
-
});
|
|
132
|
-
fs.writeFileSync(path.join(projectRoot, filename), after);
|
|
133
|
-
const afterInputs = computeMetroStaticInputs({
|
|
134
|
-
projectRoot,
|
|
135
|
-
repoRoot,
|
|
136
|
-
bundlerMetroVersion: '1.0.0',
|
|
137
|
-
});
|
|
138
|
-
expect(beforeInputs.lockfileHash).not.toBe(afterInputs.lockfileHash);
|
|
139
|
-
});
|
|
140
|
-
it('picks up .config/metro.js as a candidate at projectRoot', () => {
|
|
141
|
-
const configDir = path.join(projectRoot, '.config');
|
|
142
|
-
fs.mkdirSync(configDir, { recursive: true });
|
|
143
|
-
fs.writeFileSync(path.join(configDir, 'metro.js'), 'config-a');
|
|
144
|
-
const before = computeMetroStaticInputs({
|
|
145
|
-
projectRoot,
|
|
146
|
-
repoRoot,
|
|
147
|
-
bundlerMetroVersion: '1.0.0',
|
|
148
|
-
});
|
|
149
|
-
fs.writeFileSync(path.join(configDir, 'metro.js'), 'config-b');
|
|
150
|
-
const after = computeMetroStaticInputs({
|
|
151
|
-
projectRoot,
|
|
152
|
-
repoRoot,
|
|
153
|
-
bundlerMetroVersion: '1.0.0',
|
|
154
|
-
});
|
|
155
|
-
expect(before.lockfileHash).not.toBe(after.lockfileHash);
|
|
156
|
-
});
|
|
157
|
-
it('no longer treats babel.config.ts as a candidate', () => {
|
|
158
|
-
const before = computeMetroStaticInputs({
|
|
159
|
-
projectRoot,
|
|
160
|
-
repoRoot,
|
|
161
|
-
bundlerMetroVersion: '1.0.0',
|
|
162
|
-
});
|
|
163
|
-
fs.writeFileSync(path.join(projectRoot, 'babel.config.ts'), 'babel-ts-a');
|
|
164
|
-
const afterCreate = computeMetroStaticInputs({
|
|
165
|
-
projectRoot,
|
|
166
|
-
repoRoot,
|
|
167
|
-
bundlerMetroVersion: '1.0.0',
|
|
168
|
-
});
|
|
169
|
-
fs.writeFileSync(path.join(projectRoot, 'babel.config.ts'), 'babel-ts-b');
|
|
170
|
-
const afterChange = computeMetroStaticInputs({
|
|
171
|
-
projectRoot,
|
|
172
|
-
repoRoot,
|
|
173
|
-
bundlerMetroVersion: '1.0.0',
|
|
174
|
-
});
|
|
175
|
-
expect(afterCreate.lockfileHash).toBe(before.lockfileHash);
|
|
176
|
-
expect(afterChange.lockfileHash).toBe(before.lockfileHash);
|
|
177
|
-
});
|
|
178
|
-
it('passes bundlerMetroVersion and salt through into the returned object', () => {
|
|
179
|
-
fs.writeFileSync(path.join(repoRoot, 'pnpm-lock.yaml'), 'lockfile-a');
|
|
180
|
-
const withoutSalt = computeMetroStaticInputs({
|
|
181
|
-
projectRoot,
|
|
182
|
-
repoRoot,
|
|
183
|
-
bundlerMetroVersion: '2.3.4',
|
|
184
|
-
});
|
|
185
|
-
expect(withoutSalt.bundlerMetroVersion).toBe('2.3.4');
|
|
186
|
-
expect(withoutSalt.salt).toBeUndefined();
|
|
187
|
-
const withSalt = computeMetroStaticInputs({
|
|
188
|
-
projectRoot,
|
|
189
|
-
repoRoot,
|
|
190
|
-
bundlerMetroVersion: '2.3.4',
|
|
191
|
-
salt: 'v2',
|
|
192
|
-
});
|
|
193
|
-
expect(withSalt.salt).toBe('v2');
|
|
194
|
-
});
|
|
195
|
-
it('returns a stable fallback instead of throwing when projectRoot does not exist', () => {
|
|
196
|
-
const missingProjectRoot = path.join(repoRoot, 'does-not-exist');
|
|
197
|
-
const result = computeMetroStaticInputs({
|
|
198
|
-
projectRoot: missingProjectRoot,
|
|
199
|
-
repoRoot,
|
|
200
|
-
bundlerMetroVersion: '1.0.0',
|
|
201
|
-
});
|
|
202
|
-
expect(result).toEqual({
|
|
203
|
-
lockfileHash: 'unavailable',
|
|
204
|
-
bundlerMetroVersion: '1.0.0',
|
|
205
|
-
});
|
|
206
|
-
});
|
|
207
|
-
it('returns a stable fallback instead of throwing when repoRoot does not exist', () => {
|
|
208
|
-
const missingRepoRoot = path.join(repoRoot, 'does-not-exist');
|
|
209
|
-
const result = computeMetroStaticInputs({
|
|
210
|
-
projectRoot,
|
|
211
|
-
repoRoot: missingRepoRoot,
|
|
212
|
-
bundlerMetroVersion: '1.0.0',
|
|
213
|
-
});
|
|
214
|
-
expect(result).toEqual({
|
|
215
|
-
lockfileHash: 'unavailable',
|
|
216
|
-
bundlerMetroVersion: '1.0.0',
|
|
217
|
-
});
|
|
218
|
-
});
|
|
219
|
-
it('works when projectRoot === repoRoot (single directory examined)', () => {
|
|
220
|
-
fs.writeFileSync(path.join(repoRoot, 'pnpm-lock.yaml'), 'lockfile-a');
|
|
221
|
-
const before = computeMetroStaticInputs({
|
|
222
|
-
projectRoot: repoRoot,
|
|
223
|
-
repoRoot,
|
|
224
|
-
bundlerMetroVersion: '1.0.0',
|
|
225
|
-
});
|
|
226
|
-
fs.writeFileSync(path.join(repoRoot, 'pnpm-lock.yaml'), 'lockfile-b');
|
|
227
|
-
const after = computeMetroStaticInputs({
|
|
228
|
-
projectRoot: repoRoot,
|
|
229
|
-
repoRoot,
|
|
230
|
-
bundlerMetroVersion: '1.0.0',
|
|
231
|
-
});
|
|
232
|
-
expect(before.lockfileHash).not.toBe(after.lockfileHash);
|
|
233
|
-
});
|
|
234
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/index.test.ts"],"names":[],"mappings":""}
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
import fs from 'node:fs';
|
|
2
|
-
import os from 'node:os';
|
|
3
|
-
import path from 'node:path';
|
|
4
|
-
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
5
|
-
import { createHarnessCache } from '../index.js';
|
|
6
|
-
describe('createHarnessCache', () => {
|
|
7
|
-
let projectRoot;
|
|
8
|
-
beforeEach(() => {
|
|
9
|
-
projectRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'harness-cache-'));
|
|
10
|
-
});
|
|
11
|
-
afterEach(() => {
|
|
12
|
-
fs.rmSync(projectRoot, { recursive: true, force: true });
|
|
13
|
-
vi.restoreAllMocks();
|
|
14
|
-
});
|
|
15
|
-
it('exposes paths rooted at <projectRoot>/.harness/cache', () => {
|
|
16
|
-
const cache = createHarnessCache({ projectRoot });
|
|
17
|
-
expect(cache.paths.root).toBe(path.join(projectRoot, '.harness', 'cache'));
|
|
18
|
-
});
|
|
19
|
-
it('reports metro as cold before directories exist', () => {
|
|
20
|
-
const cache = createHarnessCache({ projectRoot });
|
|
21
|
-
expect(cache.isWarm('metro')).toBe(false);
|
|
22
|
-
});
|
|
23
|
-
it('creates domain directories and reports warm once populated', () => {
|
|
24
|
-
const cache = createHarnessCache({ projectRoot });
|
|
25
|
-
cache.ensureDomainDirectories('metro');
|
|
26
|
-
expect(fs.existsSync(cache.paths.metroTransform)).toBe(true);
|
|
27
|
-
expect(fs.existsSync(cache.paths.metroFileMap)).toBe(true);
|
|
28
|
-
expect(cache.isWarm('metro')).toBe(false);
|
|
29
|
-
fs.writeFileSync(path.join(cache.paths.metroTransform, 'entry'), 'data');
|
|
30
|
-
expect(cache.isWarm('metro')).toBe(true);
|
|
31
|
-
});
|
|
32
|
-
it('swallows and logs errors instead of throwing when directories cannot be created', () => {
|
|
33
|
-
// Point the project root at a path that cannot be created (its parent is a file).
|
|
34
|
-
const blockedRoot = path.join(projectRoot, 'blocked-file', 'nested');
|
|
35
|
-
fs.writeFileSync(path.join(projectRoot, 'blocked-file'), 'not a directory');
|
|
36
|
-
const cache = createHarnessCache({ projectRoot: blockedRoot });
|
|
37
|
-
expect(() => cache.ensureDomainDirectories('metro')).not.toThrow();
|
|
38
|
-
expect(cache.isWarm('metro')).toBe(false);
|
|
39
|
-
});
|
|
40
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"paths.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/paths.test.ts"],"names":[],"mappings":""}
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import path from 'node:path';
|
|
2
|
-
import { describe, expect, it } from 'vitest';
|
|
3
|
-
import { createHarnessCachePaths } from '../paths.js';
|
|
4
|
-
describe('createHarnessCachePaths', () => {
|
|
5
|
-
it('nests cache paths under <projectRoot>/.harness/cache', () => {
|
|
6
|
-
const projectRoot = '/tmp/some-project';
|
|
7
|
-
const paths = createHarnessCachePaths(projectRoot);
|
|
8
|
-
expect(paths.root).toBe(path.join(projectRoot, '.harness', 'cache'));
|
|
9
|
-
expect(paths.metroTransform).toBe(path.join(projectRoot, '.harness', 'cache', 'metro'));
|
|
10
|
-
expect(paths.metroFileMap).toBe(path.join(projectRoot, '.harness', 'cache', 'metro-file-map'));
|
|
11
|
-
});
|
|
12
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"plan.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/plan.test.ts"],"names":[],"mappings":""}
|
|
@@ -1,197 +0,0 @@
|
|
|
1
|
-
import fs from 'node:fs';
|
|
2
|
-
import os from 'node:os';
|
|
3
|
-
import path from 'node:path';
|
|
4
|
-
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
5
|
-
import { createHarnessCachePaths } from '../paths.js';
|
|
6
|
-
import { planRestore, planSave, snapshot, writeKeysFile, } from '../plan.js';
|
|
7
|
-
describe('planRestore', () => {
|
|
8
|
-
let projectRoot;
|
|
9
|
-
beforeEach(() => {
|
|
10
|
-
projectRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'harness-cache-'));
|
|
11
|
-
});
|
|
12
|
-
afterEach(() => {
|
|
13
|
-
fs.rmSync(projectRoot, { recursive: true, force: true });
|
|
14
|
-
});
|
|
15
|
-
const inputs = (staticInputs) => ({
|
|
16
|
-
metro: { os: 'linux', staticInputs },
|
|
17
|
-
});
|
|
18
|
-
it('produces the same restoreKey across independent calls given the same staticInputs', () => {
|
|
19
|
-
const paths = createHarnessCachePaths(projectRoot);
|
|
20
|
-
const first = planRestore(paths, inputs({ a: '1', b: '2' }));
|
|
21
|
-
const second = planRestore(paths, inputs({ a: '1', b: '2' }));
|
|
22
|
-
expect(first.domains.metro?.restoreKey).toBe(second.domains.metro?.restoreKey);
|
|
23
|
-
});
|
|
24
|
-
it('produces the same restoreKey regardless of staticInputs key order', () => {
|
|
25
|
-
const paths = createHarnessCachePaths(projectRoot);
|
|
26
|
-
const first = planRestore(paths, inputs({ a: '1', b: '2' }));
|
|
27
|
-
const second = planRestore(paths, inputs({ b: '2', a: '1' }));
|
|
28
|
-
expect(first.domains.metro?.restoreKey).toBe(second.domains.metro?.restoreKey);
|
|
29
|
-
});
|
|
30
|
-
it('produces a different restoreKey for different staticInputs', () => {
|
|
31
|
-
const paths = createHarnessCachePaths(projectRoot);
|
|
32
|
-
const first = planRestore(paths, inputs({ a: '1' }));
|
|
33
|
-
const second = planRestore(paths, inputs({ a: '2' }));
|
|
34
|
-
expect(first.domains.metro?.restoreKey).not.toBe(second.domains.metro?.restoreKey);
|
|
35
|
-
});
|
|
36
|
-
it('shapes restorePrefixes for the accretive metro domain as a single trailing-dash prefix', () => {
|
|
37
|
-
const paths = createHarnessCachePaths(projectRoot);
|
|
38
|
-
const plan = planRestore(paths, inputs({ a: '1' }));
|
|
39
|
-
const domain = plan.domains.metro;
|
|
40
|
-
expect(domain?.accretive).toBe(true);
|
|
41
|
-
expect(domain?.restorePrefixes).toEqual([`${domain?.restoreKey}-`]);
|
|
42
|
-
});
|
|
43
|
-
it('only includes domains present in the inputs', () => {
|
|
44
|
-
const paths = createHarnessCachePaths(projectRoot);
|
|
45
|
-
const plan = planRestore(paths, {});
|
|
46
|
-
expect(plan.domains).toEqual({});
|
|
47
|
-
});
|
|
48
|
-
it('reports the domain cache directories as paths', () => {
|
|
49
|
-
const paths = createHarnessCachePaths(projectRoot);
|
|
50
|
-
const plan = planRestore(paths, inputs({ a: '1' }));
|
|
51
|
-
expect(plan.domains.metro?.paths).toEqual([
|
|
52
|
-
paths.metroTransform,
|
|
53
|
-
paths.metroFileMap,
|
|
54
|
-
]);
|
|
55
|
-
});
|
|
56
|
-
});
|
|
57
|
-
describe('snapshot', () => {
|
|
58
|
-
let projectRoot;
|
|
59
|
-
beforeEach(() => {
|
|
60
|
-
projectRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'harness-cache-'));
|
|
61
|
-
});
|
|
62
|
-
afterEach(() => {
|
|
63
|
-
fs.rmSync(projectRoot, { recursive: true, force: true });
|
|
64
|
-
});
|
|
65
|
-
it('is empty for a domain whose directories do not exist', () => {
|
|
66
|
-
const paths = createHarnessCachePaths(projectRoot);
|
|
67
|
-
expect(() => snapshot(paths)).not.toThrow();
|
|
68
|
-
expect(snapshot(paths).metro).toEqual([]);
|
|
69
|
-
});
|
|
70
|
-
it('collects entries for files under the domain directories', () => {
|
|
71
|
-
const paths = createHarnessCachePaths(projectRoot);
|
|
72
|
-
fs.mkdirSync(path.join(paths.metroTransform, 'nested'), {
|
|
73
|
-
recursive: true,
|
|
74
|
-
});
|
|
75
|
-
fs.writeFileSync(path.join(paths.metroTransform, 'a.txt'), 'aaa');
|
|
76
|
-
fs.writeFileSync(path.join(paths.metroTransform, 'nested', 'b.txt'), 'bb');
|
|
77
|
-
const result = snapshot(paths);
|
|
78
|
-
const entries = result.metro ?? [];
|
|
79
|
-
expect(entries).toHaveLength(2);
|
|
80
|
-
const aEntry = entries.find((entry) => entry.path.endsWith(path.join('metro', 'a.txt')));
|
|
81
|
-
const bEntry = entries.find((entry) => entry.path.endsWith(path.join('metro', 'nested', 'b.txt')));
|
|
82
|
-
expect(aEntry?.sizeBytes).toBe(3);
|
|
83
|
-
expect(bEntry?.sizeBytes).toBe(2);
|
|
84
|
-
expect(typeof aEntry?.mtimeMs).toBe('number');
|
|
85
|
-
});
|
|
86
|
-
});
|
|
87
|
-
describe('planSave', () => {
|
|
88
|
-
let projectRoot;
|
|
89
|
-
beforeEach(() => {
|
|
90
|
-
projectRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'harness-cache-'));
|
|
91
|
-
});
|
|
92
|
-
afterEach(() => {
|
|
93
|
-
fs.rmSync(projectRoot, { recursive: true, force: true });
|
|
94
|
-
});
|
|
95
|
-
const inputs = (staticInputs = { a: '1' }) => ({
|
|
96
|
-
metro: { os: 'linux', staticInputs },
|
|
97
|
-
});
|
|
98
|
-
it('does not save when there is no change, regardless of policy', () => {
|
|
99
|
-
const paths = createHarnessCachePaths(projectRoot);
|
|
100
|
-
fs.mkdirSync(paths.metroTransform, { recursive: true });
|
|
101
|
-
fs.writeFileSync(path.join(paths.metroTransform, 'a.txt'), 'aaa');
|
|
102
|
-
const before = snapshot(paths);
|
|
103
|
-
const plan = planSave(paths, before, { mode: 'always', isDefaultBranch: true }, inputs());
|
|
104
|
-
expect(plan.domains.metro?.shouldSave).toBe(false);
|
|
105
|
-
expect(plan.domains.metro?.delta).toEqual({
|
|
106
|
-
addedEntries: 0,
|
|
107
|
-
removedEntries: 0,
|
|
108
|
-
addedBytes: 0,
|
|
109
|
-
});
|
|
110
|
-
});
|
|
111
|
-
it('saves an added file only when policy allows it', () => {
|
|
112
|
-
const paths = createHarnessCachePaths(projectRoot);
|
|
113
|
-
fs.mkdirSync(paths.metroTransform, { recursive: true });
|
|
114
|
-
const before = snapshot(paths);
|
|
115
|
-
fs.writeFileSync(path.join(paths.metroTransform, 'a.txt'), 'aaa');
|
|
116
|
-
const disallowed = planSave(paths, before, { mode: 'default-branch', isDefaultBranch: false }, inputs());
|
|
117
|
-
expect(disallowed.domains.metro?.shouldSave).toBe(false);
|
|
118
|
-
expect(disallowed.domains.metro?.delta.addedEntries).toBe(1);
|
|
119
|
-
expect(disallowed.domains.metro?.delta.addedBytes).toBe(3);
|
|
120
|
-
const allowed = planSave(paths, before, { mode: 'default-branch', isDefaultBranch: true }, inputs());
|
|
121
|
-
expect(allowed.domains.metro?.shouldSave).toBe(true);
|
|
122
|
-
});
|
|
123
|
-
it('never saves when policy mode is "never"', () => {
|
|
124
|
-
const paths = createHarnessCachePaths(projectRoot);
|
|
125
|
-
fs.mkdirSync(paths.metroTransform, { recursive: true });
|
|
126
|
-
const before = snapshot(paths);
|
|
127
|
-
fs.writeFileSync(path.join(paths.metroTransform, 'a.txt'), 'aaa');
|
|
128
|
-
const plan = planSave(paths, before, { mode: 'never', isDefaultBranch: true }, inputs());
|
|
129
|
-
expect(plan.domains.metro?.shouldSave).toBe(false);
|
|
130
|
-
});
|
|
131
|
-
it('counts a removed file in the delta', () => {
|
|
132
|
-
const paths = createHarnessCachePaths(projectRoot);
|
|
133
|
-
fs.mkdirSync(paths.metroTransform, { recursive: true });
|
|
134
|
-
fs.writeFileSync(path.join(paths.metroTransform, 'a.txt'), 'aaa');
|
|
135
|
-
const before = snapshot(paths);
|
|
136
|
-
fs.rmSync(path.join(paths.metroTransform, 'a.txt'));
|
|
137
|
-
const plan = planSave(paths, before, { mode: 'always', isDefaultBranch: true }, inputs());
|
|
138
|
-
expect(plan.domains.metro?.delta.removedEntries).toBe(1);
|
|
139
|
-
expect(plan.domains.metro?.shouldSave).toBe(true);
|
|
140
|
-
});
|
|
141
|
-
it('does not treat an mtime-only change as an added entry or change the content hash', () => {
|
|
142
|
-
const paths = createHarnessCachePaths(projectRoot);
|
|
143
|
-
fs.mkdirSync(paths.metroTransform, { recursive: true });
|
|
144
|
-
const filePath = path.join(paths.metroTransform, 'a.txt');
|
|
145
|
-
fs.writeFileSync(filePath, 'aaa');
|
|
146
|
-
const before = snapshot(paths);
|
|
147
|
-
const beforeSave = planSave(paths, before, { mode: 'always', isDefaultBranch: true }, inputs());
|
|
148
|
-
// Touch the file's mtime without changing its size/content.
|
|
149
|
-
const future = new Date(Date.now() + 10_000);
|
|
150
|
-
fs.utimesSync(filePath, future, future);
|
|
151
|
-
const afterSave = planSave(paths, before, { mode: 'always', isDefaultBranch: true }, inputs());
|
|
152
|
-
expect(afterSave.domains.metro?.delta.addedEntries).toBe(0);
|
|
153
|
-
expect(afterSave.domains.metro?.shouldSave).toBe(false);
|
|
154
|
-
expect(afterSave.domains.metro?.saveKey).toBe(beforeSave.domains.metro?.saveKey);
|
|
155
|
-
});
|
|
156
|
-
it('produces a deterministic saveKey across independent planSave calls given the same on-disk state', () => {
|
|
157
|
-
const paths = createHarnessCachePaths(projectRoot);
|
|
158
|
-
fs.mkdirSync(paths.metroTransform, { recursive: true });
|
|
159
|
-
fs.writeFileSync(path.join(paths.metroTransform, 'a.txt'), 'aaa');
|
|
160
|
-
const before = {};
|
|
161
|
-
const first = planSave(paths, before, { mode: 'always', isDefaultBranch: true }, inputs());
|
|
162
|
-
const second = planSave(paths, before, { mode: 'always', isDefaultBranch: true }, inputs());
|
|
163
|
-
expect(first.domains.metro?.saveKey).toBe(second.domains.metro?.saveKey);
|
|
164
|
-
});
|
|
165
|
-
});
|
|
166
|
-
describe('writeKeysFile', () => {
|
|
167
|
-
let projectRoot;
|
|
168
|
-
beforeEach(() => {
|
|
169
|
-
projectRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'harness-cache-'));
|
|
170
|
-
});
|
|
171
|
-
afterEach(() => {
|
|
172
|
-
fs.rmSync(projectRoot, { recursive: true, force: true });
|
|
173
|
-
});
|
|
174
|
-
it('writes keys.json under the cache root, outside domain subdirectories', () => {
|
|
175
|
-
const paths = createHarnessCachePaths(projectRoot);
|
|
176
|
-
const plan = planRestore(paths, {
|
|
177
|
-
metro: { os: 'linux', staticInputs: { a: '1' } },
|
|
178
|
-
});
|
|
179
|
-
writeKeysFile(paths, plan);
|
|
180
|
-
const filePath = path.join(paths.root, 'keys.json');
|
|
181
|
-
expect(fs.existsSync(filePath)).toBe(true);
|
|
182
|
-
expect(filePath.startsWith(paths.metroTransform)).toBe(false);
|
|
183
|
-
expect(filePath.startsWith(paths.metroFileMap)).toBe(false);
|
|
184
|
-
const written = JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
185
|
-
expect(written.version).toBe(1);
|
|
186
|
-
});
|
|
187
|
-
it('swallows errors instead of throwing when the cache root cannot be created', () => {
|
|
188
|
-
const blockedRoot = path.join(projectRoot, 'blocked-file', 'nested');
|
|
189
|
-
fs.writeFileSync(path.join(projectRoot, 'blocked-file'), 'not a directory');
|
|
190
|
-
const paths = createHarnessCachePaths(blockedRoot);
|
|
191
|
-
const plan = planRestore(paths, {
|
|
192
|
-
metro: { os: 'linux', staticInputs: { a: '1' } },
|
|
193
|
-
});
|
|
194
|
-
expect(() => writeKeysFile(paths, plan)).not.toThrow();
|
|
195
|
-
expect(fs.existsSync(path.join(paths.root, 'keys.json'))).toBe(false);
|
|
196
|
-
});
|
|
197
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"warmth.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/warmth.test.ts"],"names":[],"mappings":""}
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import fs from 'node:fs';
|
|
2
|
-
import os from 'node:os';
|
|
3
|
-
import path from 'node:path';
|
|
4
|
-
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
5
|
-
import { createHarnessCachePaths } from '../paths.js';
|
|
6
|
-
import { isDomainWarm } from '../warmth.js';
|
|
7
|
-
describe('isDomainWarm', () => {
|
|
8
|
-
let projectRoot;
|
|
9
|
-
beforeEach(() => {
|
|
10
|
-
projectRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'harness-cache-'));
|
|
11
|
-
});
|
|
12
|
-
afterEach(() => {
|
|
13
|
-
fs.rmSync(projectRoot, { recursive: true, force: true });
|
|
14
|
-
});
|
|
15
|
-
it('is false when the domain directory does not exist', () => {
|
|
16
|
-
const paths = createHarnessCachePaths(projectRoot);
|
|
17
|
-
expect(isDomainWarm('metro', paths)).toBe(false);
|
|
18
|
-
});
|
|
19
|
-
it('is false when the domain directory exists but is empty', () => {
|
|
20
|
-
const paths = createHarnessCachePaths(projectRoot);
|
|
21
|
-
fs.mkdirSync(paths.metroTransform, { recursive: true });
|
|
22
|
-
expect(isDomainWarm('metro', paths)).toBe(false);
|
|
23
|
-
});
|
|
24
|
-
it('is true when the domain directory exists and has entries', () => {
|
|
25
|
-
const paths = createHarnessCachePaths(projectRoot);
|
|
26
|
-
fs.mkdirSync(paths.metroTransform, { recursive: true });
|
|
27
|
-
fs.writeFileSync(path.join(paths.metroTransform, 'entry'), 'data');
|
|
28
|
-
expect(isDomainWarm('metro', paths)).toBe(true);
|
|
29
|
-
});
|
|
30
|
-
it('is false when the path exists but is a file, not a directory', () => {
|
|
31
|
-
const paths = createHarnessCachePaths(projectRoot);
|
|
32
|
-
fs.mkdirSync(path.dirname(paths.metroTransform), { recursive: true });
|
|
33
|
-
fs.writeFileSync(paths.metroTransform, 'not a directory');
|
|
34
|
-
expect(isDomainWarm('metro', paths)).toBe(false);
|
|
35
|
-
});
|
|
36
|
-
});
|