@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/dist/index.d.ts CHANGED
@@ -176,6 +176,7 @@ declare function summary(results: {
176
176
  success: number;
177
177
  failed: number;
178
178
  total: number;
179
+ failedProjects?: string[];
179
180
  }): void;
180
181
  declare function formatDuration(ms: number): string;
181
182
 
package/dist/index.js CHANGED
@@ -1,8 +1,8 @@
1
1
  import { spawn, execSync } from 'child_process';
2
2
  import { z } from 'zod';
3
- import { unlink, mkdir, appendFile, access, writeFile, readFile, symlink } from 'fs/promises';
3
+ import { unlink, mkdir, appendFile, access, writeFile, readFile, symlink, readdir } from 'fs/promises';
4
4
  import { dirname, join, basename, resolve } from 'path';
5
- import { stringify, parse } from 'yaml';
5
+ import { parse, stringify } from 'yaml';
6
6
  import pc from 'picocolors';
7
7
  import { homedir } from 'os';
8
8
  import { Command } from 'commander';
@@ -539,7 +539,7 @@ function commandOutput(stdout, stderr) {
539
539
  }
540
540
  }
541
541
  function summary(results) {
542
- const { success: successCount, failed, total } = results;
542
+ const { success: successCount, failed, total, failedProjects } = results;
543
543
  console.log("");
544
544
  if (failed === 0) {
545
545
  console.log(`${symbols.success} ${pc.green(`All ${total} projects completed successfully`)}`);
@@ -547,6 +547,13 @@ function summary(results) {
547
547
  console.log(
548
548
  `${symbols.warning} ${pc.yellow(`${successCount}/${total} projects succeeded, ${failed} failed`)}`
549
549
  );
550
+ if (failedProjects && failedProjects.length > 0) {
551
+ console.log("");
552
+ console.log("Failed projects:");
553
+ for (const project of failedProjects) {
554
+ console.log(` ${symbols.error} ${pc.red(project)}`);
555
+ }
556
+ }
550
557
  }
551
558
  }
552
559
  function formatDuration(ms) {
@@ -645,9 +652,14 @@ async function loop(command, context, options = {}) {
645
652
  }
646
653
  const results = options.parallel ? await runParallel(command, directories, context, options) : await runSequential(command, directories, context, options);
647
654
  if (!options.suppressOutput) {
648
- const successCount = results.filter((r) => r.success).length;
649
- const failedCount = results.length - successCount;
650
- summary({ success: successCount, failed: failedCount, total: results.length });
655
+ const failedResults = results.filter((r) => !r.success);
656
+ const successCount = results.length - failedResults.length;
657
+ summary({
658
+ success: successCount,
659
+ failed: failedResults.length,
660
+ total: results.length,
661
+ failedProjects: failedResults.map((r) => r.directory)
662
+ });
651
663
  }
652
664
  return results;
653
665
  }
@@ -1476,6 +1488,118 @@ function registerNpmCommands(program) {
1476
1488
  await runCommand2(script, options);
1477
1489
  });
1478
1490
  }
