@dafish/gogo-meta 1.5.0 → 1.7.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 CHANGED
@@ -563,6 +563,25 @@ gogo npm run lint --if-present
563
563
 
564
564
  ---
565
565
 
566
+ ### `gogo validate`
567
+
568
+ Validate your configuration. This runs two checks:
569
+
570
+ 1. **Config files** — every `.gogo` / `.gogo.*` / `.looprc` file in the current
571
+ directory is parsed and checked against its schema.
572
+ 2. **Working copy** — each project declared in the resolved config must have a
573
+ matching directory on disk.
574
+
575
+ If a configured project directory is missing, validation fails with a hint:
576
+ run `gogo migrate` if the project was moved/renamed in the config, or
577
+ `gogo git update` to clone a project that has not been cloned yet.
578
+
579
+ ```bash
580
+ gogo validate
581
+ ```
582
+
583
+ ---
584
+
566
585
  ## Examples
567
586
 
568
587
  ### Setting Up a New Meta Repository
package/dist/cli.js CHANGED
@@ -3,8 +3,8 @@ import { Command } from 'commander';
3
3
  import { readFileSync } from 'fs';
4
4
  import { fileURLToPath } from 'url';
5
5
  import { dirname, join, basename, resolve } from 'path';
6
- import { unlink, mkdir, appendFile, access, writeFile, readFile, symlink } from 'fs/promises';
7
- import { stringify, parse } from 'yaml';
6
+ import { unlink, mkdir, appendFile, access, writeFile, readFile, symlink, readdir } from 'fs/promises';
7
+ import { parse, stringify } from 'yaml';
8
8
  import { z } from 'zod';
9
9
  import pc from 'picocolors';
10
10
  import { homedir } from 'os';
@@ -470,7 +470,7 @@ function commandOutput(stdout, stderr) {
470
470
  }
471
471
  }
472
472
  function summary(results) {
473
- const { success: successCount, failed, total } = results;
473
+ const { success: successCount, failed, total, failedProjects } = results;
474
474
  console.log("");
475
475
  if (failed === 0) {
476
476
  console.log(`${symbols.success} ${pc.green(`All ${total} projects completed successfully`)}`);
@@ -478,6 +478,13 @@ function summary(results) {
478
478
  console.log(
479
479
  `${symbols.warning} ${pc.yellow(`${successCount}/${total} projects succeeded, ${failed} failed`)}`
480
480
  );
481
+ if (failedProjects && failedProjects.length > 0) {
482
+ console.log("");
483
+ console.log("Failed projects:");
484
+ for (const project of failedProjects) {
485
+ console.log(` ${symbols.error} ${pc.red(project)}`);
486
+ }
487
+ }
481
488
  }
482
489
  }
483
490
 
@@ -651,9 +658,14 @@ async function loop(command, context, options = {}) {
651
658
  }
652
659
  const results = options.parallel ? await runParallel(command, directories, context, options) : await runSequential(command, directories, context, options);
653
660
  if (!options.suppressOutput) {
654
- const successCount = results.filter((r) => r.success).length;
655
- const failedCount = results.length - successCount;
656
- summary({ success: successCount, failed: failedCount, total: results.length });
661
+ const failedResults = results.filter((r) => !r.success);
662
+ const successCount = results.length - failedResults.length;
663
+ summary({
664
+ success: successCount,
665
+ failed: failedResults.length,
666
+ total: results.length,
667
+ failedProjects: failedResults.map((r) => r.directory)
668
+ });
657
669
  }
658
670
  return results;
659
671
  }
@@ -1449,6 +1461,118 @@ function registerNpmCommands(program) {
1449
1461
  await runCommand2(script, options);
1450
1462
  });
1451
1463
  }
