npm-workspaces-publish-tool 0.0.15 → 0.0.17

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
@@ -23,13 +23,16 @@ npm install -g npm-workspaces-publish-tool
23
23
  ## Usage
24
24
 
25
25
  ```bash
26
- nw-publish [--dry-run] [--registry <url>]
26
+ nw-publish [--dry-run] [--registry <url>] [--auto-tick] [--anchor-tag-suffix <suffix>] [--allow-no-upstream]
27
27
  ```
28
28
 
29
29
  ### Command Options
30
30
 
31
31
  - `--dry-run`: Runs validation and preview without actual publishing
32
32
  - `--registry <url>`: (Optional) NPM registry URL to publish scoped packages to
33
+ - `--auto-tick`: Automatically increment patch versions for packages that need version bumps
34
+ - `--anchor-tag-suffix <suffix>`: (Optional) Suffix to append to git tags (e.g., `-rc` creates tags like `v1.2.3-rc`)
35
+ - `--allow-no-upstream`: Allow publishing from branches without upstream tracking (see [CI/CD Usage](#cicd-usage))
33
36
 
34
37
  ## Workflow
35
38
  1. **Validation Phase**
@@ -43,6 +46,39 @@ nw-publish [--dry-run] [--registry <url>]
43
46
  - Publishes packages in topological order
44
47
  - Restores original `package.json` files after publishing
45
48
 
49
+ ## CI/CD Usage
50
+
51
+ ### Publishing from New Branches
52
+
53
+ By default, the tool requires branches to have upstream tracking configured (via `git push -u origin <branch>`). This is a safety check for manual usage to prevent accidentally publishing from local-only branches.
54
+
55
+ However, in CI/CD pipelines that create new release branches dynamically, you can use the `--allow-no-upstream` flag:
56
+
57
+ ```bash
58
+ # CI/CD pipeline example
59
+ nw-publish --allow-no-upstream --anchor-tag-suffix=-rc
60
+ ```
61
+
62
+ **When the flag is set:**
63
+ - The tool will automatically set upstream tracking when pushing tags
64
+ - No manual `git push -u` is required beforehand
65
+ - The branch and tags will be pushed together
66
+
67
+ **When to use:**
68
+ - CI/CD pipelines that create new release branches (e.g., `RELEASE/v1.2.3`)
69
+ - Automated release workflows where the branch doesn't exist on remote yet
70
+
71
+ **When not to use:**
72
+ - Manual publishing from your local machine (push your branch first instead)
73
+
74
+ **Safety notes:**
75
+ - The flag only bypasses the "no upstream" check
76
+ - The tool still validates:
77
+ - Clean git status (no uncommitted changes)
78
+ - Unpushed commits (if upstream exists)
79
+ - Version increments
80
+ - Build success
81
+
46
82
  ## Requirements
47
83
  - Node.js 16+
48
84
  - Git
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "npm-workspaces-publish-tool",
3
- "version": "0.0.15",
3
+ "version": "0.0.17",
4
4
  "description": "An unopinionated tool to assist publishing of npm based mono-repo workspaces",
5
5
  "main": "build/src/cli.js",
6
6
  "type": "module",
package/build/src/cli.js CHANGED
@@ -123,7 +123,7 @@ function getDirtyMap(workspaces, lastTag, cwd) {
123
123
  }
124
124
  return dirtyMap;
125
125
  }
126
- function checkForUnpushedCommits(cwd, autoTick) {
126
+ function checkForUnpushedCommits(cwd, autoTick, allowNoUpstream) {
127
127
  const branchResult = git(['rev-parse', '--abbrev-ref', 'HEAD'], { cwd });
128
128
  if (!branchResult.success) {
129
129
  console.error('ERROR: Could not determine current branch.');
@@ -141,8 +141,14 @@ function checkForUnpushedCommits(cwd, autoTick) {
141
141
  console.log(`ℹ️ Branch '${currentBranch}' has no upstream. Will set it during push.`);
142
142
  return true;
143
143
  }
144
- // In manual mode without auto-tick, require upstream to catch mistakes
144
+ // If --allow-no-upstream is set, allow new branches (for CI/CD pipelines)
145
+ if (allowNoUpstream) {
146
+ console.warn(`⚠️ Branch '${currentBranch}' has no upstream. Upstream will be set automatically when pushing tags.`);
147
+ return true;
148
+ }
149
+ // In manual mode, require upstream to catch mistakes
145
150
  console.error(`ERROR: Branch '${currentBranch}' has no upstream.`);
151
+ console.error(`Please push your branch first, or use --allow-no-upstream if this is intentional.`);
146
152
  return false;
147
153
  }
148
154
  const upstream = upstreamResult.stdout.trim();
@@ -158,7 +164,7 @@ function checkForUnpushedCommits(cwd, autoTick) {
158
164
  /**
159
165
  * Check git status for uncommitted changes
160
166
  */
161
- function checkGitStatus(workspaces, dirtyMap, cwd, dryRun, autoTick) {
167
+ function checkGitStatus(workspaces, dirtyMap, cwd, dryRun, autoTick, allowNoUpstream) {
162
168
  const stagedFilesRaw = git(['diff', '--cached', '--name-only'], { cwd })
163
169
  .stdout.trim()
164
170
  .split('\n')
@@ -329,7 +335,7 @@ function checkGitStatus(workspaces, dirtyMap, cwd, dryRun, autoTick) {
329
335
  }
330
336
  }
331
337
  }
332
- return checkForUnpushedCommits(cwd, autoTick);
338
+ return checkForUnpushedCommits(cwd, autoTick, allowNoUpstream);
333
339
  }
334
340
  /**
335
341
  * Get version info for dirty workspaces
@@ -494,7 +500,7 @@ function buildPackages(releaseOrder, packageInfos) {
494
500
  }
495
501
  }
496
502
  }
497
- function validatePublish(dryRun, autoTick) {
503
+ function validatePublish(dryRun, autoTick, allowNoUpstream) {
498
504
  // Push any previous auto-tick commits if in full mode
499
505
  if (!dryRun) {
500
506
  const upstreamResult = git(['rev-parse', '--abbrev-ref', '@{u}'], {
@@ -538,7 +544,7 @@ function validatePublish(dryRun, autoTick) {
538
544
  console.log('\n🗃️ Validating git status...\n');
539
545
  const dirtyMap = getDirtyMap(workspaces, lastTag, cwd);
540
546
  // Check git status
541
- const gitClean = checkGitStatus(workspaces, dirtyMap, cwd, dryRun, autoTick);
547
+ const gitClean = checkGitStatus(workspaces, dirtyMap, cwd, dryRun, autoTick, allowNoUpstream);
542
548
  if (!gitClean) {
543
549
  console.error('❌ Git issues detected. Commit/stash changes before publishing.\n');
544
550
  process.exit(1);
@@ -621,7 +627,7 @@ function validatePublish(dryRun, autoTick) {
621
627
  console.log('\n🗃️ Re-validating...\n');
622
628
  // In dry-run mode, we allow the auto-tick commit to remain unpushed
623
629
  if (!dryRun) {
624
- if (!checkForUnpushedCommits(cwd, autoTick)) {
630
+ if (!checkForUnpushedCommits(cwd, autoTick, allowNoUpstream)) {
625
631
  process.exit(1);
626
632
  }
627
633
  }
@@ -734,7 +740,44 @@ function runNpmPublishInReleaseOrder(releaseOrder, packageInfos, packagesToRelea
734
740
  }
735
741
  }
736
742
  }
743
+ /**
744
+ * Ensures the current branch has an upstream remote tracking branch.
745
+ * If no upstream exists, pushes the branch with -u to set it.
746
+ * This is necessary before pushing tags to ensure the branch is on remote.
747
+ */
748
+ function ensureBranchHasUpstream() {
749
+ try {
750
+ const currentBranch = execSync('git branch --show-current', {
751
+ encoding: 'utf8',
752
+ }).trim();
753
+ // Check if upstream exists (command throws if not)
754
+ execSync(`git rev-parse --abbrev-ref ${currentBranch}@{u}`, {
755
+ encoding: 'utf8',
756
+ stdio: ['pipe', 'pipe', 'pipe'],
757
+ });
758
+ // Upstream exists - nothing to do
759
+ }
760
+ catch (err) {
761
+ // No upstream - set it by pushing current branch
762
+ console.log(`ℹ️ Branch has no upstream, setting it now...`);
763
+ const currentBranch = execSync('git branch --show-current', {
764
+ encoding: 'utf8',
765
+ }).trim();
766
+ try {
767
+ execSync(`git push -u origin ${currentBranch}`, {
768
+ stdio: 'inherit',
769
+ });
770
+ console.log(`✅ Upstream set for ${currentBranch}`);
771
+ }
772
+ catch (pushErr) {
773
+ console.error(`❌ Failed to set upstream for ${currentBranch}`);
774
+ throw pushErr;
775
+ }
776
+ }
777
+ }
737
778
  function tagAndPushRepo(version, tagSuffix) {
779
+ // Ensure branch has upstream before pushing tags
780
+ ensureBranchHasUpstream();
738
781
  try {
739
782
  execSync(`git tag v${version}${tagSuffix}`, { stdio: 'inherit' });
740
783
  execSync(`git push origin v${version}${tagSuffix}`, {
@@ -765,13 +808,15 @@ program
765
808
  .option('--registry <url>', 'NPM registry to publish to')
766
809
  .option('--auto-tick', 'Automatically tick patch versions if needed')
767
810
  .option('--anchor-tag-suffix <suffix>', "A tagging suffix convention which deviates from vx.x.x. E.g. if --anchor-tag-suffix=-rc is provided, tags (when not in dry-run mode) will be created in the form 'vx.x.x-rc'")
811
+ .option('--allow-no-upstream', 'Allow publishing from branches without upstream tracking. The tool will automatically set upstream when pushing tags. Intended for CI/CD pipelines that create new release branches.')
768
812
  .action((options) => {
769
813
  printHeader();
770
814
  const dryRun = options.dryRun;
771
815
  const registryUrl = options.registry;
772
816
  const autoTick = options.autoTick;
773
817
  const anchorTagSuffix = options.anchorTagSuffix ?? '';
774
- const { packageInfos, releaseOrder, packagesToRelease, currentRootVersion, } = validatePublish(dryRun, autoTick);
818
+ const allowNoUpstream = options.allowNoUpstream ?? false;
819
+ const { packageInfos, releaseOrder, packagesToRelease, currentRootVersion, } = validatePublish(dryRun, autoTick, allowNoUpstream);
775
820
  if (packagesToRelease.length === 0) {
776
821
  return console.log('\n ̄\\_(ツ)_/ ̄ No workspaces to publish\n');
777
822
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "npm-workspaces-publish-tool",
3
- "version": "0.0.15",
3
+ "version": "0.0.17",
4
4
  "description": "An unopinionated tool to assist publishing of npm based mono-repo workspaces",
5
5
  "main": "build/src/cli.js",
6
6
  "type": "module",