@dotbep/core 0.2.7 → 0.2.8
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/dist/index.d.ts +7 -5
- package/dist/index.js +604 -596
- package/package.json +4 -1
- package/examples/01-participants.ts +0 -127
- package/examples/02-files.ts +0 -100
- package/examples/03-workflows.ts +0 -149
- package/examples/04-bim-uses.ts +0 -70
- package/examples/05-standards.ts +0 -60
- package/examples/06-schedule.ts +0 -124
- package/examples/07-loin.ts +0 -133
- package/examples/08-deliverables.ts +0 -126
- package/examples/09-notes.ts +0 -73
- package/examples/10-llm.ts +0 -109
- package/examples/11-resolved.ts +0 -133
- package/examples/12-history.ts +0 -166
- package/examples/13-engine.ts +0 -152
- package/examples/bep.d.ts +0 -38
- package/examples/example.bep +0 -0
- package/examples/run-all.ts +0 -38
- package/src/base/entity.ts +0 -148
- package/src/base/history.ts +0 -497
- package/src/base/index.ts +0 -5
- package/src/base/singleton.ts +0 -26
- package/src/entities/actions.ts +0 -25
- package/src/entities/adapters.ts +0 -16
- package/src/entities/annexes.ts +0 -17
- package/src/entities/assetTypes.ts +0 -30
- package/src/entities/automations.ts +0 -24
- package/src/entities/bimUses.ts +0 -50
- package/src/entities/deliverables.ts +0 -66
- package/src/entities/disciplines.ts +0 -21
- package/src/entities/effects.ts +0 -28
- package/src/entities/env.ts +0 -17
- package/src/entities/events.ts +0 -24
- package/src/entities/extensions.ts +0 -16
- package/src/entities/flags.ts +0 -17
- package/src/entities/guides.ts +0 -26
- package/src/entities/index.ts +0 -32
- package/src/entities/lbsNodes.ts +0 -193
- package/src/entities/lods.ts +0 -22
- package/src/entities/loin.ts +0 -127
- package/src/entities/lois.ts +0 -22
- package/src/entities/members.ts +0 -137
- package/src/entities/milestones.ts +0 -32
- package/src/entities/notes.ts +0 -27
- package/src/entities/objectives.ts +0 -17
- package/src/entities/phases.ts +0 -17
- package/src/entities/remoteData.ts +0 -17
- package/src/entities/resolvers.ts +0 -20
- package/src/entities/roles.ts +0 -29
- package/src/entities/softwares.ts +0 -26
- package/src/entities/standards.ts +0 -68
- package/src/entities/teams.ts +0 -42
- package/src/entities/workflows.ts +0 -256
- package/src/index.ts +0 -464
- package/src/runtime/Engine.ts +0 -352
- package/src/runtime/MemoryStorage.ts +0 -31
- package/src/runtime/Runtime.ts +0 -106
- package/src/runtime/index.ts +0 -4
- package/src/runtime/transitions.ts +0 -456
- package/src/runtime/types.ts +0 -279
- package/src/types/history.ts +0 -37
- package/src/types/index.ts +0 -24
- package/src/types/resolved.ts +0 -137
- package/src/types/schema.ts +0 -757
- package/src/utils/diff.ts +0 -109
- package/src/utils/index.ts +0 -9
- package/src/utils/integrity.ts +0 -108
- package/src/utils/lbs.ts +0 -116
- package/src/utils/mermaid.ts +0 -110
- package/src/utils/naming.ts +0 -62
- package/src/utils/nomenclature.ts +0 -107
- package/src/utils/normalize.ts +0 -35
- package/src/utils/raci.ts +0 -25
- package/src/utils/textFile.ts +0 -24
- package/tsconfig.json +0 -12
- package/vite.config.ts +0 -24
package/src/base/history.ts
DELETED
|
@@ -1,497 +0,0 @@
|
|
|
1
|
-
import { compare, applyPatch, type Operation } from 'fast-json-patch'
|
|
2
|
-
import type JSZip from 'jszip'
|
|
3
|
-
import type { BEP, BEPVersion, Changelog, Standard } from '../types/schema.js'
|
|
4
|
-
import { diffBep } from '../utils/diff.js'
|
|
5
|
-
import { normalizeBep } from '../utils/normalize.js'
|
|
6
|
-
import type { BepDiff, BepStatus, StandardChange, CommitParams, SquashParams } from '../types/history.js'
|
|
7
|
-
|
|
8
|
-
export type { BepStatus, StandardChange, CommitParams, SquashParams }
|
|
9
|
-
|
|
10
|
-
export type CompareResult = {
|
|
11
|
-
diff: Operation[]
|
|
12
|
-
standards: {
|
|
13
|
-
added: { id: string; name: string }[]
|
|
14
|
-
removed: { id: string; name: string }[]
|
|
15
|
-
contentModified: { id: string; name: string; changedIn: string[] }[]
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export type BEPVersionResolved = {
|
|
20
|
-
version: string
|
|
21
|
-
type: 'patch' | 'version'
|
|
22
|
-
date: string
|
|
23
|
-
description: string
|
|
24
|
-
diff: string | null
|
|
25
|
-
isCurrent: boolean
|
|
26
|
-
author: { email: string; name: string | null } | null
|
|
27
|
-
approvedBy?: { email: string; name: string | null }[]
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
// ─── History ──────────────────────────────────────────────────────────────────
|
|
31
|
-
|
|
32
|
-
export class History {
|
|
33
|
-
constructor(
|
|
34
|
-
private getBep: () => BEP,
|
|
35
|
-
private setBep: (bep: BEP) => void,
|
|
36
|
-
private getZip: () => JSZip,
|
|
37
|
-
) {}
|
|
38
|
-
|
|
39
|
-
// ─── Version helpers ──────────────────────────────────────────────────────
|
|
40
|
-
|
|
41
|
-
private static parseVersion(v: string): { major: number; minor: number } {
|
|
42
|
-
const [major, minor] = v.split('.').map(Number)
|
|
43
|
-
return { major, minor }
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
static compareVersions(a: string, b: string): number {
|
|
47
|
-
const pa = History.parseVersion(a)
|
|
48
|
-
const pb = History.parseVersion(b)
|
|
49
|
-
if (pa.major !== pb.major) return pa.major - pb.major
|
|
50
|
-
return pa.minor - pb.minor
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
private static bumpVersion(current: string, type: BEPVersion['type']): string {
|
|
54
|
-
const [major, minor] = current.split('.').map(Number)
|
|
55
|
-
return type === 'version' ? `${major + 1}.0` : `${major}.${minor + 1}`
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
// ─── Zip helpers ──────────────────────────────────────────────────────────
|
|
59
|
-
|
|
60
|
-
private async readChangelog(): Promise<Changelog | null> {
|
|
61
|
-
const file = this.getZip().file('changelog.json')
|
|
62
|
-
if (!file) return null
|
|
63
|
-
return JSON.parse(await file.async('string')) as Changelog
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
private async readBaseline(): Promise<BEP | null> {
|
|
67
|
-
const file = this.getZip().file('baseline/bep.json')
|
|
68
|
-
if (!file) return null
|
|
69
|
-
return normalizeBep(JSON.parse(await file.async('string')) as BEP)
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
// ─── Standards versioning helpers ─────────────────────────────────────────
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* At commit time: for each standard whose .md content changed since the last
|
|
76
|
-
* snapshot, saves changelog/standards/{id}/v{version}.md.
|
|
77
|
-
*/
|
|
78
|
-
private async snapshotChangedStandards(bep: BEP, newVersion: string): Promise<void> {
|
|
79
|
-
const zip = this.getZip()
|
|
80
|
-
for (const standard of bep.standards) {
|
|
81
|
-
const currentFile = zip.file(standard.contentPath)
|
|
82
|
-
if (!currentFile) continue
|
|
83
|
-
const currentContent = await currentFile.async('string')
|
|
84
|
-
|
|
85
|
-
const prefix = `changelog/standards/${standard.id}/`
|
|
86
|
-
const existingVersions = Object.keys(zip.files)
|
|
87
|
-
.filter(k => k.startsWith(prefix) && k.endsWith('.md') && k.slice(prefix.length).startsWith('v'))
|
|
88
|
-
.map(k => k.slice(prefix.length + 1, -3)) // strip prefix+'v' and '.md' → "0.1"
|
|
89
|
-
|
|
90
|
-
let prevContent: string | null = null
|
|
91
|
-
if (existingVersions.length > 0) {
|
|
92
|
-
const latest = existingVersions.sort((a, b) => History.compareVersions(b, a))[0]
|
|
93
|
-
const snapshotFile = zip.file(`${prefix}v${latest}.md`)
|
|
94
|
-
if (snapshotFile) prevContent = await snapshotFile.async('string')
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
if (prevContent !== currentContent) {
|
|
98
|
-
zip.file(`${prefix}v${newVersion}.md`, currentContent)
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
/**
|
|
104
|
-
* Copies each standards/{uuid}.md to baseline/standards/{id}.md so that
|
|
105
|
-
* discard() can restore the .md files to their last committed state.
|
|
106
|
-
*/
|
|
107
|
-
private async snapshotBaseStandards(bep: BEP): Promise<void> {
|
|
108
|
-
const zip = this.getZip()
|
|
109
|
-
for (const standard of bep.standards) {
|
|
110
|
-
const currentFile = zip.file(standard.contentPath)
|
|
111
|
-
if (!currentFile) continue
|
|
112
|
-
const content = await currentFile.async('string')
|
|
113
|
-
zip.file(`baseline/standards/${standard.id}.md`, content)
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
/**
|
|
118
|
-
* Resolves the .md content of a standard at a specific historical version.
|
|
119
|
-
* Finds the latest snapshot in changelog/standards/{id}/ with version ≤ target.
|
|
120
|
-
* Falls back to the current file if no snapshot exists (content never changed).
|
|
121
|
-
*/
|
|
122
|
-
async resolveStandardContent(standard: Standard, targetVersion: string): Promise<string | null> {
|
|
123
|
-
const zip = this.getZip()
|
|
124
|
-
const prefix = `changelog/standards/${standard.id}/`
|
|
125
|
-
const candidates = Object.keys(zip.files)
|
|
126
|
-
.filter(k => k.startsWith(prefix) && k.endsWith('.md') && k.slice(prefix.length).startsWith('v'))
|
|
127
|
-
.map(k => k.slice(prefix.length + 1, -3)) // strip 'v' and '.md'
|
|
128
|
-
.filter(v => History.compareVersions(v, targetVersion) <= 0)
|
|
129
|
-
.sort((a, b) => History.compareVersions(b, a))
|
|
130
|
-
|
|
131
|
-
if (candidates.length === 0) {
|
|
132
|
-
const currentFile = zip.file(standard.contentPath)
|
|
133
|
-
return currentFile ? currentFile.async('string') : null
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
const file = zip.file(`${prefix}v${candidates[0]}.md`)
|
|
137
|
-
return file ? file.async('string') : null
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
// ─── Public API ───────────────────────────────────────────────────────────
|
|
141
|
-
|
|
142
|
-
async current(): Promise<string> {
|
|
143
|
-
const changelog = await this.readChangelog()
|
|
144
|
-
return changelog?.current ?? '0.0'
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
async list(): Promise<BEPVersion[]> {
|
|
148
|
-
const changelog = await this.readChangelog()
|
|
149
|
-
return changelog?.versions ?? []
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
async commit(params: CommitParams, force = false): Promise<BEPVersion> {
|
|
153
|
-
const zip = this.getZip()
|
|
154
|
-
const currentBep = this.getBep()
|
|
155
|
-
const changelog = await this.readChangelog()
|
|
156
|
-
const baseline = await this.readBaseline()
|
|
157
|
-
const date = new Date().toISOString()
|
|
158
|
-
|
|
159
|
-
if (!baseline) throw new Error('No baseline found — create a BEP with Bep.create() or open one with Bep.open()')
|
|
160
|
-
|
|
161
|
-
if (params.type === 'version') {
|
|
162
|
-
const missing = params.approvedBy.filter(email => !currentBep.members.some(m => m.email === email))
|
|
163
|
-
if (missing.length) throw new Error(`Members not found: ${missing.join(', ')}`)
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
if (!force) {
|
|
167
|
-
const hasChanges = await this.hasPendingChanges()
|
|
168
|
-
if (!hasChanges) throw new Error('No pending changes since last commit')
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
// Bump from current version (defaults to '0.0' — the hidden terminus — on first commit,
|
|
172
|
-
// so the first user commit always produces '0.1' for patch or '1.0' for version).
|
|
173
|
-
const currentVersion = changelog?.current ?? '0.0'
|
|
174
|
-
const newVersionStr = History.bumpVersion(currentVersion, params.type)
|
|
175
|
-
const diffPath = `changelog/v${newVersionStr}.diff.json`
|
|
176
|
-
const inverseDiff: Operation[] = compare(currentBep, baseline)
|
|
177
|
-
|
|
178
|
-
const version: BEPVersion = params.type === 'patch'
|
|
179
|
-
? { version: newVersionStr, type: 'patch', date, author: params.author, description: params.description, diff: diffPath }
|
|
180
|
-
: { version: newVersionStr, type: 'version', date, author: params.author, description: params.description, approvedBy: params.approvedBy, diff: diffPath }
|
|
181
|
-
|
|
182
|
-
const newChangelog: Changelog = {
|
|
183
|
-
current: newVersionStr,
|
|
184
|
-
versions: [...(changelog?.versions ?? []), version],
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
zip.file(diffPath, JSON.stringify(inverseDiff, null, 2))
|
|
188
|
-
await this.snapshotChangedStandards(currentBep, newVersionStr)
|
|
189
|
-
zip.file('baseline/bep.json', JSON.stringify(currentBep, null, 2))
|
|
190
|
-
await this.snapshotBaseStandards(currentBep)
|
|
191
|
-
zip.file('changelog.json', JSON.stringify(newChangelog, null, 2))
|
|
192
|
-
|
|
193
|
-
return version
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
/**
|
|
197
|
-
* Reconstructs the BEP state at the given version (read-only).
|
|
198
|
-
* Applies inverse diffs backward from the current state.
|
|
199
|
-
*/
|
|
200
|
-
async get(version: string): Promise<BEP> {
|
|
201
|
-
const changelog = await this.readChangelog()
|
|
202
|
-
if (!changelog) throw new Error('No changelog found — call commit() first')
|
|
203
|
-
|
|
204
|
-
// Current version — return a clone of the live state
|
|
205
|
-
if (version === changelog.current)
|
|
206
|
-
return JSON.parse(JSON.stringify(this.getBep()))
|
|
207
|
-
|
|
208
|
-
const targetIndex = changelog.versions.findIndex(v => v.version === version)
|
|
209
|
-
|
|
210
|
-
// Terminus not in versions[] (e.g. v0.0 created by Bep.create() before any commit)
|
|
211
|
-
// — fall back to loading the snapshot file directly
|
|
212
|
-
if (targetIndex === -1) {
|
|
213
|
-
const file = this.getZip().file(`changelog/v${version}.json`)
|
|
214
|
-
if (!file) throw new Error(`Version not found: ${version}`)
|
|
215
|
-
return normalizeBep(JSON.parse(await file.async('string')))
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
// Terminus (diff === null) — load the full snapshot for that version
|
|
219
|
-
if (changelog.versions[targetIndex].diff === null) {
|
|
220
|
-
const file = this.getZip().file(`changelog/v${version}.json`)
|
|
221
|
-
if (!file) throw new Error(`Missing terminus: changelog/v${version}.json`)
|
|
222
|
-
return normalizeBep(JSON.parse(await file.async('string')))
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
// Apply inverse diffs backward from current state to reach target version
|
|
226
|
-
const newerVersions = changelog.versions
|
|
227
|
-
.filter(v => History.compareVersions(v.version, version) > 0)
|
|
228
|
-
.sort((a, b) => History.compareVersions(b.version, a.version))
|
|
229
|
-
|
|
230
|
-
const state: BEP = JSON.parse(JSON.stringify(this.getBep()))
|
|
231
|
-
for (const v of newerVersions) {
|
|
232
|
-
if (!v.diff) break
|
|
233
|
-
const diffFile = this.getZip().file(v.diff)
|
|
234
|
-
if (!diffFile) throw new Error(`Missing diff file: ${v.diff}`)
|
|
235
|
-
const ops: Operation[] = JSON.parse(await diffFile.async('string'))
|
|
236
|
-
applyPatch(state, ops)
|
|
237
|
-
}
|
|
238
|
-
return normalizeBep(state)
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
/** Returns the RFC 6902 diff and standards summary between two versions. */
|
|
242
|
-
async compare(versionA: string, versionB: string): Promise<CompareResult> {
|
|
243
|
-
const [stateA, stateB, changelog] = await Promise.all([
|
|
244
|
-
this.get(versionA),
|
|
245
|
-
this.get(versionB),
|
|
246
|
-
this.readChangelog(),
|
|
247
|
-
])
|
|
248
|
-
const diff = compare(stateA, stateB)
|
|
249
|
-
|
|
250
|
-
const fromStdIds = new Set(stateA.standards.map(s => s.id))
|
|
251
|
-
const toStdIds = new Set(stateB.standards.map(s => s.id))
|
|
252
|
-
const added = stateB.standards.filter(s => !fromStdIds.has(s.id)).map(s => ({ id: s.id, name: s.name }))
|
|
253
|
-
const removed = stateA.standards.filter(s => !toStdIds.has(s.id)).map(s => ({ id: s.id, name: s.name }))
|
|
254
|
-
|
|
255
|
-
const versions = changelog?.versions ?? []
|
|
256
|
-
const versionsInRange = versions
|
|
257
|
-
.filter(v => History.compareVersions(v.version, versionA) > 0 && History.compareVersions(v.version, versionB) <= 0)
|
|
258
|
-
.map(v => v.version)
|
|
259
|
-
|
|
260
|
-
const zip = this.getZip()
|
|
261
|
-
const contentModified = (await Promise.all(
|
|
262
|
-
stateB.standards.filter(s => fromStdIds.has(s.id)).map(async s => {
|
|
263
|
-
const changedIn = (await Promise.all(
|
|
264
|
-
versionsInRange.map(async v => {
|
|
265
|
-
const file = zip.file(`changelog/standards/${s.id}/v${v}.md`)
|
|
266
|
-
return file !== null ? v : null
|
|
267
|
-
})
|
|
268
|
-
)).filter((v): v is string => v !== null)
|
|
269
|
-
return changedIn.length > 0 ? { id: s.id, name: s.name, changedIn } : null
|
|
270
|
-
})
|
|
271
|
-
)).filter((r): r is NonNullable<typeof r> => r !== null)
|
|
272
|
-
|
|
273
|
-
return { diff, standards: { added, removed, contentModified } }
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
/**
|
|
277
|
-
* Resets in-memory BEP and restores .md files to the last committed baseline.
|
|
278
|
-
* Standards added since the baseline have their .md deleted.
|
|
279
|
-
*/
|
|
280
|
-
async discard(): Promise<void> {
|
|
281
|
-
const zip = this.getZip()
|
|
282
|
-
const baseline = await this.readBaseline()
|
|
283
|
-
if (!baseline) throw new Error('No baseline found — call commit() first')
|
|
284
|
-
|
|
285
|
-
const baseIds = new Set(baseline.standards.map(s => s.id))
|
|
286
|
-
|
|
287
|
-
// Delete .md files for standards added since baseline
|
|
288
|
-
for (const standard of this.getBep().standards) {
|
|
289
|
-
if (!baseIds.has(standard.id)) zip.remove(standard.contentPath)
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
// Restore .md files for standards that existed at baseline
|
|
293
|
-
for (const standard of baseline.standards) {
|
|
294
|
-
const baseFile = zip.file(`baseline/standards/${standard.id}.md`)
|
|
295
|
-
if (!baseFile) continue
|
|
296
|
-
const content = await baseFile.async('string')
|
|
297
|
-
zip.file(standard.contentPath, content)
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
this.setBep(baseline)
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
/**
|
|
304
|
-
* Non-destructive revert: restores BEP state and .md files to a historical
|
|
305
|
-
* version and immediately commits it as a new version.
|
|
306
|
-
*/
|
|
307
|
-
async revert(version: string, params: CommitParams): Promise<BEPVersion> {
|
|
308
|
-
const zip = this.getZip()
|
|
309
|
-
const historical = await this.get(version)
|
|
310
|
-
|
|
311
|
-
// Restore .md content for each standard to its state at target version
|
|
312
|
-
for (const standard of historical.standards) {
|
|
313
|
-
const content = await this.resolveStandardContent(standard, version)
|
|
314
|
-
if (content !== null) zip.file(standard.contentPath, content)
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
this.setBep(historical)
|
|
318
|
-
return this.commit(params)
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
/**
|
|
322
|
-
* Returns a structured diff of the current BEP state vs the last committed baseline.
|
|
323
|
-
* Includes .md content changes for standards (added, removed, modified, content-modified).
|
|
324
|
-
*/
|
|
325
|
-
async status(): Promise<BepStatus> {
|
|
326
|
-
const zip = this.getZip()
|
|
327
|
-
const baseline = await this.readBaseline()
|
|
328
|
-
if (!baseline) return { hasPendingChanges: false, project: null, sections: {}, changedKeys: [], standards: [] }
|
|
329
|
-
|
|
330
|
-
const currentBep = this.getBep()
|
|
331
|
-
const diff = diffBep(currentBep, baseline)
|
|
332
|
-
|
|
333
|
-
const standards: StandardChange[] = []
|
|
334
|
-
const baseStdMap = new Map(baseline.standards.map(s => [s.id, s]))
|
|
335
|
-
const currStdIds = new Set(currentBep.standards.map(s => s.id))
|
|
336
|
-
|
|
337
|
-
for (const s of currentBep.standards) {
|
|
338
|
-
if (!baseStdMap.has(s.id)) {
|
|
339
|
-
standards.push({ id: s.id, name: s.name, status: 'added' })
|
|
340
|
-
} else {
|
|
341
|
-
const jsonChanged = JSON.stringify(baseStdMap.get(s.id)) !== JSON.stringify(s)
|
|
342
|
-
const baseFile = zip.file(`baseline/standards/${s.id}.md`)
|
|
343
|
-
const currFile = zip.file(s.contentPath)
|
|
344
|
-
const baseContent = baseFile ? await baseFile.async('string') : ''
|
|
345
|
-
const currContent = currFile ? await currFile.async('string') : ''
|
|
346
|
-
if (jsonChanged) standards.push({ id: s.id, name: s.name, status: 'modified' })
|
|
347
|
-
else if (baseContent !== currContent) standards.push({ id: s.id, name: s.name, status: 'content-modified' })
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
|
-
for (const s of baseline.standards) {
|
|
351
|
-
if (!currStdIds.has(s.id)) standards.push({ id: s.id, name: s.name, status: 'removed' })
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
const hasPendingChanges = diff.changedKeys.length > 0 || standards.length > 0
|
|
355
|
-
return { hasPendingChanges, standards, ...diff }
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
/** Shorthand — true if there are uncommitted changes since the last commit. */
|
|
359
|
-
async hasPendingChanges(): Promise<boolean> {
|
|
360
|
-
return (await this.status()).hasPendingChanges
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
/** Returns all versions sorted ascending, with author/approvedBy resolved to { email, name } objects. */
|
|
364
|
-
async listResolved(): Promise<BEPVersionResolved[]> {
|
|
365
|
-
const [versions, current] = await Promise.all([this.list(), this.current()])
|
|
366
|
-
const members = this.getBep().members
|
|
367
|
-
return versions
|
|
368
|
-
.sort((a, b) => History.compareVersions(a.version, b.version))
|
|
369
|
-
.map(v => ({
|
|
370
|
-
version: v.version,
|
|
371
|
-
type: v.type,
|
|
372
|
-
date: v.date,
|
|
373
|
-
description: v.description,
|
|
374
|
-
diff: v.diff,
|
|
375
|
-
isCurrent: v.version === current,
|
|
376
|
-
author: v.author ? { email: v.author, name: members.find(m => m.email === v.author)?.name ?? null } : null,
|
|
377
|
-
...(v.type === 'version' ? {
|
|
378
|
-
approvedBy: v.approvedBy.map(email => ({
|
|
379
|
-
email,
|
|
380
|
-
name: members.find(m => m.email === email)?.name ?? null,
|
|
381
|
-
})),
|
|
382
|
-
} : {}),
|
|
383
|
-
}))
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
/**
|
|
387
|
-
* Resolves the markdown content of a standard at a specific historical version.
|
|
388
|
-
* Returns null if the standard did not exist at that version.
|
|
389
|
-
*/
|
|
390
|
-
async getStandardContent(standardId: string, version: string): Promise<string | null> {
|
|
391
|
-
const historical = await this.get(version)
|
|
392
|
-
const standard = historical.standards.find(s => s.id === standardId)
|
|
393
|
-
if (!standard) return null
|
|
394
|
-
return this.resolveStandardContent(standard, version)
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
/**
|
|
398
|
-
* ⚠️ Destructive: resets the BEP to a historical version and permanently
|
|
399
|
-
* deletes all subsequent diffs, standard snapshots, and changelog entries.
|
|
400
|
-
*/
|
|
401
|
-
async reset(version: string): Promise<void> {
|
|
402
|
-
const zip = this.getZip()
|
|
403
|
-
const changelog = await this.readChangelog()
|
|
404
|
-
if (!changelog) throw new Error('No changelog found')
|
|
405
|
-
const versionInHistory = changelog.versions.find(v => v.version === version)
|
|
406
|
-
const versionFileExists = !!this.getZip().file(`changelog/v${version}.json`)
|
|
407
|
-
if (!versionInHistory && !versionFileExists)
|
|
408
|
-
throw new Error(`Version not found: ${version}`)
|
|
409
|
-
if (version === changelog.current)
|
|
410
|
-
throw new Error(`Already at version ${version}`)
|
|
411
|
-
|
|
412
|
-
const toRemove = changelog.versions.filter(v => History.compareVersions(v.version, version) > 0)
|
|
413
|
-
const targetState = await this.get(version)
|
|
414
|
-
|
|
415
|
-
// Restore .md files to target version content
|
|
416
|
-
for (const standard of targetState.standards) {
|
|
417
|
-
const content = await this.resolveStandardContent(standard, version)
|
|
418
|
-
if (content !== null) zip.file(standard.contentPath, content)
|
|
419
|
-
}
|
|
420
|
-
await this.snapshotBaseStandards(targetState)
|
|
421
|
-
|
|
422
|
-
// Remove diffs and standard snapshots of deleted versions
|
|
423
|
-
for (const v of toRemove) {
|
|
424
|
-
if (v.diff) zip.remove(v.diff)
|
|
425
|
-
Object.keys(zip.files)
|
|
426
|
-
.filter(k => k.startsWith('changelog/standards/') && k.includes(`/v${v.version}.md`))
|
|
427
|
-
.forEach(k => zip.remove(k))
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
const targetSnapshot = JSON.stringify(targetState, null, 2)
|
|
431
|
-
zip.file('changelog.json', JSON.stringify({
|
|
432
|
-
current: version,
|
|
433
|
-
versions: changelog.versions.filter(v => History.compareVersions(v.version, version) <= 0),
|
|
434
|
-
} satisfies Changelog, null, 2))
|
|
435
|
-
zip.file('bep.json', targetSnapshot)
|
|
436
|
-
zip.file('baseline/bep.json', targetSnapshot)
|
|
437
|
-
this.setBep(targetState)
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
/**
|
|
441
|
-
* ⚠️ Destructive: collapses all history into a single new terminus version.
|
|
442
|
-
* All intermediate diffs and snapshots are permanently deleted.
|
|
443
|
-
* newBase must be in X.0 format and greater than the current version.
|
|
444
|
-
*/
|
|
445
|
-
async squash(params: SquashParams): Promise<BEPVersion> {
|
|
446
|
-
const zip = this.getZip()
|
|
447
|
-
const changelog = await this.readChangelog()
|
|
448
|
-
if (!changelog) throw new Error('No changelog found')
|
|
449
|
-
if (!/^\d+\.0$/.test(params.newBase))
|
|
450
|
-
throw new Error(`newBase must be in X.0 format (e.g. "2.0"), got "${params.newBase}"`)
|
|
451
|
-
if (History.compareVersions(params.newBase, changelog.current) <= 0)
|
|
452
|
-
throw new Error(`newBase "${params.newBase}" must be greater than current version "${changelog.current}"`)
|
|
453
|
-
|
|
454
|
-
const bep = this.getBep()
|
|
455
|
-
const missing = params.approvedBy.filter(email => !bep.members.some(m => m.email === email))
|
|
456
|
-
if (missing.length) throw new Error(`Members not found: ${missing.join(', ')}`)
|
|
457
|
-
|
|
458
|
-
// Remove all existing diffs and the old terminus snapshot
|
|
459
|
-
for (const v of changelog.versions) {
|
|
460
|
-
if (v.diff) zip.remove(v.diff)
|
|
461
|
-
}
|
|
462
|
-
Object.keys(zip.files)
|
|
463
|
-
.filter(k => /^changelog\/v[\d.]+\.json$/.test(k))
|
|
464
|
-
.forEach(k => zip.remove(k))
|
|
465
|
-
|
|
466
|
-
// Remove all standard snapshots from changelog
|
|
467
|
-
Object.keys(zip.files)
|
|
468
|
-
.filter(k => k.startsWith('changelog/standards/') && k.endsWith('.md'))
|
|
469
|
-
.forEach(k => zip.remove(k))
|
|
470
|
-
|
|
471
|
-
// Create new terminus + standard snapshots at newBase
|
|
472
|
-
zip.file(`changelog/v${params.newBase}.json`, JSON.stringify(bep, null, 2))
|
|
473
|
-
for (const standard of bep.standards) {
|
|
474
|
-
const currentFile = zip.file(standard.contentPath)
|
|
475
|
-
if (!currentFile) continue
|
|
476
|
-
const content = await currentFile.async('string')
|
|
477
|
-
zip.file(`changelog/standards/${standard.id}/v${params.newBase}.md`, content)
|
|
478
|
-
}
|
|
479
|
-
|
|
480
|
-
// Sync baseline to current state
|
|
481
|
-
zip.file('baseline/bep.json', JSON.stringify(bep, null, 2))
|
|
482
|
-
await this.snapshotBaseStandards(bep)
|
|
483
|
-
|
|
484
|
-
const newEntry: BEPVersion = {
|
|
485
|
-
version: params.newBase,
|
|
486
|
-
type: 'version',
|
|
487
|
-
date: new Date().toISOString(),
|
|
488
|
-
author: params.author,
|
|
489
|
-
description: params.description,
|
|
490
|
-
approvedBy: params.approvedBy,
|
|
491
|
-
diff: null,
|
|
492
|
-
}
|
|
493
|
-
zip.file('changelog.json', JSON.stringify({ current: params.newBase, versions: [newEntry] } satisfies Changelog, null, 2))
|
|
494
|
-
|
|
495
|
-
return newEntry
|
|
496
|
-
}
|
|
497
|
-
}
|
package/src/base/index.ts
DELETED
package/src/base/singleton.ts
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import { z } from 'zod'
|
|
2
|
-
import type { BEP } from '../types/schema.js'
|
|
3
|
-
|
|
4
|
-
export class Singleton<T extends object> {
|
|
5
|
-
constructor(
|
|
6
|
-
private getItem: () => T,
|
|
7
|
-
private setItem: (value: T) => void,
|
|
8
|
-
private schema: z.ZodType<T>,
|
|
9
|
-
private validate?: (item: T, bep: BEP) => string[],
|
|
10
|
-
private getBep?: () => BEP,
|
|
11
|
-
) {}
|
|
12
|
-
|
|
13
|
-
get(): T {
|
|
14
|
-
return this.getItem()
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
update(patch: { [K in keyof T]?: T[K] }): T {
|
|
18
|
-
const merged = this.schema.parse({ ...this.getItem(), ...patch })
|
|
19
|
-
if (this.validate && this.getBep) {
|
|
20
|
-
const errors = this.validate(merged, this.getBep())
|
|
21
|
-
if (errors.length) throw new Error(errors.join('; '))
|
|
22
|
-
}
|
|
23
|
-
this.setItem(merged)
|
|
24
|
-
return merged
|
|
25
|
-
}
|
|
26
|
-
}
|
package/src/entities/actions.ts
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import type { BEP, Action } from '../types/schema.js'
|
|
2
|
-
import { Entity } from '../base/entity.js'
|
|
3
|
-
import { ActionSchema } from '../types/schema.js'
|
|
4
|
-
|
|
5
|
-
export class Actions extends Entity<Action, true> {
|
|
6
|
-
constructor(getBep: () => BEP) {
|
|
7
|
-
super(
|
|
8
|
-
() => getBep().actions,
|
|
9
|
-
getBep,
|
|
10
|
-
{
|
|
11
|
-
key: 'actions',
|
|
12
|
-
schema: ActionSchema,
|
|
13
|
-
autoId: true,
|
|
14
|
-
beforeRemove: (id, bep) => {
|
|
15
|
-
for (const wf of bep.workflows) {
|
|
16
|
-
for (const [nodeKey, node] of Object.entries(wf.diagram.nodes)) {
|
|
17
|
-
if (node.type === 'process' && node.actionId === id)
|
|
18
|
-
throw new Error(`Referenced by: workflows["${wf.id}"].diagram.nodes["${nodeKey}"].actionId`)
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
},
|
|
22
|
-
},
|
|
23
|
-
)
|
|
24
|
-
}
|
|
25
|
-
}
|
package/src/entities/adapters.ts
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import type { BEP, Adapter } from '../types/schema.js'
|
|
2
|
-
import { Entity } from '../base/entity.js'
|
|
3
|
-
import { AdapterSchema } from '../types/schema.js'
|
|
4
|
-
|
|
5
|
-
export class Adapters extends Entity<Adapter> {
|
|
6
|
-
constructor(getBep: () => BEP) {
|
|
7
|
-
super(
|
|
8
|
-
() => getBep().adapters,
|
|
9
|
-
getBep,
|
|
10
|
-
{
|
|
11
|
-
key: 'adapters',
|
|
12
|
-
schema: AdapterSchema,
|
|
13
|
-
},
|
|
14
|
-
)
|
|
15
|
-
}
|
|
16
|
-
}
|
package/src/entities/annexes.ts
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import type { BEP, Annex } from '../types/schema.js'
|
|
2
|
-
import { Entity } from '../base/entity.js'
|
|
3
|
-
import { AnnexSchema } from '../types/schema.js'
|
|
4
|
-
|
|
5
|
-
export class Annexes extends Entity<Annex, true> {
|
|
6
|
-
constructor(getBep: () => BEP) {
|
|
7
|
-
super(
|
|
8
|
-
() => getBep().annexes,
|
|
9
|
-
getBep,
|
|
10
|
-
{
|
|
11
|
-
key: 'annexes',
|
|
12
|
-
schema: AnnexSchema,
|
|
13
|
-
autoId: true,
|
|
14
|
-
},
|
|
15
|
-
)
|
|
16
|
-
}
|
|
17
|
-
}
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import type { BEP, AssetType, Extension } from '../types/schema.js'
|
|
2
|
-
import { Entity } from '../base/entity.js'
|
|
3
|
-
import { AssetTypeSchema } from '../types/schema.js'
|
|
4
|
-
import type { AssetTypeResolved } from '../types/resolved.js'
|
|
5
|
-
import { validateTokenValue } from '../utils/naming.js'
|
|
6
|
-
|
|
7
|
-
export class AssetTypes extends Entity<AssetType> {
|
|
8
|
-
constructor(getBep: () => BEP) {
|
|
9
|
-
super(
|
|
10
|
-
() => getBep().assetTypes,
|
|
11
|
-
getBep,
|
|
12
|
-
{
|
|
13
|
-
key: 'assetTypes',
|
|
14
|
-
schema: AssetTypeSchema,
|
|
15
|
-
validate: (a, bep) => {
|
|
16
|
-
const err = validateTokenValue('assetType', a.id, bep.deliverableNamingConvention)
|
|
17
|
-
return err ? [err] : []
|
|
18
|
-
},
|
|
19
|
-
},
|
|
20
|
-
)
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
listResolved(): AssetTypeResolved[] {
|
|
24
|
-
const bep = this.getBep()
|
|
25
|
-
return bep.assetTypes.map(dt => ({
|
|
26
|
-
...dt,
|
|
27
|
-
extensions: (dt.extensionIds ?? []).map(id => bep.extensions.find(e => e.id === id)).filter(Boolean) as Extension[],
|
|
28
|
-
}))
|
|
29
|
-
}
|
|
30
|
-
}
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import type { BEP, FlowAutomation } from '../types/schema.js'
|
|
2
|
-
import { Entity } from '../base/entity.js'
|
|
3
|
-
import { FlowAutomationSchema } from '../types/schema.js'
|
|
4
|
-
|
|
5
|
-
export class Automations extends Entity<FlowAutomation> {
|
|
6
|
-
constructor(getBep: () => BEP) {
|
|
7
|
-
super(
|
|
8
|
-
() => getBep().automations,
|
|
9
|
-
getBep,
|
|
10
|
-
{
|
|
11
|
-
key: 'automations',
|
|
12
|
-
schema: FlowAutomationSchema,
|
|
13
|
-
beforeRemove: (id, bep) => {
|
|
14
|
-
for (const wf of bep.workflows) {
|
|
15
|
-
for (const [nodeKey, node] of Object.entries(wf.diagram.nodes)) {
|
|
16
|
-
if (node.type === 'automation' && node.automationId === id)
|
|
17
|
-
throw new Error(`Referenced by: workflows["${wf.id}"].diagram.nodes["${nodeKey}"].automationId`)
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
},
|
|
21
|
-
},
|
|
22
|
-
)
|
|
23
|
-
}
|
|
24
|
-
}
|