From ef6ccb078907043aa4960534cd0a767d97666f43 Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Wed, 26 Nov 2025 12:34:00 +0100 Subject: [PATCH] Angular: Don't kill dev command by using observables --- .../src/builders/start-storybook/index.ts | 217 ++++++++++-------- 1 file changed, 116 insertions(+), 101 deletions(-) diff --git a/code/frameworks/angular/src/builders/start-storybook/index.ts b/code/frameworks/angular/src/builders/start-storybook/index.ts index e09d6089fb3b..9562ab66ba0c 100644 --- a/code/frameworks/angular/src/builders/start-storybook/index.ts +++ b/code/frameworks/angular/src/builders/start-storybook/index.ts @@ -24,6 +24,7 @@ import type { StyleElement, } from '@angular-devkit/build-angular/src/builders/browser/schema'; import type { JsonObject } from '@angular-devkit/core'; +import { Observable } from 'rxjs'; import * as find from 'empathic/find'; import * as pkg from 'empathic/package'; @@ -72,110 +73,124 @@ export type StorybookBuilderOptions = JsonObject & { export type StorybookBuilderOutput = JsonObject & BuilderOutput & {}; -const commandBuilder: BuilderHandlerFn = async ( +const commandBuilder: BuilderHandlerFn = ( options, context -): Promise => { - logger.intro('Starting Storybook'); - - const { tsConfig } = await setup(options, context); - - const docTSConfig = find.up('tsconfig.doc.json', { - cwd: options.configDir, - last: getProjectRoot(), - }); - - if (options.compodoc) { - await runCompodoc( - { - compodocArgs: [...options.compodocArgs, ...(options.quiet ? ['--silent'] : [])], - tsconfig: docTSConfig ?? tsConfig, - }, - context - ); - } - - getEnvConfig(options, { - port: 'SBCONFIG_PORT', - host: 'SBCONFIG_HOSTNAME', - staticDir: 'SBCONFIG_STATIC_DIR', - configDir: 'SBCONFIG_CONFIG_DIR', - ci: 'CI', +): Observable => { + return new Observable((observer) => { + (async () => { + try { + logger.intro('Starting Storybook'); + + const { tsConfig } = await setup(options, context); + + const docTSConfig = find.up('tsconfig.doc.json', { + cwd: options.configDir, + last: getProjectRoot(), + }); + + if (options.compodoc) { + await runCompodoc( + { + compodocArgs: [...options.compodocArgs, ...(options.quiet ? ['--silent'] : [])], + tsconfig: docTSConfig ?? tsConfig, + }, + context + ); + } + + getEnvConfig(options, { + port: 'SBCONFIG_PORT', + host: 'SBCONFIG_HOSTNAME', + staticDir: 'SBCONFIG_STATIC_DIR', + configDir: 'SBCONFIG_CONFIG_DIR', + ci: 'CI', + }); + + options.port = parseInt(`${options.port}`, 10); + + const { + browserTarget, + stylePreprocessorOptions, + styles, + ci, + configDir, + docs, + host, + https, + port, + quiet, + enableProdMode = false, + smokeTest, + sslCa, + sslCert, + sslKey, + disableTelemetry, + assets, + initialPath, + open, + debugWebpack, + loglevel, + webpackStatsJson, + statsJson, + previewUrl, + sourceMap = false, + preserveSymlinks = false, + experimentalZoneless = !!(VERSION.major && Number(VERSION.major) >= 21), + } = options; + + const packageJsonPath = pkg.up({ cwd: __dirname }); + const packageJson = + packageJsonPath != null ? JSON.parse(readFileSync(packageJsonPath, 'utf8')) : null; + + const standaloneOptions: StandaloneOptions = { + packageJson, + ci, + configDir, + ...(docs ? { docs } : {}), + host, + https, + port, + quiet, + enableProdMode, + smokeTest, + sslCa, + sslCert, + sslKey, + disableTelemetry, + angularBrowserTarget: browserTarget, + angularBuilderContext: context, + angularBuilderOptions: { + ...(stylePreprocessorOptions ? { stylePreprocessorOptions } : {}), + ...(styles ? { styles } : {}), + ...(assets ? { assets } : {}), + preserveSymlinks, + sourceMap, + experimentalZoneless, + }, + tsConfig, + initialPath, + open, + debugWebpack, + webpackStatsJson, + statsJson, + loglevel, + previewUrl, + }; + + const startedPort = await runInstance(standaloneOptions); + + // Emit success output - the dev server is now running + observer.next({ success: true, info: { port: startedPort } } as BuilderOutput); + + // Don't call observer.complete() - this keeps the Observable alive + // so the dev server continues running. Architect will keep subscribing + // until the Observable completes, which allows watch mode to work. + } catch (error) { + observer.error(error); + } + })(); }); - - options.port = parseInt(`${options.port}`, 10); - - const { - browserTarget, - stylePreprocessorOptions, - styles, - ci, - configDir, - docs, - host, - https, - port, - quiet, - enableProdMode = false, - smokeTest, - sslCa, - sslCert, - sslKey, - disableTelemetry, - assets, - initialPath, - open, - debugWebpack, - loglevel, - webpackStatsJson, - statsJson, - previewUrl, - sourceMap = false, - preserveSymlinks = false, - experimentalZoneless = !!(VERSION.major && Number(VERSION.major) >= 21), - } = options; - - const packageJsonPath = pkg.up({ cwd: __dirname }); - const packageJson = - packageJsonPath != null ? JSON.parse(readFileSync(packageJsonPath, 'utf8')) : null; - - const standaloneOptions: StandaloneOptions = { - packageJson, - ci, - configDir, - ...(docs ? { docs } : {}), - host, - https, - port, - quiet, - enableProdMode, - smokeTest, - sslCa, - sslCert, - sslKey, - disableTelemetry, - angularBrowserTarget: browserTarget, - angularBuilderContext: context, - angularBuilderOptions: { - ...(stylePreprocessorOptions ? { stylePreprocessorOptions } : {}), - ...(styles ? { styles } : {}), - ...(assets ? { assets } : {}), - preserveSymlinks, - sourceMap, - experimentalZoneless, - }, - tsConfig, - initialPath, - open, - debugWebpack, - webpackStatsJson, - statsJson, - loglevel, - previewUrl, - }; - - const startedPort = await runInstance(standaloneOptions); - return { success: true, info: { port: startedPort } } as BuilderOutput; }; export default createBuilder(commandBuilder) as DevkitBuilder;