submodule-version 1.1.4 → 2.0.2

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 (55) hide show
  1. package/README.md +52 -9
  2. package/dist/index.d.ts +2 -0
  3. package/dist/index.js +5 -0
  4. package/dist/index.js.map +1 -0
  5. package/dist/src/__tests__/parseGitUrl.test.d.ts +1 -0
  6. package/dist/src/__tests__/parseGitUrl.test.js +30 -0
  7. package/dist/src/__tests__/parseGitUrl.test.js.map +1 -0
  8. package/dist/src/__tests__/versionUtil.test.d.ts +1 -0
  9. package/dist/src/__tests__/versionUtil.test.js +51 -0
  10. package/dist/src/__tests__/versionUtil.test.js.map +1 -0
  11. package/dist/src/buildGraph.d.ts +7 -0
  12. package/dist/src/buildGraph.js +73 -0
  13. package/dist/src/buildGraph.js.map +1 -0
  14. package/dist/src/config.d.ts +4 -0
  15. package/dist/src/config.js +8 -0
  16. package/dist/src/config.js.map +1 -0
  17. package/dist/src/git.d.ts +5 -0
  18. package/dist/src/git.js +54 -0
  19. package/dist/src/git.js.map +1 -0
  20. package/dist/src/install.d.ts +1 -0
  21. package/dist/src/install.js +43 -0
  22. package/dist/src/install.js.map +1 -0
  23. package/dist/src/log.d.ts +4 -0
  24. package/dist/src/log.js +20 -0
  25. package/dist/src/log.js.map +1 -0
  26. package/dist/src/parseGitUrl.d.ts +7 -0
  27. package/dist/src/parseGitUrl.js +26 -0
  28. package/dist/src/parseGitUrl.js.map +1 -0
  29. package/dist/src/pkgJsonUtil.d.ts +5 -0
  30. package/dist/src/pkgJsonUtil.js +38 -0
  31. package/dist/src/pkgJsonUtil.js.map +1 -0
  32. package/dist/src/submoduleVersion.d.ts +1 -0
  33. package/dist/src/submoduleVersion.js +43 -0
  34. package/dist/src/submoduleVersion.js.map +1 -0
  35. package/dist/src/versionUtil.d.ts +12 -0
  36. package/dist/src/versionUtil.js +68 -0
  37. package/dist/src/versionUtil.js.map +1 -0
  38. package/eslint.config.js +5 -0
  39. package/index-dev.ts +7 -0
  40. package/index.ts +3 -0
  41. package/jest.config.js +9 -0
  42. package/package.json +14 -4
  43. package/src/__tests__/parseGitUrl.test.ts +33 -0
  44. package/src/__tests__/versionUtil.test.ts +60 -0
  45. package/src/buildGraph.ts +105 -0
  46. package/src/config.ts +4 -0
  47. package/src/git.ts +51 -0
  48. package/src/install.ts +47 -0
  49. package/src/log.ts +16 -0
  50. package/src/parseGitUrl.ts +31 -0
  51. package/src/pkgJsonUtil.ts +36 -0
  52. package/src/submoduleVersion.ts +54 -0
  53. package/src/versionUtil.ts +82 -0
  54. package/tsconfig.json +30 -0
  55. package/index.js +0 -177
