@gogd-core/ggd 0.1.1 → 0.1.4

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/main.js CHANGED
@@ -24,9 +24,9 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
24
24
  ));
25
25
 
26
26
  // apps/ggd/src/main.ts
27
- var import_commander4 = require("commander");
27
+ var import_commander7 = require("commander");
28
28
 
29
- // apps/ggd/src/commands/env.ts
29
+ // apps/ggd/src/commands/shell/env.ts
30
30
  var import_commander = require("commander");
31
31
  function createEnvCommand() {
32
32
  const env = new import_commander.Command("env").description("Manage environments");
@@ -36,7 +36,7 @@ function createEnvCommand() {
36
36
  return env;
37
37
  }
38
38
 
39
- // apps/ggd/src/commands/run.ts
39
+ // apps/ggd/src/commands/shell/run.ts
40
40
  var import_commander2 = require("commander");
41
41
 
42
42
  // apps/ggd/src/core/exec.ts
@@ -69,7 +69,7 @@ function isExecaError(error) {
69
69
  return typeof error === "object" && error !== null && "exitCode" in error;
70
70
  }
71
71
 
72
- // apps/ggd/src/commands/run.ts
72
+ // apps/ggd/src/commands/shell/run.ts
73
73
  function createRunCommand() {
74
74
  const run = new import_commander2.Command("run").description("Execute a shell command").argument("<cmd...>", "Command and arguments to execute").allowExcessArguments(true).action(async (cmdParts) => {
75
75
  const cmd = cmdParts.join(" ");
@@ -81,8 +81,603 @@ function createRunCommand() {
81
81
  return run;
82
82
  }
83
83
 
84
- // apps/ggd/src/commands/completion.ts
84
+ // apps/ggd/src/commands/git/rsh.ts
85
85
  var import_commander3 = require("commander");
86
+ var import_execa2 = __toESM(require("execa"));
87
+ var readline = __toESM(require("readline"));
88
+ async function getCurrentBranch() {
89
+ const { stdout } = await (0, import_execa2.default)("git", ["symbolic-ref", "--short", "HEAD"]);
90
+ return stdout.trim();
91
+ }
92
+ async function getUncommittedChangeCount() {
93
+ const { stdout } = await (0, import_execa2.default)("git", ["status", "--porcelain"]);
94
+ if (!stdout.trim())
95
+ return 0;
96
+ return stdout.trim().split("\n").length;
97
+ }
98
+ function confirm(message) {
99
+ const rl = readline.createInterface({
100
+ input: process.stdin,
101
+ output: process.stderr
102
+ });
103
+ return new Promise((resolve) => {
104
+ rl.question(message, (answer) => {
105
+ rl.close();
106
+ const normalized = (answer || "Y").trim().toLowerCase();
107
+ resolve(normalized === "y" || normalized === "yes");
108
+ });
109
+ });
110
+ }
111
+ async function resetHard(branch) {
112
+ try {
113
+ await (0, import_execa2.default)("git", ["fetch", "origin", branch], { stdio: "inherit" });
114
+ await (0, import_execa2.default)("git", ["reset", "--hard", `origin/${branch}`], { stdio: "inherit" });
115
+ return 0;
116
+ } catch {
117
+ return 1;
118
+ }
119
+ }
120
+ function createRshCommand() {
121
+ const rsh = new import_commander3.Command("rsh").description("Reset current branch to origin (git reset --hard)").option("-y, --yes", "Skip confirmation prompt").action(async (opts) => {
122
+ try {
123
+ const branch = await getCurrentBranch();
124
+ const changeCount = await getUncommittedChangeCount();
125
+ console.error(`\x1B[36mBranch:\x1B[0m ${branch}`);
126
+ if (changeCount > 0) {
127
+ console.error(`\x1B[33mUncommitted changes: ${changeCount} file(s) will be lost\x1B[0m`);
128
+ }
129
+ console.error(`\x1B[31mTarget: git reset --hard origin/${branch}\x1B[0m`);
130
+ if (!opts.yes) {
131
+ const ok = await confirm("\nProceed? (Y/n): ");
132
+ if (!ok) {
133
+ console.error("Reset aborted.");
134
+ return;
135
+ }
136
+ }
137
+ const exitCode = await resetHard(branch);
138
+ if (exitCode !== 0) {
139
+ process.exitCode = exitCode;
140
+ }
141
+ } catch (error) {
142
+ const message = error instanceof Error ? error.message : String(error);
143
+ console.error(`\x1B[31mError: ${message}\x1B[0m`);
144
+ process.exitCode = 1;
145
+ }
146
+ });
147
+ return rsh;
148
+ }
149
+
150
+ // apps/ggd/src/commands/git/mt.ts
151
+ var import_commander4 = require("commander");
152
+ var import_execa3 = __toESM(require("execa"));
153
+ async function localBranchExists(branch) {
154
+ try {
155
+ await (0, import_execa3.default)("git", ["show-ref", "--verify", "--quiet", `refs/heads/${branch}`]);
156
+ return true;
157
+ } catch {
158
+ return false;
159
+ }
160
+ }
161
+ async function deleteLocalBranch(branch) {
162
+ await (0, import_execa3.default)("git", ["branch", "-D", branch], { stdio: "inherit" });
163
+ }
164
+ async function switchBranch(branch) {
165
+ await (0, import_execa3.default)("git", ["switch", branch], { stdio: "inherit" });
166
+ }
167
+ async function mergeBranch(source) {
168
+ try {
169
+ await (0, import_execa3.default)("git", ["merge", "--no-edit", source], { stdio: "inherit" });
170
+ return 0;
171
+ } catch {
172
+ return 1;
173
+ }
174
+ }
175
+ async function pushBranch(branch) {
176
+ try {
177
+ await (0, import_execa3.default)("git", ["push", "origin", branch], { stdio: "inherit" });
178
+ return 0;
179
+ } catch {
180
+ return 1;
181
+ }
182
+ }
183
+ function createMtCommand() {
184
+ const mt = new import_commander4.Command("mt").description("Merge current branch into target branch").argument("<branch>", "Target branch to merge into").option("-y, --yes", "Skip confirmation prompt").action(async (targetBranch, opts) => {
185
+ try {
186
+ const sourceBranch = await getCurrentBranch();
187
+ if (sourceBranch === targetBranch) {
188
+ console.error("\x1B[31mError: Cannot merge a branch into itself\x1B[0m");
189
+ process.exitCode = 1;
190
+ return;
191
+ }
192
+ console.error(`\x1B[36mSource:\x1B[0m ${sourceBranch}`);
193
+ console.error(`\x1B[36mTarget:\x1B[0m ${targetBranch}`);
194
+ console.error(`\x1B[33mWill: delete local ${targetBranch} (if exists), switch to it, merge ${sourceBranch}\x1B[0m`);
195
+ if (!opts.yes) {
196
+ const ok = await confirm(`
197
+ Merge ${sourceBranch} into ${targetBranch}? (Y/n): `);
198
+ if (!ok) {
199
+ console.error("Merge aborted.");
200
+ return;
201
+ }
202
+ }
203
+ if (await localBranchExists(targetBranch)) {
204
+ console.error(`\x1B[33mDeleting local ${targetBranch}...\x1B[0m`);
205
+ await deleteLocalBranch(targetBranch);
206
+ }
207
+ await switchBranch(targetBranch);
208
+ const exitCode = await mergeBranch(sourceBranch);
209
+ if (exitCode !== 0) {
210
+ console.error(`\x1B[31mMerge failed. You are now on ${targetBranch}.\x1B[0m`);
211
+ process.exitCode = exitCode;
212
+ return;
213
+ }
214
+ console.error(`\x1B[32mMerge successful!\x1B[0m`);
215
+ const shouldPush = await confirm(`Push ${targetBranch} to origin? (Y/n): `);
216
+ if (shouldPush) {
217
+ const pushExit = await pushBranch(targetBranch);
218
+ if (pushExit !== 0) {
219
+ console.error(`\x1B[31mPush failed.\x1B[0m`);
220
+ process.exitCode = pushExit;
221
+ }
222
+ }
223
+ } catch (error) {
224
+ const message = error instanceof Error ? error.message : String(error);
225
+ console.error(`\x1B[31mError: ${message}\x1B[0m`);
226
+ process.exitCode = 1;
227
+ }
228
+ });
229
+ return mt;
230
+ }
231
+
232
+ // apps/ggd/src/commands/jenkins/jenkins.ts
233
+ var import_commander5 = require("commander");
234
+ var fs2 = __toESM(require("fs"));
235
+ var path = __toESM(require("path"));
236
+ var os = __toESM(require("os"));
237
+ var readline2 = __toESM(require("readline"));
238
+
239
+ // apps/ggd/src/commands/jenkins/jenkins-build.ts
240
+ var http = __toESM(require("http"));
241
+ var https = __toESM(require("https"));
242
+ var import_child_process = require("child_process");
243
+ function findTarget(name, targets) {
244
+ return targets.find((t) => t.name === name);
245
+ }
246
+ function listTargetNames(targets) {
247
+ return targets.map((t) => t.name);
248
+ }
249
+ function parseOverrides(args) {
250
+ const overrides = {};
251
+ for (const arg of args) {
252
+ const match = arg.match(/^--([^=]+)=(.*)$/);
253
+ if (match && match[1] !== void 0) {
254
+ overrides[match[1]] = match[2] ?? "";
255
+ }
256
+ }
257
+ return overrides;
258
+ }
259
+ function mergeParams(defaults, overrides) {
260
+ const merged = { ...defaults };
261
+ for (const [key, value] of Object.entries(overrides)) {
262
+ merged[key] = value;
263
+ }
264
+ return merged;
265
+ }
266
+ function resolveJobPath(target, config) {
267
+ if (target.jobPathOverride)
268
+ return target.jobPathOverride;
269
+ return config.jobPaths[target.jobPathKey] ?? "";
270
+ }
271
+ function jenkinsRequest(url, method, user, token, headers = {}, body) {
272
+ return new Promise((resolve, reject) => {
273
+ const parsedUrl = new URL(url);
274
+ const isHttps = parsedUrl.protocol === "https:";
275
+ const lib = isHttps ? https : http;
276
+ const auth = Buffer.from(`${user}:${token}`).toString("base64");
277
+ const reqHeaders = {
278
+ Authorization: `Basic ${auth}`,
279
+ ...headers
280
+ };
281
+ if (body) {
282
+ reqHeaders["Content-Type"] = "application/x-www-form-urlencoded";
283
+ reqHeaders["Content-Length"] = Buffer.byteLength(body).toString();
284
+ }
285
+ const req = lib.request(
286
+ {
287
+ hostname: parsedUrl.hostname,
288
+ port: parsedUrl.port || (isHttps ? 443 : 80),
289
+ path: parsedUrl.pathname + parsedUrl.search,
290
+ method,
291
+ headers: reqHeaders
292
+ },
293
+ (res) => {
294
+ let data = "";
295
+ res.on("data", (chunk) => {
296
+ data += chunk.toString();
297
+ });
298
+ res.on("end", () => {
299
+ resolve({
300
+ statusCode: res.statusCode ?? 0,
301
+ headers: res.headers,
302
+ body: data
303
+ });
304
+ });
305
+ }
306
+ );
307
+ req.on("error", reject);
308
+ if (body)
309
+ req.write(body);
310
+ req.end();
311
+ });
312
+ }
313
+ async function fetchCrumb(config) {
314
+ try {
315
+ const res = await jenkinsRequest(
316
+ `${config.url}/crumbIssuer/api/json`,
317
+ "GET",
318
+ config.user,
319
+ config.token
320
+ );
321
+ const json = JSON.parse(res.body);
322
+ return json.crumb ?? null;
323
+ } catch {
324
+ return null;
325
+ }
326
+ }
327
+ async function triggerBuild(config, jobPath, params) {
328
+ const crumb = await fetchCrumb(config);
329
+ const formBody = Object.entries(params).map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`).join("&");
330
+ const headers = {};
331
+ if (crumb) {
332
+ headers["Jenkins-Crumb"] = crumb;
333
+ }
334
+ const res = await jenkinsRequest(
335
+ `${config.url}/job/${jobPath}/buildWithParameters`,
336
+ "POST",
337
+ config.user,
338
+ config.token,
339
+ headers,
340
+ formBody
341
+ );
342
+ const location = res.headers["location"];
343
+ if (typeof location === "string" && location) {
344
+ return location;
345
+ }
346
+ return null;
347
+ }
348
+ async function waitForBuild(config, queueUrl) {
349
+ const maxAttempts = 60;
350
+ for (let i = 0; i < maxAttempts; i++) {
351
+ try {
352
+ const res = await jenkinsRequest(
353
+ `${queueUrl}api/json`,
354
+ "GET",
355
+ config.user,
356
+ config.token
357
+ );
358
+ const json = JSON.parse(res.body);
359
+ if (json.executable?.url) {
360
+ return json.executable.url;
361
+ }
362
+ const why = json.why ?? "Waiting...";
363
+ const queueId = json.id ?? "?";
364
+ const blocked = json.blocked ?? false;
365
+ process.stderr.write(`\r\x1B[33mQueued #${queueId} (blocked: ${blocked}) ${why}\x1B[0m `);
366
+ } catch {
367
+ }
368
+ await sleep(2e3);
369
+ }
370
+ return null;
371
+ }
372
+ async function watchBuild(config, buildUrl) {
373
+ let lastLines = "";
374
+ const maxAttempts = 360;
375
+ for (let i = 0; i < maxAttempts; i++) {
376
+ try {
377
+ const buildRes = await jenkinsRequest(
378
+ `${buildUrl}api/json`,
379
+ "GET",
380
+ config.user,
381
+ config.token
382
+ );
383
+ const json = JSON.parse(buildRes.body);
384
+ const consoleRes = await jenkinsRequest(
385
+ `${buildUrl}consoleText`,
386
+ "GET",
387
+ config.user,
388
+ config.token
389
+ );
390
+ const lines = consoleRes.body.trim().split("\n").slice(-5).join("\n");
391
+ if (lines !== lastLines) {
392
+ console.error(`
393
+ ${lines}`);
394
+ lastLines = lines;
395
+ }
396
+ if (json.result && json.result !== "null") {
397
+ const buildNum = json.number ?? "?";
398
+ return `${json.result} (#${buildNum})`;
399
+ }
400
+ } catch {
401
+ }
402
+ await sleep(5e3);
403
+ }
404
+ return "TIMEOUT";
405
+ }
406
+ function sleep(ms) {
407
+ return new Promise((resolve) => setTimeout(resolve, ms));
408
+ }
409
+ function notify(title, message) {
410
+ try {
411
+ if (process.platform === "win32") {
412
+ (0, import_child_process.execSync)(
413
+ `powershell -Command "New-BurntToastNotification -Text '${title}','${message}'" 2>nul || powershell -Command "[void][System.Reflection.Assembly]::LoadWithPartialName('System.Windows.Forms'); $n = New-Object System.Windows.Forms.NotifyIcon; $n.Icon = [System.Drawing.SystemIcons]::Information; $n.Visible = $true; $n.ShowBalloonTip(5000, '${title}', '${message}', 'Info')"`,
414
+ { stdio: "ignore" }
415
+ );
416
+ } else if (process.platform === "darwin") {
417
+ (0, import_child_process.execSync)(
418
+ `osascript -e 'display notification "${message}" with title "${title}"'`,
419
+ { stdio: "ignore" }
420
+ );
421
+ } else {
422
+ (0, import_child_process.execSync)(`notify-send "${title}" "${message}"`, { stdio: "ignore" });
423
+ }
424
+ } catch {
425
+ }
426
+ }
427
+
428
+ // apps/ggd/src/commands/jenkins/jenkins-presets.ts
429
+ var fs = __toESM(require("fs"));
430
+ function loadPresetFile(filePath) {
431
+ if (!fs.existsSync(filePath)) {
432
+ throw new Error(`Preset file not found: ${filePath}`);
433
+ }
434
+ let raw;
435
+ try {
436
+ raw = fs.readFileSync(filePath, "utf8");
437
+ } catch {
438
+ throw new Error(`Failed to read preset file: ${filePath}`);
439
+ }
440
+ let parsed;
441
+ try {
442
+ parsed = JSON.parse(raw);
443
+ } catch {
444
+ throw new Error(`Invalid JSON in preset file: ${filePath}`);
445
+ }
446
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
447
+ throw new Error(`Preset file must contain a JSON object: ${filePath}`);
448
+ }
449
+ const obj = parsed;
450
+ if (obj["url"] !== void 0 && typeof obj["url"] !== "string") {
451
+ throw new Error(`Preset "url" must be a string`);
452
+ }
453
+ if (obj["environments"] !== void 0) {
454
+ if (typeof obj["environments"] !== "object" || obj["environments"] === null || Array.isArray(obj["environments"])) {
455
+ throw new Error(`Preset "environments" must be an object`);
456
+ }
457
+ }
458
+ if (!Array.isArray(obj["targets"])) {
459
+ throw new Error(`Preset "targets" must be an array`);
460
+ }
461
+ for (const target of obj["targets"]) {
462
+ if (typeof target !== "object" || target === null || Array.isArray(target)) {
463
+ throw new Error(`Each target must be an object`);
464
+ }
465
+ const t = target;
466
+ if (typeof t["name"] !== "string" || typeof t["displayName"] !== "string") {
467
+ throw new Error(`Each target must have "name" and "displayName" strings`);
468
+ }
469
+ if (typeof t["jobPathKey"] !== "string") {
470
+ throw new Error(`Each target must have a "jobPathKey" string`);
471
+ }
472
+ if (typeof t["defaults"] !== "object" || t["defaults"] === null || Array.isArray(t["defaults"])) {
473
+ throw new Error(`Each target must have a "defaults" object`);
474
+ }
475
+ }
476
+ return parsed;
477
+ }
478
+
479
+ // apps/ggd/src/commands/jenkins/jenkins.ts
480
+ function getConfigDir() {
481
+ return path.join(os.homedir(), ".ggd");
482
+ }
483
+ function getConfigPath() {
484
+ return path.join(getConfigDir(), "jenkins.json");
485
+ }
486
+ function readConfig() {
487
+ const configPath = getConfigPath();
488
+ try {
489
+ if (!fs2.existsSync(configPath))
490
+ return null;
491
+ const raw = fs2.readFileSync(configPath, "utf8");
492
+ return JSON.parse(raw);
493
+ } catch {
494
+ return null;
495
+ }
496
+ }
497
+ function writeConfig(config) {
498
+ const dir = getConfigDir();
499
+ if (!fs2.existsSync(dir)) {
500
+ fs2.mkdirSync(dir, { recursive: true });
501
+ }
502
+ fs2.writeFileSync(getConfigPath(), JSON.stringify(config, null, 2), "utf8");
503
+ }
504
+ function prompt(question, defaultValue) {
505
+ const rl = readline2.createInterface({
506
+ input: process.stdin,
507
+ output: process.stderr
508
+ });
509
+ const display = defaultValue ? `${question} [${defaultValue}]: ` : `${question}: `;
510
+ return new Promise((resolve) => {
511
+ rl.question(display, (answer) => {
512
+ rl.close();
513
+ resolve(answer.trim() || defaultValue || "");
514
+ });
515
+ });
516
+ }
517
+ function maskToken(token) {
518
+ if (token.length <= 8)
519
+ return "****";
520
+ return `${token.slice(0, 4)}...${token.slice(-4)}`;
521
+ }
522
+ async function runSetup(presetPath) {
523
+ const existing = readConfig();
524
+ console.error("\x1B[36mJenkins CI Setup\x1B[0m");
525
+ console.error("\u2500".repeat(40));
526
+ if (existing) {
527
+ console.error(`\x1B[33mExisting config found. Press Enter to keep current values.\x1B[0m
528
+ `);
529
+ }
530
+ let presetUrl = "";
531
+ let presetJobPaths = {};
532
+ let presetTargets = [];
533
+ if (presetPath) {
534
+ console.error(`\x1B[36mLoading preset from ${presetPath}\x1B[0m
535
+ `);
536
+ const preset = loadPresetFile(presetPath);
537
+ presetUrl = preset.url ?? "";
538
+ presetTargets = preset.targets;
539
+ if (preset.environments) {
540
+ const envNames = Object.keys(preset.environments);
541
+ console.error(`Available environments: ${envNames.join(", ")}`);
542
+ const envChoice = await prompt("Environment", envNames[0]);
543
+ presetJobPaths = preset.environments[envChoice] ?? {};
544
+ if (!preset.environments[envChoice]) {
545
+ console.error(`\x1B[31mUnknown environment: ${envChoice}. No job paths loaded.\x1B[0m`);
546
+ }
547
+ }
548
+ }
549
+ const url = await prompt("Jenkins URL", existing?.url || presetUrl || "");
550
+ const user = await prompt("API User", existing?.user);
551
+ const token = await prompt(
552
+ `API Token${existing?.token ? ` (current: ${maskToken(existing.token)})` : ""}`,
553
+ existing?.token
554
+ );
555
+ let jobPaths;
556
+ if (Object.keys(presetJobPaths).length > 0) {
557
+ jobPaths = presetJobPaths;
558
+ } else if (existing?.jobPaths && Object.keys(existing.jobPaths).length > 0) {
559
+ jobPaths = existing.jobPaths;
560
+ } else {
561
+ const uiPath = await prompt("UI job path", "");
562
+ const apiPath = await prompt("API job path", "");
563
+ const lambdaPath = await prompt("Lambda job path", "");
564
+ jobPaths = {};
565
+ if (uiPath)
566
+ jobPaths["ui"] = uiPath;
567
+ if (apiPath)
568
+ jobPaths["api"] = apiPath;
569
+ if (lambdaPath)
570
+ jobPaths["lambda"] = lambdaPath;
571
+ }
572
+ const targets = presetTargets.length > 0 ? presetTargets : existing?.targets ?? [];
573
+ const config = {
574
+ url,
575
+ user,
576
+ token,
577
+ jobPaths,
578
+ targets
579
+ };
580
+ writeConfig(config);
581
+ console.error(`
582
+ \x1B[32mConfig saved to ${getConfigPath()}\x1B[0m`);
583
+ console.error(` URL: ${config.url}`);
584
+ console.error(` User: ${config.user}`);
585
+ console.error(` Token: ${maskToken(config.token)}`);
586
+ console.error(` Targets: ${config.targets.length} build target(s)`);
587
+ console.error(` Job paths:`);
588
+ for (const [key, value] of Object.entries(config.jobPaths)) {
589
+ console.error(` ${key}: ${value}`);
590
+ }
591
+ return config;
592
+ }
593
+ async function ensureConfig() {
594
+ let config = readConfig();
595
+ if (config)
596
+ return config;
597
+ console.error("\x1B[33mNo Jenkins config found. Starting setup...\x1B[0m\n");
598
+ config = await runSetup();
599
+ return config;
600
+ }
601
+ function createJenkinsCommand() {
602
+ const jenkins = new import_commander5.Command("jenkins").description("Jenkins CI integration");
603
+ jenkins.command("setup").description("Configure Jenkins connection and environment").option("-p, --preset <path>", "Load configuration from a preset JSON file").action(async (opts) => {
604
+ await runSetup(opts.preset);
605
+ });
606
+ jenkins.command("build").description("Trigger a Jenkins build").argument("[target]", "Build target name").option("-d, --detach", "Run in detached mode (don't watch build)").allowUnknownOption(true).action(async (targetName, opts, cmd) => {
607
+ const config = await ensureConfig();
608
+ if (!config)
609
+ return;
610
+ const targets = config.targets;
611
+ if (!targetName) {
612
+ if (targets.length === 0) {
613
+ console.error("\x1B[33mNo build targets configured.\x1B[0m");
614
+ console.error("Add targets via: ggd jenkins setup --preset <file>");
615
+ return;
616
+ }
617
+ console.error("\x1B[36mAvailable build targets:\x1B[0m");
618
+ for (const t of targets) {
619
+ console.error(` ${t.name.padEnd(20)} ${t.displayName}`);
620
+ }
621
+ console.error(`
622
+ Usage: ggd jenkins build <target> [--KEY=value ...]`);
623
+ return;
624
+ }
625
+ const target = findTarget(targetName, targets);
626
+ if (!target) {
627
+ console.error(`\x1B[31mUnknown target: ${targetName}\x1B[0m`);
628
+ console.error(`Available: ${listTargetNames(targets).join(", ")}`);
629
+ process.exitCode = 1;
630
+ return;
631
+ }
632
+ const rawArgs = cmd.args.slice(1);
633
+ const overrides = parseOverrides(rawArgs);
634
+ const params = mergeParams(target.defaults, overrides);
635
+ const jobPath = resolveJobPath(target, config);
636
+ console.error(`\x1B[36mTarget:\x1B[0m ${target.displayName}`);
637
+ console.error(`\x1B[36mJob:\x1B[0m ${config.url}/job/${jobPath}`);
638
+ console.error(`\x1B[36mParams:\x1B[0m`);
639
+ for (const [k, v] of Object.entries(params)) {
640
+ console.error(` ${k}=${v || "<empty>"}`);
641
+ }
642
+ console.error("");
643
+ console.error("\x1B[33mTriggering build...\x1B[0m");
644
+ const queueUrl = await triggerBuild(config, jobPath, params);
645
+ if (!queueUrl) {
646
+ console.error("\x1B[31mFailed to trigger build. Check Jenkins URL / API token.\x1B[0m");
647
+ process.exitCode = 1;
648
+ return;
649
+ }
650
+ console.error(`\x1B[32mQueued!\x1B[0m ${queueUrl}`);
651
+ if (opts.detach) {
652
+ return;
653
+ }
654
+ const buildUrl = await waitForBuild(config, queueUrl);
655
+ if (!buildUrl) {
656
+ console.error("\n\x1B[31mTimed out waiting for build to start.\x1B[0m");
657
+ process.exitCode = 1;
658
+ return;
659
+ }
660
+ console.error(`
661
+ \x1B[32mBuild started:\x1B[0m ${buildUrl}`);
662
+ const result = await watchBuild(config, buildUrl);
663
+ console.error(`
664
+ \x1B[36mBuild result: ${result}\x1B[0m`);
665
+ notify("Jenkins Build", `${target.displayName}: ${result}`);
666
+ if (!result.startsWith("SUCCESS")) {
667
+ process.exitCode = 1;
668
+ }
669
+ });
670
+ jenkins.command("config").description("Show current Jenkins configuration").action(async () => {
671
+ const config = await ensureConfig();
672
+ if (!config)
673
+ return;
674
+ console.log(JSON.stringify(config, null, 2));
675
+ });
676
+ return jenkins;
677
+ }
678
+
679
+ // apps/ggd/src/commands/shell/completion.ts
680
+ var import_commander6 = require("commander");
86
681
  function detectShell() {
87
682
  const shell = process.env["SHELL"] || "";
88
683
  if (shell.includes("zsh"))
@@ -152,7 +747,7 @@ function getCompletions(program2, args) {
152
747
  }
153
748
  var VALID_SHELLS = ["bash", "zsh", "powershell"];
154
749
  function createCompletionCommand() {
155
- const completion = new import_commander3.Command("completion").description("Output shell completion script").argument("[shell]", "Shell type: bash, zsh, powershell (auto-detected if omitted)").action((shell) => {
750
+ const completion = new import_commander6.Command("completion").description("Output shell completion script").argument("[shell]", "Shell type: bash, zsh, powershell (auto-detected if omitted)").action((shell) => {
156
751
  const resolved = shell ? validateShell(shell) : detectShell();
157
752
  console.log(getCompletionScript(resolved));
158
753
  });
@@ -168,10 +763,13 @@ function validateShell(shell) {
168
763
  }
169
764
 
170
765
  // apps/ggd/src/main.ts
171
- var program = new import_commander4.Command();
172
- program.name("ggd").description("GoGoodDev workspace CLI").version("0.1.0");
766
+ var program = new import_commander7.Command();
767
+ program.name("ggd").description("GoGoodDev workspace CLI").version("0.1.3");
173
768
  program.addCommand(createEnvCommand());
174
769
  program.addCommand(createRunCommand());
770
+ program.addCommand(createRshCommand());
771
+ program.addCommand(createMtCommand());
772
+ program.addCommand(createJenkinsCommand());
175
773
  program.addCommand(createCompletionCommand());
176
774
  if (process.argv.includes("--get-completions")) {
177
775
  const args = process.argv.slice(process.argv.indexOf("--get-completions") + 1);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gogd-core/ggd",
3
- "version": "0.1.1",
3
+ "version": "0.1.4",
4
4
  "dependencies": {
5
5
  "commander": "12.1.0",
6
6
  "execa": "5.1.1"
package/postinstall.js CHANGED
@@ -43,7 +43,7 @@ var fs = __toESM(require("fs"));
43
43
  var path = __toESM(require("path"));
44
44
  var os = __toESM(require("os"));
45
45
 
46
- // apps/ggd/src/commands/completion.ts
46
+ // apps/ggd/src/commands/shell/completion.ts
47
47
  var import_commander = require("commander");
48
48
  function generateBashCompletion() {
49
49
  return `###-begin-ggd-completions-###
@@ -0,0 +1,32 @@
1
+ /**
2
+ * `ggd mt` command — merge current branch into a target branch.
3
+ *
4
+ * Switches to target branch (re-creating from origin if it exists locally),
5
+ * then merges the source branch into it with confirmation.
6
+ */
7
+ import { Command } from 'commander';
8
+ /**
9
+ * Checks if a local branch exists.
10
+ */
11
+ export declare function localBranchExists(branch: string): Promise<boolean>;
12
+ /**
13
+ * Deletes a local branch.
14
+ */
15
+ export declare function deleteLocalBranch(branch: string): Promise<void>;
16
+ /**
17
+ * Switches to a branch.
18
+ */
19
+ export declare function switchBranch(branch: string): Promise<void>;
20
+ /**
21
+ * Merges a source branch into the current branch.
22
+ */
23
+ export declare function mergeBranch(source: string): Promise<number>;
24
+ /**
25
+ * Pushes the current branch to origin.
26
+ */
27
+ export declare function pushBranch(branch: string): Promise<number>;
28
+ /**
29
+ * Creates the `mt` command.
30
+ * Merges current branch into a target branch with confirmation.
31
+ */
32
+ export declare function createMtCommand(): Command;
@@ -0,0 +1,27 @@
1
+ /**
2
+ * `ggd rsh` command — safe git reset --hard to origin/<current-branch>.
3
+ *
4
+ * Shows branch name and uncommitted change count before prompting for confirmation.
5
+ */
6
+ import { Command } from 'commander';
7
+ /**
8
+ * Returns the current git branch name.
9
+ */
10
+ export declare function getCurrentBranch(): Promise<string>;
11
+ /**
12
+ * Returns the number of uncommitted changes (staged + unstaged + untracked).
13
+ */
14
+ export declare function getUncommittedChangeCount(): Promise<number>;
15
+ /**
16
+ * Prompts the user with a yes/no question. Returns true for yes.
17
+ */
18
+ export declare function confirm(message: string): Promise<boolean>;
19
+ /**
20
+ * Executes git reset --hard origin/<branch>.
21
+ */
22
+ export declare function resetHard(branch: string): Promise<number>;
23
+ /**
24
+ * Creates the `rsh` command.
25
+ * Resets current branch to origin/<branch> with confirmation.
26
+ */
27
+ export declare function createRshCommand(): Command;
@@ -1,3 +1,6 @@
1
- export { createEnvCommand } from './env';
2
- export { createRunCommand } from './run';
3
- export { createCompletionCommand, getCompletions } from './completion';
1
+ export { createEnvCommand } from './shell/env';
2
+ export { createRunCommand } from './shell/run';
3
+ export { createRshCommand } from './git/rsh';
4
+ export { createMtCommand } from './git/mt';
5
+ export { createJenkinsCommand } from './jenkins/jenkins';
6
+ export { createCompletionCommand, getCompletions } from './shell/completion';
@@ -0,0 +1,65 @@
1
+ /**
2
+ * `ggd jenkins build` — trigger Jenkins builds via REST API.
3
+ *
4
+ * All build targets are loaded from user config (~/.ggd/jenkins.json),
5
+ * populated via `ggd jenkins setup --preset <file>`.
6
+ */
7
+ import { JenkinsConfig } from './jenkins';
8
+ export interface BuildTarget {
9
+ name: string;
10
+ displayName: string;
11
+ jobPathKey: string;
12
+ /** Custom job path override (for targets with unique paths) */
13
+ jobPathOverride?: string;
14
+ defaults: Record<string, string>;
15
+ }
16
+ /**
17
+ * Finds a build target by name from the given targets list.
18
+ */
19
+ export declare function findTarget(name: string, targets: BuildTarget[]): BuildTarget | undefined;
20
+ /**
21
+ * Lists all available build target names from the given targets list.
22
+ */
23
+ export declare function listTargetNames(targets: BuildTarget[]): string[];
24
+ /**
25
+ * Parses --KEY=value args into an overrides map.
26
+ */
27
+ export declare function parseOverrides(args: string[]): Record<string, string>;
28
+ /**
29
+ * Merges defaults with overrides to produce final params.
30
+ */
31
+ export declare function mergeParams(defaults: Record<string, string>, overrides: Record<string, string>): Record<string, string>;
32
+ /**
33
+ * Resolves the job path for a target.
34
+ */
35
+ export declare function resolveJobPath(target: BuildTarget, config: JenkinsConfig): string;
36
+ interface JenkinsHttpResponse {
37
+ statusCode: number;
38
+ headers: Record<string, string | string[] | undefined>;
39
+ body: string;
40
+ }
41
+ /**
42
+ * Makes an HTTP/HTTPS request to Jenkins.
43
+ */
44
+ export declare function jenkinsRequest(url: string, method: string, user: string, token: string, headers?: Record<string, string>, body?: string): Promise<JenkinsHttpResponse>;
45
+ /**
46
+ * Fetches the Jenkins CSRF crumb token.
47
+ */
48
+ export declare function fetchCrumb(config: JenkinsConfig): Promise<string | null>;
49
+ /**
50
+ * Triggers a Jenkins build. Returns the queue URL or null on failure.
51
+ */
52
+ export declare function triggerBuild(config: JenkinsConfig, jobPath: string, params: Record<string, string>): Promise<string | null>;
53
+ /**
54
+ * Polls the queue URL until the build starts. Returns the build URL.
55
+ */
56
+ export declare function waitForBuild(config: JenkinsConfig, queueUrl: string): Promise<string | null>;
57
+ /**
58
+ * Watches a build until it completes. Returns the final status.
59
+ */
60
+ export declare function watchBuild(config: JenkinsConfig, buildUrl: string): Promise<string>;
61
+ /**
62
+ * Sends a desktop notification. Best-effort — silently ignores failures.
63
+ */
64
+ export declare function notify(title: string, message: string): void;
65
+ export {};
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Jenkins preset file loader.
3
+ *
4
+ * Preset files are external JSON files that contain Jenkins configuration
5
+ * for a specific team/project. They can include environment-specific job paths
6
+ * and build targets.
7
+ */
8
+ import { BuildTarget } from './jenkins-build';
9
+ import { JenkinsJobPaths } from './jenkins';
10
+ export interface JenkinsPreset {
11
+ url?: string;
12
+ environments?: Record<string, JenkinsJobPaths>;
13
+ targets: BuildTarget[];
14
+ }
15
+ /**
16
+ * Loads and validates a preset file from disk.
17
+ * Throws on missing file or invalid JSON structure.
18
+ */
19
+ export declare function loadPresetFile(filePath: string): JenkinsPreset;
@@ -0,0 +1,54 @@
1
+ /**
2
+ * `ggd jenkins` command — Jenkins CI integration.
3
+ *
4
+ * Config is stored at ~/.ggd/jenkins.json.
5
+ * Use `ggd jenkins setup --preset <file>` to load a team preset.
6
+ */
7
+ import { Command } from 'commander';
8
+ import { BuildTarget } from './jenkins-build';
9
+ export interface JenkinsJobPaths {
10
+ [key: string]: string;
11
+ }
12
+ export interface JenkinsConfig {
13
+ url: string;
14
+ user: string;
15
+ token: string;
16
+ jobPaths: JenkinsJobPaths;
17
+ targets: BuildTarget[];
18
+ }
19
+ /**
20
+ * Returns the path to the ggd config directory (~/.ggd).
21
+ */
22
+ export declare function getConfigDir(): string;
23
+ /**
24
+ * Returns the path to the Jenkins config file.
25
+ */
26
+ export declare function getConfigPath(): string;
27
+ /**
28
+ * Reads the Jenkins config from disk. Returns null if not found.
29
+ */
30
+ export declare function readConfig(): JenkinsConfig | null;
31
+ /**
32
+ * Writes the Jenkins config to disk.
33
+ */
34
+ export declare function writeConfig(config: JenkinsConfig): void;
35
+ /**
36
+ * Prompts the user for input with a default value.
37
+ */
38
+ export declare function prompt(question: string, defaultValue?: string): Promise<string>;
39
+ /**
40
+ * Masks a token for display (shows first 4 and last 4 chars).
41
+ */
42
+ export declare function maskToken(token: string): string;
43
+ /**
44
+ * Runs the interactive setup flow.
45
+ */
46
+ export declare function runSetup(presetPath?: string): Promise<JenkinsConfig>;
47
+ /**
48
+ * Ensures Jenkins config exists. If not, runs interactive setup.
49
+ */
50
+ export declare function ensureConfig(): Promise<JenkinsConfig | null>;
51
+ /**
52
+ * Creates the `jenkins` command with subcommands.
53
+ */
54
+ export declare function createJenkinsCommand(): Command;
File without changes
File without changes