mirror of
https://github.com/shishantbiswas/bknd.git
synced 2026-03-16 04:27:21 +00:00
finalize initial starters
This commit is contained in:
@@ -6,7 +6,7 @@ import { typewriter, wait } from "cli/utils/cli";
|
||||
import { exec, getVersion } from "cli/utils/sys";
|
||||
import { Option } from "commander";
|
||||
import color from "picocolors";
|
||||
import { updateBkndPackages } from "./npm";
|
||||
import { overridePackageJson, updateBkndPackages } from "./npm";
|
||||
import { type Template, templates } from "./templates";
|
||||
|
||||
const config = {
|
||||
@@ -36,16 +36,21 @@ export const create: CliCommand = (program) => {
|
||||
.action(action);
|
||||
};
|
||||
|
||||
function errorOutro() {
|
||||
$p.outro(color.red("Failed to create project."));
|
||||
console.log(
|
||||
color.yellow("Sorry that this happened. If you think this is a bug, please report it at: ") +
|
||||
color.cyan("https://github.com/bknd-io/bknd/issues")
|
||||
);
|
||||
console.log("");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
async function action(options: { template?: string; dir?: string; integration?: string }) {
|
||||
const _config = {
|
||||
defaultDir: process.env.LOCAL ? "./.template" : "./",
|
||||
speed: {
|
||||
typewriter: process.env.LOCAL ? 0 : 20,
|
||||
wait: process.env.LOCAL ? 0 : 250
|
||||
}
|
||||
};
|
||||
console.log("");
|
||||
|
||||
const downloadOpts = {
|
||||
dir: options.dir || _config.defaultDir,
|
||||
dir: options.dir || "./",
|
||||
clean: false
|
||||
};
|
||||
|
||||
@@ -56,12 +61,8 @@ async function action(options: { template?: string; dir?: string; integration?:
|
||||
|
||||
await $p.stream.message(
|
||||
(async function* () {
|
||||
yield* typewriter(
|
||||
"Thanks for choosing to create a new project with bknd!",
|
||||
_config.speed.typewriter,
|
||||
color.dim
|
||||
);
|
||||
await wait(_config.speed.wait);
|
||||
yield* typewriter("Thanks for choosing to create a new project with bknd!", color.dim);
|
||||
await wait();
|
||||
})()
|
||||
);
|
||||
|
||||
@@ -90,6 +91,12 @@ async function action(options: { template?: string; dir?: string; integration?:
|
||||
downloadOpts.clean = clean;
|
||||
}
|
||||
|
||||
let name = downloadOpts.dir.includes("/")
|
||||
? downloadOpts.dir.split("/").pop()
|
||||
: downloadOpts.dir.replace(/[./]/g, "");
|
||||
|
||||
if (!name || name.length === 0) name = "bknd";
|
||||
|
||||
let template: Template | undefined;
|
||||
if (options.template) {
|
||||
template = templates.find((t) => t.key === options.template) as Template;
|
||||
@@ -102,14 +109,10 @@ async function action(options: { template?: string; dir?: string; integration?:
|
||||
if (!integration) {
|
||||
await $p.stream.info(
|
||||
(async function* () {
|
||||
yield* typewriter("Ready? ", _config.speed.typewriter * 1.5, color.bold);
|
||||
await wait(500);
|
||||
yield* typewriter(
|
||||
"Let's find the perfect template for you.",
|
||||
_config.speed.typewriter,
|
||||
color.dim
|
||||
);
|
||||
await wait(500);
|
||||
yield* typewriter("Ready? ", color.bold, 1.5);
|
||||
await wait(2);
|
||||
yield* typewriter("Let's find the perfect template for you.", color.dim);
|
||||
await wait(2);
|
||||
})()
|
||||
);
|
||||
|
||||
@@ -169,12 +172,13 @@ async function action(options: { template?: string; dir?: string; integration?:
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const ctx = { template, dir: downloadOpts.dir };
|
||||
const ctx = { template, dir: downloadOpts.dir, name };
|
||||
|
||||
{
|
||||
// @todo: only while in PR
|
||||
const ref = "feat/cli-starters";
|
||||
//const ref = `v${version}`;
|
||||
const ref = process.env.BKND_CLI_CREATE_REF ?? `v${version}`;
|
||||
if (process.env.BKND_CLI_CREATE_REF) {
|
||||
$p.log.warn(color.dim("[DEV] Using local ref: ") + color.yellow(ref));
|
||||
}
|
||||
|
||||
const prefix =
|
||||
template.ref === true
|
||||
@@ -187,11 +191,21 @@ async function action(options: { template?: string; dir?: string; integration?:
|
||||
//console.log("url", url);
|
||||
const s = $p.spinner();
|
||||
s.start("Downloading template...");
|
||||
const result = await downloadTemplate(url, {
|
||||
dir: ctx.dir,
|
||||
force: downloadOpts.clean ? "clean" : true
|
||||
});
|
||||
//console.log("result", result);
|
||||
try {
|
||||
await downloadTemplate(url, {
|
||||
dir: ctx.dir,
|
||||
force: downloadOpts.clean ? "clean" : true
|
||||
});
|
||||
} catch (e) {
|
||||
if (e instanceof Error) {
|
||||
s.stop("Failed to download template: " + color.red(e.message), 1);
|
||||
} else {
|
||||
console.error(e);
|
||||
s.stop("Failed to download template. Check logs above.", 1);
|
||||
}
|
||||
|
||||
errorOutro();
|
||||
}
|
||||
|
||||
s.stop("Template downloaded.");
|
||||
await updateBkndPackages(ctx.dir);
|
||||
@@ -201,6 +215,16 @@ async function action(options: { template?: string; dir?: string; integration?:
|
||||
}
|
||||
}
|
||||
|
||||
// update package name
|
||||
await overridePackageJson(
|
||||
(pkg) => ({
|
||||
...pkg,
|
||||
name: ctx.name
|
||||
}),
|
||||
{ dir: ctx.dir }
|
||||
);
|
||||
$p.log.success(`Updated package name to ${color.cyan(ctx.name)}`);
|
||||
|
||||
{
|
||||
const install = await $p.confirm({
|
||||
message: "Install dependencies?"
|
||||
@@ -209,9 +233,23 @@ async function action(options: { template?: string; dir?: string; integration?:
|
||||
if ($p.isCancel(install)) {
|
||||
process.exit(1);
|
||||
} else if (install) {
|
||||
const install_cmd = template.scripts?.install || "npm install";
|
||||
|
||||
const s = $p.spinner();
|
||||
s.start("Installing dependencies...");
|
||||
exec(`cd ${ctx.dir} && npm install`, { silent: true });
|
||||
try {
|
||||
exec(`cd ${ctx.dir} && ${install_cmd}`, { silent: true });
|
||||
} catch (e) {
|
||||
if (e instanceof Error) {
|
||||
s.stop("Failed to install: " + color.red(e.message), 1);
|
||||
} else {
|
||||
console.error(e);
|
||||
s.stop("Failed to install. Check logs above.", 1);
|
||||
}
|
||||
|
||||
errorOutro();
|
||||
}
|
||||
|
||||
s.stop("Dependencies installed.");
|
||||
|
||||
if (template!.postinstall) {
|
||||
@@ -220,12 +258,12 @@ async function action(options: { template?: string; dir?: string; integration?:
|
||||
} else {
|
||||
await $p.stream.warn(
|
||||
(async function* () {
|
||||
yield* typewriter("Remember to run ", _config.speed.typewriter, color.dim);
|
||||
await wait(_config.speed.typewriter);
|
||||
yield* typewriter("npm install", _config.speed.typewriter, color.cyan);
|
||||
await wait(_config.speed.typewriter);
|
||||
yield* typewriter(" after setup", _config.speed.typewriter, color.dim);
|
||||
await wait(_config.speed.wait / 2);
|
||||
yield* typewriter(
|
||||
color.dim("Remember to run ") +
|
||||
color.cyan("npm install") +
|
||||
color.dim(" after setup")
|
||||
);
|
||||
await wait();
|
||||
})()
|
||||
);
|
||||
}
|
||||
@@ -237,17 +275,16 @@ async function action(options: { template?: string; dir?: string; integration?:
|
||||
|
||||
await $p.stream.success(
|
||||
(async function* () {
|
||||
yield* typewriter("That's it! ", _config.speed.typewriter);
|
||||
await wait(_config.speed.wait / 2);
|
||||
yield* typewriter("That's it! ");
|
||||
await wait(0.5);
|
||||
yield "🎉";
|
||||
await wait(_config.speed.wait);
|
||||
await wait();
|
||||
yield "\n\n";
|
||||
yield* typewriter(
|
||||
`Enter your project's directory using ${color.cyan("cd " + ctx.dir)}
|
||||
If you need help, check ${color.cyan("https://docs.bknd.io")} or join our Discord!`,
|
||||
_config.speed.typewriter
|
||||
If you need help, check ${color.cyan("https://docs.bknd.io")} or join our Discord!`
|
||||
);
|
||||
await wait(_config.speed.wait * 2);
|
||||
await wait(2);
|
||||
})()
|
||||
);
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { readFile, writeFile } from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { getVersion as sysGetVersion } from "cli/utils/sys";
|
||||
|
||||
export type TPackageJson = Partial<{
|
||||
name: string;
|
||||
@@ -67,7 +68,7 @@ export async function replacePackageJsonVersions(
|
||||
|
||||
export async function updateBkndPackages(dir?: string, map?: Record<string, string>) {
|
||||
const versions = {
|
||||
bknd: "^" + (await getVersion("bknd")),
|
||||
bknd: "^" + (await sysGetVersion()),
|
||||
...(map ?? {})
|
||||
};
|
||||
await replacePackageJsonVersions(
|
||||
|
||||
@@ -4,6 +4,8 @@ import { uuid } from "core/utils";
|
||||
import color from "picocolors";
|
||||
import type { Template, TemplateSetupCtx } from ".";
|
||||
|
||||
const WRANGLER_FILE = "wrangler.json";
|
||||
|
||||
export const cloudflare = {
|
||||
key: "cloudflare",
|
||||
title: "Cloudflare Basic",
|
||||
@@ -12,11 +14,12 @@ export const cloudflare = {
|
||||
path: "gh:bknd-io/bknd/examples/cloudflare-worker",
|
||||
ref: true,
|
||||
setup: async (ctx) => {
|
||||
// overwrite assets directory
|
||||
// overwrite assets directory & name
|
||||
await overrideJson(
|
||||
"wrangler.json",
|
||||
WRANGLER_FILE,
|
||||
(json) => ({
|
||||
...json,
|
||||
name,
|
||||
assets: {
|
||||
directory: "node_modules/bknd/dist/static"
|
||||
}
|
||||
@@ -70,7 +73,7 @@ async function createD1(ctx: TemplateSetupCtx) {
|
||||
}
|
||||
|
||||
await overrideJson(
|
||||
"wrangler.json",
|
||||
WRANGLER_FILE,
|
||||
(json) => ({
|
||||
...json,
|
||||
d1_databases: [
|
||||
@@ -90,7 +93,7 @@ async function createD1(ctx: TemplateSetupCtx) {
|
||||
|
||||
async function createLibsql(ctx: TemplateSetupCtx) {
|
||||
await overrideJson(
|
||||
"wrangler.json",
|
||||
WRANGLER_FILE,
|
||||
(json) => ({
|
||||
...json,
|
||||
vars: {
|
||||
|
||||
@@ -3,10 +3,12 @@ import { cloudflare } from "./cloudflare";
|
||||
export type TemplateSetupCtx = {
|
||||
template: Template;
|
||||
dir: string;
|
||||
name: string;
|
||||
};
|
||||
|
||||
export type Integration = "node" | "bun" | "cloudflare" | "nextjs" | "remix" | "astro" | "custom";
|
||||
|
||||
type TemplateScripts = "install" | "dev" | "build" | "start";
|
||||
export type Template = {
|
||||
/**
|
||||
* unique key for the template
|
||||
@@ -23,12 +25,13 @@ export type Template = {
|
||||
* adds a ref "#{ref}" to the path. If "true", adds the current version of bknd
|
||||
*/
|
||||
ref?: true | string;
|
||||
scripts?: Partial<Record<TemplateScripts, string>>;
|
||||
preinstall?: (ctx: TemplateSetupCtx) => Promise<void>;
|
||||
postinstall?: (ctx: TemplateSetupCtx) => Promise<void>;
|
||||
setup?: (ctx: TemplateSetupCtx) => Promise<void>;
|
||||
};
|
||||
|
||||
export const templates = [
|
||||
export const templates: Template[] = [
|
||||
{
|
||||
key: "node",
|
||||
title: "Node.js Basic",
|
||||
@@ -45,5 +48,33 @@ export const templates = [
|
||||
path: "gh:bknd-io/bknd/examples/bun",
|
||||
ref: true
|
||||
},
|
||||
cloudflare
|
||||
] as const satisfies Template[];
|
||||
cloudflare,
|
||||
{
|
||||
key: "remix",
|
||||
title: "Remix Basic",
|
||||
integration: "remix",
|
||||
description: "A basic bknd Remix starter",
|
||||
path: "gh:bknd-io/bknd/examples/remix",
|
||||
ref: true
|
||||
},
|
||||
{
|
||||
// @todo: add `concurrently`?
|
||||
key: "nextjs",
|
||||
title: "Next.js Basic",
|
||||
integration: "nextjs",
|
||||
description: "A basic bknd Next.js starter",
|
||||
path: "gh:bknd-io/bknd/examples/nextjs",
|
||||
scripts: {
|
||||
install: "npm install --force"
|
||||
},
|
||||
ref: true
|
||||
},
|
||||
{
|
||||
key: "astro",
|
||||
title: "Astro Basic",
|
||||
integration: "astro",
|
||||
description: "A basic bknd Astro starter",
|
||||
path: "gh:bknd-io/bknd/examples/astro",
|
||||
ref: true
|
||||
}
|
||||
];
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { Command } from "commander";
|
||||
import color from "picocolors";
|
||||
import * as commands from "./commands";
|
||||
import { getVersion } from "./utils/sys";
|
||||
const program = new Command();
|
||||
|
||||
export async function main() {
|
||||
const version = await getVersion();
|
||||
program
|
||||
.name("bknd")
|
||||
.description("bknd cli")
|
||||
.version(await getVersion());
|
||||
.description(color.yellowBright("⚡") + " bknd cli " + color.bold(color.cyan(`v${version}`)))
|
||||
.version(version);
|
||||
|
||||
// register commands
|
||||
for (const command of Object.values(commands)) {
|
||||
|
||||
@@ -1,14 +1,56 @@
|
||||
export async function wait(ms: number) {
|
||||
const _SPEEDUP = process.env.LOCAL;
|
||||
|
||||
const DEFAULT_WAIT = _SPEEDUP ? 0 : 250;
|
||||
export async function wait(factor: number = 1, strict?: boolean) {
|
||||
const ms = strict === true ? factor : DEFAULT_WAIT * factor;
|
||||
return new Promise((r) => setTimeout(r, ms));
|
||||
}
|
||||
|
||||
// from https://github.com/chalk/ansi-regex/blob/main/index.js
|
||||
export default function ansiRegex({ onlyFirst = false } = {}) {
|
||||
// Valid string terminator sequences are BEL, ESC\, and 0x9c
|
||||
const ST = "(?:\\u0007|\\u001B\\u005C|\\u009C)";
|
||||
const pattern = [
|
||||
`[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]+)*|[a-zA-Z\\d]+(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?${ST})`,
|
||||
"(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-nq-uy=><~]))"
|
||||
].join("|");
|
||||
|
||||
return new RegExp(pattern, onlyFirst ? undefined : "g");
|
||||
}
|
||||
|
||||
const DEFAULT_WAIT_WRITER = _SPEEDUP ? 0 : 20;
|
||||
export async function* typewriter(
|
||||
text: string,
|
||||
delay: number,
|
||||
transform?: (char: string) => string
|
||||
transform?: (char: string) => string,
|
||||
_delay?: number
|
||||
) {
|
||||
for (const char of text) {
|
||||
yield transform ? transform(char) : char;
|
||||
await wait(delay);
|
||||
const delay = DEFAULT_WAIT_WRITER * (_delay ?? 1);
|
||||
const regex = ansiRegex();
|
||||
const parts: string[] = [];
|
||||
let match: RegExpExecArray | null;
|
||||
let lastIndex = 0;
|
||||
|
||||
// Extract ANSI escape sequences as standalone units
|
||||
// biome-ignore lint/suspicious/noAssignInExpressions: <explanation>
|
||||
while ((match = regex.exec(text)) !== null) {
|
||||
if (lastIndex < match.index) {
|
||||
parts.push(...text.slice(lastIndex, match.index).split(""));
|
||||
}
|
||||
parts.push(match[0]); // Add the ANSI escape sequence as a full chunk
|
||||
lastIndex = regex.lastIndex;
|
||||
}
|
||||
|
||||
// Add any remaining characters after the last ANSI sequence
|
||||
if (lastIndex < text.length) {
|
||||
parts.push(...text.slice(lastIndex).split(""));
|
||||
}
|
||||
|
||||
// Yield characters or ANSI sequences in order
|
||||
for (const chunk of parts) {
|
||||
yield transform ? transform(chunk) : chunk;
|
||||
// Delay only for normal characters, not ANSI codes
|
||||
if (!regex.test(chunk)) {
|
||||
await wait(delay, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,9 +17,9 @@ export function getRelativeDistPath() {
|
||||
return path.relative(process.cwd(), getDistPath());
|
||||
}
|
||||
|
||||
export async function getVersion() {
|
||||
export async function getVersion(_path: string = "") {
|
||||
try {
|
||||
const resolved = path.resolve(getRootPath(), "package.json");
|
||||
const resolved = path.resolve(getRootPath(), path.join(_path, "package.json"));
|
||||
const pkg = await readFile(resolved, "utf-8");
|
||||
if (pkg) {
|
||||
return JSON.parse(pkg).version ?? "preview";
|
||||
|
||||
Reference in New Issue
Block a user