Skip to content

Commit

Permalink
feat(create-guarapi-app): add create-guarapi-app package
Browse files Browse the repository at this point in the history
fix missing guarapi dev dependency eslint
fix missing package.json name and version props in examples/basic-api
fix ci workflow pnpm run test with coverage
add run script test:coverage
update pnpm-lock.yaml
  • Loading branch information
joaoneto committed Oct 22, 2023
1 parent 6fe1d19 commit 6e86bff
Show file tree
Hide file tree
Showing 27 changed files with 573 additions and 4 deletions.
5 changes: 5 additions & 0 deletions .changeset/chilled-days-flow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'guarapi': patch
---

fix missing dev dependency eslint
5 changes: 5 additions & 0 deletions .changeset/wet-ties-carry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@guarapi/create-guarapi-app': major
---

create guarapi app package
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ jobs:
run: pnpm install

- name: Test
run: pnpm test -- -- --coverage
run: pnpm run test:coverage

- name: Build
run: pnpm build
Expand Down
9 changes: 6 additions & 3 deletions examples/basic-api/package.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
{
"name": "basic-example",
"version": "0.0.0",
"private": true,
"scripts": {
"start": "npm run build && node dist/index.js",
"clean": "rimraf dist",
"dev": "nodemon",
"build": "npm run clean && swc src -d dist",
"build": "npm run clean && swc src -d dist && npm run types",
"types": "tsc --noEmit",
"lint": "eslint ./src"
},
"dependencies": {
Expand All @@ -24,7 +27,7 @@
},
"prettier": "@guarapi/eslint-config-guarapi/prettierrc.js",
"nodemonConfig": {
"ext": "*.ts",
"exec": "node -r @swc/register src/index.ts"
"ext": "*.{t,j}s",
"exec": "node -r @swc/register src/index"
}
}
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"lint": "turbo run lint --filter={./packages/*}",
"start": "turbo run start --filter={./packages/*}",
"test": "turbo run test --filter={./packages/*}",
"test:coverage": "turbo run test --filter={./packages/*} -- --coverage",
"version": "changeset version && pnpm install --no-frozen-lockfile",
"publish": "changeset publish"
},
Expand Down
3 changes: 3 additions & 0 deletions packages/create-guarapi-app/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"extends": ["@guarapi/eslint-config-guarapi"]
}
13 changes: 13 additions & 0 deletions packages/create-guarapi-app/.swcrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"module": {
"type": "commonjs"
},
"jsc": {
"parser": {
"syntax": "typescript",
"tsx": false,
"decorators": false,
"dynamicImport": false
}
}
}
39 changes: 39 additions & 0 deletions packages/create-guarapi-app/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
{
"name": "@guarapi/create-guarapi-app",
"version": "0.0.0",
"description": "Guarapi create app starter kit",
"keywords": [
"guarapi",
"create"
],
"author": "João Neto <joaopintoneto@gmail.com>",
"license": "MIT",
"homepage": "https://github.com/guarapi/guarapi#readme",
"repository": "github:guarapi/guarapi",
"prettier": "@guarapi/eslint-config-guarapi/prettierrc.js",
"files": [
"dist"
],
"bin": {
"create-guarapi-app": "./dist/index.js"
},
"scripts": {
"clean": "rimraf dist",
"build": "pnpm run clean && swc src -d dist && pnpm run types",
"lint": "eslint ./src",
"types": "tsc --noEmit false --declaration --emitDeclarationOnly"
},
"devDependencies": {
"@guarapi/eslint-config-guarapi": "^0.1.1",
"@swc/cli": "^0.1.62",
"@swc/core": "^1.3.80",
"@swc/register": "^0.1.10",
"@types/node": "^20.5.6",
"@types/node-forge": "^1.3.6",
"@types/supertest": "^2.0.13",
"eslint": "^8.51.0",
"prettier": "^3.0.2",
"rimraf": "^5.0.1",
"typescript": "^5.2.2"
}
}
7 changes: 7 additions & 0 deletions packages/create-guarapi-app/src/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import os from 'node:os';
import path from 'node:path';
import getPackageInfo from './helpers/get-package-info';

