Visitar URL original
Set GH_EXTENSION=1 when gh invokes an extension · cli/cli@408534a · GitHub
Skip to content

Commit 408534a

Browse files
Set GH_EXTENSION=1 when gh invokes an extension
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: cd6441de-bed4-4adb-88c3-904b349ba16f
1 parent c6f4067 commit 408534a

4 files changed

Lines changed: 123 additions & 2 deletions

File tree

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# Verify that gh tells an extension when it is being run as an extension
2+
3+
# Skip if Bash is not available given script extension
4+
[!exec:bash] skip
5+
6+
# Setup environment variables used for testscript
7+
env EXT_NAME=printenv-${RANDOM_STRING}
8+
env EXT_DIR=gh-${EXT_NAME}
9+
10+
# Setup a local extension that reports the value of GH_EXTENSION
11+
mkdir $EXT_DIR
12+
mv print-env.sh $EXT_DIR/$EXT_DIR
13+
chmod 777 $EXT_DIR/$EXT_DIR
14+
15+
# Install the local extension, gh extension install only supports the working directory
16+
cd $EXT_DIR
17+
exec gh extension install .
18+
defer gh extension remove $EXT_NAME
19+
20+
# Verify GH_EXTENSION is set when the extension is run as gh <extension>
21+
exec gh $EXT_NAME
22+
stdout 'GH_EXTENSION=1'
23+
24+
# Verify GH_EXTENSION is set when the extension is run via gh extension exec
25+
exec gh extension exec $EXT_NAME
26+
stdout 'GH_EXTENSION=1'
27+
28+
# Verify GH_EXTENSION is absent when the extension is run standalone
29+
exec ./$EXT_DIR
30+
stdout 'GH_EXTENSION=0'
31+
32+
# Verify GH_EXTENSION is documented
33+
exec gh help environment
34+
stdout 'GH_EXTENSION`: set to `1` by gh when it invokes an extension'
35+
36+
-- print-env.sh --
37+
#!/usr/bin/env bash
38+
echo "GH_EXTENSION=${GH_EXTENSION:-0}"

‎pkg/cmd/extension/manager.go‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,10 @@ func (m *Manager) Dispatch(args []string, stdin io.Reader, stdout, stderr io.Wri
128128
forwardArgs = append([]string{"-c", `command "$@"`, "--", exe}, forwardArgs...)
129129
externalCmd = m.newCommand(shExe, forwardArgs...)
130130
}
131+
// Signal to the extension that it is being run by gh rather than standalone, so it can
132+
// adjust things like usage strings.
133+
externalCmd.Env = append(externalCmd.Environ(), "GH_EXTENSION=1")
134+
131135
externalCmd.Stdin = stdin
132136
externalCmd.Stdout = stdout
133137
externalCmd.Stderr = stderr

‎pkg/cmd/extension/manager_test.go‎

