Skip to content

Commit

Permalink
wip: cli init cmd
Browse files Browse the repository at this point in the history
  • Loading branch information
jxom committed Oct 24, 2023
1 parent eff6ab4 commit ea7f4bb
Show file tree
Hide file tree
Showing 21 changed files with 233 additions and 52 deletions.
Binary file modified bun.lockb
Binary file not shown.
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@
"type": "module",
"scripts": {
"build": "rimraf src/_lib src/tsconfig.build.* && tsc -p tsconfig.build.json && bun scripts/postbuild.ts",
"dev": "cd playgrounds/default && bun run dev",
"docs:dev": "cd site && bun run dev",
"docs:build": "cd site && bun run build",
"format": "biome format . --write",
"lint": "biome lint . --apply-unsafe",
"playground": "cd playgrounds/default && bun run dev",
"prepare": "bun x simple-git-hooks",
"typecheck": "tsc --noEmit"
},
Expand Down
9 changes: 9 additions & 0 deletions scripts/postbuild.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,24 @@ import { readdirSync } from 'node:fs'
import { resolve } from 'node:path'
import { copy, copyFileSync, readFileSync, writeFileSync } from 'fs-extra'

// Copy index.html
copyFileSync(
resolve(import.meta.dir, '../src/vite/index.html'),
resolve(import.meta.dir, '../src/_lib/vite/index.html'),
)

// Copy styles
copy(
resolve(import.meta.dir, '../src/app/styles'),
resolve(import.meta.dir, '../src/_lib/app/styles'),
)

// Copy CLI init templates
copy(
resolve(import.meta.dir, '../src/cli/templates'),
resolve(import.meta.dir, '../src/_lib/cli/templates'),
)

rewriteExtensions(resolve(import.meta.dir, '../src/_lib'))

////////////////////////////////////////////////////////////////////
Expand Down
6 changes: 3 additions & 3 deletions site/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "bun ../src/cli.ts",
"build": "NODE_ENV=production bun ../src/cli.ts build",
"preview": "bun ../src/cli.ts preview"
"dev": "bun ../src/cli/index.ts",
"build": "NODE_ENV=production bun ../src/cli/index.ts build",
"preview": "bun ../src/cli/index.ts preview"
},
"dependencies": {
"react": "^18.2.0",
Expand Down
35 changes: 0 additions & 35 deletions src/cli.ts

This file was deleted.

4 changes: 4 additions & 0 deletions src/cli/commands/build.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export async function build() {
const { build } = await import('../../vite/build.js')
await build()
}
6 changes: 6 additions & 0 deletions src/cli/commands/dev.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export async function dev() {
const { createDevServer } = await import('../../vite/dev-server.js')
const server = await createDevServer()
await server.listen()
server.printUrls()
}
92 changes: 92 additions & 0 deletions src/cli/commands/init.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
// TODO: spice it up

import { dirname, resolve } from 'node:path'
import { fileURLToPath } from 'node:url'
// @ts-expect-error
import { detect } from 'detect-package-manager'
import { execa } from 'execa'
import { default as fs } from 'fs-extra'
import { default as prompts } from 'prompts'

type InitParameters = { name: string; git: boolean; install: boolean }

const __dirname = dirname(fileURLToPath(import.meta.url))

export async function init(options: InitParameters) {
const templateDir = resolve(__dirname, '../templates/default')

const displayName =
options.name ||
((
await prompts({
type: 'text',
name: 'displayName',
message: 'Enter the name of your project',
validate: (value) => (value ? true : 'Please enter a name'),
})
).displayName as string)
const name = kebabcase(displayName) as string

const destDir = resolve(process.cwd(), name)

console.log(`Scaffolding project in ${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 })

// Install dependencies
if (options.install) {
const packageManager =
typeof options.install === 'string' ? options.install : detectPackageManager()
await execa(packageManager, ['install', packageManager === 'npm' ? '--quiet' : '--silent'], {
cwd: destDir,
env: {
...process.env,
ADBLOCK: '1',
DISABLE_OPENCOLLECTIVE: '1',
// we set NODE_ENV to development as pnpm skips dev
// dependencies when production
NODE_ENV: 'development',
},
})
}

// Create git repository
if (options.git) {
await execa('git', ['init'], { cwd: destDir })
await execa('git', ['add', '.'], { cwd: destDir })
await execa('git', ['commit', '--no-verify', '--message', 'initial commit'], {
cwd: destDir,
})
}

console.log('Done!')
}

export function kebabcase(str: string) {
return str
.match(/[A-Z]{2,}(?=[A-Z][a-z]+[0-9]*|\b)|[A-Z]?[a-z]+[0-9]*|[A-Z]|[0-9]+/g)
?.join('-')
.toLowerCase()
}

export function detectPackageManager() {
const userAgent = process.env.npm_config_user_agent
if (!userAgent) return 'npm'
if (userAgent.includes('pnpm')) return 'pnpm'
if (userAgent.includes('npm')) return 'npm'
if (userAgent.includes('yarn')) return 'yarn'
if (userAgent.includes('bun')) return 'bun'
return 'npm'
}
5 changes: 5 additions & 0 deletions src/cli/commands/preview.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export async function preview() {
const { preview } = await import('../../vite/preview.js')
const server = await preview()
server.printUrls()
}
31 changes: 31 additions & 0 deletions src/cli/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#!/usr/bin/env node
import { cac } from 'cac'

import { build } from './commands/build.js'
import { dev } from './commands/dev.js'
import { init } from './commands/init.js'
import { preview } from './commands/preview.js'
import { version } from './version.js'

const cli = cac('vocs')

cli.command('[root]').alias('dev').action(dev)
cli
.command('init')
.option('-n, --name [name]', 'Name of project')
.option(
'-i, --install [false|npm|pnpm|yarn|bun]',
'Install dependencies (and optionally force package manager)',
{
default: true,
},
)
.option('-g, --git', 'Initialize git repository', { default: true })
.action(init)
cli.command('build').action(build)
cli.command('preview').action(preview)

cli.help()
cli.version(version)

cli.parse()
1 change: 1 addition & 0 deletions src/cli/templates/default/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
This is a [Vocs](https://vocs.dev) project bootstrapped with the Vocs CLI.
21 changes: 21 additions & 0 deletions src/cli/templates/default/_gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
/.pnp
.pnp.js

# production
/dist

# misc
.DS_Store
*.pem

# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# typescript
*.tsbuildinfo
3 changes: 3 additions & 0 deletions src/cli/templates/default/docs/index.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Hello world!

Welcome to my site!
15 changes: 15 additions & 0 deletions src/cli/templates/default/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"name": "__DOCS_NAME__",
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vocs",
"build": "vocs build",
"preview": "vocs preview"
},
"dependencies": {
"react": "latest",
"react-dom": "latest",
"vocs": "latest"
}
}
24 changes: 24 additions & 0 deletions src/cli/templates/default/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,

/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",

/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
},
"include": ["**/*.ts", "**/*.tsx"]
}
File renamed without changes.
8 changes: 7 additions & 1 deletion src/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"version": "0.0.0",
"type": "module",
"bin": {
"vocs": "./_lib/cli.js"
"vocs": "./_lib/cli/index.js"
},
"main": "./_lib/index.js",
"exports": {
Expand Down Expand Up @@ -31,6 +31,9 @@
"autoprefixer": "^10.4.16",
"cac": "^6.7.14",
"compression": "^1.7.4",
"detect-package-manager": "^3.0.1",
"execa": "^8.0.1",
"fs-extra": "^11.1.1",
"globby": "^13.2.2",
"hastscript": "^8.0.0",
"mdast": "^3.0.0",
Expand All @@ -39,6 +42,7 @@
"minimatch": "^9.0.3",
"postcss": "^8.4.31",
"postcss-prefix-selector": "^1.16.0",
"prompts": "^2.4.2",
"react-helmet": "^6.1.0",
"react-router-dom": "^6.16.0",
"remark-directive": "^3.0.0",
Expand All @@ -53,7 +57,9 @@
"vite": "4.4.11"
},
"devDependencies": {
"@types/fs-extra": "^11.0.2",
"@types/postcss-prefix-selector": "^1.16.2",
"@types/prompts": "^2.4.7",
"@types/ua-parser-js": "^0.7.38"
}
}
8 changes: 4 additions & 4 deletions src/vite/plugins/css.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { accessSync } from 'node:fs'
import { resolve } from 'node:path'
import * as autoprefixer from 'autoprefixer'
import * as tailwindcss from 'tailwindcss'
import { default as autoprefixer } from 'autoprefixer'
import { default as tailwindcss } from 'tailwindcss'
import type { PluginOption } from 'vite'
import { postcssRawStyles } from './postcss/rawStyles.js'

Expand All @@ -16,9 +16,9 @@ export function css(): PluginOption {
postcss: {
plugins: [
postcssRawStyles(),
(autoprefixer as any).default(),
autoprefixer(),
tailwindConfig
? tailwindcss.default({
? (tailwindcss as any)({
config: tailwindConfig,
})
: undefined,
Expand Down
5 changes: 2 additions & 3 deletions src/vite/plugins/dev.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { readFileSync } from 'node:fs'
import { dirname, resolve } from 'node:path'
import { fileURLToPath } from 'node:url'
import * as serveStatic from 'serve-static'
import { default as serveStatic } from 'serve-static'
import type { PluginOption } from 'vite'

const __dirname = dirname(fileURLToPath(import.meta.url))
Expand All @@ -17,8 +17,7 @@ export function dev(): PluginOption {
},
configureServer(server) {
return () => {
// @ts-expect-error
server.middlewares.use(serveStatic.default(resolve(process.cwd(), 'public')))
server.middlewares.use(serveStatic(resolve(process.cwd(), 'public')))
server.middlewares.use(async (req, res, next) => {
const url = req.url && cleanUrl(req.url)

Expand Down
6 changes: 2 additions & 4 deletions src/vite/plugins/postcss/rawStyles.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
// @ts-expect-error
import * as postcssPrefixSelector from 'postcss-prefix-selector'
import { default as postcssPrefixSelector } from 'postcss-prefix-selector'

export function postcssRawStyles() {
// @ts-expect-error
return postcssPrefixSelector.default({
return postcssPrefixSelector({
prefix: ':not([data-vocs-raw])',
includeFiles: [/elements\.css/, /layouts\.css/],
transform(prefix: string, selector_: string) {
Expand Down
2 changes: 1 addition & 1 deletion tsconfig.base.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@

// Interop constraints
"esModuleInterop": false,
"allowSyntheticDefaultImports": false,
"allowSyntheticDefaultImports": true,
"forceConsistentCasingInFileNames": true,
"verbatimModuleSyntax": true,
"importHelpers": true, // This is only used for build validation. Since we do not have `tslib` installed, this will fail if we accidentally make use of anything that'd require injection of helpers.
Expand Down

0 comments on commit ea7f4bb

Please sign in to comment.