@teambit/legacy-bit-id 1.0.3 → 1.1.0

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.
Files changed (33) hide show
  1. package/bit-id.spec.ts +225 -0
  2. package/bit-id.ts +314 -0
  3. package/dist/bit-id.js +290 -344
  4. package/dist/bit-id.js.map +1 -1
  5. package/dist/bit-id.spec.js +224 -349
  6. package/dist/bit-id.spec.js.map +1 -1
  7. package/dist/exceptions/index.js +15 -71
  8. package/dist/exceptions/index.js.map +1 -1
  9. package/dist/exceptions/invalid-bit-id.js +6 -21
  10. package/dist/exceptions/invalid-bit-id.js.map +1 -1
  11. package/dist/exceptions/invalid-id-chunk.js +6 -21
  12. package/dist/exceptions/invalid-id-chunk.js.map +1 -1
  13. package/dist/exceptions/invalid-name.js +6 -21
  14. package/dist/exceptions/invalid-name.js.map +1 -1
  15. package/dist/exceptions/invalid-scope-name-from-remote.d.ts +1 -0
  16. package/dist/exceptions/invalid-scope-name-from-remote.js +7 -17
  17. package/dist/exceptions/invalid-scope-name-from-remote.js.map +1 -1
  18. package/dist/exceptions/invalid-scope-name.js +7 -23
  19. package/dist/exceptions/invalid-scope-name.js.map +1 -1
  20. package/dist/index.js +39 -90
  21. package/dist/index.js.map +1 -1
  22. package/dist/tsconfig.json +32 -0
  23. package/dist/utils/is-valid-id-chunk.js +8 -10
  24. package/dist/utils/is-valid-id-chunk.js.map +1 -1
  25. package/dist/utils/is-valid-id-chunk.spec.js +21 -32
  26. package/dist/utils/is-valid-id-chunk.spec.js.map +1 -1
  27. package/dist/utils/is-valid-scope-name.js +5 -8
  28. package/dist/utils/is-valid-scope-name.js.map +1 -1
  29. package/exceptions/invalid-scope-name-from-remote.ts +1 -1
  30. package/index.ts +16 -0
  31. package/package.json +5 -13
  32. package/tsconfig.json +2 -10
  33. /package/dist/{preview-1697512701410.js → preview-1699895001586.js} +0 -0
