create-three-blocks-starter 0.0.1 → 0.0.3

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 +4 -4
  2. package/bin/index.js +96 -27
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # @three-blocks/create-starter
1
+ # create-three-blocks-starter
2
2
 
3
3
  Private CLI to scaffold a new project from the Three Blocks starter.
4
4
 
@@ -15,7 +15,7 @@ Then scaffold a new app:
15
15
  ```bash
16
16
  npm create @three-blocks/starter@latest my-app
17
17
  # or
18
- pnpm dlx @three-blocks/create-starter my-app
18
+ pnpm dlx create-three-blocks-starter my-app
19
19
  ```
20
20
 
21
21
  Next steps:
@@ -32,12 +32,12 @@ pnpm dev
32
32
  - To sync locally without publishing:
33
33
 
34
34
  ```bash
35
- pnpm --filter @three-blocks/create-starter run dev:sync
35
+ pnpm --filter create-three-blocks-starter run dev:sync
36
36
  ```
37
37
 
38
38
  ## Publish
39
39
 
40
40
  ```bash
41
41
  npx -y three-blocks-login --mode project --scope @three-blocks
42
- pnpm --filter @three-blocks/create-starter publish --access restricted
42
+ pnpm --filter create-three-blocks-starter publish --access restricted
43
43
  ```
package/bin/index.js CHANGED
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  // Node 18+
3
3
  // Usage:
4
- // THREE_BLOCKS_LICENSE_KEY=sk_live_... npx three-blocks-create-app my-app
5
- // npx three-blocks-create-app my-app (will prompt for key)
4
+ // THREE_BLOCKS_SECRET_KEY=sk_live_... npx create-three-blocks-starter my-app
5
+ // npx create-three-blocks-starter my-app (will prompt for key)
6
6
 
7
7
  import fs from 'node:fs';
8
8
  import fsp from 'node:fs/promises';
@@ -10,6 +10,7 @@ import path from 'node:path';
10
10
  import os from 'node:os';
11
11
  import { spawnSync } from 'node:child_process';
12
12
  import readline from 'node:readline';
13
+ import { bold, cyan, dim, gray, green, yellow } from 'kolorist';
13
14
 
14
15
  const STARTER_PKG = '@three-blocks/starter'; // private starter (lives in CodeArtifact)
15
16
  const LOGIN_CLI = 'three-blocks-login'; // public login CLI
@@ -21,6 +22,13 @@ const run = (cmd, args, opts = {}) => {
21
22
  if (r.status !== 0) process.exit(r.status ?? 1);
22
23
  };
23
24
 
