create-three-blocks-starter 0.0.1

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 +43 -0
  2. package/bin/index.js +150 -0
  3. package/package.json +28 -0
package/README.md ADDED
@@ -0,0 +1,43 @@
1
+ # @three-blocks/create-starter
2
+
3
+ Private CLI to scaffold a new project from the Three Blocks starter.
4
+
5
+ ## Usage
6
+
7
+ Authenticate to the private registry first (one time per project):
8
+
9
+ ```bash
10
+ npx -y three-blocks-login --mode project --scope @three-blocks
11
+ ```
12
+
13
+ Then scaffold a new app:
14
+
15
+ ```bash
16
+ npm create @three-blocks/starter@latest my-app
17
+ # or
18
+ pnpm dlx @three-blocks/create-starter my-app
19
+ ```
20
+
21
+ Next steps:
22
+
23
+ ```bash
24
+ cd my-app
25
+ pnpm i
26
+ pnpm dev
27
+ ```
28
+
29
+ ## Development (monorepo)
30
+
31
+ - The CLI pulls the template from `packages/three-blocks-starter/` during `prepack`.
32
+ - To sync locally without publishing:
33
+
34
+ ```bash
35
+ pnpm --filter @three-blocks/create-starter run dev:sync
36
+ ```
37
+
38
+ ## Publish
39
+
40
+ ```bash
41
+ npx -y three-blocks-login --mode project --scope @three-blocks
42
+ pnpm --filter @three-blocks/create-starter publish --access restricted
43
+ ```
package/bin/index.js ADDED
@@ -0,0 +1,150 @@
1
+ #!/usr/bin/env node
2
+ // Node 18+
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)
6
+
7
+ import fs from 'node:fs';
8
+ import fsp from 'node:fs/promises';
9
+ import path from 'node:path';
10
+ import os from 'node:os';
11
+ import { spawnSync } from 'node:child_process';
12
+ import readline from 'node:readline';
13
+
14
+ const STARTER_PKG = '@three-blocks/starter'; // private starter (lives in CodeArtifact)
15
+ const LOGIN_CLI = 'three-blocks-login'; // public login CLI
16
+ const SCOPE = '@three-blocks';
17
+
18
+ const die = (m) => { console.error(m); process.exit(1); };
19
+ const run = (cmd, args, opts = {}) => {
20
+ const r = spawnSync(cmd, args, { stdio: 'inherit', ...opts });
21
+ if (r.status !== 0) process.exit(r.status ?? 1);
22
+ };
23
+
24
+ async function ensureEmptyDir(dir) {
25
+ try { await fsp.mkdir(dir, { recursive: true }); } catch {}
26
+ const files = await fsp.readdir(dir).catch(() => []);
27
+ if (files.length) die(`Target directory '${path.basename(dir)}' is not empty.`);
28
+ }
29
+
30
+ async function promptHidden(label) {
31
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
32
+ 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('*');
38
+ };
39
+ process.stdin.on('data', onData);
40
+ rl.question('', (v) => {
41
+ process.stdin.removeListener('data', onData);
42
+ rl.close();
43
+ resolve(v.trim());
44
+ });
45
+ });
46
+ }
47
+
48
+ function mkTmpDir(prefix = 'three-blocks-') {
49
+ return fs.mkdtempSync(path.join(os.tmpdir(), prefix));
50
+ }
51
+
52
+ async function main() {
53
+ const args = process.argv.slice(2);
54
+ let appName = '';
55
+ let channel = String(process.env.THREE_BLOCKS_CHANNEL || 'stable').toLowerCase();
56
+ for (let i = 0; i < args.length; i++) {
57
+ const a = args[i];
58
+ if (!a.startsWith('-') && !appName) { appName = a; continue; }
59
+ if (a === '--channel' || a === '-c') { const v = args[i + 1]; if (v) { channel = String(v).toLowerCase(); i++; } continue; }
60
+ const m = a.match(/^--channel=(.+)$/);
61
+ if (m) { channel = String(m[1]).toLowerCase(); continue; }
62
+ }
63
+ if (!['stable', 'beta', 'alpha'].includes(channel)) channel = 'stable';
64
+ if (!appName) {
65
+ die(
66
+ `Usage:\n` +
67
+ ` THREE_BLOCKS_LICENSE_KEY=sk_live_... npx three-blocks-create-app <directory>\n` +
68
+ ` (or: npx three-blocks-create-app <directory> and enter the key when prompted)`
69
+ );
70
+ }
71
+
72
+ const targetDir = path.resolve(process.cwd(), appName);
73
+ await ensureEmptyDir(targetDir);
74
+
75
+ // 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: ');
79
+ if (!license) die('License key is required to install private packages.');
80
+ }
81
+
82
+ // 2) Pre-login in a TEMP dir to generate a temp .npmrc (no always-auth)
83
+ const tmp = mkTmpDir();
84
+ const tmpNpmrc = path.join(tmp, '.npmrc');
85
+ console.log(`> Fetching short-lived token (temp .npmrc) [channel: ${channel}] ...`);
86
+ run('npx', ['-y', LOGIN_CLI, '--mode', 'project', '--scope', SCOPE, '--channel', channel], {
87
+ cwd: tmp,
88
+ env: { ...process.env, THREE_BLOCKS_LICENSE_KEY: license, THREE_BLOCKS_CHANNEL: channel },
89
+ });
90
+ if (!fs.existsSync(tmpNpmrc)) die('Login failed: temp .npmrc not created.');
91
+
92
+ // 3) Scaffold the private starter using the temp .npmrc
93
+ const starterSpec = `${STARTER_PKG}@${channel === 'stable' ? 'latest' : channel}`;
94
+ console.log(`> Creating ${appName} from ${starterSpec} ...`);
95
+ const createEnv = {
96
+ ...process.env,
97
+ THREE_BLOCKS_LICENSE_KEY: license,
98
+ NPM_CONFIG_USERCONFIG: tmpNpmrc,
99
+ npm_config_userconfig: tmpNpmrc,
100
+ };
101
+ run('npm', ['create', starterSpec, appName], { env: createEnv });
102
+
103
+ // 4) Write .env.local and .gitignore entries
104
+ await fsp.writeFile(path.join(targetDir, '.env.local'),
105
+ `THREE_BLOCKS_LICENSE_KEY=${license}\nTHREE_BLOCKS_CHANNEL=${channel}\n`, { mode: 0o600 }).catch(() => {});
106
+ const giPath = path.join(targetDir, '.gitignore');
107
+ let gi = '';
108
+ try { gi = await fsp.readFile(giPath, 'utf8'); } catch {}
109
+ for (const line of ['.env.local', '.npmrc']) {
110
+ if (!gi.includes(line)) gi += (gi.endsWith('\n') || gi === '' ? '' : '\n') + line + '\n';
111
+ }
112
+ await fsp.writeFile(giPath, gi);
113
+
114
+ // 5) First install — the starter already has:
115
+ // "preinstall": "npx -y three-blocks-login --mode project --scope @three-blocks"
116
+ // so it will mint a fresh token and write a project .npmrc.
117
+ try {
118
+ const pkgPath = path.join(targetDir, 'package.json');
119
+ const pkgRaw = await fsp.readFile(pkgPath, 'utf8');
120
+ const pkg = JSON.parse(pkgRaw);
121
+ pkg.scripts = pkg.scripts || {};
122
+ if (!pkg.scripts.preinstall) {
123
+ pkg.scripts.preinstall = `npx -y ${LOGIN_CLI} --mode project --scope ${SCOPE} --channel ${channel}`;
124
+ }
125
+ await fsp.writeFile(pkgPath, JSON.stringify(pkg, null, 2));
126
+ console.log('> Added preinstall to generated package.json to refresh token on installs.');
127
+ } catch (e) {
128
+ console.log('> Warning: could not add preinstall to package.json:', e?.message || String(e));
129
+ }
130
+
131
+ console.log('> Installing dependencies (preinstall will refresh token) ...');
132
+ const hasPnpm = spawnSync('pnpm', ['-v'], { stdio: 'ignore' }).status === 0;
133
+ run(hasPnpm ? 'pnpm' : 'npm', [hasPnpm ? 'install' : 'ci'], {
134
+ cwd: targetDir,
135
+ env: { ...process.env, THREE_BLOCKS_LICENSE_KEY: license, THREE_BLOCKS_CHANNEL: channel },
136
+ });
137
+
138
+ // 6) Cleanup temp dir
139
+ try { fs.rmSync(tmp, { recursive: true, force: true }); } catch {}
140
+
141
+ console.log(`\nSuccess! ${appName} is ready.`);
142
+ console.log(`\nNext:`);
143
+ console.log(` cd ${appName}`);
144
+ console.log(` ${hasPnpm ? 'pnpm dev' : 'npm run dev'}`);
145
+ console.log(`\nNotes:`);
146
+ console.log(` • Your license key is stored in .env.local (gitignored).`);
147
+ console.log(` • ${SCOPE} packages are private; each install refreshes a short-lived token via preinstall.`);
148
+ }
149
+
150
+ main().catch((e) => { console.error(e); process.exit(1); });
package/package.json ADDED
@@ -0,0 +1,28 @@
1
+ {
2
+ "name": "create-three-blocks-starter",
3
+ "version": "0.0.1",
4
+ "private": false,
5
+ "type": "module",
6
+ "description": "Create a new Three Blocks starter app",
7
+ "bin": {
8
+ "create-three-blocks-starter": "bin/index.js"
9
+ },
10
+ "files": [
11
+ "bin",
12
+ "README.md"
13
+ ],
14
+ "publishConfig": {
15
+ "access": "public"
16
+ },
17
+ "keywords": [
18
+ "three",
19
+ "threejs",
20
+ "webgpu",
21
+ "starter",
22
+ "scaffold"
23
+ ],
24
+ "license": "UNLICENSED",
25
+ "dependencies": {
26
+ "kolorist": "^1.8.0"
27
+ }
28
+ }