gh-stack 0.17.2 → 0.19.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.
@@ -0,0 +1,133 @@
1
+ // gh-stack config — Show the resolved flag defaults and where each came from.
2
+ //
3
+ // Answers "why did this open as a draft?" in one command. Read-only: it never
4
+ // writes a config file (edit the TOML directly).
5
+ import pc from "picocolors";
6
+ import {
7
+ SETTINGS,
8
+ describeSource,
9
+ loadConfigLayers,
10
+ resolveSetting,
11
+ type ConfigFile,
12
+ type ConfigLayers,
13
+ type ConfigSource,
14
+ type SettingName,
15
+ ConfigError,
16
+ } from "../lib/config.ts";
17
+
18
+ const HELP = `
19
+ gh-stack config — Show flag defaults and where each value comes from
20
+
21
+ USAGE
22
+ gh-stack config [--json]
23
+
24
+ Prints every configurable default with its resolved value and source. Values
25
+ resolve, highest first: CLI flag > env var > repo config > user config >
26
+ built-in default. Outside a git repo it shows the user and env layers only.
27
+
28
+ FILES
29
+ .gh-stack.toml Repo root (commit it to share conventions)
30
+ ~/.config/gh-stack/config.toml Personal defaults (honors XDG_CONFIG_HOME)
31
+
32
+ [submit]
33
+ draft = true # new PRs open as drafts (--draft / --no-draft)
34
+
35
+ [restack]
36
+ push = false # restack and sync stay local (--push / --no-push)
37
+ # (merge and submit --restack always push)
38
+
39
+ ENVIRONMENT (1/0, true/false, or yes/no)
40
+ GH_STACK_DRAFT submit draft default
41
+ GH_STACK_NO_PUSH restack/sync push default (1 = don't push)
42
+
43
+ --yes is not configurable from a file: auto-confirming destructive operations
44
+ must be asked for per run (--yes or GH_STACK_YES=1).
45
+
46
+ OPTIONS
47
+ --json Output as JSON
48
+ `;
49
+
50
+ export interface ConfigReport {
51
+ settings: {
52
+ key: string;
53
+ value: boolean;
54
+ source: ConfigSource;
55
+ origin: string | null;
56
+ flags: string[];
57
+ env: string;
58
+ }[];
59
+ files: { repo: FileInfo | null; user: FileInfo | null };
60
+ }
61
+
62
+ interface FileInfo {
63
+ path: string;
64
+ exists: boolean;
65
+ }
66
+
67
+ function fileInfo(f: ConfigFile | null): FileInfo | null {
68
+ return f ? { path: f.path, exists: f.exists } : null;
69
+ }
70
+
71
+ function fileLine(label: string, f: FileInfo | null): string {
72
+ return f
73
+ ? ` ${label} ${f.path} ${f.exists ? "" : pc.dim("(not found)")}`.trimEnd()
74
+ : ` ${label} ${pc.dim("(not in a git work tree)")}`;
75
+ }
76
+
77
+ /** Build the report from loaded layers. Pure, so it's unit-testable. */
78
+ export function buildConfigReport(layers: ConfigLayers): ConfigReport {
79
+ return {
80
+ settings: (Object.keys(SETTINGS) as SettingName[]).map((name) => {
81
+ const spec = SETTINGS[name];
82
+ const r = resolveSetting(name, [], layers);
83
+ return {
84
+ key: name,
85
+ value: r.value,
86
+ source: r.source,
87
+ origin: r.origin ?? null,
88
+ flags: [...spec.onFlags, ...spec.offFlags],
89
+ env: spec.env.name,
90
+ };
91
+ }),
92
+ files: { repo: fileInfo(layers.repo), user: fileInfo(layers.user) },
93
+ };
94
+ }
95
+
96
+ export default async function config(args: string[]): Promise<void> {
97
+ if (args.includes("--help")) {
98
+ console.log(HELP);
99
+ return;
100
+ }
101
+
102
+ // Both steps can throw ConfigError: loading (bad TOML, wrong-typed value)
103
+ // and resolving (a non-boolean env var).
104
+ let report: ConfigReport;
105
+ try {
106
+ const loaded = await loadConfigLayers();
107
+ for (const w of loaded.warnings) console.error(`gh-stack: warning: ${w}`);
108
+ report = buildConfigReport(loaded.layers);
109
+ } catch (err) {
110
+ if (!(err instanceof ConfigError)) throw err;
111
+ console.error(`gh-stack: ${err.message}`);
112
+ process.exit(1);
113
+ }
114
+
115
+ if (args.includes("--json")) {
116
+ console.log(JSON.stringify(report, null, 2));
117
+ return;
118
+ }
119
+
120
+ console.log();
121
+ const width = Math.max(...report.settings.map((s) => s.key.length));
122
+ for (const s of report.settings) {
123
+ const value = s.value ? pc.green("true ") : pc.yellow("false");
124
+ const source = describeSource({ ...s, origin: s.origin ?? undefined });
125
+ console.log(` ${s.key.padEnd(width)} ${value} ${pc.dim(source)}`);
126
+ }
127
+
128
+ console.log();
129
+ console.log(pc.dim("Config files:"));
130
+ console.log(fileLine("repo", report.files.repo));
131
+ console.log(fileLine("user", report.files.user));
132
+ console.log();
133
+ }
@@ -13,6 +13,7 @@ import {
13
13
  import { getPrNumber } from "../lib/github.ts";
14
14
  import { isAutoYes } from "../lib/ui.ts";
15
15
  import { detectBranchChain } from "../lib/chain.ts";
16
+ import { warnIfColocated } from "./add.ts";
16
17
  import { gateLegacyMetadata } from "../lib/safety.ts";
17
18
  import type { StackMetadata, Branch } from "../types.ts";
18
19
 
@@ -151,6 +152,9 @@ export default async function init(args: string[]): Promise<void> {
151
152
 
152
153
  p.outro(pc.green(`Stack ${pc.yellow(stackName)} created with ${chain.length} branch(es)!`));
153
154
 
155
+ // A zero-commit branch can't see its parent through ancestry (#37).
156
+ if (chain.length === 1) await warnIfColocated(branch, trunk);
157
+
154
158
  // Next steps
155
159
  console.log();
156
160
  if (chain.length === 1) {