@atcute/bluesky-richtext-builder 2.0.4 → 3.0.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 CHANGED
@@ -1,19 +1,191 @@
1
1
  # @atcute/bluesky-richtext-builder
2
2
 
3
- builder pattern for Bluesky's rich text facets.
3
+ fluent builder for constructing Bluesky rich text with facets.
4
+
5
+ ```sh
6
+ npm install @atcute/bluesky-richtext-builder
7
+ ```
8
+
9
+ Bluesky posts support rich text with mentions, links, and hashtags. these are represented as
10
+ "facets" - byte ranges with attached features. this package provides a builder that handles the byte
11
+ offset calculations for you.
12
+
13
+ ## why use a builder?
14
+
15
+ Bluesky facets use byte offsets, not character positions. this matters for non-ASCII text:
16
+
17
+ ```ts
18
+ // "café" is 5 bytes in UTF-8 (c=1, a=1, f=1, é=2)
19
+ const text = 'café ☕';
20
+
21
+ // the coffee emoji starts at byte 5, not character 5
22
+ ```
23
+
24
+ the builder handles these calculations automatically, so you don't have to think about byte offsets.
25
+
26
+ ## usage
27
+
28
+ ### basic usage
4
29
 
5
30
  ```ts
6
31
  import RichtextBuilder from '@atcute/bluesky-richtext-builder';
7
32
 
8
- const { text, facets } = new RichtextBuilder()
9
- .addText(`hello, `)
10
- .addMention(`@user`, 'did:plc:ia76kvnndjutgedggx2ibrem')
11
- .addText(`! please visit my`)
12
- .addLink(`website`, 'https://example.com');
33
+ const rt = new RichtextBuilder()
34
+ .addText('hello, ')
35
+ .addMention('@alice', 'did:plc:abc123')
36
+ .addText('! check out ')
37
+ .addLink('my website', 'https://example.com');
38
+
39
+ console.log(rt.text);
40
+ // -> "hello, @alice! check out my website"
41
+
42
+ console.log(rt.facets);
43
+ // -> [
44
+ // { index: { byteStart: 7, byteEnd: 13 }, features: [{ $type: 'app.bsky.richtext.facet#mention', did: '...' }] },
45
+ // { index: { byteStart: 25, byteEnd: 35 }, features: [{ $type: 'app.bsky.richtext.facet#link', uri: '...' }] }
46
+ // ]
47
+ ```
48
+
49
+ ### creating a post
50
+
51
+ use with `@atcute/client` to create a post:
52
+
53
+ ```sh
54
+ npm install @atcute/client @atcute/password-session @atcute/bluesky
55
+ ```
56
+
57
+ ```ts
58
+ import { Client, ok } from '@atcute/client';
59
+ import { PasswordSession } from '@atcute/password-session';
60
+ import RichtextBuilder from '@atcute/bluesky-richtext-builder';
61
+
62
+ import type {} from '@atcute/bluesky';
63
+
64
+ const session = await PasswordSession.login({
65
+ service: 'https://bsky.social',
66
+ identifier: 'you.bsky.social',
67
+ password: 'your-app-password',
68
+ });
69
+
70
+ const rpc = new Client({ handler: session });
71
+
72
+ const rt = new RichtextBuilder()
73
+ .addText('hello ')
74
+ .addMention('@bsky.app', 'did:plc:z72i7hdynmk6r22z27h6tvur')
75
+ .addText('! ')
76
+ .addTag('#atproto', 'atproto');
77
+
78
+ await ok(
79
+ rpc.post('com.atproto.repo.createRecord', {
80
+ input: {
81
+ repo: session.did,
82
+ collection: 'app.bsky.feed.post',
83
+ record: {
84
+ $type: 'app.bsky.feed.post',
85
+ text: rt.text,
86
+ facets: rt.facets,
87
+ createdAt: new Date().toISOString(),
88
+ },
89
+ },
90
+ }),
91
+ );
92
+ ```
93
+
94
+ ### adding links
95
+
96
+ ```ts
97
+ const rt = new RichtextBuilder()
98
+ .addText('read the ')
99
+ .addLink('documentation', 'https://atproto.com/docs')
100
+ .addText(' for more info');
101
+ ```
102
+
103
+ the link text can be anything - it doesn't have to be the URL:
104
+
105
+ ```ts
106
+ const rt = new RichtextBuilder().addLink('click here', 'https://example.com');
107
+ ```
108
+
109
+ ### adding mentions
110
+
111
+ mentions require both the display text and the user's DID:
112
+
113
+ ```ts
114
+ const rt = new RichtextBuilder().addMention('@alice.bsky.social', 'did:plc:abc123');
115
+ ```
116
+
117
+ you'll typically resolve the handle to a DID first:
118
+
119
+ ```ts
120
+ import {
121
+ CompositeHandleResolver,
122
+ DohJsonHandleResolver,
123
+ WellKnownHandleResolver,
124
+ } from '@atcute/identity-resolver';
125
+
126
+ const handleResolver = new CompositeHandleResolver({
127
+ methods: {
128
+ dns: new DohJsonHandleResolver({
129
+ dohUrl: 'https://mozilla.cloudflare-dns.com/dns-query',
130
+ }),
131
+ http: new WellKnownHandleResolver(),
132
+ },
133
+ });
134
+
135
+ const handle = 'alice.bsky.social';
136
+ const did = await handleResolver.resolve(handle);
137
+
138
+ const rt = new RichtextBuilder().addMention(`@${handle}`, did);
139
+ ```
140
+
141
+ ### adding hashtags
142
+
143
+ ```ts
144
+ const rt = new RichtextBuilder()
145
+ .addText('loving ')
146
+ .addTag('#atproto', 'atproto')
147
+ .addText(' development!');
148
+
149
+ // text: "loving #atproto development!"
150
+ ```
13
151
 
14
- text;
15
- // ^? `hello, @user! please visit my website`
152
+ ### custom facet features
153
+
154
+ use `addDecoratedText()` for custom facet features:
155
+
156
+ ```ts
157
+ import type { FacetFeature } from '@atcute/bluesky-richtext-builder';
158
+
159
+ const feature: FacetFeature = {
160
+ $type: 'app.bsky.richtext.facet#link',
161
+ uri: 'https://example.com',
162
+ };
163
+
164
+ const rt = new RichtextBuilder().addDecoratedText('custom link', feature);
165
+ ```
166
+
167
+ ### getting the result
168
+
169
+ there are multiple ways to get the composed rich text:
170
+
171
+ ```ts
172
+ const rt = new RichtextBuilder().addText('hello ').addTag('#world', 'world');
173
+
174
+ // via getters
175
+ const text = rt.text;
176
+ const facets = rt.facets;
177
+
178
+ // via build() method
179
+ const { text, facets } = rt.build();
180
+ ```
181
+
182
+ ### cloning the builder
183
+
184
+ clone a builder to create variations:
185
+
186
+ ```ts
187
+ const base = new RichtextBuilder().addText('hello ');
16
188
 
17
- facets;
18
- // ^? [{ index: { byteStart: 7, byteEnd: 12 }, ... }, { index: { byteStart: 30, byteEnd: 37 }, ... }];
189
+ const withMention = base.clone().addMention('@alice', 'did:plc:abc');
190
+ const withLink = base.clone().addLink('world', 'https://example.com');
19
191
  ```
package/dist/index.d.ts CHANGED
@@ -23,37 +23,38 @@ declare class RichtextBuilder {
23
23
  clone(): RichtextBuilder;
24
24
  /**
25
25
  * Add plain text to the rich text
26
- * @param substr The plain text
26
+ * @param text The plain text
27
27
  * @returns The builder instance, for chaining
28
28
  */
29
- addText(substr: string): this;
29
+ addText(text: string): this;
30
30
  /**
31
31
  * Add decorated text to the rich text
32
- * @param substr The text itself
32
+ * @param text The text itself
33
33
  * @param feature Feature to imbue on the text
34
34
  * @returns The builder instance, for chaining
35
35
  */
36
- addDecoratedText(substr: string, feature: FacetFeature): this;
36
+ addDecoratedText(text: string, feature: FacetFeature): this;
37
37
  /**
38
38
  * Add link to the rich text
39
- * @param substr Text of the link
39
+ * @param text Text of the link
40
40
  * @param uri Valid URL, for example: https://example.com
41
41
  * @returns The builder instance, for chaining
42
42
  */
43
- addLink(substr: string, uri: GenericUri): this;
43
+ addLink(text: string, uri: GenericUri): this;
44
44
  /**
45
- * Mentions a user in rich text
46
- * @param substr Text of the mention, this is usually in the form of `@handle`
45
+ * Mention a user in rich text
46
+ * @param text Text of the mention, usually in the form of `@handle`
47
47
  * @param did Valid DID, for example: did:plc:ia76kvnndjutgedggx2ibrem
48
48
  * @returns The builder instance, for chaining
49
49
  */
50
- addMention(substr: string, did: Did): this;
50
+ addMention(text: string, did: Did): this;
51
51
  /**
52
52
  * Add inline hashtag to the rich text
53
+ * @param text Text to display
53
54
  * @param tag The tag, without the pound prefix
54
- * @returns THe builder instance, for chaining
55
+ * @returns The builder instance, for chaining
55
56
  */
56
- addTag(tag: string): this;
57
+ addTag(text: string, tag: string): this;
57
58
  }
58
59
  export default RichtextBuilder;
59
60
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../lib/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AAC5D,OAAO,KAAK,EAAE,GAAG,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAExD,KAAK,WAAW,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,EAAE,GAAG,CAAC,GAAG,KAAK,CAAC;AAExD,uEAAuE;AACvE,MAAM,MAAM,KAAK,GAAG,oBAAoB,CAAC,IAAI,CAAC;AAC9C,gDAAgD;AAChD,MAAM,MAAM,YAAY,GAAG,WAAW,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC;AAI1D,0BAA0B;AAC1B,MAAM,WAAW,aAAa;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,KAAK,EAAE,CAAC;CAChB;AAED,kDAAkD;AAClD,cAAM,eAAe;;IAMpB,8BAA8B;IAC9B,IAAI,IAAI,IAAI,MAAM,CASjB;IAED,gCAAgC;IAChC,IAAI,MAAM,IAAI,KAAK,EAAE,CASpB;IAED,sCAAsC;IACtC,KAAK,IAAI,aAAa;IAOtB,uCAAuC;IACvC,KAAK,IAAI,eAAe;IAOxB;;;;OAIG;IACH,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAO7B;;;;;OAKG;IACH,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,YAAY,GAAG,IAAI;IAyB7D;;;;;OAKG;IACH,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,UAAU,GAAG,IAAI;IAI9C;;;;;OAKG;IACH,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,GAAG,IAAI;IAI1C;;;;OAIG;IACH,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;CAGzB;AAED,eAAe,eAAe,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../lib/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AAC5D,OAAO,KAAK,EAAE,GAAG,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAGxD,KAAK,WAAW,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,EAAE,GAAG,CAAC,GAAG,KAAK,CAAC;AAExD,uEAAuE;AACvE,MAAM,MAAM,KAAK,GAAG,oBAAoB,CAAC,IAAI,CAAC;AAC9C,gDAAgD;AAChD,MAAM,MAAM,YAAY,GAAG,WAAW,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC;AAE1D,0BAA0B;AAC1B,MAAM,WAAW,aAAa;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,KAAK,EAAE,CAAC;CAChB;AAED,kDAAkD;AAClD,cAAM,eAAe;;IAMpB,8BAA8B;IAC9B,IAAI,IAAI,IAAI,MAAM,CASjB;IAED,gCAAgC;IAChC,IAAI,MAAM,IAAI,KAAK,EAAE,CASpB;IAED,sCAAsC;IACtC,KAAK,IAAI,aAAa,CAKrB;IAED,uCAAuC;IACvC,KAAK,IAAI,eAAe,CAKvB;IAED;;;;OAIG;IACH,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAK1B;IAED;;;;;OAKG;IACH,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,YAAY,GAAG,IAAI,CAyB1D;IAED;;;;;OAKG;IACH,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,UAAU,GAAG,IAAI,CAE3C;IAED;;;;;OAKG;IACH,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,GAAG,IAAI,CAEvC;IAED;;;;;OAKG;IACH,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI,CAEtC;CACD;AAED,eAAe,eAAe,CAAC"}
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- const encoder = new TextEncoder();
1
+ import { getUtf8Length } from '@atcute/uint8array';
2
2
  /** Builder for constructing Bluesky rich texts */
