mirror of
https://github.com/clockworklabs/SpacetimeDB.git
synced 2026-05-06 07:26:43 -04:00
Improve spacetime build for typescript (#4330)
# Description of Changes
This fixes the confusing error encountered by @gefjon:
```
$ spacetime publish -s local --module-path spacetimedb --no-config event-table-test-ts
Rolldown warning: Could not resolve 'spacetimedb/server' in spacetimedb/src/index.ts
Error: Your module doesn't import the `spacetimedb/server` package at all - this is likely a mistake, as your module will not be able to interface with the SpacetimeDB host.
```
After:
```
[UNRESOLVED_IMPORT] Error: Could not resolve 'spacetimedb/server' in src/index.ts
╭─[ src/index.ts:1:47 ]
│
1 │ import { schema, table, t, SenderError } from 'spacetimedb/server';
│ ──────────┬─────────
│ ╰─────────── Module not found.
│
│ Help: You may have forgotten to install dependencies with your package manager.
───╯
```
And also runs `tsc`, so that modules with type errors aren't silently
published. Perhaps we want a `--skip-tsc` so that people can run anyway
and get runtime errors?
# Expected complexity level and risk
2
# Testing
- [x] Ensured that tsc and rolldown errors are properly emitted.
This commit is contained in:
Generated
+1
@@ -7618,6 +7618,7 @@ dependencies = [
|
||||
"reqwest 0.12.24",
|
||||
"rolldown",
|
||||
"rolldown_common",
|
||||
"rolldown_error",
|
||||
"rolldown_utils",
|
||||
"rustyline",
|
||||
"serde",
|
||||
|
||||
@@ -259,6 +259,7 @@ regex = "1"
|
||||
reqwest = { version = "0.12", features = ["stream", "json"] }
|
||||
rolldown = { git = "https://github.com/rolldown/rolldown.git", tag = "v1.0.0-rc.3" }
|
||||
rolldown_common = { git = "https://github.com/rolldown/rolldown.git", tag = "v1.0.0-rc.3" }
|
||||
rolldown_error = { git = "https://github.com/rolldown/rolldown.git", tag = "v1.0.0-rc.3" }
|
||||
rolldown_utils = { git = "https://github.com/rolldown/rolldown.git", tag = "v1.0.0-rc.3" }
|
||||
ron = "0.8"
|
||||
rusqlite = { version = "0.29.0", features = ["bundled", "column_decltype"] }
|
||||
|
||||
@@ -81,6 +81,7 @@ glob.workspace = true
|
||||
dialoguer = { workspace = true, features = ["fuzzy-select"] }
|
||||
rolldown.workspace = true
|
||||
rolldown_common.workspace = true
|
||||
rolldown_error.workspace = true
|
||||
rolldown_utils.workspace = true
|
||||
xmltree.workspace = true
|
||||
quick-xml.workspace = true
|
||||
|
||||
@@ -74,3 +74,14 @@ pub async fn exec_subcommand(
|
||||
}
|
||||
.map(|()| ExitCode::SUCCESS)
|
||||
}
|
||||
|
||||
/// An error type indicating that the process should exit silently with the
|
||||
/// given `ExitCode`.
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
#[error("exit with {0:?}")]
|
||||
pub struct ExitWithCode(pub ExitCode);
|
||||
|
||||
impl ExitWithCode {
|
||||
/// Basic unsuccessful termination.
|
||||
pub const FAILURE: Self = ExitWithCode(ExitCode::FAILURE);
|
||||
}
|
||||
|
||||
@@ -43,7 +43,9 @@ async fn main() -> anyhow::Result<ExitCode> {
|
||||
.unwrap_or_else(|| paths.cli_config_dir.cli_toml());
|
||||
let config = Config::load(cli_toml)?;
|
||||
|
||||
exec_subcommand(config, &paths, root_dir, cmd, subcommand_args).await
|
||||
exec_subcommand(config, &paths, root_dir, cmd, subcommand_args)
|
||||
.await
|
||||
.or_else(|e| e.downcast::<ExitWithCode>().map(|e| e.0))
|
||||
}
|
||||
|
||||
#[cfg(feature = "markdown-docs")]
|
||||
|
||||
@@ -1,14 +1,19 @@
|
||||
use anyhow::Context;
|
||||
use regex::Regex;
|
||||
use rolldown::{Bundler, BundlerOptions, Either, SourceMapType};
|
||||
use rolldown::{BundleOutput, Bundler, BundlerOptions, Either, SourceMapType};
|
||||
use rolldown_error::{BuildDiagnostic, DiagnosableResolveError, DiagnosticOptions, Severity};
|
||||
use rolldown_utils::indexmap::FxIndexMap;
|
||||
use rolldown_utils::js_regex::HybridRegex;
|
||||
use rolldown_utils::pattern_filter::StringOrRegex;
|
||||
use std::fs;
|
||||
use std::io::IsTerminal;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::ExitCode;
|
||||
use std::sync::{Arc, OnceLock};
|
||||
use tokio::runtime::{Builder, Handle, Runtime};
|
||||
|
||||
use crate::ExitWithCode;
|
||||
|
||||
static RUNTIME: OnceLock<Runtime> = OnceLock::new();
|
||||
|
||||
fn runtime() -> &'static Runtime {
|
||||
@@ -38,6 +43,33 @@ where
|
||||
|
||||
pub(crate) fn build_javascript(project_path: &Path, build_debug: bool) -> anyhow::Result<PathBuf> {
|
||||
let cwd = fs::canonicalize(project_path)?;
|
||||
|
||||
let mut tsc_path = cwd.join("node_modules/.bin/tsc");
|
||||
if cfg!(windows) {
|
||||
tsc_path.set_extension(".cmd");
|
||||
}
|
||||
if tsc_path.exists() {
|
||||
let status = std::process::Command::new(tsc_path)
|
||||
.arg("--noEmit")
|
||||
.current_dir(&cwd)
|
||||
.status()
|
||||
.context("Failed to execute tsc")?;
|
||||
if !status.success() {
|
||||
if let Some(code) = status.code() {
|
||||
if let Ok(code) = u8::try_from(code).map(ExitCode::from) {
|
||||
anyhow::bail!(ExitWithCode(code));
|
||||
}
|
||||
}
|
||||
// For an abnormal exit, show the details of the status.
|
||||
anyhow::bail!("tsc exited with {status}");
|
||||
}
|
||||
} else {
|
||||
eprintln!(
|
||||
"tsc not found in node_modules. Make sure you have the `typescript` package \
|
||||
as a dev-dependency and that your dependencies are installed."
|
||||
)
|
||||
}
|
||||
|
||||
let mut bundler = Bundler::new(BundlerOptions {
|
||||
input: Some(vec!["./src/index.ts".to_string().into()]),
|
||||
cwd: Some(cwd.clone()),
|
||||
@@ -188,14 +220,42 @@ pub(crate) fn build_javascript(project_path: &Path, build_debug: bool) -> anyhow
|
||||
strict_execution_order: None,
|
||||
})?;
|
||||
|
||||
let bundle_output = run_blocking(async move { bundler.write().await })?;
|
||||
let bundle_result = run_blocking(async move { bundler.write().await });
|
||||
|
||||
bundle_output.warnings.into_iter().for_each(|w| {
|
||||
eprintln!("Rolldown warning: {w}");
|
||||
});
|
||||
let (mut bundle_result, diagnostics) = match bundle_result {
|
||||
Ok(BundleOutput { warnings, assets }) => (Some(assets), warnings),
|
||||
Err(errors) => (None, errors.into_vec()),
|
||||
};
|
||||
|
||||
let output_chunk = bundle_output
|
||||
.assets
|
||||
let color = std::io::stderr().is_terminal();
|
||||
let diag_options = DiagnosticOptions { cwd };
|
||||
for mut diag in diagnostics {
|
||||
// if an import failed to resolve, force it to be an error.
|
||||
if let Some(err) = diag.downcast_mut::<DiagnosableResolveError>() {
|
||||
err.help = Some("Module not found".into());
|
||||
// `BuildDiagnostic` doesn't let us change its severity to error. Instead, we
|
||||
// construct a fresh `BuildDiagnostic` (which will have Severity::Error), then
|
||||
// swap in the real `DiagnosableResolveError`.
|
||||
let mut new_diag = BuildDiagnostic::resolve_error(
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
rolldown_error::DiagnosableArcstr::String(Default::default()),
|
||||
Default::default(),
|
||||
err.diagnostic_kind,
|
||||
Default::default(),
|
||||
);
|
||||
std::mem::swap(new_diag.downcast_mut::<DiagnosableResolveError>().unwrap(), err);
|
||||
diag = new_diag;
|
||||
}
|
||||
// if there are any errors, we want to bail after printing
|
||||
if diag.severity() == Severity::Error {
|
||||
bundle_result = None;
|
||||
}
|
||||
eprintln!("{}", diag.to_diagnostic_with(&diag_options).convert_to_string(color));
|
||||
}
|
||||
let bundle_assets = bundle_result.ok_or(ExitWithCode::FAILURE)?;
|
||||
|
||||
let output_chunk = bundle_assets
|
||||
.into_iter()
|
||||
.find_map(|chunk| match chunk {
|
||||
rolldown_common::Output::Chunk(chunk) if chunk.is_entry && chunk.filename == "bundle.js" => Some(chunk),
|
||||
|
||||
Reference in New Issue
Block a user