Skip to content

Commit

Permalink
feat: replace vercel flow with rest calls (#28)
Browse files Browse the repository at this point in the history
  • Loading branch information
maneike authored Nov 13, 2024
1 parent 7507059 commit b306dc1
Show file tree
Hide file tree
Showing 7 changed files with 201 additions and 59 deletions.
47 changes: 37 additions & 10 deletions packages/core/installMachine/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ import { createSupabaseProject } from './installSteps/supabase/createProject';
import { installSupabase } from './installSteps/supabase/install';
import { createTurboRepo } from './installSteps/turbo/create';
import { deployVercelProject } from './installSteps/vercel/deploy';
import { setupAndCreateVercelProject } from './installSteps/vercel/setupAndCreate';
import { linkVercelProject } from './installSteps/vercel/link';
import { updateVercelProjectSettings } from './installSteps/vercel/updateProjectSettings';
import { prepareDrink } from './installSteps/bar/prepareDrink';
import { createDocFiles } from './installSteps/docs/create';
import { pushToGitHub } from './installSteps/github/repositoryManager';
Expand Down Expand Up @@ -159,26 +160,40 @@ const createInstallMachine = (initialContext: InstallMachineContext) => {
always: [
{
guard: isStepCompleted('createSupabaseProject'),
target: 'setupAndCreateVercelProject',
target: 'linkVercelProject',
},
],
invoke: {
input: ({ context }) => context,
src: 'createSupabaseProjectActor',
onDone: 'setupAndCreateVercelProject',
onDone: 'linkVercelProject',
onError: 'failed',
},
},
setupAndCreateVercelProject: {
linkVercelProject: {
always: [
{
guard: isStepCompleted('setupAndCreateVercelProject'),
guard: isStepCompleted('linkVercelProject'),
target: 'updateVercelProjectSettings',
},
],
invoke: {
input: ({ context }) => context,
src: 'linkVercelProjectActor',
onDone: 'updateVercelProjectSettings',
onError: 'failed',
},
},
updateVercelProjectSettings: {
always: [
{
guard: isStepCompleted('updateVercelProjectSettings'),
target: 'connectSupabaseProject',
},
],
invoke: {
input: ({ context }) => context,
src: 'setupAndCreateVercelProjectActor',
src: 'updateVercelProjectSettingsActor',
onDone: 'connectSupabaseProject',
onError: 'failed',
},
Expand Down Expand Up @@ -343,14 +358,26 @@ const createInstallMachine = (initialContext: InstallMachineContext) => {
}
}),
),
setupAndCreateVercelProjectActor: createStepMachine(
linkVercelProjectActor: createStepMachine(
fromPromise<void, InstallMachineContext, AnyEventObject>(async ({ input }) => {
try {
await linkVercelProject();
input.stateData.stepsCompleted.linkVercelProject = true;
saveStateToRcFile(input.stateData, input.projectDir);
} catch (error) {
console.error('Error in linkVercelProjectActor:', error);
throw error;
}
}),
),
updateVercelProjectSettingsActor: createStepMachine(
fromPromise<void, InstallMachineContext, AnyEventObject>(async ({ input }) => {
try {
await setupAndCreateVercelProject();
input.stateData.stepsCompleted.setupAndCreateVercelProject = true;
await updateVercelProjectSettings();
input.stateData.stepsCompleted.updateVercelProjectSettings = true;
saveStateToRcFile(input.stateData, input.projectDir);
} catch (error) {
console.error('Error in setupAndCreateVercelProjectActor:', error);
console.error('Error in updateVercelProjectSettingsActor:', error);
throw error;
}
}),
Expand Down
48 changes: 48 additions & 0 deletions packages/core/installMachine/installSteps/vercel/link.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { execSync } from 'child_process';
import chalk from 'chalk';
import { logWithColoredPrefix } from '../../../utils/logWithColoredPrefix';

const getUsername = (): string | null => {
try {
const user = execSync('npx vercel whoami', { stdio: 'pipe', encoding: 'utf-8' }).trim();
return user || null;
} catch {
return null;
}
};

const loginIfNeeded = () => {
logWithColoredPrefix('vercel', 'Logging in...');
try {
execSync('npx vercel login', { stdio: 'inherit' });
} catch (error) {
logWithColoredPrefix('vercel', [
'Oops! Something went wrong while logging in...',
'\nYou might already be logged in with this email in another project.',
'\nIn this case, select "Continue with Email" and enter the email you\'re already logged in with.\n',
]);
try {
execSync('npx vercel login', { stdio: 'inherit' });
} catch {
logWithColoredPrefix('vercel', [
'Please check the error above and try again.',
'\nAfter successfully logging in with "vercel login", please run create-stapler-app again.\n',
]);
process.exit(1);
}
}
};

export const linkVercelProject = async () => {
let vercelUserName = getUsername();

if (!vercelUserName) {
loginIfNeeded();
vercelUserName = getUsername(); // Retry getting username after login
}

logWithColoredPrefix('vercel', `You are logged in as ${chalk.cyan(vercelUserName)}`);

logWithColoredPrefix('vercel', 'Linking project...');
execSync('npx vercel link --yes', { stdio: 'ignore' });
};
47 changes: 0 additions & 47 deletions packages/core/installMachine/installSteps/vercel/setupAndCreate.ts

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import fs from 'fs/promises';
import path from 'path';
import { getGlobalPathConfig } from './utils/getGlobalPathConfig';
import { logWithColoredPrefix } from '../../../utils/logWithColoredPrefix';

const getTokenFromAuthFile = async (filePath: string): Promise<string | null> => {
try {
const data = await fs.readFile(filePath, 'utf-8');
const jsonData = JSON.parse(data);
return jsonData.token || null;
} catch (error) {
console.error('Failed to read or parse auth.json:', `\n${error}`);
process.exit(1);
}
};

const getProjectIdFromVercelConfig = async (): Promise<string | null> => {
const data = await fs.readFile('.vercel/project.json', 'utf-8');
try {
const jsonData = JSON.parse(data);
return jsonData.projectId;
} catch (error) {
console.error('Failed to read or parse vercel.json:', `\n${error}`);
process.exit(1);
}
};

export const updateVercelProjectSettings = async () => {
logWithColoredPrefix('vercel', 'Changing project settings...');
const globalPath = await getGlobalPathConfig();
if (!globalPath) {
console.error('Global path not found. Cannot update project properties.');
process.exit(1);
}
const filePath = path.join(globalPath, 'auth.json');

const token = await getTokenFromAuthFile(filePath);
if (!token) {
console.error('Token not found. Cannot update project properties.');
process.exit(1);
}

const projectId = await getProjectIdFromVercelConfig();

const response = await fetch(`https://api.vercel.com/v9/projects/${projectId}`, {
headers: {
Authorization: `Bearer ${token}`,
},
body: JSON.stringify({
framework: 'nextjs',
rootDirectory: 'apps/web',
}),
method: 'PATCH',
});

if (!response.ok) {
throw new Error(`Failed to update project properties: ${response.statusText}`);
}
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { homedir } from 'os';
import fs from 'fs';
import path from 'path';

const getXDGPaths = (appName: string) => {
const homeDir = homedir();

if (process.platform === 'win32') {
// Windows paths, typically within %AppData%
return {
dataDirs: [path.join(process.env.APPDATA || path.join(homeDir, 'AppData', 'Roaming'), appName)],
configDirs: [path.join(process.env.APPDATA || path.join(homeDir, 'AppData', 'Roaming'), appName)],
cacheDir: path.join(process.env.LOCALAPPDATA || path.join(homeDir, 'AppData', 'Local'), appName, 'Cache'),
};
} else if (process.platform === 'darwin') {
// macOS paths, typically in ~/Library/Application Support
return {
dataDirs: [path.join(homeDir, 'Library', 'Application Support', appName)],
configDirs: [path.join(homeDir, 'Library', 'Application Support', appName)],
cacheDir: path.join(homeDir, 'Library', 'Caches', appName),
};
} else {
// Linux/Unix paths, following the XDG Base Directory Specification
return {
dataDirs: [process.env.XDG_DATA_HOME || path.join(homeDir, '.local', 'share', appName)],
configDirs: [process.env.XDG_CONFIG_HOME || path.join(homeDir, '.config', appName)],
cacheDir: process.env.XDG_CACHE_HOME || path.join(homeDir, '.cache', appName),
};
}
};

// Returns whether a directory exists
const isDirectory = (path: string): boolean => {
try {
return fs.lstatSync(path).isDirectory();
} catch (_) {
// We don't care which kind of error occured, it isn't a directory anyway.
return false;
}
};

// Returns in which directory the config should be present
export const getGlobalPathConfig = async (): Promise<string> => {
const vercelDirectories = getXDGPaths('com.vercel.cli').dataDirs;

const possibleConfigPaths = [
...vercelDirectories, // latest vercel directory
path.join(homedir(), '.now'), // legacy config in user's home directory
...getXDGPaths('now').dataDirs, // legacy XDG directory
];

return possibleConfigPaths.find((configPath) => isDirectory(configPath)) || vercelDirectories[0];
};
3 changes: 2 additions & 1 deletion packages/core/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ export interface StepsCompleted {
initializeRepository: boolean;
pushToGitHub: boolean;
createSupabaseProject: boolean;
setupAndCreateVercelProject: boolean;
linkVercelProject: boolean;
updateVercelProjectSettings: boolean;
connectSupabaseProject: boolean;
deployVercelProject: boolean;
}
Expand Down
3 changes: 2 additions & 1 deletion packages/core/utils/rcFileManager/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ export const initializeRcFile = (projectDir: string, name: string, usePayload: b
prettifyCode: false,
createDocFiles: false,
createSupabaseProject: false,
setupAndCreateVercelProject: false,
linkVercelProject: false,
updateVercelProjectSettings: false,
connectSupabaseProject: false,
deployVercelProject: false,
prepareDrink: false,
Expand Down

0 comments on commit b306dc1

Please sign in to comment.