Upload files instead ofsources (#17608)

This commit is contained in:
martmull
2026-02-03 12:11:10 +01:00
committed by GitHub
parent cdaa8f1d9d
commit 65678ed99f
64 changed files with 1148 additions and 473 deletions
@@ -2,6 +2,7 @@ import { Injectable } from '@nestjs/common';
import fs from 'fs/promises';
import { dirname, join } from 'path';
import crypto from 'crypto';
import { build } from 'esbuild';
import { FileFolder } from 'twenty-shared/types';
@@ -13,6 +14,7 @@ import {
getLogicFunctionBaseFolderPath,
getRelativePathFromBase,
} from 'src/engine/core-modules/logic-function/logic-function-build/utils/get-logic-function-base-folder-path.util';
import { FlatLogicFunctionLayer } from 'src/engine/metadata-modules/logic-function-layer/types/flat-logic-function-layer.type';
export type FunctionBuildParams = {
flatLogicFunction: FlatLogicFunction;
@@ -23,6 +25,64 @@ export type FunctionBuildParams = {
export class LogicFunctionBuildService {
constructor(private readonly fileStorageService: FileStorageService) {}
async hasLayerDependencies({
flatLogicFunctionLayer,
applicationUniversalIdentifier,
}: {
flatLogicFunctionLayer: FlatLogicFunctionLayer;
applicationUniversalIdentifier: string;
}): Promise<boolean> {
const packageJsonExists = await this.fileStorageService.checkFileExists_v2({
workspaceId: flatLogicFunctionLayer.workspaceId,
applicationUniversalIdentifier,
fileFolder: FileFolder.Source,
resourcePath: 'package.json',
});
const yarnLockExists = await this.fileStorageService.checkFileExists_v2({
workspaceId: flatLogicFunctionLayer.workspaceId,
applicationUniversalIdentifier,
fileFolder: FileFolder.Source,
resourcePath: 'yarn.lock',
});
return packageJsonExists && yarnLockExists;
}
async uploadDependencies({
flatLogicFunctionLayer,
applicationUniversalIdentifier,
}: {
flatLogicFunctionLayer: FlatLogicFunctionLayer;
applicationUniversalIdentifier: string;
}) {
await this.fileStorageService.writeFile_v2({
workspaceId: flatLogicFunctionLayer.workspaceId,
applicationUniversalIdentifier,
fileFolder: FileFolder.Source,
resourcePath: 'package.json',
sourceFile: JSON.stringify(flatLogicFunctionLayer.packageJson, null, 2),
mimeType: undefined,
settings: {
isTemporaryFile: false,
toDelete: false,
},
});
await this.fileStorageService.writeFile_v2({
workspaceId: flatLogicFunctionLayer.workspaceId,
applicationUniversalIdentifier,
fileFolder: FileFolder.Source,
resourcePath: 'yarn.lock',
sourceFile: flatLogicFunctionLayer.yarnLock,
mimeType: undefined,
settings: {
isTemporaryFile: false,
toDelete: false,
},
});
}
async isBuilt({
flatLogicFunction,
applicationUniversalIdentifier,
@@ -38,7 +98,7 @@ export class LogicFunctionBuildService {
async buildAndUpload({
flatLogicFunction,
applicationUniversalIdentifier,
}: FunctionBuildParams): Promise<void> {
}: FunctionBuildParams): Promise<{ checksum: string }> {
const lambdaBuildDirectoryManager = new LambdaBuildDirectoryManager();
try {
@@ -85,6 +145,10 @@ export class LogicFunctionBuildService {
toDelete: false,
},
});
return {
checksum: crypto.createHash('md5').update(builtFile).digest('hex'),
};
} finally {
await lambdaBuildDirectoryManager.clean();
}
@@ -0,0 +1,8 @@
var main = async (params) => {
const { a, b } = params;
const message = `Hello, input: ${a} and ${b}`;
return { message };
};
export {
main
};
@@ -29,7 +29,7 @@ import {
} from 'src/engine/core-modules/logic-function/logic-function-drivers/interfaces/logic-function-executor-driver.interface';
import { type FileStorageService } from 'src/engine/core-modules/file-storage/file-storage.service';
import { copyAndBuildDependencies } from 'src/engine/core-modules/logic-function/logic-function-drivers/utils/copy-and-build-dependencies';
import { copyYarnEngineAndBuildDependencies } from 'src/engine/core-modules/logic-function/logic-function-drivers/utils/copy-yarn-engine-and-build-dependencies';
import { copyExecutor } from 'src/engine/core-modules/logic-function/logic-function-drivers/utils/copy-executor';
import { createZipFile } from 'src/engine/core-modules/logic-function/logic-function-drivers/utils/create-zip-file';
import {
@@ -139,12 +139,43 @@ export class LambdaDriver implements LogicFunctionExecutorDriver {
}
private getLayerName(flatLogicFunctionLayer: FlatLogicFunctionLayer) {
return flatLogicFunctionLayer.checksum;
return flatLogicFunctionLayer.yarnLockChecksum;
}
private async createLayerIfNotExists(
flatLogicFunctionLayer: FlatLogicFunctionLayer,
): Promise<string> {
private async copyDependenciesInMemory({
applicationUniversalIdentifier,
workspaceId,
inMemoryLayerFolderPath,
}: {
applicationUniversalIdentifier: string;
workspaceId: string;
inMemoryLayerFolderPath: string;
}) {
await Promise.all([
this.fileStorageService.downloadFile_v2({
workspaceId,
applicationUniversalIdentifier,
fileFolder: FileFolder.Source,
resourcePath: 'package.json',
localPath: join(inMemoryLayerFolderPath, 'package.json'),
}),
this.fileStorageService.downloadFile_v2({
workspaceId,
applicationUniversalIdentifier,
fileFolder: FileFolder.Source,
resourcePath: 'yarn.lock',
localPath: join(inMemoryLayerFolderPath, 'yarn.lock'),
}),
]);
}
private async createLayerIfNotExists({
flatLogicFunctionLayer,
applicationUniversalIdentifier,
}: {
flatLogicFunctionLayer: FlatLogicFunctionLayer;
applicationUniversalIdentifier: string;
}): Promise<string> {
const layerName = this.getLayerName(flatLogicFunctionLayer);
const listLayerParams: ListLayerVersionsCommandInput = {
@@ -171,10 +202,12 @@ export class LambdaDriver implements LogicFunctionExecutorDriver {
NODE_LAYER_SUBFOLDER,
);
await copyAndBuildDependencies(
nodeDependenciesFolder,
flatLogicFunctionLayer,
);
await this.copyDependenciesInMemory({
applicationUniversalIdentifier,
workspaceId: flatLogicFunctionLayer.workspaceId,
inMemoryLayerFolderPath: nodeDependenciesFolder,
});
await copyYarnEngineAndBuildDependencies(nodeDependenciesFolder);
await createZipFile(sourceTemporaryDir, lambdaZipPath);
@@ -257,15 +290,23 @@ export class LambdaDriver implements LogicFunctionExecutorDriver {
return false;
}
private async build(
flatLogicFunction: FlatLogicFunction,
flatLogicFunctionLayer: FlatLogicFunctionLayer,
) {
private async build({
flatLogicFunction,
flatLogicFunctionLayer,
applicationUniversalIdentifier,
}: {
flatLogicFunction: FlatLogicFunction;
flatLogicFunctionLayer: FlatLogicFunctionLayer;
applicationUniversalIdentifier: string;
}) {
if (await this.isAlreadyBuilt(flatLogicFunction, flatLogicFunctionLayer)) {
return;
}
const layerArn = await this.createLayerIfNotExists(flatLogicFunctionLayer);
const layerArn = await this.createLayerIfNotExists({
flatLogicFunctionLayer,
applicationUniversalIdentifier,
});
const lambdaBuildDirectoryManager = new LambdaBuildDirectoryManager();
@@ -317,7 +358,11 @@ export class LambdaDriver implements LogicFunctionExecutorDriver {
payload,
env,
}: LogicFunctionExecuteParams): Promise<LogicFunctionExecuteResult> {
await this.build(flatLogicFunction, flatLogicFunctionLayer);
await this.build({
flatLogicFunction,
flatLogicFunctionLayer,
applicationUniversalIdentifier,
});
await this.waitFunctionUpdates(flatLogicFunction);
@@ -5,14 +5,14 @@ import { join } from 'path';
import { FileFolder } from 'twenty-shared/types';
import {
type LogicFunctionExecutorDriver,
type LogicFunctionExecuteParams,
type LogicFunctionExecuteResult,
type LogicFunctionExecutorDriver,
} from 'src/engine/core-modules/logic-function/logic-function-drivers/interfaces/logic-function-executor-driver.interface';
import { type FileStorageService } from 'src/engine/core-modules/file-storage/file-storage.service';
import { LOGIC_FUNCTION_EXECUTOR_TMPDIR_FOLDER } from 'src/engine/core-modules/logic-function/logic-function-drivers/constants/logic-function-executor-tmpdir-folder';
import { copyAndBuildDependencies } from 'src/engine/core-modules/logic-function/logic-function-drivers/utils/copy-and-build-dependencies';
import { copyYarnEngineAndBuildDependencies } from 'src/engine/core-modules/logic-function/logic-function-drivers/utils/copy-yarn-engine-and-build-dependencies';
import { ConsoleListener } from 'src/engine/core-modules/logic-function/logic-function-drivers/utils/intercept-console';
import { LambdaBuildDirectoryManager } from 'src/engine/core-modules/logic-function/logic-function-drivers/utils/lambda-build-directory-manager';
import { type FlatLogicFunctionLayer } from 'src/engine/metadata-modules/logic-function-layer/types/flat-logic-function-layer.type';
@@ -38,13 +38,44 @@ export class LocalDriver implements LogicFunctionExecutorDriver {
) => {
return join(
LOGIC_FUNCTION_EXECUTOR_TMPDIR_FOLDER,
flatLogicFunctionLayer.checksum,
flatLogicFunctionLayer.yarnLockChecksum,
);
};
private async createLayerIfNotExists(
flatLogicFunctionLayer: FlatLogicFunctionLayer,
) {
private async copyDependenciesInMemory({
applicationUniversalIdentifier,
workspaceId,
inMemoryLayerFolderPath,
}: {
applicationUniversalIdentifier: string;
workspaceId: string;
inMemoryLayerFolderPath: string;
}) {
await Promise.all([
this.fileStorageService.downloadFile_v2({
workspaceId,
applicationUniversalIdentifier,
fileFolder: FileFolder.Source,
resourcePath: 'package.json',
localPath: join(inMemoryLayerFolderPath, 'package.json'),
}),
this.fileStorageService.downloadFile_v2({
workspaceId,
applicationUniversalIdentifier,
fileFolder: FileFolder.Source,
resourcePath: 'yarn.lock',
localPath: join(inMemoryLayerFolderPath, 'yarn.lock'),
}),
]);
}
private async createLayerIfNotExists({
flatLogicFunctionLayer,
applicationUniversalIdentifier,
}: {
flatLogicFunctionLayer: FlatLogicFunctionLayer;
applicationUniversalIdentifier: string;
}) {
const inMemoryLayerFolderPath = this.getInMemoryLayerFolderPath(
flatLogicFunctionLayer,
);
@@ -52,17 +83,28 @@ export class LocalDriver implements LogicFunctionExecutorDriver {
try {
await fs.access(inMemoryLayerFolderPath);
} catch {
await copyAndBuildDependencies(
await this.copyDependenciesInMemory({
applicationUniversalIdentifier,
workspaceId: flatLogicFunctionLayer.workspaceId,
inMemoryLayerFolderPath,
flatLogicFunctionLayer,
);
});
await copyYarnEngineAndBuildDependencies(inMemoryLayerFolderPath);
}
}
async delete() {}
private async build(flatLogicFunctionLayer: FlatLogicFunctionLayer) {
await this.createLayerIfNotExists(flatLogicFunctionLayer);
private async build({
flatLogicFunctionLayer,
applicationUniversalIdentifier,
}: {
flatLogicFunctionLayer: FlatLogicFunctionLayer;
applicationUniversalIdentifier: string;
}) {
await this.createLayerIfNotExists({
flatLogicFunctionLayer,
applicationUniversalIdentifier,
});
}
async execute({
@@ -72,7 +114,10 @@ export class LocalDriver implements LogicFunctionExecutorDriver {
payload,
env,
}: LogicFunctionExecuteParams): Promise<LogicFunctionExecuteResult> {
await this.build(flatLogicFunctionLayer);
await this.build({
flatLogicFunctionLayer,
applicationUniversalIdentifier,
});
const startTime = Date.now();
@@ -4,30 +4,16 @@ import { join } from 'path';
import { promisify } from 'util';
import { getLayerDependenciesDirName } from 'src/engine/core-modules/logic-function/logic-function-drivers/utils/get-layer-dependencies-dir-name';
import { type FlatLogicFunctionLayer } from 'src/engine/metadata-modules/logic-function-layer/types/flat-logic-function-layer.type';
const execFilePromise = promisify(execFile);
export const copyAndBuildDependencies = async (
export const copyYarnEngineAndBuildDependencies = async (
buildDirectory: string,
flatLogicFunctionLayer: FlatLogicFunctionLayer,
) => {
await fs.mkdir(buildDirectory, {
recursive: true,
});
const packageJson = flatLogicFunctionLayer.packageJson;
const yarnLock = flatLogicFunctionLayer.yarnLock;
await fs.writeFile(
join(buildDirectory, 'package.json'),
JSON.stringify(packageJson, null, 2),
'utf8',
);
await fs.writeFile(join(buildDirectory, 'yarn.lock'), yarnLock, 'utf8');
await fs.cp(getLayerDependenciesDirName('engine'), buildDirectory, {
recursive: true,
});
@@ -1,13 +1,11 @@
import fs from 'fs/promises';
import { join } from 'path';
import { type PackageJson } from 'twenty-shared/application';
import { getLayerDependenciesDirName } from 'src/engine/core-modules/logic-function/logic-function-drivers/utils/get-layer-dependencies-dir-name';
import { LAST_LAYER_VERSION } from 'src/engine/core-modules/logic-function/logic-function-drivers/layers/last-layer-version';
export type LayerDependencies = {
packageJson: PackageJson;
packageJson: string;
yarnLock: string;
};
@@ -20,5 +18,5 @@ export const getLastCommonLayerDependencies = async (
fs.readFile(join(lastVersionLayerDirName, 'yarn.lock'), 'utf8'),
]);
return { packageJson: JSON.parse(packageJson), yarnLock };
return { packageJson, yarnLock };
};
@@ -16,7 +16,7 @@ const getAllFiles = async (
const fullPath = path.join(dir, entry.name);
if (entry.isDirectory()) {
files.push(...(await getAllFiles(rootDir, fullPath, files)));
await getAllFiles(rootDir, fullPath, files);
} else {
files.push({
path: path.relative(rootDir, dir),
@@ -12,6 +12,7 @@ import { ThrottlerModule } from 'src/engine/core-modules/throttler/throttler.mod
import { LogicFunctionEntity } from 'src/engine/metadata-modules/logic-function/logic-function.entity';
import { SubscriptionsModule } from 'src/engine/subscriptions/subscriptions.module';
import { WorkspaceCacheModule } from 'src/engine/workspace-cache/workspace-cache.module';
import { LogicFunctionLayerModule } from 'src/engine/metadata-modules/logic-function-layer/logic-function-layer.module';
@Module({
imports: [
@@ -22,6 +23,7 @@ import { WorkspaceCacheModule } from 'src/engine/workspace-cache/workspace-cache
SubscriptionsModule,
WorkspaceCacheModule,
LogicFunctionBuildModule,
LogicFunctionLayerModule,
FileModule,
TypeOrmModule.forFeature([LogicFunctionEntity]),
],
@@ -6,7 +6,7 @@ import {
DEFAULT_API_URL_NAME,
} from 'twenty-shared/application';
import { FileFolder } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import { isDefined, isEmptyObject } from 'twenty-shared/utils';
import { Repository } from 'typeorm';
import {
@@ -35,6 +35,7 @@ import { SubscriptionChannel } from 'src/engine/subscriptions/enums/subscription
import { SubscriptionService } from 'src/engine/subscriptions/subscription.service';
import { WorkspaceCacheService } from 'src/engine/workspace-cache/services/workspace-cache.service';
import { cleanServerUrl } from 'src/utils/clean-server-url';
import { LogicFunctionLayerService } from 'src/engine/core-modules/logic-function/logic-function-layer/services/logic-function-layer.service';
const MIN_TOKEN_EXPIRATION_IN_SECONDS = 5;
@@ -68,6 +69,7 @@ export class LogicFunctionExecutorService
private readonly functionBuildService: LogicFunctionBuildService,
private readonly subscriptionService: SubscriptionService,
private readonly auditService: AuditService,
private readonly logicFunctionLayerService: LogicFunctionLayerService,
private readonly fileStorageService: FileStorageService,
@InjectRepository(LogicFunctionEntity)
private readonly logicFunctionRepository: Repository<LogicFunctionEntity>,
@@ -178,6 +180,19 @@ export class LogicFunctionExecutorService
);
}
// TODO: remove when all logic functions are migrated
if (
!(await this.functionBuildService.hasLayerDependencies({
flatLogicFunctionLayer,
applicationUniversalIdentifier,
}))
) {
await this.functionBuildService.uploadDependencies({
flatLogicFunctionLayer,
applicationUniversalIdentifier,
});
}
if (
!(await this.functionBuildService.isBuilt({
flatLogicFunction,
@@ -189,6 +204,7 @@ export class LogicFunctionExecutorService
applicationUniversalIdentifier,
});
}
// END TODO
const resultLogicFunction = await this.callWithTimeout({
callback: () =>
@@ -291,30 +307,26 @@ export class LogicFunctionExecutorService
async getAvailablePackages(logicFunctionId: string) {
const logicFunction = await this.logicFunctionRepository.findOneOrFail({
where: { id: logicFunctionId },
relations: ['logicFunctionLayer'],
relations: ['logicFunctionLayer', 'application'],
});
const packageJson = logicFunction.logicFunctionLayer.packageJson;
if (isEmptyObject(logicFunction.logicFunctionLayer.availablePackages)) {
await this.logicFunctionLayerService.update(
logicFunction.logicFunctionLayer.id,
{},
logicFunction.application.universalIdentifier,
logicFunction.workspaceId,
);
const yarnLock = logicFunction.logicFunctionLayer.yarnLock;
const packageVersionRegex = /^"([^@]+)@.*?":\n\s+version: (.+)$/gm;
const versions: Record<string, string> = {};
let match: RegExpExecArray | null;
while ((match = packageVersionRegex.exec(yarnLock)) !== null) {
const packageName = match[1].split('@', 1)[0];
const version = match[2];
// @ts-expect-error legacy noImplicitAny
if (packageJson.dependencies?.[packageName]) {
versions[packageName] = version;
}
return (
await this.logicFunctionRepository.findOneOrFail({
where: { id: logicFunctionId },
relations: ['logicFunctionLayer'],
})
).logicFunctionLayer.availablePackages;
}
return versions;
return logicFunction.logicFunctionLayer.availablePackages;
}
private async throttleExecution(workspaceId: string) {
@@ -3,6 +3,8 @@ import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { isDefined } from 'twenty-shared/utils';
import { FileFolder } from 'twenty-shared/types';
import { PackageJson } from 'type-fest';
import type { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity';
@@ -11,6 +13,8 @@ import { CreateLogicFunctionLayerInput } from 'src/engine/metadata-modules/logic
import { getLastCommonLayerDependencies } from 'src/engine/core-modules/logic-function/logic-function-drivers/utils/get-last-common-layer-dependencies';
import { logicFunctionCreateHash } from 'src/engine/metadata-modules/logic-function/utils/logic-function-create-hash.utils';
import { WorkspaceCacheService } from 'src/engine/workspace-cache/services/workspace-cache.service';
import { FileStorageService } from 'src/engine/core-modules/file-storage/file-storage.service';
import { streamToBuffer } from 'src/utils/stream-to-buffer';
@Injectable()
export class LogicFunctionLayerService {
@@ -18,23 +22,34 @@ export class LogicFunctionLayerService {
@InjectRepository(LogicFunctionLayerEntity)
private readonly logicFunctionLayerRepository: Repository<LogicFunctionLayerEntity>,
private readonly workspaceCacheService: WorkspaceCacheService,
private readonly fileStorageService: FileStorageService,
) {}
async create(
{ packageJson, yarnLock }: CreateLogicFunctionLayerInput,
{
packageJsonChecksum,
yarnLockChecksum,
applicationUniversalIdentifier,
}: CreateLogicFunctionLayerInput,
workspaceId: string,
) {
const checksum = logicFunctionCreateHash(yarnLock);
const logicFunctionLayer = this.logicFunctionLayerRepository.create({
packageJson,
yarnLock,
checksum,
packageJson: {}, // TODO: Delete when migration to Source files storage is done
yarnLock: '', // TODO: Delete when migration to Source files storage is done
packageJsonChecksum,
yarnLockChecksum,
workspaceId,
} as Omit<LogicFunctionLayerEntity, 'workspace'>);
const availablePackages = await this.getAvailablePackages({
workspaceId,
applicationUniversalIdentifier,
});
const savedLayer =
await this.logicFunctionLayerRepository.save(logicFunctionLayer);
const savedLayer = await this.logicFunctionLayerRepository.save({
...logicFunctionLayer,
availablePackages,
});
await this.workspaceCacheService.invalidateAndRecompute(workspaceId, [
'logicFunctionLayerMaps',
@@ -45,19 +60,19 @@ export class LogicFunctionLayerService {
async update(
id: string,
data: QueryDeepPartialEntity<LogicFunctionLayerEntity>,
data: QueryDeepPartialEntity<Omit<LogicFunctionLayerEntity, 'workspace'>>,
applicationUniversalIdentifier: string,
workspaceId: string,
) {
const checksum = data.yarnLock
? logicFunctionCreateHash(data.yarnLock as string)
: undefined;
const availablePackages = await this.getAvailablePackages({
workspaceId,
applicationUniversalIdentifier,
});
const updateData = { ...data, ...(checksum && { checksum }) };
const result = await this.logicFunctionLayerRepository.update(
id,
updateData,
);
const result = await this.logicFunctionLayerRepository.update(id, {
...data,
availablePackages,
});
await this.workspaceCacheService.invalidateAndRecompute(workspaceId, [
'logicFunctionLayerMaps',
@@ -66,12 +81,45 @@ export class LogicFunctionLayerService {
return result;
}
async createCommonLayerIfNotExist(workspaceId: string) {
async createCommonLayer({
workspaceId,
applicationUniversalIdentifier,
}: {
workspaceId: string;
applicationUniversalIdentifier: string;
}) {
const { packageJson, yarnLock } = await getLastCommonLayerDependencies();
const checksum = logicFunctionCreateHash(yarnLock);
await this.fileStorageService.writeFile_v2({
sourceFile: packageJson,
mimeType: undefined,
fileFolder: FileFolder.Source,
applicationUniversalIdentifier,
workspaceId,
resourcePath: 'package.json',
settings: { isTemporaryFile: false, toDelete: false },
});
await this.fileStorageService.writeFile_v2({
sourceFile: yarnLock,
mimeType: undefined,
fileFolder: FileFolder.Source,
applicationUniversalIdentifier,
workspaceId,
resourcePath: 'yarn.lock',
settings: { isTemporaryFile: false, toDelete: false },
});
const packageJsonChecksum = logicFunctionCreateHash(
JSON.stringify(packageJson),
);
const yarnLockChecksum = logicFunctionCreateHash(yarnLock);
const commonLayer = await this.logicFunctionLayerRepository.findOne({
where: {
checksum,
yarnLockChecksum,
packageJsonChecksum,
workspaceId,
},
});
@@ -80,6 +128,59 @@ export class LogicFunctionLayerService {
return commonLayer;
}
return this.create({ packageJson, yarnLock }, workspaceId);
return this.create(
{ packageJsonChecksum, yarnLockChecksum, applicationUniversalIdentifier },
workspaceId,
);
}
private async getAvailablePackages({
workspaceId,
applicationUniversalIdentifier,
}: {
workspaceId: string;
applicationUniversalIdentifier: string;
}) {
const packageJson = JSON.parse(
(
await streamToBuffer(
await this.fileStorageService.readFile_v2({
workspaceId,
applicationUniversalIdentifier,
fileFolder: FileFolder.Source,
resourcePath: 'package.json',
}),
)
).toString('utf-8'),
) as PackageJson;
const yarnLock = (
await streamToBuffer(
await this.fileStorageService.readFile_v2({
workspaceId,
applicationUniversalIdentifier,
fileFolder: FileFolder.Source,
resourcePath: 'yarn.lock',
}),
)
).toString('utf-8');
const packageVersionRegex =
/^"(@?[^@]+(?:\/[^@]+)?)@.*?":\n\s+version:\s*(.+)$/gm;
const versions: Record<string, string> = {};
let match: RegExpExecArray | null;
while ((match = packageVersionRegex.exec(yarnLock)) !== null) {
const packageName = match[1];
const version = match[2];
if (packageJson.dependencies?.[packageName]) {
versions[packageName] = version;
}
}
return versions;
}
}