diff --git a/packages/workbench-cli/README.md b/packages/workbench-cli/README.md index ced3c93..996c987 100644 --- a/packages/workbench-cli/README.md +++ b/packages/workbench-cli/README.md @@ -5,7 +5,7 @@ [![JSR](https://jsr.io/badges/@pap/workbench)](https://jsr.io/@pap/workbench) [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](../../LICENSE) -A terminal UI for initializing a workbench repository — fork, clone, and wire up git submodules interactively or non-interactively. +A terminal UI for initializing a workbench repository — clone and wire up git submodules interactively or non-interactively. ## Prerequisites @@ -36,7 +36,7 @@ bun run src/index.ts workbench --init ``` -Launches an interactive flow: select a fork target (org or personal account), name your workbench, fork and clone the template repo, then optionally run the setup wizard. +Launches an interactive flow: enter source repository and name, clone the template repo, optionally create a private remote, then optionally run the setup wizard. ### Non-interactive @@ -62,9 +62,10 @@ workbench --tui | Flag | Description | Default | |------|-------------|---------| -| `--init` | Initialize a new workbench (fork & clone) | `false` | -| `--name ` | Name for the fork and local folder | `workbench` | -| `--no-fork` | Clone without forking (read-only) | `false` | +| `--init` | Initialize a new workbench (clone) | `false` | +| `--name ` | Name for the local folder | `workbench` | +| `--source ` | Source repository to clone from | `plan-and-publish/workbench` | +| `--remote` | Create a private GitHub repo and set as origin | `false` | | `--no-tui` | Skip TUI, use defaults or provided values | `false` | ## Setup flags @@ -92,11 +93,14 @@ workbench --init # Non-interactive init with custom name workbench --init --no-tui --name my-project -# Clone without forking (read-only) -workbench --init --no-tui --no-fork --name explore-wb +# Clone from a custom source +workbench --init --no-tui --name my-project --source myorg/custom-workbench -# Init + setup in one command -workbench --init --no-tui --name my-project --org myorg --code-repository https://github.com/myorg/api +# Create a private remote repository +workbench --init --no-tui --name my-project --remote --org myorg + +# Init + remote + setup in one command +workbench --init --no-tui --name my-project --remote --org myorg --code-repository https://github.com/myorg/api # Standalone setup (existing repo) workbench --org myorg --code-repository https://github.com/myorg/backend @@ -109,7 +113,7 @@ workbench --tui Running init walks through: -1. Select a GitHub organization or personal account. +1. Enter source repository and name. 2. Select code repositories — added as submodules under `projects/`. 3. Select resource repositories — added as submodules under `resources/`. 4. Configure the target branch per repository. @@ -121,8 +125,8 @@ Afterwards, `.workbench/config.yaml` is written with the selected configuration. | Error | Cause | Resolution | |-------|-------|------------| -| `A repository named "X" already exists under Y` | Fork name conflict | Choose a different `--name` | | `A folder named "X" already exists in the current directory` | Local folder conflict | Remove or rename the folder, or choose a different name | +| `Remote creation failed` | `gh repo create` failed | Check `gh auth login` and org permissions | | `gh CLI is not authenticated` | `gh auth` not set up | Run `gh auth login` | | `Invalid name "X"` | Bad characters in name | Use only alphanumeric, `-`, `.`, `_` | diff --git a/packages/workbench-cli/src/args.ts b/packages/workbench-cli/src/args.ts index 730b7db..fec3bf1 100644 --- a/packages/workbench-cli/src/args.ts +++ b/packages/workbench-cli/src/args.ts @@ -10,7 +10,8 @@ export interface CliArgs { resourceBranch: string index: boolean init: boolean - noFork: boolean + source: string + remote: boolean name: string noTui: boolean } @@ -27,7 +28,8 @@ export function parseCliArgs(): CliArgs { "resource-branch": { type: "string", default: "main" }, index: { type: "string", default: "on" }, init: { type: "boolean", default: false }, - "no-fork": { type: "boolean", default: false }, + source: { type: "string", default: "plan-and-publish/workbench" }, + remote: { type: "boolean", default: false }, name: { type: "string", default: "workbench" }, "no-tui": { type: "boolean", default: false }, }, @@ -45,7 +47,8 @@ export function parseCliArgs(): CliArgs { resourceBranch: values["resource-branch"] as string, index: values.index === "on", init: values.init, - noFork: values["no-fork"] as boolean, + source: values.source as string, + remote: values.remote as boolean, name: values.name as string, noTui: values["no-tui"] as boolean, } @@ -62,9 +65,10 @@ USAGE: workbench --help OPTIONS: - --init Initialize a new workbench (fork & clone) - --name Name for the fork and local folder (default: workbench) - --no-fork Clone without forking (read-only) + --init Initialize a new workbench (clone) + --name Name for the local folder (default: workbench) + --source Source repository to clone from (default: plan-and-publish/workbench) + --remote Create a private GitHub repo and set as origin --no-tui Skip TUI, use defaults or provided values --org GitHub organization name --code-repository Code repository URL (can be repeated) @@ -78,8 +82,9 @@ OPTIONS: EXAMPLES: workbench --init workbench --init --no-tui --name my-project - workbench --init --no-tui --no-fork --name explore-wb - workbench --init --no-tui --name my-project --org myorg --code-repository https://github.com/myorg/api + workbench --init --no-tui --name my-project --source myorg/custom-wb + workbench --init --no-tui --name my-project --remote --org myorg + workbench --init --no-tui --name my-project --remote --org myorg --code-repository https://github.com/myorg/api workbench --org myorg --code-repository https://github.com/myorg/backend workbench --tui `) diff --git a/packages/workbench-cli/src/commands/initialise.ts b/packages/workbench-cli/src/commands/initialise.ts index a77ef94..a29d958 100644 --- a/packages/workbench-cli/src/commands/initialise.ts +++ b/packages/workbench-cli/src/commands/initialise.ts @@ -1,21 +1,21 @@ import { existsSync } from "fs" import { runCommand } from "../utils/spawn.ts" -import { forkRepo, repoExists, validateRepoName } from "../utils/gh.ts" +import { createRepo, validateRepoName } from "../utils/gh.ts" import { showInitOrgSelect } from "../screens/initOrgSelect.ts" import { showInitNameInput } from "../screens/initNameInput.ts" import { showInitSetupPrompt } from "../screens/initSetupPrompt.ts" import { showExecutingScreen } from "../screens/executing.ts" +import { showSourceInput } from "../screens/initSourceInput.ts" +import { showRemotePrompt } from "../screens/initRemotePrompt.ts" +import { showRemoteNameInput } from "../screens/initRemoteNameInput.ts" import type { InitProgress } from "./init.ts" import type { CliRenderer } from "@opentui/core" import type { CliArgs } from "../args.ts" -const SOURCE_REPO = "plan-and-publish/workbench" - export interface InitialiseState { name: string - noFork: boolean - targetOrg: string - isPersonalAccount: boolean + source: string + targetOrg?: string } export interface InitialiseResult { @@ -34,48 +34,15 @@ export function validateInitialiseState(state: InitialiseState): string | null { return null } -export async function executeFork( - state: InitialiseState, - progress: InitProgress -): Promise<{ success: boolean; error?: string }> { - if (state.noFork) { - return { success: true } - } - - const exists = await repoExists(state.targetOrg, state.name) - if (exists) { - return { success: false, error: `A repository named "${state.name}" already exists under ${state.targetOrg}.` } - } - - const { onLine, startThrottle, stopThrottle } = progress - onLine(`--- Forking and cloning into ./${state.name}/ ---`, true, false) - startThrottle() - try { - await forkRepo( - SOURCE_REPO, - state.isPersonalAccount ? undefined : state.targetOrg, - state.name, - (line, isStderr, isCR) => onLine(line, false, isCR) - ) - } finally { - stopThrottle() - } - - return { success: true } -} - export async function executeClone( - cloneUrl: string, + source: string, name: string, progress: InitProgress ): Promise { const { onLine, startThrottle, stopThrottle } = progress + const cloneUrl = source.includes("://") ? source : `https://github.com/${source}.git` try { - if (existsSync(name)) { - return { success: false, error: `A folder named "${name}" already exists in the current directory.` } - } - onLine(`--- Cloning into ./${name}/ ---`, true, false) startThrottle() try { @@ -88,13 +55,47 @@ export async function executeClone( process.chdir(name) onLine(`Working directory changed to ./${name}/`, false, false) - return { success: true, targetDir: name } } catch (error) { return { success: false, error: String(error) } } } +export async function executeRemoveOrigin( + progress: InitProgress +): Promise { + try { + await runCommand("git", ["remote", "remove", "origin"], (line, _, isCR) => + progress.onLine(line, false, isCR) + ) + progress.onLine("Removed origin remote. To add a remote later: git remote add origin ", false, false) + } catch { + progress.onLine("No origin remote to remove (skipped)", false, false) + } +} + +export async function executeCreateRemote( + org: string, + repoName: string, + progress: InitProgress +): Promise { + const { onLine } = progress + try { + onLine(`--- Creating private repository ${org}/${repoName} ---`, true, false) + const output = await createRepo(org, repoName) + const urlMatch = output.match(/https:\/\/github\.com\/[^\s]+/) + const repoUrl = urlMatch ? urlMatch[0] : output + + await runCommand("git", ["remote", "add", "origin", repoUrl.endsWith(".git") ? repoUrl : `${repoUrl}.git`], (line, _, isCR) => + onLine(line, false, isCR) + ) + onLine(`Remote origin set to ${repoUrl}`, false, false) + return { success: true } + } catch (error) { + return { success: false, error: String(error) } + } +} + export async function executeInitialise( state: InitialiseState, progress: InitProgress @@ -104,19 +105,13 @@ export async function executeInitialise( return { success: false, error: validationError } } - if (state.noFork) { - const cloneUrl = `https://github.com/${SOURCE_REPO}.git` - return executeClone(cloneUrl, state.name, progress) - } - - const forkResult = await executeFork(state, progress) - if (!forkResult.success) { - return { success: false, error: forkResult.error } + const result = await executeClone(state.source, state.name, progress) + if (!result.success) { + return result } - process.chdir(state.name) - progress.onLine(`Working directory changed to ./${state.name}/`, false, false) - return { success: true, targetDir: state.name } + await executeRemoveOrigin(progress) + return result } export function runInitialiseFlow( @@ -124,30 +119,52 @@ export function runInitialiseFlow( args: CliArgs, onComplete: () => void ): void { - void showInitOrgSelect(renderer, args.org, (targetOrg, isPersonalAccount) => { - setTimeout(() => { - showInitNameInput(renderer, args.name, (name) => { - const state: InitialiseState = { name, noFork: args.noFork, targetOrg, isPersonalAccount } - void runTuiInitialise(renderer, state, (success) => { - if (!success) { - onComplete() - return - } - setTimeout(() => { - showInitSetupPrompt(renderer, (shouldSetup) => { - if (shouldSetup) { - void runTuiSetupAfterInit(renderer, onComplete) - } else { - console.log("\nTo set up your workbench later, run: workbench --tui") - renderer.destroy() - process.exit(0) - } - }) - }, 0) + setTimeout(() => { + showSourceInput(renderer, args.source, (source) => { + setTimeout(() => { + showInitNameInput(renderer, args.name, (name) => { + const state: InitialiseState = { name, source } + void runTuiInitialise(renderer, state, (success) => { + if (!success) { + onComplete() + return + } + setTimeout(() => { + showRemotePrompt(renderer, (shouldCreateRemote) => { + if (shouldCreateRemote) { + void runTuiRemoteCreation(renderer, args.org, name, () => { + setTimeout(() => { + showInitSetupPrompt(renderer, (shouldSetup) => { + if (shouldSetup) { + void runTuiSetupAfterInit(renderer, onComplete) + } else { + console.log("\nTo set up your workbench later, run: workbench --tui") + renderer.destroy() + process.exit(0) + } + }) + }, 0) + }) + } else { + setTimeout(() => { + showInitSetupPrompt(renderer, (shouldSetup) => { + if (shouldSetup) { + void runTuiSetupAfterInit(renderer, onComplete) + } else { + console.log("\nTo set up your workbench later, run: workbench --tui") + renderer.destroy() + process.exit(0) + } + }) + }, 0) + } + }) + }, 0) + }) }) - }) - }, 0) - }) + }, 0) + }) + }, 0) } async function runTuiInitialise( @@ -162,35 +179,60 @@ async function runTuiInitialise( stopThrottle, } - let success: boolean - - if (!state.noFork) { - const forkResult = await executeFork(state, progress) - if (!forkResult.success) { - appendLine(`Error: ${forkResult.error}`, true) - success = false - } else { - process.chdir(state.name) - progress.onLine(`Working directory changed to ./${state.name}/`, false, false) - success = true - } - } else { - const cloneUrl = `https://github.com/${SOURCE_REPO}.git` - const result = await executeClone(cloneUrl, state.name, progress) - success = result.success - if (!result.success) { - appendLine(`Error: ${result.error}`, true) - } + const result = await executeInitialise(state, progress) + if (!result.success) { + appendLine(`Error: ${result.error}`, true) } - if (success) { + if (result.success) { appendLine("--- Initialisation complete ---", true) } const handler = () => { renderer.keyInput.off("keypress", handler) container.visible = false - onDone(success) + onDone(result.success) + } + renderer.keyInput.on("keypress", handler) +} + +async function runTuiRemoteCreation( + renderer: CliRenderer, + preselectedOrg: string | undefined, + defaultRepoName: string, + onDone: () => void +): Promise { + void showInitOrgSelect(renderer, preselectedOrg, "Create Remote Under", (orgLogin) => { + setTimeout(() => { + showRemoteNameInput(renderer, defaultRepoName, (repoName) => { + void runTuiCreateRemote(renderer, orgLogin, repoName, onDone) + }) + }, 0) + }) +} + +async function runTuiCreateRemote( + renderer: CliRenderer, + org: string, + repoName: string, + onDone: () => void +): Promise { + const { appendLine, startThrottle, stopThrottle, container } = showExecutingScreen(renderer) + const progress: InitProgress = { + onLine: (line, isHeader, isCR) => appendLine(line, isHeader, isCR), + startThrottle, + stopThrottle, + } + + const result = await executeCreateRemote(org, repoName, progress) + if (!result.success) { + appendLine(`Error: ${result.error}`, true) + } + + const handler = () => { + renderer.keyInput.off("keypress", handler) + container.visible = false + onDone() } renderer.keyInput.on("keypress", handler) } diff --git a/packages/workbench-cli/src/index.ts b/packages/workbench-cli/src/index.ts index 7662b36..23bebb7 100644 --- a/packages/workbench-cli/src/index.ts +++ b/packages/workbench-cli/src/index.ts @@ -5,7 +5,7 @@ import { dirname, join } from "node:path" import { checkAuth, checkRepoRoot, getCurrentUserLogin } from "./utils/gh.ts" import { showMainMenu } from "./screens/mainMenu.ts" import { runInitFlow, executeInit, type InitState, type InitProgress } from "./commands/init.ts" -import { executeInitialise, validateInitialiseState, runInitialiseFlow, type InitialiseState } from "./commands/initialise.ts" +import { executeInitialise, executeCreateRemote, validateInitialiseState, runInitialiseFlow, type InitialiseState } from "./commands/initialise.ts" import { parseCliArgs, printHelp, type CliArgs } from "./args.ts" import { buildRepoFromUrl } from "./utils/repo.ts" import type { Repo } from "./screens/repoSelect.ts" @@ -171,12 +171,9 @@ async function runNonInteractiveInit(args: CliArgs): Promise { } async function runNonInteractiveInitCmd(args: CliArgs): Promise { - const targetOrg = args.org ?? (await getCurrentUserLogin()) const state: InitialiseState = { name: args.name, - noFork: args.noFork, - targetOrg, - isPersonalAccount: !args.org, + source: args.source, } const validationError = validateInitialiseState(state) @@ -204,9 +201,23 @@ async function runNonInteractiveInitCmd(args: CliArgs): Promise { process.exit(1) } + if (args.remote) { + const targetOrg = args.org ?? (await getCurrentUserLogin()) + const remoteResult = await executeCreateRemote(targetOrg, args.name, stdoutProgress) + if (!remoteResult.success) { + console.error(remoteResult.error || "Remote creation failed") + process.exit(1) + } + } + const hasRepos = args.codeRepositories.length > 0 || args.resourceRepositories.length > 0 if (!hasRepos) { - console.log(`\nWorkbench initialised in ./${state.name}/`) + if (args.remote) { + console.log(`\nWorkbench initialised and remote configured in ./${state.name}/`) + } else { + console.log(`\nWorkbench initialised in ./${state.name}/`) + console.log("To add a remote: git remote add origin ") + } console.log("To set up your workbench, run: workbench --tui") process.exit(0) } @@ -216,6 +227,7 @@ async function runNonInteractiveInitCmd(args: CliArgs): Promise { process.exit(1) } + const targetOrg = args.org ?? (await getCurrentUserLogin()) const codeRepos: Repo[] = args.codeRepositories.map((url) => buildRepoFromUrl(url, args.codeBranch) ) diff --git a/packages/workbench-cli/src/screens/initOrgSelect.ts b/packages/workbench-cli/src/screens/initOrgSelect.ts index 427dab7..f470e8f 100644 --- a/packages/workbench-cli/src/screens/initOrgSelect.ts +++ b/packages/workbench-cli/src/screens/initOrgSelect.ts @@ -16,7 +16,8 @@ const SCREEN_ID = "init-org-select-screen" export async function showInitOrgSelect( renderer: CliRenderer, preselectedOrg: string | undefined, - onSelect: (orgLogin: string, isPersonalAccount: boolean) => void + title: string, + onSelect: (orgLogin: string) => void ): Promise { const existing = renderer.root.getRenderable(SCREEN_ID) if (existing) { @@ -48,12 +49,12 @@ export async function showInitOrgSelect( } spinner.stop() - const title = new TextRenderable(renderer, { + const titleText = new TextRenderable(renderer, { id: "init-org-title", - content: "Select Fork Target", + content: title, fg: "#00FFFF", }) - container.add(title) + container.add(titleText) const hint = new TextRenderable(renderer, { id: "init-org-hint", @@ -117,9 +118,7 @@ export async function showInitOrgSelect( (_index: number, option: { value: string }) => { renderer.keyInput.off("keypress", keypressHandler) container.visible = false - const selectedOrg = orgs.find((o) => o.login === option.value) - const isPersonalAccount = selectedOrg?.description === "Personal account" - onSelect(option.value, isPersonalAccount) + onSelect(option.value) } ) diff --git a/packages/workbench-cli/src/screens/initRemoteNameInput.ts b/packages/workbench-cli/src/screens/initRemoteNameInput.ts new file mode 100644 index 0000000..1c7379f --- /dev/null +++ b/packages/workbench-cli/src/screens/initRemoteNameInput.ts @@ -0,0 +1,79 @@ +import { + InputRenderable, + TextRenderable, + BoxRenderable, + type CliRenderer, + type KeyEvent, +} from "@opentui/core" +import { validateRepoName } from "../utils/gh.ts" + +const SCREEN_ID = "remote-name-input-screen" + +export function showRemoteNameInput( + renderer: CliRenderer, + prefilledName: string, + onConfirm: (name: string) => void +): void { + const existing = renderer.root.getRenderable(SCREEN_ID) + if (existing) { + renderer.root.remove(SCREEN_ID) + } + + const container = new BoxRenderable(renderer, { + id: SCREEN_ID, + flexDirection: "column", + padding: 1, + }) + + const title = new TextRenderable(renderer, { + id: "remote-name-title", + content: "Remote Repository Name", + fg: "#00FFFF", + }) + container.add(title) + + const hint = new TextRenderable(renderer, { + id: "remote-name-hint", + content: "Enter a name (alphanumeric, -, ., _) | Enter to confirm", + fg: "#888888", + }) + container.add(hint) + + const nameInput = new InputRenderable(renderer, { + id: "remote-name-input", + width: 50, + value: prefilledName, + backgroundColor: "#1a1a1a", + textColor: "#FFFFFF", + focusedBackgroundColor: "#2a2a2a", + }) + + const errorText = new TextRenderable(renderer, { + id: "remote-name-error", + content: "", + fg: "#FF4444", + }) + errorText.visible = false + + container.add(nameInput) + container.add(errorText) + renderer.root.add(container) + + const keypressHandler = (key: KeyEvent) => { + if (key.name === "return" || key.name === "enter") { + const name = nameInput.value.trim() + if (!validateRepoName(name)) { + errorText.content = `Invalid name "${name}". Use only alphanumeric characters, hyphens, dots, and underscores.` + errorText.visible = true + return + } + renderer.keyInput.off("keypress", keypressHandler) + container.visible = false + onConfirm(name) + } + } + + renderer.keyInput.on("keypress", keypressHandler) + + nameInput.focus() +} diff --git a/packages/workbench-cli/src/screens/initRemotePrompt.ts b/packages/workbench-cli/src/screens/initRemotePrompt.ts new file mode 100644 index 0000000..b3d4506 --- /dev/null +++ b/packages/workbench-cli/src/screens/initRemotePrompt.ts @@ -0,0 +1,64 @@ +import { + SelectRenderable, + SelectRenderableEvents, + TextRenderable, + BoxRenderable, + type CliRenderer, +} from "@opentui/core" + +const SCREEN_ID = "remote-prompt-screen" + +export function showRemotePrompt( + renderer: CliRenderer, + onAnswer: (shouldCreateRemote: boolean) => void +): void { + const existing = renderer.root.getRenderable(SCREEN_ID) + if (existing) { + renderer.root.remove(SCREEN_ID) + } + + const container = new BoxRenderable(renderer, { + id: SCREEN_ID, + flexDirection: "column", + padding: 1, + }) + + const title = new TextRenderable(renderer, { + id: "remote-prompt-title", + content: "Set up a remote?", + fg: "#00FFFF", + }) + container.add(title) + + const hint = new TextRenderable(renderer, { + id: "remote-prompt-hint", + content: "A private repo will be created on GitHub and set as origin", + fg: "#888888", + }) + container.add(hint) + + const options = [ + { name: "yes", description: "Create a private GitHub repository", value: "yes" }, + { name: "no", description: "Add a remote manually later", value: "no" }, + ] + + const select = new SelectRenderable(renderer, { + id: "remote-prompt-select", + width: 40, + height: 4, + options, + selectedIndex: 1, + }) + + select.on( + SelectRenderableEvents.ITEM_SELECTED, + (_index: number, option: { value: string }) => { + container.visible = false + onAnswer(option.value === "yes") + } + ) + + container.add(select) + renderer.root.add(container) + select.focus() +} diff --git a/packages/workbench-cli/src/screens/initSourceInput.ts b/packages/workbench-cli/src/screens/initSourceInput.ts new file mode 100644 index 0000000..2fa02ff --- /dev/null +++ b/packages/workbench-cli/src/screens/initSourceInput.ts @@ -0,0 +1,78 @@ +import { + InputRenderable, + TextRenderable, + BoxRenderable, + type CliRenderer, + type KeyEvent, +} from "@opentui/core" + +const SCREEN_ID = "source-input-screen" + +export function showSourceInput( + renderer: CliRenderer, + prefilledSource: string, + onConfirm: (source: string) => void +): void { + const existing = renderer.root.getRenderable(SCREEN_ID) + if (existing) { + renderer.root.remove(SCREEN_ID) + } + + const container = new BoxRenderable(renderer, { + id: SCREEN_ID, + flexDirection: "column", + padding: 1, + }) + + const title = new TextRenderable(renderer, { + id: "source-input-title", + content: "Source Repository", + fg: "#00FFFF", + }) + container.add(title) + + const hint = new TextRenderable(renderer, { + id: "source-input-hint", + content: "Enter source repo (owner/repo or URL) | Enter to confirm", + fg: "#888888", + }) + container.add(hint) + + const sourceInput = new InputRenderable(renderer, { + id: "source-input-field", + width: 50, + value: prefilledSource, + backgroundColor: "#1a1a1a", + textColor: "#FFFFFF", + focusedBackgroundColor: "#2a2a2a", + }) + + const errorText = new TextRenderable(renderer, { + id: "source-input-error", + content: "", + fg: "#FF4444", + }) + errorText.visible = false + + container.add(sourceInput) + container.add(errorText) + renderer.root.add(container) + + const keypressHandler = (key: KeyEvent) => { + if (key.name === "return" || key.name === "enter") { + const source = sourceInput.value.trim() + if (source.length === 0) { + errorText.content = "Source cannot be empty" + errorText.visible = true + return + } + renderer.keyInput.off("keypress", keypressHandler) + container.visible = false + onConfirm(source) + } + } + + renderer.keyInput.on("keypress", keypressHandler) + + sourceInput.focus() +} diff --git a/packages/workbench-cli/src/utils/gh.ts b/packages/workbench-cli/src/utils/gh.ts index 8986729..5b2984e 100644 --- a/packages/workbench-cli/src/utils/gh.ts +++ b/packages/workbench-cli/src/utils/gh.ts @@ -2,7 +2,6 @@ import { execSync, execFile } from "child_process" import { promisify } from "util" import { existsSync } from "fs" import { join } from "path" -import { runCommand, type LineHandler } from "./spawn.ts" const execFileAsync = promisify(execFile) @@ -78,22 +77,16 @@ export async function repoExists(owner: string, repo: string): Promise } } -export async function forkRepo( - sourceRepo: string, - targetOrg: string | undefined, - forkName: string, - onLine: LineHandler -): Promise { - const args = [ - "repo", "fork", sourceRepo, - "--fork-name", forkName, - "--clone", - "--default-branch-only", - ] - if (targetOrg !== undefined) { - args.push("--org", targetOrg) - } - await runCommand("gh", args, onLine) +export async function createRepo( + org: string, + name: string +): Promise { + const { stdout } = await execFileAsync( + "gh", + ["repo", "create", `${org}/${name}`, "--private"], + { encoding: "utf8" } + ) + return stdout.trim() } export async function getCurrentUserLogin(): Promise {