diff --git a/CHANGELOG.md b/CHANGELOG.md index 7bad52b9..cd48bfe2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ Full release notes with details on each version: [GitHub Releases](https://githu - Feat: TypeScript extraction parity -- interface, enum, type alias, and module-level const nodes extracted; new_expression emits calls edges; parity with Java/C# class_types (#708) - Feat: Quarto (`.qmd`) file support -- routed through existing Markdown extractor; Quarto executable code blocks (` ```{python} `) extracted as code nodes (#761) - Feat: optional Google Workspace shortcut export for headless extraction -- `graphify extract ./docs --google-workspace` converts `.gdoc`, `.gsheet`, and `.gslides` files into Markdown sidecars with the `gws` CLI before semantic extraction; account email pseudonymized via SHA256 hash; `[google]` extra adds Sheets table rendering support (#752) +- Fix: Google Workspace exports now run `gws` from the sidecar output directory with a relative `-o` path, matching `gws` path validation and avoiding failures when extracting a corpus outside the current working directory. - Feat: AWS Bedrock backend -- `graphify extract ./docs --backend bedrock`; credentials via standard AWS provider chain (AWS_PROFILE, AWS_REGION, IAM roles, SSO); model via GRAPHIFY_BEDROCK_MODEL (default anthropic.claude-3-5-sonnet-20241022-v2:0); `[bedrock]` extra adds boto3 (#757) ## 0.7.8 (2026-05-06) diff --git a/graphify/google_workspace.py b/graphify/google_workspace.py index e80b082f..e9e60d81 100644 --- a/graphify/google_workspace.py +++ b/graphify/google_workspace.py @@ -100,12 +100,17 @@ def _run_gws_export(file_id: str, mime_type: str, output: Path, resource_key: st ) params: dict[str, str] = {"fileId": file_id, "mimeType": mime_type} - if resource_key: - params["resourceKey"] = resource_key + # Drive resource keys are sent via X-Goog-Drive-Resource-Keys. The current + # gws export command has no custom-header flag, so do not pass resourceKey + # as an unsupported query parameter. + _ = resource_key + output = output.resolve() + output.parent.mkdir(parents=True, exist_ok=True) timeout = int(os.environ.get("GRAPHIFY_GOOGLE_WORKSPACE_TIMEOUT", "120")) result = subprocess.run( - [exe, "drive", "files", "export", "--params", json.dumps(params), "-o", str(output)], + [exe, "drive", "files", "export", "--params", json.dumps(params), "-o", output.name], capture_output=True, + cwd=output.parent, text=True, timeout=timeout, ) diff --git a/tests/test_google_workspace.py b/tests/test_google_workspace.py index 0f0c1b42..9d8cbfa4 100644 --- a/tests/test_google_workspace.py +++ b/tests/test_google_workspace.py @@ -1,4 +1,5 @@ from pathlib import Path +import json import graphify.google_workspace as gw @@ -73,6 +74,53 @@ def test_convert_gsheet_uses_xlsx_markdown_callback(tmp_path, monkeypatch): assert "## Sheet: Main" in out.read_text(encoding="utf-8") +def test_run_gws_export_uses_output_directory_as_cwd(tmp_path, monkeypatch): + output = tmp_path / "converted" / "doc.md" + calls = [] + + class Result: + returncode = 0 + stdout = "" + stderr = "" + + def fake_run(cmd, **kwargs): + calls.append((cmd, kwargs)) + return Result() + + monkeypatch.setattr(gw.shutil, "which", lambda name: "/usr/local/bin/gws") + monkeypatch.setattr(gw.subprocess, "run", fake_run) + + gw._run_gws_export("doc-123", "text/markdown", output) + + assert output.parent.exists() + cmd, kwargs = calls[0] + assert kwargs["cwd"] == output.parent.resolve() + assert cmd[:4] == ["/usr/local/bin/gws", "drive", "files", "export"] + assert cmd[-2:] == ["-o", "doc.md"] + + +def test_run_gws_export_does_not_send_resource_key_as_query_param(tmp_path, monkeypatch): + output = tmp_path / "converted" / "doc.md" + calls = [] + + class Result: + returncode = 0 + stdout = "" + stderr = "" + + def fake_run(cmd, **kwargs): + calls.append(cmd) + return Result() + + monkeypatch.setattr(gw.shutil, "which", lambda name: "/usr/local/bin/gws") + monkeypatch.setattr(gw.subprocess, "run", fake_run) + + gw._run_gws_export("doc-123", "text/markdown", output, resource_key="rk-1") + + params = json.loads(calls[0][calls[0].index("--params") + 1]) + assert params == {"fileId": "doc-123", "mimeType": "text/markdown"} + + def test_google_workspace_enabled_env(monkeypatch): monkeypatch.setenv("GRAPHIFY_GOOGLE_WORKSPACE", "yes") assert gw.google_workspace_enabled()