blok0 0.1.0 → 0.1.2

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.
@@ -0,0 +1,130 @@
1
+ import { isAuthenticated, clearCredentials, storeAccessToken, AuthCallback } from '../auth';
2
+ import { AuthServer } from '../auth/server';
3
+ import open from 'open';
4
+
5
+ // Add SIGINT handler for graceful cleanup
6
+ process.on('SIGINT', () => {
7
+ console.log('\n\n⚠️ Authentication cancelled by user.');
8
+ process.exit(0);
9
+ });
10
+
11
+ /**
12
+ * Handle login command
13
+ */
14
+ export async function handleLogin(token?: string, manual?: boolean): Promise<void> {
15
+ // Direct token authentication (CI/CD)
16
+ if (token) {
17
+ try {
18
+ console.log('🔐 Saving authentication token...');
19
+ await storeAccessToken(token);
20
+ console.log('✅ Successfully authenticated!');
21
+ console.log('');
22
+ console.log('You can now use blok0 commands that require authentication.');
23
+ } catch (error) {
24
+ console.error('❌ Failed to save authentication token:', (error as Error).message);
25
+ process.exit(1);
26
+ }
27
+ return;
28
+ }
29
+
30
+ // Manual authentication instructions
31
+ if (manual) {
32
+ showManualInstructions();
33
+ return;
34
+ }
35
+
36
+ // Default: Browser-based authentication
37
+ try {
38
+ await handleBrowserLogin();
39
+ } catch (error) {
40
+ console.error('❌ Browser authentication failed:', (error as Error).message);
41
+ console.log('');
42
+ console.log('💡 Try manual authentication:');
43
+ console.log(' blok0 login --manual');
44
+ process.exit(1);
45
+ }
46
+ }
47
+
48
+ /**
49
+ * Handle browser-based authentication flow
50
+ */
51
+ async function handleBrowserLogin(): Promise<void> {
52
+ console.log('🔐 Blok0 Authentication');
53
+ console.log('======================');
54
+ console.log('');
55
+
56
+ // Create authentication server
57
+ const authServer = new AuthServer();
58
+
59
+ try {
60
+ // Initialize server (find available port)
61
+ console.log('🚀 Starting authentication server...');
62
+ await authServer.initialize();
63
+
64
+ // Get the authorization URL (now port is available)
65
+ const authUrl = authServer.getAuthorizationUrl();
66
+
67
+ console.log('🌐 Opening browser for authentication...');
68
+ await open(authUrl);
69
+
70
+ console.log('📱 Please complete authentication in your browser.');
71
+ console.log('⏳ Waiting for authentication to complete...');
72
+
73
+ // Start server and wait for callback
74
+ const authCallback: AuthCallback = await authServer.start();
75
+
76
+ // Store the token
77
+ console.log('🔐 Saving authentication token...');
78
+ await storeAccessToken(authCallback.token);
79
+ console.log('✅ Successfully authenticated!');
80
+ console.log('');
81
+ console.log('You can now use blok0 commands that require authentication.');
82
+
83
+ } catch (error) {
84
+ authServer.stop();
85
+ throw error;
86
+ }
87
+ }
88
+
89
+ /**
90
+ * Show manual authentication instructions
91
+ */
92
+ function showManualInstructions(): void {
93
+ console.log('🔐 Blok0 Manual Authentication');
94
+ console.log('==============================');
95
+ console.log('');
96
+ console.log('To authenticate with the Blok0 API, make a POST request to:');
97
+ console.log('https://www.blok0.xyz/api/customers/login');
98
+ console.log('');
99
+ console.log('Example using curl:');
100
+ console.log('curl -X POST https://www.blok0.xyz/api/customers/login \\');
101
+ console.log(' -H "Content-Type: application/json" \\');
102
+ console.log(' -d \'{"email": "your-email@example.com", "password": "your-password"}\'');
103
+ console.log('');
104
+ console.log('Then copy the access token and run:');
105
+ console.log('blok0 login --token <your-token>');
106
+ console.log('');
107
+ console.log('For CI/CD environments, set the BLOK0_TOKEN environment variable.');
108
+ console.log('');
109
+ console.log('💡 For browser-based login, run: blok0 login');
110
+ }
111
+
112
+ /**
113
+ * Handle logout command
114
+ */
115
+ export async function handleLogout(): Promise<void> {
116
+ try {
117
+ const wasAuthenticated = await isAuthenticated();
118
+
119
+ if (!wasAuthenticated) {
120
+ console.log('You are not currently logged in.');
121
+ return;
122
+ }
123
+
124
+ await clearCredentials();
125
+ console.log('✅ Successfully logged out and cleared stored credentials.');
126
+ } catch (error) {
127
+ console.error('❌ Failed to logout:', (error as Error).message);
128
+ process.exit(1);
129
+ }
130
+ }
package/src/index.ts CHANGED
@@ -1,51 +1,212 @@
1
- #!/usr/bin/env bun
2
-
3
- import { mkdirSync } from 'fs';
4
- import { createInterface } from 'readline';
5
- import { checkEmptyDirectory } from './detectors';
6
- import { generateStarter } from './handlers/generate';
7
-
8
- function prompt(question: string): Promise<string> {
9
- return new Promise((resolve) => {
10
- const rl = createInterface({
11
- input: process.stdin,
12
- output: process.stdout,
13
- });
14
- rl.question(question, (answer) => {
15
- rl.close();
16
- resolve(answer.trim());
17
- });
18
- });
19
- }
20
-
21
- async function main() {
22
- const args = process.argv.slice(2);
23
-
24
- if (args.length < 2 || args[0] !== 'generate' || args[1] !== 'starter') {
25
- console.error('Error: Invalid command. Supported: generate starter [folder]');
26
- process.exit(1);
27
- }
28
-
29
- let targetFolder = args[2];
30
- if (!targetFolder) {
31
- targetFolder = await prompt('Enter project folder name: ');
32
- }
33
-
34
- if (targetFolder !== '.') {
35
- mkdirSync(targetFolder, { recursive: true });
36
- process.chdir(targetFolder);
37
- }
38
-
39
- if (!checkEmptyDirectory()) {
40
- process.exit(1);
41
- }
42
-
43
- try {
44
- await generateStarter();
45
- } catch (error) {
46
- console.error(`Error: ${(error as Error).message}`);
47
- process.exit(1);
48
- }
49
- }
50
-
51
- main();
1
+ #!/usr/bin/env node
2
+
3
+ import { mkdirSync } from 'fs';
4
+ import { createInterface } from 'readline';
5
+ import { checkEmptyDirectory } from './detectors';
6
+ import { generateStarter } from './handlers/generate';
7
+ import { handleLogin, handleLogout } from './handlers/login';
8
+ import { handleAddBlock } from './handlers/add-block';
9
+ import { createEmptyRegistry } from './registry';
10
+
11
+ function prompt(question: string): Promise<string> {
12
+ return new Promise((resolve) => {
13
+ const rl = createInterface({
14
+ input: process.stdin,
15
+ output: process.stdout,
16
+ });
17
+ rl.question(question, (answer) => {
18
+ rl.close();
19
+ resolve(answer.trim());
20
+ });
21
+ });
22
+ }
23
+
24
+ async function showHelp() {
25
+ console.log(`
26
+ Blok0 - PayloadCMS Block Management CLI
27
+
28
+ USAGE:
29
+ blok0 <command> [subcommand] [options]
30
+
31
+ COMMANDS:
32
+ login Authenticate via browser or token
33
+ logout Remove stored credentials
34
+ debug Show authentication debug info
35
+ generate starter [folder] Generate PayloadCMS starter project
36
+ add block <url> Add a block from remote API
37
+ update block <id> Update existing block (future)
38
+ remove block <id> Remove block and clean up (future)
39
+ registry validate Validate registry integrity (future)
40
+
41
+ OPTIONS:
42
+ --help, -h Show this help message
43
+ --version, -v Show version information
44
+ --verbose Enable verbose logging
45
+ --dry-run Preview changes without applying them
46
+
47
+ EXAMPLES:
48
+ blok0 login
49
+ blok0 generate starter my-project
50
+ blok0 add block https://www.blok0.com/api/cli/sections/123
51
+
52
+ For more information, visit: https://github.com/blok0-payload/cli
53
+ `);
54
+ }
55
+
56
+ async function main() {
57
+ const args = process.argv.slice(2);
58
+
59
+ if (args.length === 0 || args.includes('--help') || args.includes('-h')) {
60
+ showHelp();
61
+ process.exit(0);
62
+ }
63
+
64
+ if (args.includes('--version') || args.includes('-v')) {
65
+ const pkg = require('../package.json');
66
+ console.log(`blok0 v${pkg.version}`);
67
+ process.exit(0);
68
+ }
69
+
70
+ const [command, ...restArgs] = args;
71
+
72
+ try {
73
+ switch (command) {
74
+ case 'generate':
75
+ const [genSubcommand, ...genRestArgs] = restArgs;
76
+ if (genSubcommand === 'starter') {
77
+ await handleGenerateStarter(genRestArgs);
78
+ } else {
79
+ console.error('Error: Invalid subcommand. Use: blok0 generate starter [folder]');
80
+ process.exit(1);
81
+ }
82
+ break;
83
+
84
+ case 'login':
85
+ // Check for flags
86
+ const tokenIndex = restArgs.indexOf('--token');
87
+ const manualIndex = restArgs.indexOf('--manual');
88
+
89
+ if (tokenIndex !== -1 && tokenIndex + 1 < restArgs.length) {
90
+ const token = restArgs[tokenIndex + 1];
91
+ await handleLogin(token);
92
+ } else if (manualIndex !== -1) {
93
+ await handleLogin(undefined, true);
94
+ } else {
95
+ await handleLogin();
96
+ }
97
+ break;
98
+
99
+ case 'logout':
100
+ await handleLogout();
101
+ break;
102
+
103
+ case 'debug':
104
+ await handleDebug();
105
+ break;
106
+
107
+ case 'add':
108
+ const [addSubcommand, ...addRestArgs] = restArgs;
109
+ if (addSubcommand === 'block') {
110
+ const blockUrl = `https://www.blok0.com/api/cli/sections/${addRestArgs[0]}`;
111
+ if (!blockUrl) {
112
+ console.error('Error: Block Slug is required. Use: blok0 add block <slug>');
113
+ process.exit(1);
114
+ }
115
+ const options = {
116
+ force: addRestArgs.includes('--force'),
117
+ dryRun: addRestArgs.includes('--dry-run')
118
+ };
119
+ await handleAddBlock(blockUrl, options);
120
+ } else {
121
+ console.error('Error: Invalid subcommand. Use: blok0 add block <url>');
122
+ process.exit(1);
123
+ }
124
+ break;
125
+
126
+ case 'update':
127
+ case 'remove':
128
+ case 'registry':
129
+ console.log(`${command} functionality coming soon...`);
130
+ break;
131
+
132
+ default:
133
+ console.error(`Error: Unknown command '${command}'`);
134
+ showHelp();
135
+ process.exit(1);
136
+ }
137
+ } catch (error) {
138
+ console.error(`Error: ${(error as Error).message}`);
139
+ process.exit(1);
140
+ }
141
+ }
142
+
143
+ async function handleGenerateStarter(args: string[]) {
144
+ let targetFolder = args[0];
145
+ if (!targetFolder) {
146
+ targetFolder = await prompt('Enter project folder name: ');
147
+ }
148
+
149
+ if (targetFolder !== '.') {
150
+ mkdirSync(targetFolder, { recursive: true });
151
+ process.chdir(targetFolder);
152
+ }
153
+
154
+ if (!checkEmptyDirectory()) {
155
+ process.exit(1);
156
+ }
157
+
158
+ await generateStarter();
159
+
160
+ // Initialize empty registry for the new project
161
+ try {
162
+ createEmptyRegistry();
163
+ console.log('📝 Initialized blok0-registry.json');
164
+ } catch (error) {
165
+ console.warn('⚠️ Failed to initialize registry:', (error as Error).message);
166
+ }
167
+ }
168
+
169
+ async function handleDebug() {
170
+ console.log('🔍 Blok0 CLI Debug Information');
171
+ console.log('==============================');
172
+ console.log('');
173
+
174
+ // Check stored token
175
+ const { getAccessToken, isAuthenticated } = await import('./auth');
176
+ const token = await getAccessToken();
177
+ const isAuth = await isAuthenticated();
178
+
179
+ console.log('🔐 Authentication Status:');
180
+ console.log(` Authenticated: ${isAuth ? '✅ Yes' : '❌ No'}`);
181
+ console.log(` Token Stored: ${token ? '✅ Yes' : '❌ No'}`);
182
+
183
+ if (token) {
184
+ console.log(` Token Preview: ${token.substring(0, 20)}...`);
185
+ console.log(` Authorization Header: Bearer ${token}`);
186
+ }
187
+
188
+ console.log('');
189
+ console.log('🌐 API Configuration:');
190
+ console.log(' Base URL: https://www.blok0.xyz');
191
+ console.log(' User Agent: blok0-cli/1.0.0');
192
+
193
+ console.log('');
194
+ console.log('🧪 Test API Connection:');
195
+
196
+ // Test API connection
197
+ const { apiClient } = await import('./api');
198
+ try {
199
+ const connectionTest = await apiClient.testConnection();
200
+ console.log(` Connection Test: ${connectionTest ? '✅ Passed' : '❌ Failed'}`);
201
+ } catch (error) {
202
+ console.log(` Connection Test: ❌ Failed - ${(error as Error).message}`);
203
+ }
204
+
205
+ console.log('');
206
+ console.log('💡 Next Steps:');
207
+ console.log(' 1. If no token, run: blok0 login');
208
+ console.log(' 2. Test API with: blok0 add block <url>');
209
+ console.log(' 3. Check server logs for detailed request info');
210
+ }
211
+
212
+ main();
@@ -0,0 +1,244 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+ import * as crypto from 'crypto';
4
+
5
+ export interface BlockSource {
6
+ url: string;
7
+ id: number;
8
+ }
9
+
10
+ export interface BlockEntry {
11
+ id: number;
12
+ name: string;
13
+ slug: string;
14
+ dir: string;
15
+ configPath: string;
16
+ componentPath: string;
17
+ source: BlockSource & {
18
+ fetchedAt: string;
19
+ };
20
+ checksums: {
21
+ [filename: string]: string;
22
+ };
23
+ }
24
+
25
+ export interface RegistryData {
26
+ version: string;
27
+ blocks: {
28
+ [slug: string]: BlockEntry;
29
+ };
30
+ }
31
+
32
+ const REGISTRY_FILE = 'blok0-registry.json';
33
+ const REGISTRY_VERSION = '1.0';
34
+
35
+ /**
36
+ * Get registry file path
37
+ */
38
+ function getRegistryPath(): string {
39
+ return path.join(process.cwd(), REGISTRY_FILE);
40
+ }
41
+
42
+ /**
43
+ * Load registry from file
44
+ */
45
+ export function loadRegistry(): RegistryData {
46
+ const registryPath = getRegistryPath();
47
+
48
+ if (!fs.existsSync(registryPath)) {
49
+ return {
50
+ version: REGISTRY_VERSION,
51
+ blocks: {}
52
+ };
53
+ }
54
+
55
+ try {
56
+ const data = fs.readFileSync(registryPath, 'utf-8');
57
+ const registry = JSON.parse(data);
58
+
59
+ // Validate registry structure
60
+ if (!registry.version || !registry.blocks) {
61
+ throw new Error('Invalid registry structure');
62
+ }
63
+
64
+ return registry;
65
+ } catch (error) {
66
+ throw new Error(`Failed to load registry: ${(error as Error).message}`);
67
+ }
68
+ }
69
+
70
+ /**
71
+ * Save registry to file
72
+ */
73
+ export function saveRegistry(registry: RegistryData): void {
74
+ const registryPath = getRegistryPath();
75
+
76
+ try {
77
+ fs.writeFileSync(registryPath, JSON.stringify(registry, null, 2));
78
+ } catch (error) {
79
+ throw new Error(`Failed to save registry: ${(error as Error).message}`);
80
+ }
81
+ }
82
+
83
+ /**
84
+ * Check if block slug already exists in registry
85
+ */
86
+ export function isBlockRegistered(slug: string): boolean {
87
+ const registry = loadRegistry();
88
+ return slug in registry.blocks;
89
+ }
90
+
91
+ /**
92
+ * Get block entry by slug
93
+ */
94
+ export function getBlockEntry(slug: string): BlockEntry | null {
95
+ const registry = loadRegistry();
96
+ return registry.blocks[slug] || null;
97
+ }
98
+
99
+ /**
100
+ * Add block to registry
101
+ */
102
+ export function addBlockToRegistry(entry: BlockEntry): void {
103
+ const registry = loadRegistry();
104
+
105
+ if (entry.slug in registry.blocks) {
106
+ throw new Error(`Block with slug '${entry.slug}' is already registered`);
107
+ }
108
+
109
+ registry.blocks[entry.slug] = entry;
110
+ saveRegistry(registry);
111
+ }
112
+
113
+ /**
114
+ * Remove block from registry
115
+ */
116
+ export function removeBlockFromRegistry(slug: string): void {
117
+ const registry = loadRegistry();
118
+
119
+ if (!(slug in registry.blocks)) {
120
+ throw new Error(`Block with slug '${slug}' is not registered`);
121
+ }
122
+
123
+ delete registry.blocks[slug];
124
+ saveRegistry(registry);
125
+ }
126
+
127
+ /**
128
+ * Update block checksums
129
+ */
130
+ export function updateBlockChecksums(slug: string, checksums: { [filename: string]: string }): void {
131
+ const registry = loadRegistry();
132
+
133
+ if (!(slug in registry.blocks)) {
134
+ throw new Error(`Block with slug '${slug}' is not registered`);
135
+ }
136
+
137
+ registry.blocks[slug].checksums = checksums;
138
+ saveRegistry(registry);
139
+ }
140
+
141
+ /**
142
+ * Calculate file checksum
143
+ */
144
+ export function calculateChecksum(filePath: string): string {
145
+ const fileBuffer = fs.readFileSync(filePath);
146
+ const hashSum = crypto.createHash('sha256');
147
+ hashSum.update(fileBuffer);
148
+ return hashSum.digest('hex');
149
+ }
150
+
151
+ /**
152
+ * Calculate checksums for all files in a directory
153
+ */
154
+ export function calculateDirectoryChecksums(dirPath: string): { [filename: string]: string } {
155
+ const checksums: { [filename: string]: string } = {};
156
+
157
+ function walkDirectory(dir: string): void {
158
+ const files = fs.readdirSync(dir);
159
+
160
+ for (const file of files) {
161
+ const filePath = path.join(dir, file);
162
+ const stat = fs.statSync(filePath);
163
+
164
+ if (stat.isDirectory()) {
165
+ walkDirectory(filePath);
166
+ } else {
167
+ const relativePath = path.relative(dirPath, filePath);
168
+ checksums[relativePath] = calculateChecksum(filePath);
169
+ }
170
+ }
171
+ }
172
+
173
+ walkDirectory(dirPath);
174
+ return checksums;
175
+ }
176
+
177
+ /**
178
+ * Validate registry integrity
179
+ */
180
+ export function validateRegistry(): { valid: boolean; errors: string[] } {
181
+ const errors: string[] = [];
182
+
183
+ try {
184
+ const registry = loadRegistry();
185
+
186
+ for (const [slug, entry] of Object.entries(registry.blocks)) {
187
+ // Check if block directory exists
188
+ if (!fs.existsSync(entry.dir)) {
189
+ errors.push(`Block '${slug}': directory '${entry.dir}' does not exist`);
190
+ continue;
191
+ }
192
+
193
+ // Check if config file exists
194
+ if (!fs.existsSync(entry.configPath)) {
195
+ errors.push(`Block '${slug}': config file '${entry.configPath}' does not exist`);
196
+ }
197
+
198
+ // Check if component file exists
199
+ if (!fs.existsSync(entry.componentPath)) {
200
+ errors.push(`Block '${slug}': component file '${entry.componentPath}' does not exist`);
201
+ }
202
+
203
+ // Validate checksums if they exist
204
+ if (entry.checksums) {
205
+ for (const [file, expectedChecksum] of Object.entries(entry.checksums)) {
206
+ const filePath = path.join(entry.dir, file);
207
+ if (fs.existsSync(filePath)) {
208
+ const actualChecksum = calculateChecksum(filePath);
209
+ if (actualChecksum !== expectedChecksum) {
210
+ errors.push(`Block '${slug}': checksum mismatch for '${file}'`);
211
+ }
212
+ } else {
213
+ errors.push(`Block '${slug}': file '${file}' referenced in checksums does not exist`);
214
+ }
215
+ }
216
+ }
217
+ }
218
+ } catch (error) {
219
+ errors.push(`Registry validation failed: ${(error as Error).message}`);
220
+ }
221
+
222
+ return {
223
+ valid: errors.length === 0,
224
+ errors
225
+ };
226
+ }
227
+
228
+ /**
229
+ * Create empty registry for new projects
230
+ */
231
+ export function createEmptyRegistry(): void {
232
+ const registryPath = getRegistryPath();
233
+
234
+ if (fs.existsSync(registryPath)) {
235
+ throw new Error('Registry already exists');
236
+ }
237
+
238
+ const emptyRegistry: RegistryData = {
239
+ version: REGISTRY_VERSION,
240
+ blocks: {}
241
+ };
242
+
243
+ saveRegistry(emptyRegistry);
244
+ }