Skip to content

Commit

Permalink
feat(create-abell): migrate create-abell to monorepo and TS (#136)
Browse files Browse the repository at this point in the history
* feat: initiate basic create-abell code

* feat: add install dependecies step

* feat: add deleteDir step

* docs: elaborate comment

* feat: add default template

* feat: add logs

* fix: missing projectname error

* fix error format

* 0.0.15
  • Loading branch information
saurabhdaware authored May 7, 2022
1 parent 387cf15 commit cfe8f93
Show file tree
Hide file tree
Showing 22 changed files with 1,254 additions and 0 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# Logs
.DS_Store
logs
*.log
npm-debug.log*
Expand Down
1 change: 1 addition & 0 deletions packages/create-abell/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
scaffold-dir/
9 changes: 9 additions & 0 deletions packages/create-abell/.npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
node_modules
dist
scaffold-dir
src
templates/**/dist/
templates/**/node_modules/
templates/**/yarn.lock
scripts
tsconfig.json
37 changes: 37 additions & 0 deletions packages/create-abell/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
{
"name": "create-abell",
"version": "0.0.15",
"description": "Boilerplate for abell. npx create-abell my-blog",
"main": "dist/create.js",
"types": "dist/create.d.ts",
"bin": {
"create-abell": "dist/bin.js"
},
"scripts": {
"build": "tsc && node scripts/post-build.js",
"dev": "nodemon --exec \"yarn build\" --watch src --watch templates -e js,ts,abell,css",
"clean-scaffolds": "node scripts/clean-scaffolds.js",
"scaffold": "npm run clean-scaffolds && cd scaffold-dir && node ../dist/bin.js",
"prepublishOnly": "yarn build"
},
"repository": {
"type": "git",
"url": "git+https://github.com/abelljs/abell.git"
},
"keywords": [
"abell"
],
"author": "saurabhdaware",
"license": "MIT",
"bugs": {
"url": "https://github.com/abelljs/abell/issues"
},
"homepage": "https://github.com/abelljs/abell#readme",
"dependencies": {
"commander": "^9.2.0",
"prompts": "^2.4.2"
},
"devDependencies": {
"@types/prompts": "^2.0.14"
}
}
11 changes: 11 additions & 0 deletions packages/create-abell/scripts/clean-scaffolds.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
const fs = require('fs');
const path = require('path');
const { deleteDir } = require('../dist/utils');

function clean() {
const scaffoldDir = path.join(__dirname, '..', 'scaffold-dir');
deleteDir(scaffoldDir);
fs.mkdirSync(scaffoldDir);
}

clean();
12 changes: 12 additions & 0 deletions packages/create-abell/scripts/post-build.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
const fs = require('fs');
const path = require('path');

const DIST = path.join(__dirname, '..', 'dist');

try {
const fd = fs.openSync(path.join(DIST, 'bin.js'), 'r');
fs.fchmodSync(fd, 511);
console.log('> Changed bin.js file persmission to executable');
} catch (error) {
console.log(error);
}
25 changes: 25 additions & 0 deletions packages/create-abell/src/bin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#!/usr/bin/env node
import { createCommand } from 'commander';
import create, { CreateAbellOptions } from './create';

const program = createCommand();
/**
* npx create-abell [projectName] --template <template> --installer <installer>
*/
program
.option('-t|--template <template>', 'Specify template for abell app')
.option(
'-i|--installer <installer>',
'Specify package installer. npm or yarn.'
)
.arguments('[projectName]')
.action((projectName: string | undefined, options: CreateAbellOptions) =>
create(projectName, {
template: options.template,
installer: options.installer
})
);

// eslint-disable-next-line @typescript-eslint/no-var-requires
program.version(require('../package.json').version, '-v|--version');
program.parse(process.argv);
58 changes: 58 additions & 0 deletions packages/create-abell/src/create.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import {
getInstallCommand,
getProjectInfo,
getTemplate,
scaffoldTemplate,
setNameInPackageJSON
} from './steps';
import { deleteDir, log, relative, run } from './utils';

export type CreateAbellOptions = {
installer?: 'npm' | 'yarn';
template?: string;
};

async function create(
projectNameArg: string | undefined,
options: CreateAbellOptions
): Promise<void> {
// 1. Get all the required project information
const { projectDisplayName, projectPath } = await getProjectInfo(
projectNameArg
);
const relProjectPath = relative(projectPath);
const template = getTemplate(options.template);
const installCommand = await getInstallCommand(options.installer);
log.info(`Scaffolding \`${relProjectPath}\` using \`${template}\` template`);

// 2. Scaffold Project
await scaffoldTemplate({
projectPath,
template
});

log.info(`Running \`${installCommand}\``);
// 3. Install Dependencies
try {
await run(installCommand, {
cwd: projectPath
});
} catch (err) {
log.failure(`Could not install dependencies. Skipping ${installCommand}`);
}

// 4. Set name in project's package.json
setNameInPackageJSON(`${projectPath}/package.json`, projectDisplayName);

// 5. Delete `.git` (For projects scaffolded from github)
deleteDir(`${projectPath}/.git`);

// 6. Log Success @todo
log.success(`${projectDisplayName} scaffolded successfully`);
const runCommand = installCommand === 'yarn' ? 'yarn dev' : 'npm run dev';
log.info(
`cd ${relProjectPath} and run \`${runCommand}\` to run the dev-server`
);
}

export default create;
146 changes: 146 additions & 0 deletions packages/create-abell/src/steps.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
import fs from 'fs';
import path from 'path';
import prompts from 'prompts';
import { colors, copyFolderSync, log, normalizePath, run } from './utils';

/**
* Prompts user for projectName if not defined, returns the information required related to project
*/
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
export const getProjectInfo = async (projectNameArg: string | undefined) => {
let projectName = '';
if (!projectNameArg) {
projectName = (
await prompts({
type: 'text',
message: 'Enter Name of your project',
name: 'projectName',
initial: 'hello-abell'
})
).projectName;
} else {
projectName = projectNameArg;
}

if (!projectName) {
throw new Error(log.failure('Project name is required', false));
}

const projectSlugName = projectName.toLowerCase().replace(/ |_/g, '-');
const projectPath = path.join(process.cwd(), projectSlugName);
const projectDisplayName = path.basename(projectPath);

if (fs.existsSync(projectPath)) {
// oops. Can be an issue
if (fs.readdirSync(projectPath).length !== 0) {
// Not an empty directory so break!
console.error(
`${colors.red(
'>> '
)} The directory already exists and is not an empty directory`
);
process.exit(0);
}
}

return { projectDisplayName, projectPath };
};

/**
* Prompts user to choose package installer if not defined
*/
export const getInstallCommand = async (
installerVal: 'npm' | 'yarn' | undefined
): Promise<'npm install' | 'yarn'> => {
if (!installerVal) {
// if installer flag is undefined, ask user.
const answers = await prompts({
type: 'select',
message: 'Select Installer',
name: 'installer',
choices: [
{
title: 'npm',
value: 'npm install'
},
{
title: 'yarn',
value: 'yarn'
}
]
});

installerVal = answers.installer;
}

if (installerVal === 'yarn') {
return 'yarn';
} else {
return 'npm install';
}
};

/**
* Some validations on top of template names
*/
export const getTemplate = (templateVal: string | undefined): string => {
// return default when value is not defined
if (!templateVal) return 'default';

if (templateVal === 'default' || templateVal === 'minimal') {
// 'default' and 'minimal' are valid templates. Return them as it is
return templateVal;
}

// when `--template abelljs/abell-starter-portfolio`
if (!templateVal.startsWith('https://github.com/')) {
// If template value is `abelljs/abell-starter-portfolio`, add https://github.com before it.
return 'https://github.com/' + templateVal;
}

// when `--template https://github.com/abelljs/abell-starter-portfolio`
return templateVal;
};

export const scaffoldTemplate = async ({
projectPath,
template
}: {
projectPath: string;
template: string;
}): Promise<void> => {
if (template === 'default' || template === 'minimal') {
// copy default template from templates directory
const templatesDir = path.join(__dirname, '..', 'templates');
const templatePath = path.join(templatesDir, template);
copyFolderSync(templatePath, projectPath, [
path.join(templatePath, 'node_modules'),
path.join(templatePath, 'yarn.lock'),
path.join(templatePath, 'dist')
]);
} else {
// Execute git clone
try {
const errorCode = await run(`git clone ${template} ${projectPath}`);
if (errorCode === 1) {
throw new Error(log.failure('Git clone failed', false));
}
} catch (err) {
throw err;
}
}
};

export const setNameInPackageJSON = (
packagePath: string,
appName: string
): void => {
try {
// eslint-disable-next-line @typescript-eslint/no-var-requires
const packageJSON = require(normalizePath(packagePath));
packageJSON.name = appName;
fs.writeFileSync(packagePath, JSON.stringify(packageJSON, null, 2));
} catch (err) {
// Do nothing. Skip the step if error.
}
};
Loading

0 comments on commit cfe8f93

Please sign in to comment.