diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 78a81d98..01948904 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -7,7 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased -- Added `rsconnect deploy nodejs` command for deploying Node.js API applications +- Added `rsconnect deploy nodejs` command for deploying Node.js applications (Express, Fastify, etc.) to Posit Connect. Supports JavaScript and TypeScript entry points with auto-detection from package.json. Requires Posit Connect with Node.js runtime enabled. diff --git a/rsconnect/bundle.py b/rsconnect/bundle.py index fc18efd8..6e855cf0 100644 --- a/rsconnect/bundle.py +++ b/rsconnect/bundle.py @@ -1362,7 +1362,7 @@ def make_nodejs_manifest( env_management_node: Optional[bool] = None, ) -> tuple[ManifestData, list[str]]: """ - Makes a manifest for a Node.js API application. + Makes a manifest for a Node.js application. :param directory: the directory containing the files to deploy. :param entry_point: the main entry point file (e.g., "app.js"). @@ -1386,7 +1386,7 @@ def make_nodejs_manifest( manifest: ManifestData = { "version": 1, "metadata": { - "appmode": AppModes.NODE_API.name(), + "appmode": AppModes.NODE_JS.name(), "entrypoint": entry_point, }, "node": { @@ -1427,7 +1427,7 @@ def make_nodejs_bundle( env_management_node: Optional[bool] = None, ) -> typing.IO[bytes]: """ - Create a Node.js API bundle, given a directory path. + Create a Node.js application bundle, given a directory path. :param directory: the directory containing the files to deploy. :param entry_point: the main entry point file (e.g., "app.js"). @@ -2148,7 +2148,7 @@ def write_nodejs_manifest_json( env_management_node: Optional[bool] = None, ) -> None: """ - Creates and writes a manifest.json file for a Node.js API application. + Creates and writes a manifest.json file for a Node.js application. :param directory: the root directory of the Node.js application. :param entry_point: the entry point file (e.g., "app.js"). diff --git a/rsconnect/main.py b/rsconnect/main.py index eac0b220..b2dc0dcc 100644 --- a/rsconnect/main.py +++ b/rsconnect/main.py @@ -2130,9 +2130,9 @@ def deploy_app( # noinspection SpellCheckingInspection @deploy.command( name="nodejs", - short_help="Deploy a Node.js API to Posit Connect.", + short_help="Deploy a Node.js application to Posit Connect.", help=( - "Deploy a Node.js API application to Posit Connect. " + "Deploy a Node.js application to Posit Connect. " 'The "directory" argument must refer to an existing directory that contains ' "a package.json file and a JavaScript or TypeScript entry point." ), @@ -2222,7 +2222,7 @@ def deploy_nodejs( extra_files_list = validate_extra_files(directory, extra_files) node_environment = NodeEnvironment.create(directory, node_executable=node) - app_mode = AppModes.NODE_API + app_mode = AppModes.NODE_JS server_version = None @@ -2907,9 +2907,9 @@ def manifest_writer( # noinspection SpellCheckingInspection @write_manifest.command( name="nodejs", - short_help="Create a manifest.json file for a Node.js API.", + short_help="Create a manifest.json file for a Node.js application.", help=( - "Create a manifest.json file for a Node.js API for later deployment. " + "Create a manifest.json file for a Node.js application for later deployment. " "All files are created in the same directory as the application code." ), ) diff --git a/rsconnect/models.py b/rsconnect/models.py index 797256f4..fc23f02b 100644 --- a/rsconnect/models.py +++ b/rsconnect/models.py @@ -99,7 +99,7 @@ class AppModes: JUPYTER_VOILA = AppMode(16, "jupyter-voila", "Jupyter Voila Application") PYTHON_GRADIO = AppMode(17, "python-gradio", "Gradio Application") PYTHON_PANEL = AppMode(18, "python-panel", "Panel Application") - NODE_API = AppMode(20, "nodejs-api", "Node.js API") + NODE_JS = AppMode(20, "nodejs", "Node.js application") _modes = [ UNKNOWN, @@ -121,7 +121,7 @@ class AppModes: JUPYTER_VOILA, PYTHON_GRADIO, PYTHON_PANEL, - NODE_API, + NODE_JS, ] Modes = Literal[ @@ -144,7 +144,7 @@ class AppModes: "jupyter-voila", "python-gradio", "python-panel", - "nodejs-api", + "nodejs", ] _cloud_to_connect_modes = { @@ -168,9 +168,17 @@ def get_by_ordinal(cls, ordinal: int, return_unknown: bool = False) -> AppMode: return_unknown, ) + # Aliases for app mode names that have been renamed in Connect. Local + # AppStore metadata may still contain the old name from a prior deploy; + # this lets resolve() succeed instead of raising on re-deploys. + _name_aliases = { + "nodejs-api": "nodejs", + } + @classmethod def get_by_name(cls, name: str, return_unknown: bool = False) -> AppMode: """Get an AppMode by name""" + name = cls._name_aliases.get(name, name) return cls._find_by(lambda mode: mode.name() == name, "named %s" % name, return_unknown) @classmethod diff --git a/tests/test_bundle.py b/tests/test_bundle.py index eb0680f5..3420bb29 100644 --- a/tests/test_bundle.py +++ b/tests/test_bundle.py @@ -3048,7 +3048,7 @@ def test_manifest_structure(self): manifest, files = make_nodejs_manifest(_NODE_EXPRESS_DIR, "app.js", env, [], []) assert manifest["version"] == 1 - assert manifest["metadata"]["appmode"] == "nodejs-api" + assert manifest["metadata"]["appmode"] == "nodejs" assert manifest["metadata"]["entrypoint"] == "app.js" assert manifest["node"]["version"] == "22.22.1" assert manifest["node"]["package_manager"]["name"] == "npm" @@ -3120,7 +3120,7 @@ def test_bundle_manifest_content(self): with tarfile.open(mode="r:gz", fileobj=bundle_file) as tar: manifest = json.loads(tar.extractfile("manifest.json").read().decode("utf-8")) - assert manifest["metadata"]["appmode"] == "nodejs-api" + assert manifest["metadata"]["appmode"] == "nodejs" assert manifest["metadata"]["entrypoint"] == "app.js" assert manifest["node"]["version"] == "22.22.1" @@ -3190,7 +3190,7 @@ def test_ts_manifest_structure(self): env = _make_node_env(node_version="24.14.0") manifest, files = make_nodejs_manifest(_NODE_TS_EXPRESS_DIR, "app.ts", env, [], []) - assert manifest["metadata"]["appmode"] == "nodejs-api" + assert manifest["metadata"]["appmode"] == "nodejs" assert manifest["metadata"]["entrypoint"] == "app.ts" assert manifest["node"]["version"] == "24.14.0" diff --git a/tests/test_main.py b/tests/test_main.py index a446631d..e841f5a2 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -1081,7 +1081,7 @@ def test_help(self): runner = CliRunner() result = runner.invoke(cli, ["deploy", "nodejs", "--help"]) assert result.exit_code == 0 - assert "Node.js API" in result.output + assert "Node.js application" in result.output assert "--entrypoint" in result.output assert "--node" in result.output assert "--exclude" in result.output @@ -1128,7 +1128,7 @@ def test_help(self): runner = CliRunner() result = runner.invoke(cli, ["write-manifest", "nodejs", "--help"]) assert result.exit_code == 0 - assert "Node.js API" in result.output + assert "Node.js application" in result.output assert "--entrypoint" in result.output assert "--node" in result.output assert "--overwrite" in result.output