Implement sync in dev mode (#17405)
implement orchestrator to sync application in dev mode Log example when starting dev mode, delete and add back functions ```bash 👩💻 Workspace - default [init] 🚀 Starting Twenty Application Development Mode [init] 📁 App Path: /Users/martinmuller/Desktop/twenty/packages/twenty-apps/toto [dev-mode] File changed: /Users/martinmuller/Desktop/twenty/packages/twenty-apps/toto/.gitignore [dev-mode] File changed: /Users/martinmuller/Desktop/twenty/packages/twenty-apps/toto/.nvmrc [dev-mode] File changed: /Users/martinmuller/Desktop/twenty/packages/twenty-apps/toto/.yarnrc.yml [dev-mode] File changed: /Users/martinmuller/Desktop/twenty/packages/twenty-apps/toto/README.md [dev-mode] File changed: /Users/martinmuller/Desktop/twenty/packages/twenty-apps/toto/eslint.config.mjs [dev-mode] File changed: /Users/martinmuller/Desktop/twenty/packages/twenty-apps/toto/package.json [dev-mode] File changed: /Users/martinmuller/Desktop/twenty/packages/twenty-apps/toto/tsconfig.json [dev-mode] File changed: /Users/martinmuller/Desktop/twenty/packages/twenty-apps/toto/yarn.lock [dev-mode] File changed: /Users/martinmuller/Desktop/twenty/packages/twenty-apps/toto/.yarn/install-state.gz [dev-mode] File changed: /Users/martinmuller/Desktop/twenty/packages/twenty-apps/toto/src/ooo.front-component.tsx [dev-mode] File changed: /Users/martinmuller/Desktop/twenty/packages/twenty-apps/toto/src/tata.object.ts [dev-mode] File changed: /Users/martinmuller/Desktop/twenty/packages/twenty-apps/toto/src/app/application.config.ts [dev-mode] File changed: /Users/martinmuller/Desktop/twenty/packages/twenty-apps/toto/src/app/default-function.role.ts [dev-mode] File changed: /Users/martinmuller/Desktop/twenty/packages/twenty-apps/toto/src/app/hello-world-2.function.ts [dev-mode] File changed: /Users/martinmuller/Desktop/twenty/packages/twenty-apps/toto/src/app/hello-world-3.function.ts [dev-mode] File changed: /Users/martinmuller/Desktop/twenty/packages/twenty-apps/toto/src/app/hello-world.front-component.tsx [dev-mode] File changed: /Users/martinmuller/Desktop/twenty/packages/twenty-apps/toto/src/app/hello-world.function.ts [dev-mode] File changed: /Users/martinmuller/Desktop/twenty/packages/twenty-apps/toto/src/app/myObject.object.ts [dev-mode] File changed: /Users/martinmuller/Desktop/twenty/packages/twenty-apps/toto/src/utils/toto.ts [dev-mode] Building manifest... [dev-mode] Successfully built manifest [dev-mode] Syncing... [dev-mode] ✓ Successfully built src/ooo.front-component.tsx [dev-mode] Uploading .twenty/output/src/ooo.front-component.mjs... [dev-mode] ✓ Successfully built src/app/hello-world-2.function.ts [dev-mode] Uploading .twenty/output/src/app/hello-world-2.function.mjs... [dev-mode] ✓ Successfully built src/app/hello-world.front-component.tsx [dev-mode] Uploading .twenty/output/src/app/hello-world.front-component.mjs... [dev-mode] ✓ Successfully built src/app/hello-world-3.function.ts [dev-mode] Uploading .twenty/output/src/app/hello-world-3.function.mjs... [dev-mode] ✓ Successfully built src/app/hello-world.function.ts [dev-mode] Uploading .twenty/output/src/app/hello-world.function.mjs... [dev-mode] Successfully uploaded .twenty/output/src/ooo.front-component.mjs [dev-mode] Successfully uploaded .twenty/output/src/app/hello-world-3.function.mjs [dev-mode] Successfully uploaded .twenty/output/src/app/hello-world.front-component.mjs [dev-mode] Successfully uploaded .twenty/output/src/app/hello-world.function.mjs [dev-mode] Successfully uploaded .twenty/output/src/app/hello-world-2.function.mjs [dev-mode] ✓ Synced [dev-mode] ✓ Successfully built src/app/hello-world-2.function.ts [dev-mode] Uploading .twenty/output/src/app/hello-world-2.function.mjs... [dev-mode] Successfully uploaded .twenty/output/src/app/hello-world-2.function.mjs [dev-mode] File changed: /Users/martinmuller/Desktop/twenty/packages/twenty-apps/toto/src/app/hello-world-2.function.ts [dev-mode] Building manifest... [dev-mode] Successfully built manifest [dev-mode] Syncing... [dev-mode] ✓ Synced [dev-mode] Build failed: [dev-mode] Could not resolve "/Users/martinmuller/Desktop/twenty/packages/twenty-apps/toto/src/app/hello-world.function.ts" [dev-mode] Could not resolve "/Users/martinmuller/Desktop/twenty/packages/twenty-apps/toto/src/app/hello-world-3.function.ts" [dev-mode] Could not resolve "/Users/martinmuller/Desktop/twenty/packages/twenty-apps/toto/src/app/hello-world-2.function.ts" [dev-mode] File changed: /Users/martinmuller/Desktop/twenty/packages/twenty-apps/toto/src/app/hello-world.function.ts [dev-mode] File changed: /Users/martinmuller/Desktop/twenty/packages/twenty-apps/toto/src/app/hello-world-2.function.ts [dev-mode] File changed: /Users/martinmuller/Desktop/twenty/packages/twenty-apps/toto/src/app/hello-world-3.function.ts [dev-mode] Building manifest... [dev-mode] ⚠ No functions defined [dev-mode] Successfully built manifest [dev-mode] Syncing... 🗑️ Removed src/app/hello-world-2.function.mjs 🗑️ Removed src/app/hello-world-3.function.mjs 🗑️ Removed src/app/hello-world.function.mjs [dev-mode] ✓ Synced [dev-mode] File changed: /Users/martinmuller/Desktop/twenty/packages/twenty-apps/toto/src/app/hello-world-2.function.ts [dev-mode] File changed: /Users/martinmuller/Desktop/twenty/packages/twenty-apps/toto/src/app/hello-world-3.function.ts [dev-mode] File changed: /Users/martinmuller/Desktop/twenty/packages/twenty-apps/toto/src/app/hello-world.function.ts [dev-mode] Building manifest... [dev-mode] Successfully built manifest [dev-mode] Syncing... [dev-mode] ✓ Successfully built src/app/hello-world-2.function.ts [dev-mode] Uploading .twenty/output/src/app/hello-world-2.function.mjs... [dev-mode] ✓ Successfully built src/app/hello-world-3.function.ts [dev-mode] Uploading .twenty/output/src/app/hello-world-3.function.mjs... [dev-mode] ✓ Successfully built src/app/hello-world.function.ts [dev-mode] Uploading .twenty/output/src/app/hello-world.function.mjs... [dev-mode] Successfully uploaded .twenty/output/src/app/hello-world.function.mjs [dev-mode] Successfully uploaded .twenty/output/src/app/hello-world-2.function.mjs [dev-mode] Successfully uploaded .twenty/output/src/app/hello-world-3.function.mjs [dev-mode] ✓ Synced ```
This commit is contained in:
@@ -0,0 +1,203 @@
|
||||
import { createLogger } from '@/cli/utilities/build/common/logger';
|
||||
import {
|
||||
type ManifestBuildResult,
|
||||
runManifestBuild,
|
||||
updateManifestChecksum,
|
||||
} from '@/cli/utilities/build/manifest/manifest-build';
|
||||
import { writeManifestToOutput } from '@/cli/utilities/build/manifest/manifest-writer';
|
||||
import { ApiService } from '@/cli/utilities/api/api-service';
|
||||
import { FileUploader } from '@/cli/utilities/file/file-uploader';
|
||||
import { type FileFolder } from 'twenty-shared/types';
|
||||
import { validateManifest } from '@/cli/utilities/build/manifest/manifest-validate';
|
||||
|
||||
const logger = createLogger('dev-mode');
|
||||
|
||||
export type DevModeOrchestratorOptions = {
|
||||
appPath: string;
|
||||
debounceMs?: number;
|
||||
handleManifestBuilt: (result: ManifestBuildResult) => void | Promise<void>;
|
||||
};
|
||||
|
||||
export class DevModeOrchestrator {
|
||||
private appPath: string;
|
||||
private debounceMs: number;
|
||||
|
||||
private builtFileInfos = new Map<
|
||||
string,
|
||||
{ checksum: string; builtPath: string; fileFolder: FileFolder }
|
||||
>();
|
||||
|
||||
private fileUploader: FileUploader | null = null;
|
||||
private apiService = new ApiService();
|
||||
|
||||
private activeUploads = new Set<Promise<void>>();
|
||||
|
||||
private syncTimer: NodeJS.Timeout | null = null;
|
||||
private isSyncing = false;
|
||||
|
||||
private handleManifestBuilt: (
|
||||
result: ManifestBuildResult,
|
||||
) => void | Promise<void>;
|
||||
|
||||
constructor(options: DevModeOrchestratorOptions) {
|
||||
this.appPath = options.appPath;
|
||||
this.debounceMs = options.debounceMs ?? 200;
|
||||
this.handleManifestBuilt = options.handleManifestBuilt;
|
||||
}
|
||||
|
||||
async handleChangeDetected(filePath: string) {
|
||||
logger.log(`File changed: ${filePath}`);
|
||||
this.scheduleSync();
|
||||
}
|
||||
|
||||
handleFileBuildError(errors: string[]): void {
|
||||
logger.error(`Build failed:`);
|
||||
for (const error of errors) {
|
||||
logger.error(` ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
handleFileBuilt({
|
||||
fileFolder,
|
||||
builtPath,
|
||||
filePath,
|
||||
checksum,
|
||||
}: {
|
||||
fileFolder: FileFolder;
|
||||
builtPath: string;
|
||||
filePath: string;
|
||||
checksum: string;
|
||||
}): void {
|
||||
logger.success(`✓ Successfully built ${filePath}`);
|
||||
|
||||
this.builtFileInfos.set(builtPath, { checksum, builtPath, fileFolder });
|
||||
|
||||
if (this.fileUploader) {
|
||||
this.uploadFile(builtPath, fileFolder);
|
||||
}
|
||||
|
||||
this.scheduleSync();
|
||||
}
|
||||
|
||||
private uploadFile(builtPath: string, fileFolder: FileFolder): void {
|
||||
logger.log(`Uploading ${builtPath}...`);
|
||||
const uploadPromise = this.fileUploader!.uploadFile({
|
||||
builtPath,
|
||||
fileFolder,
|
||||
})
|
||||
.then((result) => {
|
||||
if (result.success) {
|
||||
logger.success(`Successfully uploaded ${builtPath}`);
|
||||
} else {
|
||||
logger.error(`Failed to upload ${builtPath}: ${result.error}`);
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
logger.error(`Upload failed for ${builtPath}: ${error}`);
|
||||
})
|
||||
.finally(() => {
|
||||
this.activeUploads.delete(uploadPromise);
|
||||
});
|
||||
|
||||
this.activeUploads.add(uploadPromise);
|
||||
}
|
||||
|
||||
private cancelPendingSync(): void {
|
||||
if (this.syncTimer) {
|
||||
clearTimeout(this.syncTimer);
|
||||
this.syncTimer = null;
|
||||
}
|
||||
}
|
||||
|
||||
private scheduleSync(): void {
|
||||
this.cancelPendingSync();
|
||||
|
||||
this.syncTimer = setTimeout(() => {
|
||||
this.syncTimer = null;
|
||||
void this.performSync();
|
||||
}, this.debounceMs);
|
||||
}
|
||||
|
||||
private async performSync(): Promise<void> {
|
||||
if (this.isSyncing) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.isSyncing = true;
|
||||
|
||||
try {
|
||||
logger.log(`Building manifest...`);
|
||||
|
||||
const result = await runManifestBuild(this.appPath);
|
||||
|
||||
if (result.error || !result.manifest) {
|
||||
logger.error(
|
||||
`Failed to build manifest: ${result.error ?? 'Unknown error'}`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const validation = validateManifest(result.manifest);
|
||||
|
||||
if (!validation.isValid) {
|
||||
const messages = validation.errors
|
||||
.map((e) => ` • ${e.path}: ${e.message}`)
|
||||
.join('\n');
|
||||
logger.error(`Invalid manifest:\n${messages}`);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (validation.warnings.length > 0) {
|
||||
for (const warning of validation.warnings) {
|
||||
const path = warning.path ? `${warning.path}: ` : '';
|
||||
logger.warn(`⚠ ${path}${warning.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
logger.success(`Successfully built manifest`);
|
||||
|
||||
await this.handleManifestBuilt(result);
|
||||
|
||||
if (!this.fileUploader) {
|
||||
this.fileUploader = new FileUploader({
|
||||
appPath: this.appPath,
|
||||
applicationUniversalIdentifier:
|
||||
result.manifest.application.universalIdentifier,
|
||||
});
|
||||
for (const [
|
||||
builtPath,
|
||||
{ fileFolder },
|
||||
] of this.builtFileInfos.entries()) {
|
||||
this.uploadFile(builtPath, fileFolder);
|
||||
}
|
||||
}
|
||||
|
||||
while (this.activeUploads.size > 0) {
|
||||
await Promise.all(this.activeUploads);
|
||||
}
|
||||
|
||||
const manifest = updateManifestChecksum({
|
||||
manifest: result.manifest,
|
||||
builtFileInfos: this.builtFileInfos,
|
||||
});
|
||||
|
||||
await writeManifestToOutput(this.appPath, manifest);
|
||||
|
||||
logger.log('Syncing...');
|
||||
const syncResult = await this.apiService.syncApplication(manifest);
|
||||
|
||||
if (syncResult.success) {
|
||||
logger.success('✓ Synced');
|
||||
} else {
|
||||
logger.error(
|
||||
`✗ Sync failed: ${JSON.stringify(syncResult.error, null, 2)}`,
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error(`✗ Sync failed: ${JSON.stringify(error)}`);
|
||||
} finally {
|
||||
this.isSyncing = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user