Skip to content

Commit a0b1704

Browse files
committed
Merge branch 'master' into appComposer/flakyTest
2 parents 1e88ae5 + 43299f3 commit a0b1704

File tree

13 files changed

+1424
-616
lines changed

13 files changed

+1424
-616
lines changed

packages/core/src/dev/activation.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -164,9 +164,10 @@ export async function activate(ctx: vscode.ExtensionContext): Promise<void> {
164164
// eslint-disable-next-line aws-toolkits/no-banned-usages
165165
globalState = targetContext.globalState
166166
targetAuth = opts.auth
167+
const options = menuOptions()
167168
void openMenu(
168-
entries(menuOptions())
169-
.filter((e) => (opts.menuOptions ?? Object.keys(menuOptions)).includes(e[0]))
169+
entries(options)
170+
.filter((e) => (opts.menuOptions ?? Object.keys(options)).includes(e[0]))
170171
.map((e) => e[1])
171172
)
172173
}),

packages/core/src/shared/sam/build.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
*/
55

66
import * as vscode from 'vscode'
7-
import { TemplateItem, createTemplatePrompter, getSamCliPathAndVersion, runInTerminal } from './sync'
7+
import { TemplateItem, createTemplatePrompter } from './sync'
88
import { ChildProcess } from '../utilities/processUtils'
99
import { addTelemetryEnvVar } from './cli/samCliInvokerUtils'
1010
import { Wizard } from '../wizards/wizard'
@@ -19,8 +19,9 @@ import globals from '../extensionGlobals'
1919
import { TreeNode } from '../treeview/resourceTreeDataProvider'
2020
import { telemetry } from '../telemetry/telemetry'
2121
import { getSpawnEnv } from '../env/resolveEnv'
22-
import { getProjectRoot, isDotnetRuntime } from './utils'
22+
import { getProjectRoot, getSamCliPathAndVersion, isDotnetRuntime } from './utils'
2323
import { getConfigFileUri, validateSamBuildConfig } from './config'
24+
import { runInTerminal } from './processTerminal'
2425

