Repository: stateful/vscode-runme
Pull request #94 (2022-12-14 16:31:17)

Bring back CLI button (and respective metadata)
Slightly refactored to better leverage types plus unit & e2e tests.

Relates to https://github.com/stateful/runme/issues/83. Tests will pass once PR (https://github.com/stateful/runme/pull/84) is merged.


Patch:
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 21b5efbe..03f0102a 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -156,3 +156,5 @@ jobs:
       - name: 🐛 Debug Build
         uses: stateful/vscode-server-action@v1
         if: failure()
+        with:
+          timeout: '300000'
diff --git a/.yarnrc b/.yarnrc
deleted file mode 100644
index f757a6ac..00000000
--- a/.yarnrc
+++ /dev/null
@@ -1,1 +0,0 @@
---ignore-engines true
\ No newline at end of file
diff --git a/src/extension/commands/index.ts b/src/extension/commands/index.ts
index afc3cb40..d4d802c7 100644
--- a/src/extension/commands/index.ts
+++ b/src/extension/commands/index.ts
@@ -3,7 +3,7 @@ import path from 'node:path'
 import { NotebookCell, Uri, window, env, NotebookDocument, TextDocument, ViewColumn } from 'vscode'
 
 import { CliProvider } from '../provider/cli'
-import { getTerminalByCell } from '../utils'
+import { getMetadata, getTerminalByCell } from '../utils'
 
 function showWarningMessage () {
   return window.showWarningMessage('Couldn\'t find terminal! Was it already closed?')
@@ -40,7 +40,8 @@ export async function runCLICommand (cell: NotebookCell) {
       Uri.parse('https://github.com/stateful/runme/releases')
     ))
   }
-  const cliName: string = (cell.metadata?.['cliName'] || '').trim()
+  const metadata = getMetadata(cell)
+  const cliName: string = (metadata?.['runme.dev/name'] || '').trim()
   const term = window.createTerminal(`CLI: ${cliName}`)
   term.show(false)
   term.sendText(`runme run ${cliName} --chdir="${path.dirname(cell.document.uri.fsPath)}"`)
diff --git a/src/extension/executors/shell.ts b/src/extension/executors/shell.ts
index 1dbcdd61..498b24a7 100644
--- a/src/extension/executors/shell.ts
+++ b/src/extension/executors/shell.ts
@@ -5,6 +5,7 @@ import { NotebookCellOutput, NotebookCellOutputItem, NotebookCellExecution } fro
 import { OutputType } from '../../constants'
 import type { CellOutputPayload } from '../../types'
 import type { Kernel } from '../kernel'
+import { getMetadata } from '../utils'
 
 const MIME_TYPES_WITH_CUSTOM_RENDERERS = ['text/plain']
 
