Skip to content

Commit

Permalink
Add a top-bun --eject flag
Browse files Browse the repository at this point in the history
This allows one to eject out the default layout and style to more easily customize a default layout site.

Specify the --src flag to determine the target path of the eject.
  • Loading branch information
bcomnes committed Nov 23, 2023
1 parent e83ea56 commit 20039e3
Show file tree
Hide file tree
Showing 9 changed files with 160 additions and 21 deletions.
87 changes: 85 additions & 2 deletions bin.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,23 @@
import minimist from 'minimist'
// @ts-ignore
import cliclopts from 'cliclopts'
import { readFile } from 'fs/promises'
import { resolve, join } from 'path'
import { readFile } from 'node:fs/promises'
import { resolve, join, relative } from 'node:path'
import readline from 'node:readline'
import desm from 'desm'
import process from 'process'
// @ts-ignore
import tree from 'pretty-tree'
import { inspect } from 'util'
import { packageDirectory } from 'pkg-dir'
import { readPackage } from 'read-pkg'
import { addPackageDependencies } from 'write-package'

import { copyFile } from './lib/helpers/copy-file.js'
import { TopBun } from './index.js'
import { TopBunAggregateError } from './lib/helpers/top-bun-aggregate-error.js'
import { generateTreeData } from './lib/helpers/generate-tree-data.js'
import { askYesNo } from './lib/helpers/cli-prompt.js'

/**
* @typedef {import('./lib/builder.js').TopBunOpts} TopBunOpts
Expand Down Expand Up @@ -47,6 +53,11 @@ const clopts = cliclopts([
abbr: 'i',
help: 'comma separated gitignore style ignore string'
},
{
name: 'eject',
abbr: 'e',
help: 'eject the top bun default layout, style and client into the src flag directory'
},
{
name: 'watch',
abbr: 'w',
Expand Down Expand Up @@ -93,6 +104,78 @@ async function run () {
const src = resolve(join(cwd, argv['src']))
const dest = resolve(join(cwd, argv['dest']))

// Eject task
if (argv['eject']) {
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
})

const localPkg = await packageDirectory({ cwd: src })

if (!localPkg) {
console.error('Can\'t locate package.json, exiting without making changes')
process.exit(1)
}

const localPkgJson = join(localPkg, 'package.json')
const localPkgJsonContents = await readPackage({ cwd: localPkg })
const targetIsModule = localPkgJsonContents.type === 'module'

const relativeSrc = relative(process.cwd(), src)
const relativePkg = relative(process.cwd(), localPkgJson)

const targetLayoutPath = `layouts/root.layout.${targetIsModule ? 'js' : 'mjs'}`
const targetGlobalStylePath = 'globals/global.css'
const targetGlobalClientPath = `globals/global.client.${targetIsModule ? 'js' : 'mjs'}`

const tbPkgContents = await readPackage({ cwd: __dirname })
const mineVersion = tbPkgContents?.['dependencies']?.['mine.css']
const uhtmlVersion = tbPkgContents?.['dependencies']?.['uhtml-isomorphic']
const highlightVersion = tbPkgContents?.['dependencies']?.['highlight.js']

if (!mineVersion || !uhtmlVersion || !highlightVersion) {
console.error('Unable to resolve ejected depdeency versions. Exiting...')
process.exit(1)
}

console.log(`
top-bun eject actions:
- Write ${join(relativeSrc, targetLayoutPath)}
- Write ${join(relativeSrc, targetGlobalStylePath)}
- Write ${join(relativeSrc, targetGlobalClientPath)}
- Add mine.css@${mineVersion}, uhtml-isomorphic@${uhtmlVersion} and highlight.js@${highlightVersion} to ${relativePkg}
`)
const answer = await askYesNo(rl, 'Continue?')
if (answer === false) {
console.log('No action taken. Exiting.')
process.exit(0)
}

const defaultLayoutPath = join(__dirname, 'lib/defaults/default.root.layout.js')
const defaultGlobalStylePath = join(__dirname, 'lib/defaults/default.style.css')
const defaultGlobalClientPath = join(__dirname, 'lib/defaults/default.client.js')

await Promise.all([
copyFile(defaultLayoutPath, join(src, targetLayoutPath)),
copyFile(defaultGlobalStylePath, join(src, targetGlobalStylePath)),
copyFile(defaultGlobalClientPath, join(src, targetGlobalClientPath))
])

await addPackageDependencies(
localPkgJson,
{
dependencies: {
'mine.css': mineVersion,
'uhtml-isomorphic': uhtmlVersion,
'highlight.js': highlightVersion
}
})

console.log('Done ejecting files!')
process.exit(0)
}

/** @type {TopBunOpts} */
const opts = {}

Expand Down
2 changes: 1 addition & 1 deletion examples/basic/src/js-page/loose-assets/style.css
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
@import './local-import.css'
@import './local-import.css';

