@proteinjs/build 1.9.1 → 2.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/CHANGELOG.md +11 -0
- package/dist/src/versionWorkspace.d.ts +23 -0
- package/dist/src/versionWorkspace.d.ts.map +1 -1
- package/dist/src/versionWorkspace.js +413 -106
- package/dist/src/versionWorkspace.js.map +1 -1
- package/dist/test/classifyCommitMessage.test.d.ts +2 -0
- package/dist/test/classifyCommitMessage.test.d.ts.map +1 -0
- package/dist/test/classifyCommitMessage.test.js +75 -0
- package/dist/test/classifyCommitMessage.test.js.map +1 -0
- package/package.json +1 -1
- package/src/versionWorkspace.ts +357 -48
- package/test/classifyCommitMessage.test.ts +95 -0
|
@@ -82,7 +82,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
82
82
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
83
83
|
};
|
|
84
84
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
85
|
-
exports.evictGitLocks = exports.versionWorkspace = void 0;
|
|
85
|
+
exports.evictGitLocks = exports.classifyCommitMessage = exports.classifyUnpushedCommits = exports.versionWorkspace = void 0;
|
|
86
86
|
var path = __importStar(require("path"));
|
|
87
87
|
var child_process_1 = require("child_process");
|
|
88
88
|
var util_node_1 = require("@proteinjs/util-node");
|
|
@@ -95,12 +95,18 @@ var logger = new logger_1.Logger({ name: cw.color('workspace:', logColors_1.prim
|
|
|
95
95
|
var fixedVersionWorkspacesToVersion = {};
|
|
96
96
|
function versionWorkspace() {
|
|
97
97
|
return __awaiter(this, void 0, void 0, function () {
|
|
98
|
-
var dryRun, workspacePath, workspaceRootDirty, _a, packageMap, packageGraph, sortedPackageNames, workspaceToPackageMap, skippedPackages, filteredPackageNames, _i, filteredPackageNames_1, packageName, localPackage, skipBumpingPackageVersion, dependenciesChanged, pushWithoutSync;
|
|
98
|
+
var dryRun, planOnly, workspacePath, workspaceRootDirty, _a, packageMap, packageGraph, sortedPackageNames, workspaceToPackageMap, skippedPackages, filteredPackageNames, commitBumps, scanSummary, commitLeaves, _i, filteredPackageNames_1, packageName, localPackage, skipBumpingPackageVersion, ownBump, dependenciesChanged, cascadeBump, effectiveBump, currentVersion, sourceNote, planPrefix, pushWithoutSync;
|
|
99
99
|
return __generator(this, function (_b) {
|
|
100
100
|
switch (_b.label) {
|
|
101
101
|
case 0:
|
|
102
102
|
dryRun = isDryRun();
|
|
103
|
-
|
|
103
|
+
planOnly = isPlanOnly();
|
|
104
|
+
if (planOnly) {
|
|
105
|
+
logger.info({
|
|
106
|
+
message: 'Plan-only mode enabled. Scan + commit-leaves + would-be bumps will be computed and logged; nothing will be written to disk, built, tested, published, committed, or pushed.',
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
else if (dryRun) {
|
|
104
110
|
logger.info({ message: 'Dry run mode enabled. Publish and push operations will be skipped.' });
|
|
105
111
|
}
|
|
106
112
|
workspacePath = process.cwd();
|
|
@@ -113,8 +119,8 @@ function versionWorkspace() {
|
|
|
113
119
|
if (workspaceRootDirty) {
|
|
114
120
|
logger.info({ message: "> Workspace root is dirty, will skip pull/push for root repo" });
|
|
115
121
|
}
|
|
116
|
-
if (!dryRun) return [3 /*break*/, 3];
|
|
117
|
-
logger.info({ message: ">
|
|
122
|
+
if (!(dryRun || planOnly)) return [3 /*break*/, 3];
|
|
123
|
+
logger.info({ message: "> Skipping pullWorkspace for (".concat(workspacePath, ")") });
|
|
118
124
|
return [3 /*break*/, 5];
|
|
119
125
|
case 3: return [4 /*yield*/, pullWorkspace(workspacePath, workspaceRootDirty)];
|
|
120
126
|
case 4:
|
|
@@ -132,51 +138,118 @@ function versionWorkspace() {
|
|
|
132
138
|
!skippedPackages.includes(packageName));
|
|
133
139
|
});
|
|
134
140
|
logger.info({ message: "> Versioning workspace (".concat(workspacePath, ")") });
|
|
135
|
-
|
|
136
|
-
_b.label = 7;
|
|
141
|
+
return [4 /*yield*/, scanCommitBumps(filteredPackageNames, packageMap)];
|
|
137
142
|
case 7:
|
|
138
|
-
|
|
143
|
+
commitBumps = _b.sent();
|
|
144
|
+
if (commitBumps.size === 0) {
|
|
145
|
+
logger.info({ message: "> No packages have unpushed commits" });
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
scanSummary = Array.from(commitBumps.entries())
|
|
149
|
+
.map(function (_a) {
|
|
150
|
+
var name = _a[0], bump = _a[1];
|
|
151
|
+
return "".concat(cw.color(name), ":").concat(bump);
|
|
152
|
+
})
|
|
153
|
+
.join(', ');
|
|
154
|
+
logger.info({ message: "> Packages with unpushed commits: ".concat(scanSummary) });
|
|
155
|
+
}
|
|
156
|
+
commitLeaves = computeCommitLeaves(commitBumps, packageGraph, filteredPackageNames);
|
|
157
|
+
if (commitLeaves.length > 0) {
|
|
158
|
+
logger.info({
|
|
159
|
+
message: "> Commit-leaves (root changes, will publish first): ".concat(commitLeaves.map(function (n) { return cw.color(n); }).join(', ')),
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
_i = 0, filteredPackageNames_1 = filteredPackageNames;
|
|
163
|
+
_b.label = 8;
|
|
164
|
+
case 8:
|
|
165
|
+
if (!(_i < filteredPackageNames_1.length)) return [3 /*break*/, 17];
|
|
139
166
|
packageName = filteredPackageNames_1[_i];
|
|
140
167
|
localPackage = packageMap[packageName];
|
|
141
168
|
skipBumpingPackageVersion = isInFixedVersionWorkspace(localPackage);
|
|
142
|
-
|
|
143
|
-
|
|
169
|
+
ownBump = skipBumpingPackageVersion ? undefined : commitBumps.get(packageName);
|
|
170
|
+
return [4 /*yield*/, applyDependencyVersionRewrites(localPackage, packageMap, packageGraph)];
|
|
171
|
+
case 9:
|
|
144
172
|
dependenciesChanged = _b.sent();
|
|
145
|
-
|
|
146
|
-
|
|
173
|
+
cascadeBump = dependenciesChanged ? 'patch' : undefined;
|
|
174
|
+
effectiveBump = maxBump(ownBump, cascadeBump);
|
|
175
|
+
if (!effectiveBump && !dependenciesChanged) {
|
|
176
|
+
return [3 /*break*/, 16];
|
|
147
177
|
}
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
178
|
+
if (effectiveBump && !skipBumpingPackageVersion) {
|
|
179
|
+
currentVersion = localPackage.packageJson.version;
|
|
180
|
+
// In-memory version mutation runs in plan-only too so downstream
|
|
181
|
+
// packages see the right dep versions when we simulate cascades.
|
|
182
|
+
localPackage.packageJson.version = semver_1.default.inc(currentVersion, effectiveBump);
|
|
183
|
+
sourceNote = ownBump
|
|
184
|
+
? cascadeBump
|
|
185
|
+
? "own+cascade, own=".concat(ownBump)
|
|
186
|
+
: "own=".concat(ownBump)
|
|
187
|
+
: 'dep cascade';
|
|
188
|
+
planPrefix = planOnly ? 'would bump' : 'bumping';
|
|
153
189
|
logger.info({
|
|
154
|
-
message: "(".concat(cw.color(packageName), ")
|
|
190
|
+
message: "(".concat(cw.color(packageName), ") ").concat(planPrefix, " version (").concat(effectiveBump, "; ").concat(sourceNote, ") from ").concat(currentVersion, " -> ").concat(localPackage.packageJson.version),
|
|
155
191
|
});
|
|
156
|
-
return [3 /*break*/, 13];
|
|
157
192
|
}
|
|
158
|
-
|
|
159
|
-
|
|
193
|
+
// Fixed-version workspace tracking: record even in plan-only so we can
|
|
194
|
+
// report the would-be synced version below. This is metadata only — the
|
|
195
|
+
// actual sync (disk write, commit, push) is gated by the planOnly /
|
|
196
|
+
// dryRun guards after the loop.
|
|
197
|
+
if (isInFixedVersionWorkspace(localPackage) && localPackage.workspace) {
|
|
198
|
+
fixedVersionWorkspacesToVersion[localPackage.workspace.path] = true;
|
|
199
|
+
}
|
|
200
|
+
if (planOnly) {
|
|
201
|
+
// Plan-only: skip the disk write, the build/test, publish, push/tag,
|
|
202
|
+
// and the eventual syncFixedVersionWorkspaces pass. The in-memory
|
|
203
|
+
// mutation above is enough to keep the simulated cascade correct for
|
|
204
|
+
// the remaining packages in this loop.
|
|
205
|
+
return [3 /*break*/, 16];
|
|
206
|
+
}
|
|
207
|
+
if (!(effectiveBump || dependenciesChanged)) return [3 /*break*/, 11];
|
|
208
|
+
return [4 /*yield*/, util_node_1.Fs.writeFiles([
|
|
209
|
+
{ path: localPackage.filePath, content: JSON.stringify(localPackage.packageJson, null, 2) },
|
|
210
|
+
])];
|
|
160
211
|
case 10:
|
|
161
212
|
_b.sent();
|
|
162
213
|
_b.label = 11;
|
|
163
|
-
case 11: return [4 /*yield*/,
|
|
214
|
+
case 11: return [4 /*yield*/, buildAndTest(localPackage)];
|
|
164
215
|
case 12:
|
|
165
216
|
_b.sent();
|
|
166
|
-
|
|
217
|
+
if (isInFixedVersionWorkspace(localPackage) && localPackage.workspace) {
|
|
218
|
+
logger.info({
|
|
219
|
+
message: "(".concat(cw.color(packageName), ") skipping version push for package in a fixed-version workspace"),
|
|
220
|
+
});
|
|
221
|
+
return [3 /*break*/, 16];
|
|
222
|
+
}
|
|
223
|
+
if (!shouldPublishPackage(localPackage)) return [3 /*break*/, 14];
|
|
224
|
+
return [4 /*yield*/, publish(localPackage)];
|
|
167
225
|
case 13:
|
|
226
|
+
_b.sent();
|
|
227
|
+
_b.label = 14;
|
|
228
|
+
case 14: return [4 /*yield*/, pushAndTag(localPackage)];
|
|
229
|
+
case 15:
|
|
230
|
+
_b.sent();
|
|
231
|
+
_b.label = 16;
|
|
232
|
+
case 16:
|
|
168
233
|
_i++;
|
|
169
|
-
return [3 /*break*/,
|
|
170
|
-
case
|
|
234
|
+
return [3 /*break*/, 8];
|
|
235
|
+
case 17:
|
|
236
|
+
if (!planOnly) return [3 /*break*/, 19];
|
|
237
|
+
return [4 /*yield*/, logPlannedFixedVersionSyncs(Object.keys(fixedVersionWorkspacesToVersion), packageMap, workspaceToPackageMap)];
|
|
238
|
+
case 18:
|
|
239
|
+
_b.sent();
|
|
240
|
+
logger.info({ message: "> Plan-only: skipping fixed-version sync, metarepo push, and symlink refresh" });
|
|
241
|
+
logger.info({ message: "> Finished versioning workspace (".concat(workspacePath, ") [plan-only]") });
|
|
242
|
+
return [2 /*return*/];
|
|
243
|
+
case 19:
|
|
171
244
|
pushWithoutSync = true;
|
|
172
245
|
return [4 /*yield*/, syncFixedVersionWorkspaces(Object.keys(fixedVersionWorkspacesToVersion), packageMap, workspaceToPackageMap, pushWithoutSync)];
|
|
173
|
-
case
|
|
246
|
+
case 20:
|
|
174
247
|
_b.sent();
|
|
175
248
|
return [4 /*yield*/, pushMetarepos(workspacePath, workspaceRootDirty)];
|
|
176
|
-
case
|
|
249
|
+
case 21:
|
|
177
250
|
_b.sent();
|
|
178
251
|
return [4 /*yield*/, symlinkWorkspace(workspacePath, filteredPackageNames, packageMap)];
|
|
179
|
-
case
|
|
252
|
+
case 22:
|
|
180
253
|
_b.sent();
|
|
181
254
|
logger.info({ message: "> Finished versioning workspace (".concat(workspacePath, ")") });
|
|
182
255
|
return [2 /*return*/];
|
|
@@ -197,6 +270,33 @@ function isDryRun() {
|
|
|
197
270
|
}
|
|
198
271
|
return false;
|
|
199
272
|
}
|
|
273
|
+
/**
|
|
274
|
+
* Plan-only mode: the strictest possible preview. Runs Phase 0 (commit-bump
|
|
275
|
+
* scan) and Phase 1 (commit-leaf detection) so you can see which packages
|
|
276
|
+
* would be published and at what level, then simulates the topo-ordered
|
|
277
|
+
* cascade in memory (for correct would-be bump reporting) but:
|
|
278
|
+
*
|
|
279
|
+
* - does NOT write any package.json or lerna.json to disk
|
|
280
|
+
* - does NOT run `npm install` / lint / build / test
|
|
281
|
+
* - does NOT publish, commit, push, tag, or refresh symlinks
|
|
282
|
+
*
|
|
283
|
+
* Intended for a fast preview of "what will this release do?" Implies
|
|
284
|
+
* dry-run for the few checks that need a binary answer (e.g. `publish`'s
|
|
285
|
+
* own dry-run guard), but the stricter plan-only guards in the main loop
|
|
286
|
+
* mean those code paths never execute in the first place.
|
|
287
|
+
*/
|
|
288
|
+
function isPlanOnly() {
|
|
289
|
+
var _a;
|
|
290
|
+
var args = process.argv.slice(2);
|
|
291
|
+
if (args.includes('--plan-only') || args.includes('--planonly') || args.includes('--plan')) {
|
|
292
|
+
return true;
|
|
293
|
+
}
|
|
294
|
+
var envFlag = (_a = process.env.VERSION_WORKSPACE_PLAN_ONLY) !== null && _a !== void 0 ? _a : process.env.PLAN_ONLY;
|
|
295
|
+
if (envFlag) {
|
|
296
|
+
return envFlag === 'true' || envFlag === '1';
|
|
297
|
+
}
|
|
298
|
+
return false;
|
|
299
|
+
}
|
|
200
300
|
function isInFixedVersionWorkspace(localPackage) {
|
|
201
301
|
return (localPackage.workspace &&
|
|
202
302
|
localPackage.workspace.lernaJson &&
|
|
@@ -277,60 +377,117 @@ function pullWorkspace(workspacePath, skipRootRepo) {
|
|
|
277
377
|
});
|
|
278
378
|
});
|
|
279
379
|
}
|
|
280
|
-
|
|
281
|
-
|
|
380
|
+
/**
|
|
381
|
+
* Rewrite `localPackage`'s dependency-version fields in package.json to match
|
|
382
|
+
* the current in-memory versions of its workspace-local deps. Returns true if
|
|
383
|
+
* any dep version was rewritten (i.e. a dep's already-bumped version now
|
|
384
|
+
* differs from what this package.json records). Pure rewrite — does NOT bump
|
|
385
|
+
* `localPackage`'s own version; the caller decides that by combining this
|
|
386
|
+
* result with the commit-scan map (see `versionWorkspace`).
|
|
387
|
+
*
|
|
388
|
+
* Writing the package.json to disk is left to the caller too, so we can apply
|
|
389
|
+
* the own-version bump in the same write.
|
|
390
|
+
*/
|
|
391
|
+
function applyDependencyVersionRewrites(localPackage, packageMap, packageGraph) {
|
|
392
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
393
|
+
var localDependencies, dependenciesChanged, _i, localDependencies_1, localDependency, localDependencyPackage, localDependencyVersion, currentDependencyVersion, newDependencyVersion;
|
|
394
|
+
return __generator(this, function (_a) {
|
|
395
|
+
localDependencies = packageGraph.successors(localPackage.name);
|
|
396
|
+
if (!localDependencies || localDependencies.length == 0) {
|
|
397
|
+
return [2 /*return*/, false];
|
|
398
|
+
}
|
|
399
|
+
dependenciesChanged = false;
|
|
400
|
+
for (_i = 0, localDependencies_1 = localDependencies; _i < localDependencies_1.length; _i++) {
|
|
401
|
+
localDependency = localDependencies_1[_i];
|
|
402
|
+
localDependencyPackage = packageMap[localDependency];
|
|
403
|
+
localDependencyVersion = localDependencyPackage.packageJson.version;
|
|
404
|
+
currentDependencyVersion = getDependencyVersion(localDependency, localPackage);
|
|
405
|
+
if (!currentDependencyVersion) {
|
|
406
|
+
throw new Error("Package (".concat(cw.color(localPackage.name), ") has dependency on ").concat(localDependency, ", but cannot find version in ").concat(cw.color(localPackage.name), "'s package.json"));
|
|
407
|
+
}
|
|
408
|
+
if (currentDependencyVersion.isLocalPath) {
|
|
409
|
+
continue;
|
|
410
|
+
}
|
|
411
|
+
if ((currentDependencyVersion === null || currentDependencyVersion === void 0 ? void 0 : currentDependencyVersion.version) == localDependencyVersion) {
|
|
412
|
+
continue;
|
|
413
|
+
}
|
|
414
|
+
newDependencyVersion = {
|
|
415
|
+
prefix: currentDependencyVersion.prefix,
|
|
416
|
+
version: localDependencyVersion,
|
|
417
|
+
};
|
|
418
|
+
setDependencyVersion(localDependency, currentDependencyVersion, newDependencyVersion, localPackage);
|
|
419
|
+
dependenciesChanged = true;
|
|
420
|
+
}
|
|
421
|
+
return [2 /*return*/, dependenciesChanged];
|
|
422
|
+
});
|
|
423
|
+
});
|
|
424
|
+
}
|
|
425
|
+
/**
|
|
426
|
+
* Up-front scan: for each candidate package, classify the unpushed commits
|
|
427
|
+
* in its own git repo into a semver bump level. Returns a map (only contains
|
|
428
|
+
* entries for packages with classifiable unpushed commits). Packages with no
|
|
429
|
+
* unpushed commits or missing upstream branches are absent from the map.
|
|
430
|
+
*
|
|
431
|
+
* This is the input to `computeCommitLeaves` and to the per-package bump-level
|
|
432
|
+
* combine in the main loop.
|
|
433
|
+
*/
|
|
434
|
+
function scanCommitBumps(packageNames, packageMap) {
|
|
282
435
|
return __awaiter(this, void 0, void 0, function () {
|
|
283
|
-
var
|
|
436
|
+
var result, _i, packageNames_1, packageName, localPackage, packageDir, bump;
|
|
284
437
|
return __generator(this, function (_a) {
|
|
285
438
|
switch (_a.label) {
|
|
286
439
|
case 0:
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
}
|
|
291
|
-
dependenciesChanged = false;
|
|
292
|
-
for (_i = 0, localDependencies_1 = localDependencies; _i < localDependencies_1.length; _i++) {
|
|
293
|
-
localDependency = localDependencies_1[_i];
|
|
294
|
-
localDependencyPackage = packageMap[localDependency];
|
|
295
|
-
localDependencyVersion = localDependencyPackage.packageJson.version;
|
|
296
|
-
currentDependencyVersion = getDependencyVersion(localDependency, localPackage);
|
|
297
|
-
if (!currentDependencyVersion) {
|
|
298
|
-
throw new Error("Package (".concat(cw.color(localPackage.name), ") has dependency on ").concat(localDependency, ", but cannot find version in ").concat(cw.color(localPackage.name), "'s package.json"));
|
|
299
|
-
}
|
|
300
|
-
if (currentDependencyVersion.isLocalPath) {
|
|
301
|
-
continue;
|
|
302
|
-
}
|
|
303
|
-
if ((currentDependencyVersion === null || currentDependencyVersion === void 0 ? void 0 : currentDependencyVersion.version) == localDependencyVersion) {
|
|
304
|
-
continue;
|
|
305
|
-
}
|
|
306
|
-
newDependencyVersion = {
|
|
307
|
-
prefix: currentDependencyVersion.prefix,
|
|
308
|
-
version: localDependencyVersion,
|
|
309
|
-
};
|
|
310
|
-
setDependencyVersion(localDependency, currentDependencyVersion, newDependencyVersion, localPackage);
|
|
311
|
-
dependenciesChanged = true;
|
|
312
|
-
}
|
|
313
|
-
if (!dependenciesChanged) return [3 /*break*/, 4];
|
|
314
|
-
if (!!skipBumpingPackageVersion) return [3 /*break*/, 2];
|
|
315
|
-
currentVersion = localPackage.packageJson.version;
|
|
316
|
-
packageDir = path.dirname(localPackage.filePath);
|
|
317
|
-
return [4 /*yield*/, hasFeatureCommits(packageDir)];
|
|
440
|
+
result = new Map();
|
|
441
|
+
_i = 0, packageNames_1 = packageNames;
|
|
442
|
+
_a.label = 1;
|
|
318
443
|
case 1:
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
444
|
+
if (!(_i < packageNames_1.length)) return [3 /*break*/, 4];
|
|
445
|
+
packageName = packageNames_1[_i];
|
|
446
|
+
localPackage = packageMap[packageName];
|
|
447
|
+
packageDir = path.dirname(localPackage.filePath);
|
|
448
|
+
return [4 /*yield*/, classifyUnpushedCommits(packageDir)];
|
|
449
|
+
case 2:
|
|
450
|
+
bump = _a.sent();
|
|
451
|
+
if (bump) {
|
|
452
|
+
result.set(packageName, bump);
|
|
453
|
+
}
|
|
454
|
+
_a.label = 3;
|
|
326
455
|
case 3:
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
case 4: return [2 /*return*/,
|
|
456
|
+
_i++;
|
|
457
|
+
return [3 /*break*/, 1];
|
|
458
|
+
case 4: return [2 /*return*/, result];
|
|
330
459
|
}
|
|
331
460
|
});
|
|
332
461
|
});
|
|
333
462
|
}
|
|
463
|
+
/**
|
|
464
|
+
* A package is a "commit-leaf" if it has unpushed commits (i.e. appears in
|
|
465
|
+
* `scanMap`) AND none of its direct workspace-local deps have unpushed
|
|
466
|
+
* commits. These are the true sources of change — they trigger the entire
|
|
467
|
+
* cascade, so they must be versioned + published first.
|
|
468
|
+
*
|
|
469
|
+
* Direct deps only (not transitive): the topo-ordered main loop handles
|
|
470
|
+
* transitivity naturally by cascading dep-version rewrites forward.
|
|
471
|
+
*
|
|
472
|
+
* Returns leaves in topo order (deps-first) so independent leaves publish
|
|
473
|
+
* in a deterministic, dependency-respecting sequence.
|
|
474
|
+
*/
|
|
475
|
+
function computeCommitLeaves(scanMap, packageGraph, sortedPackageNames) {
|
|
476
|
+
var _a;
|
|
477
|
+
var leaves = [];
|
|
478
|
+
for (var _i = 0, sortedPackageNames_1 = sortedPackageNames; _i < sortedPackageNames_1.length; _i++) {
|
|
479
|
+
var packageName = sortedPackageNames_1[_i];
|
|
480
|
+
if (!scanMap.has(packageName)) {
|
|
481
|
+
continue;
|
|
482
|
+
}
|
|
483
|
+
var directDeps = (_a = packageGraph.successors(packageName)) !== null && _a !== void 0 ? _a : [];
|
|
484
|
+
var anyDepHasCommits = directDeps.some(function (dep) { return scanMap.has(dep); });
|
|
485
|
+
if (!anyDepHasCommits) {
|
|
486
|
+
leaves.push(packageName);
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
return leaves;
|
|
490
|
+
}
|
|
334
491
|
function getDependencyVersion(dependencyPackageName, localPackage) {
|
|
335
492
|
var currentRawDependencyVersion = localPackage.packageJson.dependencies
|
|
336
493
|
? localPackage.packageJson.dependencies[dependencyPackageName]
|
|
@@ -412,27 +569,74 @@ function syncFixedVersionWorkspaces(fixedVersionWorkspacePaths, packageMap, work
|
|
|
412
569
|
});
|
|
413
570
|
});
|
|
414
571
|
}
|
|
572
|
+
/**
|
|
573
|
+
* Plan-only companion to `syncFixedVersionWorkspaces`: for each fixed-version
|
|
574
|
+
* workspace that would be touched, classify unpushed commits at the workspace
|
|
575
|
+
* root and log the version it would sync to. No disk writes, no git ops.
|
|
576
|
+
*/
|
|
577
|
+
function logPlannedFixedVersionSyncs(fixedVersionWorkspacePaths, packageMap, workspaceToPackageMap) {
|
|
578
|
+
var _a, _b, _c;
|
|
579
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
580
|
+
var _i, fixedVersionWorkspacePaths_2, workspacePath, workspacePackageNames, anyPackageInWs, lernaJson, repoName, bump, wouldBeVersion;
|
|
581
|
+
return __generator(this, function (_d) {
|
|
582
|
+
switch (_d.label) {
|
|
583
|
+
case 0:
|
|
584
|
+
if (fixedVersionWorkspacePaths.length === 0) {
|
|
585
|
+
return [2 /*return*/];
|
|
586
|
+
}
|
|
587
|
+
logger.info({ message: "> Fixed-version workspaces that would sync:" });
|
|
588
|
+
_i = 0, fixedVersionWorkspacePaths_2 = fixedVersionWorkspacePaths;
|
|
589
|
+
_d.label = 1;
|
|
590
|
+
case 1:
|
|
591
|
+
if (!(_i < fixedVersionWorkspacePaths_2.length)) return [3 /*break*/, 4];
|
|
592
|
+
workspacePath = fixedVersionWorkspacePaths_2[_i];
|
|
593
|
+
workspacePackageNames = (_a = workspaceToPackageMap[workspacePath]) !== null && _a !== void 0 ? _a : [];
|
|
594
|
+
anyPackageInWs = workspacePackageNames.map(function (n) { return packageMap[n]; }).find(function (p) { var _a; return (_a = p === null || p === void 0 ? void 0 : p.workspace) === null || _a === void 0 ? void 0 : _a.lernaJson; });
|
|
595
|
+
lernaJson = (_b = anyPackageInWs === null || anyPackageInWs === void 0 ? void 0 : anyPackageInWs.workspace) === null || _b === void 0 ? void 0 : _b.lernaJson;
|
|
596
|
+
repoName = path.basename(workspacePath.endsWith(path.sep) ? workspacePath.slice(0, -1) : workspacePath);
|
|
597
|
+
if (!lernaJson) {
|
|
598
|
+
logger.info({
|
|
599
|
+
message: " (".concat(cw.color(repoName), ") would sync fixed-version workspace (lerna.json not available; bump level unknown)"),
|
|
600
|
+
});
|
|
601
|
+
return [3 /*break*/, 3];
|
|
602
|
+
}
|
|
603
|
+
return [4 /*yield*/, classifyUnpushedCommits(workspacePath)];
|
|
604
|
+
case 2:
|
|
605
|
+
bump = (_c = (_d.sent())) !== null && _c !== void 0 ? _c : 'patch';
|
|
606
|
+
wouldBeVersion = semver_1.default.inc(lernaJson.version, bump);
|
|
607
|
+
logger.info({
|
|
608
|
+
message: " (".concat(cw.color(repoName), ") would sync fixed-version workspace ").concat(lernaJson.version, " -> ").concat(wouldBeVersion, " (").concat(bump, ")"),
|
|
609
|
+
});
|
|
610
|
+
_d.label = 3;
|
|
611
|
+
case 3:
|
|
612
|
+
_i++;
|
|
613
|
+
return [3 /*break*/, 1];
|
|
614
|
+
case 4: return [2 /*return*/];
|
|
615
|
+
}
|
|
616
|
+
});
|
|
617
|
+
});
|
|
618
|
+
}
|
|
415
619
|
function syncFixedVersions(workspacePath, localPackages) {
|
|
416
|
-
var _a;
|
|
620
|
+
var _a, _b;
|
|
417
621
|
return __awaiter(this, void 0, void 0, function () {
|
|
418
622
|
var lernaJson, bump, highestVersion, syncedFixedVersions, _i, localPackages_1, localPackage, currentVersion, lernaJsonPath;
|
|
419
|
-
return __generator(this, function (
|
|
420
|
-
switch (
|
|
623
|
+
return __generator(this, function (_c) {
|
|
624
|
+
switch (_c.label) {
|
|
421
625
|
case 0:
|
|
422
626
|
lernaJson = (_a = localPackages[0].workspace) === null || _a === void 0 ? void 0 : _a.lernaJson;
|
|
423
627
|
if (!lernaJson) {
|
|
424
628
|
throw new Error("Cannot find lerna.json for workspace: ".concat(workspacePath));
|
|
425
629
|
}
|
|
426
|
-
return [4 /*yield*/,
|
|
630
|
+
return [4 /*yield*/, classifyUnpushedCommits(workspacePath)];
|
|
427
631
|
case 1:
|
|
428
|
-
bump = (_b.sent()) ?
|
|
632
|
+
bump = (_b = (_c.sent())) !== null && _b !== void 0 ? _b : 'patch';
|
|
429
633
|
highestVersion = semver_1.default.inc(lernaJson.version, bump);
|
|
430
634
|
if (!highestVersion) {
|
|
431
635
|
throw new Error("Lerna version not specified for workspace: ".concat(workspacePath));
|
|
432
636
|
}
|
|
433
637
|
syncedFixedVersions = false;
|
|
434
638
|
_i = 0, localPackages_1 = localPackages;
|
|
435
|
-
|
|
639
|
+
_c.label = 2;
|
|
436
640
|
case 2:
|
|
437
641
|
if (!(_i < localPackages_1.length)) return [3 /*break*/, 5];
|
|
438
642
|
localPackage = localPackages_1[_i];
|
|
@@ -446,9 +650,9 @@ function syncFixedVersions(workspacePath, localPackages) {
|
|
|
446
650
|
});
|
|
447
651
|
return [4 /*yield*/, util_node_1.Fs.writeFiles([{ path: localPackage.filePath, content: JSON.stringify(localPackage.packageJson, null, 2) }])];
|
|
448
652
|
case 3:
|
|
449
|
-
|
|
653
|
+
_c.sent();
|
|
450
654
|
syncedFixedVersions = true;
|
|
451
|
-
|
|
655
|
+
_c.label = 4;
|
|
452
656
|
case 4:
|
|
453
657
|
_i++;
|
|
454
658
|
return [3 /*break*/, 2];
|
|
@@ -458,8 +662,8 @@ function syncFixedVersions(workspacePath, localPackages) {
|
|
|
458
662
|
lernaJson.version = highestVersion;
|
|
459
663
|
return [4 /*yield*/, util_node_1.Fs.writeFiles([{ path: lernaJsonPath, content: JSON.stringify(lernaJson, null, 2) }])];
|
|
460
664
|
case 6:
|
|
461
|
-
|
|
462
|
-
|
|
665
|
+
_c.sent();
|
|
666
|
+
_c.label = 7;
|
|
463
667
|
case 7: return [2 /*return*/, syncedFixedVersions ? highestVersion : false];
|
|
464
668
|
}
|
|
465
669
|
});
|
|
@@ -640,6 +844,12 @@ function pushAndTagFixedVersionRepo(dir, version, skipTagging, skipCi) {
|
|
|
640
844
|
return [2 /*return*/, undefined];
|
|
641
845
|
}
|
|
642
846
|
repoName = path.basename(dir.endsWith(path.sep) ? dir.slice(0, -1) : dir);
|
|
847
|
+
return [4 /*yield*/, hasPendingChanges(dir)];
|
|
848
|
+
case 1:
|
|
849
|
+
if (!!(_a.sent())) return [3 /*break*/, 2];
|
|
850
|
+
logger.info({ message: "(".concat(cw.color(repoName), ") fixed-version repo has no pending changes, skipping commit/push") });
|
|
851
|
+
return [3 /*break*/, 6];
|
|
852
|
+
case 2:
|
|
643
853
|
if (version) {
|
|
644
854
|
logger.info({ message: "(".concat(cw.color(repoName), ") pushing latest version (").concat(version, ")") });
|
|
645
855
|
}
|
|
@@ -647,13 +857,13 @@ function pushAndTagFixedVersionRepo(dir, version, skipTagging, skipCi) {
|
|
|
647
857
|
logger.info({ message: "(".concat(cw.color(repoName), ") pushing dependency bumps") });
|
|
648
858
|
}
|
|
649
859
|
return [4 /*yield*/, (0, util_node_1.cmd)('git', ['add', '.'], { cwd: dir }, { logPrefix: "[".concat(cw.color(repoName), "] ") })];
|
|
650
|
-
case
|
|
860
|
+
case 3:
|
|
651
861
|
_a.sent();
|
|
652
862
|
return [4 /*yield*/, (0, util_node_1.cmd)('git', ['commit', '-m', "chore(version): bumping dependency versions".concat(skipCi ? ' [skip ci]' : '')], { cwd: dir }, { logPrefix: "[".concat(cw.color(repoName), "] ") })];
|
|
653
|
-
case
|
|
863
|
+
case 4:
|
|
654
864
|
_a.sent();
|
|
655
865
|
return [4 /*yield*/, (0, util_node_1.cmd)('git', ['push'], { cwd: dir }, { logPrefix: "[".concat(cw.color(repoName), "] ") })];
|
|
656
|
-
case
|
|
866
|
+
case 5:
|
|
657
867
|
_a.sent();
|
|
658
868
|
if (version) {
|
|
659
869
|
logger.info({ message: "(".concat(cw.color(repoName), ") pushed latest version (").concat(version, ")") });
|
|
@@ -661,25 +871,26 @@ function pushAndTagFixedVersionRepo(dir, version, skipTagging, skipCi) {
|
|
|
661
871
|
else {
|
|
662
872
|
logger.info({ message: "(".concat(cw.color(repoName), ") pushed dependency bumps") });
|
|
663
873
|
}
|
|
664
|
-
|
|
665
|
-
case 4
|
|
874
|
+
_a.label = 6;
|
|
875
|
+
case 6: return [4 /*yield*/, getLatestCommitSha(dir)];
|
|
876
|
+
case 7:
|
|
666
877
|
latestCommitSha = _a.sent();
|
|
667
878
|
return [4 /*yield*/, getRepoInfo(dir)];
|
|
668
|
-
case
|
|
879
|
+
case 8:
|
|
669
880
|
repoInfo = _a.sent();
|
|
670
881
|
commit = __assign({ sha: latestCommitSha }, repoInfo);
|
|
671
|
-
if (!!skipTagging) return [3 /*break*/,
|
|
882
|
+
if (!!skipTagging) return [3 /*break*/, 11];
|
|
672
883
|
tagName = "v".concat(version);
|
|
673
884
|
logger.info({ message: "(".concat(cw.color(repoName), ") pushing tag (").concat(tagName, ")") });
|
|
674
885
|
return [4 /*yield*/, (0, util_node_1.cmd)('git', ['tag', '-a', tagName, '-m', "Release ".concat(tagName)], { cwd: dir }, { logPrefix: "[".concat(cw.color(repoName), "] ") })];
|
|
675
|
-
case
|
|
886
|
+
case 9:
|
|
676
887
|
_a.sent();
|
|
677
888
|
return [4 /*yield*/, (0, util_node_1.cmd)('git', ['push', 'origin', tagName], { cwd: dir }, { logPrefix: "[".concat(cw.color(repoName), "] ") })];
|
|
678
|
-
case
|
|
889
|
+
case 10:
|
|
679
890
|
_a.sent();
|
|
680
891
|
logger.info({ message: "(".concat(cw.color(repoName), ") pushed tag (").concat(tagName, ")") });
|
|
681
|
-
_a.label =
|
|
682
|
-
case
|
|
892
|
+
_a.label = 11;
|
|
893
|
+
case 11: return [2 /*return*/, commit];
|
|
683
894
|
}
|
|
684
895
|
});
|
|
685
896
|
});
|
|
@@ -732,18 +943,28 @@ function pushMetarepo(dir) {
|
|
|
732
943
|
return [2 /*return*/];
|
|
733
944
|
}
|
|
734
945
|
repoName = path.basename(dir.endsWith(path.sep) ? dir.slice(0, -1) : dir);
|
|
946
|
+
return [4 /*yield*/, hasPendingChanges(dir)];
|
|
947
|
+
case 1:
|
|
948
|
+
// Nothing to commit means `git commit` would exit 1 and blow up the whole
|
|
949
|
+
// run. This happens routinely — e.g. the proteinjs metarepo has no pending
|
|
950
|
+
// submodule pointer bumps on a run that only touched unrelated packages.
|
|
951
|
+
// Silently skip those repos instead of aborting.
|
|
952
|
+
if (!(_a.sent())) {
|
|
953
|
+
logger.info({ message: "(".concat(cw.color(repoName), ") metarepo has no pending changes, skipping commit/push") });
|
|
954
|
+
return [2 /*return*/];
|
|
955
|
+
}
|
|
735
956
|
logger.info({ message: "(".concat(cw.color(repoName), ") pushing metarepo (").concat(dir, ")") });
|
|
736
957
|
return [4 /*yield*/, (0, util_node_1.cmd)('git', ['add', '.'], { cwd: dir }, { logPrefix: "[".concat(cw.color(repoName), "] ") })];
|
|
737
|
-
case
|
|
958
|
+
case 2:
|
|
738
959
|
_a.sent();
|
|
739
960
|
return [4 /*yield*/, (0, util_node_1.cmd)('git', ['commit', '-m', "chore(version): bumping submodule versions [skip ci]"], { cwd: dir }, { logPrefix: "[".concat(cw.color(repoName), "] ") })];
|
|
740
|
-
case
|
|
961
|
+
case 3:
|
|
741
962
|
_a.sent();
|
|
742
963
|
return [4 /*yield*/, (0, util_node_1.cmd)('git', ['pull'], { cwd: dir }, { logPrefix: "[".concat(cw.color(repoName), "] ") })];
|
|
743
|
-
case
|
|
964
|
+
case 4:
|
|
744
965
|
_a.sent();
|
|
745
966
|
return [4 /*yield*/, (0, util_node_1.cmd)('git', ['push'], { cwd: dir }, { logPrefix: "[".concat(cw.color(repoName), "] ") })];
|
|
746
|
-
case
|
|
967
|
+
case 5:
|
|
747
968
|
_a.sent();
|
|
748
969
|
logger.info({ message: "(".concat(cw.color(repoName), ") pushed metarepo (").concat(dir, ")") });
|
|
749
970
|
return [2 /*return*/];
|
|
@@ -753,16 +974,16 @@ function pushMetarepo(dir) {
|
|
|
753
974
|
}
|
|
754
975
|
function symlinkWorkspace(workspacePath, packageNames, packageMap) {
|
|
755
976
|
return __awaiter(this, void 0, void 0, function () {
|
|
756
|
-
var _i,
|
|
977
|
+
var _i, packageNames_2, packageName, localPackage;
|
|
757
978
|
return __generator(this, function (_a) {
|
|
758
979
|
switch (_a.label) {
|
|
759
980
|
case 0:
|
|
760
981
|
logger.info({ message: "> Symlinking local dependencies in workspace (".concat(workspacePath, ")") });
|
|
761
|
-
_i = 0,
|
|
982
|
+
_i = 0, packageNames_2 = packageNames;
|
|
762
983
|
_a.label = 1;
|
|
763
984
|
case 1:
|
|
764
|
-
if (!(_i <
|
|
765
|
-
packageName =
|
|
985
|
+
if (!(_i < packageNames_2.length)) return [3 /*break*/, 4];
|
|
986
|
+
packageName = packageNames_2[_i];
|
|
766
987
|
localPackage = packageMap[packageName];
|
|
767
988
|
return [4 /*yield*/, util_node_1.PackageUtil.symlinkDependencies(localPackage, packageMap)];
|
|
768
989
|
case 2:
|
|
@@ -968,22 +1189,108 @@ function isRepoDirty(dir) {
|
|
|
968
1189
|
});
|
|
969
1190
|
});
|
|
970
1191
|
}
|
|
971
|
-
|
|
1192
|
+
/**
|
|
1193
|
+
* True iff there is anything staged or unstaged that a `git commit` would
|
|
1194
|
+
* capture. Used to guard commit/push steps in the metarepo and fixed-version
|
|
1195
|
+
* flows — without this, a repo with no pending changes (e.g. a parent
|
|
1196
|
+
* metarepo whose submodule pointers were already bumped in a prior run, or
|
|
1197
|
+
* the proteinjs metarepo on a run that only bumped unrelated packages) will
|
|
1198
|
+
* fail `git commit` with "nothing to commit, working tree clean" and the
|
|
1199
|
+
* entire version-workspace run throws.
|
|
1200
|
+
*/
|
|
1201
|
+
function hasPendingChanges(dir) {
|
|
972
1202
|
return __awaiter(this, void 0, void 0, function () {
|
|
973
1203
|
return __generator(this, function (_a) {
|
|
974
1204
|
return [2 /*return*/, new Promise(function (resolve) {
|
|
975
|
-
(0, child_process_1.exec)('git
|
|
976
|
-
if (error
|
|
1205
|
+
(0, child_process_1.exec)('git status --porcelain', { cwd: dir }, function (error, stdout) {
|
|
1206
|
+
if (error) {
|
|
977
1207
|
resolve(false);
|
|
978
1208
|
return;
|
|
979
1209
|
}
|
|
980
|
-
|
|
981
|
-
|
|
1210
|
+
resolve(stdout.trim().length > 0);
|
|
1211
|
+
});
|
|
1212
|
+
})];
|
|
1213
|
+
});
|
|
1214
|
+
});
|
|
1215
|
+
}
|
|
1216
|
+
var BUMP_ORDER = { patch: 1, minor: 2, major: 3 };
|
|
1217
|
+
function maxBump(a, b) {
|
|
1218
|
+
if (!a) {
|
|
1219
|
+
return b;
|
|
1220
|
+
}
|
|
1221
|
+
if (!b) {
|
|
1222
|
+
return a;
|
|
1223
|
+
}
|
|
1224
|
+
return BUMP_ORDER[a] >= BUMP_ORDER[b] ? a : b;
|
|
1225
|
+
}
|
|
1226
|
+
/**
|
|
1227
|
+
* Classify the net semver bump implied by the set of commits in HEAD that
|
|
1228
|
+
* aren't yet on the tracked upstream branch (`git log @{u}..HEAD`).
|
|
1229
|
+
*
|
|
1230
|
+
* - `major`: any commit declares `BREAKING CHANGE` in subject/body, or
|
|
1231
|
+
* uses the conventional-commits `!` marker (`feat!:`,
|
|
1232
|
+
* `fix(scope)!:`, etc.).
|
|
1233
|
+
* - `minor`: any commit is a `feat` (type, optionally scoped, no `!`).
|
|
1234
|
+
* - `patch`: any other commit is present (fix/chore/refactor/docs/…).
|
|
1235
|
+
* - `undefined`: no unpushed commits, OR no tracked upstream (the `@{u}`
|
|
1236
|
+
* shorthand errors silently — treated as "nothing to ship").
|
|
1237
|
+
*
|
|
1238
|
+
* Uses `%B` (full body) separated by a null byte so footer-style
|
|
1239
|
+
* `BREAKING CHANGE:` lines are visible. Shells out to `git` directly so we
|
|
1240
|
+
* don't depend on any repo state beyond a valid upstream ref.
|
|
1241
|
+
*/
|
|
1242
|
+
function classifyUnpushedCommits(dir) {
|
|
1243
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
1244
|
+
return __generator(this, function (_a) {
|
|
1245
|
+
return [2 /*return*/, new Promise(function (resolve) {
|
|
1246
|
+
// `\x00` as record separator; each record is the full commit message body.
|
|
1247
|
+
(0, child_process_1.exec)('git log @{u}..HEAD --format=%B%x00', { cwd: dir }, function (error, stdout) {
|
|
1248
|
+
if (error || !stdout.trim()) {
|
|
1249
|
+
resolve(undefined);
|
|
1250
|
+
return;
|
|
1251
|
+
}
|
|
1252
|
+
var commits = stdout.split('\x00').map(function (s) { return s.trim(); }).filter(function (s) { return s.length > 0; });
|
|
1253
|
+
var result;
|
|
1254
|
+
for (var _i = 0, commits_1 = commits; _i < commits_1.length; _i++) {
|
|
1255
|
+
var commit = commits_1[_i];
|
|
1256
|
+
result = maxBump(result, classifyCommitMessage(commit));
|
|
1257
|
+
if (result === 'major') {
|
|
1258
|
+
break;
|
|
1259
|
+
}
|
|
1260
|
+
}
|
|
1261
|
+
resolve(result);
|
|
982
1262
|
});
|
|
983
1263
|
})];
|
|
984
1264
|
});
|
|
985
1265
|
});
|
|
986
1266
|
}
|
|
1267
|
+
exports.classifyUnpushedCommits = classifyUnpushedCommits;
|
|
1268
|
+
/**
|
|
1269
|
+
* Classify a single commit message by conventional-commits rules. Exported
|
|
1270
|
+
* so unit tests can exercise it without spawning git.
|
|
1271
|
+
*/
|
|
1272
|
+
function classifyCommitMessage(message) {
|
|
1273
|
+
if (!message.trim()) {
|
|
1274
|
+
return undefined;
|
|
1275
|
+
}
|
|
1276
|
+
var _a = message.split('\n'), subject = _a[0], rest = _a.slice(1);
|
|
1277
|
+
var body = rest.join('\n');
|
|
1278
|
+
// Breaking-change markers. `type!:` / `type(scope)!:` on the subject,
|
|
1279
|
+
// or `BREAKING CHANGE:` / `BREAKING-CHANGE:` anywhere (subject or body/footer).
|
|
1280
|
+
if (/^\w+(\([^)]*\))?!:/.test(subject)) {
|
|
1281
|
+
return 'major';
|
|
1282
|
+
}
|
|
1283
|
+
if (/BREAKING[ -]CHANGE\s*:/i.test("".concat(subject, "\n").concat(body))) {
|
|
1284
|
+
return 'major';
|
|
1285
|
+
}
|
|
1286
|
+
// `feat` type (optionally scoped) on the subject → minor.
|
|
1287
|
+
if (/^feat(\([^)]*\))?:/i.test(subject)) {
|
|
1288
|
+
return 'minor';
|
|
1289
|
+
}
|
|
1290
|
+
// Any other commit counts as patch.
|
|
1291
|
+
return 'patch';
|
|
1292
|
+
}
|
|
1293
|
+
exports.classifyCommitMessage = classifyCommitMessage;
|
|
987
1294
|
function evictGitLocks(workspacePath) {
|
|
988
1295
|
return __awaiter(this, void 0, void 0, function () {
|
|
989
1296
|
var gitDir, lockFiles;
|