three-blocks-login 0.1.4 → 0.1.6

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.
Files changed (3) hide show
  1. package/README.md +248 -0
  2. package/bin/login.js +72 -8
  3. package/package.json +1 -1
package/README.md ADDED
@@ -0,0 +1,248 @@
1
+ # three-blocks-login
2
+
3
+ Minimal, dependency-free CLI for authenticating to Three Blocks private npm registry (AWS CodeArtifact).
4
+
5
+ ## Quick Start
6
+
7
+ ```bash
8
+ # Interactive login (writes to .npmrc in current directory)
9
+ npx -y three-blocks-login@latest
10
+
11
+ # Using pnpm (recommended - no warnings)
12
+ pnpm dlx three-blocks-login@latest@latest
13
+ ```
14
+
15
+ ## Authentication Modes
16
+
17
+ The CLI supports three modes for storing authentication:
18
+
19
+ ### 1. Project Mode (Recommended for CI/Vercel)
20
+
21
+ Writes `.npmrc` to the current directory. Best for project-specific authentication.
22
+
23
+ ```bash
24
+ npx -y three-blocks-login@latest --mode project --scope @three-blocks --channel stable
25
+ ```
26
+
27
+ **Use this in package.json preinstall scripts:**
28
+
29
+ ```json
30
+ {
31
+ "scripts": {
32
+ "preinstall": "npx -y three-blocks-login@latest"
33
+ }
34
+ }
35
+ ```
36
+
37
+ **Note:** Flags are optional - the CLI auto-detects `--mode project` in CI environments, defaults to `--scope @three-blocks`, and `--channel stable`.
38
+
39
+ ### 2. Env Mode (Temporary)
40
+
41
+ Writes to a temporary `.npmrc` file and exports `NPM_CONFIG_USERCONFIG`. Useful for shell sessions.
42
+
43
+ ```bash
44
+ eval "$(npx -y three-blocks-login@latest --mode env --print-shell)"
45
+ ```
46
+
47
+ ### 3. User Mode (Global)
48
+
49
+ Updates your user's npm config (`~/.npmrc`). Affects all projects.
50
+
51
+ ```bash
52
+ npx -y three-blocks-login@latest --mode user
53
+ ```
54
+
55
+ ## CLI Options
56
+
57
+ | Option | Description | Default |
58
+ |--------|-------------|---------|
59
+ | `--mode <env\|project\|user>` | Authentication storage mode | Auto-detected |
60
+ | `--scope <scope>` | npm scope to authenticate | `@three-blocks` |
61
+ | `--channel <stable\|alpha\|beta>` | Release channel | `stable` or `THREE_BLOCKS_CHANNEL` env |
62
+ | `--license <key>` | License key (or use `THREE_BLOCKS_SECRET_KEY` env) | From env or prompt |
63
+ | `--endpoint <url>` | Custom broker endpoint | `https://www.threejs-blocks.com/api/npm/token` |
64
+ | `--quiet` / `-q` | Suppress non-error output | `false` |
65
+ | `--verbose` / `-v` | Enable verbose logging | `false` |
66
+ | `--debug` | Enable debug logging | `false` |
67
+ | `--non-interactive` | Fail instead of prompting | `false` or `CI=1` |
68
+ | `--print-shell` | Print shell export commands (env mode) | `false` or `THREE_BLOCKS_LOGIN_PRINT_SHELL=1` |
69
+
70
+ ## Environment Variables
71
+
72
+ | Variable | Description |
73
+ |----------|-------------|
74
+ | `THREE_BLOCKS_SECRET_KEY` | License key (format: `tb_...`) |
75
+ | `THREE_BLOCKS_CHANNEL` | Release channel (`stable`, `alpha`, `beta`) |
76
+ | `THREE_BLOCKS_BROKER_URL` | Custom broker endpoint URL |
77
+ | `THREE_BLOCKS_DEBUG` | Enable debug logging (`1`, `true`, `yes`) |
78
+ | `THREE_BLOCKS_USER_NAME` | Display name for welcome message |
79
+ | `THREE_BLOCKS_LOGIN_PRINT_SHELL` | Print shell exports in env mode (`1`) |
80
+ | `CI` | Enable non-interactive mode (`1`) |
81
+
82
+ ## CI/CD & Vercel Setup
83
+
84
+ ### Quick Setup (3 steps)
85
+
86
+ 1. **Commit a base `.npmrc`** to your repository (safe - no secrets):
87
+ ```
88
+ @three-blocks:registry=https://three-blocks-196905988268.d.codeartifact.ap-northeast-1.amazonaws.com/npm/core/
89
+ ```
90
+
91
+ 2. **Add preinstall script** to package.json:
92
+ ```json
93
+ {
94
+ "scripts": {
95
+ "preinstall": "npx -y three-blocks-login@latest"
96
+ }
97
+ }
98
+ ```
99
+
100
+ 3. **Set environment variable** in Vercel/CI:
101
+ - **Name**: `THREE_BLOCKS_SECRET_KEY`
102
+ - **Value**: Your license key (starts with `tb_...`)
103
+
104
+ ### How It Works
105
+
106
+ - Committed `.npmrc` tells pnpm **WHERE** to find packages
107
+ - Preinstall script adds the **AUTH TOKEN** dynamically
108
+ - This solves pnpm's resolution timing issue (it needs to know the registry before running preinstall)
109
+
110
+ ### Vercel Environment Variables
111
+
112
+ 1. Go to your Vercel project settings
113
+ 2. Navigate to: Settings → Environment Variables
114
+ 3. Add `THREE_BLOCKS_SECRET_KEY` environment variable
115
+ 4. Set the value to your license key (starts with `tb_...`)
116
+ 5. Make sure it's available in all environments (Production, Preview, Development)
117
+
118
+ ### GitHub Actions
119
+
120
+ ```yaml
121
+ - name: Authenticate to Three Blocks
122
+ run: npx -y three-blocks-login@latest
123
+ env:
124
+ THREE_BLOCKS_SECRET_KEY: ${{ secrets.THREE_BLOCKS_SECRET_KEY }}
125
+ ```
126
+
127
+ **Or better yet**, just rely on the preinstall script - GitHub Actions will run it automatically during `npm install` or `pnpm install`.
128
+
129
+ ## Troubleshooting
130
+
131
+ ### 401 Unauthorized on Vercel
132
+
133
+ **Problem:** Getting `ERR_PNPM_FETCH_401` or npm 401 errors on Vercel.
134
+
135
+ **Root Cause:** pnpm resolves package locations BEFORE running preinstall scripts.
136
+
137
+ **Solution (3 steps):**
138
+
139
+ 1. **Commit a base `.npmrc`** with just the registry URL (no auth token):
140
+ ```
141
+ @three-blocks:registry=https://three-blocks-196905988268.d.codeartifact.ap-northeast-1.amazonaws.com/npm/core/
142
+ ```
143
+
144
+ 2. **Add preinstall script**:
145
+ ```json
146
+ {
147
+ "scripts": {
148
+ "preinstall": "npx -y three-blocks-login@latest"
149
+ }
150
+ }
151
+ ```
152
+
153
+ 3. **Verify `THREE_BLOCKS_SECRET_KEY`** is set in Vercel environment variables
154
+
155
+ **Why this works:**
156
+ - Committed `.npmrc` tells pnpm WHERE to look for packages
157
+ - Preinstall script adds the auth token dynamically
158
+ - pnpm can now resolve packages AND authenticate when downloading
159
+
160
+ ### License Key Format
161
+
162
+ License keys must:
163
+ - Start with `tb_` prefix
164
+ - Contain at least 10 characters after the prefix
165
+ - Only contain characters: `A-Z`, `a-z`, `0-9`, `_`, `-`
166
+
167
+ Example: `tb_AbC123XyZ456...`
168
+
169
+ ### Token Expiry
170
+
171
+ Tokens are short-lived (typically 12 hours). If you see 401 errors:
172
+
173
+ 1. Check that `THREE_BLOCKS_SECRET_KEY` is set correctly
174
+ 2. Run the preinstall script manually to test: `npm run preinstall` or `pnpm preinstall`
175
+ 3. Verify the broker endpoint is accessible: `curl -I https://www.threejs-blocks.com/api/npm/token`
176
+
177
+ ### npm Config Warnings
178
+
179
+ If you see warnings like `npm WARN invalid config unknown="..."`:
180
+
181
+ - Use `pnpm dlx` instead of `npx`
182
+ - Or ignore the warnings (they're harmless)
183
+
184
+ ## Security
185
+
186
+ - License keys are validated locally before being sent to the broker
187
+ - Tokens are masked in CLI output (`••••{last4}`)
188
+ - `.npmrc` files are created with `0o600` permissions (read/write for owner only)
189
+ - Sensitive environment variables are scrubbed before running npm commands
190
+
191
+ ### What's Safe to Commit?
192
+
193
+ ✅ **SAFE** - Base `.npmrc` with registry URL only:
194
+ ```
195
+ @three-blocks:registry=https://three-blocks-196905988268.d.codeartifact.ap-northeast-1.amazonaws.com/npm/core/
196
+ ```
197
+
198
+ ❌ **NEVER COMMIT** - Lines containing auth tokens:
199
+ ```
200
+ //three-blocks-196905988268.d.codeartifact.ap-northeast-1.amazonaws.com/npm/core/:_authToken=eyJ2...
201
+ ```
202
+
203
+ ❌ **NEVER COMMIT** - `.env.local` with license keys
204
+
205
+ Add to your `.gitignore`:
206
+
207
+ ```
208
+ .env.local
209
+ ```
210
+
211
+ **Note:** You can now commit `.npmrc` safely - the preinstall script merges auth tokens without overwriting your base configuration.
212
+
213
+ ## How It Works
214
+
215
+ 1. **License Key Validation**: The CLI validates your license key format locally
216
+ 2. **Broker Request**: Sends your license key to the broker endpoint via Bearer auth
217
+ 3. **Token Exchange**: The broker returns a short-lived npm token and registry URL
218
+ 4. **npm Configuration**: Writes authentication to `.npmrc` in the selected mode
219
+
220
+ ```
221
+ License Key → Broker Service → Short-lived NPM Token → CodeArtifact Access
222
+ ```
223
+
224
+ ## License Tiers
225
+
226
+ The CLI supports multiple license tiers:
227
+
228
+ - **Indie/Core**: Access to `@three-blocks/core` package
229
+ - **Pro**: Access to all `@three-blocks/*` packages including starter templates
230
+
231
+ Your tier is determined by your license key and displayed in the CLI output.
232
+
233
+ ## Support
234
+
235
+ For issues or questions:
236
+ - Check the troubleshooting guide above
237
+ - Review your Vercel environment variables
238
+ - Verify your license key format
239
+ - Contact Three Blocks support with your license ID (not the full key)
240
+
241
+ ## Version
242
+
243
+ Current version: 0.1.4
244
+
245
+ For the latest version, always use `@latest`:
246
+ ```bash
247
+ npx -y three-blocks-login@latest
248
+ ```
package/bin/login.js CHANGED
@@ -355,7 +355,7 @@ if ( DEBUG ) logDebug( "Debug logging enabled." );
355
355
 
356
356
  const SCOPE = ( args.scope || "@three-blocks" ).replace( /^\s+|\s+$/g, "" );
357
357
 
358
- const MODE = ( args.mode || "env" ).toLowerCase(); // env | project | user
358
+ const MODE = ( args.mode || getDefaultMode() ).toLowerCase(); // env | project | user
359
359
  const QUIET = !! args.quiet;
360
360
  const VERBOSE = !! args.verbose;
361
361
  let CHANNEL = String( args.channel || process.env.THREE_BLOCKS_CHANNEL || "stable" ).toLowerCase();
@@ -608,10 +608,6 @@ const log = {
608
608
  ];
609
609
  console.log( [ ...exportLines, '', ...summaryPrint ].join( '\n' ) );
610
610
 
611
- } else {
612
-
613
- log.info( `${plainBanner} temp npmrc ready. Use --print-shell to emit export commands.` );
614
-
615
611
  }
