Commit cb8dcf8
Add
* Add `quarto publish posit-connect-cloud` provider
Implements a new publish provider for Posit Connect Cloud
(connect.posit.cloud) supporting static content publishing.
Authentication uses OAuth 2.0 Device Code flow (RFC 8628) with
dual-strategy token refresh (proactive before expiry + reactive
on 401). Bundle upload follows the rsconnect pattern: create
tar.gz with manifest.json, upload to presigned URL, then trigger
revision-based deployment with polling.
Key implementation details:
- Environment-aware: production, staging, development configs
selectable via CONNECT_CLOUD_ENVIRONMENT env var
- CI/CD support via CONNECT_CLOUD_ACCESS_TOKEN env var
- Multi-account selection when user belongs to multiple orgs
- Account creation polling for users without publishable accounts
- Content state tracking: handles deleted content gracefully
Staging-first development: defaults to staging environment while
production client_id (quarto-cli) registration is pending. The
staging client_id (quarto-cli-staging) is available now.
Closes #14027
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* docs: add publishing architecture overview for LLM context
* Move OAuth params to POST body, consume publish response, add account polling spinner
Move OAuth parameters from URL query strings to POST request body in
initiateDeviceAuth, pollForToken, and refreshAccessToken per RFC 6749
§2.3.1 best practice — keeps refresh_token out of server/proxy access
logs. Add response body consumption in publishContent to prevent
resource leak. Wrap account creation polling in withSpinner for progress
indication.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Rename to use full name posit-connect-cloud
* Address design review: eliminate non-null assertions, use async file read
Restructure publish() to return values from withSpinner callback via
destructuring, removing all non-null assertions. Switch bundle read from
Deno.readFileSync to async Deno.readFile. Update architecture doc to
reflect posit-connect-cloud provider addition.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Update copyright year
* Avoid duplicate disk read for stored token in publish flow
clientForAccount already called findStoredToken internally, but
publish() also called it separately. Pass the token through instead.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Add manual test fixtures for posit-connect-cloud publishing
Minimal single document and website project for manual testing
against the staging environment.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Fix publish bugs: document staging, server URL display, getUser 401
Use renderForPublish() instead of render() directly so single documents
are staged correctly (document.html copied to index.html). Without this,
the Connect Cloud API rejects the bundle with "Unable to locate index.html".
Set server URL in AccountToken objects so account listings show which
environment is being used (e.g. "cderv (https://staging.connect.posit.cloud)").
Wrap getUser() in try/catch to handle 401 for users who authenticated with
Posit but haven't completed Connect Cloud signup. The account creation
polling in Step 6 handles this case.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Add gitignore for test fixtures, document renderForPublish in llm-docs
Add .gitignore to manual test fixture directories to exclude rendered
output, _publish.yml, and _site/ from version control.
Document renderForPublish() in publishing-architecture.md — providers
publishing documents must use this instead of render() directly to
ensure proper staging (HTML→index.html, PDF→pdf.js wrapper).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Add manual testing protocol for posit-connect-cloud publishing
Covers 12 tests: authorization, token persistence, website publishing,
content updates, account management, CI/CD mode, provider selection,
deprecation warnings, error cases, and token refresh.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Address review findings: add debug log for 401, fix test protocol docs
Add debug trace when getUser returns 401 during authorization so the
event is visible in verbose output. Fix manual testing protocol: remove
unreferenced REFRESH_TOKEN from cleanup, add refreshToken pre-condition
to Test 10.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Consume response body before 401 retry to prevent connection leak
In fetchWithRetry_, the original 401 response body was not consumed
before making the retry request after token refresh. Unconsumed
response bodies in Deno's fetch can leak TCP connections.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Guard against empty accountId before content creation
Validates accountId is non-empty before calling createContent(),
preventing a confusing API error if stored token data is corrupted.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Trim accountId in guard to also reject whitespace-only values
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Add publishDebug wrapper, help example, and --token guard
- Centralize the [publish][posit-connect-cloud] debug prefix into a
publishDebug() helper (exported as positConnectCloudDebug) to avoid
repeating the prefix across 30 debug calls in two files.
- Add quarto publish posit-connect-cloud example to help text.
- Reject --token early with a clear error directing users to the
environment variable approach for CI/CD (Connect Cloud uses
short-lived OAuth tokens, not permanent API keys).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Switch default environment from staging to production
The production client_id (quarto-cli) is now registered.
Staging/development environments remain available via
POSIT_CONNECT_CLOUD_ENVIRONMENT for testing.
* Remove outdated _README.md for manual test fixtures
README.md already covers the full testing protocol with correct
production-default instructions.
* Address review findings for posit-connect-cloud provider
Fix two bugs: guard writeAccessToken on non-empty accountId to prevent
ghost tokens from env pseudo-tokens (.1), and skip proactive refresh
when expiresAt is 0 to avoid redundant refreshes for env tokens (.2).
Unify account selection after polling to match rsconnect behavior —
after account creation polling succeeds, fall through to the same
selection logic used for pre-existing accounts (.6).
Additional improvements: add listAccounts pagination comment (.4),
extract buildUrl_ helper (.5), pass stored token explicitly in
resolveTarget (.7), move timeout check before sleep in pollForToken
(.8), extract kOAuthScope constant (.9).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Address review findings batch 2 for posit-connect-cloud provider
- Pass siteUrl to renderForPublish for site re-publishes so canonical
URLs and external link filtering work correctly (.11)
- Remove dead findStoredToken fallback in clientForAccount, make
storedToken parameter explicit (.12)
- Narrow catch in account polling loop to ApiError|TypeError, rethrow
programming bugs instead of silently swallowing them (.14)
- Add comment explaining response body drain in publishContent (.15)
- Add external link to simple-website fixture for siteUrl testability
- Add siteUrl verification items to Test 4 in manual testing README
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Revert siteUrl pass-through for Connect Cloud rendering (.11)
During production testing, we discovered that Connect Cloud serves
content at a different domain than the dashboard URL stored in
_publish.yml:
- Dashboard URL: connect.posit.cloud/account/content/<id>
- Serving URL: <id>.share.connect.posit.cloud/
Passing the dashboard URL as siteUrl would override the correct
window.location.host fallback in linkExternalFilter, causing all
links to be misclassified as external. The same mismatch would
affect canonical URLs and Open Graph metadata.
This differs from gh-pages where the published URL and serving URL
are the same domain, making siteUrl pass-through correct there.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Address review findings batch 3 for posit-connect-cloud provider
Three hardening fixes:
- Trim POSIT_CONNECT_CLOUD_ACCOUNT_ID env var to prevent CI/CD
copy-paste whitespace from causing silent match failure (.17)
- Enforce minimum 5-second device code poll interval per RFC 8628 §3.5
to prevent tight loop if server returns interval=0 (.18)
- Catch transient errors (HTTP 500, network timeouts) during 30-minute
revision polling with consecutive error threshold before aborting,
since ~1,800 poll calls makes transient failures likely (.19)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Add debug log for 404 in resolveTarget, clarify Test 9b expectations
During production testing, deleted content was silently handled by
resolveTarget returning undefined (correct behavior). Added debug
logging for the 404 case to match the existing deleted-state logging.
Updated README to document the actual flow: silent detection, no
prompt for stale target, falls through to new publish.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Fail on ambiguous account selection in non-interactive mode
Other publish providers fail when --no-prompt is set and multiple
accounts exist, via the shared resolveAccount logic. Connect Cloud
bypasses that shared path for environment token account resolution,
so it needs its own guard: throw an error when multiple publishable
accounts exist without POSIT_CONNECT_CLOUD_ACCOUNT_ID in
non-interactive mode, instead of silently picking the first.
Also add Test 6b to the manual testing protocol covering this case.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Update publishing architecture doc for posit-connect-cloud provider
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* reduce copyright years on new files
* Extract buildHeaders helper to deduplicate auth headers in fetchWithRetry
* Add a note on where the handled error code comes from
* Use a `postFormUrlEncoded` to make clear this is the same call
* Use error helpers everywhere they should be used
* Add a note about revision logic
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>quarto publish posit-connect-cloud provider (#14041)1 parent aaaba63 commit cb8dcf8
14 files changed
Lines changed: 1922 additions & 3 deletions
File tree
- llm-docs
- news
- src
- command/publish
- publish
- posit-connect-cloud
- api
- tests/docs/manual/publish-connect-cloud
- simple-website
- single-doc
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
| 201 | + | |
| 202 | + | |
| 203 | + | |
| 204 | + | |
| 205 | + | |
| 206 | + | |
| 207 | + | |
| 208 | + | |
| 209 | + | |
| 210 | + | |
| 211 | + | |
| 212 | + | |
| 213 | + | |
| 214 | + | |
| 215 | + | |
| 216 | + | |
| 217 | + | |
| 218 | + | |
| 219 | + | |
| 220 | + | |
| 221 | + | |
| 222 | + | |
| 223 | + | |
| 224 | + | |
| 225 | + | |
| 226 | + | |
| 227 | + | |
| 228 | + | |
| 229 | + | |
| 230 | + | |
| 231 | + | |
| 232 | + | |
| 233 | + | |
| 234 | + | |
| 235 | + | |
| 236 | + | |
| 237 | + | |
| 238 | + | |
| 239 | + | |
| 240 | + | |
| 241 | + | |
| 242 | + | |
| 243 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
119 | 119 | | |
120 | 120 | | |
121 | 121 | | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
122 | 126 | | |
123 | 127 | | |
124 | 128 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
2 | 2 | | |
3 | 3 | | |
4 | | - | |
| 4 | + | |
5 | 5 | | |
6 | 6 | | |
7 | 7 | | |
| |||
50 | 50 | | |
51 | 51 | | |
52 | 52 | | |
| 53 | + | |
53 | 54 | | |
54 | 55 | | |
55 | 56 | | |
| |||
113 | 114 | | |
114 | 115 | | |
115 | 116 | | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
116 | 121 | | |
117 | 122 | | |
118 | 123 | | |
| |||
0 commit comments