Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow to specify an output prefix and load app id from appinfo #207

Merged
merged 2 commits into from
Jun 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 38 additions & 9 deletions lib/appConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,27 +7,32 @@
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'
import injectCSSPlugin from 'vite-plugin-css-injected-by-js'

type VitePluginInjectCSSOptions = Parameters<typeof injectCSSPlugin>[0]

export const appVersion = process.env.npm_package_version
export const sanitizeAppName = (appName: string) => appName.replace(/[/\\]/, '-')

export interface AppOptions extends Omit<BaseOptions, 'inlineCSS'> {
/**
* 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

/**
* 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
Expand Down Expand Up @@ -72,7 +77,6 @@ export interface AppOptions extends Omit<BaseOptions, 'inlineCSS'> {
export const createAppConfig = (entries: { [entryAlias: string]: string }, options: AppOptions = {}): UserConfigFn => {
// Add default options
options = {
appName: process.env.npm_package_name,
config: {},
nodePolyfills: {
protocolImports: true,
Expand All @@ -81,11 +85,36 @@ 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>([^<]+)<\/version>/i)[1]
const id = content.match(/<id>([^<]+)<\/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) => {
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)
Expand Down Expand Up @@ -162,17 +191,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'] } : {}),
Expand Down
46 changes: 46 additions & 0 deletions lib/utils/appinfo.ts
Original file line number Diff line number Diff line change
@@ -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
}
Loading