| OLD | NEW |
| 1 #!/usr/bin/env python2 | 1 #!/usr/bin/env python2 |
| 2 # -*- coding: utf-8 -*- | 2 # -*- coding: utf-8 -*- |
| 3 | 3 |
| 4 # Copyright 2015 Google Inc. All Rights Reserved. | 4 # Copyright 2015 Google Inc. All Rights Reserved. |
| 5 # | 5 # |
| 6 # Licensed under the Apache License, Version 2.0 (the "License"); | 6 # Licensed under the Apache License, Version 2.0 (the "License"); |
| 7 # you may not use this file except in compliance with the License. | 7 # you may not use this file except in compliance with the License. |
| 8 # You may obtain a copy of the License at | 8 # You may obtain a copy of the License at |
| 9 # | 9 # |
| 10 # http://www.apache.org/licenses/LICENSE-2.0 | 10 # http://www.apache.org/licenses/LICENSE-2.0 |
| (...skipping 51 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 62 | 62 |
| 63 # Name of the service worker static script. | 63 # Name of the service worker static script. |
| 64 SW_STATIC_SCRIPT_NAME = 'sw_static.js' | 64 SW_STATIC_SCRIPT_NAME = 'sw_static.js' |
| 65 | 65 |
| 66 # Largest number that the cache version can be. | 66 # Largest number that the cache version can be. |
| 67 MAX_CACHE_VERSION = 1000000 | 67 MAX_CACHE_VERSION = 1000000 |
| 68 | 68 |
| 69 # Where this file is located (so we can find resources). | 69 # Where this file is located (so we can find resources). |
| 70 SCRIPT_DIR = os.path.dirname(__file__) | 70 SCRIPT_DIR = os.path.dirname(__file__) |
| 71 | 71 |
| 72 # Maps dependency managers to the folder they install dependencies into. |
| 73 DEPENDENCY_MANAGER_INSTALL_FOLDER = { |
| 74 'bower': 'bower_components', |
| 75 'npm': 'node_modules', |
| 76 } |
| 77 |
| 72 SW_FORMAT_STRING = """/** | 78 SW_FORMAT_STRING = """/** |
| 73 * @file Service worker generated by Caterpillar. | 79 * @file Service worker generated by Caterpillar. |
| 74 */ | 80 */ |
| 75 | 81 |
| 76 /** | 82 /** |
| 77 * @const Current cache version. | 83 * @const Current cache version. |
| 78 * | 84 * |
| 79 * Increment this to force cache to clear. | 85 * Increment this to force cache to clear. |
| 80 */ | 86 */ |
| 81 var CACHE_VERSION = {cache_version}; | 87 var CACHE_VERSION = {cache_version}; |
| (...skipping 258 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 340 output_dir: Directory of the web app to insert TODOs into. | 346 output_dir: Directory of the web app to insert TODOs into. |
| 341 """ | 347 """ |
| 342 logging.debug('Inserting TODOs.') | 348 logging.debug('Inserting TODOs.') |
| 343 dirwalk = os.walk(output_dir) | 349 dirwalk = os.walk(output_dir) |
| 344 for (dirpath, _, filenames) in dirwalk: | 350 for (dirpath, _, filenames) in dirwalk: |
| 345 for filename in filenames: | 351 for filename in filenames: |
| 346 if filename.endswith('.js'): | 352 if filename.endswith('.js'): |
| 347 path = os.path.join(dirpath, filename) | 353 path = os.path.join(dirpath, filename) |
| 348 insert_todos_into_file(path) | 354 insert_todos_into_file(path) |
| 349 | 355 |
| 350 | 356 def generate_service_worker(output_dir, ca_manifest, required_js_paths, |
| 351 def generate_service_worker(output_dir, ca_manifest, polyfill_paths, | |
| 352 boilerplate_dir): | 357 boilerplate_dir): |
| 353 """Generates code for a service worker. | 358 """Generates code for a service worker. |
| 354 | 359 |
| 355 Args: | 360 Args: |
| 356 output_dir: Directory of the web app that this service worker will run in. | 361 output_dir: Directory of the web app that this service worker will run in. |
| 357 ca_manifest: Chrome App manifest dictionary. | 362 ca_manifest: Chrome App manifest dictionary. |
| 358 polyfill_paths: List of paths to required polyfill scripts, relative to the | 363 required_js_paths: List of paths to required scripts, relative to the |
| 359 boilerplate directory. | 364 boilerplate directory. |
| 360 boilerplate_dir: Caterpillar script directory within output web app. | 365 boilerplate_dir: Caterpillar script directory within output web app. |
| 361 | 366 |
| 362 Returns: | 367 Returns: |
| 363 JavaScript string. | 368 JavaScript string. |
| 364 """ | 369 """ |
| 365 # Get the paths of files we will cache. | 370 # Get the paths of files we will cache. |
| 366 all_filepaths = [] | 371 all_filepaths = [] |
| 367 logging.debug('Looking for files to cache.') | 372 logging.debug('Looking for files to cache.') |
| 368 dirwalk = os.walk(output_dir) | 373 dirwalk = os.walk(output_dir) |
| 369 for (dirpath, _, filenames) in dirwalk: | 374 for (dirpath, _, filenames) in dirwalk: |
| 370 # Add the relative file paths of each file to the filepaths list. | 375 # Add the relative file paths of each file to the filepaths list. |
| 371 all_filepaths.extend( | 376 all_filepaths.extend( |
| 372 os.path.relpath(os.path.join(dirpath, filename), output_dir) | 377 os.path.relpath(os.path.join(dirpath, filename), output_dir) |
| 373 for filename in filenames) | 378 for filename in filenames) |
| 374 logging.debug('Cached files:\n\t%s', '\n\t'.join(all_filepaths)) | 379 logging.debug('Cached files:\n\t%s', '\n\t'.join(all_filepaths)) |
| 375 # Format the file paths as JavaScript strings. | 380 # Format the file paths as JavaScript strings. |
| 376 all_filepaths = ["'{}'".format(fp) for fp in all_filepaths] | 381 all_filepaths = ["'{}'".format(fp) for fp in all_filepaths] |
| 377 | 382 |
| 378 logging.debug('Generating service worker.') | 383 logging.debug('Generating service worker.') |
| 379 | 384 |
| 380 sw_js = SW_FORMAT_STRING.format( | 385 sw_js = SW_FORMAT_STRING.format( |
| 381 cache_version=random.randrange(MAX_CACHE_VERSION), | 386 cache_version=random.randrange(MAX_CACHE_VERSION), |
| 382 joined_filepaths=',\n '.join(all_filepaths), | 387 joined_filepaths=',\n '.join(all_filepaths), |
| 383 boilerplate_dir=boilerplate_dir | 388 boilerplate_dir=boilerplate_dir |
| 384 ) | 389 ) |
| 385 | 390 |
| 386 # The polyfills we get as input are relative to the boilerplate directory, but | 391 # The polyfills we get as input are relative to the boilerplate directory, but |
| 387 # the service worker is in the root directory, so we need to change the paths. | 392 # the service worker is in the root directory, so we need to change the paths. |
| 388 polyfills_paths = [os.path.join(boilerplate_dir, path) | 393 required_js_paths = [os.path.join(boilerplate_dir, path) |
| 389 for path in polyfill_paths] | 394 for path in required_js_paths] |
| 390 | 395 |
| 391 background_scripts = ca_manifest['app']['background'].get('scripts', []) | 396 background_scripts = ca_manifest['app']['background'].get('scripts', []) |
| 392 for script in polyfill_paths + background_scripts: | 397 for script in required_js_paths + background_scripts: |
| 393 logging.debug('Importing `%s` to the service worker.', script) | 398 logging.debug('Importing `%s` to the service worker.', script) |
| 394 sw_js += "importScripts('{}');\n".format(script) | 399 sw_js += "importScripts('{}');\n".format(script) |
| 395 | 400 |
| 396 return sw_js | 401 return sw_js |
| 397 | 402 |
| 398 | 403 |
| 399 def copy_script(script, directory): | 404 def copy_script(script, directory): |
| 400 """Copies a script from Caterpillar into the given directory. | 405 """Copies a script from Caterpillar into the given directory. |
| 401 | 406 |
| 402 Args: | 407 Args: |
| 403 script: Caterpillar JavaScript filename. | 408 script: Caterpillar JavaScript filename. |
| 404 directory: Path to directory. | 409 directory: Path to directory. |
| 405 """ | 410 """ |
| 406 path = os.path.join(SCRIPT_DIR, 'js', script) | 411 path = os.path.join(SCRIPT_DIR, 'js', script) |
| 407 new_path = os.path.join(directory, script) | 412 new_path = os.path.join(directory, script) |
| 408 logging.debug('Writing `%s` to `%s`.', path, new_path) | 413 logging.debug('Writing `%s` to `%s`.', path, new_path) |
| 409 shutil.copyfile(path, new_path) | 414 shutil.copyfile(path, new_path) |
| 410 | 415 |
| 411 | 416 def add_service_worker(output_dir, ca_manifest, required_js_paths, |
| 412 def add_service_worker(output_dir, ca_manifest, polyfill_paths, | |
| 413 boilerplate_dir): | 417 boilerplate_dir): |
| 414 """Adds service worker scripts to a web app. | 418 """Adds service worker scripts to a web app. |
| 415 | 419 |
| 416 Args: | 420 Args: |
| 417 output_dir: Path to web app to add service worker scripts to. | 421 output_dir: Path to web app to add service worker scripts to. |
| 418 ca_manifest: Chrome App manifest dictionary. | 422 ca_manifest: Chrome App manifest dictionary. |
| 419 polyfill_paths: List of paths to required polyfill scripts, relative to the | 423 required_js_paths: List of paths to required scripts, relative to the |
| 420 boilerplate directory. | 424 boilerplate directory. |
| 421 boilerplate_dir: Caterpillar script directory within web app. | 425 boilerplate_dir: Caterpillar script directory within web app. |
| 422 """ | 426 """ |
| 423 # We have to copy the other scripts before we generate the service worker | 427 # We have to copy the other scripts before we generate the service worker |
| 424 # caching script, or else they won't be cached. | 428 # caching script, or else they won't be cached. |
| 425 boilerplate_path = os.path.join(output_dir, boilerplate_dir) | 429 boilerplate_path = os.path.join(output_dir, boilerplate_dir) |
| 426 copy_script(REGISTER_SCRIPT_NAME, boilerplate_path) | 430 copy_script(REGISTER_SCRIPT_NAME, boilerplate_path) |
| 427 copy_script(SW_STATIC_SCRIPT_NAME, boilerplate_path) | 431 copy_script(SW_STATIC_SCRIPT_NAME, boilerplate_path) |
| 428 | 432 |
| 429 sw_js = generate_service_worker(output_dir, ca_manifest, polyfill_paths, | 433 sw_js = generate_service_worker(output_dir, ca_manifest, required_js_paths, |
| 430 boilerplate_dir) | 434 boilerplate_dir) |
| 431 | 435 |
| 432 # We can now write the service worker. Note that it must be in the root. | 436 # We can now write the service worker. Note that it must be in the root. |
| 433 sw_path = os.path.join(output_dir, SW_SCRIPT_NAME) | 437 sw_path = os.path.join(output_dir, SW_SCRIPT_NAME) |
| 434 logging.debug('Writing service worker to `%s`.', sw_path) | 438 logging.debug('Writing service worker to `%s`.', sw_path) |
| 435 with open(sw_path, 'w') as sw_file: | 439 with open(sw_path, 'w') as sw_file: |
| 436 sw_file.write(surrogateescape.encode(sw_js)) | 440 sw_file.write(surrogateescape.encode(sw_js)) |
| 437 | 441 |
| 438 | 442 |
| 439 class InstallationError(Exception): | 443 class InstallationError(Exception): |
| (...skipping 146 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 586 for api in apis: | 590 for api in apis: |
| 587 if api in POLYFILLS: | 591 if api in POLYFILLS: |
| 588 polyfillable.append(api) | 592 polyfillable.append(api) |
| 589 else: | 593 else: |
| 590 not_polyfillable.append(api) | 594 not_polyfillable.append(api) |
| 591 | 595 |
| 592 logging.info('Polyfilled Chrome APIs: %s', ', '.join(polyfillable)) | 596 logging.info('Polyfilled Chrome APIs: %s', ', '.join(polyfillable)) |
| 593 logging.warning('Could not polyfill Chrome APIs: %s', | 597 logging.warning('Could not polyfill Chrome APIs: %s', |
| 594 ', '.join(not_polyfillable)) | 598 ', '.join(not_polyfillable)) |
| 595 | 599 |
| 600 # Read in the polyfill manifests and store their dependencies. We can't |
| 601 # install them yet, though, since that has to be done after editing code or |
| 602 # the dependencies will also be edited. |
| 603 polyfill_manifests = polyfill_manifest.load_many(polyfillable) |
| 604 dependencies = [dependency |
| 605 for manifest in polyfill_manifests.values() |
| 606 for dependency in manifest['dependencies']] |
| 607 |
| 596 # List of paths of static code to be copied from Caterpillar into the output | 608 # List of paths of static code to be copied from Caterpillar into the output |
| 597 # web app, relative to Caterpillar's JS source directory. | 609 # web app, relative to Caterpillar's JS source directory. |
| 598 required_js_paths = [ | 610 required_always_paths = [ |
| 599 'caterpillar.js', | 611 'caterpillar.js', |
| 600 REGISTER_SCRIPT_NAME, | 612 REGISTER_SCRIPT_NAME, |
| 601 ] + polyfill_paths(polyfillable) | 613 ] |
| 614 |
| 615 # The dependencies and polyfills are also requirements, but we need to handle |
| 616 # them differently, so they're split up into two lists. |
| 617 required_dependency_paths = [] |
| 618 for dependency in dependencies: |
| 619 # Note that dependencies are installed into the root, but we need paths |
| 620 # relative to Caterpillar's boilerplate directory. |
| 621 dependency_path = os.path.join('..', |
| 622 DEPENDENCY_MANAGER_INSTALL_FOLDER[dependency['manager']], |
| 623 dependency['name'], dependency['path']) |
| 624 required_dependency_paths.append(dependency_path) |
| 625 |
| 626 required_polyfill_paths = polyfill_paths(polyfillable) |
| 602 | 627 |
| 603 # Read in and check the manifest file. | 628 # Read in and check the manifest file. |
| 604 try: | 629 try: |
| 605 ca_manifest = chrome_app.manifest.get(input_dir) | 630 ca_manifest = chrome_app.manifest.get(input_dir) |
| 606 chrome_app.manifest.localize(ca_manifest, input_dir) | 631 chrome_app.manifest.localize(ca_manifest, input_dir) |
| 607 chrome_app.manifest.verify(ca_manifest) | 632 chrome_app.manifest.verify(ca_manifest) |
| 608 except ValueError as e: | 633 except ValueError as e: |
| 609 logging.error(e.message) | 634 logging.error(e.message) |
| 610 return | 635 return |
| 611 | 636 |
| 612 # TODO(alger): Identify background scripts and determine start_url. | 637 # TODO(alger): Identify background scripts and determine start_url. |
| 613 start_url = config['start_url'] | 638 start_url = config['start_url'] |
| 614 logging.info('Got start URL from config file: `%s`', start_url) | 639 logging.info('Got start URL from config file: `%s`', start_url) |
| 615 | 640 |
| 616 # Generate a progressive web app manifest. | 641 # Generate a progressive web app manifest. |
| 617 pwa_manifest = generate_web_manifest(ca_manifest, start_url) | 642 pwa_manifest = generate_web_manifest(ca_manifest, start_url) |
| 618 pwa_manifest_path = os.path.join(output_dir, PWA_MANIFEST_FILENAME) | 643 pwa_manifest_path = os.path.join(output_dir, PWA_MANIFEST_FILENAME) |
| 619 with open(pwa_manifest_path, 'w') as pwa_manifest_file: | 644 with open(pwa_manifest_path, 'w') as pwa_manifest_file: |
| 620 json.dump(pwa_manifest, pwa_manifest_file, indent=4, sort_keys=True) | 645 json.dump(pwa_manifest, pwa_manifest_file, indent=4, sort_keys=True) |
| 621 logging.debug('Wrote `%s` to `%s`.', PWA_MANIFEST_FILENAME, pwa_manifest_path) | 646 logging.debug('Wrote `%s` to `%s`.', PWA_MANIFEST_FILENAME, pwa_manifest_path) |
| 622 | 647 |
| 623 # Remove unnecessary files from the output web app. This must be done before | 648 # Remove unnecessary files from the output web app. This must be done before |
| 624 # the service worker is generated, or these files will be cached. | 649 # the service worker is generated, or these files will be cached. |
| 625 cleanup_output_dir(output_dir) | 650 cleanup_output_dir(output_dir) |
| 626 | 651 |
| 627 # Edit the HTML and JS code of the output web app. | 652 # Edit the HTML and JS code of the output web app. |
| 628 # This is adding TODOs, injecting tags, etc. - anything that involves editing | 653 # This is adding TODOs, injecting tags, etc. - anything that involves editing |
| 629 # user code directly. This must be done before the static code is copied | 654 # user code directly. This must be done before the static code is copied |
| 630 # across, or the polyfills will have TODOs added to them. | 655 # across, or the polyfills will have TODOs added to them. |
| 631 edit_code(output_dir, required_js_paths, ca_manifest, config) | 656 # Order is significant here - always, then dependencies, then polyfills. |
| 657 required_script_paths = (required_always_paths + required_dependency_paths |
| 658 + required_polyfill_paths) |
| 659 edit_code(output_dir, required_script_paths, ca_manifest, config) |
| 632 | 660 |
| 633 # We want the static SW file to be copied in too, so we add it here. | 661 # We want the static SW file to be copied in too, so we add it here. |
| 634 # We have to add it after edit_code or it would be included in the HTML, but | 662 # We have to add it after edit_code or it would be included in the HTML, but |
| 635 # this is service worker-only code, and shouldn't be included there. | 663 # this is service worker-only code, and shouldn't be included there. |
| 636 required_js_paths.append(SW_STATIC_SCRIPT_NAME) | 664 required_always_paths.append(SW_STATIC_SCRIPT_NAME) |
| 637 | 665 |
| 638 # Copy static code from Caterpillar into the output web app. | 666 # Copy static code from Caterpillar into the output web app. |
| 639 # This must be done before the service worker is generated, or these files | 667 # This must be done before the service worker is generated, or these files |
| 640 # will not be cached. | 668 # will not be cached. |
| 641 copy_static_code(required_js_paths, output_dir, boilerplate_dir) | 669 required_static_paths = required_always_paths + required_polyfill_paths |
| 670 copy_static_code(required_static_paths, output_dir, boilerplate_dir) |
| 642 | 671 |
| 643 # Read in the polyfill manifests and install polyfill dependencies. | 672 # Install the polyfill dependencies. This must be done before the service |
| 644 # This must be done after editing code (or the dependencies will also be | 673 # worker is generated, or the dependencies won't be cached. |
| 645 # edited) and before the service worker is generated (or the dependencies | |
| 646 # won't be cached). | |
| 647 polyfill_manifests = polyfill_manifest.load_many(polyfillable) | |
| 648 dependencies = [dependency | |
| 649 for manifest in polyfill_manifests.values() | |
| 650 for dependency in manifest['dependencies']] | |
| 651 try: | 674 try: |
| 652 install_dependencies(dependencies, output_dir) | 675 install_dependencies(dependencies, output_dir) |
| 653 except ValueError as e: | 676 except ValueError as e: |
| 654 logging.error(e.message) | 677 logging.error(e.message) |
| 655 return | 678 return |
| 656 | 679 |
| 657 # Generate and write a service worker. | 680 # Generate and write a service worker. |
| 658 add_service_worker(output_dir, ca_manifest, polyfill_paths(polyfillable), | 681 required_sw_paths = required_dependency_paths + required_polyfill_paths |
| 682 add_service_worker(output_dir, ca_manifest, required_sw_paths, |
| 659 boilerplate_dir) | 683 boilerplate_dir) |
| 660 | 684 |
| 661 logging.info('Conversion complete.') | 685 logging.info('Conversion complete.') |
| 662 | 686 |
| 663 | 687 |
| 664 class Formatter(logging.Formatter): | 688 class Formatter(logging.Formatter): |
| 665 """Caterpillar logging formatter. | 689 """Caterpillar logging formatter. |
| 666 | 690 |
| 667 Adds color to the logged information. | 691 Adds color to the logged information. |
| 668 """ | 692 """ |
| (...skipping 54 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 723 if args.mode == 'config': | 747 if args.mode == 'config': |
| 724 configuration.generate_and_save(args.output, args.interactive) | 748 configuration.generate_and_save(args.output, args.interactive) |
| 725 | 749 |
| 726 elif args.mode == 'convert': | 750 elif args.mode == 'convert': |
| 727 config = configuration.load(args.config) | 751 config = configuration.load(args.config) |
| 728 convert_app(args.input, args.output, config, args.force) | 752 convert_app(args.input, args.output, config, args.force) |
| 729 | 753 |
| 730 | 754 |
| 731 if __name__ == '__main__': | 755 if __name__ == '__main__': |
| 732 sys.exit(main()) | 756 sys.exit(main()) |
| OLD | NEW |