Compare commits

...
Author SHA1 Message Date
Sonarly Claude Code a730cf3f7d Logic function FILE_NOT_FOUND: missing package.json check
https://sonarly.com/issue/26891?type=bug

Logic function execution fails with FileStorageException FILE_NOT_FOUND because copyDependenciesInMemory unconditionally downloads package.json from S3 without checking if it exists first, unlike yarn.lock which has a fallback. Dependency files are only uploaded during new workspace creation, so upgraded workspaces lack them entirely.

Fix: Added a checkFileExists guard for package.json in copyDependenciesInMemory, mirroring the existing yarn.lock pattern from commit f3e0c12ce6.

Before this fix, copyDependenciesInMemory unconditionally called fileStorageService.downloadFile for package.json without checking if it exists in S3. If the file was missing (e.g., workspace upgraded from pre-application-system version, or S3 upload failed during workspace creation), the S3 driver threw FileStorageException(FILE_NOT_FOUND) which killed logic function execution.

The fix:
1. Both checkFileExists calls (package.json and yarn.lock) now run in parallel via Promise.all for efficiency
2. If package.json exists in S3, it is downloaded as before
3. If package.json is missing, the default seed package.json from SEED_DEPENDENCIES_DIRNAME is copied to the in-memory folder using fs.copyFile — this provides the full default dependency set so logic functions can still execute
4. Added import for SEED_DEPENDENCIES_DIRNAME constant and path module

This is the exact same defensive pattern already used for yarn.lock, which was added in commit f3e0c12ce6 but was not applied to package.json at that time.
2026-04-16 03:52:20 +00:00
@@ -2,7 +2,7 @@ import { Injectable } from '@nestjs/common';
import crypto from 'crypto';
import { promises as fs } from 'fs';
import { dirname, join } from 'path';
import path, { dirname, join } from 'path';
import { type QueryRunner } from 'typeorm';
import { FileFolder } from 'twenty-shared/types';
@@ -10,6 +10,7 @@ import { isDefined } from 'twenty-shared/utils';
import { FileStorageExceptionCode } from 'src/engine/core-modules/file-storage/interfaces/file-storage-exception';
import { SEED_DEPENDENCIES_DIRNAME } from 'src/engine/core-modules/application/application-package/constants/seed-dependencies-dirname';
import { FileStorageService } from 'src/engine/core-modules/file-storage/file-storage.service';
import {
getLogicFunctionSeedProjectFiles,
@@ -257,24 +258,45 @@ export class LogicFunctionResourceService {
workspaceId: string;
inMemoryFolderPath: string;
}) {
const yarnLockExists = await this.fileStorageService.checkFileExists({
workspaceId,
applicationUniversalIdentifier,
fileFolder: FileFolder.Dependencies,
resourcePath: 'yarn.lock',
});
const promises = [];
promises.push(
this.fileStorageService.downloadFile({
const [packageJsonExists, yarnLockExists] = await Promise.all([
this.fileStorageService.checkFileExists({
workspaceId,
applicationUniversalIdentifier,
fileFolder: FileFolder.Dependencies,
resourcePath: 'package.json',
localPath: join(inMemoryFolderPath, 'package.json'),
}),
);
this.fileStorageService.checkFileExists({
workspaceId,
applicationUniversalIdentifier,
fileFolder: FileFolder.Dependencies,
resourcePath: 'yarn.lock',
}),
]);
const promises = [];
if (packageJsonExists) {
promises.push(
this.fileStorageService.downloadFile({
workspaceId,
applicationUniversalIdentifier,
fileFolder: FileFolder.Dependencies,
resourcePath: 'package.json',
localPath: join(inMemoryFolderPath, 'package.json'),
}),
);
} else {
const packageJsonPath = join(inMemoryFolderPath, 'package.json');
promises.push(
fs.mkdir(dirname(packageJsonPath), { recursive: true }).then(() =>
fs.copyFile(
path.join(SEED_DEPENDENCIES_DIRNAME, 'package.json'),
packageJsonPath,
),
),
);
}
if (yarnLockExists) {
promises.push(