feat: use serde to build package json and check npm package name#2428
feat: use serde to build package json and check npm package name#2428
Conversation
There was a problem hiding this comment.
Pull request overview
This PR hardens TypeScript bindings project generation by validating the generated npm package name and updating package.json via JSON parsing/serialization (instead of raw string replacement) to prevent invalid or injectable package.json output.
Changes:
- Add
validate_npm_package_name(with tests) to enforce npm naming rules, including scoped names. - Validate the
--output-dirbasename / contract name before generating the TS bindings project. - Update TS project template handling to set
package.json.namethroughserde_json, enablingpreserve_order.
Reviewed changes
Copilot reviewed 4 out of 5 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| cmd/soroban-cli/src/commands/contract/bindings/typescript.rs | Validates --output-dir basename as an npm package name and introduces a new CLI error variant. |
| cmd/crates/soroban-spec-typescript/src/lib.rs | Adds npm package name validation helper and unit tests. |
| cmd/crates/soroban-spec-typescript/src/boilerplate.rs | Switches package.json update to serde_json parsing/serialization; adds tests for name setting and invalid input. |
| cmd/crates/soroban-spec-typescript/Cargo.toml | Enables serde_json’s preserve_order feature to keep template key ordering stable. |
| Cargo.lock | Updates lockfile to include new transitive dependency introduced by preserve_order. |
| soroban_spec_typescript::validate_npm_package_name(contract_name) | ||
| .map_err(Error::InvalidContractName)?; |
There was a problem hiding this comment.
validate_npm_package_name returns an error reason string, but it’s being passed directly into Error::InvalidContractName. As a result, the error message will print the reason where it claims to print the --output-dir basename, and it won’t include the actual invalid basename. Consider changing InvalidContractName to carry both the basename and the validation reason (or format them together) and map the error with a closure so the message is accurate.
| obj.insert( | ||
| "name".to_string(), | ||
| serde_json::Value::String(contract_name.to_string()), | ||
| ); |
There was a problem hiding this comment.
If package.json parses successfully but isn’t a JSON object, this silently skips updating the name field and writes the file back unchanged. Since the template is expected to be an object, it would be safer to return an InvalidData error when json.as_object_mut() is None so template regressions are caught early.
| ); | |
| ); | |
| } else { | |
| return Err(std::io::Error::new( | |
| std::io::ErrorKind::InvalidData, | |
| "package.json template must be a JSON object", | |
| )); |
| for key in obj.keys() { | ||
| assert!( | ||
| expected_keys.contains(&key.as_str()), | ||
| "unexpected key in package.json: {key}" |
There was a problem hiding this comment.
This test hard-codes the complete set of top-level keys in package.json and will fail if the template is legitimately extended (e.g., adding license, files, keywords). To reduce brittleness, consider asserting that the expected keys are present (and/or that key count is unchanged) rather than rejecting any new keys.
| for key in obj.keys() { | |
| assert!( | |
| expected_keys.contains(&key.as_str()), | |
| "unexpected key in package.json: {key}" | |
| for key in expected_keys { | |
| assert!( | |
| obj.contains_key(key), | |
| "missing expected key in package.json: {key}" |
| let contract_name = &file_name | ||
| .to_str() | ||
| .ok_or_else(|| Error::NotUtf8(file_name.to_os_string()))?; | ||
| soroban_spec_typescript::validate_npm_package_name(contract_name) |
There was a problem hiding this comment.
validate_npm_package_name is called here and then again in boilerplate.rs::init(). Not a big deal, but we'll hit it twice.
| }) | ||
| } | ||
|
|
||
| fn replace_package_json(&self, root: &Path, contract_name: &str) -> std::io::Result<()> { |
There was a problem hiding this comment.
This doesn't use self, so maybe we could extract it as a static function?
What
Use serde to modify package.json file during typescript binding generation. And check that the npm package name is valid.
Why
Fixes the issue where users can create invalid package.json files
Known limitations
None