@famgia/omnify-gui 1.0.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/README.md +146 -0
- package/dist/client/assets/index-CSrgTwea.js +462 -0
- package/dist/client/assets/index-CSrgTwea.js.map +1 -0
- package/dist/client/assets/index-vy33xj5r.css +1 -0
- package/dist/client/index.html +14 -0
- package/dist/client/logo.svg +10 -0
- package/dist/server/index.d.ts +2 -0
- package/dist/server/index.js +1087 -0
- package/dist/server/index.js.map +1 -0
- package/package.json +88 -0
|
@@ -0,0 +1,1087 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/server/index.ts
|
|
4
|
+
import { createServer } from "http";
|
|
5
|
+
import { join as join3 } from "path";
|
|
6
|
+
import open from "open";
|
|
7
|
+
|
|
8
|
+
// src/server/app.ts
|
|
9
|
+
import express from "express";
|
|
10
|
+
import { fileURLToPath } from "url";
|
|
11
|
+
import { dirname, join as join2 } from "path";
|
|
12
|
+
|
|
13
|
+
// src/server/api/schemas.ts
|
|
14
|
+
import { Router } from "express";
|
|
15
|
+
|
|
16
|
+
// src/server/services/schemaService.ts
|
|
17
|
+
import { loadSchemas } from "@famgia/omnify-core";
|
|
18
|
+
import { writeFile, unlink } from "fs/promises";
|
|
19
|
+
import { join } from "path";
|
|
20
|
+
import { stringify } from "yaml";
|
|
21
|
+
function normalizeEnumValues(values) {
|
|
22
|
+
if (!values || !Array.isArray(values)) return void 0;
|
|
23
|
+
return values.map((v) => {
|
|
24
|
+
if (typeof v === "object" && v !== null && "value" in v) {
|
|
25
|
+
const obj = v;
|
|
26
|
+
return {
|
|
27
|
+
value: String(obj.value),
|
|
28
|
+
label: obj.label ? String(obj.label) : void 0,
|
|
29
|
+
extra: obj.extra
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
return { value: String(v) };
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
var SchemaService = class {
|
|
36
|
+
cache = /* @__PURE__ */ new Map();
|
|
37
|
+
async loadAll(schemasDir2) {
|
|
38
|
+
try {
|
|
39
|
+
const schemas = await loadSchemas(schemasDir2);
|
|
40
|
+
const guiSchemas = {};
|
|
41
|
+
for (const [name, schema] of Object.entries(schemas)) {
|
|
42
|
+
guiSchemas[name] = {
|
|
43
|
+
name: schema.name,
|
|
44
|
+
kind: schema.kind ?? "object",
|
|
45
|
+
displayName: schema.displayName,
|
|
46
|
+
filePath: schema.filePath,
|
|
47
|
+
relativePath: schema.relativePath,
|
|
48
|
+
properties: schema.properties,
|
|
49
|
+
options: schema.options,
|
|
50
|
+
values: normalizeEnumValues(schema.values),
|
|
51
|
+
isDirty: false,
|
|
52
|
+
validationErrors: []
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
this.cache.set(schemasDir2, guiSchemas);
|
|
56
|
+
return guiSchemas;
|
|
57
|
+
} catch (error) {
|
|
58
|
+
if (error.code === "ENOENT") {
|
|
59
|
+
return {};
|
|
60
|
+
}
|
|
61
|
+
throw error;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
async load(schemasDir2, name) {
|
|
65
|
+
const schemas = await this.loadAll(schemasDir2);
|
|
66
|
+
return schemas[name] ?? null;
|
|
67
|
+
}
|
|
68
|
+
async save(schemasDir2, schema) {
|
|
69
|
+
const { isDirty: _isDirty, validationErrors: _validationErrors, filePath: _filePath, ...schemaData } = schema;
|
|
70
|
+
const fileName = `${schema.name}.yaml`;
|
|
71
|
+
const targetPath = join(schemasDir2, fileName);
|
|
72
|
+
const yamlContent = stringify(schemaData, {
|
|
73
|
+
lineWidth: 120,
|
|
74
|
+
defaultKeyType: "PLAIN",
|
|
75
|
+
defaultStringType: "PLAIN"
|
|
76
|
+
});
|
|
77
|
+
await writeFile(targetPath, yamlContent, "utf-8");
|
|
78
|
+
return {
|
|
79
|
+
...schemaData,
|
|
80
|
+
filePath: targetPath,
|
|
81
|
+
isDirty: false,
|
|
82
|
+
validationErrors: []
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
async delete(schemasDir2, name) {
|
|
86
|
+
const fileName = `${name}.yaml`;
|
|
87
|
+
const targetPath = join(schemasDir2, fileName);
|
|
88
|
+
await unlink(targetPath);
|
|
89
|
+
this.cache.delete(schemasDir2);
|
|
90
|
+
}
|
|
91
|
+
clearCache(schemasDir2) {
|
|
92
|
+
if (schemasDir2) {
|
|
93
|
+
this.cache.delete(schemasDir2);
|
|
94
|
+
} else {
|
|
95
|
+
this.cache.clear();
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
var schemaService = new SchemaService();
|
|
100
|
+
|
|
101
|
+
// src/server/api/schemas.ts
|
|
102
|
+
var schemasRouter = Router();
|
|
103
|
+
schemasRouter.get("/", async (req, res) => {
|
|
104
|
+
try {
|
|
105
|
+
const config = req.app.locals.config;
|
|
106
|
+
const schemas = await schemaService.loadAll(config.schemasDir);
|
|
107
|
+
const response = {
|
|
108
|
+
success: true,
|
|
109
|
+
data: schemas
|
|
110
|
+
};
|
|
111
|
+
res.json(response);
|
|
112
|
+
} catch (error) {
|
|
113
|
+
const response = {
|
|
114
|
+
success: false,
|
|
115
|
+
error: {
|
|
116
|
+
code: "LOAD_ERROR",
|
|
117
|
+
message: error.message
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
res.status(500).json(response);
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
schemasRouter.get("/:name", async (req, res) => {
|
|
124
|
+
try {
|
|
125
|
+
const config = req.app.locals.config;
|
|
126
|
+
const { name } = req.params;
|
|
127
|
+
const schema = await schemaService.load(config.schemasDir, name);
|
|
128
|
+
if (!schema) {
|
|
129
|
+
const response2 = {
|
|
130
|
+
success: false,
|
|
131
|
+
error: {
|
|
132
|
+
code: "NOT_FOUND",
|
|
133
|
+
message: `Schema "${name}" not found`
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
res.status(404).json(response2);
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
const response = {
|
|
140
|
+
success: true,
|
|
141
|
+
data: schema
|
|
142
|
+
};
|
|
143
|
+
res.json(response);
|
|
144
|
+
} catch (error) {
|
|
145
|
+
const response = {
|
|
146
|
+
success: false,
|
|
147
|
+
error: {
|
|
148
|
+
code: "LOAD_ERROR",
|
|
149
|
+
message: error.message
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
res.status(500).json(response);
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
schemasRouter.post("/", async (req, res) => {
|
|
156
|
+
try {
|
|
157
|
+
const config = req.app.locals.config;
|
|
158
|
+
const schema = req.body;
|
|
159
|
+
if (!schema.name) {
|
|
160
|
+
const response2 = {
|
|
161
|
+
success: false,
|
|
162
|
+
error: {
|
|
163
|
+
code: "VALIDATION_ERROR",
|
|
164
|
+
message: "Schema name is required"
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
res.status(400).json(response2);
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
const saved = await schemaService.save(config.schemasDir, schema);
|
|
171
|
+
const response = {
|
|
172
|
+
success: true,
|
|
173
|
+
data: saved
|
|
174
|
+
};
|
|
175
|
+
res.status(201).json(response);
|
|
176
|
+
} catch (error) {
|
|
177
|
+
const response = {
|
|
178
|
+
success: false,
|
|
179
|
+
error: {
|
|
180
|
+
code: "SAVE_ERROR",
|
|
181
|
+
message: error.message
|
|
182
|
+
}
|
|
183
|
+
};
|
|
184
|
+
res.status(500).json(response);
|
|
185
|
+
}
|
|
186
|
+
});
|
|
187
|
+
schemasRouter.put("/:name", async (req, res) => {
|
|
188
|
+
try {
|
|
189
|
+
const config = req.app.locals.config;
|
|
190
|
+
const { name } = req.params;
|
|
191
|
+
const schema = req.body;
|
|
192
|
+
schema.name = name;
|
|
193
|
+
const saved = await schemaService.save(config.schemasDir, schema);
|
|
194
|
+
const response = {
|
|
195
|
+
success: true,
|
|
196
|
+
data: saved
|
|
197
|
+
};
|
|
198
|
+
res.json(response);
|
|
199
|
+
} catch (error) {
|
|
200
|
+
const response = {
|
|
201
|
+
success: false,
|
|
202
|
+
error: {
|
|
203
|
+
code: "SAVE_ERROR",
|
|
204
|
+
message: error.message
|
|
205
|
+
}
|
|
206
|
+
};
|
|
207
|
+
res.status(500).json(response);
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
schemasRouter.delete("/:name", async (req, res) => {
|
|
211
|
+
try {
|
|
212
|
+
const config = req.app.locals.config;
|
|
213
|
+
const { name } = req.params;
|
|
214
|
+
await schemaService.delete(config.schemasDir, name);
|
|
215
|
+
const response = {
|
|
216
|
+
success: true
|
|
217
|
+
};
|
|
218
|
+
res.json(response);
|
|
219
|
+
} catch (error) {
|
|
220
|
+
const response = {
|
|
221
|
+
success: false,
|
|
222
|
+
error: {
|
|
223
|
+
code: "DELETE_ERROR",
|
|
224
|
+
message: error.message
|
|
225
|
+
}
|
|
226
|
+
};
|
|
227
|
+
res.status(500).json(response);
|
|
228
|
+
}
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
// src/server/api/validate.ts
|
|
232
|
+
import { Router as Router2 } from "express";
|
|
233
|
+
|
|
234
|
+
// src/server/services/validationService.ts
|
|
235
|
+
import { loadSchemas as loadSchemas2, validateSchemas } from "@famgia/omnify-core";
|
|
236
|
+
function toLoadedSchema(schema) {
|
|
237
|
+
const result = {
|
|
238
|
+
name: schema.name,
|
|
239
|
+
kind: schema.kind,
|
|
240
|
+
filePath: schema.filePath,
|
|
241
|
+
relativePath: schema.relativePath ?? schema.name + ".yaml"
|
|
242
|
+
};
|
|
243
|
+
if (schema.displayName !== void 0) {
|
|
244
|
+
result.displayName = schema.displayName;
|
|
245
|
+
}
|
|
246
|
+
if (schema.properties !== void 0) {
|
|
247
|
+
result.properties = schema.properties;
|
|
248
|
+
}
|
|
249
|
+
if (schema.options !== void 0) {
|
|
250
|
+
result.options = schema.options;
|
|
251
|
+
}
|
|
252
|
+
if (schema.values !== void 0) {
|
|
253
|
+
result.values = schema.values;
|
|
254
|
+
}
|
|
255
|
+
return result;
|
|
256
|
+
}
|
|
257
|
+
var ValidationService = class {
|
|
258
|
+
async validateSchema(schema, schemasDir2) {
|
|
259
|
+
try {
|
|
260
|
+
const allSchemas = await loadSchemas2(schemasDir2);
|
|
261
|
+
const loadedSchema = toLoadedSchema(schema);
|
|
262
|
+
const schemasToValidate = { ...allSchemas };
|
|
263
|
+
schemasToValidate[schema.name] = loadedSchema;
|
|
264
|
+
const result = validateSchemas(schemasToValidate);
|
|
265
|
+
const schemaResult = result.schemas.find((s) => s.schemaName === schema.name);
|
|
266
|
+
const schemaErrors = [];
|
|
267
|
+
if (schemaResult) {
|
|
268
|
+
for (const e of schemaResult.errors) {
|
|
269
|
+
schemaErrors.push({
|
|
270
|
+
path: this.getErrorPath(e, schema.name),
|
|
271
|
+
message: e.message,
|
|
272
|
+
severity: "error"
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
return {
|
|
277
|
+
valid: schemaErrors.length === 0,
|
|
278
|
+
errors: schemaErrors
|
|
279
|
+
};
|
|
280
|
+
} catch (error) {
|
|
281
|
+
return {
|
|
282
|
+
valid: false,
|
|
283
|
+
errors: [
|
|
284
|
+
{
|
|
285
|
+
path: schema.name,
|
|
286
|
+
message: error.message,
|
|
287
|
+
severity: "error"
|
|
288
|
+
}
|
|
289
|
+
]
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
async validateAll(schemas) {
|
|
294
|
+
try {
|
|
295
|
+
const loadedSchemas = {};
|
|
296
|
+
for (const [name, schema] of Object.entries(schemas)) {
|
|
297
|
+
loadedSchemas[name] = toLoadedSchema(schema);
|
|
298
|
+
}
|
|
299
|
+
const result = validateSchemas(loadedSchemas);
|
|
300
|
+
const errors = [];
|
|
301
|
+
for (const schemaResult of result.schemas) {
|
|
302
|
+
for (const e of schemaResult.errors) {
|
|
303
|
+
errors.push({
|
|
304
|
+
path: this.getErrorPath(e, schemaResult.schemaName),
|
|
305
|
+
message: e.message,
|
|
306
|
+
severity: "error"
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
return {
|
|
311
|
+
valid: result.valid,
|
|
312
|
+
errors
|
|
313
|
+
};
|
|
314
|
+
} catch (error) {
|
|
315
|
+
return {
|
|
316
|
+
valid: false,
|
|
317
|
+
errors: [
|
|
318
|
+
{
|
|
319
|
+
path: "root",
|
|
320
|
+
message: error.message,
|
|
321
|
+
severity: "error"
|
|
322
|
+
}
|
|
323
|
+
]
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
async validateFromDisk(schemasDir2) {
|
|
328
|
+
try {
|
|
329
|
+
const schemas = await loadSchemas2(schemasDir2);
|
|
330
|
+
const guiSchemas = {};
|
|
331
|
+
for (const [name, schema] of Object.entries(schemas)) {
|
|
332
|
+
guiSchemas[name] = {
|
|
333
|
+
name: schema.name,
|
|
334
|
+
kind: schema.kind ?? "object",
|
|
335
|
+
displayName: schema.displayName,
|
|
336
|
+
filePath: schema.filePath,
|
|
337
|
+
relativePath: schema.relativePath,
|
|
338
|
+
properties: schema.properties,
|
|
339
|
+
options: schema.options,
|
|
340
|
+
values: schema.values
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
return this.validateAll(guiSchemas);
|
|
344
|
+
} catch (error) {
|
|
345
|
+
return {
|
|
346
|
+
valid: false,
|
|
347
|
+
errors: [
|
|
348
|
+
{
|
|
349
|
+
path: "root",
|
|
350
|
+
message: error.message,
|
|
351
|
+
severity: "error"
|
|
352
|
+
}
|
|
353
|
+
]
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
getErrorPath(error, schemaName) {
|
|
358
|
+
const details = error.details;
|
|
359
|
+
if (details && "propertyName" in details && details.propertyName) {
|
|
360
|
+
return `${schemaName}.${String(details.propertyName)}`;
|
|
361
|
+
}
|
|
362
|
+
return schemaName;
|
|
363
|
+
}
|
|
364
|
+
};
|
|
365
|
+
var validationService = new ValidationService();
|
|
366
|
+
|
|
367
|
+
// src/server/api/validate.ts
|
|
368
|
+
var validateRouter = Router2();
|
|
369
|
+
validateRouter.post("/", async (req, res) => {
|
|
370
|
+
try {
|
|
371
|
+
const config = req.app.locals.config;
|
|
372
|
+
const body = req.body;
|
|
373
|
+
let result;
|
|
374
|
+
if (body.schema) {
|
|
375
|
+
result = await validationService.validateSchema(body.schema, config.schemasDir);
|
|
376
|
+
} else if (body.schemas) {
|
|
377
|
+
result = await validationService.validateAll(body.schemas);
|
|
378
|
+
} else {
|
|
379
|
+
result = await validationService.validateFromDisk(config.schemasDir);
|
|
380
|
+
}
|
|
381
|
+
const response = {
|
|
382
|
+
success: true,
|
|
383
|
+
data: result
|
|
384
|
+
};
|
|
385
|
+
res.json(response);
|
|
386
|
+
} catch (error) {
|
|
387
|
+
const response = {
|
|
388
|
+
success: false,
|
|
389
|
+
error: {
|
|
390
|
+
code: "VALIDATION_ERROR",
|
|
391
|
+
message: error.message
|
|
392
|
+
}
|
|
393
|
+
};
|
|
394
|
+
res.status(500).json(response);
|
|
395
|
+
}
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
// src/server/api/preview.ts
|
|
399
|
+
import { Router as Router3 } from "express";
|
|
400
|
+
|
|
401
|
+
// src/server/services/previewService.ts
|
|
402
|
+
import { loadSchemas as loadSchemas3 } from "@famgia/omnify-core";
|
|
403
|
+
import { generateMigrations as generateLaravelMigrations } from "@famgia/omnify-laravel";
|
|
404
|
+
import { generateMigrations as generateSqlMigrations } from "@famgia/omnify-sql";
|
|
405
|
+
var PreviewService = class {
|
|
406
|
+
async generateAll(schemasDir2, type) {
|
|
407
|
+
const schemas = await loadSchemas3(schemasDir2);
|
|
408
|
+
const previews = [];
|
|
409
|
+
switch (type) {
|
|
410
|
+
case "laravel": {
|
|
411
|
+
const migrations = await generateLaravelMigrations(schemas);
|
|
412
|
+
for (const migration of migrations) {
|
|
413
|
+
previews.push({
|
|
414
|
+
type: "laravel",
|
|
415
|
+
content: migration.content,
|
|
416
|
+
fileName: migration.fileName
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
break;
|
|
420
|
+
}
|
|
421
|
+
case "sql": {
|
|
422
|
+
const migrations = generateSqlMigrations(schemas, { dialect: "mysql" });
|
|
423
|
+
for (const migration of migrations) {
|
|
424
|
+
previews.push({
|
|
425
|
+
type: "sql",
|
|
426
|
+
content: migration.content,
|
|
427
|
+
fileName: migration.fileName
|
|
428
|
+
});
|
|
429
|
+
}
|
|
430
|
+
break;
|
|
431
|
+
}
|
|
432
|
+
case "typescript": {
|
|
433
|
+
for (const [name, schema] of Object.entries(schemas)) {
|
|
434
|
+
if (schema.kind === "enum") {
|
|
435
|
+
previews.push({
|
|
436
|
+
type: "typescript",
|
|
437
|
+
content: this.generateEnumType(name, schema),
|
|
438
|
+
fileName: `${name}.ts`
|
|
439
|
+
});
|
|
440
|
+
} else {
|
|
441
|
+
previews.push({
|
|
442
|
+
type: "typescript",
|
|
443
|
+
content: this.generateInterfaceType(name, schema),
|
|
444
|
+
fileName: `${name}.ts`
|
|
445
|
+
});
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
break;
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
return previews;
|
|
452
|
+
}
|
|
453
|
+
async generateForSchema(schemasDir2, schemaName, type) {
|
|
454
|
+
const schemas = await loadSchemas3(schemasDir2);
|
|
455
|
+
const schema = schemas[schemaName];
|
|
456
|
+
if (!schema) {
|
|
457
|
+
return null;
|
|
458
|
+
}
|
|
459
|
+
switch (type) {
|
|
460
|
+
case "laravel": {
|
|
461
|
+
const migrations = await generateLaravelMigrations({ [schemaName]: schema });
|
|
462
|
+
const migration = migrations[0];
|
|
463
|
+
return migration ? {
|
|
464
|
+
type: "laravel",
|
|
465
|
+
content: migration.content,
|
|
466
|
+
fileName: migration.fileName
|
|
467
|
+
} : null;
|
|
468
|
+
}
|
|
469
|
+
case "sql": {
|
|
470
|
+
const migrations = generateSqlMigrations({ [schemaName]: schema }, { dialect: "mysql" });
|
|
471
|
+
const migration = migrations[0];
|
|
472
|
+
return migration ? {
|
|
473
|
+
type: "sql",
|
|
474
|
+
content: migration.content,
|
|
475
|
+
fileName: migration.fileName
|
|
476
|
+
} : null;
|
|
477
|
+
}
|
|
478
|
+
case "typescript": {
|
|
479
|
+
if (schema.kind === "enum") {
|
|
480
|
+
return {
|
|
481
|
+
type: "typescript",
|
|
482
|
+
content: this.generateEnumType(schemaName, schema),
|
|
483
|
+
fileName: `${schemaName}.ts`
|
|
484
|
+
};
|
|
485
|
+
}
|
|
486
|
+
return {
|
|
487
|
+
type: "typescript",
|
|
488
|
+
content: this.generateInterfaceType(schemaName, schema),
|
|
489
|
+
fileName: `${schemaName}.ts`
|
|
490
|
+
};
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
generateEnumType(name, schema) {
|
|
495
|
+
const values = schema.values ?? [];
|
|
496
|
+
return `export type ${name} = ${values.map((v) => `'${v}'`).join(" | ") || "never"};
|
|
497
|
+
`;
|
|
498
|
+
}
|
|
499
|
+
generateInterfaceType(name, schema) {
|
|
500
|
+
const lines = [`export interface ${name} {`];
|
|
501
|
+
if (schema.properties) {
|
|
502
|
+
for (const [propName, prop] of Object.entries(schema.properties)) {
|
|
503
|
+
const tsType = this.mapToTsType(prop.type);
|
|
504
|
+
const optional = prop.nullable ? "?" : "";
|
|
505
|
+
lines.push(` ${propName}${optional}: ${tsType};`);
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
lines.push("}");
|
|
509
|
+
return lines.join("\n") + "\n";
|
|
510
|
+
}
|
|
511
|
+
mapToTsType(omnifyType) {
|
|
512
|
+
const typeMap = {
|
|
513
|
+
String: "string",
|
|
514
|
+
Int: "number",
|
|
515
|
+
BigInt: "number",
|
|
516
|
+
Float: "number",
|
|
517
|
+
Decimal: "number",
|
|
518
|
+
Boolean: "boolean",
|
|
519
|
+
Text: "string",
|
|
520
|
+
LongText: "string",
|
|
521
|
+
Date: "string",
|
|
522
|
+
Time: "string",
|
|
523
|
+
Timestamp: "string",
|
|
524
|
+
Json: "Record<string, unknown>",
|
|
525
|
+
Email: "string",
|
|
526
|
+
Password: "string",
|
|
527
|
+
File: "string",
|
|
528
|
+
MultiFile: "string[]",
|
|
529
|
+
Point: "{ lat: number; lng: number }",
|
|
530
|
+
Coordinates: "{ latitude: number; longitude: number }"
|
|
531
|
+
};
|
|
532
|
+
return typeMap[omnifyType] ?? "unknown";
|
|
533
|
+
}
|
|
534
|
+
};
|
|
535
|
+
var previewService = new PreviewService();
|
|
536
|
+
|
|
537
|
+
// src/server/api/preview.ts
|
|
538
|
+
var previewRouter = Router3();
|
|
539
|
+
previewRouter.get("/:type", async (req, res) => {
|
|
540
|
+
try {
|
|
541
|
+
const config = req.app.locals.config;
|
|
542
|
+
const { type } = req.params;
|
|
543
|
+
if (!["laravel", "typescript", "sql"].includes(type)) {
|
|
544
|
+
const response2 = {
|
|
545
|
+
success: false,
|
|
546
|
+
error: {
|
|
547
|
+
code: "INVALID_TYPE",
|
|
548
|
+
message: `Invalid preview type: ${type}. Valid types: laravel, typescript, sql`
|
|
549
|
+
}
|
|
550
|
+
};
|
|
551
|
+
res.status(400).json(response2);
|
|
552
|
+
return;
|
|
553
|
+
}
|
|
554
|
+
const previews = await previewService.generateAll(config.schemasDir, type);
|
|
555
|
+
const response = {
|
|
556
|
+
success: true,
|
|
557
|
+
data: previews
|
|
558
|
+
};
|
|
559
|
+
res.json(response);
|
|
560
|
+
} catch (error) {
|
|
561
|
+
const response = {
|
|
562
|
+
success: false,
|
|
563
|
+
error: {
|
|
564
|
+
code: "PREVIEW_ERROR",
|
|
565
|
+
message: error.message
|
|
566
|
+
}
|
|
567
|
+
};
|
|
568
|
+
res.status(500).json(response);
|
|
569
|
+
}
|
|
570
|
+
});
|
|
571
|
+
previewRouter.get("/:type/:name", async (req, res) => {
|
|
572
|
+
try {
|
|
573
|
+
const config = req.app.locals.config;
|
|
574
|
+
const { type, name } = req.params;
|
|
575
|
+
if (!["laravel", "typescript", "sql"].includes(type)) {
|
|
576
|
+
const response2 = {
|
|
577
|
+
success: false,
|
|
578
|
+
error: {
|
|
579
|
+
code: "INVALID_TYPE",
|
|
580
|
+
message: `Invalid preview type: ${type}. Valid types: laravel, typescript, sql`
|
|
581
|
+
}
|
|
582
|
+
};
|
|
583
|
+
res.status(400).json(response2);
|
|
584
|
+
return;
|
|
585
|
+
}
|
|
586
|
+
const preview = await previewService.generateForSchema(
|
|
587
|
+
config.schemasDir,
|
|
588
|
+
name,
|
|
589
|
+
type
|
|
590
|
+
);
|
|
591
|
+
if (!preview) {
|
|
592
|
+
const response2 = {
|
|
593
|
+
success: false,
|
|
594
|
+
error: {
|
|
595
|
+
code: "NOT_FOUND",
|
|
596
|
+
message: `Schema "${name}" not found`
|
|
597
|
+
}
|
|
598
|
+
};
|
|
599
|
+
res.status(404).json(response2);
|
|
600
|
+
return;
|
|
601
|
+
}
|
|
602
|
+
const response = {
|
|
603
|
+
success: true,
|
|
604
|
+
data: preview
|
|
605
|
+
};
|
|
606
|
+
res.json(response);
|
|
607
|
+
} catch (error) {
|
|
608
|
+
const response = {
|
|
609
|
+
success: false,
|
|
610
|
+
error: {
|
|
611
|
+
code: "PREVIEW_ERROR",
|
|
612
|
+
message: error.message
|
|
613
|
+
}
|
|
614
|
+
};
|
|
615
|
+
res.status(500).json(response);
|
|
616
|
+
}
|
|
617
|
+
});
|
|
618
|
+
|
|
619
|
+
// src/server/api/config.ts
|
|
620
|
+
import { Router as Router4 } from "express";
|
|
621
|
+
|
|
622
|
+
// src/shared/constants.ts
|
|
623
|
+
var DEFAULT_PORT = 3456;
|
|
624
|
+
var DEFAULT_HOST = "localhost";
|
|
625
|
+
|
|
626
|
+
// src/server/api/config.ts
|
|
627
|
+
var configRouter = Router4();
|
|
628
|
+
configRouter.get("/", (req, res) => {
|
|
629
|
+
const appConfig = req.app.locals.config;
|
|
630
|
+
const config = {
|
|
631
|
+
schemasDir: appConfig.schemasDir,
|
|
632
|
+
port: Number(process.env.PORT) || DEFAULT_PORT,
|
|
633
|
+
host: process.env.HOST ?? DEFAULT_HOST
|
|
634
|
+
};
|
|
635
|
+
const response = {
|
|
636
|
+
success: true,
|
|
637
|
+
data: config
|
|
638
|
+
};
|
|
639
|
+
res.json(response);
|
|
640
|
+
});
|
|
641
|
+
|
|
642
|
+
// src/server/api/versions.ts
|
|
643
|
+
import { Router as Router5 } from "express";
|
|
644
|
+
|
|
645
|
+
// src/server/services/versionService.ts
|
|
646
|
+
import { loadSchemas as loadSchemas4 } from "@famgia/omnify-core";
|
|
647
|
+
import {
|
|
648
|
+
createVersionStore
|
|
649
|
+
} from "@famgia/omnify-core";
|
|
650
|
+
var store = null;
|
|
651
|
+
var schemasDir = null;
|
|
652
|
+
function initVersionStore(baseDir, schemasDirPath) {
|
|
653
|
+
store = createVersionStore({ baseDir, maxVersions: 100 });
|
|
654
|
+
schemasDir = schemasDirPath;
|
|
655
|
+
}
|
|
656
|
+
function getStore() {
|
|
657
|
+
if (!store) {
|
|
658
|
+
throw new Error("Version store not initialized. Call initVersionStore first.");
|
|
659
|
+
}
|
|
660
|
+
return store;
|
|
661
|
+
}
|
|
662
|
+
async function listVersions() {
|
|
663
|
+
return getStore().listVersions();
|
|
664
|
+
}
|
|
665
|
+
async function getVersion(version) {
|
|
666
|
+
return getStore().readVersion(version);
|
|
667
|
+
}
|
|
668
|
+
async function getLatestVersion() {
|
|
669
|
+
return getStore().readLatestVersion();
|
|
670
|
+
}
|
|
671
|
+
async function diffVersions(fromVersion, toVersion) {
|
|
672
|
+
return getStore().diffVersions(fromVersion, toVersion);
|
|
673
|
+
}
|
|
674
|
+
function propertyToSnapshot(prop) {
|
|
675
|
+
return {
|
|
676
|
+
type: prop.type,
|
|
677
|
+
...prop.displayName !== void 0 && { displayName: prop.displayName },
|
|
678
|
+
...prop.description !== void 0 && { description: prop.description },
|
|
679
|
+
...prop.nullable !== void 0 && { nullable: prop.nullable },
|
|
680
|
+
...prop.unique !== void 0 && { unique: prop.unique },
|
|
681
|
+
...prop.default !== void 0 && { default: prop.default },
|
|
682
|
+
...prop.length !== void 0 && { length: prop.length },
|
|
683
|
+
...prop.unsigned !== void 0 && { unsigned: prop.unsigned },
|
|
684
|
+
...prop.precision !== void 0 && { precision: prop.precision },
|
|
685
|
+
...prop.scale !== void 0 && { scale: prop.scale },
|
|
686
|
+
...prop.enum !== void 0 && { enum: prop.enum },
|
|
687
|
+
...prop.relation !== void 0 && { relation: prop.relation },
|
|
688
|
+
...prop.target !== void 0 && { target: prop.target },
|
|
689
|
+
...prop.targets !== void 0 && { targets: prop.targets },
|
|
690
|
+
...prop.morphName !== void 0 && { morphName: prop.morphName },
|
|
691
|
+
...prop.onDelete !== void 0 && { onDelete: prop.onDelete },
|
|
692
|
+
...prop.onUpdate !== void 0 && { onUpdate: prop.onUpdate },
|
|
693
|
+
...prop.mappedBy !== void 0 && { mappedBy: prop.mappedBy },
|
|
694
|
+
...prop.inversedBy !== void 0 && { inversedBy: prop.inversedBy },
|
|
695
|
+
...prop.joinTable !== void 0 && { joinTable: prop.joinTable },
|
|
696
|
+
...prop.owning !== void 0 && { owning: prop.owning }
|
|
697
|
+
};
|
|
698
|
+
}
|
|
699
|
+
function schemasToSnapshot(schemas) {
|
|
700
|
+
const snapshot = {};
|
|
701
|
+
for (const [name, schema] of Object.entries(schemas)) {
|
|
702
|
+
const properties = {};
|
|
703
|
+
if (schema.properties) {
|
|
704
|
+
for (const [propName, prop] of Object.entries(schema.properties)) {
|
|
705
|
+
properties[propName] = propertyToSnapshot(prop);
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
const opts = schema.options;
|
|
709
|
+
snapshot[name] = {
|
|
710
|
+
name: schema.name,
|
|
711
|
+
kind: schema.kind ?? "object",
|
|
712
|
+
...Object.keys(properties).length > 0 && { properties },
|
|
713
|
+
...schema.values && { values: schema.values },
|
|
714
|
+
...opts && {
|
|
715
|
+
options: {
|
|
716
|
+
...opts.id !== void 0 && { id: opts.id },
|
|
717
|
+
...opts.idType !== void 0 && { idType: opts.idType },
|
|
718
|
+
...opts.timestamps !== void 0 && { timestamps: opts.timestamps },
|
|
719
|
+
...opts.softDelete !== void 0 && { softDelete: opts.softDelete },
|
|
720
|
+
...opts.tableName !== void 0 && { tableName: opts.tableName },
|
|
721
|
+
...opts.translations !== void 0 && { translations: opts.translations },
|
|
722
|
+
...opts.authenticatable !== void 0 && { authenticatable: opts.authenticatable }
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
};
|
|
726
|
+
}
|
|
727
|
+
return snapshot;
|
|
728
|
+
}
|
|
729
|
+
async function getPendingChanges() {
|
|
730
|
+
if (!schemasDir) {
|
|
731
|
+
throw new Error("Schemas directory not initialized");
|
|
732
|
+
}
|
|
733
|
+
const storeInstance = getStore();
|
|
734
|
+
const currentSchemas = await loadSchemas4(schemasDir);
|
|
735
|
+
const currentSnapshot = schemasToSnapshot(currentSchemas);
|
|
736
|
+
const latestVersion = await storeInstance.readLatestVersion();
|
|
737
|
+
if (!latestVersion) {
|
|
738
|
+
const changes2 = Object.keys(currentSnapshot).map((name) => ({
|
|
739
|
+
action: "schema_added",
|
|
740
|
+
schema: name
|
|
741
|
+
}));
|
|
742
|
+
return {
|
|
743
|
+
hasChanges: changes2.length > 0,
|
|
744
|
+
changes: changes2,
|
|
745
|
+
currentSchemaCount: Object.keys(currentSnapshot).length,
|
|
746
|
+
previousSchemaCount: 0,
|
|
747
|
+
latestVersion: null
|
|
748
|
+
};
|
|
749
|
+
}
|
|
750
|
+
const changes = storeInstance.computeSnapshotDiff(latestVersion.snapshot, currentSnapshot);
|
|
751
|
+
return {
|
|
752
|
+
hasChanges: changes.length > 0,
|
|
753
|
+
changes,
|
|
754
|
+
currentSchemaCount: Object.keys(currentSnapshot).length,
|
|
755
|
+
previousSchemaCount: Object.keys(latestVersion.snapshot).length,
|
|
756
|
+
latestVersion: latestVersion.version
|
|
757
|
+
};
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
// src/server/api/versions.ts
|
|
761
|
+
var versionsRouter = Router5();
|
|
762
|
+
versionsRouter.get("/", async (_req, res) => {
|
|
763
|
+
try {
|
|
764
|
+
const versions = await listVersions();
|
|
765
|
+
const response = {
|
|
766
|
+
success: true,
|
|
767
|
+
data: versions
|
|
768
|
+
};
|
|
769
|
+
res.json(response);
|
|
770
|
+
} catch (error) {
|
|
771
|
+
const response = {
|
|
772
|
+
success: false,
|
|
773
|
+
error: {
|
|
774
|
+
code: "VERSION_LIST_ERROR",
|
|
775
|
+
message: error.message
|
|
776
|
+
}
|
|
777
|
+
};
|
|
778
|
+
res.status(500).json(response);
|
|
779
|
+
}
|
|
780
|
+
});
|
|
781
|
+
versionsRouter.get("/pending", async (_req, res) => {
|
|
782
|
+
try {
|
|
783
|
+
const pending = await getPendingChanges();
|
|
784
|
+
const response = {
|
|
785
|
+
success: true,
|
|
786
|
+
data: pending
|
|
787
|
+
};
|
|
788
|
+
res.json(response);
|
|
789
|
+
} catch (error) {
|
|
790
|
+
const response = {
|
|
791
|
+
success: false,
|
|
792
|
+
error: {
|
|
793
|
+
code: "PENDING_CHANGES_ERROR",
|
|
794
|
+
message: error.message
|
|
795
|
+
}
|
|
796
|
+
};
|
|
797
|
+
res.status(500).json(response);
|
|
798
|
+
}
|
|
799
|
+
});
|
|
800
|
+
versionsRouter.get("/latest", async (_req, res) => {
|
|
801
|
+
try {
|
|
802
|
+
const version = await getLatestVersion();
|
|
803
|
+
const response = {
|
|
804
|
+
success: true,
|
|
805
|
+
data: version
|
|
806
|
+
};
|
|
807
|
+
res.json(response);
|
|
808
|
+
} catch (error) {
|
|
809
|
+
const response = {
|
|
810
|
+
success: false,
|
|
811
|
+
error: {
|
|
812
|
+
code: "VERSION_READ_ERROR",
|
|
813
|
+
message: error.message
|
|
814
|
+
}
|
|
815
|
+
};
|
|
816
|
+
res.status(500).json(response);
|
|
817
|
+
}
|
|
818
|
+
});
|
|
819
|
+
versionsRouter.get("/:version", async (req, res) => {
|
|
820
|
+
try {
|
|
821
|
+
const versionNum = parseInt(req.params.version, 10);
|
|
822
|
+
if (isNaN(versionNum)) {
|
|
823
|
+
const response2 = {
|
|
824
|
+
success: false,
|
|
825
|
+
error: {
|
|
826
|
+
code: "INVALID_VERSION",
|
|
827
|
+
message: "Version must be a number"
|
|
828
|
+
}
|
|
829
|
+
};
|
|
830
|
+
res.status(400).json(response2);
|
|
831
|
+
return;
|
|
832
|
+
}
|
|
833
|
+
const version = await getVersion(versionNum);
|
|
834
|
+
if (!version) {
|
|
835
|
+
const response2 = {
|
|
836
|
+
success: false,
|
|
837
|
+
error: {
|
|
838
|
+
code: "VERSION_NOT_FOUND",
|
|
839
|
+
message: `Version ${versionNum} not found`
|
|
840
|
+
}
|
|
841
|
+
};
|
|
842
|
+
res.status(404).json(response2);
|
|
843
|
+
return;
|
|
844
|
+
}
|
|
845
|
+
const response = {
|
|
846
|
+
success: true,
|
|
847
|
+
data: version
|
|
848
|
+
};
|
|
849
|
+
res.json(response);
|
|
850
|
+
} catch (error) {
|
|
851
|
+
const response = {
|
|
852
|
+
success: false,
|
|
853
|
+
error: {
|
|
854
|
+
code: "VERSION_READ_ERROR",
|
|
855
|
+
message: error.message
|
|
856
|
+
}
|
|
857
|
+
};
|
|
858
|
+
res.status(500).json(response);
|
|
859
|
+
}
|
|
860
|
+
});
|
|
861
|
+
versionsRouter.get("/diff/:from/:to", async (req, res) => {
|
|
862
|
+
try {
|
|
863
|
+
const fromVersion = parseInt(req.params.from, 10);
|
|
864
|
+
const toVersion = parseInt(req.params.to, 10);
|
|
865
|
+
if (isNaN(fromVersion) || isNaN(toVersion)) {
|
|
866
|
+
const response2 = {
|
|
867
|
+
success: false,
|
|
868
|
+
error: {
|
|
869
|
+
code: "INVALID_VERSION",
|
|
870
|
+
message: "Version numbers must be integers"
|
|
871
|
+
}
|
|
872
|
+
};
|
|
873
|
+
res.status(400).json(response2);
|
|
874
|
+
return;
|
|
875
|
+
}
|
|
876
|
+
const diff = await diffVersions(fromVersion, toVersion);
|
|
877
|
+
if (!diff) {
|
|
878
|
+
const response2 = {
|
|
879
|
+
success: false,
|
|
880
|
+
error: {
|
|
881
|
+
code: "DIFF_ERROR",
|
|
882
|
+
message: "Could not compute diff. One or both versions may not exist."
|
|
883
|
+
}
|
|
884
|
+
};
|
|
885
|
+
res.status(404).json(response2);
|
|
886
|
+
return;
|
|
887
|
+
}
|
|
888
|
+
const response = {
|
|
889
|
+
success: true,
|
|
890
|
+
data: diff
|
|
891
|
+
};
|
|
892
|
+
res.json(response);
|
|
893
|
+
} catch (error) {
|
|
894
|
+
const response = {
|
|
895
|
+
success: false,
|
|
896
|
+
error: {
|
|
897
|
+
code: "DIFF_ERROR",
|
|
898
|
+
message: error.message
|
|
899
|
+
}
|
|
900
|
+
};
|
|
901
|
+
res.status(500).json(response);
|
|
902
|
+
}
|
|
903
|
+
});
|
|
904
|
+
|
|
905
|
+
// src/server/app.ts
|
|
906
|
+
var __filename = fileURLToPath(import.meta.url);
|
|
907
|
+
var __dirname = dirname(__filename);
|
|
908
|
+
function createApp(config) {
|
|
909
|
+
const app = express();
|
|
910
|
+
app.locals.config = config;
|
|
911
|
+
initVersionStore(config.cwd, config.schemasDir);
|
|
912
|
+
app.use(express.json());
|
|
913
|
+
app.use("/api/schemas", schemasRouter);
|
|
914
|
+
app.use("/api/validate", validateRouter);
|
|
915
|
+
app.use("/api/preview", previewRouter);
|
|
916
|
+
app.use("/api/config", configRouter);
|
|
917
|
+
app.use("/api/versions", versionsRouter);
|
|
918
|
+
if (process.env.NODE_ENV === "production") {
|
|
919
|
+
const clientDist = join2(__dirname, "../client");
|
|
920
|
+
app.use(express.static(clientDist));
|
|
921
|
+
app.get("*", (_req, res) => {
|
|
922
|
+
res.sendFile(join2(clientDist, "index.html"));
|
|
923
|
+
});
|
|
924
|
+
}
|
|
925
|
+
app.use((err, _req, res, _next) => {
|
|
926
|
+
console.error("Server error:", err);
|
|
927
|
+
const response = {
|
|
928
|
+
success: false,
|
|
929
|
+
error: {
|
|
930
|
+
code: "INTERNAL_ERROR",
|
|
931
|
+
message: err.message
|
|
932
|
+
}
|
|
933
|
+
};
|
|
934
|
+
res.status(500).json(response);
|
|
935
|
+
});
|
|
936
|
+
return app;
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
// src/server/ws/handler.ts
|
|
940
|
+
import { WebSocketServer, WebSocket } from "ws";
|
|
941
|
+
function createWsHandler(server) {
|
|
942
|
+
const wss = new WebSocketServer({ server, path: "/ws" });
|
|
943
|
+
const clients = /* @__PURE__ */ new Set();
|
|
944
|
+
wss.on("connection", (ws) => {
|
|
945
|
+
clients.add(ws);
|
|
946
|
+
console.log(" WebSocket client connected");
|
|
947
|
+
const readyEvent = {
|
|
948
|
+
type: "connection:ready",
|
|
949
|
+
payload: {
|
|
950
|
+
schemasDir: process.env.SCHEMAS_DIR ?? "schemas",
|
|
951
|
+
schemaCount: 0
|
|
952
|
+
}
|
|
953
|
+
};
|
|
954
|
+
ws.send(JSON.stringify(readyEvent));
|
|
955
|
+
ws.on("message", (data) => {
|
|
956
|
+
try {
|
|
957
|
+
const event = JSON.parse(data.toString());
|
|
958
|
+
handleClientEvent(event, ws);
|
|
959
|
+
} catch {
|
|
960
|
+
console.error("Invalid WebSocket message");
|
|
961
|
+
}
|
|
962
|
+
});
|
|
963
|
+
ws.on("close", () => {
|
|
964
|
+
clients.delete(ws);
|
|
965
|
+
console.log(" WebSocket client disconnected");
|
|
966
|
+
});
|
|
967
|
+
ws.on("error", (error) => {
|
|
968
|
+
console.error("WebSocket error:", error);
|
|
969
|
+
clients.delete(ws);
|
|
970
|
+
});
|
|
971
|
+
});
|
|
972
|
+
function handleClientEvent(_event, _ws) {
|
|
973
|
+
}
|
|
974
|
+
function broadcast(event) {
|
|
975
|
+
const message = JSON.stringify(event);
|
|
976
|
+
for (const client of clients) {
|
|
977
|
+
if (client.readyState === WebSocket.OPEN) {
|
|
978
|
+
client.send(message);
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
}
|
|
982
|
+
function close() {
|
|
983
|
+
for (const client of clients) {
|
|
984
|
+
client.close();
|
|
985
|
+
}
|
|
986
|
+
wss.close();
|
|
987
|
+
}
|
|
988
|
+
return { broadcast, close };
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
// src/server/watcher/fileWatcher.ts
|
|
992
|
+
import chokidar from "chokidar";
|
|
993
|
+
import { basename } from "path";
|
|
994
|
+
function createFileWatcher(schemasDir2, wsHandler) {
|
|
995
|
+
const watcher = chokidar.watch(`${schemasDir2}/*.yaml`, {
|
|
996
|
+
persistent: true,
|
|
997
|
+
ignoreInitial: true,
|
|
998
|
+
awaitWriteFinish: {
|
|
999
|
+
stabilityThreshold: 200,
|
|
1000
|
+
pollInterval: 100
|
|
1001
|
+
}
|
|
1002
|
+
});
|
|
1003
|
+
watcher.on("add", (filePath) => {
|
|
1004
|
+
console.log(` Schema added: ${basename(filePath)}`);
|
|
1005
|
+
void notifySchemaChange(filePath, "file");
|
|
1006
|
+
});
|
|
1007
|
+
watcher.on("change", (filePath) => {
|
|
1008
|
+
console.log(` Schema changed: ${basename(filePath)}`);
|
|
1009
|
+
void notifySchemaChange(filePath, "file");
|
|
1010
|
+
});
|
|
1011
|
+
watcher.on("unlink", (filePath) => {
|
|
1012
|
+
console.log(` Schema deleted: ${basename(filePath)}`);
|
|
1013
|
+
void notifyReload();
|
|
1014
|
+
});
|
|
1015
|
+
async function notifySchemaChange(filePath, source) {
|
|
1016
|
+
try {
|
|
1017
|
+
schemaService.clearCache(schemasDir2);
|
|
1018
|
+
const schemas = await schemaService.loadAll(schemasDir2);
|
|
1019
|
+
const name = basename(filePath, ".yaml");
|
|
1020
|
+
const schema = schemas[name];
|
|
1021
|
+
if (schema) {
|
|
1022
|
+
const event = {
|
|
1023
|
+
type: "schema:changed",
|
|
1024
|
+
payload: { name, schema, source }
|
|
1025
|
+
};
|
|
1026
|
+
wsHandler.broadcast(event);
|
|
1027
|
+
}
|
|
1028
|
+
} catch (error) {
|
|
1029
|
+
console.error("Error notifying schema change:", error);
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
async function notifyReload() {
|
|
1033
|
+
try {
|
|
1034
|
+
schemaService.clearCache(schemasDir2);
|
|
1035
|
+
const schemas = await schemaService.loadAll(schemasDir2);
|
|
1036
|
+
const event = {
|
|
1037
|
+
type: "schemas:reloaded",
|
|
1038
|
+
payload: { schemas }
|
|
1039
|
+
};
|
|
1040
|
+
wsHandler.broadcast(event);
|
|
1041
|
+
} catch (error) {
|
|
1042
|
+
console.error("Error notifying reload:", error);
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
return {
|
|
1046
|
+
close: () => watcher.close()
|
|
1047
|
+
};
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
// src/server/index.ts
|
|
1051
|
+
async function main() {
|
|
1052
|
+
const port = Number(process.env.PORT) || DEFAULT_PORT;
|
|
1053
|
+
const host = process.env.HOST ?? DEFAULT_HOST;
|
|
1054
|
+
const cwd = process.cwd();
|
|
1055
|
+
const schemasDir2 = process.env.SCHEMAS_DIR ?? join3(cwd, "schemas");
|
|
1056
|
+
console.log("Starting Omnify GUI...");
|
|
1057
|
+
console.log(` Schemas directory: ${schemasDir2}`);
|
|
1058
|
+
const app = createApp({ schemasDir: schemasDir2, cwd });
|
|
1059
|
+
const server = createServer(app);
|
|
1060
|
+
const wsHandler = createWsHandler(server);
|
|
1061
|
+
const watcher = createFileWatcher(schemasDir2, wsHandler);
|
|
1062
|
+
server.listen(port, host, () => {
|
|
1063
|
+
const url = `http://${host}:${port}`;
|
|
1064
|
+
console.log(` GUI running at: ${url}`);
|
|
1065
|
+
console.log(" Press Ctrl+C to stop\n");
|
|
1066
|
+
if (process.env.NODE_ENV !== "production") {
|
|
1067
|
+
open(url).catch(() => {
|
|
1068
|
+
});
|
|
1069
|
+
}
|
|
1070
|
+
});
|
|
1071
|
+
const shutdown = () => {
|
|
1072
|
+
console.log("\nShutting down...");
|
|
1073
|
+
watcher.close();
|
|
1074
|
+
wsHandler.close();
|
|
1075
|
+
server.close(() => {
|
|
1076
|
+
console.log("Server closed");
|
|
1077
|
+
process.exit(0);
|
|
1078
|
+
});
|
|
1079
|
+
};
|
|
1080
|
+
process.on("SIGINT", shutdown);
|
|
1081
|
+
process.on("SIGTERM", shutdown);
|
|
1082
|
+
}
|
|
1083
|
+
main().catch((error) => {
|
|
1084
|
+
console.error("Failed to start server:", error);
|
|
1085
|
+
process.exit(1);
|
|
1086
|
+
});
|
|
1087
|
+
//# sourceMappingURL=index.js.map
|