@flexiberry/berrycore 0.1.0
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/LICENSE +21 -0
- package/dist/adapter/cli-adapter.d.ts +37 -0
- package/dist/adapter/cli-adapter.js +119 -0
- package/dist/berry-core.d.ts +108 -0
- package/dist/berry-core.js +258 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +18 -0
- package/dist/interpreter/environment.d.ts +45 -0
- package/dist/interpreter/environment.js +96 -0
- package/dist/interpreter/errors.d.ts +16 -0
- package/dist/interpreter/errors.js +27 -0
- package/dist/interpreter/interpreter.d.ts +111 -0
- package/dist/interpreter/interpreter.js +682 -0
- package/dist/interpreter/interpreter.types.d.ts +182 -0
- package/dist/interpreter/interpreter.types.js +73 -0
- package/dist/parser/ast/ast.engine.d.ts +103 -0
- package/dist/parser/ast/ast.engine.js +526 -0
- package/dist/parser/ast/ast.types.d.ts +242 -0
- package/dist/parser/ast/ast.types.js +37 -0
- package/dist/parser/formatter/formatter.d.ts +44 -0
- package/dist/parser/formatter/formatter.js +214 -0
- package/dist/parser/tokenizer/reader/grammer/api.grammer.d.ts +2 -0
- package/dist/parser/tokenizer/reader/grammer/api.grammer.js +102 -0
- package/dist/parser/tokenizer/reader/grammer/capture.grammer.d.ts +2 -0
- package/dist/parser/tokenizer/reader/grammer/capture.grammer.js +21 -0
- package/dist/parser/tokenizer/reader/grammer/check.grammer.d.ts +2 -0
- package/dist/parser/tokenizer/reader/grammer/check.grammer.js +21 -0
- package/dist/parser/tokenizer/reader/grammer/comment.grammer.d.ts +2 -0
- package/dist/parser/tokenizer/reader/grammer/comment.grammer.js +13 -0
- package/dist/parser/tokenizer/reader/grammer/conditions.grammer.d.ts +2 -0
- package/dist/parser/tokenizer/reader/grammer/conditions.grammer.js +68 -0
- package/dist/parser/tokenizer/reader/grammer/input.grammer.d.ts +2 -0
- package/dist/parser/tokenizer/reader/grammer/input.grammer.js +17 -0
- package/dist/parser/tokenizer/reader/grammer/keyvalue.grammer.d.ts +2 -0
- package/dist/parser/tokenizer/reader/grammer/keyvalue.grammer.js +240 -0
- package/dist/parser/tokenizer/reader/grammer/link.grammer.d.ts +2 -0
- package/dist/parser/tokenizer/reader/grammer/link.grammer.js +17 -0
- package/dist/parser/tokenizer/reader/grammer/params.grammer.d.ts +2 -0
- package/dist/parser/tokenizer/reader/grammer/params.grammer.js +21 -0
- package/dist/parser/tokenizer/reader/grammer/step.grammer.d.ts +2 -0
- package/dist/parser/tokenizer/reader/grammer/step.grammer.js +25 -0
- package/dist/parser/tokenizer/reader/grammer/task.grammer.d.ts +2 -0
- package/dist/parser/tokenizer/reader/grammer/task.grammer.js +17 -0
- package/dist/parser/tokenizer/reader/grammer/var.grammer.d.ts +2 -0
- package/dist/parser/tokenizer/reader/grammer/var.grammer.js +47 -0
- package/dist/parser/tokenizer/reader/lexer.engine.d.ts +43 -0
- package/dist/parser/tokenizer/reader/lexer.engine.js +178 -0
- package/dist/parser/tokenizer/reader/lexer.types.d.ts +18 -0
- package/dist/parser/tokenizer/reader/lexer.types.js +1 -0
- package/dist/parser/tokenizer/token.d.ts +13 -0
- package/dist/parser/tokenizer/token.js +13 -0
- package/dist/parser/tokenizer/tokenType.d.ts +58 -0
- package/dist/parser/tokenizer/tokenType.js +64 -0
- package/dist/script/format-util.d.ts +33 -0
- package/dist/script/format-util.js +94 -0
- package/dist/script/postman.util.d.ts +88 -0
- package/dist/script/postman.util.js +176 -0
- package/dist/script/swagger.util.d.ts +80 -0
- package/dist/script/swagger.util.js +202 -0
- package/dist/util/store-util.d.ts +5 -0
- package/dist/util/store-util.js +22 -0
- package/package.json +25 -0
- package/readme.md +107 -0
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import * as fs from "fs";
|
|
2
|
+
export class PostmanUtil {
|
|
3
|
+
/**
|
|
4
|
+
* Convert a Postman collection file (JSON) to Berry API code
|
|
5
|
+
* @param filePath Path to the Postman collection JSON file
|
|
6
|
+
*/
|
|
7
|
+
static convertFromPostmanFile(filePath) {
|
|
8
|
+
let content;
|
|
9
|
+
try {
|
|
10
|
+
content = fs.readFileSync(filePath, "utf-8");
|
|
11
|
+
}
|
|
12
|
+
catch (e) {
|
|
13
|
+
throw new Error(`Failed to read file: ${e}`);
|
|
14
|
+
}
|
|
15
|
+
let collection;
|
|
16
|
+
try {
|
|
17
|
+
collection = JSON.parse(content);
|
|
18
|
+
}
|
|
19
|
+
catch (e) {
|
|
20
|
+
throw new Error(`Invalid JSON in Postman file`);
|
|
21
|
+
}
|
|
22
|
+
if (!collection.item) {
|
|
23
|
+
throw new Error("No items found in Postman collection");
|
|
24
|
+
}
|
|
25
|
+
const lines = [];
|
|
26
|
+
// Recursively process items
|
|
27
|
+
this.processItems(collection.item, lines);
|
|
28
|
+
return lines.join("\n").trim() + "\n";
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Internal recursive processor for Postman items
|
|
32
|
+
*/
|
|
33
|
+
static processItems(items, lines, parentName = "") {
|
|
34
|
+
for (const item of items) {
|
|
35
|
+
if (item.item) {
|
|
36
|
+
// It's a folder/group
|
|
37
|
+
this.processItems(item.item, lines, item.name);
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
if (!item.request)
|
|
41
|
+
continue;
|
|
42
|
+
const req = item.request;
|
|
43
|
+
const name = item.name || parentName || "api";
|
|
44
|
+
const method = (req.method || "GET").toUpperCase();
|
|
45
|
+
// 1. URL Resolution
|
|
46
|
+
let url = this.buildUrl(req.url);
|
|
47
|
+
// Replace postman path params :param with berry {{param}}
|
|
48
|
+
url = url.replace(/\/:([a-zA-Z0-9_]+)/g, "/{{$1}}");
|
|
49
|
+
// 2. ID Generation (Abbreviation)
|
|
50
|
+
const apiId = this.abbreviate(name);
|
|
51
|
+
// 3. Construct Berry Block
|
|
52
|
+
lines.push(`Api ${method} #${apiId} ${name}`);
|
|
53
|
+
lines.push(`Url ${url}`);
|
|
54
|
+
// 4. Headers
|
|
55
|
+
const headers = this.extractHeaders(req.header);
|
|
56
|
+
if (headers.length > 0) {
|
|
57
|
+
lines.push("Header");
|
|
58
|
+
for (const h of headers) {
|
|
59
|
+
lines.push(`- ${h}`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
// 5. Body
|
|
63
|
+
const bodyContent = this.extractBody(req.body);
|
|
64
|
+
if (bodyContent) {
|
|
65
|
+
const bodyType = this.detectBodyType(req.header, req.body);
|
|
66
|
+
// Clean body to single line for preview-style import or preserve formatting?
|
|
67
|
+
// Let's preserve formatting if it's multiple lines
|
|
68
|
+
if (bodyContent.includes("\n")) {
|
|
69
|
+
lines.push(`Body ${bodyType} \`${bodyContent.trim()}\``);
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
lines.push(`Body ${bodyType} \`${bodyContent}\``);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
lines.push(""); // Spacer
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Build URL string from Postman URL object
|
|
80
|
+
*/
|
|
81
|
+
static buildUrl(url) {
|
|
82
|
+
if (typeof url === "string")
|
|
83
|
+
return url;
|
|
84
|
+
if (!url)
|
|
85
|
+
return "";
|
|
86
|
+
if (url.raw)
|
|
87
|
+
return url.raw;
|
|
88
|
+
if (url.host) {
|
|
89
|
+
const protocol = url.protocol || "https";
|
|
90
|
+
const host = url.host.join(".");
|
|
91
|
+
const path = url.path ? "/" + url.path.join("/") : "";
|
|
92
|
+
let fullUrl = `${protocol}://${host}${path}`;
|
|
93
|
+
if (url.query && url.query.length > 0) {
|
|
94
|
+
const queryParams = url.query
|
|
95
|
+
.filter((q) => q.key)
|
|
96
|
+
.map((q) => `${q.key}={{${q.key}}}`)
|
|
97
|
+
.join("&");
|
|
98
|
+
fullUrl += `?${queryParams}`;
|
|
99
|
+
}
|
|
100
|
+
return fullUrl;
|
|
101
|
+
}
|
|
102
|
+
return "";
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Abbreviate title to camelCase ID
|
|
106
|
+
*/
|
|
107
|
+
static abbreviate(title) {
|
|
108
|
+
const stopWords = new Set(["a", "in", "the", "with", "and", "of", "to", "for", "on", "at", "by", "is", "it"]);
|
|
109
|
+
return title
|
|
110
|
+
.toLowerCase()
|
|
111
|
+
.replace(/[^a-z0-9 ]/g, "") // Remove special chars
|
|
112
|
+
.split(" ")
|
|
113
|
+
.filter((word) => word.length > 0 && !stopWords.has(word))
|
|
114
|
+
.map((word, index) => (index === 0 ? word : word[0].toUpperCase() + word.slice(1)))
|
|
115
|
+
.join("");
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Extract headers to Berry format
|
|
119
|
+
*/
|
|
120
|
+
static extractHeaders(headers) {
|
|
121
|
+
if (!headers || !Array.isArray(headers))
|
|
122
|
+
return [];
|
|
123
|
+
return headers
|
|
124
|
+
.filter((h) => h.key && h.value !== undefined)
|
|
125
|
+
.map((h) => `${h.key.trim()}: '${h.value.trim()}'`);
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Extract body content from Postman request
|
|
129
|
+
*/
|
|
130
|
+
static extractBody(body) {
|
|
131
|
+
if (!body || !body.mode)
|
|
132
|
+
return "";
|
|
133
|
+
switch (body.mode) {
|
|
134
|
+
case "raw":
|
|
135
|
+
return body.raw || "";
|
|
136
|
+
case "urlencoded":
|
|
137
|
+
if (body.urlencoded) {
|
|
138
|
+
const obj = {};
|
|
139
|
+
body.urlencoded.forEach((kv) => (obj[kv.key] = kv.value));
|
|
140
|
+
return JSON.stringify(obj, null, 2);
|
|
141
|
+
}
|
|
142
|
+
break;
|
|
143
|
+
case "formdata":
|
|
144
|
+
if (body.formdata) {
|
|
145
|
+
const obj = {};
|
|
146
|
+
body.formdata.forEach((kv) => (obj[kv.key] = kv.value));
|
|
147
|
+
return JSON.stringify(obj, null, 2);
|
|
148
|
+
}
|
|
149
|
+
break;
|
|
150
|
+
case "graphql":
|
|
151
|
+
// Postman stores graphql in a specific way, but we'll treat as raw for now
|
|
152
|
+
return body.raw || "";
|
|
153
|
+
}
|
|
154
|
+
return "";
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Detect Berry body type from content-type or Postman options
|
|
158
|
+
*/
|
|
159
|
+
static detectBodyType(headers, body) {
|
|
160
|
+
if (body?.options?.raw?.language) {
|
|
161
|
+
const lang = body.options.raw.language.toUpperCase();
|
|
162
|
+
if (["JSON", "XML"].includes(lang))
|
|
163
|
+
return lang;
|
|
164
|
+
}
|
|
165
|
+
const contentType = headers?.find((h) => h.key.toLowerCase() === "content-type")?.value.toLowerCase() || "";
|
|
166
|
+
if (contentType.includes("json"))
|
|
167
|
+
return "JSON";
|
|
168
|
+
if (contentType.includes("xml"))
|
|
169
|
+
return "XML";
|
|
170
|
+
if (contentType.includes("form"))
|
|
171
|
+
return "FORM";
|
|
172
|
+
if (contentType.includes("octet-stream"))
|
|
173
|
+
return "BINARY";
|
|
174
|
+
return "RAW";
|
|
175
|
+
}
|
|
176
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Swagger / OpenAPI Utility
|
|
3
|
+
*
|
|
4
|
+
* Utilities for converting Swagger/OpenAPI definitions to Berry code.
|
|
5
|
+
*/
|
|
6
|
+
export interface OpenApiSchema {
|
|
7
|
+
type?: string;
|
|
8
|
+
properties?: Record<string, OpenApiSchema>;
|
|
9
|
+
items?: OpenApiSchema;
|
|
10
|
+
$ref?: string;
|
|
11
|
+
example?: any;
|
|
12
|
+
default?: any;
|
|
13
|
+
}
|
|
14
|
+
export interface OpenApiParameter {
|
|
15
|
+
name: string;
|
|
16
|
+
in: "path" | "query" | "header" | "body";
|
|
17
|
+
required?: boolean;
|
|
18
|
+
schema?: OpenApiSchema;
|
|
19
|
+
example?: any;
|
|
20
|
+
default?: any;
|
|
21
|
+
}
|
|
22
|
+
export interface OpenApiOperation {
|
|
23
|
+
operationId?: string;
|
|
24
|
+
summary?: string;
|
|
25
|
+
parameters?: OpenApiParameter[];
|
|
26
|
+
requestBody?: {
|
|
27
|
+
content: Record<string, {
|
|
28
|
+
schema: OpenApiSchema;
|
|
29
|
+
example?: any;
|
|
30
|
+
}>;
|
|
31
|
+
};
|
|
32
|
+
consumes?: string[];
|
|
33
|
+
produces?: string[];
|
|
34
|
+
}
|
|
35
|
+
export interface OpenApiDefinition {
|
|
36
|
+
openapi?: string;
|
|
37
|
+
swagger?: string;
|
|
38
|
+
host?: string;
|
|
39
|
+
basePath?: string;
|
|
40
|
+
schemes?: string[];
|
|
41
|
+
servers?: Array<{
|
|
42
|
+
url: string;
|
|
43
|
+
}>;
|
|
44
|
+
paths: Record<string, Record<string, OpenApiOperation>>;
|
|
45
|
+
components?: {
|
|
46
|
+
schemas: Record<string, OpenApiSchema>;
|
|
47
|
+
};
|
|
48
|
+
definitions?: Record<string, OpenApiSchema>;
|
|
49
|
+
}
|
|
50
|
+
export declare class SwaggerUtil {
|
|
51
|
+
/**
|
|
52
|
+
* Fetch a Swagger/OpenAPI doc from a URL and convert it to Berry code
|
|
53
|
+
*/
|
|
54
|
+
static convertFromSwaggerApi(url: string): Promise<string>;
|
|
55
|
+
/**
|
|
56
|
+
* Convert a Swagger/OpenAPI JSON string to Berry code
|
|
57
|
+
*/
|
|
58
|
+
static convertSwaggerToBerry(swagger: string | OpenApiDefinition, id: string): string;
|
|
59
|
+
/**
|
|
60
|
+
* Replace {param} with {{param}} and extract query params
|
|
61
|
+
*/
|
|
62
|
+
private static handlePathParams;
|
|
63
|
+
/**
|
|
64
|
+
* Build the full URL
|
|
65
|
+
*/
|
|
66
|
+
private static buildUrl;
|
|
67
|
+
/**
|
|
68
|
+
* Extract headers from operation details
|
|
69
|
+
*/
|
|
70
|
+
private static extractHeaders;
|
|
71
|
+
/**
|
|
72
|
+
* Generate the Body JSON block
|
|
73
|
+
*/
|
|
74
|
+
private static generateBodyBlock;
|
|
75
|
+
private static mapContentTypeToBodyType;
|
|
76
|
+
/**
|
|
77
|
+
* Recursively generate dummy data from an OpenAPI schema
|
|
78
|
+
*/
|
|
79
|
+
private static generateDummy;
|
|
80
|
+
}
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Swagger / OpenAPI Utility
|
|
3
|
+
*
|
|
4
|
+
* Utilities for converting Swagger/OpenAPI definitions to Berry code.
|
|
5
|
+
*/
|
|
6
|
+
export class SwaggerUtil {
|
|
7
|
+
/**
|
|
8
|
+
* Fetch a Swagger/OpenAPI doc from a URL and convert it to Berry code
|
|
9
|
+
*/
|
|
10
|
+
static async convertFromSwaggerApi(url) {
|
|
11
|
+
try {
|
|
12
|
+
const response = await fetch(url);
|
|
13
|
+
if (!response.ok) {
|
|
14
|
+
return `Failed to fetch Swagger: ${response.status} ${response.statusText}`;
|
|
15
|
+
}
|
|
16
|
+
const swaggerJson = await response.text();
|
|
17
|
+
const id = url.split("/").pop() || "api";
|
|
18
|
+
return this.convertSwaggerToBerry(swaggerJson, id);
|
|
19
|
+
}
|
|
20
|
+
catch (e) {
|
|
21
|
+
return `Error fetching Swagger: ${e instanceof Error ? e.message : String(e)}`;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Convert a Swagger/OpenAPI JSON string to Berry code
|
|
26
|
+
*/
|
|
27
|
+
static convertSwaggerToBerry(swagger, id) {
|
|
28
|
+
let obj;
|
|
29
|
+
try {
|
|
30
|
+
obj = typeof swagger === "string" ? JSON.parse(swagger) : swagger;
|
|
31
|
+
}
|
|
32
|
+
catch (e) {
|
|
33
|
+
return `Invalid Swagger/OpenAPI JSON`;
|
|
34
|
+
}
|
|
35
|
+
if (!obj.paths)
|
|
36
|
+
return "No paths found in Swagger definition";
|
|
37
|
+
const lines = [];
|
|
38
|
+
const serverUrl = obj.servers?.[0]?.url || "";
|
|
39
|
+
const schemas = obj.components?.schemas || obj.definitions || {};
|
|
40
|
+
let apiCount = 1;
|
|
41
|
+
for (const [path, methods] of Object.entries(obj.paths)) {
|
|
42
|
+
if (!methods)
|
|
43
|
+
continue;
|
|
44
|
+
for (const [method, details] of Object.entries(methods)) {
|
|
45
|
+
if (typeof details !== "object")
|
|
46
|
+
continue;
|
|
47
|
+
const opId = details.operationId || `${id}${apiCount}`;
|
|
48
|
+
const { modPath, queryParams } = this.handlePathParams(path, details.parameters || []);
|
|
49
|
+
const finalUrl = this.buildUrl(modPath, serverUrl, obj, queryParams);
|
|
50
|
+
const headers = this.extractHeaders(details);
|
|
51
|
+
const bodyBlock = this.generateBodyBlock(details, schemas);
|
|
52
|
+
// ── Construct API Block ──
|
|
53
|
+
lines.push(`Api ${method.toUpperCase()} #${opId} ${details.summary || ""}`);
|
|
54
|
+
lines.push(`Url ${finalUrl}`);
|
|
55
|
+
if (headers.length > 0) {
|
|
56
|
+
lines.push("Header");
|
|
57
|
+
for (const h of headers)
|
|
58
|
+
lines.push(`- ${h}`);
|
|
59
|
+
}
|
|
60
|
+
if (bodyBlock)
|
|
61
|
+
lines.push(bodyBlock);
|
|
62
|
+
lines.push(""); // Spacer
|
|
63
|
+
apiCount++;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return lines.join("\n").trim() + "\n";
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Replace {param} with {{param}} and extract query params
|
|
70
|
+
*/
|
|
71
|
+
static handlePathParams(path, params) {
|
|
72
|
+
let modPath = path;
|
|
73
|
+
const queryParams = [];
|
|
74
|
+
for (const param of params) {
|
|
75
|
+
if (param.in === "path") {
|
|
76
|
+
modPath = modPath.replace(new RegExp(`{${param.name}}`, "g"), `{{${param.name}}}`);
|
|
77
|
+
}
|
|
78
|
+
else if (param.in === "query") {
|
|
79
|
+
queryParams.push(`${param.name}={{${param.name}}}`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return { modPath, queryParams };
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Build the full URL
|
|
86
|
+
*/
|
|
87
|
+
static buildUrl(modPath, serverUrl, obj, queryParams) {
|
|
88
|
+
let url = modPath;
|
|
89
|
+
if (serverUrl) {
|
|
90
|
+
url = serverUrl.endsWith("/") && modPath.startsWith("/") ? serverUrl + modPath.slice(1) : serverUrl + modPath;
|
|
91
|
+
}
|
|
92
|
+
else if (obj.host) {
|
|
93
|
+
const scheme = obj.schemes?.[0] || "https";
|
|
94
|
+
const base = obj.basePath || "";
|
|
95
|
+
url = `${scheme}://${obj.host}${base}${modPath}`;
|
|
96
|
+
}
|
|
97
|
+
if (queryParams.length > 0) {
|
|
98
|
+
url += (url.includes("?") ? "&" : "?") + queryParams.join("&");
|
|
99
|
+
}
|
|
100
|
+
return url;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Extract headers from operation details
|
|
104
|
+
*/
|
|
105
|
+
static extractHeaders(details) {
|
|
106
|
+
const headers = [];
|
|
107
|
+
// Content-Type
|
|
108
|
+
if (details.consumes?.[0]) {
|
|
109
|
+
headers.push(`Content-Type: '${details.consumes[0]}'`);
|
|
110
|
+
}
|
|
111
|
+
else if (details.requestBody?.content) {
|
|
112
|
+
const types = Object.keys(details.requestBody.content);
|
|
113
|
+
if (types[0])
|
|
114
|
+
headers.push(`Content-Type: '${types[0]}'`);
|
|
115
|
+
}
|
|
116
|
+
// Accept
|
|
117
|
+
if (details.produces?.[0]) {
|
|
118
|
+
headers.push(`Accept: '${details.produces[0]}'`);
|
|
119
|
+
}
|
|
120
|
+
// Custom headers from parameters
|
|
121
|
+
if (details.parameters) {
|
|
122
|
+
for (const p of details.parameters) {
|
|
123
|
+
if (p.in === "header") {
|
|
124
|
+
headers.push(`${p.name}: '${p.example || p.default || ""}'`);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return headers;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Generate the Body JSON block
|
|
132
|
+
*/
|
|
133
|
+
static generateBodyBlock(details, schemas) {
|
|
134
|
+
// OpenAPI 3
|
|
135
|
+
if (details.requestBody?.content) {
|
|
136
|
+
const content = details.requestBody.content;
|
|
137
|
+
for (const [ctype, cval] of Object.entries(content)) {
|
|
138
|
+
const dummy = cval.example || this.generateDummy(cval.schema, schemas);
|
|
139
|
+
const bodyType = this.mapContentTypeToBodyType(ctype);
|
|
140
|
+
return `Body ${bodyType} \`${JSON.stringify(dummy, null, 2)}\``;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
// Swagger 2
|
|
144
|
+
if (details.parameters) {
|
|
145
|
+
const bodyParam = details.parameters.find((p) => p.in === "body");
|
|
146
|
+
if (bodyParam?.schema) {
|
|
147
|
+
const dummy = this.generateDummy(bodyParam.schema, schemas);
|
|
148
|
+
return `Body JSON \`${JSON.stringify(dummy, null, 2)}\``;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return "";
|
|
152
|
+
}
|
|
153
|
+
static mapContentTypeToBodyType(ctype) {
|
|
154
|
+
const ct = ctype.toLowerCase();
|
|
155
|
+
if (ct.includes("json"))
|
|
156
|
+
return "JSON";
|
|
157
|
+
if (ct.includes("xml"))
|
|
158
|
+
return "XML";
|
|
159
|
+
if (ct.includes("form"))
|
|
160
|
+
return "FORM";
|
|
161
|
+
if (ct.includes("octet-stream"))
|
|
162
|
+
return "BINARY";
|
|
163
|
+
return "RAW";
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Recursively generate dummy data from an OpenAPI schema
|
|
167
|
+
*/
|
|
168
|
+
static generateDummy(schema, schemas, depth = 0) {
|
|
169
|
+
if (!schema || depth > 5)
|
|
170
|
+
return {};
|
|
171
|
+
if (schema.example !== undefined)
|
|
172
|
+
return schema.example;
|
|
173
|
+
if (schema.default !== undefined)
|
|
174
|
+
return schema.default;
|
|
175
|
+
if (schema.$ref) {
|
|
176
|
+
const refName = schema.$ref.replace(/^#\/(definitions|components\/schemas)\//, "");
|
|
177
|
+
const resolved = schemas[refName];
|
|
178
|
+
return resolved ? this.generateDummy(resolved, schemas, depth + 1) : {};
|
|
179
|
+
}
|
|
180
|
+
switch (schema.type) {
|
|
181
|
+
case "string":
|
|
182
|
+
return "string";
|
|
183
|
+
case "integer":
|
|
184
|
+
case "number":
|
|
185
|
+
return 0;
|
|
186
|
+
case "boolean":
|
|
187
|
+
return false;
|
|
188
|
+
case "array":
|
|
189
|
+
return schema.items ? [this.generateDummy(schema.items, schemas, depth + 1)] : [];
|
|
190
|
+
case "object":
|
|
191
|
+
default:
|
|
192
|
+
if (schema.properties) {
|
|
193
|
+
const obj = {};
|
|
194
|
+
for (const [key, prop] of Object.entries(schema.properties)) {
|
|
195
|
+
obj[key] = this.generateDummy(prop, schemas, depth + 1);
|
|
196
|
+
}
|
|
197
|
+
return obj;
|
|
198
|
+
}
|
|
199
|
+
return {};
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import * as fs from "fs";
|
|
2
|
+
import os from "os";
|
|
3
|
+
import path from "path";
|
|
4
|
+
export class StoreUtility {
|
|
5
|
+
systemDocumentFolder = path.join(os.homedir(), "Documents");
|
|
6
|
+
static async storeData(data, fileName) {
|
|
7
|
+
const fileContent = Object.entries(data)
|
|
8
|
+
.map(([key, value]) => `${key}:${value}`)
|
|
9
|
+
.join("\n");
|
|
10
|
+
fs.writeFileSync(fileName, fileContent);
|
|
11
|
+
}
|
|
12
|
+
static async retriveData(fileName) {
|
|
13
|
+
const fileData = fs.readFileSync(fileName, "utf8");
|
|
14
|
+
const keyValueMap = {};
|
|
15
|
+
fileData.split("\n").forEach((line) => {
|
|
16
|
+
const [key, value] = line.split(":");
|
|
17
|
+
if (key && value) {
|
|
18
|
+
keyValueMap[key.trim()] = value.trim();
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@flexiberry/berrycore",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"bin": {
|
|
8
|
+
"berrycore": "./dist/index.js"
|
|
9
|
+
},
|
|
10
|
+
"keywords": [],
|
|
11
|
+
"author": "rintu raj c",
|
|
12
|
+
"license": "MIT",
|
|
13
|
+
"files": [
|
|
14
|
+
"dist"
|
|
15
|
+
],
|
|
16
|
+
"dependencies": {},
|
|
17
|
+
"devDependencies": {
|
|
18
|
+
"typescript": "^5.9.2"
|
|
19
|
+
},
|
|
20
|
+
"scripts": {
|
|
21
|
+
"build": "tsc && node scripts/fix-esm.mjs",
|
|
22
|
+
"watch": "tsc --watch",
|
|
23
|
+
"clean": "rm -rf dist"
|
|
24
|
+
}
|
|
25
|
+
}
|
package/readme.md
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
<img src="https://raw.githubusercontent.com/Flexiberry/flexiberry/main/assets/favicon/android-icon-192x192.png" height="120" width="120" alt="FlexiBerry Logo" />
|
|
3
|
+
<h1>🚀 @flexiberry/berrycore</h1>
|
|
4
|
+
<p><strong>The official Lexer, Parser, AST, and Runtime Engine for the Berry DSL</strong></p>
|
|
5
|
+
|
|
6
|
+
[](https://www.npmjs.com/package/@flexiberry/berrycore)
|
|
7
|
+
[](https://opensource.org/licenses/MIT)
|
|
8
|
+
|
|
9
|
+
<br />
|
|
10
|
+
</div>
|
|
11
|
+
|
|
12
|
+
## ✨ Overview
|
|
13
|
+
|
|
14
|
+
**BerryCore** is a lightweight, zero-dependency compiler architecture built entirely in TypeScript. It provides the foundational tools required to interpret the **Berry DSL** (Domain Specific Language), a custom scripting language explicitly designed to orchestrate API flows, manage testing environments, and execute complex task runners.
|
|
15
|
+
|
|
16
|
+
This package exposes the complete language engineering pipeline:
|
|
17
|
+
1. **LexerEngine** - Scans characters and generates grammar-aware tokens.
|
|
18
|
+
2. **AstEngine** - Parses tokens into a strongly-typed Abstract Syntax Tree (AST).
|
|
19
|
+
3. **Interpreter** - The runtime execution engine that evaluates the AST, manages isolated environment scopes, and fires runtime events.
|
|
20
|
+
4. **Formatter** - Converts the AST back into cleanly formatted `.berry` script.
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## 📦 Installation
|
|
25
|
+
|
|
26
|
+
Install `@flexiberry/berrycore` as a core dependency for your own test runners, CI pipelines, or editor integrations:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
npm install @flexiberry/berrycore
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## 🛠️ Programmatic Usage
|
|
35
|
+
|
|
36
|
+
The core engine is heavily decoupled, allowing you to use just the parts you need—whether you are building a language server, an IDE, or a CLI.
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
## 📖 Language Syntax Reference
|
|
40
|
+
|
|
41
|
+
The Berry DSL was designed to be easily readable, removing complex syntax clutter in favor of a clean, indentation-aware syntax.
|
|
42
|
+
|
|
43
|
+
### 1. Environments (`Env`)
|
|
44
|
+
Defines the deployment environments available for the script.
|
|
45
|
+
```berry
|
|
46
|
+
Env UAT, SIT, PROD
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### 2. Variables (`Var`)
|
|
50
|
+
Isolated memory scopes for storing state. Variables are initialized directly in the runtime environment.
|
|
51
|
+
```berry
|
|
52
|
+
Var UserData
|
|
53
|
+
- id: 101
|
|
54
|
+
- name: "John Doe"
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### 3. API Declarations (`Api`)
|
|
58
|
+
Defines HTTP endpoints with strict schema definitions.
|
|
59
|
+
```berry
|
|
60
|
+
Api POST #CreateUser "Creates a new user"
|
|
61
|
+
Url 'https://api.example.com/users'
|
|
62
|
+
Body JSON
|
|
63
|
+
`{
|
|
64
|
+
"name": "{{UserData.name}}"
|
|
65
|
+
}`
|
|
66
|
+
Header
|
|
67
|
+
- Authorization: 'Bearer {{token}}'
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### 4. Tasks and Steps (`Task` / `Step`)
|
|
71
|
+
The execution blocks where the interpreter orchestrates logic.
|
|
72
|
+
```berry
|
|
73
|
+
Task "User Onboarding"
|
|
74
|
+
Step "Register New User"
|
|
75
|
+
Call #CreateUser
|
|
76
|
+
Capture
|
|
77
|
+
- token: "response.data.token"
|
|
78
|
+
Check
|
|
79
|
+
- status == 201
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### 5. Control Flow (`Jump`, `Break`)
|
|
83
|
+
The interpreter supports localized control flow within Tasks.
|
|
84
|
+
```berry
|
|
85
|
+
Break if status == 400
|
|
86
|
+
Jump To @2 If status == 401
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
## 🧠 Architectural Principles
|
|
92
|
+
|
|
93
|
+
Following standard language engineering practices, `berrycore` adheres strictly to:
|
|
94
|
+
- **Separation of Concerns:** The parser never executes code. The interpreter never scans tokens.
|
|
95
|
+
- **Type Safety:** All AST nodes are backed by strict TypeScript interfaces (`BaseNode`, `TaskBlockNode`, etc.).
|
|
96
|
+
- **Immutability:** The AST generated by `AstEngine` is treated as a pure, immutable data layer.
|
|
97
|
+
- **Event-Driven Execution:** The `Interpreter` engine acts as an event emitter, allowing external UI layers (like CLI spinners or web dashboards) to hook directly into the runtime state.
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## 🤝 Contributing
|
|
102
|
+
|
|
103
|
+
This package is part of the FlexiBerry monorepo. We welcome contributions to grammar definitions, execution optimizations, and AST tooling. Please see the root `CONTRIBUTING.md` for guidelines.
|
|
104
|
+
|
|
105
|
+
## 📄 License
|
|
106
|
+
|
|
107
|
+
MIT © FlexiBerry.dev
|