Skip to content

Commit

Permalink
merge commit
Browse files Browse the repository at this point in the history
  • Loading branch information
KarolinaKopacz committed Oct 15, 2024
1 parent 001d5a9 commit 07c1e6e
Show file tree
Hide file tree
Showing 17 changed files with 2,491 additions and 70 deletions.
1 change: 0 additions & 1 deletion .github/workflows/prettier.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
name: Prettier Check on Merge

on:
pull_request:
push:
branches:
- main
Expand Down
11 changes: 9 additions & 2 deletions packages/cli/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import { createProject } from '@create-stapler-app/core';
import chalk from 'chalk';
import { Command } from 'commander';
import gradient from 'gradient-string';
import inquirer from 'inquirer';

const asciiArt = `
Expand All @@ -17,8 +18,14 @@ const asciiArt = `
`;

function displayHeader() {
console.log(chalk.hex('#3100F5').bold(asciiArt));
console.log(chalk.bold('\n๐Ÿธ Welcome to Stapler!\n'));
const metalGradient = gradient([
{ color: '#4F4F4F', pos: 0 },
{ color: '#B0B0B0', pos: 0.5 },
{ color: '#4F4F4F', pos: 1 },
]);

console.log(metalGradient(asciiArt));
console.log(chalk.bold('\n๐Ÿ–‡๏ธ Welcome to Stapler!\n'));
}

const program = new Command();
Expand Down
1 change: 1 addition & 0 deletions packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"@create-stapler-app/core": "workspace:*",
"chalk": "^5.3.0",
"commander": "^12.1.0",
"gradient-string": "^3.0.0",
"inquirer": "^10.2.2"
},
"devDependencies": {
Expand Down
10 changes: 7 additions & 3 deletions packages/core/index.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { execSync } from 'child_process';
import { prepareDrink } from './utils/bar/prepareDrink';
import { createEnvFile } from './utils/env/createEnvFile';
import { initializeRepository } from './utils/github/install';
import { preparePayload } from './utils/payload/install';
import { prettify } from './utils/prettier/prettify';
import { continueOnAnyKeypress } from './utils/shared/continueOnKeypress';
import { installSupabase } from './utils/supabase/install';

interface ProjectOptions {
Expand All @@ -14,9 +14,8 @@ interface ProjectOptions {

export async function createProject(options: ProjectOptions) {
const { name, usePayload } = options;
await continueOnAnyKeypress('๐Ÿธ When you are ready to be redirected to the supabase page press Enter');

console.log(`๐Ÿธ Stapling ${name}...`);
console.log(`๐Ÿ–‡๏ธ Stapling ${name}...`);
execSync(`npx create-turbo@latest ${name} -m pnpm`, {
stdio: 'inherit',
});
Expand All @@ -33,5 +32,10 @@ export async function createProject(options: ProjectOptions) {

await prettify();

initializeRepository({
projectName: name,
visibility: 'private',
});

prepareDrink(name);
}
2 changes: 1 addition & 1 deletion packages/core/utils/env/createEnvFile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ const requiredEnvVariables: Record<string, 'required' | 'optional'> = {

// Function to create .env file with empty fields
export const createEnvFile = (destinationDirectory: string) => {
console.log('๐Ÿธ Creating .env file...');
console.log('๐Ÿ–‡๏ธ Creating .env file...');
let envTemplate = '';
for (const [key, status] of Object.entries(requiredEnvVariables)) {
envTemplate += `${key}=\n`;
Expand Down
68 changes: 68 additions & 0 deletions packages/core/utils/github/ghInstaller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { execSync } from 'child_process';
import * as os from 'os';

export function isGitHubCLIInstalled(): boolean {
try {
execSync('gh --version', { stdio: 'ignore' });
return true;
} catch (error) {
return false;
}
}

export function installGitHubCLI(): boolean {
const platform = os.platform();
let installCommand: string;

switch (platform) {
case 'darwin': // macOS
installCommand = 'brew install gh';
break;
case 'linux': // Linux
const linuxDistro = getLinuxDistro();
if (linuxDistro === 'ubuntu' || linuxDistro === 'debian') {
installCommand =
'sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-key C99B11DEB97541F0 && sudo apt-add-repository https://cli.github.com/packages && sudo apt update && sudo apt install gh';
} else if (linuxDistro === 'fedora' || linuxDistro === 'centos' || linuxDistro === 'rhel') {
installCommand =
'sudo dnf config-manager --add-repo https://cli.github.com/packages/rpm/gh-cli.repo && sudo dnf install gh';
} else {
console.log('๐Ÿ–‡๏ธ Automatic installation is not supported for your Linux distribution.');
console.log('๐Ÿ–‡๏ธ Please visit https://github.com/cli/cli#installation for installation instructions.');
return false;
}
break;
case 'win32': // Windows
installCommand = 'winget install --id GitHub.cli';
break;
default:
console.log('๐Ÿ–‡๏ธ Automatic installation is not supported for your operating system.');
console.log('๐Ÿ–‡๏ธ Please visit https://github.com/cli/cli#installation for installation instructions.');
return false;
}

console.log('๐Ÿ–‡๏ธ Installing GitHub CLI...');
try {
execSync(installCommand, { stdio: 'inherit' });
console.log('๐Ÿ–‡๏ธ GitHub CLI installed successfully.');
return true;
} catch (error) {
console.error('๐Ÿ–‡๏ธ Failed to install GitHub CLI.');
console.log('๐Ÿ–‡๏ธ Please install it manually from: https://github.com/cli/cli#installation');
return false;
}
}

export function getLinuxDistro(): string {
try {
const osRelease = execSync('cat /etc/os-release').toString();
if (osRelease.includes('Ubuntu')) return 'ubuntu';
if (osRelease.includes('Debian')) return 'debian';
if (osRelease.includes('Fedora')) return 'fedora';
if (osRelease.includes('CentOS')) return 'centos';
if (osRelease.includes('Red Hat')) return 'rhel';
return 'unknown';
} catch (error) {
return 'unknown';
}
}
65 changes: 65 additions & 0 deletions packages/core/utils/github/install.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import {
isGitHubAuthenticated,
createGitHubRepository,
fetchGitHubUsername,
authenticateGitHub,
setupGitRepository,
} from './repositoryManager';
import { installGitHubCLI, isGitHubCLIInstalled } from './ghInstaller';

interface ProjectOptions {
projectName: string;
visibility: 'public' | 'private';
}

// Helper function to check if GitHub CLI is installed
function checkGitHubCLI() {
console.log('๐Ÿ–‡๏ธ Checking GitHub CLI installation...');
if (!isGitHubCLIInstalled()) {
console.log('๐Ÿ–‡๏ธ GitHub CLI is not installed.');
const installed = installGitHubCLI();
if (!installed) {
console.error('๐Ÿ–‡๏ธ GitHub CLI installation failed. Exiting...');
process.exit(1);
}
}
}

// Helper function to ensure GitHub authentication
function ensureGitHubAuthentication() {
console.log('๐Ÿ–‡๏ธ Checking GitHub authentication...');

// Check if the user is already authenticated
if (isGitHubAuthenticated()) {
console.log('๐Ÿ–‡๏ธ You are already logged in to GitHub.');
return; // Exit early if authenticated
}

if (!authenticateGitHub()) {
console.error('๐Ÿ–‡๏ธ Authentication failed. Run "gh auth login" in your terminal and try again.');
process.exit(1);
}
}

export async function initializeRepository(options: ProjectOptions) {
const { projectName, visibility } = options;

checkGitHubCLI();
ensureGitHubAuthentication();

// Retrieve GitHub username once
const username = await fetchGitHubUsername();
if (!username) {
console.error('๐Ÿ–‡๏ธ Failed to retrieve GitHub username. Aborting repository creation.');
process.exit(1);
}

// Check if the repository exists and create it
const repoCreated = await createGitHubRepository(projectName, visibility, username);
if (!repoCreated) {
console.error('๐Ÿ–‡๏ธ Failed to create GitHub repository. Check your permissions or if the repository already exists.');
process.exit(1);
}

await setupGitRepository(projectName, username);
}
128 changes: 128 additions & 0 deletions packages/core/utils/github/repositoryManager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
import { execSync } from 'child_process';

async function executeCommand(command: string, silent = false): Promise<string | null> {
try {
const result = execSync(command, { stdio: 'pipe' }); // Use 'pipe' to capture output
const output = result.toString().trim(); // Convert buffer to string
return output || null; // Ensure we return null if output is empty
} catch (error) {
console.error(`Error executing command: ${command}`);
if (error instanceof Error) {
console.error(`Error message: ${error.message}`);
if (error) {
console.error(`Command stdout: ${error.toString()}`); // Log stdout
}
}
return null;
}
}

export function isGitHubAuthenticated(): boolean {
console.log('๐Ÿ–‡๏ธ Checking GitHub authentication status...');

try {
// Use execSync to run the command and capture output
const result = execSync('gh auth status', { stdio: 'pipe' }).toString();

// Check if the output includes "Logged in" - this is to be changed in the future but couldn't find a better way
return result.includes('Logged in');
} catch (error) {
console.error(`๐Ÿ–‡๏ธ Error checking authentication status: ${error}`);
return false;
}
}

export async function authenticateGitHub(): Promise<boolean> {
console.log('๐Ÿ–‡๏ธ Attempting to authenticate with GitHub...');

const result = await executeCommand('gh auth login');

if (result) {
// Immediately check authentication status after login attempt
const isAuthenticated = isGitHubAuthenticated();

if (isAuthenticated) {
console.log('๐Ÿ–‡๏ธ Authentication was successful.');
return true;
} else {
console.error('๐Ÿ–‡๏ธ Authentication failed after login attempt.');
return false;
}
}

console.error('๐Ÿ–‡๏ธ No output from gh auth login command.');
return false;
}

export async function fetchGitHubUsername(): Promise<string | null> {
try {
// Run the command without --jq first to inspect raw output
const username = await executeCommand('echo "$(gh api user --jq .login)"');

if (username) {
console.log(`๐Ÿ–‡๏ธ Hello \x1b[36m${username}\x1b[0m!`);
return username;
} else {
console.log('๐Ÿ–‡๏ธ No username returned or an error occurred.');
return null;
}
} catch (error) {
console.error('๐Ÿ–‡๏ธ Error fetching GitHub username:', error);
return null;
}
}

export async function createGitHubRepository(
projectName: string,
repositoryVisibility: 'public' | 'private',
username: string,
): Promise<boolean> {
console.log(`๐Ÿ–‡๏ธ Checking if repository already exists...`);

// Check if the repository exists
const repoCheckCommand = `echo "$(gh repo view ${username}/${projectName} --json name)"`;
const existingRepo = await executeCommand(repoCheckCommand, true); // Silent mode to suppress output

if (existingRepo) {
console.error(`๐Ÿ–‡๏ธ Repository "${projectName}" already exists.`);
return false; // Return false to indicate the repo was not created
}

console.log(`๐Ÿ–‡๏ธ Creating GitHub repository: \x1b[36m${projectName}\x1b[0m`);

const visibility = repositoryVisibility === 'public' ? '--public' : '--private';
const command = `gh repo create ${projectName} ${visibility} --confirm`;

const result = await executeCommand(command);

if (result) {
console.log(`๐Ÿ–‡๏ธ Repository created successfully at \x1b[36m${result}\x1b[0m`);
return true; // Return true to indicate success
}

console.error('๐Ÿ–‡๏ธ Failed to create GitHub repository.');
return false; // Return false on failure
}

// New function to set up the local Git repository
export async function setupGitRepository(projectName: string, username: string) {
console.log(`๐Ÿ–‡๏ธ Setting up Git for the repository...`);

// Set the remote origin and push to GitHub
const commands = [
`git init`,
`git add .`,
`git commit -m "feat: initial commit"`,
`git branch -M main`,
`git remote add origin [email protected]:${username}/${projectName}.git`,
`git push -u origin main`,
];

for (const cmd of commands) {
const result = executeCommand(cmd);
if (!result) {
console.error(`๐Ÿ–‡๏ธ Failed to execute command: ${cmd}`);
process.exit(1);
}
}
}
8 changes: 4 additions & 4 deletions packages/core/utils/payload/install.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,23 @@ import { updatePackages } from './updatePackages';
import { preparePayloadConfig } from './preparePayloadConfig';

export const preparePayload = async () => {
console.log('๐Ÿธ Initializing Payload...');
console.log('๐Ÿ–‡๏ธ Initializing Payload...');

process.chdir('./apps/web/');

prepareTsConfig();

updatePackages();

console.log('๐Ÿธ Moving files to (app) directory...');
console.log('๐Ÿ–‡๏ธ Moving files to (app) directory...');
execSync(
`mkdir -p ./app/\\(app\\) && find ./app -maxdepth 1 ! -path './app' ! -path './app/\\(app\\)' -exec mv {} ./app/\\(app\\)/ \\;`,
{
stdio: 'inherit',
},
);

console.log('๐Ÿธ Installing Payload to Next.js...');
console.log('๐Ÿ–‡๏ธ Installing Payload to Next.js...');
execSync(`npx create-payload-app@beta`, { stdio: 'inherit' });

// Payload doesn't work with Turbopack yet
Expand All @@ -32,7 +32,7 @@ export const preparePayload = async () => {
// Check if the payload configuration file exists
const payloadConfigPath = join(process.cwd(), 'payload.config.ts');
if (!existsSync(payloadConfigPath)) {
console.error('๐Ÿธ Payload installation cancelled/failed.');
console.error('๐Ÿ–‡๏ธ Payload installation cancelled/failed.');
} else {
await preparePayloadConfig(payloadConfigPath);
}
Expand Down
4 changes: 2 additions & 2 deletions packages/core/utils/payload/preparePayloadConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type { PathLike } from 'fs';
import fs from 'fs/promises';

export const preparePayloadConfig = async (configPath: PathLike) => {
console.log('๐Ÿธ Preparing payload.config.ts...');
console.log('๐Ÿ–‡๏ธ Preparing payload.config.ts...');

try {
// Read the payload.config.ts file
Expand All @@ -20,6 +20,6 @@ export const preparePayloadConfig = async (configPath: PathLike) => {
// Write the updated payload.config.ts back to the file
await fs.writeFile(configPath, updatedConfig);
} catch (err) {
console.error('๐Ÿธ Error during processing payload.config.ts', err);
console.error('๐Ÿ–‡๏ธ Error during processing payload.config.ts', err);
}
};
Loading

0 comments on commit 07c1e6e

Please sign in to comment.