From bd7926c9b6a0374bd6e4218de95d0674c8b975f5 Mon Sep 17 00:00:00 2001 From: David First Date: Wed, 27 Mar 2024 14:17:02 -0400 Subject: [PATCH 1/6] move snap/hash generation to component-version --- .bitmap | 7 +++++++ scopes/component/snapping/tag-model-component.ts | 2 +- src/consumer/component/consumer-component.ts | 6 +++--- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/.bitmap b/.bitmap index 019524bc7fb..5f714cb0d27 100644 --- a/.bitmap +++ b/.bitmap @@ -380,6 +380,13 @@ "mainFile": "index.ts", "rootDir": "scopes/component/component-tree" }, + "component-version": { + "name": "component-version", + "scope": "teambit.component", + "version": "1.0.3", + "mainFile": "index.ts", + "rootDir": "components/component-version" + }, "component-writer": { "name": "component-writer", "scope": "teambit.component", diff --git a/scopes/component/snapping/tag-model-component.ts b/scopes/component/snapping/tag-model-component.ts index 36e7d9ae39c..d77c8bb4e7e 100644 --- a/scopes/component/snapping/tag-model-component.ts +++ b/scopes/component/snapping/tag-model-component.ts @@ -82,7 +82,7 @@ function updateDependenciesVersions( function setHashes(componentsToTag: ConsumerComponent[]): void { componentsToTag.forEach((componentToTag) => { - componentToTag.setNewVersion(sha1(v4())); + componentToTag.setNewVersion(); }); } diff --git a/src/consumer/component/consumer-component.ts b/src/consumer/component/consumer-component.ts index ecaa3fea30a..9619d87c48d 100644 --- a/src/consumer/component/consumer-component.ts +++ b/src/consumer/component/consumer-component.ts @@ -1,10 +1,10 @@ import { ComponentID, ComponentIdList } from '@teambit/component-id'; import fs from 'fs-extra'; -import { v4 } from 'uuid'; import * as path from 'path'; import R from 'ramda'; import { IssuesList } from '@teambit/component-issues'; import { BitId } from '@teambit/legacy-bit-id'; +import { generateSnap } from '@teambit/component-version'; import { BitError } from '@teambit/bit-error'; import { getCloudDomain, BIT_WORKSPACE_TMP_DIRNAME, BuildStatus, DEFAULT_LANGUAGE, Extensions } from '../../constants'; import docsParser from '../../jsdoc/parser'; @@ -12,7 +12,7 @@ import { Doclet } from '../../jsdoc/types'; import logger from '../../logger/logger'; import { ScopeListItem } from '../../scope/models/model-component'; import Version, { DepEdge, Log } from '../../scope/models/version'; -import { pathNormalizeToLinux, sha1 } from '../../utils'; +import { pathNormalizeToLinux } from '../../utils'; import { PathLinux, PathOsBased, PathOsBasedRelative } from '../../utils/path'; import ComponentMap from '../bit-map/component-map'; import { IgnoredDirectory } from '../component-ops/add-components/exceptions/ignored-directory'; @@ -269,7 +269,7 @@ export default class Component { this.peerDependencies = new Dependencies(peerDependencies); } - setNewVersion(version = sha1(v4())) { + setNewVersion(version = generateSnap()) { this.previouslyUsedVersion = this.id.hasVersion() ? this.version : undefined; this.version = version; } From 380042dc6b637a902e253a1ed1e37379bbd90758 Mon Sep 17 00:00:00 2001 From: David First Date: Wed, 27 Mar 2024 14:23:22 -0400 Subject: [PATCH 2/6] adding components/component-version/ --- .../component-version/exceptions/index.ts | 3 + .../exceptions/invalid-version.ts | 7 ++ components/component-version/index.ts | 23 ++++++ .../component-version/version-parser.spec.ts | 29 +++++++ .../component-version/version-parser.ts | 78 +++++++++++++++++++ components/component-version/version.spec.ts | 25 ++++++ components/component-version/version.ts | 33 ++++++++ 7 files changed, 198 insertions(+) create mode 100644 components/component-version/exceptions/index.ts create mode 100644 components/component-version/exceptions/invalid-version.ts create mode 100644 components/component-version/index.ts create mode 100644 components/component-version/version-parser.spec.ts create mode 100644 components/component-version/version-parser.ts create mode 100644 components/component-version/version.spec.ts create mode 100644 components/component-version/version.ts diff --git a/components/component-version/exceptions/index.ts b/components/component-version/exceptions/index.ts new file mode 100644 index 00000000000..fb67ac3e694 --- /dev/null +++ b/components/component-version/exceptions/index.ts @@ -0,0 +1,3 @@ +import InvalidVersion from './invalid-version'; + +export { InvalidVersion }; diff --git a/components/component-version/exceptions/invalid-version.ts b/components/component-version/exceptions/invalid-version.ts new file mode 100644 index 00000000000..339d9b601e5 --- /dev/null +++ b/components/component-version/exceptions/invalid-version.ts @@ -0,0 +1,7 @@ +import { BitError } from '@teambit/bit-error'; + +export default class InvalidVersion extends BitError { + constructor(version?: string | null) { + super(`error: version ${version || '(empty)'} is not a valid semantic version. learn more: https://semver.org`); + } +} diff --git a/components/component-version/index.ts b/components/component-version/index.ts new file mode 100644 index 00000000000..1654132c7fe --- /dev/null +++ b/components/component-version/index.ts @@ -0,0 +1,23 @@ +import { Version, LATEST_VERSION } from './version'; +import versionParser, { + isHash, + isSnap, + isTag, + HASH_SIZE, + SHORT_HASH_MINIMUM_SIZE, + generateSnap, +} from './version-parser'; +import { InvalidVersion } from './exceptions'; + +export { + Version, + LATEST_VERSION, + versionParser, + isHash, + isSnap, + isTag, + generateSnap, + InvalidVersion, + HASH_SIZE, + SHORT_HASH_MINIMUM_SIZE, +}; diff --git a/components/component-version/version-parser.spec.ts b/components/component-version/version-parser.spec.ts new file mode 100644 index 00000000000..1b39933d2c8 --- /dev/null +++ b/components/component-version/version-parser.spec.ts @@ -0,0 +1,29 @@ +import { expect } from 'chai'; + +import versionParser from './version-parser'; +import { InvalidVersion } from './exceptions'; + +describe('versionParser()', () => { + it('should return latest version representation', () => { + const version = versionParser('latest'); + expect(version.latest).to.equal(true); + expect(version.versionNum).to.equal(null); + }); + + it('should throw invalid version', () => { + const version = () => versionParser('$1'); + expect(version).to.throw(InvalidVersion); + }); + + it('should return a concrete version', () => { + const version = versionParser('0.0.1'); + expect(version.latest).to.equal(false); + expect(version.versionNum).to.equal('0.0.1'); + }); + + it('should parse given version as latest', () => { + const version = versionParser('latest'); + expect(version.versionNum).to.equal(null); + expect(version.latest).to.equal(true); + }); +}); diff --git a/components/component-version/version-parser.ts b/components/component-version/version-parser.ts new file mode 100644 index 00000000000..ca830db4fb3 --- /dev/null +++ b/components/component-version/version-parser.ts @@ -0,0 +1,78 @@ +import semver from 'semver'; +import crypto from 'crypto'; +import { InvalidVersion } from './exceptions'; +import { Version, LATEST_VERSION } from './version'; + +export const HASH_SIZE = 40; + +/** + * because the directory structure is `XX/YY....`, it needs to have at least three characters. + */ +export const SHORT_HASH_MINIMUM_SIZE = 3; + +function isLatest(versionStr: string): boolean { + return versionStr === LATEST_VERSION; +} + +function isSemverValid(versionStr: string) { + return Boolean(semver.valid(versionStr)); +} + +function returnSemver(versionStr: string): Version { + return new Version(versionStr, false); +} + +function returnLatest(): Version { + return new Version(null, true); +} + +function returnSnap(hash: string): Version { + return new Version(hash, false); +} + +/** + * a snap is a 40 characters hash encoded in HEX. so it can be a-f and 0-9. + * also, for convenience, a short-hash can be used, which is a minimum of 3 characters. + */ +export function isHash(str: string | null | undefined): boolean { + return typeof str === 'string' && isHex(str) && str.length >= SHORT_HASH_MINIMUM_SIZE && str.length <= HASH_SIZE; +} + +/** + * a component version can be a tag (semver) or a snap (hash) + */ +export function isTag(str?: string): boolean { + return typeof str === 'string' && isSemverValid(str); +} + +/** + * a component version can be a tag (semver) or a snap (hash). + * a snap must be 40 characters long and consist of a-f and 0-9. (hex). + */ +export function isSnap(str: string | null | undefined): boolean { + return isHash(str) && typeof str === 'string' && str.length === HASH_SIZE; +} + +/** + * generate a random valid snap hash (which is a 40 characters long hash encoded in HEX) + */ +export function generateSnap(): string { + return crypto.createHash('sha1').update(crypto.randomUUID()).digest('hex'); +} + +export default function versionParser(versionStr: string | null | undefined): Version { + if (!versionStr) return returnLatest(); + if (isLatest(versionStr)) return returnLatest(); + if (isSemverValid(versionStr)) return returnSemver(versionStr); + if (isHash(versionStr)) return returnSnap(versionStr); + + throw new InvalidVersion(versionStr.toString()); +} + +/** + * check if the string consists of valid hexadecimal characters + */ +function isHex(str: string) { + const hexRegex = /^[0-9a-fA-F]+$/; + return hexRegex.test(str); +} diff --git a/components/component-version/version.spec.ts b/components/component-version/version.spec.ts new file mode 100644 index 00000000000..bd9eeb426df --- /dev/null +++ b/components/component-version/version.spec.ts @@ -0,0 +1,25 @@ +import { expect } from 'chai'; + +import { Version } from './version'; + +describe('Version', () => { + describe('toString()', () => { + it('should return latest', () => { + const version = new Version(null, true); + expect(version.toString()).to.equal('latest'); + }); + + it('should return concrete version number', () => { + // @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX! + const version = new Version(12, false); + expect(version.toString()).to.equal('12'); + }); + + it('should throw an invalid version exception', () => { + const version = new Version(null, false); + expect(() => { + version.toString(); + }).to.throw(); + }); + }); +}); diff --git a/components/component-version/version.ts b/components/component-version/version.ts new file mode 100644 index 00000000000..f4069ef6ffb --- /dev/null +++ b/components/component-version/version.ts @@ -0,0 +1,33 @@ +import semver from 'semver'; +import { InvalidVersion } from './exceptions'; + +export const LATEST_VERSION = 'latest'; + +export class Version { + versionNum: string | null | undefined; + latest: boolean; + + constructor(versionNum: string | null | undefined, latest: boolean) { + this.versionNum = versionNum; + this.latest = latest; + if (versionNum && latest) { + throw new Error(`a component version cannot have both: version and "latest"`); + } + } + + toString() { + if (!this.versionNum && this.latest) return 'latest'; + if (this.versionNum && !this.latest) return this.versionNum.toString(); + throw new InvalidVersion(this.versionNum); + } + + isLaterThan(otherVersion: Version): boolean { + if (!this.versionNum || this.versionNum === LATEST_VERSION) { + return true; + } + if (!otherVersion.versionNum || otherVersion.versionNum === LATEST_VERSION) { + return false; + } + return semver.gt(this.versionNum, otherVersion.versionNum); + } +} From 48541152f3071afff54325ee31c42ce5740312d1 Mon Sep 17 00:00:00 2001 From: David First Date: Wed, 27 Mar 2024 15:25:25 -0400 Subject: [PATCH 3/6] fix lint --- scopes/component/snapping/tag-model-component.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/scopes/component/snapping/tag-model-component.ts b/scopes/component/snapping/tag-model-component.ts index d77c8bb4e7e..77a0e4f7372 100644 --- a/scopes/component/snapping/tag-model-component.ts +++ b/scopes/component/snapping/tag-model-component.ts @@ -1,7 +1,6 @@ import mapSeries from 'p-map-series'; import { isEmpty } from 'lodash'; import { ReleaseType } from 'semver'; -import { v4 } from 'uuid'; import { BitError } from '@teambit/bit-error'; import { Scope } from '@teambit/legacy/dist/scope'; import { ComponentID, ComponentIdList } from '@teambit/component-id'; @@ -15,7 +14,6 @@ import { getBasicLog } from '@teambit/legacy/dist/utils/bit/basic-log'; import { Component } from '@teambit/component'; import deleteComponentsFiles from '@teambit/legacy/dist/consumer/component-ops/delete-component-files'; import logger from '@teambit/legacy/dist/logger/logger'; -import { sha1 } from '@teambit/legacy/dist/utils'; import { AutoTagResult, getAutoTagInfo } from '@teambit/legacy/dist/scope/component-ops/auto-tag'; import { getValidVersionOrReleaseType } from '@teambit/legacy/dist/utils/semver-helper'; import { BuilderMain, OnTagOpts } from '@teambit/builder'; From 6c5042b9d31f6618c5f8a390210cc419f473b0f8 Mon Sep 17 00:00:00 2001 From: David First Date: Wed, 27 Mar 2024 15:50:21 -0400 Subject: [PATCH 4/6] upgrade types/node --- workspace.jsonc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/workspace.jsonc b/workspace.jsonc index 168d706250c..9fef0c980c5 100644 --- a/workspace.jsonc +++ b/workspace.jsonc @@ -335,7 +335,7 @@ "@types/mime": "2.0.3", "@types/mini-css-extract-plugin": "2.2.0", "@types/mousetrap": "1.6.5", - "@types/node": "14.14.31", + "@types/node": "20.2.5", "@types/pluralize": "0.0.29", "@types/react": "^17.0.67", "@types/react-dev-utils": "9.0.10", From 51220769a2577c8b342f64958f2f075b2a186915 Mon Sep 17 00:00:00 2001 From: David First Date: Fri, 12 Apr 2024 17:10:53 -0400 Subject: [PATCH 5/6] revert back node version --- workspace.jsonc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/workspace.jsonc b/workspace.jsonc index 314bb76dc1f..88eee6e6d28 100644 --- a/workspace.jsonc +++ b/workspace.jsonc @@ -341,7 +341,7 @@ "@types/mime": "2.0.3", "@types/mini-css-extract-plugin": "2.2.0", "@types/mousetrap": "1.6.5", - "@types/node": "20.2.5", + "@types/node": "14.14.31", "@types/pluralize": "0.0.29", "@types/react": "^17.0.67", "@types/react-dev-utils": "9.0.10", From 5be4445510d0c9ce5deb713c5f52ee11f23c5191 Mon Sep 17 00:00:00 2001 From: David First Date: Fri, 12 Apr 2024 19:05:13 -0400 Subject: [PATCH 6/6] fix tests --- components/component-version/version-parser.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/components/component-version/version-parser.ts b/components/component-version/version-parser.ts index ca830db4fb3..243530e1be3 100644 --- a/components/component-version/version-parser.ts +++ b/components/component-version/version-parser.ts @@ -57,6 +57,7 @@ export function isSnap(str: string | null | undefined): boolean { * generate a random valid snap hash (which is a 40 characters long hash encoded in HEX) */ export function generateSnap(): string { + // @ts-ignore until @types/node is updated to v18 or above return crypto.createHash('sha1').update(crypto.randomUUID()).digest('hex'); }