bonescript-compiler 0.5.4 → 0.5.6

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,344 @@
1
+ /**
2
+ * BoneScript OpenAPI Emitter
3
+ * Generates OpenAPI 3.0.3 YAML and JSON specs from an IRSystem.
4
+ */
5
+
6
+ import * as IR from "./ir";
7
+
8
+ // ─── Helpers ──────────────────────────────────────────────────────────────────
9
+
10
+ function toSnakeCase(s: string): string {
11
+ return s.replace(/([a-z])([A-Z])/g, "$1_$2").toLowerCase();
12
+ }
13
+
14
+ function toDashCase(s: string): string {
15
+ return toSnakeCase(s).replace(/_/g, "-");
16
+ }
17
+
18
+ function toPascalCase(s: string): string {
19
+ return s.replace(/(^|_)([a-z])/g, (_: string, _p: string, c: string) => c.toUpperCase());
20
+ }
21
+
22
+ function irTypeToOpenApi(irType: string): Record<string, unknown> {
23
+ if (irType === "string") return { type: "string" };
24
+ if (irType === "uint" || irType === "int") return { type: "integer" };
25
+ if (irType === "float") return { type: "number" };
26
+ if (irType === "bool") return { type: "boolean" };
27
+ if (irType === "timestamp") return { type: "string", format: "date-time" };
28
+ if (irType === "uuid") return { type: "string", format: "uuid" };
29
+ if (irType === "bytes") return { type: "string", format: "byte" };
30
+ if (irType === "json") return { type: "object" };
31
+ const listMatch = irType.match(/^list<(.+)>$/);
32
+ if (listMatch) return { type: "array", items: irTypeToOpenApi(listMatch[1]) };
33
+ const setMatch = irType.match(/^set<(.+)>$/);
34
+ if (setMatch) return { type: "array", items: irTypeToOpenApi(setMatch[1]) };
35
+ const optMatch = irType.match(/^optional<(.+)>$/);
36
+ if (optMatch) return { ...irTypeToOpenApi(optMatch[1]), nullable: true };
37
+ return { type: "string" };
38
+ }
39
+
40
+ function ind(n: number): string {
41
+ return " ".repeat(n);
42
+ }
43
+
44
+ function yamlValue(v: unknown, depth: number): string {
45
+ if (v === null || v === undefined) return "null";
46
+ if (typeof v === "boolean") return String(v);
47
+ if (typeof v === "number") return String(v);
48
+ if (typeof v === "string") {
49
+ if (
50
+ v.includes(":") ||
51
+ v.includes("#") ||
52
+ v.includes("'") ||
53
+ v.startsWith("{") ||
54
+ v.startsWith("[")
55
+ ) {
56
+ return JSON.stringify(v);
57
+ }
58
+ return v;
59
+ }
60
+ if (Array.isArray(v)) {
61
+ if (v.length === 0) return "[]";
62
+ return (
63
+ "\n" +
64
+ v
65
+ .map((item) => ind(depth) + "- " + yamlValue(item, depth + 1))
66
+ .join("\n")
67
+ );
68
+ }
69
+ if (typeof v === "object") {
70
+ const entries = Object.entries(v as Record<string, unknown>);
71
+ if (entries.length === 0) return "{}";
72
+ return (
73
+ "\n" +
74
+ entries
75
+ .map(([k, val]) => {
76
+ const valStr = yamlValue(val, depth + 1);
77
+ if (valStr.startsWith("\n")) {
78
+ return ind(depth) + k + ":" + valStr;
79
+ }
80
+ return ind(depth) + k + ": " + valStr;
81
+ })
82
+ .join("\n")
83
+ );
84
+ }
85
+ return String(v);
86
+ }
87
+
88
+ function objToYaml(obj: Record<string, unknown>, depth = 0): string {
89
+ const lines: string[] = [];
90
+ for (const [k, v] of Object.entries(obj)) {
91
+ const valStr = yamlValue(v, depth + 1);
92
+ if (valStr.startsWith("\n")) {
93
+ lines.push(ind(depth) + k + ":" + valStr);
94
+ } else {
95
+ lines.push(ind(depth) + k + ": " + valStr);
96
+ }
97
+ }
98
+ return lines.join("\n");
99
+ }
100
+
101
+ // ─── Spec builder ─────────────────────────────────────────────────────────────
102
+
103
+ function buildSpec(system: IR.IRSystem): Record<string, unknown> {
104
+ const paths: Record<string, unknown> = {};
105
+ const schemas: Record<string, unknown> = {};
106
+
107
+ for (const mod of system.modules) {
108
+ if (mod.kind !== "api_service" || mod.models.length === 0) continue;
109
+
110
+ const model = mod.models[0];
111
+ const tableName = toSnakeCase(model.name);
112
+ const modelName = toPascalCase(model.name);
113
+ const collectionPath = "/" + tableName + "s";
114
+ const itemPath = "/" + tableName + "s/{id}";
115
+
116
+ const allMethods: IR.IRMethod[] = mod.interfaces.flatMap((i) => i.methods);
117
+ const crudNames = new Set(["create", "read", "update", "delete", "list"]);
118
+ const capabilityMethods = allMethods.filter(
119
+ (m) => !crudNames.has(m.name.toLowerCase())
120
+ );
121
+
122
+ const securityRef = [{ BearerAuth: [] }];
123
+
124
+ const listOp: Record<string, unknown> = {
125
+ summary: "List " + modelName,
126
+ operationId: "list" + modelName,
127
+ tags: [modelName],
128
+ parameters: [
129
+ { name: "page", in: "query", schema: { type: "integer", default: 1 } },
130
+ {
131
+ name: "page_size",
132
+ in: "query",
133
+ schema: { type: "integer", default: 50 },
134
+ },
135
+ ],
136
+ responses: {
137
+ "200": {
138
+ description: "List of " + modelName,
139
+ content: {
140
+ "application/json": {
141
+ schema: {
142
+ type: "object",
143
+ properties: {
144
+ items: {
145
+ type: "array",
146
+ items: { $ref: "#/components/schemas/" + modelName },
147
+ },
148
+ total: { type: "integer" },
149
+ page: { type: "integer" },
150
+ page_size: { type: "integer" },
151
+ },
152
+ },
153
+ },
154
+ },
155
+ },
156
+ "401": { description: "Unauthorized" },
157
+ },
158
+ };
159
+
160
+ const createOp: Record<string, unknown> = {
161
+ summary: "Create " + modelName,
162
+ operationId: "create" + modelName,
163
+ tags: [modelName],
164
+ security: securityRef,
165
+ requestBody: {
166
+ required: true,
167
+ content: {
168
+ "application/json": {
169
+ schema: { $ref: "#/components/schemas/" + modelName },
170
+ },
171
+ },
172
+ },
173
+ responses: {
174
+ "200": {
175
+ description: "Created",
176
+ content: {
177
+ "application/json": {
178
+ schema: { $ref: "#/components/schemas/" + modelName },
179
+ },
180
+ },
181
+ },
182
+ "401": { description: "Unauthorized" },
183
+ "422": { description: "Precondition failed" },
184
+ "400": { description: "Bad request" },
185
+ },
186
+ };
187
+
188
+ paths[collectionPath] = { get: listOp, post: createOp };
189
+
190
+ const idParam = [
191
+ {
192
+ name: "id",
193
+ in: "path",
194
+ required: true,
195
+ schema: { type: "string", format: "uuid" },
196
+ },
197
+ ];
198
+
199
+ paths[itemPath] = {
200
+ get: {
201
+ summary: "Get " + modelName,
202
+ operationId: "get" + modelName,
203
+ tags: [modelName],
204
+ parameters: idParam,
205
+ security: securityRef,
206
+ responses: {
207
+ "200": {
208
+ description: "Found",
209
+ content: {
210
+ "application/json": {
211
+ schema: { $ref: "#/components/schemas/" + modelName },
212
+ },
213
+ },
214
+ },
215
+ "401": { description: "Unauthorized" },
216
+ "400": { description: "Not found" },
217
+ },
218
+ },
219
+ put: {
220
+ summary: "Update " + modelName,
221
+ operationId: "update" + modelName,
222
+ tags: [modelName],
223
+ parameters: idParam,
224
+ security: securityRef,
225
+ requestBody: {
226
+ required: true,
227
+ content: {
228
+ "application/json": {
229
+ schema: { $ref: "#/components/schemas/" + modelName },
230
+ },
231
+ },
232
+ },
233
+ responses: {
234
+ "200": {
235
+ description: "Updated",
236
+ content: {
237
+ "application/json": {
238
+ schema: { $ref: "#/components/schemas/" + modelName },
239
+ },
240
+ },
241
+ },
242
+ "401": { description: "Unauthorized" },
243
+ "422": { description: "Precondition failed" },
244
+ "400": { description: "Bad request" },
245
+ },
246
+ },
247
+ delete: {
248
+ summary: "Delete " + modelName,
249
+ operationId: "delete" + modelName,
250
+ tags: [modelName],
251
+ parameters: idParam,
252
+ security: securityRef,
253
+ responses: {
254
+ "200": { description: "Deleted" },
255
+ "401": { description: "Unauthorized" },
256
+ "400": { description: "Not found" },
257
+ },
258
+ },
259
+ };
260
+
261
+ for (const method of capabilityMethods) {
262
+ const capPath = collectionPath + "/" + toDashCase(method.name);
263
+ const capOp: Record<string, unknown> = {
264
+ summary: method.name + " on " + modelName,
265
+ operationId: method.name + modelName,
266
+ tags: [modelName],
267
+ requestBody: {
268
+ required: true,
269
+ content: {
270
+ "application/json": {
271
+ schema: { $ref: "#/components/schemas/" + modelName },
272
+ },
273
+ },
274
+ },
275
+ responses: {
276
+ "200": {
277
+ description: "Success",
278
+ content: {
279
+ "application/json": {
280
+ schema: {
281
+ type: "object",
282
+ properties: {
283
+ ok: { type: "boolean" },
284
+ action: { type: "string" },
285
+ },
286
+ },
287
+ },
288
+ },
289
+ },
290
+ "401": { description: "Unauthorized" },
291
+ "422": { description: "Precondition failed" },
292
+ "400": { description: "Bad request" },
293
+ },
294
+ };
295
+ if (method.authenticated) {
296
+ capOp.security = securityRef;
297
+ }
298
+ paths[capPath] = { post: capOp };
299
+ }
300
+
301
+ const properties: Record<string, unknown> = {};
302
+ for (const field of model.fields) {
303
+ properties[field.name] = irTypeToOpenApi(field.type);
304
+ }
305
+ schemas[modelName] = {
306
+ type: "object",
307
+ properties,
308
+ };
309
+ }
310
+
311
+ return {
312
+ openapi: "3.0.3",
313
+ info: {
314
+ title: system.name,
315
+ version: system.version,
316
+ description: "Generated by BoneScript compiler",
317
+ },
318
+ servers: [{ url: "http://localhost:3000" }],
319
+ paths,
320
+ components: {
321
+ securitySchemes: {
322
+ BearerAuth: {
323
+ type: "http",
324
+ scheme: "bearer",
325
+ bearerFormat: "JWT",
326
+ },
327
+ },
328
+ schemas,
329
+ },
330
+ };
331
+ }
332
+
333
+ // ─── Public API ───────────────────────────────────────────────────────────────
334
+
335
+ export function emitOpenApiSpec(system: IR.IRSystem): string {
336
+ const spec = buildSpec(system);
337
+ const lines: string[] = ["# Generated by BoneScript compiler"];
338
+ lines.push(objToYaml(spec));
339
+ return lines.join("\n") + "\n";
340
+ }
341
+
342
+ export function emitOpenApiJson(system: IR.IRSystem): string {
343
+ return JSON.stringify(buildSpec(system), null, 2);
344
+ }
@@ -0,0 +1,145 @@
1
+ /**
2
+ * BoneScript Postman Collection Emitter
3
+ * Generates a Postman Collection v2.1 JSON from an IRSystem.
4
+ */
5
+
6
+ import * as IR from "./ir";
7
+
8
+ // ─── Helpers ──────────────────────────────────────────────────────────────────
9
+
10
+ function toSnakeCase(s: string): string {
11
+ return s.replace(/([a-z])([A-Z])/g, "$1_$2").toLowerCase();
12
+ }
13
+
14
+ function toDashCase(s: string): string {
15
+ return toSnakeCase(s).replace(/_/g, "-");
16
+ }
17
+
18
+ function sampleValue(irType: string): unknown {
19
+ if (irType === "string") return "example";
20
+ if (irType === "uint" || irType === "int") return 1;
21
+ if (irType === "float") return 1.0;
22
+ if (irType === "bool") return true;
23
+ if (irType === "uuid") return "00000000-0000-0000-0000-000000000001";
24
+ if (irType === "timestamp") return "2024-01-01T00:00:00.000Z";
25
+ if (irType === "bytes") return "";
26
+ if (irType === "json") return {};
27
+ const listMatch = irType.match(/^list<(.+)>$/);
28
+ if (listMatch) return [];
29
+ const setMatch = irType.match(/^set<(.+)>$/);
30
+ if (setMatch) return [];
31
+ const optMatch = irType.match(/^optional<(.+)>$/);
32
+ if (optMatch) return null;
33
+ return "example";
34
+ }
35
+
36
+ function buildSampleBody(model: IR.IRModel): Record<string, unknown> {
37
+ const body: Record<string, unknown> = {};
38
+ for (const field of model.fields) {
39
+ body[field.name] = sampleValue(field.type);
40
+ }
41
+ return body;
42
+ }
43
+
44
+ function makeRequest(
45
+ name: string,
46
+ method: string,
47
+ url: string,
48
+ body?: Record<string, unknown>
49
+ ): Record<string, unknown> {
50
+ const headers = [
51
+ { key: "Content-Type", value: "application/json" },
52
+ { key: "Authorization", value: "Bearer {{token}}" },
53
+ ];
54
+
55
+ const req: Record<string, unknown> = {
56
+ name,
57
+ request: {
58
+ method,
59
+ header: headers,
60
+ url: {
61
+ raw: url,
62
+ host: ["{{baseUrl}}"],
63
+ path: url
64
+ .replace("{{baseUrl}}/", "")
65
+ .split("/")
66
+ .filter(Boolean),
67
+ },
68
+ },
69
+ };
70
+
71
+ if (body !== undefined) {
72
+ (req.request as Record<string, unknown>).body = {
73
+ mode: "raw",
74
+ raw: JSON.stringify(body, null, 2),
75
+ options: { raw: { language: "json" } },
76
+ };
77
+ }
78
+
79
+ return req;
80
+ }
81
+
82
+ // ─── Public API ───────────────────────────────────────────────────────────────
83
+
84
+ export function emitPostmanCollection(system: IR.IRSystem): string {
85
+ const folders: unknown[] = [];
86
+
87
+ for (const mod of system.modules) {
88
+ if (mod.kind !== "api_service" || mod.models.length === 0) continue;
89
+
90
+ const model = mod.models[0];
91
+ const tableName = toSnakeCase(model.name);
92
+ const baseUrl = `{{baseUrl}}/${tableName}s`;
93
+ const sampleBody = buildSampleBody(model);
94
+
95
+ const items: unknown[] = [
96
+ makeRequest(`List ${model.name}s`, "GET", baseUrl),
97
+ makeRequest(`Create ${model.name}`, "POST", baseUrl, sampleBody),
98
+ makeRequest(`Get ${model.name}`, "GET", `${baseUrl}/:id`),
99
+ makeRequest(`Update ${model.name}`, "PUT", `${baseUrl}/:id`, sampleBody),
100
+ makeRequest(`Delete ${model.name}`, "DELETE", `${baseUrl}/:id`),
101
+ ];
102
+
103
+ const crudNames = new Set(["create", "read", "update", "delete", "list"]);
104
+ const allMethods: IR.IRMethod[] = mod.interfaces.flatMap((i) => i.methods);
105
+ const capabilityMethods = allMethods.filter(
106
+ (m) => !crudNames.has(m.name.toLowerCase())
107
+ );
108
+
109
+ for (const method of capabilityMethods) {
110
+ const dashName = toDashCase(method.name);
111
+ items.push(
112
+ makeRequest(
113
+ method.name + " " + model.name,
114
+ "POST",
115
+ `${baseUrl}/${dashName}`,
116
+ sampleBody
117
+ )
118
+ );
119
+ }
120
+
121
+ folders.push({
122
+ name: mod.name,
123
+ item: items,
124
+ });
125
+ }
126
+
127
+ const collection = {
128
+ info: {
129
+ name: system.name,
130
+ schema:
131
+ "https://schema.getpostman.com/json/collection/v2.1.0/collection.json",
132
+ },
133
+ auth: {
134
+ type: "bearer",
135
+ bearer: [{ key: "token", value: "{{token}}", type: "string" }],
136
+ },
137
+ variable: [
138
+ { key: "baseUrl", value: "http://localhost:3000" },
139
+ { key: "token", value: "" },
140
+ ],
141
+ item: folders,
142
+ };
143
+
144
+ return JSON.stringify(collection, null, 2);
145
+ }
@@ -0,0 +1,195 @@
1
+ /**
2
+ * BoneScript TypeScript SDK Emitter
3
+ * Generates a typed fetch client SDK from an IRSystem.
4
+ */
5
+
6
+ import * as IR from "./ir";
7
+
8
+ // ─── Helpers ──────────────────────────────────────────────────────────────────
9
+
10
+ function toSnakeCase(s: string): string {
11
+ return s.replace(/([a-z])([A-Z])/g, "$1_$2").toLowerCase();
12
+ }
13
+
14
+ function toCamelCase(s: string): string {
15
+ return s.replace(/_([a-z])/g, (_: string, c: string) => c.toUpperCase());
16
+ }
17
+
18
+ function toPascalCase(s: string): string {
19
+ const c = toCamelCase(s);
20
+ return c.charAt(0).toUpperCase() + c.slice(1);
21
+ }
22
+
23
+ function irTypeToTs(irType: string): string {
24
+ if (irType === "string") return "string";
25
+ if (irType === "uint" || irType === "int") return "number";
26
+ if (irType === "float") return "number";
27
+ if (irType === "bool") return "boolean";
28
+ if (irType === "timestamp") return "string";
29
+ if (irType === "uuid") return "string";
30
+ if (irType === "bytes") return "string";
31
+ if (irType === "json") return "unknown";
32
+ const listMatch = irType.match(/^list<(.+)>$/);
33
+ if (listMatch) return irTypeToTs(listMatch[1]) + "[]";
34
+ const setMatch = irType.match(/^set<(.+)>$/);
35
+ if (setMatch) return irTypeToTs(setMatch[1]) + "[]";
36
+ const optMatch = irType.match(/^optional<(.+)>$/);
37
+ if (optMatch) return irTypeToTs(optMatch[1]) + " | null";
38
+ return "unknown";
39
+ }
40
+
41
+ // ─── SDK generator ────────────────────────────────────────────────────────────
42
+
43
+ export function emitTypescriptSdk(system: IR.IRSystem): string {
44
+ const lines: string[] = [];
45
+ const systemPascal = toPascalCase(system.name.replace(/[^a-zA-Z0-9_]/g, "_"));
46
+
47
+ lines.push("// Generated by BoneScript compiler. Import this file into your frontend.");
48
+ lines.push(`// System: ${system.name} v${system.version}`);
49
+ lines.push("");
50
+
51
+ // Collect all models for interface generation
52
+ const allModels: { mod: IR.IRModule; model: IR.IRModel }[] = [];
53
+ for (const mod of system.modules) {
54
+ if (mod.kind === "api_service" && mod.models.length > 0) {
55
+ for (const model of mod.models) {
56
+ allModels.push({ mod, model });
57
+ }
58
+ }
59
+ }
60
+
61
+ // Emit TypeScript interfaces
62
+ for (const { model } of allModels) {
63
+ const typeName = toPascalCase(model.name);
64
+ lines.push(`export interface ${typeName} {`);
65
+ for (const field of model.fields) {
66
+ const nullable = field.nullable ? " | null" : "";
67
+ lines.push(` ${field.name}: ${irTypeToTs(field.type)}${nullable};`);
68
+ }
69
+ lines.push("}");
70
+ lines.push("");
71
+ }
72
+
73
+ // Emit client class
74
+ lines.push(`export class ${systemPascal}Client {`);
75
+ lines.push(
76
+ ` constructor(private baseUrl: string, private getToken?: () => string | null) {}`
77
+ );
78
+ lines.push("");
79
+
80
+ // Private request helper
81
+ lines.push(
82
+ ` private async request<T>(method: string, path: string, body?: unknown): Promise<T> {`
83
+ );
84
+ lines.push(` const headers: Record<string, string> = {`);
85
+ lines.push(` "Content-Type": "application/json",`);
86
+ lines.push(` };`);
87
+ lines.push(` if (this.getToken) {`);
88
+ lines.push(` const token = this.getToken();`);
89
+ lines.push(` if (token) headers["Authorization"] = "Bearer " + token;`);
90
+ lines.push(` }`);
91
+ lines.push(` const res = await fetch(this.baseUrl + path, {`);
92
+ lines.push(` method,`);
93
+ lines.push(` headers,`);
94
+ lines.push(` body: body !== undefined ? JSON.stringify(body) : undefined,`);
95
+ lines.push(` });`);
96
+ lines.push(` if (!res.ok) {`);
97
+ lines.push(
98
+ ` const text = await res.text().catch(() => res.statusText);`
99
+ );
100
+ lines.push(` throw new Error(\`HTTP \${res.status}: \${text}\`);`);
101
+ lines.push(` }`);
102
+ lines.push(` if (res.status === 204) return undefined as unknown as T;`);
103
+ lines.push(` return res.json() as Promise<T>;`);
104
+ lines.push(` }`);
105
+ lines.push("");
106
+
107
+ // Per-entity methods
108
+ for (const { mod, model } of allModels) {
109
+ const entity = toPascalCase(model.name);
110
+ const tableName = toSnakeCase(model.name);
111
+ const basePath = `/${tableName}s`;
112
+
113
+ lines.push(` // ─── ${entity} ───`);
114
+ lines.push("");
115
+
116
+ lines.push(
117
+ ` list${entity}(page = 1, pageSize = 50): Promise<{ items: ${entity}[]; total: number; page: number; page_size: number }> {`
118
+ );
119
+ lines.push(
120
+ ` return this.request("GET", \`${basePath}?page=\${page}&page_size=\${pageSize}\`);`
121
+ );
122
+ lines.push(` }`);
123
+ lines.push("");
124
+
125
+ lines.push(` get${entity}(id: string): Promise<${entity}> {`);
126
+ lines.push(` return this.request("GET", \`${basePath}/\${id}\`);`);
127
+ lines.push(` }`);
128
+ lines.push("");
129
+
130
+ lines.push(
131
+ ` create${entity}(data: Partial<${entity}>): Promise<${entity}> {`
132
+ );
133
+ lines.push(` return this.request("POST", "${basePath}", data);`);
134
+ lines.push(` }`);
135
+ lines.push("");
136
+
137
+ lines.push(
138
+ ` update${entity}(id: string, data: Partial<${entity}>): Promise<${entity}> {`
139
+ );
140
+ lines.push(` return this.request("PUT", \`${basePath}/\${id}\`, data);`);
141
+ lines.push(` }`);
142
+ lines.push("");
143
+
144
+ lines.push(` delete${entity}(id: string): Promise<void> {`);
145
+ lines.push(` return this.request("DELETE", \`${basePath}/\${id}\`);`);
146
+ lines.push(` }`);
147
+ lines.push("");
148
+
149
+ // Capability methods
150
+ const crudNames = new Set(["create", "read", "update", "delete", "list"]);
151
+ const allMethods: IR.IRMethod[] = mod.interfaces.flatMap((i) => i.methods);
152
+ const capabilityMethods = allMethods.filter(
153
+ (m) => !crudNames.has(m.name.toLowerCase())
154
+ );
155
+
156
+ for (const method of capabilityMethods) {
157
+ const methodCamel = toCamelCase(method.name);
158
+ const dashName = toSnakeCase(method.name).replace(/_/g, "-");
159
+ lines.push(
160
+ ` ${methodCamel}(data: Record<string, unknown>): Promise<{ ok: boolean; action: string }> {`
161
+ );
162
+ lines.push(
163
+ ` return this.request("POST", "${basePath}/${dashName}", data);`
164
+ );
165
+ lines.push(` }`);
166
+ lines.push("");
167
+ }
168
+ }
169
+
170
+ lines.push("}");
171
+ lines.push("");
172
+
173
+ return lines.join("\n");
174
+ }
175
+
176
+ export function emitSdkPackageJson(system: IR.IRSystem): string {
177
+ const pkgName = toSnakeCase(system.name).replace(/_/g, "-") + "-sdk";
178
+ return JSON.stringify(
179
+ {
180
+ name: pkgName,
181
+ version: system.version,
182
+ description: `TypeScript SDK for ${system.name} — generated by BoneScript compiler`,
183
+ main: "client.js",
184
+ types: "client.d.ts",
185
+ scripts: {
186
+ build: "tsc",
187
+ },
188
+ devDependencies: {
189
+ typescript: "5.3.3",
190
+ },
191
+ },
192
+ null,
193
+ 2
194
+ );
195
+ }