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,46 @@
|
|
|
1
|
+
export interface BlockMetadata {
|
|
2
|
+
id: number;
|
|
3
|
+
name: string;
|
|
4
|
+
slug: string;
|
|
5
|
+
codeFiles: Array<{
|
|
6
|
+
sourceCode?: {
|
|
7
|
+
name: string;
|
|
8
|
+
url: string;
|
|
9
|
+
};
|
|
10
|
+
}>;
|
|
11
|
+
_status: 'published' | 'draft';
|
|
12
|
+
}
|
|
13
|
+
export interface CodeFile {
|
|
14
|
+
name: string;
|
|
15
|
+
content: string;
|
|
16
|
+
}
|
|
17
|
+
declare class APIClient {
|
|
18
|
+
private client;
|
|
19
|
+
private baseURL;
|
|
20
|
+
constructor(baseURL?: string);
|
|
21
|
+
/**
|
|
22
|
+
* Fetch block metadata from URL
|
|
23
|
+
*/
|
|
24
|
+
fetchBlockMetadata(url: string): Promise<BlockMetadata>;
|
|
25
|
+
/**
|
|
26
|
+
* Download source code file
|
|
27
|
+
*/
|
|
28
|
+
downloadSourceCode(url: string): Promise<string>;
|
|
29
|
+
/**
|
|
30
|
+
* Validate block metadata
|
|
31
|
+
*/
|
|
32
|
+
validateBlockMetadata(metadata: BlockMetadata): void;
|
|
33
|
+
/**
|
|
34
|
+
* Fetch complete block data including source files
|
|
35
|
+
*/
|
|
36
|
+
fetchBlockData(url: string): Promise<{
|
|
37
|
+
metadata: BlockMetadata;
|
|
38
|
+
files: CodeFile[];
|
|
39
|
+
}>;
|
|
40
|
+
/**
|
|
41
|
+
* Test API connectivity and authentication
|
|
42
|
+
*/
|
|
43
|
+
testConnection(): Promise<boolean>;
|
|
44
|
+
}
|
|
45
|
+
export declare const apiClient: APIClient;
|
|
46
|
+
export { APIClient };
|
|
@@ -0,0 +1,147 @@
|
|
|
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.APIClient = exports.apiClient = void 0;
|
|
7
|
+
const axios_1 = __importDefault(require("axios"));
|
|
8
|
+
const auth_1 = require("../auth");
|
|
9
|
+
class APIClient {
|
|
10
|
+
client;
|
|
11
|
+
baseURL;
|
|
12
|
+
constructor(baseURL = 'https://www.blok0.xyz') {
|
|
13
|
+
this.baseURL = baseURL;
|
|
14
|
+
this.client = axios_1.default.create({
|
|
15
|
+
baseURL,
|
|
16
|
+
timeout: 30000,
|
|
17
|
+
headers: {
|
|
18
|
+
'User-Agent': 'blok0-cli/1.0.0'
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
// Add auth header to all requests
|
|
22
|
+
this.client.interceptors.request.use(async (config) => {
|
|
23
|
+
const authHeader = await (0, auth_1.getAuthHeader)();
|
|
24
|
+
if (authHeader) {
|
|
25
|
+
config.headers.Authorization = authHeader;
|
|
26
|
+
console.log(`🔐 API Request: ${config.method?.toUpperCase()} ${config.url}`);
|
|
27
|
+
console.log(`🔑 Authorization: ${authHeader}`);
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
console.log(`🚫 API Request: ${config.method?.toUpperCase()} ${config.url} (No auth)`);
|
|
31
|
+
}
|
|
32
|
+
return config;
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Fetch block metadata from URL
|
|
37
|
+
*/
|
|
38
|
+
async fetchBlockMetadata(url) {
|
|
39
|
+
try {
|
|
40
|
+
const response = await this.client.get(url);
|
|
41
|
+
return response.data;
|
|
42
|
+
}
|
|
43
|
+
catch (error) {
|
|
44
|
+
if (axios_1.default.isAxiosError(error)) {
|
|
45
|
+
if (error.response?.status === 401) {
|
|
46
|
+
throw new Error('Authentication required. Please run `blok0 login` first.');
|
|
47
|
+
}
|
|
48
|
+
if (error.response?.status === 404) {
|
|
49
|
+
throw new Error(`Block not found at URL: ${url}`);
|
|
50
|
+
}
|
|
51
|
+
throw new Error(`API request failed: ${error.response?.data?.message || error.message}`);
|
|
52
|
+
}
|
|
53
|
+
throw error;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Download source code file
|
|
58
|
+
*/
|
|
59
|
+
async downloadSourceCode(url) {
|
|
60
|
+
try {
|
|
61
|
+
const response = await this.client.get(url, {
|
|
62
|
+
responseType: 'text'
|
|
63
|
+
});
|
|
64
|
+
return response.data;
|
|
65
|
+
}
|
|
66
|
+
catch (error) {
|
|
67
|
+
if (axios_1.default.isAxiosError(error)) {
|
|
68
|
+
throw new Error(`Failed to download source code from ${url}: ${error.message}`);
|
|
69
|
+
}
|
|
70
|
+
throw error;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Validate block metadata
|
|
75
|
+
*/
|
|
76
|
+
validateBlockMetadata(metadata) {
|
|
77
|
+
if (!metadata.id || typeof metadata.id !== 'number') {
|
|
78
|
+
throw new Error('Invalid block metadata: missing or invalid id');
|
|
79
|
+
}
|
|
80
|
+
if (!metadata.name || typeof metadata.name !== 'string') {
|
|
81
|
+
throw new Error('Invalid block metadata: missing or invalid name');
|
|
82
|
+
}
|
|
83
|
+
if (!metadata.slug || typeof metadata.slug !== 'string') {
|
|
84
|
+
throw new Error('Invalid block metadata: missing or invalid slug');
|
|
85
|
+
}
|
|
86
|
+
// Validate slug format
|
|
87
|
+
const slugRegex = /^[a-z0-9-]+$/;
|
|
88
|
+
if (!slugRegex.test(metadata.slug)) {
|
|
89
|
+
throw new Error('Invalid block slug format. Must contain only lowercase letters, numbers, and dashes.');
|
|
90
|
+
}
|
|
91
|
+
if (!metadata.codeFiles || !Array.isArray(metadata.codeFiles)) {
|
|
92
|
+
throw new Error('Invalid block metadata: missing or invalid codeFiles');
|
|
93
|
+
}
|
|
94
|
+
// Filter out malformed codeFiles entries
|
|
95
|
+
const validCodeFiles = metadata.codeFiles.filter(file => file.sourceCode && file.sourceCode.name && file.sourceCode.url);
|
|
96
|
+
if (validCodeFiles.length === 0) {
|
|
97
|
+
throw new Error('Invalid block metadata: no valid code files specified');
|
|
98
|
+
}
|
|
99
|
+
// Check for required files
|
|
100
|
+
const hasConfig = validCodeFiles.some(file => file.sourceCode.name === 'config.ts');
|
|
101
|
+
if (!hasConfig) {
|
|
102
|
+
throw new Error('Invalid block metadata: config.ts file is required');
|
|
103
|
+
}
|
|
104
|
+
if (metadata._status !== 'published') {
|
|
105
|
+
throw new Error('Block is not published and cannot be installed');
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Fetch complete block data including source files
|
|
110
|
+
*/
|
|
111
|
+
async fetchBlockData(url) {
|
|
112
|
+
const metadata = await this.fetchBlockMetadata(url);
|
|
113
|
+
this.validateBlockMetadata(metadata);
|
|
114
|
+
const files = [];
|
|
115
|
+
// Filter out malformed codeFiles entries (same logic as validation)
|
|
116
|
+
const validCodeFiles = metadata.codeFiles.filter(file => file.sourceCode && file.sourceCode.name && file.sourceCode.url);
|
|
117
|
+
for (const fileInfo of validCodeFiles) {
|
|
118
|
+
const { name, url: fileUrl } = fileInfo.sourceCode;
|
|
119
|
+
// Resolve relative URLs
|
|
120
|
+
const resolvedUrl = fileUrl.startsWith('http') ? fileUrl : `${this.baseURL}${fileUrl}`;
|
|
121
|
+
try {
|
|
122
|
+
const content = await this.downloadSourceCode(resolvedUrl);
|
|
123
|
+
files.push({ name, content });
|
|
124
|
+
}
|
|
125
|
+
catch (error) {
|
|
126
|
+
throw new Error(`Failed to download ${name}: ${error.message}`);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return { metadata, files };
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Test API connectivity and authentication
|
|
133
|
+
*/
|
|
134
|
+
async testConnection() {
|
|
135
|
+
try {
|
|
136
|
+
// Try to access a test endpoint or just check auth header
|
|
137
|
+
const authHeader = await (0, auth_1.getAuthHeader)();
|
|
138
|
+
return authHeader !== null;
|
|
139
|
+
}
|
|
140
|
+
catch (error) {
|
|
141
|
+
return false;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
exports.APIClient = APIClient;
|
|
146
|
+
// Export singleton instance
|
|
147
|
+
exports.apiClient = new APIClient();
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Update Payload page collection config to include new block
|
|
3
|
+
*/
|
|
4
|
+
export declare function updatePageCollectionConfig(pagesCollectionPath: string, blockConfigPath: string, blockName: string): void;
|
|
5
|
+
/**
|
|
6
|
+
* Update RenderBlocks.tsx to include new block component
|
|
7
|
+
*/
|
|
8
|
+
export declare function updateRenderBlocksComponent(componentPath: string, blockSlug: string, blockComponentPath: string): void;
|
|
9
|
+
/**
|
|
10
|
+
* Validate that a file can be parsed as TypeScript
|
|
11
|
+
*/
|
|
12
|
+
export declare function validateTypeScriptFile(filePath: string): {
|
|
13
|
+
valid: boolean;
|
|
14
|
+
errors: string[];
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Find Payload config file in project
|
|
18
|
+
*/
|
|
19
|
+
export declare function findPayloadConfig(): string | null;
|
|
20
|
+
/**
|
|
21
|
+
* Find RenderBlocks component file
|
|
22
|
+
*/
|
|
23
|
+
export declare function findRenderBlocksComponent(): string | null;
|
|
24
|
+
/**
|
|
25
|
+
* Find Pages collection file in project
|
|
26
|
+
*/
|
|
27
|
+
export declare function findPagesCollection(): string | null;
|
|
28
|
+
/**
|
|
29
|
+
* Extract the component name from a Component.tsx file
|
|
30
|
+
*/
|
|
31
|
+
export declare function extractComponentName(componentPath: string): string | null;
|
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.updatePageCollectionConfig = updatePageCollectionConfig;
|
|
4
|
+
exports.updateRenderBlocksComponent = updateRenderBlocksComponent;
|
|
5
|
+
exports.validateTypeScriptFile = validateTypeScriptFile;
|
|
6
|
+
exports.findPayloadConfig = findPayloadConfig;
|
|
7
|
+
exports.findRenderBlocksComponent = findRenderBlocksComponent;
|
|
8
|
+
exports.findPagesCollection = findPagesCollection;
|
|
9
|
+
exports.extractComponentName = extractComponentName;
|
|
10
|
+
const ts_morph_1 = require("ts-morph");
|
|
11
|
+
/**
|
|
12
|
+
* Update Payload page collection config to include new block
|
|
13
|
+
*/
|
|
14
|
+
function updatePageCollectionConfig(pagesCollectionPath, blockConfigPath, blockName) {
|
|
15
|
+
const project = new ts_morph_1.Project();
|
|
16
|
+
const sourceFile = project.addSourceFileAtPath(pagesCollectionPath);
|
|
17
|
+
// Check if block is already imported (do this first before any modifications)
|
|
18
|
+
const importDeclarations = sourceFile.getImportDeclarations();
|
|
19
|
+
const existingImport = importDeclarations.find(imp => imp.getModuleSpecifier().getLiteralValue() === blockConfigPath);
|
|
20
|
+
let needsImport = !existingImport;
|
|
21
|
+
if (needsImport) {
|
|
22
|
+
// Add import statement
|
|
23
|
+
const lastImport = importDeclarations[importDeclarations.length - 1];
|
|
24
|
+
const importText = `\nimport { ${blockName} } from '${blockConfigPath}';\n`;
|
|
25
|
+
sourceFile.insertText(lastImport.getEnd(), importText);
|
|
26
|
+
sourceFile.saveSync(); // Save the import addition
|
|
27
|
+
}
|
|
28
|
+
// Re-load the source file after modification to get fresh AST
|
|
29
|
+
const updatedProject = new ts_morph_1.Project();
|
|
30
|
+
const updatedSourceFile = updatedProject.addSourceFileAtPath(pagesCollectionPath);
|
|
31
|
+
// Find the Pages collection export (fresh references)
|
|
32
|
+
const pagesExport = updatedSourceFile.getVariableDeclaration('Pages');
|
|
33
|
+
if (!pagesExport) {
|
|
34
|
+
throw new Error('Could not find Pages collection export');
|
|
35
|
+
}
|
|
36
|
+
const pagesObject = pagesExport.getInitializer();
|
|
37
|
+
if (!pagesObject || !ts_morph_1.Node.isObjectLiteralExpression(pagesObject)) {
|
|
38
|
+
throw new Error('Could not find Pages collection object');
|
|
39
|
+
}
|
|
40
|
+
// Find the fields array
|
|
41
|
+
const fieldsProperty = pagesObject.getProperty('fields');
|
|
42
|
+
if (!fieldsProperty || !ts_morph_1.Node.isPropertyAssignment(fieldsProperty)) {
|
|
43
|
+
throw new Error('Could not find fields property in Pages collection');
|
|
44
|
+
}
|
|
45
|
+
const fieldsArray = fieldsProperty.getInitializer();
|
|
46
|
+
if (!fieldsArray || !ts_morph_1.Node.isArrayLiteralExpression(fieldsArray)) {
|
|
47
|
+
throw new Error('Could not find fields array in Pages collection');
|
|
48
|
+
}
|
|
49
|
+
// Find the tabs array within fields
|
|
50
|
+
const tabsField = fieldsArray.getElements().find(element => {
|
|
51
|
+
if (ts_morph_1.Node.isObjectLiteralExpression(element)) {
|
|
52
|
+
const typeProperty = element.getProperty('type');
|
|
53
|
+
if (ts_morph_1.Node.isPropertyAssignment(typeProperty)) {
|
|
54
|
+
const initializer = typeProperty.getInitializer();
|
|
55
|
+
return ts_morph_1.Node.isStringLiteral(initializer) && initializer.getLiteralValue() === 'tabs';
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return false;
|
|
59
|
+
});
|
|
60
|
+
if (!tabsField || !ts_morph_1.Node.isObjectLiteralExpression(tabsField)) {
|
|
61
|
+
throw new Error('Could not find tabs field in Pages collection');
|
|
62
|
+
}
|
|
63
|
+
const tabsProperty = tabsField.getProperty('tabs');
|
|
64
|
+
if (!tabsProperty || !ts_morph_1.Node.isPropertyAssignment(tabsProperty)) {
|
|
65
|
+
throw new Error('Could not find tabs property');
|
|
66
|
+
}
|
|
67
|
+
const tabsArray = tabsProperty.getInitializer();
|
|
68
|
+
if (!tabsArray || !ts_morph_1.Node.isArrayLiteralExpression(tabsArray)) {
|
|
69
|
+
throw new Error('Could not find tabs array');
|
|
70
|
+
}
|
|
71
|
+
// Find the "Content" tab (which contains the layout)
|
|
72
|
+
const contentTab = tabsArray.getElements().find(element => {
|
|
73
|
+
if (ts_morph_1.Node.isObjectLiteralExpression(element)) {
|
|
74
|
+
const labelProperty = element.getProperty('label');
|
|
75
|
+
if (ts_morph_1.Node.isPropertyAssignment(labelProperty)) {
|
|
76
|
+
const initializer = labelProperty.getInitializer();
|
|
77
|
+
return ts_morph_1.Node.isStringLiteral(initializer) && initializer.getLiteralValue() === 'Content';
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return false;
|
|
81
|
+
});
|
|
82
|
+
if (!contentTab || !ts_morph_1.Node.isObjectLiteralExpression(contentTab)) {
|
|
83
|
+
throw new Error('Could not find Content tab in Pages collection');
|
|
84
|
+
}
|
|
85
|
+
// Find the layout field within the Content tab
|
|
86
|
+
const contentFields = contentTab.getProperty('fields');
|
|
87
|
+
if (!contentFields || !ts_morph_1.Node.isPropertyAssignment(contentFields)) {
|
|
88
|
+
throw new Error('Could not find fields in Content tab');
|
|
89
|
+
}
|
|
90
|
+
const contentFieldsArray = contentFields.getInitializer();
|
|
91
|
+
if (!contentFieldsArray || !ts_morph_1.Node.isArrayLiteralExpression(contentFieldsArray)) {
|
|
92
|
+
throw new Error('Could not find fields array in Content tab');
|
|
93
|
+
}
|
|
94
|
+
// Find the layout field
|
|
95
|
+
const layoutField = contentFieldsArray.getElements().find(element => {
|
|
96
|
+
if (ts_morph_1.Node.isObjectLiteralExpression(element)) {
|
|
97
|
+
const nameProperty = element.getProperty('name');
|
|
98
|
+
if (ts_morph_1.Node.isPropertyAssignment(nameProperty)) {
|
|
99
|
+
const initializer = nameProperty.getInitializer();
|
|
100
|
+
return ts_morph_1.Node.isStringLiteral(initializer) && initializer.getLiteralValue() === 'layout';
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return false;
|
|
104
|
+
});
|
|
105
|
+
if (!layoutField || !ts_morph_1.Node.isObjectLiteralExpression(layoutField)) {
|
|
106
|
+
throw new Error('Could not find layout field');
|
|
107
|
+
}
|
|
108
|
+
// Find the blocks array
|
|
109
|
+
const blocksProperty = layoutField.getProperty('blocks');
|
|
110
|
+
if (!blocksProperty || !ts_morph_1.Node.isPropertyAssignment(blocksProperty)) {
|
|
111
|
+
throw new Error('Could not find blocks property in layout');
|
|
112
|
+
}
|
|
113
|
+
const blocksArray = blocksProperty.getInitializer();
|
|
114
|
+
if (!blocksArray || !ts_morph_1.Node.isArrayLiteralExpression(blocksArray)) {
|
|
115
|
+
throw new Error('Could not find blocks array in layout');
|
|
116
|
+
}
|
|
117
|
+
// Check if block is already in array
|
|
118
|
+
const existingElements = blocksArray.getElements();
|
|
119
|
+
const alreadyExists = existingElements.some(element => {
|
|
120
|
+
if (ts_morph_1.Node.isIdentifier(element)) {
|
|
121
|
+
return element.getText() === blockName;
|
|
122
|
+
}
|
|
123
|
+
return false;
|
|
124
|
+
});
|
|
125
|
+
if (!alreadyExists) {
|
|
126
|
+
// Add block to array
|
|
127
|
+
const lastElement = existingElements[existingElements.length - 1];
|
|
128
|
+
if (lastElement) {
|
|
129
|
+
blocksArray.insertElement(existingElements.length, blockName);
|
|
130
|
+
}
|
|
131
|
+
else {
|
|
132
|
+
blocksArray.addElement(blockName);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
updatedSourceFile.saveSync();
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Update RenderBlocks.tsx to include new block component
|
|
139
|
+
*/
|
|
140
|
+
function updateRenderBlocksComponent(componentPath, blockSlug, blockComponentPath) {
|
|
141
|
+
// Extract the actual component name from the Component.tsx file
|
|
142
|
+
const fullComponentPath = require('path').resolve(blockComponentPath.replace('./', 'src/blocks/') + '.tsx');
|
|
143
|
+
const componentName = extractComponentName(fullComponentPath);
|
|
144
|
+
if (!componentName) {
|
|
145
|
+
throw new Error(`Could not extract component name from ${fullComponentPath}`);
|
|
146
|
+
}
|
|
147
|
+
// Convert slug to blockType key (camelCase)
|
|
148
|
+
const blockTypeKey = blockSlug.split('-').map((word, index) => index === 0 ? word : word.charAt(0).toUpperCase() + word.slice(1)).join('');
|
|
149
|
+
const project = new ts_morph_1.Project();
|
|
150
|
+
const sourceFile = project.addSourceFileAtPath(componentPath);
|
|
151
|
+
// Find the blockComponents object
|
|
152
|
+
const blockComponents = sourceFile.getVariableDeclaration('blockComponents')?.getInitializer();
|
|
153
|
+
if (!blockComponents || !ts_morph_1.Node.isObjectLiteralExpression(blockComponents)) {
|
|
154
|
+
throw new Error('Could not find blockComponents object in RenderBlocks.tsx');
|
|
155
|
+
}
|
|
156
|
+
// Check if component is already imported (check both default and named imports)
|
|
157
|
+
const importDeclarations = sourceFile.getImportDeclarations();
|
|
158
|
+
const existingImport = importDeclarations.find(imp => {
|
|
159
|
+
// Check default import
|
|
160
|
+
const defaultImport = imp.getDefaultImport();
|
|
161
|
+
if (defaultImport && defaultImport.getText() === componentName) {
|
|
162
|
+
return true;
|
|
163
|
+
}
|
|
164
|
+
// Check named imports
|
|
165
|
+
const namedImports = imp.getNamedImports();
|
|
166
|
+
return namedImports.some(namedImport => namedImport.getName() === componentName);
|
|
167
|
+
});
|
|
168
|
+
let needsImport = !existingImport;
|
|
169
|
+
if (needsImport) {
|
|
170
|
+
// Add import statement (named import)
|
|
171
|
+
const lastImport = importDeclarations[importDeclarations.length - 1];
|
|
172
|
+
const importText = `\nimport { ${componentName} } from '${blockComponentPath}';\n`;
|
|
173
|
+
sourceFile.insertText(lastImport.getEnd(), importText);
|
|
174
|
+
sourceFile.saveSync(); // Save the import addition
|
|
175
|
+
}
|
|
176
|
+
// Re-load the source file after modification to get fresh AST
|
|
177
|
+
const updatedProject = new ts_morph_1.Project();
|
|
178
|
+
const updatedSourceFile = updatedProject.addSourceFileAtPath(componentPath);
|
|
179
|
+
const updatedBlockComponents = updatedSourceFile.getVariableDeclaration('blockComponents')?.getInitializer();
|
|
180
|
+
if (!updatedBlockComponents || !ts_morph_1.Node.isObjectLiteralExpression(updatedBlockComponents)) {
|
|
181
|
+
throw new Error('Could not find blockComponents object in RenderBlocks.tsx after reload');
|
|
182
|
+
}
|
|
183
|
+
// Check if property already exists
|
|
184
|
+
const existingProperties = updatedBlockComponents.getProperties();
|
|
185
|
+
const propertyExists = existingProperties.some(prop => {
|
|
186
|
+
if (ts_morph_1.Node.isPropertyAssignment(prop)) {
|
|
187
|
+
const name = prop.getName();
|
|
188
|
+
return name === `'${blockTypeKey}'` || name === `"${blockTypeKey}"`;
|
|
189
|
+
}
|
|
190
|
+
return false;
|
|
191
|
+
});
|
|
192
|
+
if (!propertyExists) {
|
|
193
|
+
// Add new property to object
|
|
194
|
+
const lastProperty = existingProperties[existingProperties.length - 1];
|
|
195
|
+
if (lastProperty) {
|
|
196
|
+
const insertPos = lastProperty.getEnd();
|
|
197
|
+
const newPropertyText = `,\n '${blockTypeKey}': ${componentName}`;
|
|
198
|
+
updatedSourceFile.insertText(insertPos, newPropertyText);
|
|
199
|
+
}
|
|
200
|
+
else {
|
|
201
|
+
// Object is empty, add first property
|
|
202
|
+
const objectStart = updatedBlockComponents.getStart() + 1; // After opening brace
|
|
203
|
+
updatedSourceFile.insertText(objectStart, `\n '${blockTypeKey}': ${componentName}\n`);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
updatedSourceFile.saveSync();
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Validate that a file can be parsed as TypeScript
|
|
210
|
+
*/
|
|
211
|
+
function validateTypeScriptFile(filePath) {
|
|
212
|
+
const errors = [];
|
|
213
|
+
try {
|
|
214
|
+
const project = new ts_morph_1.Project();
|
|
215
|
+
const sourceFile = project.addSourceFileAtPath(filePath);
|
|
216
|
+
// Check for syntax errors
|
|
217
|
+
const diagnostics = sourceFile.getPreEmitDiagnostics();
|
|
218
|
+
for (const diagnostic of diagnostics) {
|
|
219
|
+
errors.push(diagnostic.getMessageText().toString());
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
catch (error) {
|
|
223
|
+
errors.push(`Failed to parse TypeScript file: ${error.message}`);
|
|
224
|
+
}
|
|
225
|
+
return { valid: errors.length === 0, errors };
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Find Payload config file in project
|
|
229
|
+
*/
|
|
230
|
+
function findPayloadConfig() {
|
|
231
|
+
const possiblePaths = [
|
|
232
|
+
'payload.config.ts',
|
|
233
|
+
'payload.config.js',
|
|
234
|
+
'src/payload.config.ts',
|
|
235
|
+
'src/payload.config.js'
|
|
236
|
+
];
|
|
237
|
+
for (const path of possiblePaths) {
|
|
238
|
+
if (require('fs').existsSync(path)) {
|
|
239
|
+
return path;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
return null;
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Find RenderBlocks component file
|
|
246
|
+
*/
|
|
247
|
+
function findRenderBlocksComponent() {
|
|
248
|
+
const possiblePaths = [
|
|
249
|
+
'src/blocks/RenderBlocks.tsx',
|
|
250
|
+
'src/blocks/RenderBlocks.ts',
|
|
251
|
+
'src/components/RenderBlocks.tsx',
|
|
252
|
+
'src/components/RenderBlocks.ts'
|
|
253
|
+
];
|
|
254
|
+
for (const path of possiblePaths) {
|
|
255
|
+
if (require('fs').existsSync(path)) {
|
|
256
|
+
return path;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
return null;
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* Find Pages collection file in project
|
|
263
|
+
*/
|
|
264
|
+
function findPagesCollection() {
|
|
265
|
+
const possiblePaths = [
|
|
266
|
+
'src/collections/Pages/index.ts',
|
|
267
|
+
'src/collections/Pages.ts',
|
|
268
|
+
'src/collections/pages/index.ts',
|
|
269
|
+
'src/collections/pages.ts'
|
|
270
|
+
];
|
|
271
|
+
for (const path of possiblePaths) {
|
|
272
|
+
if (require('fs').existsSync(path)) {
|
|
273
|
+
return path;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
return null;
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* Extract the component name from a Component.tsx file
|
|
280
|
+
*/
|
|
281
|
+
function extractComponentName(componentPath) {
|
|
282
|
+
try {
|
|
283
|
+
const project = new ts_morph_1.Project();
|
|
284
|
+
const sourceFile = project.addSourceFileAtPath(componentPath);
|
|
285
|
+
// Look for named exports like "export const ComponentName"
|
|
286
|
+
const variableDeclarations = sourceFile.getVariableDeclarations();
|
|
287
|
+
for (const declaration of variableDeclarations) {
|
|
288
|
+
if (declaration.isExported()) {
|
|
289
|
+
const name = declaration.getName();
|
|
290
|
+
// Check if it's a React component (function or const with JSX)
|
|
291
|
+
const initializer = declaration.getInitializer();
|
|
292
|
+
if (initializer) {
|
|
293
|
+
// Look for React.FC or function patterns
|
|
294
|
+
const type = declaration.getType();
|
|
295
|
+
const typeText = type.getText();
|
|
296
|
+
if (typeText.includes('React.FC') || typeText.includes('FC<') ||
|
|
297
|
+
initializer.getText().includes('React.FC') || initializer.getText().includes('FC<')) {
|
|
298
|
+
return name;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
// Look for export default statements
|
|
304
|
+
const defaultExport = sourceFile.getDefaultExportSymbol();
|
|
305
|
+
if (defaultExport) {
|
|
306
|
+
return defaultExport.getName();
|
|
307
|
+
}
|
|
308
|
+
// Look for export default expressions
|
|
309
|
+
const exportAssignments = sourceFile.getExportAssignments();
|
|
310
|
+
for (const assignment of exportAssignments) {
|
|
311
|
+
if (assignment.isExportEquals() === false) { // export default
|
|
312
|
+
const expression = assignment.getExpression();
|
|
313
|
+
if (expression) {
|
|
314
|
+
return expression.getText();
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
return null;
|
|
319
|
+
}
|
|
320
|
+
catch (error) {
|
|
321
|
+
console.error('Error extracting component name:', error);
|
|
322
|
+
return null;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export declare const AUTH_BASE_URL = "https://www.blok0.xyz";
|
|
2
|
+
export declare const AUTHORIZE_ENDPOINT = "/api/authorize/cli";
|
|
3
|
+
export declare const DEFAULT_TIMEOUT: number;
|
|
4
|
+
export declare const PORT_RANGE: {
|
|
5
|
+
min: number;
|
|
6
|
+
max: number;
|
|
7
|
+
};
|
|
8
|
+
export declare const CALLBACK_PATH = "/callback";
|
|
9
|
+
export declare const SUCCESS_HTML = "\n<!DOCTYPE html>\n<html>\n<head>\n <title>Authentication Successful</title>\n <style>\n body {\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n background: #f5f5f5;\n margin: 0;\n padding: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n height: 100vh;\n }\n .container {\n background: white;\n padding: 2rem;\n border-radius: 8px;\n box-shadow: 0 2px 10px rgba(0,0,0,0.1);\n text-align: center;\n max-width: 400px;\n }\n .success-icon {\n color: #10b981;\n font-size: 3rem;\n margin-bottom: 1rem;\n }\n h1 {\n color: #1f2937;\n margin: 0 0 0.5rem 0;\n font-size: 1.5rem;\n }\n p {\n color: #6b7280;\n margin: 0;\n }\n </style>\n</head>\n<body>\n <div class=\"container\">\n <div class=\"success-icon\">\u2713</div>\n <h1>Authentication Successful!</h1>\n <p>You can now close this window and return to your terminal.</p>\n </div>\n</body>\n</html>\n";
|
|
10
|
+
export declare const ERROR_HTML = "\n<!DOCTYPE html>\n<html>\n<head>\n <title>Authentication Failed</title>\n <style>\n body {\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n background: #f5f5f5;\n margin: 0;\n padding: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n height: 100vh;\n }\n .container {\n background: white;\n padding: 2rem;\n border-radius: 8px;\n box-shadow: 0 2px 10px rgba(0,0,0,0.1);\n text-align: center;\n max-width: 400px;\n }\n .error-icon {\n color: #ef4444;\n font-size: 3rem;\n margin-bottom: 1rem;\n }\n h1 {\n color: #1f2937;\n margin: 0 0 0.5rem 0;\n font-size: 1.5rem;\n }\n p {\n color: #6b7280;\n margin: 0;\n }\n </style>\n</head>\n<body>\n <div class=\"container\">\n <div class=\"error-icon\">\u2717</div>\n <h1>Authentication Failed</h1>\n <p>Please try again or contact support if the problem persists.</p>\n </div>\n</body>\n</html>\n";
|
|
11
|
+
export declare const TIMEOUT_HTML = "\n<!DOCTYPE html>\n<html>\n<head>\n <title>Authentication Timeout</title>\n <style>\n body {\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n background: #f5f5f5;\n margin: 0;\n padding: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n height: 100vh;\n }\n .container {\n background: white;\n padding: 2rem;\n border-radius: 8px;\n box-shadow: 0 2px 10px rgba(0,0,0,0.1);\n text-align: center;\n max-width: 400px;\n }\n .timeout-icon {\n color: #f59e0b;\n font-size: 3rem;\n margin-bottom: 1rem;\n }\n h1 {\n color: #1f2937;\n margin: 0 0 0.5rem 0;\n font-size: 1.5rem;\n }\n p {\n color: #6b7280;\n margin: 0;\n }\n </style>\n</head>\n<body>\n <div class=\"container\">\n <div class=\"timeout-icon\">\u23F1</div>\n <h1>Authentication Timeout</h1>\n <p>The authentication request has timed out. Please try again.</p>\n </div>\n</body>\n</html>\n";
|