create-three-blocks-starter 0.0.7 → 0.0.8
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 +39 -2
- package/bin/index.js +314 -206
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -7,9 +7,15 @@ Private CLI to scaffold a new project from the Three Blocks starter.
|
|
|
7
7
|
Authenticate to the private registry first (one time per project):
|
|
8
8
|
|
|
9
9
|
```bash
|
|
10
|
-
|
|
10
|
+
# Using pnpm (recommended - no warnings)
|
|
11
|
+
pnpm dlx three-blocks-login@latest@latest --mode project --scope @three-blocks --channel stable
|
|
12
|
+
|
|
13
|
+
# Using npx (may show harmless npm config warnings)
|
|
14
|
+
npx -y three-blocks-login@latest --mode project --scope @three-blocks --channel stable
|
|
11
15
|
```
|
|
12
16
|
|
|
17
|
+
> **Note:** If you see npm warnings about unknown env configs, use `pnpm dlx` instead of `npx`, or see [three-blocks-login NPM warnings fix](https://www.npmjs.com/package/three-blocks-login).
|
|
18
|
+
|
|
13
19
|
Then scaffold a new app:
|
|
14
20
|
|
|
15
21
|
```bash
|
|
@@ -26,6 +32,37 @@ pnpm i
|
|
|
26
32
|
pnpm dev
|
|
27
33
|
```
|
|
28
34
|
|
|
35
|
+
## CI/CD & Vercel Setup
|
|
36
|
+
|
|
37
|
+
The CLI now automatically generates the correct setup, but if you need to configure it manually:
|
|
38
|
+
|
|
39
|
+
### Quick Setup (3 steps)
|
|
40
|
+
|
|
41
|
+
1. **Commit `.npmrc`** to your repository (generated automatically by create-three-blocks-starter):
|
|
42
|
+
```
|
|
43
|
+
@three-blocks:registry=https://three-blocks-196905988268.d.codeartifact.ap-northeast-1.amazonaws.com/npm/core/
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
2. **Preinstall script** (added automatically by create-three-blocks-starter):
|
|
47
|
+
```json
|
|
48
|
+
{
|
|
49
|
+
"scripts": {
|
|
50
|
+
"preinstall": "npx -y three-blocks-login@latest"
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
3. **Set environment variable** in your CI/CD platform:
|
|
56
|
+
- **Vercel**: Project Settings → Environment Variables → Add `THREE_BLOCKS_SECRET_KEY`
|
|
57
|
+
- **GitHub Actions**: Repository Settings → Secrets → Add `THREE_BLOCKS_SECRET_KEY`
|
|
58
|
+
- **Other CI**: Add `THREE_BLOCKS_SECRET_KEY` secret with your license key
|
|
59
|
+
|
|
60
|
+
### How It Works
|
|
61
|
+
|
|
62
|
+
- Committed `.npmrc` tells pnpm WHERE to find packages (no auth needed yet)
|
|
63
|
+
- Preinstall script fetches a short-lived token and adds it to `.npmrc`
|
|
64
|
+
- This solves pnpm's timing issue (it resolves packages before running preinstall)
|
|
65
|
+
|
|
29
66
|
## Development (monorepo)
|
|
30
67
|
|
|
31
68
|
- The CLI pulls the template from `packages/three-blocks-starter/` during `prepack`.
|
|
@@ -38,6 +75,6 @@ pnpm --filter create-three-blocks-starter run dev:sync
|
|
|
38
75
|
## Publish
|
|
39
76
|
|
|
40
77
|
```bash
|
|
41
|
-
|
|
78
|
+
pnpm dlx three-blocks-login@latest --mode project --scope @three-blocks
|
|
42
79
|
pnpm --filter create-three-blocks-starter publish --access restricted
|
|
43
80
|
```
|
package/bin/index.js
CHANGED
|
@@ -36,6 +36,24 @@ const logDebug = ( msg ) => {
|
|
|
36
36
|
|
|
37
37
|
};
|
|
38
38
|
|
|
39
|
+
let tempDirForCleanup = '';
|
|
40
|
+
const cleanupTmpDir = () => {
|
|
41
|
+
|
|
42
|
+
if ( ! tempDirForCleanup ) return;
|
|
43
|
+
try {
|
|
44
|
+
|
|
45
|
+
fs.rmSync( tempDirForCleanup, { recursive: true, force: true } );
|
|
46
|
+
|
|
47
|
+
} catch ( cleanupErr ) {
|
|
48
|
+
|
|
49
|
+
logDebug( `Unable to remove temp directory ${tempDirForCleanup}: ${cleanupErr?.message || cleanupErr}` );
|
|
50
|
+
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
tempDirForCleanup = '';
|
|
54
|
+
|
|
55
|
+
};
|
|
56
|
+
|
|
39
57
|
class CliError extends Error {
|
|
40
58
|
|
|
41
59
|
constructor( message, {
|
|
@@ -90,6 +108,7 @@ const cleanNpmEnv = ( extra = {} ) => {
|
|
|
90
108
|
|
|
91
109
|
};
|
|
92
110
|
|
|
111
|
+
|
|
93
112
|
const STEP_ICON = '⏺ ';
|
|
94
113
|
const logInfo = ( msg ) => {
|
|
95
114
|
|
|
@@ -121,6 +140,50 @@ const logError = ( msg ) => {
|
|
|
121
140
|
|
|
122
141
|
};
|
|
123
142
|
|
|
143
|
+
const appendAuthTokenLines = ( npmrcPath, authLines = [] ) => {
|
|
144
|
+
|
|
145
|
+
if ( ! authLines.length ) return false;
|
|
146
|
+
try {
|
|
147
|
+
|
|
148
|
+
const existing = fs.existsSync( npmrcPath ) ? fs.readFileSync( npmrcPath, 'utf8' ) : '';
|
|
149
|
+
const needsNewline = existing && ! existing.endsWith( os.EOL ) ? os.EOL : '';
|
|
150
|
+
const next = `${existing}${needsNewline}${authLines.join( os.EOL )}${os.EOL}`;
|
|
151
|
+
fs.writeFileSync( npmrcPath, next, { mode: 0o600 } );
|
|
152
|
+
return true;
|
|
153
|
+
|
|
154
|
+
} catch ( err ) {
|
|
155
|
+
|
|
156
|
+
logWarn( `Warning: could not append auth token to .npmrc: ${err?.message || err}` );
|
|
157
|
+
return false;
|
|
158
|
+
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
const stripAuthTokens = ( npmrcPath, hostPatterns = [] ) => {
|
|
164
|
+
|
|
165
|
+
if ( ! hostPatterns.length ) return;
|
|
166
|
+
try {
|
|
167
|
+
|
|
168
|
+
if ( ! fs.existsSync( npmrcPath ) ) return;
|
|
169
|
+
const raw = fs.readFileSync( npmrcPath, 'utf8' );
|
|
170
|
+
const lines = raw.split( /\r?\n/ );
|
|
171
|
+
const patterns = hostPatterns.map( ( host ) => new RegExp( `^\\s*\\/\\/${host.replace( /[.*+?^${}()|[\]\\]/g, '\\$&' )}:_authToken\\s*=`, 'i' ) );
|
|
172
|
+
const filtered = lines.filter( ( line ) => ! patterns.some( ( re ) => re.test( line ) ) );
|
|
173
|
+
let end = filtered.length;
|
|
174
|
+
while ( end > 0 && ! filtered[ end - 1 ].trim() ) end --;
|
|
175
|
+
const compact = filtered.slice( 0, end ).join( os.EOL );
|
|
176
|
+
const finalContent = compact ? `${compact}${os.EOL}` : '';
|
|
177
|
+
fs.writeFileSync( npmrcPath, finalContent, { mode: 0o600 } );
|
|
178
|
+
|
|
179
|
+
} catch ( err ) {
|
|
180
|
+
|
|
181
|
+
logWarn( `Warning: could not scrub auth token from .npmrc: ${err?.message || err}` );
|
|
182
|
+
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
};
|
|
186
|
+
|
|
124
187
|
const die = ( m ) => {
|
|
125
188
|
|
|
126
189
|
throw new CliError( m );
|
|
@@ -405,7 +468,7 @@ const padText = ( value, width, align = 'left' ) => {
|
|
|
405
468
|
const makeHeaderRow = ( left, right = '', leftAlign = 'left', rightAlign = 'left' ) =>
|
|
406
469
|
`│${padText( left, LEFT_WIDTH, leftAlign )}│${padText( right, RIGHT_WIDTH, rightAlign )}│`;
|
|
407
470
|
|
|
408
|
-
const HEADER_COLOR = ESC(
|
|
471
|
+
const HEADER_COLOR = ESC( '38;2;21;69;245' ); // threejs-blue (#1545F5)
|
|
409
472
|
const CONTENT_COLOR = ESC( 90 );
|
|
410
473
|
const reapplyColor = ( value, color ) => {
|
|
411
474
|
|
|
@@ -665,204 +728,205 @@ async function main() {
|
|
|
665
728
|
const headerLicenseId = tokenMetadata?.licenseId ? String( tokenMetadata.licenseId ) : '';
|
|
666
729
|
|
|
667
730
|
// 2) Pre-login in a TEMP dir to generate a temp .npmrc (no always-auth)
|
|
668
|
-
let tmp =
|
|
669
|
-
|
|
731
|
+
let tmp = mkTmpDir();
|
|
732
|
+
tempDirForCleanup = tmp;
|
|
733
|
+
let tmpNpmrc = path.join( tmp, '.npmrc' );
|
|
734
|
+
logProgress( `Fetching short-lived token (temp .npmrc) [channel: ${channel}] ...` );
|
|
670
735
|
try {
|
|
671
736
|
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
run( 'npx', [ '-y', LOGIN_SPEC, '--mode', 'project', '--scope', SCOPE, '--channel', channel ], {
|
|
678
|
-
cwd: tmp,
|
|
679
|
-
env: { ...process.env, THREE_BLOCKS_SECRET_KEY: license, THREE_BLOCKS_CHANNEL: channel },
|
|
680
|
-
} );
|
|
737
|
+
run( 'npx', [ '-y', LOGIN_SPEC, '--mode', 'project', '--scope', SCOPE, '--channel', channel ], {
|
|
738
|
+
cwd: tmp,
|
|
739
|
+
env: { ...process.env, THREE_BLOCKS_SECRET_KEY: license, THREE_BLOCKS_CHANNEL: channel },
|
|
740
|
+
} );
|
|
681
741
|
|
|
682
|
-
|
|
742
|
+
} catch ( err ) {
|
|
683
743
|
|
|
684
|
-
|
|
744
|
+
if ( err instanceof CliError ) {
|
|
685
745
|
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
746
|
+
throw new CliError(
|
|
747
|
+
'Login failed while minting short-lived token.',
|
|
748
|
+
{
|
|
749
|
+
exitCode: err.exitCode,
|
|
750
|
+
command: err.command,
|
|
751
|
+
args: err.args,
|
|
752
|
+
suggestion: 'Ensure the three-blocks-login CLI is reachable and your license key is valid.',
|
|
753
|
+
cause: err,
|
|
754
|
+
}
|
|
755
|
+
);
|
|
696
756
|
|
|
697
|
-
|
|
757
|
+
}
|
|
698
758
|
|
|
699
|
-
|
|
759
|
+
throw err;
|
|
700
760
|
|
|
701
|
-
|
|
761
|
+
}
|
|
702
762
|
|
|
703
|
-
|
|
763
|
+
if ( ! fs.existsSync( tmpNpmrc ) ) {
|
|
704
764
|
|
|
705
|
-
|
|
765
|
+
throw new CliError( 'Login failed: temp .npmrc not created.' );
|
|
706
766
|
|
|
707
|
-
|
|
767
|
+
}
|
|
708
768
|
|
|
709
769
|
// Extract registry URL from temp .npmrc so we can pass it explicitly to npm create
|
|
710
770
|
let registryUrl = '';
|
|
771
|
+
const tempAuthLines = [];
|
|
772
|
+
const tempAuthHosts = new Set();
|
|
711
773
|
try {
|
|
712
774
|
|
|
713
775
|
const txt = fs.readFileSync( tmpNpmrc, 'utf8' );
|
|
714
|
-
const
|
|
715
|
-
|
|
776
|
+
const lines = txt.split( /\r?\n/ );
|
|
777
|
+
for ( const rawLine of lines ) {
|
|
716
778
|
|
|
717
|
-
|
|
779
|
+
const line = rawLine.replace( /\r/g, '' );
|
|
780
|
+
if ( ! line ) continue;
|
|
781
|
+
if ( ! registryUrl ) {
|
|
718
782
|
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
}
|
|
722
|
-
|
|
723
|
-
if ( ! headerRegistry && registryUrl ) headerRegistry = registryUrl;
|
|
724
|
-
|
|
725
|
-
// 3) Scaffold the private starter by packing and extracting the tarball (avoids npm create naming transform)
|
|
726
|
-
const starterSpec = `${STARTER_PKG}@${channel === 'stable' ? 'latest' : channel}`;
|
|
727
|
-
logProgress( `Fetching starter tarball ${starterSpec} ...` );
|
|
728
|
-
const createEnv = {
|
|
729
|
-
...process.env,
|
|
730
|
-
THREE_BLOCKS_SECRET_KEY: license,
|
|
731
|
-
NPM_CONFIG_USERCONFIG: tmpNpmrc,
|
|
732
|
-
npm_config_userconfig: tmpNpmrc,
|
|
733
|
-
};
|
|
734
|
-
const packArgs = [ 'pack', starterSpec, '--json' ];
|
|
735
|
-
if ( ! DEBUG ) packArgs.push( '--silent' );
|
|
736
|
-
let packedOut = '';
|
|
737
|
-
try {
|
|
783
|
+
const m = line.match( /^@[^:]+:registry=(.+)$/ );
|
|
784
|
+
if ( m && m[ 1 ] ) registryUrl = m[ 1 ].trim();
|
|
738
785
|
|
|
739
|
-
|
|
786
|
+
}
|
|
740
787
|
|
|
741
|
-
|
|
788
|
+
const authMatch = line.match( /^\/\/([^:]+):_authToken=.+$/ );
|
|
789
|
+
if ( authMatch && authMatch[ 1 ] ) {
|
|
742
790
|
|
|
743
|
-
|
|
791
|
+
tempAuthLines.push( line );
|
|
792
|
+
tempAuthHosts.add( authMatch[ 1 ] );
|
|
744
793
|
|
|
745
|
-
|
|
746
|
-
const detail = ( err.stderr || err.stdout || '' ).trim();
|
|
747
|
-
const message = detail ? baseMessage : `${baseMessage} ${err.message || ''}`.trim();
|
|
748
|
-
throw new CliError(
|
|
749
|
-
message,
|
|
750
|
-
{
|
|
751
|
-
exitCode: err.exitCode,
|
|
752
|
-
command: err.command,
|
|
753
|
-
args: err.args,
|
|
754
|
-
stdout: err.stdout,
|
|
755
|
-
stderr: err.stderr,
|
|
756
|
-
suggestion: 'Confirm your license has access to the selected channel and that the package version exists.',
|
|
757
|
-
cause: err,
|
|
758
|
-
}
|
|
759
|
-
);
|
|
794
|
+
}
|
|
760
795
|
|
|
761
796
|
}
|
|
762
797
|
|
|
763
|
-
|
|
764
|
-
`Failed to fetch starter tarball ${starterSpec}.`,
|
|
765
|
-
{ cause: err }
|
|
766
|
-
);
|
|
767
|
-
|
|
768
|
-
}
|
|
769
|
-
|
|
770
|
-
let tarName = '';
|
|
771
|
-
let starterVersion = '';
|
|
772
|
-
try {
|
|
773
|
-
|
|
774
|
-
const info = JSON.parse( packedOut );
|
|
775
|
-
if ( Array.isArray( info ) ) {
|
|
776
|
-
|
|
777
|
-
tarName = info[ 0 ]?.filename || '';
|
|
778
|
-
starterVersion = info[ 0 ]?.version || '';
|
|
779
|
-
|
|
780
|
-
} else {
|
|
798
|
+
} catch ( readErr ) {
|
|
781
799
|
|
|
782
|
-
|
|
783
|
-
starterVersion = info.version || '';
|
|
800
|
+
logDebug( `Could not read temp .npmrc: ${readErr?.message || readErr}` );
|
|
784
801
|
|
|
785
|
-
|
|
802
|
+
}
|
|
786
803
|
|
|
787
|
-
|
|
804
|
+
if ( ! headerRegistry && registryUrl ) headerRegistry = registryUrl;
|
|
805
|
+
const authHostList = Array.from( tempAuthHosts );
|
|
806
|
+
|
|
807
|
+
// 3) Scaffold the private starter by packing and extracting the tarball (avoids npm create naming transform)
|
|
808
|
+
const starterSpec = `${STARTER_PKG}@${channel === 'stable' ? 'latest' : channel}`;
|
|
809
|
+
logProgress( `Fetching starter tarball ${starterSpec} ...` );
|
|
810
|
+
const createEnv = {
|
|
811
|
+
...process.env,
|
|
812
|
+
THREE_BLOCKS_SECRET_KEY: license,
|
|
813
|
+
NPM_CONFIG_USERCONFIG: tmpNpmrc,
|
|
814
|
+
npm_config_userconfig: tmpNpmrc,
|
|
815
|
+
};
|
|
816
|
+
const packArgs = [ 'pack', starterSpec, '--json' ];
|
|
817
|
+
if ( ! DEBUG ) packArgs.push( '--silent' );
|
|
818
|
+
let packedOut = '';
|
|
819
|
+
try {
|
|
788
820
|
|
|
789
|
-
|
|
790
|
-
tarName = lines[ lines.length - 1 ] || '';
|
|
821
|
+
packedOut = exec( 'npm', packArgs, { cwd: tmp, env: createEnv } );
|
|
791
822
|
|
|
792
|
-
|
|
823
|
+
} catch ( err ) {
|
|
793
824
|
|
|
794
|
-
|
|
795
|
-
if ( ! tarName || ! fs.existsSync( tarPath ) ) {
|
|
825
|
+
if ( err instanceof CliError ) {
|
|
796
826
|
|
|
827
|
+
const baseMessage = `Failed to fetch starter tarball ${starterSpec}.`;
|
|
828
|
+
const detail = ( err.stderr || err.stdout || '' ).trim();
|
|
829
|
+
const message = detail ? baseMessage : `${baseMessage} ${err.message || ''}`.trim();
|
|
797
830
|
throw new CliError(
|
|
798
|
-
|
|
799
|
-
{
|
|
831
|
+
message,
|
|
832
|
+
{
|
|
833
|
+
exitCode: err.exitCode,
|
|
834
|
+
command: err.command,
|
|
835
|
+
args: err.args,
|
|
836
|
+
stdout: err.stdout,
|
|
837
|
+
stderr: err.stderr,
|
|
838
|
+
suggestion: 'Confirm your license has access to the selected channel and that the package version exists.',
|
|
839
|
+
cause: err,
|
|
840
|
+
}
|
|
800
841
|
);
|
|
801
842
|
|
|
802
843
|
}
|
|
803
844
|
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
userDisplayName,
|
|
809
|
-
planName,
|
|
810
|
-
repoPath,
|
|
811
|
-
projectName: appName,
|
|
812
|
-
channel: headerChannel,
|
|
813
|
-
coreVersion: headerCoreVersion,
|
|
814
|
-
license,
|
|
815
|
-
registry: headerRegistry,
|
|
816
|
-
domain: headerDomain,
|
|
817
|
-
region: headerRegion,
|
|
818
|
-
repository: headerRepository,
|
|
819
|
-
expiresAt: headerExpires,
|
|
820
|
-
teamId: headerTeamId,
|
|
821
|
-
teamName: headerTeamName,
|
|
822
|
-
licenseId: headerLicenseId,
|
|
823
|
-
} );
|
|
824
|
-
logProgress( 'Extracting files ...' );
|
|
825
|
-
try {
|
|
845
|
+
throw new CliError(
|
|
846
|
+
`Failed to fetch starter tarball ${starterSpec}.`,
|
|
847
|
+
{ cause: err }
|
|
848
|
+
);
|
|
826
849
|
|
|
827
|
-
|
|
850
|
+
}
|
|
828
851
|
|
|
829
|
-
|
|
852
|
+
let tarName = '';
|
|
853
|
+
let starterVersion = '';
|
|
854
|
+
try {
|
|
830
855
|
|
|
831
|
-
|
|
856
|
+
const info = JSON.parse( packedOut );
|
|
857
|
+
if ( Array.isArray( info ) ) {
|
|
832
858
|
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
{
|
|
836
|
-
exitCode: err.exitCode,
|
|
837
|
-
command: err.command,
|
|
838
|
-
args: err.args,
|
|
839
|
-
suggestion: 'Check file permissions and available disk space before retrying.',
|
|
840
|
-
cause: err,
|
|
841
|
-
}
|
|
842
|
-
);
|
|
859
|
+
tarName = info[ 0 ]?.filename || '';
|
|
860
|
+
starterVersion = info[ 0 ]?.version || '';
|
|
843
861
|
|
|
844
|
-
|
|
862
|
+
} else {
|
|
845
863
|
|
|
846
|
-
|
|
864
|
+
tarName = info.filename || '';
|
|
865
|
+
starterVersion = info.version || '';
|
|
847
866
|
|
|
848
867
|
}
|
|
849
868
|
|
|
850
|
-
}
|
|
869
|
+
} catch {
|
|
851
870
|
|
|
852
|
-
|
|
871
|
+
const lines = String( packedOut || '' ).trim().split( /\r?\n/ );
|
|
872
|
+
tarName = lines[ lines.length - 1 ] || '';
|
|
853
873
|
|
|
854
|
-
|
|
874
|
+
}
|
|
855
875
|
|
|
856
|
-
|
|
876
|
+
const tarPath = path.join( tmp, tarName );
|
|
877
|
+
if ( ! tarName || ! fs.existsSync( tarPath ) ) {
|
|
857
878
|
|
|
858
|
-
|
|
879
|
+
throw new CliError(
|
|
880
|
+
`Failed to fetch starter tarball ${starterSpec}.`,
|
|
881
|
+
{ suggestion: 'Ensure the requested release is published and accessible for your channel.' }
|
|
882
|
+
);
|
|
859
883
|
|
|
860
|
-
|
|
884
|
+
}
|
|
861
885
|
|
|
862
|
-
|
|
886
|
+
const headerStarterVersion = starterVersion || extractVersionFromTarball( tarName );
|
|
887
|
+
const headerCoreVersion = headerChannel === 'stable' ? 'latest' : headerChannel;
|
|
888
|
+
renderHeader( {
|
|
889
|
+
starterVersion: headerStarterVersion,
|
|
890
|
+
userDisplayName,
|
|
891
|
+
planName,
|
|
892
|
+
repoPath,
|
|
893
|
+
projectName: appName,
|
|
894
|
+
channel: headerChannel,
|
|
895
|
+
coreVersion: headerCoreVersion,
|
|
896
|
+
license,
|
|
897
|
+
registry: headerRegistry,
|
|
898
|
+
domain: headerDomain,
|
|
899
|
+
region: headerRegion,
|
|
900
|
+
repository: headerRepository,
|
|
901
|
+
expiresAt: headerExpires,
|
|
902
|
+
teamId: headerTeamId,
|
|
903
|
+
teamName: headerTeamName,
|
|
904
|
+
licenseId: headerLicenseId,
|
|
905
|
+
} );
|
|
906
|
+
logProgress( 'Extracting files ...' );
|
|
907
|
+
try {
|
|
908
|
+
|
|
909
|
+
run( 'tar', [ '-xzf', tarPath, '-C', targetDir, '--strip-components=1' ] );
|
|
910
|
+
|
|
911
|
+
} catch ( err ) {
|
|
912
|
+
|
|
913
|
+
if ( err instanceof CliError ) {
|
|
914
|
+
|
|
915
|
+
throw new CliError(
|
|
916
|
+
'Failed to extract starter files.',
|
|
917
|
+
{
|
|
918
|
+
exitCode: err.exitCode,
|
|
919
|
+
command: err.command,
|
|
920
|
+
args: err.args,
|
|
921
|
+
suggestion: 'Check file permissions and available disk space before retrying.',
|
|
922
|
+
cause: err,
|
|
923
|
+
}
|
|
924
|
+
);
|
|
863
925
|
|
|
864
926
|
}
|
|
865
927
|
|
|
928
|
+
throw err;
|
|
929
|
+
|
|
866
930
|
}
|
|
867
931
|
|
|
868
932
|
// 4) Write .env.local and .gitignore entries
|
|
@@ -876,7 +940,8 @@ async function main() {
|
|
|
876
940
|
|
|
877
941
|
} catch {}
|
|
878
942
|
|
|
879
|
-
|
|
943
|
+
// Only add .env.local to .gitignore (NOT .npmrc - we want to commit the registry config)
|
|
944
|
+
for ( const line of [ '.env.local' ] ) {
|
|
880
945
|
|
|
881
946
|
if ( ! gi.includes( line ) ) gi += ( gi.endsWith( '\n' ) || gi === '' ? '' : '\n' ) + line + '\n';
|
|
882
947
|
|
|
@@ -884,9 +949,29 @@ async function main() {
|
|
|
884
949
|
|
|
885
950
|
await fsp.writeFile( giPath, gi );
|
|
886
951
|
|
|
887
|
-
//
|
|
888
|
-
|
|
889
|
-
|
|
952
|
+
// Create base .npmrc with registry URL (tokens added dynamically)
|
|
953
|
+
const npmrcPath = path.join( targetDir, '.npmrc' );
|
|
954
|
+
const npmrcContent = [
|
|
955
|
+
'# Three Blocks registry configuration',
|
|
956
|
+
'# Auth tokens are appended automatically by three-blocks-login and expire quickly.',
|
|
957
|
+
'# Avoid committing auth tokens; rerun pnpm install to refresh access if needed.',
|
|
958
|
+
'',
|
|
959
|
+
`${SCOPE}:registry=${headerRegistry || registryUrl}`,
|
|
960
|
+
'',
|
|
961
|
+
'# Auth tokens are appended during installs/preinstall hooks.',
|
|
962
|
+
'# DO NOT hard-code long-lived tokens in this file.',
|
|
963
|
+
''
|
|
964
|
+
].join( '\n' );
|
|
965
|
+
await fsp.writeFile( npmrcPath, npmrcContent ).catch( ( e ) => {
|
|
966
|
+
|
|
967
|
+
logWarn( `Warning: could not write .npmrc: ${e?.message || String( e )}` );
|
|
968
|
+
|
|
969
|
+
} );
|
|
970
|
+
const primedAuth = appendAuthTokenLines( npmrcPath, tempAuthLines );
|
|
971
|
+
if ( primedAuth ) logDebug( 'Primed .npmrc with short-lived auth token.' );
|
|
972
|
+
|
|
973
|
+
// 5) Add simplified preinstall script
|
|
974
|
+
// The script will automatically append auth token to existing .npmrc
|
|
890
975
|
try {
|
|
891
976
|
|
|
892
977
|
const pkgPath = path.join( targetDir, 'package.json' );
|
|
@@ -895,12 +980,13 @@ async function main() {
|
|
|
895
980
|
pkg.scripts = pkg.scripts || {};
|
|
896
981
|
if ( ! pkg.scripts.preinstall ) {
|
|
897
982
|
|
|
898
|
-
|
|
983
|
+
// Simplified: defaults to --mode project in CI, --scope @three-blocks, --channel stable
|
|
984
|
+
pkg.scripts.preinstall = `npx -y ${LOGIN_CLI}@latest`;
|
|
899
985
|
|
|
900
986
|
}
|
|
901
987
|
|
|
902
988
|
await fsp.writeFile( pkgPath, JSON.stringify( pkg, null, 2 ) );
|
|
903
|
-
logSuccess( 'Added preinstall
|
|
989
|
+
logSuccess( 'Added preinstall script to refresh auth tokens on installs.' );
|
|
904
990
|
|
|
905
991
|
} catch ( e ) {
|
|
906
992
|
|
|
@@ -908,61 +994,77 @@ async function main() {
|
|
|
908
994
|
|
|
909
995
|
}
|
|
910
996
|
|
|
911
|
-
|
|
997
|
+
const installMessage = primedAuth
|
|
998
|
+
? 'Installing dependencies (token primed; future installs refresh automatically) ...'
|
|
999
|
+
: 'Installing dependencies (preinstall will refresh token) ...';
|
|
1000
|
+
logProgress( installMessage );
|
|
912
1001
|
const hasPnpm = spawnSync( 'pnpm', [ '-v' ], { stdio: 'ignore', env: cleanNpmEnv() } ).status === 0;
|
|
1002
|
+
const sharedInstallEnv = {
|
|
1003
|
+
...process.env,
|
|
1004
|
+
THREE_BLOCKS_SECRET_KEY: license,
|
|
1005
|
+
THREE_BLOCKS_CHANNEL: channel,
|
|
1006
|
+
};
|
|
1007
|
+
if ( primedAuth ) sharedInstallEnv.THREE_BLOCKS_LOGIN_SKIP = '1';
|
|
1008
|
+
const cleanupAuthAfterBootstrap = () => {
|
|
1009
|
+
|
|
1010
|
+
if ( primedAuth && authHostList.length ) stripAuthTokens( npmrcPath, authHostList );
|
|
1011
|
+
|
|
1012
|
+
};
|
|
1013
|
+
|
|
913
1014
|
try {
|
|
914
1015
|
|
|
915
|
-
|
|
916
|
-
cwd: targetDir,
|
|
917
|
-
env: {
|
|
918
|
-
...process.env,
|
|
919
|
-
THREE_BLOCKS_SECRET_KEY: license,
|
|
920
|
-
THREE_BLOCKS_CHANNEL: channel,
|
|
921
|
-
},
|
|
922
|
-
} );
|
|
1016
|
+
try {
|
|
923
1017
|
|
|
924
|
-
|
|
1018
|
+
run( hasPnpm ? 'pnpm' : 'npm', [ hasPnpm ? 'install' : 'ci' ], {
|
|
1019
|
+
cwd: targetDir,
|
|
1020
|
+
env: sharedInstallEnv,
|
|
1021
|
+
} );
|
|
925
1022
|
|
|
926
|
-
|
|
1023
|
+
} catch ( err ) {
|
|
927
1024
|
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
1025
|
+
if ( err instanceof CliError ) {
|
|
1026
|
+
|
|
1027
|
+
throw new CliError(
|
|
1028
|
+
'Dependency installation failed.',
|
|
1029
|
+
{
|
|
1030
|
+
exitCode: err.exitCode,
|
|
1031
|
+
command: err.command,
|
|
1032
|
+
args: err.args,
|
|
1033
|
+
stdout: err.stdout,
|
|
1034
|
+
stderr: err.stderr,
|
|
1035
|
+
suggestion: 'Inspect the log above for npm/pnpm errors or retry with --debug for full output.',
|
|
1036
|
+
cause: err,
|
|
1037
|
+
}
|
|
1038
|
+
);
|
|
1039
|
+
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
throw err;
|
|
940
1043
|
|
|
941
1044
|
}
|
|
942
1045
|
|
|
943
|
-
|
|
1046
|
+
{
|
|
944
1047
|
|
|
945
|
-
|
|
1048
|
+
const coreSpec = `@three-blocks/core@${channel === 'stable' ? 'latest' : channel}`;
|
|
1049
|
+
logProgress( `Installing ${coreSpec} ...` );
|
|
1050
|
+
const addArgs = hasPnpm ? [ 'add', '--save-exact', coreSpec ] : [ 'install', '--save-exact', coreSpec ];
|
|
1051
|
+
const r = spawnSync( hasPnpm ? 'pnpm' : 'npm', addArgs, {
|
|
1052
|
+
stdio: 'inherit',
|
|
1053
|
+
cwd: targetDir,
|
|
1054
|
+
env: cleanNpmEnv( sharedInstallEnv ),
|
|
1055
|
+
} );
|
|
1056
|
+
if ( r.status !== 0 ) {
|
|
946
1057
|
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
const coreSpec = `@three-blocks/core@${channel === 'stable' ? 'latest' : channel}`;
|
|
950
|
-
logProgress( `Installing ${coreSpec} ...` );
|
|
951
|
-
const addArgs = hasPnpm ? [ 'add', '--save-exact', coreSpec ] : [ 'install', '--save-exact', coreSpec ];
|
|
952
|
-
const r = spawnSync( hasPnpm ? 'pnpm' : 'npm', addArgs, {
|
|
953
|
-
stdio: 'inherit',
|
|
954
|
-
cwd: targetDir,
|
|
955
|
-
env: cleanNpmEnv( {
|
|
956
|
-
THREE_BLOCKS_SECRET_KEY: license,
|
|
957
|
-
THREE_BLOCKS_CHANNEL: channel,
|
|
958
|
-
} ),
|
|
959
|
-
} );
|
|
960
|
-
if ( r.status !== 0 ) {
|
|
1058
|
+
logWarn( `Warning: could not install @three-blocks/core (exit ${r.status}).` );
|
|
961
1059
|
|
|
962
|
-
|
|
1060
|
+
}
|
|
963
1061
|
|
|
964
1062
|
}
|
|
965
1063
|
|
|
1064
|
+
} finally {
|
|
1065
|
+
|
|
1066
|
+
cleanupAuthAfterBootstrap();
|
|
1067
|
+
|
|
966
1068
|
}
|
|
967
1069
|
|
|
968
1070
|
console.log( '' );
|
|
@@ -978,32 +1080,38 @@ async function main() {
|
|
|
978
1080
|
|
|
979
1081
|
}
|
|
980
1082
|
|
|
981
|
-
main()
|
|
1083
|
+
main()
|
|
1084
|
+
.catch( ( err ) => {
|
|
982
1085
|
|
|
983
|
-
|
|
1086
|
+
if ( err instanceof CliError ) {
|
|
984
1087
|
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
1088
|
+
logError( err.message );
|
|
1089
|
+
const detail = ( err.stderr || err.stdout || '' ).trim();
|
|
1090
|
+
if ( detail ) {
|
|
988
1091
|
|
|
989
|
-
|
|
990
|
-
|
|
1092
|
+
const lines = detail.split( /\r?\n/ );
|
|
1093
|
+
for ( const line of lines ) console.error( dim( ` ${line}` ) );
|
|
991
1094
|
|
|
992
|
-
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
if ( err.suggestion ) logInfo( dim( `Hint: ${err.suggestion}` ) );
|
|
1098
|
+
if ( DEBUG && err.cause && err.cause !== err && err.cause?.stack ) {
|
|
1099
|
+
|
|
1100
|
+
console.error( dim( err.cause.stack ) );
|
|
993
1101
|
|
|
994
|
-
|
|
995
|
-
if ( DEBUG && err.cause && err.cause !== err && err.cause?.stack ) {
|
|
1102
|
+
}
|
|
996
1103
|
|
|
997
|
-
|
|
1104
|
+
process.exit( err.exitCode ?? 1 );
|
|
998
1105
|
|
|
999
1106
|
}
|
|
1000
1107
|
|
|
1001
|
-
|
|
1108
|
+
const fallback = err?.stack || err?.message || String( err );
|
|
1109
|
+
logError( fallback );
|
|
1110
|
+
process.exit( 1 );
|
|
1002
1111
|
|
|
1003
|
-
}
|
|
1112
|
+
} )
|
|
1113
|
+
.finally( () => {
|
|
1004
1114
|
|
|
1005
|
-
|
|
1006
|
-
logError( fallback );
|
|
1007
|
-
process.exit( 1 );
|
|
1115
|
+
cleanupTmpDir();
|
|
1008
1116
|
|
|
1009
|
-
} );
|
|
1117
|
+
} );
|