submodule-version 2.4.1 → 3.0.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.
- package/README.md +98 -20
- package/dist/runner/handleErrors/handleErrors.d.ts +1 -1
- package/dist/runner/handleErrors/handleErrors.js +3 -10
- package/dist/runner/handleErrors/handleErrors.js.map +1 -1
- package/dist/runner/index.js +0 -0
- package/dist/runner/runner.d.ts +4 -0
- package/dist/runner/runner.js +91 -37
- package/dist/runner/runner.js.map +1 -1
- package/dist/src/PubGrub/PubGrub.d.ts +28 -0
- package/dist/src/PubGrub/PubGrub.js +366 -0
- package/dist/src/PubGrub/PubGrub.js.map +1 -0
- package/dist/src/PubGrub/index.d.ts +2 -0
- package/dist/src/PubGrub/index.js +7 -0
- package/dist/src/PubGrub/index.js.map +1 -0
- package/dist/src/PubGrub/types.d.ts +56 -0
- package/dist/src/PubGrub/types.js +3 -0
- package/dist/src/PubGrub/types.js.map +1 -0
- package/dist/src/entryStore.d.ts +16 -0
- package/dist/src/entryStore.js +152 -0
- package/dist/src/entryStore.js.map +1 -0
- package/dist/src/errors.d.ts +28 -0
- package/dist/src/errors.js +30 -0
- package/dist/src/errors.js.map +1 -0
- package/dist/src/git/api/GithubError.d.ts +4 -0
- package/dist/src/git/api/GithubError.js +11 -0
- package/dist/src/git/api/GithubError.js.map +1 -0
- package/dist/src/git/api/api.d.ts +8 -0
- package/dist/src/git/api/api.js +133 -0
- package/dist/src/git/api/api.js.map +1 -0
- package/dist/src/git/api/index.d.ts +3 -0
- package/dist/src/git/api/index.js +8 -0
- package/dist/src/git/api/index.js.map +1 -0
- package/dist/src/git/git.d.ts +30 -23
- package/dist/src/git/git.js +8 -258
- package/dist/src/git/git.js.map +1 -1
- package/dist/src/git/index.d.ts +1 -0
- package/dist/src/git/index.js +3 -1
- package/dist/src/git/index.js.map +1 -1
- package/dist/src/git/local/index.d.ts +1 -0
- package/dist/src/git/local/index.js +6 -0
- package/dist/src/git/local/index.js.map +1 -0
- package/dist/src/git/local/local.d.ts +26 -0
- package/dist/src/git/local/local.js +288 -0
- package/dist/src/git/local/local.js.map +1 -0
- package/dist/src/index.d.ts +7 -0
- package/dist/src/index.js +16 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/printError.d.ts +1 -0
- package/dist/src/printError.js +158 -0
- package/dist/src/printError.js.map +1 -0
- package/dist/src/sv.d.ts +16 -8
- package/dist/src/sv.js +222 -143
- package/dist/src/sv.js.map +1 -1
- package/dist/src/versionUtil/index.d.ts +2 -0
- package/dist/src/versionUtil/index.js +6 -0
- package/dist/src/versionUtil/index.js.map +1 -0
- package/dist/src/versionUtil/versionUtil.d.ts +16 -0
- package/dist/src/versionUtil/versionUtil.js +70 -0
- package/dist/src/versionUtil/versionUtil.js.map +1 -0
- package/package.json +4 -2
- package/runner/handleErrors/handleErrors.ts +3 -10
- package/runner/runner.ts +128 -46
- package/src/PubGrub/PubGrub.ts +578 -0
- package/src/PubGrub/__tests__/PubGrub.test.ts +351 -0
- package/src/PubGrub/index.ts +12 -0
- package/src/PubGrub/types.ts +72 -0
- package/src/__tests__/entryStore.test.ts +248 -0
- package/src/__tests__/printError.test.ts +148 -0
- package/src/__tests__/sv.test.ts +441 -0
- package/src/entryStore.ts +212 -0
- package/src/errors.ts +39 -0
- package/src/git/api/GithubError.ts +5 -0
- package/src/git/api/api.ts +180 -0
- package/src/git/api/index.ts +3 -0
- package/src/git/git.ts +8 -396
- package/src/git/index.ts +1 -0
- package/src/git/local/index.ts +1 -0
- package/src/git/local/local.ts +430 -0
- package/src/index.ts +17 -0
- package/src/printError.ts +189 -0
- package/src/sv.ts +304 -146
- package/src/versionUtil/__tests__/versionUtil.test.ts +62 -0
- package/src/versionUtil/index.ts +2 -0
- package/src/versionUtil/versionUtil.ts +103 -0
- package/tsconfig.json +1 -1
- package/dist/runner/handleErrors/handleBuildGraphError.d.ts +0 -1
- package/dist/runner/handleErrors/handleBuildGraphError.js +0 -42
- package/dist/runner/handleErrors/handleBuildGraphError.js.map +0 -1
- package/dist/runner/handleErrors/handleGitError.d.ts +0 -1
- package/dist/runner/handleErrors/handleGitError.js +0 -27
- package/dist/runner/handleErrors/handleGitError.js.map +0 -1
- package/dist/runner/handleErrors/handleInstallError.d.ts +0 -1
- package/dist/runner/handleErrors/handleInstallError.js +0 -30
- package/dist/runner/handleErrors/handleInstallError.js.map +0 -1
- package/dist/src/__tests__/versionUtil.test.d.ts +0 -1
- package/dist/src/__tests__/versionUtil.test.js +0 -80
- package/dist/src/__tests__/versionUtil.test.js.map +0 -1
- package/dist/src/buildGraph/GraphError.d.ts +0 -11
- package/dist/src/buildGraph/GraphError.js +0 -11
- package/dist/src/buildGraph/GraphError.js.map +0 -1
- package/dist/src/buildGraph/buildGraph.d.ts +0 -8
- package/dist/src/buildGraph/buildGraph.js +0 -100
- package/dist/src/buildGraph/buildGraph.js.map +0 -1
- package/dist/src/buildGraph/index.d.ts +0 -2
- package/dist/src/buildGraph/index.js +0 -8
- package/dist/src/buildGraph/index.js.map +0 -1
- package/dist/src/git/__tests__/parseGitUrl.test.d.ts +0 -1
- package/dist/src/git/__tests__/parseGitUrl.test.js +0 -30
- package/dist/src/git/__tests__/parseGitUrl.test.js.map +0 -1
- package/dist/src/versionUtil.d.ts +0 -13
- package/dist/src/versionUtil.js +0 -79
- package/dist/src/versionUtil.js.map +0 -1
- package/runner/handleErrors/handleBuildGraphError.ts +0 -49
- package/runner/handleErrors/handleGitError.ts +0 -28
- package/runner/handleErrors/handleInstallError.ts +0 -26
- package/src/__tests__/versionUtil.test.ts +0 -96
- package/src/buildGraph/GraphError.ts +0 -12
- package/src/buildGraph/buildGraph.ts +0 -150
- package/src/buildGraph/index.ts +0 -2
- package/src/versionUtil.ts +0 -97
package/src/sv.ts
CHANGED
|
@@ -1,153 +1,333 @@
|
|
|
1
1
|
import path from 'path';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
|
|
2
|
+
import { readFile } from 'fs/promises';
|
|
3
|
+
import { pkgJSONManager } from './pkgJSONManager';
|
|
4
|
+
import {
|
|
5
|
+
PubGrub,
|
|
6
|
+
IOverride,
|
|
7
|
+
TDependencies,
|
|
8
|
+
TPubGrubResult,
|
|
9
|
+
} from './PubGrub';
|
|
10
|
+
import { EntryStore } from './entryStore';
|
|
11
|
+
import { RunOptions } from './runOptions';
|
|
12
|
+
import { versionUtil } from './versionUtil';
|
|
13
|
+
import { git, GitError } from './git';
|
|
14
|
+
import { SVError } from './errors';
|
|
15
|
+
|
|
16
|
+
/*
|
|
17
|
+
* Правка package.json родителя (внутри его сабмодуля): parent-level
|
|
18
|
+
* мутации обязаны пережить публикацию родителя, поэтому диапазон
|
|
19
|
+
* пишется в его поле sv.
|
|
20
|
+
*/
|
|
21
|
+
const updateModuleJson = async (
|
|
22
|
+
parent: string,
|
|
23
|
+
override: IOverride,
|
|
24
|
+
): Promise<void> => {
|
|
25
|
+
const parentJson = await pkgJSONManager.read(parent);
|
|
26
|
+
|
|
27
|
+
if (!parentJson) throw new SVError('ADDON_NOT_FOUND', { name: parent });
|
|
28
|
+
|
|
29
|
+
const sv = { ...((parentJson.sv || {}) as TDependencies) };
|
|
30
|
+
|
|
31
|
+
if (override.add) sv[override.add] = override.version || '*';
|
|
32
|
+
|
|
33
|
+
if (override.delete) {
|
|
34
|
+
const key = Object.keys(sv)
|
|
35
|
+
.find(depUrl => git.parseUrl(depUrl).name === override.delete);
|
|
36
|
+
|
|
37
|
+
if (!key) throw new SVError('ADDON_NOT_FOUND', { name: override.delete });
|
|
38
|
+
|
|
39
|
+
delete sv[key];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
parentJson.sv = sv;
|
|
43
|
+
await pkgJSONManager.write(parentJson, parent);
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
/*
|
|
47
|
+
* modulesDir должен быть npm-workspace, иначе сабмодули не попадут
|
|
48
|
+
* в node_modules. Поддерживаем оба формата: workspaces: [] и
|
|
49
|
+
* workspaces: { packages: [] }. Возвращает true, если json изменён.
|
|
50
|
+
*/
|
|
51
|
+
const ensureWorkspaces = (baseJson: any): boolean => {
|
|
52
|
+
const entry = `${RunOptions.modulesDir}/*`;
|
|
53
|
+
|
|
54
|
+
if (Array.isArray(baseJson.workspaces)) {
|
|
55
|
+
if (baseJson.workspaces.includes(entry)) return false;
|
|
56
|
+
|
|
57
|
+
baseJson.workspaces.push(entry);
|
|
58
|
+
|
|
59
|
+
return true;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const packages = baseJson.workspaces?.packages;
|
|
63
|
+
|
|
64
|
+
if (Array.isArray(packages)) {
|
|
65
|
+
if (packages.includes(entry)) return false;
|
|
66
|
+
|
|
67
|
+
packages.push(entry);
|
|
68
|
+
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
baseJson.workspaces = [entry];
|
|
73
|
+
|
|
74
|
+
return true;
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const MODULES_DIR_FIELD = 'sv-dir';
|
|
78
|
+
const DEFAULT_MODULES_DIR = 'modules';
|
|
79
|
+
|
|
80
|
+
/*
|
|
81
|
+
* Уже добавленные сабмодули — источник правды о директории:
|
|
82
|
+
* .gitmodules с единым родителем у всех path (addons/X, addons/Y → addons).
|
|
83
|
+
*/
|
|
84
|
+
const deriveModulesDir = async (): Promise<string | null> => {
|
|
85
|
+
try {
|
|
86
|
+
const content = await readFile(
|
|
87
|
+
path.join(RunOptions.cwd, '.gitmodules'),
|
|
88
|
+
'utf-8',
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
const dirs = new Set(
|
|
92
|
+
[...content.matchAll(/^\s*path\s*=\s*(.+)$/gm)]
|
|
93
|
+
.map(match => match[1].trim().split('/')[0]),
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
return dirs.size === 1 ? [...dirs][0] : null;
|
|
97
|
+
} catch {
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
};
|
|
7
101
|
|
|
8
102
|
export class SV {
|
|
103
|
+
private resolution: TPubGrubResult | null = null;
|
|
104
|
+
|
|
105
|
+
private store = new EntryStore();
|
|
106
|
+
|
|
107
|
+
private explicitModulesDir?: string;
|
|
108
|
+
|
|
9
109
|
constructor (
|
|
10
110
|
projectDir: string,
|
|
11
|
-
modulesDir
|
|
111
|
+
modulesDir?: string,
|
|
112
|
+
options: { githubToken?: string } = {},
|
|
12
113
|
) {
|
|
13
114
|
RunOptions.cwd = projectDir;
|
|
14
|
-
RunOptions.modulesDir = modulesDir;
|
|
115
|
+
RunOptions.modulesDir = modulesDir || DEFAULT_MODULES_DIR;
|
|
116
|
+
this.explicitModulesDir = modulesDir;
|
|
117
|
+
git.api.setToken(options.githubToken);
|
|
15
118
|
}
|
|
16
119
|
|
|
17
|
-
|
|
120
|
+
/*
|
|
121
|
+
* Директория модулей: явно переданная (CLI/JS) → записанная в
|
|
122
|
+
* package.json#sv-dir → выведенная из .gitmodules → 'modules'.
|
|
123
|
+
* Эффективная записывается в sv-dir. Возвращает true, если json изменён.
|
|
124
|
+
*/
|
|
125
|
+
private syncModulesDir = async (baseJson: any): Promise<boolean> => {
|
|
126
|
+
const recorded = baseJson[MODULES_DIR_FIELD] as string | undefined;
|
|
18
127
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
128
|
+
if (!this.explicitModulesDir) {
|
|
129
|
+
if (recorded) {
|
|
130
|
+
RunOptions.modulesDir = recorded;
|
|
22
131
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
}
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
26
134
|
|
|
27
|
-
|
|
28
|
-
const installedPkg = await pkgJSONManager.read(name);
|
|
135
|
+
const derived = await deriveModulesDir();
|
|
29
136
|
|
|
30
|
-
|
|
31
|
-
await git.addSumbmodule(url);
|
|
137
|
+
if (derived) RunOptions.modulesDir = derived;
|
|
32
138
|
}
|
|
33
139
|
|
|
34
|
-
|
|
35
|
-
const latestVersion = versionUtil.latest(versions);
|
|
36
|
-
const requestedVersion = version || latestVersion;
|
|
140
|
+
if (recorded === RunOptions.modulesDir) return false;
|
|
37
141
|
|
|
38
|
-
|
|
39
|
-
throw new Error('REQUESTED_VERSION_NOT_EXISTS');
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
if (!targetPkg.sv) {
|
|
43
|
-
targetPkg.sv = {};
|
|
44
|
-
}
|
|
142
|
+
baseJson[MODULES_DIR_FIELD] = RunOptions.modulesDir;
|
|
45
143
|
|
|
46
|
-
|
|
47
|
-
await pkgJSONManager.write(targetPkg, parentModule);
|
|
48
|
-
await buildGraph();
|
|
144
|
+
return true;
|
|
49
145
|
};
|
|
50
146
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
147
|
+
private resolveDeps = (
|
|
148
|
+
rootDeps: TDependencies,
|
|
149
|
+
): Promise<TPubGrubResult> => new PubGrub(this.store).resolve(rootDeps);
|
|
150
|
+
|
|
151
|
+
/*
|
|
152
|
+
* Синк рабочей копии с резолвом: недостающие сабмодули добавляются,
|
|
153
|
+
* версии переключаются, исчезнувшие из резолва модули удаляются.
|
|
154
|
+
*/
|
|
155
|
+
private syncModules = async (resolution: TPubGrubResult): Promise<void> => {
|
|
156
|
+
const previous = this.resolution || {};
|
|
157
|
+
|
|
158
|
+
await Promise.all(Object.entries(resolution).map(async ([name, entry]) => {
|
|
159
|
+
const dir = path.join(RunOptions.modulesDir, name);
|
|
160
|
+
|
|
161
|
+
if (!await git.local.isGitRepo(dir)) {
|
|
162
|
+
await git.local.addSubmodule(entry.url);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (await git.local.currentVersion(name) !== entry.version) {
|
|
166
|
+
await git.local.checkout(name, entry.version);
|
|
60
167
|
}
|
|
61
168
|
}));
|
|
169
|
+
|
|
170
|
+
const removed = Object.keys(previous).filter(name => !resolution[name]);
|
|
171
|
+
|
|
172
|
+
await Promise.all(removed.map(name => git.local.rm(name)));
|
|
62
173
|
};
|
|
63
174
|
|
|
64
175
|
/*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
* последней имеет смысл передать `^<version>`, чтобы новые версии продолжали приходить.
|
|
176
|
+
* Общий путь мутаций: проверка через resolve внутри simulate, и только
|
|
177
|
+
* если ошибки нет — установка/переключение модулей и правка package.json
|
|
178
|
+
* (корня или родителя).
|
|
69
179
|
*/
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
180
|
+
private apply = async (
|
|
181
|
+
baseJson: any,
|
|
182
|
+
override: IOverride,
|
|
183
|
+
parent?: string,
|
|
184
|
+
): Promise<void> => {
|
|
185
|
+
const dirChanged = await this.syncModulesDir(baseJson);
|
|
186
|
+
const rootDeps = (baseJson.sv || {}) as TDependencies;
|
|
187
|
+
let nextRootDeps = rootDeps;
|
|
188
|
+
|
|
189
|
+
const resolution = await this.store.simulate(
|
|
190
|
+
[override],
|
|
191
|
+
rootDeps,
|
|
192
|
+
deps => {
|
|
193
|
+
nextRootDeps = deps;
|
|
194
|
+
|
|
195
|
+
return this.resolveDeps(deps);
|
|
196
|
+
},
|
|
197
|
+
);
|
|
198
|
+
|
|
199
|
+
await this.syncModules(resolution);
|
|
200
|
+
|
|
201
|
+
if (parent) {
|
|
202
|
+
await updateModuleJson(parent, override);
|
|
203
|
+
|
|
204
|
+
if (dirChanged) await pkgJSONManager.write(baseJson);
|
|
205
|
+
} else {
|
|
206
|
+
baseJson.sv = nextRootDeps;
|
|
207
|
+
await pkgJSONManager.write(baseJson);
|
|
76
208
|
}
|
|
77
209
|
|
|
78
|
-
|
|
79
|
-
|
|
210
|
+
this.resolution = resolution;
|
|
211
|
+
};
|
|
80
212
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
* же изменениями — сообщаем, что перенос нужно сделать вручную.
|
|
86
|
-
*/
|
|
87
|
-
try {
|
|
88
|
-
await git.checkout(name, version);
|
|
89
|
-
} catch (e) {
|
|
90
|
-
if (dirty) {
|
|
91
|
-
throw new GitError('GIT_DIRTY_SWITCH_CONFLICT');
|
|
92
|
-
}
|
|
213
|
+
public init = async (): Promise<void> => {
|
|
214
|
+
const baseJson = await pkgJSONManager.read();
|
|
215
|
+
|
|
216
|
+
if (!baseJson) throw new SVError('NOT_A_NPM');
|
|
93
217
|
|
|
94
|
-
|
|
218
|
+
if (!await git.local.isGitRepo(RunOptions.cwd)) {
|
|
219
|
+
throw new SVError('NOT_A_GIT_REPO');
|
|
95
220
|
}
|
|
96
221
|
|
|
97
|
-
const
|
|
98
|
-
if (!json?.sv) return;
|
|
222
|
+
const dirChanged = await this.syncModulesDir(baseJson);
|
|
99
223
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
if (key && json.sv[key] !== nextConstraint) {
|
|
103
|
-
json.sv[key] = nextConstraint;
|
|
104
|
-
await pkgJSONManager.write(json);
|
|
224
|
+
if (ensureWorkspaces(baseJson) || dirChanged) {
|
|
225
|
+
await pkgJSONManager.write(baseJson);
|
|
105
226
|
}
|
|
227
|
+
|
|
228
|
+
const rootDeps = (baseJson.sv || {}) as TDependencies;
|
|
229
|
+
const resolution = await this.resolveDeps(rootDeps);
|
|
230
|
+
|
|
231
|
+
await this.syncModules(resolution);
|
|
232
|
+
|
|
233
|
+
this.resolution = resolution;
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
public getResolution = (): TPubGrubResult => {
|
|
237
|
+
if (!this.resolution) throw new SVError('NOT_INITIALIZED');
|
|
238
|
+
|
|
239
|
+
return this.resolution;
|
|
106
240
|
};
|
|
107
241
|
|
|
108
242
|
/*
|
|
109
|
-
*
|
|
243
|
+
* Установка/смена версии модуля. Первый аргумент — url репозитория
|
|
244
|
+
* или имя уже установленного модуля. Без parentName — в корне проекта,
|
|
245
|
+
* без version — максимально возможная ('*'). Второй аргумент —
|
|
246
|
+
* версия, если это валидный semver-диапазон, иначе имя родителя.
|
|
110
247
|
*/
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
248
|
+
public put = async (
|
|
249
|
+
url: string,
|
|
250
|
+
versionOrParent?: string,
|
|
251
|
+
parentName?: string,
|
|
252
|
+
): Promise<void> => {
|
|
253
|
+
const isVersion = !!versionOrParent
|
|
254
|
+
&& versionUtil.validate(versionOrParent, true);
|
|
115
255
|
|
|
116
|
-
const
|
|
256
|
+
const range = (isVersion ? versionOrParent : undefined) || '*';
|
|
257
|
+
const parent = (isVersion ? parentName : versionOrParent) || undefined;
|
|
258
|
+
const baseJson = await pkgJSONManager.read();
|
|
117
259
|
|
|
118
|
-
|
|
119
|
-
};
|
|
260
|
+
if (!baseJson) throw new SVError('NOT_A_NPM');
|
|
120
261
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
262
|
+
/*
|
|
263
|
+
* Вместо url можно передать имя уже установленного модуля —
|
|
264
|
+
* url берём из текущего резолва.
|
|
265
|
+
*/
|
|
266
|
+
let moduleUrl = url;
|
|
267
|
+
|
|
268
|
+
if (!git.parseUrl(url).name) {
|
|
269
|
+
const existing = this.getResolution()[url];
|
|
125
270
|
|
|
126
|
-
|
|
271
|
+
if (!existing) throw new SVError('NOT_A_GIT_URL', { url });
|
|
127
272
|
|
|
128
|
-
|
|
129
|
-
json.sv[key] = constraint;
|
|
130
|
-
await pkgJSONManager.write(json);
|
|
273
|
+
moduleUrl = existing.url;
|
|
131
274
|
}
|
|
275
|
+
|
|
276
|
+
if (parent && !this.getResolution()[parent]) {
|
|
277
|
+
throw new SVError('ADDON_NOT_FOUND', { name: parent });
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
await this.apply(
|
|
281
|
+
baseJson,
|
|
282
|
+
{ parent, add: moduleUrl, version: range },
|
|
283
|
+
parent,
|
|
284
|
+
);
|
|
132
285
|
};
|
|
133
286
|
|
|
134
|
-
|
|
135
|
-
public
|
|
136
|
-
await
|
|
137
|
-
const json = await pkgJSONManager.read(parentModule);
|
|
287
|
+
/* Удаление модуля из родителя (без parentName — из корня проекта). */
|
|
288
|
+
public delete = async (name: string, parentName?: string): Promise<void> => {
|
|
289
|
+
const baseJson = await pkgJSONManager.read();
|
|
138
290
|
|
|
139
|
-
if (!
|
|
291
|
+
if (!baseJson) throw new SVError('NOT_A_NPM');
|
|
140
292
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
293
|
+
if (parentName) {
|
|
294
|
+
const parentEntry = this.getResolution()[parentName];
|
|
295
|
+
|
|
296
|
+
if (!parentEntry) {
|
|
297
|
+
throw new SVError('ADDON_NOT_FOUND', { name: parentName });
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
if (!(name in parentEntry.dependencies)) {
|
|
301
|
+
throw new SVError('ADDON_NOT_FOUND', { name });
|
|
144
302
|
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
await this.apply(
|
|
306
|
+
baseJson,
|
|
307
|
+
{ parent: parentName, delete: name },
|
|
308
|
+
parentName,
|
|
309
|
+
);
|
|
310
|
+
};
|
|
311
|
+
|
|
312
|
+
public listVersions = (
|
|
313
|
+
name?: string,
|
|
314
|
+
): Record<string, string[]> | string[] => {
|
|
315
|
+
const resolution = this.getResolution();
|
|
316
|
+
|
|
317
|
+
if (name) {
|
|
318
|
+
const entry = resolution[name];
|
|
145
319
|
|
|
146
|
-
|
|
147
|
-
|
|
320
|
+
if (!entry) throw new SVError('ADDON_NOT_FOUND', { name });
|
|
321
|
+
|
|
322
|
+
return versionUtil.sort(Object.keys(entry.versions));
|
|
323
|
+
}
|
|
148
324
|
|
|
149
|
-
|
|
150
|
-
|
|
325
|
+
return Object.fromEntries(
|
|
326
|
+
Object.entries(resolution).map(([entryName, entry]) => [
|
|
327
|
+
entryName,
|
|
328
|
+
versionUtil.sort(Object.keys(entry.versions)),
|
|
329
|
+
]),
|
|
330
|
+
);
|
|
151
331
|
};
|
|
152
332
|
|
|
153
333
|
// eslint-disable-next-line class-methods-use-this
|
|
@@ -156,11 +336,9 @@ export class SV {
|
|
|
156
336
|
? path.join(RunOptions.modulesDir, module)
|
|
157
337
|
: RunOptions.cwd;
|
|
158
338
|
|
|
159
|
-
|
|
160
|
-
* нет ли незапушенных коммитов (идёт fetch в remote) */
|
|
161
|
-
if (await git.hasChanges(dir)) return true;
|
|
339
|
+
if (await git.local.hasChanges(dir)) return true;
|
|
162
340
|
|
|
163
|
-
return git.hasUnpushedCommits(dir);
|
|
341
|
+
return git.local.hasUnpushedCommits(dir);
|
|
164
342
|
};
|
|
165
343
|
|
|
166
344
|
// eslint-disable-next-line class-methods-use-this
|
|
@@ -169,7 +347,7 @@ export class SV {
|
|
|
169
347
|
? path.join(RunOptions.modulesDir, module)
|
|
170
348
|
: RunOptions.cwd;
|
|
171
349
|
|
|
172
|
-
return git.hasChanges(dir);
|
|
350
|
+
return git.local.hasChanges(dir);
|
|
173
351
|
};
|
|
174
352
|
|
|
175
353
|
// eslint-disable-next-line class-methods-use-this
|
|
@@ -178,7 +356,7 @@ export class SV {
|
|
|
178
356
|
? path.join(RunOptions.modulesDir, module)
|
|
179
357
|
: RunOptions.cwd;
|
|
180
358
|
|
|
181
|
-
const tags = await git.tagsAtHead(dir);
|
|
359
|
+
const tags = await git.local.tagsAtHead(dir);
|
|
182
360
|
|
|
183
361
|
return versionUtil.latest(tags) || null;
|
|
184
362
|
};
|
|
@@ -189,7 +367,7 @@ export class SV {
|
|
|
189
367
|
? path.join(RunOptions.modulesDir, module)
|
|
190
368
|
: RunOptions.cwd;
|
|
191
369
|
|
|
192
|
-
const versions = await git.listTags(dir);
|
|
370
|
+
const versions = await git.local.listTags(dir);
|
|
193
371
|
|
|
194
372
|
return versionUtil.latest(versions) || '0.0.0';
|
|
195
373
|
};
|
|
@@ -200,7 +378,7 @@ export class SV {
|
|
|
200
378
|
? path.join(RunOptions.modulesDir, module)
|
|
201
379
|
: RunOptions.cwd;
|
|
202
380
|
|
|
203
|
-
return git.getRemote(dir);
|
|
381
|
+
return git.local.getRemote(dir);
|
|
204
382
|
};
|
|
205
383
|
|
|
206
384
|
public isPublished = async (module?: string): Promise<boolean> => {
|
|
@@ -208,38 +386,29 @@ export class SV {
|
|
|
208
386
|
? path.join(RunOptions.modulesDir, module)
|
|
209
387
|
: RunOptions.cwd;
|
|
210
388
|
|
|
211
|
-
if (!await git.isGitRepo(dir)) return false;
|
|
389
|
+
if (!await git.local.isGitRepo(dir)) return false;
|
|
212
390
|
|
|
213
391
|
return !!(await this.getRemote(module));
|
|
214
392
|
};
|
|
215
393
|
|
|
216
|
-
/*
|
|
217
|
-
* Remote-ветка впереди локальной (fetch + rev-list HEAD..@{u}).
|
|
218
|
-
* Нет репозитория/remote/upstream — обновлений нет.
|
|
219
|
-
*/
|
|
220
394
|
// eslint-disable-next-line class-methods-use-this
|
|
221
395
|
public isRemoteAhead = async (module?: string): Promise<boolean> => {
|
|
222
396
|
const dir = module
|
|
223
397
|
? path.join(RunOptions.modulesDir, module)
|
|
224
398
|
: RunOptions.cwd;
|
|
225
399
|
|
|
226
|
-
if (!await git.isGitRepo(dir)) return false;
|
|
400
|
+
if (!await git.local.isGitRepo(dir)) return false;
|
|
227
401
|
|
|
228
|
-
return git.isRemoteAhead(dir);
|
|
402
|
+
return git.local.isRemoteAhead(dir);
|
|
229
403
|
};
|
|
230
404
|
|
|
231
|
-
/*
|
|
232
|
-
* Догоняем remote: pull --rebase --autostash. При конфликте всё
|
|
233
|
-
* восстанавливается как было (rebase --abort + stash pop) и кидается
|
|
234
|
-
* GIT_SYNC_CONFLICT — дальше возможен только ручной ребейз.
|
|
235
|
-
*/
|
|
236
405
|
// eslint-disable-next-line class-methods-use-this
|
|
237
406
|
public pullRebaseAutostash = async (module?: string): Promise<void> => {
|
|
238
407
|
const dir = module
|
|
239
408
|
? path.join(RunOptions.modulesDir, module)
|
|
240
409
|
: RunOptions.cwd;
|
|
241
410
|
|
|
242
|
-
return git.pullRebaseAutostash(dir);
|
|
411
|
+
return git.local.pullRebaseAutostash(dir);
|
|
243
412
|
};
|
|
244
413
|
|
|
245
414
|
public publish = async (opts: {
|
|
@@ -249,24 +418,19 @@ export class SV {
|
|
|
249
418
|
bump: 'release' | 'minor' | 'major',
|
|
250
419
|
}): Promise<string> => {
|
|
251
420
|
const { module, repoUrl, message, bump } = opts;
|
|
421
|
+
|
|
252
422
|
const dir = module
|
|
253
423
|
? path.join(RunOptions.modulesDir, module)
|
|
254
424
|
: RunOptions.cwd;
|
|
255
425
|
|
|
256
426
|
if (!await this.isPublished(module)) {
|
|
257
|
-
if (!repoUrl)
|
|
258
|
-
throw new GitError('GIT_REPO_URL_REQUIRED');
|
|
259
|
-
}
|
|
427
|
+
if (!repoUrl) throw new GitError('GIT_REPO_URL_REQUIRED');
|
|
260
428
|
|
|
261
|
-
if (!await git.isGitRepo(dir))
|
|
262
|
-
await git.init(dir);
|
|
263
|
-
}
|
|
429
|
+
if (!await git.local.isGitRepo(dir)) await git.local.init(dir);
|
|
264
430
|
|
|
265
|
-
await git.addRemote(dir, repoUrl);
|
|
431
|
+
await git.local.addRemote(dir, repoUrl);
|
|
266
432
|
}
|
|
267
433
|
|
|
268
|
-
/* Публикация не последней версии запрещена: HEAD на теге, отличном
|
|
269
|
-
* от последнего, — обновлять такую базу можно только вручную */
|
|
270
434
|
const currentTag = await this.currentVersion(module);
|
|
271
435
|
const latestKnown = await this.latestVersion(module);
|
|
272
436
|
|
|
@@ -274,41 +438,35 @@ export class SV {
|
|
|
274
438
|
throw new GitError('GIT_NOT_LATEST_VERSION');
|
|
275
439
|
}
|
|
276
440
|
|
|
277
|
-
|
|
278
|
-
* иначе коммиты и push уйдут в detached HEAD */
|
|
279
|
-
await git.ensureBranch(dir);
|
|
441
|
+
await git.local.ensureBranch(dir);
|
|
280
442
|
|
|
281
|
-
if (await git.isRemoteAhead(dir)) {
|
|
282
|
-
await git.pullRebaseAutostash(dir);
|
|
443
|
+
if (await git.local.isRemoteAhead(dir)) {
|
|
444
|
+
await git.local.pullRebaseAutostash(dir);
|
|
283
445
|
}
|
|
284
446
|
|
|
285
|
-
const dirty = await git.hasChanges(dir);
|
|
447
|
+
const dirty = await git.local.hasChanges(dir);
|
|
286
448
|
const headTag = await this.currentVersion(module);
|
|
287
449
|
|
|
288
|
-
/* Дерево чистое и HEAD уже отмечен версионным тегом:
|
|
289
|
-
* коммитить и бампить нечего — просто доставляем ветку и тег на remote */
|
|
290
450
|
if (!dirty && headTag) {
|
|
291
|
-
await git.push(dir, headTag);
|
|
451
|
+
await git.local.push(dir, headTag);
|
|
292
452
|
|
|
293
453
|
return headTag;
|
|
294
454
|
}
|
|
295
455
|
|
|
296
456
|
const next = versionUtil.bump(await this.latestVersion(module), bump);
|
|
297
|
-
|
|
298
|
-
/* Фиксируем версию и в package.json, чтобы она не расходилась с git-тегом */
|
|
299
457
|
const pkg = await pkgJSONManager.read(module);
|
|
458
|
+
|
|
300
459
|
if (pkg) {
|
|
301
460
|
pkg.version = next;
|
|
302
461
|
await pkgJSONManager.write(pkg, module);
|
|
303
462
|
}
|
|
304
463
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
await git.commitAll(dir, message || 'Update');
|
|
464
|
+
if (await git.local.hasChanges(dir)) {
|
|
465
|
+
await git.local.commitAll(dir, message || 'Update');
|
|
308
466
|
}
|
|
309
467
|
|
|
310
|
-
await git.addTag(dir, next);
|
|
311
|
-
await git.push(dir, next);
|
|
468
|
+
await git.local.addTag(dir, next);
|
|
469
|
+
await git.local.push(dir, next);
|
|
312
470
|
|
|
313
471
|
return next;
|
|
314
472
|
};
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { versionUtil } from '../versionUtil';
|
|
2
|
+
|
|
3
|
+
describe('versionUtil: SemVer primitives', () => {
|
|
4
|
+
it('validates exact versions and ranges separately', () => {
|
|
5
|
+
expect(versionUtil.validate('1.2.3')).toBe(true);
|
|
6
|
+
expect(versionUtil.validate('1.2.3-beta.1')).toBe(true);
|
|
7
|
+
expect(versionUtil.validate('v1.2.3')).toBe(false);
|
|
8
|
+
expect(versionUtil.validate('^1.2.3')).toBe(false);
|
|
9
|
+
expect(versionUtil.validate('^1.2.3', true)).toBe(true);
|
|
10
|
+
expect(versionUtil.validate('~1.2.3', true)).toBe(true);
|
|
11
|
+
expect(versionUtil.validate('*', true)).toBe(true);
|
|
12
|
+
expect(versionUtil.validate('', true)).toBe(false);
|
|
13
|
+
expect(versionUtil.validate('not-a-range', true)).toBe(false);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it('uses standard caret semantics for zero versions', () => {
|
|
17
|
+
expect(versionUtil.satisfies('1.9.0', '^1.2.3')).toBe(true);
|
|
18
|
+
expect(versionUtil.satisfies('2.0.0', '^1.2.3')).toBe(false);
|
|
19
|
+
expect(versionUtil.satisfies('0.2.9', '^0.2.3')).toBe(true);
|
|
20
|
+
expect(versionUtil.satisfies('0.3.0', '^0.2.3')).toBe(false);
|
|
21
|
+
expect(versionUtil.satisfies('0.0.3', '^0.0.3')).toBe(true);
|
|
22
|
+
expect(versionUtil.satisfies('0.0.4', '^0.0.3')).toBe(false);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('selects the highest version satisfying every constraint', () => {
|
|
26
|
+
const versions = ['2.0.0', '1.4.2', '1.3.2', 'broken'];
|
|
27
|
+
|
|
28
|
+
expect(versionUtil.sort(versions)).toEqual(['2.0.0', '1.4.2', '1.3.2']);
|
|
29
|
+
expect(versionUtil.select(versions, ['^1.3.2', '^1.4.0']))
|
|
30
|
+
.toEqual(['1.4.2']);
|
|
31
|
+
expect(versionUtil.latest(versions, ['^1.0.0'])).toBe('1.4.2');
|
|
32
|
+
expect(versionUtil.compare('1.2.0', '1.1.9')).toBeGreaterThan(0);
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
describe('versionUtil: finite sets used by PubGrub', () => {
|
|
37
|
+
const one = ['1.0.0', '1.2.0'];
|
|
38
|
+
const two = ['1.2.0', '2.0.0'];
|
|
39
|
+
|
|
40
|
+
it('intersects, joins and subtracts version sets', () => {
|
|
41
|
+
expect(versionUtil.intersection(one, two)).toEqual(['1.2.0']);
|
|
42
|
+
expect(versionUtil.union(one, two)).toEqual(['2.0.0', '1.2.0', '1.0.0']);
|
|
43
|
+
expect(versionUtil.difference(one, two)).toEqual(['1.0.0']);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('checks intersection and subset relations', () => {
|
|
47
|
+
expect(versionUtil.intersects(one, two)).toBe(true);
|
|
48
|
+
expect(versionUtil.intersects(['1.0.0'], ['2.0.0'])).toBe(false);
|
|
49
|
+
expect(versionUtil.isSubset(['1.2.0'], one)).toBe(true);
|
|
50
|
+
expect(versionUtil.isSubset(one, ['1.2.0'])).toBe(false);
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
describe('versionUtil.bump', () => {
|
|
55
|
+
it.each([
|
|
56
|
+
['release', '1.2.4'],
|
|
57
|
+
['minor', '1.3.0'],
|
|
58
|
+
['major', '2.0.0'],
|
|
59
|
+
] as const)('bumps %s', (kind, expected) => {
|
|
60
|
+
expect(versionUtil.bump('1.2.3', kind)).toBe(expected);
|
|
61
|
+
});
|
|
62
|
+
});
|