UNPKG

@interaktiv/mibuilder-core

Version:

Core libraries to interact with MiBuilder projects.

456 lines (387 loc) 16.7 kB
"use strict"; var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard"); var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); Object.defineProperty(exports, "__esModule", { value: true }); exports.MiBuilderProject = exports.MiBuilderProjectJson = exports.MIBUILDER_PROJECT_TYPE = void 0; var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends")); var _path = _interopRequireDefault(require("path")); var _types = require("@interaktiv/types"); var _dxl = require("@interaktiv/dxl"); var _uuid = require("uuid"); var _configAggregator = require("./config/config-aggregator"); var _constants = require("./config/constants"); var _configFile = require("./config/config-file"); var _resolveConfigPath = require("./config/resolve-config-path"); var deliver = _interopRequireWildcard(require("./fastlane/deliver")); var _constants2 = require("./util/constants"); var _mibuilderError = require("./mibuilder-error"); var _tiapp = require("./tiapp"); /** * Returns the first key within the object that has an upper case first letter. * * @param {Object} data The object in which to check key casing. * @param {Object} sectionBlacklist properties in the object to exclude from the search. e.g. a blacklist of `["a"]` and data of `{ "a": { "B" : "b"}}` would ignore `B` because it is in the object value under `a`. * @return {String} The first upper case key */ function findUpperCaseKeys(data, sectionBlacklist = []) { let key; (0, _dxl.findKey)(data, (val, k) => { if (k[0] === k[0].toUpperCase()) { key = k; } else if ((0, _types.isJsonMap)(val)) { if (sectionBlacklist.includes(k)) { return key; } key = findUpperCaseKeys((0, _types.asJsonMap)(val)); } return key; }); return key; } const MIBUILDER_PROJECT_TYPE = { RA: 'Rechtsanwalt', STB: 'Steuerberater' }; /** * The mibuilder-project.json config object. This file determines if a folder * is a valid mibuilder project. * * *Note:* Any non-standard properties stored in * mibuilder-project.json should be in a top level property that represents your * project or plugin. * * ``` * const project = await MiBuilderProjectJson.retrieve(); * const myPluginProperties = project.get('myplugin') || {}; * * myPluginProperties.myprop = 'someValue'; * * project.set('myplugin', myPluginProperties); * * await project.write(); * ``` */ exports.MIBUILDER_PROJECT_TYPE = MIBUILDER_PROJECT_TYPE; class MiBuilderProjectJson extends _configFile.ConfigFile { static getFileName() { return _constants.MIBUILDER_PROJECT_JSON; } static getDefaultOptions(isGlobal = false) { const options = _configFile.ConfigFile.getDefaultOptions(isGlobal, MiBuilderProjectJson.getFileName()); options.isState = false; return options; } static getDefaultContents() { throw new _mibuilderError.MiBuilderError('No default contents for project config file'); } static async resolveRootDir(isGlobal) { if ((0, _types.isBoolean)(isGlobal) === false) { throw new _mibuilderError.MiBuilderError('isGlobal must be a boolean', 'InvalidTypeForIsGlobal'); } const configPath = await (0, _resolveConfigPath.resolveConfigPath)(isGlobal ? _constants.MIBUILDER_PROJECT_JSON_DIR : '', process.cwd()); return _path.default.dirname(configPath); } async read() { const contents = await super.read(); // Verify that the configObject does not have upper case keys; throw if it // does. Must be heads down camel case. const upperCaseKey = findUpperCaseKeys(this.toObject(), MiBuilderProjectJson.BLACKLIST); if ((0, _types.isString)(upperCaseKey)) { throw new _mibuilderError.MiBuilderError(`Die ${_constants.MIBUILDER_PROJECT_JSON} Datei enhält nicht das korrekte Format`, 'InvalidJsonCasing', ['Hast du `mibuilder migrate` bereits ausgeführt?', `Oder die ${_constants.MIBUILDER_PROJECT_JSON_LEGACY} Datei manuell in ${_constants.MIBUILDER_PROJECT_JSON} umbenannt?`, `Dann benenne die ${_constants.MIBUILDER_PROJECT_JSON} Datei wieder zurück zu ${_constants.MIBUILDER_PROJECT_JSON_LEGACY} um und führe den Befehl \`mibuilder migrate\` aus`]); } return contents; } write(newContents = this.getContents()) { // Verify that the configObject does not have upper case keys; throw if it does. Must be heads down camel case. const upperCaseKey = findUpperCaseKeys(newContents, MiBuilderProjectJson.BLACKLIST); if (upperCaseKey) throw _mibuilderError.MiBuilderError.wrap('InvalidJsonCasing'); // For now only write default properties omitting all other ones const defaultProperties = MiBuilderProjectJson.getDefaultProperties(); const defaultPropertiesPaths = defaultProperties.map(def => `${def.group}.${def.key}`); const reducedContents = defaultPropertiesPaths // Get entries with [path,value] .map(p => [p, (0, _types.get)(newContents, p)]) // Filter out ones that are there .filter(([, value]) => Boolean(value)) // Reduced to result obj .reduce((result, [p, value]) => { (0, _dxl.set)(result, p, value); return result; }, {}); return super.write(reducedContents); } getDefaultOptions(options = {}) { return (0, _extends2.default)({ isState: false }, options); } async init() { await super.init(); if (!MiBuilderProjectJson.defaultProperties) { MiBuilderProjectJson.defaultProperties = [// App { group: 'app', key: 'name' }, { group: 'app', key: 'bundleIdentifier' }, { group: 'app', key: 'packageName' }, { group: 'app', key: 'companyUrl' }, { group: 'app', key: 'copyright' }, { group: 'app', key: 'description' }, { group: 'app', key: 'guid' }, { group: 'app', key: 'projectName' }, { group: 'app', key: 'publisher' }, { group: 'app', key: 'servicecode' }, // Appc { group: 'appc', key: 'appcAppId' }, // Push { group: 'push', key: 'appKeyDevelopment' }, { group: 'push', key: 'appSecretDevelopment' }, { group: 'push', key: 'appKeyProduction' }, { group: 'push', key: 'appSecretProduction' }, { group: 'push', key: 'gcmSenderId' }, // Launch screens { group: 'launchScreen', key: 'color' }, // - iOS { group: 'ios', key: 'appleId' }, { group: 'ios', key: 'teamId' }, { group: 'ios', key: 'ascTeamId' }]; } } static getDefaultProperties() { if (!MiBuilderProjectJson.defaultProperties) { throw new _mibuilderError.MiBuilderError('MiBuilderProjectJson meta information has not been initialized.', null, ['Use MiBuilderProjectJson.create()']); } return MiBuilderProjectJson.defaultProperties; } } exports.MiBuilderProjectJson = MiBuilderProjectJson; MiBuilderProjectJson.BLACKLIST = ['']; /** * Represents an MiBuilder project directory. This directory contains a * {@link MiBuilderProjectJson} config file as well as a hidden .mibuilder * folder that contains all the other local project config files. * * ``` * const project = await MiBuilderProject.resolve(); * const projectJson = await project.resolveProjectConfig(); * * console.log(projectJson.sfdcLoginUrl); * ``` */ class MiBuilderProject { /** * Do not directly construct instances of this class * -- use {@link MiBuilderProject.resolve} instead. * * @ignore * @param {String} config The path to the project config */ constructor(config) { this.configPath = config; } /** * Get a Project from the given config path or the working directory. * * **Throws** *{@link MiBuilderError}{ name: 'InvalidProjectFileWorkspace' }* If the current folder is not located in a workspace. * * @param {String} [rootOrConfig] Either a direct path to the config or a path to the directory containing the config. * @return {Object<MiBuilderProject>} The MiBuilderProject instance */ static async resolve(rootOrConfig = _constants.MIBUILDER_PROJECT_JSON_DIR) { return new MiBuilderProject(await this.resolveConfigPath(rootOrConfig)); } static resolveConfigPath(rootOrConfig) { return (0, _resolveConfigPath.resolveConfigPath)(rootOrConfig, process.cwd()); } /** * Performs an upward directory search for an mibuilder project file. Returns * the absolute path to the project. * * **Throws** *{@link MiBuilderError}{ name: 'InvalidProjectFileWorkspace' }* If the current folder is not located in a workspace. * * **See** {@link traverseForFile} * * **See** [process.cwd()](https://nodejs.org/api/process.html#process_process_cwd) * * @param {String} [dir=localConfig.DIR] The directory path to start traversing from. * @return {Promise} A promise that resolves with the project path */ /** * Returns the project config path. * * @return {String} The project config path */ getProjectConfigPath() { return this.configPath; } /** * Returns the project path. * * @return {String} The project path */ getPath() { return this.path; } /** * Get the mibuilder-project.json config. * * *Note:* When reading values from {@link MiBuilderProjectJson}, it is * recommended to use {@link MiBuilderProject.resolveProjectConfig} instead. * * @return {Object<MiBuilderProjectJson>} The mibuilder project json instance */ async retrieveMiBuilderProjectJson() { const options = MiBuilderProjectJson.getDefaultOptions(false); if (this.mibuilderProjectJson == null) { const configFilePath = this.getProjectConfigPath(); options.rootDir = _path.default.dirname(configFilePath); options.filename = _path.default.basename(configFilePath); this.mibuilderProjectJson = await MiBuilderProjectJson.create(options); } return this.mibuilderProjectJson; } /** * The project config is resolved from local and global * {@link MiBuilderProjectJson}, {@link ConfigAggregator}, and a set of * defaults. It is recommended to use this when reading values from * MiBuilderProjectJson. * * @param {Boolean} [force=false] Forcing a fresh fetch of the cached config * @return {Promise<Object>} A resolved config object that contains a bunch of different properties, including some 3rd party custom properties. */ async resolveProjectConfig(force = false) { if (this.projectConfig && force !== true) return this.projectConfig; const global = await this.retrieveMiBuilderProjectJson(); await global.read(); const defaultValues = { app: { guid: (0, _uuid.v4)() } }; this.projectConfig = (0, _dxl.defaults)(global ? global.toObject() : {}, defaultValues); // Add fields in mibuilder-config.json this.projectConfig = (0, _dxl.merge)(this.projectConfig, (await _configAggregator.ConfigAggregator.create()).getConfig()); // Set `packageName` to `bundleIdentifier` if not given. Allows handling of // default tax consultant app with different Android Package Name / iOS // Bundle Identifier const bundleIdentifier = (0, _types.get)(this.projectConfig, 'app.bundleIdentifier'); const packageName = (0, _types.get)(this.projectConfig, 'app.packageName', bundleIdentifier); (0, _dxl.set)(this.projectConfig, 'app.packageName', packageName); // Sanitize app name and take it as project name const appName = (0, _types.get)(this.projectConfig, 'app.name', ''); let projectName = (0, _types.get)(this.projectConfig, 'app.projectName', null); if (projectName == null) projectName = (0, _tiapp.sanitizeAppName)(appName); (0, _dxl.set)(this.projectConfig, 'app.projectName', projectName); const sanitizedAppName = (0, _tiapp.sanitizeAppName)(appName); (0, _dxl.set)(this.projectConfig, 'app.sanitizedAppName', sanitizedAppName); // Create Activity Name for Android const activityName = (0, _tiapp.generateActivityName)(sanitizedAppName); (0, _dxl.set)(this.projectConfig, 'app.activityName', activityName); // Determine app type let appType = MIBUILDER_PROJECT_TYPE.STB; const servicecode = (0, _types.get)(this.projectConfig, 'app.servicecode'); if (servicecode.match(/(1)[0-9]{6}/)) { appType = MIBUILDER_PROJECT_TYPE.RA; } (0, _dxl.set)(this.projectConfig, 'app.type', appType); (0, _dxl.set)(this.projectConfig, 'app.isDefault', ['000000', '1000000'].includes(servicecode)); this.projectConfig.configDir = _path.default.dirname(this.getProjectConfigPath()); this.projectConfig.rootDir = _path.default.resolve((0, _types.get)(this.projectConfig, 'app.workspace'), (0, _types.get)(this.projectConfig, 'app.projectName')); this.path = this.projectConfig.rootDir; await this.resolveProjectPaths(); await (0, _dxl.mkdirp)(this.path); return this.projectConfig; } /** * Get a Project from the `app.workspace` set in the config. * * @return {Promise} A promise that resolves with the project path */ async resolveProjectPaths() { if (this.paths) return this.paths; if (this.projectConfig == null) { await this.resolveProjectConfig(); } const paths = {}; paths.rootDir = this.getPath(); paths.configDir = this.projectConfig.configDir; // Assign paths objects paths.assets = _path.default.resolve(paths.configDir, _constants2.ASSETS_DIR); paths.debug = _path.default.join(paths.configDir, _constants2.DEBUG_REPLACED_FILES_DIR); paths.projectPlatform = _path.default.resolve(paths.rootDir, _constants2.PLATFORM_DIR); paths.projectPlatformRes = _path.default.resolve(paths.rootDir, _constants2.PLATFORM_RES_DIR); paths.projectI18n = _path.default.resolve(paths.rootDir, _constants2.I18N_DIR); paths.projectResources = _path.default.resolve(paths.rootDir, _constants2.RESOURCES_DIR); paths.projectiOsSettings = _path.default.resolve(paths.rootDir, _constants2.SETTINGS_IOS_DIR); paths.settingsAboutPlistFile = _path.default.resolve(paths.projectiOsSettings, 'About.plist'); paths.webAppDir = _path.default.resolve(paths.rootDir, _constants2.WEB_APP_DIR); paths.remoteConfigFile = _path.default.resolve(paths.rootDir, _constants2.WEB_APP_DIR, this.projectConfig.app.servicecode, _constants.DATA_CONFIG_JSON); paths.splashes = { input: _path.default.resolve(paths.assets, 'splash.png'), inputIOS: _path.default.resolve(paths.assets, 'splash_ios.png'), inputAndroid: _path.default.resolve(paths.assets, 'splash_android.png'), inputDefault: _path.default.resolve(paths.assets, 'splash.png'), outputDir: paths.rootDir, launchLogo: { inputs: [// Maybe we should reuse an existent one? // path.join(paths.assets, 'LaunchLogo.png'), _path.default.resolve(paths.assets, 'appicon_ios.png'), _path.default.resolve(paths.assets, 'appicon.png')], temp: _path.default.resolve(paths.assets, 'LaunchLogo.png'), outputDir: _path.default.resolve(paths.rootDir) } }; paths.icons = { input: _path.default.resolve(paths.assets, 'appicon.png'), inputIOS: _path.default.resolve(paths.assets, 'appicon_ios.png'), inputAndroid: _path.default.resolve(paths.assets, 'appicon_android.png'), outputDir: paths.rootDir, outputIOS: _path.default.resolve(paths.rootDir, 'DefaultIcon-ios.png'), pushicon: { inputPath: _path.default.join(paths.projectPlatformRes, 'drawable-hdpi/appicon.png'), outputPath: _path.default.join(paths.projectPlatformRes, 'drawable/pushicon.png') }, 'pushicon-v21': { inputPath: _path.default.join(paths.assets, 'pushicon-v21.png'), outputPath: _path.default.join(paths.projectPlatformRes, 'drawable-v21/pushicon.png') } }; paths.fastlaneDir = _path.default.join(paths.rootDir, 'fastlane'); paths.fastlaneMetaDataDiriOS = _path.default.join(paths.fastlaneDir, 'ios', 'metadata'); paths.fastlaneScreenshotsDiriOS = _path.default.join(paths.fastlaneDir, 'ios', deliver.SCREENSHOTS_FOLDER_NAME); paths.fastlaneMetaDataDirAndroid = _path.default.join(paths.fastlaneDir, 'android', 'metadata'); paths.googleServicesConfig = _path.default.resolve(paths.configDir, 'google-services.json'); this.paths = paths; return paths; } } exports.MiBuilderProject = MiBuilderProject;