Skip to content

Commit

Permalink
feat: no more template repos (#1395)
Browse files Browse the repository at this point in the history
* feat: no more template repos

* fix: rename commonjs template dir

* fix: separate mocharc templates

* fix: lower case module type in template path

* refactor: share repeated logic via a function (#1397)

---------

Co-authored-by: Shane McLaughlin <shane.mclaughlin@salesforce.com>
  • Loading branch information
mdonnalley and mshanemc committed May 1, 2024
1 parent a7a4abf commit ddab537
Show file tree
Hide file tree
Showing 30 changed files with 870 additions and 109 deletions.
173 changes: 85 additions & 88 deletions src/commands/generate.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
/* eslint-disable unicorn/no-await-expression-member */
import {Args, Errors, Flags} from '@oclif/core'
import chalk from 'chalk'
import {readFile, rm, writeFile} from 'node:fs/promises'
import {existsSync} from 'node:fs'
import {readdir} from 'node:fs/promises'
import {join, resolve, sep} from 'node:path'
import validatePkgName from 'validate-npm-package-name'

import {FlaggablePrompt, GeneratorCommand, exec, makeFlags, readPJSON} from '../generator'
import {compact, uniq, validateBin} from '../util'
import {FlaggablePrompt, GeneratorCommand, exec, makeFlags} from '../generator'
import {validateBin} from '../util'

async function fetchGithubUserFromAPI(): Promise<{login: string; name: string} | undefined> {
const token = process.env.GITHUB_TOKEN ?? process.env.GH_TOKEN
Expand Down Expand Up @@ -46,18 +47,6 @@ function determineDefaultAuthor(
return defaultValue
}

async function clone(repo: string, location: string): Promise<void> {
try {
await exec(`git clone https://github.com/oclif/${repo}.git "${location}" --depth=1`)
} catch (error) {
const err =
error instanceof Error
? new Errors.CLIError(error)
: new Errors.CLIError('An error occurred while cloning the template repo')
throw err
}
}

const FLAGGABLE_PROMPTS = {
author: {
message: 'Author',
Expand Down Expand Up @@ -146,47 +135,45 @@ export default class Generate extends GeneratorCommand<typeof Generate> {
const location = this.flags['output-dir'] ? join(this.flags['output-dir'], this.args.name) : resolve(this.args.name)
this.log(`Generating ${this.args.name} in ${chalk.green(location)}`)

if (existsSync(location)) {
throw new Errors.CLIError(`The directory ${location} already exists.`)
}

const moduleType = await this.getFlagOrPrompt({
defaultValue: 'ESM',
name: 'module-type',
type: 'select',
})

const template = moduleType === 'ESM' ? 'hello-world-esm' : 'hello-world'
await clone(template, location)
await rm(join(location, '.git'), {force: true, recursive: true})
// We just cloned the template repo so we're sure it has a package.json
const packageJSON = (await readPJSON(location))!

const githubUser = await fetchGithubUser()

const name = await this.getFlagOrPrompt({defaultValue: this.args.name, name: 'name', type: 'input'})
const bin = await this.getFlagOrPrompt({defaultValue: name, name: 'bin', type: 'input'})
const description = await this.getFlagOrPrompt({
defaultValue: packageJSON.description,
defaultValue: 'A new CLI generated with oclif',
name: 'description',
type: 'input',
})
const author = await this.getFlagOrPrompt({
defaultValue: determineDefaultAuthor(githubUser, packageJSON.author),
defaultValue: determineDefaultAuthor(githubUser, 'Your Name Here'),
name: 'author',
type: 'input',
})

const license = await this.getFlagOrPrompt({
defaultValue: packageJSON.license,
defaultValue: 'MIT',
name: 'license',
type: 'input',
})

const owner = await this.getFlagOrPrompt({
defaultValue: githubUser?.login ?? location.split(sep).at(-2) ?? packageJSON.author,
defaultValue: githubUser?.login ?? location.split(sep).at(-2) ?? 'Your Name Here',
name: 'owner',
type: 'input',
})

const repository = await this.getFlagOrPrompt({
defaultValue: (name ?? packageJSON.repository ?? packageJSON.name).split('/').at(-1) ?? name,
defaultValue: name.split('/').at(-1) ?? name,
name: 'repository',
type: 'input',
})
Expand All @@ -197,68 +184,67 @@ export default class Generate extends GeneratorCommand<typeof Generate> {
type: 'select',
})

const updatedPackageJSON = {
...packageJSON,
author,
bin: {[bin]: './bin/run.js'},
bugs: `https://github.com/${owner}/${repository}/issues`,
description,
homepage: `https://github.com/${owner}/${repository}`,
license,
name,
oclif: {
...packageJSON.oclif,
bin,
dirname: bin,
},
repository: `${owner}/${repository}`,
version: '0.0.0',
}

if (packageManager !== 'yarn') {
const scripts = (updatedPackageJSON.scripts || {}) as Record<string, string>
updatedPackageJSON.scripts = Object.fromEntries(
Object.entries(scripts).map(([k, v]) => [k, v.replace('yarn', `${packageManager} run`)]),
)
}

const {default: sortPackageJson} = await import('sort-package-json')
await writeFile(join(location, 'package.json'), JSON.stringify(sortPackageJson(updatedPackageJSON), null, 2))
await rm(join(location, 'LICENSE'))
await rm(join(location, '.github', 'workflows', 'automerge.yml'))

const existing = (await readFile(join(location, '.gitignore'), 'utf8')).split('\n')
const updated =
uniq(
compact([
'*-debug.log',
'*-error.log',
'node_modules',
'/tmp',
'/dist',
'/lib',
...(packageManager === 'yarn'
? [
'/package-lock.json',
'.pnp.*',
'.yarn/*',
'!.yarn/patches',
'!.yarn/plugins',
'!.yarn/releases',
'!.yarn/sdks',
'!.yarn/versions',
]
: ['/yarn.lock']),
...existing,
]),
)
.sort()
.join('\n') + '\n'

await writeFile(join(location, '.gitignore'), updated)

if (packageManager !== 'yarn') {
await rm(join(location, 'yarn.lock'))
const [sharedFiles, moduleSpecificFiles] = await Promise.all(
['shared', moduleType.toLowerCase()].map((f) => join(this.templatesDir, 'cli', f)).map(findEjsFiles(location)),
)

await Promise.all(
[...sharedFiles, ...moduleSpecificFiles].map(async (file) => {
switch (file.name) {
case 'package.json.ejs': {
const data = {
author,
bin,
description,
license,
moduleType,
name,
owner,
pkgManagerScript: packageManager === 'yarn' ? 'yarn' : `${packageManager} run`,
repository,
}
await this.template(file.src, file.destination, data)

break
}

case '.gitignore.ejs': {
await this.template(file.src, file.destination, {packageManager})

break
}

case 'README.md.ejs': {
await this.template(file.src, file.destination, {description, name, repository})

break
}

case 'onPushToMain.yml.ejs':
case 'onRelease.yml.ejs':
case 'test.yml.ejs': {
await this.template(file.src, file.destination, {
exec: packageManager === 'yarn' ? packageManager : `${packageManager} exec`,
install: packageManager === 'yarn' ? packageManager : `${packageManager} install`,
packageManager,
run: packageManager === 'yarn' ? packageManager : `${packageManager} run`,
})

break
}

default: {
await this.template(file.src, file.destination)
}
}
}),
)

if (process.platform !== 'win32') {
await Promise.all([
exec(`chmod +x ${join(location, 'bin', 'run.js')}`),
exec(`chmod +x ${join(location, 'bin', 'dev.js')}`),
])
}

await exec(`${packageManager} install`, {cwd: location, silent: false})
Expand All @@ -276,3 +262,14 @@ export default class Generate extends GeneratorCommand<typeof Generate> {
this.log(`\nCreated ${chalk.green(name)}`)
}
}

const findEjsFiles =
(location: string) =>
async (dir: string): Promise<Array<{destination: string; name: string; src: string}>> =>
(await readdir(dir, {recursive: true, withFileTypes: true}))
.filter((f) => f.isFile() && f.name.endsWith('.ejs'))
.map((f) => ({
destination: join(f.path.replace(dir, location), f.name.replace('.ejs', '')),
name: f.name,
src: join(f.path, f.name),
}))
26 changes: 5 additions & 21 deletions src/commands/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -134,28 +134,12 @@ export default class Generate extends GeneratorCommand<typeof Generate> {
this.log(`Using module type ${chalk.green(moduleType)}`)
this.log(`Using package manager ${chalk.green(packageManager)}`)

const templateOptions = {moduleType}
const projectBinPath = join(location, 'bin')
await this.template(
join(this.templatesDir, 'src', 'init', 'dev.cmd.ejs'),
join(projectBinPath, 'dev.cmd'),
templateOptions,
)
await this.template(
join(this.templatesDir, 'src', 'init', 'dev.js.ejs'),
join(projectBinPath, 'dev.js'),
templateOptions,
)
await this.template(
join(this.templatesDir, 'src', 'init', 'run.cmd.ejs'),
join(projectBinPath, 'run.cmd'),
templateOptions,
)
await this.template(
join(this.templatesDir, 'src', 'init', 'run.js.ejs'),
join(projectBinPath, 'run.js'),
templateOptions,
)
const templateBinPath = join(this.templatesDir, 'cli', moduleType.toLowerCase(), 'bin')
await this.template(join(templateBinPath, 'dev.cmd.ejs'), join(projectBinPath, 'dev.cmd'))
await this.template(join(templateBinPath, 'dev.js.ejs'), join(projectBinPath, 'dev.js'))
await this.template(join(templateBinPath, 'run.cmd.ejs'), join(projectBinPath, 'run.cmd'))
await this.template(join(templateBinPath, 'run.js.ejs'), join(projectBinPath, 'run.js'))

if (process.platform !== 'win32') {
await exec(`chmod +x "${join(projectBinPath, 'run.js')}"`)
Expand Down
11 changes: 11 additions & 0 deletions templates/cli/commonjs/.mocharc.json.ejs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"require": [
"ts-node/register"
],
"watch-extensions": [
"ts"
],
"recursive": true,
"reporter": "spec",
"timeout": 60000
}
3 changes: 3 additions & 0 deletions templates/cli/commonjs/bin/dev.cmd.ejs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
@echo off

node "%~dp0\dev" %*
6 changes: 6 additions & 0 deletions templates/cli/commonjs/bin/dev.js.ejs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#!/usr/bin/env node_modules/.bin/ts-node
// eslint-disable-next-line node/shebang, unicorn/prefer-top-level-await
;(async () => {
const oclif = await import('@oclif/core')
await oclif.execute({development: true, dir: __dirname})
})()
3 changes: 3 additions & 0 deletions templates/cli/commonjs/bin/run.cmd.ejs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
@echo off

node "%~dp0\run" %*
7 changes: 7 additions & 0 deletions templates/cli/commonjs/bin/run.js.ejs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#!/usr/bin/env node

// eslint-disable-next-line unicorn/prefer-top-level-await
(async () => {
const oclif = await import('@oclif/core')
await oclif.execute({dir: __dirname})
})()
11 changes: 11 additions & 0 deletions templates/cli/commonjs/tsconfig.json.ejs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"compilerOptions": {
"declaration": true,
"module": "commonjs",
"outDir": "dist",
"rootDir": "src",
"strict": true,
"target": "es2022"
},
"include": ["src/**/*"]
}
15 changes: 15 additions & 0 deletions templates/cli/esm/.mocharc.json.ejs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"require": [
"ts-node/register"
],
"watch-extensions": [
"ts"
],
"recursive": true,
"reporter": "spec",
"timeout": 60000,
"node-option": [
"loader=ts-node/esm",
"experimental-specifier-resolution=node"
]
}
3 changes: 3 additions & 0 deletions templates/cli/esm/bin/dev.cmd.ejs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
@echo off

node --loader ts-node/esm --no-warnings=ExperimentalWarning "%~dp0\dev" %*
6 changes: 6 additions & 0 deletions templates/cli/esm/bin/dev.js.ejs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#!/usr/bin/env -S node --loader ts-node/esm --no-warnings=ExperimentalWarning

// eslint-disable-next-line n/shebang
import {execute} from '@oclif/core'

await execute({development: true, dir: import.meta.url})
3 changes: 3 additions & 0 deletions templates/cli/esm/bin/run.cmd.ejs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
@echo off

node "%~dp0\run" %*
5 changes: 5 additions & 0 deletions templates/cli/esm/bin/run.js.ejs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#!/usr/bin/env node

import {execute} from '@oclif/core'

await execute({dir: import.meta.url})
15 changes: 15 additions & 0 deletions templates/cli/esm/tsconfig.json.ejs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"compilerOptions": {
"declaration": true,
"module": "Node16",
"outDir": "dist",
"rootDir": "src",
"strict": true,
"target": "es2022",
"moduleResolution": "node16"
},
"include": ["./src/**/*"],
"ts-node": {
"esm": true
}
}
1 change: 1 addition & 0 deletions templates/cli/shared/.eslintignore.ejs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/dist
3 changes: 3 additions & 0 deletions templates/cli/shared/.eslintrc.json.ejs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"extends": ["oclif", "oclif-typescript", "prettier"]
}

0 comments on commit ddab537

Please sign in to comment.