submodule-version 3.2.7 → 3.3.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.
@@ -52,6 +52,23 @@ const bump = (version, kind) => {
52
52
  throw new Error(`Invalid semantic version: ${version}`);
53
53
  return next;
54
54
  };
55
+ /* Публикация с не последнего тега образует отдельную patch-линейку:
56
+ * 1.0.2 -> 1.0.2-patch.1, затем автоматически .2, .3 и т.д. */
57
+ const nextPatchVersion = (version, versions) => {
58
+ const parsed = semver_1.default.parse(version);
59
+ if (!parsed)
60
+ throw new Error(`Invalid semantic version: ${version}`);
61
+ const base = `${parsed.major}.${parsed.minor}.${parsed.patch}`;
62
+ const prefix = `${base}-patch.`;
63
+ const numbers = versions.flatMap(candidate => {
64
+ if (!candidate.startsWith(prefix))
65
+ return [];
66
+ const value = candidate.slice(prefix.length);
67
+ return /^\d+$/.test(value) ? [Number(value)] : [];
68
+ });
69
+ const next = (numbers.length ? Math.max(...numbers) : 0) + 1;
70
+ return `${prefix}${next}`;
71
+ };
55
72
  exports.versionUtil = {
56
73
  normalizeConstraint,
57
74
  validate,
@@ -66,5 +83,6 @@ exports.versionUtil = {
66
83
  intersects,
67
84
  isSubset,
68
85
  bump,
86
+ nextPatchVersion,
69
87
  };
70
88
  //# sourceMappingURL=versionUtil.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"versionUtil.js","sourceRoot":"","sources":["../../../src/versionUtil/versionUtil.ts"],"names":[],"mappings":";;;;;;AAAA,oDAA4B;AAI5B,MAAM,mBAAmB,GAAG,CAAC,UAAU,GAAG,GAAG,EAAiB,EAAE;IAC9D,MAAM,KAAK,GAAG,UAAU,CAAC,IAAI,EAAE,CAAC;IAEhC,IAAI,CAAC,KAAK;QAAE,OAAO,IAAI,CAAC;IAExB,OAAO,gBAAM,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC;AAClC,CAAC,CAAC;AAEF,MAAM,QAAQ,GAAG,CAAC,KAAa,EAAE,KAAK,GAAG,KAAK,EAAW,EAAE;IACzD,MAAM,KAAK,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC;IAE3B,IAAI,CAAC,KAAK;QAAE,OAAO,KAAK,CAAC;IAEzB,IAAI,KAAK;QAAE,OAAO,mBAAmB,CAAC,KAAK,CAAC,KAAK,IAAI,CAAC;IAEtD,OAAO,gBAAM,CAAC,KAAK,CAAC,KAAK,CAAC,KAAK,KAAK,CAAC;AACvC,CAAC,CAAC;AAEF,MAAM,OAAO,GAAG,CAAC,CAAS,EAAE,CAAS,EAAU,EAAE,CAAC,gBAAM,CAAC,OAAO,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;AAEvE,MAAM,SAAS,GAAG,CAAC,OAAe,EAAE,UAAU,GAAG,GAAG,EAAW,EAAE;IAC/D,MAAM,KAAK,GAAG,mBAAmB,CAAC,UAAU,CAAC,CAAC;IAE9C,OAAO,KAAK,KAAK,IAAI,IAAI,gBAAM,CAAC,SAAS,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;AAC5D,CAAC,CAAC;AAEF,MAAM,IAAI,GAAG,CAAC,QAAkB,EAAY,EAAE,CAAC,CAC7C,CAAC,GAAG,QAAQ,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,gBAAM,CAAC,QAAQ,CAAC,CAC7D,CAAC;AAEF,MAAM,MAAM,GAAG,CACb,QAAkB,EAClB,cAAwB,CAAC,GAAG,CAAC,EACnB,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC,CAC9C,WAAW,CAAC,KAAK,CAAC,UAAU,CAAC,EAAE,CAAC,SAAS,CAAC,OAAO,EAAE,UAAU,CAAC,CAAC,CAChE,CAAC,CAAC;AAEH,MAAM,MAAM,GAAG,CACb,QAAkB,EAClB,cAAwB,CAAC,GAAG,CAAC,EACrB,EAAE,CAAC,MAAM,CAAC,QAAQ,EAAE,WAAW,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;AAEpD,MAAM,YAAY,GAAG,CAAC,GAAG,IAAgB,EAAY,EAAE;IACrD,IAAI,CAAC,IAAI,CAAC,MAAM;QAAE,OAAO,EAAE,CAAC;IAE5B,OAAO,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC,CACpC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAClD,CAAC,CAAC,CAAC;AACN,CAAC,CAAC;AAEF,MAAM,KAAK,GAAG,CAAC,GAAG,IAAgB,EAAY,EAAE,CAAC,CAC/C,IAAI,CAAC,CAAC,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAChC,CAAC;AAEF,MAAM,UAAU,GAAG,CAAC,IAAc,EAAE,KAAe,EAAY,EAAE;IAC/D,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC;IAEhC,OAAO,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;AAC9D,CAAC,CAAC;AAEF,MAAM,UAAU,GAAG,CAAC,IAAc,EAAE,KAAe,EAAW,EAAE;IAC9D,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC;IAEhC,OAAO,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC;AACrD,CAAC,CAAC;AAEF,MAAM,QAAQ,GAAG,CAAC,MAAgB,EAAE,QAAkB,EAAW,EAAE;IACjE,MAAM,OAAO,GAAG,IAAI,GAAG,CAAC,QAAQ,CAAC,CAAC;IAElC,OAAO,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC;AACvD,CAAC,CAAC;AAEF,MAAM,IAAI,GAAG,CACX,OAAe,EACf,IAAmC,EAC3B,EAAE;IACV,MAAM,OAAO,GAAG,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC;IACpD,MAAM,IAAI,GAAG,gBAAM,CAAC,GAAG,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;IAE1C,IAAI,CAAC,IAAI;QAAE,MAAM,IAAI,KAAK,CAAC,6BAA6B,OAAO,EAAE,CAAC,CAAC;IAEnE,OAAO,IAAI,CAAC;AACd,CAAC,CAAC;AAEW,QAAA,WAAW,GAAG;IACzB,mBAAmB;IACnB,QAAQ;IACR,OAAO;IACP,SAAS;IACT,IAAI;IACJ,MAAM;IACN,MAAM;IACN,YAAY;IACZ,KAAK;IACL,UAAU;IACV,UAAU;IACV,QAAQ;IACR,IAAI;CACL,CAAC"}
1
+ {"version":3,"file":"versionUtil.js","sourceRoot":"","sources":["../../../src/versionUtil/versionUtil.ts"],"names":[],"mappings":";;;;;;AAAA,oDAA4B;AAI5B,MAAM,mBAAmB,GAAG,CAAC,UAAU,GAAG,GAAG,EAAiB,EAAE;IAC9D,MAAM,KAAK,GAAG,UAAU,CAAC,IAAI,EAAE,CAAC;IAEhC,IAAI,CAAC,KAAK;QAAE,OAAO,IAAI,CAAC;IAExB,OAAO,gBAAM,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC;AAClC,CAAC,CAAC;AAEF,MAAM,QAAQ,GAAG,CAAC,KAAa,EAAE,KAAK,GAAG,KAAK,EAAW,EAAE;IACzD,MAAM,KAAK,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC;IAE3B,IAAI,CAAC,KAAK;QAAE,OAAO,KAAK,CAAC;IAEzB,IAAI,KAAK;QAAE,OAAO,mBAAmB,CAAC,KAAK,CAAC,KAAK,IAAI,CAAC;IAEtD,OAAO,gBAAM,CAAC,KAAK,CAAC,KAAK,CAAC,KAAK,KAAK,CAAC;AACvC,CAAC,CAAC;AAEF,MAAM,OAAO,GAAG,CAAC,CAAS,EAAE,CAAS,EAAU,EAAE,CAAC,gBAAM,CAAC,OAAO,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;AAEvE,MAAM,SAAS,GAAG,CAAC,OAAe,EAAE,UAAU,GAAG,GAAG,EAAW,EAAE;IAC/D,MAAM,KAAK,GAAG,mBAAmB,CAAC,UAAU,CAAC,CAAC;IAE9C,OAAO,KAAK,KAAK,IAAI,IAAI,gBAAM,CAAC,SAAS,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;AAC5D,CAAC,CAAC;AAEF,MAAM,IAAI,GAAG,CAAC,QAAkB,EAAY,EAAE,CAAC,CAC7C,CAAC,GAAG,QAAQ,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,gBAAM,CAAC,QAAQ,CAAC,CAC7D,CAAC;AAEF,MAAM,MAAM,GAAG,CACb,QAAkB,EAClB,cAAwB,CAAC,GAAG,CAAC,EACnB,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC,CAC9C,WAAW,CAAC,KAAK,CAAC,UAAU,CAAC,EAAE,CAAC,SAAS,CAAC,OAAO,EAAE,UAAU,CAAC,CAAC,CAChE,CAAC,CAAC;AAEH,MAAM,MAAM,GAAG,CACb,QAAkB,EAClB,cAAwB,CAAC,GAAG,CAAC,EACrB,EAAE,CAAC,MAAM,CAAC,QAAQ,EAAE,WAAW,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;AAEpD,MAAM,YAAY,GAAG,CAAC,GAAG,IAAgB,EAAY,EAAE;IACrD,IAAI,CAAC,IAAI,CAAC,MAAM;QAAE,OAAO,EAAE,CAAC;IAE5B,OAAO,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC,CACpC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAClD,CAAC,CAAC,CAAC;AACN,CAAC,CAAC;AAEF,MAAM,KAAK,GAAG,CAAC,GAAG,IAAgB,EAAY,EAAE,CAAC,CAC/C,IAAI,CAAC,CAAC,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAChC,CAAC;AAEF,MAAM,UAAU,GAAG,CAAC,IAAc,EAAE,KAAe,EAAY,EAAE;IAC/D,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC;IAEhC,OAAO,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;AAC9D,CAAC,CAAC;AAEF,MAAM,UAAU,GAAG,CAAC,IAAc,EAAE,KAAe,EAAW,EAAE;IAC9D,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC;IAEhC,OAAO,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC;AACrD,CAAC,CAAC;AAEF,MAAM,QAAQ,GAAG,CAAC,MAAgB,EAAE,QAAkB,EAAW,EAAE;IACjE,MAAM,OAAO,GAAG,IAAI,GAAG,CAAC,QAAQ,CAAC,CAAC;IAElC,OAAO,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC;AACvD,CAAC,CAAC;AAEF,MAAM,IAAI,GAAG,CACX,OAAe,EACf,IAAmC,EAC3B,EAAE;IACV,MAAM,OAAO,GAAG,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC;IACpD,MAAM,IAAI,GAAG,gBAAM,CAAC,GAAG,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;IAE1C,IAAI,CAAC,IAAI;QAAE,MAAM,IAAI,KAAK,CAAC,6BAA6B,OAAO,EAAE,CAAC,CAAC;IAEnE,OAAO,IAAI,CAAC;AACd,CAAC,CAAC;AAEF;+DAC+D;AAC/D,MAAM,gBAAgB,GAAG,CAAC,OAAe,EAAE,QAAkB,EAAU,EAAE;IACvE,MAAM,MAAM,GAAG,gBAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IAErC,IAAI,CAAC,MAAM;QAAE,MAAM,IAAI,KAAK,CAAC,6BAA6B,OAAO,EAAE,CAAC,CAAC;IAErE,MAAM,IAAI,GAAG,GAAG,MAAM,CAAC,KAAK,IAAI,MAAM,CAAC,KAAK,IAAI,MAAM,CAAC,KAAK,EAAE,CAAC;IAC/D,MAAM,MAAM,GAAG,GAAG,IAAI,SAAS,CAAC;IAEhC,MAAM,OAAO,GAAG,QAAQ,CAAC,OAAO,CAAC,SAAS,CAAC,EAAE;QAC3C,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,MAAM,CAAC;YAAE,OAAO,EAAE,CAAC;QAE7C,MAAM,KAAK,GAAG,SAAS,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QAE7C,OAAO,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IACpD,CAAC,CAAC,CAAC;IAEH,MAAM,IAAI,GAAG,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;IAE7D,OAAO,GAAG,MAAM,GAAG,IAAI,EAAE,CAAC;AAC5B,CAAC,CAAC;AAEW,QAAA,WAAW,GAAG;IACzB,mBAAmB;IACnB,QAAQ;IACR,OAAO;IACP,SAAS;IACT,IAAI;IACJ,MAAM;IACN,MAAM;IACN,YAAY;IACZ,KAAK;IACL,UAAU;IACV,UAAU;IACV,QAAQ;IACR,IAAI;IACJ,gBAAgB;CACjB,CAAC"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "submodule-version",
3
3
  "description": "Git <S>ubmodule <V>ersioning tool",
4
- "version": "3.2.7",
4
+ "version": "3.3.0",
5
5
  "repository": "github:guljeny/submodule-version",
6
6
  "main": "dist/src/index.js",
7
7
  "bin": {
@@ -7,6 +7,7 @@ import {
7
7
  IIncompatibility,
8
8
  IPackageEntry,
9
9
  IRequirement,
10
+ TIsCandidateCompatible,
10
11
  ITerm,
11
12
  IVersionConflict,
12
13
  TConflict,
@@ -83,7 +84,12 @@ export class PubGrub {
83
84
 
84
85
  private incompatibilityKeys = new Set<string>();
85
86
 
86
- constructor (private source: IEntrySource = entryStore) {}
87
+ private compatibleVersions = new Map<string, string[]>();
88
+
89
+ constructor (
90
+ private source: IEntrySource = entryStore,
91
+ private isCandidateCompatible?: TIsCandidateCompatible,
92
+ ) {}
87
93
 
88
94
  public resolve = async (
89
95
  rootDependencies: TDependencies,
@@ -93,6 +99,7 @@ export class PubGrub {
93
99
  this.partialState = null;
94
100
  this.incompatibilities = [];
95
101
  this.incompatibilityKeys = new Set();
102
+ this.compatibleVersions = new Map();
96
103
 
97
104
  const state: ISolverState = {
98
105
  selected: new Map([[ROOT, ROOT_VERSION]]),
@@ -170,8 +177,10 @@ export class PubGrub {
170
177
  .filter(requirement => requirement.positive)
171
178
  .map(requirement => requirement.range);
172
179
 
173
- version = versionUtil.latest(Object.keys(entry.versions), ranges)
174
- || versionUtil.latest(Object.keys(entry.versions));
180
+ const versions = this.versionsOf(entry);
181
+
182
+ version = versionUtil.latest(versions, ranges)
183
+ || versionUtil.latest(versions);
175
184
  }
176
185
 
177
186
  if (!version) return [];
@@ -225,7 +234,7 @@ export class PubGrub {
225
234
  const conflict = this.versionConflict(
226
235
  choice.name,
227
236
  choice.requirements,
228
- Object.keys(choice.entry.versions),
237
+ this.versionsOf(choice.entry),
229
238
  );
230
239
 
231
240
  this.learn(state, conflict);
@@ -267,7 +276,7 @@ export class PubGrub {
267
276
  conflict: lastConflict || this.versionConflict(
268
277
  choice.name,
269
278
  choice.requirements,
270
- Object.keys(choice.entry.versions),
279
+ this.versionsOf(choice.entry),
271
280
  ),
272
281
  };
273
282
  };
@@ -355,7 +364,8 @@ export class PubGrub {
355
364
  };
356
365
 
357
366
  private candidates = (name: string, state: ISolverState): string[] => {
358
- const versions = Object.keys(this.entries.get(name)?.versions || {});
367
+ const entry = this.entries.get(name);
368
+ const versions = entry ? this.versionsOf(entry) : [];
359
369
  const requirements = state.requirements.get(name) || [];
360
370
 
361
371
  return versionUtil.sort(versions).filter(version => (
@@ -367,6 +377,25 @@ export class PubGrub {
367
377
  ));
368
378
  };
369
379
 
380
+ private versionsOf = (entry: IPackageEntry): string[] => {
381
+ const cached = this.compatibleVersions.get(entry.name);
382
+
383
+ if (cached) return cached;
384
+
385
+ const versions = Object.keys(entry.versions).filter(version => (
386
+ !this.isCandidateCompatible || this.isCandidateCompatible({
387
+ name: entry.name,
388
+ url: entry.url,
389
+ version,
390
+ packageJson: entry.manifests?.[version] ?? null,
391
+ })
392
+ ));
393
+
394
+ this.compatibleVersions.set(entry.name, versions);
395
+
396
+ return versions;
397
+ };
398
+
370
399
  private addDependencies = async (
371
400
  state: ISolverState,
372
401
  entry: IPackageEntry,
@@ -479,7 +508,7 @@ export class PubGrub {
479
508
  return this.versionConflict(
480
509
  name,
481
510
  state.requirements.get(name) || [],
482
- Object.keys(this.entries.get(name)?.versions || {}),
511
+ this.entries.has(name) ? this.versionsOf(this.entries.get(name)!) : [],
483
512
  );
484
513
  };
485
514
 
@@ -556,7 +585,7 @@ export class PubGrub {
556
585
  return this.versionConflict(
557
586
  name,
558
587
  requirements,
559
- Object.keys(this.entries.get(name)?.versions || {}),
588
+ this.entries.has(name) ? this.versionsOf(this.entries.get(name)!) : [],
560
589
  );
561
590
  }
562
591
 
@@ -123,6 +123,17 @@ const SelfCycle = defineModule('SelfCycle', {
123
123
  '1.0.0': { SelfCycle: '*' },
124
124
  });
125
125
 
126
+ const CandidateA = {
127
+ ...defineModule('CandidateA', {
128
+ '1.0.0': {},
129
+ '2.0.0': {},
130
+ }),
131
+ manifests: {
132
+ '1.0.0': { compatible: true },
133
+ '2.0.0': { compatible: false },
134
+ },
135
+ };
136
+
126
137
  const ALL_MODULES = [
127
138
  A,
128
139
  B,
@@ -152,6 +163,7 @@ const ALL_MODULES = [
152
163
  CycleB,
153
164
  CycleC,
154
165
  SelfCycle,
166
+ CandidateA,
155
167
  ];
156
168
 
157
169
  const source = (modules: IPackageEntry[]) => {
@@ -277,6 +289,36 @@ describe('PubGrub resolution', () => {
277
289
 
278
290
  expect(selected(result)).toEqual({ FallbackA: '1.0.0' });
279
291
  });
292
+
293
+ it('selects the highest candidate accepted by the host', async () => {
294
+ const fixture = source(ALL_MODULES);
295
+
296
+ const resolver = new PubGrub(
297
+ fixture.store,
298
+ candidate => (candidate.packageJson as any)?.compatible !== false,
299
+ );
300
+
301
+ const result = await resolver.resolve({ [CandidateA.url]: '*' });
302
+
303
+ expect(selected(result)).toEqual({ CandidateA: '1.0.0' });
304
+ expect(result.CandidateA.versions).toEqual({
305
+ '1.0.0': {},
306
+ '2.0.0': {},
307
+ });
308
+ });
309
+
310
+ it('reports a conflict when the host rejects every candidate', async () => {
311
+ const fixture = source(ALL_MODULES);
312
+ const resolver = new PubGrub(fixture.store, () => false);
313
+
314
+ await expect(resolver.resolve({ [CandidateA.url]: '*' }))
315
+ .rejects.toMatchObject({
316
+ error: 'VERSION_CONFLICT',
317
+ details: { name: 'CandidateA', versions: [] },
318
+ });
319
+
320
+ expect(resolver.getResolution()).toEqual({});
321
+ });
280
322
  });
281
323
 
282
324
  describe('PubGrub errors', () => {
@@ -1,5 +1,6 @@
1
1
  export { PubGrub, pubGrub } from './PubGrub';
2
2
  export type {
3
+ ICandidate,
3
4
  IEntrySource,
4
5
  IIncompatibility,
5
6
  IOverride,
@@ -8,5 +9,6 @@ export type {
8
9
  IResolvedPackage,
9
10
  ITerm,
10
11
  TDependencies,
12
+ TIsCandidateCompatible,
11
13
  TPubGrubResult,
12
14
  } from './types';
@@ -1,9 +1,19 @@
1
1
  export type TDependencies = Record<string, string>;
2
2
 
3
+ export interface ICandidate {
4
+ name: string;
5
+ url: string;
6
+ version: string;
7
+ packageJson: unknown | null;
8
+ }
9
+
10
+ export type TIsCandidateCompatible = (candidate: ICandidate) => boolean;
11
+
3
12
  export interface IPackageEntry {
4
13
  name: string;
5
14
  url: string;
6
15
  versions: Record<string, TDependencies>;
16
+ manifests?: Record<string, unknown | null>;
7
17
  }
8
18
 
9
19
  export interface IResolvedPackage extends IPackageEntry {
@@ -59,6 +59,10 @@ describe('entryStore.fetch', () => {
59
59
  '1.0.0': { B: '^1.0.0' },
60
60
  '2.0.0': { C: '^2.0.0' },
61
61
  },
62
+ manifests: {
63
+ '1.0.0': { sv: { [url('B')]: '^1.0.0' } },
64
+ '2.0.0': { sv: { [url('C')]: '^2.0.0' } },
65
+ },
62
66
  });
63
67
 
64
68
  expect(fetchVersionsMock).toHaveBeenCalledTimes(1);
@@ -163,6 +167,7 @@ describe('entryStore.simulate', () => {
163
167
  name: 'B',
164
168
  url: url('B'),
165
169
  versions: { '2.0.0': {} },
170
+ manifests: { '2.0.0': {} },
166
171
  });
167
172
  });
168
173
 
@@ -277,12 +282,22 @@ describe('entryStore local overlay', () => {
277
282
  ]);
278
283
  isGitRepoMock.mockResolvedValueOnce(true);
279
284
  tagsAtHeadMock.mockResolvedValueOnce(['1.0.0']);
280
- readPkgMock.mockResolvedValueOnce({ sv: { [url('B')]: '^0.2.0' } });
285
+ readPkgMock.mockResolvedValueOnce({
286
+ ewaApi: '2-*',
287
+ sv: { [url('B')]: '^0.2.0' },
288
+ });
281
289
 
282
290
  const entry = await entryStore.fetch(url('A'));
283
291
 
284
292
  expect(entry.versions['1.0.0']).toEqual({ B: '^0.2.0' });
285
293
  expect(entry.versions['2.0.0']).toEqual({ C: '^2.0.0' });
294
+ expect(entry.manifests?.['1.0.0']).toEqual({
295
+ ewaApi: '2-*',
296
+ sv: { [url('B')]: '^0.2.0' },
297
+ });
298
+ expect(entry.manifests?.['2.0.0']).toEqual({
299
+ sv: { [url('C')]: '^2.0.0' },
300
+ });
286
301
  expect(entryStore.urlOf('B')).toBe(url('B'));
287
302
  });
288
303
 
@@ -0,0 +1,86 @@
1
+ import { execFileSync } from 'child_process';
2
+ import fs from 'fs';
3
+ import os from 'os';
4
+ import path from 'path';
5
+ import { SV } from '../sv';
6
+
7
+ const git = (cwd: string, args: string[]): string => execFileSync(
8
+ 'git',
9
+ args,
10
+ { cwd, encoding: 'utf8' },
11
+ ).trim();
12
+
13
+ const writePackage = (dir: string, version: string): void => {
14
+ fs.writeFileSync(
15
+ path.join(dir, 'package.json'),
16
+ `${JSON.stringify({ name: 'fixture', version }, null, 2)}\n`,
17
+ );
18
+ };
19
+
20
+ describe('SV.publish git integration', () => {
21
+ it(
22
+ 'pushes an automatic patch.4 branch and tag from an old version',
23
+ async () => {
24
+ const fixture = fs.mkdtempSync(path.join(os.tmpdir(), 'sv-publish-'));
25
+ const remote = path.join(fixture, 'remote.git');
26
+ const project = path.join(fixture, 'project');
27
+
28
+ try {
29
+ fs.mkdirSync(project);
30
+ git(fixture, ['init', '--bare', remote]);
31
+ git(project, ['init', '-b', 'main']);
32
+ git(project, ['config', 'user.name', 'SV Test']);
33
+ git(project, ['config', 'user.email', 'sv@example.test']);
34
+ git(project, ['remote', 'add', 'origin', remote]);
35
+
36
+ writePackage(project, '1.0.2');
37
+ git(project, ['add', 'package.json']);
38
+ git(project, ['commit', '-m', 'version 1.0.2']);
39
+ git(project, ['tag', '1.0.2']);
40
+ git(project, ['tag', '1.0.2-patch.1']);
41
+ git(project, ['tag', '1.0.2-patch.2']);
42
+ git(project, ['tag', '1.0.2-patch.3']);
43
+
44
+ writePackage(project, '2.0.0');
45
+ git(project, ['add', 'package.json']);
46
+ git(project, ['commit', '-m', 'version 2.0.0']);
47
+ git(project, ['tag', '2.0.0']);
48
+ git(project, ['push', '-u', 'origin', 'main']);
49
+ git(project, ['push', 'origin', '--tags']);
50
+
51
+ git(project, ['checkout', '--detach', '1.0.2']);
52
+ fs.writeFileSync(path.join(project, 'fix.txt'), 'backport\n');
53
+
54
+ const version = await new SV(project).publish({
55
+ /* bump игнорируется, потому что HEAD находится на старом теге. */
56
+ bump: 'major',
57
+ message: 'backport fix',
58
+ });
59
+
60
+ expect(version).toBe('1.0.2-patch.4');
61
+ expect(git(project, ['branch', '--show-current']))
62
+ .toBe('1.0.2-patch.4');
63
+ expect(git(project, ['tag', '--points-at', 'HEAD']))
64
+ .toContain('1.0.2-patch.4');
65
+ expect(JSON.parse(
66
+ fs.readFileSync(path.join(project, 'package.json'), 'utf8'),
67
+ ).version).toBe('1.0.2-patch.4');
68
+ expect(git(project, [
69
+ 'ls-remote',
70
+ '--heads',
71
+ 'origin',
72
+ '1.0.2-patch.4',
73
+ ])).toContain('refs/heads/1.0.2-patch.4');
74
+ expect(git(project, [
75
+ 'ls-remote',
76
+ '--tags',
77
+ 'origin',
78
+ '1.0.2-patch.4',
79
+ ])).toContain('refs/tags/1.0.2-patch.4');
80
+ } finally {
81
+ fs.rmSync(fixture, { recursive: true, force: true });
82
+ }
83
+ },
84
+ 20000,
85
+ );
86
+ });
@@ -29,6 +29,19 @@ jest.mock('../git', () => {
29
29
  currentVersion: jest.fn(async () => null),
30
30
  listVersions: jest.fn(async () => []),
31
31
  getRemote: jest.fn(async () => null),
32
+ tagsAtHead: jest.fn(async () => []),
33
+ listTags: jest.fn(async () => []),
34
+ hasChanges: jest.fn(async () => false),
35
+ hasUnpushedCommits: jest.fn(async () => false),
36
+ ensureBranch: jest.fn(async () => undefined),
37
+ createBranch: jest.fn(async () => undefined),
38
+ isRemoteAhead: jest.fn(async () => false),
39
+ pullRebaseAutostash: jest.fn(async () => undefined),
40
+ commitAll: jest.fn(async () => undefined),
41
+ addTag: jest.fn(async () => undefined),
42
+ push: jest.fn(async () => undefined),
43
+ init: jest.fn(async () => undefined),
44
+ addRemote: jest.fn(async () => undefined),
32
45
  },
33
46
  api: {
34
47
  setToken: jest.fn(),
@@ -85,6 +98,11 @@ beforeEach(() => {
85
98
  localMock.isGitRepo.mockResolvedValue(true);
86
99
  localMock.currentVersion.mockResolvedValue(null);
87
100
  localMock.listVersions.mockResolvedValue([]);
101
+ localMock.tagsAtHead.mockResolvedValue([]);
102
+ localMock.listTags.mockResolvedValue([]);
103
+ localMock.hasChanges.mockResolvedValue(false);
104
+ localMock.hasUnpushedCommits.mockResolvedValue(false);
105
+ localMock.isRemoteAhead.mockResolvedValue(false);
88
106
  });
89
107
 
90
108
  afterEach(() => {
@@ -141,6 +159,21 @@ describe('SV.init', () => {
141
159
  expect(sv.listVersions()).toEqual({ A: ['1.2.0'] });
142
160
  });
143
161
 
162
+ it('passes the candidate compatibility callback to PubGrub', async () => {
163
+ const isCandidateCompatible = jest.fn(() => true);
164
+
165
+ readMock.mockResolvedValue({ sv: {} });
166
+
167
+ const sv = new SV('/project', undefined, { isCandidateCompatible });
168
+
169
+ await sv.init();
170
+
171
+ expect(PubGrubMock).toHaveBeenCalledWith(
172
+ expect.anything(),
173
+ isCandidateCompatible,
174
+ );
175
+ });
176
+
144
177
  it('guards result before init', () => {
145
178
  expect(() => new SV('/project').getResolution()).toThrow(
146
179
  expect.objectContaining({ error: 'NOT_INITIALIZED' }),
@@ -776,3 +809,88 @@ describe('SV.listVersions', () => {
776
809
  );
777
810
  });
778
811
  });
812
+
813
+ describe('SV.publish', () => {
814
+ it(
815
+ 'publishes an old version on the next automatic patch branch',
816
+ async () => {
817
+ const versions = [
818
+ '2.0.0',
819
+ '1.0.2',
820
+ '1.0.2-patch.1',
821
+ '1.0.2-patch.3',
822
+ ];
823
+
824
+ localMock.getRemote.mockResolvedValue(url('A'));
825
+ localMock.tagsAtHead.mockResolvedValue(['1.0.2']);
826
+ localMock.listTags.mockResolvedValue(versions);
827
+ localMock.hasChanges.mockResolvedValue(true);
828
+ readMock.mockResolvedValue({ version: '1.0.2', sv: {} });
829
+ const sv = new SV('/project');
830
+ const clear = jest.spyOn((sv as any).store, 'clear');
831
+
832
+ await expect(sv.publish({
833
+ module: 'A',
834
+ /* Старый релиз игнорирует выбранный bump. */
835
+ bump: 'major',
836
+ message: 'backport fix',
837
+ })).resolves.toBe('1.0.2-patch.4');
838
+
839
+ expect(localMock.createBranch).toHaveBeenCalledWith(
840
+ 'modules/A',
841
+ '1.0.2-patch.4',
842
+ );
843
+ expect(localMock.ensureBranch).not.toHaveBeenCalled();
844
+ expect(writeMock).toHaveBeenCalledWith({
845
+ version: '1.0.2-patch.4',
846
+ sv: {},
847
+ }, 'A');
848
+ expect(localMock.commitAll).toHaveBeenCalledWith(
849
+ 'modules/A',
850
+ 'backport fix',
851
+ );
852
+ expect(localMock.addTag).toHaveBeenCalledWith(
853
+ 'modules/A',
854
+ '1.0.2-patch.4',
855
+ );
856
+ expect(localMock.push).toHaveBeenCalledWith(
857
+ 'modules/A',
858
+ '1.0.2-patch.4',
859
+ );
860
+ expect(clear).toHaveBeenCalledTimes(1);
861
+ },
862
+ );
863
+
864
+ it(
865
+ 'continues a patch line and commits the generated version',
866
+ async () => {
867
+ localMock.getRemote.mockResolvedValue(url('A'));
868
+ localMock.tagsAtHead.mockResolvedValue(['1.0.2-patch.4']);
869
+ localMock.listTags.mockResolvedValue([
870
+ '2.0.0',
871
+ '1.0.2',
872
+ '1.0.2-patch.4',
873
+ ]);
874
+ localMock.hasChanges.mockResolvedValue(true);
875
+ readMock.mockResolvedValue({ version: '1.0.2-patch.4' });
876
+ const sv = new SV('/project');
877
+
878
+ await expect(sv.publish({
879
+ module: 'A',
880
+ bump: 'release',
881
+ })).resolves.toBe('1.0.2-patch.5');
882
+
883
+ expect(localMock.createBranch).toHaveBeenCalledWith(
884
+ 'modules/A',
885
+ '1.0.2-patch.5',
886
+ );
887
+ expect(writeMock).toHaveBeenCalledWith({
888
+ version: '1.0.2-patch.5',
889
+ }, 'A');
890
+ expect(localMock.commitAll).toHaveBeenCalledWith(
891
+ 'modules/A',
892
+ 'Update',
893
+ );
894
+ },
895
+ );
896
+ });
package/src/entryStore.ts CHANGED
@@ -94,6 +94,7 @@ export class EntryStore implements IEntrySource {
94
94
  private localOverlay = async (
95
95
  name: string,
96
96
  versions: Record<string, TDependencies>,
97
+ manifests: Record<string, unknown | null>,
97
98
  ): Promise<void> => {
98
99
  if (!RunOptions.cwd || !RunOptions.modulesDir) return;
99
100
 
@@ -110,19 +111,22 @@ export class EntryStore implements IEntrySource {
110
111
  if (!pkg) return;
111
112
 
112
113
  versions[version] = this.normalizeDependencies(pkg.sv || {});
114
+ manifests[version] = pkg;
113
115
  };
114
116
 
115
117
  private load = async (url: string, name: string): Promise<IPackageEntry> => {
116
118
  const fetched = await git.api.fetchVersions(url);
117
119
  const versions: Record<string, TDependencies> = {};
120
+ const manifests: Record<string, unknown | null> = {};
118
121
 
119
122
  fetched.forEach(({ version, pkg }) => {
120
123
  versions[version] = this.normalizeDependencies(pkg?.sv || {});
124
+ manifests[version] = pkg;
121
125
  });
122
126
 
123
- await this.localOverlay(name, versions);
127
+ await this.localOverlay(name, versions, manifests);
124
128
 
125
- return { name, url, versions };
129
+ return { name, url, versions, manifests };
126
130
  };
127
131
 
128
132
  private resolveIdentity = (
@@ -261,6 +261,13 @@ export const local = {
261
261
  }
262
262
  },
263
263
 
264
+ createBranch: async (dir: string, branch: string): Promise<void> => {
265
+ await execAsync(
266
+ `git -C "${dir}" checkout -b ${JSON.stringify(branch)}`,
267
+ { cwd: RunOptions.cwd },
268
+ );
269
+ },
270
+
264
271
  commitAll: async (dir: string, message: string): Promise<void> => {
265
272
  await execAsync(
266
273
  `git -C "${dir}" add -A`,
@@ -288,7 +295,7 @@ export const local = {
288
295
 
289
296
  if (tag) {
290
297
  await execAsync(
291
- `git -C "${dir}" push origin ${tag}`,
298
+ `git -C "${dir}" push origin refs/tags/${tag}`,
292
299
  { cwd: RunOptions.cwd },
293
300
  );
294
301
  }
@@ -368,7 +375,7 @@ export const local = {
368
375
  const module = path.join(RunOptions.modulesDir, name);
369
376
  const tags = await local.tagsAtHead(module);
370
377
 
371
- return versionUtil.latest(tags) || null;
378
+ return versionUtil.sort(tags)[0] || null;
372
379
  },
373
380
 
374
381
  listTags: async (dir: string): Promise<string[]> => {
package/src/index.ts CHANGED
@@ -2,6 +2,7 @@ export { SV } from './sv';
2
2
  export { versionUtil } from './versionUtil';
3
3
  export { PubGrub, pubGrub } from './PubGrub';
4
4
  export type {
5
+ ICandidate,
5
6
  IEntrySource,
6
7
  IIncompatibility,
7
8
  IOverride,
@@ -10,6 +11,7 @@ export type {
10
11
  IResolvedPackage,
11
12
  ITerm,
12
13
  TDependencies,
14
+ TIsCandidateCompatible,
13
15
  TPubGrubResult,
14
16
  } from './PubGrub';
15
17
  export { EntryStore, entryStore } from './entryStore';