config.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", { value: true });
  3. exports.computeDefaultAppDirectory = exports.validateConfig = exports.doMergeConfigs = exports.getConfig = void 0;
  4. const builder_util_1 = require("builder-util");
  5. const fs_1 = require("builder-util/out/fs");
  6. const fs_extra_1 = require("fs-extra");
  7. const lazy_val_1 = require("lazy-val");
  8. const path = require("path");
  9. const read_config_file_1 = require("read-config-file");
  10. const rectCra_1 = require("../presets/rectCra");
  11. const version_1 = require("../version");
  12. // eslint-disable-next-line @typescript-eslint/no-var-requires
  13. const validateSchema = require("@develar/schema-utils");
  14. // https://github.com/electron-userland/electron-builder/issues/1847
  15. function mergePublish(config, configFromOptions) {
  16. // if config from disk doesn't have publish (or object), no need to handle, it will be simply merged by deepAssign
  17. const publish = Array.isArray(config.publish) ? configFromOptions.publish : null;
  18. if (publish != null) {
  19. delete configFromOptions.publish;
  20. }
  21. (0, builder_util_1.deepAssign)(config, configFromOptions);
  22. if (publish == null) {
  23. return;
  24. }
  25. const listOnDisk = config.publish;
  26. if (listOnDisk.length === 0) {
  27. config.publish = publish;
  28. }
  29. else {
  30. // apply to first
  31. Object.assign(listOnDisk[0], publish);
  32. }
  33. }
  34. async function getConfig(projectDir, configPath, configFromOptions, packageMetadata = new lazy_val_1.Lazy(() => (0, read_config_file_1.orNullIfFileNotExist)((0, fs_extra_1.readJson)(path.join(projectDir, "package.json"))))) {
  35. const configRequest = { packageKey: "build", configFilename: "electron-builder", projectDir, packageMetadata };
  36. const configAndEffectiveFile = await (0, read_config_file_1.getConfig)(configRequest, configPath);
  37. const config = configAndEffectiveFile == null ? {} : configAndEffectiveFile.result;
  38. if (configFromOptions != null) {
  39. mergePublish(config, configFromOptions);
  40. }
  41. if (configAndEffectiveFile != null) {
  42. builder_util_1.log.info({ file: configAndEffectiveFile.configFile == null ? 'package.json ("build" field)' : configAndEffectiveFile.configFile }, "loaded configuration");
  43. }
  44. if (config.extends == null && config.extends !== null) {
  45. const metadata = (await packageMetadata.value) || {};
  46. const devDependencies = metadata.devDependencies;
  47. const dependencies = metadata.dependencies;
  48. if ((dependencies != null && "react-scripts" in dependencies) || (devDependencies != null && "react-scripts" in devDependencies)) {
  49. config.extends = "react-cra";
  50. }
  51. else if (devDependencies != null && "electron-webpack" in devDependencies) {
  52. let file = "electron-webpack/out/electron-builder.js";
  53. try {
  54. file = require.resolve(file);
  55. }
  56. catch (ignore) {
  57. file = require.resolve("electron-webpack/electron-builder.yml");
  58. }
  59. config.extends = `file:${file}`;
  60. }
  61. }
  62. const parentConfigs = await loadParentConfigsRecursively(config.extends, async (configExtend) => {
  63. if (configExtend === "react-cra") {
  64. const result = await (0, rectCra_1.reactCra)(projectDir);
  65. builder_util_1.log.info({ preset: configExtend }, "loaded parent configuration");
  66. return result;
  67. }
  68. else {
  69. const { configFile, result } = await (0, read_config_file_1.loadParentConfig)(configRequest, configExtend);
  70. builder_util_1.log.info({ file: configFile }, "loaded parent configuration");
  71. return result;
  72. }
  73. });
  74. return doMergeConfigs([...parentConfigs, config]);
  75. }
  76. exports.getConfig = getConfig;
  77. function asArray(value) {
  78. return Array.isArray(value) ? value : typeof value === "string" ? [value] : [];
  79. }
  80. async function loadParentConfigsRecursively(configExtends, loader) {
  81. const configs = [];
  82. for (const configExtend of asArray(configExtends)) {
  83. const result = await loader(configExtend);
  84. const parentConfigs = await loadParentConfigsRecursively(result.extends, loader);
  85. configs.push(...parentConfigs, result);
  86. }
  87. return configs;
  88. }
  89. // normalize for easy merge
  90. function normalizeFiles(configuration, name) {
  91. let value = configuration[name];
  92. if (value == null) {
  93. return;
  94. }
  95. if (!Array.isArray(value)) {
  96. value = [value];
  97. }
  98. itemLoop: for (let i = 0; i < value.length; i++) {
  99. let item = value[i];
  100. if (typeof item === "string") {
  101. // merge with previous if possible
  102. if (i !== 0) {
  103. let prevItemIndex = i - 1;
  104. let prevItem;
  105. do {
  106. prevItem = value[prevItemIndex--];
  107. } while (prevItem == null);
  108. if (prevItem.from == null && prevItem.to == null) {
  109. if (prevItem.filter == null) {
  110. prevItem.filter = [item];
  111. }
  112. else {
  113. ;
  114. prevItem.filter.push(item);
  115. }
  116. value[i] = null;
  117. continue itemLoop;
  118. }
  119. }
  120. item = {
  121. filter: [item],
  122. };
  123. value[i] = item;
  124. }
  125. else if (Array.isArray(item)) {
  126. throw new Error(`${name} configuration is invalid, nested array not expected for index ${i}: ${item}`);
  127. }
  128. // make sure that merge logic is not complex - unify different presentations
  129. if (item.from === ".") {
  130. item.from = undefined;
  131. }
  132. if (item.to === ".") {
  133. item.to = undefined;
  134. }
  135. if (item.filter != null && typeof item.filter === "string") {
  136. item.filter = [item.filter];
  137. }
  138. }
  139. configuration[name] = value.filter(it => it != null);
  140. }
  141. function isSimilarFileSet(value, other) {
  142. return value.from === other.from && value.to === other.to;
  143. }
  144. function mergeFilters(value, other) {
  145. return asArray(value).concat(asArray(other));
  146. }
  147. function mergeFileSets(lists) {
  148. const result = [];
  149. for (const list of lists) {
  150. for (const item of list) {
  151. const existingItem = result.find(i => isSimilarFileSet(i, item));
  152. if (existingItem) {
  153. existingItem.filter = mergeFilters(item.filter, existingItem.filter);
  154. }
  155. else {
  156. result.push(item);
  157. }
  158. }
  159. }
  160. return result;
  161. }
  162. /**
  163. * `doMergeConfigs` takes configs in the order you would pass them to
  164. * Object.assign as sources.
  165. */
  166. function doMergeConfigs(configs) {
  167. for (const config of configs) {
  168. normalizeFiles(config, "files");
  169. normalizeFiles(config, "extraFiles");
  170. normalizeFiles(config, "extraResources");
  171. }
  172. const result = (0, builder_util_1.deepAssign)(getDefaultConfig(), ...configs);
  173. // `deepAssign` prioritises latter configs, while `mergeFilesSets` prioritises
  174. // former configs, so we have to reverse the order, because latter configs
  175. // must have higher priority.
  176. configs = configs.slice().reverse();
  177. result.files = mergeFileSets(configs.map(config => { var _a; return ((_a = config.files) !== null && _a !== void 0 ? _a : []); }));
  178. return result;
  179. }
  180. exports.doMergeConfigs = doMergeConfigs;
  181. function getDefaultConfig() {
  182. return {
  183. directories: {
  184. output: "dist",
  185. buildResources: "build",
  186. },
  187. };
  188. }
  189. const schemeDataPromise = new lazy_val_1.Lazy(() => (0, fs_extra_1.readJson)(path.join(__dirname, "..", "..", "scheme.json")));
  190. async function validateConfig(config, debugLogger) {
  191. const extraMetadata = config.extraMetadata;
  192. if (extraMetadata != null) {
  193. if (extraMetadata.build != null) {
  194. throw new builder_util_1.InvalidConfigurationError(`--em.build is deprecated, please specify as -c"`);
  195. }
  196. if (extraMetadata.directories != null) {
  197. throw new builder_util_1.InvalidConfigurationError(`--em.directories is deprecated, please specify as -c.directories"`);
  198. }
  199. }
  200. const oldConfig = config;
  201. if (oldConfig.npmSkipBuildFromSource === false) {
  202. throw new builder_util_1.InvalidConfigurationError(`npmSkipBuildFromSource is deprecated, please use buildDependenciesFromSource"`);
  203. }
  204. if (oldConfig.appImage != null && oldConfig.appImage.systemIntegration != null) {
  205. throw new builder_util_1.InvalidConfigurationError(`appImage.systemIntegration is deprecated, https://github.com/TheAssassin/AppImageLauncher is used for desktop integration"`);
  206. }
  207. // noinspection JSUnusedGlobalSymbols
  208. validateSchema(await schemeDataPromise.value, config, {
  209. name: `electron-builder ${version_1.PACKAGE_VERSION}`,
  210. postFormatter: (formattedError, error) => {
  211. if (debugLogger.isEnabled) {
  212. debugLogger.add("invalidConfig", (0, builder_util_1.safeStringifyJson)(error));
  213. }
  214. const site = "https://www.electron.build";
  215. let url = `${site}/configuration/configuration`;
  216. const targets = new Set(["mac", "dmg", "pkg", "mas", "win", "nsis", "appx", "linux", "appimage", "snap"]);
  217. const dataPath = error.dataPath == null ? null : error.dataPath;
  218. const targetPath = dataPath.startsWith(".") ? dataPath.substr(1).toLowerCase() : null;
  219. if (targetPath != null && targets.has(targetPath)) {
  220. url = `${site}/configuration/${targetPath}`;
  221. }
  222. return `${formattedError}\n How to fix:
  223. 1. Open ${url}
  224. 2. Search the option name on the page (or type in into Search to find across the docs).
  225. * Not found? The option was deprecated or not exists (check spelling).
  226. * Found? Check that the option in the appropriate place. e.g. "title" only in the "dmg", not in the root.
  227. `;
  228. },
  229. });
  230. }
  231. exports.validateConfig = validateConfig;
  232. const DEFAULT_APP_DIR_NAMES = ["app", "www"];
  233. async function computeDefaultAppDirectory(projectDir, userAppDir) {
  234. if (userAppDir != null) {
  235. const absolutePath = path.resolve(projectDir, userAppDir);
  236. const stat = await (0, fs_1.statOrNull)(absolutePath);
  237. if (stat == null) {
  238. throw new builder_util_1.InvalidConfigurationError(`Application directory ${userAppDir} doesn't exist`);
  239. }
  240. else if (!stat.isDirectory()) {
  241. throw new builder_util_1.InvalidConfigurationError(`Application directory ${userAppDir} is not a directory`);
  242. }
  243. else if (projectDir === absolutePath) {
  244. builder_util_1.log.warn({ appDirectory: userAppDir }, `Specified application directory equals to project dir — superfluous or wrong configuration`);
  245. }
  246. return absolutePath;
  247. }
  248. for (const dir of DEFAULT_APP_DIR_NAMES) {
  249. const absolutePath = path.join(projectDir, dir);
  250. const packageJson = path.join(absolutePath, "package.json");
  251. const stat = await (0, fs_1.statOrNull)(packageJson);
  252. if (stat != null && stat.isFile()) {
  253. return absolutePath;
  254. }
  255. }
  256. return projectDir;
  257. }
  258. exports.computeDefaultAppDirectory = computeDefaultAppDirectory;
  259. //# sourceMappingURL=config.js.map