@flitt/api 1.0.0 → 1.2.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/CHANGELOG.md +14 -0
- package/README.md +65 -22
- package/dist/cjs/client.d.ts +65 -5
- package/dist/cjs/client.d.ts.map +1 -1
- package/dist/cjs/client.js +136 -12
- package/dist/cjs/client.js.map +1 -1
- package/dist/cjs/http.d.ts +6 -1
- package/dist/cjs/http.d.ts.map +1 -1
- package/dist/cjs/http.js +17 -3
- package/dist/cjs/http.js.map +1 -1
- package/dist/cjs/index.d.ts +1 -1
- package/dist/cjs/index.js +1 -1
- package/dist/cjs/types.d.ts +24 -4
- package/dist/cjs/types.d.ts.map +1 -1
- package/dist/cjs/types.js +2 -2
- package/dist/cjs/version.d.ts +1 -1
- package/dist/cjs/version.js +1 -1
- package/dist/esm/client.d.ts +65 -5
- package/dist/esm/client.d.ts.map +1 -1
- package/dist/esm/client.js +136 -12
- package/dist/esm/client.js.map +1 -1
- package/dist/esm/http.d.ts +6 -1
- package/dist/esm/http.d.ts.map +1 -1
- package/dist/esm/http.js +17 -3
- package/dist/esm/http.js.map +1 -1
- package/dist/esm/index.d.ts +1 -1
- package/dist/esm/index.js +1 -1
- package/dist/esm/types.d.ts +24 -4
- package/dist/esm/types.d.ts.map +1 -1
- package/dist/esm/types.js +2 -2
- package/dist/esm/version.d.ts +1 -1
- package/dist/esm/version.js +1 -1
- package/package.json +2 -2
- package/src/client.ts +156 -13
- package/src/http.ts +19 -3
- package/src/index.ts +1 -1
- package/src/types.ts +29 -5
- package/src/version.ts +1 -1
package/src/client.ts
CHANGED
|
@@ -30,6 +30,7 @@ import type {
|
|
|
30
30
|
PlayerList,
|
|
31
31
|
PlayerStatusFilter,
|
|
32
32
|
RateLimit,
|
|
33
|
+
Review,
|
|
33
34
|
ReviewList,
|
|
34
35
|
ReviewListParams,
|
|
35
36
|
Server,
|
|
@@ -46,6 +47,7 @@ import type {
|
|
|
46
47
|
VoteListParams,
|
|
47
48
|
VoteServer,
|
|
48
49
|
VoteStats,
|
|
50
|
+
VoteSummary,
|
|
49
51
|
} from "./types.js";
|
|
50
52
|
|
|
51
53
|
export const DEFAULT_BASE_URL = "https://api.flitt.me";
|
|
@@ -74,12 +76,22 @@ export type ClientOptions = {
|
|
|
74
76
|
};
|
|
75
77
|
|
|
76
78
|
export type FlittOptions = ClientOptions & {
|
|
77
|
-
/**
|
|
79
|
+
/**
|
|
80
|
+
* Account API key (`flt_…`, Profile → API): every endpoint, on every server
|
|
81
|
+
* you own or are a member of. Default: `process.env.FLITT_API_KEY`.
|
|
82
|
+
*/
|
|
78
83
|
apiKey?: string;
|
|
84
|
+
/**
|
|
85
|
+
* Server token (`flv_…`, Dashboard → your server → Votes → API): votes and
|
|
86
|
+
* reviews of that one server only. The credential to put on a game server.
|
|
87
|
+
* Default: `process.env.FLITT_SERVER_TOKEN` (or `FLITT_VOTE_TOKEN`).
|
|
88
|
+
*/
|
|
89
|
+
serverToken?: string;
|
|
79
90
|
};
|
|
80
91
|
|
|
92
|
+
/** @deprecated since 1.1: use `new Flitt({ serverToken })` and `flitt.votes`. */
|
|
81
93
|
export type FlittVotesOptions = ClientOptions & {
|
|
82
|
-
/**
|
|
94
|
+
/** Server token (`flv_…`). Default: `process.env.FLITT_VOTE_TOKEN`. */
|
|
83
95
|
token?: string;
|
|
84
96
|
};
|
|
85
97
|
|
|
@@ -137,7 +149,7 @@ function positiveInt(value: number | undefined, name: string, max: number): numb
|
|
|
137
149
|
* ```
|
|
138
150
|
*/
|
|
139
151
|
export class Flitt {
|
|
140
|
-
|
|
152
|
+
#lastRateLimit: RateLimit | null = null;
|
|
141
153
|
|
|
142
154
|
/** Your account's monthly quota. */
|
|
143
155
|
readonly usage: {
|
|
@@ -148,19 +160,45 @@ export class Flitt {
|
|
|
148
160
|
readonly firewall: FirewallResource;
|
|
149
161
|
readonly discord: DiscordResource;
|
|
150
162
|
readonly commands: CommandsResource;
|
|
163
|
+
/** Votes: check, claim (idempotent), list, top voters, summary, stats. Account key or server token. */
|
|
164
|
+
readonly votes: VotesResource;
|
|
165
|
+
/** Published reviews. Account key or server token. */
|
|
166
|
+
readonly reviews: ReviewsResource;
|
|
151
167
|
|
|
152
168
|
constructor(options: FlittOptions = {}) {
|
|
153
169
|
const apiKey = (options.apiKey ?? envVar("FLITT_API_KEY") ?? "").trim();
|
|
154
|
-
|
|
155
|
-
if (!
|
|
170
|
+
const serverToken = (options.serverToken ?? envVar("FLITT_SERVER_TOKEN") ?? envVar("FLITT_VOTE_TOKEN") ?? "").trim();
|
|
171
|
+
if (!apiKey && !serverToken) {
|
|
172
|
+
throw new FlittConfigError(
|
|
173
|
+
"Missing credentials: pass { apiKey } (flt_…) or { serverToken } (flv_…), or set FLITT_API_KEY / FLITT_SERVER_TOKEN.",
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
if (apiKey && !API_KEY_FORMAT.test(apiKey)) {
|
|
156
177
|
throw new FlittConfigError(
|
|
157
178
|
apiKey.startsWith("flv_")
|
|
158
|
-
? "This is a
|
|
179
|
+
? "This is a server token (flv_…): pass it as { serverToken }."
|
|
159
180
|
: "Invalid API key format: Flitt API keys start with flt_ (Profile → API).",
|
|
160
181
|
);
|
|
161
182
|
}
|
|
162
|
-
|
|
163
|
-
|
|
183
|
+
if (serverToken && !VOTE_TOKEN_FORMAT.test(serverToken)) {
|
|
184
|
+
throw new FlittConfigError(
|
|
185
|
+
serverToken.startsWith("flt_")
|
|
186
|
+
? "This is an account API key (flt_…): pass it as { apiKey }."
|
|
187
|
+
: "Invalid server token format: server tokens start with flv_ (Dashboard → your server → Votes → API).",
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
const tracked: ClientOptions = {
|
|
191
|
+
...options,
|
|
192
|
+
onRateLimit: (rl: RateLimit) => {
|
|
193
|
+
this.#lastRateLimit = rl;
|
|
194
|
+
options.onRateLimit?.(rl);
|
|
195
|
+
},
|
|
196
|
+
};
|
|
197
|
+
const accountHttp = apiKey ? buildTransport(apiKey, tracked, false) : null;
|
|
198
|
+
const tokenHttp = serverToken ? buildTransport(serverToken, tracked, false) : null;
|
|
199
|
+
// Account endpoints need the account key; votes and reviews take either (account key first).
|
|
200
|
+
const http = accountHttp ?? (missingApiKey as unknown as Transport);
|
|
201
|
+
const votesHttp = (accountHttp ?? tokenHttp) as Transport;
|
|
164
202
|
const base = "/v1/public-api";
|
|
165
203
|
|
|
166
204
|
this.usage = { get: async (o) => http.request<Usage>("GET", `${base}/usage`, o) };
|
|
@@ -169,19 +207,21 @@ export class Flitt {
|
|
|
169
207
|
this.firewall = new FirewallResource(http, base);
|
|
170
208
|
this.discord = new DiscordResource(http, base);
|
|
171
209
|
this.commands = new CommandsResource(http, base);
|
|
210
|
+
this.votes = new VotesResource(votesHttp, base);
|
|
211
|
+
this.reviews = new ReviewsResource(votesHttp, base);
|
|
172
212
|
}
|
|
173
213
|
|
|
174
214
|
/** Quota state from the last response (`null` before the first request). */
|
|
175
215
|
get rateLimit(): RateLimit | null {
|
|
176
|
-
return this.#
|
|
216
|
+
return this.#lastRateLimit;
|
|
177
217
|
}
|
|
178
218
|
|
|
179
|
-
/** Never prints the
|
|
219
|
+
/** Never prints the credentials. */
|
|
180
220
|
toJSON() {
|
|
181
|
-
return { client: "Flitt",
|
|
221
|
+
return { client: "Flitt", credentials: "[redacted]" };
|
|
182
222
|
}
|
|
183
223
|
[INSPECT]() {
|
|
184
|
-
return "Flitt {
|
|
224
|
+
return "Flitt { credentials: [redacted] }";
|
|
185
225
|
}
|
|
186
226
|
}
|
|
187
227
|
|
|
@@ -442,6 +482,105 @@ class CommandsResource {
|
|
|
442
482
|
}
|
|
443
483
|
}
|
|
444
484
|
|
|
485
|
+
/** Stand-in transport when only a server token was given: account endpoints explain what is missing. */
|
|
486
|
+
const missingApiKey = {
|
|
487
|
+
lastRateLimit: null,
|
|
488
|
+
async request(): Promise<never> {
|
|
489
|
+
throw new FlittConfigError(
|
|
490
|
+
"This endpoint needs an account API key (flt_…, Profile → API). A server token (flv_…) only opens votes and reviews.",
|
|
491
|
+
);
|
|
492
|
+
},
|
|
493
|
+
};
|
|
494
|
+
|
|
495
|
+
class VotesResource {
|
|
496
|
+
constructor(private readonly http: Transport, private readonly base: string) {}
|
|
497
|
+
|
|
498
|
+
private path(serverId: string, rest = ""): string {
|
|
499
|
+
return `${this.base}/servers/${segment(serverId, "serverId")}/votes${rest}`;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
/** Whether the player has a vote waiting to be claimed (last 24 h). */
|
|
503
|
+
async check(serverId: string, identity: VoteIdentity, options?: RequestOptions): Promise<VoteCheck> {
|
|
504
|
+
return this.http.request<VoteCheck>("GET", this.path(serverId, "/check"), { ...options, query: identityParams(identity) });
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
/**
|
|
508
|
+
* Claims the player's oldest unclaimed vote, exactly once across every
|
|
509
|
+
* channel (addon, API, NuVotifier). Give the reward only when `status` is
|
|
510
|
+
* `CLAIMED_NOW`. An idempotency key is generated so automatic retries can
|
|
511
|
+
* never claim twice; pass your own to make your retries safe too. With an
|
|
512
|
+
* account key, your role needs the "manage votes" permission.
|
|
513
|
+
*/
|
|
514
|
+
async claim(serverId: string, identity: VoteIdentity, options?: RequestOptions & { idempotencyKey?: string }): Promise<VoteClaim> {
|
|
515
|
+
const idempotencyKey = options?.idempotencyKey ?? randomKey();
|
|
516
|
+
if (idempotencyKey.length > 128) throw new FlittConfigError("idempotencyKey: 128 characters max.");
|
|
517
|
+
return this.http.request<VoteClaim>("POST", this.path(serverId, "/claim"), { ...options, body: identityParams(identity), idempotencyKey });
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
/** Recent votes, oldest first (up to 30 days back), cursor pagination. */
|
|
521
|
+
async list(serverId: string, params: VoteListParams = {}, options?: RequestOptions): Promise<VoteList> {
|
|
522
|
+
return this.http.request<VoteList>("GET", this.path(serverId), {
|
|
523
|
+
...options,
|
|
524
|
+
query: { since: params.since, limit: positiveInt(params.limit, "limit", 100), cursor: params.cursor },
|
|
525
|
+
});
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
/** Iterates over every vote since `since`. */
|
|
529
|
+
async *listAll(serverId: string, params: Omit<VoteListParams, "cursor"> = {}, options?: RequestOptions): AsyncGenerator<VoteListItem> {
|
|
530
|
+
let cursor: string | undefined;
|
|
531
|
+
for (;;) {
|
|
532
|
+
const res = await this.list(serverId, { ...params, cursor }, options);
|
|
533
|
+
yield* res.votes;
|
|
534
|
+
if (!res.nextCursor) return;
|
|
535
|
+
cursor = res.nextCursor;
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
/** Top voters of a month (`current_month`, `last_month`, `all_time` or `YYYY-MM`). */
|
|
540
|
+
async top(serverId: string, params: { period?: TopVotersPeriod; limit?: number } = {}, options?: RequestOptions): Promise<TopVoters> {
|
|
541
|
+
return this.http.request<TopVoters>("GET", this.path(serverId, "/top"), {
|
|
542
|
+
...options,
|
|
543
|
+
query: { period: params.period, limit: positiveInt(params.limit, "limit", 50) },
|
|
544
|
+
});
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
/** Vote counters, ranking, status and rating (and the history with `includeStats`). */
|
|
548
|
+
async summary(serverId: string, params: { includeStats?: boolean } = {}, options?: RequestOptions): Promise<VoteSummary> {
|
|
549
|
+
return this.http.request<VoteSummary>("GET", this.path(serverId, "/summary"), {
|
|
550
|
+
...options,
|
|
551
|
+
query: { include: params.includeStats ? "stats" : undefined },
|
|
552
|
+
});
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
/** Daily, weekly and monthly votes, page views and join clicks. */
|
|
556
|
+
async stats(serverId: string, options?: RequestOptions): Promise<VoteStats> {
|
|
557
|
+
return this.http.request<VoteStats>("GET", this.path(serverId, "/stats"), options);
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
class ReviewsResource {
|
|
562
|
+
constructor(private readonly http: Transport, private readonly base: string) {}
|
|
563
|
+
|
|
564
|
+
/** Published reviews of a server. */
|
|
565
|
+
async list(serverId: string, params: ReviewListParams = {}, options?: RequestOptions): Promise<ReviewList> {
|
|
566
|
+
return this.http.request<ReviewList>("GET", `${this.base}/servers/${segment(serverId, "serverId")}/reviews`, {
|
|
567
|
+
...options,
|
|
568
|
+
query: { limit: positiveInt(params.limit, "limit", 50), cursor: params.cursor, sort: params.sort },
|
|
569
|
+
});
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
/** Iterates over every published review. */
|
|
573
|
+
async *listAll(serverId: string, params: Omit<ReviewListParams, "cursor"> = {}, options?: RequestOptions): AsyncGenerator<Review> {
|
|
574
|
+
let cursor: string | undefined;
|
|
575
|
+
for (;;) {
|
|
576
|
+
const res = await this.list(serverId, { ...params, cursor }, options);
|
|
577
|
+
yield* res.reviews;
|
|
578
|
+
if (!res.nextCursor) return;
|
|
579
|
+
cursor = res.nextCursor;
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
|
|
445
584
|
function snowflake(value: string): string {
|
|
446
585
|
const v = String(value ?? "").trim();
|
|
447
586
|
if (!/^\d{15,22}$/.test(v)) throw new FlittConfigError("discordId must be a Discord user id (15 to 22 digits).");
|
|
@@ -474,6 +613,10 @@ function identityParams(identity: VoteIdentity): Record<string, string> {
|
|
|
474
613
|
}
|
|
475
614
|
|
|
476
615
|
/**
|
|
616
|
+
* @deprecated since 1.1 — votes are part of the Public API: use
|
|
617
|
+
* `new Flitt({ serverToken })` and `flitt.votes.claim(serverId, identity)`.
|
|
618
|
+
* Kept so 1.0 code keeps working (it calls the legacy /v1/vote-api routes).
|
|
619
|
+
*
|
|
477
620
|
* Client of the Flitt Vote API: reward voters from your own scripts when the
|
|
478
621
|
* Flitt addon is not installed. One token = one server.
|
|
479
622
|
*
|
|
@@ -493,7 +636,7 @@ export class FlittVotes {
|
|
|
493
636
|
if (!VOTE_TOKEN_FORMAT.test(token)) {
|
|
494
637
|
throw new FlittConfigError(
|
|
495
638
|
token.startsWith("flt_")
|
|
496
|
-
? "This is
|
|
639
|
+
? "This is an account API key (flt_…): use `new Flitt({ apiKey })` instead."
|
|
497
640
|
: "Invalid vote token format: vote tokens start with flv_ (Dashboard → your server → Votes → Vote API).",
|
|
498
641
|
);
|
|
499
642
|
}
|
package/src/http.ts
CHANGED
|
@@ -191,12 +191,28 @@ function describeError(status: number, statusText: string, body: unknown, text:
|
|
|
191
191
|
}
|
|
192
192
|
|
|
193
193
|
export class Transport {
|
|
194
|
-
private
|
|
194
|
+
// Real private fields (not TypeScript `private`): the token and the options
|
|
195
|
+
// holding it cannot be reached by console.log, util.inspect or JSON.stringify.
|
|
196
|
+
readonly #opts: Omit<TransportOptions, "token">;
|
|
197
|
+
readonly #token: string;
|
|
195
198
|
/** Quota state from the last response carrying X-RateLimit-* headers. */
|
|
196
199
|
lastRateLimit: RateLimit | null = null;
|
|
197
200
|
|
|
198
201
|
constructor(opts: TransportOptions) {
|
|
199
|
-
|
|
202
|
+
const { token, ...rest } = opts;
|
|
203
|
+
this.#token = token;
|
|
204
|
+
this.#opts = rest;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
toJSON() {
|
|
208
|
+
return { baseUrl: this.#opts.baseUrl, token: "[redacted]" };
|
|
209
|
+
}
|
|
210
|
+
[Symbol.for("nodejs.util.inspect.custom")]() {
|
|
211
|
+
return `Transport { baseUrl: ${this.#opts.baseUrl}, token: [redacted] }`;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
private get opts() {
|
|
215
|
+
return this.#opts;
|
|
200
216
|
}
|
|
201
217
|
|
|
202
218
|
async request<T>(method: string, path: string, call: Call = {}): Promise<T> {
|
|
@@ -208,7 +224,7 @@ export class Transport {
|
|
|
208
224
|
const headers: Record<string, string> = {
|
|
209
225
|
Accept: "application/json",
|
|
210
226
|
...this.opts.headers,
|
|
211
|
-
Authorization: `Bearer ${this
|
|
227
|
+
Authorization: `Bearer ${this.#token}`,
|
|
212
228
|
};
|
|
213
229
|
// Browsers forbid setting User-Agent; elsewhere it identifies the SDK in our logs.
|
|
214
230
|
if (!isBrowserLike()) headers["User-Agent"] = this.opts.userAgent ?? `flitt-api-js/${VERSION}`;
|
package/src/index.ts
CHANGED
package/src/types.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Response types of the Flitt Public API v1 (`/v1/public-api`) and
|
|
3
|
-
*
|
|
2
|
+
* Response types of the Flitt Public API v1 (`/v1/public-api`), votes and
|
|
3
|
+
* reviews included. Dates are ISO 8601 strings, as sent by the API.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
export type Game = "GMOD" | "FIVEM" | "MC";
|
|
@@ -48,6 +48,20 @@ export type Usage = {
|
|
|
48
48
|
resetAt: string;
|
|
49
49
|
};
|
|
50
50
|
|
|
51
|
+
/**
|
|
52
|
+
* Extended status set by the owner: maintenance, development or temporary
|
|
53
|
+
* closure. While set, the uptime is paused (checks count neither as online nor
|
|
54
|
+
* offline) and up / down alerts are muted.
|
|
55
|
+
*/
|
|
56
|
+
export type ServerHold = {
|
|
57
|
+
kind: "MAINTENANCE" | "DEVELOPMENT" | "CLOSED";
|
|
58
|
+
/** Owner's note for the players, if any. */
|
|
59
|
+
message: string | null;
|
|
60
|
+
since: string;
|
|
61
|
+
/** Planned end (the status ends by itself then), `null` when not announced. */
|
|
62
|
+
until: string | null;
|
|
63
|
+
};
|
|
64
|
+
|
|
51
65
|
export type ServerSummary = {
|
|
52
66
|
id: string;
|
|
53
67
|
game: Game;
|
|
@@ -61,6 +75,8 @@ export type ServerSummary = {
|
|
|
61
75
|
rating: number;
|
|
62
76
|
reviewsCount: number;
|
|
63
77
|
verifiedBadge: boolean;
|
|
78
|
+
/** Maintenance / development / closure, `null` when the server runs normally. */
|
|
79
|
+
hold: ServerHold | null;
|
|
64
80
|
createdAt: string | null;
|
|
65
81
|
};
|
|
66
82
|
|
|
@@ -92,6 +108,8 @@ export type Server = {
|
|
|
92
108
|
rating: number;
|
|
93
109
|
reviewsCount: number;
|
|
94
110
|
status: OnlineStatus;
|
|
111
|
+
/** Maintenance / development / closure, `null` when the server runs normally. */
|
|
112
|
+
hold: ServerHold | null;
|
|
95
113
|
accessRole: AccessRole;
|
|
96
114
|
};
|
|
97
115
|
|
|
@@ -264,7 +282,9 @@ export type WebhookEventType =
|
|
|
264
282
|
| "review_created"
|
|
265
283
|
| "vote_created"
|
|
266
284
|
| "affluence_low"
|
|
267
|
-
| "affluence_high"
|
|
285
|
+
| "affluence_high"
|
|
286
|
+
| "server_hold_started"
|
|
287
|
+
| "server_hold_ended";
|
|
268
288
|
|
|
269
289
|
/** Body of a generic (HTTP) webhook. */
|
|
270
290
|
export type WebhookEvent<T = Record<string, unknown> | null> = {
|
|
@@ -275,7 +295,7 @@ export type WebhookEvent<T = Record<string, unknown> | null> = {
|
|
|
275
295
|
};
|
|
276
296
|
|
|
277
297
|
// ---------------------------------------------------------------------------
|
|
278
|
-
//
|
|
298
|
+
// Votes and reviews (part of the Public API)
|
|
279
299
|
// ---------------------------------------------------------------------------
|
|
280
300
|
|
|
281
301
|
type IdentityFields = {
|
|
@@ -355,7 +375,8 @@ export type VoteStats = {
|
|
|
355
375
|
monthly: Array<{ month: string; votes: number; views: number; joinClicks: number; rank: number | null }>;
|
|
356
376
|
};
|
|
357
377
|
|
|
358
|
-
|
|
378
|
+
/** Vote counters, ranking, status and rating of a server (`flitt.votes.summary`). */
|
|
379
|
+
export type VoteSummary = {
|
|
359
380
|
id: string;
|
|
360
381
|
name: string | null;
|
|
361
382
|
game: Game;
|
|
@@ -370,6 +391,9 @@ export type VoteServer = {
|
|
|
370
391
|
stats?: VoteStats;
|
|
371
392
|
};
|
|
372
393
|
|
|
394
|
+
/** @deprecated since 1.1: renamed VoteSummary. */
|
|
395
|
+
export type VoteServer = VoteSummary;
|
|
396
|
+
|
|
373
397
|
export type Review = {
|
|
374
398
|
id: string;
|
|
375
399
|
rating: number;
|
package/src/version.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
/** Package version, kept in sync with package.json by scripts/sync-version.mjs. */
|
|
2
|
-
export const VERSION = "1.
|
|
2
|
+
export const VERSION = "1.2.0";
|