UNPKG

@shockpkg/ria-packager

Version:

Package for creating Adobe AIR packages

880 lines (689 loc) 22.3 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); Object.defineProperty(exports, "__esModule", { value: true }); exports.PackagerBundleMac = void 0; var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty")); var _path = require("path"); var _fsExtra = _interopRequireDefault(require("fs-extra")); var _archiveFiles = require("@shockpkg/archive-files"); var _iconEncoder = require("@shockpkg/icon-encoder"); var _plistDom = require("@shockpkg/plist-dom"); var _util = require("../../util"); var _bundle = require("../bundle"); /** * PackagerBundleMac constructor. * * @param path Output path. */ class PackagerBundleMac extends _bundle.PackagerBundle { /** * Create modern application icon file. * Enables higher resolutions icons and PNG compression. * Default false uses the legacy formats of the official packager. */ /** * Create modern document type icon file. * Enables higher resolutions icons and PNG compression. * Default false uses the legacy formats of the official packager. */ /** * Info.plist file. */ /** * Info.plist data. */ /** * PkgInfo file. */ /** * PkgInfo data. */ /** * Remove unnecessary OS files from older versions of the framework. * The official packages will include these if they are present in SDK. */ /** * Optionally preserve resource mtime. * The official packager does not preserve resource mtimes. */ /** * Value of CFBundleDocumentTypes CFBundleTypeName is description, not name. * Tag value controlled by application descriptor. * Set to false to match the behavior of SDK versions before 3.2.0.2070. */ /** * Add an NSHighResolutionCapable tag to the Info.plist file. * Tag value controlled by application descriptor. * Set to false to match the behavior of SDK versions before 3.6.0.6090. */ /** * Remove unnecessary helper files from framework. * Set to false to match the behavior of SDK versions before 25.0.0.134. */ /** * Add an NSAppTransportSecurity tag to the Info.plist file. * Tag value controlled by application descriptor. * Set to false to match the behavior of SDK versions before 27.0.0.128. */ /** * Extension mapping. */ constructor(path) { super(path); (0, _defineProperty2.default)(this, "applicationIconModern", false); (0, _defineProperty2.default)(this, "fileTypeIconModern", false); (0, _defineProperty2.default)(this, "infoPlistFile", null); (0, _defineProperty2.default)(this, "infoPlistData", null); (0, _defineProperty2.default)(this, "pkgInfoFile", null); (0, _defineProperty2.default)(this, "pkgInfoData", null); (0, _defineProperty2.default)(this, "frameworkCleanOsFiles", false); (0, _defineProperty2.default)(this, "preserveResourceMtime", false); (0, _defineProperty2.default)(this, "plistDocumentTypeNameIsDescription", true); (0, _defineProperty2.default)(this, "plistHighResolutionCapable", true); (0, _defineProperty2.default)(this, "frameworkCleanHelpers", true); (0, _defineProperty2.default)(this, "plistHasAppTransportSecurity", true); (0, _defineProperty2.default)(this, "_extensionMapping", new Map()); } /** * If Info.plist is specified. * * @returns Is specified. */ get hasInfoPlist() { return !!(this.infoPlistData || this.infoPlistFile); } /** * If PkgInfo is specified. * * @returns Is specified. */ get hasPkgInfo() { return !!(this.pkgInfoData || this.pkgInfoFile); } /** * Get app icns file. * * @returns File name. */ get appIcnsFile() { return 'Icon.icns'; } /** * Get app icns path. * * @returns File path. */ get appIcnsPath() { return `Contents/Resources/${this.appIcnsFile}`; } /** * Get app Info.plist path. * * @returns File path. */ get appInfoPlistPath() { return 'Contents/Info.plist'; } /** * Get app PkgInfo path. * * @returns File path. */ get appPkgInfoPath() { return 'Contents/PkgInfo'; } /** * Get app resources path. * * @returns Resources path. */ get appResourcesPath() { return 'Contents/Resources'; } /** * Get app binary path. * * @returns Binary path. */ getAppBinaryPath() { return `Contents/MacOS/${this._getFilename()}`; } /** * Get app framework path. * * @returns Framework path. */ getAppFrameworkPath() { return 'Contents/Frameworks/Adobe AIR.framework'; } /** * Get SDK binary path. * * @returns Binary path. */ getSdkBinaryPath() { return 'lib/nai/lib/CaptiveAppEntry'; } /** * Get SDK framework path. * * @returns Framework path. */ getSdkFrameworkPath() { return 'runtimes/air-captive/mac/Adobe AIR.framework'; } /** * Get framework files excluded. * * @returns Excluded files in framework. */ getFrameworkExcludes() { const r = []; if (this.frameworkCleanHelpers) { // Some files used to create applications, not used after that. r.push('Versions/1.0/Adobe AIR_64 Helper', 'Versions/1.0/Resources/ExtendedAppEntryTemplate64'); } if (this.frameworkCleanOsFiles) { // Some empty junk likely leftover from an Apple ZIP file. r.push('Versions/1.0/Resources/__MACOSX'); } return r; } /** * Get Info.plist data if any specified, from data or file. * * @returns Info.plist data or null. */ async getInfoPlistData() { return this._dataFromValueOrFile(this.infoPlistData, this.infoPlistFile, '\n', 'utf8'); } /** * Get Info.plist data as DOM if any specified. * * @returns Info.plist DOM or null. */ async getInfoPlistDom() { const data = await this.getInfoPlistData(); if (!data) { return null; } const dom = new _plistDom.Plist(); dom.fromXml(data.toString('utf8')); return dom; } /** * Get Info.plist data as DOM if any specified, or default. * * @returns Info.plist DOM or null. */ async getInfoPlistDomOrDefault() { const dom = await this.getInfoPlistDom(); return dom || new _plistDom.Plist(); } /** * Get PkgInfo data if any specified, from data or file. * * @returns PkgInfo data or null. */ async getPkgInfoData() { return this._dataFromValueOrFile(this.pkgInfoData, this.pkgInfoFile, null, 'ascii'); } /** * Get PkgInfo data if any specified, or default. * * @returns PkgInfo data. */ async getPkgInfoDataOrDefault() { const r = await this.getPkgInfoData(); return r || Buffer.from('APPL????', 'ascii'); } /** * Get file mode value. * * @param executable Is the entry executable. * @returns File mode. */ _getFileMode(executable) { return executable ? 0b111100100 : 0b110100100; } /** * Get plist CFBundleExecutable value. * * @returns The value or null if excluded. */ _getPlistCFBundleExecutable() { return new _plistDom.ValueString(this._getFilename()); } /** * Get plist CFBundleIdentifier value. * * @returns The value or null if excluded. */ _getPlistCFBundleIdentifier() { return new _plistDom.ValueString(this._getId()); } /** * Get plist CFBundleShortVersionString value. * * @returns The value or null if excluded. */ _getPlistCFBundleShortVersionString() { return new _plistDom.ValueString(this._getVersionNumber()); } /** * Get plist CFBundleGetInfoString value. * * @returns The value or null if excluded. */ _getPlistCFBundleGetInfoString() { // Strange when no copyright but matches official packager. const copyright = this._getCopyright(); const versionNumber = this._getVersionNumber(); const add = copyright ? ` ${copyright}` : ''; return new _plistDom.ValueString(`${versionNumber},${add}`); } /** * Get plist NSHumanReadableCopyright value. * * @returns The value or null if excluded. */ _getPlistNSHumanReadableCopyright() { return new _plistDom.ValueString(this._getCopyright() || ''); } /** * Get plist CFBundleIconFile value. * * @returns The value or null if excluded. */ _getPlistCFBundleIconFile() { const icon = this._getIcon(); return icon && this._uidIcon(icon) ? new _plistDom.ValueString(this.appIcnsFile) : null; } /** * Get plist CFBundleLocalizations value. * * @returns The value or null if excluded. */ _getPlistCFBundleLocalizations() { const langs = this._applicationInfoSupportedLanguages; const list = langs ? langs.trim().split(/\s+/) : null; return list && list.length ? new _plistDom.ValueArray(list.map(s => new _plistDom.ValueString(s))) : null; } /** * Get plist NSHighResolutionCapable value. * * @returns The value or null if excluded. */ _getPlistNSHighResolutionCapable() { return this.plistHighResolutionCapable ? new _plistDom.ValueBoolean(this._applicationInfoRequestedDisplayResolution === 'high') : null; } /** * Get plist NSAppTransportSecurity value. * * @returns The value or null if excluded. */ _getPlistNSAppTransportSecurity() { return this.plistHasAppTransportSecurity ? new _plistDom.ValueDict(new Map([['NSAllowsArbitraryLoads', new _plistDom.ValueBoolean(true)]])) : null; } /** * Get plist CFBundleDocumentTypes value. * * @returns The value or null if excluded. */ _getPlistCFBundleDocumentTypes() { const extensionMapping = this._extensionMapping; const fileTypes = this._applicationInfoFileTypes; if (!fileTypes || !fileTypes.size) { return null; } const useDesc = this.plistDocumentTypeNameIsDescription; const list = []; for (const [ext, info] of fileTypes) { const dict = new _plistDom.ValueDict(); const { value } = dict; value.set('CFBundleTypeExtensions', new _plistDom.ValueArray([new _plistDom.ValueString(ext)])); value.set('CFBundleTypeMIMETypes', new _plistDom.ValueArray([new _plistDom.ValueString(info.contentType)])); value.set('CFBundleTypeName', new _plistDom.ValueString(useDesc ? info.description || '' : info.name)); value.set('CFBundleTypeRole', new _plistDom.ValueString('Editor')); const iconFile = extensionMapping.get(ext); if (iconFile) { value.set('CFBundleTypeIconFile', new _plistDom.ValueString(iconFile)); } list.push(dict); } return new _plistDom.ValueArray(list); } /** * Get plist CFBundleAllowMixedLocalizations value. * * @returns The value or null if excluded. */ _getPlistCFBundleAllowMixedLocalizations() { return new _plistDom.ValueBoolean(true); } /** * Get plist CFBundlePackageType value. * * @returns The value or null if excluded. */ _getPlistCFBundlePackageType() { return new _plistDom.ValueString('APPL'); } /** * Get plist CFBundleInfoDictionaryVersion value. * * @returns The value or null if excluded. */ _getPlistCFBundleInfoDictionaryVersion() { return new _plistDom.ValueString('6.0'); } /** * Get plist LSMinimumSystemVersion value. * * @returns The value or null if excluded. */ _getPlistLSMinimumSystemVersion() { return new _plistDom.ValueString('10.6'); } /** * Get plist LSRequiresCarbon value. * * @returns The value or null if excluded. */ _getPlistLSRequiresCarbon() { return new _plistDom.ValueBoolean(true); } /** * Open implementation. * * @param applicationData The application descriptor data. */ async _open(applicationData) { this._extensionMapping.clear(); const appBinaryPath = this.getAppBinaryPath(); const appFrameworkPath = this.getAppFrameworkPath(); const sdkBinaryPath = this.getSdkBinaryPath(); const sdkFrameworkPath = this.getSdkFrameworkPath(); const frameworkExcludes = new Set(this.getFrameworkExcludes().map(s => s.toLowerCase())); const appBinaryPathFull = (0, _path.join)(this.path, appBinaryPath); const appFrameworkPathFull = (0, _path.join)(this.path, appFrameworkPath); let extractedBinary = false; let extractedFramework = false; // Extract everything needed from the SDK. const sdk = await this._openSdk(); await sdk.read(async entry => { // Ignore any resource forks. if (entry.type === _archiveFiles.PathType.RESOURCE_FORK) { return true; } const path = entry.volumePath; // Extract if the binary. const sdkBinaryPathRel = (0, _util.pathRelativeBase)(path, sdkBinaryPath, true); if (sdkBinaryPathRel !== null) { const dest = (0, _path.join)(appBinaryPathFull, sdkBinaryPathRel); await entry.extract(dest); extractedBinary = true; return true; } // Extract if the framework. const frameworkPathRel = (0, _util.pathRelativeBase)(path, sdkFrameworkPath, true); if (frameworkPathRel !== null) { // If this is an excluded path, skip over. if (frameworkExcludes.has(frameworkPathRel.toLowerCase())) { return null; } const dest = (0, _path.join)(appFrameworkPathFull, frameworkPathRel); await entry.extract(dest); extractedFramework = true; return true; } // Optimization to avoid walking unrelated directories if possible. return (0, _util.pathRelativeBaseMatch)(sdkFrameworkPath, path, true) || (0, _util.pathRelativeBaseMatch)(sdkBinaryPath, path, true) ? true : null; }); // Check that required components were extracted. if (!extractedBinary) { throw new Error(`Failed to locate binary in SDK: ${sdkBinaryPath}`); } if (!extractedFramework) { throw new Error(`Failed to locate framework in SDK: ${sdkFrameworkPath}`); } } /** * Close implementation. */ async _close() { await this._writeApplicationIcon(); await this._writeFileTypeIcons(); await this._writePkgInfo(); await this._writeInfoPlist(); this._extensionMapping.clear(); } /** * Write resource with data implementation. * * @param destination Packaged file relative destination. * @param data Resource data. * @param options Resource options. */ async _writeResource(destination, data, options) { // Write resource to file. const mode = this._getFileMode(options.executable || false); const dest = this._getResourcePath(destination); await _fsExtra.default.outputFile(dest, data, { mode }); // Optionally preserve mtime information. if (this.preserveResourceMtime) { const { mtime } = options; if (mtime) { await _fsExtra.default.utimes(dest, mtime, mtime); } } } /** * Get path to a resource file. * * @param parts Path parts. * @returns Full path. */ _getResourcePath(...parts) { return (0, _path.join)(this.path, this.appResourcesPath, ...parts); } /** * Write the application icon if specified. */ async _writeApplicationIcon() { const icon = this._getIcon(); if (!icon || !this._uidIcon(icon)) { return; } const modern = this.applicationIconModern; const path = (0, _path.join)(this.path, this.appIcnsPath); // Write either a modern or a reference icon. if (modern) { // eslint-disable-next-line no-await-in-loop await this._writeIconModern(path, icon); } else { // eslint-disable-next-line no-await-in-loop await this._writeIconReference(path, icon); } } /** * Write file type icons, creating extension name mapping. * Avoids writting duplicate icons where the file/data is the same. */ async _writeFileTypeIcons() { this._extensionMapping.clear(); const mapping = this._extensionMapping; const fileIcons = this._getFileTypes(); if (!fileIcons) { return; } const modern = this.fileTypeIconModern; const did = new Map(); let index = 0; for (const [ext, { icon }] of fileIcons) { if (!icon) { continue; } // Compute a unique identifier for the used icon set paths. const uid = this._uidIcon(icon); if (!uid) { continue; } // Check if file was already generated for this icon set. const done = did.get(uid); if (done) { mapping.set(ext, done); continue; } // Compute name for this icon set and cache. const name = this._getFileTypeIconName(index++); did.set(uid, name); mapping.set(ext, name); // Get the path to write to. const path = (0, _path.join)(this.path, this._getFileTypeIconPath(name)); // Write either a modern or a reference icon. if (modern) { // eslint-disable-next-line no-await-in-loop await this._writeIconModern(path, icon); } else { // eslint-disable-next-line no-await-in-loop await this._writeIconReference(path, icon); } } } /** * Write out PkgInfo file. */ async _writePkgInfo() { const data = await this.getPkgInfoDataOrDefault(); const path = (0, _path.join)(this.path, this.appPkgInfoPath); await _fsExtra.default.writeFile(path, data); } /** * Generate Info.plist DOM object. * * @returns Plist DOM. */ async _generateInfoPlist() { const dom = await this.getInfoPlistDomOrDefault(); const existing = dom.value && dom.value.type === _plistDom.ValueDict.TYPE ? dom.value : null; const dict = dom.value = new _plistDom.ValueDict(); // A little helper to set values only once. const done = new Set(); const val = (key, value) => { if (done.has(key)) { return; } if (value) { dict.value.set(key, value); } done.add(key); }; // Set all the values in the same order as the official packager. val('CFBundleAllowMixedLocalizations', this._getPlistCFBundleAllowMixedLocalizations()); val('CFBundlePackageType', this._getPlistCFBundlePackageType()); val('CFBundleInfoDictionaryVersion', this._getPlistCFBundleInfoDictionaryVersion()); val('LSMinimumSystemVersion', this._getPlistLSMinimumSystemVersion()); val('LSRequiresCarbon', this._getPlistLSRequiresCarbon()); val('CFBundleIdentifier', this._getPlistCFBundleIdentifier()); val('CFBundleGetInfoString', this._getPlistCFBundleGetInfoString()); val('CFBundleShortVersionString', this._getPlistCFBundleShortVersionString()); val('NSHumanReadableCopyright', this._getPlistNSHumanReadableCopyright()); val('CFBundleExecutable', this._getPlistCFBundleExecutable()); val('NSAppTransportSecurity', this._getPlistNSAppTransportSecurity()); val('NSHighResolutionCapable', this._getPlistNSHighResolutionCapable()); val('CFBundleIconFile', this._getPlistCFBundleIconFile()); val('CFBundleDocumentTypes', this._getPlistCFBundleDocumentTypes()); val('CFBundleLocalizations', this._getPlistCFBundleLocalizations()); // If any existing values, copy the ones not already set. if (existing) { for (const [key, value] of existing.value) { val(key, value); } } return dom; } /** * Write out Info.plist file. */ async _writeInfoPlist() { const dom = await this._generateInfoPlist(); const path = (0, _path.join)(this.path, this.appInfoPlistPath); await _fsExtra.default.writeFile(path, dom.toXml({ indentRoot: true, indentString: ' ' })); } /** * Calculate UID for icon, or null if none of required icons set. * * @param icon Icon info. * @returns UID string or null. */ _uidIcon(icon) { const paths = [icon.image16x16, icon.image32x32, icon.image48x48, icon.image128x128]; // If none set, skip. let has = false; for (const p of paths) { if (p) { has = true; break; } } // Compute a unique identifier for the used icon set paths. return has ? paths.map(s => `${s ? s.length : 0}:${s || ''}`).join('|') : null; } /** * Write icon matching official format. * * @param path Icon path. * @param icon Icon info. */ async _writeIconReference(path, icon) { // Add icons in the same order official packager would use. const icns = new _iconEncoder.IconIcns(); for (const [path, types] of [[icon.image16x16, ['is32', 's8mk']], [icon.image32x32, ['il32', 'l8mk']], [icon.image48x48, ['ih32', 'h8mk']], [icon.image128x128, ['it32', 't8mk']]]) { if (!path) { continue; } // eslint-disable-next-line no-await-in-loop const data = await _fsExtra.default.readFile(this._getResourcePath(path)); icns.addFromPng(data, types); } await _fsExtra.default.writeFile(path, icns.encode()); } /** * Write icon using modern format. * * @param path Icon path. * @param icon Icon info. * @returns Icon written. */ async _writeIconModern(path, icon) { // Add icons in the same order iconutil would. const icns = new _iconEncoder.IconIcns(); for (const [path, type] of [// [icon.image64x64, 'ic12'], [icon.image128x128, 'ic07'], // [icon.image256x256, 'ic13'], // [icon.image256x256, 'ic08'], [icon.image16x16, 'ic04'], [icon.image512x512, 'ic14'], [icon.image512x512, 'ic09'], [icon.image32x32, 'ic05'], [icon.image1024x1024, 'ic10'], [icon.image32x32, 'ic11']]) { if (!path) { continue; } // eslint-disable-next-line no-await-in-loop const data = await _fsExtra.default.readFile(this._getResourcePath(path)); icns.addFromPng(data, [type]); } await _fsExtra.default.writeFile(path, icns.encode()); } /** * Get path for a file type icon file. * * @param name File name. * @returns File path. */ _getFileTypeIconPath(name) { return (0, _path.join)('Contents', 'Resources', name); } /** * Get name for a file type icon file. * * @param index Unique index. * @returns File name. */ _getFileTypeIconName(index) { return `DocumentIcon${index}.icns`; } } exports.PackagerBundleMac = PackagerBundleMac; //# sourceMappingURL=mac.js.map