@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/dist/index.d.ts CHANGED
@@ -2,6 +2,7 @@ import { z } from 'zod';
2
2
  export { createProgram } from './cli.js';
3
3
  import 'commander';
4
4
 
5
+ type ConfigFormat = 'json' | 'yaml';
5
6
  declare const CommandConfigObjectSchema: z.ZodObject<{
6
7
  cmd: z.ZodString;
7
8
  description: z.ZodOptional<z.ZodString>;
@@ -81,14 +82,23 @@ type CommandHandler<T = void> = (options: T) => Promise<void>;
81
82
 
82
83
  declare const META_FILE = ".gogo";
83
84
  declare const LOOPRC_FILE = ".looprc";
85
+ declare const META_FILE_CANDIDATES: readonly [".gogo", ".gogo.yaml", ".gogo.yml"];
84
86
  declare class ConfigError extends Error {
85
87
  readonly path?: string | undefined;
86
88
  constructor(message: string, path?: string | undefined);
87
89
  }
90
+ declare function detectFormat(filePath: string): ConfigFormat;
91
+ declare function filenameForFormat(format: ConfigFormat): string;
88
92
  declare function fileExists(path: string): Promise<boolean>;
89
93
  declare function findFileUp(filename: string, startDir: string): Promise<string | null>;
90
- declare function readMetaConfig(cwd: string): Promise<MetaConfig>;
91
- declare function writeMetaConfig(cwd: string, config: MetaConfig): Promise<void>;
94
+ declare function findMetaFileUp(startDir: string): Promise<string | null>;
95
+ interface MetaConfigResult {
96
+ config: MetaConfig;
97
+ format: ConfigFormat;
98
+ metaDir: string;
99
+ }
100
+ declare function readMetaConfig(cwd: string): Promise<MetaConfigResult>;
101
+ declare function writeMetaConfig(cwd: string, config: MetaConfig, format?: ConfigFormat): Promise<void>;
92
102
  declare function readLoopRc(cwd: string): Promise<LoopRc | null>;
93
103
  declare function getMetaDir(cwd: string): Promise<string | null>;
94
104
  declare function createDefaultConfig(): MetaConfig;
@@ -200,4 +210,4 @@ declare function ensureSshHostsKnown(urls: string[]): Promise<{
200
210
  failed: string[];
201
211
  }>;
202
212
 
203
- export { type CommandConfig, type CommandConfigObject, CommandConfigObjectSchema, CommandConfigSchema, type CommandHandler, ConfigError, type ExecutorOptions, type ExecutorResult, type FilterOptions, LOOPRC_FILE, type LoopContext, type LoopOptions, type LoopRc, LoopRcSchema, type LoopResult, META_FILE, type MetaConfig, MetaConfigSchema, type ProjectInfo, type ResolvedCommand, addHostKey, addProject, addToGitignore, applyFilters, bold, commandOutput, createDefaultConfig, createFilterOptions, dim, ensureSshHostsKnown, error, execute, executeStreaming, executeSync, extractSshHost, extractUniqueSshHosts, fileExists, filterFromLoopRc, findFileUp, formatDuration, getCommand, getExitCode, getMetaDir, getProjectPaths, getProjectUrl, hasFailures, header, info, isHostKnown, listCommands, loop, normalizeCommand, parseFilterList, parseFilterPattern, projectStatus, readLoopRc, readMetaConfig, removeProject, success, summary, symbols, warning, writeMetaConfig };
213
+ export { type CommandConfig, type CommandConfigObject, CommandConfigObjectSchema, CommandConfigSchema, type CommandHandler, ConfigError, type ConfigFormat, type ExecutorOptions, type ExecutorResult, type FilterOptions, LOOPRC_FILE, type LoopContext, type LoopOptions, type LoopRc, LoopRcSchema, type LoopResult, META_FILE, META_FILE_CANDIDATES, type MetaConfig, type MetaConfigResult, MetaConfigSchema, type ProjectInfo, type ResolvedCommand, addHostKey, addProject, addToGitignore, applyFilters, bold, commandOutput, createDefaultConfig, createFilterOptions, detectFormat, dim, ensureSshHostsKnown, error, execute, executeStreaming, executeSync, extractSshHost, extractUniqueSshHosts, fileExists, filenameForFormat, filterFromLoopRc, findFileUp, findMetaFileUp, formatDuration, getCommand, getExitCode, getMetaDir, getProjectPaths, getProjectUrl, hasFailures, header, info, isHostKnown, listCommands, loop, normalizeCommand, parseFilterList, parseFilterPattern, projectStatus, readLoopRc, readMetaConfig, removeProject, success, summary, symbols, warning, writeMetaConfig };
package/dist/index.js CHANGED
@@ -1,7 +1,8 @@
1
1
  import { spawn, execSync } from 'child_process';
2
2
  import { z } from 'zod';
3
- import { mkdir, appendFile, access, writeFile, readFile, unlink, symlink } from 'fs/promises';
3
+ import { unlink, mkdir, appendFile, access, writeFile, readFile, symlink } from 'fs/promises';
4
4
  import { dirname, join, basename } from 'path';
5
+ import { stringify, parse } from 'yaml';
5
6
  import pc from 'picocolors';
6
7
  import { homedir } from 'os';
7
8
  import { Command } from 'commander';
@@ -206,6 +207,7 @@ var LoopRcSchema = z.object({
206
207
  });
207
208
  var META_FILE = ".gogo";
208
209
  var LOOPRC_FILE = ".looprc";
210
+ var META_FILE_CANDIDATES = [".gogo", ".gogo.yaml", ".gogo.yml"];
209
211
  var ConfigError = class extends Error {
210
212
  constructor(message, path) {
211
213
  super(message);
@@ -213,6 +215,21 @@ var ConfigError = class extends Error {
213
215
  this.name = "ConfigError";
214
216
  }
215
217
  };
218
+ function detectFormat(filePath) {
219
+ if (filePath.endsWith(".yaml") || filePath.endsWith(".yml")) {
220
+ return "yaml";
221
+ }
222
+ return "json";
223
+ }
224
+ function filenameForFormat(format) {
225
+ return format === "yaml" ? ".gogo.yaml" : ".gogo";
226
+ }
227
+ function parseContent(content, format) {
228
+ return format === "yaml" ? parse(content) : JSON.parse(content);
229
+ }
230
+ function serializeContent(data, format) {
231
+ return format === "yaml" ? stringify(data, { indent: 2 }) : JSON.stringify(data, null, 2) + "\n";
232
+ }
216
233
  async function fileExists(path) {
217
234
  try {
218
235
  await access(path);
@@ -235,31 +252,53 @@ async function findFileUp(filename, startDir) {
235
252
  currentDir = parentDir;
236
253
  }
237
254
  }
255
+ async function findMetaFileUp(startDir) {
256
+ let currentDir = startDir;
257
+ while (true) {
258
+ for (const candidate of META_FILE_CANDIDATES) {
259
+ const filePath = join(currentDir, candidate);
260
+ if (await fileExists(filePath)) {
261
+ return filePath;
262
+ }
263
+ }
264
+ const parentDir = dirname(currentDir);
265
+ if (parentDir === currentDir) {
266
+ return null;
267
+ }
268
+ currentDir = parentDir;
269
+ }
270
+ }
238
271
  async function readMetaConfig(cwd) {
239
- const metaPath = await findFileUp(META_FILE, cwd);
272
+ const metaPath = await findMetaFileUp(cwd);
240
273
  if (!metaPath) {
241
274
  throw new ConfigError(
242
275
  `No ${META_FILE} file found. Run 'gogo init' to create one, or navigate to a directory with a ${META_FILE} file.`
243
276
  );
244
277
  }
278
+ const format = detectFormat(metaPath);
245
279
  try {
246
280
  const content = await readFile(metaPath, "utf-8");
247
- const parsed = JSON.parse(content);
248
- return MetaConfigSchema.parse(parsed);
281
+ const parsed = parseContent(content, format);
282
+ const config = MetaConfigSchema.parse(parsed);
283
+ return { config, format, metaDir: dirname(metaPath) };
249
284
  } catch (error2) {
250
285
  if (error2 instanceof SyntaxError) {
251
- throw new ConfigError(`Invalid JSON in ${META_FILE} file`, metaPath);
286
+ throw new ConfigError(`Invalid JSON in config file`, metaPath);
287
+ }
288
+ if (error2 instanceof Error && error2.name === "YAMLParseError") {
289
+ throw new ConfigError(`Invalid YAML in config file`, metaPath);
252
290
  }
253
291
  if (error2 instanceof Error && error2.name === "ZodError") {
254
- throw new ConfigError(`Invalid ${META_FILE} file structure: ${error2.message}`, metaPath);
292
+ throw new ConfigError(`Invalid config file structure: ${error2.message}`, metaPath);
255
293
  }
256
294
  throw error2;
257
295
  }
258
296
  }
259
- async function writeMetaConfig(cwd, config) {
260
- const metaPath = join(cwd, META_FILE);
297
+ async function writeMetaConfig(cwd, config, format = "json") {
298
+ const filename = filenameForFormat(format);
299
+ const metaPath = join(cwd, filename);
261
300
  const validated = MetaConfigSchema.parse(config);
262
- const content = JSON.stringify(validated, null, 2) + "\n";
301
+ const content = serializeContent(validated, format);
263
302
  await writeFile(metaPath, content, "utf-8");
264
303
  }
265
304
  async function readLoopRc(cwd) {
@@ -276,7 +315,7 @@ async function readLoopRc(cwd) {
276
315
  }
277
316
  }
278
317
  function getMetaDir(cwd) {
279
- return findFileUp(META_FILE, cwd).then((path) => path ? dirname(path) : null);
318
+ return findMetaFileUp(cwd).then((path) => path ? dirname(path) : null);
280
319
  }
281
320
  function createDefaultConfig() {
282
321
  return {
@@ -643,22 +682,36 @@ async function ensureSshHostsKnown(urls) {
643
682
  }
644
683
  async function initCommand(options = {}) {
645
684
  const cwd = process.cwd();
646
- const metaPath = join(cwd, META_FILE);
647
- if (await fileExists(metaPath)) {
685
+ const format = options.format === "yaml" ? "yaml" : "json";
686
+ const existingFiles = [];
687
+ for (const candidate of META_FILE_CANDIDATES) {
688
+ const candidatePath = join(cwd, candidate);
689
+ if (await fileExists(candidatePath)) {
690
+ existingFiles.push(candidate);
691
+ }
692
+ }
693
+ if (existingFiles.length > 0) {
648
694
  if (!options.force) {
649
695
  throw new Error(
650
- `${META_FILE} file already exists. Use --force to overwrite.`
696
+ `${existingFiles[0]} file already exists. Use --force to overwrite.`
651
697
  );
652
698
  }
653
- warning(`Overwriting existing ${META_FILE} file`);
699
+ for (const candidate of existingFiles) {
700
+ await unlink(join(cwd, candidate));
701
+ }
702
+ warning(`Overwriting existing config file`);
654
703
  }
655
704
  const config = createDefaultConfig();
656
- await writeMetaConfig(cwd, config);
657
- success(`Created ${META_FILE} file in ${cwd}`);
705
+ await writeMetaConfig(cwd, config, format);
706
+ const filename = filenameForFormat(format);
707
+ success(`Created ${filename} file in ${cwd}`);
658
708
  info("Add projects with: gogo project import <folder> <repo-url>");
659
709
  }
660
710
  function registerInitCommand(program) {
661
- program.command("init").description("Initialize a new gogo-meta repository").option("-f, --force", "Overwrite existing .gogo file").action(async (options) => {
711
+ 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) => {
712
+ if (options.format && !["json", "yaml"].includes(options.format)) {
713
+ throw new Error(`Invalid format "${options.format}". Use "json" or "yaml".`);
714
+ }
662
715
  await initCommand(options);
663
716
  });
664
717
  }
@@ -670,7 +723,7 @@ async function execCommand(command, options = {}) {
670
723
  if (!metaDir) {
671
724
  throw new Error('Not in a gogo-meta repository. Run "gogo init" first.');
672
725
  }
673
- const config = await readMetaConfig(cwd);
726
+ const { config } = await readMetaConfig(cwd);
674
727
  const filterOptions = createFilterOptions(options);
675
728
  info(`Executing: ${bold(command)}`);
676
729
  const results = await loop(command, { config, metaDir }, {
@@ -712,7 +765,7 @@ async function runCommand(name, options = {}) {
712
765
  if (!metaDir) {
713
766
  throw new Error('Not in a gogo-meta repository. Run "gogo init" first.');
714
767
  }
715
- const config = await readMetaConfig(cwd);
768
+ const { config } = await readMetaConfig(cwd);
716
769
  if (options.list || name === void 0) {
717
770
  formatCommandList(listCommands(config));
718
771
  return;
@@ -773,12 +826,12 @@ async function cloneCommand(url, options = {}) {
773
826
  return;
774
827
  }
775
828
  success(`Cloned meta repository to ${repoName}`);
776
- const metaPath = join(targetDir, ".gogo");
777
- if (!await fileExists(metaPath)) {
778
- warning("No .gogo file found in cloned repository");
829
+ const metaPath = await findMetaFileUp(targetDir);
830
+ if (!metaPath) {
831
+ warning("No config file found in cloned repository");
779
832
  return;
780
833
  }
781
- const config = await readMetaConfig(targetDir);
834
+ const { config } = await readMetaConfig(targetDir);
782
835
  const projects = Object.entries(config.projects);
783
836
  if (projects.length === 0) {
784
837
  info("No child repositories defined in .gogo");
@@ -828,7 +881,7 @@ async function updateCommand(options = {}) {
828
881
  if (!metaDir) {
829
882
  throw new Error('Not in a gogo-meta repository. Run "gogo init" first.');
830
883
  }
831
- const config = await readMetaConfig(cwd);
884
+ const { config } = await readMetaConfig(cwd);
832
885
  const filterOptions = createFilterOptions(options);
833
886
  let projectEntries = Object.entries(config.projects);
834
887
  const projectPaths = projectEntries.map(([path]) => path);
@@ -908,7 +961,7 @@ async function statusCommand(options = {}) {
908
961
  if (!metaDir) {
909
962
  throw new Error('Not in a gogo-meta repository. Run "gogo init" first.');
910
963
  }
911
- const config = await readMetaConfig(cwd);
964
+ const { config } = await readMetaConfig(cwd);
912
965
  const filterOptions = createFilterOptions(options);
913
966
  info("Checking git status across repositories...");
914
967
  const results = await loop("git status --short --branch", { config, metaDir }, {
@@ -928,7 +981,7 @@ async function pullCommand(options = {}) {
928
981
  if (!metaDir) {
929
982
  throw new Error('Not in a gogo-meta repository. Run "gogo init" first.');
930
983
  }
931
- const config = await readMetaConfig(cwd);
984
+ const { config } = await readMetaConfig(cwd);
932
985
  const filterOptions = createFilterOptions(options);
933
986
  info("Pulling changes across repositories...");
934
987
  const results = await loop("git pull", { config, metaDir }, {
@@ -949,7 +1002,7 @@ async function pushCommand(options = {}) {
949
1002
  if (!metaDir) {
950
1003
  throw new Error('Not in a gogo-meta repository. Run "gogo init" first.');
951
1004
  }
952
- const config = await readMetaConfig(cwd);
1005
+ const { config } = await readMetaConfig(cwd);
953
1006
  const filterOptions = createFilterOptions(options);
954
1007
  info("Pushing changes across repositories...");
955
1008
  const results = await loop("git push", { config, metaDir }, {
@@ -969,7 +1022,7 @@ async function branchCommand(name, options = {}) {
969
1022
  if (!metaDir) {
970
1023
  throw new Error('Not in a gogo-meta repository. Run "gogo init" first.');
971
1024
  }
972
- const config = await readMetaConfig(cwd);
1025
+ const { config } = await readMetaConfig(cwd);
973
1026
  const filterOptions = createFilterOptions(options);
974
1027
  let command;
975
1028
  let actionMessage;
@@ -1003,7 +1056,7 @@ async function checkoutCommand(branch, options = {}) {
1003
1056
  if (!metaDir) {
1004
1057
  throw new Error('Not in a gogo-meta repository. Run "gogo init" first.');
1005
1058
  }
1006
- const config = await readMetaConfig(cwd);
1059
+ const { config } = await readMetaConfig(cwd);
1007
1060
  const filterOptions = createFilterOptions(options);
1008
1061
  const flag = options.create ? "-b " : "";
1009
1062
  const command = `git checkout ${flag}"${branch}"`;
@@ -1026,7 +1079,7 @@ async function commitCommand(options) {
1026
1079
  if (!metaDir) {
1027
1080
  throw new Error('Not in a gogo-meta repository. Run "gogo init" first.');
1028
1081
  }
1029
- const config = await readMetaConfig(cwd);
1082
+ const { config } = await readMetaConfig(cwd);
1030
1083
  const filterOptions = createFilterOptions(options);
1031
1084
  const escapedMessage = options.message.replace(/"/g, '\\"');
1032
1085
  const command = `git commit -m "${escapedMessage}"`;
@@ -1092,9 +1145,9 @@ async function createCommand(folder, url) {
1092
1145
  if (remoteResult.exitCode !== 0) {
1093
1146
  throw new Error(`Failed to add remote: ${remoteResult.stderr}`);
1094
1147
  }
1095
- const config = await readMetaConfig(metaDir);
1148
+ const { config, format } = await readMetaConfig(metaDir);
1096
1149
  const updatedConfig = addProject(config, folder, url);
1097
- await writeMetaConfig(metaDir, updatedConfig);
1150
+ await writeMetaConfig(metaDir, updatedConfig, format);
1098
1151
  const gitignorePath = join(metaDir, ".gitignore");
1099
1152
  if (await fileExists(gitignorePath)) {
1100
1153
  await appendFile(gitignorePath, `
@@ -1136,9 +1189,9 @@ async function importCommand(folder, url, options = {}) {
1136
1189
  info(`Existing: ${existingUrl}`);
1137
1190
  info(`Provided: ${url}`);
1138
1191
  }
1139
- const config = await readMetaConfig(metaDir);
1192
+ const { config, format } = await readMetaConfig(metaDir);
1140
1193
  const updatedConfig = addProject(config, folder, finalUrl);
1141
- await writeMetaConfig(metaDir, updatedConfig);
1194
+ await writeMetaConfig(metaDir, updatedConfig, format);
1142
1195
  success(`Imported existing project "${folder}"`);
1143
1196
  info(`Repository URL: ${finalUrl}`);
1144
1197
  } else {
@@ -1146,9 +1199,9 @@ async function importCommand(folder, url, options = {}) {
1146
1199
  throw new Error("URL is required when importing a non-existent project");
1147
1200
  }
1148
1201
  if (options.noClone) {
1149
- const config2 = await readMetaConfig(metaDir);
1202
+ const { config: config2, format: format2 } = await readMetaConfig(metaDir);
1150
1203
  const updatedConfig2 = addProject(config2, folder, url);
1151
- await writeMetaConfig(metaDir, updatedConfig2);
1204
+ await writeMetaConfig(metaDir, updatedConfig2, format2);
1152
1205
  const added2 = await addToGitignore(metaDir, folder);
1153
1206
  success(`Registered project "${folder}" (not cloned)`);
1154
1207
  if (added2) {
@@ -1166,9 +1219,9 @@ async function importCommand(folder, url, options = {}) {
1166
1219
  if (cloneResult.exitCode !== 0) {
1167
1220
  throw new Error(`Failed to clone repository: ${cloneResult.stderr}`);
1168
1221
  }
1169
- const config = await readMetaConfig(metaDir);
1222
+ const { config, format } = await readMetaConfig(metaDir);
1170
1223
  const updatedConfig = addProject(config, folder, url);
1171
- await writeMetaConfig(metaDir, updatedConfig);
1224
+ await writeMetaConfig(metaDir, updatedConfig, format);
1172
1225
  success(`Imported project "${folder}"`);
1173
1226
  }
1174
1227
  const added = await addToGitignore(metaDir, folder);
@@ -1195,7 +1248,7 @@ async function installCommand(variant, options = {}) {
1195
1248
  if (!metaDir) {
1196
1249
  throw new Error('Not in a gogo-meta repository. Run "gogo init" first.');
1197
1250
  }
1198
- const config = await readMetaConfig(cwd);
1251
+ const { config } = await readMetaConfig(cwd);
1199
1252
  const filterOptions = createFilterOptions(options);
1200
1253
  const command = `npm ${variant}`;
1201
1254
  info(`Running "${command}" across repositories...`);
@@ -1241,7 +1294,7 @@ async function linkCommand(options = {}) {
1241
1294
  if (!metaDir) {
1242
1295
  throw new Error('Not in a gogo-meta repository. Run "gogo init" first.');
1243
1296
  }
1244
- const config = await readMetaConfig(cwd);
1297
+ const { config } = await readMetaConfig(cwd);
1245
1298
  const filterOptions = createFilterOptions(options);
1246
1299
  let projectPaths = Object.keys(config.projects);
1247
1300
  projectPaths = applyFilters(projectPaths, filterOptions);
@@ -1319,7 +1372,7 @@ async function runCommand2(script, options = {}) {
1319
1372
  if (!metaDir) {
1320
1373
  throw new Error('Not in a gogo-meta repository. Run "gogo init" first.');
1321
1374
  }
1322
- const config = await readMetaConfig(cwd);
1375
+ const { config } = await readMetaConfig(cwd);
1323
1376
  const filterOptions = createFilterOptions(options);
1324
1377
  info(`Running "npm run ${script}" across repositories...`);
1325
1378
  const command = options.ifPresent ? async (dir, _projectPath) => {
@@ -1411,6 +1464,6 @@ async function main() {
1411
1464
  }
1412
1465
  main();
1413
1466
 
1414
- export { CommandConfigObjectSchema, CommandConfigSchema, ConfigError, LOOPRC_FILE, LoopRcSchema, META_FILE, MetaConfigSchema, addHostKey, addProject, addToGitignore, applyFilters, bold, commandOutput, createDefaultConfig, createFilterOptions, createProgram, dim, ensureSshHostsKnown, error, execute, executeStreaming, executeSync, extractSshHost, extractUniqueSshHosts, fileExists, filterFromLoopRc, findFileUp, formatDuration, getCommand, getExitCode, getMetaDir, getProjectPaths, getProjectUrl, hasFailures, header, info, isHostKnown, listCommands, loop, normalizeCommand, parseFilterList, parseFilterPattern, projectStatus, readLoopRc, readMetaConfig, removeProject, success, summary, symbols, warning, writeMetaConfig };
1467
+ export { CommandConfigObjectSchema, CommandConfigSchema, ConfigError, LOOPRC_FILE, LoopRcSchema, META_FILE, META_FILE_CANDIDATES, MetaConfigSchema, addHostKey, addProject, addToGitignore, applyFilters, bold, commandOutput, createDefaultConfig, createFilterOptions, createProgram, detectFormat, dim, ensureSshHostsKnown, error, execute, executeStreaming, executeSync, extractSshHost, extractUniqueSshHosts, fileExists, filenameForFormat, filterFromLoopRc, findFileUp, findMetaFileUp, formatDuration, getCommand, getExitCode, getMetaDir, getProjectPaths, getProjectUrl, hasFailures, header, info, isHostKnown, listCommands, loop, normalizeCommand, parseFilterList, parseFilterPattern, projectStatus, readLoopRc, readMetaConfig, removeProject, success, summary, symbols, warning, writeMetaConfig };
1415
1468
  //# sourceMappingURL=index.js.map
1416
1469
  //# sourceMappingURL=index.js.map