Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Release/3.1.0 rc7 #47

Merged
merged 17 commits into from
Jun 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,10 @@
"README.md"
],
"rules": {
"indent": ["error", "tab"],
"eol-last": ["error", "always", { "SwitchCase": 1 }],
"indent": ["error", "tab", { "SwitchCase": 1 }],
"eol-last": ["error", "always"],
"no-unused-vars": ["error", { "argsIgnorePattern": "^_" }],
"no-console": ["error", { "allow": ["info", "warn", "error"] }]
"no-console": ["error", { "allow": ["info", "warn", "error", "clear"] }]
}
}

Expand Down
4 changes: 3 additions & 1 deletion .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@ name: Publish

on:
release:
types: [published]
types:
- published

jobs:
publish:
name: Publish
runs-on: ubuntu-latest
steps:

Expand Down
4 changes: 3 additions & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@ name: Release

on:
push:
branches: [master]
branches:
- master

jobs:
release:
name: Release
runs-on: ubuntu-latest
steps:

Expand Down
52 changes: 52 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
name: Test

on:
pull_request:
branches:
- master

jobs:
test:
name: Test
runs-on: ubuntu-latest
steps:

# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it.
- name: Checkout Repo
id: checkout_repo
uses: actions/checkout@v4

# Gets the Node version from .nvmrc and sets it as an environment variable.
- name: Set Node Version
id: set_node_version
run: echo "NODE_VERSION=$(cat .nvmrc)" >> $GITHUB_ENV

# Setup Node.
- name: Setup Node
id: setup_node
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}

# Config a dummy git user for use in the repo initialization.
- name: Config Git User
id: config_git_user
run: |
git config --global user.name "Test User"
git config --global user.email "test@dreamsicle.io"

# Installs npm and composer dependencies.
- name: Install
id: install
run: npm ci

# Lint all files.
- name: Lint
id: lint
run: npm run lint

# Runs a test theme creation with all options set to skip prompt. Note we also set
# the path to `tests` and enable the `verbose` option for debugging.
- name: Create Theme
id: create_theme
run: npm test test-theme -- -v -p "tests" -N "Test Theme" -X "1.0.0" -T "parent-theme" -U "https://github.com/dreamsicle-io/test" -B "https://github.com/dreamsicle-io/test/issues" -R "git@github.com:dreamsicle-io/test.git" -r "git" -d "Just another WordPress theme." -A "Dreamsicle" -E "hello@dreamsicle.io" -u "https://www.dreamsicle.io" -L "gpl-3.0" -t "accessibility-ready,translation-ready" -W "6.1.0" -w "6.4.0" -F "test" -C "Test" -c "TEST"
38 changes: 34 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
# Create WP Theme

