prividium 0.1.9 → 0.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.
@@ -45,6 +45,9 @@ async function startServer(opts) {
45
45
  },
46
46
  onCall(methodName) {
47
47
  workflow.onMessage(methodName);
48
+ },
49
+ onReAuthNeeded() {
50
+ workflow.onMessage(`Please login again: ${serverUrl}`);
48
51
  }
49
52
  });
50
53
  checkHostAndPortWarnings(opts.host, opts.port, opts.allowExternalAccess);
@@ -52,7 +55,8 @@ async function startServer(opts) {
52
55
  port: opts.port,
53
56
  host: opts.host
54
57
  });
55
- await workflow.waitForAuthentication(`http://${opts.host}:${opts.port}`);
58
+ const serverUrl = `http://${opts.host}:${opts.port}`;
59
+ await workflow.waitForAuthentication(serverUrl);
56
60
  }
57
61
  export const addProxy = (cli) => {
58
62
  return cli.command('proxy', 'Starts authenticated rpc proxy server', (yargs) => yargs
@@ -82,7 +86,7 @@ export const addProxy = (cli) => {
82
86
  .option('host', {
83
87
  alias: 'h',
84
88
  description: 'Host used for local server. By default traffic from outside localhost is disabled.',
85
- default: 'localhost',
89
+ default: '127.0.0.1',
86
90
  type: 'string'
87
91
  })
88
92
  .option('unsecureAllowOutsideAccess', {
@@ -14,12 +14,17 @@ function randomStateString() {
14
14
  }
15
15
  const rpcCallSchema = z.object({ method: z.string() });
16
16
  const rpcReqSchema = z.union([rpcCallSchema, rpcCallSchema.array()]);
17
+ export const sessionSchema = z.object({
18
+ type: z.string(),
19
+ expiresAt: z.coerce.date()
20
+ });
17
21
  export function buildServer(config) {
18
22
  const app = fastifyApp();
19
23
  const validHosts = config.host === 'localhost' || config.host === '127.0.0.1' ? ['localhost', '127.0.0.1'] : [config.host];
20
24
  app.setValidatorCompiler(validatorCompiler);
21
25
  const state = randomStateString();
22
26
  let accessToken = '';
27
+ let expiresAt = new Date();
23
28
  app.register(fastifyStatic, { root: path.join(import.meta.dirname, '..', 'static') });
24
29
  app.get('/health', async (_req, reply) => {
25
30
  return reply.send('ok');
@@ -51,6 +56,16 @@ export function buildServer(config) {
51
56
  if (req.body.state !== state) {
52
57
  throw new Error('invalid state received');
53
58
  }
59
+ const { expiresAt: expirationMoment } = await fetch(config.prividiumRpcUrl.replace('rpc', 'api/auth/current-session'), {
60
+ method: 'GET',
61
+ headers: { authorization: `Bearer ${req.body.token}` }
62
+ }).then((res) => {
63
+ if (!res.ok || res.status !== 200) {
64
+ throw new Error('Error interacting with api');
65
+ }
66
+ return res.json().then((json) => sessionSchema.parse(json));
67
+ });
68
+ expiresAt = expirationMoment;
54
69
  accessToken = req.body.token;
55
70
  await config.onSubmit();
56
71
  return reply.send('ok');
@@ -63,6 +78,11 @@ export function buildServer(config) {
63
78
  routes: ['/'],
64
79
  preValidation: (request, _reply, done) => {
65
80
  const method = rpcReqSchema.safeParse(request.body);
81
+ if (new Date() > expiresAt) {
82
+ config.onReAuthNeeded();
83
+ done(new Error('err'));
84
+ return;
85
+ }
66
86
  if (!method.success) {
67
87
  config.onCall('unknown');
68
88
  }
@@ -0,0 +1,3 @@
1
+ export declare class PrividiumSessionError extends Error {
2
+ constructor();
3
+ }
@@ -0,0 +1,5 @@
1
+ export class PrividiumSessionError extends Error {
2
+ constructor() {
3
+ super('No session started or previous session expired. Please call authorize() first.');
4
+ }
5
+ }
@@ -12,7 +12,6 @@ export interface PopupAuthConfig {
12
12
  clientId: string;
13
13
  redirectUri: string;
14
14
  tokenManager: TokenManager;
15
- onAuthExpiry?: () => void;
16
15
  }
17
16
  export declare class PopupAuth {
18
17
  private config;
@@ -71,21 +71,22 @@ export class PopupAuth {
71
71
  return;
72
72
  }
73
73
  // Success - store token and resolve
74
- try {
75
- const tokenData = this.config.tokenManager.setToken(token);
74
+ this.config.tokenManager
75
+ .setToken(token)
76
+ .then((tokenData) => {
76
77
  this.config.tokenManager.clearState();
77
78
  popup.close();
78
79
  cleanup();
79
80
  resolve(tokenData.rawToken);
80
- }
81
- catch (error) {
81
+ })
82
+ .catch((error) => {
82
83
  popup.close();
83
84
  cleanup();
84
85
  this.config.tokenManager.clearState();
85
86
  this.config.tokenManager.clearToken();
86
87
  // eslint-disable-next-line @typescript-eslint/prefer-promise-reject-errors
87
88
  reject(error);
88
- }
89
+ });
89
90
  };
90
91
  // Listen for postMessage from callback page
91
92
  window.addEventListener('message', messageHandler);
@@ -1,8 +1,11 @@
1
1
  import { http } from 'viem';
2
2
  import { mainnet } from 'viem/chains';
3
+ import { authorizeTransactionResponseSchema, profileSchema } from './types.js';
3
4
  import { LocalStorage, TokenManager } from './storage.js';
4
5
  import { PopupAuth } from './popup-auth.js';
5
6
  import { hasPrividiumUnauthorizedError } from './error-utils.js';
7
+ import { PrividiumSessionError } from './errors';
8
+ import { z } from 'zod';
6
9
  function rpcUrl(apiUrl) {
7
10
  return new URL('/rpc', apiUrl).toString();
8
11
  }
@@ -14,23 +17,49 @@ export function createPrividiumChain(config) {
14
17
  throw new Error('"permissionsApiBaseUrl" was deprecated. Please use "prividiumApiBaseUrl" instead.');
15
18
  }
16
19
  const storage = config.storage || new LocalStorage();
17
- const tokenManager = new TokenManager(storage, config.chain.id);
20
+ const tokenManager = new TokenManager(storage, config.chain.id, config.prividiumApiBaseUrl, config.onAuthExpiry);
18
21
  const popupAuth = new PopupAuth({
19
22
  clientId: config.clientId,
20
23
  authBaseUrl: config.authBaseUrl,
21
24
  redirectUri: config.redirectUrl,
22
- tokenManager,
23
- onAuthExpiry: config.onAuthExpiry
25
+ tokenManager
24
26
  });
25
27
  const getAuthHeaders = () => {
26
- const token = tokenManager.getToken();
27
- if (token) {
28
- return {
29
- Authorization: `Bearer ${token.rawToken}`
30
- };
28
+ const tokenData = tokenManager.getToken();
29
+ if (!tokenData) {
30
+ return null;
31
31
  }
32
- return null;
32
+ return {
33
+ Authorization: `Bearer ${tokenData.rawToken}`
34
+ };
33
35
  };
36
+ async function prividiumApiCall(schema, url, method, body) {
37
+ const headers = getAuthHeaders();
38
+ if (!headers) {
39
+ throw new PrividiumSessionError();
40
+ }
41
+ const response = await fetch(url, {
42
+ method,
43
+ headers: {
44
+ 'Content-Type': 'application/json',
45
+ ...(body && { body }),
46
+ ...headers
47
+ }
48
+ });
49
+ if (response.status === 403 || response.status === 401) {
50
+ tokenManager.clearToken();
51
+ config.onAuthExpiry?.();
52
+ throw new PrividiumSessionError();
53
+ }
54
+ if (!response.ok) {
55
+ throw new Error(`Error calling ${url}: ${response.status} ${response.statusText}`);
56
+ }
57
+ const parsed = schema.safeParse(await response.json());
58
+ if (!parsed.success) {
59
+ throw new Error(`Unexpected api response response calling ${url}`);
60
+ }
61
+ return parsed.data;
62
+ }
34
63
  // Create transport with auth integration using viem callbacks
35
64
  const transport = http(rpcUrl(config.prividiumApiBaseUrl), {
36
65
  batch: false,
@@ -75,108 +104,27 @@ export function createPrividiumChain(config) {
75
104
  },
76
105
  getAuthHeaders,
77
106
  async fetchUser() {
78
- const headers = getAuthHeaders();
79
- if (!headers) {
80
- throw new Error('Authentication required. Please call authorize() first.');
81
- }
82
- const response = await fetch(`${config.prividiumApiBaseUrl}/api/profiles/me`, {
83
- method: 'GET',
84
- headers: {
85
- 'Content-Type': 'application/json',
86
- ...headers
87
- }
88
- });
89
- if (response.status === 403) {
90
- tokenManager.clearToken();
91
- config.onAuthExpiry?.();
92
- throw new Error('Authentication required. Please call authorize() first.');
93
- }
94
- if (!response.ok) {
95
- throw new Error(`Failed to fetch user profile: ${response.status} ${response.statusText}`);
96
- }
97
- const userData = (await response.json());
98
- return {
99
- userId: userData.userId,
100
- createdAt: new Date(userData.createdAt),
101
- displayName: userData.displayName,
102
- updatedAt: new Date(userData.updatedAt),
103
- roles: userData.roles,
104
- walletAddresses: userData.walletAddresses
105
- };
107
+ return await prividiumApiCall(profileSchema, `${config.prividiumApiBaseUrl}/api/profiles/me`, 'GET');
106
108
  },
107
109
  async getWalletToken() {
108
- const headers = getAuthHeaders();
109
- if (!headers) {
110
- throw new Error('Authentication required. Please call authorize() first.');
111
- }
112
- const response = await fetch(`${config.prividiumApiBaseUrl}/api/wallet/personal-rpc-token`, {
113
- method: 'GET',
114
- headers: {
115
- 'Content-Type': 'application/json',
116
- ...headers
117
- }
118
- });
119
- if (response.status === 403) {
120
- tokenManager.clearToken();
121
- config.onAuthExpiry?.();
122
- throw new Error('Authentication required. Please call authorize() first.');
123
- }
124
- if (!response.ok) {
125
- throw new Error(`Failed to fetch wallet token: ${response.status} ${response.statusText}`);
126
- }
127
- const data = (await response.json());
128
- return data.token;
110
+ const { token } = await prividiumApiCall(z.object({ token: z.string() }), `${config.prividiumApiBaseUrl}/api/wallet/personal-rpc-token`, 'GET');
111
+ return token;
129
112
  },
130
113
  async getWalletRpcUrl() {
131
114
  const walletToken = await this.getWalletToken();
132
115
  return walletRpcUrl(config.prividiumApiBaseUrl, walletToken);
133
116
  },
134
117
  async invalidateWalletToken() {
135
- const headers = getAuthHeaders();
136
- if (!headers) {
137
- throw new Error('Authentication required. Please call authorize() first.');
138
- }
139
- const response = await fetch(`${config.prividiumApiBaseUrl}/api/wallet/invalidate`, {
140
- method: 'POST',
141
- headers: {
142
- 'Content-Type': 'application/json',
143
- ...headers
144
- }
145
- });
146
- if (!response.ok) {
147
- throw new Error(`Failed to invalidate wallet token: ${response.status} ${response.statusText}`);
148
- }
149
- const result = (await response.json());
150
- return result.newWalletToken;
118
+ const { newWalletToken } = await prividiumApiCall(z.object({ newWalletToken: z.string(), message: z.string() }), `${config.prividiumApiBaseUrl}/api/wallet/invalidate`, 'POST');
119
+ return newWalletToken;
151
120
  },
152
121
  async authorizeTransaction(params) {
153
- const headers = getAuthHeaders();
154
- if (!headers) {
155
- throw new Error('Authentication required. Please call authorize() first.');
156
- }
157
- const response = await fetch(`${config.prividiumApiBaseUrl}/api/wallet/transaction-authorization`, {
158
- method: 'POST',
159
- headers: {
160
- 'Content-Type': 'application/json',
161
- ...headers
162
- },
163
- body: JSON.stringify({
164
- ...params,
165
- // Always pass calldata and value, even if undefined
166
- calldata: params?.calldata ?? '0x',
167
- value: params.value?.toString() ?? '0'
168
- })
169
- });
170
- if (response.status === 403) {
171
- tokenManager.clearToken();
172
- config.onAuthExpiry?.();
173
- throw new Error('Authentication required. Please call authorize() first.');
174
- }
175
- if (!response.ok) {
176
- throw new Error(`Failed to authorize transaction: ${response.status} ${response.statusText}`);
177
- }
178
- const result = (await response.json());
179
- return result;
122
+ return prividiumApiCall(authorizeTransactionResponseSchema, `${config.prividiumApiBaseUrl}/api/wallet/transaction-authorization`, 'POST', JSON.stringify({
123
+ ...params,
124
+ // Always pass calldata and value, even if undefined
125
+ calldata: params?.calldata ?? '0x',
126
+ value: params.value?.toString() ?? '0'
127
+ }));
180
128
  },
181
129
  async addNetworkToWallet(params) {
182
130
  if (typeof window === 'undefined' || !window.ethereum) {
@@ -5,14 +5,17 @@ export declare class LocalStorage implements Storage {
5
5
  removeItem(key: string): void;
6
6
  }
7
7
  export declare class TokenManager {
8
- private storage;
8
+ storage: Storage;
9
9
  private chainId;
10
+ private prividiumApiUrl;
10
11
  private tokenCache;
11
- constructor(storage: Storage, chainId: number);
12
+ private onAuthExpiry;
13
+ constructor(storage: Storage, chainId: number, prividiumApiUrl: string, onAuthExpiry?: () => void);
12
14
  private get tokenKey();
13
15
  private get stateKey();
14
16
  getToken(): TokenData | null;
15
- setToken(rawToken: string): TokenData;
17
+ private getTokenExpiration;
18
+ setToken(rawToken: string): Promise<TokenData>;
16
19
  clearToken(): void;
17
20
  isAuthorized(): boolean;
18
21
  setState(state: string): void;
@@ -1,4 +1,6 @@
1
- import { STORAGE_KEYS } from './types.js';
1
+ import { STORAGE_KEYS, tokenDataSchema } from './types.js';
2
+ import { z } from 'zod';
3
+ import { addYears } from 'date-fns/addYears';
2
4
  export class LocalStorage {
3
5
  getItem(key) {
4
6
  if (typeof localStorage === 'undefined') {
@@ -20,10 +22,14 @@ export class LocalStorage {
20
22
  export class TokenManager {
21
23
  storage;
22
24
  chainId;
25
+ prividiumApiUrl;
23
26
  tokenCache = null;
24
- constructor(storage, chainId) {
27
+ onAuthExpiry;
28
+ constructor(storage, chainId, prividiumApiUrl, onAuthExpiry) {
25
29
  this.storage = storage;
26
30
  this.chainId = chainId;
31
+ this.prividiumApiUrl = prividiumApiUrl;
32
+ this.onAuthExpiry = onAuthExpiry ?? function () { };
27
33
  }
28
34
  get tokenKey() {
29
35
  return `${STORAGE_KEYS.TOKEN_PREFIX}${this.chainId}`;
@@ -35,20 +41,63 @@ export class TokenManager {
35
41
  if (this.tokenCache) {
36
42
  return this.tokenCache;
37
43
  }
38
- const rawToken = this.storage.getItem(this.tokenKey);
39
- if (!rawToken) {
44
+ const tokenDataStr = this.storage.getItem(this.tokenKey);
45
+ if (!tokenDataStr) {
40
46
  return null;
41
47
  }
42
- const tokenData = {
43
- rawToken
44
- };
45
- this.tokenCache = tokenData;
46
- return tokenData;
48
+ try {
49
+ const tokenData = tokenDataSchema.safeParse(JSON.parse(tokenDataStr));
50
+ if (!tokenData.success) {
51
+ return null;
52
+ }
53
+ if (new Date() > tokenData.data.expiresAt) {
54
+ this.clearToken();
55
+ this.onAuthExpiry();
56
+ return null;
57
+ }
58
+ this.tokenCache = tokenData.data;
59
+ return tokenData.data;
60
+ }
61
+ catch (e) {
62
+ if (e instanceof SyntaxError) {
63
+ return null;
64
+ }
65
+ throw e;
66
+ }
47
67
  }
48
- setToken(rawToken) {
68
+ async getTokenExpiration(token) {
69
+ const currentSessionRes = await fetch(new URL('/api/auth/current-session', this.prividiumApiUrl), {
70
+ headers: {
71
+ authorization: `Bearer ${token}`
72
+ }
73
+ });
74
+ if (!currentSessionRes.ok) {
75
+ throw new Error('Error accessing prividium api');
76
+ }
77
+ if (currentSessionRes.status === 404) {
78
+ // NOTE: 404 indicates /api/auth/current-session is unavailable (older API),
79
+ // so the token expiration cannot be retrieved. The SDK must defer expiry
80
+ // handling until a 403 response is received; a far-future date prevents
81
+ // premature onExpiry callbacks before validation fails.
82
+ return addYears(new Date(), 100);
83
+ }
84
+ if (!currentSessionRes.ok || currentSessionRes.status !== 200) {
85
+ throw new Error('Error accessing prividium api');
86
+ }
87
+ const schema = z.object({
88
+ expiresAt: z.iso.datetime()
89
+ });
90
+ const parsed = schema.safeParse(await currentSessionRes.json());
91
+ if (!parsed.success) {
92
+ throw new Error('Invalid response from prividium api');
93
+ }
94
+ return new Date(parsed.data.expiresAt);
95
+ }
96
+ async setToken(rawToken) {
49
97
  try {
50
- const tokenData = { rawToken };
51
- this.storage.setItem(this.tokenKey, rawToken);
98
+ const expiresAt = await this.getTokenExpiration(rawToken);
99
+ const tokenData = { rawToken, expiresAt };
100
+ this.storage.setItem(this.tokenKey, JSON.stringify(tokenData));
52
101
  this.tokenCache = tokenData;
53
102
  return tokenData;
54
103
  }
@@ -62,8 +111,16 @@ export class TokenManager {
62
111
  this.storage.removeItem(this.tokenKey);
63
112
  }
64
113
  isAuthorized() {
65
- const token = this.getToken();
66
- return token !== null;
114
+ const tokenData = this.getToken();
115
+ if (tokenData === null) {
116
+ return false;
117
+ }
118
+ const isExpired = new Date() > tokenData.expiresAt;
119
+ if (isExpired) {
120
+ this.onAuthExpiry();
121
+ this.clearToken();
122
+ }
123
+ return !isExpired;
67
124
  }
68
125
  setState(state) {
69
126
  this.storage.setItem(this.stateKey, state);
@@ -0,0 +1,2 @@
1
+ export declare function minutesInTheFuture(minutes: number): Date;
2
+ export declare function mockSessionResponse(token: string, expiration: Date): void;
@@ -0,0 +1,11 @@
1
+ import { vi } from 'vitest';
2
+ import { addMinutes } from 'date-fns';
3
+ export function minutesInTheFuture(minutes) {
4
+ return addMinutes(new Date(), minutes);
5
+ }
6
+ export function mockSessionResponse(token, expiration) {
7
+ vi.spyOn(globalThis, 'fetch').mockResolvedValueOnce(new Response(JSON.stringify({
8
+ token: token,
9
+ expiresAt: expiration.toISOString()
10
+ })));
11
+ }
@@ -1,5 +1,6 @@
1
1
  import { type Address, type Chain, type Hex, type Transport } from 'viem';
2
2
  import { type OauthScope } from './popup-auth.js';
3
+ import { z } from 'zod';
3
4
  export interface Storage {
4
5
  getItem(key: string): string | null;
5
6
  setItem(key: string, value: string): void;
@@ -18,17 +19,21 @@ export interface PrividiumConfig {
18
19
  storage?: Storage;
19
20
  onAuthExpiry?: () => void;
20
21
  }
21
- export interface UserRole {
22
- roleName: string;
23
- }
24
- export interface UserProfile {
25
- userId: string;
26
- createdAt: Date;
27
- displayName: string | null;
28
- updatedAt: Date;
29
- roles: UserRole[];
30
- walletAddresses: string[];
31
- }
22
+ export declare const roleSchema: z.ZodObject<{
23
+ roleName: z.ZodString;
24
+ }, z.core.$strip>;
25
+ export type UserRole = z.infer<typeof roleSchema>;
26
+ export declare const profileSchema: z.ZodObject<{
27
+ id: z.ZodString;
28
+ createdAt: z.ZodCoercedDate<unknown>;
29
+ displayName: z.ZodNullable<z.ZodString>;
30
+ updatedAt: z.ZodCoercedDate<unknown>;
31
+ roles: z.ZodArray<z.ZodObject<{
32
+ roleName: z.ZodString;
33
+ }, z.core.$strip>>;
34
+ wallets: z.ZodArray<z.ZodUnknown>;
35
+ }, z.core.$strip>;
36
+ export type UserProfile = z.infer<typeof profileSchema>;
32
37
  export interface AddNetworkParams {
33
38
  chainName?: string;
34
39
  chainId: string;
@@ -58,10 +63,11 @@ export type AuthorizeTransactionParams = {
58
63
  calldata?: never;
59
64
  value: bigint;
60
65
  };
61
- export interface AuthorizeTransactionResponse {
62
- message: string;
63
- activeUntil: string;
64
- }
66
+ export declare const authorizeTransactionResponseSchema: z.ZodObject<{
67
+ message: z.ZodString;
68
+ activeUntil: z.ZodString;
69
+ }, z.core.$strip>;
70
+ export type AuthorizeTransactionResponse = z.infer<typeof authorizeTransactionResponseSchema>;
65
71
  export interface PrividiumChain {
66
72
  chain: Chain;
67
73
  transport: Transport;
@@ -76,8 +82,13 @@ export interface PrividiumChain {
76
82
  authorizeTransaction(params: AuthorizeTransactionParams): Promise<AuthorizeTransactionResponse>;
77
83
  addNetworkToWallet(params?: AddNetworkParams): Promise<void>;
78
84
  }
85
+ export declare const tokenDataSchema: z.ZodObject<{
86
+ rawToken: z.ZodString;
87
+ expiresAt: z.ZodCoercedDate<unknown>;
88
+ }, z.core.$strip>;
79
89
  export interface TokenData {
80
90
  rawToken: string;
91
+ expiresAt: Date;
81
92
  }
82
93
  export interface PopupOptions {
83
94
  popupSize?: {
package/dist/sdk/types.js CHANGED
@@ -1,3 +1,23 @@
1
+ import { z } from 'zod';
2
+ export const roleSchema = z.object({
3
+ roleName: z.string()
4
+ });
5
+ export const profileSchema = z.object({
6
+ id: z.string(),
7
+ createdAt: z.coerce.date(),
8
+ displayName: z.string().nullable(),
9
+ updatedAt: z.coerce.date(),
10
+ roles: roleSchema.array(),
11
+ wallets: z.unknown().array()
12
+ });
13
+ export const authorizeTransactionResponseSchema = z.object({
14
+ message: z.string(),
15
+ activeUntil: z.string()
16
+ });
17
+ export const tokenDataSchema = z.object({
18
+ rawToken: z.string(),
19
+ expiresAt: z.coerce.date()
20
+ });
1
21
  export const AUTH_ERRORS = {
2
22
  INVALID_STATE: 'Invalid state parameter',
3
23
  NO_RECEIVED_STATE: 'No state parameter',
@@ -1 +1 @@
1
- {"root":["../src/create-prividium-client.ts","../src/error-utils.ts","../src/index.ts","../src/popup-auth.ts","../src/prividium-chain.ts","../src/rpc-error-codes.ts","../src/storage.ts","../src/token-utils.ts","../src/types.ts"],"version":"5.8.3"}
1
+ {"root":["../src/create-prividium-client.ts","../src/error-utils.ts","../src/errors.ts","../src/index.ts","../src/popup-auth.ts","../src/prividium-chain.ts","../src/rpc-error-codes.ts","../src/storage.ts","../src/test-utils.ts","../src/token-utils.ts","../src/types.ts"],"version":"5.8.3"}
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "prividium",
3
- "version": "0.1.9",
3
+ "version": "0.2.0",
4
4
  "bin": {
5
- "prividium": "./bin/cli.js"
5
+ "prividium": "bin/cli.js"
6
6
  },
7
7
  "exports": {
8
8
  ".": {
@@ -46,11 +46,14 @@
46
46
  },
47
47
  "devDependencies": {
48
48
  "@repo/eslint-config": "workspace:*",
49
+ "@semantic-release/exec": "^7.1.0",
49
50
  "@types/node": "^22.8.6",
50
51
  "@types/yargs": "^17.0.34",
51
52
  "@vitest/coverage-v8": "3.2.4",
53
+ "date-fns": "^4.1.0",
52
54
  "eslint": "^8",
53
55
  "jsdom": "^25.0.0",
56
+ "semantic-release": "^25.0.2",
54
57
  "tsx": "^4.20.6",
55
58
  "typescript": "^5.8.3",
56
59
  "vitest": "^3.2.4"