Skip to content

Commit

Permalink
Warn about unsupported shells
Browse files Browse the repository at this point in the history
This PR introduces changes to do a best effort to determine if the shell
is supported.

Closes: #1393
  • Loading branch information
pastuxso committed Aug 16, 2024
1 parent 60a938f commit 9b8448f
Show file tree
Hide file tree
Showing 5 changed files with 116 additions and 58 deletions.
6 changes: 6 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -848,6 +848,12 @@
"type": "string",
"default": "http://localhost:8080/api",
"description": "The base URL of the AI service."
},
"runme.app.faqUrl": {
"type": "string",
"scope": "window",
"default": "https://docs.runme.dev/faq",
"markdownDescription": "Frequently Asked Questions page"
}
}
}
Expand Down
61 changes: 4 additions & 57 deletions src/extension/executors/aws.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { resolveProgramOptionsScript } from './runner'
import { IKernelExecutor } from '.'

export const aws: IKernelExecutor = async (executor) => {
const { cellText, exec, runner, runnerEnv, doc, outputs, context } = executor
const { cellText, exec, runner, runnerEnv, doc, outputs, kernel } = executor

const annotations = getAnnotations(exec.cell)

Expand All @@ -41,67 +41,14 @@ export const aws: IKernelExecutor = async (executor) => {
cellId,
})

// todo(sebastian): move down into kernel?
switch (programOptions.exec?.type) {
case 'script':
{
programOptions.exec.script = 'echo $AWS_PROFILE'
}
programOptions.exec.script = 'echo $AWS_PROFILE'
break
}

const program = await runner.createProgramSession(programOptions)
context.subscriptions.push(program)

let execRes: string | undefined
const onData = (data: string | Uint8Array) => {
if (execRes === undefined) {
execRes = ''
}
execRes += data.toString()
}

program.onDidWrite(onData)
program.onDidErr(onData)
program.run()

const success = await new Promise<boolean>((resolve, reject) => {
program.onDidClose(async (code) => {
if (code !== 0) {
return resolve(false)
}
return resolve(true)
})

program.onInternalErr((e) => {
reject(e)
})

const exitReason = program.hasExited()

// unexpected early return, likely an error
if (exitReason) {
switch (exitReason.type) {
case 'error':
{
reject(exitReason.error)
}
break

case 'exit':
{
resolve(exitReason.code === 0)
}
break

default: {
resolve(false)
}
}
}
})

const profile = success ? execRes?.trim() : 'default'
const result = await kernel.runProgram(programOptions)
const profile = result.code === 0 ? result.output : 'default'
credentials = fromIni({ profile })