Create WP Theme is a node command line tool that will scaffold a new WordPress theme with an opinionated file structure and just the right amount of starter code to get a developer started building a modern WordPress theme. This package contains just the `npx @dreamsicle.io/create-wp-theme` command, all of the actual boilerplate code comes from [WP Theme Assets](https://github.com/dreamsicle-io/wp-theme-assets).
Create WP Theme is a node command line tool that will scaffold a new WordPress theme with an opinionated file structure and just the right amount of starter code. This package contains just the `npx @dreamsicle.io/create-wp-theme` command, all of the actual boilerplate code comes from [WP Theme Assets](https://github.com/dreamsicle-io/wp-theme-assets).

## Usage

Open a terminal, `cd` to the `/path/to/wordpress/wp-content/themes` directory of a local WordPress instance, and fire the `create-wp-theme` command.

```shell
npx @dreamsicle.io/create-wp-theme [options] <file>
npx @dreamsicle.io/create-wp-theme [options] <dir>
```

## Getting Started

All that is necessary to start using the tool is a single argument of `file`, which corresponds to a param-cased string that will serve as the theme directory, the WordPress text-domain, and the package name. This will also serve as regular expression to replace all instances of `wp-theme` in the cloned package files.
All that is necessary to start using the tool is a single argument of `dir`, which corresponds to a kebab-cased string that will serve as the theme directory, the WordPress text-domain, and the package name.

### 1. Run the `create-wp-theme` command

Expand Down Expand Up @@ -53,6 +53,8 @@ Class Prefix: (WP_Theme)
Constant Prefix: (WP_THEME)
```

> **Note:** Any promptable options that have values already provided by the user will not be prompted for. If the tool detects that all possible option values have already been provided by the user, it won't display the prompt, and will instead jump directly into creation.

## Logging

The tool will log its progress and errors in the console, exiting on completion and fatal errors.
Expand Down Expand Up @@ -103,7 +105,35 @@ themeAuthorEmail: hello@dreamsicle.com
themeAuthorURI: https://www.dreamsicle.com
```

## Help
## File Generation

The tool will rename files and generate file contents if it detects placeholders in the supported [WP Theme Assets](https://github.com/dreamsicle-io/wp-theme-assets) files. Note that casing will be adjusted automatically to ensure proper conventions.

### File Content Replacement

| Placeholder | Replacement | Description |
| ----------- | ------------------------ | ------------------------------------------- |
| `wp-theme` | `<dir>` | The kebab-cased directory argument. |
| `wp_theme` | `-F`, `--functionPrefix` | The snake-cased function prefix option. |
| `WP_Theme` | `-C`, `--classPrefix` | The pascal-snake-cased class prefix option. |
| `WP_THEME` | `-c`, `--constantPrefix` | The constant-cased constant prefix option. |
| `WP Theme` | `-N`, `--themeName` | The unmodified theme name option. |

### File Renaming

| Placeholder | Replacement | Description |
| ------------------ | --------------------- | -------------------------------------------- |
| `class-wp-theme-*` | `-C`, `--classPrefix` | The parsed class prefix option, kebab-cased. |

## Licenses

The tool will write a license for you according to the [SPDX](https://spdx.dev/) license expression provided in the `-L` or `--themeLicense` option from the [GitHub License API](https://docs.github.com/en/rest/licenses/licenses). You may use any of the [supported licenses](https://api.github.com/licenses?per_page=1000&page=1) to automatically write a license for you on theme creation. While SPDX technically supports [hundreds of license identifiers](https://spdx.org/licenses/), this tool will _only_ automatically write licenses for those supported by the Github License API.

To skip license generation, use `UNLICENSED` as the license identifier.

> **Note:** Some licenses ship with placeholders for things like company name, date, etc. Make sure to fill these out once your theme is generated.

## Help

To get help with the tool and to learn more about usage and the available options, use the `--help` or `-h` flag. This will output all help information available including how to use the command, arguments, option flags, option descriptions, and option defaults.

Expand Down
91 changes: 66 additions & 25 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,11 @@ const tmpThemeReadmePath = path.join(tmpThemePath, 'README.md');
const gitURL = 'https://github.com/dreamsicle-io/wp-theme-assets.git';
const gitBranch = 'master';

// Construct license settings.
const licenseProvider = 'GitHub';
const licenseAPIEndpoint = 'https://api.github.com/licenses';
const licenseAPIDocsURL = 'https://docs.github.com/en/rest/licenses/licenses';

/**
* Construct option definitions.
* @type {OptionDef[]}
Expand Down Expand Up @@ -555,37 +560,55 @@ function validate() {
}

async function runPrompt() {
// Determine if any of the options should be prompted for by filtering
// the option definitions for those that are marked as `isPrompted`,
// and don't already have a value provided by the user.
const promptedOptionDefs = optionDefs.filter((optionDef) => {
// Determine if the option has already been set by the user.
// Possible options value sources: `default`, `env`, `config`, `cli`.
const isUserProvided = ['env', 'cli'].includes(program.getOptionValueSource(optionDef.key));
return (optionDef.isPrompted && ! isUserProvided);
});
// If there are no options to prompt for, skip the prompt.
if (promptedOptionDefs.length < 1) {
logInfo({
title: 'Creating theme',
description: `Creating "${options.themeName}" in ${path.relative(process.cwd(), themePath)}`,
emoji: '⚡',
padding: 'bottom',
verbose: options.verbose,
dataLabel: 'Options',
data: options,
});
return;
}
// Clear the console.
console.clear();
// Introduce the prompt and log the pre-processed data for debugging.
logInfo({
title: 'Let\'s get started',
description: `This tool will guide you through configuring your theme.\nFor each prompt, set a value and hit "ENTER" to continue. To exit early, hit\n"CMD+C" on Mac, or "CTRL+C" on Windows. For help, run "create-wp-theme -h" to\noutput the tool's help information. If you need to log or view issues, visit\n${pkg.bugs.url}.`,
description: `This tool will guide you through configuring your theme.\nFor each prompt, set a value and hit "ENTER" to continue. To exit early, hit\n"CMD+C" on Mac, or "CTRL+C" on Windows. For help, run the command with the "-h"\nor "--help" flags to output the tool's help information. If you need to log or\nview issues, visit ${pkg.bugs.url}.`,
emoji: '⚡',
padding: 'both',
verbose: options.verbose,
dataLabel: 'Initial options',
data: options,
});
await co(function* () {
// Loop over the option definitions and prompt if not set through the CLI.
// Loop over the prompted option definitions and prompt the user for values.
// Note this cannot be a `forEach()` loop, because `yield` can only be
// used inside of a `for` loop.
for (const optionDef of optionDefs) {
// If the option matches its default, we can safely assume it was
// not passed from the CLI, and we should prompt for it. Also check
// if the option definition marks the option as `isPrompted`.
if (optionDef.isPrompted && (options[optionDef.key] === optionDef.default)) {
const promptMessage = `${chalk.bold.cyanBright(optionDef.title + ':')} ${chalk.dim('(' + options[optionDef.key] + ')')} `;
/**
* If there is a prompt value for this option, set it. If not,
* use the program option. Sanitize again here because the prompt
* values have not been sanitized yet.
* @type {string}
*/
const promptValue = yield prompt(promptMessage);
if (promptValue) options[optionDef.key] = optionDef.sanitize ? optionDef.sanitize(promptValue) : promptValue;
}
for (const optionDef of promptedOptionDefs) {
// Construct the prompt message.
const promptMessage = `${chalk.bold.cyanBright(optionDef.title + ':')} ${chalk.dim('(' + options[optionDef.key] + ')')} `;
/**
* If there is a prompt value for this option, set it. If not,
* use the program option. Sanitize again here because the prompt
* values have not been sanitized yet.
* @type {string}
*/
const promptValue = yield prompt(promptMessage);
if (promptValue) options[optionDef.key] = optionDef.sanitize ? optionDef.sanitize(promptValue) : promptValue;
}
});
// Confirm prompt completion and log the post-processed data for debugging.
Expand All @@ -604,7 +627,7 @@ function clonePackage() {
logInfo({
title: 'Cloning package',
description: `${gitURL} (${gitBranch})`,
emoji: '📥',
emoji: '',
verbose: options.verbose,
dataLabel: 'Package information',
data: {
Expand Down Expand Up @@ -756,7 +779,7 @@ function replaceRename() {
*/
async function fetchLicense(slug) {
const formattedSlug = encodeURIComponent(slug.toLowerCase());
const response = await fetch(`https://api.github.com/licenses/${formattedSlug}`, {
const response = await fetch(`${licenseAPIEndpoint}/${formattedSlug}`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
Expand All @@ -775,8 +798,25 @@ async function fetchLicense(slug) {
async function writeLicense() {
try {
if (options.themeLicense === 'UNLICENSED') {
// If `UNLICENSED` write the file with only `UNLICENSED` as its content.
fs.writeFileSync(tmpThemeLicPath, 'UNLICENSED', { encoding: 'utf8' });
} else {
// Let the user know we are fetching the license asynchronously, in case
// the API takes a while to respond.
logInfo({
title: 'Fetching license',
description: `Fetching license for SPDX ID: "${options.themeLicense}"`,
emoji: '⏳',
verbose: options.verbose,
dataLabel: 'Request information',
data: {
spdx: options.themeLicense,
provider: licenseProvider,
endpoint: licenseAPIEndpoint,
documentation: licenseAPIDocsURL,
},
});
// Fetch the license from GitHub.
const license = await fetchLicense(options.themeLicense);
logInfo({
title: 'License fetched',
Expand All @@ -791,8 +831,10 @@ async function writeLicense() {
api: license.url,
},
});
// Write the license content.
fs.writeFileSync(tmpThemeLicPath, license.body, { encoding: 'utf8' });
}
// Log license generation success.
logInfo({
title: 'License written',
description: path.relative(tmpThemePath, tmpThemeLicPath),
Expand All @@ -808,7 +850,7 @@ async function writeLicense() {

function putPackage() {
// Double check if the directory exists already and throw an error if not to
// avoid accidnetally removing important data on the machine.
// avoid accidentally removing important data on the machine.
if (fs.existsSync(themePath)) throw new Error(`There is already a directory at "${themePath}"`);
// Copy the final build from the tmp directory to the real directory and clean the tmp directory.
fs.cpSync(tmpThemePath, themePath, { recursive: true, force: true });
Expand Down Expand Up @@ -866,8 +908,7 @@ function initGitRepo() {

function initRepo() {
try {
// Switch on the repo type and initialize a git repo with
// remote origin based on repo type.
// Switch on the repo type and initialize a repo with remote origin.
switch (options.themeRepoType) {
case 'git': {
initGitRepo();
Expand All @@ -883,15 +924,15 @@ function initRepo() {
}
}
} catch (error) {
// We don't want Git errors to exit the process, so don't throw.
// We don't want repo errors to exit the process, so don't throw.
// Instead, catch them and log them so the user is aware, while
// allowing the process to continue.
logError(error, options.verbose);
}
}

function logSuccess() {
// Prepare the final theme path.
// Prepare the relative theme path.
const relPath = path.relative(process.cwd(), themePath);
// Log information to the console.
logInfo({
Expand All @@ -902,7 +943,7 @@ function logSuccess() {
});
logInfo({
title: 'What\'s next?',
description: `Head over to your new theme directory to install dependencies\nand start cooking something up! If we\'ve initialized a repository for you, we\ncommited the initial files and added a remote origin, but we didn\'t push\nupstream. It\'s also a good idea to check your LICENSE file to fill out any\nplaceholders that may be in the text. ${chalk.bold('Now, go build something beautiful.')}`,
description: `Head over to your new theme directory to install dependencies\nand start cooking something up! If we've initialized a repository for you, we\ncommited the initial files and added a remote origin, but we didn't push\nupstream. It's also a good idea to check your LICENSE file to fill out any\nplaceholders that may be in the text. ${chalk.bold('Now, go build something beautiful.')}`,
emoji: '⚡',
padding: 'bottom',
});
Expand Down
12 changes: 6 additions & 6 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading