@utopia-studio-design/design-system-cli 0.4.0 → 0.4.1
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/bin/utopia-ds.mjs +10 -0
- package/data/docs/foundations.md +1 -0
- package/data/docs/observability.md +165 -0
- package/data/docs/quick-start-ai.md +32 -0
- package/data/manifests/components.json +12 -3
- package/lib/telemetry.mjs +74 -0
- package/package.json +1 -1
package/bin/utopia-ds.mjs
CHANGED
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
listComponents, listDocs, listMotionProfiles, listTemplates, listThemes, mcpLaunch, repositoryDoctor, search,
|
|
8
8
|
} from '../lib/api.mjs'
|
|
9
9
|
import { createTemplateSubmissionUrl, validateTemplateSubmission } from '../lib/template-submission.mjs'
|
|
10
|
+
import { installCliTelemetry } from '../lib/telemetry.mjs'
|
|
10
11
|
|
|
11
12
|
const args = process.argv.slice(2)
|
|
12
13
|
const command = args.find((arg) => !arg.startsWith('--')) ?? 'help'
|
|
@@ -15,6 +16,14 @@ const optionsWithValues = new Set(['--copy', '--theme'])
|
|
|
15
16
|
const values = commandIndex >= 0 ? positionalValues(args.slice(commandIndex + 1)) : []
|
|
16
17
|
const json = args.includes('--json')
|
|
17
18
|
const dense = args.includes('--dense')
|
|
19
|
+
const cliVersion = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8')).version
|
|
20
|
+
let telemetryErrorCode
|
|
21
|
+
|
|
22
|
+
installCliTelemetry({
|
|
23
|
+
command,
|
|
24
|
+
packageVersion: cliVersion,
|
|
25
|
+
getErrorCode: () => telemetryErrorCode,
|
|
26
|
+
})
|
|
18
27
|
|
|
19
28
|
function positionalValues(input) {
|
|
20
29
|
const positionals = []
|
|
@@ -36,6 +45,7 @@ function output(type, data, format = null) {
|
|
|
36
45
|
}
|
|
37
46
|
|
|
38
47
|
function fail(message, code = 'ERR_INPUT', suggestions = []) {
|
|
48
|
+
telemetryErrorCode = code
|
|
39
49
|
if (json) console.error(JSON.stringify({ apiVersion: 1, error: message, code, suggestions }, null, 2))
|
|
40
50
|
else console.error(message)
|
|
41
51
|
process.exitCode = 1
|
package/data/docs/foundations.md
CHANGED
|
@@ -74,6 +74,7 @@ Arabic display sizing should follow the Latin display scale at about 95%, rather
|
|
|
74
74
|
- Runtime recipes use four engine-neutral intents: `feedback`, `page`, `surface`, and `layout`.
|
|
75
75
|
- Components consume `--motion-duration-*` and `--motion-ease-*` roles rather than hardcoded milliseconds or easing curves.
|
|
76
76
|
- `MotionProvider` sets the theme profile and runtime adapter for a subtree. Motion-aware components expose `motion?: boolean` for a local override.
|
|
77
|
+
- `MotionProvider asChild` applies that policy to exactly one semantic subtree root without adding a wrapper. The consumer root must accept `className`, `style`, `data-*`, and its children.
|
|
77
78
|
- `motion={false}` and `prefers-reduced-motion: reduce` disable decorative movement while preserving state changes and accessibility.
|
|
78
79
|
- Directional motion follows logical inline start/end and mirrors in RTL when direction carries meaning.
|
|
79
80
|
- Icon motion follows the action: a bell swings from its top, download moves downward, and copy snaps once. Do not apply a generic bounce.
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
# Ceramic Observability
|
|
2
|
+
|
|
3
|
+
Ceramic observability has two explicit modes. It is not a hidden backdoor:
|
|
4
|
+
|
|
5
|
+
- `operational` starts when a host configures and enables a collector. It
|
|
6
|
+
records only minimized error diagnostics, uses an in-memory page-session ID,
|
|
7
|
+
ignores actor/account IDs and custom properties, and does not use telemetry
|
|
8
|
+
cookies, `sessionStorage`, `localStorage`, usage events, or page-view events.
|
|
9
|
+
It needs no consent click, but hosts must publish notice, assess their lawful
|
|
10
|
+
basis, and provide an immediate opt-out.
|
|
11
|
+
- `consented` records the allow-listed usage plan and optional opaque cohorts
|
|
12
|
+
only after explicit consent.
|
|
13
|
+
|
|
14
|
+
## Decisions this data supports
|
|
15
|
+
|
|
16
|
+
- Which Ceramic versions, components, and public composition paths are in use?
|
|
17
|
+
- Which anonymous app/account cohort is encountering a repeated error?
|
|
18
|
+
- Which route, viewport class, locale, direction, and release are affected?
|
|
19
|
+
- Did a CLI command succeed or fail, without collecting its arguments or path?
|
|
20
|
+
- Did a fixed error fingerprint stop recurring after a release?
|
|
21
|
+
|
|
22
|
+
## Data minimization contract
|
|
23
|
+
|
|
24
|
+
Consented Ceramic telemetry may collect:
|
|
25
|
+
|
|
26
|
+
- event name and timestamp;
|
|
27
|
+
- opaque app, account, actor, session, and event IDs;
|
|
28
|
+
- component and operation names;
|
|
29
|
+
- package version and application release;
|
|
30
|
+
- route template supplied by the host;
|
|
31
|
+
- locale, direction, and viewport bucket;
|
|
32
|
+
- stable error code, error class, and non-reversible fingerprint;
|
|
33
|
+
- allow-listed short boolean, numeric, or non-email string properties.
|
|
34
|
+
|
|
35
|
+
Ceramic does not collect:
|
|
36
|
+
|
|
37
|
+
- names, email addresses, raw database IDs, or authentication identifiers;
|
|
38
|
+
- DOM, form values, user copy, component props, command arguments, or cwd;
|
|
39
|
+
- error messages or raw stacks;
|
|
40
|
+
- IP addresses in retained records;
|
|
41
|
+
- session replay, screenshots, keystrokes, or network payloads.
|
|
42
|
+
|
|
43
|
+
Operational mode accepts only `component_error` and `runtime_error`, and removes
|
|
44
|
+
`accountId`, `actorId`, custom properties, and persistent telemetry storage even
|
|
45
|
+
if the host passes them.
|
|
46
|
+
|
|
47
|
+
In consented mode, pass pre-hashed or otherwise opaque `accountId` and `actorId` values. The SDK
|
|
48
|
+
rejects identifiers with whitespace or email syntax. Route resolvers should
|
|
49
|
+
return route templates such as `/projects/:projectId`, not raw URLs containing
|
|
50
|
+
customer identifiers.
|
|
51
|
+
|
|
52
|
+
## Start the private collector and dashboard
|
|
53
|
+
|
|
54
|
+
Create two different random secrets:
|
|
55
|
+
|
|
56
|
+
```sh
|
|
57
|
+
export CERAMIC_TELEMETRY_INGEST_KEY="$(openssl rand -hex 32)"
|
|
58
|
+
export CERAMIC_TELEMETRY_ADMIN_TOKEN="$(openssl rand -hex 32)"
|
|
59
|
+
export CERAMIC_TELEMETRY_ALLOWED_ORIGINS="http://localhost:5173"
|
|
60
|
+
npm run observability:start
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Open `http://127.0.0.1:4318` and enter the admin token. The collector binds to
|
|
64
|
+
loopback by default, retains data for 30 days, and stores newline-delimited JSON
|
|
65
|
+
under `.ceramic/observability/`, which is ignored by Git.
|
|
66
|
+
|
|
67
|
+
The dashboard shows aggregate event, error, app, opaque actor/account,
|
|
68
|
+
component, release, and fingerprint counts plus the latest sanitized events.
|
|
69
|
+
The admin can export the aggregate response or delete all retained events.
|
|
70
|
+
|
|
71
|
+
For a shared deployment, place the collector behind TLS, SSO or a private
|
|
72
|
+
network, persistent encrypted storage, rate limiting, and backups appropriate
|
|
73
|
+
to the retention policy. Do not expose the Node reference collector directly
|
|
74
|
+
to the public internet. The admin token must never be put in a `VITE_*`
|
|
75
|
+
variable or browser bundle.
|
|
76
|
+
|
|
77
|
+
## Configure a React/browser consumer
|
|
78
|
+
|
|
79
|
+
```tsx
|
|
80
|
+
import {
|
|
81
|
+
configureCeramicTelemetry,
|
|
82
|
+
installCeramicGlobalErrorTracking,
|
|
83
|
+
trackCeramicEvent,
|
|
84
|
+
} from '@utopia-studio-design/design-system/Telemetry'
|
|
85
|
+
|
|
86
|
+
configureCeramicTelemetry({
|
|
87
|
+
enabled: diagnosticsPreference !== 'disabled',
|
|
88
|
+
consent: 'unknown',
|
|
89
|
+
mode: 'operational',
|
|
90
|
+
appId: 'renacore-dashboard',
|
|
91
|
+
endpoint: 'https://observability.example.com',
|
|
92
|
+
ingestKey: publicWriteOnlyIngestKey,
|
|
93
|
+
packageVersion: '0.6.0',
|
|
94
|
+
release: appRelease,
|
|
95
|
+
routeResolver: () => currentRouteTemplate,
|
|
96
|
+
sampleRate: 0.1,
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
const removeGlobalTracking = installCeramicGlobalErrorTracking()
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Global error listeners are never installed automatically by the package. The
|
|
103
|
+
host owns notice, lawful-basis assessment, opt-out, route normalization,
|
|
104
|
+
installation, and cleanup. Errors are sampled at 100%. Repeated reports with the same
|
|
105
|
+
fingerprint inside two seconds are deduplicated so a component boundary and a
|
|
106
|
+
global listener do not create duplicate incidents.
|
|
107
|
+
|
|
108
|
+
Ceramic's validated `asChild` paths report `ERR_COMPOSITION_CHILD` through the
|
|
109
|
+
configured client before throwing their explanatory development error. With no
|
|
110
|
+
configured and enabled client, this call is a no-op.
|
|
111
|
+
|
|
112
|
+
To enable usage events, obtain separate explicit consent and configure
|
|
113
|
+
`mode: "consented"`, `consent: "granted"`, and any opaque cohort IDs.
|
|
114
|
+
|
|
115
|
+
## Configure CLI diagnostics
|
|
116
|
+
|
|
117
|
+
CLI telemetry is also disabled by default and never sends command arguments,
|
|
118
|
+
queries, target directories, generated file names, or cwd.
|
|
119
|
+
|
|
120
|
+
```sh
|
|
121
|
+
export CERAMIC_TELEMETRY_ENABLED=true
|
|
122
|
+
export CERAMIC_TELEMETRY_MODE=operational
|
|
123
|
+
export CERAMIC_TELEMETRY_ENDPOINT=https://observability.example.com
|
|
124
|
+
export CERAMIC_TELEMETRY_INGEST_KEY=replace-with-write-only-key
|
|
125
|
+
export CERAMIC_TELEMETRY_APP_ID=renacore-dashboard
|
|
126
|
+
npx utopia-ds doctor --json
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Operational CLI mode sends failed completion only, without actor/account IDs.
|
|
130
|
+
Consented mode additionally requires `CERAMIC_TELEMETRY_MODE=consented` and
|
|
131
|
+
`CERAMIC_TELEMETRY_CONSENT=granted`, and may send successful completion and
|
|
132
|
+
opaque cohorts. Network failure never changes CLI output or exit status.
|
|
133
|
+
|
|
134
|
+
## Event plan
|
|
135
|
+
|
|
136
|
+
| Event | Trigger | Decision fields |
|
|
137
|
+
| --- | --- | --- |
|
|
138
|
+
| `app_started` | Consented SDK initialization | app, release, package version |
|
|
139
|
+
| `component_used` | Explicit host instrumentation | component, operation, route |
|
|
140
|
+
| `component_error` | Ceramic component contract failure | component, operation, error code/fingerprint |
|
|
141
|
+
| `runtime_error` | Explicit boundary/global error capture | route, release, error code/fingerprint |
|
|
142
|
+
| `cli_command_completed` | Operational failure or consented CLI process exit | command, success, version |
|
|
143
|
+
|
|
144
|
+
## Public notice and controls
|
|
145
|
+
|
|
146
|
+
The Ceramic documentation site publishes `#/terms` and `#/privacy`. Its privacy
|
|
147
|
+
page includes a browser-level operational-diagnostics opt-out and honors Global
|
|
148
|
+
Privacy Control and Do Not Track. Consumer applications must publish their own
|
|
149
|
+
notice and control because they determine the collector, purpose, retention,
|
|
150
|
+
recipients, and jurisdiction.
|
|
151
|
+
|
|
152
|
+
## Operations and deletion
|
|
153
|
+
|
|
154
|
+
- Default retention: 30 days; configure 1–365 days.
|
|
155
|
+
- `GET /health`: unauthenticated liveness only.
|
|
156
|
+
- `POST /v1/events`: write-only ingest-key authentication.
|
|
157
|
+
- `GET /v1/summary`: admin Bearer authentication.
|
|
158
|
+
- `DELETE /v1/events`: admin Bearer authentication and full retained-data deletion.
|
|
159
|
+
- `DELETE /v1/events?actorId=<opaque-id>` or `?accountId=<opaque-id>`:
|
|
160
|
+
selective deletion for a consent withdrawal or data-subject request.
|
|
161
|
+
- Rotate ingest and admin secrets independently.
|
|
162
|
+
- Treat the ingest key as public/write-only when used in browsers.
|
|
163
|
+
- Treat the admin token as a server-side secret.
|
|
164
|
+
- Publish a privacy notice before enabling identifiable account or actor
|
|
165
|
+
cohorts in a production consumer.
|
|
@@ -42,6 +42,30 @@ export function SaveAction() {
|
|
|
42
42
|
}
|
|
43
43
|
```
|
|
44
44
|
|
|
45
|
+
For navigation, keep the anchor or framework link as the single interactive
|
|
46
|
+
element:
|
|
47
|
+
|
|
48
|
+
```tsx
|
|
49
|
+
import Link from 'next/link'
|
|
50
|
+
import { Button } from '@utopia-studio-design/design-system/Button'
|
|
51
|
+
|
|
52
|
+
<Button asChild variant="outline" size="sm">
|
|
53
|
+
<a href="/docs">Docs</a>
|
|
54
|
+
</Button>
|
|
55
|
+
|
|
56
|
+
<Button asChild startContent={<FolderIcon />}>
|
|
57
|
+
<Link href="/settings">Settings</Link>
|
|
58
|
+
</Button>
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
`asChild` accepts exactly one non-Fragment React element. The child must forward
|
|
62
|
+
`ref`, `className`, events, `children`, and accessibility props to one
|
|
63
|
+
interactive element. Anchor and Link props such as `href`, `target`, and
|
|
64
|
+
`aria-*` are preserved. When `disabled` or `loading` is true, Ceramic does not
|
|
65
|
+
pass the invalid `disabled` attribute to a link: it applies
|
|
66
|
+
`aria-disabled="true"`, `tabIndex={-1}`, and blocks click activation while
|
|
67
|
+
keeping the original `href`.
|
|
68
|
+
|
|
45
69
|
## Set Motion Policy
|
|
46
70
|
|
|
47
71
|
Ceramic maps BeUI-inspired interaction patterns to semantic roles: `press`, `page`, `expand`, `reveal`, and `icon`. Set the application default once and override only when a local workflow needs to be static.
|
|
@@ -96,6 +120,14 @@ The MCP server exposes the same API as the CLI. A generated `.mcp.json` uses:
|
|
|
96
120
|
|
|
97
121
|
Available tools include search, component/template/theme/doc discovery, and doctor. An MCP client must never receive capabilities that the CLI cannot expose.
|
|
98
122
|
|
|
123
|
+
## Optional diagnostics
|
|
124
|
+
|
|
125
|
+
Ceramic telemetry is disabled by default. If a product has an approved
|
|
126
|
+
diagnostics consent flow, configure the write-only SDK and private dashboard
|
|
127
|
+
using `npx utopia-ds docs observability --dense`. Never collect names, emails,
|
|
128
|
+
DOM content, component props, command arguments, filesystem paths, error
|
|
129
|
+
messages, or raw stacks.
|
|
130
|
+
|
|
99
131
|
## Agent Decision Loop
|
|
100
132
|
|
|
101
133
|
1. Run `manifest --json` to discover supported operations.
|
|
@@ -671,7 +671,7 @@
|
|
|
671
671
|
"sourcePath": "packages/design-system/src/components/Button.tsx",
|
|
672
672
|
"shadcnFoundation": [
|
|
673
673
|
"button",
|
|
674
|
-
"
|
|
674
|
+
"single-element asChild composition",
|
|
675
675
|
"cva"
|
|
676
676
|
],
|
|
677
677
|
"fallbackToShadcn": "button",
|
|
@@ -709,6 +709,7 @@
|
|
|
709
709
|
"New button colors",
|
|
710
710
|
"Gradient states",
|
|
711
711
|
"Unlabeled icon-only buttons",
|
|
712
|
+
"Fragments or multiple root children inside Button asChild",
|
|
712
713
|
"Left/right-only layout rules",
|
|
713
714
|
"Utopia brand primitives inside reusable component logic",
|
|
714
715
|
"Motion that bypasses MotionProvider or prefers-reduced-motion"
|
|
@@ -718,7 +719,10 @@
|
|
|
718
719
|
"motion": "Optional boolean. Defaults to the MotionProvider policy and disables this component pattern when false.",
|
|
719
720
|
"contentAlign": "Use 'start' for leading-aligned labels, 'center' for the default action layout, or 'between' to pin endContent to the logical inline end.",
|
|
720
721
|
"startContent": "Optional leading icon or content slot with package-owned alignment.",
|
|
721
|
-
"endContent": "Optional trailing content slot. With contentAlign='between', it remains pinned while the label truncates."
|
|
722
|
+
"endContent": "Optional trailing content slot. With contentAlign='between', it remains pinned while the label truncates.",
|
|
723
|
+
"asChild": "Use with exactly one non-Fragment anchor or framework Link that forwards ref, className, events, children, and accessibility props to one interactive element. Button preserves the child href, target, aria attributes, className, and ref while composing its internal slots inside that element.",
|
|
724
|
+
"disabled": "Native buttons receive disabled. With asChild, disabled links keep href, receive aria-disabled=true and tabIndex=-1, and block click activation without receiving a disabled attribute.",
|
|
725
|
+
"loading": "Shows the spinner/loadingText and sets aria-busy. Native buttons are disabled; asChild links use the same aria-disabled, tabIndex, and activation-blocking contract as disabled."
|
|
722
726
|
}
|
|
723
727
|
}
|
|
724
728
|
},
|
|
@@ -776,8 +780,9 @@
|
|
|
776
780
|
"label",
|
|
777
781
|
"children",
|
|
778
782
|
"ButtonGroupSeparator.orientation",
|
|
779
|
-
|
|
783
|
+
"ButtonGroupText.asChild"
|
|
780
784
|
],
|
|
785
|
+
"composition": "ButtonGroupText.asChild accepts exactly one text-compatible consumer element and preserves it as the semantic root while applying the group text class and props.",
|
|
781
786
|
"shadcnReferencePatterns": [
|
|
782
787
|
"button group",
|
|
783
788
|
"button group with dropdown menu",
|
|
@@ -4671,6 +4676,7 @@
|
|
|
4671
4676
|
"shadcnFoundation": [
|
|
4672
4677
|
"toggle",
|
|
4673
4678
|
"Radix Toggle",
|
|
4679
|
+
"Slottable single-button asChild composition",
|
|
4674
4680
|
"cva"
|
|
4675
4681
|
],
|
|
4676
4682
|
"fallbackToShadcn": "shadcn/ui toggle",
|
|
@@ -4697,6 +4703,7 @@
|
|
|
4697
4703
|
"neverInvent": [
|
|
4698
4704
|
"New pressed colors",
|
|
4699
4705
|
"Checkbox behavior",
|
|
4706
|
+
"Fragments, multiple roots, anchors, or navigation links inside ToggleButton asChild",
|
|
4700
4707
|
"Left/right-only state names",
|
|
4701
4708
|
"Utopia brand primitives inside reusable component logic"
|
|
4702
4709
|
],
|
|
@@ -4711,9 +4718,11 @@
|
|
|
4711
4718
|
"isIconOnly",
|
|
4712
4719
|
"isDisabled",
|
|
4713
4720
|
"isLoading",
|
|
4721
|
+
"asChild",
|
|
4714
4722
|
"variant",
|
|
4715
4723
|
"size"
|
|
4716
4724
|
],
|
|
4725
|
+
"composition": "ToggleButton.asChild requires exactly one non-Fragment button-compatible component that forwards ref, className, events, children, disabled, and accessibility props to one button. Ceramic keeps spinner, icon, pressed icon, and label slots inside that root.",
|
|
4717
4726
|
"arabicFriendly": [
|
|
4718
4727
|
"Use logical start/end language for labels and adjacent content.",
|
|
4719
4728
|
"In RTL, keep persistent pressed state behavior identical; only directional layout and directional icons may mirror.",
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
function safeId(value) {
|
|
2
|
+
return typeof value === 'string' && /^[a-zA-Z0-9][a-zA-Z0-9._:-]{0,127}$/.test(value)
|
|
3
|
+
? value
|
|
4
|
+
: undefined
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
function randomId() {
|
|
8
|
+
return globalThis.crypto?.randomUUID?.() ?? `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 14)}`
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const SAFE_COMMANDS = new Set([
|
|
12
|
+
'component',
|
|
13
|
+
'doctor',
|
|
14
|
+
'docs',
|
|
15
|
+
'help',
|
|
16
|
+
'init',
|
|
17
|
+
'manifest',
|
|
18
|
+
'motion',
|
|
19
|
+
'search',
|
|
20
|
+
'template',
|
|
21
|
+
'theme',
|
|
22
|
+
])
|
|
23
|
+
|
|
24
|
+
export function installCliTelemetry({ command, packageVersion, getErrorCode }) {
|
|
25
|
+
const endpoint = process.env.CERAMIC_TELEMETRY_ENDPOINT?.replace(/\/$/, '')
|
|
26
|
+
const ingestKey = process.env.CERAMIC_TELEMETRY_INGEST_KEY
|
|
27
|
+
const appId = safeId(process.env.CERAMIC_TELEMETRY_APP_ID)
|
|
28
|
+
const consented = process.env.CERAMIC_TELEMETRY_MODE === 'consented'
|
|
29
|
+
&& process.env.CERAMIC_TELEMETRY_CONSENT === 'granted'
|
|
30
|
+
const enabled = process.env.CERAMIC_TELEMETRY_ENABLED === 'true'
|
|
31
|
+
&& endpoint
|
|
32
|
+
&& ingestKey
|
|
33
|
+
&& appId
|
|
34
|
+
&& command !== 'mcp'
|
|
35
|
+
if (!enabled) return
|
|
36
|
+
|
|
37
|
+
let sent = false
|
|
38
|
+
process.on('beforeExit', async () => {
|
|
39
|
+
if (sent) return
|
|
40
|
+
sent = true
|
|
41
|
+
const errorCode = getErrorCode()
|
|
42
|
+
const success = !errorCode && (process.exitCode ?? 0) === 0
|
|
43
|
+
if (!consented && success) return
|
|
44
|
+
const event = {
|
|
45
|
+
schemaVersion: 1,
|
|
46
|
+
id: randomId(),
|
|
47
|
+
occurredAt: new Date().toISOString(),
|
|
48
|
+
name: 'cli_command_completed',
|
|
49
|
+
appId,
|
|
50
|
+
source: 'cli',
|
|
51
|
+
sessionId: randomId(),
|
|
52
|
+
accountId: consented ? safeId(process.env.CERAMIC_TELEMETRY_ACCOUNT_ID) : undefined,
|
|
53
|
+
actorId: consented ? safeId(process.env.CERAMIC_TELEMETRY_ACTOR_ID) : undefined,
|
|
54
|
+
errorCode,
|
|
55
|
+
operation: SAFE_COMMANDS.has(command) ? command : 'unknown',
|
|
56
|
+
packageVersion,
|
|
57
|
+
release: process.env.CERAMIC_RELEASE?.slice(0, 128),
|
|
58
|
+
properties: consented ? { success } : undefined,
|
|
59
|
+
}
|
|
60
|
+
try {
|
|
61
|
+
await fetch(`${endpoint}/v1/events`, {
|
|
62
|
+
method: 'POST',
|
|
63
|
+
headers: {
|
|
64
|
+
'content-type': 'application/json',
|
|
65
|
+
'x-ceramic-ingest-key': ingestKey,
|
|
66
|
+
},
|
|
67
|
+
body: JSON.stringify(event),
|
|
68
|
+
signal: AbortSignal.timeout(750),
|
|
69
|
+
})
|
|
70
|
+
} catch {
|
|
71
|
+
// Telemetry is best effort and must never change CLI behavior.
|
|
72
|
+
}
|
|
73
|
+
})
|
|
74
|
+
}
|