@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 +19 -0
- package/dist/cli.js +131 -6
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +131 -6
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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 {
|
|
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
|
|
655
|
-
const
|
|
656
|
-
summary({
|
|
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() {
|