@@ -28,7 +29,8 @@ async function shellExecutor(
   /**
    * this needs more work / specification
    */
-  const mime = exec.cell.metadata?.mimeType || 'text/plain' as const
+  const metadata = getMetadata(exec.cell)
+  const mime = metadata?.mimeType || 'text/plain' as const
   const index = exec.cell.index
 
   /**
diff --git a/src/extension/executors/task.ts b/src/extension/executors/task.ts
index 23af3fe1..e6ab920a 100644
--- a/src/extension/executors/task.ts
+++ b/src/extension/executors/task.ts
@@ -7,7 +7,7 @@ import {
 } from 'vscode'
 
 // import { ExperimentalTerminal } from "../terminal"
-import { getExecutionProperty, getCmdShellSeq } from '../utils'
+import { getExecutionProperty, getCmdShellSeq, getMetadata } from '../utils'
 import { PLATFORM_OS, ENV_STORE } from '../constants'
 import type { Kernel } from '../kernel'
 
@@ -75,7 +75,8 @@ async function taskExecutor(
     //   return new ExperimentalTerminal(scriptFile.fsPath, { cwd, env })
     // })
   )
-  const isBackground = exec.cell.metadata?.['background'] === 'true'
+  const metadata = getMetadata(exec.cell)
+  const isBackground = metadata?.['background'] === 'true'
   const closeTerminalOnSuccess = getExecutionProperty('closeTerminalOnSuccess', exec.cell)
   taskExecution.isBackground = isBackground
   taskExecution.presentationOptions = {
diff --git a/src/extension/provider/background.ts b/src/extension/provider/background.ts
index 45b14da7..3d6f35f1 100644
--- a/src/extension/provider/background.ts
+++ b/src/extension/provider/background.ts
@@ -1,6 +1,6 @@
 import vscode from 'vscode'
 
-import { getExecutionProperty, getTerminalByCell } from '../utils'
+import { getExecutionProperty, getMetadata, getTerminalByCell } from '../utils'
 
 export class ShowTerminalProvider implements vscode.NotebookCellStatusBarItemProvider {
   async provideCellStatusBarItems(cell: vscode.NotebookCell): Promise<vscode.NotebookCellStatusBarItem | undefined> {
@@ -13,7 +13,7 @@ export class ShowTerminalProvider implements vscode.NotebookCellStatusBarItemPro
 
     const terminal = getTerminalByCell(cell)
     const pid = await terminal?.processId
-    
+
     if (!Boolean(terminal) || !pid) {
       return
     }
@@ -29,7 +29,8 @@ export class ShowTerminalProvider implements vscode.NotebookCellStatusBarItemPro
 
 export class BackgroundTaskProvider implements vscode.NotebookCellStatusBarItemProvider {
   provideCellStatusBarItems(cell: vscode.NotebookCell): vscode.NotebookCellStatusBarItem | undefined {
-    const isBackground = cell.metadata?.['background'] === 'true'
+    const metadata = getMetadata(cell)
+    const isBackground = metadata?.background === 'true'
     /**
      * don't show if not a background task
      */
@@ -46,7 +47,8 @@ export class BackgroundTaskProvider implements vscode.NotebookCellStatusBarItemP
 }
 export class StopBackgroundTaskProvider implements vscode.NotebookCellStatusBarItemProvider {
   provideCellStatusBarItems(cell: vscode.NotebookCell): vscode.NotebookCellStatusBarItem | undefined {
-    const isBackground = cell.metadata?.['background'] === 'true'
+    const metadata = getMetadata(cell)
+    const isBackground = metadata?.background === 'true'
     /**
      * don't show if not a background task & if not command currently running
      */
diff --git a/src/extension/provider/cli.ts b/src/extension/provider/cli.ts
index 2b945033..cb7f6fcb 100644
--- a/src/extension/provider/cli.ts
+++ b/src/extension/provider/cli.ts
@@ -2,12 +2,15 @@ import { spawn } from 'node:child_process'
 
 import vscode from 'vscode'
 
+import { getMetadata } from '../utils'
+
 export class CliProvider implements vscode.NotebookCellStatusBarItemProvider {
   async provideCellStatusBarItems(cell: vscode.NotebookCell): Promise<vscode.NotebookCellStatusBarItem | undefined> {
     /**
-     * only show CLI if cliName is known
+     * only show CLI if runme.dev/name is known
      */
-    if (!cell.metadata?.['cliName']) {
+    const metadata = getMetadata(cell)
+    if (!metadata?.['runme.dev/name']) {
       return
     }
 
diff --git a/src/extension/serializer.ts b/src/extension/serializer.ts
index 27b30343..39a20d97 100644
--- a/src/extension/serializer.ts
+++ b/src/extension/serializer.ts
@@ -62,7 +62,7 @@ export class Serializer implements NotebookSerializer {
         throw err
       }
 
-      const { Runme } = globalThis as WasmLib.New.Serializer
+      const { Runme } = globalThis as WasmLib.Serializer
 
       const notebook = JSON.stringify(data)
       const markdown = await Runme.serialize(notebook)
@@ -93,13 +93,13 @@ export class Serializer implements NotebookSerializer {
     content: Uint8Array,
     token: CancellationToken
   ): Promise<NotebookData> {
-    let notebook: WasmLib.New.Notebook
+    let notebook: WasmLib.Notebook
     try {
       const err = await this.wasmReady
       if (err) {
         throw err
       }
-      const { Runme } = globalThis as WasmLib.New.Serializer
+      const { Runme } = globalThis as WasmLib.Serializer
 
       const markdown = content.toString()
 
@@ -143,7 +143,7 @@ export class Serializer implements NotebookSerializer {
     return new NotebookData(cells)
   }
 
-  protected static revive(notebook: WasmLib.New.Notebook) {
+  protected static revive(notebook: WasmLib.Notebook) {
     return notebook.cells.reduce((accu, elem) => {
       let cell: NotebookCellData
       // todo(sebastian): the parser will have to return unsupported as MARKUP
diff --git a/src/extension/utils.ts b/src/extension/utils.ts
index 2ef13ab4..4b2eef42 100644
--- a/src/extension/utils.ts
+++ b/src/extension/utils.ts
@@ -5,21 +5,28 @@ import cp from 'node:child_process'
 import vscode, { FileType } from 'vscode'
 
 import { CONFIGURATION_SHELL_DEFAULTS } from '../constants'
+import { WasmLib } from '../types'
 
 import executor from './executors'
 import { ENV_STORE, DEFAULT_ENV } from './constants'
 
 const HASH_PREFIX_REGEXP = /^\s*\#\s*/g
 
+export function getMetadata(cell: vscode.NotebookCell) {
+  const metadata: WasmLib.Metadata = cell.metadata
+  return metadata
+}
+
 export function getExecutionProperty (property: keyof typeof CONFIGURATION_SHELL_DEFAULTS, cell: vscode.NotebookCell) {
   const config = vscode.workspace.getConfiguration('runme.shell')
   const configSetting = config.get<boolean>(property, CONFIGURATION_SHELL_DEFAULTS[property])
+  const metadata = getMetadata(cell)
 
   /**
    * if cell is marked as interactive (default: not set or set to 'true')
    */
-  if (typeof cell.metadata?.[property] === 'string') {
-    return cell.metadata[property] === 'true'
+  if (typeof metadata?.[property] === 'string') {
+    return metadata[property] === 'true'
   }
 
   return configSetting
diff --git a/src/types.ts b/src/types.ts
index 9622aad0..64d51740 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -3,61 +3,35 @@ import { NotebookCellKind } from 'vscode'
 import { OutputType, ClientMessages } from './constants'
 
 export namespace WasmLib {
-  export namespace New {
-    export type Notebook = {
-      cells: Cell[]
-    }
-
-    export type Cell = {
-      metadata?: Attribute
-      languageId?: string
-      value: string
-      kind: NotebookCellKind.Markup
-    } | {
-      metadata?: Attribute
-      languageId?: string
-      value: string
-      kind: NotebookCellKind.Code
-    }
-
-    export interface Serializer {
-      Runme: {
-        deserialize: (content: string) => Promise<Notebook>
-        serialize: (content: string) => Promise<string>
-      }
-    }
+  export type Notebook = {
+    cells: Cell[]
   }
 
-  export interface Runme {
-    Runme: {
-      initialize: (source: string) => void
-      getCell: () => Cell
-      getCells: () => Cells
-      getSource: () => string
-      updateCell: (id: number, md: string) => Error | null
-      prepareScript: (lines: string[]) => string
-    }
-  }
   export type Cell = {
-    editable: boolean
-    source: string
-    type: 'markdown'
+    metadata?: Metadata
+    languageId?: string
+    value: string
+    kind: NotebookCellKind.Markup
   } | {
-    attributes?: Attribute
-    editable: boolean
-    executable?: string
-    lines?: string[]
-    name: string
-    source: string
-    type: 'code'
+    metadata?: Metadata
+    languageId?: string
+    value: string
+    kind: NotebookCellKind.Code
   }
 
-  export interface Cells {
-    cells?: Cell[]
+  export interface Serializer {
+    Runme: {
+      deserialize: (content: string) => Promise<Notebook>
+      serialize: (content: string) => Promise<string>
+    }
   }
 
-  export interface Attribute {
-    [key: string]: any
+  export interface Metadata {
+    background?: string
+    interactive?: string
+    closeTerminalOnSuccess?: string
+    mimeType?: string
+    ['runme.dev/name']?: string
   }
 }
 


Test patch:
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index cf40ed2b..a2a4d14f 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -30,3 +30,8 @@ jobs:
           path: |
             test/screenshots
             test/logs
+      - name: 🐛 Debug Build
+        uses: stateful/vscode-server-action@v1
+        if: failure()
+        with:
+          timeout: '300000'
diff --git a/tests/e2e/specs/basic.e2e.ts b/tests/e2e/specs/basic.e2e.ts
index 6f1f4d7d..26eb2252 100644
--- a/tests/e2e/specs/basic.e2e.ts
+++ b/tests/e2e/specs/basic.e2e.ts
@@ -37,6 +37,20 @@ describe('Runme VS Code Extension', () => {
     await webview.close()
   })
 
+  it.skip('should provide a button to run cmd via CLI', async () => {
+    const rows = await $$('.cell-statusbar-container .cell-status-item')
+    let row: WebdriverIO.Element | undefined
+    for (const r of rows) {
+      if ((await r.getText()).includes('CLI')) {
+        row = r
+      }
+    }
+
+    if (!row) {
+      throw new Error('Could not find CLI button')
+    }
+  })
+
   it('should be able to run an example', async () => {
     const rows = await $$('.cell-editor-container')
     let row: WebdriverIO.Element | undefined
diff --git a/tests/e2e/wdio.conf.ts b/tests/e2e/wdio.conf.ts
index 634b6af0..7a7b1c64 100644
--- a/tests/e2e/wdio.conf.ts
+++ b/tests/e2e/wdio.conf.ts
@@ -92,7 +92,7 @@ export const config: Options.Testrunner = {
     //
     capabilities: [{
         browserName: 'vscode',
-        browserVersion: 'stable',
+        browserVersion: '1.74.0',
         'wdio:vscodeOptions': {
           extensionPath,
           workspacePath: extensionPath,
diff --git a/tests/extension/commands/index.test.ts b/tests/extension/commands/index.test.ts
index 8ae66f02..d36324a7 100644
--- a/tests/extension/commands/index.test.ts
+++ b/tests/extension/commands/index.test.ts
@@ -5,21 +5,22 @@ import {
   terminal,
   NotebookDocument,
   TextDocument,
-  ViewColumn,  
+  ViewColumn,
 } from 'vscode'
 
-import { 
+import {
   openTerminal,
-  copyCellToClipboard, 
-  runCLICommand, 
-  openAsRunmeNotebook, 
+  copyCellToClipboard,
+  runCLICommand,
+  openAsRunmeNotebook,
   openSplitViewAsMarkdownText,
   stopBackgroundTask } from '../../../src/extension/commands'
-import { getTerminalByCell } from '../../../src/extension/utils'
+import { getTerminalByCell, getMetadata } from '../../../src/extension/utils'
 import { CliProvider } from '../../../src/extension/provider/cli'
 
 vi.mock('vscode')
 vi.mock('../../../src/extension/utils', () => ({
+  getMetadata: vi.fn(),
   getTerminalByCell: vi.fn()
 }))
 vi.mock('../../../src/extension/provider/cli', () => ({
@@ -31,6 +32,7 @@ vi.mock('../../../src/extension/provider/cli', () => ({
 beforeEach(() => {
   vi.mocked(window.showWarningMessage).mockClear()
   vi.mocked(window.showInformationMessage).mockClear()
+  vi.mocked(getMetadata).mockClear()
 })
 
 test('openTerminal', () => {
@@ -60,10 +62,11 @@ test('runCLICommand if CLI is not installed', async () => {
 
 test('runCLICommand if CLI is installed', async () => {
   const cell: any = {
-    metadata: { cliName: 'foobar' },
+    metadata: { 'runme.dev/name': 'foobar' },
     document: { uri: { fsPath: '/foo/bar' }}
   }
   vi.mocked(CliProvider.isCliInstalled).mockResolvedValue(true)
+  vi.mocked(getMetadata).mockReturnValue(cell.metadata)
   await runCLICommand(cell)
   expect(window.createTerminal).toBeCalledWith('CLI: foobar')
   expect(terminal.show).toBeCalledTimes(1)
diff --git a/tests/extension/kernel.test.ts b/tests/extension/kernel.test.ts
index 49d36b2a..619bcde7 100644
--- a/tests/extension/kernel.test.ts
+++ b/tests/extension/kernel.test.ts
@@ -8,7 +8,8 @@ import executors from '../../src/extension/executors'
 vi.mock('vscode')
 vi.mock('../../src/extension/utils', () => ({
   resetEnv: vi.fn(),
-  getKey: vi.fn().mockReturnValue('foobar')
+  getKey: vi.fn().mockReturnValue('foobar'),
+  getMetadata: vi.fn(),
 }))
 vi.mock('../../src/extension/executors/index.js', () => ({
   default: { foobar: vi.fn() }
diff --git a/tests/extension/provider/background.test.ts b/tests/extension/provider/background.test.ts
index 99b25946..95e659d0 100644
--- a/tests/extension/provider/background.test.ts
+++ b/tests/extension/provider/background.test.ts
@@ -1,6 +1,6 @@
 import { vi, describe, expect, beforeEach, it } from 'vitest'
 
-import { getExecutionProperty, getTerminalByCell } from '../../../src/extension/utils'
+import { getExecutionProperty, getMetadata, getTerminalByCell } from '../../../src/extension/utils'
 import {
   ShowTerminalProvider,
   BackgroundTaskProvider,
@@ -20,13 +20,15 @@ vi.mock('vscode', () => ({
 
 vi.mock('../../../src/extension/utils', () => ({
   getExecutionProperty: vi.fn(),
-  getTerminalByCell: vi.fn()
+  getTerminalByCell: vi.fn(),
+  getMetadata: vi.fn()
 }))
 
 describe('ShowTerminalProvider', () => {
   beforeEach(() => {
     vi.mocked(getExecutionProperty).mockClear()
     vi.mocked(getTerminalByCell).mockClear()
+    vi.mocked(getMetadata).mockClear()
   })
 
   it('dont show pid if cell is non interactive', async () => {
@@ -68,6 +70,7 @@ describe('BackgroundTaskProvider', () => {
   beforeEach(() => {
     vi.mocked(getExecutionProperty).mockClear()
     vi.mocked(getTerminalByCell).mockClear()
+    vi.mocked(getMetadata).mockClear()
   })
 
   it('dont show bg task label if cell is non a background task', async () => {
@@ -79,6 +82,7 @@ describe('BackgroundTaskProvider', () => {
   it('dont show bg task label if cell is non interactive', async () => {
     cell.metadata.background = 'true'
     vi.mocked(getExecutionProperty).mockReturnValueOnce(false)
+    vi.mocked(getMetadata).mockReturnValue(cell.metadata)
     const p = new BackgroundTaskProvider()
     expect(await p.provideCellStatusBarItems(cell as any)).toBe(undefined)
     expect(getExecutionProperty).toBeCalledTimes(1)
@@ -105,9 +109,11 @@ describe('StopBackgroundTaskProvider', () => {
   beforeEach(() => {
     vi.mocked(getExecutionProperty).mockClear()
     vi.mocked(getTerminalByCell).mockClear()
+    vi.mocked(getMetadata).mockClear()
   })
 
   it('dont show bg task label if cell is non a background task', async () => {
+    vi.mocked(getMetadata).mockReturnValue(cell.metadata)
     const p = new StopBackgroundTaskProvider()
     expect(await p.provideCellStatusBarItems(cell as any)).toBe(undefined)
     expect(getExecutionProperty).toBeCalledTimes(0)