export const CACHE_PATH = path.resolve(os.tmpdir(), getPackageInfo().name);
export const EXAMPLES_REPO = 'https://github.com/guarapi/guarapi.git';
export const EXAMPLES_FOLDER = 'examples';
16 changes: 16 additions & 0 deletions packages/create-guarapi-app/src/helpers/command-exists.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { exec } from 'node:child_process';

export default async function commandExists(
command: string,
args: string[] = ['--version'],
): Promise<void> {
return new Promise((resolve, reject) => {
exec(`${command} ${args?.join(' ')}`, (error) => {
if (error) {
reject(new Error(`Command: "${command}" not installed`));
}

resolve();
});
});
}
11 changes: 11 additions & 0 deletions packages/create-guarapi-app/src/helpers/copy-files.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import path from 'node:path';
import { cp } from 'node:fs/promises';
import { ParsedArgsValuesObj } from '../types';
import { CACHE_PATH, EXAMPLES_FOLDER } from '../constants';

export default async function copyFiles(values: ParsedArgsValuesObj) {
const src = path.resolve(CACHE_PATH, EXAMPLES_FOLDER, values.example);
const dest = path.resolve(process.cwd(), values.name);

await cp(src, dest, { recursive: true });
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import path from 'node:path';
import pathExists from './path-exists';
import { ParsedArgsValuesObj } from '../types';

export default async function projectDirNotExists(values: ParsedArgsValuesObj) {
const dest = path.resolve(process.cwd(), values.name);

if (await pathExists(dest)) {
throw new Error(`The destination "${values.name}" directory already exists`);
}
}
12 changes: 12 additions & 0 deletions packages/create-guarapi-app/src/helpers/ensure-template-dir.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { CACHE_PATH, EXAMPLES_FOLDER, EXAMPLES_REPO } from '../constants';
import dirExists from './path-exists';
import { cloneFolders, pull } from './git';

export default async function ensureTemplatesDir() {
if (await dirExists(CACHE_PATH)) {
await pull();
return;
}

await cloneFolders(EXAMPLES_REPO, [EXAMPLES_FOLDER]);
}
12 changes: 12 additions & 0 deletions packages/create-guarapi-app/src/helpers/get-package-info.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import path from 'node:path';

// eslint-disable-next-line @typescript-eslint/no-var-requires
const pkg = require(path.resolve(__dirname, '../../package.json'));

export default function getPackageInfo(packagePath?: string) {
if (!packagePath) {
return pkg;
}

return require(packagePath);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import commandExists from './command-exists';

export default async function getPackageManager() {
const packages = ['pnpm', 'yarn', 'npm'];
const results = await Promise.allSettled(packages.map((command) => commandExists(command)));

return packages[results.findIndex(({ status }) => status === 'fulfilled')];
}
37 changes: 37 additions & 0 deletions packages/create-guarapi-app/src/helpers/get-values-interactive.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { createInterface } from 'node:readline/promises';
import { ArgsValuesObj, Options, ParsedArgsValuesObj } from '../types';

async function getValuesInteractive(options: Options): Promise<ParsedArgsValuesObj> {
const readLine = createInterface({
input: process.stdin,
output: process.stdout,
});

const interactiveValues: ArgsValuesObj = {};

for (const optionKey of Object.keys(options)) {
const option = options[optionKey];

if (!option.question) {
continue;
}

if (option.type === 'boolean') {
const selected = option.answer?.selected || '';
const boolValues = '(y/n)'.replace(selected, selected.toLocaleUpperCase());
const question = `${option.question} ${boolValues}: `;
const answer = (await readLine.question(question)).toLocaleLowerCase() as 'y' | 'n';
interactiveValues[optionKey] = option.answer?.[answer] || option.default;
} else {
const question = `${option.question}${option.default ? ` (default: ${option.default})` : ''}`;
const answer = await readLine.question(question);
interactiveValues[optionKey] = answer || option.default;
}
}

await readLine.close();

return interactiveValues as ParsedArgsValuesObj;
}

export default getValuesInteractive;
129 changes: 129 additions & 0 deletions packages/create-guarapi-app/src/helpers/git.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import path from 'node:path';
import { promisify } from 'node:util';
import { writeFile } from 'node:fs/promises';
import { spawn } from 'node:child_process';
import { CACHE_PATH } from '../constants';
import { CallbackErrorOrResult, ParsedArgsValuesObj } from '../types';
import pathExists from './path-exists';

const cloneMetadata = promisify(function cloneMetadata(
repo: string,
callback: CallbackErrorOrResult,
): void {
spawn('git', ['clone', '--no-checkout', '--depth=1', '--filter=tree:0', repo, CACHE_PATH]).on(
'exit',
(exit) => {
if (exit === 1) {
callback(new Error('Command git clone exited with unexpected error'), null);
return;
}

callback(null, '');
},
);
});

const configSparseCheckout = promisify(function configSparseFolders(
folders: string[],
callback: CallbackErrorOrResult,
): void {
spawn('git', ['sparse-checkout', 'set', folders.join(',')], { cwd: CACHE_PATH }).on(
'exit',
(exit) => {
if (exit === 1) {
callback(new Error('Command git sparse-checkout exited with unexpected error'), null);
return;
}

callback(null, '');
},
);
});

const checkout = promisify(function checkout(callback) {
spawn('git', ['checkout'], { cwd: CACHE_PATH }).on('exit', (exit) => {
if (exit === 1) {
callback(new Error('Command git clone exited with unexpected error'), null);
return;
}

callback(null, '');
});
});

const initProject = promisify(function initProject(
values: ParsedArgsValuesObj,
callback: CallbackErrorOrResult,
) {
const projectCwd = path.resolve(process.cwd(), values.name);

spawn('git', ['init'], { cwd: projectCwd }).on('exit', (exit) => {
if (exit === 1) {
callback(new Error('Command git init exited with unexpected error'), null);
return;
}

callback(null, '');
});
});

const addFilesProject = promisify(function addFilesProject(
values: ParsedArgsValuesObj,
callback: CallbackErrorOrResult,
) {
const projectCwd = path.resolve(process.cwd(), values.name);

spawn('git', ['add', '.'], { cwd: projectCwd }).on('exit', (exit) => {
if (exit === 1) {
callback(new Error('Command git init exited with unexpected error'), null);
return;
}

callback(null, '');
});
});

const commitProject = promisify(function commitProject(
values: ParsedArgsValuesObj,
callback: CallbackErrorOrResult,
) {
const projectCwd = path.resolve(process.cwd(), values.name);

spawn('git', ['commit', '-am', 'Initial commit'], { cwd: projectCwd }).on('exit', (exit) => {
if (exit === 1) {
callback(new Error('Command git commit exited with unexpected error'), null);
return;
}

callback(null, '');
});
});

export const pull = promisify(function pull(callback) {
spawn('git', ['pull'], { cwd: CACHE_PATH }).on('exit', (exit) => {
if (exit === 1) {
callback(new Error('Command git pull exited with unexpected error'), null);
return;
}

callback(null, '');
});
});

export async function cloneFolders(repo: string, folders: string[]) {
await cloneMetadata(repo);
await configSparseCheckout(folders);
await checkout();
}

export async function setupProjectRepo(values: ParsedArgsValuesObj) {
const projectGitIgnore = path.resolve(process.cwd(), values.name, '.gitignore');

if (!(await pathExists(projectGitIgnore))) {
await writeFile(projectGitIgnore, 'node_modules\n');
}

await initProject(values);
await addFilesProject(values);
await commitProject(values);
}
27 changes: 27 additions & 0 deletions packages/create-guarapi-app/src/helpers/install-dependencies.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import path from 'node:path';
import { promisify } from 'node:util';
import { spawn } from 'node:child_process';
import { CallbackErrorOrResult, ParsedArgsValuesObj } from '../types';

export default promisify(function installDependencies(
packageManager: string,
values: ParsedArgsValuesObj,
callback: CallbackErrorOrResult,
) {
const installDir = path.resolve(process.cwd(), values.name);

spawn(packageManager, ['install'], { cwd: installDir, shell: true, stdio: 'inherit' }).on(
'exit',
(exit) => {
if (exit === 1) {
callback(
new Error(`Command "${packageManager} install" exited with unexpected error`),
null,
);
return;
}

callback(null, '');
},
);
});
Loading

0 comments on commit 6e86bff

Please sign in to comment.