Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
212 changes: 38 additions & 174 deletions bun.lock

Large diffs are not rendered by default.

6 changes: 2 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,9 @@
"devDependencies": {
"@turbo/gen": "2.10.7",
"@types/bun": "1.3.14",
"@types/yargs": "17.0.35",
"adamantite": "0.35.0",
"citty": "0.2.2",
"consola": "3.4.2",
"faultier": "3.0.1",
"knip": "6.32.2",
"oxfmt": "0.63.0",
"oxlint": "1.78.0",
Expand All @@ -53,8 +52,7 @@
"sherif": "1.13.0",
"turbo": "2.10.7",
"typescript": "7.0.2",
"varlock": "1.16.0",
"yargs": "18.0.0"
"varlock": "1.16.0"
},
"overrides": {
"@types/react": "19.2.4",
Expand Down
47 changes: 12 additions & 35 deletions scripts/index.ts
Original file line number Diff line number Diff line change
@@ -1,38 +1,15 @@
import consola from "consola"
import { isFault } from "faultier"
import yargs from "yargs"
import { hideBin } from "yargs/helpers"
import { defineCommand, runMain } from "citty"

import template from "./template"

try {
await yargs(hideBin(process.argv))
.scriptName("bun scripts")
.usage("$0 <command>")
.command(template)
.epilogue("Add your own project commands to scripts/index.ts.")
.demandCommand(1, "Choose a command. Run --help to see available commands.")
.strict()
.help()
.fail((message, error: unknown, yargs) => {
if (error !== undefined) throw error as Error

yargs.showHelp()
if (message) consola.error(message)
process.exitCode = 1
})
.parseAsync()
} catch (error) {
if (isFault(error)) {
consola.error(error.flatten())

const details = error.flatten({ field: "details" })
if (details) consola.error(details)
} else if (error instanceof Error) {
consola.error(error.stack ?? error.message)
} else {
consola.error(String(error))
}

process.exitCode = 1
}
const main = defineCommand({
meta: {
description: "Run project and template commands",
name: "bun scripts",
},
subCommands: {
template,
},
})

await runMain(main)
Comment thread
pullfrog[bot] marked this conversation as resolved.
57 changes: 29 additions & 28 deletions scripts/template/add.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { join } from "node:path"
import consola from "consola"

import { defineCommand } from "../utils"
import { defineCommand } from "citty"
import { restorePortlessWorkspaces } from "./portless"
import { renameProject } from "./rename"
import {
Expand All @@ -11,7 +11,6 @@ import {
readJson,
runCommand,
TEMPLATE_SCOPE,
TemplateFault,
type WorkspaceKind,
} from "./shared"

Expand Down Expand Up @@ -55,12 +54,8 @@ async function copyTemplateWorkspace(rootDir: string, workspace: TemplateWorkspa

const packageJsonPath = join(rootDir, workspacePath, "package.json")
if (!(await Bun.file(packageJsonPath).exists())) {
throw TemplateFault.create("CommandFailedError", {
command: command.join(" "),
exitCode: 1,
}).withDescription(
`Command failed: ${command.join(" ")}.`,
`The generator did not create the ${workspacePath} workspace.`
throw new Error(
`Command failed: ${command.join(" ")}. The generator did not create the ${workspacePath} workspace.`
)
}

Expand Down Expand Up @@ -129,32 +124,38 @@ async function addWorkspaces(
}

export default defineCommand({
builder: (yargs) =>
yargs
.positional("kind", {
choices: ["app", "package"] as const,
demandOption: true,
describe: "Workspace type to add",
type: "string",
})
.positional("name", {
demandOption: true,
describe: "Name of the template workspace to copy",
type: "string",
}),
command: "add <kind> <name>",
describe: "Copy an app or package from the init template",
handler: async (args) => {
args: {
kind: {
description: "Workspace type to add: app or package",
required: true,
type: "positional",
},
name: {
description: "Name of the template workspace to copy",
required: true,
type: "positional",
},
},
meta: {
description: "Copy an app or package from the init template",
name: "add",
},
run: async ({ args }) => {
if (args.kind !== "app" && args.kind !== "package") {
consola.error(`Unknown workspace type: ${args.kind}. Use app or package.`)
process.exitCode = 1
return
}

const rootDir = process.cwd()
const scope = await getProjectScope(rootDir)
const target: TemplateWorkspace = { kind: args.kind, name: args.name }
const targetPath = getWorkspacePath(target)

if (await Bun.file(join(rootDir, targetPath, "package.json")).exists()) {
throw TemplateFault.create("InvalidArgumentsError").withDescription(
`The ${targetPath} workspace already exists in this project.`,
"Remove it first if you want to copy it again from the template."
)
consola.error(`The ${targetPath} workspace already exists in this project.`)
process.exitCode = 1
return
}

const copiedPaths = await addWorkspaces(rootDir, scope, [target], new Set([targetPath]))
Expand Down
24 changes: 13 additions & 11 deletions scripts/template/index.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
import { defineCommand } from "../utils"
import { defineCommand } from "citty"

import add from "./add"
import rename from "./rename"
import setup from "./setup"

const subCommands = {
add,
rename,
setup,
}

export default defineCommand({
builder: (yargs) =>
yargs
.command(setup)
.command(rename)
.command(add)
.demandCommand(1, "Choose a template command. Run --help to see available commands.")
.strict(),
command: "template",
describe: "Configure and maintain this template project",
handler: () => Promise.resolve(),
meta: {
description: "Configure and maintain this template project",
name: "template",
},
subCommands,
})
74 changes: 39 additions & 35 deletions scripts/template/rename.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { join, resolve } from "node:path"
import { defineCommand } from "citty"
import consola from "consola"

import { defineCommand } from "../utils"
import { updatePortlessProjectName } from "./portless"
import {
checkIsPathWithinRoot,
Expand All @@ -10,7 +10,6 @@ import {
normalizeScope,
readJson,
replaceTextInFiles,
TemplateFault,
writeJson,
} from "./shared"

Expand Down Expand Up @@ -65,52 +64,57 @@ export async function renameProject({
}

export default defineCommand({
builder: (yargs) =>
yargs
.option("name", {
describe: "New root package name",
type: "string",
})
.option("scope", {
describe: "New npm scope, without the leading @ (defaults to --name)",
type: "string",
})
.option("workspace", {
describe: "Rewrite only this workspace directory",
type: "string",
}),
command: "rename",
describe: "Rename the project and replace the template workspace scope",
handler: async (args) => {
args: {
name: {
description: "New root package name",
type: "string",
},
scope: {
description: "New npm scope, without the leading @ (defaults to --name)",
type: "string",
},
workspace: {
description: "Rewrite only this workspace directory",
type: "string",
},
},
meta: {
description: "Rename the project and replace the template workspace scope",
name: "rename",
},
run: async ({ args }) => {
const projectRoot = resolve(process.cwd())
const projectName = args.name
if (!args.workspace && !projectName) {
throw TemplateFault.create("InvalidArgumentsError").withDescription(
"Provide --name when renaming a project.",
"A project rename requires --name when --workspace is not set."
)
consola.error("Provide --name when you rename a project.")
process.exitCode = 1
return
}

const scope = args.scope ?? projectName
if (!scope) {
throw TemplateFault.create("InvalidArgumentsError").withDescription(
"Provide --scope when renaming a workspace.",
"A workspace rename requires --scope when --name is not set."
)
consola.error("Provide --scope when you rename a workspace.")
process.exitCode = 1
return
}
try {
normalizeScope(scope)
} catch (error) {
consola.error(error instanceof Error ? error.message : error)
process.exitCode = 1
return
}

const rootDir = args.workspace ? resolve(projectRoot, args.workspace) : projectRoot
if (args.workspace && !checkIsPathWithinRoot(projectRoot, rootDir)) {
throw TemplateFault.create("PathEscapeError", { path: args.workspace }).withDescription(
"Workspace path must be inside the project.",
`Resolved workspace path: ${rootDir}.`
)
consola.error(`Workspace path must be inside the project: ${rootDir}.`)
process.exitCode = 1
return
}
if (!(await Bun.file(join(rootDir, "package.json")).exists())) {
throw TemplateFault.create("InvalidArgumentsError").withDescription(
"Workspace path must contain a package.json file.",
`Resolved workspace path: ${rootDir}.`
)
consola.error(`Workspace path must contain a package.json file: ${rootDir}.`)
process.exitCode = 1
return
}

const result = await renameProject({
Expand Down
Loading