@mlaursen/release-script 0.0.6 → 0.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -52,10 +52,6 @@ await release({
52
52
  // An optional flag if the build step should be skipped. `!buildCommand` by default
53
53
  // skipBuild: process.argv.includes("--skip-build"),
54
54
 
55
- // This is useful for monorepos where only a single Github release needs to
56
- // be created. Defaults to `JSON.parse(await readFile("package.json)).name`
57
- // mainPackage: "{{PACKAGE_NAME}}",
58
-
59
55
  // If the version message needs to be customized. The following is the default
60
56
  // versionMessage: "build(version): version package",
61
57
 
@@ -63,18 +59,11 @@ await release({
63
59
  // variable.
64
60
  // envPath: ".env.local",
65
61
 
66
- // An optional async function to get the next release tag name. The default
67
- // is shown below:
68
- // getTagName: async () => {
69
- // const latestTag = await (
70
- // await import("@react-md/release-script")
71
- // ).getLatestTag();
72
- // let tagName =
73
- // mainPackage && /@\d/.test(latestTag)
74
- // ? latestTag.replace(/.+(@\d)/, `${mainPackage}$1`)
75
- // : latestTag;
76
- //
77
- // return tagName;
62
+ // An optional lookup of package name -> package path in the repo used to
63
+ // find the CHANGELOG.md for each release in a monorepo. Will default to
64
+ // `"."` when omitted.
65
+ // packagePaths: {
66
+ // "@react-md/core": "./packages/core",
78
67
  // },
79
68
  });
80
69
  ```
@@ -89,7 +78,7 @@ Next, update `package.json` to include the release script:
89
78
  "format": "prettier --write .",
90
79
  "clean": "rm -rf dist",
91
80
  "build": "tsc -p tsconfig.json",
92
- + "release": "tsx index.ts"
81
+ + "release": "tsx scripts/release.ts"
93
82
  },
94
83
  ```
95
84
 
@@ -99,6 +88,16 @@ Finally, run the release script whenever a new release should go out:
99
88
  pnpm release
100
89
  ```
101
90
 
91
+ ## Adding Changesets
92
+
93
+ During normal development, add changesets and commit them. They will normally
94
+ be close to my commit messages which is a bit annoying.
95
+
96
+ ```sh
97
+ pnpm changeset
98
+ git add .changeset
99
+ ```
100
+
102
101
  ## Alpha Releases
103
102
 
104
103
  Use the changesets api to enter the pre-release flow:
package/dist/index.d.ts CHANGED
@@ -1,2 +1,67 @@
1
- export * from "./release.js";
2
- export * from "./getLatestTag.js";
1
+ interface ConfigurableCreateReleaseOptions {
2
+ repo: string;
3
+ /**
4
+ * @defaultValue `"mlaursen"`
5
+ */
6
+ owner?: string;
7
+ /**
8
+ * The `.env` file to load to get the {@link tokenName} environment variable.
9
+ *
10
+ * @defaultValue `".env.local"`
11
+ */
12
+ envPath?: string;
13
+ /**
14
+ * @defaultValue `"GITHUB_RELEASE_TOKEN"`
15
+ */
16
+ tokenName?: string;
17
+ }
18
+ interface CreateReleaseOptions extends ConfigurableCreateReleaseOptions {
19
+ body: string;
20
+ override?: boolean;
21
+ tagName: string;
22
+ prerelease: boolean;
23
+ }
24
+ declare function createRelease(options: CreateReleaseOptions): Promise<void>;
25
+
26
+ interface GetPendingReleasesOptions {
27
+ /**
28
+ * This should be a record of package names to paths for monorepos.
29
+ *
30
+ * @example Monorepo Setup
31
+ * ```tsx
32
+ * packagePaths: {
33
+ * "@react-md/core": "./packages/core",
34
+ * "docs": "./apps/docs"
35
+ * },
36
+ * ```
37
+ *
38
+ * If this is omitted or not matched, it will default to `"."`
39
+ *
40
+ * @defaultValue `{}`
41
+ */
42
+ packagePaths?: Record<string, string>;
43
+ }
44
+
45
+ interface ReleaseOptions extends ConfigurableCreateReleaseOptions, GetPendingReleasesOptions {
46
+ /**
47
+ * @defaultValue `!buildCommand`
48
+ */
49
+ skipBuild?: boolean;
50
+ /**
51
+ * @defaultValue `"clean"`
52
+ */
53
+ cleanCommand?: string;
54
+ /**
55
+ * @defaultValue `"build"`
56
+ */
57
+ buildCommand?: string;
58
+ /**
59
+ * @defaultValue `"build(version): version package"`
60
+ */
61
+ versionMessage?: string;
62
+ }
63
+ declare function release(options: ReleaseOptions): Promise<void>;
64
+
65
+ export { createRelease, release };
66
+ export type { ConfigurableCreateReleaseOptions, CreateReleaseOptions, ReleaseOptions };
67
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sources":["../types/createRelease.d.ts","../types/getPendingReleases.d.ts","../types/release.d.ts"],"sourcesContent":["export interface ConfigurableCreateReleaseOptions {\n repo: string;\n /**\n * @defaultValue `\"mlaursen\"`\n */\n owner?: string;\n /**\n * The `.env` file to load to get the {@link tokenName} environment variable.\n *\n * @defaultValue `\".env.local\"`\n */\n envPath?: string;\n /**\n * @defaultValue `\"GITHUB_RELEASE_TOKEN\"`\n */\n tokenName?: string;\n}\nexport interface CreateReleaseOptions extends ConfigurableCreateReleaseOptions {\n body: string;\n override?: boolean;\n tagName: string;\n prerelease: boolean;\n}\nexport declare function createRelease(options: CreateReleaseOptions): Promise<void>;\n","export interface GetPendingReleasesOptions {\n /**\n * This should be a record of package names to paths for monorepos.\n *\n * @example Monorepo Setup\n * ```tsx\n * packagePaths: {\n * \"@react-md/core\": \"./packages/core\",\n * \"docs\": \"./apps/docs\"\n * },\n * ```\n *\n * If this is omitted or not matched, it will default to `\".\"`\n *\n * @defaultValue `{}`\n */\n packagePaths?: Record<string, string>;\n}\nexport interface PendingRelease {\n tagName: string;\n body: string;\n}\nexport declare function getPendingReleases(options: GetPendingReleasesOptions): Promise<readonly PendingRelease[]>;\n","import { type ConfigurableCreateReleaseOptions } from \"./createRelease.js\";\nimport { type GetPendingReleasesOptions } from \"./getPendingReleases.js\";\nexport interface ReleaseOptions extends ConfigurableCreateReleaseOptions, GetPendingReleasesOptions {\n /**\n * @defaultValue `!buildCommand`\n */\n skipBuild?: boolean;\n /**\n * @defaultValue `\"clean\"`\n */\n cleanCommand?: string;\n /**\n * @defaultValue `\"build\"`\n */\n buildCommand?: string;\n /**\n * @defaultValue `\"build(version): version package\"`\n */\n versionMessage?: string;\n}\nexport declare function release(options: ReleaseOptions): Promise<void>;\n"],"names":[],"mappings":"AAAO,UAAA,gCAAA;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,UAAA,oBAAA,SAAA,gCAAA;AACP;AACA;AACA;AACA;AACA;AACO,iBAAA,aAAA,UAAA,oBAAA,GAAA,OAAA;;ACvBA,UAAA,yBAAA;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,mBAAA,MAAA;AACA;;ACfO,UAAA,cAAA,SAAA,gCAAA,EAAA,yBAAA;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,iBAAA,OAAA,UAAA,cAAA,GAAA,OAAA;;;;"}
package/dist/index.mjs ADDED
@@ -0,0 +1,171 @@
1
+ import confirm from '@inquirer/confirm';
2
+ import { Octokit } from '@octokit/core';
3
+ import dotenv from 'dotenv';
4
+ import { execSync } from 'node:child_process';
5
+ import { readFile } from 'node:fs/promises';
6
+ import { resolve } from 'node:path';
7
+ import input from '@inquirer/input';
8
+
9
+ async function createRelease(options) {
10
+ const { body, override, owner = "mlaursen", repo, prerelease, envPath = ".env.local", tagName, tokenName = "GITHUB_RELEASE_TOKEN" } = options;
11
+ dotenv.config({
12
+ path: envPath,
13
+ override,
14
+ quiet: true
15
+ });
16
+ const octokit = new Octokit({
17
+ auth: process.env[tokenName]
18
+ });
19
+ try {
20
+ const response = await octokit.request("POST /repos/{owner}/{repo}/releases", {
21
+ owner,
22
+ repo,
23
+ tag_name: tagName,
24
+ body,
25
+ prerelease
26
+ });
27
+ console.log(`Created release: ${response.data.html_url}`);
28
+ } catch (error) {
29
+ console.error(error);
30
+ console.log();
31
+ console.log("The npm token is most likely expired or never created. Update the `.env.local` to include the latest GITHUB_TOKEN");
32
+ console.log("Regenerate the token: https://github.com/settings/personal-access-tokens");
33
+ if (!await confirm({
34
+ message: "Try creating the Github release again?"
35
+ })) {
36
+ throw new Error("Unable to create a Github release");
37
+ }
38
+ return createRelease({
39
+ ...options,
40
+ override: true
41
+ });
42
+ }
43
+ }
44
+
45
+ async function continueRelease() {
46
+ const confirmed = await confirm({
47
+ message: "Continue the release?"
48
+ });
49
+ if (!confirmed) {
50
+ throw new Error("Release cancelled");
51
+ }
52
+ }
53
+
54
+ async function getPackageManager() {
55
+ const rawPackageJson = await readFile(resolve(process.cwd(), "package.json"), "utf8");
56
+ const packageJson = JSON.parse(rawPackageJson);
57
+ if (typeof packageJson.volta === "object" && packageJson.volta) {
58
+ const { volta } = packageJson;
59
+ if ("pnpm" in volta) {
60
+ return "pnpm";
61
+ }
62
+ if ("yarn" in volta) {
63
+ return "yarn";
64
+ }
65
+ return "npm";
66
+ }
67
+ if (typeof packageJson.packageManager === "string") {
68
+ const mgr = packageJson.packageManagerreplace(/@.+/, "");
69
+ if (mgr === "pnpm" || mgr === "yarn" || mgr === "npm") {
70
+ return mgr;
71
+ }
72
+ throw new Error(`Unsupported package mananger "${mgr}" in package.json`);
73
+ }
74
+ throw new Error("Unable to find a package manager");
75
+ }
76
+
77
+ function getTags(local) {
78
+ const command = local ? "git tag --sort=-creatordate" : "git ls-remote --tags origin";
79
+ const tags = execSync(command).toString().trim();
80
+ const lines = tags.split(/\r?\n/);
81
+ if (local) {
82
+ return new Set(lines);
83
+ }
84
+ return new Set(lines.map((line)=>line.replace(/^.+refs\/tags\//, "").replace("^{}", "")));
85
+ }
86
+ function getUnpushedTags() {
87
+ const localTags = getTags(true);
88
+ const pushedTags = getTags(false);
89
+ return [
90
+ ...localTags.difference(pushedTags)
91
+ ];
92
+ }
93
+
94
+ async function getPendingReleases(options) {
95
+ const { packagePaths = {} } = options;
96
+ const unpushedTags = getUnpushedTags();
97
+ if (unpushedTags.length === 0) {
98
+ throw new Error("Unable to find any pending releases");
99
+ }
100
+ const pending = [];
101
+ for (const unpushedTag of unpushedTags){
102
+ if (!await confirm({
103
+ message: `Include ${unpushedTag} in the release?`
104
+ })) {
105
+ continue;
106
+ }
107
+ const name = unpushedTag.replace(/@\d+.+$/, "");
108
+ const path = await input({
109
+ message: `${name} CHANGELOG exists at:`,
110
+ default: packagePaths[name] ?? "."
111
+ });
112
+ const changelog = await readFile(resolve(process.cwd(), path, "CHANGELOG.md"), "utf8");
113
+ let body = "";
114
+ let isTracking = false;
115
+ const lines = changelog.split(/\r?\n/);
116
+ for (const line of lines){
117
+ if (line.startsWith("## ")) {
118
+ if (isTracking) {
119
+ break;
120
+ }
121
+ isTracking = true;
122
+ body = line;
123
+ } else if (isTracking) {
124
+ body += `\n${line}`;
125
+ }
126
+ }
127
+ pending.push({
128
+ body,
129
+ tagName: unpushedTag
130
+ });
131
+ }
132
+ return pending;
133
+ }
134
+
135
+ const exec = (command, opts)=>{
136
+ console.log(command);
137
+ execSync(command, opts);
138
+ };
139
+ async function release(options) {
140
+ const { owner, repo, envPath, cleanCommand = "clean", buildCommand = "build", skipBuild = !buildCommand, versionMessage = "build(version): version package" } = options;
141
+ const pkgManager = await getPackageManager();
142
+ if (!skipBuild) {
143
+ exec(`${pkgManager} ${cleanCommand}`);
144
+ exec(`${pkgManager} ${buildCommand}`);
145
+ }
146
+ await continueRelease();
147
+ exec("pnpm changeset version", {
148
+ stdio: "inherit"
149
+ });
150
+ exec("git add -u");
151
+ await continueRelease();
152
+ exec(`git commit -m "${versionMessage}"`);
153
+ exec(`${pkgManager} changeset publish`, {
154
+ stdio: "inherit"
155
+ });
156
+ const pendingReleases = await getPendingReleases(options);
157
+ exec("git push --follow-tags");
158
+ for (const release of pendingReleases){
159
+ await createRelease({
160
+ owner,
161
+ repo,
162
+ body: release.body,
163
+ tagName: release.tagName,
164
+ envPath,
165
+ prerelease: /-(alpha|next|beta)\.\d+$/.test(release.tagName)
166
+ });
167
+ }
168
+ }
169
+
170
+ export { createRelease, release };
171
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","sources":["../src/createRelease.ts","../src/continueRelease.ts","../src/getPackageManager.ts","../src/getUnpushedTags.ts","../src/getPendingReleases.ts","../src/release.ts"],"sourcesContent":["import confirm from \"@inquirer/confirm\";\nimport { Octokit } from \"@octokit/core\";\nimport dotenv from \"dotenv\";\n\nexport interface ConfigurableCreateReleaseOptions {\n repo: string;\n\n /**\n * @defaultValue `\"mlaursen\"`\n */\n owner?: string;\n\n /**\n * The `.env` file to load to get the {@link tokenName} environment variable.\n *\n * @defaultValue `\".env.local\"`\n */\n envPath?: string;\n\n /**\n * @defaultValue `\"GITHUB_RELEASE_TOKEN\"`\n */\n tokenName?: string;\n}\n\nexport interface CreateReleaseOptions extends ConfigurableCreateReleaseOptions {\n body: string;\n override?: boolean;\n tagName: string;\n prerelease: boolean;\n}\n\nexport async function createRelease(\n options: CreateReleaseOptions\n): Promise<void> {\n const {\n body,\n override,\n owner = \"mlaursen\",\n repo,\n prerelease,\n envPath = \".env.local\",\n tagName,\n tokenName = \"GITHUB_RELEASE_TOKEN\",\n } = options;\n\n dotenv.config({ path: envPath, override, quiet: true });\n const octokit = new Octokit({ auth: process.env[tokenName] });\n try {\n const response = await octokit.request(\n \"POST /repos/{owner}/{repo}/releases\",\n {\n owner,\n repo,\n tag_name: tagName,\n body,\n prerelease,\n }\n );\n\n console.log(`Created release: ${response.data.html_url}`);\n } catch (error) {\n console.error(error);\n\n console.log();\n console.log(\n \"The npm token is most likely expired or never created. Update the `.env.local` to include the latest GITHUB_TOKEN\"\n );\n console.log(\n \"Regenerate the token: https://github.com/settings/personal-access-tokens\"\n );\n if (\n !(await confirm({ message: \"Try creating the Github release again?\" }))\n ) {\n throw new Error(\"Unable to create a Github release\");\n }\n\n return createRelease({ ...options, override: true });\n }\n}\n","import confirm from \"@inquirer/confirm\";\n\nexport async function continueRelease(): Promise<void> {\n const confirmed = await confirm({ message: \"Continue the release?\" });\n if (!confirmed) {\n throw new Error(\"Release cancelled\");\n }\n}\n","import { readFile } from \"node:fs/promises\";\nimport { resolve } from \"node:path\";\n\nexport type PackageManager = \"npm\" | \"yarn\" | \"pnpm\";\n\nexport async function getPackageManager(): Promise<PackageManager> {\n const rawPackageJson = await readFile(\n resolve(process.cwd(), \"package.json\"),\n \"utf8\"\n );\n const packageJson = JSON.parse(rawPackageJson);\n\n if (typeof packageJson.volta === \"object\" && packageJson.volta) {\n const { volta } = packageJson;\n if (\"pnpm\" in volta) {\n return \"pnpm\";\n }\n\n if (\"yarn\" in volta) {\n return \"yarn\";\n }\n\n return \"npm\";\n }\n\n if (typeof packageJson.packageManager === \"string\") {\n const mgr = packageJson.packageManagerreplace(/@.+/, \"\");\n\n if (mgr === \"pnpm\" || mgr === \"yarn\" || mgr === \"npm\") {\n return mgr;\n }\n\n throw new Error(`Unsupported package mananger \"${mgr}\" in package.json`);\n }\n\n throw new Error(\"Unable to find a package manager\");\n}\n","import { execSync } from \"node:child_process\";\n\nfunction getTags(local: boolean): Set<string> {\n const command = local\n ? \"git tag --sort=-creatordate\"\n : \"git ls-remote --tags origin\";\n const tags = execSync(command).toString().trim();\n const lines = tags.split(/\\r?\\n/);\n if (local) {\n return new Set(lines);\n }\n\n return new Set(\n lines.map((line) => line.replace(/^.+refs\\/tags\\//, \"\").replace(\"^{}\", \"\"))\n );\n}\n\nexport function getUnpushedTags(): readonly string[] {\n const localTags = getTags(true);\n const pushedTags = getTags(false);\n\n return [...localTags.difference(pushedTags)];\n}\n","import confirm from \"@inquirer/confirm\";\nimport input from \"@inquirer/input\";\nimport { readFile } from \"node:fs/promises\";\nimport { resolve } from \"node:path\";\n\nimport { getUnpushedTags } from \"./getUnpushedTags.js\";\n\nexport interface GetPendingReleasesOptions {\n /**\n * This should be a record of package names to paths for monorepos.\n *\n * @example Monorepo Setup\n * ```tsx\n * packagePaths: {\n * \"@react-md/core\": \"./packages/core\",\n * \"docs\": \"./apps/docs\"\n * },\n * ```\n *\n * If this is omitted or not matched, it will default to `\".\"`\n *\n * @defaultValue `{}`\n */\n packagePaths?: Record<string, string>;\n}\n\nexport interface PendingRelease {\n tagName: string;\n body: string;\n}\n\nexport async function getPendingReleases(\n options: GetPendingReleasesOptions\n): Promise<readonly PendingRelease[]> {\n const { packagePaths = {} } = options;\n const unpushedTags = getUnpushedTags();\n if (unpushedTags.length === 0) {\n throw new Error(\"Unable to find any pending releases\");\n }\n\n const pending: PendingRelease[] = [];\n for (const unpushedTag of unpushedTags) {\n if (\n !(await confirm({ message: `Include ${unpushedTag} in the release?` }))\n ) {\n continue;\n }\n\n const name = unpushedTag.replace(/@\\d+.+$/, \"\");\n const path = await input({\n message: `${name} CHANGELOG exists at:`,\n default: packagePaths[name] ?? \".\",\n });\n\n const changelog = await readFile(\n resolve(process.cwd(), path, \"CHANGELOG.md\"),\n \"utf8\"\n );\n\n let body = \"\";\n let isTracking = false;\n const lines = changelog.split(/\\r?\\n/);\n for (const line of lines) {\n if (line.startsWith(\"## \")) {\n if (isTracking) {\n break;\n }\n\n isTracking = true;\n body = line;\n } else if (isTracking) {\n body += `\\n${line}`;\n }\n }\n\n pending.push({\n body,\n tagName: unpushedTag,\n });\n }\n\n return pending;\n}\n","import { type ExecSyncOptions, execSync } from \"node:child_process\";\n\nimport { continueRelease } from \"./continueRelease.js\";\nimport {\n type ConfigurableCreateReleaseOptions,\n createRelease,\n} from \"./createRelease.js\";\nimport { getPackageManager } from \"./getPackageManager.js\";\nimport {\n type GetPendingReleasesOptions,\n getPendingReleases,\n} from \"./getPendingReleases.js\";\n\nconst exec = (command: string, opts?: ExecSyncOptions): void => {\n console.log(command);\n execSync(command, opts);\n};\n\nexport interface ReleaseOptions\n extends ConfigurableCreateReleaseOptions, GetPendingReleasesOptions {\n /**\n * @defaultValue `!buildCommand`\n */\n skipBuild?: boolean;\n\n /**\n * @defaultValue `\"clean\"`\n */\n cleanCommand?: string;\n\n /**\n * @defaultValue `\"build\"`\n */\n buildCommand?: string;\n\n /**\n * @defaultValue `\"build(version): version package\"`\n */\n versionMessage?: string;\n}\n\nexport async function release(options: ReleaseOptions): Promise<void> {\n const {\n owner,\n repo,\n envPath,\n cleanCommand = \"clean\",\n buildCommand = \"build\",\n skipBuild = !buildCommand,\n versionMessage = \"build(version): version package\",\n } = options;\n\n const pkgManager = await getPackageManager();\n\n if (!skipBuild) {\n exec(`${pkgManager} ${cleanCommand}`);\n exec(`${pkgManager} ${buildCommand}`);\n }\n await continueRelease();\n\n exec(\"pnpm changeset version\", { stdio: \"inherit\" });\n exec(\"git add -u\");\n await continueRelease();\n\n exec(`git commit -m \"${versionMessage}\"`);\n exec(`${pkgManager} changeset publish`, { stdio: \"inherit\" });\n const pendingReleases = await getPendingReleases(options);\n\n exec(\"git push --follow-tags\");\n\n for (const release of pendingReleases) {\n await createRelease({\n owner,\n repo,\n body: release.body,\n tagName: release.tagName,\n envPath,\n prerelease: /-(alpha|next|beta)\\.\\d+$/.test(release.tagName),\n });\n }\n}\n"],"names":["createRelease","options","body","override","owner","repo","prerelease","envPath","tagName","tokenName","dotenv","config","path","quiet","octokit","Octokit","auth","process","env","response","request","tag_name","console","log","data","html_url","error","confirm","message","Error","continueRelease","confirmed","getPackageManager","rawPackageJson","readFile","resolve","cwd","packageJson","JSON","parse","volta","packageManager","mgr","packageManagerreplace","getTags","local","command","tags","execSync","toString","trim","lines","split","Set","map","line","replace","getUnpushedTags","localTags","pushedTags","difference","getPendingReleases","packagePaths","unpushedTags","length","pending","unpushedTag","name","input","default","changelog","isTracking","startsWith","push","exec","opts","release","cleanCommand","buildCommand","skipBuild","versionMessage","pkgManager","stdio","pendingReleases","test"],"mappings":"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"}
package/package.json CHANGED
@@ -1,41 +1,47 @@
1
1
  {
2
2
  "name": "@mlaursen/release-script",
3
- "type": "module",
4
- "version": "0.0.6",
3
+ "version": "0.0.7",
5
4
  "description": "The release script I normally use for packages I publish to npm",
6
- "repository": "https://github.com/mlaursen/release-script.git",
7
- "author": "Mikkel Laursen <mlaursen03@gmail.com>",
8
- "license": "MIT",
9
- "files": [
10
- "dist/"
11
- ],
5
+ "type": "module",
12
6
  "exports": {
13
- ".": "./dist/index.js",
7
+ ".": "./dist/index.mjs",
14
8
  "./package.json": "./package.json"
15
9
  },
10
+ "author": "Mikkel Laursen <mlaursen03@gmail.com>",
11
+ "repository": {
12
+ "type": "git",
13
+ "url": "git+https://github.com/mlaursen/npm-packages.git",
14
+ "directory": "packages/release-script"
15
+ },
16
16
  "bugs": {
17
- "url": "https://github.com/mlaursen/release-script/issues"
17
+ "url": "https://github.com/mlaursen/npm-packages/issues"
18
18
  },
19
19
  "keywords": [
20
20
  "release",
21
21
  "script",
22
22
  "npm"
23
23
  ],
24
+ "license": "MIT",
24
25
  "dependencies": {
25
- "@changesets/cli": "^2.29.7",
26
- "@inquirer/confirm": "^5.1.19",
27
- "@inquirer/input": "^4.2.5",
28
- "@inquirer/rawlist": "^4.1.9",
26
+ "@changesets/cli": "^2.29.8",
27
+ "@inquirer/confirm": "^6.0.2",
28
+ "@inquirer/input": "^5.0.2",
29
+ "@inquirer/rawlist": "^5.0.2",
29
30
  "@octokit/core": "^7.0.6",
30
31
  "dotenv": "^17.2.3"
31
32
  },
32
33
  "devDependencies": {
33
- "@types/node": "^24.9.2",
34
- "husky": "^9.1.7",
35
- "lint-staged": "^16.2.6",
36
- "prettier": "^3.6.2",
37
- "tsx": "^4.20.6",
38
- "typescript": "^5.9.3"
34
+ "@rollup/plugin-node-resolve": "^16.0.3",
35
+ "@trivago/prettier-plugin-sort-imports": "^6.0.0",
36
+ "@types/node": "^24.10.1",
37
+ "concurrently": "^9.2.1",
38
+ "eslint": "^9.39.1",
39
+ "prettier": "^3.7.4",
40
+ "rollup": "^4.53.3",
41
+ "rollup-plugin-dts": "^6.3.0",
42
+ "rollup-plugin-swc3": "^0.12.1",
43
+ "typescript": "^5.9.3",
44
+ "@mlaursen/eslint-config": "11.0.1"
39
45
  },
40
46
  "publishConfig": {
41
47
  "access": "public"
@@ -50,11 +56,20 @@
50
56
  "pnpm": "10.20.0"
51
57
  },
52
58
  "scripts": {
53
- "typecheck": "tsc --noEmit",
54
- "check-format": "prettier --check .",
59
+ "clean-dist": "rm -rf dist",
60
+ "clean-cache": "rm -rf .turbo node_modules",
61
+ "clean": "concurrently 'pnpm clean-dist' 'pnpm clean-cache'",
55
62
  "format": "prettier --write .",
56
- "clean": "rm -rf dist",
57
- "build": "tsc -p tsconfig.build.json",
58
- "release": "tsx index.ts"
63
+ "check-format": "prettier --check .",
64
+ "lint": "eslint .",
65
+ "lint-fix": "pnpm lint --fix",
66
+ "typecheck": "tsc --noEmit",
67
+ "typecheck-watch": "tsc --noEmit --watch",
68
+ "build-dist": "rollup -c rollup.config.ts",
69
+ "build-dist-watch": "pnpm build-dist --watch",
70
+ "build-types": "tsc -p tsconfig.types.json",
71
+ "build-types-watch": "pnpm build-types --watch",
72
+ "build": "pnpm build-types && pnpm build-dist",
73
+ "dev": "concurrently 'pnpm build-types-watch' 'pnpm build-dist-watch'"
59
74
  }
60
75
  }
@@ -0,0 +1,8 @@
1
+ import confirm from "@inquirer/confirm";
2
+
3
+ export async function continueRelease(): Promise<void> {
4
+ const confirmed = await confirm({ message: "Continue the release?" });
5
+ if (!confirmed) {
6
+ throw new Error("Release cancelled");
7
+ }
8
+ }
@@ -0,0 +1,80 @@
1
+ import confirm from "@inquirer/confirm";
2
+ import { Octokit } from "@octokit/core";
3
+ import dotenv from "dotenv";
4
+
5
+ export interface ConfigurableCreateReleaseOptions {
6
+ repo: string;
7
+
8
+ /**
9
+ * @defaultValue `"mlaursen"`
10
+ */
11
+ owner?: string;
12
+
13
+ /**
14
+ * The `.env` file to load to get the {@link tokenName} environment variable.
15
+ *
16
+ * @defaultValue `".env.local"`
17
+ */
18
+ envPath?: string;
19
+
20
+ /**
21
+ * @defaultValue `"GITHUB_RELEASE_TOKEN"`
22
+ */
23
+ tokenName?: string;
24
+ }
25
+
26
+ export interface CreateReleaseOptions extends ConfigurableCreateReleaseOptions {
27
+ body: string;
28
+ override?: boolean;
29
+ tagName: string;
30
+ prerelease: boolean;
31
+ }
32
+
33
+ export async function createRelease(
34
+ options: CreateReleaseOptions
35
+ ): Promise<void> {
36
+ const {
37
+ body,
38
+ override,
39
+ owner = "mlaursen",
40
+ repo,
41
+ prerelease,
42
+ envPath = ".env.local",
43
+ tagName,
44
+ tokenName = "GITHUB_RELEASE_TOKEN",
45
+ } = options;
46
+
47
+ dotenv.config({ path: envPath, override, quiet: true });
48
+ const octokit = new Octokit({ auth: process.env[tokenName] });
49
+ try {
50
+ const response = await octokit.request(
51
+ "POST /repos/{owner}/{repo}/releases",
52
+ {
53
+ owner,
54
+ repo,
55
+ tag_name: tagName,
56
+ body,
57
+ prerelease,
58
+ }
59
+ );
60
+
61
+ console.log(`Created release: ${response.data.html_url}`);
62
+ } catch (error) {
63
+ console.error(error);
64
+
65
+ console.log();
66
+ console.log(
67
+ "The npm token is most likely expired or never created. Update the `.env.local` to include the latest GITHUB_TOKEN"
68
+ );
69
+ console.log(
70
+ "Regenerate the token: https://github.com/settings/personal-access-tokens"
71
+ );
72
+ if (
73
+ !(await confirm({ message: "Try creating the Github release again?" }))
74
+ ) {
75
+ throw new Error("Unable to create a Github release");
76
+ }
77
+
78
+ return createRelease({ ...options, override: true });
79
+ }
80
+ }
@@ -0,0 +1,33 @@
1
+ import confirm from "@inquirer/confirm";
2
+ import rawlist from "@inquirer/rawlist";
3
+ import { execSync } from "node:child_process";
4
+ import { readFile, readdir } from "node:fs/promises";
5
+ import { join } from "node:path";
6
+
7
+ export async function getCurrentChangeset(): Promise<string> {
8
+ let changesetName = execSync(
9
+ "git diff --name-only @{upstream} .changeset/*.md"
10
+ )
11
+ .toString()
12
+ .trim();
13
+
14
+ if (
15
+ !changesetName ||
16
+ !(await confirm({
17
+ message: `Is "${changesetName}" the correct changeset path?`,
18
+ }))
19
+ ) {
20
+ const changesetNames = await readdir(".changeset");
21
+ changesetName = await rawlist({
22
+ message: "Select the changeset path",
23
+ choices: changesetNames
24
+ .filter((changeset) => changeset.endsWith(".md"))
25
+ .map((changeset) => ({
26
+ value: changeset,
27
+ })),
28
+ });
29
+ changesetName = join(".changeset", changesetName);
30
+ }
31
+
32
+ return await readFile(changesetName, "utf8");
33
+ }
@@ -0,0 +1,37 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import { resolve } from "node:path";
3
+
4
+ export type PackageManager = "npm" | "yarn" | "pnpm";
5
+
6
+ export async function getPackageManager(): Promise<PackageManager> {
7
+ const rawPackageJson = await readFile(
8
+ resolve(process.cwd(), "package.json"),
9
+ "utf8"
10
+ );
11
+ const packageJson = JSON.parse(rawPackageJson);
12
+
13
+ if (typeof packageJson.volta === "object" && packageJson.volta) {
14
+ const { volta } = packageJson;
15
+ if ("pnpm" in volta) {
16
+ return "pnpm";
17
+ }
18
+
19
+ if ("yarn" in volta) {
20
+ return "yarn";
21
+ }
22
+
23
+ return "npm";
24
+ }
25
+
26
+ if (typeof packageJson.packageManager === "string") {
27
+ const mgr = packageJson.packageManagerreplace(/@.+/, "");
28
+
29
+ if (mgr === "pnpm" || mgr === "yarn" || mgr === "npm") {
30
+ return mgr;
31
+ }
32
+
33
+ throw new Error(`Unsupported package mananger "${mgr}" in package.json`);
34
+ }
35
+
36
+ throw new Error("Unable to find a package manager");
37
+ }
@@ -0,0 +1,83 @@
1
+ import confirm from "@inquirer/confirm";
2
+ import input from "@inquirer/input";
3
+ import { readFile } from "node:fs/promises";
4
+ import { resolve } from "node:path";
5
+
6
+ import { getUnpushedTags } from "./getUnpushedTags.js";
7
+
8
+ export interface GetPendingReleasesOptions {
9
+ /**
10
+ * This should be a record of package names to paths for monorepos.
11
+ *
12
+ * @example Monorepo Setup
13
+ * ```tsx
14
+ * packagePaths: {
15
+ * "@react-md/core": "./packages/core",
16
+ * "docs": "./apps/docs"
17
+ * },
18
+ * ```
19
+ *
20
+ * If this is omitted or not matched, it will default to `"."`
21
+ *
22
+ * @defaultValue `{}`
23
+ */
24
+ packagePaths?: Record<string, string>;
25
+ }
26
+
27
+ export interface PendingRelease {
28
+ tagName: string;
29
+ body: string;
30
+ }
31
+
32
+ export async function getPendingReleases(
33
+ options: GetPendingReleasesOptions
34
+ ): Promise<readonly PendingRelease[]> {
35
+ const { packagePaths = {} } = options;
36
+ const unpushedTags = getUnpushedTags();
37
+ if (unpushedTags.length === 0) {
38
+ throw new Error("Unable to find any pending releases");
39
+ }
40
+
41
+ const pending: PendingRelease[] = [];
42
+ for (const unpushedTag of unpushedTags) {
43
+ if (
44
+ !(await confirm({ message: `Include ${unpushedTag} in the release?` }))
45
+ ) {
46
+ continue;
47
+ }
48
+
49
+ const name = unpushedTag.replace(/@\d+.+$/, "");
50
+ const path = await input({
51
+ message: `${name} CHANGELOG exists at:`,
52
+ default: packagePaths[name] ?? ".",
53
+ });
54
+
55
+ const changelog = await readFile(
56
+ resolve(process.cwd(), path, "CHANGELOG.md"),
57
+ "utf8"
58
+ );
59
+
60
+ let body = "";
61
+ let isTracking = false;
62
+ const lines = changelog.split(/\r?\n/);
63
+ for (const line of lines) {
64
+ if (line.startsWith("## ")) {
65
+ if (isTracking) {
66
+ break;
67
+ }
68
+
69
+ isTracking = true;
70
+ body = line;
71
+ } else if (isTracking) {
72
+ body += `\n${line}`;
73
+ }
74
+ }
75
+
76
+ pending.push({
77
+ body,
78
+ tagName: unpushedTag,
79
+ });
80
+ }
81
+
82
+ return pending;
83
+ }
@@ -0,0 +1,23 @@
1
+ import { execSync } from "node:child_process";
2
+
3
+ function getTags(local: boolean): Set<string> {
4
+ const command = local
5
+ ? "git tag --sort=-creatordate"
6
+ : "git ls-remote --tags origin";
7
+ const tags = execSync(command).toString().trim();
8
+ const lines = tags.split(/\r?\n/);
9
+ if (local) {
10
+ return new Set(lines);
11
+ }
12
+
13
+ return new Set(
14
+ lines.map((line) => line.replace(/^.+refs\/tags\//, "").replace("^{}", ""))
15
+ );
16
+ }
17
+
18
+ export function getUnpushedTags(): readonly string[] {
19
+ const localTags = getTags(true);
20
+ const pushedTags = getTags(false);
21
+
22
+ return [...localTags.difference(pushedTags)];
23
+ }
package/src/index.ts ADDED
@@ -0,0 +1,2 @@
1
+ export * from "./createRelease.js";
2
+ export * from "./release.js";
package/src/release.ts ADDED
@@ -0,0 +1,81 @@
1
+ import { type ExecSyncOptions, execSync } from "node:child_process";
2
+
3
+ import { continueRelease } from "./continueRelease.js";
4
+ import {
5
+ type ConfigurableCreateReleaseOptions,
6
+ createRelease,
7
+ } from "./createRelease.js";
8
+ import { getPackageManager } from "./getPackageManager.js";
9
+ import {
10
+ type GetPendingReleasesOptions,
11
+ getPendingReleases,
12
+ } from "./getPendingReleases.js";
13
+
14
+ const exec = (command: string, opts?: ExecSyncOptions): void => {
15
+ console.log(command);
16
+ execSync(command, opts);
17
+ };
18
+
19
+ export interface ReleaseOptions
20
+ extends ConfigurableCreateReleaseOptions, GetPendingReleasesOptions {
21
+ /**
22
+ * @defaultValue `!buildCommand`
23
+ */
24
+ skipBuild?: boolean;
25
+
26
+ /**
27
+ * @defaultValue `"clean"`
28
+ */
29
+ cleanCommand?: string;
30
+
31
+ /**
32
+ * @defaultValue `"build"`
33
+ */
34
+ buildCommand?: string;
35
+
36
+ /**
37
+ * @defaultValue `"build(version): version package"`
38
+ */
39
+ versionMessage?: string;
40
+ }
41
+
42
+ export async function release(options: ReleaseOptions): Promise<void> {
43
+ const {
44
+ owner,
45
+ repo,
46
+ envPath,
47
+ cleanCommand = "clean",
48
+ buildCommand = "build",
49
+ skipBuild = !buildCommand,
50
+ versionMessage = "build(version): version package",
51
+ } = options;
52
+
53
+ const pkgManager = await getPackageManager();
54
+
55
+ if (!skipBuild) {
56
+ exec(`${pkgManager} ${cleanCommand}`);
57
+ exec(`${pkgManager} ${buildCommand}`);
58
+ }
59
+ await continueRelease();
60
+
61
+ exec("pnpm changeset version", { stdio: "inherit" });
62
+ exec("git add -u");
63
+ await continueRelease();
64
+
65
+ exec(`git commit -m "${versionMessage}"`);
66
+ exec(`${pkgManager} changeset publish`, { stdio: "inherit" });
67
+ const pendingReleases = await getPendingReleases(options);
68
+
69
+ exec("git push --follow-tags");
70
+
71
+ for (const release of pendingReleases) {
72
+ await createRelease({
73
+ owner,
74
+ repo,
75
+ body: release.body,
76
+ tagName: release.tagName,
77
+ envPath,
78
+ prerelease: /-(alpha|next|beta)\.\d+$/.test(release.tagName),
79
+ });
80
+ }
81
+ }
@@ -1 +0,0 @@
1
- export declare function continueRelease(): Promise<void>;
@@ -1,7 +0,0 @@
1
- import confirm from "@inquirer/confirm";
2
- export async function continueRelease() {
3
- const confirmed = await confirm({ message: "Continue the release?" });
4
- if (!confirmed) {
5
- process.exit(1);
6
- }
7
- }
@@ -1 +0,0 @@
1
- export declare function createGetTagName(mainPackage: string | undefined): () => Promise<string>;
@@ -1,19 +0,0 @@
1
- import confirm from "@inquirer/confirm";
2
- import input from "@inquirer/input";
3
- import { getLatestTag } from "./getLatestTag.js";
4
- export function createGetTagName(mainPackage) {
5
- return async function getTagName() {
6
- const latestTag = getLatestTag();
7
- let tagName = mainPackage && /@\d/.test(latestTag)
8
- ? // most likely a monorepo
9
- latestTag.replace(/.+(@\d)/, `${mainPackage}$1`)
10
- : latestTag;
11
- while (!tagName ||
12
- !(await confirm({ message: `Use "${tagName}" for the next tag name?` }))) {
13
- tagName = await input({
14
- message: "Enter the tag name",
15
- });
16
- }
17
- return tagName;
18
- };
19
- }
@@ -1,24 +0,0 @@
1
- export interface ConfigurableCreateReleaseOptions {
2
- repo: string;
3
- /**
4
- * @defaultValue `"mlaursen"`
5
- */
6
- owner?: string;
7
- /**
8
- * The `.env` file to load to get the {@link tokenName} environment variable.
9
- *
10
- * @defaultValue `".env.local"`
11
- */
12
- envPath?: string;
13
- /**
14
- * @defaultValue `"GITHUB_RELEASE_TOKEN"`
15
- */
16
- tokenName?: string;
17
- }
18
- export interface CreateReleaseOptions extends ConfigurableCreateReleaseOptions {
19
- body: string;
20
- override?: boolean;
21
- tagName: string;
22
- prerelease: boolean;
23
- }
24
- export declare function createRelease(options: CreateReleaseOptions): Promise<void>;
@@ -1,28 +0,0 @@
1
- import confirm from "@inquirer/confirm";
2
- import { Octokit } from "@octokit/core";
3
- import dotenv from "dotenv";
4
- export async function createRelease(options) {
5
- const { body, override, owner = "mlaursen", repo, prerelease, envPath = ".env.local", tagName, tokenName = "GITHUB_RELEASE_TOKEN", } = options;
6
- dotenv.config({ path: envPath, override, quiet: true });
7
- const octokit = new Octokit({ auth: process.env[tokenName] });
8
- try {
9
- const response = await octokit.request("POST /repos/{owner}/{repo}/releases", {
10
- owner,
11
- repo,
12
- tag_name: tagName,
13
- body,
14
- prerelease,
15
- });
16
- console.log(`Created release: ${response.data.html_url}`);
17
- }
18
- catch (e) {
19
- console.error(e);
20
- console.log();
21
- console.log("The npm token is most likely expired or never created. Update the `.env.local` to include the latest GITHUB_TOKEN");
22
- console.log("Regenerate the token: https://github.com/settings/personal-access-tokens");
23
- if (!(await confirm({ message: "Try creating the Github release again?" }))) {
24
- process.exit(1);
25
- }
26
- return createRelease({ ...options, override: true });
27
- }
28
- }
@@ -1 +0,0 @@
1
- export declare function getCurrentChangeset(): Promise<string>;
@@ -1,26 +0,0 @@
1
- import confirm from "@inquirer/confirm";
2
- import rawlist from "@inquirer/rawlist";
3
- import { execSync } from "node:child_process";
4
- import { readdir, readFile } from "node:fs/promises";
5
- import { join } from "node:path";
6
- export async function getCurrentChangeset() {
7
- let changesetName = execSync("git diff --name-only @{upstream} .changeset/*.md")
8
- .toString()
9
- .trim();
10
- if (!changesetName ||
11
- !(await confirm({
12
- message: `Is "${changesetName}" the correct changeset path?`,
13
- }))) {
14
- const changesetNames = await readdir(".changeset");
15
- changesetName = await rawlist({
16
- message: "Select the changeset path",
17
- choices: changesetNames
18
- .filter((changeset) => changeset.endsWith(".md"))
19
- .map((changeset) => ({
20
- value: changeset,
21
- })),
22
- });
23
- changesetName = join(".changeset", changesetName);
24
- }
25
- return await readFile(changesetName, "utf8");
26
- }
@@ -1 +0,0 @@
1
- export declare function getLatestTag(): string;
@@ -1,4 +0,0 @@
1
- import { execSync } from "node:child_process";
2
- export function getLatestTag() {
3
- return execSync("git tag --sort=-creatordate | head -1").toString().trim();
4
- }
@@ -1,2 +0,0 @@
1
- export type PackageManager = "npm" | "yarn" | "pnpm";
2
- export declare function getPackageManager(): Promise<PackageManager>;
@@ -1,24 +0,0 @@
1
- import { readFile } from "node:fs/promises";
2
- import { resolve } from "node:path";
3
- export async function getPackageManager() {
4
- const rawPackageJson = await readFile(resolve(process.cwd(), "package.json"), "utf8");
5
- const packageJson = JSON.parse(rawPackageJson);
6
- if (typeof packageJson.volta === "object" && packageJson.volta) {
7
- const { volta } = packageJson;
8
- if ("pnpm" in volta) {
9
- return "pnpm";
10
- }
11
- if ("yarn" in volta) {
12
- return "yarn";
13
- }
14
- return "npm";
15
- }
16
- if (typeof packageJson.packageManager === "string") {
17
- const mgr = packageJson.packageManagerreplace(/@.+/, "");
18
- if (mgr === "pnpm" || mgr === "yarn" || mgr === "npm") {
19
- return mgr;
20
- }
21
- throw new Error(`Unsupported package mananger "${mgr}" in package.json`);
22
- }
23
- throw new Error("Unable to find a package manager");
24
- }
package/dist/index.js DELETED
@@ -1,2 +0,0 @@
1
- export * from "./release.js";
2
- export * from "./getLatestTag.js";
package/dist/release.d.ts DELETED
@@ -1,22 +0,0 @@
1
- import { ConfigurableCreateReleaseOptions } from "./createRelease.js";
2
- export interface ReleaseOptions extends ConfigurableCreateReleaseOptions {
3
- /**
4
- * @defaultValue `!buildCommand`
5
- */
6
- skipBuild?: boolean;
7
- /**
8
- * @defaultValue `"clean"`
9
- */
10
- cleanCommand?: string;
11
- /**
12
- * @defaultValue `"build"`
13
- */
14
- buildCommand?: string;
15
- mainPackage?: string;
16
- /**
17
- * @defaultValue `"build(version): version package"`
18
- */
19
- versionMessage?: string;
20
- getTagName?: () => Promise<string>;
21
- }
22
- export declare function release(options: ReleaseOptions): Promise<void>;
package/dist/release.js DELETED
@@ -1,39 +0,0 @@
1
- import { execSync } from "node:child_process";
2
- import { continueRelease } from "./continueRelease.js";
3
- import { createGetTagName } from "./createGetTagName.js";
4
- import { createRelease, } from "./createRelease.js";
5
- import { getCurrentChangeset } from "./getCurrentChangeset.js";
6
- import { getPackageManager } from "./getPackageManager.js";
7
- const exec = (command, opts) => {
8
- console.log(command);
9
- execSync(command, opts);
10
- };
11
- export async function release(options) {
12
- const { owner, repo, envPath, cleanCommand = "clean", buildCommand = "build", skipBuild = !buildCommand, mainPackage, getTagName = createGetTagName(mainPackage), versionMessage = "build(version): version package", } = options;
13
- const pkgManager = await getPackageManager();
14
- if (!skipBuild) {
15
- exec(`${pkgManager} ${cleanCommand}`);
16
- exec(`${pkgManager} ${buildCommand}`);
17
- }
18
- exec(`${pkgManager} changeset`, { stdio: "inherit" });
19
- await continueRelease();
20
- exec("git add -u");
21
- exec("git add .changeset");
22
- const changeset = await getCurrentChangeset();
23
- exec("pnpm changeset version", { stdio: "inherit" });
24
- exec("git add -u");
25
- await continueRelease();
26
- exec(`git commit -m "${versionMessage}"`);
27
- exec(`${pkgManager} changeset publish`, { stdio: "inherit" });
28
- const tagName = await getTagName();
29
- await continueRelease();
30
- exec("git push --follow-tags");
31
- await createRelease({
32
- owner,
33
- repo,
34
- body: changeset,
35
- tagName,
36
- envPath,
37
- prerelease: /-(alpha|next|beta)\.\d+$/.test(tagName),
38
- });
39
- }