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.
- package/README.md +248 -0
- package/bin/login.js +72 -8
- 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 ||
|
|
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
|
|
619
|
+
// Write to local ./.npmrc - merge with existing content if present
|
|
624
620
|
const out = path.resolve( process.cwd(), ".npmrc" );
|
|
625
|
-
|
|
626
|
-
|
|
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 {
|