From fee455d37da3bc67eb15d2ac47463d9d1259130e Mon Sep 17 00:00:00 2001 From: Ferdinand Thiessen Date: Thu, 20 Jun 2024 14:31:46 +0200 Subject: [PATCH 1/2] feat(app-config): Allow to specify assets prefix Signed-off-by: Ferdinand Thiessen --- lib/appConfig.ts | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/lib/appConfig.ts b/lib/appConfig.ts index 5242917..86e24d2 100644 --- a/lib/appConfig.ts +++ b/lib/appConfig.ts @@ -28,6 +28,12 @@ export interface AppOptions extends Omit { */ appName?: string + /** + * Prefix to use for assets and chunks + * @default '{appName}-' + */ + assetsPrefix?: string + /** * Inject all styles inside the javascript bundle instead of emitting a .css file * @default false @@ -86,6 +92,8 @@ export const createAppConfig = (entries: { [entryAlias: string]: string }, optio config: async (env) => { console.info(`Building ${options.appName} for ${env.mode}`) + const assetsPrefix = (options.assetsPrefix ?? `${options.appName}-`).replace(/[/\\]/, '-') + // This config is used to extend or override our base config // Make sure we get a user config and not a promise or a user config function const userConfig = await Promise.resolve(typeof options.config === 'function' ? options.config(env) : options.config) @@ -162,17 +170,17 @@ export const createAppConfig = (entries: { [entryAlias: string]: string }, optio if (/png|jpe?g|svg|gif|tiff|bmp|ico/i.test(extType)) { return 'img/[name][extname]' } else if (/css/i.test(extType)) { - return `css/${sanitizeAppName(options.appName)}-[name].css` + return `css/${assetsPrefix}[name].css` } else if (/woff2?|ttf|otf/i.test(extType)) { return 'css/fonts/[name][extname]' } return 'dist/[name]-[hash][extname]' }, entryFileNames: () => { - return `js/${sanitizeAppName(options.appName)}-[name].mjs` + return `js/${assetsPrefix}[name].mjs` }, chunkFileNames: () => { - return 'js/[name]-[hash].mjs' + return 'js/[name].chunk.mjs' }, manualChunks: { ...(options?.coreJS ? { polyfill: ['core-js'] } : {}), From 6e8da979371a0af20aa50925c99ff7f04a48c61f Mon Sep 17 00:00:00 2001 From: Ferdinand Thiessen Date: Thu, 20 Jun 2024 14:33:14 +0200 Subject: [PATCH 2/2] fix(app-config): Load app id from appinfo if possible Signed-off-by: Ferdinand Thiessen --- lib/appConfig.ts | 33 +++++++++++++++++++++++++------ lib/utils/appinfo.ts | 46 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 73 insertions(+), 6 deletions(-) create mode 100644 lib/utils/appinfo.ts diff --git a/lib/appConfig.ts b/lib/appConfig.ts index 86e24d2..e328579 100644 --- a/lib/appConfig.ts +++ b/lib/appConfig.ts @@ -7,9 +7,12 @@ import type { Plugin, UserConfig, UserConfigFn } from 'vite' import type { BaseOptions, NodePolyfillsOptions } from './baseConfig.js' +import { readFileSync } from 'node:fs' import { relative } from 'node:path' +import { cwd } from 'node:process' import { mergeConfig } from 'vite' import { createBaseConfig } from './baseConfig.js' +import { findAppinfo } from './utils/appinfo.js' import EmptyJSDirPlugin from './plugins/EmptyJSDir.js' import replace from '@rollup/plugin-replace' @@ -17,14 +20,10 @@ import injectCSSPlugin from 'vite-plugin-css-injected-by-js' type VitePluginInjectCSSOptions = Parameters[0] -export const appVersion = process.env.npm_package_version -export const sanitizeAppName = (appName: string) => appName.replace(/[/\\]/, '-') - export interface AppOptions extends Omit { /** - * Override the `appName`, by default the name from the `package.json` is used. + * Override the `appName`, by default the name from the `appinfo/info.xml` and if not found the name from `package.json` is used. * But if that name differs from the app id used for the Nextcloud app you need to override it. - * @default process.env.npm_package_name */ appName?: string @@ -78,7 +77,6 @@ export interface AppOptions extends Omit { export const createAppConfig = (entries: { [entryAlias: string]: string }, options: AppOptions = {}): UserConfigFn => { // Add default options options = { - appName: process.env.npm_package_name, config: {}, nodePolyfills: { protocolImports: true, @@ -87,6 +85,29 @@ export const createAppConfig = (entries: { [entryAlias: string]: string }, optio ...options, } + let appVersion: string + + const appinfo = findAppinfo(cwd()) + if (appinfo) { + const content = String(readFileSync(appinfo)) + const version = content.match(/([^<]+)<\/version>/i)[1] + const id = content.match(/([^<]+)<\/id>/i)[1] + + if (version) { + appVersion = version + } + if (id && !options.appName) { + options.appName = id + } + } else { + appVersion = process.env.npm_package_version + } + + if (!options.appName) { + console.warn('No app name configured, falling back to name from `package.json`') + options.appName = process.env.npm_package_name + } + return createBaseConfig({ ...(options as BaseOptions), config: async (env) => { diff --git a/lib/utils/appinfo.ts b/lib/utils/appinfo.ts new file mode 100644 index 0000000..44d6535 --- /dev/null +++ b/lib/utils/appinfo.ts @@ -0,0 +1,46 @@ +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import { lstatSync } from 'node:fs' +import { join, resolve, sep } from 'node:path' + +/** + * Check if a given path exists and is a directory + * + * @param {string} filePath The path + * @return {boolean} + */ +function isDirectory(filePath: string): boolean { + const stats = lstatSync(filePath, { throwIfNoEntry: false }) + return stats !== undefined && stats.isDirectory() +} + +/** + * Check if a given path exists and is a directory + * + * @param {string} filePath The path + * @return {boolean} + */ +function isFile(filePath: string): boolean { + const stats = lstatSync(filePath, { throwIfNoEntry: false }) + return stats !== undefined && stats.isFile() +} + +/** + * Find the path of nearest `appinfo/info.xml` relative to given path + * + * @param {string} currentPath The path to check for appinfo + * @return {string|undefined} Either the full path including the `info.xml` part or `undefined` if no found + */ +export function findAppinfo(currentPath: string): string | null { + while (currentPath && currentPath !== sep) { + const appinfoPath = join(currentPath, 'appinfo') + if (isDirectory(appinfoPath) && isFile(join(appinfoPath, 'info.xml'))) { + return join(appinfoPath, 'info.xml') + } + currentPath = resolve(currentPath, '..') + } + return undefined +}