@dafish/gogo-meta 1.2.0 → 1.4.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 +169 -92
- package/dist/cli.js +94 -41
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +13 -3
- package/dist/index.js +95 -42
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
package/dist/cli.js
CHANGED
|
@@ -3,7 +3,8 @@ import { Command } from 'commander';
|
|
|
3
3
|
import { readFileSync } from 'fs';
|
|
4
4
|
import { fileURLToPath } from 'url';
|
|
5
5
|
import { dirname, join, basename } from 'path';
|
|
6
|
-
import { mkdir, appendFile, access, writeFile, readFile,
|
|
6
|
+
import { unlink, mkdir, appendFile, access, writeFile, readFile, symlink } from 'fs/promises';
|
|
7
|
+
import { stringify, parse } from 'yaml';
|
|
7
8
|
import { z } from 'zod';
|
|
8
9
|
import pc from 'picocolors';
|
|
9
10
|
import { homedir } from 'os';
|
|
@@ -208,6 +209,7 @@ var LoopRcSchema = z.object({
|
|
|
208
209
|
// src/core/config.ts
|
|
209
210
|
var META_FILE = ".gogo";
|
|
210
211
|
var LOOPRC_FILE = ".looprc";
|
|
212
|
+
var META_FILE_CANDIDATES = [".gogo", ".gogo.yaml", ".gogo.yml"];
|
|
211
213
|
var ConfigError = class extends Error {
|
|
212
214
|
constructor(message, path) {
|
|
213
215
|
super(message);
|
|
@@ -215,6 +217,21 @@ var ConfigError = class extends Error {
|
|
|
215
217
|
this.name = "ConfigError";
|
|
216
218
|
}
|
|
217
219
|
};
|
|
220
|
+
function detectFormat(filePath) {
|
|
221
|
+
if (filePath.endsWith(".yaml") || filePath.endsWith(".yml")) {
|
|
222
|
+
return "yaml";
|
|
223
|
+
}
|
|
224
|
+
return "json";
|
|
225
|
+
}
|
|
226
|
+
function filenameForFormat(format) {
|
|
227
|
+
return format === "yaml" ? ".gogo.yaml" : ".gogo";
|
|
228
|
+
}
|
|
229
|
+
function parseContent(content, format) {
|
|
230
|
+
return format === "yaml" ? parse(content) : JSON.parse(content);
|
|
231
|
+
}
|
|
232
|
+
function serializeContent(data, format) {
|
|
233
|
+
return format === "yaml" ? stringify(data, { indent: 2 }) : JSON.stringify(data, null, 2) + "\n";
|
|
234
|
+
}
|
|
218
235
|
async function fileExists(path) {
|
|
219
236
|
try {
|
|
220
237
|
await access(path);
|
|
@@ -237,31 +254,53 @@ async function findFileUp(filename, startDir) {
|
|
|
237
254
|
currentDir = parentDir;
|
|
238
255
|
}
|
|
239
256
|
}
|
|
257
|
+
async function findMetaFileUp(startDir) {
|
|
258
|
+
let currentDir = startDir;
|
|
259
|
+
while (true) {
|
|
260
|
+
for (const candidate of META_FILE_CANDIDATES) {
|
|
261
|
+
const filePath = join(currentDir, candidate);
|
|
262
|
+
if (await fileExists(filePath)) {
|
|
263
|
+
return filePath;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
const parentDir = dirname(currentDir);
|
|
267
|
+
if (parentDir === currentDir) {
|
|
268
|
+
return null;
|
|
269
|
+
}
|
|
270
|
+
currentDir = parentDir;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
240
273
|
async function readMetaConfig(cwd) {
|
|
241
|
-
const metaPath = await
|
|
274
|
+
const metaPath = await findMetaFileUp(cwd);
|
|
242
275
|
if (!metaPath) {
|
|
243
276
|
throw new ConfigError(
|
|
244
277
|
`No ${META_FILE} file found. Run 'gogo init' to create one, or navigate to a directory with a ${META_FILE} file.`
|
|
245
278
|
);
|
|
246
279
|
}
|
|
280
|
+
const format = detectFormat(metaPath);
|
|
247
281
|
try {
|
|
248
282
|
const content = await readFile(metaPath, "utf-8");
|
|
249
|
-
const parsed =
|
|
250
|
-
|
|
283
|
+
const parsed = parseContent(content, format);
|
|
284
|
+
const config = MetaConfigSchema.parse(parsed);
|
|
285
|
+
return { config, format, metaDir: dirname(metaPath) };
|
|
251
286
|
} catch (error2) {
|
|
252
287
|
if (error2 instanceof SyntaxError) {
|
|
253
|
-
throw new ConfigError(`Invalid JSON in
|
|
288
|
+
throw new ConfigError(`Invalid JSON in config file`, metaPath);
|
|
289
|
+
}
|
|
290
|
+
if (error2 instanceof Error && error2.name === "YAMLParseError") {
|
|
291
|
+
throw new ConfigError(`Invalid YAML in config file`, metaPath);
|
|
254
292
|
}
|
|
255
293
|
if (error2 instanceof Error && error2.name === "ZodError") {
|
|
256
|
-
throw new ConfigError(`Invalid
|
|
294
|
+
throw new ConfigError(`Invalid config file structure: ${error2.message}`, metaPath);
|
|
257
295
|
}
|
|
258
296
|
throw error2;
|
|
259
297
|
}
|
|
260
298
|
}
|
|
261
|
-
async function writeMetaConfig(cwd, config) {
|
|
262
|
-
const
|
|
299
|
+
async function writeMetaConfig(cwd, config, format = "json") {
|
|
300
|
+
const filename = filenameForFormat(format);
|
|
301
|
+
const metaPath = join(cwd, filename);
|
|
263
302
|
const validated = MetaConfigSchema.parse(config);
|
|
264
|
-
const content =
|
|
303
|
+
const content = serializeContent(validated, format);
|
|
265
304
|
await writeFile(metaPath, content, "utf-8");
|
|
266
305
|
}
|
|
267
306
|
async function readLoopRc(cwd) {
|
|
@@ -278,7 +317,7 @@ async function readLoopRc(cwd) {
|
|
|
278
317
|
}
|
|
279
318
|
}
|
|
280
319
|
function getMetaDir(cwd) {
|
|
281
|
-
return
|
|
320
|
+
return findMetaFileUp(cwd).then((path) => path ? dirname(path) : null);
|
|
282
321
|
}
|
|
283
322
|
function createDefaultConfig() {
|
|
284
323
|
return {
|
|
@@ -398,22 +437,36 @@ function summary(results) {
|
|
|
398
437
|
// src/commands/init.ts
|
|
399
438
|
async function initCommand(options = {}) {
|
|
400
439
|
const cwd = process.cwd();
|
|
401
|
-
const
|
|
402
|
-
|
|
440
|
+
const format = options.format === "yaml" ? "yaml" : "json";
|
|
441
|
+
const existingFiles = [];
|
|
442
|
+
for (const candidate of META_FILE_CANDIDATES) {
|
|
443
|
+
const candidatePath = join(cwd, candidate);
|
|
444
|
+
if (await fileExists(candidatePath)) {
|
|
445
|
+
existingFiles.push(candidate);
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
if (existingFiles.length > 0) {
|
|
403
449
|
if (!options.force) {
|
|
404
450
|
throw new Error(
|
|
405
|
-
`${
|
|
451
|
+
`${existingFiles[0]} file already exists. Use --force to overwrite.`
|
|
406
452
|
);
|
|
407
453
|
}
|
|
408
|
-
|
|
454
|
+
for (const candidate of existingFiles) {
|
|
455
|
+
await unlink(join(cwd, candidate));
|
|
456
|
+
}
|
|
457
|
+
warning(`Overwriting existing config file`);
|
|
409
458
|
}
|
|
410
459
|
const config = createDefaultConfig();
|
|
411
|
-
await writeMetaConfig(cwd, config);
|
|
412
|
-
|
|
460
|
+
await writeMetaConfig(cwd, config, format);
|
|
461
|
+
const filename = filenameForFormat(format);
|
|
462
|
+
success(`Created ${filename} file in ${cwd}`);
|
|
413
463
|
info("Add projects with: gogo project import <folder> <repo-url>");
|
|
414
464
|
}
|
|
415
465
|
function registerInitCommand(program) {
|
|
416
|
-
program.command("init").description("Initialize a new gogo-meta repository").option("-f, --force", "Overwrite existing .
|
|
466
|
+
program.command("init").description("Initialize a new gogo-meta repository").option("-f, --force", "Overwrite existing config file").option("--format <format>", "Config file format (json or yaml)", "json").action(async (options) => {
|
|
467
|
+
if (options.format && !["json", "yaml"].includes(options.format)) {
|
|
468
|
+
throw new Error(`Invalid format "${options.format}". Use "json" or "yaml".`);
|
|
469
|
+
}
|
|
417
470
|
await initCommand(options);
|
|
418
471
|
});
|
|
419
472
|
}
|
|
@@ -571,7 +624,7 @@ async function execCommand(command, options = {}) {
|
|
|
571
624
|
if (!metaDir) {
|
|
572
625
|
throw new Error('Not in a gogo-meta repository. Run "gogo init" first.');
|
|
573
626
|
}
|
|
574
|
-
const config = await readMetaConfig(cwd);
|
|
627
|
+
const { config } = await readMetaConfig(cwd);
|
|
575
628
|
const filterOptions = createFilterOptions(options);
|
|
576
629
|
info(`Executing: ${bold(command)}`);
|
|
577
630
|
const results = await loop(command, { config, metaDir }, {
|
|
@@ -613,7 +666,7 @@ async function runCommand(name, options = {}) {
|
|
|
613
666
|
if (!metaDir) {
|
|
614
667
|
throw new Error('Not in a gogo-meta repository. Run "gogo init" first.');
|
|
615
668
|
}
|
|
616
|
-
const config = await readMetaConfig(cwd);
|
|
669
|
+
const { config } = await readMetaConfig(cwd);
|
|
617
670
|
if (options.list || name === void 0) {
|
|
618
671
|
formatCommandList(listCommands(config));
|
|
619
672
|
return;
|
|
@@ -749,12 +802,12 @@ async function cloneCommand(url, options = {}) {
|
|
|
749
802
|
return;
|
|
750
803
|
}
|
|
751
804
|
success(`Cloned meta repository to ${repoName}`);
|
|
752
|
-
const metaPath =
|
|
753
|
-
if (!
|
|
754
|
-
warning("No
|
|
805
|
+
const metaPath = await findMetaFileUp(targetDir);
|
|
806
|
+
if (!metaPath) {
|
|
807
|
+
warning("No config file found in cloned repository");
|
|
755
808
|
return;
|
|
756
809
|
}
|
|
757
|
-
const config = await readMetaConfig(targetDir);
|
|
810
|
+
const { config } = await readMetaConfig(targetDir);
|
|
758
811
|
const projects = Object.entries(config.projects);
|
|
759
812
|
if (projects.length === 0) {
|
|
760
813
|
info("No child repositories defined in .gogo");
|
|
@@ -804,7 +857,7 @@ async function updateCommand(options = {}) {
|
|
|
804
857
|
if (!metaDir) {
|
|
805
858
|
throw new Error('Not in a gogo-meta repository. Run "gogo init" first.');
|
|
806
859
|
}
|
|
807
|
-
const config = await readMetaConfig(cwd);
|
|
860
|
+
const { config } = await readMetaConfig(cwd);
|
|
808
861
|
const filterOptions = createFilterOptions(options);
|
|
809
862
|
let projectEntries = Object.entries(config.projects);
|
|
810
863
|
const projectPaths = projectEntries.map(([path]) => path);
|
|
@@ -884,7 +937,7 @@ async function statusCommand(options = {}) {
|
|
|
884
937
|
if (!metaDir) {
|
|
885
938
|
throw new Error('Not in a gogo-meta repository. Run "gogo init" first.');
|
|
886
939
|
}
|
|
887
|
-
const config = await readMetaConfig(cwd);
|
|
940
|
+
const { config } = await readMetaConfig(cwd);
|
|
888
941
|
const filterOptions = createFilterOptions(options);
|
|
889
942
|
info("Checking git status across repositories...");
|
|
890
943
|
const results = await loop("git status --short --branch", { config, metaDir }, {
|
|
@@ -904,7 +957,7 @@ async function pullCommand(options = {}) {
|
|
|
904
957
|
if (!metaDir) {
|
|
905
958
|
throw new Error('Not in a gogo-meta repository. Run "gogo init" first.');
|
|
906
959
|
}
|
|
907
|
-
const config = await readMetaConfig(cwd);
|
|
960
|
+
const { config } = await readMetaConfig(cwd);
|
|
908
961
|
const filterOptions = createFilterOptions(options);
|
|
909
962
|
info("Pulling changes across repositories...");
|
|
910
963
|
const results = await loop("git pull", { config, metaDir }, {
|
|
@@ -925,7 +978,7 @@ async function pushCommand(options = {}) {
|
|
|
925
978
|
if (!metaDir) {
|
|
926
979
|
throw new Error('Not in a gogo-meta repository. Run "gogo init" first.');
|
|
927
980
|
}
|
|
928
|
-
const config = await readMetaConfig(cwd);
|
|
981
|
+
const { config } = await readMetaConfig(cwd);
|
|
929
982
|
const filterOptions = createFilterOptions(options);
|
|
930
983
|
info("Pushing changes across repositories...");
|
|
931
984
|
const results = await loop("git push", { config, metaDir }, {
|
|
@@ -945,7 +998,7 @@ async function branchCommand(name, options = {}) {
|
|
|
945
998
|
if (!metaDir) {
|
|
946
999
|
throw new Error('Not in a gogo-meta repository. Run "gogo init" first.');
|
|
947
1000
|
}
|
|
948
|
-
const config = await readMetaConfig(cwd);
|
|
1001
|
+
const { config } = await readMetaConfig(cwd);
|
|
949
1002
|
const filterOptions = createFilterOptions(options);
|
|
950
1003
|
let command;
|
|
951
1004
|
let actionMessage;
|
|
@@ -979,7 +1032,7 @@ async function checkoutCommand(branch, options = {}) {
|
|
|
979
1032
|
if (!metaDir) {
|
|
980
1033
|
throw new Error('Not in a gogo-meta repository. Run "gogo init" first.');
|
|
981
1034
|
}
|
|
982
|
-
const config = await readMetaConfig(cwd);
|
|
1035
|
+
const { config } = await readMetaConfig(cwd);
|
|
983
1036
|
const filterOptions = createFilterOptions(options);
|
|
984
1037
|
const flag = options.create ? "-b " : "";
|
|
985
1038
|
const command = `git checkout ${flag}"${branch}"`;
|
|
@@ -1002,7 +1055,7 @@ async function commitCommand(options) {
|
|
|
1002
1055
|
if (!metaDir) {
|
|
1003
1056
|
throw new Error('Not in a gogo-meta repository. Run "gogo init" first.');
|
|
1004
1057
|
}
|
|
1005
|
-
const config = await readMetaConfig(cwd);
|
|
1058
|
+
const { config } = await readMetaConfig(cwd);
|
|
1006
1059
|
const filterOptions = createFilterOptions(options);
|
|
1007
1060
|
const escapedMessage = options.message.replace(/"/g, '\\"');
|
|
1008
1061
|
const command = `git commit -m "${escapedMessage}"`;
|
|
@@ -1068,9 +1121,9 @@ async function createCommand(folder, url) {
|
|
|
1068
1121
|
if (remoteResult.exitCode !== 0) {
|
|
1069
1122
|
throw new Error(`Failed to add remote: ${remoteResult.stderr}`);
|
|
1070
1123
|
}
|
|
1071
|
-
const config = await readMetaConfig(metaDir);
|
|
1124
|
+
const { config, format } = await readMetaConfig(metaDir);
|
|
1072
1125
|
const updatedConfig = addProject(config, folder, url);
|
|
1073
|
-
await writeMetaConfig(metaDir, updatedConfig);
|
|
1126
|
+
await writeMetaConfig(metaDir, updatedConfig, format);
|
|
1074
1127
|
const gitignorePath = join(metaDir, ".gitignore");
|
|
1075
1128
|
if (await fileExists(gitignorePath)) {
|
|
1076
1129
|
await appendFile(gitignorePath, `
|
|
@@ -1112,9 +1165,9 @@ async function importCommand(folder, url, options = {}) {
|
|
|
1112
1165
|
info(`Existing: ${existingUrl}`);
|
|
1113
1166
|
info(`Provided: ${url}`);
|
|
1114
1167
|
}
|
|
1115
|
-
const config = await readMetaConfig(metaDir);
|
|
1168
|
+
const { config, format } = await readMetaConfig(metaDir);
|
|
1116
1169
|
const updatedConfig = addProject(config, folder, finalUrl);
|
|
1117
|
-
await writeMetaConfig(metaDir, updatedConfig);
|
|
1170
|
+
await writeMetaConfig(metaDir, updatedConfig, format);
|
|
1118
1171
|
success(`Imported existing project "${folder}"`);
|
|
1119
1172
|
info(`Repository URL: ${finalUrl}`);
|
|
1120
1173
|
} else {
|
|
@@ -1122,9 +1175,9 @@ async function importCommand(folder, url, options = {}) {
|
|
|
1122
1175
|
throw new Error("URL is required when importing a non-existent project");
|
|
1123
1176
|
}
|
|
1124
1177
|
if (options.noClone) {
|
|
1125
|
-
const config2 = await readMetaConfig(metaDir);
|
|
1178
|
+
const { config: config2, format: format2 } = await readMetaConfig(metaDir);
|
|
1126
1179
|
const updatedConfig2 = addProject(config2, folder, url);
|
|
1127
|
-
await writeMetaConfig(metaDir, updatedConfig2);
|
|
1180
|
+
await writeMetaConfig(metaDir, updatedConfig2, format2);
|
|
1128
1181
|
const added2 = await addToGitignore(metaDir, folder);
|
|
1129
1182
|
success(`Registered project "${folder}" (not cloned)`);
|
|
1130
1183
|
if (added2) {
|
|
@@ -1142,9 +1195,9 @@ async function importCommand(folder, url, options = {}) {
|
|
|
1142
1195
|
if (cloneResult.exitCode !== 0) {
|
|
1143
1196
|
throw new Error(`Failed to clone repository: ${cloneResult.stderr}`);
|
|
1144
1197
|
}
|
|
1145
|
-
const config = await readMetaConfig(metaDir);
|
|
1198
|
+
const { config, format } = await readMetaConfig(metaDir);
|
|
1146
1199
|
const updatedConfig = addProject(config, folder, url);
|
|
1147
|
-
await writeMetaConfig(metaDir, updatedConfig);
|
|
1200
|
+
await writeMetaConfig(metaDir, updatedConfig, format);
|
|
1148
1201
|
success(`Imported project "${folder}"`);
|
|
1149
1202
|
}
|
|
1150
1203
|
const added = await addToGitignore(metaDir, folder);
|
|
@@ -1171,7 +1224,7 @@ async function installCommand(variant, options = {}) {
|
|
|
1171
1224
|
if (!metaDir) {
|
|
1172
1225
|
throw new Error('Not in a gogo-meta repository. Run "gogo init" first.');
|
|
1173
1226
|
}
|
|
1174
|
-
const config = await readMetaConfig(cwd);
|
|
1227
|
+
const { config } = await readMetaConfig(cwd);
|
|
1175
1228
|
const filterOptions = createFilterOptions(options);
|
|
1176
1229
|
const command = `npm ${variant}`;
|
|
1177
1230
|
info(`Running "${command}" across repositories...`);
|
|
@@ -1217,7 +1270,7 @@ async function linkCommand(options = {}) {
|
|
|
1217
1270
|
if (!metaDir) {
|
|
1218
1271
|
throw new Error('Not in a gogo-meta repository. Run "gogo init" first.');
|
|
1219
1272
|
}
|
|
1220
|
-
const config = await readMetaConfig(cwd);
|
|
1273
|
+
const { config } = await readMetaConfig(cwd);
|
|
1221
1274
|
const filterOptions = createFilterOptions(options);
|
|
1222
1275
|
let projectPaths = Object.keys(config.projects);
|
|
1223
1276
|
projectPaths = applyFilters(projectPaths, filterOptions);
|
|
@@ -1295,7 +1348,7 @@ async function runCommand2(script, options = {}) {
|
|
|
1295
1348
|
if (!metaDir) {
|
|
1296
1349
|
throw new Error('Not in a gogo-meta repository. Run "gogo init" first.');
|
|
1297
1350
|
}
|
|
1298
|
-
const config = await readMetaConfig(cwd);
|
|
1351
|
+
const { config } = await readMetaConfig(cwd);
|
|
1299
1352
|
const filterOptions = createFilterOptions(options);
|
|
1300
1353
|
info(`Running "npm run ${script}" across repositories...`);
|
|
1301
1354
|
const command = options.ifPresent ? async (dir, _projectPath) => {
|