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