@hyphaene/hexa-ts-kit 1.9.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-OI2LN5K5.js → chunk-IBVI6IS2.js} +11 -1
- package/dist/cli.js +535 -1
- package/dist/mcp-server.js +1 -1
- package/package.json +1 -1
|
@@ -852,6 +852,10 @@ var ContractsScaffoldConfigSchema = z.object({
|
|
|
852
852
|
var ProjectConfigSchema = z.object({
|
|
853
853
|
type: z.enum(["contracts-lib", "nestjs-bff", "vue-frontend"])
|
|
854
854
|
});
|
|
855
|
+
var VaultConfigSchema = z.object({
|
|
856
|
+
schemaPath: z.string()
|
|
857
|
+
// Path to vault.schema.json relative to repo root
|
|
858
|
+
});
|
|
855
859
|
var HexaTsKitConfigSchema = z.object({
|
|
856
860
|
project: ProjectConfigSchema,
|
|
857
861
|
lint: z.object({
|
|
@@ -860,7 +864,8 @@ var HexaTsKitConfigSchema = z.object({
|
|
|
860
864
|
}).optional(),
|
|
861
865
|
scaffold: z.object({
|
|
862
866
|
contracts: ContractsScaffoldConfigSchema.optional()
|
|
863
|
-
}).optional()
|
|
867
|
+
}).optional(),
|
|
868
|
+
vault: VaultConfigSchema.optional()
|
|
864
869
|
});
|
|
865
870
|
var CONFIG_FILE_NAME = ".hexa-ts-kit.yaml";
|
|
866
871
|
function loadConfig(repoPath) {
|
|
@@ -924,6 +929,9 @@ function getDisabledRules(config) {
|
|
|
924
929
|
function getDisabledVaultRules(config) {
|
|
925
930
|
return config.lint?.vault?.disabledRules ?? [];
|
|
926
931
|
}
|
|
932
|
+
function getVaultConfig(config) {
|
|
933
|
+
return config.vault ?? null;
|
|
934
|
+
}
|
|
927
935
|
|
|
928
936
|
// src/lib/contracts/permissions-extractor.ts
|
|
929
937
|
import { readFileSync as readFileSync2, existsSync as existsSync3 } from "fs";
|
|
@@ -3316,6 +3324,8 @@ async function scaffoldCommand(type, path, options) {
|
|
|
3316
3324
|
}
|
|
3317
3325
|
|
|
3318
3326
|
export {
|
|
3327
|
+
loadConfig,
|
|
3328
|
+
getVaultConfig,
|
|
3319
3329
|
lintCore,
|
|
3320
3330
|
lintCommand,
|
|
3321
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