@edifice.io/cli 1.6.0-develop-docker.0 → 1.6.0-develop-b2school.11

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.
@@ -1,465 +1,445 @@
1
- // @ts-check
2
- // Originally ported to TS from https://github.com/remix-run/react-router/tree/main/scripts/{version,publish}.js
3
-
4
- import { parse as parseCommit } from "@commitlint/parse";
5
- import currentGitBranch from "current-git-branch";
6
- import { execSync } from "node:child_process";
7
- import path from "node:path";
8
- import * as semver from "semver";
9
- import { simpleGit } from "simple-git";
10
- import {
11
- capitalize,
12
- getSorterFn,
13
- readPackageJson,
14
- releaseCommitMsg,
15
- updatePackageJson,
16
- } from "./utilities.js";
17
-
18
- /**
19
- * Execute a script being published
20
- * @param {import('./index').Options} options
21
- * @returns {Promise<void>}
22
- */
23
- export const publish = async (options) => {
24
- const { branchConfigs, packages, rootDir, branch, tag, ghToken } = options;
25
-
26
- console.log({ branchConfigs, packages, rootDir, branch, tag, ghToken });
27
-
28
- const branchName = /** @type {string} */ (branch ?? currentGitBranch());
29
- const isMainBranch = branchName === "main";
30
- const npmTag = isMainBranch ? "latest" : branchName;
31
-
32
- /** @type {import('./index').BranchConfig | undefined} */
33
- const branchConfig = branchConfigs[branchName];
34
-
35
- if (!branchConfig) {
36
- throw new Error(`No publish config found for branch: ${branchName}`);
37
- }
38
-
39
- // Get tags
40
- /** @type {string[]} */
41
- const allTags = execSync("git tag").toString().split("\n");
42
-
43
- const semverTags = allTags.filter((t) => semver.valid(t)).reverse();
44
-
45
- const filteredTags = semverTags
46
- // Filter tags based on whether the branch is a release or pre-release
47
- .filter((t) => {
48
- const isPrereleaseTag = semver.prerelease(t) !== null;
49
- const prereleaseBranch = semver.prerelease(t)?.[0];
50
- // For prerelease branches, only include tags for that branch
51
- if (branchConfig.prerelease) {
52
- // If no tags exist for this prerelease branch, use main branch tags
53
- const branchTags = semverTags.filter((t) => {
54
- const tagPrerelease = semver.prerelease(t)?.[0];
55
- return tagPrerelease === branchName;
56
- });
57
- if (branchTags.length === 0) {
58
- return !isPrereleaseTag; // Use main branch tags
59
- }
60
- return isPrereleaseTag && prereleaseBranch === branchName;
61
- }
62
- // For main branch, exclude all prereleases
63
- return !isPrereleaseTag;
64
- })
65
- // sort by latest
66
- // @ts-ignore
67
- .sort(semver.compare);
68
-
69
- // Get the latest tag
70
- let latestTag = filteredTags.at(-1);
71
- let rangeFrom = latestTag;
72
-
73
- // If RELEASE_ALL is set via a commit subject or body, all packages will be
74
- // released regardless if they have changed files matching the package srcDir.
75
- let RELEASE_ALL = false;
76
-
77
- // Validate manual tag
78
- if (tag) {
79
- if (!semver.valid(tag)) {
80
- throw new Error(`tag '${tag}' is not a semantically valid version`);
81
- }
82
- if (!tag.startsWith("v")) {
83
- throw new Error(
84
- `tag must start with "v" (e.g. v0.0.0). You supplied ${tag}`,
85
- );
86
- }
87
- if (allTags.includes(tag)) {
88
- throw new Error(`tag ${tag} has already been released`);
89
- }
90
- }
91
-
92
- if (!latestTag || tag) {
93
- if (tag) {
94
- console.info(
95
- `Tag is set to ${tag}. This will force release all packages. Publishing...`,
96
- );
97
- RELEASE_ALL = true;
98
-
99
- // Is it the first release? Is it a major version?
100
- if (!latestTag || (semver.patch(tag) === 0 && semver.minor(tag) === 0)) {
101
- rangeFrom = "origin/main";
102
- latestTag = tag;
103
- }
104
- } else {
105
- throw new Error(
106
- "Could not find latest tag! To make a release tag of v0.0.1, run with TAG=v0.0.1",
107
- );
108
- }
109
- }
110
-
111
- console.info(`Git Range: ${rangeFrom}..HEAD`);
112
-
113
- const rawCommitsLog = (
114
- await simpleGit().log({ from: rangeFrom, to: "HEAD" })
115
- ).all.filter((c) => {
116
- const exclude = [
117
- c.message.startsWith("Merge branch "), // No merge commits
118
- c.message.includes("version & publish"), // No version bump commits
119
- c.message.startsWith(releaseCommitMsg("")), // No example update commits
120
- ].some(Boolean);
121
-
122
- return !exclude;
123
- });
124
-
125
- // /**
126
- // * Get the commits since the latest tag
127
- // * @type {import('./index.js').Commit[]}
128
- // */
129
- const commitsSinceLatestTag = await Promise.all(
130
- rawCommitsLog.map(async (c) => {
131
- const parsed = await parseCommit(c.message);
132
- return {
133
- hash: c.hash.substring(0, 7),
134
- body: c.body,
135
- subject: parsed.subject ?? "",
136
- author_name: c.author_name,
137
- author_email: c.author_email,
138
- type: parsed.type?.toLowerCase() ?? "other",
139
- scope: parsed.scope,
140
- };
141
- }),
142
- );
143
-
144
- console.info(
145
- `Parsing ${commitsSinceLatestTag.length} commits since ${rangeFrom}...`,
146
- );
147
-
148
- /**
149
- * Parses the commit messages, logs them, and determines the type of release needed
150
- * -1 means no release is necessary
151
- * 0 means patch release is necessary
152
- * 1 means minor release is necessary
153
- * 2 means major release is necessary
154
- * @type {number}
155
- */
156
- let recommendedReleaseLevel = commitsSinceLatestTag.reduce(
157
- (releaseLevel, commit) => {
158
- if (commit.type) {
159
- if (["fix", "refactor", "perf"].includes(commit.type)) {
160
- releaseLevel = Math.max(releaseLevel, 0);
161
- }
162
- if (["feat"].includes(commit.type)) {
163
- releaseLevel = Math.max(releaseLevel, 1);
164
- }
165
- if (commit.body.includes("BREAKING CHANGE")) {
166
- releaseLevel = Math.max(releaseLevel, 2);
167
- }
168
- if (
169
- commit.subject.includes("RELEASE_ALL") ||
170
- commit.body.includes("RELEASE_ALL")
171
- ) {
172
- RELEASE_ALL = true;
173
- }
174
- }
175
- return releaseLevel;
176
- },
177
- -1,
178
- );
179
-
180
- // If there is a breaking change and no manual tag is set, do not release
181
- /* if (recommendedReleaseLevel === 2 && !tag) {
182
- throw new Error(
183
- 'Major versions releases must be tagged and released manually.'
184
- );
185
- } */
186
-
187
- // If no release is semantically necessary and no manual tag is set, do not release
188
- if (recommendedReleaseLevel === -1 && !tag) {
189
- console.info(
190
- `There have been no changes since ${latestTag} that require a new version. You're good!`,
191
- );
192
- return;
193
- }
194
-
195
- // If no release is semantically necessary but a manual tag is set, do a patch release
196
- if (recommendedReleaseLevel === -1 && tag) {
197
- recommendedReleaseLevel = 0;
198
- }
199
-
200
- // const releaseType = branchConfig.prerelease
201
- // ? "prerelease"
202
- // : /** @type {const} */ ({ 0: "patch", 1: "minor", 2: "major" })[
203
- // recommendedReleaseLevel
204
- // ];
205
-
206
- const releaseType = /** @type {const} */ ({
207
- 0: "patch",
208
- 1: "minor",
209
- 2: "major",
210
- })[recommendedReleaseLevel];
211
-
212
- if (!releaseType) {
213
- throw new Error(`Invalid release level: ${recommendedReleaseLevel}`);
214
- }
215
-
216
- const version = tag
217
- ? semver.parse(tag)?.version
218
- : semver.inc(
219
- latestTag,
220
- branchConfig.prerelease ? `pre${releaseType}` : releaseType,
221
- npmTag,
222
- );
223
-
224
- if (!version) {
225
- throw new Error(
226
- [
227
- "Invalid version increment from semver.inc()",
228
- `- latestTag: ${latestTag}`,
229
- `- recommendedReleaseLevel: ${recommendedReleaseLevel}`,
230
- `- prerelease: ${branchConfig.prerelease}`,
231
- ].join("\n"),
232
- );
233
- }
234
-
235
- console.log(`Targeting version ${version}...`);
236
-
237
- /**
238
- * Uses git diff to determine which files have changed since the latest tag
239
- * @type {string[]}
240
- */
241
- const changedFiles = tag
242
- ? []
243
- : execSync(`git diff ${latestTag} --name-only`)
244
- .toString()
245
- .split("\n")
246
- .filter(Boolean);
247
-
248
- /** Uses packages and changedFiles to determine which packages have changed */
249
- const changedPackages = RELEASE_ALL
250
- ? packages
251
- : packages.filter((pkg) => {
252
- const changed = changedFiles.some(
253
- (file) =>
254
- file.startsWith(path.join(pkg.packageDir, "src")) ||
255
- // check if any files in the assets directory were modified
256
- file.startsWith(path.join(pkg.packageDir, "assets")) ||
257
- file.startsWith(path.join(pkg.packageDir, "package.json")),
258
- );
259
- return changed;
260
- });
261
-
262
- // If a package has a dependency that has been updated, we need to update the
263
- // package that depends on it as well.
264
- // run this multiple times so that dependencies of dependencies are also included
265
- for (let runs = 0; runs < 3; runs++) {
266
- for (const pkg of packages) {
267
- const packageJson = await readPackageJson(
268
- path.resolve(rootDir, pkg.packageDir, "package.json"),
269
- );
270
- const allDependencies = Object.keys(
271
- Object.assign(
272
- {},
273
- packageJson.dependencies ?? {},
274
- packageJson.peerDependencies ?? {},
275
- ),
276
- );
277
-
278
- if (
279
- allDependencies.find((dep) =>
280
- changedPackages.find((d) => d.name === dep),
281
- ) &&
282
- !changedPackages.find((d) => d.name === pkg.name)
283
- ) {
284
- console.info(` Adding dependency ${pkg.name} to changed packages`);
285
- changedPackages.push(pkg);
286
- }
287
- }
288
- }
289
-
290
- const changelogCommitsMd = await Promise.all(
291
- Object.entries(
292
- commitsSinceLatestTag.reduce((prev, curr) => {
293
- return {
294
- ...prev,
295
- [curr.type]: [...(prev[curr.type] ?? []), curr],
296
- };
297
- }, /** @type {Record<string, import('./index').Commit[]>} */ ({})),
298
- )
299
- .sort(
300
- getSorterFn(([type]) =>
301
- [
302
- "other",
303
- "examples",
304
- "docs",
305
- "ci",
306
- "test",
307
- "chore",
308
- "refactor",
309
- "perf",
310
- "fix",
311
- "feat",
312
- ].indexOf(type),
313
- ),
314
- )
315
- .reverse()
316
- .map(async ([type, commits]) => {
317
- return Promise.all(
318
- commits.map(async (commit) => {
319
- let username = "";
320
-
321
- if (ghToken) {
322
- const query = commit.author_email;
323
-
324
- const res = await fetch(
325
- `https://api.github.com/search/users?q=${query}`,
326
- {
327
- headers: {
328
- Authorization: `token ${ghToken}`,
329
- },
330
- },
331
- );
332
- const data = /** @type {unknown} */ (await res.json());
333
- if (data && typeof data === "object" && "items" in data) {
334
- if (Array.isArray(data.items) && data.items[0]) {
335
- const item = /** @type {object} */ (data.items[0]);
336
- if ("login" in item && typeof item.login === "string") {
337
- username = item.login;
338
- }
339
- }
340
- }
341
- }
342
-
343
- const scope = commit.scope ? `${commit.scope}: ` : "";
344
- const subject = commit.subject;
345
-
346
- return `- ${scope}${subject} (${commit.hash}) ${
347
- username
348
- ? `by @${username}`
349
- : `by ${commit.author_name || commit.author_email}`
350
- }`;
351
- }),
352
- ).then((c) => /** @type {const} */ ([type, c]));
353
- }),
354
- ).then((groups) => {
355
- return groups
356
- .map(([type, commits]) => {
357
- return [`### ${capitalize(type)}`, commits.join("\n")].join("\n\n");
358
- })
359
- .join("\n\n");
360
- });
361
-
362
- const date = new Intl.DateTimeFormat(undefined, {
363
- dateStyle: "short",
364
- timeStyle: "short",
365
- }).format(Date.now());
366
-
367
- const changelogMd = [
368
- `Version ${version} - ${date}${tag ? " (Manual Release)" : ""}`,
369
- "## Changes",
370
- changelogCommitsMd || "- None",
371
- "## Packages",
372
- changedPackages.map((d) => `- ${d.name}@${version}`).join("\n"),
373
- ].join("\n\n");
374
-
375
- console.info("Generating changelog...");
376
- console.info();
377
- console.info(changelogMd);
378
- console.info();
379
-
380
- if (changedPackages.length === 0) {
381
- console.info("No packages have been affected.");
382
- return;
383
- }
384
-
385
- if (!process.env.CI) {
386
- console.warn(
387
- `This is a dry run for version ${version}. Push to CI to publish for real or set CI=true to override!`,
388
- );
389
- return;
390
- }
391
-
392
- console.info(`Updating all changed packages to version ${version}...`);
393
- // Update each package to the new version
394
- for (const pkg of changedPackages) {
395
- console.info(` Updating ${pkg.name} version to ${version}...`);
396
-
397
- await updatePackageJson(
398
- path.resolve(rootDir, pkg.packageDir, "package.json"),
399
- (config) => {
400
- config.version = version;
401
- },
402
- );
403
- }
404
-
405
- console.info();
406
- console.info(`Publishing all packages to npm with tag "${npmTag}"`);
407
-
408
- // Publish each package
409
- for (const pkg of changedPackages) {
410
- const packageDir = path.join(rootDir, pkg.packageDir);
411
-
412
- const cmd = `cd ${packageDir} && pnpm publish --tag ${npmTag} --access=public --no-git-checks`;
413
- console.info(` Publishing ${pkg.name}@${version} to npm...`);
414
- execSync(cmd, {
415
- // @ts-ignore
416
- stdio: [process.stdin, process.stdout, process.stderr],
417
- });
418
- }
419
-
420
- console.info();
421
- console.info("Committing changes...");
422
- execSync(
423
- `git add -A && git reset -- ${changedPackages
424
- .map((pkg) => path.resolve(rootDir, pkg.packageDir, "package.json"))
425
- .join(" ")}`,
426
- );
427
- execSync(
428
- `git checkout -- ${changedPackages
429
- .map((pkg) => path.resolve(rootDir, pkg.packageDir, "package.json"))
430
- .join(" ")}`,
431
- );
432
- execSync(`git commit -m "${releaseCommitMsg(version)}" --allow-empty -n`);
433
- console.info(" Committed Changes.");
434
-
435
- console.info();
436
- console.info("Pushing changes...");
437
- execSync(`git push origin ${currentGitBranch()}`);
438
- console.info(" Changes pushed.");
439
-
440
- console.info();
441
- console.info(`Creating new git tag v${version}`);
442
- execSync(`git tag -a -m "v${version}" v${version}`);
443
-
444
- console.info();
445
- console.info("Pushing tags...");
446
- execSync("git push --tags");
447
- console.info(" Tags pushed.");
448
-
449
- if (ghToken && isMainBranch) {
450
- console.info();
451
- console.info("Creating github release...");
452
-
453
- // Stringify the markdown to escape any quotes
454
- execSync(
455
- `gh release create v${version} ${
456
- branchConfig.prerelease ? "--prerelease" : ""
457
- } --notes '${changelogMd.replace(/'/g, '"')}'`,
458
- { env: { ...process.env, GH_TOKEN: ghToken } },
459
- );
460
- console.info(" Github release created.");
461
- }
462
-
463
- console.info();
464
- console.info("All done!");
465
- };
1
+ // @ts-check
2
+ // Originally ported to TS from https://github.com/remix-run/react-router/tree/main/scripts/{version,publish}.js
3
+
4
+ import { parse as parseCommit } from "@commitlint/parse";
5
+ import currentGitBranch from "current-git-branch";
6
+ import { execSync } from "node:child_process";
7
+ import path from "node:path";
8
+ import * as semver from "semver";
9
+ import { simpleGit } from "simple-git";
10
+ import {
11
+ capitalize,
12
+ getSorterFn,
13
+ readPackageJson,
14
+ releaseCommitMsg,
15
+ updatePackageJson,
16
+ } from "./utilities.js";
17
+
18
+ /**
19
+ * Execute a script being published
20
+ * @param {import('./index').Options} options
21
+ * @returns {Promise<void>}
22
+ */
23
+ export const publish = async (options) => {
24
+ const { branchConfigs, packages, rootDir, branch, tag, ghToken } = options;
25
+
26
+ const branchName = /** @type {string} */ (branch ?? currentGitBranch());
27
+ const isMainBranch = branchName === "main";
28
+ const npmTag = isMainBranch ? "latest" : branchName;
29
+
30
+ /** @type {import('./index').BranchConfig | undefined} */
31
+ const branchConfig = branchConfigs[branchName];
32
+
33
+ if (!branchConfig) {
34
+ throw new Error(`No publish config found for branch: ${branchName}`);
35
+ }
36
+
37
+ // Get tags
38
+ /** @type {string[]} */
39
+ const allTags = execSync("git tag").toString().split("\n");
40
+
41
+ const filteredTags = allTags
42
+ // Ensure tag is valid
43
+ .filter((t) => semver.valid(t))
44
+ // Filter tags based on whether the branch is a release or pre-release
45
+ .filter((t) => {
46
+ const isPrereleaseTag = semver.prerelease(t) !== null;
47
+ const prereleaseBranch = semver.prerelease(t)?.[0];
48
+ // For prerelease branches, only include tags for that branch
49
+ if (branchConfig.prerelease) {
50
+ return isPrereleaseTag && prereleaseBranch === branchName;
51
+ }
52
+ // For main branch, exclude all prereleases
53
+ return !isPrereleaseTag;
54
+ })
55
+ // sort by latest
56
+ // @ts-ignore
57
+ .sort(semver.compare);
58
+
59
+ // Get the latest tag
60
+ let latestTag = filteredTags.at(-1);
61
+ let rangeFrom = latestTag;
62
+
63
+ // If RELEASE_ALL is set via a commit subject or body, all packages will be
64
+ // released regardless if they have changed files matching the package srcDir.
65
+ let RELEASE_ALL = false;
66
+
67
+ // Validate manual tag
68
+ if (tag) {
69
+ if (!semver.valid(tag)) {
70
+ throw new Error(`tag '${tag}' is not a semantically valid version`);
71
+ }
72
+ if (!tag.startsWith("v")) {
73
+ throw new Error(
74
+ `tag must start with "v" (e.g. v0.0.0). You supplied ${tag}`,
75
+ );
76
+ }
77
+ if (allTags.includes(tag)) {
78
+ throw new Error(`tag ${tag} has already been released`);
79
+ }
80
+ }
81
+
82
+ if (!latestTag || tag) {
83
+ if (tag) {
84
+ console.info(
85
+ `Tag is set to ${tag}. This will force release all packages. Publishing...`,
86
+ );
87
+ RELEASE_ALL = true;
88
+
89
+ // Is it the first release? Is it a major version?
90
+ if (!latestTag || (semver.patch(tag) === 0 && semver.minor(tag) === 0)) {
91
+ rangeFrom = "origin/main";
92
+ latestTag = tag;
93
+ }
94
+ } else {
95
+ throw new Error(
96
+ "Could not find latest tag! To make a release tag of v0.0.1, run with TAG=v0.0.1",
97
+ );
98
+ }
99
+ }
100
+
101
+ console.info(`Git Range: ${rangeFrom}..HEAD`);
102
+
103
+ const rawCommitsLog = (
104
+ await simpleGit().log({ from: rangeFrom, to: "HEAD" })
105
+ ).all.filter((c) => {
106
+ const exclude = [
107
+ c.message.startsWith("Merge branch "), // No merge commits
108
+ c.message.includes("version & publish"), // No version bump commits
109
+ c.message.startsWith(releaseCommitMsg("")), // No example update commits
110
+ ].some(Boolean);
111
+
112
+ return !exclude;
113
+ });
114
+
115
+ // /**
116
+ // * Get the commits since the latest tag
117
+ // * @type {import('./index.js').Commit[]}
118
+ // */
119
+ const commitsSinceLatestTag = await Promise.all(
120
+ rawCommitsLog.map(async (c) => {
121
+ const parsed = await parseCommit(c.message);
122
+ return {
123
+ hash: c.hash.substring(0, 7),
124
+ body: c.body,
125
+ subject: parsed.subject ?? "",
126
+ author_name: c.author_name,
127
+ author_email: c.author_email,
128
+ type: parsed.type?.toLowerCase() ?? "other",
129
+ scope: parsed.scope,
130
+ };
131
+ }),
132
+ );
133
+
134
+ console.info(
135
+ `Parsing ${commitsSinceLatestTag.length} commits since ${rangeFrom}...`,
136
+ );
137
+
138
+ /**
139
+ * Parses the commit messages, logs them, and determines the type of release needed
140
+ * -1 means no release is necessary
141
+ * 0 means patch release is necessary
142
+ * 1 means minor release is necessary
143
+ * 2 means major release is necessary
144
+ * @type {number}
145
+ */
146
+ let recommendedReleaseLevel = commitsSinceLatestTag.reduce(
147
+ (releaseLevel, commit) => {
148
+ if (commit.type) {
149
+ if (["fix", "refactor", "perf"].includes(commit.type)) {
150
+ releaseLevel = Math.max(releaseLevel, 0);
151
+ }
152
+ if (["feat"].includes(commit.type)) {
153
+ releaseLevel = Math.max(releaseLevel, 1);
154
+ }
155
+ if (commit.body.includes("BREAKING CHANGE")) {
156
+ releaseLevel = Math.max(releaseLevel, 2);
157
+ }
158
+ if (
159
+ commit.subject.includes("RELEASE_ALL") ||
160
+ commit.body.includes("RELEASE_ALL")
161
+ ) {
162
+ RELEASE_ALL = true;
163
+ }
164
+ }
165
+ return releaseLevel;
166
+ },
167
+ -1,
168
+ );
169
+
170
+ // If there is a breaking change and no manual tag is set, do not release
171
+ /* if (recommendedReleaseLevel === 2 && !tag) {
172
+ throw new Error(
173
+ 'Major versions releases must be tagged and released manually.'
174
+ );
175
+ } */
176
+
177
+ // If no release is semantically necessary and no manual tag is set, do not release
178
+ if (recommendedReleaseLevel === -1 && !tag) {
179
+ console.info(
180
+ `There have been no changes since ${latestTag} that require a new version. You're good!`,
181
+ );
182
+ return;
183
+ }
184
+
185
+ // If no release is semantically necessary but a manual tag is set, do a patch release
186
+ if (recommendedReleaseLevel === -1 && tag) {
187
+ recommendedReleaseLevel = 0;
188
+ }
189
+
190
+ const releaseType = branchConfig.prerelease
191
+ ? "prerelease"
192
+ : /** @type {const} */ ({ 0: "patch", 1: "minor", 2: "major" })[
193
+ recommendedReleaseLevel
194
+ ];
195
+
196
+ if (!releaseType) {
197
+ throw new Error(`Invalid release level: ${recommendedReleaseLevel}`);
198
+ }
199
+
200
+ const version = tag
201
+ ? semver.parse(tag)?.version
202
+ : semver.inc(latestTag, releaseType, npmTag);
203
+
204
+ if (!version) {
205
+ throw new Error(
206
+ [
207
+ "Invalid version increment from semver.inc()",
208
+ `- latestTag: ${latestTag}`,
209
+ `- recommendedReleaseLevel: ${recommendedReleaseLevel}`,
210
+ `- prerelease: ${branchConfig.prerelease}`,
211
+ ].join("\n"),
212
+ );
213
+ }
214
+
215
+ console.log(`Targeting version ${version}...`);
216
+
217
+ /**
218
+ * Uses git diff to determine which files have changed since the latest tag
219
+ * @type {string[]}
220
+ */
221
+ const changedFiles = tag
222
+ ? []
223
+ : execSync(`git diff ${latestTag} --name-only`)
224
+ .toString()
225
+ .split("\n")
226
+ .filter(Boolean);
227
+
228
+ /** Uses packages and changedFiles to determine which packages have changed */
229
+ const changedPackages = RELEASE_ALL
230
+ ? packages
231
+ : packages.filter((pkg) => {
232
+ const changed = changedFiles.some(
233
+ (file) =>
234
+ file.startsWith(path.join(pkg.packageDir, "src")) ||
235
+ // check if any files in the assets directory were modified
236
+ file.startsWith(path.join(pkg.packageDir, "assets")) ||
237
+ file.startsWith(path.join(pkg.packageDir, "package.json")),
238
+ );
239
+ return changed;
240
+ });
241
+
242
+ // If a package has a dependency that has been updated, we need to update the
243
+ // package that depends on it as well.
244
+ // run this multiple times so that dependencies of dependencies are also included
245
+ for (let runs = 0; runs < 3; runs++) {
246
+ for (const pkg of packages) {
247
+ const packageJson = await readPackageJson(
248
+ path.resolve(rootDir, pkg.packageDir, "package.json"),
249
+ );
250
+ const allDependencies = Object.keys(
251
+ Object.assign(
252
+ {},
253
+ packageJson.dependencies ?? {},
254
+ packageJson.peerDependencies ?? {},
255
+ ),
256
+ );
257
+
258
+ if (
259
+ allDependencies.find((dep) =>
260
+ changedPackages.find((d) => d.name === dep),
261
+ ) &&
262
+ !changedPackages.find((d) => d.name === pkg.name)
263
+ ) {
264
+ console.info(` Adding dependency ${pkg.name} to changed packages`);
265
+ changedPackages.push(pkg);
266
+ }
267
+ }
268
+ }
269
+
270
+ const changelogCommitsMd = await Promise.all(
271
+ Object.entries(
272
+ commitsSinceLatestTag.reduce((prev, curr) => {
273
+ return {
274
+ ...prev,
275
+ [curr.type]: [...(prev[curr.type] ?? []), curr],
276
+ };
277
+ }, /** @type {Record<string, import('./index').Commit[]>} */ ({})),
278
+ )
279
+ .sort(
280
+ getSorterFn(([type]) =>
281
+ [
282
+ "other",
283
+ "examples",
284
+ "docs",
285
+ "ci",
286
+ "test",
287
+ "chore",
288
+ "refactor",
289
+ "perf",
290
+ "fix",
291
+ "feat",
292
+ ].indexOf(type),
293
+ ),
294
+ )
295
+ .reverse()
296
+ .map(async ([type, commits]) => {
297
+ return Promise.all(
298
+ commits.map(async (commit) => {
299
+ let username = "";
300
+
301
+ if (ghToken) {
302
+ const query = commit.author_email;
303
+
304
+ const res = await fetch(
305
+ `https://api.github.com/search/users?q=${query}`,
306
+ {
307
+ headers: {
308
+ Authorization: `token ${ghToken}`,
309
+ },
310
+ },
311
+ );
312
+ const data = /** @type {unknown} */ (await res.json());
313
+ if (data && typeof data === "object" && "items" in data) {
314
+ if (Array.isArray(data.items) && data.items[0]) {
315
+ const item = /** @type {object} */ (data.items[0]);
316
+ if ("login" in item && typeof item.login === "string") {
317
+ username = item.login;
318
+ }
319
+ }
320
+ }
321
+ }
322
+
323
+ const scope = commit.scope ? `${commit.scope}: ` : "";
324
+ const subject = commit.subject;
325
+
326
+ return `- ${scope}${subject} (${commit.hash}) ${
327
+ username
328
+ ? `by @${username}`
329
+ : `by ${commit.author_name || commit.author_email}`
330
+ }`;
331
+ }),
332
+ ).then((c) => /** @type {const} */ ([type, c]));
333
+ }),
334
+ ).then((groups) => {
335
+ return groups
336
+ .map(([type, commits]) => {
337
+ return [`### ${capitalize(type)}`, commits.join("\n")].join("\n\n");
338
+ })
339
+ .join("\n\n");
340
+ });
341
+
342
+ const date = new Intl.DateTimeFormat(undefined, {
343
+ dateStyle: "short",
344
+ timeStyle: "short",
345
+ }).format(Date.now());
346
+
347
+ const changelogMd = [
348
+ `Version ${version} - ${date}${tag ? " (Manual Release)" : ""}`,
349
+ "## Changes",
350
+ changelogCommitsMd || "- None",
351
+ "## Packages",
352
+ changedPackages.map((d) => `- ${d.name}@${version}`).join("\n"),
353
+ ].join("\n\n");
354
+
355
+ console.info("Generating changelog...");
356
+ console.info();
357
+ console.info(changelogMd);
358
+ console.info();
359
+
360
+ if (changedPackages.length === 0) {
361
+ console.info("No packages have been affected.");
362
+ return;
363
+ }
364
+
365
+ if (!process.env.CI) {
366
+ console.warn(
367
+ `This is a dry run for version ${version}. Push to CI to publish for real or set CI=true to override!`,
368
+ );
369
+ return;
370
+ }
371
+
372
+ console.info(`Updating all changed packages to version ${version}...`);
373
+ // Update each package to the new version
374
+ for (const pkg of changedPackages) {
375
+ console.info(` Updating ${pkg.name} version to ${version}...`);
376
+
377
+ await updatePackageJson(
378
+ path.resolve(rootDir, pkg.packageDir, "package.json"),
379
+ (config) => {
380
+ config.version = version;
381
+ },
382
+ );
383
+ }
384
+
385
+ console.info();
386
+ console.info(`Publishing all packages to npm with tag "${npmTag}"`);
387
+
388
+ // Publish each package
389
+ for (const pkg of changedPackages) {
390
+ const packageDir = path.join(rootDir, pkg.packageDir);
391
+
392
+ const cmd = `cd ${packageDir} && pnpm publish --tag ${npmTag} --access=public --no-git-checks`;
393
+ console.info(` Publishing ${pkg.name}@${version} to npm...`);
394
+ execSync(cmd, {
395
+ // @ts-ignore
396
+ stdio: [process.stdin, process.stdout, process.stderr],
397
+ });
398
+ }
399
+
400
+ console.info();
401
+ console.info("Committing changes...");
402
+ execSync(
403
+ `git add -A && git reset -- ${changedPackages
404
+ .map((pkg) => path.resolve(rootDir, pkg.packageDir, "package.json"))
405
+ .join(" ")}`,
406
+ );
407
+ execSync(
408
+ `git checkout -- ${changedPackages
409
+ .map((pkg) => path.resolve(rootDir, pkg.packageDir, "package.json"))
410
+ .join(" ")}`,
411
+ );
412
+ execSync(`git commit -m "${releaseCommitMsg(version)}" --allow-empty -n`);
413
+ console.info(" Committed Changes.");
414
+
415
+ console.info();
416
+ console.info("Pushing changes...");
417
+ execSync(`git push origin ${currentGitBranch()}`);
418
+ console.info(" Changes pushed.");
419
+
420
+ console.info();
421
+ console.info(`Creating new git tag v${version}`);
422
+ execSync(`git tag -a -m "v${version}" v${version}`);
423
+
424
+ console.info();
425
+ console.info("Pushing tags...");
426
+ execSync("git push --tags");
427
+ console.info(" Tags pushed.");
428
+
429
+ if (ghToken && isMainBranch) {
430
+ console.info();
431
+ console.info("Creating github release...");
432
+
433
+ // Stringify the markdown to escape any quotes
434
+ execSync(
435
+ `gh release create v${version} ${
436
+ branchConfig.prerelease ? "--prerelease" : ""
437
+ } --notes '${changelogMd.replace(/'/g, '"')}'`,
438
+ { env: { ...process.env, GH_TOKEN: ghToken } },
439
+ );
440
+ console.info(" Github release created.");
441
+ }
442
+
443
+ console.info();
444
+ console.info("All done!");
445
+ };