Skip to content

Commit

Permalink
feat: make create-decharge work
Browse files Browse the repository at this point in the history
  • Loading branch information
trustedtomato committed Nov 3, 2021
1 parent 91a83d3 commit f966d91
Show file tree
Hide file tree
Showing 18 changed files with 277 additions and 21 deletions.
9 changes: 9 additions & 0 deletions packages/create-decharge/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# create-decharge
The official CLI utility to create a [decharge](https://trustedtomato.github.io/decharge/) project.

## Usage
Create a project directory, `cd` into it, and run:
```bash
pnpm init decharge
```
Then follow the instructions which are in the output.
122 changes: 122 additions & 0 deletions packages/create-decharge/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import pathLib from 'path'
import fs from 'fs-extra'
import { sentenceCase } from 'sentence-case'
import { globby } from 'globby'
import minimatch from 'minimatch'
import { chdirTemp } from './utils/chdir-temp.js'
import { prompt } from './utils/prompt.js'
import { fatalError } from './utils/fatal-error.js'

// Error if directory is empty.
const isDirectoryEmpty = (await fs.readdir('.')).length === 0
if (!isDirectoryEmpty) {
fatalError('Error: The directory in which you initalise the project should be empty, aborting.')
}

const createDechargeMetadata = await fs.readJSON(new URL('./package.json', import.meta.url), 'utf8')

const templates = await fs.readdir(new URL('./templates', import.meta.url))

const { template } = await prompt({
type: 'select',
name: 'template',
message: 'Pick a template',
choices: templates.map(template => ({
title: sentenceCase(template),
value: template
}))
})

if (!template) {
console.error('Error: Template ')
process.exit(1)
}

const templateDirUrl = new URL(
`./templates/${template}/`,
import.meta.url
)
const templateSettingsUrl = new URL(
`./template-settings.js`,
templateDirUrl
)
const { default: { templatePlaceholders } } = await import(templateSettingsUrl)
const templatePlaceholderReplacements = new Map()

// Get templatePlaceholder replacement values which require prompt.
const answers = await prompt(
templatePlaceholders
.filter(({ promptsConfig }) => promptsConfig)
.map(
({ promptsConfig }, index) => ({
...promptsConfig,
name: index
})
)
)

for (const [index, answer] of Object.entries(answers)) {
templatePlaceholderReplacements.set(
templatePlaceholders[index],
answer
)
}

// Copy template to current directory
// while applying the required changes.
const templateFiles = await chdirTemp(templateDirUrl.pathname, async () =>
await globby([
'**/*',
'!template-settings.js'
], {
gitignore: true,
dot: true
})
).then(paths =>
paths.map(path => ({
path,
fullPath: pathLib.join(templateDirUrl.pathname, path)
}))
)

for (const { path, fullPath } of templateFiles) {
let modifiedContent = null
const modifyContent = async (transform) => {
if (modifiedContent === null) {
modifiedContent = await fs.readFile(fullPath, 'utf8')
}
modifiedContent = transform(modifiedContent)
}

// Modify content.
for (const templatePlaceholder of templatePlaceholders) {
if (templatePlaceholder.fileMatchers.some(
matcher => minimatch(path, matcher, { matchBase: true })
)) {
await modifyContent(content =>
content.replaceAll(
`TEMPLATE_PLACEHOLDER(${templatePlaceholder.name})`,
templatePlaceholderReplacements.get(templatePlaceholder)
)
)
}
}
if (path === 'package.json') {
await modifyContent((rawContent) => {
const content = JSON.parse(rawContent)
for (const [name, version] of Object.entries(content.dependencies)) {
if (version === 'workspace:*') {
content.dependencies[name] = createDechargeMetadata.dependencies[name]
}
}
return JSON.stringify(content, null, 2)
})
}

// Finalize content.
if (modifiedContent === null) {
await fs.copy(fullPath, path)
} else {
await fs.outputFile(path, modifiedContent)
}
}
21 changes: 14 additions & 7 deletions packages/create-decharge/package.json
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
{
"name": "create-decharge",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"type": "module",
"description": "The official CLI utility to create a decharge project.",
"bin": "index.js",
"author": "Tamás Halasi",
"license": "ISC",
"pnpm": {
"overrides": {
Expand All @@ -16,5 +13,15 @@
"semver-regex@<3.1.3": ">=3.1.3",
"trim-off-newlines@<=1.0.1": "<0.0.0"
}
},
"dependencies": {
"decharge": "workspace:*",
"fs-extra": "^10.0.0",
"globby": "^12.0.2",
"minimatch": "^3.0.4",
"param-case": "^3.0.4",
"prompts": "^2.4.2",
"readdirp": "^3.6.0",
"sentence-case": "^3.0.4"
}
}
4 changes: 0 additions & 4 deletions packages/create-decharge/templates/hello-world/.gitignore

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,16 +1,14 @@
{
"name": "decharge-blog",
"name": "TEMPLATE_PLACEHOLDER(projectName)",
"private": true,
"version": "0.0.1",
"description": "",
"description": "TEMPLATE_PLACEHOLDER(projectDescription)",
"type": "module",
"author": "Tamás Halasi",
"scripts": {
"build": "decharge build && node minify-dist.js",
"watch": "decharge watch",
"dev-serve": "browser-sync dist/ --config browser-sync.config.cjs --watch"
},
"license": "ISC",
"dependencies": {
"@types/gm": "^1.18.9",
"@types/node-fetch": "^2.5.10",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,21 @@ import { ComponentChildren } from 'preact'

interface Props {
extraTitle?: string
description: string
description?: string
children: ComponentChildren
}

export default ({ extraTitle, description, children }: Props) =>
<html lang="en">
<meta charSet="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="description" content={description} />
{ description
? <meta name="description" content={description} />
: <meta name="description" content="TEMPLATE_PLACEHOLDER(projectDescription)" />
}
<link rel="shortcut icon" href="favicon.svg" />
<Styles />
<title>{ extraTitle ? `${extraTitle} — ` : ''}Hello world</title>
<title>{ extraTitle ? `${extraTitle} — ` : ''}TEMPLATE_PLACEHOLDER(projectName)</title>
{children}
<Scripts type="end-of-body" />
</html>
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import Layout from '../components/Layout.js'

export default () => <>
<Layout description="Greeting the world.">
Hello world!
<Layout>
Hello TEMPLATE_PLACEHOLDER(projectName)!
</Layout>
</>
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
const projectNameRegex = /^(?:@[a-z0-9-*~][a-z0-9-*._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/
const projectDescriptionRegex = /^[^"\\<>{}]+/i

export default {
templatePlaceholders: [
{
name: 'projectName',
fileMatchers: ['*.tsx', '*.json'],
promptsConfig: {
type: 'text',
message: 'Project name',
validate: (projectName) =>
projectNameRegex.test(projectName)
? true
: `The project name must match the regex ${projectNameRegex}`
}
}, {
name: 'projectDescription',
fileMatchers: ['*.tsx', '*.json'],
promptsConfig: {
type: 'text',
message: 'Project description',
validate: (projectDescription) =>
projectDescriptionRegex.test(projectDescription)
? true
: 'The project description shall not include the following characters: ^"\\<>{}'
}
}
]
}
7 changes: 7 additions & 0 deletions packages/create-decharge/utils/chdir-temp.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export async function chdirTemp (path, func) {
const previousCwd = process.cwd()
process.chdir(path)
const result = await func()
process.chdir(previousCwd)
return result
}
4 changes: 4 additions & 0 deletions packages/create-decharge/utils/fatal-error.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export function fatalError (message) {
console.error(`Error: ${message}`)
process.exit(1)
}
10 changes: 10 additions & 0 deletions packages/create-decharge/utils/prompt.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import prompts from 'prompts'
import { fatalError } from './fatal-error.js'

export async function prompt (config) {
return await prompts(config, {
onCancel () {
fatalError('Canceled prompt, aborting.')
}
})
}
Loading

0 comments on commit f966d91

Please sign in to comment.