Skip to content

Commit

Permalink
Revert "fix(cli): remove configLoadingMode and use import-fresh (#2629)"
Browse files Browse the repository at this point in the history
This reverts commit 5c09c8c.
  • Loading branch information
AviVahl committed Jan 20, 2025
1 parent bfd75c8 commit 3b6f97f
Show file tree
Hide file tree
Showing 9 changed files with 299 additions and 6 deletions.
13 changes: 13 additions & 0 deletions packages/engine-cli/src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,17 @@ async function engine() {
description: 'Enable config build via config loaders',
default: false,
},
configLoadingMode: {
type: (value: string) => {
if (value === 'fresh' || value === 'watch' || value === 'require') {
return value;
} else {
throw new Error(`Invalid config loading mode: ${value}`);
}
},
description: 'Config loading mode (fresh, watch, require)',
default: undefined,
},
verbose: {
type: Boolean,
description: 'Verbose output',
Expand Down Expand Up @@ -184,6 +195,7 @@ async function engine() {
} else {
const dev = argv.flags.dev ?? argv.flags.watch;
const run = argv.flags.run ?? dev;
const configLoadingMode = argv.flags.configLoadingMode ?? (argv.flags.watch ? 'watch' : 'require');
const runtimeArgs = argv.flags.runtimeArgs;
addRuntimeArgsFlagsFromEngineConfig(engineConfig, argv.flags, runtimeArgs);

Expand All @@ -193,6 +205,7 @@ async function engine() {
runtimeArgs,
dev,
run,
configLoadingMode,
engineConfig,
});
}
Expand Down
7 changes: 6 additions & 1 deletion packages/engine-cli/src/engine-build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { createBuildEndPluginHook } from './esbuild-build-end-plugin.js';
import { loadConfigFile } from './load-config-file.js';
import { writeWatchSignal } from './watch-signal.js';
import { resolveBuildEntryPoints } from './resolve-build-configurations.js';
import { launchDashboardServer } from './launch-dashboard-server.js';
import { ConfigLoadingMode, launchDashboardServer } from './launch-dashboard-server.js';
import { createBuildConfiguration } from './create-environments-build-configuration.js';
import { readEntryPoints, writeEntryPoints } from './entrypoint-files.js';
import { EntryPoints, EntryPointsPaths } from './create-entrypoints.js';
Expand Down Expand Up @@ -40,6 +40,7 @@ export interface RunEngineOptions {
writeMetadataFiles?: boolean;
publicPath?: string;
publicConfigsRoute?: string;
configLoadingMode?: ConfigLoadingMode;
staticBuild?: boolean;
title?: string;
}
Expand All @@ -63,6 +64,7 @@ export async function runEngine({
runtimeArgs = {},
writeMetadataFiles = !watch,
publicConfigsRoute = 'configs',
configLoadingMode = 'require',
staticBuild = false,
title,
}: RunEngineOptions = {}): Promise<{
Expand Down Expand Up @@ -247,8 +249,11 @@ export async function runEngine({
configMapping,
runtimeOptions,
outputPath,
configLoadingMode,
analyze,
waitForWebRebuild,
buildConditions,
extensions,
);
if (watch) {
writeWatchSignal(outputPath, { isAliveUrl: `http://localhost:${devServer.port}/is_alive` });
Expand Down
2 changes: 1 addition & 1 deletion packages/engine-cli/src/import-fresh.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { Worker, isMainThread, parentPort, workerData } from 'node:worker_thread
export async function importFresh(filePath: string[], exportSymbolName?: string): Promise<unknown[]>;
export async function importFresh(filePath: string, exportSymbolName?: string): Promise<unknown>;
export async function importFresh(filePath: string | string[], exportSymbolName = 'default'): Promise<unknown> {
const worker = new Worker(new URL(import.meta.url), {
const worker = new Worker(import.meta.url, {
workerData: { filePath, exportSymbolName } satisfies ImportFreshWorkerData,
// doesn't seem to inherit two levels deep (Worker from Worker)
execArgv: [...process.execArgv],
Expand Down
1 change: 1 addition & 0 deletions packages/engine-cli/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export { ENGINE_CONFIG_FILE_NAME } from './find-features/build-constants.js';
export { loadFeatureDirectory } from './find-features/load-feature-directory.js';
export * from './find-features/merge.js';
export { extractModuleRequests } from './find-features/resolve-module-graph.js';
export { NodeConfigManager } from './node-config-manager.js';
export { resolveRuntimeOptions, type RunNodeManagerOptions } from './resolve-runtime-options.js';
export { RunningFeatureOptions, getRunningFeature } from './run-environment.js';
export { runLocalNodeManager } from './run-local-mode-manager.js';
Expand Down
22 changes: 22 additions & 0 deletions packages/engine-cli/src/launch-dashboard-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,11 @@ import { ConfigurationEnvironmentMapping, FeatureEnvironmentMapping } from '@wix
import express from 'express';
import { LaunchOptions, RouteMiddleware, launchServer } from './start-dev-server.js';
import { runLocalNodeManager } from './run-local-mode-manager.js';
import { NodeConfigManager } from './node-config-manager.js';
import type { StaticConfig } from './types.js';

export type ConfigLoadingMode = 'fresh' | 'watch' | 'require';

export async function launchDashboardServer(
rootDir: string,
serveStatic: StaticConfig[],
Expand All @@ -15,8 +18,11 @@ export async function launchDashboardServer(
configMapping: ConfigurationEnvironmentMapping,
runtimeOptions: Map<string, string | boolean | undefined>,
outputPath: string,
configLoadingMode: ConfigLoadingMode,
analyzeForBuild: () => Promise<unknown>,
waitForBuildReady?: (cb: () => void) => boolean,
buildConditions?: string[],
extensions?: string[],
): Promise<ReturnType<typeof launchServer>> {
const staticMiddlewares = serveStatic.map(({ route, directoryPath }) => ({
path: route,
Expand All @@ -29,7 +35,10 @@ export async function launchDashboardServer(
featureEnvironmentsMapping,
configMapping,
outputPath,
configLoadingMode,
waitForBuildReady,
buildConditions,
extensions,
);
const autoRunFeatureName = runtimeOptions.get('feature') as string | undefined;
if (autoRunFeatureName) {
Expand Down Expand Up @@ -95,10 +104,21 @@ function runOnDemandSingleEnvironment(
featureEnvironmentsMapping: FeatureEnvironmentMapping,
configMapping: ConfigurationEnvironmentMapping,
outputPath: string,
configLoadingMode: 'fresh' | 'watch' | 'require',
waitForBuildReady?: (cb: () => void) => boolean,
buildConditions?: string[],
extensions?: string[],
) {
let currentlyDisposing: Promise<unknown> | undefined;
const openManagers = new Map<string, Awaited<ReturnType<typeof runLocalNodeManager>>>();
const configManager =
configLoadingMode === 'fresh' || configLoadingMode === 'watch'
? new NodeConfigManager(configLoadingMode, {
absWorkingDir: rootDir,
conditions: buildConditions,
resolveExtensions: extensions,
})
: undefined;

async function run(featureName: string, configName: string, runtimeArgs: string) {
try {
Expand All @@ -122,6 +142,7 @@ function runOnDemandSingleEnvironment(
configMapping,
runOptions,
outputPath,
configManager,
{
routeMiddlewares: [
{
Expand All @@ -138,6 +159,7 @@ function runOnDemandSingleEnvironment(
async function disposeOpenManagers() {
await currentlyDisposing;
if (openManagers.size > 0) {
await configManager?.disposeAll();
const toDispose = [];
for (const { manager } of openManagers.values()) {
toDispose.push(manager.dispose());
Expand Down
166 changes: 166 additions & 0 deletions packages/engine-cli/src/node-config-manager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
import crypto from 'node:crypto';
import esbuild from 'esbuild';
import { join } from 'node:path';
import { deferred } from 'promise-assist';
import { importFresh } from './import-fresh.js';
import { createRequire } from 'node:module';

type BuildStats = {
error: unknown;
currentValue: Promise<unknown[]> | unknown[];
dispose(): Promise<void>;
build?: ReturnType<typeof deferred<unknown[]>>;
};

export class NodeConfigManager {
constructor(
mode: 'watch' | 'fresh' = 'watch',
private config: esbuild.BuildOptions,
) {
if (mode === 'watch') {
this.loadConfigs = (entryPoints: string[]) => this.watchConfigs(entryPoints);
} else {
this.loadConfigs = (entryPoints: string[]) => importFresh(entryPoints, 'default');
}
}
runningBuilds = new Map<string, BuildStats>();
hashConfig(entryPoints: string[]) {
return crypto.createHash('sha256').update(entryPoints.join(',')).digest('hex');
}
loadConfigs: (_entryPoints: string[]) => Promise<unknown[]>;
async watchConfigs(entryPoints: string[]) {
const key = this.hashConfig(entryPoints);
const currentBuild = this.runningBuilds.get(key);
if (!currentBuild) {
const buildStats: BuildStats = {
error: undefined,
// this is cannot be undefined after build
currentValue: undefined as any,
build: undefined,
dispose() {
return ctx.dispose();
},
};
this.runningBuilds.set(key, buildStats);

const ctx = await this.createBuildTask(entryPoints, {
onStart() {
const newBuild = deferred<unknown[]>();
newBuild.promise.catch(() => {
// we don't want to throw unhandled promise rejection when we reject the deferred promise we always wait for the promise
// noop
});
buildStats.error = undefined;
buildStats.currentValue = newBuild.promise;
buildStats.build = newBuild;
},
onEnd(files) {
buildStats.error = undefined;
buildStats.currentValue = files;
buildStats.build?.resolve(files);
},
onError(err) {
buildStats.error = err;
buildStats.build?.reject(err);
},
});

if (!buildStats.build) {
throw new Error('No build');
}

return buildStats.build.promise;
} else {
if (currentBuild.error) {
throw currentBuild.error as Error;
}
return currentBuild.currentValue;
}
}
disposeBuild(entryPoints: string[]): Promise<void> | void {
const key = this.hashConfig(entryPoints);
const build = this.runningBuilds.get(key);
if (build) {
this.runningBuilds.delete(key);
return build.dispose();
}
}
async disposeAll() {
for (const build of this.runningBuilds.values()) {
await build.dispose();
}
this.runningBuilds.clear();
}
async dispose() {
await this.disposeAll();
}
private async createBuildTask(
entryPoints: string[],
hooks: {
onStart(): void;
onEnd(files: unknown[]): void;
onError(err: unknown): void;
},
) {
const deferredStart = deferred<void>();
const absWorkingDir = this.config.absWorkingDir || process.cwd();
const ctx = await esbuild.context({
...this.config,
stdin: {
contents: `
${entryPoints.map((entry, i) => `import config_${i} from ${JSON.stringify(entry)};`).join('\n')}
export default [${entryPoints.map((_, i) => `config_${i}`).join(',')}];
`,
loader: 'js',
sourcefile: 'generated-configs-entry.js',
resolveDir: absWorkingDir,
},
plugins: [
{
name: 'on-build',
setup(build) {
build.onStart(() => {
hooks.onStart();
deferredStart.resolve();
});
build.onEnd(({ outputFiles }) => {
if (!outputFiles || outputFiles.length === 0) {
hooks.onError(new Error('No output files'));
return;
}
try {
const text = outputFiles[0]!.text;
const module = { exports: { default: undefined as any } };
const require = createRequire(import.meta.url);
// eslint-disable-next-line @typescript-eslint/no-implied-eval
new Function('module', 'exports', 'require', '__dirname', '__filename', text)(
module,
module.exports,
require,
absWorkingDir,
join(absWorkingDir, 'generated-configs-entry.js'),
);
if (!Array.isArray(module.exports.default)) {
throw new Error('Expected default export to be an array');
}
hooks.onEnd(module.exports.default as unknown[]);
} catch (err) {
hooks.onError(err);
return;
}
});
},
},
...(this.config.plugins || []),
],
platform: 'node',
treeShaking: true,
format: 'cjs',
bundle: true,
write: false,
});

await Promise.all([ctx.watch(), deferredStart.promise]);
return ctx;
}
}
5 changes: 3 additions & 2 deletions packages/engine-cli/src/run-local-mode-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,21 @@ import {
} from '@wixc3/engine-runtime-node';
import { join } from 'node:path';
import { pathToFileURL } from 'node:url';
import { importFresh } from './import-fresh.js';
import { NodeConfigManager } from './node-config-manager.js';

export async function runLocalNodeManager(
featureEnvironmentsMapping: FeatureEnvironmentMapping,
configMapping: ConfigurationEnvironmentMapping,
execRuntimeOptions: Map<string, string | boolean | undefined>,
outputPath: string = 'dist-engine',
configManager?: NodeConfigManager,
serverOptions?: ILaunchHttpServerOptions,
) {
const manager = new NodeEnvManager(
{ url: pathToFileURL(join(outputPath, 'node/')).href },
featureEnvironmentsMapping,
configMapping,
(entryPoints: string[]) => importFresh(entryPoints, 'default'),
configManager?.loadConfigs,
);
const { port } = await manager.autoLaunch(execRuntimeOptions, serverOptions);
return { port, manager };
Expand Down
Loading

0 comments on commit 3b6f97f

Please sign in to comment.