@teambit/objects 0.0.0-00042bc99514ea12d897f81bcd0181e2b034c615
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/fixtures/version-model-extended.json +48 -0
- package/dist/fixtures/version-model-object.json +87 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.js +371 -0
- package/dist/index.js.map +1 -0
- package/dist/models/dependencies-graph.d.ts +46 -0
- package/dist/models/dependencies-graph.js +105 -0
- package/dist/models/dependencies-graph.js.map +1 -0
- package/dist/models/detach-heads.d.ts +25 -0
- package/dist/models/detach-heads.js +84 -0
- package/dist/models/detach-heads.js.map +1 -0
- package/dist/models/export-metadata.d.ts +24 -0
- package/dist/models/export-metadata.js +74 -0
- package/dist/models/export-metadata.js.map +1 -0
- package/dist/models/index.d.ts +10 -0
- package/dist/models/index.js +125 -0
- package/dist/models/index.js.map +1 -0
- package/dist/models/lane-history.d.ts +43 -0
- package/dist/models/lane-history.js +131 -0
- package/dist/models/lane-history.js.map +1 -0
- package/dist/models/lane-history.spec.d.ts +1 -0
- package/dist/models/lane-history.spec.js +46 -0
- package/dist/models/lane-history.spec.js.map +1 -0
- package/dist/models/lane.d.ts +128 -0
- package/dist/models/lane.js +485 -0
- package/dist/models/lane.js.map +1 -0
- package/dist/models/model-component.d.ts +338 -0
- package/dist/models/model-component.js +1509 -0
- package/dist/models/model-component.js.map +1 -0
- package/dist/models/model-component.spec.d.ts +1 -0
- package/dist/models/model-component.spec.js +71 -0
- package/dist/models/model-component.spec.js.map +1 -0
- package/dist/models/scopeMeta.d.ts +20 -0
- package/dist/models/scopeMeta.js +68 -0
- package/dist/models/scopeMeta.js.map +1 -0
- package/dist/models/source.d.ts +10 -0
- package/dist/models/source.js +42 -0
- package/dist/models/source.js.map +1 -0
- package/dist/models/symlink.d.ts +30 -0
- package/dist/models/symlink.js +91 -0
- package/dist/models/symlink.js.map +1 -0
- package/dist/models/version-history.d.ts +59 -0
- package/dist/models/version-history.js +296 -0
- package/dist/models/version-history.js.map +1 -0
- package/dist/models/version.d.ts +288 -0
- package/dist/models/version.js +780 -0
- package/dist/models/version.js.map +1 -0
- package/dist/models/version.spec.d.ts +1 -0
- package/dist/models/version.spec.js +332 -0
- package/dist/models/version.spec.js.map +1 -0
- package/dist/objects/bit-object-list.d.ts +24 -0
- package/dist/objects/bit-object-list.js +65 -0
- package/dist/objects/bit-object-list.js.map +1 -0
- package/dist/objects/index.d.ts +5 -0
- package/dist/objects/index.js +60 -0
- package/dist/objects/index.js.map +1 -0
- package/dist/objects/object-list-to-graph.d.ts +13 -0
- package/dist/objects/object-list-to-graph.js +93 -0
- package/dist/objects/object-list-to-graph.js.map +1 -0
- package/dist/objects/object-list.d.ts +58 -0
- package/dist/objects/object-list.js +389 -0
- package/dist/objects/object-list.js.map +1 -0
- package/dist/objects/object.d.ts +35 -0
- package/dist/objects/object.js +189 -0
- package/dist/objects/object.js.map +1 -0
- package/dist/objects/objects-readable-generator.d.ts +31 -0
- package/dist/objects/objects-readable-generator.js +203 -0
- package/dist/objects/objects-readable-generator.js.map +1 -0
- package/dist/objects/raw-object.d.ts +23 -0
- package/dist/objects/raw-object.js +145 -0
- package/dist/objects/raw-object.js.map +1 -0
- package/dist/objects/ref.d.ts +14 -0
- package/dist/objects/ref.js +45 -0
- package/dist/objects/ref.js.map +1 -0
- package/dist/objects/repository.d.ts +165 -0
- package/dist/objects/repository.js +866 -0
- package/dist/objects/repository.js.map +1 -0
- package/dist/objects/scope-index.d.ts +74 -0
- package/dist/objects/scope-index.js +255 -0
- package/dist/objects/scope-index.js.map +1 -0
- package/dist/objects/scope-index.spec.d.ts +1 -0
- package/dist/objects/scope-index.spec.js +152 -0
- package/dist/objects/scope-index.spec.js.map +1 -0
- package/dist/objects.aspect.d.ts +2 -0
- package/dist/objects.aspect.js +18 -0
- package/dist/objects.aspect.js.map +1 -0
- package/dist/objects.main.runtime.d.ts +7 -0
- package/dist/objects.main.runtime.js +36 -0
- package/dist/objects.main.runtime.js.map +1 -0
- package/dist/preview-1781548568469.js +7 -0
- package/fixtures/version-model-extended.json +48 -0
- package/fixtures/version-model-object.json +87 -0
- package/models/dependencies-graph.ts +120 -0
- package/models/detach-heads.ts +79 -0
- package/models/export-metadata.ts +56 -0
- package/models/index.ts +11 -0
- package/models/lane-history.spec.ts +35 -0
- package/models/lane-history.ts +131 -0
- package/models/lane.ts +400 -0
- package/models/model-component.spec.ts +55 -0
- package/models/model-component.ts +1522 -0
- package/models/scopeMeta.ts +58 -0
- package/models/source.ts +31 -0
- package/models/symlink.ts +66 -0
- package/models/version-history.ts +283 -0
- package/models/version.spec.ts +280 -0
- package/models/version.ts +821 -0
- package/objects/bit-object-list.ts +59 -0
- package/objects/index.ts +6 -0
- package/objects/object-list-to-graph.ts +69 -0
- package/objects/object-list.ts +327 -0
- package/objects/object.ts +152 -0
- package/objects/objects-readable-generator.ts +174 -0
- package/objects/raw-object.ts +132 -0
- package/objects/ref.ts +45 -0
- package/objects/repository.ts +780 -0
- package/objects/scope-index.spec.ts +95 -0
- package/objects/scope-index.ts +197 -0
- package/package.json +97 -0
- package/types/asset.d.ts +41 -0
- package/types/style.d.ts +42 -0
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { expect } from 'chai';
|
|
2
|
+
|
|
3
|
+
import { LaneHistory } from './lane-history';
|
|
4
|
+
|
|
5
|
+
const makeItem = (date: string) => ({
|
|
6
|
+
log: { date, username: 'tester' },
|
|
7
|
+
components: [],
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
describe('LaneHistory', () => {
|
|
11
|
+
describe('getHistoryIds', () => {
|
|
12
|
+
it('returns ids sorted by date even when insertion order is not chronological', () => {
|
|
13
|
+
const laneHistory = LaneHistory.parse(
|
|
14
|
+
JSON.stringify({
|
|
15
|
+
name: 'tmp4',
|
|
16
|
+
scope: 'my-scope',
|
|
17
|
+
laneHash: 'abc',
|
|
18
|
+
history: {
|
|
19
|
+
'newer-local': makeItem('1746369091000'), // 5/4/2026, 12:51 PM
|
|
20
|
+
'older-remote-1': makeItem('1746127977000'), // 5/1/2026, 3:32 PM
|
|
21
|
+
'older-remote-2': makeItem('1746127986000'), // 5/1/2026, 3:33 PM
|
|
22
|
+
'between-remote': makeItem('1746358729000'), // 5/4/2026, 9:58 AM
|
|
23
|
+
},
|
|
24
|
+
})
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
expect(laneHistory.getHistoryIds()).to.deep.equal([
|
|
28
|
+
'older-remote-1',
|
|
29
|
+
'older-remote-2',
|
|
30
|
+
'between-remote',
|
|
31
|
+
'newer-local',
|
|
32
|
+
]);
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
});
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { v4 } from 'uuid';
|
|
2
|
+
import { getStringifyArgs } from '@teambit/legacy.utils';
|
|
3
|
+
import { getBasicLog } from '@teambit/harmony.modules.get-basic-log';
|
|
4
|
+
import { BitObject } from '../objects';
|
|
5
|
+
import type Lane from './lane';
|
|
6
|
+
|
|
7
|
+
type Log = { date: string; username?: string; email?: string; message?: string };
|
|
8
|
+
|
|
9
|
+
export type HistoryItem = {
|
|
10
|
+
log: Log;
|
|
11
|
+
components: string[];
|
|
12
|
+
deleted?: string[];
|
|
13
|
+
// hidden lane.updateDependents at the time of the snapshot. Recorded in their own field so
|
|
14
|
+
// `historyItem.components` keeps its workspace-checkout/revert contract intact — those flows
|
|
15
|
+
// must not materialize hidden entries into the bitmap.
|
|
16
|
+
updateDependents?: string[];
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
type History = { [uuid: string]: HistoryItem };
|
|
20
|
+
|
|
21
|
+
type LaneHistoryProps = {
|
|
22
|
+
name: string;
|
|
23
|
+
scope: string;
|
|
24
|
+
laneHash: string;
|
|
25
|
+
history: History;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export class LaneHistory extends BitObject {
|
|
29
|
+
private name: string;
|
|
30
|
+
private scope: string;
|
|
31
|
+
private laneHash: string;
|
|
32
|
+
private history: History;
|
|
33
|
+
|
|
34
|
+
constructor(props: LaneHistoryProps) {
|
|
35
|
+
super();
|
|
36
|
+
this.name = props.name;
|
|
37
|
+
this.scope = props.scope;
|
|
38
|
+
this.laneHash = props.laneHash;
|
|
39
|
+
this.history = props.history;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
id() {
|
|
43
|
+
return `${this.laneHash}:${LaneHistory.name}`;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
static fromLaneObject(laneObject: Lane): LaneHistory {
|
|
47
|
+
return new LaneHistory({
|
|
48
|
+
scope: laneObject.scope,
|
|
49
|
+
name: laneObject.name,
|
|
50
|
+
laneHash: laneObject.hash().toString(),
|
|
51
|
+
history: {},
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
toObject(): Record<string, any> {
|
|
56
|
+
return {
|
|
57
|
+
name: this.name,
|
|
58
|
+
scope: this.scope,
|
|
59
|
+
laneHash: this.laneHash,
|
|
60
|
+
history: this.history,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
toString(pretty: boolean): string {
|
|
65
|
+
const args = getStringifyArgs(pretty);
|
|
66
|
+
return JSON.stringify(this.toObject(), ...args);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
toBuffer(pretty): Buffer {
|
|
70
|
+
return Buffer.from(this.toString(pretty));
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
getHistory(): History {
|
|
74
|
+
return this.history;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
getHistoryIds(): string[] {
|
|
78
|
+
return Object.entries(this.history)
|
|
79
|
+
.sort(([, a], [, b]) => Number(a.log.date) - Number(b.log.date))
|
|
80
|
+
.map(([id]) => id);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async addHistory(laneObj: Lane, msg?: string, historyKey?: string) {
|
|
84
|
+
const log: Log = await getBasicLog();
|
|
85
|
+
if (msg) log.message = msg;
|
|
86
|
+
const components = laneObj.toComponentIds().toStringArray();
|
|
87
|
+
const deleted = laneObj.components
|
|
88
|
+
.filter((c) => c.isDeleted)
|
|
89
|
+
.map((c) => c.id.changeVersion(c.head.toString()).toString());
|
|
90
|
+
// Always write `updateDependents` (even when empty) so checkout/revert can distinguish a
|
|
91
|
+
// post-PR entry that legitimately had no hidden entries (drop current hidden) from a legacy
|
|
92
|
+
// pre-PR entry that never recorded the field at all (leave current hidden alone).
|
|
93
|
+
const updateDependents = (laneObj.updateDependents || []).map((id) => id.toString());
|
|
94
|
+
this.history[historyKey || v4()] = {
|
|
95
|
+
log,
|
|
96
|
+
components,
|
|
97
|
+
...(deleted.length && { deleted }),
|
|
98
|
+
updateDependents,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
removeHistoryEntries(keys: string[]) {
|
|
103
|
+
for (const key of keys) {
|
|
104
|
+
delete this.history[key];
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
merge(laneHistory: LaneHistory) {
|
|
109
|
+
this.history = { ...this.history, ...laneHistory.history };
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
static create(name: string, scope: string, laneHash: string) {
|
|
113
|
+
return new LaneHistory({
|
|
114
|
+
name,
|
|
115
|
+
scope,
|
|
116
|
+
laneHash,
|
|
117
|
+
history: {},
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
static parse(contents: string): LaneHistory {
|
|
122
|
+
const parsed = JSON.parse(contents);
|
|
123
|
+
const props: LaneHistoryProps = {
|
|
124
|
+
name: parsed.name,
|
|
125
|
+
scope: parsed.scope,
|
|
126
|
+
laneHash: parsed.laneHash,
|
|
127
|
+
history: parsed.history,
|
|
128
|
+
};
|
|
129
|
+
return new LaneHistory(props);
|
|
130
|
+
}
|
|
131
|
+
}
|
package/models/lane.ts
ADDED
|
@@ -0,0 +1,400 @@
|
|
|
1
|
+
import { v4 } from 'uuid';
|
|
2
|
+
import { gte } from 'semver';
|
|
3
|
+
import { cloneDeep, isEqual, pickBy } from 'lodash';
|
|
4
|
+
import { BitError } from '@teambit/bit-error';
|
|
5
|
+
import { ComponentID, ComponentIdList } from '@teambit/component-id';
|
|
6
|
+
import { isSnap } from '@teambit/component-version';
|
|
7
|
+
import { LaneId, DEFAULT_LANE, LANE_REMOTE_DELIMITER } from '@teambit/lane-id';
|
|
8
|
+
import type { Scope } from '@teambit/legacy.scope';
|
|
9
|
+
import { CFG_USER_EMAIL_KEY, CFG_USER_NAME_KEY, PREVIOUS_DEFAULT_LANE } from '@teambit/legacy.constants';
|
|
10
|
+
import { ValidationError } from '@teambit/legacy.cli.error';
|
|
11
|
+
import { logger } from '@teambit/legacy.logger';
|
|
12
|
+
import { getStringifyArgs } from '@teambit/legacy.utils';
|
|
13
|
+
import { sha1 } from '@teambit/toolbox.crypto.sha1';
|
|
14
|
+
import { hasVersionByRef } from '@teambit/component.snap-distance';
|
|
15
|
+
import type { Repository } from '../objects';
|
|
16
|
+
import { BitObject, Ref } from '../objects';
|
|
17
|
+
import type Version from './version';
|
|
18
|
+
import { getConfig } from '@teambit/config-store';
|
|
19
|
+
|
|
20
|
+
export type Log = { date: string; username?: string; email?: string; profileImage?: string };
|
|
21
|
+
|
|
22
|
+
export type LaneProps = {
|
|
23
|
+
name: string;
|
|
24
|
+
scope: string;
|
|
25
|
+
log: Log;
|
|
26
|
+
components?: LaneComponent[];
|
|
27
|
+
hash: string;
|
|
28
|
+
schema?: string;
|
|
29
|
+
readmeComponent?: LaneReadmeComponent;
|
|
30
|
+
forkedFrom?: LaneId;
|
|
31
|
+
updateDependents?: ComponentID[];
|
|
32
|
+
overrideUpdateDependents?: boolean;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const OLD_LANE_SCHEMA = '0.0.0';
|
|
36
|
+
const SCHEMA_INCLUDING_DELETED_COMPONENTS_DATA = '1.0.0';
|
|
37
|
+
const CURRENT_LANE_SCHEMA = SCHEMA_INCLUDING_DELETED_COMPONENTS_DATA;
|
|
38
|
+
|
|
39
|
+
export type LaneComponent = { id: ComponentID; head: Ref; isDeleted?: boolean };
|
|
40
|
+
export type LaneReadmeComponent = { id: ComponentID; head: Ref | null };
|
|
41
|
+
export default class Lane extends BitObject {
|
|
42
|
+
name: string;
|
|
43
|
+
scope: string;
|
|
44
|
+
components: LaneComponent[];
|
|
45
|
+
log: Log;
|
|
46
|
+
schema: string;
|
|
47
|
+
readmeComponent?: LaneReadmeComponent;
|
|
48
|
+
forkedFrom?: LaneId;
|
|
49
|
+
_hash: string; // reason for the underscore prefix is that we already have hash as a method
|
|
50
|
+
isNew = false; // doesn't get saved in the object. only needed for in-memory instance
|
|
51
|
+
hasChanged = false; // doesn't get saved in the object. only needed for in-memory instance
|
|
52
|
+
/**
|
|
53
|
+
* components that are dependents of components in the lane and need to be re-snapped against the
|
|
54
|
+
* lane's heads. populated by the bare-scope cascade producer (UI "snap updates" / `bit _snap`)
|
|
55
|
+
* and by `bit _merge-lane`. these entries are part of the lane's graph (Ripple CI builds them,
|
|
56
|
+
* merge engine refreshes them) but are intentionally hidden from workspace-facing flows
|
|
57
|
+
* (`bit status`, `bit compile`, `bit install`, the bitmap).
|
|
58
|
+
*/
|
|
59
|
+
updateDependents?: ComponentID[];
|
|
60
|
+
private overrideUpdateDependents?: boolean;
|
|
61
|
+
constructor(props: LaneProps) {
|
|
62
|
+
super();
|
|
63
|
+
if (!props.name) throw new TypeError('Lane constructor expects to get a name parameter');
|
|
64
|
+
this.name = props.name;
|
|
65
|
+
this.scope = props.scope;
|
|
66
|
+
this.components = props.components || [];
|
|
67
|
+
this.log = props.log || { date: Date.now().toString() };
|
|
68
|
+
this._hash = props.hash;
|
|
69
|
+
this.readmeComponent = props.readmeComponent;
|
|
70
|
+
this.forkedFrom = props.forkedFrom;
|
|
71
|
+
this.schema = props.schema || OLD_LANE_SCHEMA;
|
|
72
|
+
this.updateDependents = props.updateDependents;
|
|
73
|
+
this.overrideUpdateDependents = props.overrideUpdateDependents;
|
|
74
|
+
}
|
|
75
|
+
id(): string {
|
|
76
|
+
return this.scope + LANE_REMOTE_DELIMITER + this.name;
|
|
77
|
+
}
|
|
78
|
+
hash(): Ref {
|
|
79
|
+
if (!this._hash) {
|
|
80
|
+
throw new Error('hash is missing from a Lane object');
|
|
81
|
+
}
|
|
82
|
+
return new Ref(this._hash);
|
|
83
|
+
}
|
|
84
|
+
changeName(name: string) {
|
|
85
|
+
this.name = name;
|
|
86
|
+
this.hasChanged = true;
|
|
87
|
+
}
|
|
88
|
+
changeScope(scope: string) {
|
|
89
|
+
this.scope = scope;
|
|
90
|
+
this.hasChanged = true;
|
|
91
|
+
}
|
|
92
|
+
refs(): Ref[] {
|
|
93
|
+
return this.components.map((c) => c.head);
|
|
94
|
+
}
|
|
95
|
+
validateBeforePersisting(str: string) {
|
|
96
|
+
logger.debug(`validating lane object: ${this.hash().toString()} ${this.id()}`);
|
|
97
|
+
const lane = Lane.parse(str, this.hash().toString());
|
|
98
|
+
lane.validate();
|
|
99
|
+
}
|
|
100
|
+
toObject() {
|
|
101
|
+
const obj = pickBy(
|
|
102
|
+
{
|
|
103
|
+
name: this.name,
|
|
104
|
+
scope: this.scope,
|
|
105
|
+
components: this.components.map((component) => ({
|
|
106
|
+
id: { scope: component.id.scope, name: component.id.fullName },
|
|
107
|
+
head: component.head.toString(),
|
|
108
|
+
...(component.isDeleted && { isDeleted: component.isDeleted }),
|
|
109
|
+
})),
|
|
110
|
+
log: this.log,
|
|
111
|
+
readmeComponent: this.readmeComponent && {
|
|
112
|
+
id: { scope: this.readmeComponent.id.scope, name: this.readmeComponent.id.fullName },
|
|
113
|
+
head: this.readmeComponent.head?.toString() ?? null,
|
|
114
|
+
},
|
|
115
|
+
forkedFrom: this.forkedFrom && this.forkedFrom.toObject(),
|
|
116
|
+
schema: this.schema,
|
|
117
|
+
updateDependents: this.updateDependents?.map((c) => c.toString()),
|
|
118
|
+
overrideUpdateDependents: this.overrideUpdateDependents,
|
|
119
|
+
},
|
|
120
|
+
(val) => !!val
|
|
121
|
+
);
|
|
122
|
+
return obj;
|
|
123
|
+
}
|
|
124
|
+
static from(props: LaneProps): Lane {
|
|
125
|
+
return new Lane(props);
|
|
126
|
+
}
|
|
127
|
+
static create(
|
|
128
|
+
name: string,
|
|
129
|
+
scope: string,
|
|
130
|
+
forkedFrom?: LaneId,
|
|
131
|
+
bitCloudUser?: {
|
|
132
|
+
username?: string;
|
|
133
|
+
email?: string;
|
|
134
|
+
profileImage?: string;
|
|
135
|
+
}
|
|
136
|
+
) {
|
|
137
|
+
const log = {
|
|
138
|
+
date: Date.now().toString(),
|
|
139
|
+
username: bitCloudUser?.username || getConfig(CFG_USER_NAME_KEY),
|
|
140
|
+
email: bitCloudUser?.email || getConfig(CFG_USER_EMAIL_KEY),
|
|
141
|
+
profileImage: bitCloudUser?.profileImage,
|
|
142
|
+
};
|
|
143
|
+
const lane = new Lane({ name, scope, hash: sha1(v4()), log, forkedFrom, schema: CURRENT_LANE_SCHEMA });
|
|
144
|
+
lane.isNew = true;
|
|
145
|
+
lane.hasChanged = true;
|
|
146
|
+
return lane;
|
|
147
|
+
}
|
|
148
|
+
static parse(contents: string, hash: string): Lane {
|
|
149
|
+
const laneObject = JSON.parse(contents);
|
|
150
|
+
return Lane.from({
|
|
151
|
+
name: laneObject.name,
|
|
152
|
+
scope: laneObject.scope,
|
|
153
|
+
log: laneObject.log,
|
|
154
|
+
components: laneObject.components.map((component) => ({
|
|
155
|
+
id: ComponentID.fromObject({ scope: component.id.scope, name: component.id.name }),
|
|
156
|
+
head: new Ref(component.head),
|
|
157
|
+
isDeleted: component.isDeleted,
|
|
158
|
+
})),
|
|
159
|
+
readmeComponent: laneObject.readmeComponent && {
|
|
160
|
+
id: ComponentID.fromObject({
|
|
161
|
+
scope: laneObject.readmeComponent.id.scope,
|
|
162
|
+
name: laneObject.readmeComponent.id.name,
|
|
163
|
+
}),
|
|
164
|
+
head: laneObject.readmeComponent.head && new Ref(laneObject.readmeComponent.head),
|
|
165
|
+
},
|
|
166
|
+
forkedFrom: laneObject.forkedFrom && LaneId.from(laneObject.forkedFrom.name, laneObject.forkedFrom.scope),
|
|
167
|
+
updateDependents: laneObject.updateDependents?.map((c) => ComponentID.fromString(c)),
|
|
168
|
+
overrideUpdateDependents: laneObject.overrideUpdateDependents,
|
|
169
|
+
hash: laneObject.hash || hash,
|
|
170
|
+
schema: laneObject.schema,
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
toBuffer(pretty?: boolean) {
|
|
174
|
+
const args = getStringifyArgs(pretty);
|
|
175
|
+
const obj = this.toObject();
|
|
176
|
+
const str = JSON.stringify(obj, ...args);
|
|
177
|
+
if (this.validateBeforePersist) this.validateBeforePersisting(str);
|
|
178
|
+
return Buffer.from(str);
|
|
179
|
+
}
|
|
180
|
+
addComponent(component: LaneComponent) {
|
|
181
|
+
const existsComponent = this.getComponent(component.id);
|
|
182
|
+
if (existsComponent) {
|
|
183
|
+
if (
|
|
184
|
+
!existsComponent.head.isEqual(component.head) ||
|
|
185
|
+
Boolean(existsComponent.isDeleted) !== Boolean(component.isDeleted)
|
|
186
|
+
) {
|
|
187
|
+
this.hasChanged = true;
|
|
188
|
+
}
|
|
189
|
+
existsComponent.id = component.id;
|
|
190
|
+
existsComponent.head = component.head;
|
|
191
|
+
existsComponent.isDeleted = component.isDeleted;
|
|
192
|
+
} else {
|
|
193
|
+
logger.debug(`Lane.addComponent, adding component ${component.id.toString()} to lane ${this.id()}`);
|
|
194
|
+
this.components.push(component);
|
|
195
|
+
this.hasChanged = true;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
findUpdateDependent(componentId: ComponentID): ComponentID | undefined {
|
|
199
|
+
return this.updateDependents?.find((c) => c.isEqualWithoutVersion(componentId));
|
|
200
|
+
}
|
|
201
|
+
getUpdateDependentAsLaneComponent(componentId: ComponentID): LaneComponent | undefined {
|
|
202
|
+
const found = this.findUpdateDependent(componentId);
|
|
203
|
+
if (!found?.version) return undefined;
|
|
204
|
+
return { id: found.changeVersion(undefined), head: Ref.from(found.version) };
|
|
205
|
+
}
|
|
206
|
+
removeComponentFromUpdateDependentsIfExist(componentId: ComponentID) {
|
|
207
|
+
if (!this.findUpdateDependent(componentId)) return;
|
|
208
|
+
this.updateDependents = this.updateDependents?.filter((c) => !c.isEqualWithoutVersion(componentId));
|
|
209
|
+
if (!this.updateDependents?.length) this.updateDependents = undefined;
|
|
210
|
+
this.hasChanged = true;
|
|
211
|
+
}
|
|
212
|
+
addComponentToUpdateDependents(componentId: ComponentID) {
|
|
213
|
+
if (!componentId.hasVersion()) {
|
|
214
|
+
throw new ValidationError(`Lane.addComponentToUpdateDependents: ${componentId.toString()} is missing a version`);
|
|
215
|
+
}
|
|
216
|
+
this.removeComponentFromUpdateDependentsIfExist(componentId);
|
|
217
|
+
(this.updateDependents ||= []).push(componentId);
|
|
218
|
+
this.hasChanged = true;
|
|
219
|
+
}
|
|
220
|
+
removeAllUpdateDependents() {
|
|
221
|
+
if (!this.updateDependents?.length) return;
|
|
222
|
+
this.updateDependents = undefined;
|
|
223
|
+
this.hasChanged = true;
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* Signals an explicit `_snap --update-dependents` insertion. On export, the receiving scope
|
|
227
|
+
* gates its "add this new hidden entry" branch on this flag so a stale workspace lane can't
|
|
228
|
+
* resurrect entries the Cloud UI dropped. Workspace cascades — which only update existing
|
|
229
|
+
* entries — deliberately do NOT set the flag. The field is also serialized by `Lane.toObject`
|
|
230
|
+
* for older remotes that gate their pre-diverge-check override branch on it.
|
|
231
|
+
*/
|
|
232
|
+
setOverrideUpdateDependents(overrideUpdateDependents: boolean) {
|
|
233
|
+
this.overrideUpdateDependents = overrideUpdateDependents;
|
|
234
|
+
this.hasChanged = true;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
shouldOverrideUpdateDependents(): boolean {
|
|
238
|
+
return Boolean(this.overrideUpdateDependents);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
removeComponent(id: ComponentID): boolean {
|
|
242
|
+
const existsComponent = this.getComponent(id);
|
|
243
|
+
if (!existsComponent) return false;
|
|
244
|
+
this.components = this.components.filter((c) => !c.id.isEqualWithoutVersion(id));
|
|
245
|
+
this.hasChanged = true;
|
|
246
|
+
return true;
|
|
247
|
+
}
|
|
248
|
+
getComponent(id: ComponentID): LaneComponent | undefined {
|
|
249
|
+
return this.components.find((c) => c.id.isEqualWithoutVersion(id));
|
|
250
|
+
}
|
|
251
|
+
getComponentHead(bitId: ComponentID): Ref | null {
|
|
252
|
+
const found = this.components.find((c) => c.id.isEqual(bitId));
|
|
253
|
+
if (found) return found.head;
|
|
254
|
+
return null;
|
|
255
|
+
}
|
|
256
|
+
setLaneComponents(laneComponents: LaneComponent[]) {
|
|
257
|
+
// this gets called when adding lane-components from other lanes/remotes, so it's better to
|
|
258
|
+
// clone the objects to not change the original data.
|
|
259
|
+
this.components = laneComponents.map((c) => ({
|
|
260
|
+
id: c.id.clone(),
|
|
261
|
+
head: c.head.clone(),
|
|
262
|
+
...(c.isDeleted && { isDeleted: c.isDeleted }),
|
|
263
|
+
}));
|
|
264
|
+
this.hasChanged = true;
|
|
265
|
+
}
|
|
266
|
+
setReadmeComponent(id?: ComponentID) {
|
|
267
|
+
const previousReadme = this.readmeComponent;
|
|
268
|
+
if (!id) {
|
|
269
|
+
this.readmeComponent = undefined;
|
|
270
|
+
if (previousReadme) this.hasChanged = true;
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
const readmeComponent = this.getComponent(id);
|
|
274
|
+
if (!readmeComponent) {
|
|
275
|
+
this.readmeComponent = { id, head: null };
|
|
276
|
+
} else {
|
|
277
|
+
this.readmeComponent = readmeComponent;
|
|
278
|
+
}
|
|
279
|
+
if (
|
|
280
|
+
!previousReadme ||
|
|
281
|
+
!previousReadme.id.isEqual(id) ||
|
|
282
|
+
previousReadme.head?.toString() !== this.readmeComponent.head?.toString()
|
|
283
|
+
) {
|
|
284
|
+
this.hasChanged = true;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
async isFullyMerged(scope: Scope): Promise<boolean> {
|
|
289
|
+
const { unmerged } = await this.getMergedAndUnmergedIds(scope);
|
|
290
|
+
return unmerged.length === 0;
|
|
291
|
+
}
|
|
292
|
+
async getMergedAndUnmergedIds(scope: Scope): Promise<{ merged: ComponentID[]; unmerged: ComponentID[] }> {
|
|
293
|
+
const merged: ComponentID[] = [];
|
|
294
|
+
const unmerged: ComponentID[] = [];
|
|
295
|
+
await Promise.all(
|
|
296
|
+
this.components.map(async (component) => {
|
|
297
|
+
const modelComponent = await scope.getModelComponentIfExist(component.id);
|
|
298
|
+
if (!modelComponent) {
|
|
299
|
+
unmerged.push(component.id);
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
const startTraverseFrom = modelComponent.getHead() || null; // it's important to have it as null and not as undefined, see hasVersionByRef
|
|
303
|
+
const headExist = await hasVersionByRef(modelComponent, component.head, scope.objects, startTraverseFrom);
|
|
304
|
+
if (headExist) merged.push(component.id);
|
|
305
|
+
else unmerged.push(component.id);
|
|
306
|
+
})
|
|
307
|
+
);
|
|
308
|
+
return { merged, unmerged };
|
|
309
|
+
}
|
|
310
|
+
/**
|
|
311
|
+
* @deprecated use toComponentIds instead
|
|
312
|
+
*/
|
|
313
|
+
toBitIds(): ComponentIdList {
|
|
314
|
+
return this.toComponentIds();
|
|
315
|
+
}
|
|
316
|
+
toComponentIds(): ComponentIdList {
|
|
317
|
+
return ComponentIdList.fromArray(this.components.map((c) => c.id.changeVersion(c.head.toString())));
|
|
318
|
+
}
|
|
319
|
+
toComponentIdsIncludeUpdateDependents(): ComponentIdList {
|
|
320
|
+
return ComponentIdList.fromArray(this.toComponentIds().concat(this.updateDependents || []));
|
|
321
|
+
}
|
|
322
|
+
toLaneId() {
|
|
323
|
+
return new LaneId({ scope: this.scope, name: this.name });
|
|
324
|
+
}
|
|
325
|
+
collectObjectsById(repo: Repository): Promise<Array<{ id: ComponentID; objects: BitObject[] }>> {
|
|
326
|
+
return Promise.all(
|
|
327
|
+
this.components.map(async (component) => {
|
|
328
|
+
const headVersion = (await component.head.load(repo)) as Version;
|
|
329
|
+
const objects = [headVersion, ...headVersion.collect(repo)];
|
|
330
|
+
return { id: component.id, objects };
|
|
331
|
+
})
|
|
332
|
+
);
|
|
333
|
+
}
|
|
334
|
+
includeDeletedData(): boolean {
|
|
335
|
+
return gte(this.schema, SCHEMA_INCLUDING_DELETED_COMPONENTS_DATA);
|
|
336
|
+
}
|
|
337
|
+
setSchemaToSupportDeletedData() {
|
|
338
|
+
this.schema = SCHEMA_INCLUDING_DELETED_COMPONENTS_DATA;
|
|
339
|
+
this.hasChanged = true;
|
|
340
|
+
}
|
|
341
|
+
setSchemaToNotSupportDeletedData() {
|
|
342
|
+
this.schema = OLD_LANE_SCHEMA;
|
|
343
|
+
this.hasChanged = true;
|
|
344
|
+
}
|
|
345
|
+
getCompHeadIncludeUpdateDependents(componentId: ComponentID): Ref | undefined {
|
|
346
|
+
const comp = this.getComponent(componentId);
|
|
347
|
+
if (comp) return comp.head;
|
|
348
|
+
return this.getUpdateDependentAsLaneComponent(componentId)?.head;
|
|
349
|
+
}
|
|
350
|
+
validate() {
|
|
351
|
+
const message = `unable to save Lane object "${this.id()}"`;
|
|
352
|
+
const bitIds = this.toComponentIds();
|
|
353
|
+
this.components.forEach((component) => {
|
|
354
|
+
if (bitIds.filterWithoutVersion(component.id).length > 1) {
|
|
355
|
+
throw new ValidationError(`${message}, the following component is duplicated "${component.id.fullName}"`);
|
|
356
|
+
}
|
|
357
|
+
if (!isSnap(component.head.hash)) {
|
|
358
|
+
throw new ValidationError(
|
|
359
|
+
`${message}, lane component ${component.id.toStringWithoutVersion()} head should be a hash, got ${
|
|
360
|
+
component.head.hash
|
|
361
|
+
}`
|
|
362
|
+
);
|
|
363
|
+
}
|
|
364
|
+
});
|
|
365
|
+
if (this.name === DEFAULT_LANE) {
|
|
366
|
+
throw new BitError(`${message}, this name is reserved as the default lane`);
|
|
367
|
+
}
|
|
368
|
+
if (this.name === PREVIOUS_DEFAULT_LANE) {
|
|
369
|
+
throw new BitError(`${message}, this name is reserved as the old default lane`);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
isEqual(lane: Lane): boolean {
|
|
373
|
+
if (this.id() !== lane.id()) return false;
|
|
374
|
+
// include isDeleted in equality so a soft-delete flip with the same head still counts as a
|
|
375
|
+
// state change — `Lane.isEqual` is used by importers to decide whether to write a
|
|
376
|
+
// LaneHistory entry.
|
|
377
|
+
const normalize = (l: Lane) =>
|
|
378
|
+
l.components
|
|
379
|
+
.map((c) => ({
|
|
380
|
+
id: c.id.toStringWithoutVersion(),
|
|
381
|
+
head: c.head.toString(),
|
|
382
|
+
isDeleted: Boolean(c.isDeleted),
|
|
383
|
+
}))
|
|
384
|
+
.sort((a, b) =>
|
|
385
|
+
`${a.id}@${a.head}:${a.isDeleted ? 1 : 0}`.localeCompare(`${b.id}@${b.head}:${b.isDeleted ? 1 : 0}`)
|
|
386
|
+
);
|
|
387
|
+
const thisUpdDeps = (this.updateDependents || []).map((c) => c.toString()).sort();
|
|
388
|
+
const otherUpdDeps = (lane.updateDependents || []).map((c) => c.toString()).sort();
|
|
389
|
+
return isEqual(normalize(this), normalize(lane)) && isEqual(thisUpdDeps, otherUpdDeps);
|
|
390
|
+
}
|
|
391
|
+
clone() {
|
|
392
|
+
return new Lane({
|
|
393
|
+
...this,
|
|
394
|
+
hash: this._hash,
|
|
395
|
+
overrideUpdateDependents: this.overrideUpdateDependents,
|
|
396
|
+
components: cloneDeep(this.components),
|
|
397
|
+
updateDependents: this.updateDependents ? cloneDeep(this.updateDependents) : undefined,
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { expect } from 'chai';
|
|
2
|
+
|
|
3
|
+
import ModelComponent from './model-component';
|
|
4
|
+
import { clone } from 'lodash';
|
|
5
|
+
|
|
6
|
+
const modelComponentFixture = {
|
|
7
|
+
name: 'bar/foo',
|
|
8
|
+
scope: 'remote-scope',
|
|
9
|
+
versions: {
|
|
10
|
+
'0.0.1': '125a37bdb17220bdc1406a9a28a3dde4eec91225',
|
|
11
|
+
},
|
|
12
|
+
lang: 'javascript',
|
|
13
|
+
deprecated: false,
|
|
14
|
+
bindingPrefix: '@bit',
|
|
15
|
+
remotes: [
|
|
16
|
+
{
|
|
17
|
+
url: 'file:///tmp/remote-scope',
|
|
18
|
+
name: 'remote-scope',
|
|
19
|
+
date: '1572532837438',
|
|
20
|
+
},
|
|
21
|
+
],
|
|
22
|
+
state: {
|
|
23
|
+
versions: {
|
|
24
|
+
'0.0.1': {
|
|
25
|
+
local: true,
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const getModelComponentFixture = (): typeof modelComponentFixture => {
|
|
32
|
+
return clone(modelComponentFixture);
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const getModelComponent = (obj: object): ModelComponent => {
|
|
36
|
+
return ModelComponent.parse(JSON.stringify(obj));
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
describe('ModelComponent', () => {
|
|
40
|
+
describe('validate', () => {
|
|
41
|
+
let validateFunc: Function;
|
|
42
|
+
describe('duplicate hashes', () => {
|
|
43
|
+
let modelComponent: ModelComponent;
|
|
44
|
+
before(() => {
|
|
45
|
+
const fixture = getModelComponentFixture();
|
|
46
|
+
fixture.versions['0.0.2'] = fixture.versions['0.0.1'];
|
|
47
|
+
modelComponent = getModelComponent(fixture);
|
|
48
|
+
validateFunc = () => modelComponent.validate();
|
|
49
|
+
});
|
|
50
|
+
it('should throw an error', () => {
|
|
51
|
+
expect(validateFunc).to.throw('the following hash(es) are duplicated');
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
});
|