Skip to content

Commit

Permalink
Project creation flow with CLI (#322)
Browse files Browse the repository at this point in the history
* Plop in create-honc-app cli

* HACK - Add cli deps to api deps in package.json since we do not bundle them in the cli package oopsies

* Modify cli to work with create app

* Add cli prompt for description of app

* Format

* Modify github workflow to include new cli in dev builds

* Fix typescript errors in cli

* Modify app description prompt

* Add honc-supercharger

* Honc Supercharger: Add D1 database records and cahnge api a little

* Fix things for the deploy

* Add the worlds best auth ever

* Add worker observability

* Start adding support in CLI for the supercharger

* Integrate honc-supercharger and the cli

* Use HONC_* vars in cli context

* Remove deprecation warning about fs.rmdir

* Update templates temporarily

* Simplify ending commands

* Small cli tweak and update fiberplane command to reflect new templates

* Generate code in the background while dependencies install

* Change name of supercharger to codegen

* Update supercharger to code-gen in biome.jsonc

* Continue renaming supercharger to coddegen

* Update code-gen prompt to give more context about Drizzle

* Escape curly braces in prompts

* Add a simple test for the prompt invocation

* Update honc code gen to point to the cf worker by default

* Comment out "sample api template"

* Update cli/README.md

* Update cli/README.md

* Update cli/README.md

* Update honc-code-gen to use openai (needed lots of prompting changes)

* Tweak prompt one more time

* Add drizzle schema examples to prompts

* Update project name in wrangler toml and package json (fail silently)

* Fix type errors

* Add example of get operator

* Update prompt for creating api code

* Give an example on deleting a resource

* Add project description to package.json

* Trigger new pkg

* Move span metadata after cloudflare bits

* Add smol duration indicator

* Add view timeline banner
  • Loading branch information
brettimus authored Oct 18, 2024
1 parent 297c0f2 commit 8a0398c
Show file tree
Hide file tree
Showing 70 changed files with 4,762 additions and 107 deletions.
8 changes: 7 additions & 1 deletion .github/workflows/build_frontends.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@ on:
pull_request:
branches: ["*"]
paths:
- "cli/**"
- "api/**"
- "packages/client-library-otel/**"
- "studio/**"
push:
branches: ["main", "release-*"]
paths:
- "cli/**"
- "api/**"
- "packages/client-library-otel/**"
- "studio/**"
Expand Down Expand Up @@ -78,11 +80,12 @@ jobs:

# Building

- name: Build api, frontend, and client library
- name: Build api, frontend, cli, and client library
run: |
pnpm \
--filter=@fiberplane/studio-frontend \
--filter=@fiberplane/studio \
--filter=@fiberplane/studio-cli \
--filter=@fiberplane/hono-otel \
build
Expand All @@ -91,5 +94,8 @@ jobs:
- name: Copy the frontend files into the api
run: cp -r ./studio/dist/* ./api/dist

- name: Copy the cli files into the api
run: cp -r ./cli/dist ./api/dist/cli

- name: Publish a temporary preview version
run: pnpx pkg-pr-new publish --pnpm "./api" "./packages/client-library-otel"
40 changes: 40 additions & 0 deletions api/bin/cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ const script = (args[0] ?? "").trim();
const scriptsToRun = !script ? ["migrate", "studio"] : [script];

const validScripts = {
create: "dist/cli/index.js",
migrate: "dist/migrate.js",
studio: "dist/src/index.node.js",
};
Expand Down Expand Up @@ -59,11 +60,45 @@ loadUserConfigIntoUserVars();

runWizard();

function createApp() {
// HACK - Clear the user's config dir (since we might have a placeholder config after the last steps)
// so we start fresh when creating a new app
clearUserConfigDir();
runScript("create");
}

/**
* Run the wizard to get the user's configuration for FPX
* If there are valid values in .fpxconfig, we skip asking questions
*/
async function runWizard() {
logger.debug("Running wizard");
logger.debug("scriptsToRun", scriptsToRun);
logger.debug("PROJECT_ROOT_DIR", PROJECT_ROOT_DIR);

const isRunningCreate = scriptsToRun.includes("create");

if (isRunningCreate) {
createApp();
return;
}

const MIGHT_BE_CREATING =
IS_INITIALIZING_FPX && !WRANGLER_TOML && !PACKAGE_JSON;

logger.debug("MIGHT_BE_CREATING", MIGHT_BE_CREATING);

const question = chalk.green("🕵️ No project detected. Create a new app?");
const isCreatingAnswer = MIGHT_BE_CREATING
? await askUser(question, "y")
: "n";
const shouldCreateApp = cliAnswerToBool(isCreatingAnswer);

if (shouldCreateApp) {
createApp();
return;
}

if (IS_INITIALIZING_FPX) {
logger.info(chalk.dim("\nInitializing FPX...\n"));
} else {
Expand Down Expand Up @@ -415,6 +450,11 @@ function readUserConfig() {
};
}

function clearUserConfigDir() {
const configDir = path.join(PROJECT_ROOT_DIR, CONFIG_DIR_NAME);
fs.rmSync(configDir, { recursive: true });
}

/**
* Load the user's configuration into the USER_VARS object
*
Expand Down
7 changes: 7 additions & 0 deletions api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,13 @@
"@ai-sdk/anthropic": "^0.0.51",
"@ai-sdk/mistral": "^0.0.42",
"@ai-sdk/openai": "^0.0.66",
"@clack/core": "^0.3.4",
"@clack/prompts": "^0.7.0",
"@neondatabase/api-client": "^1.10.3",
"giget": "^1.2.3",
"open": "^10.1.0",
"oslo": "^1.2.1",
"picocolors": "^1.0.1",
"@anthropic-ai/sdk": "^0.24.3",
"@fiberplane/fpx-types": "workspace:*",
"@hono/node-server": "^1.11.1",
Expand Down
7 changes: 6 additions & 1 deletion biome.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,12 @@
}
},
{
"include": ["packages/client-library-otel", "api"],
"include": [
"packages/client-library-otel",
"api",
"cli",
"honc-code-gen"
],
"linter": {
"enabled": true,
"rules": {
Expand Down
33 changes: 33 additions & 0 deletions cli/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# prod
dist/

# dev
.yarn/
!.yarn/releases
.vscode/*
!.vscode/launch.json
!.vscode/*.code-snippets
.idea/workspace.xml
.idea/usage.statistics.xml
.idea/shelf

# deps
node_modules/
.wrangler

# env
.env
.env.production
.dev.vars

# logs
logs/
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*

# misc
.DS_Store
23 changes: 23 additions & 0 deletions cli/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the
Software without restriction, including without
limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following
conditions:

The above copyright notice and this permission notice
shall be included in all copies or substantial portions
of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
91 changes: 91 additions & 0 deletions cli/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@

<div align="center">
<h1>Honc Honc!</h1>
<img src="https://github.com/fiberplane/create-honc-app/blob/9290786147fe1efa2079899064853cf754f175e5/assets/honc.png" width="200" height="200" />
</div>

<p align="center">
Scaffolding CLI for creating modular data APIs using TypeScript
</p>

<div align="center">
<code>npx @fiberplane/studio@latest create</code>
</div>

[HONC](https://honc.dev) is a modular collection of choice technologies for building lightweight, type-safe, edge-enabled data apis that scale seamlessly to their demand.

🪿 **[Hono](https://hono.dev)** as an api framework
🪿 **[Neon](https://neon.tech)** for a relational Postgres database
🪿 **[Drizzle](https://orm.drizzle.team/)** as the ORM and migrations manager
🪿 **[Cloudflare](https://workers.cloudflare.com/)** Workers for deployment hosting

## Quickstart

To get started run the following command:

```sh
npx @fiberplane/studio@latest create
```

You'll be prompted a few simple questions, and then a fresh HONC project will arrive in a new directory on your machine.

### Commands

Run the development server:

```sh
npm run dev
```

Once you've set up a Neon database (see below) and added the connection string to a `DATABASE_URL=..` in `dev.vars`, you can generate some migrations, apply them, and seed the database:

```sh
npm run db:generate
npm run db:migrate
npm run db:seed
```

If you're inclined to deploy the app to the wild wild internet, you can do so as follows (requires a Cloudflare account):

```sh
npm run deploy
```

## Setting up a Neon database

Create a Neon account and project, retrieve the connection key from the dashboard, and add it to your `dev.vars` file.

Alternatively, you can use the Neon CLI to create a project and set the context:

```sh
# Authenticate with neon cli
neonctl auth

# Create project if you haven't already
#
# > *skip this* if you already created a project,
# > and grab the DATABASE_URL from your dashboard
PROJECT_NAME=my-project
neonctl projects create --name $PROJECT_NAME --set-context

# Set project id because the call to `set-context` below needs it
PROJECT_ID=$(neonctl projects list --output=json | jq --arg name "$PROJECT_NAME" '.projects[] | select(.name == $name) | .id')

# Create a `dev` db branch then set context
BRANCH_NAME=dev
neonctl branches create --name=$BRANCH_NAME
neonctl set-context --project-id=$PROJECT_ID --branch=$BRANCH_NAME

# Finally, add connection string to .dev.vars
DATABASE_URL=$(neonctl connection-string)
echo -e '\nDATABASE_URL='$DATABASE_URL'\n' >> .dev.vars
```

This will create a `.neon` file, which is used by the `neonctl` command to know the proper context for running commands.

This file can be kept in version control. From [the Neon docs](https://neon.tech/docs/reference/cli-set-context):

> **Neon does not save any confidential information to the context file (for example, auth tokens).** You can safely commit this file to your repository or share with others.
## More resources
We have an [awesome HONC list](https://github.com/fiberplane/create-honc-app/blob/main/awesome-honc.md) with further guides, use cases and examples.
42 changes: 42 additions & 0 deletions cli/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
{
"name": "@fiberplane/studio-cli",
"type": "module",
"version": "0.0.1",
"description": "An interactive CLI to create modular data APIs using Hono, and connect them to Fiberplane Studio",
"scripts": {
"build": "tsup",
"dev": "tsup --watch && node dist/index.js",
"format": "biome format . --write",
"typecheck": "tsc --noEmit"
},
"exports": "./dist/index.js",
"files": ["dist", "README.md", "LICENSE", ".gitignore"],
"bin": {
"create-honc-app": "dist/index.js"
},
"author": "Fiberplane<[email protected]>",
"license": "MIT",
"repository": {
"url": "https://github.com/fiberplane/create-honc-app",
"type": "git"
},
"dependencies": {
"@clack/core": "^0.3.4",
"@clack/prompts": "^0.7.0",
"@neondatabase/api-client": "^1.10.3",
"giget": "^1.2.3",
"open": "^10.1.0",
"oslo": "^1.2.1",
"picocolors": "^1.0.1"
},
"devDependencies": {
"@types/node": "^22.2.0",
"tsup": "^8.2.3"
},
"engines": {
"node": ">=18.17.0"
},
"publishConfig": {
"access": "public"
}
}
62 changes: 62 additions & 0 deletions cli/src/actions/code-gen.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { writeFileSync } from "node:fs";
import path from "node:path";
import type { Context } from "@/context";
import { getScaffoldedFiles, shouldSkipCodeGen } from "@/integrations/code-gen";
import { CodeGenError } from "@/types";
import { spinner } from "@clack/prompts";

/**
* Start the code generation request in the background.
* We save it as a promise so we can await the result later, in `actionCodeGenFinish`.
*
* @param ctx - The context object.
*/
export async function actionCodeGenStart(ctx: Context) {
if (shouldSkipCodeGen(ctx)) {
return;
}

ctx.codeGenPromise = getScaffoldedFiles(ctx);
}

export async function actionCodeGenFinish(ctx: Context) {
if (shouldSkipCodeGen(ctx)) {
return;
}

const s = spinner();
s.start("Generating code from project description...");

try {
const scaffoldedFiles = await ctx.codeGenPromise;

if (scaffoldedFiles) {
const currentPath = ctx.path ?? ".";
if (scaffoldedFiles.indexFile) {
writeFileSync(
path.join(currentPath, "src", "index.ts"),
scaffoldedFiles.indexFile,
);
}
if (scaffoldedFiles.schemaFile) {
writeFileSync(
path.join(currentPath, "src", "db", "schema.ts"),
scaffoldedFiles.schemaFile,
);
}
if (scaffoldedFiles.seedFile) {
writeFileSync(
path.join(currentPath, "seed.ts"),
scaffoldedFiles.seedFile,
);
}
}
s.stop();
return;
} catch (error) {
s.stop();
return new CodeGenError(
error instanceof Error ? error.message : "Unknown error",
);
}
}
Loading

0 comments on commit 8a0398c

Please sign in to comment.