@oaklandzoo/ostup 0.11.0 → 0.13.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 +12 -0
- package/bin/cli.mjs +57 -1
- package/package.json +1 -1
- package/scripts/verify-auth.sh +30 -0
- package/scripts/verify-studio.sh +16 -0
- package/src/agency.mjs +45 -0
- package/src/auth-clerk.mjs +146 -0
- package/src/auth-google.mjs +137 -0
- package/src/brand-pack-cmd.mjs +76 -0
- package/src/brand-packs.mjs +101 -0
- package/src/credential-prompts-npm.mjs +154 -0
- package/src/credential-prompts.mjs +5 -0
- package/src/mvp-flow.mjs +70 -0
- package/src/private.mjs +39 -1
- package/src/template-resolver.mjs +38 -0
- package/src/white-label.mjs +17 -2
- package/src/workspaces-cmd.mjs +50 -0
- package/src/workspaces.mjs +74 -0
- package/templates/.claude/commands/add-auth.md +87 -0
- package/templates/.claude/commands/handoff-package.md +37 -0
- package/templates/.claude/commands/publish.md +77 -0
- package/templates/auth-clerk/env.example.additions +10 -0
- package/templates/auth-clerk/layout.tsx +25 -0
- package/templates/auth-clerk/middleware.ts +20 -0
- package/templates/auth-clerk/package.json.additions +5 -0
- package/templates/auth-clerk/sign-in-page.tsx +9 -0
- package/templates/auth-clerk/sign-up-page.tsx +9 -0
- package/templates/auth-google/auth.ts +12 -0
- package/templates/auth-google/env.example.additions +9 -0
- package/templates/auth-google/package.json.additions +5 -0
- package/templates/auth-google/route.ts +2 -0
- package/templates/auth-google/sign-in-page.tsx +32 -0
- package/templates/auth-google/sign-up-page.tsx +32 -0
- package/templates/private/middleware.ts +8 -1
- package/templates/private/package.json.additions +5 -0
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Publish a new version of this package to npm. Bumps version, runs tests, publishes, tags, pushes.
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Publish to npm
|
|
6
|
+
|
|
7
|
+
Use this when the operator says "publish a patch / minor / major" or "ship a new version".
|
|
8
|
+
|
|
9
|
+
## Preflight
|
|
10
|
+
|
|
11
|
+
- This command only makes sense if `package.json` has a `version` field and the project is an npm package (not a private app).
|
|
12
|
+
- Confirm `~/.npmrc` has an authToken or `npm whoami` succeeds. If neither, stop and tell the operator to run `npm login` or paste a Bypass-2FA token from `https://www.npmjs.com/settings/<username>/tokens/new`.
|
|
13
|
+
- Working tree must be clean (`git status --porcelain` returns empty). If dirty, refuse and ask the operator to commit or stash first.
|
|
14
|
+
|
|
15
|
+
## Steps
|
|
16
|
+
|
|
17
|
+
### 1. Pick the version bump
|
|
18
|
+
|
|
19
|
+
If the operator named a level (patch / minor / major), use it. Otherwise ask:
|
|
20
|
+
|
|
21
|
+
| Level | When |
|
|
22
|
+
|---|---|
|
|
23
|
+
| patch | Bug fixes, no API change |
|
|
24
|
+
| minor | New backwards-compatible features |
|
|
25
|
+
| major | Breaking changes |
|
|
26
|
+
|
|
27
|
+
### 2. Bump the version
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
npm version <patch|minor|major> --no-git-tag-version
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
This updates `package.json` only. We tag manually at the end so the commit message + tag stay in sync.
|
|
34
|
+
|
|
35
|
+
### 3. Run tests
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
npm test
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
If tests fail, stop. Revert the version bump (`git checkout package.json package-lock.json`) and report to the operator.
|
|
42
|
+
|
|
43
|
+
### 4. Publish
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
npm publish
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
If npm asks for an OTP and the bypass token is missing, fail clearly and tell the operator to set up a token first.
|
|
50
|
+
|
|
51
|
+
### 5. Commit + tag + push
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
VERSION=$(node -p "require('./package.json').version")
|
|
55
|
+
git add package.json package-lock.json
|
|
56
|
+
git commit -m "chore: release v$VERSION"
|
|
57
|
+
git tag "v$VERSION"
|
|
58
|
+
git push
|
|
59
|
+
git push origin "v$VERSION"
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### 6. Verify
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
npm view <package-name> version
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Expect the new version. If npm's CDN hasn't propagated yet, retry after 30s.
|
|
69
|
+
|
|
70
|
+
## Report
|
|
71
|
+
|
|
72
|
+
```
|
|
73
|
+
Published <package-name>@<version>
|
|
74
|
+
- npm: https://www.npmjs.com/package/<package-name>
|
|
75
|
+
- Tag: v<version> pushed to origin
|
|
76
|
+
- Verify: npm view <package-name> version
|
|
77
|
+
```
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# --- Clerk auth additions ---
|
|
2
|
+
# Get keys at https://dashboard.clerk.com/apps (create an app, copy from API Keys)
|
|
3
|
+
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=
|
|
4
|
+
CLERK_SECRET_KEY=
|
|
5
|
+
|
|
6
|
+
# Optional: where Clerk redirects after sign-in / sign-up
|
|
7
|
+
NEXT_PUBLIC_CLERK_SIGN_IN_URL=/sign-in
|
|
8
|
+
NEXT_PUBLIC_CLERK_SIGN_UP_URL=/sign-up
|
|
9
|
+
NEXT_PUBLIC_CLERK_AFTER_SIGN_IN_URL=/dashboard
|
|
10
|
+
NEXT_PUBLIC_CLERK_AFTER_SIGN_UP_URL=/dashboard
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { Metadata } from 'next';
|
|
2
|
+
import { ClerkProvider } from '@clerk/nextjs';
|
|
3
|
+
import './globals.css';
|
|
4
|
+
|
|
5
|
+
export const metadata: Metadata = {
|
|
6
|
+
title: '{{DISPLAY_NAME}}',
|
|
7
|
+
description: '{{PROJECT_PURPOSE_ONE_SENTENCE}}',
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
// Build-safe: wrap with ClerkProvider only when the publishable key is set.
|
|
11
|
+
// This lets `next build` succeed in CI / fresh scaffolds before the operator
|
|
12
|
+
// has configured the env. In production the key is present and Clerk handles auth.
|
|
13
|
+
const Wrapper = process.env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY
|
|
14
|
+
? ClerkProvider
|
|
15
|
+
: ({ children }: { children: React.ReactNode }) => <>{children}</>;
|
|
16
|
+
|
|
17
|
+
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
|
18
|
+
return (
|
|
19
|
+
<Wrapper>
|
|
20
|
+
<html lang="en">
|
|
21
|
+
<body className="bg-white text-slate-900 antialiased">{children}</body>
|
|
22
|
+
</html>
|
|
23
|
+
</Wrapper>
|
|
24
|
+
);
|
|
25
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { clerkMiddleware, createRouteMatcher } from '@clerk/nextjs/server';
|
|
2
|
+
|
|
3
|
+
const isProtectedRoute = createRouteMatcher([
|
|
4
|
+
'/dashboard(.*)',
|
|
5
|
+
'/api/account(.*)',
|
|
6
|
+
]);
|
|
7
|
+
|
|
8
|
+
export default clerkMiddleware(async (auth, req) => {
|
|
9
|
+
if (isProtectedRoute(req)) {
|
|
10
|
+
await auth.protect();
|
|
11
|
+
}
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
export const config = {
|
|
15
|
+
matcher: [
|
|
16
|
+
// Run middleware on all non-static routes.
|
|
17
|
+
'/((?!_next/static|_next/image|favicon\\.ico).*)',
|
|
18
|
+
'/(api|trpc)(.*)',
|
|
19
|
+
],
|
|
20
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import NextAuth from 'next-auth';
|
|
2
|
+
import Google from 'next-auth/providers/google';
|
|
3
|
+
|
|
4
|
+
export const { handlers, signIn, signOut, auth } = NextAuth({
|
|
5
|
+
providers: [
|
|
6
|
+
Google({
|
|
7
|
+
clientId: process.env.GOOGLE_CLIENT_ID || '',
|
|
8
|
+
clientSecret: process.env.GOOGLE_CLIENT_SECRET || '',
|
|
9
|
+
}),
|
|
10
|
+
],
|
|
11
|
+
trustHost: true,
|
|
12
|
+
});
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# --- Google OAuth (NextAuth v5) additions ---
|
|
2
|
+
# Create an OAuth 2.0 Client ID at:
|
|
3
|
+
# https://console.cloud.google.com/apis/credentials/oauthclient
|
|
4
|
+
# Authorized redirect URI: <your-prod-url>/api/auth/callback/google
|
|
5
|
+
GOOGLE_CLIENT_ID=
|
|
6
|
+
GOOGLE_CLIENT_SECRET=
|
|
7
|
+
|
|
8
|
+
# NextAuth secret. Generate one with: openssl rand -base64 32
|
|
9
|
+
AUTH_SECRET=
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { signIn } from '@/auth';
|
|
2
|
+
|
|
3
|
+
export default function SignInPage() {
|
|
4
|
+
return (
|
|
5
|
+
<main className="flex min-h-screen items-center justify-center p-6">
|
|
6
|
+
<div className="w-full max-w-sm rounded-lg border border-slate-200 bg-white p-8">
|
|
7
|
+
<h1 className="text-2xl font-semibold tracking-tight">Sign in</h1>
|
|
8
|
+
<p className="mt-2 text-sm text-slate-600">Continue to {`{{DISPLAY_NAME}}`}.</p>
|
|
9
|
+
<form
|
|
10
|
+
action={async () => {
|
|
11
|
+
'use server';
|
|
12
|
+
await signIn('google', { redirectTo: '/dashboard' });
|
|
13
|
+
}}
|
|
14
|
+
className="mt-8"
|
|
15
|
+
>
|
|
16
|
+
<button
|
|
17
|
+
type="submit"
|
|
18
|
+
className="flex w-full items-center justify-center gap-2 rounded-md border border-slate-300 bg-white px-4 py-2 font-medium text-slate-900 hover:bg-slate-50"
|
|
19
|
+
>
|
|
20
|
+
<svg className="h-5 w-5" viewBox="0 0 24 24" aria-hidden="true">
|
|
21
|
+
<path d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09Z" fill="#4285F4"/>
|
|
22
|
+
<path d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.99.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84A11 11 0 0 0 12 23Z" fill="#34A853"/>
|
|
23
|
+
<path d="M5.84 14.1A6.6 6.6 0 0 1 5.5 12c0-.73.13-1.43.34-2.1V7.07H2.18A11 11 0 0 0 1 12c0 1.77.42 3.45 1.18 4.93l3.66-2.83Z" fill="#FBBC05"/>
|
|
24
|
+
<path d="M12 5.38c1.62 0 3.06.56 4.21 1.65l3.15-3.15C17.45 2.09 14.97 1 12 1A11 11 0 0 0 2.18 7.07l3.66 2.83C6.71 7.3 9.14 5.38 12 5.38Z" fill="#EA4335"/>
|
|
25
|
+
</svg>
|
|
26
|
+
Sign in with Google
|
|
27
|
+
</button>
|
|
28
|
+
</form>
|
|
29
|
+
</div>
|
|
30
|
+
</main>
|
|
31
|
+
);
|
|
32
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { signIn } from '@/auth';
|
|
2
|
+
|
|
3
|
+
export default function SignUpPage() {
|
|
4
|
+
return (
|
|
5
|
+
<main className="flex min-h-screen items-center justify-center p-6">
|
|
6
|
+
<div className="w-full max-w-sm rounded-lg border border-slate-200 bg-white p-8">
|
|
7
|
+
<h1 className="text-2xl font-semibold tracking-tight">Create account</h1>
|
|
8
|
+
<p className="mt-2 text-sm text-slate-600">Sign up with Google to get started.</p>
|
|
9
|
+
<form
|
|
10
|
+
action={async () => {
|
|
11
|
+
'use server';
|
|
12
|
+
await signIn('google', { redirectTo: '/dashboard' });
|
|
13
|
+
}}
|
|
14
|
+
className="mt-8"
|
|
15
|
+
>
|
|
16
|
+
<button
|
|
17
|
+
type="submit"
|
|
18
|
+
className="flex w-full items-center justify-center gap-2 rounded-md border border-slate-300 bg-white px-4 py-2 font-medium text-slate-900 hover:bg-slate-50"
|
|
19
|
+
>
|
|
20
|
+
<svg className="h-5 w-5" viewBox="0 0 24 24" aria-hidden="true">
|
|
21
|
+
<path d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09Z" fill="#4285F4"/>
|
|
22
|
+
<path d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.99.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84A11 11 0 0 0 12 23Z" fill="#34A853"/>
|
|
23
|
+
<path d="M5.84 14.1A6.6 6.6 0 0 1 5.5 12c0-.73.13-1.43.34-2.1V7.07H2.18A11 11 0 0 0 1 12c0 1.77.42 3.45 1.18 4.93l3.66-2.83Z" fill="#FBBC05"/>
|
|
24
|
+
<path d="M12 5.38c1.62 0 3.06.56 4.21 1.65l3.15-3.15C17.45 2.09 14.97 1 12 1A11 11 0 0 0 2.18 7.07l3.66 2.83C6.71 7.3 9.14 5.38 12 5.38Z" fill="#EA4335"/>
|
|
25
|
+
</svg>
|
|
26
|
+
Continue with Google
|
|
27
|
+
</button>
|
|
28
|
+
</form>
|
|
29
|
+
</div>
|
|
30
|
+
</main>
|
|
31
|
+
);
|
|
32
|
+
}
|
|
@@ -3,7 +3,14 @@ import type { NextRequest } from 'next/server';
|
|
|
3
3
|
import { rateLimit } from '@/lib/rate-limit';
|
|
4
4
|
import { audit } from '@/lib/audit';
|
|
5
5
|
|
|
6
|
-
const SESSION_COOKIE_NAMES = [
|
|
6
|
+
const SESSION_COOKIE_NAMES = [
|
|
7
|
+
'__session', // Clerk
|
|
8
|
+
'__clerk_db_jwt', // Clerk db cookie
|
|
9
|
+
'authjs.session-token', // NextAuth v5
|
|
10
|
+
'__Secure-authjs.session-token', // NextAuth v5 secure
|
|
11
|
+
'better-auth.session_token', // Better Auth
|
|
12
|
+
'session_token', // Generic fallback
|
|
13
|
+
];
|
|
7
14
|
|
|
8
15
|
function getIp(req: NextRequest): string {
|
|
9
16
|
const xff = req.headers.get('x-forwarded-for');
|