Lines changed: 78 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,13 @@ func TestHelperProcess(t *testing.T) {
2929
return
3030
}
3131
if err := func(args []string) error {
32+
// Dispatch tests use this marker argument to inspect the environment gh handed to
33+
// the extension, rather than echoing the arguments back.
34+
if len(args) > 0 && args[len(args)-1] == "print-env" {
35+
fmt.Fprintf(os.Stdout, "GH_EXTENSION=%s\n", os.Getenv("GH_EXTENSION"))
36+
fmt.Fprintf(os.Stdout, "GH_HELPER_INHERITED=%s\n", os.Getenv("GH_HELPER_INHERITED"))
37+
return nil
38+
}
3239
fmt.Fprintf(os.Stdout, "%v\n", args)
3340
return nil
3441
}(os.Args[3:]); err != nil {
@@ -38,7 +45,7 @@ func TestHelperProcess(t *testing.T) {
3845
os.Exit(0)
3946
}
4047

41-
func newTestManager(dataDir, updateDir string, client *http.Client, gitClient gitClient, ios *iostreams.IOStreams) *Manager {
48+
func newTestManager(dataDir, updateDir string, client *http.Client, gitClient gitClient, ios *iostreams.IOStreams, extraEnv ...string) *Manager {
4249
return &Manager{
4350
dataDir: func() string { return dataDir },
4451
updateDir: func() string { return updateDir },
@@ -51,7 +58,7 @@ func newTestManager(dataDir, updateDir string, client *http.Client, gitClient gi
5158
cmd.Stdout = ios.Out
5259
cmd.Stderr = ios.ErrOut
5360
}
54-
cmd.Env = []string{"GH_WANT_HELPER_PROCESS=1"}
61+
cmd.Env = append([]string{"GH_WANT_HELPER_PROCESS=1"}, extraEnv...)
5562
return cmd
5663
},
5764
config: config.NewBlankConfig(),
@@ -191,6 +198,75 @@ func TestManager_Dispatch_binary(t *testing.T) {
191198
assert.Equal(t, "", stderr.String())
192199
}
193200

201+
func TestManager_Dispatch_ghExtensionEnv(t *testing.T) {
202+
tests := []struct {
203+
name string
204+
extraEnv []string
205+
wantOut string
206+
}{
207+
{
208+
name: "sets GH_EXTENSION",
209+
wantOut: "GH_EXTENSION=1\nGH_HELPER_INHERITED=\n",
210+
},
211+
{
212+
name: "preserves the rest of the environment",
213+
extraEnv: []string{"GH_HELPER_INHERITED=yes"},
214+
wantOut: "GH_EXTENSION=1\nGH_HELPER_INHERITED=yes\n",
215+
},
216+
{
217+
name: "overrides an inherited GH_EXTENSION",
218+
extraEnv: []string{"GH_EXTENSION=0", "GH_HELPER_INHERITED=yes"},
219+
wantOut: "GH_EXTENSION=1\nGH_HELPER_INHERITED=yes\n",
220+
},
221+
}
222+
223+
for _, tt := range tests {
224+
t.Run("script extension: "+tt.name, func(t *testing.T) {
225+
dataDir := t.TempDir()
226+
updateDir := t.TempDir()
227+
extDir := filepath.Join(dataDir, "extensions", "gh-hello")
228+
require.NoError(t, stubExtension(filepath.Join(extDir, "gh-hello")))
229+
230+
gc, gcOne := &mockGitClient{}, &mockGitClient{}
231+
gc.On("ForRepo", extDir).Return(gcOne).Once()
232+
233+
m := newTestManager(dataDir, updateDir, nil, gc, nil, tt.extraEnv...)
234+
235+
stdout := &bytes.Buffer{}
236+
stderr := &bytes.Buffer{}
237+
found, err := m.Dispatch([]string{"hello", "print-env"}, nil, stdout, stderr)
238+
require.NoError(t, err)
239+
require.True(t, found)
240+
241+
assert.Equal(t, tt.wantOut, stdout.String())
242+
assert.Equal(t, "", stderr.String())
243+
})
244+
245+
t.Run("binary extension: "+tt.name, func(t *testing.T) {
246+
dataDir := t.TempDir()
247+
updateDir := t.TempDir()
248+
extDir := filepath.Join(dataDir, "extensions", "gh-hello")
249+
require.NoError(t, stubBinaryExtension(extDir, binManifest{
250+
Owner: "owner",
251+
Name: "gh-hello",
252+
Host: "github.com",
253+
Tag: "v1.0.0",
254+
}))
255+
256+
m := newTestManager(dataDir, updateDir, nil, nil, nil, tt.extraEnv...)
257+
258+
stdout := &bytes.Buffer{}
259+
stderr := &bytes.Buffer{}
260+
found, err := m.Dispatch([]string{"hello", "print-env"}, nil, stdout, stderr)
261+
require.NoError(t, err)
262+
require.True(t, found)
263+
264+
assert.Equal(t, tt.wantOut, stdout.String())
265+
assert.Equal(t, "", stderr.String())
266+
})
267+
}
268+
}
269+
194270
func TestManager_Remove(t *testing.T) {
195271
dataDir := t.TempDir()
196272
updateDir := t.TempDir()

‎pkg/cmd/root/help_topic.go‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,9 @@ var HelpTopics = []helpTopic{
9999
When an extension is executed, gh checks for new versions for the executed extension once every 24 hours.
100100
If a newer version was found, an upgrade notice is displayed on standard error.
101101
102+
%[1]sGH_EXTENSION%[1]s: set to %[1]s1%[1]s by gh when it invokes an extension, allowing an extension to
103+
tell whether it was run as %[1]sgh <extension>%[1]s or directly as a standalone program.
104+
102105
%[1]sGH_CONFIG_DIR%[1]s: the directory where gh will store configuration files. If not specified,
103106
the default value will be one of the following paths (in order of precedence):
104107
- %[1]s$XDG_CONFIG_HOME/gh%[1]s (if %[1]s$XDG_CONFIG_HOME%[1]s is set),

0 commit comments

Comments
 (0)