Skip to content

Commit

Permalink
CLI: add nogit and noinstall option
Browse files Browse the repository at this point in the history
  • Loading branch information
= committed Feb 6, 2024
1 parent 72d6aa6 commit 94912a3
Show file tree
Hide file tree
Showing 7 changed files with 209 additions and 16 deletions.
33 changes: 24 additions & 9 deletions packages/create-opub-app/create-app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import path from 'path';
import retry from 'async-retry';
import fse from 'fs-extra';

import { initializeGit } from './utils/git';
import { install } from './utils/install';
import { isFolderEmpty } from './utils/is-folder-empty';
import { isWriteable } from './utils/is-writeable';
Expand All @@ -22,18 +23,21 @@ function isErrorLike(err: unknown): err is { message: string } {

export async function createApp({
example,
projectPath,
noInstall,
noGit,
packageManager,
projectPath,
}: {
example: string;
projectPath: string;
noInstall: boolean;
noGit: boolean;
packageManager: string;
projectPath: string;
}) {
console.log();
logger.info(
`Using ${packageManager} as package manager. ${packageManager !== 'npm' ? 'Do make sure you have it installed locally.' : ''}`
);

console.log();

const root = path.resolve(projectPath);
Expand Down Expand Up @@ -83,20 +87,31 @@ export async function createApp({
);
console.log();

// install all packages in the project
await install(packageManager);
console.log();

// update the project name in package json
// add changes to package.json
const pkgJson = fse.readJSONSync(packageJsonPath);
// update the project name in package json
pkgJson.name = appName;
// add opub-ui as a dependency
pkgJson.dependencies['opub-ui'] = 'latest';
fse.writeJSONSync(packageJsonPath, pkgJson, {
spaces: 2,
});

// install all packages in the project
if (!noInstall) {
await install(packageManager);
console.log();
}

if (!noGit) {
await initializeGit(root);
}

console.log();
console.log(
`${colors.info('Success!')} Created ${appName} at ${projectPath}`
`${colors.success('Success!')} Created ${colors.info(appName)} at ${root}`
);
console.log();

nextSteps({
appName,
Expand Down
6 changes: 6 additions & 0 deletions packages/create-opub-app/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@ async function run(): Promise<void> {
);
process.exit(1);
}
const noGit = initOptions.noGit;
const noInstall = initOptions.noInstall;

// UI only
renderTitle();
Expand All @@ -78,6 +80,8 @@ async function run(): Promise<void> {
projectPath,
example,
manager,
noGit,
noInstall,
});

const resolvedProjectPath = path.resolve(promptOptions.projectPath);
Expand All @@ -100,6 +104,8 @@ async function run(): Promise<void> {
example: examples[promptOptions.example].link,
projectPath: promptOptions.projectPath,
packageManager: promptOptions.manager,
noGit: promptOptions.noGit,
noInstall: promptOptions.noInstall,
});
}

Expand Down
136 changes: 136 additions & 0 deletions packages/create-opub-app/utils/git.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
import { execSync } from 'child_process';
import path from 'path';
import * as p from '@clack/prompts';
import chalk from 'chalk';
import { execa } from 'execa';
import fs from 'fs-extra';
import ora from 'ora';

import { logger } from './logger';

const isGitInstalled = (dir: string): boolean => {
try {
execSync('git --version', { cwd: dir });
return true;
} catch (_e) {
return false;
}
};

/** @returns Whether or not the provided directory has a `.git` subdirectory in it. */
export const isRootGitRepo = (dir: string): boolean => {
return fs.existsSync(path.join(dir, '.git'));
};

/** @returns Whether or not this directory or a parent directory has a `.git` directory. */
export const isInsideGitRepo = async (dir: string): Promise<boolean> => {
try {
// If this command succeeds, we're inside a git repo
await execa('git', ['rev-parse', '--is-inside-work-tree'], {
cwd: dir,
stdout: 'ignore',
});
return true;
} catch (_e) {
// Else, it will throw a git-error and we return false
return false;
}
};

const getGitVersion = () => {
const stdout = execSync('git --version').toString().trim();
const gitVersionTag = stdout.split(' ')[2];
const major = gitVersionTag?.split('.')[0];
const minor = gitVersionTag?.split('.')[1];
return { major: Number(major), minor: Number(minor) };
};

/** @returns The git config value of "init.defaultBranch". If it is not set, returns "main". */
const getDefaultBranch = () => {
const stdout = execSync('git config --global init.defaultBranch || echo main')
.toString()
.trim();

return stdout;
};

// This initializes the Git-repository for the project
export const initializeGit = async (projectDir: string) => {
logger.info('Initializing Git...');

if (!isGitInstalled(projectDir)) {
logger.warn('Git is not installed. Skipping Git initialization.');
return;
}

const spinner = ora('Creating a new git repo...\n').start();

const isRoot = isRootGitRepo(projectDir);
const isInside = await isInsideGitRepo(projectDir);
const dirName = path.parse(projectDir).name; // skip full path for logging

if (isInside && isRoot) {
// Dir is a root git repo
spinner.stop();
const overwriteGit = await p.confirm({
message: `${chalk.redBright.bold(
'Warning:'
)} Git is already initialized in "${dirName}". Initializing a new git repository would delete the previous history. Would you like to continue anyways?`,
initialValue: false,
});

if (!overwriteGit) {
spinner.info('Skipping Git initialization.');
return;
}
// Deleting the .git folder
fs.removeSync(path.join(projectDir, '.git'));
} else if (isInside && !isRoot) {
// Dir is inside a git worktree
spinner.stop();
const initializeChildGitRepo = await p.confirm({
message: `${chalk.redBright.bold(
'Warning:'
)} "${dirName}" is already in a git worktree. Would you still like to initialize a new git repository in this directory?`,
initialValue: false,
});
if (!initializeChildGitRepo) {
spinner.info('Skipping Git initialization.');
return;
}
}

// We're good to go, initializing the git repo
try {
const branchName = getDefaultBranch();

// --initial-branch flag was added in git v2.28.0
const { major, minor } = getGitVersion();
if (major < 2 || (major == 2 && minor < 28)) {
await execa('git', ['init'], { cwd: projectDir });
// symbolic-ref is used here due to refs/heads/master not existing
// It is only created after the first commit
// https://superuser.com/a/1419674
await execa('git', ['symbolic-ref', 'HEAD', `refs/heads/${branchName}`], {
cwd: projectDir,
});
} else {
await execa('git', ['init', `--initial-branch=${branchName}`], {
cwd: projectDir,
});
}
await execa('git', ['add', '.'], { cwd: projectDir });
spinner.succeed(
`${chalk.green('Successfully initialized and staged')} ${chalk.green.bold(
'git'
)}\n`
);
} catch (error) {
// Safeguard, should be unreachable
spinner.fail(
`${chalk.bold.red(
'Failed:'
)} could not initialize git. Update git to the latest version!\n`
);
}
};
13 changes: 13 additions & 0 deletions packages/create-opub-app/utils/initCli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,27 @@ export function initCli(packageJson: {
and ${colors.success('data-exchange')}
`
)
.option(
'--noGit',
'Explicitly tell the CLI to not initialize a new git repo in the project',
false
)
.option(
'--noInstall',
"Explicitly tell the CLI to not run the package manager's install command",
false
)
.allowUnknownOption();
program.parse();

const options: {
manager: string | boolean;
example: boolean | string;
path: string;
noGit: boolean;
noInstall: boolean;
} = program.opts();

options.path = projectPath;

return options;
Expand Down
8 changes: 4 additions & 4 deletions packages/create-opub-app/utils/logger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,15 @@ export const logger = {

export const colors = {
error(args: string | number | null | undefined) {
red(args);
return red(args);
},
warn(args: string | number | null | undefined) {
yellow(args);
return yellow(args);
},
info(args: string | number | null | undefined) {
cyan(args);
return cyan(args);
},
success(args: string | number | null | undefined) {
green(args);
return green(args);
},
};
2 changes: 1 addition & 1 deletion packages/create-opub-app/utils/next-steps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export function nextSteps({
console.log();

console.log(colors.info(' cd'), cdpath);
logger.info(`${packageManager} ${useNpm ? 'run ' : ''}dev`);
logger.info(` ${packageManager} ${useNpm ? 'run ' : ''}dev`);

console.log();
}
27 changes: 25 additions & 2 deletions packages/create-opub-app/utils/prompts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,14 @@ export async function prompts({
projectPath,
example,
manager,
noGit,
noInstall,
}: {
projectPath: string | null;
example: string | null;
manager: string | null;
noGit: boolean;
noInstall: boolean;
}) {
const project = await p.group(
{
Expand All @@ -36,7 +40,7 @@ export async function prompts({
},
}),
...(!manager && {
packageManager: () => {
manager: () => {
return p.select({
message: 'Which package manager will you use?',
options: [
Expand All @@ -49,6 +53,23 @@ export async function prompts({
});
},
}),
...(!noGit && {
git: () => {
return p.confirm({
message:
'Should we initialize a Git repository and stage the changes?',
initialValue: true,
});
},
}),
...(!noInstall && {
install: () => {
return p.confirm({
message: `Should we install the dependencies for you?`,
initialValue: true,
});
},
}),
},
{
onCancel() {
Expand All @@ -60,7 +81,9 @@ export async function prompts({
const options = {
projectPath: projectPath || (project.projectPath as string),
example: example || (project.example as string),
manager: manager || (project.packageManager as string),
manager: manager || (project.manager as string),
noGit: noGit || !project.git,
noInstall: noInstall || !project.install,
};

return options;
Expand Down

0 comments on commit 94912a3

Please sign in to comment.