manifest.js 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", { value: true });
  3. exports.LiveUpdatesManifestCommand = void 0;
  4. const tslib_1 = require("tslib");
  5. const cli_framework_output_1 = require("@ionic/cli-framework-output");
  6. const utils_array_1 = require("@ionic/utils-array");
  7. const utils_fs_1 = require("@ionic/utils-fs");
  8. const utils_terminal_1 = require("@ionic/utils-terminal");
  9. const crypto = tslib_1.__importStar(require("crypto"));
  10. const fs = tslib_1.__importStar(require("fs"));
  11. const lodash = require("lodash");
  12. const path = tslib_1.__importStar(require("path"));
  13. const debug_1 = require("debug");
  14. const color_1 = require("../../lib/color");
  15. const errors_1 = require("../../lib/errors");
  16. const shell_1 = require("../../lib/shell");
  17. const logger_1 = require("../../lib/utils/logger");
  18. const capacitor_1 = require("./capacitor");
  19. const core_1 = require("./core");
  20. const debug = (0, debug_1.debug)('ionic:commands:live-update:manifest');
  21. const CAPACITOR_CONFIG_JSON_FILE = 'capacitor.config.json';
  22. class LiveUpdatesManifestCommand extends core_1.LiveUpdatesCoreCommand {
  23. constructor() {
  24. super(...arguments);
  25. this.getCapacitorCLIConfig = lodash.memoize(async () => {
  26. // I had to create a new shell to force prependNodeModulesBinToPath.
  27. // If ionic.config.json is not present, then this.env.shell will not implement this, and the Capacitor command will fail.
  28. const args = ['config', '--json'];
  29. const log = new logger_1.Logger({
  30. level: cli_framework_output_1.LOGGER_LEVELS.INFO,
  31. handlers: (0, logger_1.createDefaultLoggerHandlers)(),
  32. });
  33. const shell = new shell_1.Shell({ log }, { alterPath: p => { return (0, shell_1.prependNodeModulesBinToPath)(this.env.ctx.execPath, p); } });
  34. debug('Getting config with Capacitor CLI: %O', args);
  35. const output = await shell.cmdinfo('capacitor', args);
  36. if (!output) {
  37. debug('Could not get config from Capacitor CLI (probably old version)');
  38. return;
  39. }
  40. try {
  41. return JSON.parse(output);
  42. }
  43. catch (e) {
  44. debug('Could not get config from Capacitor CLI (probably old version)', e);
  45. return;
  46. }
  47. });
  48. this.getCapacitorConfig = lodash.memoize(async () => {
  49. const cli = await this.getCapacitorCLIConfig();
  50. if (cli) {
  51. debug('Loaded Capacitor config!');
  52. return cli.app.extConfig;
  53. }
  54. // fallback to reading capacitor.config.json if it exists
  55. const confPath = this.getCapacitorConfigJsonPath();
  56. if (!(await (0, utils_fs_1.pathExists)(confPath))) {
  57. debug('Capacitor config file does not exist at %O', confPath);
  58. debug('Failed to load Capacitor config');
  59. return;
  60. }
  61. const conf = new capacitor_1.CapacitorJSONConfig(confPath);
  62. const extConfig = conf.c;
  63. debug('Loaded Capacitor config!');
  64. return extConfig;
  65. });
  66. }
  67. async getMetadata() {
  68. // This command is set as type 'global' in order to support Capacitor apps without an ionic.config.json
  69. return {
  70. name: 'manifest',
  71. type: 'global',
  72. summary: 'Generates a manifest file for the Ionic Live Updates service from a built app directory',
  73. groups: ["paid" /* MetadataGroup.PAID */],
  74. };
  75. }
  76. async run() {
  77. const capacitorConfig = await this.getCapacitorConfig();
  78. if (!this.project && !capacitorConfig) {
  79. throw new errors_1.FatalException(`Cannot run ${(0, color_1.input)('ionic live-update manifest')} outside a project directory.`);
  80. }
  81. let buildDir;
  82. if (this.project) {
  83. await this.requireNativeIntegration();
  84. buildDir = await this.project.getDistDir();
  85. }
  86. else {
  87. buildDir = capacitorConfig.webDir ? capacitorConfig.webDir : 'www';
  88. }
  89. const manifest = await this.getFilesAndSizesAndHashesForGlobPattern(buildDir);
  90. const manifestPath = path.resolve(buildDir, 'pro-manifest.json');
  91. await (0, utils_fs_1.writeFile)(manifestPath, JSON.stringify(manifest, undefined, 2), { encoding: 'utf8' });
  92. this.env.log.ok(`Ionic Live Updates manifest written to ${(0, color_1.input)((0, utils_terminal_1.prettyPath)(manifestPath))}!`);
  93. }
  94. async getFilesAndSizesAndHashesForGlobPattern(buildDir) {
  95. const contents = await (0, utils_fs_1.readdirp)(buildDir, { filter: item => !/(css|js)\.map$/.test(item.path) });
  96. const stats = await (0, utils_array_1.map)(contents, async (f) => [f, await (0, utils_fs_1.stat)(f)]);
  97. const files = stats.filter(([, s]) => !s.isDirectory());
  98. const items = await Promise.all(files.map(([f, s]) => this.getFileAndSizeAndHashForFile(buildDir, f, s)));
  99. return items.filter(item => item.href !== 'pro-manifest.json');
  100. }
  101. async getFileAndSizeAndHashForFile(buildDir, file, s) {
  102. const buffer = await this.readFile(file);
  103. return {
  104. href: path.relative(buildDir, file),
  105. size: s.size,
  106. integrity: this.getIntegrity(buffer),
  107. };
  108. }
  109. async readFile(file) {
  110. return new Promise((resolve, reject) => {
  111. fs.readFile(file, (err, buffer) => {
  112. if (err) {
  113. return reject(err);
  114. }
  115. resolve(buffer);
  116. });
  117. });
  118. }
  119. getIntegrity(data) {
  120. return ['sha256', 'sha384', 'sha512']
  121. .map(algorithm => {
  122. const hash = crypto.createHash(algorithm);
  123. hash.update(data);
  124. return algorithm + '-' + hash.digest('base64');
  125. })
  126. .join(' ');
  127. }
  128. getCapacitorConfigJsonPath() {
  129. return path.resolve(this.env.ctx.execPath, CAPACITOR_CONFIG_JSON_FILE);
  130. }
  131. }
  132. exports.LiveUpdatesManifestCommand = LiveUpdatesManifestCommand;