diff --git a/packages/server/lib/cloud/api/studio/get_studio_bundle.ts b/packages/server/lib/cloud/api/studio/get_studio_bundle.ts index 6347b3f1046c..77bf1711ff30 100644 --- a/packages/server/lib/cloud/api/studio/get_studio_bundle.ts +++ b/packages/server/lib/cloud/api/studio/get_studio_bundle.ts @@ -5,12 +5,11 @@ import os from 'os' import { agent } from '@packages/network' import { PUBLIC_KEY_VERSION } from '../../constants' import { createWriteStream } from 'fs' -import { verifySignatureFromFile } from '../../encryption' const pkg = require('@packages/root') const _delay = linearDelay(500) -export const getStudioBundle = async ({ studioUrl, projectId, bundlePath }: { studioUrl: string, projectId?: string, bundlePath: string }) => { +export const getStudioBundle = async ({ studioUrl, bundlePath }: { studioUrl: string, bundlePath: string }): Promise => { let responseSignature: string | null = null await (asyncRetry(async () => { @@ -54,9 +53,5 @@ export const getStudioBundle = async ({ studioUrl, projectId, bundlePath }: { st throw new Error('Unable to get studio signature') } - const verified = await verifySignatureFromFile(bundlePath, responseSignature) - - if (!verified) { - throw new Error('Unable to verify studio signature') - } + return responseSignature } diff --git a/packages/server/lib/cloud/encryption.ts b/packages/server/lib/cloud/encryption.ts index b8fc892788be..2b762d0c6b1a 100644 --- a/packages/server/lib/cloud/encryption.ts +++ b/packages/server/lib/cloud/encryption.ts @@ -1,4 +1,4 @@ -import crypto from 'crypto' +import crypto, { BinaryLike } from 'crypto' import { TextEncoder, promisify } from 'util' import { generalDecrypt, GeneralJWE } from 'jose' import base64Url from 'base64url' @@ -37,7 +37,7 @@ export interface EncryptRequestData { secretKey: crypto.KeyObject } -export function verifySignature (body: string, signature: string, publicKey?: crypto.KeyObject) { +export function verifySignature (body: BinaryLike, signature: string, publicKey?: crypto.KeyObject) { const verify = crypto.createVerify('SHA256') verify.update(body) diff --git a/packages/server/lib/cloud/studio/StudioLifecycleManager.ts b/packages/server/lib/cloud/studio/StudioLifecycleManager.ts index 7235f6554623..391b81363aa5 100644 --- a/packages/server/lib/cloud/studio/StudioLifecycleManager.ts +++ b/packages/server/lib/cloud/studio/StudioLifecycleManager.ts @@ -22,12 +22,13 @@ import { initializeTelemetryReporter, reportTelemetry } from './telemetry/Teleme import { telemetryManager } from './telemetry/TelemetryManager' import { BUNDLE_LIFECYCLE_MARK_NAMES, BUNDLE_LIFECYCLE_TELEMETRY_GROUP_NAMES } from './telemetry/constants/bundle-lifecycle' import { INITIALIZATION_TELEMETRY_GROUP_NAMES } from './telemetry/constants/initialization' +import crypto from 'crypto' const debug = Debug('cypress:server:studio-lifecycle-manager') const routes = require('../routes') export class StudioLifecycleManager { - private static hashLoadingMap: Map> = new Map() + private static hashLoadingMap: Map>> = new Map() private static watcher: chokidar.FSWatcher | null = null private studioManagerPromise?: Promise private studioManager?: StudioManager @@ -157,6 +158,7 @@ export class StudioLifecycleManager { }): Promise { let studioPath: string let studioHash: string + let manifest: Record initializeTelemetryReporter({ projectSlug: projectId, @@ -190,10 +192,11 @@ export class StudioLifecycleManager { StudioLifecycleManager.hashLoadingMap.set(studioHash, hashLoadingPromise) } - await hashLoadingPromise + manifest = await hashLoadingPromise } else { studioPath = process.env.CYPRESS_LOCAL_STUDIO_PATH studioHash = 'local' + manifest = {} } telemetryManager.mark(BUNDLE_LIFECYCLE_MARK_NAMES.ENSURE_STUDIO_BUNDLE_END) @@ -201,6 +204,18 @@ export class StudioLifecycleManager { const serverFilePath = path.join(studioPath, 'server', 'index.js') const script = await readFile(serverFilePath, 'utf8') + + const expectedHash = manifest[path.join('server', 'index.js')] + + // TODO: once the services have deployed, we should remove this check + if (expectedHash) { + const actualHash = crypto.createHash('sha256').update(script).digest('hex') + + if (!process.env.CYPRESS_LOCAL_STUDIO_PATH && actualHash !== expectedHash) { + throw new Error('Invalid hash for studio server script') + } + } + const studioManager = new StudioManager() telemetryManager.mark(BUNDLE_LIFECYCLE_MARK_NAMES.STUDIO_MANAGER_SETUP_START) @@ -220,6 +235,7 @@ export class StudioLifecycleManager { asyncRetry, }, shouldEnableStudio: this.cloudStudioRequested, + manifest, }) telemetryManager.mark(BUNDLE_LIFECYCLE_MARK_NAMES.STUDIO_MANAGER_SETUP_END) diff --git a/packages/server/lib/cloud/studio/ensure_studio_bundle.ts b/packages/server/lib/cloud/studio/ensure_studio_bundle.ts index 5b4e09ca39b1..b11106e9e4a0 100644 --- a/packages/server/lib/cloud/studio/ensure_studio_bundle.ts +++ b/packages/server/lib/cloud/studio/ensure_studio_bundle.ts @@ -1,8 +1,9 @@ -import { remove, ensureDir } from 'fs-extra' +import { remove, ensureDir, readFile } from 'fs-extra' import tar from 'tar' import { getStudioBundle } from '../api/studio/get_studio_bundle' import path from 'path' +import { verifySignature } from '../encryption' interface EnsureStudioBundleOptions { studioUrl: string @@ -26,7 +27,7 @@ export const ensureStudioBundle = async ({ projectId, studioPath, downloadTimeoutMs = DOWNLOAD_TIMEOUT, -}: EnsureStudioBundleOptions) => { +}: EnsureStudioBundleOptions): Promise> => { const bundlePath = path.join(studioPath, 'bundle.tar') // First remove studioPath to ensure we have a clean slate @@ -35,10 +36,9 @@ export const ensureStudioBundle = async ({ let timeoutId: NodeJS.Timeout - await Promise.race([ + const responseSignature = await Promise.race([ getStudioBundle({ studioUrl, - projectId, bundlePath, }), new Promise((_, reject) => { @@ -48,10 +48,21 @@ export const ensureStudioBundle = async ({ }), ]).finally(() => { clearTimeout(timeoutId) - }) + }) as string await tar.extract({ file: bundlePath, cwd: studioPath, }) + + const manifestPath = path.join(studioPath, 'manifest.json') + const manifestContents = await readFile(manifestPath, 'utf8') + + const verified = await verifySignature(manifestContents, responseSignature) + + if (!verified) { + throw new Error('Unable to verify studio signature') + } + + return JSON.parse(manifestContents) } diff --git a/packages/server/lib/cloud/studio/studio.ts b/packages/server/lib/cloud/studio/studio.ts index b095c9e8512c..7b0c4cae3624 100644 --- a/packages/server/lib/cloud/studio/studio.ts +++ b/packages/server/lib/cloud/studio/studio.ts @@ -5,6 +5,7 @@ import Debug from 'debug' import { requireScript } from '../require_script' import path from 'path' import { reportStudioError, ReportStudioErrorOptions } from '../api/studio/report_studio_error' +import crypto, { BinaryLike } from 'crypto' interface StudioServer { default: StudioServerDefaultShape } @@ -15,6 +16,7 @@ interface SetupOptions { projectSlug?: string cloudApi: StudioCloudApi shouldEnableStudio: boolean + manifest: Record } const debug = Debug('cypress:server:studio') @@ -41,7 +43,7 @@ export class StudioManager implements StudioManagerShape { return manager } - async setup ({ script, studioPath, studioHash, projectSlug, cloudApi, shouldEnableStudio }: SetupOptions): Promise { + async setup ({ script, studioPath, studioHash, projectSlug, cloudApi, shouldEnableStudio, manifest }: SetupOptions): Promise { const { createStudioServer } = requireScript(script).default this._studioServer = await createStudioServer({ @@ -50,6 +52,18 @@ export class StudioManager implements StudioManagerShape { projectSlug, cloudApi, betterSqlite3Path: path.dirname(require.resolve('better-sqlite3/package.json')), + manifest, + verifyHash: (contents: BinaryLike, expectedHash: string) => { + // If we are running locally, we don't need to verify the signature. This + // environment variable will get stripped in the binary. + if (process.env.CYPRESS_LOCAL_STUDIO_PATH) { + return true + } + + const actualHash = crypto.createHash('sha256').update(contents).digest('hex') + + return actualHash === expectedHash + }, }) this.status = shouldEnableStudio ? 'ENABLED' : 'INITIALIZED' diff --git a/packages/server/test/unit/cloud/api/studio/get_studio_bundle_spec.ts b/packages/server/test/unit/cloud/api/studio/get_studio_bundle_spec.ts index 7b22a25897d2..a6c7bd02cb54 100644 --- a/packages/server/test/unit/cloud/api/studio/get_studio_bundle_spec.ts +++ b/packages/server/test/unit/cloud/api/studio/get_studio_bundle_spec.ts @@ -7,13 +7,11 @@ describe('getStudioBundle', () => { let readStream: Readable let createWriteStreamStub: sinon.SinonStub let crossFetchStub: sinon.SinonStub - let verifySignatureFromFileStub: sinon.SinonStub let getStudioBundle: typeof import('../../../../../lib/cloud/api/studio/get_studio_bundle').getStudioBundle beforeEach(() => { createWriteStreamStub = sinon.stub() crossFetchStub = sinon.stub() - verifySignatureFromFileStub = sinon.stub() readStream = Readable.from('console.log("studio bundle")') writeResult = '' @@ -31,9 +29,6 @@ describe('getStudioBundle', () => { createWriteStream: createWriteStreamStub, }, 'cross-fetch': crossFetchStub, - '../../encryption': { - verifySignatureFromFile: verifySignatureFromFileStub, - }, 'os': { platform: () => 'linux', }, @@ -57,11 +52,7 @@ describe('getStudioBundle', () => { }, }) - verifySignatureFromFileStub.resolves(true) - - const projectId = '12345' - - await getStudioBundle({ studioUrl: 'http://localhost:1234/studio/bundle/abc.tgz', projectId, bundlePath: '/tmp/cypress/studio/abc/bundle.tar' }) + const responseSignature = await getStudioBundle({ studioUrl: 'http://localhost:1234/studio/bundle/abc.tgz', bundlePath: '/tmp/cypress/studio/abc/bundle.tar' }) expect(crossFetchStub).to.be.calledWith('http://localhost:1234/studio/bundle/abc.tgz', { agent: sinon.match.any, @@ -77,7 +68,7 @@ describe('getStudioBundle', () => { expect(writeResult).to.eq('console.log("studio bundle")') - expect(verifySignatureFromFileStub).to.be.calledWith('/tmp/cypress/studio/abc/bundle.tar', '159') + expect(responseSignature).to.eq('159') }) it('downloads the studio bundle and extracts it after 1 fetch failure', async () => { @@ -95,11 +86,7 @@ describe('getStudioBundle', () => { }, }) - verifySignatureFromFileStub.resolves(true) - - const projectId = '12345' - - await getStudioBundle({ studioUrl: 'http://localhost:1234/studio/bundle/abc.tgz', projectId, bundlePath: '/tmp/cypress/studio/abc/bundle.tar' }) + const responseSignature = await getStudioBundle({ studioUrl: 'http://localhost:1234/studio/bundle/abc.tgz', bundlePath: '/tmp/cypress/studio/abc/bundle.tar' }) expect(crossFetchStub).to.be.calledWith('http://localhost:1234/studio/bundle/abc.tgz', { agent: sinon.match.any, @@ -115,7 +102,7 @@ describe('getStudioBundle', () => { expect(writeResult).to.eq('console.log("studio bundle")') - expect(verifySignatureFromFileStub).to.be.calledWith('/tmp/cypress/studio/abc/bundle.tar', '159') + expect(responseSignature).to.eq('159') }) it('throws an error and returns a studio manager in error state if the fetch fails more than twice', async () => { @@ -123,9 +110,7 @@ describe('getStudioBundle', () => { crossFetchStub.rejects(error) - const projectId = '12345' - - await expect(getStudioBundle({ studioUrl: 'http://localhost:1234/studio/bundle/abc.tgz', projectId, bundlePath: '/tmp/cypress/studio/abc/bundle.tar' })).to.be.rejected + await expect(getStudioBundle({ studioUrl: 'http://localhost:1234/studio/bundle/abc.tgz', bundlePath: '/tmp/cypress/studio/abc/bundle.tar' })).to.be.rejected expect(crossFetchStub).to.be.calledThrice expect(crossFetchStub).to.be.calledWith('http://localhost:1234/studio/bundle/abc.tgz', { @@ -147,9 +132,7 @@ describe('getStudioBundle', () => { statusText: 'Some failure', }) - const projectId = '12345' - - await expect(getStudioBundle({ studioUrl: 'http://localhost:1234/studio/bundle/abc.tgz', projectId, bundlePath: '/tmp/cypress/studio/abc/bundle.tar' })).to.be.rejected + await expect(getStudioBundle({ studioUrl: 'http://localhost:1234/studio/bundle/abc.tgz', bundlePath: '/tmp/cypress/studio/abc/bundle.tar' })).to.be.rejected expect(crossFetchStub).to.be.calledWith('http://localhost:1234/studio/bundle/abc.tgz', { agent: sinon.match.any, @@ -164,45 +147,6 @@ describe('getStudioBundle', () => { }) }) - it('throws an error and returns a studio manager in error state if the signature verification fails', async () => { - verifySignatureFromFileStub.resolves(false) - - crossFetchStub.resolves({ - ok: true, - statusText: 'OK', - body: readStream, - headers: { - get: (header) => { - if (header === 'x-cypress-signature') { - return '159' - } - }, - }, - }) - - verifySignatureFromFileStub.resolves(false) - - const projectId = '12345' - - await expect(getStudioBundle({ studioUrl: 'http://localhost:1234/studio/bundle/abc.tgz', projectId, bundlePath: '/tmp/cypress/studio/abc/bundle.tar' })).to.be.rejected - - expect(writeResult).to.eq('console.log("studio bundle")') - - expect(crossFetchStub).to.be.calledWith('http://localhost:1234/studio/bundle/abc.tgz', { - agent: sinon.match.any, - method: 'GET', - headers: { - 'x-route-version': '1', - 'x-cypress-signature': '1', - 'x-os-name': 'linux', - 'x-cypress-version': '1.2.3', - }, - encrypt: 'signed', - }) - - expect(verifySignatureFromFileStub).to.be.calledWith('/tmp/cypress/studio/abc/bundle.tar', '159') - }) - it('throws an error if there is no signature in the response headers', async () => { crossFetchStub.resolves({ ok: true, @@ -213,9 +157,7 @@ describe('getStudioBundle', () => { }, }) - const projectId = '12345' - - await expect(getStudioBundle({ studioUrl: 'http://localhost:1234/studio/bundle/abc.tgz', projectId, bundlePath: '/tmp/cypress/studio/abc/bundle.tar' })).to.be.rejected + await expect(getStudioBundle({ studioUrl: 'http://localhost:1234/studio/bundle/abc.tgz', bundlePath: '/tmp/cypress/studio/abc/bundle.tar' })).to.be.rejected expect(crossFetchStub).to.be.calledWith('http://localhost:1234/studio/bundle/abc.tgz', { agent: sinon.match.any, diff --git a/packages/server/test/unit/cloud/studio/StudioLifecycleManager_spec.ts b/packages/server/test/unit/cloud/studio/StudioLifecycleManager_spec.ts index dbd6c31e4251..6b97a1b204b7 100644 --- a/packages/server/test/unit/cloud/studio/StudioLifecycleManager_spec.ts +++ b/packages/server/test/unit/cloud/studio/StudioLifecycleManager_spec.ts @@ -43,6 +43,7 @@ describe('StudioLifecycleManager', () => { let markStub: sinon.SinonStub let initializeTelemetryReporterStub: sinon.SinonStub let reportTelemetryStub: sinon.SinonStub + const mockContents = 'console.log("studio script")' beforeEach(() => { postStudioSessionStub = sinon.stub() @@ -83,7 +84,7 @@ describe('StudioLifecycleManager', () => { }, }, 'fs-extra': { - readFile: readFileStub.resolves('console.log("studio script")'), + readFile: readFileStub.resolves(mockContents), }, '../get_cloud_metadata': { getCloudMetadata: sinon.stub().resolves({ @@ -215,6 +216,12 @@ describe('StudioLifecycleManager', () => { }) }) + const mockManifest = { + 'server/index.js': 'e1ed3dc8ba9eb8ece23914004b99ad97bba37e80a25d8b47c009e1e4948a6159', + } + + ensureStudioBundleStub.resolves(mockManifest) + await studioReadyPromise expect(mockCtx.update).to.be.calledOnce @@ -237,6 +244,7 @@ describe('StudioLifecycleManager', () => { asyncRetry, }, shouldEnableStudio: false, + manifest: mockManifest, }) expect(postStudioSessionStub).to.be.calledWith({ @@ -292,6 +300,12 @@ describe('StudioLifecycleManager', () => { }) }) + const mockManifest = { + 'server/index.js': 'e1ed3dc8ba9eb8ece23914004b99ad97bba37e80a25d8b47c009e1e4948a6159', + } + + ensureStudioBundleStub.resolves(mockManifest) + await studioReadyPromise expect(mockCtx.update).to.be.calledOnce @@ -314,6 +328,7 @@ describe('StudioLifecycleManager', () => { asyncRetry, }, shouldEnableStudio: false, + manifest: mockManifest, }) expect(postStudioSessionStub).to.be.calledWith({ @@ -389,6 +404,12 @@ describe('StudioLifecycleManager', () => { }) }) + const mockManifest = { + 'server/index.js': 'e1ed3dc8ba9eb8ece23914004b99ad97bba37e80a25d8b47c009e1e4948a6159', + } + + ensureStudioBundleStub.resolves(mockManifest) + await studioReadyPromise expect(mockCtx.update).to.be.calledOnce @@ -407,6 +428,7 @@ describe('StudioLifecycleManager', () => { asyncRetry, }, shouldEnableStudio: true, + manifest: {}, }) expect(postStudioSessionStub).to.be.calledWith({ @@ -590,6 +612,14 @@ describe('StudioLifecycleManager', () => { }) describe('registerStudioReadyListener', () => { + beforeEach(() => { + const mockManifest = { + 'server/index.js': 'e1ed3dc8ba9eb8ece23914004b99ad97bba37e80a25d8b47c009e1e4948a6159', + } + + ensureStudioBundleStub.resolves(mockManifest) + }) + it('registers a listener that will be called when studio is ready', () => { const listener = sinon.stub() @@ -735,6 +765,14 @@ describe('StudioLifecycleManager', () => { }) describe('status tracking', () => { + beforeEach(() => { + const mockManifest = { + 'server/index.js': 'e1ed3dc8ba9eb8ece23914004b99ad97bba37e80a25d8b47c009e1e4948a6159', + } + + ensureStudioBundleStub.resolves(mockManifest) + }) + it('updates status and emits events when status changes', async () => { // Setup the context to test status updates // @ts-expect-error - accessing private property diff --git a/packages/server/test/unit/cloud/studio/ensure_studio_bundle_spec.ts b/packages/server/test/unit/cloud/studio/ensure_studio_bundle_spec.ts index 2fbb9dc63d00..a71f5a325fb1 100644 --- a/packages/server/test/unit/cloud/studio/ensure_studio_bundle_spec.ts +++ b/packages/server/test/unit/cloud/studio/ensure_studio_bundle_spec.ts @@ -8,9 +8,14 @@ describe('ensureStudioBundle', () => { let rmStub: sinon.SinonStub = sinon.stub() let ensureStub: sinon.SinonStub = sinon.stub() let copyStub: sinon.SinonStub = sinon.stub() - let readFileStub: sinon.SinonStub = sinon.stub() let extractStub: sinon.SinonStub = sinon.stub() let getStudioBundleStub: sinon.SinonStub = sinon.stub() + let readFileStub: sinon.SinonStub = sinon.stub() + let verifySignatureStub: sinon.SinonStub = sinon.stub() + const mockResponseSignature = '159' + const mockManifest = { + 'server/index.js': 'abcdefg', + } beforeEach(() => { rmStub = sinon.stub() @@ -19,6 +24,7 @@ describe('ensureStudioBundle', () => { readFileStub = sinon.stub() extractStub = sinon.stub() getStudioBundleStub = sinon.stub() + verifySignatureStub = sinon.stub() ensureStudioBundle = (proxyquire('../lib/cloud/studio/ensure_studio_bundle', { os: { @@ -29,13 +35,16 @@ describe('ensureStudioBundle', () => { remove: rmStub.resolves(), ensureDir: ensureStub.resolves(), copy: copyStub.resolves(), - readFile: readFileStub.resolves('console.log("studio bundle")'), + readFile: readFileStub.resolves(JSON.stringify(mockManifest)), }, tar: { extract: extractStub.resolves(), }, '../api/studio/get_studio_bundle': { - getStudioBundle: getStudioBundleStub.resolves(), + getStudioBundle: getStudioBundleStub.resolves(mockResponseSignature), + }, + '../encryption': { + verifySignature: verifySignatureStub.resolves(true), }, })).ensureStudioBundle }) @@ -44,7 +53,7 @@ describe('ensureStudioBundle', () => { const studioPath = path.join(os.tmpdir(), 'cypress', 'studio', '123') const bundlePath = path.join(studioPath, 'bundle.tar') - await ensureStudioBundle({ + const manifest = await ensureStudioBundle({ studioPath, studioUrl: 'https://cypress.io/studio', projectId: '123', @@ -52,9 +61,9 @@ describe('ensureStudioBundle', () => { expect(rmStub).to.be.calledWith(studioPath) expect(ensureStub).to.be.calledWith(studioPath) + expect(readFileStub).to.be.calledWith(path.join(studioPath, 'manifest.json'), 'utf8') expect(getStudioBundleStub).to.be.calledWith({ studioUrl: 'https://cypress.io/studio', - projectId: '123', bundlePath, }) @@ -62,6 +71,22 @@ describe('ensureStudioBundle', () => { file: bundlePath, cwd: studioPath, }) + + expect(verifySignatureStub).to.be.calledWith(JSON.stringify(mockManifest), mockResponseSignature) + + expect(manifest).to.deep.eq(mockManifest) + }) + + it('should throw an error if the studio bundle signature is invalid', async () => { + verifySignatureStub.resolves(false) + + const ensureStudioBundlePromise = ensureStudioBundle({ + studioPath: '/tmp/cypress/studio/123', + studioUrl: 'https://cypress.io/studio', + projectId: '123', + }) + + await expect(ensureStudioBundlePromise).to.be.rejectedWith('Unable to verify studio signature') }) it('should throw an error if the studio bundle download times out', async () => { diff --git a/packages/server/test/unit/cloud/studio/studio_spec.ts b/packages/server/test/unit/cloud/studio/studio_spec.ts index 0261119d46b4..245921849f4b 100644 --- a/packages/server/test/unit/cloud/studio/studio_spec.ts +++ b/packages/server/test/unit/cloud/studio/studio_spec.ts @@ -37,6 +37,9 @@ describe('lib/cloud/studio', () => { projectSlug: '1234', cloudApi: {} as any, shouldEnableStudio: true, + manifest: { + 'server/index.js': 'abcdefg', + }, }) studio = (studioManager as any)._studioServer diff --git a/packages/types/src/studio/studio-server-types.ts b/packages/types/src/studio/studio-server-types.ts index e87f6603bfe0..2f9489b5a093 100644 --- a/packages/types/src/studio/studio-server-types.ts +++ b/packages/types/src/studio/studio-server-types.ts @@ -3,6 +3,7 @@ import type { Router } from 'express' import type { AxiosInstance } from 'axios' import type { Socket } from 'socket.io' +import type { BinaryLike } from 'crypto' export const StudioMetricsTypes = { STUDIO_STARTED: 'studio:started', @@ -50,6 +51,8 @@ export interface StudioServerOptions { projectSlug?: string cloudApi: StudioCloudApi betterSqlite3Path: string + manifest: Record + verifyHash: (contents: BinaryLike, expectedHash: string) => boolean } export interface StudioAIInitializeOptions { diff --git a/scripts/after-pack-hook.js b/scripts/after-pack-hook.js index ea0e365c283a..632c2250d3c6 100644 --- a/scripts/after-pack-hook.js +++ b/scripts/after-pack-hook.js @@ -91,14 +91,22 @@ module.exports = async function (params) { const encryptionFileSource = await getEncryptionFileSource(encryptionFilePath) const cloudEnvironmentFilePath = path.join(CY_ROOT_DIR, 'packages/server/lib/cloud/environment.ts') const cloudEnvironmentFileSource = await getCloudEnvironmentFileSource(cloudEnvironmentFilePath) + + // Remove local protocol env const cloudApiFilePath = path.join(CY_ROOT_DIR, 'packages/server/lib/cloud/api/index.ts') const cloudApiFileSource = await getProtocolFileSource(cloudApiFilePath) const cloudProtocolFilePath = path.join(CY_ROOT_DIR, 'packages/server/lib/cloud/protocol.ts') const cloudProtocolFileSource = await getProtocolFileSource(cloudProtocolFilePath) + + // Remove local studio env const reportStudioErrorPath = path.join(CY_ROOT_DIR, 'packages/server/lib/cloud/api/studio/report_studio_error.ts') const reportStudioErrorFileSource = await getStudioFileSource(reportStudioErrorPath) const StudioLifecycleManagerPath = path.join(CY_ROOT_DIR, 'packages/server/lib/cloud/studio/StudioLifecycleManager.ts') const StudioLifecycleManagerFileSource = await getStudioFileSource(StudioLifecycleManagerPath) + const studioProtocolFilePath = path.join(CY_ROOT_DIR, 'packages/server/lib/cloud/protocol.ts') + const studioProtocolFileSource = await getStudioFileSource(studioProtocolFilePath) + const studioPath = path.join(CY_ROOT_DIR, 'packages/server/lib/cloud/studio/studio.ts') + const studioPathFileSource = await getStudioFileSource(studioPath) await Promise.all([ fs.writeFile(encryptionFilePath, encryptionFileSource), @@ -107,6 +115,8 @@ module.exports = async function (params) { fs.writeFile(cloudProtocolFilePath, cloudProtocolFileSource), fs.writeFile(reportStudioErrorPath, reportStudioErrorFileSource), fs.writeFile(StudioLifecycleManagerPath, StudioLifecycleManagerFileSource), + fs.writeFile(studioProtocolFilePath, studioProtocolFileSource), + fs.writeFile(studioPath, studioPathFileSource), fs.writeFile(path.join(outputFolder, 'index.js'), binaryEntryPointSource), ]) @@ -121,6 +131,8 @@ module.exports = async function (params) { validateProtocolFile(cloudProtocolFilePath), validateStudioFile(reportStudioErrorPath), validateStudioFile(StudioLifecycleManagerPath), + validateStudioFile(studioProtocolFilePath), + validateStudioFile(studioPath), ]) await flipFuses(