-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
91a83d3
commit f966d91
Showing
18 changed files
with
277 additions
and
21 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
File renamed without changes.
File renamed without changes.
File renamed without changes.
6 changes: 2 additions & 4 deletions
6
...charge/templates/hello-world/package.json → ...ge/templates/minimal-project/package.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
File renamed without changes.
File renamed without changes
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
4 changes: 2 additions & 2 deletions
4
...emplates/hello-world/src/routes/index.tsx → ...ates/minimal-project/src/routes/index.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> | ||
</> |
30 changes: 30 additions & 0 deletions
30
packages/create-decharge/templates/minimal-project/template-settings.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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: ^"\\<>{}' | ||
} | ||
} | ||
] | ||
} |
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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.') | ||
} | ||
}) | ||
} |
Oops, something went wrong.