buildwright 0.0.14 → 0.0.15
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 +6 -0
- package/package.json +2 -1
- package/src/commands/update.js +65 -6
- package/src/commands/update.test.js +86 -0
package/README.md
CHANGED
|
@@ -95,6 +95,12 @@ and Buildwright-owned support scripts. It also removes paths from the old
|
|
|
95
95
|
pre-`/bw-work` model so generated tool configs do not contain both old and new
|
|
96
96
|
workflows.
|
|
97
97
|
|
|
98
|
+
Steering is only touched if Buildwright ships the file. The default
|
|
99
|
+
`philosophy.md` is refreshed in place only when it is unmodified (a known shipped
|
|
100
|
+
version); a customized `philosophy.md` is preserved. Any steering file Buildwright
|
|
101
|
+
does not ship — your `tech.md`, `product.md`, or org-injected docs such as
|
|
102
|
+
`quality-gates.md` — is never deleted or overwritten.
|
|
103
|
+
|
|
98
104
|
### From Source
|
|
99
105
|
|
|
100
106
|
```bash
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "buildwright",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.15",
|
|
4
4
|
"description": "Lightweight engineering workflow for agent-led development.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"engines": {
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
"templates/"
|
|
16
16
|
],
|
|
17
17
|
"scripts": {
|
|
18
|
+
"test": "node --test",
|
|
18
19
|
"prepack": "node scripts/prepack.js",
|
|
19
20
|
"postpack": "node scripts/postpack.js"
|
|
20
21
|
},
|
package/src/commands/update.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const fs = require('fs');
|
|
4
4
|
const path = require('path');
|
|
5
|
+
const crypto = require('crypto');
|
|
5
6
|
const https = require('https');
|
|
6
7
|
const { execSync } = require('child_process');
|
|
7
8
|
const { isBuildwrightInstalled } = require('../utils/detect');
|
|
@@ -35,9 +36,6 @@ const REMOVED_PATHS = [
|
|
|
35
36
|
'.buildwright/claws',
|
|
36
37
|
'.buildwright/skills',
|
|
37
38
|
'.buildwright/tasks/TEMPLATE.md',
|
|
38
|
-
'.buildwright/steering/quality-gates.md',
|
|
39
|
-
'.buildwright/steering/naming-conventions.md',
|
|
40
|
-
'.buildwright/steering/engineering-philosophy.md',
|
|
41
39
|
'docs/requirements/TEMPLATE.md',
|
|
42
40
|
'.claude/commands/bw-new-feature.md',
|
|
43
41
|
'.claude/commands/bw-quick.md',
|
|
@@ -65,6 +63,60 @@ const REMOVED_PATHS = [
|
|
|
65
63
|
'skills/bw-help',
|
|
66
64
|
];
|
|
67
65
|
|
|
66
|
+
// Steering files Buildwright ships and may update in place. Keyed by filename,
|
|
67
|
+
// each value is the set of SHA-256 hashes of every version Buildwright has ever
|
|
68
|
+
// shipped for that file. An existing steering file is overwritten on update ONLY
|
|
69
|
+
// when its hash is in this set (i.e. it is an unmodified, previously-shipped
|
|
70
|
+
// copy); a customized file (hash absent) is preserved. Files Buildwright does not
|
|
71
|
+
// ship at all are never touched.
|
|
72
|
+
//
|
|
73
|
+
// RELEASE STEP: whenever a managed steering file changes, append the superseded
|
|
74
|
+
// version's SHA-256 here so unmodified installs keep auto-updating.
|
|
75
|
+
const MANAGED_STEERING_HASHES = {
|
|
76
|
+
'philosophy.md': new Set([
|
|
77
|
+
'476fe491e139a211d9483942bd60435513813227c589ae0c29ba1e082672757a',
|
|
78
|
+
]),
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
function sha256(filePath) {
|
|
82
|
+
return crypto.createHash('sha256').update(fs.readFileSync(filePath)).digest('hex');
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Copy shipped steering files into dest. New shipped files are added. An existing
|
|
87
|
+
* file is overwritten only when its content matches a known shipped hash (i.e. the
|
|
88
|
+
* user has not customized it); customized or unmanaged files are preserved. Files
|
|
89
|
+
* not shipped by Buildwright are never touched. Steering is a flat dir of .md files.
|
|
90
|
+
* @param {object} [managedHashes] - filename -> Set of known shipped hashes
|
|
91
|
+
* @returns {{updated: string[], preserved: string[]}}
|
|
92
|
+
*/
|
|
93
|
+
function updateSteering(src, dest, managedHashes = MANAGED_STEERING_HASHES) {
|
|
94
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
95
|
+
const updated = [];
|
|
96
|
+
const preserved = [];
|
|
97
|
+
for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
|
|
98
|
+
if (!entry.isFile()) continue;
|
|
99
|
+
const realSrc = fs.realpathSync(path.join(src, entry.name));
|
|
100
|
+
const destPath = path.join(dest, entry.name);
|
|
101
|
+
if (!fs.existsSync(destPath)) {
|
|
102
|
+
fs.copyFileSync(realSrc, destPath);
|
|
103
|
+
updated.push(entry.name);
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
const known = managedHashes[entry.name];
|
|
107
|
+
const localHash = sha256(destPath);
|
|
108
|
+
if (known && known.has(localHash)) {
|
|
109
|
+
if (sha256(realSrc) !== localHash) {
|
|
110
|
+
fs.copyFileSync(realSrc, destPath);
|
|
111
|
+
updated.push(entry.name);
|
|
112
|
+
}
|
|
113
|
+
} else {
|
|
114
|
+
preserved.push(entry.name);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return { updated, preserved };
|
|
118
|
+
}
|
|
119
|
+
|
|
68
120
|
/**
|
|
69
121
|
* Download a URL following redirects. Returns a Buffer.
|
|
70
122
|
*/
|
|
@@ -120,7 +172,7 @@ async function update() {
|
|
|
120
172
|
|
|
121
173
|
console.log(`${BOLD}Updating Buildwright in ${cwd}...${RESET}\n`);
|
|
122
174
|
console.log(`Updating: ${UPDATE_DIRS.map(d => `.buildwright/${d}/`).join(', ')}`);
|
|
123
|
-
console.log(`Preserving:
|
|
175
|
+
console.log(`Preserving: customized and org-injected steering files (only an unmodified philosophy.md is refreshed)\n`);
|
|
124
176
|
|
|
125
177
|
let tmpDir;
|
|
126
178
|
try {
|
|
@@ -163,7 +215,14 @@ async function update() {
|
|
|
163
215
|
}
|
|
164
216
|
console.log(` Updating .buildwright/${dir}/`);
|
|
165
217
|
fs.mkdirSync(dest, { recursive: true });
|
|
166
|
-
|
|
218
|
+
if (dir === 'steering') {
|
|
219
|
+
const { preserved } = updateSteering(src, dest);
|
|
220
|
+
if (preserved.length > 0) {
|
|
221
|
+
console.log(` Preserved customized steering files: ${preserved.join(', ')}`);
|
|
222
|
+
}
|
|
223
|
+
} else {
|
|
224
|
+
copyDir(src, dest);
|
|
225
|
+
}
|
|
167
226
|
}
|
|
168
227
|
|
|
169
228
|
for (const file of SUPPORT_FILES) {
|
|
@@ -208,4 +267,4 @@ async function update() {
|
|
|
208
267
|
}
|
|
209
268
|
}
|
|
210
269
|
|
|
211
|
-
module.exports = { update };
|
|
270
|
+
module.exports = { update, updateSteering, REMOVED_PATHS, MANAGED_STEERING_HASHES };
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const test = require('node:test');
|
|
4
|
+
const assert = require('node:assert');
|
|
5
|
+
const fs = require('fs');
|
|
6
|
+
const os = require('os');
|
|
7
|
+
const path = require('path');
|
|
8
|
+
const crypto = require('crypto');
|
|
9
|
+
|
|
10
|
+
const { updateSteering, REMOVED_PATHS, MANAGED_STEERING_HASHES } = require('./update');
|
|
11
|
+
|
|
12
|
+
const sha256 = (s) => crypto.createHash('sha256').update(s).digest('hex');
|
|
13
|
+
|
|
14
|
+
function tmpProject() {
|
|
15
|
+
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'bw-update-test-'));
|
|
16
|
+
const src = path.join(root, 'src');
|
|
17
|
+
const dest = path.join(root, 'dest');
|
|
18
|
+
fs.mkdirSync(src, { recursive: true });
|
|
19
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
20
|
+
return { root, src, dest };
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
test('copies a shipped steering file that is absent locally', () => {
|
|
24
|
+
const { src, dest } = tmpProject();
|
|
25
|
+
fs.writeFileSync(path.join(src, 'philosophy.md'), 'NEW philosophy');
|
|
26
|
+
|
|
27
|
+
const { updated, preserved } = updateSteering(src, dest, {});
|
|
28
|
+
|
|
29
|
+
assert.deepStrictEqual(updated, ['philosophy.md']);
|
|
30
|
+
assert.deepStrictEqual(preserved, []);
|
|
31
|
+
assert.strictEqual(fs.readFileSync(path.join(dest, 'philosophy.md'), 'utf8'), 'NEW philosophy');
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test('overwrites an unmodified shipped file (hash match) with the latest', () => {
|
|
35
|
+
const { src, dest } = tmpProject();
|
|
36
|
+
const oldContent = 'OLD philosophy';
|
|
37
|
+
fs.writeFileSync(path.join(dest, 'philosophy.md'), oldContent);
|
|
38
|
+
fs.writeFileSync(path.join(src, 'philosophy.md'), 'NEW philosophy');
|
|
39
|
+
|
|
40
|
+
const managed = { 'philosophy.md': new Set([sha256(oldContent)]) };
|
|
41
|
+
const { updated, preserved } = updateSteering(src, dest, managed);
|
|
42
|
+
|
|
43
|
+
assert.deepStrictEqual(updated, ['philosophy.md']);
|
|
44
|
+
assert.deepStrictEqual(preserved, []);
|
|
45
|
+
assert.strictEqual(fs.readFileSync(path.join(dest, 'philosophy.md'), 'utf8'), 'NEW philosophy');
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test('preserves a customized steering file (hash not in the managed set)', () => {
|
|
49
|
+
const { src, dest } = tmpProject();
|
|
50
|
+
const customContent = 'CUSTOM org philosophy';
|
|
51
|
+
fs.writeFileSync(path.join(dest, 'philosophy.md'), customContent);
|
|
52
|
+
fs.writeFileSync(path.join(src, 'philosophy.md'), 'NEW philosophy');
|
|
53
|
+
|
|
54
|
+
// managed set only knows some other (shipped) hash, not the custom content
|
|
55
|
+
const managed = { 'philosophy.md': new Set([sha256('some shipped version')]) };
|
|
56
|
+
const { updated, preserved } = updateSteering(src, dest, managed);
|
|
57
|
+
|
|
58
|
+
assert.deepStrictEqual(updated, []);
|
|
59
|
+
assert.deepStrictEqual(preserved, ['philosophy.md']);
|
|
60
|
+
assert.strictEqual(fs.readFileSync(path.join(dest, 'philosophy.md'), 'utf8'), customContent);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test('never touches an org steering file that Buildwright does not ship', () => {
|
|
64
|
+
const { src, dest } = tmpProject();
|
|
65
|
+
// Buildwright ships only philosophy.md
|
|
66
|
+
fs.writeFileSync(path.join(src, 'philosophy.md'), 'NEW philosophy');
|
|
67
|
+
// org injected its own doc at a colliding-style path
|
|
68
|
+
const orgDoc = 'org quality gates';
|
|
69
|
+
fs.writeFileSync(path.join(dest, 'quality-gates.md'), orgDoc);
|
|
70
|
+
|
|
71
|
+
updateSteering(src, dest, MANAGED_STEERING_HASHES);
|
|
72
|
+
|
|
73
|
+
assert.strictEqual(fs.existsSync(path.join(dest, 'quality-gates.md')), true);
|
|
74
|
+
assert.strictEqual(fs.readFileSync(path.join(dest, 'quality-gates.md'), 'utf8'), orgDoc);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test('REMOVED_PATHS no longer deletes org-injected steering docs', () => {
|
|
78
|
+
assert.ok(!REMOVED_PATHS.includes('.buildwright/steering/quality-gates.md'));
|
|
79
|
+
assert.ok(!REMOVED_PATHS.includes('.buildwright/steering/naming-conventions.md'));
|
|
80
|
+
assert.ok(!REMOVED_PATHS.includes('.buildwright/steering/engineering-philosophy.md'));
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
test('REMOVED_PATHS still cleans up non-steering legacy paths', () => {
|
|
84
|
+
assert.ok(REMOVED_PATHS.includes('.buildwright/commands/bw-quick.md'));
|
|
85
|
+
assert.ok(REMOVED_PATHS.includes('.buildwright/agents/architect.md'));
|
|
86
|
+
});
|