@taprootio/site-authoring 0.23.1 → 0.26.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +78 -7
- package/package.json +2 -2
- package/src/analytics-campaign.json +31 -0
- package/src/analytics-link-label.json +22 -0
- package/src/analytics-source-vocabulary.json +17 -0
- package/src/api.js +27 -5
- package/src/capabilities.js +2 -1
- package/src/changelog.json +38 -0
- package/src/cli.js +155 -7
- package/src/constants.js +4 -1
- package/src/credentials.js +55 -14
- package/src/marketing-api.js +179 -0
- package/src/marketing-contract.js +289 -0
- package/src/marketing-workspace.js +77 -0
- package/src/redirects-contract.js +0 -3
- package/src/reference-help.js +109 -3
- package/src/session.js +35 -1
- package/src/site-check-findings.js +0 -1
- package/src/site-check-rules.json +0 -1
- package/src/site-checks.js +0 -6
- package/src/typed-page-reference.js +19 -26
- package/src/typed-pages.js +51 -6
- package/src/utm-link.js +214 -0
- package/src/verbs/approve.js +25 -9
- package/src/verbs/index.js +12 -0
- package/src/verbs/link-build.js +195 -0
- package/src/verbs/login.js +33 -17
- package/src/verbs/logout.js +15 -1
- package/src/verbs/marketing-pull.js +80 -0
- package/src/verbs/marketing-push.js +250 -0
- package/src/verbs/pages-delete.js +6 -3
- package/src/verbs/pages-push.js +41 -3
- package/src/verbs/plan.js +6 -1
- package/src/verbs/pull.js +63 -9
- package/src/verbs/whoami.js +42 -2
- package/src/workspace.js +3 -0
package/README.md
CHANGED
|
@@ -235,8 +235,8 @@ Which verbs apply depends on the site:
|
|
|
235
235
|
`pull` snapshots its four settings documents, `theme push`, `footer push`,
|
|
236
236
|
and `media upload` author them, and `deploy` stages and promotes them.
|
|
237
237
|
Its pages, navigation, and redirects come from the Docs artifact, so
|
|
238
|
-
`pages push`, `nav push`, the `redirects` and `
|
|
239
|
-
`preview` verbs refuse with `surface.presentation_only` before any request.
|
|
238
|
+
`pages push`, `nav push`, the `redirects`, `forms` and `marketing` verbs, `link build`,
|
|
239
|
+
`approve`, and the `preview` verbs refuse with `surface.presentation_only` before any request.
|
|
240
240
|
- A **prebuilt Docs** site (`none`) serves its artifact unchanged and takes no
|
|
241
241
|
verb; each refuses with `surface.none`.
|
|
242
242
|
|
|
@@ -246,7 +246,9 @@ answers Not found like any site the account can no longer author, and the
|
|
|
246
246
|
CLI adds a hint to run `use` again because the recording says the site was
|
|
247
247
|
authorable before.
|
|
248
248
|
|
|
249
|
-
`logout` discards the stored sign-in locally
|
|
249
|
+
`logout` discards only the stored account sign-in locally. Separately stored
|
|
250
|
+
site legal draft grants remain; select each site and use
|
|
251
|
+
`taproot-site --legal-drafts logout` to discard its grant. Logout revokes nothing; the
|
|
250
252
|
credential stays valid until an owner revokes it under Account → Settings →
|
|
251
253
|
API keys. The secret itself is never displayed, logged, or placed in any
|
|
252
254
|
result: credentials are named by id and display prefix only.
|
|
@@ -396,6 +398,12 @@ the site's own document is kept as internal state under
|
|
|
396
398
|
`.taproot-site-state/`, which is never a page source and never pushed. A page
|
|
397
399
|
edited on the site since the last pull is a `pages.pull_conflict` refusal
|
|
398
400
|
before anything under `pages/` changes, with both recovery paths named.
|
|
401
|
+
Pull before creating a new JSON source. Once a new page is declared with a
|
|
402
|
+
file, title or path but has no server page ID, save it with `pages push` before
|
|
403
|
+
pulling again, or back up both the source and its manifest entry and move them
|
|
404
|
+
out of the workspace. Pull refuses these pending entries rather than dropping
|
|
405
|
+
their metadata. It also refuses an untracked legal or unreadable JSON source
|
|
406
|
+
that would be replaced by a new remote page.
|
|
399
407
|
|
|
400
408
|
Pages are not only free-form. A source chooses its template with `template:` in
|
|
401
409
|
its front matter — `article`, `recipe`, `album` or `place-review`; free-form is
|
|
@@ -406,10 +414,25 @@ its original publication date) and `coverImage`, which the site requires to be a
|
|
|
406
414
|
image the page itself uses (place it in the body too; `pages.cover_image_unused`). A page's template is fixed when
|
|
407
415
|
it is created: a source for another template is refused with
|
|
408
416
|
`pages.template_immutable`. `taproot-site help page <type>` gives each
|
|
409
|
-
template's fields and one worked example.
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
417
|
+
template's fields and one worked example. Pull includes structured legal pages and their
|
|
418
|
+
bodies as `.pm.json` with the usual Content read permission. Create and update
|
|
419
|
+
legal drafts in JSON only; Markdown conversion is not supported. The document
|
|
420
|
+
key, role and path are immutable, and publication assigns the version, effective
|
|
421
|
+
date and superseded version. See `taproot-site help page legal` for the fields.
|
|
422
|
+
|
|
423
|
+
Select a standard site and run `taproot-site login --legal-drafts`; the owner
|
|
424
|
+
reviews and approves a site-specific grant in the app, valid for at most 24
|
|
425
|
+
hours. Ordinary account login stays separate. Use the global option before the
|
|
426
|
+
verb, for example `taproot-site --legal-drafts pages push`,
|
|
427
|
+
`taproot-site --legal-drafts plan`, or `taproot-site --legal-drafts apply`.
|
|
428
|
+
It also selects the grant for `pull`, although ordinary Content access can read
|
|
429
|
+
legal pages. Changed or new legal drafts require this grant before writes begin;
|
|
430
|
+
unchanged legal pages do not block ordinary work. `TAPROOT_SITE_KEY` still takes
|
|
431
|
+
precedence, and legal writes with an unclassified environment override are
|
|
432
|
+
refused: unset it and use the explicit site login. Saving a draft does not
|
|
433
|
+
approve it. Legal approval, deletion and deployment remain in the app. A bare
|
|
434
|
+
`taproot-site approve` skips legal drafts and stages other tracked drafts; explicitly
|
|
435
|
+
naming a legal page refuses the whole selection.
|
|
413
436
|
A privacy policy or other legal page that names Taproot should link the first
|
|
414
437
|
mention to <https://www.taproot.io>; `help page free-form` and `help page legal`
|
|
415
438
|
give a sample sentence.
|
|
@@ -618,6 +641,54 @@ and takes effect without publishing or a new form version. Delivery requires
|
|
|
618
641
|
Taproot's rollout and email provider readiness; opt-in does not enable either
|
|
619
642
|
or backfill earlier submissions. The CLI never pulls visitor answers.
|
|
620
643
|
|
|
644
|
+
### Campaigns, placements and tracked links
|
|
645
|
+
|
|
646
|
+
A campaign groups the links of one push, and a placement names the spot a
|
|
647
|
+
link appears, such as a newsletter footer. Both are bounded sets of public
|
|
648
|
+
keys the site's release admits, so a page view that carries a matching
|
|
649
|
+
`utm_campaign` or `utm_content` is counted under it. Each is one JSON file:
|
|
650
|
+
`marketing/campaigns.json` is `{ campaigns: [{ key, name, note?, starts_on?,
|
|
651
|
+
ends_on? }] }` and `marketing/placements.json` is `{ placements: [{ key, name
|
|
652
|
+
}] }`.
|
|
653
|
+
|
|
654
|
+
```bash
|
|
655
|
+
taproot-site marketing pull # write both files, with a baseline
|
|
656
|
+
# edit marketing/campaigns.json and marketing/placements.json
|
|
657
|
+
taproot-site marketing push # create, update and restore
|
|
658
|
+
taproot-site marketing push --archive # also archive what the files leave out
|
|
659
|
+
taproot-site link build --path /recipes/spring-salad --source newsletter \
|
|
660
|
+
--campaign spring-launch --placement nl-footer
|
|
661
|
+
```
|
|
662
|
+
|
|
663
|
+
`marketing pull` replaces both files with the site's live records, each with
|
|
664
|
+
the read-only `state` (live, publishing, goes_live_with_first_publish,
|
|
665
|
+
not_in_current_release) and `counted_from`, the day its key first went live.
|
|
666
|
+
Archived records are listed and not written. `marketing push` validates both
|
|
667
|
+
files, compares every record with the site, and then creates a key the site
|
|
668
|
+
lacks, updates the labels of an existing key, and restores an archived key a
|
|
669
|
+
file names. A key is public URL text and never changes or returns to use, so a
|
|
670
|
+
changed key is a new record; the old one is archived only when you pass
|
|
671
|
+
`--archive`, and only if this workspace pulled it. A record that changed on the
|
|
672
|
+
site since the pull is refused (`marketing.concurrent_modification`), as is a
|
|
673
|
+
key the site has that this workspace never pulled (`marketing.pull_required`).
|
|
674
|
+
Quota and key refusals are shown as Taproot words them. Like `forms push`, it
|
|
675
|
+
has no plan step: it reads the site, refuses a stale baseline, and writes.
|
|
676
|
+
Creating or restoring a key queues the silent release that starts counting it;
|
|
677
|
+
nothing is deployed or waited for.
|
|
678
|
+
|
|
679
|
+
`link build` prints one address and saves nothing. It uses the Marketing link
|
|
680
|
+
builder's own rules, compiled from the app: the source decides the medium
|
|
681
|
+
(`reader` is not offered), a `utm_source`, `utm_medium`, `utm_campaign` or `utm_content` already on the path is replaced (other `utm_` pairs are kept), and a
|
|
682
|
+
campaign or placement is checked against `marketing/*.json` when the workspace
|
|
683
|
+
has them, and otherwise against the live site. A key that is not live yet is
|
|
684
|
+
allowed, with a note that page views count under the source only until it is
|
|
685
|
+
(a note read from a file is as of the last `marketing pull` or push).
|
|
686
|
+
The host is the signed-in site's public address; pass `--domain <host>` when
|
|
687
|
+
only a site credential is available. The `delegation.content` capability
|
|
688
|
+
carries what these verbs need (`site.analytics.view`, plus the publish
|
|
689
|
+
permission to change anything); `whoami` reports whether the last exchange
|
|
690
|
+
held it. `help marketing` and `help link` state the contracts.
|
|
691
|
+
|
|
621
692
|
`preview page <page-path-or-id>` renders one persisted draft behind the
|
|
622
693
|
staging gate and mints a browser handoff URL, reported only in the final JSON
|
|
623
694
|
result. **Each handoff is single-use.** Opening the URL consumes it; a reused,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@taprootio/site-authoring",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.26.0",
|
|
4
4
|
"description": "Taproot site authoring CLI: sign in once in the browser, then pull, validate, push, preview, approve, and deploy a site from the terminal",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "ISC",
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"README.md"
|
|
29
29
|
],
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"@taprootio/espalier": "6.
|
|
31
|
+
"@taprootio/espalier": "6.7.0",
|
|
32
32
|
"mediabunny": "1.61.1"
|
|
33
33
|
},
|
|
34
34
|
"scripts": {
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 1,
|
|
3
|
+
"keyPattern": "^[a-z0-9-]{3,32}$",
|
|
4
|
+
"keyMinLength": 3,
|
|
5
|
+
"keyMaxLength": 32,
|
|
6
|
+
"maxActivePerSite": 50,
|
|
7
|
+
"artifactPath": "_taproot/analytics-campaigns.json",
|
|
8
|
+
"artifactVersion": 1,
|
|
9
|
+
"collectorMarker": "c1",
|
|
10
|
+
"maxDigitRun": 8,
|
|
11
|
+
"maxTotalPerSite": 500,
|
|
12
|
+
"nameMaxLength": 80,
|
|
13
|
+
"noteMaxLength": 500,
|
|
14
|
+
"vectors": [
|
|
15
|
+
{"value": "spring-26", "valid": true},
|
|
16
|
+
{"value": "abc", "valid": true},
|
|
17
|
+
{"value": "ab", "valid": false},
|
|
18
|
+
{"value": "a-very-long-campaign-key-that-is-over-32", "valid": false},
|
|
19
|
+
{"value": "Spring-26", "valid": false},
|
|
20
|
+
{"value": "spring_26", "valid": false},
|
|
21
|
+
{"value": "spring 26", "valid": false},
|
|
22
|
+
{"value": "spring|26", "valid": false},
|
|
23
|
+
{"value": "spring.26", "valid": false},
|
|
24
|
+
{"value": "", "valid": false},
|
|
25
|
+
{"value": "-leading", "valid": true},
|
|
26
|
+
{"value": "x12345678y", "valid": true},
|
|
27
|
+
{"value": "promo-123456789", "valid": false},
|
|
28
|
+
{"value": "spring-26\n", "valid": false},
|
|
29
|
+
{"value": "0123456789", "valid": false}
|
|
30
|
+
]
|
|
31
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 1,
|
|
3
|
+
"keyGrammar": "campaign",
|
|
4
|
+
"artifactPath": "_taproot/analytics-links.json",
|
|
5
|
+
"artifactVersion": 1,
|
|
6
|
+
"collectorMarker": "c2",
|
|
7
|
+
"maxActivePerSite": 200,
|
|
8
|
+
"maxTotalPerSite": 2000,
|
|
9
|
+
"nameMaxLength": 80,
|
|
10
|
+
"vectors": [
|
|
11
|
+
{"value": "nl-footer", "valid": true},
|
|
12
|
+
{"value": "abc", "valid": true},
|
|
13
|
+
{"value": "ab", "valid": false},
|
|
14
|
+
{"value": "NL-footer", "valid": false},
|
|
15
|
+
{"value": "nl_footer", "valid": false},
|
|
16
|
+
{"value": "nl footer", "valid": false},
|
|
17
|
+
{"value": "nl|footer", "valid": false},
|
|
18
|
+
{"value": "a-label-key-that-is-longer-than-32", "valid": false},
|
|
19
|
+
{"value": "promo-123456789", "valid": false},
|
|
20
|
+
{"value": "", "valid": false}
|
|
21
|
+
]
|
|
22
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 1,
|
|
3
|
+
"media": ["social", "email", "share"],
|
|
4
|
+
"sources": [
|
|
5
|
+
{ "key": "linkedin", "name": "LinkedIn", "medium": "social", "aliases": ["linkedin.com", "lnkd.in", "com.linkedin.android"] },
|
|
6
|
+
{ "key": "x", "name": "X", "medium": "social", "aliases": ["x.com", "twitter.com", "t.co", "com.twitter.android"] },
|
|
7
|
+
{ "key": "facebook", "name": "Facebook", "medium": "social", "aliases": ["facebook.com", "fb.com", "fb.me", "com.facebook.katana", "com.facebook.lite"] },
|
|
8
|
+
{ "key": "instagram", "name": "Instagram", "medium": "social", "aliases": ["instagram.com", "com.instagram.android"] },
|
|
9
|
+
{ "key": "threads", "name": "Threads", "medium": "social", "aliases": ["threads.net", "threads.com", "com.instagram.barcelona"] },
|
|
10
|
+
{ "key": "bluesky", "name": "Bluesky", "medium": "social", "aliases": ["bsky.app", "bsky.social", "xyz.blueskyweb.app"] },
|
|
11
|
+
{ "key": "mastodon", "name": "Mastodon", "medium": "social", "aliases": ["mastodon.social", "mastodon.online", "org.joinmastodon.android"] },
|
|
12
|
+
{ "key": "reddit", "name": "Reddit", "medium": "social", "aliases": ["reddit.com", "redd.it", "com.reddit.frontpage"] },
|
|
13
|
+
{ "key": "youtube", "name": "YouTube", "medium": "social", "aliases": ["youtube.com", "youtu.be", "com.google.android.youtube"] },
|
|
14
|
+
{ "key": "newsletter", "name": "Newsletter", "medium": "email", "aliases": [] },
|
|
15
|
+
{ "key": "reader", "name": "Reader shares", "medium": "share", "aliases": [] }
|
|
16
|
+
]
|
|
17
|
+
}
|
package/src/api.js
CHANGED
|
@@ -25,6 +25,7 @@ import {
|
|
|
25
25
|
// must equal acceptance on save — a prefix admitted here but refused there
|
|
26
26
|
// would turn a successful mint into an orphaned key.
|
|
27
27
|
import { KEY_PREFIX } from "./credentials.js";
|
|
28
|
+
import { CAPABILITY_LEGAL_DRAFTS, SITE_AUTHORING_CAPABILITIES } from "./capabilities.js";
|
|
28
29
|
import { isCanonicalUuid, normalizePreviewDiagnostic, sanitizeDiagnostic, SiteAuthoringError } from "./errors.js";
|
|
29
30
|
import { normalizeAuthorListing, parseAuthorReference } from "./authors-contract.js";
|
|
30
31
|
import { docsPublicationModeFromWire, siteKindFromWire, surfaceFromWire } from "./surface.js";
|
|
@@ -2238,7 +2239,7 @@ function requireBoundedSeconds(value, minimum, maximum, field) {
|
|
|
2238
2239
|
* caller from the already-allowlisted API origin, so a compromised or
|
|
2239
2240
|
* misconfigured server cannot steer the owner somewhere else to approve.
|
|
2240
2241
|
*/
|
|
2241
|
-
export async function startCliAuthorization(client, { keyName }, requestOptions = {}) {
|
|
2242
|
+
export async function startCliAuthorization(client, { keyName, legalDraftSiteId }, requestOptions = {}) {
|
|
2242
2243
|
const response = requireObject(
|
|
2243
2244
|
// No site: TR00645 made the account-level sign-in the front door, so the
|
|
2244
2245
|
// CLI no longer has to know a site id before it can authorize at all.
|
|
@@ -2248,7 +2249,7 @@ export async function startCliAuthorization(client, { keyName }, requestOptions
|
|
|
2248
2249
|
await client.request(CLI_AUTHORIZATION_PATH, {
|
|
2249
2250
|
...requestOptions,
|
|
2250
2251
|
method: "POST",
|
|
2251
|
-
body: { keyName, cliVersion: CLI_VERSION },
|
|
2252
|
+
body: { keyName, cliVersion: CLI_VERSION, ...(legalDraftSiteId === undefined ? {} : { legalDraftSiteId }) },
|
|
2252
2253
|
}),
|
|
2253
2254
|
"login.start_contract",
|
|
2254
2255
|
"CLI authorization",
|
|
@@ -2263,9 +2264,13 @@ export async function startCliAuthorization(client, { keyName }, requestOptions
|
|
|
2263
2264
|
field: "userCode",
|
|
2264
2265
|
});
|
|
2265
2266
|
}
|
|
2267
|
+
if (legalDraftSiteId !== undefined && response.legalDraftSiteId !== legalDraftSiteId) {
|
|
2268
|
+
throw new SiteAuthoringError("login.start_contract", "Taproot did not confirm the selected legal draft site. Update the server and try again.", { field: "legalDraftSiteId" });
|
|
2269
|
+
}
|
|
2266
2270
|
return {
|
|
2267
2271
|
deviceCode: response.deviceCode,
|
|
2268
2272
|
userCode: response.userCode,
|
|
2273
|
+
...(legalDraftSiteId === undefined ? {} : { legalDraftSiteId }),
|
|
2269
2274
|
expiresInSeconds: requireBoundedSeconds(
|
|
2270
2275
|
response.expiresInSeconds,
|
|
2271
2276
|
1,
|
|
@@ -2387,7 +2392,7 @@ export async function exchangeSiteAuthoringToken(client, { siteId, capabilities
|
|
|
2387
2392
|
};
|
|
2388
2393
|
}
|
|
2389
2394
|
|
|
2390
|
-
function normalizeIssuedCredential(response) {
|
|
2395
|
+
function normalizeIssuedCredential(response, legalDraftSiteId) {
|
|
2391
2396
|
if (!isWellFormedCredential(response.rawKey)) {
|
|
2392
2397
|
// The value is never quoted, here or anywhere: it is the secret.
|
|
2393
2398
|
throw new SiteAuthoringError("login.claim_unusable", "Taproot returned an invalid issued key.", {
|
|
@@ -2411,7 +2416,24 @@ function normalizeIssuedCredential(response) {
|
|
|
2411
2416
|
|| response.keyExpiresAt === ""
|
|
2412
2417
|
? undefined
|
|
2413
2418
|
: requireCanonicalTimestamp(response.keyExpiresAt, "login.claim_unusable", "keyExpiresAt").value;
|
|
2419
|
+
if (legalDraftSiteId !== undefined) {
|
|
2420
|
+
const capabilities = response.capabilities;
|
|
2421
|
+
if (response.legalDraftSiteId !== legalDraftSiteId || !Array.isArray(capabilities)
|
|
2422
|
+
|| capabilities.length !== SITE_AUTHORING_CAPABILITIES.length + 1
|
|
2423
|
+
|| new Set(capabilities).size !== capabilities.length
|
|
2424
|
+
|| ![...SITE_AUTHORING_CAPABILITIES, CAPABILITY_LEGAL_DRAFTS].every((value) => capabilities.includes(value))
|
|
2425
|
+
|| (response.legalDraftPlatformSite !== undefined && typeof response.legalDraftPlatformSite !== "boolean")
|
|
2426
|
+
|| keyExpiresAt === undefined) {
|
|
2427
|
+
throw new SiteAuthoringError("login.claim_unusable", "Taproot returned an invalid legal draft grant.");
|
|
2428
|
+
}
|
|
2429
|
+
} else if (response.legalDraftSiteId || (Array.isArray(response.capabilities) && response.capabilities.length > 0)) {
|
|
2430
|
+
throw new SiteAuthoringError("login.claim_unusable", "Taproot returned an unexpected site grant.");
|
|
2431
|
+
}
|
|
2414
2432
|
return {
|
|
2433
|
+
...(legalDraftSiteId === undefined ? {} : {
|
|
2434
|
+
purpose: "legal-drafts", siteId: legalDraftSiteId, capabilities: [...response.capabilities],
|
|
2435
|
+
legalDraftPlatformSite: response.legalDraftPlatformSite ?? false,
|
|
2436
|
+
}),
|
|
2415
2437
|
key: response.rawKey,
|
|
2416
2438
|
keyId,
|
|
2417
2439
|
keyPrefix: response.keyPrefix,
|
|
@@ -2427,7 +2449,7 @@ function normalizeIssuedCredential(response) {
|
|
|
2427
2449
|
* `..._CONSUMED`, and the caller reports the orphaned credential honestly
|
|
2428
2450
|
* rather than hiding it behind a network error.
|
|
2429
2451
|
*/
|
|
2430
|
-
export async function claimCliAuthorization(client, deviceCode, requestOptions = {}) {
|
|
2452
|
+
export async function claimCliAuthorization(client, deviceCode, requestOptions = {}, { legalDraftSiteId } = {}) {
|
|
2431
2453
|
// Every `login.claim_contract` raised here — a non-object envelope, an
|
|
2432
2454
|
// absent status, a status string this vocabulary does not know — is an
|
|
2433
2455
|
// answer this client could not read, not proof the claim was refused. A
|
|
@@ -2451,7 +2473,7 @@ export async function claimCliAuthorization(client, deviceCode, requestOptions =
|
|
|
2451
2473
|
);
|
|
2452
2474
|
}
|
|
2453
2475
|
if (status === CLI_AUTHORIZATION_CLAIM_STATUS_ISSUED) {
|
|
2454
|
-
return { status, credential: normalizeIssuedCredential(response) };
|
|
2476
|
+
return { status, credential: normalizeIssuedCredential(response, legalDraftSiteId) };
|
|
2455
2477
|
}
|
|
2456
2478
|
if (status === CLI_AUTHORIZATION_CLAIM_STATUS_CONSUMED) {
|
|
2457
2479
|
// A consumed answer names the credential an earlier claim minted, so the
|
package/src/capabilities.js
CHANGED
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
export const CAPABILITY_CONTENT = "delegation.content";
|
|
15
15
|
export const CAPABILITY_DESIGN = "delegation.design";
|
|
16
16
|
export const CAPABILITY_DEPLOYMENTS = "delegation.deployments";
|
|
17
|
+
export const CAPABILITY_LEGAL_DRAFTS = "delegation.legal-drafts";
|
|
17
18
|
|
|
18
19
|
export const SITE_AUTHORING_CAPABILITIES = Object.freeze([
|
|
19
20
|
CAPABILITY_CONTENT,
|
|
@@ -23,7 +24,7 @@ export const SITE_AUTHORING_CAPABILITIES = Object.freeze([
|
|
|
23
24
|
|
|
24
25
|
/** What each capability lets an exchanged credential do, for help and `whoami`. */
|
|
25
26
|
export const SITE_AUTHORING_CAPABILITY_SUMMARIES = Object.freeze({
|
|
26
|
-
"delegation.content": "write pages, media,
|
|
27
|
+
"delegation.content": "write pages, media, form definitions, campaigns, and placements",
|
|
27
28
|
"delegation.design": "set the theme, navigation, and footer",
|
|
28
29
|
"delegation.deployments": "stage and deploy",
|
|
29
30
|
});
|
package/src/changelog.json
CHANGED
|
@@ -1,5 +1,43 @@
|
|
|
1
1
|
{
|
|
2
2
|
"entries": [
|
|
3
|
+
{
|
|
4
|
+
"version": "0.26.0",
|
|
5
|
+
"note": "marketing pull and marketing push keep a site's campaigns and placements as files, and link build prints a tracked address, so an agent can run a site's attribution over the CLI.",
|
|
6
|
+
"behavior": [
|
|
7
|
+
"New verbs: taproot-site marketing pull, marketing push [--archive] and link build --path <page> --source <key> [--campaign <key>] [--placement <key>] [--domain <host>]. A key now carries the permissions they need: delegation.content includes site.analytics.view, so a credential minted before this release gains read access to the site's campaigns and placements and, with the publish permission it already held, may change them.",
|
|
8
|
+
"marketing pull writes marketing/campaigns.json and marketing/placements.json (key, name, a campaign's note, starts_on and ends_on, and the read-only state and counted_from) and records a baseline in the pull manifest; a plain pull carries it forward. marketing push validates both files, compares every record with the site, and creates a new key, updates the labels of an existing one, or restores an archived key the file names. A key is public and never changes, so a changed key is a new record; a record the file leaves out is archived only with --archive, and only if the workspace pulled it. A record that changed on the site since the pull is refused (marketing.concurrent_modification, marketing.pull_required). Quota and key refusals are shown as Taproot returns them. There is no plan or apply step, as with forms push.",
|
|
9
|
+
"link build prints one address and saves nothing, with the Marketing link builder's own rules: the source decides the medium, the reader source is not offered, a utm_source, utm_medium, utm_campaign or utm_content on the path is replaced, and a campaign or placement is checked against marketing/*.json when the workspace has it, otherwise against the site. One that is not live yet is allowed with the builder's note that page views count under the source only until it is. The host is the signed-in site's public address; pass --domain when only a site credential is available.",
|
|
10
|
+
"whoami reports whether the last exchange allows campaigns and placements (marketing.access).",
|
|
11
|
+
"The help reference version is 43.",
|
|
12
|
+
"Built against Espalier 6.7.0. The default site theme is rebuilt against it and its content is unchanged except for the recorded Espalier version, so pulled and validated themes behave as before."
|
|
13
|
+
],
|
|
14
|
+
"help": [
|
|
15
|
+
"marketing",
|
|
16
|
+
"link"
|
|
17
|
+
]
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"version": "0.25.0",
|
|
21
|
+
"note": "Redirects from /s/ addresses are ordinary redirects now that tracked share links are retired.",
|
|
22
|
+
"behavior": [
|
|
23
|
+
"A redirect whose source is /s/<anything> saves and validates like any other source. The redirect.source_share_link site-check rule is gone, so offline checks and the server no longer refuse it."
|
|
24
|
+
],
|
|
25
|
+
"help": []
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"version": "0.24.0",
|
|
29
|
+
"note": "Site-specific consent enables legal draft authoring through JSON; legal approval and publication remain in the app.",
|
|
30
|
+
"behavior": [
|
|
31
|
+
"Select a standard site, then run login --legal-drafts and approve the pinned site grant in the app. It lasts at most 24 hours and is separate from ordinary account login. Use the global --legal-drafts option before pull, pages push, plan or apply to select that grant.",
|
|
32
|
+
"Pull includes legal bodies as .pm.json with ordinary Content read access. Legal create and update accept JSON only; document key, role and path are immutable, and version, effective date and superseded version are assigned by publication. A changed or new legal draft needs the explicit grant before writes begin.",
|
|
33
|
+
"TAPROOT_SITE_KEY takes precedence; legal writes with an unclassified environment key are refused. Legal approval, deletion and deployment remain app-only. Saving a draft does not approve it.",
|
|
34
|
+
"The help reference version is 42."
|
|
35
|
+
],
|
|
36
|
+
"help": [
|
|
37
|
+
"page",
|
|
38
|
+
"pages"
|
|
39
|
+
]
|
|
40
|
+
},
|
|
3
41
|
{
|
|
4
42
|
"version": "0.23.1",
|
|
5
43
|
"note": "Image uploads support Taproot’s single-use upload gateway and send an operation key with each upload request.",
|