From da1e098cf5c54e286a140f9c2a0ed3712c50aa09 Mon Sep 17 00:00:00 2001 From: Joe Yeager Date: Mon, 16 Dec 2024 08:57:48 -0800 Subject: [PATCH 1/3] Update uploadProject method to support IR. --- api/projects.ts | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/api/projects.ts b/api/projects.ts index ce7be00..4923cee 100644 --- a/api/projects.ts +++ b/api/projects.ts @@ -53,8 +53,27 @@ export function uploadProject( projectName: string, projectFile: string, uploadMessage: string, - platformVersion?: string + platformVersion?: string, + intermediateRepresentation?: unknown ): HubSpotPromise { + if (intermediateRepresentation) { + const formData = { + projectFilesZip: fs.createReadStream(projectFile), + uploadRequest: JSON.stringify({ + ...intermediateRepresentation, + projectName, + buildMessage: uploadMessage, + }), + }; + + return http.post(accountId, { + url: `project-components-external/v3/upload/new-api`, + timeout: 60_000, + data: formData, + headers: { 'Content-Type': 'multipart/form-data' }, + }); + } + const formData: FormData = { file: fs.createReadStream(projectFile), uploadMessage, @@ -62,7 +81,6 @@ export function uploadProject( if (platformVersion) { formData.platformVersion = platformVersion; } - return http.post(accountId, { url: `${PROJECTS_API_PATH}/upload/${encodeURIComponent(projectName)}`, timeout: 60_000, From 0c1886a91e0174e69111109ff351d9a35b2c0cb6 Mon Sep 17 00:00:00 2001 From: Joe Yeager Date: Tue, 17 Dec 2024 14:15:14 -0800 Subject: [PATCH 2/3] Get upload working, optimize walk --- api/projects.ts | 13 +++++++++++-- lib/fs.ts | 15 +++++++++++---- 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/api/projects.ts b/api/projects.ts index 4923cee..b39e1ef 100644 --- a/api/projects.ts +++ b/api/projects.ts @@ -48,7 +48,7 @@ export function createProject( }); } -export function uploadProject( +export async function uploadProject( accountId: number, projectName: string, projectFile: string, @@ -66,12 +66,21 @@ export function uploadProject( }), }; - return http.post(accountId, { + const response = await http.post<{ + buildId: number; + createdBuildId: number; + }>(accountId, { url: `project-components-external/v3/upload/new-api`, timeout: 60_000, data: formData, headers: { 'Content-Type': 'multipart/form-data' }, }); + + // Remap the response to match the expected shape + response.data.buildId = response.data.createdBuildId; + + // @ts-expect-error Fix me later + return response; } const formData: FormData = { diff --git a/lib/fs.ts b/lib/fs.ts index 56a7bc0..3291f3c 100644 --- a/lib/fs.ts +++ b/lib/fs.ts @@ -42,12 +42,13 @@ export function flattenAndRemoveSymlinks( const generateRecursiveFilePromise = async ( dir: string, - file: string + file: string, + ignoreDirs?: string[] ): Promise => { return getFileInfoAsync(dir, file).then(fileData => { return new Promise(resolve => { if (fileData.type === STAT_TYPES.DIRECTORY) { - walk(fileData.filepath).then(files => { + walk(fileData.filepath, ignoreDirs).then(files => { resolve({ ...fileData, files }); }); } else { @@ -57,10 +58,16 @@ const generateRecursiveFilePromise = async ( }); }; -export async function walk(dir: string): Promise> { +export async function walk( + dir: string, + ignoreDirs?: string[] +): Promise> { function processFiles(files: Array) { + if (ignoreDirs?.some(ignored => dir.includes(ignored))) { + return []; + } return Promise.all( - files.map(file => generateRecursiveFilePromise(dir, file)) + files.map(file => generateRecursiveFilePromise(dir, file, ignoreDirs)) ); } From 051fda51c5afc94a0db090dc53f18478ad082212 Mon Sep 17 00:00:00 2001 From: Joe Yeager Date: Wed, 18 Dec 2024 10:45:15 -0800 Subject: [PATCH 3/3] Comment and test --- api/__tests__/projects.test.ts | 47 ++++++++++++++++++++++++++++++++++ api/projects.ts | 14 +++++----- lib/fs.ts | 1 + types/Project.ts | 5 ++++ 4 files changed, 60 insertions(+), 7 deletions(-) diff --git a/api/__tests__/projects.test.ts b/api/__tests__/projects.test.ts index b9dac3d..9c2920a 100644 --- a/api/__tests__/projects.test.ts +++ b/api/__tests__/projects.test.ts @@ -36,6 +36,8 @@ const createReadStreamMock = createReadStream as jest.MockedFunction< typeof createReadStream >; +const httpPostMock = http.post as jest.MockedFunction; + describe('api/projects', () => { const accountId = 999999; const projectId = 888888; @@ -132,6 +134,51 @@ describe('api/projects', () => { headers: { 'Content-Type': 'multipart/form-data' }, }); }); + + it('should call the v3 api when optional intermediateRepresentation is provided', async () => { + // @ts-expect-error Wants full axios response + httpPostMock.mockResolvedValue({ + data: { + createdBuildId: 123, + }, + }); + + const intermediateRepresentation = { + intermediateNodesIndexedByUid: { + 'calling-1': { + componentType: 'APP', + uid: 'calling-1', + config: {}, + componentDeps: {}, + files: {}, + }, + }, + }; + + await uploadProject( + accountId, + projectName, + projectFile, + uploadMessage, + platformVersion, + intermediateRepresentation + ); + expect(http.post).toHaveBeenCalledTimes(1); + expect(http.post).toHaveBeenCalledWith(accountId, { + url: `project-components-external/v3/upload/new-api`, + timeout: 60_000, + data: { + projectFilesZip: formData, + platformVersion, + uploadRequest: JSON.stringify({ + ...intermediateRepresentation, + projectName, + buildMessage: uploadMessage, + }), + }, + headers: { 'Content-Type': 'multipart/form-data' }, + }); + }); }); describe('fetchProject', () => { diff --git a/api/projects.ts b/api/projects.ts index b39e1ef..caeee48 100644 --- a/api/projects.ts +++ b/api/projects.ts @@ -7,6 +7,7 @@ import { UploadProjectResponse, ProjectSettings, FetchPlatformVersionResponse, + UploadIRResponse, } from '../types/Project'; import { Build, FetchProjectBuildsResponse } from '../types/Build'; import { @@ -28,6 +29,8 @@ const PROJECTS_LOGS_API_PATH = 'dfs/logging/v1'; const DEVELOPER_PROJECTS_API_PATH = 'developer/projects/v1'; const MIGRATIONS_API_PATH = 'dfs/migrations/v1'; +const PROJECTS_V3_API_PATH = 'project-components-external/v3'; + export function fetchProjects( accountId: number ): HubSpotPromise { @@ -55,10 +58,11 @@ export async function uploadProject( uploadMessage: string, platformVersion?: string, intermediateRepresentation?: unknown -): HubSpotPromise { +): HubSpotPromise { if (intermediateRepresentation) { const formData = { projectFilesZip: fs.createReadStream(projectFile), + platformVersion, uploadRequest: JSON.stringify({ ...intermediateRepresentation, projectName, @@ -66,11 +70,8 @@ export async function uploadProject( }), }; - const response = await http.post<{ - buildId: number; - createdBuildId: number; - }>(accountId, { - url: `project-components-external/v3/upload/new-api`, + const response = await http.post(accountId, { + url: `${PROJECTS_V3_API_PATH}/upload/new-api`, timeout: 60_000, data: formData, headers: { 'Content-Type': 'multipart/form-data' }, @@ -79,7 +80,6 @@ export async function uploadProject( // Remap the response to match the expected shape response.data.buildId = response.data.createdBuildId; - // @ts-expect-error Fix me later return response; } diff --git a/lib/fs.ts b/lib/fs.ts index 3291f3c..c3e6ce9 100644 --- a/lib/fs.ts +++ b/lib/fs.ts @@ -63,6 +63,7 @@ export async function walk( ignoreDirs?: string[] ): Promise> { function processFiles(files: Array) { + // If the directory is in the ignore list, return an empty array to skip the directory contents if (ignoreDirs?.some(ignored => dir.includes(ignored))) { return []; } diff --git a/types/Project.ts b/types/Project.ts index e37c037..35c3e28 100644 --- a/types/Project.ts +++ b/types/Project.ts @@ -37,6 +37,11 @@ export type UploadProjectResponse = { }; }; +export type UploadIRResponse = { + buildId?: number; + createdBuildId: number; +}; + export type ProjectSettings = { isAutoDeployEnabled: boolean; };