build.py 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190
  1. """Building the app and initializing all prerequisites."""
  2. from __future__ import annotations
  3. import json
  4. import os
  5. import random
  6. import subprocess
  7. from pathlib import Path
  8. from typing import (
  9. TYPE_CHECKING,
  10. Optional,
  11. )
  12. from pynecone import constants
  13. from pynecone.config import get_config
  14. from pynecone.utils import path_ops, prerequisites
  15. if TYPE_CHECKING:
  16. from pynecone.app import App
  17. def update_json_file(file_path, key, value):
  18. """Update the contents of a json file.
  19. Args:
  20. file_path: the path to the JSON file.
  21. key: object key to update.
  22. value: value of key.
  23. """
  24. with open(file_path) as f: # type: ignore
  25. json_object = json.load(f)
  26. json_object[key] = value
  27. with open(file_path, "w") as f:
  28. json.dump(json_object, f, ensure_ascii=False)
  29. def set_pynecone_project_hash():
  30. """Write the hash of the Pynecone project to a PCVERSION_APP_FILE."""
  31. update_json_file(
  32. constants.PCVERSION_APP_FILE, "project_hash", random.getrandbits(128)
  33. )
  34. def set_pynecone_upload_endpoint():
  35. """Write the upload url to a PCVERSION_APP_FILE."""
  36. update_json_file(
  37. constants.PCVERSION_APP_FILE, "uploadUrl", constants.Endpoint.UPLOAD.get_url()
  38. )
  39. def generate_sitemap(deploy_url: str):
  40. """Generate the sitemap config file.
  41. Args:
  42. deploy_url: The URL of the deployed app.
  43. """
  44. # Import here to avoid circular imports.
  45. from pynecone.compiler import templates
  46. config = json.dumps(
  47. {
  48. "siteUrl": deploy_url,
  49. "generateRobotsTxt": True,
  50. }
  51. )
  52. with open(constants.SITEMAP_CONFIG_FILE, "w") as f:
  53. f.write(templates.SITEMAP_CONFIG(config=config))
  54. def export_app(
  55. app: App,
  56. backend: bool = True,
  57. frontend: bool = True,
  58. zip: bool = False,
  59. deploy_url: Optional[str] = None,
  60. ):
  61. """Zip up the app for deployment.
  62. Args:
  63. app: The app.
  64. backend: Whether to zip up the backend app.
  65. frontend: Whether to zip up the frontend app.
  66. zip: Whether to zip the app.
  67. deploy_url: The URL of the deployed app.
  68. """
  69. # Force compile the app.
  70. app.compile(force_compile=True)
  71. # Remove the static folder.
  72. path_ops.rm(constants.WEB_STATIC_DIR)
  73. # Generate the sitemap file.
  74. if deploy_url is not None:
  75. generate_sitemap(deploy_url)
  76. # Export the Next app.
  77. subprocess.run(
  78. [prerequisites.get_package_manager(), "run", "export"], cwd=constants.WEB_DIR
  79. )
  80. # Zip up the app.
  81. if zip:
  82. if os.name == "posix":
  83. posix_export(backend, frontend)
  84. if os.name == "nt":
  85. nt_export(backend, frontend)
  86. def nt_export(backend: bool = True, frontend: bool = True):
  87. """Export for nt (Windows) systems.
  88. Args:
  89. backend: Whether to zip up the backend app.
  90. frontend: Whether to zip up the frontend app.
  91. """
  92. cmd = r""
  93. if frontend:
  94. cmd = r'''powershell -Command "Set-Location .web/_static; Compress-Archive -Path .\* -DestinationPath ..\..\frontend.zip -Force"'''
  95. os.system(cmd)
  96. if backend:
  97. cmd = r'''powershell -Command "Get-ChildItem -File | Where-Object { $_.Name -notin @('.web', 'assets', 'frontend.zip', 'backend.zip') } | Compress-Archive -DestinationPath backend.zip -Update"'''
  98. os.system(cmd)
  99. def posix_export(backend: bool = True, frontend: bool = True):
  100. """Export for posix (Linux, OSX) systems.
  101. Args:
  102. backend: Whether to zip up the backend app.
  103. frontend: Whether to zip up the frontend app.
  104. """
  105. cmd = r""
  106. if frontend:
  107. cmd = r"cd .web/_static && zip -r ../../frontend.zip ./*"
  108. os.system(cmd)
  109. if backend:
  110. cmd = r"zip -r backend.zip ./* -x .web/\* ./assets\* ./frontend.zip\* ./backend.zip\*"
  111. os.system(cmd)
  112. def setup_frontend(root: Path, disable_telemetry: bool = True):
  113. """Set up the frontend.
  114. Args:
  115. root: root path of the project.
  116. disable_telemetry: Whether to disable the Next telemetry.
  117. """
  118. # Initialize the web directory if it doesn't exist.
  119. web_dir = prerequisites.create_web_directory(root)
  120. # Install frontend packages.
  121. prerequisites.install_frontend_packages(web_dir)
  122. # Copy asset files to public folder.
  123. path_ops.mkdir(str(root / constants.WEB_ASSETS_DIR))
  124. path_ops.cp(
  125. src=str(root / constants.APP_ASSETS_DIR),
  126. dest=str(root / constants.WEB_ASSETS_DIR),
  127. )
  128. # Disable the Next telemetry.
  129. if disable_telemetry:
  130. subprocess.Popen(
  131. [
  132. prerequisites.get_package_manager(),
  133. "run",
  134. "next",
  135. "telemetry",
  136. "disable",
  137. ],
  138. cwd=constants.WEB_DIR,
  139. env=os.environ,
  140. stdout=subprocess.DEVNULL,
  141. stderr=subprocess.STDOUT,
  142. )
  143. def setup_backend():
  144. """Set up backend.
  145. Specifically ensures backend database is updated when running --no-frontend.
  146. """
  147. # Import here to avoid circular imports.
  148. from pynecone.model import Model
  149. config = get_config()
  150. if config.db_url is not None:
  151. Model.create_all()