diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..daa5abb --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,33 @@ +--- +name: Bug report +about: Create a report to help us improve +title: "[BUG] " +labels: bug +assignees: "" +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: + +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Desktop (please complete the following information):** + +- OS: [e.g. iOS] +- Browser [e.g. chrome, safari] +- Version [e.g. 22] + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..205ef6c --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,19 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: "[FEATURE] " +labels: enhancement +assignees: "" +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml new file mode 100644 index 0000000..c260c67 --- /dev/null +++ b/.github/workflows/test.yaml @@ -0,0 +1,27 @@ +name: Run tests for the CLI + +on: + push: + branches: ["main"] + pull_request: + branches: ["main"] + workflow_dispatch: + +jobs: + test: + runs-on: ubuntu-latest + permissions: + contents: read + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version-file: "go.mod" + cache: true + + - name: Run tests + run: make test diff --git a/.goreleaser.yaml b/.goreleaser.yaml index 9be041e..d85f2a7 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -1,9 +1,6 @@ version: 2 project_name: kernel -monorepo: - tag_prefix: cli/ - before: hooks: - go mod tidy @@ -28,8 +25,6 @@ archives: - id: archive formats: [tar.gz] name_template: "{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}" - # files: - # - LICENSE blobs: - provider: s3 diff --git a/go.mod b/go.mod index c2379ed..a8b4fb9 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/onkernel/cli -go 1.24.3 +go 1.25.0 require ( github.com/boyter/gocodewalker v1.4.0 diff --git a/index.ts b/index.ts deleted file mode 100644 index 056d841..0000000 --- a/index.ts +++ /dev/null @@ -1,142 +0,0 @@ -#!/usr/bin/env bun -import { Kernel } from '@onkernel/sdk'; -import chalk from 'chalk'; -import { Command } from 'commander'; -import fs, { createReadStream } from 'fs'; -import path from 'path'; -import * as tmp from 'tmp'; -import { getPackageVersion, zipDirectory } from './lib/util'; - -const program = new Command(); - -if (process.argv.length === 3 && ['-v', '--version'].includes(process.argv[2]!)) { - console.log(getPackageVersion()); - process.exit(0); -} - -program.name('kernel').description('CLI for Kernel deployment and invocation'); - -program - .command('deploy') - .description('Deploy a Kernel application') - .argument('', 'Path to entrypoint file (TypeScript or Python)') - .option('--version ', 'Specify a version for the app (default: latest)') - .option('--force', 'Allow overwrite of an existing version with the same name') - .action(async (entrypoint, options) => { - let { version: versionArg, force } = options; - // if version not specified, use latest - // if version and force not specified, user latest and force true - if (!versionArg) { - versionArg = 'latest'; - if (force && force !== 'true') { - console.error('Error: --force must be used when version is latest'); - process.exit(1); - } else if (!force) { - force = 'true'; - } - } - const resolvedEntrypoint = path.resolve(entrypoint); - if (!fs.existsSync(resolvedEntrypoint)) { - console.error(`Error: Entrypoint ${resolvedEntrypoint} doesn't exist`); - process.exit(1); - } - const sourceDir = path.dirname(resolvedEntrypoint); // TODO: handle nested entrypoint, i.e. ./src/entrypoint.ts - - if (!process.env['KERNEL_API_KEY']) { - console.error('Error: KERNEL_API_KEY environment variable is not set'); - console.error('Please set your Kernel API key using: export KERNEL_API_KEY=your_api_key'); - process.exit(1); - } - const client = new Kernel(); - - console.log(chalk.green(`Compressing files...`)); - const tmpZipFile = tmp.fileSync({ postfix: '.zip' }); - try { - await zipDirectory(path.join(sourceDir), tmpZipFile.name); - console.log(chalk.green(`Deploying app (version: ${versionArg})...`)); - const response = await client.apps.deploy( - { - file: createReadStream(tmpZipFile.name), - version: versionArg, - force, - entrypointRelPath: path.relative(sourceDir, resolvedEntrypoint), - }, - { maxRetries: 0 }, - ); - - // todo: pull app name from response - if (!response.success) { - console.error('Error deploying to Kernel:', response.message); - process.exit(1); - } - - for (const app of response.apps) { - console.log( - chalk.green( - `App "${app.name}" successfully deployed to Kernel with action${app.actions.length > 1 ? 's' : ''}: ${app.actions.map((a) => a.name).join(', ')}`, - ), - ); - console.log( - `You can invoke it with: kernel invoke${versionArg !== 'latest' ? ` --version ${versionArg}` : ''} ${quoteIfNeeded(app.name)} ${quoteIfNeeded(app.actions[0]!.name)} '{ ... JSON payload ... }'`, - ); - } - } catch (error) { - console.error('Error deploying to Kernel:', error); - process.exit(1); - } finally { - // Clean up temp file - tmpZipFile.removeCallback(); - } - }); - -function quoteIfNeeded(str: string) { - if (str.includes(' ')) { - return `"${str}"`; - } - return str; -} - -program - .command('invoke') - .description('Invoke a deployed Kernel application') - .option('--version ', 'Specify a version of the app to invoke') - .argument('', 'Name of the application to invoke') - .argument('', 'Name of the action to invoke') - .argument('', 'JSON payload to send to the application') - .action(async (appName, actionName, payload, options) => { - let parsedPayload; - try { - parsedPayload = JSON.parse(payload); - } catch (error) { - console.error('Error: Invalid JSON payload'); - process.exit(1); - } - - if (!process.env['KERNEL_API_KEY']) { - console.error('Error: KERNEL_API_KEY environment variable is not set'); - console.error('Please set your Kernel API key using: export KERNEL_API_KEY=your_api_key'); - process.exit(1); - } - const client = new Kernel(); - - console.log(`Invoking "${appName}" with action "${actionName}" and payload:`); - console.log(JSON.stringify(parsedPayload, null, 2)); - - try { - const response = await client.apps.invoke({ - appName, - actionName, - payload, - ...(options.version && { version: options.version }), - }); - - console.log('Result:'); - console.log(JSON.stringify(JSON.parse(response.output || '{}'), null, 2)); - } catch (error) { - console.error('Error invoking application:', error); - process.exit(1); - } - return; - }); - -program.parse();