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.
- package/dist/api/index.d.ts +46 -0
- package/dist/api/index.js +147 -0
- package/dist/ast/index.d.ts +31 -0
- package/dist/ast/index.js +324 -0
- package/dist/auth/constants.d.ts +11 -0
- package/dist/auth/constants.js +155 -0
- package/dist/auth/index.d.ts +61 -0
- package/dist/auth/index.js +168 -0
- package/dist/auth/server.d.ts +55 -0
- package/dist/auth/server.js +236 -0
- package/dist/blocks/index.d.ts +56 -0
- package/dist/blocks/index.js +189 -0
- package/dist/handlers/add-block.d.ts +7 -0
- package/dist/handlers/add-block.js +142 -0
- package/dist/handlers/login.d.ts +8 -0
- package/dist/handlers/login.js +124 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +187 -7
- package/dist/registry/index.d.ts +75 -0
- package/dist/registry/index.js +231 -0
- package/package.json +33 -25
- package/src/api/index.ts +177 -0
- package/src/ast/index.ts +368 -0
- package/src/auth/constants.ts +155 -0
- package/src/auth/index.ts +154 -0
- package/src/auth/server.ts +240 -0
- package/src/blocks/index.ts +186 -0
- package/src/handlers/add-block.ts +132 -0
- package/src/handlers/login.ts +130 -0
- package/src/index.ts +212 -51
- package/src/registry/index.ts +244 -0
- package/test-ast.js +150 -0
|
@@ -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
|
|
2
|
-
|
|
3
|
-
import { mkdirSync } from 'fs';
|
|
4
|
-
import { createInterface } from 'readline';
|
|
5
|
-
import { checkEmptyDirectory } from './detectors';
|
|
6
|
-
import { generateStarter } from './handlers/generate';
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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
|
+
}
|