@@ -0,0 +1,68 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.versionUtil = void 0;
4
+ var ComapreMode;
5
+ (function (ComapreMode) {
6
+ ComapreMode[ComapreMode["major"] = 0] = "major";
7
+ ComapreMode[ComapreMode["minor"] = 1] = "minor";
8
+ ComapreMode[ComapreMode["path"] = 2] = "path";
9
+ })(ComapreMode || (ComapreMode = {}));
10
+ const cleanup = (v) => v.replace(/^\^?~?/, '');
11
+ const toArray = (v) => (cleanup(v).split('.').map(Number));
12
+ const mode = (v) => {
13
+ // Minor and path can be vary. Only major should be same
14
+ if (v.startsWith('^'))
15
+ return ComapreMode.major;
16
+ // Path can be vary. Major and minor should be same
17
+ if (v.startsWith('~'))
18
+ return ComapreMode.minor;
19
+ // Everything should be same
20
+ return ComapreMode.path;
21
+ };
22
+ const compare = (baseVer, actualVer, compareMode) => {
23
+ const val = new Array(3).fill(null).reduce((acc, _, i) => {
24
+ // Alrady is not compatible
25
+ if (!acc)
26
+ return acc;
27
+ // Mode not include comparation, version should be same or hier
28
+ if (i > compareMode)
29
+ return actualVer[i] >= baseVer[i];
30
+ return baseVer[i] === actualVer[i];
31
+ }, true);
32
+ return val;
33
+ };
34
+ const pick = (allVersions, v) => {
35
+ if (v === '*')
36
+ return allVersions;
37
+ const vMode = mode(v);
38
+ const vArr = toArray(v);
39
+ return allVersions.filter(av => {
40
+ const avArr = toArray(av);
41
+ return compare(vArr, avArr, vMode);
42
+ });
43
+ };
44
+ const validate = (v, relative) => {
45
+ if (relative) {
46
+ if (v === '*')
47
+ return true;
48
+ return /^[\^$]?\d+\.\d+\.\d+$/.test(v);
49
+ }
50
+ return /^\d+\.\d+\.\d+$/.test(v);
51
+ };
52
+ const latest = (versions) => (cleanup([...versions].sort((v1, v2) => {
53
+ const v1Arr = toArray(v1);
54
+ const v2Arr = toArray(v2);
55
+ return v1Arr.reduce((acc, v1Val, i) => {
56
+ if (acc !== 0)
57
+ return acc;
58
+ const v2Val = v2Arr[i];
59
+ return v2Val - v1Val;
60
+ }, 0);
61
+ })[0]));
62
+ exports.versionUtil = {
63
+ compare,
64
+ pick,
65
+ validate,
66
+ latest,
67
+ };
68
+ //# sourceMappingURL=versionUtil.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"versionUtil.js","sourceRoot":"","sources":["../../src/versionUtil.ts"],"names":[],"mappings":"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"}
package/eslint.config.js CHANGED
@@ -4,6 +4,11 @@ const globals = require('globals');
4
4
  module.exports = [
5
5
  ...soft,
6
6
  {
7
+ settings: {
8
+ react: {
9
+ "version": "100.0.0",
10
+ },
11
+ },
7
12
  languageOptions: {
8
13
  globals: globals.node,
9
14
  sourceType: "commonjs",
package/index-dev.ts ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ts-node
2
+
3
+ import chalk from 'chalk';
4
+ import { log } from './src/log';
5
+
6
+ log.message(chalk.bgRed.white.bold(' RUNNED IN DEV MODE '));
7
+ import './src/submoduleVersion';
package/index.ts ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+
3
+ import './src/submoduleVersion';
package/jest.config.js ADDED
@@ -0,0 +1,9 @@
1
+ /** @type {import('ts-jest').JestConfigWithTsJest} **/
2
+ module.exports = {
3
+ testMatch: ['<rootDir>/src/**/*.test.ts'],
4
+ moduleDirectories: ['node_modules'],
5
+ testEnvironment: "node",
6
+ transform: {
7
+ "^.+.tsx?$": ["ts-jest", {}],
8
+ },
9
+ };
package/package.json CHANGED
@@ -1,20 +1,30 @@
1
1
  {
2
2
  "name": "submodule-version",
3
3
  "description": "Git <S>ubmodule <V>ersioning tool",
4
- "version": "1.1.4",
4
+ "version": "2.0.2",
5
5
  "bin": {
6
- "sv": "index.js"
6
+ "sv": "dist/index.js",
7
+ "sv-dev": "index-dev.ts"
7
8
  },
8
9
  "scripts": {
9
- "lint": "npx eslint"
10
+ "prepare": "npm run build",
11
+ "test": "npx jest",
12
+ "build": "npx tsc",
13
+ "lint": "npx eslint ./src"
10
14
  },
11
15
  "author": "",
12
16
  "license": "ISC",
13
17
  "devDependencies": {
18
+ "@types/jest": "^29.5.13",
14
19
  "eslint": "9.9.1",
15
- "eslint-config-soft": "^1.0.5"
20
+ "eslint-config-soft": "^1.0.5",
21
+ "ts-jest": "^29.2.5",
22
+ "ts-node": "^10.9.2"
16
23
  },
17
24
  "dependencies": {
25
+ "chalk": "^4.1.2",
26
+ "jest": "^29.7.0",
27
+ "typescript": "^5.6.2",
18
28
  "yargs": "^17.7.2"
19
29
  }
20
30
  }
@@ -0,0 +1,33 @@
1
+ import { parseGitUrl } from '../parseGitUrl';
2
+
3
+ describe('Parse git url', () => {
4
+ it('Without version', () => {
5
+ const url = 'git@github.com:Casique-front/audio-mixer.git';
6
+
7
+ expect(parseGitUrl(url)).toEqual({
8
+ url: 'git@github.com:Casique-front/audio-mixer.git',
9
+ version: undefined,
10
+ name: 'audio-mixer',
11
+ });
12
+ });
13
+
14
+ it('With version', () => {
15
+ const url = 'git@github.com:Casique-front/audio-mixer.git@1.0.1';
16
+
17
+ expect(parseGitUrl(url)).toEqual({
18
+ url: 'git@github.com:Casique-front/audio-mixer.git',
19
+ version: '1.0.1',
20
+ name: 'audio-mixer',
21
+ });
22
+ });
23
+
24
+ it('Wrong string', () => {
25
+ const url = 'xxx@version';
26
+
27
+ expect(parseGitUrl(url)).toEqual({
28
+ url: 'xxx@version',
29
+ version: null,
30
+ name: '',
31
+ });
32
+ });
33
+ });
@@ -0,0 +1,60 @@
1
+ import { versionUtil } from '../versionUtil';
2
+
3
+ describe('Latest', () => {
4
+ it('Single version', () => {
5
+ const v = versionUtil.latest(['1.0.0']);
6
+
7
+ expect(v).toBe('1.0.0');
8
+ });
9
+
10
+ it('Path versions', () => {
11
+ const v = versionUtil.latest(['^1.0.0', '^1.0.1']);
12
+
13
+ expect(v).toBe('1.0.1');
14
+ });
15
+
16
+ it('Minor versions', () => {
17
+ const v = versionUtil.latest(['^1.1.0', '^1.0.23']);
18
+
19
+ expect(v).toBe('1.1.0');
20
+ });
21
+
22
+ it('Major versions', () => {
23
+ const v = versionUtil.latest(['^2.0.0', '^1.10.23']);
24
+
25
+ expect(v).toBe('2.0.0');
26
+ });
27
+ });
28
+
29
+ describe('Validate', () => {
30
+ it('Pass', () => {
31
+ expect(versionUtil.validate('')).toBe(false);
32
+ expect(versionUtil.validate('123')).toBe(false);
33
+ expect(versionUtil.validate('1.2.3')).toBe(true);
34
+ expect(versionUtil.validate('1.2.3', true)).toBe(true);
35
+ expect(versionUtil.validate('^1.2.3', true)).toBe(true);
36
+ expect(versionUtil.validate('^1.2.3')).toBe(false);
37
+ expect(versionUtil.validate('*')).toBe(false);
38
+ expect(versionUtil.validate('*', true)).toBe(true);
39
+ });
40
+ });
41
+
42
+ describe('Compare', () => {
43
+ it('Major mode', () => {
44
+ expect(versionUtil.compare([1, 2, 3], [1, 3, 4], 0)).toBe(true);
45
+ expect(versionUtil.compare([1, 3, 3], [1, 2, 4], 0)).toBe(false);
46
+ expect(versionUtil.compare([1, 2, 3], [1, 2, 4], 1)).toBe(true);
47
+ expect(versionUtil.compare([1, 2, 4], [1, 2, 3], 1)).toBe(false);
48
+ });
49
+
50
+ it('Minor mode', () => {
51
+ expect(versionUtil.compare([1, 2, 3], [1, 2, 4], 1)).toBe(true);
52
+ expect(versionUtil.compare([1, 2, 4], [1, 2, 3], 1)).toBe(false);
53
+ });
54
+
55
+ it('Path mode', () => {
56
+ expect(versionUtil.compare([1, 2, 3], [1, 2, 3], 2)).toBe(true);
57
+ expect(versionUtil.compare([1, 2, 4], [1, 2, 3], 2)).toBe(false);
58
+ expect(versionUtil.compare([1, 2, 3], [1, 2, 4], 2)).toBe(false);
59
+ });
60
+ });
@@ -0,0 +1,105 @@
1
+ import chalk from 'chalk';
2
+ import { pkgJsonUtil } from './pkgJsonUtil';
3
+ import { log } from './log';
4
+ import { git } from './git';
5
+ import { parseGitUrl } from './parseGitUrl';
6
+ import { versionUtil } from './versionUtil';
7
+
8
+ type TSV = Record<string, string>;
9
+
10
+ interface ITreeEntry {
11
+ meta: {
12
+ version: string;
13
+ sv: TSV;
14
+ };
15
+ parents: Record<string, string>;
16
+ }
17
+
18
+ type TBaseTree = Record<string, ITreeEntry>;
19
+
20
+ export interface IGraphEntry {
21
+ versions: string[];
22
+ parents: Record<string, string>;
23
+ used: string[];
24
+ version: string;
25
+ }
26
+
27
+ export const buildGraph = (): Record<string, IGraphEntry> | void => {
28
+ const baseJson = pkgJsonUtil.read();
29
+ const baseTree: TBaseTree = {};
30
+
31
+ if (!baseJson) return log.error('Not a npm project');
32
+ if (!baseJson.sv) return log.message('There is no dependencies');
33
+
34
+ const recursiveBuilder = (dependencies: TSV = {}, parent = '*') => {
35
+ Object.entries(dependencies).forEach(([url, version]) => {
36
+ const { name } = parseGitUrl(url);
37
+ const jsonPath = pkgJsonUtil.getPkgJsonPath(name);
38
+ if (!versionUtil.validate(version, true)) {
39
+ const em = `<${version}> for [${name}] in [${parent}]`;
40
+
41
+ return log.error('Unknown version', em);
42
+ }
43
+ if (!jsonPath) {
44
+ git.addSumbmodule(url);
45
+ }
46
+
47
+ const exists = baseTree[name] || null;
48
+ const mJson = exists ? null : pkgJsonUtil.read(name);
49
+
50
+ if (!version) return log.error('Module', name, 'not installed');
51
+
52
+ const meta = exists?.meta || { version: mJson.version, sv: mJson.sv };
53
+ const parents = exists?.parents || {};
54
+
55
+ baseTree[name] = {
56
+ meta,
57
+ parents: { ...parents, [parent]: version },
58
+ };
59
+
60
+ recursiveBuilder(meta.sv, name);
61
+ });
62
+ };
63
+
64
+ recursiveBuilder(baseJson.sv, baseJson.name);
65
+
66
+ const graph = Object.entries(baseTree).reduce((acc, [name, data]) => {
67
+ const versions = git.listVersions(name);
68
+ const { parents, meta } = data;
69
+
70
+ const used = Object.entries(parents).reduce((selective, [, v]) => (
71
+ versionUtil.pick(selective, v)
72
+ ), versions);
73
+
74
+ if (!used.length) {
75
+ const longestVersion = Object.values(parents).reduce((len, v) => (
76
+ v.length > len ? v.length : len
77
+ ), 0);
78
+
79
+ const formattedName = chalk.green.bold(name);
80
+ const baseMsg = `${chalk.red('Confilict')} for ${formattedName}:`;
81
+
82
+ const conflictMsg = Object.entries(parents).reduce((msg, [pName, v]) => {
83
+ const formatedV = chalk.bold.red(String(v).padEnd(longestVersion, ' '));
84
+
85
+ return [
86
+ ...msg,
87
+ ` - Version ${formatedV} used in ${chalk.green.bold(pName)}`,
88
+ ];
89
+ }, [baseMsg]);
90
+
91
+ log.error(conflictMsg.join('\n'));
92
+ }
93
+
94
+ if (!used.includes(meta.version)) {
95
+ git.checkout(name, versionUtil.latest(used));
96
+ }
97
+
98
+ return {
99
+ ...acc,
100
+ [name]: { versions, parents, used, version: meta.version },
101
+ };
102
+ }, {});
103
+
104
+ return graph;
105
+ };
package/src/config.ts ADDED
@@ -0,0 +1,4 @@
1
+ export const config = {
2
+ dir: 'modules',
3
+ tag: 'master',
4
+ };
package/src/git.ts ADDED
@@ -0,0 +1,51 @@
1
+ import chalk from 'chalk';
2
+ import { execSync } from 'child_process';
3
+ import path from 'path';
4
+ import { config } from './config';
5
+ import { log } from './log';
6
+ import { parseGitUrl } from './parseGitUrl';
7
+ import { versionUtil } from './versionUtil';
8
+
9
+ const addSumbmodule = (url: string) => {
10
+ const { name } = parseGitUrl(url);
11
+ const module = path.join(config.dir, name);
12
+
13
+ try {
14
+ execSync(`git submodule add ${url} ${module}`);
15
+ } catch {
16
+ log.error(url, 'Is not a git repo');
17
+ }
18
+ };
19
+
20
+ const checkout = (name: string, version: string) => {
21
+ const module = path.join(config.dir, name);
22
+ const styledName = chalk.green.bold(name);
23
+ const styledVer = chalk.green.bold(version);
24
+ log.message('Checkout', styledName, 'to', styledVer);
25
+
26
+ try {
27
+ execSync(`git -C ${module} fetch`);
28
+ execSync(`git -C ${module} checkout ${version} -q`);
29
+ } catch {
30
+ log.error('Checkout failded');
31
+ };
32
+ };
33
+
34
+ const listVersions = (projectName: string) => {
35
+ const module = path.join(config.dir, projectName);
36
+ execSync(`git -C ${module} fetch`);
37
+ const res = execSync(`git -C ${module} tag -l`);
38
+ const tagList = res.toString().split(/\r?\n/);
39
+
40
+ return tagList.filter(tag => {
41
+ if (!tag) return false;
42
+
43
+ return versionUtil.validate(tag);
44
+ });
45
+ };
46
+
47
+ export const git = {
48
+ addSumbmodule,
49
+ checkout,
50
+ listVersions,
51
+ };
package/src/install.ts ADDED
@@ -0,0 +1,47 @@
1
+ import chalk from "chalk";
2
+ import { parseGitUrl } from "./parseGitUrl";
3
+ import { pkgJsonUtil } from "./pkgJsonUtil";
4
+ import { log } from "./log";
5
+ import { git } from "./git";
6
+ import { versionUtil } from "./versionUtil";
7
+ import { buildGraph } from "./buildGraph";
8
+
9
+ interface IArg {
10
+ url: string;
11
+ target?: string;
12
+ }
13
+
14
+ /* Yargs typed as any */
15
+ export const install = (arg: any) => {
16
+ const { target } = arg as IArg;
17
+ const { url, version, name } = parseGitUrl(arg.url);
18
+
19
+ if (!name) {
20
+ return log.error(chalk.red.bold(url), '- is not a git url!');
21
+ }
22
+
23
+ const targetPkg = pkgJsonUtil.read(target);
24
+ let installedPkg = pkgJsonUtil.read(name);
25
+
26
+ if (!installedPkg) {
27
+ git.addSumbmodule(url);
28
+ installedPkg = pkgJsonUtil.read(name);
29
+ }
30
+
31
+ const versions = git.listVersions(name);
32
+ const latestVersion = versionUtil.latest(versions);
33
+ const requestedVersion = version || latestVersion;
34
+
35
+ if (!versions.includes(requestedVersion)) {
36
+ const styledName = `Package ${chalk.bold.green(name)}`;
37
+ const styledVer = chalk.bgRed.bold.white(` ${requestedVersion} `);
38
+ const errMsg = `does not contain version ${styledVer}.`;
39
+ const styledVList = chalk.bold.green(versions.join(', '));
40
+ const helpMsg = `Use one from [${styledVList}]`;
41
+ log.error(styledName, errMsg, helpMsg);
42
+ }
43
+
44
+ targetPkg.sv[url] = `^${requestedVersion}`;
45
+ pkgJsonUtil.write(targetPkg, target);
46
+ buildGraph();
47
+ };
package/src/log.ts ADDED
@@ -0,0 +1,16 @@
1
+ import yargs from 'yargs';
2
+
3
+ const message = (...args: string[]) => {
4
+ // eslint-disable-next-line no-console
5
+ console.log(...args);
6
+ };
7
+
8
+ const error = (...args: string[]) => {
9
+ message(...args);
10
+ yargs.exit(1, new Error(args.join(' ')));
11
+ };
12
+
13
+ export const log = {
14
+ message,
15
+ error,
16
+ };
@@ -0,0 +1,31 @@
1
+ interface IParseResult {
2
+ name: string;
3
+ url: string;
4
+ version: string | null;
5
+ }
6
+
7
+ const PARSE_REGEX = /^(?<url>.*\.git)(@(?<version>.*))?$/;
8
+ const NAME_REGEX = /\/(?<name>.*)\.git/;
9
+
10
+ const makeEmptyObject = (url: string) => ({
11
+ name: '',
12
+ url,
13
+ version: null,
14
+ });
15
+
16
+ export const parseGitUrl = (url: string): IParseResult => {
17
+ const { groups } = PARSE_REGEX.exec(url) || {};
18
+
19
+ if (!groups || !groups.url) return makeEmptyObject(url);
20
+
21
+ const { groups: nameGroups } = NAME_REGEX.exec(url) || {};
22
+ const { name } = nameGroups || {};
23
+
24
+ if (!name) return makeEmptyObject(url);
25
+
26
+ return {
27
+ url: groups.url,
28
+ version: groups.version,
29
+ name,
30
+ };
31
+ };
@@ -0,0 +1,36 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import { config } from './config';
4
+
5
+ const JSON_NAME = 'package.json';
6
+
7
+ const getPkgJsonPath = (name?: string) => {
8
+ const folder = name ? [config.dir, name] : [];
9
+ const jsonPath = path.join(process.cwd(), ...folder, JSON_NAME);
10
+ const isJsonExists = fs.existsSync(jsonPath);
11
+ if (!isJsonExists) return null;
12
+
13
+ return jsonPath;
14
+ };
15
+
16
+ const read = (name?: string) => {
17
+ const jsonPath = getPkgJsonPath(name);
18
+ if (!jsonPath) return null;
19
+ const jsonString = fs.readFileSync(jsonPath, 'utf-8');
20
+
21
+ return JSON.parse(jsonString);
22
+ };
23
+
24
+ const write = (data: Object, name?: string) => {
25
+ const jsonPath = getPkgJsonPath(name);
26
+ if (!jsonPath) return null;
27
+ fs.writeFileSync(jsonPath, JSON.stringify(data, null, 2));
28
+ };
29
+
30
+ export const pkgJsonUtil = {
31
+ getPkgJsonPath,
32
+ read,
33
+ write,
34
+ };
35
+
36
+ module.exports = { pkgJsonUtil };
@@ -0,0 +1,54 @@
1
+ import chalk from 'chalk';
2
+ import yargs from 'yargs';
3
+ import { buildGraph } from './buildGraph';
4
+ import { versionUtil } from './versionUtil';
5
+ import { install } from './install';
6
+ import { log } from './log';
7
+ import { git } from './git';
8
+
9
+ const mainProcess = () => {
10
+ buildGraph();
11
+ log.message(chalk.green.bold('Everything is up to date!🔥'));
12
+ };
13
+
14
+ const update = () => {
15
+ const graph = buildGraph();
16
+ if (!graph) return;
17
+ Object.entries(graph).forEach(([name, data]) => {
18
+ const { version, used } = data;
19
+ const latestVersion = versionUtil.latest(used);
20
+ if (latestVersion !== version) {
21
+ git.checkout(name, latestVersion);
22
+ }
23
+ });
24
+ };
25
+
26
+ // eslint-disable-next-line @typescript-eslint/no-unused-expressions
27
+ yargs.scriptName('sv')
28
+ .usage('$0 <cmd> [args]')
29
+ .command(
30
+ ['validate', '$0', 'v'],
31
+ 'Validate and install modules',
32
+ () => {},
33
+ mainProcess,
34
+ )
35
+ .command(
36
+ ['update', 'u'],
37
+ 'Update git modules to actual versions',
38
+ () => {},
39
+ update,
40
+ )
41
+ .command(
42
+ ['install <url> [target]', 'i'],
43
+ 'Install new submodule',
44
+ y => y.positional('url', {
45
+ type: 'string',
46
+ describe: 'Git submodule url',
47
+ }).positional('target', {
48
+ type: 'string',
49
+ describe: 'Target module name',
50
+ }),
51
+ install,
52
+ )
53
+ .help()
54
+ .argv;
@@ -0,0 +1,82 @@
1
+ enum ComapreMode {
2
+ major = 0,
3
+ minor = 1,
4
+ path = 2,
5
+ }
6
+
7
+ const cleanup = (v: string) => v.replace(/^\^?~?/, '');
8
+
9
+ const toArray = (v: string): number[] => (
10
+ cleanup(v).split('.').map(Number)
11
+ );
12
+
13
+ const mode = (v: string) => {
14
+ // Minor and path can be vary. Only major should be same
15
+ if (v.startsWith('^')) return ComapreMode.major;
16
+ // Path can be vary. Major and minor should be same
17
+ if (v.startsWith('~')) return ComapreMode.minor;
18
+
19
+ // Everything should be same
20
+ return ComapreMode.path;
21
+ };
22
+
23
+ const compare = (
24
+ baseVer: number[],
25
+ actualVer: number[],
26
+ compareMode: ComapreMode,
27
+ ) => {
28
+ const val = new Array(3).fill(null).reduce((acc, _, i) => {
29
+ // Alrady is not compatible
30
+ if (!acc) return acc;
31
+ // Mode not include comparation, version should be same or hier
32
+ if (i > compareMode) return actualVer[i] >= baseVer[i];
33
+
34
+ return baseVer[i] === actualVer[i];
35
+ }, true);
36
+
37
+ return val;
38
+ };
39
+
40
+ const pick = (allVersions: string[], v: string) => {
41
+ if (v === '*') return allVersions;
42
+
43
+ const vMode = mode(v);
44
+ const vArr = toArray(v);
45
+
46
+ return allVersions.filter(av => {
47
+ const avArr = toArray(av);
48
+
49
+ return compare(vArr, avArr, vMode);
50
+ });
51
+ };
52
+
53
+ const validate = (v: string, relative?: boolean) => {
54
+ if (relative) {
55
+ if (v === '*') return true;
56
+
57
+ return /^[\^$]?\d+\.\d+\.\d+$/.test(v);
58
+ }
59
+
60
+ return /^\d+\.\d+\.\d+$/.test(v);
61
+ };
62
+
63
+ const latest = (versions: string[]) => (
64
+ cleanup([...versions].sort((v1, v2) => {
65
+ const v1Arr = toArray(v1);
66
+ const v2Arr = toArray(v2);
67
+
68
+ return v1Arr.reduce((acc, v1Val, i) => {
69
+ if (acc !== 0) return acc;
70
+ const v2Val = v2Arr[i];
71
+
72
+ return v2Val - v1Val;
73
+ }, 0);
74
+ })[0])
75
+ );
76
+
77
+ export const versionUtil = {
78
+ compare,
79
+ pick,
80
+ validate,
81
+ latest,
82
+ };