25
+ // capture stdout (used for npm pack)
26
+ const exec = (cmd, args, opts = {}) => {
27
+ const r = spawnSync(cmd, args, { stdio: ['ignore', 'pipe', 'inherit'], ...opts });
28
+ if (r.status !== 0) process.exit(r.status ?? 1);
29
+ return (r.stdout || Buffer.alloc(0)).toString();
30
+ };
31
+
24
32
  async function ensureEmptyDir(dir) {
25
33
  try { await fsp.mkdir(dir, { recursive: true }); } catch {}
26
34
  const files = await fsp.readdir(dir).catch(() => []);
@@ -28,20 +36,44 @@ async function ensureEmptyDir(dir) {
28
36
  }
29
37
 
30
38
  async function promptHidden(label) {
31
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
32
39
  return await new Promise((resolve) => {
33
- process.stdout.write(label);
34
- const onData = (char) => {
35
- char = String(char);
36
- if (char === '\n' || char === '\r' || char === '\u0004') process.stdout.write('\n');
37
- else process.stdout.write('*');
40
+ const stdin = process.stdin;
41
+ const stdout = process.stdout;
42
+ let value = '';
43
+ const cleanup = () => {
44
+ stdin.removeListener('data', onData);
45
+ if (stdin.isTTY) stdin.setRawMode(false);
46
+ stdin.pause();
38
47
  };
39
- process.stdin.on('data', onData);
40
- rl.question('', (v) => {
41
- process.stdin.removeListener('data', onData);
42
- rl.close();
43
- resolve(v.trim());
44
- });
48
+ const onData = (chunk) => {
49
+ const str = String(chunk);
50
+ for (const ch of str) {
51
+ if (ch === '\u0003') { cleanup(); process.exit(1); }
52
+ if (ch === '\r' || ch === '\n' || ch === '\u0004') {
53
+ stdout.write('\n');
54
+ cleanup();
55
+ return resolve(value.trim());
56
+ }
57
+ if (ch === '\u0008' || ch === '\u007f') {
58
+ if (value.length) {
59
+ value = value.slice(0, -1);
60
+ readline.moveCursor(stdout, -1, 0);
61
+ stdout.write(' ');
62
+ readline.moveCursor(stdout, -1, 0);
63
+ }
64
+ continue;
65
+ }
66
+ // Ignore ESC start of control sequences
67
+ if (ch === '\u001b') continue;
68
+ value += ch;
69
+ stdout.write('•');
70
+ }
71
+ };
72
+ stdout.write(label);
73
+ stdin.setEncoding('utf8');
74
+ if (stdin.isTTY) stdin.setRawMode(true);
75
+ stdin.resume();
76
+ stdin.on('data', onData);
45
77
  });
46
78
  }
47
79
 
@@ -49,6 +81,13 @@ function mkTmpDir(prefix = 'three-blocks-') {
49
81
  return fs.mkdtempSync(path.join(os.tmpdir(), prefix));
50
82
  }
51
83
 
84
+ function mask(s) {
85
+ if (!s) return '';
86
+ const v = String(s);
87
+ if (v.length <= 6) return '••••';
88
+ return v.slice(0, 3) + '••••' + v.slice(-4);
89
+ }
90
+
52
91
  async function main() {
53
92
  const args = process.argv.slice(2);
54
93
  let appName = '';
@@ -64,7 +103,7 @@ async function main() {
64
103
  if (!appName) {
65
104
  die(
66
105
  `Usage:\n` +
67
- ` THREE_BLOCKS_LICENSE_KEY=sk_live_... npx three-blocks-create-app <directory>\n` +
106
+ ` THREE_BLOCKS_SECRET_KEY=sk_live_... npx three-blocks-create-app <directory>\n` +
68
107
  ` (or: npx three-blocks-create-app <directory> and enter the key when prompted)`
69
108
  );
70
109
  }
@@ -73,9 +112,15 @@ async function main() {
73
112
  await ensureEmptyDir(targetDir);
74
113
 
75
114
  // 1) License key (env or prompt)
76
- let license = process.env.THREE_BLOCKS_LICENSE_KEY;
77
- if (!license) {
78
- license = await promptHidden('Enter your THREE_BLOCKS_LICENSE_KEY: ');
115
+ let license = process.env.THREE_BLOCKS_SECRET_KEY;
116
+ if (license) {
117
+ console.log(`${gray('›')} Using ${bold('THREE_BLOCKS_SECRET_KEY')} from environment ${dim(`(${mask(license)})`)}`);
118
+ } else {
119
+ console.log('');
120
+ console.log(bold(cyan('Three Blocks Starter')) + ' ' + dim(`[channel: ${channel}]`));
121
+ console.log(dim('Enter your license key to continue.'));
122
+ console.log(dim('Tip: paste it here; input is hidden. Press Enter to submit.'));
123
+ license = await promptHidden(`${gray('›')} ${bold('License key')} ${dim('(tb_…)')}: `);
79
124
  if (!license) die('License key is required to install private packages.');
80
125
  }
81
126
 
@@ -85,24 +130,44 @@ async function main() {
85
130
  console.log(`> Fetching short-lived token (temp .npmrc) [channel: ${channel}] ...`);
86
131
  run('npx', ['-y', LOGIN_CLI, '--mode', 'project', '--scope', SCOPE, '--channel', channel], {
87
132
  cwd: tmp,
88
- env: { ...process.env, THREE_BLOCKS_LICENSE_KEY: license, THREE_BLOCKS_CHANNEL: channel },
133
+ env: { ...process.env, THREE_BLOCKS_SECRET_KEY: license, THREE_BLOCKS_CHANNEL: channel },
89
134
  });
90
135
  if (!fs.existsSync(tmpNpmrc)) die('Login failed: temp .npmrc not created.');
136
+ // Extract registry URL from temp .npmrc so we can pass it explicitly to npm create
137
+ let registryUrl = '';
138
+ try {
139
+ const txt = fs.readFileSync(tmpNpmrc, 'utf8');
140
+ const m = txt.match(/^@[^:]+:registry=(.+)$/m);
141
+ if (m && m[1]) registryUrl = m[1].trim();
142
+ } catch {}
91
143
 
92
- // 3) Scaffold the private starter using the temp .npmrc
144
+ // 3) Scaffold the private starter by packing and extracting the tarball (avoids npm create naming transform)
93
145
  const starterSpec = `${STARTER_PKG}@${channel === 'stable' ? 'latest' : channel}`;
94
- console.log(`> Creating ${appName} from ${starterSpec} ...`);
146
+ console.log(`> Fetching starter tarball ${starterSpec} ...`);
95
147
  const createEnv = {
96
148
  ...process.env,
97
- THREE_BLOCKS_LICENSE_KEY: license,
149
+ THREE_BLOCKS_SECRET_KEY: license,
98
150
  NPM_CONFIG_USERCONFIG: tmpNpmrc,
99
151
  npm_config_userconfig: tmpNpmrc,
100
152
  };
101
- run('npm', ['create', starterSpec, appName], { env: createEnv });
153
+ let packedOut = exec('npm', ['pack', starterSpec, '--json', '--silent'], { cwd: tmp, env: createEnv });
154
+ let tarName = '';
155
+ try {
156
+ const info = JSON.parse(packedOut);
157
+ if (Array.isArray(info)) tarName = info[0]?.filename || '';
158
+ else tarName = info.filename || '';
159
+ } catch {
160
+ const lines = String(packedOut || '').trim().split(/\r?\n/);
161
+ tarName = lines[lines.length - 1] || '';
162
+ }
163
+ const tarPath = path.join(tmp, tarName);
164
+ if (!tarName || !fs.existsSync(tarPath)) die('Failed to fetch starter tarball.');
165
+ console.log('> Extracting files ...');
166
+ run('tar', ['-xzf', tarPath, '-C', targetDir, '--strip-components=1']);
102
167
 
103
168
  // 4) Write .env.local and .gitignore entries
104
169
  await fsp.writeFile(path.join(targetDir, '.env.local'),
105
- `THREE_BLOCKS_LICENSE_KEY=${license}\nTHREE_BLOCKS_CHANNEL=${channel}\n`, { mode: 0o600 }).catch(() => {});
170
+ `THREE_BLOCKS_SECRET_KEY=${license}\nTHREE_BLOCKS_CHANNEL=${channel}\n`, { mode: 0o600 }).catch(() => {});
106
171
  const giPath = path.join(targetDir, '.gitignore');
107
172
  let gi = '';
108
173
  try { gi = await fsp.readFile(giPath, 'utf8'); } catch {}
@@ -123,16 +188,20 @@ async function main() {
123
188
  pkg.scripts.preinstall = `npx -y ${LOGIN_CLI} --mode project --scope ${SCOPE} --channel ${channel}`;
124
189
  }
125
190
  await fsp.writeFile(pkgPath, JSON.stringify(pkg, null, 2));
126
- console.log('> Added preinstall to generated package.json to refresh token on installs.');
191
+ console.log(`${green('✔')} Added preinstall to generated package.json to refresh token on installs.`);
127
192
  } catch (e) {
128
- console.log('> Warning: could not add preinstall to package.json:', e?.message || String(e));
193
+ console.log(`${yellow('⚠')} Warning: could not add preinstall to package.json:`, e?.message || String(e));
129
194
  }
130
195
 
131
196
  console.log('> Installing dependencies (preinstall will refresh token) ...');
132
197
  const hasPnpm = spawnSync('pnpm', ['-v'], { stdio: 'ignore' }).status === 0;
133
198
  run(hasPnpm ? 'pnpm' : 'npm', [hasPnpm ? 'install' : 'ci'], {
134
199
  cwd: targetDir,
135
- env: { ...process.env, THREE_BLOCKS_LICENSE_KEY: license, THREE_BLOCKS_CHANNEL: channel },
200
+ env: {
201
+ ...process.env,
202
+ THREE_BLOCKS_SECRET_KEY: license,
203
+ THREE_BLOCKS_CHANNEL: channel,
204
+ },
136
205
  });
137
206
 
138
207
  // 6) Cleanup temp dir
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-three-blocks-starter",
3
- "version": "0.0.1",
3
+ "version": "0.0.3",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "Create a new Three Blocks starter app",