vag_tools 0.0.3 → 0.0.5
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/README.md +2 -2
- package/dist/vag_cli.js +4 -3
- package/dist/vag_core.d.ts +93 -0
- package/dist/vag_core.js +623 -0
- package/package.json +4 -3
package/README.md
CHANGED
package/dist/vag_cli.js
CHANGED
|
@@ -15,11 +15,11 @@ import { simpleGit } from "simple-git";
|
|
|
15
15
|
// package.json
|
|
16
16
|
var package_default = {
|
|
17
17
|
name: "vag_tools",
|
|
18
|
-
version: "0.0.
|
|
18
|
+
version: "0.0.5",
|
|
19
19
|
description: "api and cli for managing sub-git-repositories",
|
|
20
20
|
type: "module",
|
|
21
|
-
main: "./dist/
|
|
22
|
-
types: "./dist/
|
|
21
|
+
main: "./dist/vag_core.js",
|
|
22
|
+
types: "./dist/vag_core.d.ts",
|
|
23
23
|
bin: {
|
|
24
24
|
vag: "dist/vag_cli.js",
|
|
25
25
|
vagg: "dist/vagg_server.js"
|
|
@@ -35,6 +35,7 @@ var package_default = {
|
|
|
35
35
|
},
|
|
36
36
|
tsup: {
|
|
37
37
|
entry: [
|
|
38
|
+
"src/vag_core.ts",
|
|
38
39
|
"src/vag_cli.ts",
|
|
39
40
|
"src/vagg_server.ts"
|
|
40
41
|
],
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/** The interface `tListC` is used to construct the property `listC`. */
|
|
2
|
+
interface tListC {
|
|
3
|
+
[p: string]: {
|
|
4
|
+
url: string;
|
|
5
|
+
version: string;
|
|
6
|
+
};
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* A class that contains a list of configured repositories, a list of discovered repositories and methods that use those two lists.
|
|
10
|
+
*/
|
|
11
|
+
declare class Vag {
|
|
12
|
+
/** The top directory from where to search repositories. */
|
|
13
|
+
discoverDir: string;
|
|
14
|
+
/** When a repository is encountered, should it be inspected for searching sub-repositories? */
|
|
15
|
+
deepSearch: boolean;
|
|
16
|
+
/** The path to the yaml-file that provides the repositories to be cloned. */
|
|
17
|
+
importYaml: string;
|
|
18
|
+
/** The base directory where the directories listed in the yaml-file should be cloned. */
|
|
19
|
+
importDir: string;
|
|
20
|
+
/** The list of the path of the discovered repositories. It is populated by the `init()` method. */
|
|
21
|
+
listD: string[];
|
|
22
|
+
/** An object with the content of the yaml-file. It is populated by the `init()` method. */
|
|
23
|
+
listC: tListC;
|
|
24
|
+
/**
|
|
25
|
+
* The constructor of the `Vag` class.
|
|
26
|
+
* `listD` is initialized to an empty list.
|
|
27
|
+
* `listC` is initialized to an empty object.
|
|
28
|
+
*/
|
|
29
|
+
constructor(discoverDir?: string, deepSearch?: boolean, importYaml?: string, importDir?: string);
|
|
30
|
+
discover_repos(discoverDir?: string, deepSearch?: boolean): Promise<number>;
|
|
31
|
+
import_yaml(importYaml?: string, importDir?: string): Promise<number>;
|
|
32
|
+
/**
|
|
33
|
+
* The `init` method populate populate `listD` and `listC`.
|
|
34
|
+
* It must be called just after instanciating `Vag`.
|
|
35
|
+
* The configuration properties `discoverDir`, `deepSearch`, `importYaml` and `importDir` can be reassinged by `init`.
|
|
36
|
+
*/
|
|
37
|
+
init(discoverDir?: string, deepSearch?: boolean, importYaml?: string, importDir?: string): Promise<number>;
|
|
38
|
+
/** List the discovered repositories. */
|
|
39
|
+
d_list(): string[];
|
|
40
|
+
/** List the wished repositories (a.k.a. configured repositories). */
|
|
41
|
+
c_list(): string[];
|
|
42
|
+
/** List the git-repos which are in the D-list and in the C-list. I.e. Intersection of D and C. */
|
|
43
|
+
cd_list(): string[];
|
|
44
|
+
/** List the git-repos which are in the D-list but not in the C-list. I.e. D not C. */
|
|
45
|
+
dnc_list(): string[];
|
|
46
|
+
/** List the git-repos which are in the C-list but not in the D-list. I.e. C not D. */
|
|
47
|
+
cnd_list(): string[];
|
|
48
|
+
/** Clone the repositories of `listC`. */
|
|
49
|
+
c_clone(): Promise<number>;
|
|
50
|
+
/** Checkout the repositories listed in `listC` and `listD`. */
|
|
51
|
+
cd_checkout(): Promise<number>;
|
|
52
|
+
/** For the repositories listed in `listC` and `listD`, verify if they fit with the configuration of Yaml-file. */
|
|
53
|
+
cd_verify(): Promise<number>;
|
|
54
|
+
/** Run a custom git-command on the discoverd repositories (`listD`). */
|
|
55
|
+
d_custom(git_command: string, only_configured_repo?: boolean): Promise<number>;
|
|
56
|
+
/** Git-fetch on the discoverd repositories (`listD`). */
|
|
57
|
+
d_fetch(only_configured_repo?: boolean): Promise<number>;
|
|
58
|
+
/** Git-pull on the discoverd repositories (`listD`). */
|
|
59
|
+
d_pull(only_configured_repo?: boolean): Promise<number>;
|
|
60
|
+
/** Git-push on the discoverd repositories (`listD`). */
|
|
61
|
+
d_push(only_configured_repo?: boolean): Promise<number>;
|
|
62
|
+
/** Show the current branch of the discoverd repositories (`listD`). */
|
|
63
|
+
d_branch(only_configured_repo?: boolean): Promise<number>;
|
|
64
|
+
/** Git-status on the discoverd repositories (`listD`). */
|
|
65
|
+
d_status(only_configured_repo?: boolean): Promise<number>;
|
|
66
|
+
/** Git-diff on the discoverd repositories (`listD`). */
|
|
67
|
+
d_diff(only_configured_repo?: boolean): Promise<number>;
|
|
68
|
+
/** `Git-log -n 3` on the discoverd repositories (`listD`). */
|
|
69
|
+
d_log(only_configured_repo?: boolean): Promise<number>;
|
|
70
|
+
/** `Git-remote -vv` on the discoverd repositories (`listD`). */
|
|
71
|
+
d_remote(only_configured_repo?: boolean): Promise<number>;
|
|
72
|
+
/** `Git-stash list` on the discoverd repositories (`listD`). */
|
|
73
|
+
d_stash_list(only_configured_repo?: boolean): Promise<number>;
|
|
74
|
+
/** `Git-clean -dxf` on the discoverd repositories (`listD`). */
|
|
75
|
+
d_clean(only_configured_repo?: boolean): Promise<number>;
|
|
76
|
+
/** Export in a Yaml-file the list of discoverd repositories (`listD`). */
|
|
77
|
+
d_export_yaml(yamlPath: string, commit_version?: boolean): Promise<number>;
|
|
78
|
+
/**
|
|
79
|
+
* Validate a yaml-file that could be imported later on.
|
|
80
|
+
*
|
|
81
|
+
* @param yamlPath the path to the yaml-file to be checked/validated.
|
|
82
|
+
* @returns an integer-code. 0 if the validation is successful, negative otherwise.
|
|
83
|
+
*/
|
|
84
|
+
validate_yaml(yamlPath: string): Promise<number>;
|
|
85
|
+
/**
|
|
86
|
+
* Return a string with the three numbers (major, minor, patch) written in the package.json.
|
|
87
|
+
*
|
|
88
|
+
* @returns the string Major.Minor.Patch
|
|
89
|
+
*/
|
|
90
|
+
static version_short(): string;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export { Vag, type tListC };
|
package/dist/vag_core.js
ADDED
|
@@ -0,0 +1,623 @@
|
|
|
1
|
+
// src/vag_core.ts
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import fsp from "node:fs/promises";
|
|
5
|
+
import fse from "fs-extra";
|
|
6
|
+
import YAML from "yaml";
|
|
7
|
+
import { simpleGit } from "simple-git";
|
|
8
|
+
|
|
9
|
+
// package.json
|
|
10
|
+
var package_default = {
|
|
11
|
+
name: "vag_tools",
|
|
12
|
+
version: "0.0.5",
|
|
13
|
+
description: "api and cli for managing sub-git-repositories",
|
|
14
|
+
type: "module",
|
|
15
|
+
main: "./dist/vag_core.js",
|
|
16
|
+
types: "./dist/vag_core.d.ts",
|
|
17
|
+
bin: {
|
|
18
|
+
vag: "dist/vag_cli.js",
|
|
19
|
+
vagg: "dist/vagg_server.js"
|
|
20
|
+
},
|
|
21
|
+
files: [
|
|
22
|
+
"dist/",
|
|
23
|
+
"!dist/**/*.map",
|
|
24
|
+
"!dist/**/*.test.*",
|
|
25
|
+
"!dist/**/*.spec.*"
|
|
26
|
+
],
|
|
27
|
+
engines: {
|
|
28
|
+
node: ">=20.10.0"
|
|
29
|
+
},
|
|
30
|
+
tsup: {
|
|
31
|
+
entry: [
|
|
32
|
+
"src/vag_core.ts",
|
|
33
|
+
"src/vag_cli.ts",
|
|
34
|
+
"src/vagg_server.ts"
|
|
35
|
+
],
|
|
36
|
+
format: "esm",
|
|
37
|
+
splitting: false,
|
|
38
|
+
dts: true,
|
|
39
|
+
sourcemap: true,
|
|
40
|
+
clean: true
|
|
41
|
+
},
|
|
42
|
+
scripts: {
|
|
43
|
+
dev: "tsup --watch",
|
|
44
|
+
build: "tsup",
|
|
45
|
+
check: "tsc --noEmit",
|
|
46
|
+
pretty: "prettier --check .",
|
|
47
|
+
format: "prettier --write .",
|
|
48
|
+
lint: "eslint .",
|
|
49
|
+
"test:unit": "vitest",
|
|
50
|
+
"test:unit:once": "vitest --run",
|
|
51
|
+
ci: "run-s check build pretty lint",
|
|
52
|
+
clean: "shx rm -fr build dist tmp",
|
|
53
|
+
"test:basic": "node build/tests/vag.test.js",
|
|
54
|
+
"test:cli": "tests/vag-cli.test.sh",
|
|
55
|
+
"test:coverage": "vitest run --coverage",
|
|
56
|
+
test: "run-s test:basic test:cli test:vitest",
|
|
57
|
+
"dist:build": "tsc --build tsconfig.dist.json",
|
|
58
|
+
clean_all: "run-s clean"
|
|
59
|
+
},
|
|
60
|
+
repository: {
|
|
61
|
+
type: "git",
|
|
62
|
+
url: "git+https://github.com/charlyoleg2/vag.git"
|
|
63
|
+
},
|
|
64
|
+
keywords: [
|
|
65
|
+
"git",
|
|
66
|
+
"sub-repo",
|
|
67
|
+
"multi-repo",
|
|
68
|
+
"submodule",
|
|
69
|
+
"subtree",
|
|
70
|
+
"vcs",
|
|
71
|
+
"vcstool",
|
|
72
|
+
"repositories"
|
|
73
|
+
],
|
|
74
|
+
author: "charlyoleg",
|
|
75
|
+
license: "ISC",
|
|
76
|
+
bugs: {
|
|
77
|
+
url: "https://github.com/charlyoleg2/vag/issues"
|
|
78
|
+
},
|
|
79
|
+
homepage: "https://github.com/charlyoleg2/vag#readme",
|
|
80
|
+
dependencies: {
|
|
81
|
+
"fs-extra": "^11.2.0",
|
|
82
|
+
"simple-git": "^3.22.0",
|
|
83
|
+
yaml: "^2.3.4",
|
|
84
|
+
yargs: "^17.7.2"
|
|
85
|
+
},
|
|
86
|
+
devDependencies: {
|
|
87
|
+
"@types/fs-extra": "^11.0.4",
|
|
88
|
+
"@types/node": "^20.11.19",
|
|
89
|
+
"@types/yargs": "^17.0.32",
|
|
90
|
+
eslint: "^8.56.0",
|
|
91
|
+
"npm-run-all": "^4.1.5",
|
|
92
|
+
prettier: "^3.2.5",
|
|
93
|
+
shx: "^0.3.4",
|
|
94
|
+
tsup: "^8.0.2",
|
|
95
|
+
typescript: "^5.3.3",
|
|
96
|
+
"typescript-eslint": "^7.0.1",
|
|
97
|
+
vitest: "^1.2.2"
|
|
98
|
+
},
|
|
99
|
+
prettier: {
|
|
100
|
+
useTabs: true,
|
|
101
|
+
singleQuote: true,
|
|
102
|
+
trailingComma: "none",
|
|
103
|
+
printWidth: 100,
|
|
104
|
+
plugins: [],
|
|
105
|
+
overrides: []
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
// src/vag_core.ts
|
|
110
|
+
var vag_version_short = package_default.version;
|
|
111
|
+
var regex_pointSlash = /^\.\//;
|
|
112
|
+
var regex_trailingSlash = /\/$/;
|
|
113
|
+
async function isGitRepo(pathDir2) {
|
|
114
|
+
let isRepo = false;
|
|
115
|
+
let isRepoCandidate = false;
|
|
116
|
+
const subdirs = await fse.readdir(pathDir2, { withFileTypes: true });
|
|
117
|
+
for (const subitem of subdirs) {
|
|
118
|
+
if (subitem.isDirectory() && subitem.name === ".git") {
|
|
119
|
+
isRepoCandidate = true;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
if (isRepoCandidate) {
|
|
123
|
+
try {
|
|
124
|
+
const git2 = simpleGit(pathDir2);
|
|
125
|
+
const git2cmd = await git2.revparse(["--show-prefix"]);
|
|
126
|
+
if (git2cmd === "") {
|
|
127
|
+
isRepo = true;
|
|
128
|
+
}
|
|
129
|
+
} catch (err) {
|
|
130
|
+
}
|
|
131
|
+
if (!isRepo) {
|
|
132
|
+
console.log(
|
|
133
|
+
`INFO299: the directory ${pathDir2} contains a sub-directory .git but is not a git-repository`
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return isRepo;
|
|
138
|
+
}
|
|
139
|
+
async function searchGitRepo(pathDir, deepSearch = true) {
|
|
140
|
+
let r_list = [];
|
|
141
|
+
const local_list = await fse.readdir(pathDir, { withFileTypes: true });
|
|
142
|
+
for (const item of local_list) {
|
|
143
|
+
if (item.isDirectory()) {
|
|
144
|
+
const pathDir2 = pathDir + "/" + item.name;
|
|
145
|
+
if ([".git", "node_modules"].includes(item.name)) {
|
|
146
|
+
} else {
|
|
147
|
+
const isRepo = await isGitRepo(pathDir2);
|
|
148
|
+
if (isRepo) {
|
|
149
|
+
r_list.push(pathDir2);
|
|
150
|
+
}
|
|
151
|
+
if (deepSearch || !isRepo) {
|
|
152
|
+
r_list = r_list.concat(await searchGitRepo(pathDir2, deepSearch));
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return r_list;
|
|
158
|
+
}
|
|
159
|
+
function array_intersection(arr1, arr2) {
|
|
160
|
+
return arr1.filter((elem) => arr2.includes(elem));
|
|
161
|
+
}
|
|
162
|
+
function array_exclude(arr_base, arr_exclude) {
|
|
163
|
+
return arr_base.filter((elem) => !arr_exclude.includes(elem));
|
|
164
|
+
}
|
|
165
|
+
async function git_clone(localPath, remote_url, version) {
|
|
166
|
+
let r_code = -1;
|
|
167
|
+
try {
|
|
168
|
+
if (!fs.existsSync(localPath)) {
|
|
169
|
+
const git = simpleGit();
|
|
170
|
+
const gitlog = await git.clone(remote_url, localPath);
|
|
171
|
+
console.log(gitlog);
|
|
172
|
+
r_code = await git_checkout(localPath, version);
|
|
173
|
+
} else {
|
|
174
|
+
const fstat = await fsp.stat(localPath);
|
|
175
|
+
if (fstat.isDirectory()) {
|
|
176
|
+
const isRepo = await isGitRepo(localPath);
|
|
177
|
+
if (isRepo) {
|
|
178
|
+
const git2 = simpleGit(localPath);
|
|
179
|
+
const remote = await git2.getRemotes(true);
|
|
180
|
+
const remote_url2 = remote[0].refs.fetch;
|
|
181
|
+
if (remote_url2 === remote_url2) {
|
|
182
|
+
console.log(
|
|
183
|
+
`INFO398: the git-repo ${localPath} is already cloned! Then just git-pull!`
|
|
184
|
+
);
|
|
185
|
+
const gitlog2 = await git2.pull();
|
|
186
|
+
console.log(gitlog2);
|
|
187
|
+
r_code = await git_checkout(localPath, version);
|
|
188
|
+
} else {
|
|
189
|
+
console.log(
|
|
190
|
+
`WARN381: Warning, the git-repo ${localPath} already exist but with an unexpected remote! git-clone/pull aborted!`
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
} else {
|
|
194
|
+
console.log(
|
|
195
|
+
`WARN869: Warning, the directory ${localPath} already exist but is not a git-repo! git-clone aborted!`
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
} else {
|
|
199
|
+
console.log(
|
|
200
|
+
`WARN537: Warning, the path ${localPath} already exist and is a file! git-clone aborted!`
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
} catch (error) {
|
|
205
|
+
console.log(`ERR162: Error by cloning ${localPath} from ${remote_url}`);
|
|
206
|
+
console.error(error);
|
|
207
|
+
}
|
|
208
|
+
return r_code;
|
|
209
|
+
}
|
|
210
|
+
async function git_checkout(repoPath, version) {
|
|
211
|
+
let r_code = -2;
|
|
212
|
+
try {
|
|
213
|
+
const git = simpleGit(repoPath);
|
|
214
|
+
const gitlog = await git.checkout(version);
|
|
215
|
+
console.log(gitlog);
|
|
216
|
+
r_code = 0;
|
|
217
|
+
} catch (error) {
|
|
218
|
+
console.log(`ERR523: Error by checkout ${repoPath} for version ${version}`);
|
|
219
|
+
console.error(error);
|
|
220
|
+
}
|
|
221
|
+
return r_code;
|
|
222
|
+
}
|
|
223
|
+
async function git_verify(repoPath, remote_url, version) {
|
|
224
|
+
let r_code = 0;
|
|
225
|
+
const one_info = await one_repo_info(repoPath);
|
|
226
|
+
if (one_info.url === remote_url) {
|
|
227
|
+
console.log(`remote_url: Ok`);
|
|
228
|
+
} else {
|
|
229
|
+
console.log(`remote_url: Nok ${remote_url} versus ${one_info.url}`);
|
|
230
|
+
r_code = -1;
|
|
231
|
+
}
|
|
232
|
+
if (one_info.branch === version || one_info.commit === version) {
|
|
233
|
+
console.log(`version: Ok`);
|
|
234
|
+
} else {
|
|
235
|
+
console.log(
|
|
236
|
+
`version: Nok ${version} versus ${one_info.branch} versus ${one_info.commit}`
|
|
237
|
+
);
|
|
238
|
+
r_code = -1;
|
|
239
|
+
}
|
|
240
|
+
return r_code;
|
|
241
|
+
}
|
|
242
|
+
async function git_custom(repoPath, gitCommand) {
|
|
243
|
+
let r_code = -1;
|
|
244
|
+
try {
|
|
245
|
+
const git = simpleGit(repoPath);
|
|
246
|
+
const gitCommand2 = gitCommand.split(" ");
|
|
247
|
+
const gitlog = await git.raw(...gitCommand2);
|
|
248
|
+
console.log(gitlog);
|
|
249
|
+
r_code = 0;
|
|
250
|
+
} catch (error) {
|
|
251
|
+
console.log(`ERR772: Error by git-command ${gitCommand} on repo ${repoPath}`);
|
|
252
|
+
console.error(error);
|
|
253
|
+
}
|
|
254
|
+
return r_code;
|
|
255
|
+
}
|
|
256
|
+
async function one_repo_info(localPath) {
|
|
257
|
+
let one_info = {
|
|
258
|
+
localPath: "undefined",
|
|
259
|
+
url: "undefined",
|
|
260
|
+
branch: "undefined",
|
|
261
|
+
commit: "undefined"
|
|
262
|
+
};
|
|
263
|
+
const localPath2 = localPath.replace(regex_pointSlash, "");
|
|
264
|
+
try {
|
|
265
|
+
const git = simpleGit(localPath);
|
|
266
|
+
const remote = await git.getRemotes(true);
|
|
267
|
+
const remote_url = remote[0].refs.fetch;
|
|
268
|
+
const branch = await git.branch();
|
|
269
|
+
const branch_current = branch.current;
|
|
270
|
+
const commit = await git.log();
|
|
271
|
+
const commit_hash = commit.latest.hash;
|
|
272
|
+
one_info = {
|
|
273
|
+
localPath: localPath2,
|
|
274
|
+
url: remote_url,
|
|
275
|
+
branch: branch_current,
|
|
276
|
+
commit: commit_hash
|
|
277
|
+
};
|
|
278
|
+
} catch (error) {
|
|
279
|
+
console.log(`ERR398: Error by git-operations on repo ${localPath}`);
|
|
280
|
+
console.error(error);
|
|
281
|
+
}
|
|
282
|
+
return one_info;
|
|
283
|
+
}
|
|
284
|
+
async function get_repos_info(repos) {
|
|
285
|
+
const repos_info = [];
|
|
286
|
+
for (const [idx, localPath] of repos.entries()) {
|
|
287
|
+
console.log(`===> ${idx + 1} - get info of git-repo ${localPath}`);
|
|
288
|
+
const one_info = await one_repo_info(localPath);
|
|
289
|
+
repos_info.push(one_info);
|
|
290
|
+
}
|
|
291
|
+
return repos_info;
|
|
292
|
+
}
|
|
293
|
+
async function validate_yaml_external(yamlPath) {
|
|
294
|
+
let fyaml;
|
|
295
|
+
try {
|
|
296
|
+
const fstr = await fse.readFile(yamlPath, "utf-8");
|
|
297
|
+
fyaml = YAML.parse(fstr);
|
|
298
|
+
} catch (error) {
|
|
299
|
+
console.log(`ERR439: Error by reading the yaml-file ${yamlPath}!`);
|
|
300
|
+
console.error(error);
|
|
301
|
+
return -1;
|
|
302
|
+
}
|
|
303
|
+
try {
|
|
304
|
+
if (!Object.hasOwn(fyaml, "repositories"))
|
|
305
|
+
throw 'The property "repositories" is missing!';
|
|
306
|
+
for (const repo in fyaml.repositories) {
|
|
307
|
+
if (!Object.hasOwn(fyaml.repositories[repo], "url"))
|
|
308
|
+
throw `The property "url" is missing for repo ${repo} !`;
|
|
309
|
+
if (!Object.hasOwn(fyaml.repositories[repo], "version"))
|
|
310
|
+
throw `The property "version" is missing for repo ${repo} !`;
|
|
311
|
+
if (!Object.hasOwn(fyaml.repositories[repo], "type")) {
|
|
312
|
+
console.log(`WARN390: Warning, the property "type" is missing for repo ${repo} !`);
|
|
313
|
+
} else if (fyaml.repositories[repo].type !== "git") {
|
|
314
|
+
console.log(
|
|
315
|
+
`WARN395: Warning, the property "type" of repo ${repo} is not git but ${fyaml.repositories[repo].type}!`
|
|
316
|
+
);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
console.log(`The yaml-file ${yamlPath} is valid!`);
|
|
320
|
+
return 0;
|
|
321
|
+
} catch (error) {
|
|
322
|
+
console.error(error);
|
|
323
|
+
console.log(`Invalid yaml-file ${yamlPath}!`);
|
|
324
|
+
return -2;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
function isPathAbsolute(path2) {
|
|
328
|
+
let r_absolute = false;
|
|
329
|
+
const regex_slash = /^\//;
|
|
330
|
+
if (regex_slash.test(path2)) {
|
|
331
|
+
r_absolute = true;
|
|
332
|
+
}
|
|
333
|
+
return r_absolute;
|
|
334
|
+
}
|
|
335
|
+
var Vag = class {
|
|
336
|
+
/** The top directory from where to search repositories. */
|
|
337
|
+
discoverDir;
|
|
338
|
+
/** When a repository is encountered, should it be inspected for searching sub-repositories? */
|
|
339
|
+
deepSearch;
|
|
340
|
+
/** The path to the yaml-file that provides the repositories to be cloned. */
|
|
341
|
+
importYaml;
|
|
342
|
+
/** The base directory where the directories listed in the yaml-file should be cloned. */
|
|
343
|
+
importDir;
|
|
344
|
+
/** The list of the path of the discovered repositories. It is populated by the `init()` method. */
|
|
345
|
+
listD;
|
|
346
|
+
/** An object with the content of the yaml-file. It is populated by the `init()` method. */
|
|
347
|
+
listC;
|
|
348
|
+
/**
|
|
349
|
+
* The constructor of the `Vag` class.
|
|
350
|
+
* `listD` is initialized to an empty list.
|
|
351
|
+
* `listC` is initialized to an empty object.
|
|
352
|
+
*/
|
|
353
|
+
constructor(discoverDir = ".", deepSearch = true, importYaml = "", importDir = "") {
|
|
354
|
+
this.discoverDir = discoverDir;
|
|
355
|
+
this.deepSearch = deepSearch;
|
|
356
|
+
this.importYaml = importYaml;
|
|
357
|
+
this.importDir = importDir;
|
|
358
|
+
this.listD = [];
|
|
359
|
+
this.listC = {};
|
|
360
|
+
}
|
|
361
|
+
// this init function cannot be included in the constructor because the constructor can not be async
|
|
362
|
+
async discover_repos(discoverDir = this.discoverDir, deepSearch = this.deepSearch) {
|
|
363
|
+
this.discoverDir = discoverDir;
|
|
364
|
+
this.deepSearch = deepSearch;
|
|
365
|
+
if (this.discoverDir === "") {
|
|
366
|
+
console.log(`ERR282: Error, the discoverDir cannot be an empty string`);
|
|
367
|
+
return -1;
|
|
368
|
+
}
|
|
369
|
+
if (!isPathAbsolute(this.discoverDir)) {
|
|
370
|
+
if (!regex_pointSlash.test(this.discoverDir) && this.discoverDir !== ".") {
|
|
371
|
+
this.discoverDir = "./" + this.discoverDir;
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
this.discoverDir = this.discoverDir.replace(regex_trailingSlash, "");
|
|
375
|
+
try {
|
|
376
|
+
await fse.readdir(this.discoverDir, { withFileTypes: true });
|
|
377
|
+
} catch (err) {
|
|
378
|
+
console.log(`ERR638: Error, the path ${this.discoverDir} doesn't exist!`);
|
|
379
|
+
console.log(err);
|
|
380
|
+
return -1;
|
|
381
|
+
}
|
|
382
|
+
this.listD = await searchGitRepo(this.discoverDir, this.deepSearch);
|
|
383
|
+
console.log(`Number of discovered cloned git repos: ${this.listD.length}`);
|
|
384
|
+
return 0;
|
|
385
|
+
}
|
|
386
|
+
async import_yaml(importYaml = this.importYaml, importDir = this.importDir) {
|
|
387
|
+
let r_code = 0;
|
|
388
|
+
this.importYaml = importYaml;
|
|
389
|
+
this.importDir = importDir;
|
|
390
|
+
if (this.importYaml !== "") {
|
|
391
|
+
let baseDir = path.dirname(this.importYaml);
|
|
392
|
+
if (this.importDir !== "") {
|
|
393
|
+
baseDir = this.importDir;
|
|
394
|
+
}
|
|
395
|
+
if (["", "."].includes(baseDir)) {
|
|
396
|
+
baseDir = "";
|
|
397
|
+
} else {
|
|
398
|
+
if (!regex_trailingSlash.test(baseDir)) {
|
|
399
|
+
baseDir = baseDir + "/";
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
const list_non_git = [];
|
|
403
|
+
try {
|
|
404
|
+
const fstr = await fse.readFile(this.importYaml, "utf-8");
|
|
405
|
+
const fyaml = YAML.parse(fstr);
|
|
406
|
+
for (const repoDir in fyaml.repositories) {
|
|
407
|
+
let repoDir2 = repoDir;
|
|
408
|
+
if (!isPathAbsolute(repoDir2)) {
|
|
409
|
+
repoDir2 = baseDir + repoDir;
|
|
410
|
+
if (!regex_pointSlash.test(repoDir2)) {
|
|
411
|
+
repoDir2 = "./" + repoDir2;
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
if (!Object.hasOwn(fyaml.repositories[repoDir], "type") || fyaml.repositories[repoDir].type === "git") {
|
|
415
|
+
this.listC[repoDir2] = {
|
|
416
|
+
url: fyaml.repositories[repoDir].url,
|
|
417
|
+
version: fyaml.repositories[repoDir].version
|
|
418
|
+
};
|
|
419
|
+
} else {
|
|
420
|
+
list_non_git.push(repoDir2);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
} catch (error) {
|
|
424
|
+
console.log(
|
|
425
|
+
`ERR826: Error, the imported-yaml-file ${this.importYaml} is not valid!`
|
|
426
|
+
);
|
|
427
|
+
console.error(error);
|
|
428
|
+
r_code = -1;
|
|
429
|
+
}
|
|
430
|
+
console.log(
|
|
431
|
+
`From imported Yaml, number of git-repos: ${Object.keys(this.listC).length}`
|
|
432
|
+
);
|
|
433
|
+
console.log(`From imported Yaml, number of excluded repos: ${list_non_git.length}`);
|
|
434
|
+
for (const [idx, repoDir] of list_non_git.entries()) {
|
|
435
|
+
console.log(
|
|
436
|
+
` ${(idx + 1).toString().padStart(3, " ")} - Excluded repo: ${repoDir}`
|
|
437
|
+
);
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
return r_code;
|
|
441
|
+
}
|
|
442
|
+
/**
|
|
443
|
+
* The `init` method populate populate `listD` and `listC`.
|
|
444
|
+
* It must be called just after instanciating `Vag`.
|
|
445
|
+
* The configuration properties `discoverDir`, `deepSearch`, `importYaml` and `importDir` can be reassinged by `init`.
|
|
446
|
+
*/
|
|
447
|
+
async init(discoverDir = this.discoverDir, deepSearch = this.deepSearch, importYaml = this.importYaml, importDir = this.importDir) {
|
|
448
|
+
let r_code = 0;
|
|
449
|
+
r_code += await this.discover_repos(discoverDir, deepSearch);
|
|
450
|
+
r_code += await this.import_yaml(importYaml, importDir);
|
|
451
|
+
return r_code;
|
|
452
|
+
}
|
|
453
|
+
/** List the discovered repositories. */
|
|
454
|
+
d_list() {
|
|
455
|
+
return this.listD;
|
|
456
|
+
}
|
|
457
|
+
/** List the wished repositories (a.k.a. configured repositories). */
|
|
458
|
+
c_list() {
|
|
459
|
+
return Object.keys(this.listC);
|
|
460
|
+
}
|
|
461
|
+
/** List the git-repos which are in the D-list and in the C-list. I.e. Intersection of D and C. */
|
|
462
|
+
cd_list() {
|
|
463
|
+
return array_intersection(this.listD, Object.keys(this.listC));
|
|
464
|
+
}
|
|
465
|
+
/** List the git-repos which are in the D-list but not in the C-list. I.e. D not C. */
|
|
466
|
+
dnc_list() {
|
|
467
|
+
return array_exclude(this.listD, Object.keys(this.listC));
|
|
468
|
+
}
|
|
469
|
+
/** List the git-repos which are in the C-list but not in the D-list. I.e. C not D. */
|
|
470
|
+
cnd_list() {
|
|
471
|
+
return array_exclude(Object.keys(this.listC), this.listD);
|
|
472
|
+
}
|
|
473
|
+
/** Clone the repositories of `listC`. */
|
|
474
|
+
async c_clone() {
|
|
475
|
+
let r_code = 0;
|
|
476
|
+
for (const [idx, localPath] of Object.keys(this.listC).entries()) {
|
|
477
|
+
const repo = this.listC[localPath];
|
|
478
|
+
console.log(
|
|
479
|
+
`===> ${idx + 1} - clone ${localPath} from ${repo.url} at version ${repo.version}`
|
|
480
|
+
);
|
|
481
|
+
r_code += await git_clone(localPath, repo.url, repo.version);
|
|
482
|
+
}
|
|
483
|
+
return r_code;
|
|
484
|
+
}
|
|
485
|
+
/** Checkout the repositories listed in `listC` and `listD`. */
|
|
486
|
+
async cd_checkout() {
|
|
487
|
+
let r_code = 0;
|
|
488
|
+
const list_cd = this.cd_list();
|
|
489
|
+
for (const [idx, localPath] of list_cd.entries()) {
|
|
490
|
+
const repo = this.listC[localPath];
|
|
491
|
+
console.log(`===> ${idx + 1} - checkout ${localPath} at version ${repo.version}`);
|
|
492
|
+
r_code += await git_checkout(localPath, repo.version);
|
|
493
|
+
}
|
|
494
|
+
return r_code;
|
|
495
|
+
}
|
|
496
|
+
/** For the repositories listed in `listC` and `listD`, verify if they fit with the configuration of Yaml-file. */
|
|
497
|
+
async cd_verify() {
|
|
498
|
+
let r_code = 0;
|
|
499
|
+
const list_cd = this.cd_list();
|
|
500
|
+
for (const [idx, localPath] of list_cd.entries()) {
|
|
501
|
+
const repo = this.listC[localPath];
|
|
502
|
+
console.log(`===> ${idx + 1} - verify ${localPath}`);
|
|
503
|
+
r_code += await git_verify(localPath, repo.url, repo.version);
|
|
504
|
+
}
|
|
505
|
+
const all_nb = list_cd.length;
|
|
506
|
+
const nok_nb = Math.abs(r_code);
|
|
507
|
+
const ok_nb = all_nb - nok_nb;
|
|
508
|
+
console.log(`Verify ${all_nb} repos : ${ok_nb} Ok, ${nok_nb} Nok`);
|
|
509
|
+
return r_code;
|
|
510
|
+
}
|
|
511
|
+
/** Run a custom git-command on the discoverd repositories (`listD`). */
|
|
512
|
+
async d_custom(git_command, only_configured_repo = false) {
|
|
513
|
+
let r_code = 0;
|
|
514
|
+
let repos = this.d_list();
|
|
515
|
+
if (only_configured_repo) {
|
|
516
|
+
repos = this.cd_list();
|
|
517
|
+
}
|
|
518
|
+
for (const [idx, localPath] of repos.entries()) {
|
|
519
|
+
console.log(
|
|
520
|
+
`===> ${idx + 1} - On git-repo ${localPath} with command git ${git_command}`
|
|
521
|
+
);
|
|
522
|
+
r_code += await git_custom(localPath, git_command);
|
|
523
|
+
}
|
|
524
|
+
return r_code;
|
|
525
|
+
}
|
|
526
|
+
/** Git-fetch on the discoverd repositories (`listD`). */
|
|
527
|
+
async d_fetch(only_configured_repo = false) {
|
|
528
|
+
return await this.d_custom("fetch --prune", only_configured_repo);
|
|
529
|
+
}
|
|
530
|
+
/** Git-pull on the discoverd repositories (`listD`). */
|
|
531
|
+
async d_pull(only_configured_repo = false) {
|
|
532
|
+
return await this.d_custom("pull", only_configured_repo);
|
|
533
|
+
}
|
|
534
|
+
/** Git-push on the discoverd repositories (`listD`). */
|
|
535
|
+
async d_push(only_configured_repo = false) {
|
|
536
|
+
return await this.d_custom("push", only_configured_repo);
|
|
537
|
+
}
|
|
538
|
+
/** Show the current branch of the discoverd repositories (`listD`). */
|
|
539
|
+
async d_branch(only_configured_repo = false) {
|
|
540
|
+
return await this.d_custom("branch --show-current", only_configured_repo);
|
|
541
|
+
}
|
|
542
|
+
/** Git-status on the discoverd repositories (`listD`). */
|
|
543
|
+
async d_status(only_configured_repo = false) {
|
|
544
|
+
return await this.d_custom("status", only_configured_repo);
|
|
545
|
+
}
|
|
546
|
+
/** Git-diff on the discoverd repositories (`listD`). */
|
|
547
|
+
async d_diff(only_configured_repo = false) {
|
|
548
|
+
return await this.d_custom("diff", only_configured_repo);
|
|
549
|
+
}
|
|
550
|
+
/** `Git-log -n 3` on the discoverd repositories (`listD`). */
|
|
551
|
+
async d_log(only_configured_repo = false) {
|
|
552
|
+
return await this.d_custom("log -n 3", only_configured_repo);
|
|
553
|
+
}
|
|
554
|
+
/** `Git-remote -vv` on the discoverd repositories (`listD`). */
|
|
555
|
+
async d_remote(only_configured_repo = false) {
|
|
556
|
+
return await this.d_custom("remote -vv", only_configured_repo);
|
|
557
|
+
}
|
|
558
|
+
/** `Git-stash list` on the discoverd repositories (`listD`). */
|
|
559
|
+
async d_stash_list(only_configured_repo = false) {
|
|
560
|
+
return await this.d_custom("stash list", only_configured_repo);
|
|
561
|
+
}
|
|
562
|
+
/** `Git-clean -dxf` on the discoverd repositories (`listD`). */
|
|
563
|
+
async d_clean(only_configured_repo = false) {
|
|
564
|
+
return await this.d_custom("clean -dxf", only_configured_repo);
|
|
565
|
+
}
|
|
566
|
+
/** Export in a Yaml-file the list of discoverd repositories (`listD`). */
|
|
567
|
+
async d_export_yaml(yamlPath, commit_version = false) {
|
|
568
|
+
let r_code = -1;
|
|
569
|
+
if (yamlPath === "") {
|
|
570
|
+
console.log(`ERR482: Error, the discoverDir cannot be an empty string`);
|
|
571
|
+
return -1;
|
|
572
|
+
}
|
|
573
|
+
const repos = this.d_list();
|
|
574
|
+
const repos_info = await get_repos_info(repos);
|
|
575
|
+
const fyaml = { repositories: {} };
|
|
576
|
+
for (const repo of repos_info) {
|
|
577
|
+
let version = repo.branch;
|
|
578
|
+
if (commit_version) {
|
|
579
|
+
version = repo.commit;
|
|
580
|
+
}
|
|
581
|
+
fyaml.repositories[repo.localPath] = {
|
|
582
|
+
type: "git",
|
|
583
|
+
url: repo.url,
|
|
584
|
+
version
|
|
585
|
+
};
|
|
586
|
+
}
|
|
587
|
+
const fstr = YAML.stringify(fyaml);
|
|
588
|
+
try {
|
|
589
|
+
await fse.outputFile(yamlPath, fstr);
|
|
590
|
+
r_code = 0;
|
|
591
|
+
} catch (error) {
|
|
592
|
+
console.log(`ERR218: Error by writting the yaml-file ${yamlPath}!`);
|
|
593
|
+
console.error(error);
|
|
594
|
+
}
|
|
595
|
+
console.log(`The yaml-file ${yamlPath} has been written!`);
|
|
596
|
+
return r_code;
|
|
597
|
+
}
|
|
598
|
+
/**
|
|
599
|
+
* Validate a yaml-file that could be imported later on.
|
|
600
|
+
*
|
|
601
|
+
* @param yamlPath the path to the yaml-file to be checked/validated.
|
|
602
|
+
* @returns an integer-code. 0 if the validation is successful, negative otherwise.
|
|
603
|
+
*/
|
|
604
|
+
async validate_yaml(yamlPath) {
|
|
605
|
+
if (yamlPath === "") {
|
|
606
|
+
console.log(`ERR482: Error, the discoverDir cannot be an empty string`);
|
|
607
|
+
return -1;
|
|
608
|
+
}
|
|
609
|
+
return await validate_yaml_external(yamlPath);
|
|
610
|
+
}
|
|
611
|
+
/**
|
|
612
|
+
* Return a string with the three numbers (major, minor, patch) written in the package.json.
|
|
613
|
+
*
|
|
614
|
+
* @returns the string Major.Minor.Patch
|
|
615
|
+
*/
|
|
616
|
+
static version_short() {
|
|
617
|
+
return vag_version_short;
|
|
618
|
+
}
|
|
619
|
+
};
|
|
620
|
+
export {
|
|
621
|
+
Vag
|
|
622
|
+
};
|
|
623
|
+
//# sourceMappingURL=vag_core.js.map
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vag_tools",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.5",
|
|
4
4
|
"description": "api and cli for managing sub-git-repositories",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"main": "./dist/
|
|
7
|
-
"types": "./dist/
|
|
6
|
+
"main": "./dist/vag_core.js",
|
|
7
|
+
"types": "./dist/vag_core.d.ts",
|
|
8
8
|
"bin": {
|
|
9
9
|
"vag": "dist/vag_cli.js",
|
|
10
10
|
"vagg": "dist/vagg_server.js"
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
},
|
|
21
21
|
"tsup": {
|
|
22
22
|
"entry": [
|
|
23
|
+
"src/vag_core.ts",
|
|
23
24
|
"src/vag_cli.ts",
|
|
24
25
|
"src/vagg_server.ts"
|
|
25
26
|
],
|