switch (awsResolver.view) {
Expand Down
22 changes: 22 additions & 0 deletions src/extension/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import Channel from 'tangle/webviews'

import { NotebookUiEvent, Serializer, SyncSchema } from '../types'
import {
getFaqUrl,
getForceNewWindowConfig,
getRunmeAppUrl,
getSessionOutputs,
Expand Down Expand Up @@ -80,6 +81,8 @@ import { NotebookCellStatusBarProvider } from './provider/cellStatusBar/notebook
import { SessionOutputCellStatusBarProvider } from './provider/cellStatusBar/sessionOutput'
import * as generate from './ai/generate'
import * as manager from './ai/manager'
import getLogger from './logger'

export class RunmeExtension {
protected serializer?: SerializerBase

Expand Down Expand Up @@ -353,6 +356,25 @@ export class RunmeExtension {
context.subscriptions.push(new CloudAuthProvider(context))
}

try {
const { output } = await kernel.runProgram('echo $0')
const supportedShells = ['zsh', 'bash']
const isSupported = supportedShells.some((sh) => output?.includes(sh))

if (!isSupported) {
const showMore = 'Show more'
const answer = await window.showWarningMessage('Unsupported shell', showMore)

if (answer === showMore) {
const dashboardUri = getFaqUrl('supported-shells')
const uri = Uri.parse(dashboardUri)
env.openExternal(uri)
}
}
} catch (_e) {
getLogger().warn('An error occurred while verifying the supported shell')
}

await bootFile(context)
}

Expand Down
77 changes: 76 additions & 1 deletion src/extension/kernel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ import {
import { getSystemShellPath, isShellLanguage } from './executors/utils'
import './wasm/wasm_exec.js'
import { RpcError } from './grpc/client'
import { IRunner, IRunnerReady } from './runner'
import { IRunner, IRunnerReady, RunnerExitReason, RunProgramOptions } from './runner'
import { IRunnerEnvironment } from './runner/environment'
import { IKernelRunnerOptions, executeRunner } from './executors/runner'
import { ITerminalState, NotebookTerminalType } from './terminal/terminalState'
Expand Down Expand Up @@ -1119,4 +1119,79 @@ export class Kernel implements Disposable {
public getPlainCache(cacheId: string): Promise<Uint8Array> | undefined {
return this.serializer?.getPlainCache(cacheId)
}

async runProgram(program?: RunProgramOptions | string) {
let programOptions: RunProgramOptions

if (!this.runner) {
return Promise.reject(new Error('No runner available'))
}

if (typeof program === 'object') {
programOptions = program
} else if (typeof program === 'string') {
programOptions = {
programName: '',
background: false,
exec: {
type: 'script',
script: program,
},
languageId: 'sh',
storeLastOutput: false,
tty: true,
}
} else {
return Promise.reject(new Error('Invalid runProgram arguments'))
}

const programSession = await this.runner.createProgramSession(programOptions)
this.context.subscriptions.push(programSession)

let execRes: string | undefined
const onData = (data: string | Uint8Array) => {
if (execRes === undefined) {
execRes = ''
}
execRes += data.toString()
}

programSession.onDidWrite(onData)
programSession.onDidErr(onData)
await programSession.run()

return new Promise<{ exitReason?: RunnerExitReason; code?: number | void; output?: string }>(
(resolve, reject) => {
programSession.onDidClose(async (code) => {
return resolve({ code, output: execRes?.trim() })
})

programSession.onInternalErr((e) => {
reject(e)
})

const exitReason = programSession.hasExited()

if (exitReason) {
switch (exitReason.type) {
case 'error':
{
reject({ exitReason, output: execRes?.trim() })
}
break

case 'exit':
{
resolve({ exitReason, code: exitReason.code, output: execRes?.trim() })
}
break

default: {
resolve({ exitReason, output: execRes?.trim() })
}
}
}
},
)
}
}
8 changes: 8 additions & 0 deletions src/utils/configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ const DEFAULT_WORKSPACE_FILE_ORDER = ['.env.local', '.env']
const DEFAULT_RUNME_APP_API_URL = 'https://platform.stateful.com'
const DEFAULT_RUNME_BASE_DOMAIN = 'platform.stateful.com'
const DEFAULT_RUNME_REMOTE_DEV = 'staging.platform.stateful.com'
const DEFAULT_FAQ_URL = 'https://docs.runme.dev/faq'
const APP_LOOPBACKS = ['127.0.0.1', 'localhost']
const APP_LOOPBACK_MAPPING = new Map<string, string>([
['api.', ':4000'],
Expand Down Expand Up @@ -90,6 +91,7 @@ const configurationSchema = {
maskOutputs: z.boolean().default(true),
loginPrompt: z.boolean().default(true),
platformAuth: z.boolean().default(false),
faqUrl: z.string().default(DEFAULT_FAQ_URL),
},
}

Expand Down Expand Up @@ -432,6 +434,11 @@ const isPlatformAuthEnabled = (): boolean => {
return getCloudConfigurationValue('platformAuth', false)
}

const getFaqUrl = (hash: string): string => {
const baseUrl = getCloudConfigurationValue('faqUrl', DEFAULT_FAQ_URL)
return `${baseUrl}#${hash}`
}

export {
enableServerLogs,
getActionsOpenViewInEditor,
Expand Down Expand Up @@ -462,4 +469,5 @@ export {
getSessionOutputs,
getMaskOutputs,
getLoginPrompt,
getFaqUrl,
}

0 comments on commit 9b8448f

Please sign in to comment.