1491
+ var MISSING_DIRECTORY_HINT = "directory missing \u2014 run 'gogo migrate' if it moved, or 'gogo git update' to clone";
1492
+ function isGogoConfigFile(filename) {
1493
+ return filename === ".gogo" || filename.startsWith(".gogo.");
1494
+ }
1495
+ async function findConfigFiles(cwd) {
1496
+ const entries = await readdir(cwd);
1497
+ const configFiles = [];
1498
+ for (const entry of entries) {
1499
+ if (isGogoConfigFile(entry)) {
1500
+ configFiles.push(entry);
1501
+ }
1502
+ }
1503
+ if (entries.includes(LOOPRC_FILE)) {
1504
+ configFiles.push(LOOPRC_FILE);
1505
+ }
1506
+ return configFiles.sort();
1507
+ }
1508
+ async function validateCommand() {
1509
+ const cwd = process.cwd();
1510
+ const results = [];
1511
+ const configFiles = await findConfigFiles(cwd);
1512
+ for (const filename of configFiles) {
1513
+ const filePath = join(cwd, filename);
1514
+ if (filename === LOOPRC_FILE) {
1515
+ results.push(await validateLoopRcFile(filePath));
1516
+ } else {
1517
+ results.push(await validateConfigFile(filePath, filename));
1518
+ }
1519
+ }
1520
+ if (results.length === 0) {
1521
+ warning("No config files found in current directory");
1522
+ return;
1523
+ }
1524
+ const hasErrors = results.some((r) => !r.valid);
1525
+ for (const result of results) {
1526
+ if (result.valid) {
1527
+ projectStatus(result.file, "success");
1528
+ } else {
1529
+ projectStatus(result.file, "error", result.error);
1530
+ }
1531
+ }
1532
+ const workingCopyHasErrors = await validateWorkingCopy(cwd);
1533
+ if (hasErrors || workingCopyHasErrors) {
1534
+ throw new Error("Validation failed");
1535
+ }
1536
+ }
1537
+ async function validateWorkingCopy(cwd) {
1538
+ let config;
1539
+ let metaDir;
1540
+ try {
1541
+ ({ config, metaDir } = await readMetaConfig(cwd));
1542
+ } catch {
1543
+ return false;
1544
+ }
1545
+ const projectPaths = Object.keys(config.projects);
1546
+ if (projectPaths.length === 0) {
1547
+ return false;
1548
+ }
1549
+ let hasErrors = false;
1550
+ for (const projectPath of projectPaths) {
1551
+ const projectDir = join(metaDir, projectPath);
1552
+ if (!await fileExists(projectDir)) {
1553
+ projectStatus(projectPath, "error", MISSING_DIRECTORY_HINT);
1554
+ hasErrors = true;
1555
+ }
1556
+ }
1557
+ if (!hasErrors) {
1558
+ success(`All ${projectPaths.length} project directories present`);
1559
+ }
1560
+ return hasErrors;
1561
+ }
1562
+ async function validateConfigFile(filePath, filename) {
1563
+ const format = detectFormat(filePath);
1564
+ try {
1565
+ const content = await readFile(filePath, "utf-8");
1566
+ const parsed = format === "yaml" ? parse(content) : JSON.parse(content);
1567
+ MetaConfigSchema.parse(parsed);
1568
+ return { file: filename, valid: true };
1569
+ } catch (error2) {
1570
+ if (error2 instanceof SyntaxError) {
1571
+ return { file: filename, valid: false, error: "Invalid JSON" };
1572
+ }
1573
+ if (error2 instanceof Error && error2.name === "YAMLParseError") {
1574
+ return { file: filename, valid: false, error: "Invalid YAML" };
1575
+ }
1576
+ if (error2 instanceof Error && error2.name === "ZodError") {
1577
+ return { file: filename, valid: false, error: `Invalid structure: ${error2.message}` };
1578
+ }
1579
+ return { file: filename, valid: false, error: String(error2) };
1580
+ }
1581
+ }
1582
+ async function validateLoopRcFile(filePath) {
1583
+ try {
1584
+ const content = await readFile(filePath, "utf-8");
1585
+ const parsed = JSON.parse(content);
1586
+ LoopRcSchema.parse(parsed);
1587
+ return { file: LOOPRC_FILE, valid: true };
1588
+ } catch (error2) {
1589
+ if (error2 instanceof SyntaxError) {
1590
+ return { file: LOOPRC_FILE, valid: false, error: "Invalid JSON" };
1591
+ }
1592
+ if (error2 instanceof Error && error2.name === "ZodError") {
1593
+ return { file: LOOPRC_FILE, valid: false, error: `Invalid structure: ${error2.message}` };
1594
+ }
1595
+ return { file: LOOPRC_FILE, valid: false, error: String(error2) };
1596
+ }
1597
+ }
1598
+ function registerValidateCommand(program) {
1599
+ program.command("validate").description("Validate config files and check that configured projects exist in the working copy").action(async () => {
1600
+ await validateCommand();
1601
+ });
1602
+ }
1479
1603
 
1480
1604
  // src/cli.ts
1481
1605
  function getVersion() {
@@ -1506,6 +1630,7 @@ function createProgram() {
1506
1630
  registerGitCommands(program);
1507
1631
  registerProjectCommands(program);
1508
1632
  registerNpmCommands(program);
1633
+ registerValidateCommand(program);
1509
1634
  return program;
1510
1635
  }
1511
1636
  async function main() {