@spawn-llc/auth 0.1.0 → 0.2.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/CHANGELOG.md +58 -0
- package/LICENSE +21 -0
- package/dist/{chunk-SISIAS4C.js → chunk-ZIYU4K3F.js} +17 -4
- package/dist/config.d.ts +16 -1
- package/dist/config.js +1 -1
- package/dist/index.js +1 -1
- package/dist/nextjs/index.d.ts +27 -5
- package/dist/nextjs/index.js +33 -2
- package/package.json +17 -14
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.2.1
|
|
4
|
+
|
|
5
|
+
**Fix: the session readers now force dynamic rendering before any early return.**
|
|
6
|
+
|
|
7
|
+
`currentUser()`, `session()` and `rejectedEmail()` returned `null` when WorkOS was unconfigured
|
|
8
|
+
*before* touching `headers()`. Every build runs without `WORKOS_*` env, so that short-circuit told
|
|
9
|
+
Next the page was static — baking a signed-out verdict into HTML, and failing the build outright on
|
|
10
|
+
any page that queries a database while prerendering. Found while migrating `admin`, whose own
|
|
11
|
+
pre-migration code touched `headers()` first for exactly this reason.
|
|
12
|
+
|
|
13
|
+
## 0.2.0
|
|
14
|
+
|
|
15
|
+
**Security — read this before upgrading an internal tool.**
|
|
16
|
+
|
|
17
|
+
- **The email-domain gate now runs in the headless flows, not only on session read.** Previously
|
|
18
|
+
`signUp` would create a real user in the shared "Spawn" WorkOS directory (and email them) for any
|
|
19
|
+
domain, and `signInWithPassword` would seal a session cookie before anything checked policy. Both
|
|
20
|
+
now deny up front, so no credential is minted and no directory write happens. `verifyEmail`
|
|
21
|
+
re-checks after redeeming a token, and `requestPasswordReset` sends no mail off-domain while still
|
|
22
|
+
reporting `ok` (no account enumeration).
|
|
23
|
+
- **Added `AUTH_REQUIRE_EMAIL_DOMAINS=true` + `assertEmailDomainGate()`.** Every Spawn app shares one
|
|
24
|
+
WorkOS user pool, so an internal console whose `ALLOWED_EMAIL_DOMAINS` went missing would silently
|
|
25
|
+
admit every customer of every customer-facing product. Apps that must never be world-readable now
|
|
26
|
+
declare it: `isAllowedEmail` fails **closed** when a gate is required but unset, and
|
|
27
|
+
`assertEmailDomainGate()` turns the misconfiguration into a failed boot instead of an open door.
|
|
28
|
+
- **`isAllowedEmail` rejects malformed input.** `"@allowed.com"` (empty local part) previously read
|
|
29
|
+
as allowed, because splitting on `@` yielded the allowed domain. A gate must never answer "allowed"
|
|
30
|
+
for something that is not an address.
|
|
31
|
+
|
|
32
|
+
Non-breaking for customer products: with no `ALLOWED_EMAIL_DOMAINS` set, behaviour is unchanged.
|
|
33
|
+
|
|
34
|
+
**Also**
|
|
35
|
+
|
|
36
|
+
- First tests (27). They cover the pending-token thread, unverified sign-in routing, the guarantee
|
|
37
|
+
that no raw WorkOS string reaches a screen, and every domain-gate boundary.
|
|
38
|
+
- CI now runs on push and PR — previously nothing validated a commit until a release tag was cut.
|
|
39
|
+
- `publish.yml` runs the tests and installs with `--frozen-lockfile`.
|
|
40
|
+
- Working git hooks. The `prepare` script pointed at a `.githooks/` directory that did not exist, so
|
|
41
|
+
no hook had ever run.
|
|
42
|
+
- Documented why the OAuth callback gate is enforced on read rather than at the callback: the
|
|
43
|
+
address is only known after the code exchange, and authkit's `onSuccess` cannot veto a login.
|
|
44
|
+
|
|
45
|
+
## 0.1.1
|
|
46
|
+
|
|
47
|
+
Republished from a clean tree so the artifact is reproducible from source. No code change from what
|
|
48
|
+
0.1.0 actually shipped.
|
|
49
|
+
|
|
50
|
+
- Untracked the committed `.tgz` and ignored build/debug artifacts.
|
|
51
|
+
|
|
52
|
+
## 0.1.0 — deprecated
|
|
53
|
+
|
|
54
|
+
Do not use. Published from a dirty working tree, so the tarball contains code that exists in no
|
|
55
|
+
commit and cannot be rebuilt from the repository. Superseded by 0.1.1.
|
|
56
|
+
|
|
57
|
+
Initial release: headless WorkOS over one shared "Spawn" project — `IdentityGateway`, the edge-safe
|
|
58
|
+
policy layer, the Next.js session seam, `authProxy`, `handleCallback`, and the headless flows.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Spawn LLC
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -16,12 +16,25 @@ function allowedEmailDomains() {
|
|
|
16
16
|
const raw = process.env.ALLOWED_EMAIL_DOMAINS;
|
|
17
17
|
return (raw ? raw.split(",") : []).map((d) => d.trim().toLowerCase().replace(/^@/, "")).filter(Boolean);
|
|
18
18
|
}
|
|
19
|
+
function requiresEmailDomainGate() {
|
|
20
|
+
return process.env.AUTH_REQUIRE_EMAIL_DOMAINS === "true";
|
|
21
|
+
}
|
|
22
|
+
function assertEmailDomainGate() {
|
|
23
|
+
if (requiresEmailDomainGate() && allowedEmailDomains().length === 0) {
|
|
24
|
+
throw new Error(
|
|
25
|
+
"AUTH_REQUIRE_EMAIL_DOMAINS=true but ALLOWED_EMAIL_DOMAINS is empty. This app must not run ungated: every Spawn app shares one WorkOS user pool, so an empty gate admits everyone."
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
19
29
|
function isAllowedEmail(email) {
|
|
20
30
|
const domains = allowedEmailDomains();
|
|
21
|
-
if (domains.length === 0) return
|
|
31
|
+
if (domains.length === 0) return !requiresEmailDomainGate();
|
|
22
32
|
if (!email) return false;
|
|
23
|
-
const
|
|
24
|
-
|
|
33
|
+
const parts = email.trim().toLowerCase().split("@");
|
|
34
|
+
if (parts.length !== 2) return false;
|
|
35
|
+
const [local, domain] = parts;
|
|
36
|
+
if (!local || !domain) return false;
|
|
37
|
+
return domains.includes(domain);
|
|
25
38
|
}
|
|
26
39
|
function workosClientId() {
|
|
27
40
|
const id = process.env.WORKOS_CLIENT_ID || process.env.NEXT_PUBLIC_WORKOS_CLIENT_ID;
|
|
@@ -29,4 +42,4 @@ function workosClientId() {
|
|
|
29
42
|
return id;
|
|
30
43
|
}
|
|
31
44
|
|
|
32
|
-
export { DEFAULT_PUBLIC_PATHS, allowedEmailDomains, isAllowedEmail, isPublicPath, isWorkosConfigured, publicPaths, workosClientId };
|
|
45
|
+
export { DEFAULT_PUBLIC_PATHS, allowedEmailDomains, assertEmailDomainGate, isAllowedEmail, isPublicPath, isWorkosConfigured, publicPaths, requiresEmailDomainGate, workosClientId };
|
package/dist/config.d.ts
CHANGED
|
@@ -20,8 +20,23 @@ declare function isWorkosConfigured(): boolean;
|
|
|
20
20
|
* `spawnpartners.com`. Returns true (allowed) when no gate is configured.
|
|
21
21
|
*/
|
|
22
22
|
declare function allowedEmailDomains(): string[];
|
|
23
|
+
/**
|
|
24
|
+
* Internal tools opt in with `AUTH_REQUIRE_EMAIL_DOMAINS=true`, declaring "I am gated".
|
|
25
|
+
*
|
|
26
|
+
* Why this exists: the whole suite shares ONE WorkOS client, so every app's user pool is every
|
|
27
|
+
* other app's user pool. An internal console that relies on the env default would, the moment
|
|
28
|
+
* `ALLOWED_EMAIL_DOMAINS` went missing or got typo'd, silently admit every customer of every
|
|
29
|
+
* customer-facing product — with no error and no visible symptom. Declaring the requirement turns
|
|
30
|
+
* that silent full-access failure into a loud boot failure.
|
|
31
|
+
*/
|
|
32
|
+
declare function requiresEmailDomainGate(): boolean;
|
|
33
|
+
/**
|
|
34
|
+
* Throw unless the domain gate is actually configured. Call at boot (module scope) in any app that
|
|
35
|
+
* must never be world-readable, so a misconfiguration fails the deploy instead of opening the door.
|
|
36
|
+
*/
|
|
37
|
+
declare function assertEmailDomainGate(): void;
|
|
23
38
|
declare function isAllowedEmail(email: string | null | undefined): boolean;
|
|
24
39
|
/** The WorkOS client id, from either the server or the public var. */
|
|
25
40
|
declare function workosClientId(): string;
|
|
26
41
|
|
|
27
|
-
export { DEFAULT_PUBLIC_PATHS, allowedEmailDomains, isAllowedEmail, isPublicPath, isWorkosConfigured, publicPaths, workosClientId };
|
|
42
|
+
export { DEFAULT_PUBLIC_PATHS, allowedEmailDomains, assertEmailDomainGate, isAllowedEmail, isPublicPath, isWorkosConfigured, publicPaths, requiresEmailDomainGate, workosClientId };
|
package/dist/config.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { DEFAULT_PUBLIC_PATHS, allowedEmailDomains, isAllowedEmail, isPublicPath, isWorkosConfigured, publicPaths, workosClientId } from './chunk-
|
|
1
|
+
export { DEFAULT_PUBLIC_PATHS, allowedEmailDomains, assertEmailDomainGate, isAllowedEmail, isPublicPath, isWorkosConfigured, publicPaths, requiresEmailDomainGate, workosClientId } from './chunk-ZIYU4K3F.js';
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { DEFAULT_PUBLIC_PATHS, allowedEmailDomains, isAllowedEmail, isPublicPath, isWorkosConfigured, publicPaths, workosClientId } from './chunk-
|
|
1
|
+
export { DEFAULT_PUBLIC_PATHS, allowedEmailDomains, isAllowedEmail, isPublicPath, isWorkosConfigured, publicPaths, workosClientId } from './chunk-ZIYU4K3F.js';
|
|
2
2
|
|
|
3
3
|
// src/identity/workos-gateway.ts
|
|
4
4
|
var ROLES = ["owner", "admin", "member"];
|
package/dist/nextjs/index.d.ts
CHANGED
|
@@ -1,11 +1,6 @@
|
|
|
1
1
|
import { U as User, S as Session } from '../domain-CmEhGW2N.js';
|
|
2
2
|
import * as next_server from 'next/server';
|
|
3
3
|
|
|
4
|
-
/**
|
|
5
|
-
* The Spawn session seam for Next apps. ONE choke point: app code only ever calls `currentUser()` /
|
|
6
|
-
* `requireUser()` / `session()` / `signOut()` — nothing outside this package knows it's WorkOS.
|
|
7
|
-
* Generalized from admin's `@admin/auth` + audit's `lib/session.ts`.
|
|
8
|
-
*/
|
|
9
4
|
/** The signed-in Spawn user, or null. Applies the optional email-domain gate. */
|
|
10
5
|
declare function currentUser(): Promise<User | null>;
|
|
11
6
|
/** The signed-in user; redirects to `/login` when absent. */
|
|
@@ -32,6 +27,18 @@ declare function userDisplayName(user: User): string;
|
|
|
32
27
|
*
|
|
33
28
|
* Pass `{ enforce: true }` only if you want middleware-level gating to WorkOS's hosted UI instead
|
|
34
29
|
* of your own screens (not the Spawn default).
|
|
30
|
+
*
|
|
31
|
+
* ## There is deliberately no "redirect to my own /login" middleware mode
|
|
32
|
+
*
|
|
33
|
+
* It looks like the obvious third option, and it is a trap. The default mode does NOT protect
|
|
34
|
+
* anything — it only refreshes. So an app that has routes reachable *only* because middleware
|
|
35
|
+
* gates them (typically API route handlers with no in-handler check) becomes wide open the moment
|
|
36
|
+
* it adopts this proxy. Middleware gating reads as protection while being the easiest thing to
|
|
37
|
+
* silently lose to a matcher edit.
|
|
38
|
+
*
|
|
39
|
+
* Gate in the route instead: `requireUser()` in authed layouts, and an explicit `requireUser()` at
|
|
40
|
+
* the top of every API handler that returns data. That survives matcher changes, is greppable, and
|
|
41
|
+
* fails closed. Before adopting this proxy, audit for handlers whose only protection is the matcher.
|
|
35
42
|
*/
|
|
36
43
|
declare function authProxy(options?: {
|
|
37
44
|
enforce?: boolean;
|
|
@@ -45,6 +52,21 @@ declare function authProxy(options?: {
|
|
|
45
52
|
*
|
|
46
53
|
* Exchanges the WorkOS code, seals the Spawn session cookie, and forwards on. This is used for the
|
|
47
54
|
* social-login round-trip (`startOAuth`) — headless email/password uses `saveSession` directly.
|
|
55
|
+
*
|
|
56
|
+
* ## Where the email-domain gate runs for social login
|
|
57
|
+
*
|
|
58
|
+
* NOT here, deliberately. The password flows in `flows.ts` gate before calling WorkOS, so no
|
|
59
|
+
* credential is ever minted for an off-domain address. Social login cannot do that: the address is
|
|
60
|
+
* only known after the code exchange, and authkit's `onSuccess` hook is typed
|
|
61
|
+
* `(data) => void | Promise<void>` — it can observe the result but cannot redirect or veto it.
|
|
62
|
+
*
|
|
63
|
+
* So for OAuth the gate is enforced on READ, by `currentUser()`/`session()`/`requireUser()` in
|
|
64
|
+
* `session.ts`, which return null for a disallowed domain. An off-domain social login therefore
|
|
65
|
+
* receives a session cookie that grants nothing — every read treats it as signed out and the app
|
|
66
|
+
* bounces to `/login`.
|
|
67
|
+
*
|
|
68
|
+
* The consequence worth knowing: for OAuth, those readers are load-bearing. An app that reaches
|
|
69
|
+
* past them to `withAuth()` directly would see a user the gate intends to reject. Use the seam.
|
|
48
70
|
*/
|
|
49
71
|
declare function handleCallback(options?: {
|
|
50
72
|
returnPathname?: string;
|
package/dist/nextjs/index.js
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
|
-
import { isWorkosConfigured, isAllowedEmail, publicPaths, workosClientId } from '../chunk-
|
|
1
|
+
import { isWorkosConfigured, isAllowedEmail, publicPaths, workosClientId } from '../chunk-ZIYU4K3F.js';
|
|
2
|
+
import { headers, cookies } from 'next/headers';
|
|
2
3
|
import { redirect } from 'next/navigation';
|
|
3
4
|
import { authkitMiddleware, handleAuth, getWorkOS, saveSession } from '@workos-inc/authkit-nextjs';
|
|
4
|
-
import { headers, cookies } from 'next/headers';
|
|
5
5
|
|
|
6
|
+
async function markDynamic() {
|
|
7
|
+
await headers();
|
|
8
|
+
}
|
|
6
9
|
async function currentUser() {
|
|
10
|
+
await markDynamic();
|
|
7
11
|
if (!isWorkosConfigured()) return null;
|
|
8
12
|
const { withAuth } = await import('@workos-inc/authkit-nextjs');
|
|
9
13
|
const { user } = await withAuth();
|
|
@@ -16,6 +20,7 @@ async function requireUser(redirectTo = "/login") {
|
|
|
16
20
|
return user;
|
|
17
21
|
}
|
|
18
22
|
async function session() {
|
|
23
|
+
await markDynamic();
|
|
19
24
|
if (!isWorkosConfigured()) return null;
|
|
20
25
|
const { withAuth } = await import('@workos-inc/authkit-nextjs');
|
|
21
26
|
const info = await withAuth();
|
|
@@ -27,6 +32,7 @@ async function session() {
|
|
|
27
32
|
};
|
|
28
33
|
}
|
|
29
34
|
async function rejectedEmail() {
|
|
35
|
+
await markDynamic();
|
|
30
36
|
if (!isWorkosConfigured()) return null;
|
|
31
37
|
const { withAuth } = await import('@workos-inc/authkit-nextjs');
|
|
32
38
|
const { user } = await withAuth();
|
|
@@ -94,9 +100,20 @@ function errorCode(err) {
|
|
|
94
100
|
const code = err.code;
|
|
95
101
|
return typeof code === "string" ? code : void 0;
|
|
96
102
|
}
|
|
103
|
+
function requirementCodes(err) {
|
|
104
|
+
const msg = err?.message;
|
|
105
|
+
if (typeof msg !== "string") return [];
|
|
106
|
+
return msg.split("\n").slice(1).map((line) => line.trim()).filter((line) => /^[a-z0-9_]+$/.test(line));
|
|
107
|
+
}
|
|
97
108
|
function message(err, fallback) {
|
|
98
109
|
const code = errorCode(err);
|
|
99
110
|
if (code && FRIENDLY[code]) return FRIENDLY[code];
|
|
111
|
+
const requirements = requirementCodes(err);
|
|
112
|
+
for (const requirement of requirements) {
|
|
113
|
+
if (FRIENDLY[requirement]) return FRIENDLY[requirement];
|
|
114
|
+
}
|
|
115
|
+
if (requirements.some((r) => r.includes("password"))) return FRIENDLY.password_strength_error;
|
|
116
|
+
if (requirements.some((r) => r.includes("email"))) return "That email doesn't look right.";
|
|
100
117
|
const errors = err?.errors;
|
|
101
118
|
if (Array.isArray(errors)) {
|
|
102
119
|
const field = errors.find(
|
|
@@ -113,6 +130,10 @@ function pendingVerificationToken(err) {
|
|
|
113
130
|
if (e.code !== "email_verification_required") return void 0;
|
|
114
131
|
return typeof e.pendingAuthenticationToken === "string" ? e.pendingAuthenticationToken : void 0;
|
|
115
132
|
}
|
|
133
|
+
function domainRejection(email) {
|
|
134
|
+
if (isAllowedEmail(email)) return null;
|
|
135
|
+
return { status: "error", error: "That email address isn't allowed to sign in here." };
|
|
136
|
+
}
|
|
116
137
|
var PENDING_COOKIE = "spawn_pending_verification";
|
|
117
138
|
var PENDING_TTL_SECONDS = 15 * 60;
|
|
118
139
|
async function rememberPendingToken(token) {
|
|
@@ -131,6 +152,8 @@ async function clearPendingToken() {
|
|
|
131
152
|
(await cookies()).delete(PENDING_COOKIE);
|
|
132
153
|
}
|
|
133
154
|
async function signInWithPassword(input) {
|
|
155
|
+
const rejected = domainRejection(input.email);
|
|
156
|
+
if (rejected) return rejected;
|
|
134
157
|
try {
|
|
135
158
|
const res = await getWorkOS().userManagement.authenticateWithPassword({
|
|
136
159
|
clientId: workosClientId(),
|
|
@@ -149,6 +172,8 @@ async function signInWithPassword(input) {
|
|
|
149
172
|
}
|
|
150
173
|
}
|
|
151
174
|
async function signUp(input) {
|
|
175
|
+
const rejected = domainRejection(input.email);
|
|
176
|
+
if (rejected) return rejected;
|
|
152
177
|
const workos = getWorkOS();
|
|
153
178
|
try {
|
|
154
179
|
const user = await workos.userManagement.createUser({
|
|
@@ -193,6 +218,11 @@ async function verifyEmail(input) {
|
|
|
193
218
|
code: input.code,
|
|
194
219
|
pendingAuthenticationToken: pendingToken
|
|
195
220
|
});
|
|
221
|
+
const rejected = domainRejection(res.user.email);
|
|
222
|
+
if (rejected) {
|
|
223
|
+
await clearPendingToken();
|
|
224
|
+
return rejected;
|
|
225
|
+
}
|
|
196
226
|
await saveSession(res, await origin());
|
|
197
227
|
await clearPendingToken();
|
|
198
228
|
return { status: "ok" };
|
|
@@ -209,6 +239,7 @@ async function verifyEmail(input) {
|
|
|
209
239
|
}
|
|
210
240
|
}
|
|
211
241
|
async function requestPasswordReset(input) {
|
|
242
|
+
if (!isAllowedEmail(input.email)) return { status: "ok" };
|
|
212
243
|
try {
|
|
213
244
|
await getWorkOS().userManagement.createPasswordReset({ email: input.email });
|
|
214
245
|
} catch {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@spawn-llc/auth",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Spawn shared identity — headless auth for the whole Spawn suite. One WorkOS project (named \"Spawn\") backs every app; this is the only place that touches it. WorkOS is an invisible engine.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -29,18 +29,10 @@
|
|
|
29
29
|
"files": [
|
|
30
30
|
"dist",
|
|
31
31
|
"README.md",
|
|
32
|
-
"IDENTITY.md"
|
|
32
|
+
"IDENTITY.md",
|
|
33
|
+
"CHANGELOG.md",
|
|
34
|
+
"LICENSE"
|
|
33
35
|
],
|
|
34
|
-
"scripts": {
|
|
35
|
-
"prepare": "git config core.hooksPath .githooks || true",
|
|
36
|
-
"build": "rm -rf dist && tsup",
|
|
37
|
-
"typecheck": "tsc --noEmit",
|
|
38
|
-
"prepublishOnly": "pnpm run typecheck && pnpm run build",
|
|
39
|
-
"release": "pnpm run release:patch",
|
|
40
|
-
"release:patch": "pnpm version patch -m \"v%s\" && git push --follow-tags",
|
|
41
|
-
"release:minor": "pnpm version minor -m \"v%s\" && git push --follow-tags",
|
|
42
|
-
"release:major": "pnpm version major -m \"v%s\" && git push --follow-tags"
|
|
43
|
-
},
|
|
44
36
|
"dependencies": {
|
|
45
37
|
"@workos-inc/authkit-nextjs": "^4.2.0",
|
|
46
38
|
"@workos-inc/node": "^10.8.0"
|
|
@@ -57,6 +49,17 @@
|
|
|
57
49
|
"@types/react": "^19.0.0",
|
|
58
50
|
"next": "^16.2.10",
|
|
59
51
|
"tsup": "^8.5.1",
|
|
60
|
-
"typescript": "^5.7.0"
|
|
52
|
+
"typescript": "^5.7.0",
|
|
53
|
+
"vitest": "^4.1.10"
|
|
54
|
+
},
|
|
55
|
+
"scripts": {
|
|
56
|
+
"build": "rm -rf dist && tsup",
|
|
57
|
+
"typecheck": "tsc --noEmit",
|
|
58
|
+
"test": "vitest run",
|
|
59
|
+
"test:watch": "vitest",
|
|
60
|
+
"release": "pnpm run release:patch",
|
|
61
|
+
"release:patch": "pnpm version patch -m \"v%s\" && git push --follow-tags",
|
|
62
|
+
"release:minor": "pnpm version minor -m \"v%s\" && git push --follow-tags",
|
|
63
|
+
"release:major": "pnpm version major -m \"v%s\" && git push --follow-tags"
|
|
61
64
|
}
|
|
62
|
-
}
|
|
65
|
+
}
|