-
Notifications
You must be signed in to change notification settings - Fork 1
run wavedash dev with local sdk #56
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -28,13 +28,23 @@ const SDK_JS_VERSION: &str = include_str!("sdk-js-version"); | |
| /// Classic parser-blocking IIFE (auto-runs setupWavedashSDK): the only way | ||
| /// `window.Wavedash` exists before game scripts parse — module scripts are | ||
| /// always deferred. jsdelivr sends ACAO * + CORP, satisfying COEP. | ||
| fn inject_url() -> String { | ||
| fn cdn_inject_url() -> String { | ||
| format!( | ||
| "https://cdn.jsdelivr.net/npm/@wvdsh/sdk-js@{}/dist/inject.global.js", | ||
| SDK_JS_VERSION.trim() | ||
| ) | ||
| } | ||
|
|
||
| /// Same-origin, which COEP allows without the CORP a cross-origin build needs. | ||
| const LOCAL_SDK_URL: &str = "/__wavedash/sdk.js"; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Serving the bundle under a renamed URL breaks its sourcemap, which is exactly what you want when debugging a local sdk-js build. A dev Cheapest fix: serve any sibling |
||
|
|
||
| fn inject_url(sdk_js: Option<&Path>) -> String { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Since // in ServeConfig
/// The SDK bundle URL injected into every page — local when `sdk_js` is set.
pub inject_url: String,Then Not worth blocking on — but if you'd rather keep the current shape, |
||
| match sdk_js { | ||
| Some(_) => LOCAL_SDK_URL.to_string(), | ||
| None => cdn_inject_url(), | ||
| } | ||
| } | ||
|
|
||
| /// `{{NAME}}` placeholders substitute data only — logic stays in the template. | ||
| const SHELL_TEMPLATE: &str = include_str!("shell.html"); | ||
| const DEV_JS: &str = include_str!("dev.js"); | ||
|
|
@@ -64,6 +74,8 @@ pub struct ServeConfig { | |
| pub client: reqwest::Client, | ||
| /// Engine builds boot via play's real default entrypoint — the prod path. | ||
| pub engine_entry: Option<EngineEntry>, | ||
| /// Local sdk-js bundle to serve in place of the pinned CDN build. | ||
| pub sdk_js: Option<PathBuf>, | ||
| /// Backend public keys (/.well-known/jwks.json), fetched once on demand. | ||
| pub jwks: tokio::sync::OnceCell<jsonwebtoken::jwk::JwkSet>, | ||
| } | ||
|
|
@@ -100,6 +112,7 @@ pub async fn run(listener: TcpListener, cfg: ServeConfig) -> Result<()> { | |
| let mut app = Router::new() | ||
| .route("/__wavedash/callback", get(handle_callback)) | ||
| .route("/__wavedash/dev.js", get(handle_dev_js)) | ||
| .route(LOCAL_SDK_URL, get(handle_sdk_js)) | ||
| .route("/auth/refresh", post(handle_auth_refresh)) | ||
| .route("/", get(handle_index)) | ||
| .fallback(get(handle_static)) | ||
|
|
@@ -490,14 +503,20 @@ async fn handle_index( | |
|
|
||
| if let Some(engine_entry) = &cfg.engine_entry { | ||
| let html = shell( | ||
| cfg.sdk_js.as_deref(), | ||
| &engine_entry.entrypoint_url, | ||
| Some(&engine_entry.params_json), | ||
| &config, | ||
| ); | ||
| return respond(StatusCode::OK, HTML, None, html); | ||
| } | ||
| if cfg.entry.ends_with(".js") { | ||
| let html = shell(&format!("/{}", cfg.entry), None, &config); | ||
| let html = shell( | ||
| cfg.sdk_js.as_deref(), | ||
| &format!("/{}", cfg.entry), | ||
| None, | ||
| &config, | ||
| ); | ||
| return respond(StatusCode::OK, HTML, None, html); | ||
| } | ||
| // The HTML entry serves at its real path so relative assets resolve. | ||
|
|
@@ -529,6 +548,22 @@ async fn handle_dev_js() -> Response { | |
| respond(StatusCode::OK, "text/javascript; charset=utf-8", None, DEV_JS) | ||
| } | ||
|
|
||
| /// Read per request, so rebuilding sdk-js only needs a page reload. | ||
| async fn handle_sdk_js(State(cfg): State<Arc<ServeConfig>>) -> Response { | ||
| let Some(path) = cfg.sdk_js.as_deref() else { | ||
| return respond(StatusCode::NOT_FOUND, TEXT, None, "Not Found"); | ||
| }; | ||
| match std::fs::read(path) { | ||
| Ok(bytes) => respond(StatusCode::OK, "text/javascript; charset=utf-8", None, bytes), | ||
| Err(e) => respond( | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The read-failure path fails silently from the user's point of view. The doc comment invites the workflow "rebuild sdk-js, reload the page", so reloading mid-rebuild (file momentarily missing, or read back truncated) is a realistic race. In either case Consider responding with JS that surfaces the reason through the existing gate, e.g. body |
||
| StatusCode::INTERNAL_SERVER_ERROR, | ||
| TEXT, | ||
| None, | ||
| format!("could not read {}: {e}", path.display()), | ||
| ), | ||
| } | ||
| } | ||
|
|
||
| /// Authed HTML navigations get the config global + SDK tags injected (prod's | ||
| /// embed.js mechanism); everything else streams as-is. | ||
| fn serve_file(cfg: &ServeConfig, url_path: &str, config: Option<&str>) -> Response { | ||
|
|
@@ -542,7 +577,11 @@ fn serve_file(cfg: &ServeConfig, url_path: &str, config: Option<&str>) -> Respon | |
| let (content_type, encoding) = content_type_and_encoding(url_path); | ||
| if let Some(config) = config { | ||
| if encoding.is_none() && content_type.starts_with("text/html") { | ||
| let injected = inject_sdk(&String::from_utf8_lossy(&bytes), config); | ||
| let injected = inject_sdk( | ||
| cfg.sdk_js.as_deref(), | ||
| &String::from_utf8_lossy(&bytes), | ||
| config, | ||
| ); | ||
| return respond(StatusCode::OK, HTML, None, injected); | ||
| } | ||
| } | ||
|
|
@@ -572,9 +611,14 @@ fn respond( | |
| /// Boot shell: play's real default entrypoint for engine builds (the prod | ||
| /// path), the game's own script with null params for `.js` entries. The `<` | ||
| /// escape keeps params values from closing the script tag. | ||
| fn shell(script_src: &str, params_json: Option<&str>, config: &str) -> String { | ||
| fn shell( | ||
| sdk_js: Option<&Path>, | ||
| script_src: &str, | ||
| params_json: Option<&str>, | ||
| config: &str, | ||
| ) -> String { | ||
| SHELL_TEMPLATE | ||
| .replace("{{INJECT_URL}}", &inject_url()) | ||
| .replace("{{INJECT_URL}}", &inject_url(sdk_js)) | ||
| .replace( | ||
| "{{ENTRYPOINT_PARAMS}}", | ||
| ¶ms_json.unwrap_or("null").replace('<', "\\u003c"), | ||
|
|
@@ -593,13 +637,13 @@ fn js_string_literal(s: &str) -> String { | |
|
|
||
| /// Inject the config global + SDK bundle + init gate right after | ||
| /// `<head ...>`, or prepended if there's no head. | ||
| fn inject_sdk(html: &str, config: &str) -> String { | ||
| fn inject_sdk(sdk_js: Option<&Path>, html: &str, config: &str) -> String { | ||
| let tags = format!( | ||
| "<script>window.__wavedashSdkConfig = {};</script>\ | ||
| <script src=\"{}\" crossorigin=\"anonymous\"></script>\ | ||
| <script src=\"/__wavedash/dev.js\"></script>", | ||
| js_string_literal(config), | ||
| inject_url() | ||
| inject_url(sdk_js) | ||
| ); | ||
| match head_insert_pos(&html.to_ascii_lowercase()) { | ||
| Some(pos) => format!("{}{}{}", &html[..pos], tags, &html[pos..]), | ||
|
|
@@ -691,11 +735,18 @@ mod tests { | |
|
|
||
| #[test] | ||
| fn the_inject_url_never_carries_the_version_files_trailing_newline() { | ||
| let url = inject_url(); | ||
| let url = inject_url(None); | ||
| assert!(!url.contains(char::is_whitespace), "{url:?}"); | ||
| assert!( | ||
| url.contains(&format!("@wvdsh/sdk-js@{}/", SDK_JS_VERSION.trim())), | ||
| "{url:?}" | ||
| ); | ||
| } | ||
|
|
||
| #[test] | ||
| fn a_local_bundle_is_served_from_this_origin() { | ||
| let url = inject_url(Some(Path::new("/tmp/sdk-js/dist/inject.global.js"))); | ||
| assert_eq!(url, LOCAL_SDK_URL); | ||
| assert!(!url.contains("jsdelivr"), "{url:?}"); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -94,6 +94,13 @@ enum Commands { | |
| help = "Attribute the build to the tool running the CLI instead of the CLI itself" | ||
| )] | ||
| upload_source: Option<UploadSource>, | ||
| #[arg( | ||
| long = "sdk-js", | ||
| value_name = "PATH", | ||
| hide = true, | ||
| help = "Serve a local sdk-js build instead of the pinned CDN one — an inject.global.js, or a directory holding it" | ||
| )] | ||
| sdk_js: Option<PathBuf>, | ||
| }, | ||
| #[command( | ||
| about = "Publish an uploaded build to wavedash.com", | ||
|
|
@@ -645,12 +652,14 @@ async fn run() -> Result<()> { | |
| config, | ||
| no_open, | ||
| upload_source, | ||
| sdk_js, | ||
| } => { | ||
| handle_dev( | ||
| config, | ||
| cli.verbose, | ||
| no_open, | ||
| upload_source.unwrap_or_default(), | ||
| sdk_js, | ||
| ) | ||
| .await?; | ||
| } | ||
|
|
@@ -930,51 +939,6 @@ mod tests { | |
| } | ||
| } | ||
|
|
||
| #[test] | ||
| fn upload_source_is_hidden_and_only_offers_the_godot_plugin() { | ||
| fn walk(cmd: &clap::Command, path: &[String], found: &mut Vec<String>) { | ||
| if let Some(arg) = cmd | ||
| .get_arguments() | ||
| .find(|arg| arg.get_long() == Some("upload-source")) | ||
| { | ||
| let command = path.join(" "); | ||
| assert!( | ||
| arg.is_hide_set(), | ||
| "`{}` lists --upload-source in its help", | ||
| command | ||
| ); | ||
| let values: Vec<String> = arg | ||
| .get_possible_values() | ||
| .into_iter() | ||
| .map(|value| value.get_name().to_string()) | ||
| .collect(); | ||
| assert_eq!( | ||
| values, | ||
| ["godot-plugin"], | ||
| "`{}` offers a source other than the Godot plugin's", | ||
| command | ||
| ); | ||
| found.push(command); | ||
| } | ||
|
|
||
| for sub in cmd.get_subcommands() { | ||
| let mut sub_path = path.to_vec(); | ||
| sub_path.push(sub.get_name().to_string()); | ||
| walk(sub, &sub_path, found); | ||
| } | ||
| } | ||
|
|
||
| let mut found = Vec::new(); | ||
| walk(&Cli::command(), &["wavedash".to_string()], &mut found); | ||
| found.sort(); | ||
|
|
||
| assert_eq!( | ||
| found, | ||
| ["wavedash build push", "wavedash dev"], | ||
| "every command that creates a build row should be able to name its source" | ||
| ); | ||
| } | ||
|
|
||
| #[test] | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This PR deletes If the concern is the test name advertising a hidden flag, renaming it (e.g. |
||
| fn upload_source_parses_the_plugin_and_defaults_to_the_cli() { | ||
| fn push_source(argv: &[&str]) -> Option<UploadSource> { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
resolve_sdk_jsis the one piece of new logic here with real branching — dir →dist/inject.global.js→<dir>/inject.global.js→ direct-file passthrough → error — and it's the piece a user hits first and most often (--sdk-js ~/code/sdk-jsvs--sdk-js ~/code/sdk-js/distvs a direct path). It has no test, while the much thinnerinject_urlswitch got one (a_local_bundle_is_served_from_this_origin).tempfileis already a dev-dependency andsrc/config.rs:1026has thetempfile::tempdir()pattern to copy, so this is cheap:The
distcase is the one worth locking down: the candidate order is what keeps it from resolvingdist/dist/inject.global.js, and reordering thevec!would silently break it.