blok0 0.1.1 → 0.1.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.
package/dist/ui.js ADDED
@@ -0,0 +1,222 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.log = exports.ProgressBar = exports.Spinner = exports.ciMode = exports.noEmoji = exports.noAnimation = exports.isTTY = exports.colors = exports.EMOJIS = void 0;
7
+ exports.setUIFlags = setUIFlags;
8
+ exports.withSpinner = withSpinner;
9
+ exports.showSection = showSection;
10
+ exports.showNextSteps = showNextSteps;
11
+ const ora_1 = __importDefault(require("ora"));
12
+ const chalk_1 = __importDefault(require("chalk"));
13
+ const cli_progress_1 = require("cli-progress");
14
+ // Emoji constants for consistent usage
15
+ exports.EMOJIS = {
16
+ SUCCESS: '✅',
17
+ ERROR: '❌',
18
+ WARNING: '⚠️',
19
+ INFO: 'ℹ️',
20
+ LOCK: '🔐',
21
+ PACKAGE: '📦',
22
+ FOLDER: '📁',
23
+ GEAR: '🔧',
24
+ SEARCH: '🔍',
25
+ ROCKET: '🚀',
26
+ DOWNLOAD: '📥',
27
+ PARTY: '🎉',
28
+ WRENCH: '🔧',
29
+ CHECK: '✅',
30
+ CROSS: '❌',
31
+ ARROW: '→',
32
+ };
33
+ // Color utilities
34
+ exports.colors = {
35
+ success: chalk_1.default.green,
36
+ error: chalk_1.default.red,
37
+ warning: chalk_1.default.yellow,
38
+ info: chalk_1.default.blue,
39
+ accent: chalk_1.default.cyan,
40
+ muted: chalk_1.default.gray,
41
+ };
42
+ // Check if we're in a TTY environment
43
+ exports.isTTY = process.stdout.isTTY;
44
+ // Global flags for disabling features
45
+ exports.noAnimation = false;
46
+ exports.noEmoji = false;
47
+ exports.ciMode = false;
48
+ // Set global flags
49
+ function setUIFlags(flags) {
50
+ exports.noAnimation = flags.noAnimation || exports.ciMode;
51
+ exports.noEmoji = flags.noEmoji || exports.ciMode;
52
+ exports.ciMode = flags.ci || false;
53
+ }
54
+ // Apply emoji settings to text
55
+ function applyEmoji(text, emoji) {
56
+ if (exports.noEmoji || !emoji)
57
+ return text;
58
+ return `${emoji} ${text}`;
59
+ }
60
+ // Spinner wrapper with TTY detection
61
+ class Spinner {
62
+ text;
63
+ emoji;
64
+ spinner = null;
65
+ startTime = 0;
66
+ constructor(text, emoji) {
67
+ this.text = text;
68
+ this.emoji = emoji;
69
+ }
70
+ start() {
71
+ if (!exports.isTTY || exports.noAnimation) {
72
+ console.log(applyEmoji(this.text, this.emoji));
73
+ this.startTime = Date.now();
74
+ return this;
75
+ }
76
+ this.spinner = (0, ora_1.default)({
77
+ text: applyEmoji(this.text, this.emoji),
78
+ spinner: 'dots',
79
+ }).start();
80
+ this.startTime = Date.now();
81
+ return this;
82
+ }
83
+ update(text, emoji) {
84
+ if (!this.spinner) {
85
+ if (exports.isTTY && !exports.noAnimation) {
86
+ this.spinner = (0, ora_1.default)(applyEmoji(text, emoji)).start();
87
+ }
88
+ else {
89
+ console.log(applyEmoji(text, emoji));
90
+ }
91
+ return this;
92
+ }
93
+ this.spinner.text = applyEmoji(text, emoji || this.emoji);
94
+ return this;
95
+ }
96
+ succeed(text) {
97
+ const duration = this.getDuration();
98
+ const successText = text || this.text;
99
+ const durationInfo = duration > 1000 ? ` (${duration}ms)` : '';
100
+ if (this.spinner) {
101
+ this.spinner.succeed(applyEmoji(successText, exports.EMOJIS.SUCCESS) + durationInfo);
102
+ }
103
+ else {
104
+ console.log(applyEmoji(successText + durationInfo, exports.EMOJIS.SUCCESS));
105
+ }
106
+ return this;
107
+ }
108
+ fail(text) {
109
+ const failText = text || this.text;
110
+ if (this.spinner) {
111
+ this.spinner.fail(applyEmoji(failText, exports.EMOJIS.ERROR));
112
+ }
113
+ else {
114
+ console.error(applyEmoji(failText, exports.EMOJIS.ERROR));
115
+ }
116
+ return this;
117
+ }
118
+ stop() {
119
+ if (this.spinner) {
120
+ this.spinner.stop();
121
+ }
122
+ return this;
123
+ }
124
+ getDuration() {
125
+ return Date.now() - this.startTime;
126
+ }
127
+ }
128
+ exports.Spinner = Spinner;
129
+ // Utility function to wrap async operations with spinner
130
+ async function withSpinner(text, operation, options = {}) {
131
+ const spinner = new Spinner(text, options.emoji);
132
+ spinner.start();
133
+ try {
134
+ const result = await operation();
135
+ spinner.succeed(options.successText);
136
+ return result;
137
+ }
138
+ catch (error) {
139
+ spinner.fail(options.failText);
140
+ throw error;
141
+ }
142
+ }
143
+ // Progress bar utilities
144
+ class ProgressBar {
145
+ options;
146
+ bar = null;
147
+ constructor(options) {
148
+ this.options = options;
149
+ }
150
+ start() {
151
+ if (!exports.isTTY || exports.noAnimation) {
152
+ if (this.options.title) {
153
+ console.log(this.options.title);
154
+ }
155
+ return this;
156
+ }
157
+ this.bar = new cli_progress_1.SingleBar({
158
+ format: this.options.format || '{bar} {percentage}% | {value}/{total} | {eta}s',
159
+ barCompleteChar: '\u2588',
160
+ barIncompleteChar: '\u2591',
161
+ hideCursor: true,
162
+ });
163
+ if (this.options.title) {
164
+ console.log(this.options.title);
165
+ }
166
+ this.bar.start(this.options.total, 0);
167
+ return this;
168
+ }
169
+ update(current) {
170
+ if (this.bar) {
171
+ this.bar.update(current);
172
+ }
173
+ else if (exports.isTTY && !exports.noAnimation) {
174
+ // Fallback to simple progress if bar not initialized
175
+ const percent = Math.round((current / this.options.total) * 100);
176
+ process.stdout.write(`\r${this.options.title || 'Progress'}: ${percent}% (${current}/${this.options.total})`);
177
+ }
178
+ return this;
179
+ }
180
+ increment(amount = 1) {
181
+ if (this.bar) {
182
+ this.bar.increment(amount);
183
+ }
184
+ return this;
185
+ }
186
+ stop() {
187
+ if (this.bar) {
188
+ this.bar.stop();
189
+ }
190
+ else if (exports.isTTY && !exports.noAnimation) {
191
+ process.stdout.write('\n');
192
+ }
193
+ return this;
194
+ }
195
+ }
196
+ exports.ProgressBar = ProgressBar;
197
+ // Enhanced console methods
198
+ exports.log = {
199
+ success: (text) => console.log(exports.colors.success(applyEmoji(text, exports.EMOJIS.SUCCESS))),
200
+ error: (text) => console.error(exports.colors.error(applyEmoji(text, exports.EMOJIS.ERROR))),
201
+ warning: (text) => console.warn(exports.colors.warning(applyEmoji(text, exports.EMOJIS.WARNING))),
202
+ info: (text) => console.log(exports.colors.info(applyEmoji(text, exports.EMOJIS.INFO))),
203
+ plain: (text) => console.log(text),
204
+ header: (text) => console.log(exports.colors.accent(`\n${text}\n`)),
205
+ step: (step, total, text) => {
206
+ const stepText = `${step}/${total}`;
207
+ console.log(exports.colors.muted(`[${stepText}]`) + ' ' + text);
208
+ },
209
+ };
210
+ // Utility to show a section header
211
+ function showSection(title, emoji) {
212
+ console.log('\n' + exports.colors.accent('='.repeat(50)));
213
+ console.log(applyEmoji(title, emoji));
214
+ console.log(exports.colors.accent('='.repeat(50)) + '\n');
215
+ }
216
+ // Utility to show next steps
217
+ function showNextSteps(steps) {
218
+ console.log(exports.colors.accent('\nNext steps:'));
219
+ steps.forEach((step, index) => {
220
+ console.log(` ${index + 1}. ${step}`);
221
+ });
222
+ }
package/package.json CHANGED
@@ -1,33 +1,37 @@
1
- {
2
- "name": "blok0",
3
- "version": "0.1.1",
4
- "description": "CLI tool for Payload CMS scaffolding",
5
- "main": "dist/index.js",
6
- "bin": {
7
- "blok0": "./dist/index.js"
8
- },
9
- "scripts": {
10
- "build": "bun run tsc",
11
- "dev": "bun run tsx src/index.ts"
12
- },
13
- "keywords": [
14
- "cli",
15
- "payload",
16
- "cms"
17
- ],
18
- "author": "Your Name",
19
- "license": "MIT",
20
- "dependencies": {
21
- "axios": "^1.13.2",
22
- "keytar": "^7.9.0",
23
- "open": "^11.0.0",
24
- "payload": "^2.0.0",
25
- "ts-morph": "^27.0.2"
26
- },
27
- "devDependencies": {
28
- "@types/node": "^20.19.27",
29
- "bun-types": "^1.0.0",
30
- "tsx": "^4.0.0",
31
- "typescript": "^5.0.0"
32
- }
33
- }
1
+ {
2
+ "name": "blok0",
3
+ "version": "0.1.3",
4
+ "description": "CLI tool for Payload CMS scaffolding",
5
+ "main": "dist/index.js",
6
+ "bin": {
7
+ "blok0": "./dist/index.js"
8
+ },
9
+ "scripts": {
10
+ "build": "bun run tsc",
11
+ "dev": "bun run tsx src/index.ts"
12
+ },
13
+ "keywords": [
14
+ "cli",
15
+ "payload",
16
+ "cms"
17
+ ],
18
+ "author": "Your Name",
19
+ "license": "MIT",
20
+ "dependencies": {
21
+ "axios": "^1.13.2",
22
+ "chalk": "^5.6.2",
23
+ "cli-progress": "^3.12.0",
24
+ "keytar": "^7.9.0",
25
+ "open": "^11.0.0",
26
+ "ora": "^9.0.0",
27
+ "payload": "^2.0.0",
28
+ "ts-morph": "^27.0.2"
29
+ },
30
+ "devDependencies": {
31
+ "@types/cli-progress": "^3.11.6",
32
+ "@types/node": "^20.19.27",
33
+ "bun-types": "^1.0.0",
34
+ "tsx": "^4.0.0",
35
+ "typescript": "^5.0.0"
36
+ }
37
+ }
package/src/detectors.ts CHANGED
@@ -1,22 +1,22 @@
1
- import { existsSync } from 'fs';
2
- import { join } from 'path';
3
-
4
- export function checkEmptyDirectory(): boolean {
5
- const cwd = process.cwd();
6
-
7
- const pkgPath = join(cwd, 'package.json');
8
- const configJs = join(cwd, 'payload.config.js');
9
- const configTs = join(cwd, 'payload.config.ts');
10
-
11
- if (existsSync(pkgPath)) {
12
- console.error('Error: package.json already exists. Please run in an empty directory.');
13
- return false;
14
- }
15
-
16
- if (existsSync(configJs) || existsSync(configTs)) {
17
- console.error('Error: Payload config file already exists. Please run in an empty directory.');
18
- return false;
19
- }
20
-
21
- return true;
22
- }
1
+ import { existsSync } from 'fs';
2
+ import { join } from 'path';
3
+
4
+ export function checkEmptyDirectory(): boolean {
5
+ const cwd = process.cwd();
6
+
7
+ const pkgPath = join(cwd, 'package.json');
8
+ const configJs = join(cwd, 'payload.config.js');
9
+ const configTs = join(cwd, 'payload.config.ts');
10
+
11
+ if (existsSync(pkgPath)) {
12
+ console.error('Error: package.json already exists. Please run in an empty directory.');
13
+ return false;
14
+ }
15
+
16
+ if (existsSync(configJs) || existsSync(configTs)) {
17
+ console.error('Error: Payload config file already exists. Please run in an empty directory.');
18
+ return false;
19
+ }
20
+
21
+ return true;
22
+ }
@@ -10,40 +10,47 @@ import {
10
10
  validateBlockDirectory
11
11
  } from '../blocks';
