Add checksum to manifest (#17368)

## Refactor app-dev state management and build utilities

- Store only `manifest` in `AppDevState` instead of full
`ManifestBuildResult`; add `sourcePath` to `FileStatus`
- Pass `sourcePaths` directly to watchers instead of
`ManifestBuildResult`
- Only reset `fileUploadStatus` for functions/components when their
source paths change
- Add pure `updateManifestChecksum` utility that returns a new manifest
without side effects
- Extract `processEsbuildResult` to deduplicate build result processing
between watchers
- Rename `serverlessFunctions` → `functions` in SDK code (API unchanged)
- Extract `writeManifestToOutput` to shared `manifest-writer.ts`
This commit is contained in:
Charles Bochet
2026-01-22 18:16:55 +01:00
committed by GitHub
parent 843fda7564
commit 210c66b5dd
25 changed files with 459 additions and 155 deletions
@@ -17,6 +17,7 @@
"frontComponents": [
{
"builtComponentPath": "front-components/src/root.front-component.mjs",
"builtComponentChecksum": "[checksum]",
"componentName": "RootComponent",
"description": "A root-level front component",
"name": "root-component",
@@ -25,6 +26,7 @@
},
{
"builtComponentPath": "front-components/src/components/card.front-component.mjs",
"builtComponentChecksum": "[checksum]",
"componentName": "CardDisplay",
"description": "A component using an external component file",
"name": "card-component",
@@ -33,6 +35,7 @@
},
{
"builtComponentPath": "front-components/src/components/greeting.front-component.mjs",
"builtComponentChecksum": "[checksum]",
"componentName": "GreetingComponent",
"description": "A component that uses greeting utility",
"name": "greeting-component",
@@ -41,6 +44,7 @@
},
{
"builtComponentPath": "front-components/src/components/test.front-component.mjs",
"builtComponentChecksum": "[checksum]",
"componentName": "TestComponent",
"description": "A test front component",
"name": "test-component",
@@ -280,8 +284,9 @@
"universalIdentifier": "b648f87b-1d26-4961-b974-0908fd991061"
}
],
"serverlessFunctions": [
"functions": [
{
"builtHandlerChecksum": "[checksum]",
"builtHandlerPath": "functions/src/root.function.mjs",
"handlerName": "rootHandler",
"name": "root-function",
@@ -299,6 +304,7 @@
"universalIdentifier": "f0f1f2f3-f4f5-4000-8000-000000000001"
},
{
"builtHandlerChecksum": "[checksum]",
"builtHandlerPath": "functions/src/functions/greeting.function.mjs",
"handlerName": "greetingHandler",
"name": "greeting-function",
@@ -316,7 +322,8 @@
"universalIdentifier": "g0g1g2g3-g4g5-4000-8000-000000000001"
},
{
"builtHandlerPath": "functions/src/utils/test-function-2.util.mjs",
"builtHandlerChecksum": "[checksum]",
"builtHandlerPath": "functions/src/functions/test-function-2.function.mjs",
"handlerName": "testFunction2",
"name": "test-function-2",
"sourceHandlerPath": "src/utils/test-function-2.util.ts",
@@ -331,6 +338,7 @@
"universalIdentifier": "eb3ffc98-88ec-45d4-9b4a-56833b219ccb"
},
{
"builtHandlerChecksum": "[checksum]",
"builtHandlerPath": "functions/src/functions/test-function.function.mjs",
"handlerName": "handler",
"name": "test-function",
@@ -1,6 +1,7 @@
import * as fs from 'fs-extra';
import { join } from 'path';
import { normalizeManifestForComparison } from '@/cli/__tests__/integration/utils/normalize-manifest.util';
import expectedManifest from '../manifest.expected.json';
export const defineManifestTests = (appPath: string): void => {
@@ -14,7 +15,21 @@ export const defineManifestTests = (appPath: string): void => {
const { sources: _sources, ...sanitizedManifest } = manifest;
expect(sanitizedManifest).toEqual(expectedManifest);
expect(normalizeManifestForComparison(sanitizedManifest)).toEqual(
normalizeManifestForComparison(expectedManifest),
);
for (const fn of manifest.functions) {
expect(fn.builtHandlerChecksum).toBeDefined();
expect(fn.builtHandlerChecksum).not.toBeNull();
expect(typeof fn.builtHandlerChecksum).toBe('string');
}
for (const component of manifest.frontComponents ?? []) {
expect(component.builtComponentChecksum).toBeDefined();
expect(component.builtComponentChecksum).not.toBeNull();
expect(typeof component.builtComponentChecksum).toBe('string');
}
});
it('should have correct application config', async () => {
@@ -28,7 +43,7 @@ export const defineManifestTests = (appPath: string): void => {
const manifest = await fs.readJson(manifestOutputPath);
expect(manifest?.objects).toHaveLength(2);
expect(manifest?.serverlessFunctions).toHaveLength(4);
expect(manifest?.functions).toHaveLength(4);
expect(manifest?.frontComponents).toHaveLength(4);
expect(manifest?.roles).toHaveLength(2);
expect(manifest?.objectExtensions).toHaveLength(1);
@@ -25,7 +25,7 @@
]
}
],
"serverlessFunctions": [
"functions": [
{
"universalIdentifier": "e1e2e3e4-e5e6-4000-8000-000000000010",
"name": "my-function",
@@ -41,7 +41,8 @@
],
"handlerName": "myHandler",
"sourceHandlerPath": "my.function.ts",
"builtHandlerPath": "functions/my.function.mjs"
"builtHandlerPath": "functions/my.function.mjs",
"builtHandlerChecksum": "[checksum]"
}
],
"frontComponents": [
@@ -51,7 +52,8 @@
"description": "A root-level front component",
"componentName": "MyComponent",
"sourceComponentPath": "my.front-component.tsx",
"builtComponentPath": "front-components/my.front-component.mjs"
"builtComponentPath": "front-components/my.front-component.mjs",
"builtComponentChecksum": "[checksum]"
}
],
"roles": [
@@ -2,6 +2,8 @@ import * as fs from 'fs-extra';
import { join } from 'path';
import { type ApplicationManifest } from 'twenty-shared/application';
import { normalizeManifestForComparison } from '@/cli/__tests__/integration/utils/normalize-manifest.util';
export const defineManifestTests = (appPath: string): void => {
describe('manifest', () => {
it('should have generated manifest.json', async () => {
@@ -19,8 +21,28 @@ export const defineManifestTests = (appPath: string): void => {
expect(manifest.application).toEqual(expected.application);
expect(manifest.objects).toEqual(expected.objects);
expect(manifest.serverlessFunctions).toEqual(expected.serverlessFunctions);
expect(manifest.frontComponents).toEqual(expected.frontComponents);
expect(normalizeManifestForComparison({ functions: manifest.functions }).functions).toEqual(
normalizeManifestForComparison({ functions: expected.functions }).functions,
);
for (const fn of manifest.functions) {
expect(fn.builtHandlerChecksum).toBeDefined();
expect(fn.builtHandlerChecksum).not.toBeNull();
expect(typeof fn.builtHandlerChecksum).toBe('string');
}
expect(
normalizeManifestForComparison({ frontComponents: manifest.frontComponents }).frontComponents,
).toEqual(
normalizeManifestForComparison({ frontComponents: expected.frontComponents }).frontComponents,
);
for (const component of manifest.frontComponents ?? []) {
expect(component.builtComponentChecksum).toBeDefined();
expect(component.builtComponentChecksum).not.toBeNull();
expect(typeof component.builtComponentChecksum).toBe('string');
}
expect(manifest.roles).toEqual(expected.roles);
});
});
@@ -0,0 +1,20 @@
import { type ApplicationManifest } from 'twenty-shared/application';
// Replace dynamic checksum values with a placeholder for consistent comparisons
export const normalizeManifestForComparison = <
T extends Partial<ApplicationManifest>,
>(
manifest: T,
): T => ({
...manifest,
functions: manifest.functions?.map((fn) => ({
...fn,
builtHandlerChecksum: fn.builtHandlerChecksum ? '[checksum]' : null,
})),
frontComponents: manifest.frontComponents?.map((component) => ({
...component,
builtComponentChecksum: component.builtComponentChecksum
? '[checksum]'
: null,
})),
});