package/bit-id.spec.ts ADDED
@@ -0,0 +1,225 @@
1
+ import { expect } from 'chai';
2
+
3
+ import BitId from './bit-id';
4
+ import { InvalidName, InvalidScopeName } from './exceptions';
5
+
6
+ describe('Bit-id', () => {
7
+ // TODO: those tests are now verified by TS compiler, I think we can remove them
8
+ it('should be frozen', () => {
9
+ const bitId = new BitId({ name: 'my-name' });
10
+ // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX!
11
+ expect(() => (bitId.scope = 'name')).to.throw();
12
+ // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX!
13
+ expect(() => (bitId.name = 'name')).to.throw();
14
+ // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX!
15
+ expect(() => (bitId.version = 'name')).to.throw();
16
+ });
17
+ describe('constructor', () => {
18
+ it('should not throw for an invalid scope as it hurts performance (it is done on .parse() instead)', () => {
19
+ // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX!
20
+ const invalidScope = () => new BitId({ scope: 123, name: 'my-name' });
21
+ expect(invalidScope).to.not.throw(InvalidScopeName);
22
+ });
23
+ it('should not throw for an invalid name as it hurts performance (it is done on .parse() instead)', () => {
24
+ // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX!
25
+ const invalidName = () => new BitId({ name: ['a', 'b'] });
26
+ expect(invalidName).to.not.throw(InvalidName);
27
+ });
28
+ it('should accept an empty scope', () => {
29
+ const invalidScope = () => new BitId({ scope: null, name: 'my-name' });
30
+ expect(invalidScope).to.not.throw();
31
+ });
32
+ it('should not accept an empty name', () => {
33
+ // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX!
34
+ expect(() => new BitId({ scope: 'my-scope', name: null })).to.throw(InvalidName);
35
+ // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX!
36
+ expect(() => new BitId({ scope: 'my-scope', name: undefined })).to.throw(InvalidName);
37
+ // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX!
38
+ expect(() => new BitId({ scope: 'my-scope' })).to.throw(InvalidName);
39
+ });
40
+ });
41
+ describe('changeScope', () => {
42
+ it('should return a new BitId with different scope', () => {
43
+ const bitId = new BitId({ name: 'my-name' });
44
+ expect(bitId.changeScope('my-scope')).to.have.property('scope').that.equal('my-scope');
45
+ });
46
+ it('should accept an empty parameter to remove the scope', () => {
47
+ const bitId = new BitId({ scope: 'my-scope', name: 'my-name' });
48
+ expect(bitId.changeScope()).to.have.property('scope').that.is.null;
49
+ });
50
+ });
51
+ describe('changeVersion', () => {
52
+ it('should return a new BitId with different version', () => {
53
+ const bitId = new BitId({ name: 'my-name' });
54
+ expect(bitId.changeVersion('0.0.1')).to.have.property('version').that.equal('0.0.1');
55
+ });
56
+ it('should accept an empty parameter to remove the version', () => {
57
+ const bitId = new BitId({ scope: 'my-scope', name: 'my-name', version: '0.0.1' });
58
+ // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX!
59
+ expect(bitId.changeVersion()).to.have.property('version').that.is.undefined;
60
+ });
61
+ });
62
+ describe('hasScope', () => {
63
+ it('should return false when there is no scope', () => {
64
+ const bitId = new BitId({ name: 'my-name' });
65
+ expect(bitId.hasScope()).to.be.false;
66
+ });
67
+ it('should return true when there is scope', () => {
68
+ const bitId = new BitId({ scope: 'my-scope', name: 'my-name' });
69
+ expect(bitId.hasScope()).to.be.true;
70
+ });
71
+ });
72
+ describe('getValidBitId', () => {
73
+ it('should convert CSSComp to css-comp', () => {
74
+ const bitName = 'CSSComp';
75
+ expect(BitId.getValidBitId('global', bitName).toString()).to.equal('global/css-comp');
76
+ // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX!
77
+ });
78
+ it('should convert EN-US to en-us', () => {
79
+ const bitName = 'EN-US';
80
+ expect(BitId.getValidBitId('global', bitName).toString()).to.equal('global/en-us');
81
+ });
82
+ it('should convert AppBar to app-bar', () => {
83
+ const bitName = 'AppBar';
84
+ expect(BitId.getValidBitId('global', bitName).toString()).to.equal('global/app-bar');
85
+ });
86
+ it('should convert app-bar to app-bar', () => {
87
+ const bitName = 'app-bar';
88
+ expect(BitId.getValidBitId('global', bitName).toString()).to.equal('global/app-bar');
89
+ });
90
+ it('should convert app-Bar to app-bar', () => {
91
+ const bitName = 'app-Bar';
92
+ expect(BitId.getValidBitId('global', bitName).toString()).to.equal('global/app-bar');
93
+ });
94
+ it('should convert App-Bar to app-bar', () => {
95
+ const bitName = 'App-Bar';
96
+ expect(BitId.getValidBitId('global', bitName).toString()).to.equal('global/app-bar');
97
+ });
98
+ it('should convert CSScomp to cs-scomp ', () => {
99
+ const bitName = 'CSScomp';
100
+ expect(BitId.getValidBitId('global', bitName).toString()).to.equal('global/cs-scomp');
101
+ });
102
+ it('should convert CSS@comp/CSScomp to css@comp/cs-scomp ', () => {
103
+ const bitName = 'CSScomp';
104
+ const global = 'CSS@comp';
105
+ expect(BitId.getValidBitId(global, bitName).toString()).to.equal('css@comp/cs-scomp');
106
+ });
107
+ it('should convert CSS!!####@comp/app-Bar to app-Bar', () => {
108
+ const bitName = 'app-Bar';
109
+ const global = 'CSS!!####@comp';
110
+ expect(BitId.getValidBitId(global, bitName).toString()).to.equal('css!!####@comp/app-bar');
111
+ });
112
+ it('should convert app bar to app-bar', () => {
113
+ const bitName = 'app bar';
114
+ expect(BitId.getValidBitId(undefined, bitName).toString()).to.equal('app-bar');
115
+ });
116
+ });
117
+ describe('toString', () => {
118
+ it('should not contain the version as latest', () => {
119
+ const bitId = BitId.parse('bit.envs/mocha/react@latest');
120
+ expect(bitId.toString()).to.equal('bit.envs/mocha/react');
121
+ });
122
+ });
123
+ describe('parse', () => {
124
+ it('should parse id with scope correctly', () => {
125
+ const bitId = BitId.parse('scope/my/long/comp/id', true);
126
+ expect(bitId.scope).to.equal('scope');
127
+ expect(bitId.name).to.equal('my/long/comp/id');
128
+ expect(bitId.box).to.be.undefined;
129
+ expect(bitId.version).to.equal('latest');
130
+ });
131
+ it('should parse id without scope correctly', () => {
132
+ const bitId = BitId.parse('scope/my/long/comp/id', false);
133
+ expect(bitId.scope).to.be.null;
134
+ expect(bitId.name).to.equal('scope/my/long/comp/id');
135
+ expect(bitId.box).to.be.undefined;
136
+ expect(bitId.version).to.equal('latest');
137
+ });
138
+ it('should throw for an invalid id', () => {
139
+ const bitId = () => BitId.parse('scope/my/long/co*mp/id', false);
140
+ expect(bitId).to.throw();
141
+ });
142
+ });
143
+ describe('hasSameScope', () => {
144
+ it('should return true when it has the same scope', () => {
145
+ const bitId = new BitId({ scope: 'my-scope', name: 'my-name' });
146
+ const anotherId = new BitId({ scope: 'my-scope', name: 'another-name' });
147
+ expect(bitId.hasSameScope(anotherId)).to.be.true;
148
+ });
149
+ it('should return false when it does not the same scope', () => {
150
+ const bitId = new BitId({ scope: 'my-scope', name: 'my-name' });
151
+ const anotherId = new BitId({ scope: 'another-scope', name: 'my-name' });
152
+ expect(bitId.hasSameScope(anotherId)).to.be.false;
153
+ });
154
+ it('should return true when both scopes are null', () => {
155
+ const bitId = new BitId({ scope: null, name: 'my-name' });
156
+ const anotherId = new BitId({ scope: null, name: 'my-name' });
157
+ expect(bitId.hasSameScope(anotherId)).to.be.true;
158
+ });
159
+ it('should return false when one of the scope is null', () => {
160
+ const bitId = new BitId({ scope: null, name: 'my-name' });
161
+ const anotherId = new BitId({ scope: 'another-scope', name: 'my-name' });
162
+ expect(bitId.hasSameScope(anotherId)).to.be.false;
163
+ });
164
+ });
165
+ describe('isEqual', () => {
166
+ it('should return true for an exact match', () => {
167
+ const bitId = new BitId({ scope: 'my-scope', name: 'my-name', version: '0.0.1' });
168
+ const anotherId = new BitId({ scope: 'my-scope', name: 'my-name', version: '0.0.1' });
169
+ expect(bitId.isEqual(anotherId)).to.be.true;
170
+ });
171
+ it('should return false for a version mismatch', () => {
172
+ const bitId = new BitId({ scope: 'my-scope', name: 'my-name', version: '0.0.1' });
173
+ const anotherId = new BitId({ scope: 'my-scope', name: 'my-name', version: '0.0.2' });
174
+ expect(bitId.isEqual(anotherId)).to.be.false;
175
+ });
176
+ it('should return false for a name mismatch', () => {
177
+ const bitId = new BitId({ scope: 'my-scope', name: 'my-name', version: '0.0.1' });
178
+ const anotherId = new BitId({ scope: 'my-scope', name: 'another-name', version: '0.0.1' });
179
+ expect(bitId.isEqual(anotherId)).to.be.false;
180
+ });
181
+ it('should return false for a scope mismatch', () => {
182
+ const bitId = new BitId({ scope: 'my-scope', name: 'my-name', version: '0.0.1' });
183
+ const anotherId = new BitId({ scope: 'another-scope', name: 'my-name', version: '0.0.1' });
184
+ expect(bitId.isEqual(anotherId)).to.be.false;
185
+ });
186
+ });
187
+ describe('serialize', () => {
188
+ it('should delete the scope property if not set', () => {
189
+ const bitId = new BitId({ name: 'my-name', version: '0.0.1' });
190
+ expect(bitId.serialize()).to.not.have.property('scope');
191
+ });
192
+ it('should delete the version property if not set', () => {
193
+ const bitId = new BitId({ name: 'my-name' });
194
+ expect(bitId.serialize()).to.not.have.property('version');
195
+ });
196
+ it('should include all properties if they are all set', () => {
197
+ const bitId = new BitId({ scope: 'my-scope', name: 'my-name', version: '0.0.1' });
198
+ expect(bitId.serialize()).to.have.property('name');
199
+ expect(bitId.serialize()).to.have.property('scope');
200
+ expect(bitId.serialize()).to.have.property('version');
201
+ });
202
+ it('should not be an instance of BitId', () => {
203
+ const bitId = new BitId({ scope: 'my-scope', name: 'my-name', version: '0.0.1' });
204
+ expect(bitId.serialize()).to.not.be.an.instanceof(BitId);
205
+ });
206
+ });
207
+ describe('parseBackwardCompatible', () => {
208
+ it('should parse the new format, which is an object', () => {
209
+ const bitIdObj = new BitId({ scope: 'my-scope', name: 'my-name', version: '0.0.1' }).serialize();
210
+ const bitId = BitId.parseBackwardCompatible(bitIdObj);
211
+ expect(bitId).to.be.an.instanceof(BitId);
212
+ expect(bitId.name).to.equal('my-name');
213
+ expect(bitId.scope).to.equal('my-scope');
214
+ expect(bitId.version).to.equal('0.0.1');
215
+ });
216
+ it('should parse the old format, which is a string', () => {
217
+ const bitIdStr = 'my-scope/my-box/my-name@0.0.1';
218
+ const bitId = BitId.parseBackwardCompatible(bitIdStr);
219
+ expect(bitId).to.be.an.instanceof(BitId);
220
+ expect(bitId.name).to.equal('my-box/my-name');
221
+ expect(bitId.scope).to.equal('my-scope');
222
+ expect(bitId.version).to.equal('0.0.1');
223
+ });
224
+ });
225
+ });
package/bit-id.ts ADDED
@@ -0,0 +1,314 @@
1
+ import decamelize from 'decamelize';
2
+ import * as path from 'path';
3
+ import * as semver from 'semver';
4
+ import { head, tail } from 'lodash';
5
+ import { versionParser, isHash, isTag, LATEST_VERSION } from '@teambit/component-version';
6
+ import isValidIdChunk from './utils/is-valid-id-chunk';
7
+ import isValidScopeName from './utils/is-valid-scope-name';
8
+ import { InvalidBitId, InvalidIdChunk, InvalidName, InvalidScopeName } from './exceptions';
9
+
10
+ export type BitIdProps = {
11
+ scope?: string | null | undefined;
12
+ box?: string | undefined;
13
+ name: string;
14
+ version?: string | undefined;
15
+ };
16
+
17
+ type BitIdSerialized = { scope?: string; name: string; version?: string };
18
+
19
+ export type BitIdStr = string;
20
+
21
+ export const VERSION_DELIMITER = '@';
22
+
23
+ export default class BitId {
24
+ readonly scope: string | null | undefined;
25
+ readonly box: string | undefined;
26
+ readonly name: string;
27
+ readonly version: string | undefined;
28
+
29
+ constructor({ scope, box, name, version }: BitIdProps) {
30
+ // don't validate the id parts using isValidIdChunk here. we instance this class tons of times
31
+ // and running regex so many times impact the performance
32
+ if (!name) throw new InvalidName(name);
33
+ this.scope = scope || null;
34
+ this.box = undefined;
35
+ this.name = box ? `${box}/${name}` : name;
36
+ this.version = version || undefined;
37
+ Object.freeze(this);
38
+ }
39
+
40
+ clone(): BitId {
41
+ return new BitId(this);
42
+ }
43
+
44
+ changeScope(newScope?: string | null | undefined): BitId {
45
+ return new BitId({ scope: newScope, name: this.name, version: this.version });
46
+ }
47
+
48
+ changeVersion(newVersion: string | undefined): BitId {
49
+ return new BitId({ scope: this.scope, name: this.name, version: newVersion });
50
+ }
51
+
52
+ isLocal(scopeName?: string): boolean {
53
+ return !this.scope || Boolean(scopeName && scopeName === this.scope);
54
+ }
55
+
56
+ getVersion() {
57
+ return versionParser(this.version);
58
+ }
59
+
60
+ hasVersion(): boolean {
61
+ return Boolean(this.version && this.version !== LATEST_VERSION);
62
+ }
63
+
64
+ hasScope(): boolean {
65
+ return Boolean(this.scope);
66
+ }
67
+
68
+ hasSameName(id: BitId): boolean {
69
+ return this.name === id.name;
70
+ }
71
+
72
+ hasSameScope(id: BitId): boolean {
73
+ if (this.hasScope() && id.hasScope()) return this.scope === id.scope;
74
+ if (!this.hasScope() && !id.hasScope()) return true;
75
+ return false; // one has scope but not the other
76
+ }
77
+
78
+ hasSameVersion(id: BitId): boolean {
79
+ if (this.hasVersion() && id.hasVersion()) return this.version === id.version;
80
+ if (!this.hasVersion() && !id.hasVersion()) return true;
81
+ return false; // one has version but not the other
82
+ }
83
+
84
+ toString(ignoreScope = false, ignoreVersion = false): BitIdStr {
85
+ const { name, version } = this;
86
+ const scope = this.scope;
87
+ const componentStr = ignoreScope || !scope ? name : [scope, name].join('/');
88
+ // when there is no scope and the version is latest, omit the version.
89
+ if (ignoreVersion || !this.hasVersion()) return componentStr;
90
+ return componentStr.concat(`${VERSION_DELIMITER}${version}`);
91
+ }
92
+
93
+ toStringWithoutScope(): BitIdStr {
94
+ return this.toString(true);
95
+ }
96
+
97
+ toStringWithoutVersion(): BitIdStr {
98
+ return this.toString(false, true);
99
+ }
100
+
101
+ toStringWithoutScopeAndVersion(): BitIdStr {
102
+ return this.toString(true, true);
103
+ }
104
+
105
+ isEqual(bitId: BitId): boolean {
106
+ return this.hasSameName(bitId) && this.hasSameScope(bitId) && this.hasSameVersion(bitId);
107
+ }
108
+
109
+ isEqualWithoutVersion(bitId: BitId): boolean {
110
+ return this.hasSameName(bitId) && this.hasSameScope(bitId);
111
+ }
112
+
113
+ isEqualWithoutScopeAndVersion(bitId: BitId): boolean {
114
+ return this.hasSameName(bitId);
115
+ }
116
+
117
+ serialize(): BitIdSerialized {
118
+ const obj = { scope: this.scope, name: this.name, version: this.version };
119
+ if (!this.hasVersion()) delete obj.version;
120
+ if (!this.hasScope()) delete obj.scope;
121
+ return obj as BitIdSerialized;
122
+ }
123
+
124
+ toFullPath(): string {
125
+ if (!this.scope || !this.version) {
126
+ throw new Error('BitId.toFullPath is unable to generate a path without a scope or a version');
127
+ }
128
+ return path.join(this.name, this.scope, this.version);
129
+ }
130
+
131
+ isVersionSnap() {
132
+ return !isTag(this.version);
133
+ }
134
+
135
+ /**
136
+ * examples:
137
+ * 1.0.0 => null
138
+ * 1.0.0-dev.1 => ['dev', 1]
139
+ * 1.0.0-dev.1.alpha.2 => ['dev', 1, 'alpha', 2]
140
+ * 1.0.0-0 => [0]
141
+ */
142
+ getVersionPreReleaseData(): null | readonly string[] {
143
+ if (!this.version) return null;
144
+ return semver.prerelease(this.version);
145
+ }
146
+
147
+ /**
148
+ * Get a string id and return a string without the version part
149
+ * @param {string} id
150
+ * @return {string} id - id without version
151
+ */
152
+ static getStringWithoutVersion(id: string): string {
153
+ const splitted = id.split(VERSION_DELIMITER);
154
+ let res = splitted[0];
155
+ // the delimiter is @. now with the new owner prefix
156
+ // many times the id starts with the @ sign as part of the @owner prefix
157
+ // do not treat this @ at the beginning as the version delimiter
158
+ if (id.startsWith(VERSION_DELIMITER)) {
159
+ res = `${VERSION_DELIMITER}${splitted[1]}`;
160
+ }
161
+ return res;
162
+ }
163
+
164
+ static getVersionOnlyFromString(id: string): string {
165
+ return id.split(VERSION_DELIMITER)[1];
166
+ }
167
+
168
+ static parse(id: BitIdStr, hasScope = true, version: string = LATEST_VERSION): BitId {
169
+ if (typeof id !== 'string') {
170
+ throw new TypeError(`BitId.parse expects to get "id" as a string, instead, got ${typeof id}`);
171
+ }
172
+
173
+ if (id.includes(VERSION_DELIMITER) && id.lastIndexOf(VERSION_DELIMITER) > 0) {
174
+ const [newId, newVersion] = id.split(VERSION_DELIMITER);
175
+ id = newId;
176
+ version = newVersion;
177
+ }
178
+
179
+ const getScopeAndName = () => {
180
+ if (hasScope) {
181
+ return BitId.getScopeAndName(id);
182
+ }
183
+
184
+ return {
185
+ scope: undefined,
186
+ name: id,
187
+ };
188
+ };
189
+ const { scope, name } = getScopeAndName();
190
+
191
+ if (!isValidIdChunk(name)) throw new InvalidName(name);
192
+ if (scope && !isValidScopeName(scope)) {
193
+ throw new InvalidScopeName(scope, id);
194
+ }
195
+
196
+ return new BitId({
197
+ scope,
198
+ name,
199
+ version,
200
+ });
201
+ }
202
+
203
+ static getScopeAndName(id: string) {
204
+ const delimiterIndex = id.indexOf('/');
205
+ if (delimiterIndex < 0) throw new InvalidBitId(id);
206
+ const scope = id.substring(0, delimiterIndex);
207
+ const name = id.substring(delimiterIndex + 1);
208
+ return {
209
+ scope,
210
+ name,
211
+ };
212
+ }
213
+
214
+ static parseObsolete(id: BitIdStr, version: string = LATEST_VERSION): BitId {
215
+ if (id.includes(VERSION_DELIMITER)) {
216
+ const [newId, newVersion] = id.split(VERSION_DELIMITER);
217
+ id = newId;
218
+ version = newVersion;
219
+ }
220
+
221
+ const idSplit = id.split('/');
222
+ if (idSplit.length === 3) {
223
+ const [scope, box, name] = idSplit;
224
+ if (!isValidIdChunk(name, false) || !isValidIdChunk(box, false) || !isValidScopeName(scope)) {
225
+ throw new InvalidIdChunk(`${scope}/${box}/${name}`);
226
+ }
227
+ // $FlowFixMe (in this case the realScopeName is not undefined)
228
+ return new BitId({
229
+ scope,
230
+ box,
231
+ name,
232
+ version,
233
+ });
234
+ }
235
+
236
+ if (idSplit.length === 2) {
237
+ const [box, name] = idSplit;
238
+ if (!isValidIdChunk(name, false) || !isValidIdChunk(box, false)) {
239
+ throw new InvalidIdChunk(`${box}/${name}`);
240
+ }
241
+ return new BitId({
242
+ box,
243
+ name,
244
+ version,
245
+ });
246
+ }
247
+
248
+ if (idSplit.length === 1) {
249
+ const [name] = idSplit;
250
+ if (!isValidIdChunk(name)) {
251
+ throw new InvalidIdChunk(name);
252
+ }
253
+ return new BitId({
254
+ name,
255
+ version,
256
+ });
257
+ }
258
+
259
+ throw new InvalidBitId(id);
260
+ }
261
+
262
+ /**
263
+ * before version 13.0.3 bitmap and component-dependencies ids were written as strings (e.g. scope/box/name@version)
264
+ * since that version the ids are written as objects ({ scope: scopeName, name: compName, version: 0.0.1 })
265
+ */
266
+ static parseBackwardCompatible(id: string | BitIdProps): BitId {
267
+ return typeof id === 'string' ? BitId.parseObsolete(id) : new BitId(id);
268
+ }
269
+
270
+ static getValidScopeName(scope: string) {
271
+ const suggestedName = scope.toLowerCase();
272
+ let cleanName = suggestedName
273
+ .split('')
274
+ .map((char) => {
275
+ if (/^[$\-_!.a-z0-9]+$/.test(char)) return char;
276
+ return '';
277
+ })
278
+ .join('');
279
+
280
+ // allow only one dot
281
+ const nameSplitByDot = cleanName.split('.');
282
+ if (nameSplitByDot.length > 1) {
283
+ cleanName = `${head(nameSplitByDot)}.${tail(nameSplitByDot).join('')}`;
284
+ }
285
+
286
+ if (!cleanName) {
287
+ // @todo: change it to BitError
288
+ throw new Error('scope name created by directory name have to contains at least one character or number');
289
+ }
290
+ return cleanName;
291
+ }
292
+
293
+ static getValidIdChunk(chunk: string): string {
294
+ if (!isValidIdChunk(chunk)) {
295
+ chunk = chunk.replace(/\./g, ''); // remove "."
296
+ chunk = chunk.replace(/ /g, '-'); // replace a space with a dash.
297
+ chunk = decamelize(chunk, '-');
298
+ }
299
+ return chunk;
300
+ }
301
+
302
+ static getValidBitId(box: string | undefined, name: string): BitId {
303
+ return new BitId({ name: BitId.getValidIdChunk(name), box: box ? BitId.getValidIdChunk(box) : undefined });
304
+ }
305
+
306
+ static isValidVersion(version: string): boolean {
307
+ // a version can be a tag (semver) or a snap (hash)
308
+ return isTag(version) || isHash(version);
309
+ }
310
+
311
+ static isValidSemver(version: string): boolean {
312
+ return Boolean(semver.valid(version));
313
+ }
314
+ }