aberlaas-release 2.20.1 → 2.21.1

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.
@@ -3,66 +3,49 @@ import {
3
3
  consoleInfo,
4
4
  consoleWarn,
5
5
  env,
6
- firostError,
6
+ exists,
7
7
  prompt,
8
+ read,
8
9
  readJson,
9
10
  run,
10
11
  sleep,
11
- wrap,
12
12
  write,
13
13
  } from 'firost';
14
14
  import { hostGitPath, hostPackagePath } from 'aberlaas-helper';
15
+ import { parse as parseEnvrc, stringify as stringifyEnvrc } from 'envfile';
15
16
 
16
- export const __ = {
17
- /**
18
- * Ensures the user is logged in to npm by checking authentication status and prompting for login if needed.
19
- * @returns {boolean|undefined} Returns true if already logged in, undefined if login was required and completed
20
- * @throws {Error} Throws error if npm authentication fails for reasons other than E401 unauthorized
21
- */
22
- async ensureNpmLogin() {
23
- try {
24
- await __.npmRun('whoami');
25
- return true;
26
- } catch (err) {
27
- if (err.code == 'ABERLAAS_RELEASE_NPM_ERROR_E401') {
28
- await __.waitForNpmLogin();
29
- return;
30
- }
31
- throw err;
32
- }
33
- },
17
+ export let __;
18
+
19
+ /**
20
+ * Ensures the user is logged in to npm by checking authentication status and prompting for login if needed.
21
+ * @returns {boolean|undefined} Returns true if already logged in, undefined if login was required and completed
22
+ * @throws {Error} Throws error if npm authentication fails for reasons other than E401 unauthorized
23
+ */
24
+ export async function ensureNpmLogin() {
25
+ if (await __.isAuthenticated()) {
26
+ return true;
27
+ }
34
28
 
29
+ await __.waitForNpmLogin();
30
+ }
31
+
32
+ __ = {
33
+ ensureNpmLogin,
35
34
  /**
36
- * Executes an npm command and returns its output
37
- * @param {string} command - The npm command to execute (without 'npm' prefix)
38
- * @returns {string} The stdout output from the npm command
39
- * @throws {Error} Throws a formatted error if the npm command fails
35
+ * Checks if the user is authenticated with npm by running 'yarn npm whoami' command
36
+ * @returns {Promise<boolean>} Promise that resolves to true if authenticated, false otherwise
40
37
  */
41
- async npmRun(command) {
38
+ async isAuthenticated() {
42
39
  try {
43
- const { stdout } = await __.run(`npm ${command}`, {
44
- stdout: false,
40
+ await __.run('yarn npm whoami', {
45
41
  stderr: false,
42
+ stdout: false,
46
43
  });
47
- return stdout;
48
- } catch ({ stderr }) {
49
- const errorLines = _.split(stderr, '\n');
50
-
51
- // Identify known npm errors
52
- if (_.startsWith(errorLines[0], 'npm error code')) {
53
- const npmErrorCode = _.replace(errorLines[0], 'npm error code ', '');
54
- const npmErrorMessage = _.chain(errorLines).slice(1).join('\n').value();
55
- throw firostError(
56
- `ABERLAAS_RELEASE_NPM_ERROR_${npmErrorCode}`,
57
- npmErrorMessage,
58
- );
59
- }
60
-
61
- // Throw unknown errors up
62
- throw firostError('ABERLAAS_RELEASE_NPM_UNKNOWN_ERROR', stderr);
44
+ return true;
45
+ } catch (_err) {
46
+ return false;
63
47
  }
64
48
  },
65
-
66
49
  /**
67
50
  * Prompts the user to create and configure an npm authentication token when not logged in.
68
51
  * Opens the npm token creation page in browser, guides user through token setup,
@@ -83,9 +66,8 @@ export const __ = {
83
66
  * Displays login instructions to the user
84
67
  */
85
68
  async displayLoginInstructions() {
86
- const rootPackage = await readJson(hostPackagePath('package.json'));
87
- const packageName = rootPackage.name;
88
- const tokenName = __.generateTokenName(packageName);
69
+ const packageJson = await readJson(hostPackagePath('package.json'));
70
+ const tokenName = __.generateTokenName(packageJson);
89
71
 
90
72
  __.consoleInfo('');
91
73
  __.consoleInfo('Your npm token page will open.');
@@ -97,7 +79,7 @@ export const __ = {
97
79
  __.consoleInfo('');
98
80
  __.consoleInfo('PACKAGE AND SCOPES');
99
81
  __.consoleInfo('Permissions: Read and write');
100
- __.consoleInfo(`🔘 Only select packages and scopes: ${packageName}`);
82
+ __.consoleInfo('🔘 All packages');
101
83
  __.consoleInfo('');
102
84
  __.consoleInfo('EXPIRATION');
103
85
  __.consoleInfo('Expiration date: 90 days');
@@ -105,15 +87,25 @@ export const __ = {
105
87
  },
106
88
 
107
89
  /**
108
- * Generates a suggested token name from package name
109
- * @param {string} packageName - The package name
90
+ * Generates a suggested token name from package.json data
91
+ * @param {object} packageJson - The package.json content
110
92
  * @returns {string} The suggested token name
111
93
  */
112
- generateTokenName(packageName) {
94
+ generateTokenName(packageJson) {
95
+ let packageName = packageJson.name;
96
+
97
+ // For workspace roots (monorepo/libdocs), strip suffixes
98
+ if (packageJson.workspaces) {
99
+ packageName = _.chain(packageName)
100
+ .replace(/-monorepo$/, '')
101
+ .replace(/-root$/, '')
102
+ .value();
103
+ }
104
+
113
105
  const cleanPackageName = _.chain(packageName)
114
- .replace('-', '_')
115
- .replace('@', '')
116
- .replace('/', '_')
106
+ .replaceAll('-', '_')
107
+ .replaceAll('@', '')
108
+ .replaceAll('/', '_')
117
109
  .toUpper()
118
110
  .value();
119
111
  return `ABERLAAS_RELEASE_${cleanPackageName}`;
@@ -151,13 +143,19 @@ export const __ = {
151
143
  },
152
144
 
153
145
  /**
154
- * Saves the npm token to .npmrc file
146
+ * Saves the npm token to .envrc file
155
147
  */
156
148
  async saveNpmToken() {
149
+ // TODO: replace with keyleth
150
+ const envrcPath = hostGitPath('.envrc');
151
+ const envrcAsJson = (await exists(envrcPath))
152
+ ? parseEnvrc(await read(envrcPath))
153
+ : {};
154
+
157
155
  const npmToken = await __.prompt('Enter you new token here:');
158
- const npmrcContent = `//registry.npmjs.org/:_authToken=${npmToken}`;
159
- const npmrcPath = hostGitPath('.npmrc');
160
- await write(npmrcContent, npmrcPath);
156
+ envrcAsJson.ABERLAAS_RELEASE_NPM_AUTH_TOKEN = npmToken;
157
+
158
+ await write(stringifyEnvrc(envrcAsJson), envrcPath);
161
159
  },
162
160
  run,
163
161
  env,
@@ -166,5 +164,3 @@ export const __ = {
166
164
  consoleInfo,
167
165
  sleep,
168
166
  };
169
-
170
- export const ensureNpmLogin = wrap(__, 'ensureNpmLogin');
@@ -4,7 +4,44 @@ import { hostGitRoot, yarnRun } from 'aberlaas-helper';
4
4
  import Gilmore from 'gilmore';
5
5
  import { ensureNpmLogin } from './ensureNpmLogin.js';
6
6
 
7
- export const __ = {
7
+ export let __;
8
+
9
+ /**
10
+ * Validate all pre-conditions before starting the release
11
+ * @param {object} cliArgs Release options
12
+ * @param {boolean} [cliArgs.test=true] Run test execution
13
+ * @param {boolean} [cliArgs.lint=true] Run lint execution
14
+ * @returns {Promise<void>}
15
+ */
16
+ export async function ensureValidSetup(cliArgs = {}) {
17
+ // Default options: test and lint enabled unless explicitly disabled via CLI
18
+ const options = {
19
+ test: true,
20
+ lint: true,
21
+ ...cliArgs,
22
+ };
23
+
24
+ __.ensureCorrectBumpType(cliArgs);
25
+
26
+ const repo = new Gilmore(hostGitRoot());
27
+
28
+ // Need to be on branch main
29
+ await __.ensureCorrectBranch(repo);
30
+
31
+ // Need to have a clean directory
32
+ await __.ensureCleanRepository(repo);
33
+
34
+ // Check npm login
35
+ await __.ensureNpmLogin();
36
+
37
+ // Check tests are passing
38
+ await __.ensureTestsArePassing(options);
39
+
40
+ // Check lint is passing
41
+ await __.ensureLintIsPassing(options);
42
+ }
43
+
44
+ __ = {
8
45
  /**
9
46
  * Validates that the provided bump type is one of the accepted semantic
10
47
  * versioning types, or empty for auto-detection.
@@ -67,11 +104,12 @@ export const __ = {
67
104
 
68
105
  /**
69
106
  * Ensures that all tests are passing before proceeding with a release
70
- * @param {object} cliArgs Release options
107
+ * @param {object} options Release options
108
+ * @param {boolean} [options.test=true] Run tests
71
109
  * @returns {Promise<void>} A promise that resolves if tests pass, rejects with ABERLAAS_RELEASE_TESTS_FAILING error if tests fail
72
110
  */
73
- async ensureTestsArePassing(cliArgs = {}) {
74
- if (cliArgs['skip-test']) {
111
+ async ensureTestsArePassing(options = {}) {
112
+ if (!options.test) {
75
113
  return false;
76
114
  }
77
115
  __.consoleInfo('Running tests...');
@@ -85,12 +123,13 @@ export const __ = {
85
123
 
86
124
  /**
87
125
  * Ensures that linting passes by running the lint process and throwing an error if it fails
88
- * @param {object} cliArgs Release options
126
+ * @param {object} options Release options
127
+ * @param {boolean} [options.lint=true] Run lint
89
128
  * @returns {Promise<void>} A promise that resolves if linting passes
90
129
  * @throws {Error} Throws ABERLAAS_RELEASE_LINT_FAILING error if linting fails
91
130
  */
92
- async ensureLintIsPassing(cliArgs = {}) {
93
- if (cliArgs['skip-lint']) {
131
+ async ensureLintIsPassing(options = {}) {
132
+ if (!options.lint) {
94
133
  return false;
95
134
  }
96
135
  __.consoleInfo('Running lint...');
@@ -107,31 +146,3 @@ export const __ = {
107
146
  firostRun,
108
147
  yarnRun,
109
148
  };
110
-
111
- /**
112
- * Validate all pre-conditions before starting the release
113
- * @param {object} cliArgs Release options
114
- * @param {boolean} cliArgs.skipTest Skip test execution
115
- * @param {boolean} cliArgs.skipLint Skip lint execution
116
- * @returns {Promise<void>}
117
- */
118
- export async function ensureValidSetup(cliArgs = {}) {
119
- __.ensureCorrectBumpType(cliArgs);
120
-
121
- const repo = new Gilmore(hostGitRoot());
122
-
123
- // Need to be on branch main
124
- await __.ensureCorrectBranch(repo);
125
-
126
- // Need to have a clean directory
127
- await __.ensureCleanRepository(repo);
128
-
129
- // Check npm login
130
- await __.ensureNpmLogin();
131
-
132
- // Check tests are passing
133
- await __.ensureTestsArePassing(cliArgs);
134
-
135
- // Check lint is passing
136
- await __.ensureLintIsPassing(cliArgs);
137
- }
@@ -90,22 +90,27 @@ export const __ = {
90
90
  /**
91
91
  * Gathers all release information from CLI arguments
92
92
  * @param {object} cliArgs - CLI arguments from minimist
93
- * @returns {object} Release data containing bumpType, allPackages, currentVersion, newVersion, skipChangelog
93
+ * @returns {object} Release data containing bumpType, allPackages, currentVersion, newVersion, changelog
94
94
  */
95
95
  export async function getReleaseData(cliArgs) {
96
+ // Default options: changelog enabled unless explicitly disabled via CLI
97
+ const options = {
98
+ changelog: true,
99
+ ...cliArgs,
100
+ };
101
+
96
102
  const allPackages = await __.getAllPackagesToRelease();
97
103
  const currentVersion = allPackages[0].content.version;
98
104
 
99
105
  const bumpType = await __.getBumpType(cliArgs, currentVersion);
100
106
 
101
107
  const newVersion = semver.inc(currentVersion, bumpType);
102
- const skipChangelog = !!cliArgs['skip-changelog'];
103
108
 
104
109
  return {
105
110
  bumpType,
106
111
  allPackages,
107
112
  currentVersion,
108
113
  newVersion,
109
- skipChangelog,
114
+ changelog: options.changelog,
110
115
  };
111
116
  }
package/lib/main.js CHANGED
@@ -1,8 +1,7 @@
1
- import path from 'node:path';
2
- import { pMap } from 'golgoth';
3
1
  import { consoleInfo, run as firostRun } from 'firost';
4
2
  import { ensureValidSetup } from './ensureValidSetup.js';
5
3
  import { getReleaseData } from './getReleaseData.js';
4
+ import { publishToNpm } from './publishToNpm.js';
6
5
  import { updateGitRepo } from './updateGitRepo.js';
7
6
 
8
7
  export let __;
@@ -20,31 +19,14 @@ export async function run(cliArgs = {}) {
20
19
 
21
20
  await __.updateGitRepo(releaseData);
22
21
 
23
- await __.publishAllPackagesToNpm(releaseData);
22
+ await __.publishToNpm(releaseData);
24
23
  }
25
24
 
26
25
  __ = {
27
- /**
28
- * Publishes all packages to npm
29
- * @param {object} releaseData - Release data containing allPackages
30
- */
31
- async publishAllPackagesToNpm(releaseData) {
32
- await pMap(
33
- releaseData.allPackages,
34
- async ({ filepath, content }) => {
35
- const packageName = content.name;
36
- __.consoleInfo(`Publishing ${packageName} to npm`);
37
-
38
- const packageDir = path.dirname(filepath);
39
- await __.firostRun('npm publish --access public', { cwd: packageDir });
40
- },
41
- { concurrency: 1 },
42
- );
43
- },
44
-
45
26
  ensureValidSetup,
46
- updateGitRepo,
47
27
  getReleaseData,
28
+ publishToNpm,
29
+ updateGitRepo,
48
30
  consoleInfo,
49
31
  firostRun,
50
32
  };
@@ -0,0 +1,57 @@
1
+ import path from 'node:path';
2
+ import { pMap } from 'golgoth';
3
+ import { firostError, run, spinner } from 'firost';
4
+
5
+ export let __;
6
+
7
+ /**
8
+ * Publishes all packages in the release data to npm with public access
9
+ * @param {object} releaseData - The release data containing package information
10
+ * @param {Array<object>} releaseData.allPackages - Array of package objects to publish
11
+ * @param {string} releaseData.allPackages[].filepath - Path to the package.json file
12
+ * @param {object} releaseData.allPackages[].content - Package.json content object
13
+ * @param {string} releaseData.allPackages[].content.name - Name of the package
14
+ * @returns {Promise<void>} Promise that resolves when all packages are published
15
+ */
16
+ export async function publishToNpm(releaseData) {
17
+ const { allPackages, newVersion } = releaseData;
18
+ const progress = __.spinner(allPackages.length);
19
+
20
+ await pMap(
21
+ allPackages,
22
+ async (packageData) => {
23
+ const packageName = packageData.content.name;
24
+ progress.tick(`Publishing ${packageName}@${newVersion}`);
25
+
26
+ await __.publishPackage(packageData);
27
+ },
28
+ { concurrency: 5 },
29
+ );
30
+ progress.success('All packages published to npm');
31
+ }
32
+
33
+ __ = {
34
+ async publishPackage(packageData) {
35
+ const { filepath, content } = packageData;
36
+
37
+ try {
38
+ // Note:
39
+ // ✘ npm publish <= Keeps workspace:* in dependencies
40
+ // ✔ yarn npm publish <= Replaces workspace:* with actual versions
41
+ await __.run('yarn npm publish --access public', {
42
+ cwd: path.dirname(filepath),
43
+ stdout: false,
44
+ stderr: false,
45
+ });
46
+ return true;
47
+ } catch (err) {
48
+ const packageName = content.name;
49
+ throw firostError(
50
+ 'ABERLAAS_RELEASE_NPM_PUBLISH_FAILED',
51
+ `Failed to publish ${packageName} to npm:\n${err.message}`,
52
+ );
53
+ }
54
+ },
55
+ run,
56
+ spinner,
57
+ };
@@ -15,7 +15,7 @@ import cliMarkdown from 'cli-markdown';
15
15
  export const __ = {
16
16
  /**
17
17
  * Generate changelog markdown from git commits between two versions
18
- * @param {object} releaseData - Release data containing currentVersion, newVersion, and skipChangelog
18
+ * @param {object} releaseData - Release data containing currentVersion, newVersion, and changeLog
19
19
  * @returns {string} Generated changelog markdown
20
20
  */
21
21
  async generateChangelogFromGit(releaseData) {
@@ -136,10 +136,11 @@ export const __ = {
136
136
 
137
137
  /**
138
138
  * Update the CHANGELOG.md file with new additions
139
- * @param {object} releaseData - Release data containing currentVersion, newVersion, and skipChangelog
139
+ * @param {object} releaseData - Release data containing currentVersion, newVersion, and changeLog
140
+ * @param {boolean} [releaseData.changelog=true] Generate changelog
140
141
  */
141
142
  export async function updateChangelog(releaseData) {
142
- if (releaseData.skipChangelog) {
143
+ if (!releaseData.changelog) {
143
144
  return;
144
145
  }
145
146
 
@@ -10,10 +10,14 @@ export const __ = {
10
10
  * @param {object} releaseData - Release data containing allPackages and newVersion
11
11
  */
12
12
  async bumpAllPackageVersions(releaseData) {
13
- await pMap(releaseData.allPackages, async ({ filepath, content }) => {
13
+ const { newVersion } = releaseData;
14
+
15
+ await pMap(releaseData.allPackages, async (packageData) => {
16
+ const { filepath, content } = packageData;
14
17
  const packageName = content.name;
15
- __.consoleInfo(`Updating ${packageName} to ${releaseData.newVersion}`);
16
- const newContent = { ...content, version: releaseData.newVersion };
18
+ __.consoleInfo(`Updating ${packageName} to ${newVersion}`);
19
+
20
+ const newContent = { ...content, version: newVersion };
17
21
  await writeJson(newContent, filepath, {
18
22
  sort: false,
19
23
  });
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "type": "module",
4
4
  "sideEffects": false,
5
5
  "description": "aberlaas release command: Release and publish new versions",
6
- "version": "2.20.1",
6
+ "version": "2.21.1",
7
7
  "repository": "pixelastic/aberlaas",
8
8
  "homepage": "https://projects.pixelastic.com/aberlaas/",
9
9
  "author": "Tim Carry (@pixelastic)",
@@ -19,14 +19,15 @@
19
19
  "node": ">=18.18.0"
20
20
  },
21
21
  "dependencies": {
22
- "aberlaas-helper": "workspace:*",
23
- "aberlaas-lint": "workspace:*",
24
- "aberlaas-test": "workspace:*",
22
+ "aberlaas-helper": "2.21.1",
23
+ "aberlaas-lint": "2.21.1",
24
+ "aberlaas-test": "2.21.1",
25
25
  "changelogen": "0.6.2",
26
26
  "cli-markdown": "3.5.1",
27
+ "envfile": "7.1.0",
27
28
  "firost": "5.5.1",
28
29
  "gilmore": "1.2.0",
29
- "golgoth": "3.0.0",
30
+ "golgoth": "3.1.0",
30
31
  "semver": "7.7.3"
31
32
  },
32
33
  "scripts": {
@@ -42,4 +43,4 @@
42
43
  "lint": "cd ../.. && ./scripts/lint",
43
44
  "lint:fix": "cd ../.. && ./scripts/lint-fix"
44
45
  }
45
- }
46
+ }