1464
+ var MISSING_DIRECTORY_HINT = "directory missing \u2014 run 'gogo migrate' if it moved, or 'gogo git update' to clone";
1465
+ function isGogoConfigFile(filename) {
1466
+ return filename === ".gogo" || filename.startsWith(".gogo.");
1467
+ }
1468
+ async function findConfigFiles(cwd) {
1469
+ const entries = await readdir(cwd);
1470
+ const configFiles = [];
1471
+ for (const entry of entries) {
1472
+ if (isGogoConfigFile(entry)) {
1473
+ configFiles.push(entry);
1474
+ }
1475
+ }
1476
+ if (entries.includes(LOOPRC_FILE)) {
1477
+ configFiles.push(LOOPRC_FILE);
1478
+ }
1479
+ return configFiles.sort();
1480
+ }
1481
+ async function validateCommand() {
1482
+ const cwd = process.cwd();
1483
+ const results = [];
1484
+ const configFiles = await findConfigFiles(cwd);
1485
+ for (const filename of configFiles) {
1486
+ const filePath = join(cwd, filename);
1487
+ if (filename === LOOPRC_FILE) {
1488
+ results.push(await validateLoopRcFile(filePath));
1489
+ } else {
1490
+ results.push(await validateConfigFile(filePath, filename));
1491
+ }
1492
+ }
1493
+ if (results.length === 0) {
1494
+ warning("No config files found in current directory");
1495
+ return;
1496
+ }
1497
+ const hasErrors = results.some((r) => !r.valid);
1498
+ for (const result of results) {
1499
+ if (result.valid) {
1500
+ projectStatus(result.file, "success");
1501
+ } else {
1502
+ projectStatus(result.file, "error", result.error);
1503
+ }
1504
+ }
1505
+ const workingCopyHasErrors = await validateWorkingCopy(cwd);
1506
+ if (hasErrors || workingCopyHasErrors) {
1507
+ throw new Error("Validation failed");
1508
+ }
1509
+ }
1510
+ async function validateWorkingCopy(cwd) {
1511
+ let config;
1512
+ let metaDir;
1513
+ try {
1514
+ ({ config, metaDir } = await readMetaConfig(cwd));
1515
+ } catch {
1516
+ return false;
1517
+ }
1518
+ const projectPaths = Object.keys(config.projects);
1519
+ if (projectPaths.length === 0) {
1520
+ return false;
1521
+ }
1522
+ let hasErrors = false;
1523
+ for (const projectPath of projectPaths) {
1524
+ const projectDir = join(metaDir, projectPath);
1525
+ if (!await fileExists(projectDir)) {
1526
+ projectStatus(projectPath, "error", MISSING_DIRECTORY_HINT);
1527
+ hasErrors = true;
1528
+ }
1529
+ }
1530
+ if (!hasErrors) {
1531
+ success(`All ${projectPaths.length} project directories present`);
1532
+ }
1533
+ return hasErrors;
1534
+ }
1535
+ async function validateConfigFile(filePath, filename) {
1536
+ const format = detectFormat(filePath);
1537
+ try {
1538
+ const content = await readFile(filePath, "utf-8");
1539
+ const parsed = format === "yaml" ? parse(content) : JSON.parse(content);
1540
+ MetaConfigSchema.parse(parsed);
1541
+ return { file: filename, valid: true };
1542
+ } catch (error2) {
1543
+ if (error2 instanceof SyntaxError) {
1544
+ return { file: filename, valid: false, error: "Invalid JSON" };
1545
+ }
1546
+ if (error2 instanceof Error && error2.name === "YAMLParseError") {
1547
+ return { file: filename, valid: false, error: "Invalid YAML" };
1548
+ }
1549
+ if (error2 instanceof Error && error2.name === "ZodError") {
1550
+ return { file: filename, valid: false, error: `Invalid structure: ${error2.message}` };
1551
+ }
1552
+ return { file: filename, valid: false, error: String(error2) };
1553
+ }
1554
+ }
1555
+ async function validateLoopRcFile(filePath) {
1556
+ try {
1557
+ const content = await readFile(filePath, "utf-8");
1558
+ const parsed = JSON.parse(content);
1559
+ LoopRcSchema.parse(parsed);
1560
+ return { file: LOOPRC_FILE, valid: true };
1561
+ } catch (error2) {
1562
+ if (error2 instanceof SyntaxError) {
1563
+ return { file: LOOPRC_FILE, valid: false, error: "Invalid JSON" };
1564
+ }
1565
+ if (error2 instanceof Error && error2.name === "ZodError") {
1566
+ return { file: LOOPRC_FILE, valid: false, error: `Invalid structure: ${error2.message}` };
1567
+ }
1568
+ return { file: LOOPRC_FILE, valid: false, error: String(error2) };
1569
+ }
1570
+ }
1571
+ function registerValidateCommand(program) {
1572
+ program.command("validate").description("Validate config files and check that configured projects exist in the working copy").action(async () => {
1573
+ await validateCommand();
1574
+ });
1575
+ }
1452
1576
 
1453
1577
  // src/cli.ts
1454
1578
  function getVersion() {
@@ -1479,6 +1603,7 @@ function createProgram() {
1479
1603
  registerGitCommands(program);
1480
1604
  registerProjectCommands(program);
1481
1605
  registerNpmCommands(program);
1606
+ registerValidateCommand(program);
1482
1607
  return program;
1483
1608
  }
1484
1609
  async function main() {