diff --git a/app/angular/src/builders/build-storybook/index.spec.ts b/app/angular/src/builders/build-storybook/index.spec.ts index c1c9b784daa1..3bd4ffc84c08 100644 --- a/app/angular/src/builders/build-storybook/index.spec.ts +++ b/app/angular/src/builders/build-storybook/index.spec.ts @@ -5,6 +5,7 @@ import * as path from 'path'; const buildStandaloneMock = jest.fn(); jest.doMock('@storybook/angular/standalone', () => buildStandaloneMock); +jest.doMock('find-up', () => ({ sync: () => './storybook/tsconfig.ts' })); const cpSpawnMock = { spawn: jest.fn(), @@ -75,15 +76,13 @@ describe('Build Storybook Builder', () => { expect(buildStandaloneMock).toHaveBeenCalledWith({ angularBrowserTarget: 'angular-cli:build-2', angularBuilderContext: expect.any(Object), + angularBuilderOptions: {}, configDir: '.storybook', - docsMode: false, loglevel: undefined, quiet: false, outputDir: 'storybook-static', mode: 'static', - compodoc: false, - compodocArgs: ['-e', 'json'], - tsConfig: 'src/tsconfig.app.json', + tsConfig: './storybook/tsconfig.ts', }); }); @@ -102,14 +101,12 @@ describe('Build Storybook Builder', () => { expect(buildStandaloneMock).toHaveBeenCalledWith({ angularBrowserTarget: null, angularBuilderContext: expect.any(Object), + angularBuilderOptions: {}, configDir: '.storybook', - docsMode: false, loglevel: undefined, quiet: false, outputDir: 'storybook-static', mode: 'static', - compodoc: false, - compodocArgs: ['-e', 'json'], tsConfig: 'path/to/tsConfig.json', }); }); @@ -147,7 +144,7 @@ describe('Build Storybook Builder', () => { expect(output.success).toBeTruthy(); expect(cpSpawnMock.spawn).toHaveBeenCalledWith('compodoc', [ '-p', - 'src/tsconfig.app.json', + './storybook/tsconfig.ts', '-d', '', '-e', @@ -156,15 +153,39 @@ describe('Build Storybook Builder', () => { expect(buildStandaloneMock).toHaveBeenCalledWith({ angularBrowserTarget: 'angular-cli:build-2', angularBuilderContext: expect.any(Object), + angularBuilderOptions: {}, configDir: '.storybook', - docsMode: false, loglevel: undefined, quiet: false, outputDir: 'storybook-static', mode: 'static', - compodoc: true, - compodocArgs: ['-e', 'json'], - tsConfig: 'src/tsconfig.app.json', + tsConfig: './storybook/tsconfig.ts', + }); + }); + + it('should start storybook with styles options', async () => { + const run = await architect.scheduleBuilder('@storybook/angular:build-storybook', { + tsConfig: 'path/to/tsConfig.json', + compodoc: false, + styles: ['style.scss'], + }); + + const output = await run.result; + + await run.stop(); + + expect(output.success).toBeTruthy(); + expect(cpSpawnMock.spawn).not.toHaveBeenCalledWith(); + expect(buildStandaloneMock).toHaveBeenCalledWith({ + angularBrowserTarget: null, + angularBuilderContext: expect.any(Object), + angularBuilderOptions: { styles: ['style.scss'] }, + configDir: '.storybook', + loglevel: undefined, + quiet: false, + outputDir: 'storybook-static', + mode: 'static', + tsConfig: 'path/to/tsConfig.json', }); }); }); diff --git a/app/angular/src/builders/build-storybook/index.ts b/app/angular/src/builders/build-storybook/index.ts index 64827de6e809..6108034e9a34 100644 --- a/app/angular/src/builders/build-storybook/index.ts +++ b/app/angular/src/builders/build-storybook/index.ts @@ -9,10 +9,15 @@ import { JsonObject } from '@angular-devkit/core'; import { from, Observable, of, throwError } from 'rxjs'; import { CLIOptions } from '@storybook/core-common'; import { catchError, map, mapTo, switchMap } from 'rxjs/operators'; +import { sync as findUpSync } from 'find-up'; // eslint-disable-next-line import/no-extraneous-dependencies import buildStandalone, { StandaloneOptions } from '@storybook/angular/standalone'; -import { BrowserBuilderOptions } from '@angular-devkit/build-angular'; +import { + BrowserBuilderOptions, + ExtraEntryPoint, + StylePreprocessorOptions, +} from '@angular-devkit/build-angular'; import { runCompodoc } from '../utils/run-compodoc'; import { buildStandaloneErrorHandler } from '../utils/build-standalone-errors-handler'; @@ -21,6 +26,8 @@ export type StorybookBuilderOptions = JsonObject & { tsConfig?: string; compodoc: boolean; compodocArgs: string[]; + styles?: ExtraEntryPoint[]; + stylePreprocessorOptions?: StylePreprocessorOptions; } & Pick< // makes sure the option exists CLIOptions, @@ -46,12 +53,29 @@ function commandBuilder( return runCompodoc$.pipe(mapTo({ tsConfig })); }), map(({ tsConfig }) => { - const { browserTarget, ...otherOptions } = options; + const { + browserTarget, + stylePreprocessorOptions, + styles, + configDir, + docs, + loglevel, + outputDir, + quiet, + } = options; const standaloneOptions: StandaloneOptions = { - ...otherOptions, + configDir, + docs, + loglevel, + outputDir, + quiet, angularBrowserTarget: browserTarget, angularBuilderContext: context, + angularBuilderOptions: { + ...(stylePreprocessorOptions ? { stylePreprocessorOptions } : {}), + ...(styles ? { styles } : {}), + }, tsConfig, }; return standaloneOptions; @@ -76,7 +100,10 @@ async function setup(options: StorybookBuilderOptions, context: BuilderContext) } return { - tsConfig: options.tsConfig ?? browserOptions.tsConfig ?? undefined, + tsConfig: + options.tsConfig ?? + findUpSync('tsconfig.json', { cwd: options.configDir }) ?? + browserOptions.tsConfig, }; } diff --git a/app/angular/src/builders/build-storybook/schema.json b/app/angular/src/builders/build-storybook/schema.json index a4e894863922..d20f55765ebf 100644 --- a/app/angular/src/builders/build-storybook/schema.json +++ b/app/angular/src/builders/build-storybook/schema.json @@ -51,15 +51,62 @@ "items": { "type": "string" } + }, + "styles": { + "type": "array", + "description": "Global styles to be included in the build.", + "items": { + "$ref": "#/definitions/extraEntryPoint" + }, + "default": "" + }, + "stylePreprocessorOptions": { + "description": "Options to pass to style preprocessors.", + "type": "object", + "properties": { + "includePaths": { + "description": "Paths to include. Paths will be resolved to workspace root.", + "type": "array", + "items": { + "type": "string" + }, + "default": [] + } + }, + "additionalProperties": false, + "default": "" } }, "additionalProperties": false, - "oneOf": [ - { - "required": ["browserTarget"] - }, - { - "required": ["tsConfig"] + "definitions": { + "extraEntryPoint": { + "oneOf": [ + { + "type": "object", + "properties": { + "input": { + "type": "string", + "description": "The file to include." + }, + "bundleName": { + "type": "string", + "pattern": "^[\\w\\-.]*$", + "description": "The bundle name for this extra entry point." + }, + "inject": { + "type": "boolean", + "description": "If the bundle will be referenced in the HTML file.", + "default": true + } + }, + "additionalProperties": false, + "required": ["input"] + }, + { + "type": "string", + "description": "The file to include." + } + ] } - ] + } } diff --git a/app/angular/src/builders/start-storybook/index.spec.ts b/app/angular/src/builders/start-storybook/index.spec.ts index 57463172af23..2b4893eda268 100644 --- a/app/angular/src/builders/start-storybook/index.spec.ts +++ b/app/angular/src/builders/start-storybook/index.spec.ts @@ -5,6 +5,7 @@ import * as path from 'path'; const buildStandaloneMock = jest.fn(); jest.doMock('@storybook/angular/standalone', () => buildStandaloneMock); +jest.doMock('find-up', () => ({ sync: () => './storybook/tsconfig.ts' })); const cpSpawnMock = { spawn: jest.fn(), @@ -76,9 +77,9 @@ describe('Start Storybook Builder', () => { expect(buildStandaloneMock).toHaveBeenCalledWith({ angularBrowserTarget: 'angular-cli:build-2', angularBuilderContext: expect.any(Object), + angularBuilderOptions: {}, ci: false, configDir: '.storybook', - docsMode: false, host: 'localhost', https: false, port: 4400, @@ -87,9 +88,7 @@ describe('Start Storybook Builder', () => { sslCa: undefined, sslCert: undefined, sslKey: undefined, - compodoc: false, - compodocArgs: ['-e', 'json'], - tsConfig: 'src/tsconfig.app.json', + tsConfig: './storybook/tsconfig.ts', }); }); @@ -109,9 +108,9 @@ describe('Start Storybook Builder', () => { expect(buildStandaloneMock).toHaveBeenCalledWith({ angularBrowserTarget: null, angularBuilderContext: expect.any(Object), + angularBuilderOptions: {}, ci: false, configDir: '.storybook', - docsMode: false, host: 'localhost', https: false, port: 4400, @@ -120,8 +119,6 @@ describe('Start Storybook Builder', () => { sslCa: undefined, sslCert: undefined, sslKey: undefined, - compodoc: false, - compodocArgs: ['-e', 'json'], tsConfig: 'path/to/tsConfig.json', }); }); @@ -159,7 +156,7 @@ describe('Start Storybook Builder', () => { expect(output.success).toBeTruthy(); expect(cpSpawnMock.spawn).toHaveBeenCalledWith('compodoc', [ '-p', - 'src/tsconfig.app.json', + './storybook/tsconfig.ts', '-d', '', '-e', @@ -168,9 +165,9 @@ describe('Start Storybook Builder', () => { expect(buildStandaloneMock).toHaveBeenCalledWith({ angularBrowserTarget: 'angular-cli:build-2', angularBuilderContext: expect.any(Object), + angularBuilderOptions: {}, ci: false, configDir: '.storybook', - docsMode: false, host: 'localhost', https: false, port: 9009, @@ -179,9 +176,41 @@ describe('Start Storybook Builder', () => { sslCa: undefined, sslCert: undefined, sslKey: undefined, - compodoc: true, - compodocArgs: ['-e', 'json'], - tsConfig: 'src/tsconfig.app.json', + tsConfig: './storybook/tsconfig.ts', + }); + }); + + it('should start storybook with styles options', async () => { + const run = await architect.scheduleBuilder('@storybook/angular:start-storybook', { + tsConfig: 'path/to/tsConfig.json', + port: 4400, + compodoc: false, + styles: ['src/styles.css'], + }); + + const output = await run.result; + + await run.stop(); + + expect(output.success).toBeTruthy(); + expect(cpSpawnMock.spawn).not.toHaveBeenCalledWith(); + expect(buildStandaloneMock).toHaveBeenCalledWith({ + angularBrowserTarget: null, + angularBuilderContext: expect.any(Object), + angularBuilderOptions: { + styles: ['src/styles.css'], + }, + ci: false, + configDir: '.storybook', + host: 'localhost', + https: false, + port: 4400, + quiet: false, + smokeTest: false, + sslCa: undefined, + sslCert: undefined, + sslKey: undefined, + tsConfig: 'path/to/tsConfig.json', }); }); }); diff --git a/app/angular/src/builders/start-storybook/index.ts b/app/angular/src/builders/start-storybook/index.ts index eade7127f29f..4f80db9866b2 100644 --- a/app/angular/src/builders/start-storybook/index.ts +++ b/app/angular/src/builders/start-storybook/index.ts @@ -6,10 +6,15 @@ import { Target, } from '@angular-devkit/architect'; import { JsonObject } from '@angular-devkit/core'; -import { BrowserBuilderOptions } from '@angular-devkit/build-angular'; +import { + BrowserBuilderOptions, + ExtraEntryPoint, + StylePreprocessorOptions, +} from '@angular-devkit/build-angular'; import { from, Observable, of } from 'rxjs'; import { CLIOptions } from '@storybook/core-common'; import { map, switchMap, mapTo } from 'rxjs/operators'; +import { sync as findUpSync } from 'find-up'; // eslint-disable-next-line import/no-extraneous-dependencies import buildStandalone, { StandaloneOptions } from '@storybook/angular/standalone'; @@ -21,6 +26,8 @@ export type StorybookBuilderOptions = JsonObject & { tsConfig?: string; compodoc: boolean; compodocArgs: string[]; + styles?: ExtraEntryPoint[]; + stylePreprocessorOptions?: StylePreprocessorOptions; } & Pick< // makes sure the option exists CLIOptions, @@ -56,12 +63,41 @@ function commandBuilder( return runCompodoc$.pipe(mapTo({ tsConfig })); }), map(({ tsConfig }) => { - const { browserTarget, ...otherOptions } = options; + const { + browserTarget, + stylePreprocessorOptions, + styles, + ci, + configDir, + docs, + host, + https, + port, + quiet, + smokeTest, + sslCa, + sslCert, + sslKey, + } = options; const standaloneOptions: StandaloneOptions = { - ...otherOptions, + ci, + configDir, + docs, + host, + https, + port, + quiet, + smokeTest, + sslCa, + sslCert, + sslKey, angularBrowserTarget: browserTarget, angularBuilderContext: context, + angularBuilderOptions: { + ...(stylePreprocessorOptions ? { stylePreprocessorOptions } : {}), + ...(styles ? { styles } : {}), + }, tsConfig, }; @@ -87,7 +123,10 @@ async function setup(options: StorybookBuilderOptions, context: BuilderContext) } return { - tsConfig: options.tsConfig ?? browserOptions.tsConfig ?? undefined, + tsConfig: + options.tsConfig ?? + findUpSync('tsconfig.json', { cwd: options.configDir }) ?? + browserOptions.tsConfig, }; } function runInstance(options: StandaloneOptions) { diff --git a/app/angular/src/builders/start-storybook/schema.json b/app/angular/src/builders/start-storybook/schema.json index 6c429fd26850..9911fe8e7bed 100644 --- a/app/angular/src/builders/start-storybook/schema.json +++ b/app/angular/src/builders/start-storybook/schema.json @@ -78,7 +78,62 @@ "items": { "type": "string" } + }, + "styles": { + "type": "array", + "description": "Global styles to be included in the build.", + "items": { + "$ref": "#/definitions/extraEntryPoint" + }, + "default": "" + }, + "stylePreprocessorOptions": { + "description": "Options to pass to style preprocessors.", + "type": "object", + "properties": { + "includePaths": { + "description": "Paths to include. Paths will be resolved to workspace root.", + "type": "array", + "items": { + "type": "string" + }, + "default": [] + } + }, + "additionalProperties": false, + "default": "" } }, - "additionalProperties": false + "additionalProperties": false, + "definitions": { + "extraEntryPoint": { + "oneOf": [ + { + "type": "object", + "properties": { + "input": { + "type": "string", + "description": "The file to include." + }, + "bundleName": { + "type": "string", + "pattern": "^[\\w\\-.]*$", + "description": "The bundle name for this extra entry point." + }, + "inject": { + "type": "boolean", + "description": "If the bundle will be referenced in the HTML file.", + "default": true + } + }, + "additionalProperties": false, + "required": ["input"] + }, + { + "type": "string", + "description": "The file to include." + } + ] + } + } } diff --git a/app/angular/src/server/framework-preset-angular-cli.ts b/app/angular/src/server/framework-preset-angular-cli.ts index 6d907a8d9302..12bfbf2f371d 100644 --- a/app/angular/src/server/framework-preset-angular-cli.ts +++ b/app/angular/src/server/framework-preset-angular-cli.ts @@ -114,12 +114,11 @@ async function getBuilderOptions( */ const builderOptions = { ...browserTargetOptions, - ...options.angularBuilderOptions, + ...(options.angularBuilderOptions as JsonObject), tsConfig: options.tsConfig ?? - options.angularBuilderOptions?.tsConfig ?? - browserTargetOptions.tsConfig ?? - findUpSync('tsconfig.json', { cwd: options.configDir }), + findUpSync('tsconfig.json', { cwd: options.configDir }) ?? + browserTargetOptions.tsConfig, }; logger.info(`=> Using angular project with "tsConfig:${builderOptions.tsConfig}"`); diff --git a/app/angular/src/server/options.ts b/app/angular/src/server/options.ts index 061841dfb52a..252519968710 100644 --- a/app/angular/src/server/options.ts +++ b/app/angular/src/server/options.ts @@ -2,13 +2,16 @@ import { sync } from 'read-pkg-up'; import { LoadOptions, Options as CoreOptions } from '@storybook/core-common'; import { BuilderContext } from '@angular-devkit/architect'; -import { JsonObject } from '@angular-devkit/core'; +import { ExtraEntryPoint, StylePreprocessorOptions } from '@angular-devkit/build-angular'; export type PresetOptions = CoreOptions & { /* Allow to get the options of a targeted "browser builder" */ angularBrowserTarget?: string | null; /* Defined set of options. These will take over priority from angularBrowserTarget options */ - angularBuilderOptions?: JsonObject | null; + angularBuilderOptions?: { + styles?: ExtraEntryPoint[]; + stylePreprocessorOptions?: StylePreprocessorOptions; + }; /* Angular context from builder */ angularBuilderContext?: BuilderContext | null; tsConfig?: string; diff --git a/app/angular/standalone.d.ts b/app/angular/standalone.d.ts index 481849593fce..0349246d5392 100644 --- a/app/angular/standalone.d.ts +++ b/app/angular/standalone.d.ts @@ -1,6 +1,7 @@ import { CLIOptions, LoadOptions, BuilderOptions } from '@storybook/core-common'; import { BuilderContext } from '@angular-devkit/architect'; -import { JsonObject } from '@angular-devkit/core'; +import { JsonValue } from '@angular-devkit/core'; +import { JsonSchema } from '@angular-devkit/core/src/json/schema'; export type StandaloneOptions = Partial< CLIOptions & @@ -8,7 +9,10 @@ export type StandaloneOptions = Partial< BuilderOptions & { mode?: 'static' | 'dev'; angularBrowserTarget?: string | null; - angularBuilderOptions?: JsonObject; + angularBuilderOptions?: JsonObject & { + styles?: ExtraEntryPoint[]; + stylePreprocessorOptions?: StylePreprocessorOptions; + }; angularBuilderContext?: BuilderContext | null; tsConfig?: string; }