diff --git a/src/rollout-tools/demo/cli.mjs b/src/rollout-tools/demo/cli.mjs index c936f40..31c60e3 100755 --- a/src/rollout-tools/demo/cli.mjs +++ b/src/rollout-tools/demo/cli.mjs @@ -47,7 +47,6 @@ requiredEnvVars.forEach((envVar) => { } }); - async function localRollout({ inputs, secrets }) { const { EMAIL: email, @@ -60,314 +59,270 @@ async function localRollout({ inputs, secrets }) { const summary = {}; - try { - if (!(email && isValidEmail(email))) { - console.error('Email is not valid'); - return; - } - - const username = email - .split('@')[0] - .toLowerCase() - .replace(/[^a-z0-9]/g, '') // prevent forbidden symbols - .replace(/\./g, '') // prevent forbidden symbols - .slice(0, 50); // prevent project name from being too long - const finalProjectName = `${projectPrefix}-${username}-${projectName}`; + if (!(email && isValidEmail(email))) { + console.error('Email is not valid'); + return; + } - summary.username = username; - summary.email = email; - summary.finalProjectName = finalProjectName; + const username = email + .split('@')[0] + .toLowerCase() + .replace(/[^a-z0-9]/g, '') // prevent forbidden symbols + .replace(/\./g, '') // prevent forbidden symbols + .slice(0, 50); // prevent project name from being too long + const finalProjectName = `${projectPrefix}-${username}-${projectName}`; - console.log('Username: ' + username); - console.log('Email: ' + email); - console.log('Final Project Name (Sanity): ' + finalProjectName); + summary.username = username; + summary.email = email; + summary.finalProjectName = finalProjectName; - console.log('Fetching existing Vercel projects...'); - const existingProjects = await getVercelProjects(); - console.log('Fetched existing Vercel projects.'); + console.log('Username: ' + username); + console.log('Email: ' + email); + console.log('Final Project Name (Sanity): ' + finalProjectName); - const allowToCreateProject = - existingProjects && - existingProjects.length < parseInt(secrets.MAX_NUMBER_OF_PROJECTS || '5'); + console.log('Fetching existing Vercel projects...'); + const existingProjects = await getVercelProjects(); + console.log('Fetched existing Vercel projects.'); - const existingProject = existingProjects?.find( - (project) => project.name === finalProjectName, - ); + const allowToCreateProject = + existingProjects && + existingProjects.length < parseInt(secrets.MAX_NUMBER_OF_PROJECTS || '5'); - if (!allowToCreateProject) { - console.error('Limit of the projects reached'); - return; - } - if (existingProject) { - console.error('Project with this email already exists'); - return; - } - - console.log('Creating Sanity project...'); - const sanityProjectId = await createSanityProject(finalProjectName); + const existingProject = existingProjects?.find( + (project) => project.name === finalProjectName, + ); - if (!sanityProjectId) { - console.error('Failed to create Sanity project.'); - process.exit(1); - } - console.log('Sanity project created...'); - - summary.sanityProjectId = sanityProjectId; - console.log('Sanity Project ID: ' + sanityProjectId); - - console.log('Creating Sanity read token...'); - const sanityReadToken = await createSanityReadToken(sanityProjectId); - console.log('Sanity read token created.'); - - console.log('Creating Vercel project...'); - const projectData = await createVercelProject({ - sanityReadToken: sanityReadToken, - projectName: finalProjectName, - sanityProjectId, - sanityDatasetName: datasetName, - }); - - if (!projectData?.projectId) { - console.error('Failed to create Vercel project.'); - process.exit(1); - } - console.log('Vercel project created.'); - - summary.projectName = projectData.projectName; - summary.projectId = projectData.projectId; - summary.deploymentUrl = projectData.deploymentUrl; - summary.studioUrl = `${projectData.deploymentUrl}/admin`; - summary.vercelUrl = `https://vercel.com/${selectedTeam.slug}/${projectData.projectName}`; - summary.sanityUrl = `https://www.sanity.io/organizations/${selectedOrg.id}/project/${sanityProjectId}`; - summary.datasetName = datasetName; - summary.REPO_NAME = secrets.REPO_NAME; - - console.log('Vercel Project Name: ' + projectData.projectName); - console.log('Vercel Project ID: ' + projectData.projectId); - console.log('Deployment URL: ' + projectData.deploymentUrl); - console.log('Sanity Studio: ' + summary.studioUrl); - console.log('Sanity Dataset Name: ' + datasetName); - console.log('Repo Name: ' + secrets.REPO_NAME); - - console.log('Starting local flow...\n\n'); - - // Step 1: Add envs to Vercel project - console.log('Adding environment variables to Vercel project...'); - try { - await fetch( - `https://api.vercel.com/v10/projects/${projectData.projectId}/env?teamId=${secrets.VERCEL_FR_TEAM_ID}`, - { - method: 'POST', - headers: { - Authorization: `Bearer ${secrets.VERCEL_PERSONAL_AUTH_TOKEN}`, - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - key: 'VERCEL_PROJECT_ID', - value: projectData.projectId, - type: 'encrypted', - target: ['production', 'preview', 'development'], - }), - }, - ); + if (!allowToCreateProject) { + console.error('Limit of the projects reached'); + return; + } + if (existingProject) { + console.error('Project with this email already exists'); + return; + } - await fetch( - `https://api.vercel.com/v10/projects/${projectData.projectId}/env?teamId=${secrets.VERCEL_FR_TEAM_ID}`, - { - method: 'POST', - headers: { - Authorization: `Bearer ${secrets.VERCEL_PERSONAL_AUTH_TOKEN}`, - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - key: 'VERCEL_PROJECT_NAME', - value: projectData.projectName, - type: 'encrypted', - target: ['production', 'preview', 'development'], - }), - }, - ); - console.log('Environment variables added to Vercel project.'); - } catch (error) { - console.error( - 'Failed to add environment variables to Vercel project.', - error, - ); - throw error; - } + console.log('Creating Sanity project...'); + const sanityProjectId = await createSanityProject(finalProjectName); - // Step 2: Add Sanity CORS entry - console.log('Adding Sanity CORS entry...'); - try { - await fetch( - `https://api.sanity.io/v2021-06-07/projects/${sanityProjectId}/cors`, - { - method: 'POST', - headers: { - Authorization: `Bearer ${secrets.SANITY_PERSONAL_AUTH_TOKEN}`, - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - origin: projectData.deploymentUrl, - allowCredentials: true, - }), - }, - ); + if (!sanityProjectId) { + console.error('Failed to create Sanity project.'); + process.exit(1); + } + console.log('Sanity project created...'); - await fetch( - `https://api.sanity.io/v2021-06-07/projects/${sanityProjectId}/cors`, - { - method: 'POST', - headers: { - Authorization: `Bearer ${secrets.SANITY_PERSONAL_AUTH_TOKEN}`, - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - origin: 'http://localhost:3000/', - allowCredentials: true, - }), - }, - ); - console.log('Sanity CORS entry added.'); - } catch (error) { - console.error('Failed to add Sanity CORS entry.', error); - throw error; - } + summary.sanityProjectId = sanityProjectId; + console.log('Sanity Project ID: ' + sanityProjectId); - // Step 3: Invite user to Sanity project - console.log('Inviting user to Sanity project...'); - try { - await fetch( - `https://api.sanity.io/v2021-06-07/invitations/project/${sanityProjectId}`, - { - method: 'POST', - headers: { - Authorization: `Bearer ${secrets.SANITY_PERSONAL_AUTH_TOKEN}`, - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - email: email, - role: 'editor', - }), - }, - ); - console.log('User invited to Sanity project.'); - } catch (error) { - console.error('Failed to invite user to Sanity project.', error); - throw error; - } + console.log('Creating Sanity read token...'); + const sanityReadToken = await createSanityReadToken(sanityProjectId); + console.log('Sanity read token created.'); - // Step 4: Create a new Sanity dataset - console.log('Creating a new Sanity dataset...'); - try { - await fetch( - `https://api.sanity.io/v2021-06-07/projects/${sanityProjectId}/datasets/${datasetName}`, - { - method: 'PUT', - headers: { - Authorization: `Bearer ${secrets.SANITY_PERSONAL_AUTH_TOKEN}`, - }, - }, - ); - console.log('New Sanity dataset created.'); - } catch (error) { - console.error('Failed to create a new Sanity dataset.', error); - throw error; - } + console.log('Creating Vercel project...'); + const projectData = await createVercelProject({ + sanityReadToken: sanityReadToken, + projectName: finalProjectName, + sanityProjectId, + sanityDatasetName: datasetName, + }); - // Step 5: Fill the dataset with data from prod-copy.tar.gz - console.log('Filling the dataset with data from prod-copy.tar.gz...'); - try { - await new Promise((resolve, reject) => { - exec( - `SANITY_AUTH_TOKEN=${secrets.SANITY_PERSONAL_AUTH_TOKEN} npx sanity dataset import initial-data.tar.gz ${datasetName}`, - (error, stdout, stderr) => { - if (error) { - console.error(`Error importing dataset: ${stderr}`); - return reject(error); - } - console.log(`Dataset imported: ${stdout}`); - resolve(); - }, - ); - }); - } catch (error) { - console.error('Failed to fill the dataset with data.', error); - throw error; - } + if (!projectData?.projectId) { + console.error('Failed to create Vercel project.'); + process.exit(1); + } + console.log('Vercel project created.'); + + summary.projectName = projectData.projectName; + summary.projectId = projectData.projectId; + summary.deploymentUrl = projectData.deploymentUrl; + summary.studioUrl = `${projectData.deploymentUrl}/admin`; + summary.vercelUrl = `https://vercel.com/${selectedTeam.slug}/${projectData.projectName}`; + summary.sanityUrl = `https://www.sanity.io/organizations/${selectedOrg.id}/project/${sanityProjectId}`; + summary.datasetName = datasetName; + summary.REPO_NAME = secrets.REPO_NAME; + + console.log('Vercel Project Name: ' + projectData.projectName); + console.log('Vercel Project ID: ' + projectData.projectId); + console.log('Deployment URL: ' + projectData.deploymentUrl); + console.log('Sanity Studio: ' + summary.studioUrl); + console.log('Sanity Dataset Name: ' + datasetName); + console.log('Repo Name: ' + secrets.REPO_NAME); + + console.log('Starting local flow...\n\n'); + + // Step 1: Add envs to Vercel project + console.log('Adding environment variables to Vercel project...'); + await fetch( + `https://api.vercel.com/v10/projects/${projectData.projectId}/env?teamId=${secrets.VERCEL_FR_TEAM_ID}`, + { + method: 'POST', + headers: { + Authorization: `Bearer ${secrets.VERCEL_PERSONAL_AUTH_TOKEN}`, + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + key: 'VERCEL_PROJECT_ID', + value: projectData.projectId, + type: 'encrypted', + target: ['production', 'preview', 'development'], + }), + }, + ); - // Step 6: Add deploy hook(/api/roll-out/deploy) to Sanity project - console.log('Adding deploy hook to Sanity project...'); - try { - await fetch( - `https://api.sanity.io/v2021-10-04/hooks/projects/${sanityProjectId}`, - { - method: 'POST', - headers: { - Authorization: `Bearer ${secrets.SANITY_PERSONAL_AUTH_TOKEN}`, - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - type: 'document', - name: 'Sanity Studio', - url: `https://${projectData.projectName}.vercel.app/api/roll-out/deploy`, - httpMethod: 'POST', - apiVersion: 'v2021-03-25', - includeDrafts: false, - dataset: '*', - rule: { - on: ['create', 'update', 'delete'], - }, - headers: { - Authorization: `Bearer ${secrets.ROLL_OUT_API_TOKEN}`, - }, - }), - }, - ); - console.log('Deploy hook added to Sanity project.'); - } catch (error) { - console.error('Failed to add deploy hook to Sanity project.', error); - throw error; - } + await fetch( + `https://api.vercel.com/v10/projects/${projectData.projectId}/env?teamId=${secrets.VERCEL_FR_TEAM_ID}`, + { + method: 'POST', + headers: { + Authorization: `Bearer ${secrets.VERCEL_PERSONAL_AUTH_TOKEN}`, + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + key: 'VERCEL_PROJECT_NAME', + value: projectData.projectName, + type: 'encrypted', + target: ['production', 'preview', 'development'], + }), + }, + ); + console.log('Environment variables added to Vercel project.'); + + // Step 2: Add Sanity CORS entry + console.log('Adding Sanity CORS entry...'); + await fetch( + `https://api.sanity.io/v2021-06-07/projects/${sanityProjectId}/cors`, + { + method: 'POST', + headers: { + Authorization: `Bearer ${secrets.SANITY_PERSONAL_AUTH_TOKEN}`, + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + origin: projectData.deploymentUrl, + allowCredentials: true, + }), + }, + ); - // Step 7: Create a new Vercel deployment - console.log('Creating a new Vercel deployment...'); - try { - const body = { - name: projectData.projectName, - project: projectData.projectId, - target: 'production', - gitSource: { - repoId: secrets.REPO_ID, - ref: secrets.REPO_PROD_BRANCH, - type: 'github', + await fetch( + `https://api.sanity.io/v2021-06-07/projects/${sanityProjectId}/cors`, + { + method: 'POST', + headers: { + Authorization: `Bearer ${secrets.SANITY_PERSONAL_AUTH_TOKEN}`, + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + origin: 'http://localhost:3000/', + allowCredentials: true, + }), + }, + ); + console.log('Sanity CORS entry added.'); + + // Step 3: Invite user to Sanity project + console.log('Inviting user to Sanity project...'); + await fetch( + `https://api.sanity.io/v2021-06-07/invitations/project/${sanityProjectId}`, + { + method: 'POST', + headers: { + Authorization: `Bearer ${secrets.SANITY_PERSONAL_AUTH_TOKEN}`, + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + email: email, + role: 'editor', + }), + }, + ); + console.log('User invited to Sanity project.'); + + // Step 4: Create a new Sanity dataset + console.log('Creating a new Sanity dataset...'); + await fetch( + `https://api.sanity.io/v2021-06-07/projects/${sanityProjectId}/datasets/${datasetName}`, + { + method: 'PUT', + headers: { + Authorization: `Bearer ${secrets.SANITY_PERSONAL_AUTH_TOKEN}`, + }, + }, + ); + console.log('New Sanity dataset created.'); + + // Step 5: Fill the dataset with data from prod-copy.tar.gz + console.log('Filling the dataset with data from prod-copy.tar.gz...'); + await new Promise((resolve, reject) => { + exec( + `SANITY_AUTH_TOKEN=${secrets.SANITY_PERSONAL_AUTH_TOKEN} npx sanity dataset import initial-data.tar.gz ${datasetName}`, + (error, stdout, stderr) => { + if (error) { + console.error(`Error importing dataset: ${stderr}`); + return reject(error); + } + console.log(`Dataset imported: ${stdout}`); + resolve(); + }, + ); + }); + + // Step 6: Add deploy hook(/api/roll-out/deploy) to Sanity project + console.log('Adding deploy hook to Sanity project...'); + await fetch( + `https://api.sanity.io/v2021-10-04/hooks/projects/${sanityProjectId}`, + { + method: 'POST', + headers: { + Authorization: `Bearer ${secrets.SANITY_PERSONAL_AUTH_TOKEN}`, + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + type: 'document', + name: 'Sanity Studio', + url: `https://${projectData.projectName}.vercel.app/api/roll-out/deploy`, + httpMethod: 'POST', + apiVersion: 'v2021-03-25', + includeDrafts: false, + dataset: '*', + rule: { + on: ['create', 'update', 'delete'], }, - }; - const response = await fetch( - `https://api.vercel.com/v13/deployments?teamId=${secrets.VERCEL_FR_TEAM_ID}`, - { - method: 'POST', - headers: { - Authorization: `Bearer ${secrets.VERCEL_PERSONAL_AUTH_TOKEN}`, - 'Content-Type': 'application/json', - }, - body: JSON.stringify(body), + headers: { + Authorization: `Bearer ${secrets.ROLL_OUT_API_TOKEN}`, }, - ); - const data = await response.json(); - console.log('New Vercel deployment created.'); - } catch (error) { - console.error('Failed to create a new Vercel deployment.', error); - throw error; - } + }), + }, + ); + console.log('Deploy hook added to Sanity project.'); + + // Step 7: Create a new Vercel deployment + console.log('Creating a new Vercel deployment...'); + const body = { + name: projectData.projectName, + project: projectData.projectId, + target: 'production', + gitSource: { + repoId: secrets.REPO_ID, + ref: secrets.REPO_PROD_BRANCH, + type: 'github', + }, + }; + const response = await fetch( + `https://api.vercel.com/v13/deployments?teamId=${secrets.VERCEL_FR_TEAM_ID}`, + { + method: 'POST', + headers: { + Authorization: `Bearer ${secrets.VERCEL_PERSONAL_AUTH_TOKEN}`, + 'Content-Type': 'application/json', + }, + body: JSON.stringify(body), + }, + ); + const data = await response.json(); + console.log('New Vercel deployment created.'); - console.log('All steps were successful 🎉\n\n'); - return summary; - } catch (error) { - console.error('One of the steps was not successful 😿'); - console.log(JSON.stringify(summary, null, 2)); - return null; - } + console.log('All steps were successful 🎉\n\n'); + return summary; } async function main() {