| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272 |
- /* eslint-disable no-console */
- import { LanguageCode } from '@vendure/common/lib/generated-types';
- import { AdminUiAppConfig, AdminUiAppDevModeConfig } from '@vendure/common/lib/shared-types';
- import { ChildProcess, spawn } from 'child_process';
- import { FSWatcher, watch as chokidarWatch } from 'chokidar';
- import * as fs from 'fs-extra';
- import * as path from 'path';
- import { DEFAULT_BASE_HREF, MODULES_OUTPUT_DIR } from './constants';
- import { copyGlobalStyleFile, setBaseHref, setupScaffold } from './scaffold';
- import { getAllTranslationFiles, mergeExtensionTranslations } from './translations';
- import {
- Extension,
- StaticAssetDefinition,
- UiExtensionCompilerOptions,
- UiExtensionCompilerProcessArgument,
- } from './types';
- import {
- copyStaticAsset,
- copyUiDevkit,
- getStaticAssetPath,
- isAdminUiExtension,
- isGlobalStylesExtension,
- isStaticAssetExtension,
- isTranslationExtension,
- normalizeExtensions,
- shouldUseYarn,
- } from './utils';
- /**
- * @description
- * Compiles the Admin UI app with the specified extensions.
- *
- * @docsCategory UiDevkit
- */
- export function compileUiExtensions(
- options: UiExtensionCompilerOptions,
- ): AdminUiAppConfig | AdminUiAppDevModeConfig {
- const { outputPath, baseHref, devMode, watchPort, extensions, command, additionalProcessArguments } =
- options;
- const usingYarn = options.command && options.command === 'npm' ? false : shouldUseYarn();
- if (devMode) {
- return runWatchMode(
- outputPath,
- baseHref || DEFAULT_BASE_HREF,
- watchPort || 4200,
- extensions,
- usingYarn,
- additionalProcessArguments,
- );
- } else {
- return runCompileMode(
- outputPath,
- baseHref || DEFAULT_BASE_HREF,
- extensions,
- usingYarn,
- additionalProcessArguments,
- );
- }
- }
- function runCompileMode(
- outputPath: string,
- baseHref: string,
- extensions: Extension[],
- usingYarn: boolean,
- args?: UiExtensionCompilerProcessArgument[],
- ): AdminUiAppConfig {
- const cmd = usingYarn ? 'yarn' : 'npm';
- const distPath = path.join(outputPath, 'dist');
- const compile = () =>
- new Promise<void>(async (resolve, reject) => {
- await setupScaffold(outputPath, extensions);
- await setBaseHref(outputPath, baseHref);
- const commandArgs = ['run', 'build', ...buildProcessArguments(args)];
- if (!usingYarn) {
- // npm requires `--` before any command line args being passed to a script
- commandArgs.splice(2, 0, '--');
- }
- const buildProcess = spawn(cmd, commandArgs, {
- cwd: outputPath,
- shell: true,
- stdio: 'inherit',
- });
- buildProcess.on('close', code => {
- if (code !== 0) {
- reject(code);
- } else {
- resolve();
- }
- });
- });
- return {
- path: distPath,
- compile,
- route: baseHrefToRoute(baseHref),
- };
- }
- function runWatchMode(
- outputPath: string,
- baseHref: string,
- port: number,
- extensions: Extension[],
- usingYarn: boolean,
- args?: UiExtensionCompilerProcessArgument[],
- ): AdminUiAppDevModeConfig {
- const cmd = usingYarn ? 'yarn' : 'npm';
- const devkitPath = require.resolve('@vendure/ui-devkit');
- let buildProcess: ChildProcess;
- let watcher: FSWatcher | undefined;
- let close: () => void = () => {
- /* */
- };
- const compile = () =>
- new Promise<void>(async (resolve, reject) => {
- await setupScaffold(outputPath, extensions);
- await setBaseHref(outputPath, baseHref);
- const adminUiExtensions = extensions.filter(isAdminUiExtension);
- const normalizedExtensions = normalizeExtensions(adminUiExtensions);
- const globalStylesExtensions = extensions.filter(isGlobalStylesExtension);
- const staticAssetExtensions = extensions.filter(isStaticAssetExtension);
- const allTranslationFiles = getAllTranslationFiles(extensions.filter(isTranslationExtension));
- buildProcess = spawn(cmd, ['run', 'start', `--port=${port}`, ...buildProcessArguments(args)], {
- cwd: outputPath,
- shell: true,
- stdio: 'inherit',
- });
- buildProcess.on('close', code => {
- if (code !== 0) {
- reject(code);
- } else {
- resolve();
- }
- close();
- });
- for (const extension of normalizedExtensions) {
- if (!watcher) {
- watcher = chokidarWatch(extension.extensionPath, {
- depth: 4,
- ignored: '**/node_modules/',
- });
- } else {
- watcher.add(extension.extensionPath);
- }
- }
- for (const extension of staticAssetExtensions) {
- for (const staticAssetDef of extension.staticAssets) {
- const assetPath = getStaticAssetPath(staticAssetDef);
- if (!watcher) {
- watcher = chokidarWatch(assetPath);
- } else {
- watcher.add(assetPath);
- }
- }
- }
- for (const extension of globalStylesExtensions) {
- const globalStylePaths = Array.isArray(extension.globalStyles)
- ? extension.globalStyles
- : [extension.globalStyles];
- for (const stylePath of globalStylePaths) {
- if (!watcher) {
- watcher = chokidarWatch(stylePath);
- } else {
- watcher.add(stylePath);
- }
- }
- }
- for (const translationFiles of Object.values(allTranslationFiles)) {
- if (!translationFiles) {
- continue;
- }
- for (const file of translationFiles) {
- if (!watcher) {
- watcher = chokidarWatch(file);
- } else {
- watcher.add(file);
- }
- }
- }
- if (watcher) {
- // watch the ui-devkit package files too
- watcher.add(devkitPath);
- }
- if (watcher) {
- const allStaticAssetDefs = staticAssetExtensions.reduce(
- (defs, e) => [...defs, ...(e.staticAssets || [])],
- [] as StaticAssetDefinition[],
- );
- const allGlobalStyles = globalStylesExtensions.reduce(
- (defs, e) => [
- ...defs,
- ...(Array.isArray(e.globalStyles) ? e.globalStyles : [e.globalStyles]),
- ],
- [] as string[],
- );
- watcher.on('change', async filePath => {
- const extension = normalizedExtensions.find(e => filePath.includes(e.extensionPath));
- if (extension) {
- const outputDir = path.join(outputPath, MODULES_OUTPUT_DIR, extension.id);
- const filePart = path.relative(extension.extensionPath, filePath);
- const dest = path.join(outputDir, filePart);
- await fs.copyFile(filePath, dest);
- }
- if (filePath.includes(devkitPath)) {
- copyUiDevkit(outputPath);
- }
- for (const staticAssetDef of allStaticAssetDefs) {
- const assetPath = getStaticAssetPath(staticAssetDef);
- if (filePath.includes(assetPath)) {
- await copyStaticAsset(outputPath, staticAssetDef);
- return;
- }
- }
- for (const stylePath of allGlobalStyles) {
- if (filePath.includes(stylePath)) {
- await copyGlobalStyleFile(outputPath, stylePath);
- return;
- }
- }
- for (const languageCode of Object.keys(allTranslationFiles)) {
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
- const translationFiles = allTranslationFiles[languageCode as LanguageCode]!;
- for (const file of translationFiles) {
- if (filePath.includes(path.normalize(file))) {
- await mergeExtensionTranslations(outputPath, {
- [languageCode]: translationFiles,
- });
- }
- }
- }
- });
- }
- resolve();
- });
- close = () => {
- if (watcher) {
- void watcher.close();
- }
- if (buildProcess) {
- buildProcess.kill();
- }
- };
- process.on('SIGINT', close);
- return { sourcePath: outputPath, port, compile, route: baseHrefToRoute(baseHref) };
- }
- function buildProcessArguments(args?: UiExtensionCompilerProcessArgument[]): string[] {
- return (args ?? []).map(arg => {
- if (Array.isArray(arg)) {
- const [key, value] = arg;
- return `${key}=${value as string}`;
- }
- return arg;
- });
- }
- function baseHrefToRoute(baseHref: string): string {
- return baseHref.replace(/^\//, '').replace(/\/$/, '');
- }
|