2526
export interface BuildParams {
2627
readonly template: TemplateItem

packages/core/src/shared/sam/deploy.ts

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,15 +23,9 @@ import { CancellationError } from '../utilities/timeoutUtils'
2323
import { Wizard } from '../wizards/wizard'
2424
import { addTelemetryEnvVar } from './cli/samCliInvokerUtils'
2525
import { validateSamDeployConfig, SamConfig, writeSamconfigGlobal } from './config'
26-
import {
27-
TemplateItem,
28-
createStackPrompter,
29-
createBucketPrompter,
30-
createTemplatePrompter,
31-
getSamCliPathAndVersion,
32-
runInTerminal,
33-
} from './sync'
34-
import { getProjectRoot, getSource } from './utils'
26+
import { TemplateItem, createStackPrompter, createBucketPrompter, createTemplatePrompter } from './sync'
27+
import { getProjectRoot, getSamCliPathAndVersion, getSource } from './utils'
28+
import { runInTerminal } from './processTerminal'
3529

3630
export interface DeployParams {
3731
readonly paramsSource: ParamsSource
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
/*!
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
import * as vscode from 'vscode'
6+
7+
import { ToolkitError, UnknownError } from '../errors'
8+
import globals from '../extensionGlobals'
9+
import { isCloud9 } from '../extensionUtilities'
10+
import { ChildProcess, ChildProcessResult } from '../utilities/processUtils'
11+
import { CancellationError } from '../utilities/timeoutUtils'
12+
import { getLogger } from '../logger'
13+
import { removeAnsi } from '../utilities/textUtilities'
14+
import { isAutomation } from '../vscode/env'
15+
16+
let oldTerminal: ProcessTerminal | undefined
17+
export async function runInTerminal(proc: ChildProcess, cmd: string) {
18+
const handleResult = (result?: ChildProcessResult) => {
19+
if (result && result.exitCode !== 0) {
20+
const message = `sam ${cmd} exited with a non-zero exit code: ${result.exitCode}`
21+
if (result.stderr.includes('is up to date')) {
22+
throw ToolkitError.chain(result.error, message, {
23+
code: 'NoUpdateExitCode',
24+
})
25+
}
26+
throw ToolkitError.chain(result.error, message, {
27+
code: 'NonZeroExitCode',
28+
})
29+
}
30+
}
31+
32+
// `createTerminal` doesn't work on C9 so we use the output channel instead
33+
if (isCloud9()) {
34+
globals.outputChannel.show()
35+
36+
const result = proc.run({
37+
onStdout: (text) => globals.outputChannel.append(removeAnsi(text)),
38+
onStderr: (text) => globals.outputChannel.append(removeAnsi(text)),
39+
})
40+
await proc.send('\n')
41+
42+
return handleResult(await result)
43+
}
44+
45+
// The most recent terminal won't get garbage collected until the next run
46+
if (oldTerminal?.stopped === true) {
47+
oldTerminal.close()
48+
}
49+
const pty = (oldTerminal = new ProcessTerminal(proc))
50+
const terminal = vscode.window.createTerminal({ pty, name: `SAM ${cmd}` })
51+
terminal.sendText('\n')
52+
terminal.show()
53+
54+
const result = await new Promise<ChildProcessResult>((resolve) => pty.onDidExit(resolve))
55+
if (pty.cancelled) {
56+
throw result.error !== undefined
57+
? ToolkitError.chain(result.error, 'SAM CLI was cancelled before exiting', { cancelled: true })
58+
: new CancellationError('user')
59+
} else {
60+
return handleResult(result)
61+
}
62+
}
63+
64+
// This is a decent improvement over using the output channel but it isn't a tty/pty
65+
// SAM CLI uses `click` which has reduced functionality if `os.isatty` returns false
66+
// Historically, Windows lack of a pty-equivalent is why it's not available in libuv
67+
// Maybe it's doable now with the ConPTY API? https://github.com/libuv/libuv/issues/2640
68+
class ProcessTerminal implements vscode.Pseudoterminal {
69+
private readonly onDidCloseEmitter = new vscode.EventEmitter<number | void>()
70+
private readonly onDidWriteEmitter = new vscode.EventEmitter<string>()
71+
private readonly onDidExitEmitter = new vscode.EventEmitter<ChildProcessResult>()
72+
public readonly onDidWrite = this.onDidWriteEmitter.event
73+
public readonly onDidClose = this.onDidCloseEmitter.event
74+
public readonly onDidExit = this.onDidExitEmitter.event
75+
76+
public constructor(private readonly process: ChildProcess) {
77+
// Used in integration tests
78+
if (isAutomation()) {
79+
// Disable because it is a test.
80+
// eslint-disable-next-line aws-toolkits/no-console-log
81+
this.onDidWrite((text) => console.log(text.trim()))
82+
}
83+
}
84+
85+
#cancelled = false
86+
public get cancelled() {
87+
return this.#cancelled
88+
}
89+
90+
public get stopped() {
91+
return this.process.stopped
92+
}
93+
94+
public open(initialDimensions: vscode.TerminalDimensions | undefined): void {
95+
this.process
96+
.run({
97+
onStdout: (text) => this.mapStdio(text),
98+
onStderr: (text) => this.mapStdio(text),
99+
})
100+
.then((result) => this.onDidExitEmitter.fire(result))
101+
.catch((err) =>
102+
this.onDidExitEmitter.fire({ error: UnknownError.cast(err), exitCode: -1, stderr: '', stdout: '' })
103+
)
104+
.finally(() => this.onDidWriteEmitter.fire('\r\nPress any key to close this terminal'))
105+
}
106+
107+
public close(): void {
108+
this.process.stop()
109+
this.onDidCloseEmitter.fire()
110+
}
111+
112+
public handleInput(data: string) {
113+
// ETX
114+
if (data === '\u0003' || this.process.stopped) {
115+
this.#cancelled ||= data === '\u0003'
116+
return this.close()
117+
}
118+
119+
// enter
120+
if (data === '\u000D') {
121+
this.process.send('\n').then(undefined, (e) => {
122+
getLogger().error('ProcessTerminal: process.send() failed: %s', (e as Error).message)
123+
})
124+
this.onDidWriteEmitter.fire('\r\n')
125+
} else {
126+
this.process.send(data).then(undefined, (e) => {
127+
getLogger().error('ProcessTerminal: process.send() failed: %s', (e as Error).message)
128+
})
129+
this.onDidWriteEmitter.fire(data)
130+
}
131+
}
132+
133+
private mapStdio(text: string): void {
134+
const lines = text.split('\n')
135+
const first = lines.shift()
136+
137+
if (first) {
138+
this.onDidWriteEmitter.fire(first)
139+
}
140+
141+
for (const line of lines) {
142+
this.onDidWriteEmitter.fire('\r\n')
143+
this.onDidWriteEmitter.fire(line)
144+
}
145+
}
146+
}

0 commit comments

Comments
 (0)