4 changes: 3 additions & 1 deletion examples/basic/src/root.layout.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,9 @@ export default async function RootLayout ({
: null}
</head>
<body>
${typeof children === 'string' ? html([children]) : children /* Support both uhtml and string children. Optional. */}
<div class="mine-layout">
${typeof children === 'string' ? html([children]) : children /* Support both uhtml and string children. Optional. */}
</div>
</body>
</html>
`)
Expand Down
3 changes: 1 addition & 2 deletions examples/default-layout/src/README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
# Default Layout

TopBun ships a default layout when you don't have a `root.layout.js` file in the root
of your `src` directory.
TopBun ships a default layout when you don't have a `root.layout.js` file your `src` directory.

2 changes: 1 addition & 1 deletion lib/build-static/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ const copy = cpx.copy
* @return {string} - The copy clob
*/
export function getCopyGlob (src) {
return `${src}/**/!(*.js|*.css|*.html|*.md)`
return `${src}/**/!(*.js|*.cjs|*.mjs|*.css|*.html|*.md)`
}

/**
Expand Down
28 changes: 28 additions & 0 deletions lib/helpers/cli-prompt.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/**
* @typedef {import('node:readline').Interface} Interface
*/

/**
* @param {Interface} rl
* @param {string} question
* @return {Promise<boolean>} [description]
*/
export function askYesNo (rl, question) {
return new Promise((resolve) => {
const query = () => {
rl.question(`${question} (Y/n) `, (answer) => {
const lowercaseAnswer = answer.toLowerCase()
if (['yes', 'ye', 'y', ''].includes(lowercaseAnswer)) {
resolve(true)
} else if (['no', 'n'].includes(lowercaseAnswer)) {
resolve(false)
} else {
console.log('Invalid response. Please answer with yes (Y) or no (n).')
query() // Re-prompt if invalid input
}
})
}

query()
})
}
17 changes: 17 additions & 0 deletions lib/helpers/copy-file.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { promises as fs } from 'node:fs'
import path from 'node:path'

/**
* @param {string} source
* @param {string} target
*/
export async function copyFile (source, target) {
// Extract the directory part of the target path
const targetDir = path.dirname(target)

// Ensure the target directory exists
await fs.mkdir(targetDir, { recursive: true })

// Copy the file
await fs.copyFile(source, target)
}
32 changes: 19 additions & 13 deletions lib/identify-pages.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,13 @@ import { TopBunDuplicatePageError } from './helpers/top-bun-error.js'

const __dirname = desm(import.meta.url)

const layoutSuffix = '.layout.js'
const layoutClientSuffix = '.layout.client.js'
const layoutSuffixs = ['.layout.js', '.layout.mjs', '.layout.cjs']
const layoutClientSuffixs = ['.layout.client.js', '.layout.client.mjs', '.layout.client.cjs']
const layoutStyleSuffix = '.layout.css'
export const templateSuffix = '.template.js'
const templateSuffixs = ['.template.js', '.template.mjs', '.template.cjs']
const globalStyleName = 'global.css'
const globalClientName = 'global.client.js'
const globalVarsName = 'global.vars.js'
const globalClientNames = ['global.client.js', 'global.client.mjs', 'global.client.cjs']
const globalVarsNames = ['global.vars.js', 'global.vars.mjs', 'global.vars.mjs']

/**
* Shape the file walker object
Expand Down Expand Up @@ -211,8 +211,10 @@ export async function identifyPages (src, opts = {}) {
})
}

if (fileName.endsWith(layoutSuffix)) {
const layoutName = fileName.slice(0, -layoutSuffix.length)
if (layoutSuffixs.some(suffix => fileName.endsWith(suffix))) {
const suffix = layoutSuffixs.find(suffix => fileName.endsWith(suffix))
if (!suffix) throw new Error('layout suffix not found')
const layoutName = fileName.slice(0, -suffix.length)

if (layouts?.[layoutName]?.relname) {
warnings.push(
Expand Down Expand Up @@ -247,8 +249,10 @@ export async function identifyPages (src, opts = {}) {
}
}

if (fileName.endsWith(layoutClientSuffix)) {
const layoutClientName = fileName.slice(0, -layoutClientSuffix.length)
if (layoutClientSuffixs.some(suffix => fileName.endsWith(suffix))) {
const suffix = layoutClientSuffixs.find(suffix => fileName.endsWith(suffix))
if (!suffix) throw new Error('layout client suffix not found')
const layoutClientName = fileName.slice(0, -suffix.length)

if (layouts?.[layoutClientName]?.layoutClient) {
warnings.push({
Expand All @@ -268,8 +272,10 @@ export async function identifyPages (src, opts = {}) {
}
}

if (fileName.endsWith(templateSuffix)) {
const temlateFileName = fileName.slice(0, -templateSuffix.length)
if (templateSuffixs.some(suffix => fileName.endsWith(suffix))) {
const suffix = templateSuffixs.find(suffix => fileName.endsWith(suffix))
if (!suffix) throw new Error('template suffix not found')
const temlateFileName = fileName.slice(0, -suffix.length)

templates.push({
templateFile: fileInfo,
Expand All @@ -289,7 +295,7 @@ export async function identifyPages (src, opts = {}) {
}
}

if (basename(fileName) === globalClientName) {
if (globalClientNames.some(name => basename(fileName) === name)) {
if (globalClient) {
warnings.push({
code: 'TOP_BUN_WARNING_DUPLICATE_GLOBAL_CLIENT',
Expand All @@ -300,7 +306,7 @@ export async function identifyPages (src, opts = {}) {
}
}

if (basename(fileName) === globalVarsName) {
if (globalVarsNames.some(name => basename(fileName) === name)) {
if (globalVars) {
warnings.push({
code: 'TOP_BUN_WARNING_DUPLICATE_GLOBAL_VARS',
Expand Down
6 changes: 5 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,13 @@
"mine.css": "^9.0.1",
"minimist": "^1.2.5",
"p-map": "^6.0.0",
"package-json": "^8.1.1",
"pretty": "^2.0.0",
"pretty-tree": "^1.0.0",
"uhtml-isomorphic": "^2.0.0"
"read-pkg": "^9.0.1",
"pkg-dir": "^8.0.0",
"uhtml-isomorphic": "^2.0.0",
"write-package": "^7.0.0"
},
"devDependencies": {
"@types/browser-sync": "^2.27.3",
Expand Down

0 comments on commit 20039e3

Please sign in to comment.