diff --git a/package.json b/package.json index bc6018b1..b4552a38 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,7 @@ "compile": "vite build --mode production", "compile-dev": "vite build --mode development", "serve": "vite build --mode development --watch --minify false", + "openapi": "openapi-ts", "analyse-dev": "vite-bundle-visualizer --mode development", "analyse-prod": "vite-bundle-visualizer --mode production", "check": "biome check --write" diff --git a/staticfiles/management/commands/collectstatic.py b/staticfiles/management/commands/collectstatic.py index 5b441f6f..f7889087 100644 --- a/staticfiles/management/commands/collectstatic.py +++ b/staticfiles/management/commands/collectstatic.py @@ -50,7 +50,13 @@ class Command(CollectStatic): return Path(location) Scss.compile(self.collect_scss()) - OpenApi.compile() # This needs to be prior to javascript bundling + openapi = OpenApi.compile() # This needs to be prior to javascript bundling + if openapi is not None: + _ = openapi.wait() + if openapi.returncode: + raise RuntimeError( + f"Openapi generation failed with returncode {openapi.returncode}" + ) JSBundler.compile() collected = super().collect() diff --git a/staticfiles/management/commands/runserver.py b/staticfiles/management/commands/runserver.py index 7138dee1..cb44fd60 100644 --- a/staticfiles/management/commands/runserver.py +++ b/staticfiles/management/commands/runserver.py @@ -15,11 +15,18 @@ class Command(Runserver): """Light wrapper around default runserver that integrates javascirpt auto bundling.""" def run(self, **options): - OpenApi.compile() - if ( - os.environ.get(DJANGO_AUTORELOAD_ENV) is None - and settings.PROCFILE_STATIC is not None - ): + is_django_reload = os.environ.get(DJANGO_AUTORELOAD_ENV) is not None + + proc = OpenApi.compile() + # Ensure that the first runserver launch creates openapi files + # before the bundler starts so that it detects them + # When django is reloaded, we can keep this process in background + # to reduce reload time + if proc is not None and not is_django_reload: + _ = proc.wait() + + if not is_django_reload and settings.PROCFILE_STATIC is not None: start_composer(settings.PROCFILE_STATIC) _ = atexit.register(stop_composer, procfile=settings.PROCFILE_STATIC) + super().run(**options) diff --git a/staticfiles/processors.py b/staticfiles/processors.py index bacac363..5f44731b 100644 --- a/staticfiles/processors.py +++ b/staticfiles/processors.py @@ -95,7 +95,7 @@ class JSBundler: def compile(): """Bundle js files with the javascript bundler for production.""" process = subprocess.Popen(["npm", "run", "compile"]) - process.wait() + _ = process.wait() if process.returncode: raise RuntimeError(f"Bundler failed with returncode {process.returncode}") @@ -163,7 +163,7 @@ class OpenApi: OPENAPI_DIR = GENERATED_ROOT / "openapi" @classmethod - def compile(cls): + def compile(cls) -> subprocess.Popen[bytes] | None: """Compile a TS client for the sith API. Only generates it if it changed.""" logging.getLogger("django").info("Compiling open api typescript client") out = cls.OPENAPI_DIR / "schema.json" @@ -191,4 +191,4 @@ class OpenApi: with open(out, "w") as f: _ = f.write(schema) - subprocess.run(["npx", "openapi-ts"], check=True) + return subprocess.Popen(["npm", "run", "openapi"])