3
3
  class RichtextBuilder {
4
4
  // Even-numbered are substrings, odd-numbered are facets
@@ -38,65 +38,67 @@ class RichtextBuilder {
38
38
  }
39
39
  /**
40
40
  * Add plain text to the rich text
41
- * @param substr The plain text
41
+ * @param text The plain text
42
42
  * @returns The builder instance, for chaining
43
43
  */
44
- addText(substr) {
44
+ addText(text) {
45
45
  const segments = this.#segments;
46
- segments[segments.length - 1] += substr;
46
+ segments[segments.length - 1] += text;
47
47
  return this;
48
48
  }
49
49
  /**
50
50
  * Add decorated text to the rich text
51
- * @param substr The text itself
51
+ * @param text The text itself
52
52
  * @param feature Feature to imbue on the text
53
53
  * @returns The builder instance, for chaining
54
54
  */
55
- addDecoratedText(substr, feature) {
55
+ addDecoratedText(text, feature) {
56
56
  const segments = this.#segments;
57
57
  const last = segments.length - 1;
58
58
  // Calculate the starting index
59
59
  let start = 0;
60
- start += encoder.encode(segments[last]).byteLength;
60
+ start += getUtf8Length(segments[last]);
61
61
  if (last !== 0) {
62
62
  start += segments[last - 1].index.byteEnd;
63
63
  }
64
+ const byteLength = getUtf8Length(text);
64
65
  const facet = {
65
66
  index: {
66
67
  byteStart: start,
67
- byteEnd: start + encoder.encode(substr).byteLength,
68
+ byteEnd: start + byteLength,
68
69
  },
69
70
  features: [feature],
70
71
  };
71
- segments[last] += substr;
72
+ segments[last] += text;
72
73
  segments.push(facet, '');
73
74
  return this;
74
75
  }
75
76
  /**
76
77
  * Add link to the rich text
77
- * @param substr Text of the link
78
+ * @param text Text of the link
78
79
  * @param uri Valid URL, for example: https://example.com
79
80
  * @returns The builder instance, for chaining
80
81
  */
81
- addLink(substr, uri) {
82
- return this.addDecoratedText(substr, { $type: 'app.bsky.richtext.facet#link', uri: uri });
82
+ addLink(text, uri) {
83
+ return this.addDecoratedText(text, { $type: 'app.bsky.richtext.facet#link', uri: uri });
83
84
  }
84
85
  /**
85
- * Mentions a user in rich text
86
- * @param substr Text of the mention, this is usually in the form of `@handle`
86
+ * Mention a user in rich text
87
+ * @param text Text of the mention, usually in the form of `@handle`
87
88
  * @param did Valid DID, for example: did:plc:ia76kvnndjutgedggx2ibrem
88
89
  * @returns The builder instance, for chaining
89
90
  */
90
- addMention(substr, did) {
91
- return this.addDecoratedText(substr, { $type: 'app.bsky.richtext.facet#mention', did: did });
91
+ addMention(text, did) {
92
+ return this.addDecoratedText(text, { $type: 'app.bsky.richtext.facet#mention', did: did });
92
93
  }
93
94
  /**
94
95
  * Add inline hashtag to the rich text
96
+ * @param text Text to display
95
97
  * @param tag The tag, without the pound prefix
96
- * @returns THe builder instance, for chaining
98
+ * @returns The builder instance, for chaining
97
99
  */
98
- addTag(tag) {
99
- return this.addDecoratedText('#' + tag, { $type: 'app.bsky.richtext.facet#tag', tag: tag });
100
+ addTag(text, tag) {
101
+ return this.addDecoratedText(text, { $type: 'app.bsky.richtext.facet#tag', tag: tag });
100
102
  }
101
103
  }
102
104
  export default RichtextBuilder;
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../lib/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../lib/index.ts"],"names":[],"mappings":"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"}
package/lib/index.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import type { AppBskyRichtextFacet } from '@atcute/bluesky';
2
2
  import type { Did, GenericUri } from '@atcute/lexicons';
3
+ import { getUtf8Length } from '@atcute/uint8array';
3
4
 
4
5
  type UnwrapArray<T> = T extends (infer V)[] ? V : never;
5
6
 
@@ -8,8 +9,6 @@ export type Facet = AppBskyRichtextFacet.Main;
8
9
  /** Feature union type from Facet['features'] */
9
10
  export type FacetFeature = UnwrapArray<Facet['features']>;
10
11
 
11
- const encoder = new TextEncoder();
12
-
13
12
  /** Resulting rich text */
14
13
  export interface BakedRichtext {
15
14
  text: string;
@@ -65,74 +64,77 @@ class RichtextBuilder {
65
64
 
66
65
  /**
67
66
  * Add plain text to the rich text
68
- * @param substr The plain text
67
+ * @param text The plain text
69
68
  * @returns The builder instance, for chaining
70
69
  */
71
- addText(substr: string): this {
70
+ addText(text: string): this {
72
71
  const segments = this.#segments;
73
- segments[segments.length - 1] += substr;
72
+ segments[segments.length - 1] += text;
74
73
 
75
74
  return this;
76
75
  }
77
76
 
78
77
  /**
79
78
  * Add decorated text to the rich text
80
- * @param substr The text itself
79
+ * @param text The text itself
81
80
  * @param feature Feature to imbue on the text
82
81
  * @returns The builder instance, for chaining
83
82
  */
84
- addDecoratedText(substr: string, feature: FacetFeature): this {
83
+ addDecoratedText(text: string, feature: FacetFeature): this {
85
84
  const segments = this.#segments;
86
85
  const last = segments.length - 1;
87
86
 
88
87
  // Calculate the starting index
89
88
  let start = 0;
90
89
 
91
- start += encoder.encode(segments[last] as string).byteLength;
90
+ start += getUtf8Length(segments[last] as string);
92
91
  if (last !== 0) {
93
92
  start += (segments[last - 1] as Facet).index.byteEnd;
94
93
  }
95
94
 
95
+ const byteLength = getUtf8Length(text);
96
+
96
97
  const facet: Facet = {
97
98
  index: {
98
99
  byteStart: start,
99
- byteEnd: start + encoder.encode(substr).byteLength,
100
+ byteEnd: start + byteLength,
100
101
  },
101
102
  features: [feature],
102
103
  };
103
104
 
104
- segments[last] += substr;
105
+ segments[last] += text;
105
106
  segments.push(facet, '');
106
107
  return this;
107
108
  }
108
109
 
109
110
  /**
110
111
  * Add link to the rich text
111
- * @param substr Text of the link
112
+ * @param text Text of the link
112
113
  * @param uri Valid URL, for example: https://example.com
113
114
  * @returns The builder instance, for chaining
114
115
  */
115
- addLink(substr: string, uri: GenericUri): this {
116
- return this.addDecoratedText(substr, { $type: 'app.bsky.richtext.facet#link', uri: uri });
116
+ addLink(text: string, uri: GenericUri): this {
117
+ return this.addDecoratedText(text, { $type: 'app.bsky.richtext.facet#link', uri: uri });
117
118
  }
118
119
 
119
120
  /**
120
- * Mentions a user in rich text
121
- * @param substr Text of the mention, this is usually in the form of `@handle`
121
+ * Mention a user in rich text
122
+ * @param text Text of the mention, usually in the form of `@handle`
122
123
  * @param did Valid DID, for example: did:plc:ia76kvnndjutgedggx2ibrem
123
124
  * @returns The builder instance, for chaining
124
125
  */
125
- addMention(substr: string, did: Did): this {
126
- return this.addDecoratedText(substr, { $type: 'app.bsky.richtext.facet#mention', did: did });
126
+ addMention(text: string, did: Did): this {
127
+ return this.addDecoratedText(text, { $type: 'app.bsky.richtext.facet#mention', did: did });
127
128
  }
128
129
 
129
130
  /**
130
131
  * Add inline hashtag to the rich text
132
+ * @param text Text to display
131
133
  * @param tag The tag, without the pound prefix
132
- * @returns THe builder instance, for chaining
134
+ * @returns The builder instance, for chaining
133
135
  */
134
- addTag(tag: string): this {
135
- return this.addDecoratedText('#' + tag, { $type: 'app.bsky.richtext.facet#tag', tag: tag });
136
+ addTag(text: string, tag: string): this {
137
+ return this.addDecoratedText(text, { $type: 'app.bsky.richtext.facet#tag', tag: tag });
136
138
  }
137
139
  }
138
140
 
package/package.json CHANGED
@@ -1,7 +1,6 @@
1
1
  {
2
- "type": "module",
3
2
  "name": "@atcute/bluesky-richtext-builder",
4
- "version": "2.0.4",
3
+ "version": "3.0.0",
5
4
  "description": "builder pattern for Bluesky's rich text facets",
6
5
  "license": "0BSD",
7
6
  "repository": {
@@ -14,20 +13,21 @@
14
13
  "!lib/**/*.bench.ts",
15
14
  "!lib/**/*.test.ts"
16
15
  ],
16
+ "type": "module",
17
+ "sideEffects": false,
17
18
  "exports": {
18
19
  ".": "./dist/index.js"
19
20
  },
20
- "sideEffects": false,
21
- "dependencies": {
22
- "@atcute/bluesky": "^3.2.5",
23
- "@atcute/lexicons": "^1.2.2"
21
+ "publishConfig": {
22
+ "access": "public"
24
23
  },
25
- "devDependencies": {
26
- "@types/bun": "^1.2.21"
24
+ "dependencies": {
25
+ "@atcute/bluesky": "^3.2.21",
26
+ "@atcute/lexicons": "^1.2.9",
27
+ "@atcute/uint8array": "^1.1.1"
27
28
  },
28
29
  "scripts": {
29
- "build": "tsc --project tsconfig.build.json",
30
- "test": "bun test --coverage",
30
+ "build": "tsgo --project tsconfig.build.json",
31
31
  "prepublish": "rm -rf dist; pnpm run build"
32
32
  }
33
33
  }