diff --git a/.github/workflows/canary.yml b/.github/workflows/canary.yml index 9c334764..0fabed5a 100644 --- a/.github/workflows/canary.yml +++ b/.github/workflows/canary.yml @@ -27,13 +27,18 @@ jobs: run: | jq --arg prop "workspaces" 'del(.[$prop])' package.json > package.tmp.json && rm package.json && cp package.tmp.json package.json && rm package.tmp.json cd src - npm --no-git-tag-version version 0.0.0 + npm --no-git-tag-version version $(npm pkg get version | sed 's/"//g')-$(git branch --show-current | tr -cs '[:alnum:]-' '-' | tr '[:upper:]' '[:lower:]' | sed 's/-$//').$(date +'%Y%m%dT%H%M%S') + cd ../create-vocs npm --no-git-tag-version version $(npm pkg get version | sed 's/"//g')-$(git branch --show-current | tr -cs '[:alnum:]-' '-' | tr '[:upper:]' '[:lower:]' | sed 's/-$//').$(date +'%Y%m%dT%H%M%S') - name: Build run: bun run build - name: Publish to npm - run: cd src && npm publish --tag $(git branch --show-current | tr -cs '[:alnum:]-' '-' | tr '[:upper:]' '[:lower:]' | sed 's/-$//') + run: | + cd src + npm publish --tag $(git branch --show-current | tr -cs '[:alnum:]-' '-' | tr '[:upper:]' '[:lower:]' | sed 's/-$//') + cd ../create-vocs + npm publish --tag $(git branch --show-current | tr -cs '[:alnum:]-' '-' | tr '[:upper:]' '[:lower:]' | sed 's/-$//') env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/bun.lockb b/bun.lockb index 2d2c6c1a..7d0e8c3a 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/create-farc/bin.ts b/create-farc/bin.ts new file mode 100644 index 00000000..a241c751 --- /dev/null +++ b/create-farc/bin.ts @@ -0,0 +1,18 @@ +#!/usr/bin/env node +import { createRequire } from 'node:module' +import { cac } from 'cac' +import { type CreateParameters, create } from './create.js' + +const require = createRequire(import.meta.url) +const pkg = require('../package.json') + +const cli = cac('create-vocs') + +cli.usage('[options]').option('-n, --name [name]', 'Name of project') + +cli.help() +cli.version(pkg.version) + +const { options } = cli.parse() + +if (!options.help) create(options as CreateParameters) diff --git a/create-farc/create.ts b/create-farc/create.ts new file mode 100644 index 00000000..0ceae64d --- /dev/null +++ b/create-farc/create.ts @@ -0,0 +1,95 @@ +import { dirname, resolve } from 'node:path' +import { fileURLToPath } from 'node:url' +import { intro, log, outro, text } from '@clack/prompts' +import { default as fs } from 'fs-extra' +import { default as pc } from 'picocolors' + +export type CreateParameters = { name: string } + +const __dirname = dirname(fileURLToPath(import.meta.url)) + +export async function create(params: CreateParameters) { + intro('Welcome to Farc!') + + const templateDir = resolve(__dirname, '../templates/default') + + const displayName = + params.name || + ((await text({ + message: 'Enter the name of your project', + placeholder: 'my-first-farc', + validate(value) { + if (!value) return 'Please enter a name.' + return + }, + })) as string) + const name = kebabcase(displayName) + + const destDir = resolve(process.cwd(), name) + + // Copy contents + fs.copySync(templateDir, destDir) + + // Replace dotfiles + for (const file of fs.readdirSync(destDir)) { + if (!file.startsWith('_')) continue + fs.renameSync(resolve(destDir, file), resolve(destDir, `.${file.slice(1)}`)) + } + + // Replace package.json properties + const pkgJson = fs.readJsonSync(resolve(destDir, 'package.json')) + pkgJson.name = name + fs.writeJsonSync(resolve(destDir, 'package.json'), pkgJson, { spaces: 2 }) + + // Wrap up + log.success(`Project successfully scaffolded in ${pc.blue(destDir)}!`) + + const pkgManager = detectPackageManager() + + log.message('Next steps:') + log.step(`1. ${pc.blue(`cd ./${name}`)} - Navigate to project`) + log.step( + `2. ${pc.blue( + pkgManagerInstallCommand(pkgManager), + )} - Install dependencies`, + ) + log.step( + `3. ${pc.blue(pkgManagerRunCommand(pkgManager, 'dev'))} - Start dev server`, + ) + log.step(`4. Head to ${pc.blue('http://localhost:3000')}`) + + outro('Done! 🤠') +} + +type PackageManager = 'npm' | 'yarn' | 'pnpm' | 'bun' + +function detectPackageManager(): PackageManager { + const userAgent = process.env.npm_config_user_agent + if (!userAgent) return 'npm' + if (userAgent.includes('bun')) return 'bun' + if (userAgent.includes('yarn')) return 'yarn' + if (userAgent.includes('pnpm')) return 'pnpm' + if (userAgent.includes('npm')) return 'npm' + return 'npm' +} + +function pkgManagerInstallCommand(pkgManager: PackageManager) { + if (pkgManager === 'bun') return 'bun install' + if (pkgManager === 'yarn') return 'yarn' + if (pkgManager === 'pnpm') return 'pnpm install' + return 'npm install' +} + +function pkgManagerRunCommand(pkgManager: PackageManager, command: string) { + if (pkgManager === 'bun') return `bun run ${command}` + if (pkgManager === 'yarn') return `yarn ${command}` + if (pkgManager === 'pnpm') return `pnpm ${command}` + return `npm run ${command}` +} + +function kebabcase(str: string) { + return str + .replace(/([a-z])([A-Z])/g, '$1-$2') + .replace(/[\s_]+/g, '-') + .toLowerCase() +} diff --git a/create-farc/package.json b/create-farc/package.json new file mode 100644 index 00000000..f23589eb --- /dev/null +++ b/create-farc/package.json @@ -0,0 +1,16 @@ +{ + "name": "create-farc", + "version": "0.0.1", + "type": "module", + "bin": { + "create-farc": "./_lib/bin.js" + }, + "dependencies": { + "@clack/prompts": "^0.7.0", + "cac": "^6.7.14", + "detect-package-manager": "^3.0.1", + "fs-extra": "^11.1.1", + "picocolors": "^1.0.0" + }, + "repository": "wevm/farc" +} diff --git a/create-farc/templates/default/.gitignore b/create-farc/templates/default/.gitignore new file mode 100644 index 00000000..d654dad5 --- /dev/null +++ b/create-farc/templates/default/.gitignore @@ -0,0 +1,21 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js + +# production +/docs/dist + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# typescript +*.tsbuildinfo diff --git a/create-farc/templates/default/README.md b/create-farc/templates/default/README.md new file mode 100644 index 00000000..61b52124 --- /dev/null +++ b/create-farc/templates/default/README.md @@ -0,0 +1 @@ +This is a [Farc](https://farc.dev) project bootstrapped with `create-farc`. diff --git a/create-farc/templates/default/package.json b/create-farc/templates/default/package.json new file mode 100644 index 00000000..d1aee99d --- /dev/null +++ b/create-farc/templates/default/package.json @@ -0,0 +1,15 @@ +{ + "name": "Example", + "private": true, + "scripts": { + "dev": "bun run --hot src/index.tsx" + }, + "dependencies": { + "bun": "latest", + "farc": "main", + "hono": "^3.12.8" + }, + "devDependencies": { + "@types/bun": "latest" + } +} diff --git a/create-farc/templates/default/src/index.tsx b/create-farc/templates/default/src/index.tsx new file mode 100644 index 00000000..3fe31784 --- /dev/null +++ b/create-farc/templates/default/src/index.tsx @@ -0,0 +1,58 @@ +import { Button, Farc, TextInput } from 'farc' + +const app = new Farc() + +app.frame('/', (context) => { + const { buttonValue, inputText, status } = context + const fruit = inputText || buttonValue + return { + image: ( + <div + style={{ + alignItems: 'center', + background: + status === 'response' + ? 'linear-gradient(to right, #432889, #17101F)' + : 'black', + backgroundSize: '100% 100%', + display: 'flex', + flexDirection: 'column', + flexWrap: 'nowrap', + height: '100%', + justifyContent: 'center', + textAlign: 'center', + width: '100%', + }} + > + <div + style={{ + color: 'white', + fontSize: 60, + fontStyle: 'normal', + letterSpacing: '-0.025em', + lineHeight: 1.4, + marginTop: 30, + padding: '0 120px', + whiteSpace: 'pre-wrap', + }} + > + {status === 'response' + ? `Nice choice.${fruit ? ` ${fruit.toUpperCase()}!!` : ''}` + : 'Welcome!'} + </div> + </div> + ), + intents: [ + <TextInput placeholder="Enter custom fruit..." />, + <Button value="apples">Apples</Button>, + <Button value="oranges">Oranges</Button>, + <Button value="bananas">Bananas</Button>, + status === 'response' && <Button.Reset>Reset</Button.Reset>, + ], + } +}) + +export default { + port: 3000, + fetch: app.fetch, +} diff --git a/create-farc/templates/default/tsconfig.json b/create-farc/templates/default/tsconfig.json new file mode 100644 index 00000000..a7c4db3d --- /dev/null +++ b/create-farc/templates/default/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "target": "ESNext", + "module": "ESNext", + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + "jsxImportSource": "hono/jsx", + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["**/*.ts", "**/*.tsx"] +} diff --git a/create-farc/tsconfig.build.json b/create-farc/tsconfig.build.json new file mode 100644 index 00000000..5279b9e9 --- /dev/null +++ b/create-farc/tsconfig.build.json @@ -0,0 +1,15 @@ +{ + // This file is used to compile the for cjs and esm (see package.json build scripts). It should exclude all test files. + "extends": "../tsconfig.base.json", + "exclude": ["_lib", "node_modules"], + "include": ["*"], + "compilerOptions": { + "allowSyntheticDefaultImports": true, + "declaration": true, + "declarationMap": true, + "module": "ESNext", + "moduleResolution": "Node", + "outDir": "./_lib", + "sourceMap": true + } +} diff --git a/package.json b/package.json index 2e397b9f..f01e4409 100644 --- a/package.json +++ b/package.json @@ -1,8 +1,10 @@ { - "workspaces": ["example", "src"], + "workspaces": ["create-farc", "example", "src"], "scripts": { "dev": "bun run --hot ./example/src/index.tsx", - "build": "bun run clean && tsc --project ./tsconfig.build.json", + "build": "bun run clean && bun run build:farc && bun run build:create-farc", + "build:farc": "tsc --project ./tsconfig.build.json", + "build:create-farc": "rimraf create-farc/_lib && tsc -p create-farc/tsconfig.build.json", "changeset": "changeset", "changeset:release": "bun run build && changeset publish", "changeset:version": "changeset version && bun install --lockfile-only", @@ -18,6 +20,7 @@ "@changesets/changelog-github": "^0.5.0", "@changesets/cli": "^2.27.1", "@types/bun": "latest", + "@types/fs-extra": "^11.0.4", "@vitest/coverage-v8": "^1.2.2", "hono": "^3.12.8", "rimraf": "^5.0.5",