@hyphaene/hexa-ts-kit 1.8.0 → 1.10.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/dist/{chunk-QTZTVZQA.js → chunk-IBVI6IS2.js} +27 -3
- package/dist/cli.js +535 -1
- package/dist/mcp-server.js +1 -1
- package/package.json +1 -1
|
@@ -840,6 +840,9 @@ var ContractsLintConfigSchema = z.object({
|
|
|
840
840
|
}),
|
|
841
841
|
disabledRules: z.array(z.string()).optional()
|
|
842
842
|
});
|
|
843
|
+
var VaultLintConfigSchema = z.object({
|
|
844
|
+
disabledRules: z.array(z.string()).optional()
|
|
845
|
+
});
|
|
843
846
|
var ContractsScaffoldConfigSchema = z.object({
|
|
844
847
|
path: z.string(),
|
|
845
848
|
// Absolute path to contracts lib
|
|
@@ -849,14 +852,20 @@ var ContractsScaffoldConfigSchema = z.object({
|
|
|
849
852
|
var ProjectConfigSchema = z.object({
|
|
850
853
|
type: z.enum(["contracts-lib", "nestjs-bff", "vue-frontend"])
|
|
851
854
|
});
|
|
855
|
+
var VaultConfigSchema = z.object({
|
|
856
|
+
schemaPath: z.string()
|
|
857
|
+
// Path to vault.schema.json relative to repo root
|
|
858
|
+
});
|
|
852
859
|
var HexaTsKitConfigSchema = z.object({
|
|
853
860
|
project: ProjectConfigSchema,
|
|
854
861
|
lint: z.object({
|
|
855
|
-
contracts: ContractsLintConfigSchema.optional()
|
|
862
|
+
contracts: ContractsLintConfigSchema.optional(),
|
|
863
|
+
vault: VaultLintConfigSchema.optional()
|
|
856
864
|
}).optional(),
|
|
857
865
|
scaffold: z.object({
|
|
858
866
|
contracts: ContractsScaffoldConfigSchema.optional()
|
|
859
|
-
}).optional()
|
|
867
|
+
}).optional(),
|
|
868
|
+
vault: VaultConfigSchema.optional()
|
|
860
869
|
});
|
|
861
870
|
var CONFIG_FILE_NAME = ".hexa-ts-kit.yaml";
|
|
862
871
|
function loadConfig(repoPath) {
|
|
@@ -917,6 +926,12 @@ function resolveConfigPath(repoPath, relativePath) {
|
|
|
917
926
|
function getDisabledRules(config) {
|
|
918
927
|
return config.lint?.contracts?.disabledRules ?? [];
|
|
919
928
|
}
|
|
929
|
+
function getDisabledVaultRules(config) {
|
|
930
|
+
return config.lint?.vault?.disabledRules ?? [];
|
|
931
|
+
}
|
|
932
|
+
function getVaultConfig(config) {
|
|
933
|
+
return config.vault ?? null;
|
|
934
|
+
}
|
|
920
935
|
|
|
921
936
|
// src/lib/contracts/permissions-extractor.ts
|
|
922
937
|
import { readFileSync as readFileSync2, existsSync as existsSync3 } from "fs";
|
|
@@ -2485,6 +2500,8 @@ var vaultChecker = {
|
|
|
2485
2500
|
if (!vault) {
|
|
2486
2501
|
return results;
|
|
2487
2502
|
}
|
|
2503
|
+
const configResult = loadConfig(ctx.cwd);
|
|
2504
|
+
const disabledRules = configResult.success ? getDisabledVaultRules(configResult.config) : [];
|
|
2488
2505
|
const project = new Project({
|
|
2489
2506
|
skipAddingFilesFromTsConfig: true,
|
|
2490
2507
|
skipFileDependencyResolution: true
|
|
@@ -2499,7 +2516,12 @@ var vaultChecker = {
|
|
|
2499
2516
|
}
|
|
2500
2517
|
}
|
|
2501
2518
|
for (const sourceFile of sourceFiles) {
|
|
2502
|
-
|
|
2519
|
+
const astResults = checkAstPatterns(sourceFile);
|
|
2520
|
+
for (const result of astResults) {
|
|
2521
|
+
if (!disabledRules.includes(result.ruleId)) {
|
|
2522
|
+
results.push(result);
|
|
2523
|
+
}
|
|
2524
|
+
}
|
|
2503
2525
|
}
|
|
2504
2526
|
return results;
|
|
2505
2527
|
}
|
|
@@ -3302,6 +3324,8 @@ async function scaffoldCommand(type, path, options) {
|
|
|
3302
3324
|
}
|
|
3303
3325
|
|
|
3304
3326
|
export {
|
|
3327
|
+
loadConfig,
|
|
3328
|
+
getVaultConfig,
|
|
3305
3329
|
lintCore,
|
|
3306
3330
|
lintCommand,
|
|
3307
3331
|
analyzeCore,
|
package/dist/cli.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
analyzeCommand,
|
|
4
|
+
getVaultConfig,
|
|
4
5
|
lintCommand,
|
|
6
|
+
loadConfig,
|
|
5
7
|
scaffoldCommand
|
|
6
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-IBVI6IS2.js";
|
|
7
9
|
|
|
8
10
|
// src/cli.ts
|
|
9
11
|
import { program } from "commander";
|
|
@@ -257,6 +259,533 @@ function padEnd(str, length) {
|
|
|
257
259
|
return str.length >= length ? str.slice(0, length - 1) + " " : str + " ".repeat(length - str.length);
|
|
258
260
|
}
|
|
259
261
|
|
|
262
|
+
// src/vault/extractors/json-schema.ts
|
|
263
|
+
import { execSync } from "child_process";
|
|
264
|
+
import { existsSync, readFileSync as readFileSync2 } from "fs";
|
|
265
|
+
import { join } from "path";
|
|
266
|
+
function extractFromJsonSchema(source, version, options) {
|
|
267
|
+
const { schemaPath, cwd = process.cwd() } = options;
|
|
268
|
+
let jsonContent;
|
|
269
|
+
const isGitRef = options.isGitRef ?? isLikelyGitRef(source);
|
|
270
|
+
if (isGitRef) {
|
|
271
|
+
jsonContent = readFromGit(source, schemaPath, cwd);
|
|
272
|
+
} else {
|
|
273
|
+
jsonContent = readFromFilesystem(source, schemaPath);
|
|
274
|
+
}
|
|
275
|
+
const jsonSchema = JSON.parse(jsonContent);
|
|
276
|
+
const root = convertJsonSchemaToSchemaNode(
|
|
277
|
+
jsonSchema,
|
|
278
|
+
jsonSchema.definitions
|
|
279
|
+
);
|
|
280
|
+
return {
|
|
281
|
+
root,
|
|
282
|
+
sourceFile: schemaPath,
|
|
283
|
+
version
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
function isLikelyGitRef(source) {
|
|
287
|
+
if (source === "HEAD" || source.startsWith("HEAD")) return true;
|
|
288
|
+
if (source === "main" || source === "master" || source === "develop")
|
|
289
|
+
return true;
|
|
290
|
+
if (source.startsWith("origin/")) return true;
|
|
291
|
+
if (source.startsWith("refs/")) return true;
|
|
292
|
+
if (source.match(/^[a-f0-9]{7,40}$/)) return true;
|
|
293
|
+
if (source.match(/^v?\d+\.\d+/)) return true;
|
|
294
|
+
if (source.startsWith(".") || source.startsWith("/") || source.startsWith("~"))
|
|
295
|
+
return false;
|
|
296
|
+
if (source.includes("/") && !source.startsWith("origin/")) return false;
|
|
297
|
+
return false;
|
|
298
|
+
}
|
|
299
|
+
function readFromGit(gitRef, schemaPath, cwd) {
|
|
300
|
+
let fullRef;
|
|
301
|
+
if (gitRef.includes(":") && gitRef.endsWith(".json")) {
|
|
302
|
+
fullRef = gitRef;
|
|
303
|
+
} else if (gitRef.includes(":")) {
|
|
304
|
+
fullRef = gitRef;
|
|
305
|
+
} else {
|
|
306
|
+
fullRef = `${gitRef}:${schemaPath}`;
|
|
307
|
+
}
|
|
308
|
+
try {
|
|
309
|
+
const content = execSync(`git show "${fullRef}"`, {
|
|
310
|
+
cwd,
|
|
311
|
+
encoding: "utf-8",
|
|
312
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
313
|
+
});
|
|
314
|
+
return content;
|
|
315
|
+
} catch (error) {
|
|
316
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
317
|
+
throw new Error(
|
|
318
|
+
`Cannot read schema from git ref "${fullRef}": ${errorMsg}`
|
|
319
|
+
);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
function readFromFilesystem(repoPath, schemaPath) {
|
|
323
|
+
const fullPath = join(repoPath, schemaPath);
|
|
324
|
+
if (!existsSync(fullPath)) {
|
|
325
|
+
throw new Error(
|
|
326
|
+
`Schema file not found: ${fullPath}
|
|
327
|
+
Run 'npm run vault:build' to generate it.`
|
|
328
|
+
);
|
|
329
|
+
}
|
|
330
|
+
return readFileSync2(fullPath, "utf-8");
|
|
331
|
+
}
|
|
332
|
+
function convertJsonSchemaToSchemaNode(schema, definitions) {
|
|
333
|
+
const localDefs = { ...definitions, ...schema.definitions };
|
|
334
|
+
if (schema.$ref) {
|
|
335
|
+
const refPath = schema.$ref.replace("#/definitions/", "");
|
|
336
|
+
const refSchema = localDefs?.[refPath];
|
|
337
|
+
if (refSchema) {
|
|
338
|
+
return convertJsonSchemaToSchemaNode(refSchema, localDefs);
|
|
339
|
+
}
|
|
340
|
+
return { type: "unknown" };
|
|
341
|
+
}
|
|
342
|
+
const type = Array.isArray(schema.type) ? schema.type[0] : schema.type;
|
|
343
|
+
switch (type) {
|
|
344
|
+
case "string":
|
|
345
|
+
if (schema.enum) {
|
|
346
|
+
return {
|
|
347
|
+
type: "enum",
|
|
348
|
+
enumValues: schema.enum.map(String)
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
return { type: "string" };
|
|
352
|
+
case "number":
|
|
353
|
+
case "integer":
|
|
354
|
+
return { type: "number" };
|
|
355
|
+
case "boolean":
|
|
356
|
+
return { type: "boolean" };
|
|
357
|
+
case "array":
|
|
358
|
+
if (schema.items) {
|
|
359
|
+
return {
|
|
360
|
+
type: "array",
|
|
361
|
+
arrayItemType: convertJsonSchemaToSchemaNode(schema.items, localDefs)
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
return { type: "array" };
|
|
365
|
+
case "object": {
|
|
366
|
+
const children = {};
|
|
367
|
+
const requiredSet = new Set(schema.required ?? []);
|
|
368
|
+
if (schema.properties) {
|
|
369
|
+
for (const [key, propSchema] of Object.entries(schema.properties)) {
|
|
370
|
+
if (key.startsWith("$") || key === "definitions") continue;
|
|
371
|
+
const propDefs = { ...localDefs, ...propSchema.definitions };
|
|
372
|
+
const propNode = convertJsonSchemaToSchemaNode(propSchema, propDefs);
|
|
373
|
+
children[key] = requiredSet.has(key) ? propNode : { ...propNode, optional: true };
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
return { type: "object", children };
|
|
377
|
+
}
|
|
378
|
+
default: {
|
|
379
|
+
if (schema.properties) {
|
|
380
|
+
const children = {};
|
|
381
|
+
const requiredSet = new Set(schema.required ?? []);
|
|
382
|
+
for (const [key, propSchema] of Object.entries(schema.properties)) {
|
|
383
|
+
if (key.startsWith("$") || key === "definitions") continue;
|
|
384
|
+
const propDefs = { ...localDefs, ...propSchema.definitions };
|
|
385
|
+
const propNode = convertJsonSchemaToSchemaNode(propSchema, propDefs);
|
|
386
|
+
children[key] = requiredSet.has(key) ? propNode : { ...propNode, optional: true };
|
|
387
|
+
}
|
|
388
|
+
return { type: "object", children };
|
|
389
|
+
}
|
|
390
|
+
return { type: "unknown" };
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
function gitRefExists(ref, cwd) {
|
|
395
|
+
try {
|
|
396
|
+
execSync(`git rev-parse --verify "${ref}"`, {
|
|
397
|
+
cwd,
|
|
398
|
+
encoding: "utf-8",
|
|
399
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
400
|
+
});
|
|
401
|
+
return true;
|
|
402
|
+
} catch {
|
|
403
|
+
return false;
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
function getCurrentBranch(cwd) {
|
|
407
|
+
try {
|
|
408
|
+
return execSync("git rev-parse --abbrev-ref HEAD", {
|
|
409
|
+
cwd,
|
|
410
|
+
encoding: "utf-8"
|
|
411
|
+
}).trim();
|
|
412
|
+
} catch {
|
|
413
|
+
return "HEAD";
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
function fileExistsAtRef(ref, filePath, cwd) {
|
|
417
|
+
try {
|
|
418
|
+
execSync(`git show "${ref}:${filePath}"`, {
|
|
419
|
+
cwd,
|
|
420
|
+
encoding: "utf-8",
|
|
421
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
422
|
+
});
|
|
423
|
+
return true;
|
|
424
|
+
} catch {
|
|
425
|
+
return false;
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
// src/vault/diff.ts
|
|
430
|
+
function diffSchemas(source, target, sourceVersion, targetVersion) {
|
|
431
|
+
const changes = [];
|
|
432
|
+
compareNodes(source, target, "", changes);
|
|
433
|
+
const hasBreakingChanges = changes.some(
|
|
434
|
+
(c) => c.type === "removed" || c.type === "changed"
|
|
435
|
+
);
|
|
436
|
+
return {
|
|
437
|
+
sourceVersion,
|
|
438
|
+
targetVersion,
|
|
439
|
+
changes,
|
|
440
|
+
hasBreakingChanges
|
|
441
|
+
};
|
|
442
|
+
}
|
|
443
|
+
function compareNodes(source, target, path, changes) {
|
|
444
|
+
if (!source && target) {
|
|
445
|
+
changes.push({
|
|
446
|
+
path: path || "(root)",
|
|
447
|
+
type: "added",
|
|
448
|
+
newValue: target
|
|
449
|
+
});
|
|
450
|
+
return;
|
|
451
|
+
}
|
|
452
|
+
if (source && !target) {
|
|
453
|
+
changes.push({
|
|
454
|
+
path: path || "(root)",
|
|
455
|
+
type: "removed",
|
|
456
|
+
oldValue: source
|
|
457
|
+
});
|
|
458
|
+
return;
|
|
459
|
+
}
|
|
460
|
+
if (!source || !target) {
|
|
461
|
+
return;
|
|
462
|
+
}
|
|
463
|
+
if (source.type !== target.type) {
|
|
464
|
+
changes.push({
|
|
465
|
+
path: path || "(root)",
|
|
466
|
+
type: "changed",
|
|
467
|
+
oldValue: source,
|
|
468
|
+
newValue: target
|
|
469
|
+
});
|
|
470
|
+
return;
|
|
471
|
+
}
|
|
472
|
+
if (source.type === "enum" && target.type === "enum") {
|
|
473
|
+
const sourceValues = new Set(source.enumValues ?? []);
|
|
474
|
+
const targetValues = new Set(target.enumValues ?? []);
|
|
475
|
+
for (const val of targetValues) {
|
|
476
|
+
if (!sourceValues.has(val)) {
|
|
477
|
+
changes.push({
|
|
478
|
+
path: `${path}[enum:${val}]`,
|
|
479
|
+
type: "added",
|
|
480
|
+
newValue: { type: "enum", enumValues: [val] }
|
|
481
|
+
});
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
for (const val of sourceValues) {
|
|
485
|
+
if (!targetValues.has(val)) {
|
|
486
|
+
changes.push({
|
|
487
|
+
path: `${path}[enum:${val}]`,
|
|
488
|
+
type: "removed",
|
|
489
|
+
oldValue: { type: "enum", enumValues: [val] }
|
|
490
|
+
});
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
return;
|
|
494
|
+
}
|
|
495
|
+
if (source.optional !== target.optional) {
|
|
496
|
+
changes.push({
|
|
497
|
+
path,
|
|
498
|
+
type: "changed",
|
|
499
|
+
oldValue: source,
|
|
500
|
+
newValue: target
|
|
501
|
+
});
|
|
502
|
+
}
|
|
503
|
+
if (source.type === "array" && target.type === "array") {
|
|
504
|
+
compareNodes(
|
|
505
|
+
source.arrayItemType,
|
|
506
|
+
target.arrayItemType,
|
|
507
|
+
`${path}[]`,
|
|
508
|
+
changes
|
|
509
|
+
);
|
|
510
|
+
return;
|
|
511
|
+
}
|
|
512
|
+
if (source.type === "object" && target.type === "object") {
|
|
513
|
+
const sourceKeys = new Set(Object.keys(source.children ?? {}));
|
|
514
|
+
const targetKeys = new Set(Object.keys(target.children ?? {}));
|
|
515
|
+
for (const key of targetKeys) {
|
|
516
|
+
const childPath = path ? `${path}.${key}` : key;
|
|
517
|
+
if (!sourceKeys.has(key)) {
|
|
518
|
+
changes.push({
|
|
519
|
+
path: childPath,
|
|
520
|
+
type: "added",
|
|
521
|
+
newValue: target.children[key]
|
|
522
|
+
});
|
|
523
|
+
} else {
|
|
524
|
+
compareNodes(
|
|
525
|
+
source.children[key],
|
|
526
|
+
target.children[key],
|
|
527
|
+
childPath,
|
|
528
|
+
changes
|
|
529
|
+
);
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
for (const key of sourceKeys) {
|
|
533
|
+
if (!targetKeys.has(key)) {
|
|
534
|
+
const childPath = path ? `${path}.${key}` : key;
|
|
535
|
+
changes.push({
|
|
536
|
+
path: childPath,
|
|
537
|
+
type: "removed",
|
|
538
|
+
oldValue: source.children[key]
|
|
539
|
+
});
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
function formatType(node) {
|
|
545
|
+
switch (node.type) {
|
|
546
|
+
case "string":
|
|
547
|
+
return "string";
|
|
548
|
+
case "number":
|
|
549
|
+
return "number";
|
|
550
|
+
case "boolean":
|
|
551
|
+
return "boolean";
|
|
552
|
+
case "array":
|
|
553
|
+
if (node.arrayItemType) {
|
|
554
|
+
return `${formatType(node.arrayItemType)}[]`;
|
|
555
|
+
}
|
|
556
|
+
return "array";
|
|
557
|
+
case "enum":
|
|
558
|
+
if (node.enumValues) {
|
|
559
|
+
return `enum(${node.enumValues.join(" | ")})`;
|
|
560
|
+
}
|
|
561
|
+
return "enum";
|
|
562
|
+
case "object":
|
|
563
|
+
if (node.children) {
|
|
564
|
+
const keys = Object.keys(node.children).filter((k) => k !== "__ref");
|
|
565
|
+
if (keys.length <= 3) {
|
|
566
|
+
return `{ ${keys.join(", ")} }`;
|
|
567
|
+
}
|
|
568
|
+
return `{ ${keys.slice(0, 3).join(", ")}, ... }`;
|
|
569
|
+
}
|
|
570
|
+
return "object";
|
|
571
|
+
default:
|
|
572
|
+
return "unknown";
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
// src/commands/vault-diff.ts
|
|
577
|
+
function getSchemaPath(cwd) {
|
|
578
|
+
const configResult = loadConfig(cwd);
|
|
579
|
+
if (!configResult.success) {
|
|
580
|
+
throw new Error(
|
|
581
|
+
`Cannot load .hexa-ts-kit.yaml: ${configResult.error}
|
|
582
|
+
Add vault.schemaPath to your config file.`
|
|
583
|
+
);
|
|
584
|
+
}
|
|
585
|
+
const vaultConfig = getVaultConfig(configResult.config);
|
|
586
|
+
if (!vaultConfig) {
|
|
587
|
+
throw new Error(
|
|
588
|
+
`Missing vault config in .hexa-ts-kit.yaml
|
|
589
|
+
Add:
|
|
590
|
+
vault:
|
|
591
|
+
schemaPath: path/to/vault.schema.json`
|
|
592
|
+
);
|
|
593
|
+
}
|
|
594
|
+
return vaultConfig.schemaPath;
|
|
595
|
+
}
|
|
596
|
+
async function vaultDiffCommand(sourceRef, targetRef, options) {
|
|
597
|
+
const format = options.format ?? "console";
|
|
598
|
+
const cwd = options.cwd ?? process.cwd();
|
|
599
|
+
let schemaPath;
|
|
600
|
+
try {
|
|
601
|
+
schemaPath = getSchemaPath(cwd);
|
|
602
|
+
} catch (error) {
|
|
603
|
+
console.error(
|
|
604
|
+
`Error: ${error instanceof Error ? error.message : String(error)}`
|
|
605
|
+
);
|
|
606
|
+
process.exit(1);
|
|
607
|
+
}
|
|
608
|
+
if (!gitRefExists(sourceRef, cwd)) {
|
|
609
|
+
console.error(`Error: Git ref "${sourceRef}" does not exist`);
|
|
610
|
+
process.exit(1);
|
|
611
|
+
}
|
|
612
|
+
if (!gitRefExists(targetRef, cwd)) {
|
|
613
|
+
console.error(`Error: Git ref "${targetRef}" does not exist`);
|
|
614
|
+
process.exit(1);
|
|
615
|
+
}
|
|
616
|
+
if (!fileExistsAtRef(sourceRef, schemaPath, cwd)) {
|
|
617
|
+
console.error(
|
|
618
|
+
`Error: vault.schema.json not found at "${sourceRef}:${schemaPath}"
|
|
619
|
+
The schema file may not exist in this version. Run 'npm run vault:build' on that branch first.`
|
|
620
|
+
);
|
|
621
|
+
process.exit(1);
|
|
622
|
+
}
|
|
623
|
+
if (!fileExistsAtRef(targetRef, schemaPath, cwd)) {
|
|
624
|
+
console.error(
|
|
625
|
+
`Error: vault.schema.json not found at "${targetRef}:${schemaPath}"
|
|
626
|
+
The schema file may not exist in this version. Run 'npm run vault:build' on that branch first.`
|
|
627
|
+
);
|
|
628
|
+
process.exit(1);
|
|
629
|
+
}
|
|
630
|
+
try {
|
|
631
|
+
const extractOpts = { schemaPath, isGitRef: true, cwd };
|
|
632
|
+
const sourceSchema = extractFromJsonSchema(
|
|
633
|
+
sourceRef,
|
|
634
|
+
sourceRef,
|
|
635
|
+
extractOpts
|
|
636
|
+
);
|
|
637
|
+
const targetSchema = extractFromJsonSchema(
|
|
638
|
+
targetRef,
|
|
639
|
+
targetRef,
|
|
640
|
+
extractOpts
|
|
641
|
+
);
|
|
642
|
+
const result = diffSchemas(
|
|
643
|
+
sourceSchema.root,
|
|
644
|
+
targetSchema.root,
|
|
645
|
+
sourceSchema.version,
|
|
646
|
+
targetSchema.version
|
|
647
|
+
);
|
|
648
|
+
if (format === "json") {
|
|
649
|
+
console.log(JSON.stringify(result, null, 2));
|
|
650
|
+
} else {
|
|
651
|
+
printConsoleOutput(result);
|
|
652
|
+
}
|
|
653
|
+
if (result.hasBreakingChanges) {
|
|
654
|
+
process.exit(1);
|
|
655
|
+
}
|
|
656
|
+
} catch (error) {
|
|
657
|
+
console.error(
|
|
658
|
+
`Error: ${error instanceof Error ? error.message : String(error)}`
|
|
659
|
+
);
|
|
660
|
+
process.exit(1);
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
async function vaultExtractCommand(source, options) {
|
|
664
|
+
const format = options.format ?? "json";
|
|
665
|
+
const cwd = options.cwd ?? process.cwd();
|
|
666
|
+
let schemaPath;
|
|
667
|
+
try {
|
|
668
|
+
schemaPath = getSchemaPath(cwd);
|
|
669
|
+
} catch (error) {
|
|
670
|
+
console.error(
|
|
671
|
+
`Error: ${error instanceof Error ? error.message : String(error)}`
|
|
672
|
+
);
|
|
673
|
+
process.exit(1);
|
|
674
|
+
}
|
|
675
|
+
try {
|
|
676
|
+
let schema;
|
|
677
|
+
if (source === ".") {
|
|
678
|
+
schema = extractFromJsonSchema(cwd, "local", {
|
|
679
|
+
schemaPath,
|
|
680
|
+
isGitRef: false
|
|
681
|
+
});
|
|
682
|
+
} else if (gitRefExists(source, cwd)) {
|
|
683
|
+
if (!fileExistsAtRef(source, schemaPath, cwd)) {
|
|
684
|
+
console.error(
|
|
685
|
+
`Error: vault.schema.json not found at "${source}:${schemaPath}"`
|
|
686
|
+
);
|
|
687
|
+
process.exit(1);
|
|
688
|
+
}
|
|
689
|
+
schema = extractFromJsonSchema(source, source, {
|
|
690
|
+
schemaPath,
|
|
691
|
+
isGitRef: true,
|
|
692
|
+
cwd
|
|
693
|
+
});
|
|
694
|
+
} else {
|
|
695
|
+
schema = extractFromJsonSchema(source, "path", {
|
|
696
|
+
schemaPath,
|
|
697
|
+
isGitRef: false
|
|
698
|
+
});
|
|
699
|
+
}
|
|
700
|
+
if (format === "json") {
|
|
701
|
+
console.log(JSON.stringify(schema, null, 2));
|
|
702
|
+
} else {
|
|
703
|
+
console.log(`Schema from ${schema.sourceFile} (${schema.version}):
|
|
704
|
+
`);
|
|
705
|
+
printSchemaTree(schema.root, 0);
|
|
706
|
+
}
|
|
707
|
+
} catch (error) {
|
|
708
|
+
console.error(
|
|
709
|
+
`Error: ${error instanceof Error ? error.message : String(error)}`
|
|
710
|
+
);
|
|
711
|
+
process.exit(1);
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
function printConsoleOutput(result) {
|
|
715
|
+
console.log(`
|
|
716
|
+
Vault Schema Diff`);
|
|
717
|
+
console.log(`Source: ${result.sourceVersion}`);
|
|
718
|
+
console.log(`Target: ${result.targetVersion}`);
|
|
719
|
+
console.log("\u2500".repeat(50));
|
|
720
|
+
if (result.changes.length === 0) {
|
|
721
|
+
console.log("\n\u2713 No changes detected");
|
|
722
|
+
return;
|
|
723
|
+
}
|
|
724
|
+
const added = result.changes.filter((c) => c.type === "added");
|
|
725
|
+
const removed = result.changes.filter((c) => c.type === "removed");
|
|
726
|
+
const changed = result.changes.filter((c) => c.type === "changed");
|
|
727
|
+
if (added.length > 0) {
|
|
728
|
+
console.log("\n+ Added:");
|
|
729
|
+
for (const change of added) {
|
|
730
|
+
const type = change.newValue ? formatType(change.newValue) : "unknown";
|
|
731
|
+
console.log(` ${change.path}: ${type}`);
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
if (removed.length > 0) {
|
|
735
|
+
console.log("\n- Removed:");
|
|
736
|
+
for (const change of removed) {
|
|
737
|
+
const type = change.oldValue ? formatType(change.oldValue) : "unknown";
|
|
738
|
+
console.log(` ${change.path}: ${type}`);
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
if (changed.length > 0) {
|
|
742
|
+
console.log("\n~ Changed:");
|
|
743
|
+
for (const change of changed) {
|
|
744
|
+
const oldType = change.oldValue ? formatType(change.oldValue) : "unknown";
|
|
745
|
+
const newType = change.newValue ? formatType(change.newValue) : "unknown";
|
|
746
|
+
console.log(` ${change.path}: ${oldType} \u2192 ${newType}`);
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
console.log("\n" + "\u2500".repeat(50));
|
|
750
|
+
console.log(
|
|
751
|
+
`Summary: +${added.length} -${removed.length} ~${changed.length}`
|
|
752
|
+
);
|
|
753
|
+
if (result.hasBreakingChanges) {
|
|
754
|
+
console.log("\n\u26A0 Breaking changes detected (removals or type changes)");
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
function printSchemaTree(node, indent) {
|
|
758
|
+
const prefix = " ".repeat(indent);
|
|
759
|
+
if (node.type === "object" && node.children) {
|
|
760
|
+
for (const [key, child] of Object.entries(node.children)) {
|
|
761
|
+
if (key === "__ref") continue;
|
|
762
|
+
const opt = child.optional ? "?" : "";
|
|
763
|
+
console.log(`${prefix}${key}${opt}: ${formatType(child)}`);
|
|
764
|
+
if (child.type === "object" && child.children) {
|
|
765
|
+
printSchemaTree(child, indent + 1);
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
} else {
|
|
769
|
+
console.log(`${prefix}${formatType(node)}`);
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
async function vaultQuickDiffCommand(options) {
|
|
773
|
+
const cwd = options.cwd ?? process.cwd();
|
|
774
|
+
const currentBranch = getCurrentBranch(cwd);
|
|
775
|
+
const baseBranch = gitRefExists("main", cwd) ? "main" : gitRefExists("master", cwd) ? "master" : null;
|
|
776
|
+
if (!baseBranch) {
|
|
777
|
+
console.error("Error: Cannot find main or master branch");
|
|
778
|
+
process.exit(1);
|
|
779
|
+
}
|
|
780
|
+
if (currentBranch === baseBranch) {
|
|
781
|
+
console.log(`Already on ${baseBranch}, nothing to diff`);
|
|
782
|
+
return;
|
|
783
|
+
}
|
|
784
|
+
console.log(`Comparing ${baseBranch} \u2192 ${currentBranch}
|
|
785
|
+
`);
|
|
786
|
+
await vaultDiffCommand(baseBranch, currentBranch, options);
|
|
787
|
+
}
|
|
788
|
+
|
|
260
789
|
// src/cli.ts
|
|
261
790
|
var require2 = createRequire(import.meta.url);
|
|
262
791
|
var { version: VERSION } = require2("../package.json");
|
|
@@ -286,4 +815,9 @@ program.command("scaffold <type> <path>").description("Generate a colocated feat
|
|
|
286
815
|
program.command("analyze-bff <bff-path>").description(
|
|
287
816
|
"Analyze BFF to extract endpoint \u2192 permission \u2192 apiService mappings"
|
|
288
817
|
).option("-o, --output <file>", "Output JSON file path").option("--format <type>", "Output format: json, table", "json").action(analyzeBffCommand);
|
|
818
|
+
program.command("vault:diff <source-ref> <target-ref>").description(
|
|
819
|
+
"Compare Vault schemas between two git refs (branches, tags, commits)"
|
|
820
|
+
).option("-f, --format <type>", "Output format: console, json", "console").option("--cwd <path>", "Working directory (git repo)").action(vaultDiffCommand);
|
|
821
|
+
program.command("vault:quick-diff").alias("vault:qd").description("Compare current branch vault schema against main/master").option("-f, --format <type>", "Output format: console, json", "console").option("--cwd <path>", "Working directory (git repo)").action(vaultQuickDiffCommand);
|
|
822
|
+
program.command("vault:extract <source>").description("Extract and display Vault schema from git ref or path").option("-f, --format <type>", "Output format: json, tree", "json").option("--cwd <path>", "Working directory (git repo)").action(vaultExtractCommand);
|
|
289
823
|
program.parse();
|
package/dist/mcp-server.js
CHANGED