diff --git a/rsconnect/bundle.py b/rsconnect/bundle.py index fe02ae51..4960d992 100644 --- a/rsconnect/bundle.py +++ b/rsconnect/bundle.py @@ -1401,12 +1401,10 @@ def make_quarto_manifest( if isdir(file_or_directory): # Directory as a Quarto project. excludes = list(excludes or []) + [".quarto"] - project_config = quarto_inspection.get("config", {}).get("project", {}) output_dir = cast(Union[str, None], project_config.get("output-dir", None)) if output_dir: excludes = excludes + [output_dir] - files_data = quarto_inspection.get("files", {}) files_input_data = files_data.get("input", []) # files.input is a list of absolute paths to input (rendered) @@ -1419,18 +1417,24 @@ def make_quarto_manifest( for each in files_input_data: t, _ = splitext(os.path.relpath(each, file_or_directory)) excludes = excludes + [t + ".html", t + "_files/**/*"] - # relevant files don't need to include requirements.txt file because it is # always added to the manifest (as a buffer) from the environment contents if environment: excludes.append(environment.filename) - relevant_files = _create_quarto_file_list(base_dir, extra_files, excludes) else: # Standalone Quarto document - base_dir = dirname(file_or_directory) - file_name = basename(file_or_directory) - relevant_files = [file_name] + list(extra_files or []) + # Use the common directory of the qmd and any extra files as base_dir. + # This avoids having the subfolder appear in both base_dir and rel_path. + all_files = [file_or_directory] + extra_files + base_dir = os.path.commonpath(all_files) + + # Ensure base_dir is a directory, not a file + if os.path.isfile(base_dir): + base_dir = os.path.dirname(base_dir) + + # Store paths relative to base_dir + relevant_files = [os.path.relpath(p, base_dir) for p in all_files] manifest = make_source_manifest( app_mode, @@ -1441,13 +1445,10 @@ def make_quarto_manifest( env_management_py, env_management_r, ) - if environment: manifest_add_buffer(manifest, environment.filename, environment.contents) - for rel_path in relevant_files: manifest_add_file(manifest, rel_path, base_dir) - return manifest, relevant_files diff --git a/tests/test_bundle.py b/tests/test_bundle.py index a9f88196..b5419928 100644 --- a/tests/test_bundle.py +++ b/tests/test_bundle.py @@ -622,6 +622,53 @@ def test_make_quarto_source_bundle_from_file(self): }, ) + def test_make_quarto_source_bundle_from_file_with_extra_files(self): + temp_proj = tempfile.mkdtemp() + nested_dir = join(temp_proj, "subdir") + os.makedirs(nested_dir) + + filename = join(nested_dir, "myquarto.qmd") + with open(filename, "w") as fp: + fp.write("---\n") + fp.write("title: myquarto\n") + fp.write("engine: markdown\n") + fp.write("---\n\n") + fp.write("### This is a test\n") + + extra_filename = join(nested_dir, "data.csv") + with open(extra_filename, "w") as fp: + fp.write("a,b,c\n1,2,3\n") + + inspect = { + "quarto": {"version": "1.3.433"}, + "engines": ["markdown"], + } + + with make_quarto_source_bundle( + filename, + inspect, + AppModes.STATIC_QUARTO, + None, + [extra_filename], + [], + None, + ) as bundle, tarfile.open(mode="r:gz", fileobj=bundle) as tar: + names = sorted(tar.getnames()) + self.assertEqual( + names, + [ + "data.csv", + "manifest.json", + "myquarto.qmd", + ], + ) + + manifest = json.loads(tar.extractfile("manifest.json").read().decode("utf-8")) + self.assertEqual( + sorted(manifest["files"].keys()), + ["data.csv", "myquarto.qmd"], + ) + def test_list_files(self): # noinspection SpellCheckingInspection paths = [ @@ -1018,16 +1065,8 @@ def test_make_quarto_manifest_project_with_env(self): def test_make_quarto_manifest_project_with_extra_files(self): temp_proj = tempfile.mkdtemp() - # include extra_files parameter - fp = open(join(temp_proj, "a"), "w") - fp.write("This is file a\n") - fp.close() - fp = open(join(temp_proj, "b"), "w") - fp.write("This is file b\n") - fp.close() - fp = open(join(temp_proj, "c"), "w") - fp.write("This is file c\n") - fp.close() + for name, contents in [("a", "This is file a\n"), ("b", "This is file b\n"), ("c", "This is file c\n")]: + Path(temp_proj, name).write_text(contents) manifest, _ = make_quarto_manifest( temp_proj, @@ -1043,15 +1082,6 @@ def test_make_quarto_manifest_project_with_extra_files(self): None, ) - if sys.platform == "win32": - a_hash = "f4751c084b3ade4d736c6293ab8468c9" - b_hash = "4976d559975b5232cf09a10afaf8d0a8" - c_hash = "09c56e1b9e6ae34c6662717c47a7e187" - else: - a_hash = "4a3eb92956aa3e16a9f0a84a43c943e7" - b_hash = "b249e5b536d30e6282cea227f3a73669" - c_hash = "53b36f1d5b6f7fb2cfaf0c15af7ffb2d" - self.assertEqual( manifest, { @@ -1059,9 +1089,9 @@ def test_make_quarto_manifest_project_with_extra_files(self): "metadata": {"appmode": "quarto-shiny"}, "quarto": {"version": "0.9.16", "engines": ["jupyter"]}, "files": { - "a": {"checksum": a_hash}, - "b": {"checksum": b_hash}, - "c": {"checksum": c_hash}, + "a": {"checksum": mock.ANY}, + "b": {"checksum": mock.ANY}, + "c": {"checksum": mock.ANY}, }, }, )