From 586f6e6ea247f709e401c2e530a1c026feb276a8 Mon Sep 17 00:00:00 2001 From: Dimitris-Rafail Katsampas Date: Mon, 14 Oct 2024 12:33:43 +0300 Subject: [PATCH] feat: Added support for nameStyle option in Manifest (#95) Co-authored-by: Zyie <24736175+Zyie@users.noreply.github.com> --- .../assetpack/src/manifest/pixiManifest.ts | 72 +++++- .../assetpack/test/manifest/Manifest.test.ts | 230 ++++++++++++++++++ packages/docs/docs/guide/pipes/manifest.mdx | 14 +- 3 files changed, 305 insertions(+), 11 deletions(-) diff --git a/packages/assetpack/src/manifest/pixiManifest.ts b/packages/assetpack/src/manifest/pixiManifest.ts index d439f51..2013a1b 100644 --- a/packages/assetpack/src/manifest/pixiManifest.ts +++ b/packages/assetpack/src/manifest/pixiManifest.ts @@ -1,5 +1,5 @@ import fs from 'fs-extra'; -import { path, stripTags } from '../core/index.js'; +import { Logger, path, stripTags } from '../core/index.js'; import type { Asset, @@ -11,6 +11,7 @@ export interface PixiBundle { name: string; assets: PixiManifestEntry[]; + relativeName?: string; } export interface PixiManifest @@ -46,6 +47,11 @@ export interface PixiManifestOptions extends PluginOptions * if true, the metaData will be outputted in the data field of the manifest. */ includeMetaData?: boolean; + /** + * The name style for asset bundles in the manifest file. + * When set to relative, asset bundles will use their relative paths as names. + */ + nameStyle?: 'short' | 'relative'; /** * if true, the all tags will be outputted in the data.tags field of the manifest. * If false, only internal tags will be outputted to the data.tags field. All other tags will be outputted to the data field directly. @@ -85,6 +91,7 @@ export function pixiManifest(_options: PixiManifestOptions = {}): AssetPipe(); + const isNameStyleShort = options.nameStyle !== 'relative'; + const bundleNames = new Set(); + const duplicateBundleNames = new Set(); manifest.bundles.forEach((bundle) => - bundle.assets.forEach((asset) => nameMap.set(asset, asset.alias as string[]))); + { + if (isNameStyleShort) + { + if (bundleNames.has(bundle.name)) + { + duplicateBundleNames.add(bundle.name); + Logger.warn(`[AssetPack][manifest] Duplicate bundle name '${bundle.name}'. All bundles with that name will be renamed to their relative name instead.`); + } + else + { + bundleNames.add(bundle.name); + } + } + + bundle.assets.forEach((asset) => nameMap.set(asset, asset.alias as string[])); + }); const arrays = Array.from(nameMap.values()); const sets = arrays.map((arr) => new Set(arr)); @@ -134,6 +159,15 @@ function filterUniqueNames(manifest: PixiManifest) manifest.bundles.forEach((bundle) => { + if (isNameStyleShort) + { + // Switch to relative bundle name to avoid duplications + if (duplicateBundleNames.has(bundle.name)) + { + bundle.name = bundle.relativeName ?? bundle.name; + } + } + bundle.assets.forEach((asset) => { const names = nameMap.get(asset) as string[]; @@ -143,6 +177,21 @@ function filterUniqueNames(manifest: PixiManifest) }); } +function getRelativeBundleName(asset: Asset, entryPath: string): string +{ + let name = asset.filename; + let parent = asset.parent; + + // Exclude assets the paths of which equal to the entry path + while (parent && parent.path !== entryPath) + { + name = `${parent.filename}/${name}`; + parent = parent.parent; + } + + return stripTags(name); +} + function collectAssets( asset: Asset, options: PixiManifestOptions, @@ -163,10 +212,23 @@ function collectAssets( if (asset.metaData[tags!.manifest!]) { localBundle = { - name: stripTags(asset.filename), + name: options.nameStyle === 'relative' ? getRelativeBundleName(asset, entryPath) : stripTags(asset.filename), assets: [] }; + // This property helps rename duplicate bundle declarations + // Also, mark it as non-enumerable to prevent fs from including it into output + if (options.nameStyle !== 'relative') + { + Object.defineProperty(localBundle, 'relativeName', { + enumerable: false, + get() + { + return getRelativeBundleName(asset, entryPath); + } + }); + } + bundles.push(localBundle); } diff --git a/packages/assetpack/test/manifest/Manifest.test.ts b/packages/assetpack/test/manifest/Manifest.test.ts index d4ca29d..826c229 100644 --- a/packages/assetpack/test/manifest/Manifest.test.ts +++ b/packages/assetpack/test/manifest/Manifest.test.ts @@ -1038,6 +1038,236 @@ describe('Manifest', () => }); }); + it('should ensure sub-manifests are created correctly with short names', async () => + { + const testName = 'manifest-sub-manifest-short'; + const inputDir = getInputDir(pkg, testName); + const outputDir = getOutputDir(pkg, testName); + + createFolder(pkg, { + name: testName, + files: [], + folders: [ + { + name: 'sound{m}', + files: [ + { + name: '1.mp3', + content: assetPath('audio/1.mp3'), + }, + ], + folders: [], + }, + { + name: 'sound2{m}', + files: [ + { + name: '2.mp3', + content: assetPath('audio/1.mp3'), + }, + ], + folders: [], + }, + { + name: 'sound3{m}', + files: [ + { + name: '3.mp3', + content: assetPath('audio/1.mp3'), + }, + ], + folders: [ + { + name: 'sound2{m}', + files: [ + { + name: '2.mp3', + content: assetPath('audio/1.mp3'), + }, + ], + folders: [], + }, + ], + }, + ], + }); + + const assetpack = new AssetPack({ + entry: inputDir, + cacheLocation: getCacheDir(pkg, testName), + output: outputDir, + cache: false, + pipes: [ + pixiManifest({ + includeMetaData: false, + }), + ], + }); + + await assetpack.run(); + + expect(fs.readJSONSync(`${outputDir}/manifest.json`)).toEqual({ + bundles: [ + { + name: 'default', + assets: [], + }, + { + name: 'sound2', + assets: [ + { + alias: ['sound2/2.mp3'], + src: ['sound2/2.mp3'], + }, + ], + }, + { + name: 'sound3', + assets: [ + { + alias: ['sound3/3.mp3'], + src: ['sound3/3.mp3'], + }, + ], + }, + { + name: 'sound3/sound2', + assets: [ + { + alias: ['sound3/sound2/2.mp3'], + src: ['sound3/sound2/2.mp3'], + }, + ], + }, + { + name: 'sound', + assets: [ + { + alias: ['sound/1.mp3'], + src: ['sound/1.mp3'], + }, + ], + }, + ], + }); + }); + + it('should ensure sub-manifests are created correctly with relative names', async () => + { + const testName = 'manifest-sub-manifest-relative'; + const inputDir = getInputDir(pkg, testName); + const outputDir = getOutputDir(pkg, testName); + + createFolder(pkg, { + name: testName, + files: [], + folders: [ + { + name: 'sound{m}', + files: [ + { + name: '1.mp3', + content: assetPath('audio/1.mp3'), + }, + ], + folders: [ + { + name: 'sound2{m}', + files: [ + { + name: '2.mp3', + content: assetPath('audio/1.mp3'), + }, + ], + folders: [], + }, + ], + }, + { + name: 'sound3{m}', + files: [ + { + name: '3.mp3', + content: assetPath('audio/1.mp3'), + }, + ], + folders: [ + { + name: 'sound2{m}', + files: [ + { + name: '2.mp3', + content: assetPath('audio/1.mp3'), + }, + ], + folders: [], + }, + ], + }, + ], + }); + + const assetpack = new AssetPack({ + entry: inputDir, + cacheLocation: getCacheDir(pkg, testName), + output: outputDir, + cache: false, + pipes: [ + pixiManifest({ + includeMetaData: false, + nameStyle: 'relative', + }), + ], + }); + + await assetpack.run(); + + expect(fs.readJSONSync(`${outputDir}/manifest.json`)).toEqual({ + bundles: [ + { + name: 'default', + assets: [], + }, + { + name: 'sound3', + assets: [ + { + alias: ['sound3/3.mp3'], + src: ['sound3/3.mp3'], + }, + ], + }, + { + name: 'sound3/sound2', + assets: [ + { + alias: ['sound3/sound2/2.mp3'], + src: ['sound3/sound2/2.mp3'], + }, + ], + }, + { + name: 'sound', + assets: [ + { + alias: ['sound/1.mp3'], + src: ['sound/1.mp3'], + }, + ], + }, + { + name: 'sound/sound2', + assets: [ + { + alias: ['sound/sound2/2.mp3'], + src: ['sound/sound2/2.mp3'], + }, + ], + }, + ], + }); + }); + it('should ignore files with the mIgnore tag', async () => { const testName = 'manifest-ignore'; diff --git a/packages/docs/docs/guide/pipes/manifest.mdx b/packages/docs/docs/guide/pipes/manifest.mdx index 8133867..d0f60e8 100644 --- a/packages/docs/docs/guide/pipes/manifest.mdx +++ b/packages/docs/docs/guide/pipes/manifest.mdx @@ -40,6 +40,7 @@ export default { createShortcuts: false, trimExtensions: false, includeMetaData: true, + nameStyle: 'short' }) ], }; @@ -47,12 +48,13 @@ export default { ## API -| Option | Type | Description | -| --------------- | --------- | ---------------------------------------------------------------------------------------------- | -| output | `string` | The path to the manifest file.
Defaults to the output folder defined in your config. | -| createShortcuts | `boolean` | Whether to create the shortest possible alias for an asset.
Defaults to `false`. | -| trimExtensions | `boolean` | Whether to trim the extensions from the asset aliases.
Defaults to `false`. | -| includeMetaData | `boolean` | Whether to include the tags the asset has used in the manifest file.
Defaults to `true`. | +| Option | Type | Description | +| --------------- | ----------------- | ---------------------------------------------------------------------------------------------- | +| output | `string` | The path to the manifest file.
Defaults to the output folder defined in your config. | +| createShortcuts | `boolean` | Whether to create the shortest possible alias for an asset.
Defaults to `false`. | +| trimExtensions | `boolean` | Whether to trim the extensions from the asset aliases.
Defaults to `false`. | +| includeMetaData | `boolean` | Whether to include the tags the asset has used in the manifest file.
Defaults to `true`. | +| nameStyle | `short\|relative` | The name style for the bundle names in the manifest file.
Defaults to `short`. | ## Tags