616
612
 
617
613
  return;
@@ -620,10 +616,62 @@ const log = {
620
616
 
621
617
  if ( MODE === "project" ) {
622
618
 
623
- // Write to local ./.npmrc (recommend users gitignore this)
619
+ // Write to local ./.npmrc - merge with existing content if present
624
620
  const out = path.resolve( process.cwd(), ".npmrc" );
625
- fs.writeFileSync( out, npmrcContent, { mode: 0o600 } );
626
- log.ok( `${banner} wrote ${bold( out )} ${dim( `(${SCOPE} → ${u.href}, expires ${expiresAt ?? "unknown"})` )}` );
621
+ let existingContent = '';
622
+ try {
623
+
624
+ if ( fs.existsSync( out ) ) {
625
+
626
+ existingContent = fs.readFileSync( out, 'utf8' );
627
+
628
+ }
629
+
630
+ } catch {}
631
+
632
+ // Merge: remove any existing registry/auth lines for this scope, then append new ones
633
+ const lines = existingContent.split( /\r?\n/ );
634
+ const scopePattern = new RegExp( `^\\s*${normalizeScope( SCOPE ).replace( /[.*+?^${}()|[\]\\]/g, '\\$&' )}:registry\\s*=` );
635
+ const authPattern = new RegExp( `^\\s*\\/\\/${hostPath.replace( /[.*+?^${}()|[\]\\]/g, '\\$&' )}:_authToken\\s*=` );
636
+ const filtered = lines.filter( ( line ) => ! scopePattern.test( line ) && ! authPattern.test( line ) );
637
+
638
+ // Preserve existing content but ensure it ends with newline
639
+ const base = filtered.join( os.EOL ).trim();
640
+ const merged = base ? `${base}${os.EOL}${os.EOL}${npmrcContent}` : npmrcContent;
641
+
642
+ fs.writeFileSync( out, merged, { mode: 0o600 } );
643
+
644
+ // Show authentication status (unless in quiet mode)
645
+
646
+ if ( ! QUIET ) {
647
+
648
+ const maskedLicense = maskLicense( LICENSE_CLEAN );
649
+
650
+ renderEnvHeader( {
651
+
652
+ scope: SCOPE,
653
+ registry: u.href,
654
+ expiresAt,
655
+ tmpFile: out,
656
+ licenseMasked: maskedLicense,
657
+ licenseId,
658
+ channel: CHANNEL,
659
+ status: authStatus,
660
+ plan,
661
+ teamName,
662
+ teamId,
663
+ repository,
664
+ domain,
665
+ region,
666
+ userDisplayName: USER_DISPLAY_NAME,
667
+ } );
668
+
669
+ } else {
670
+
671
+ log.ok( `${banner} wrote ${bold( out )} ${dim( `(${SCOPE} → ${u.href}, expires ${expiresAt ?? "unknown"})` )}` );
672
+
673
+ }
674
+
627
675
  return;
628
676
 
629
677
  }
@@ -714,6 +762,22 @@ function ensureTrailingSlash( url ) {
714
762
 
715
763
  }
716
764
 
765
+ function getDefaultMode() {
766
+
767
+ try {
768
+
769
+ const lifecycle = String( process.env.npm_lifecycle_event || '' );
770
+ const userAgent = String( process.env.npm_config_user_agent || '' );
771
+ const isNpmScript = !! lifecycle || /npm|pnpm|yarn/i.test( userAgent );
772
+ const isCi = String( process.env.CI || '' ) === '1';
773
+ if ( isNpmScript || isCi ) return 'project';
774
+
775
+ } catch {}
776
+
777
+ return 'env';
778
+
779
+ }
780
+
717
781
  function loadEnvFromDotfile( dir ) {
718
782
 
719
783
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "three-blocks-login",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
4
  "description": "Fetch a short-lived token from the three-blocks broker and configure npm for the current context.",
5
5
  "type": "module",
6
6
  "bin": {