submodule-version 2.3.2 → 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 +103 -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 +18 -8
- package/dist/src/sv.js +235 -131
- 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 +321 -134
- 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];
|
|
270
|
+
|
|
271
|
+
if (!existing) throw new SVError('NOT_A_GIT_URL', { url });
|
|
125
272
|
|
|
126
|
-
|
|
273
|
+
moduleUrl = existing.url;
|
|
274
|
+
}
|
|
127
275
|
|
|
128
|
-
if (
|
|
129
|
-
|
|
130
|
-
await pkgJSONManager.write(json);
|
|
276
|
+
if (parent && !this.getResolution()[parent]) {
|
|
277
|
+
throw new SVError('ADDON_NOT_FOUND', { name: parent });
|
|
131
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');
|
|
292
|
+
|
|
293
|
+
if (parentName) {
|
|
294
|
+
const parentEntry = this.getResolution()[parentName];
|
|
295
|
+
|
|
296
|
+
if (!parentEntry) {
|
|
297
|
+
throw new SVError('ADDON_NOT_FOUND', { name: parentName });
|
|
298
|
+
}
|
|
140
299
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
return acc;
|
|
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 });
|
|
148
321
|
|
|
149
|
-
|
|
150
|
-
|
|
322
|
+
return versionUtil.sort(Object.keys(entry.versions));
|
|
323
|
+
}
|
|
324
|
+
|
|
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,11 +386,31 @@ 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
|
|
|
394
|
+
// eslint-disable-next-line class-methods-use-this
|
|
395
|
+
public isRemoteAhead = async (module?: string): Promise<boolean> => {
|
|
396
|
+
const dir = module
|
|
397
|
+
? path.join(RunOptions.modulesDir, module)
|
|
398
|
+
: RunOptions.cwd;
|
|
399
|
+
|
|
400
|
+
if (!await git.local.isGitRepo(dir)) return false;
|
|
401
|
+
|
|
402
|
+
return git.local.isRemoteAhead(dir);
|
|
403
|
+
};
|
|
404
|
+
|
|
405
|
+
// eslint-disable-next-line class-methods-use-this
|
|
406
|
+
public pullRebaseAutostash = async (module?: string): Promise<void> => {
|
|
407
|
+
const dir = module
|
|
408
|
+
? path.join(RunOptions.modulesDir, module)
|
|
409
|
+
: RunOptions.cwd;
|
|
410
|
+
|
|
411
|
+
return git.local.pullRebaseAutostash(dir);
|
|
412
|
+
};
|
|
413
|
+
|
|
216
414
|
public publish = async (opts: {
|
|
217
415
|
module?: string,
|
|
218
416
|
repoUrl?: string,
|
|
@@ -220,24 +418,19 @@ export class SV {
|
|
|
220
418
|
bump: 'release' | 'minor' | 'major',
|
|
221
419
|
}): Promise<string> => {
|
|
222
420
|
const { module, repoUrl, message, bump } = opts;
|
|
421
|
+
|
|
223
422
|
const dir = module
|
|
224
423
|
? path.join(RunOptions.modulesDir, module)
|
|
225
424
|
: RunOptions.cwd;
|
|
226
425
|
|
|
227
426
|
if (!await this.isPublished(module)) {
|
|
228
|
-
if (!repoUrl)
|
|
229
|
-
throw new GitError('GIT_REPO_URL_REQUIRED');
|
|
230
|
-
}
|
|
427
|
+
if (!repoUrl) throw new GitError('GIT_REPO_URL_REQUIRED');
|
|
231
428
|
|
|
232
|
-
if (!await git.isGitRepo(dir))
|
|
233
|
-
await git.init(dir);
|
|
234
|
-
}
|
|
429
|
+
if (!await git.local.isGitRepo(dir)) await git.local.init(dir);
|
|
235
430
|
|
|
236
|
-
await git.addRemote(dir, repoUrl);
|
|
431
|
+
await git.local.addRemote(dir, repoUrl);
|
|
237
432
|
}
|
|
238
433
|
|
|
239
|
-
/* Публикация не последней версии запрещена: HEAD на теге, отличном
|
|
240
|
-
* от последнего, — обновлять такую базу можно только вручную */
|
|
241
434
|
const currentTag = await this.currentVersion(module);
|
|
242
435
|
const latestKnown = await this.latestVersion(module);
|
|
243
436
|
|
|
@@ -245,41 +438,35 @@ export class SV {
|
|
|
245
438
|
throw new GitError('GIT_NOT_LATEST_VERSION');
|
|
246
439
|
}
|
|
247
440
|
|
|
248
|
-
|
|
249
|
-
* иначе коммиты и push уйдут в detached HEAD */
|
|
250
|
-
await git.ensureBranch(dir);
|
|
441
|
+
await git.local.ensureBranch(dir);
|
|
251
442
|
|
|
252
|
-
if (await git.isRemoteAhead(dir)) {
|
|
253
|
-
await git.pullRebaseAutostash(dir);
|
|
443
|
+
if (await git.local.isRemoteAhead(dir)) {
|
|
444
|
+
await git.local.pullRebaseAutostash(dir);
|
|
254
445
|
}
|
|
255
446
|
|
|
256
|
-
const dirty = await git.hasChanges(dir);
|
|
447
|
+
const dirty = await git.local.hasChanges(dir);
|
|
257
448
|
const headTag = await this.currentVersion(module);
|
|
258
449
|
|
|
259
|
-
/* Дерево чистое и HEAD уже отмечен версионным тегом:
|
|
260
|
-
* коммитить и бампить нечего — просто доставляем ветку и тег на remote */
|
|
261
450
|
if (!dirty && headTag) {
|
|
262
|
-
await git.push(dir, headTag);
|
|
451
|
+
await git.local.push(dir, headTag);
|
|
263
452
|
|
|
264
453
|
return headTag;
|
|
265
454
|
}
|
|
266
455
|
|
|
267
456
|
const next = versionUtil.bump(await this.latestVersion(module), bump);
|
|
268
|
-
|
|
269
|
-
/* Фиксируем версию и в package.json, чтобы она не расходилась с git-тегом */
|
|
270
457
|
const pkg = await pkgJSONManager.read(module);
|
|
458
|
+
|
|
271
459
|
if (pkg) {
|
|
272
460
|
pkg.version = next;
|
|
273
461
|
await pkgJSONManager.write(pkg, module);
|
|
274
462
|
}
|
|
275
463
|
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
await git.commitAll(dir, message || 'Update');
|
|
464
|
+
if (await git.local.hasChanges(dir)) {
|
|
465
|
+
await git.local.commitAll(dir, message || 'Update');
|
|
279
466
|
}
|
|
280
467
|
|
|
281
|
-
await git.addTag(dir, next);
|
|
282
|
-
await git.push(dir, next);
|
|
468
|
+
await git.local.addTag(dir, next);
|
|
469
|
+
await git.local.push(dir, next);
|
|
283
470
|
|
|
284
471
|
return next;
|
|
285
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
|
+
});
|