12
12
  import { updatePageCollectionConfig, updateRenderBlocksComponent, findPagesCollection, findRenderBlocksComponent } from '../ast';
13
+ import { withSpinner, log, showSection, showNextSteps, EMOJIS, ProgressBar } from '../ui';
13
14
 
14
15
  /**
15
16
  * Handle add block command
16
17
  */
17
18
  export async function handleAddBlock(blockUrl: string, options: { force?: boolean; dryRun?: boolean } = {}): Promise<void> {
18
- console.log('📦 Adding Blok0 Block');
19
- console.log('====================');
20
- console.log('');
19
+ showSection('📦 Adding Blok0 Block', EMOJIS.PACKAGE);
21
20
 
22
21
  try {
23
22
  // Step 1: Authentication check
24
- console.log('🔐 Checking authentication...');
25
- const authenticated = await isAuthenticated();
23
+ const authenticated = await withSpinner(
24
+ 'Checking authentication',
25
+ () => isAuthenticated(),
26
+ { emoji: EMOJIS.LOCK }
27
+ );
28
+
26
29
  if (!authenticated) {
27
- console.error('You are not logged in. Please run `blok0 login` first.');
30
+ log.error('You are not logged in. Please run `blok0 login` first.');
28
31
  process.exit(1);
29
32
  }
30
33
 
31
34
  // Step 2: Fetch block data from API
32
- console.log(`📡 Fetching block from: ${blockUrl}`);
33
- const { metadata, files } = await apiClient.fetchBlockData(blockUrl);
34
- console.log(`✅ Found block: "${metadata.name}" (${metadata.slug})`);
35
+ const { metadata, files } = await withSpinner(
36
+ `Fetching block from ${blockUrl}`,
37
+ () => apiClient.fetchBlockData(blockUrl),
38
+ { emoji: EMOJIS.SEARCH }
39
+ );
40
+
41
+ log.success(`Found block: "${metadata.name}" (${metadata.slug})`);
35
42
 
36
43
  // Step 3: Check if block is already registered
37
44
  if (isBlockRegistered(metadata.slug)) {
38
45
  if (!options.force) {
39
- console.error(`❌ Block "${metadata.slug}" is already installed. Use --force to reinstall.`);
46
+ log.error(`Block "${metadata.slug}" is already installed. Use --force to reinstall.`);
40
47
  process.exit(1);
41
48
  }
42
- console.log('⚠️ Block already exists, reinstalling...');
49
+ log.warning('Block already exists, reinstalling...');
43
50
  }
44
51
 
45
52
  if (options.dryRun) {
46
- console.log('🔍 Dry run mode - would perform the following actions:');
53
+ log.info('Dry run mode - would perform the following actions:');
47
54
  console.log(` - Create directory: src/blocks/${metadata.slug}`);
48
55
  console.log(` - Download ${files.length} files`);
49
56
  console.log(' - Update Payload config');
@@ -56,14 +63,13 @@ export async function handleAddBlock(blockUrl: string, options: { force?: boolea
56
63
  const blocksDir = ensureBlocksDirectory();
57
64
 
58
65
  // Step 5: Create block directory and files
59
- console.log('📁 Creating block directory and files...');
60
66
  const { dir, configPath, componentPath } = createBlockDirectory(blocksDir, metadata.slug, files);
61
- console.log(`✅ Created block directory: ${path.relative(process.cwd(), dir)}`);
67
+ log.success(`Created block directory: ${path.relative(process.cwd(), dir)}`);
62
68
 
63
69
  // Step 6: Validate created block
64
70
  const validation = validateBlockDirectory(dir);
65
71
  if (!validation.valid) {
66
- console.error('Block validation failed:');
72
+ log.error('Block validation failed:');
67
73
  validation.errors.forEach(error => console.error(` - ${error}`));
68
74
  // Cleanup on failure
69
75
  require('fs').rmSync(dir, { recursive: true, force: true });
@@ -90,43 +96,50 @@ export async function handleAddBlock(blockUrl: string, options: { force?: boolea
90
96
  // Step 9: Update Pages collection (AST manipulation)
91
97
  const pagesCollectionPath = findPagesCollection();
92
98
  if (pagesCollectionPath) {
93
- console.log('🔧 Updating Pages collection...');
94
- const blockIdentifier = slugToIdentifier(metadata.slug);
95
- const relativeConfigPath = `@/blocks/${metadata.slug}/config`;
96
-
97
- updatePageCollectionConfig(pagesCollectionPath, relativeConfigPath, blockIdentifier);
98
- console.log(`✅ Added ${blockIdentifier} to Pages collection`);
99
+ await withSpinner(
100
+ 'Updating Pages collection',
101
+ async () => {
102
+ const blockIdentifier = slugToIdentifier(metadata.slug);
103
+ const relativeConfigPath = `@/blocks/${metadata.slug}/config`;
104
+ updatePageCollectionConfig(pagesCollectionPath, relativeConfigPath, blockIdentifier);
105
+ },
106
+ { emoji: EMOJIS.GEAR, successText: `Added ${slugToIdentifier(metadata.slug)} to Pages collection` }
107
+ );
99
108
  } else {
100
- console.warn('⚠️ Could not find Pages collection file. You may need to manually add the block to your collections.');
109
+ log.warning('Could not find Pages collection file. You may need to manually add the block to your collections.');
101
110
  }
102
111
 
103
112
  // Step 10: Update RenderBlocks component (AST manipulation)
104
113
  const renderBlocksPath = findRenderBlocksComponent();
105
114
  if (renderBlocksPath) {
106
- console.log('🔧 Updating RenderBlocks component...');
107
- const relativeComponentPath = `./${metadata.slug}/Component`;
108
-
109
- updateRenderBlocksComponent(renderBlocksPath, metadata.slug, relativeComponentPath);
110
- console.log(`✅ Added ${metadata.slug} component to RenderBlocks`);
115
+ await withSpinner(
116
+ 'Updating RenderBlocks component',
117
+ async () => {
118
+ const relativeComponentPath = `./${metadata.slug}/Component`;
119
+ updateRenderBlocksComponent(renderBlocksPath, metadata.slug, relativeComponentPath);
120
+ },
121
+ { emoji: EMOJIS.GEAR, successText: `Added ${metadata.slug} component to RenderBlocks` }
122
+ );
111
123
  } else {
112
- console.warn('⚠️ Could not find RenderBlocks component. You may need to manually add the block component.');
124
+ log.warning('Could not find RenderBlocks component. You may need to manually add the block component.');
113
125
  }
114
126
 
115
127
  // Step 11: Register block in registry
116
- console.log('📝 Registering block...');
117
- addBlockToRegistry(blockEntry);
118
- console.log('✅ Block registered successfully');
119
-
120
- console.log('');
121
- console.log('🎉 Block installation complete!');
122
- console.log('');
123
- console.log('Next steps:');
124
- console.log('1. Review the installed files in src/blocks/' + metadata.slug);
125
- console.log('2. Test your application to ensure the block works correctly');
126
- console.log('3. Commit the changes to your repository');
128
+ await withSpinner(
129
+ 'Registering block',
130
+ async () => addBlockToRegistry(blockEntry),
131
+ { emoji: EMOJIS.CHECK, successText: 'Block registered successfully' }
132
+ );
133
+
134
+ log.success('Block installation complete!');
135
+ showNextSteps([
136
+ `Review the installed files in src/blocks/${metadata.slug}`,
137
+ 'Test your application to ensure the block works correctly',
138
+ 'Commit the changes to your repository'
139
+ ]);
127
140
 
128
141
  } catch (error) {
129
142
  console.error('❌ Failed to add block:', (error as Error).message);
130
143
  process.exit(1);
131
144
  }
132
- }
145
+ }
@@ -1,62 +1,59 @@
1
- import { createInterface } from 'readline';
2
- import { exec, spawn } from 'child_process';
3
- import { promisify } from 'util';
4
-
5
- const execAsync = promisify(exec);
6
-
7
- const repoUrl = 'https://github.com/blok0-payload/starter.git';
8
-
9
- function prompt(question: string): Promise<boolean> {
10
- return new Promise((resolve) => {
11
- const rl = createInterface({
12
- input: process.stdin,
13
- output: process.stdout,
14
- });
15
- rl.question(question, (answer) => {
16
- rl.close();
17
- resolve(answer.toLowerCase().startsWith('y'));
18
- });
19
- });
20
- }
21
-
22
- export async function generateStarter(): Promise<void> {
23
- console.log('Cloning starter repository...');
24
- try {
25
- await execAsync(`git clone --depth 1 ${repoUrl} .`);
26
- console.log('Repository cloned successfully.');
27
- } catch (error) {
28
- throw new Error(`Failed to clone repository: ${error}`);
29
- }
30
-
31
- // Prompt for bun install
32
- const installDeps = await prompt('Run \'bun install\' to install dependencies? (y/n): ');
33
- if (installDeps) {
34
- console.log('Installing dependencies...');
35
- try {
36
- await new Promise<void>((resolve, reject) => {
37
- const child = spawn('bun', ['install'], { stdio: 'inherit' });
38
- child.on('close', (code) => {
39
- if (code === 0) resolve();
40
- else reject(new Error('Failed to install dependencies'));
41
- });
42
- child.on('error', reject);
43
- });
44
- } catch (error) {
45
- console.error('Failed to install dependencies:', error);
46
- }
47
- }
48
-
49
- // Prompt for git init
50
- const initGit = await prompt('Initialize git repository? (y/n): ');
51
- if (initGit) {
52
- console.log('Initializing git repository...');
53
- try {
54
- await execAsync('git init');
55
- console.log('Git repository initialized.');
56
- } catch (error) {
57
- console.error('Failed to initialize git:', error);
58
- }
59
- }
60
-
61
- console.log('Blok0 starter project created successfully!');
62
- }
1
+ import { createInterface } from 'readline';
2
+ import { exec, spawn } from 'child_process';
3
+ import { promisify } from 'util';
4
+ import { withSpinner, log, showNextSteps, EMOJIS } from '../ui';
5
+
6
+ const execAsync = promisify(exec);
7
+
8
+ const repoUrl = 'https://github.com/blok0-payload/starter.git';
9
+
10
+ function prompt(question: string): Promise<boolean> {
11
+ return new Promise((resolve) => {
12
+ const rl = createInterface({
13
+ input: process.stdin,
14
+ output: process.stdout,
15
+ });
16
+ rl.question(question, (answer) => {
17
+ rl.close();
18
+ resolve(answer.toLowerCase().startsWith('y'));
19
+ });
20
+ });
21
+ }
22
+
23
+ export async function generateStarter(): Promise<void> {
24
+ log.header('🚀 Setting up Blok0 starter project...');
25
+
26
+ // Clone repository with spinner
27
+ await withSpinner(
28
+ 'Cloning starter repository',
29
+ async () => {
30
+ await execAsync(`git clone --depth 1 ${repoUrl} .`);
31
+ },
32
+ {
33
+ emoji: EMOJIS.DOWNLOAD,
34
+ successText: 'Repository cloned successfully'
35
+ }
36
+ );
37
+
38
+ // Prompt for git init
39
+ const initGit = await prompt('Initialize git repository? (y/n): ');
40
+ if (initGit) {
41
+ await withSpinner(
42
+ 'Initializing git repository',
43
+ async () => {
44
+ await execAsync('git init');
45
+ },
46
+ {
47
+ emoji: EMOJIS.GEAR,
48
+ successText: 'Git repository initialized'
49
+ }
50
+ );
51
+ }
52
+
53
+ log.success('Starter project ready!');
54
+ showNextSteps([
55
+ 'Run \'npm install\' or \'bun install\' to install dependencies',
56
+ 'Start developing your Blok0 x PayloadCMS project'
57
+ ]);
58
+ }
59
+