@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.
Files changed (35) hide show
  1. package/README.md +12 -0
  2. package/bin/cli.mjs +57 -1
  3. package/package.json +1 -1
  4. package/scripts/verify-auth.sh +30 -0
  5. package/scripts/verify-studio.sh +16 -0
  6. package/src/agency.mjs +45 -0
  7. package/src/auth-clerk.mjs +146 -0
  8. package/src/auth-google.mjs +137 -0
  9. package/src/brand-pack-cmd.mjs +76 -0
  10. package/src/brand-packs.mjs +101 -0
  11. package/src/credential-prompts-npm.mjs +154 -0
  12. package/src/credential-prompts.mjs +5 -0
  13. package/src/mvp-flow.mjs +70 -0
  14. package/src/private.mjs +39 -1
  15. package/src/template-resolver.mjs +38 -0
  16. package/src/white-label.mjs +17 -2
  17. package/src/workspaces-cmd.mjs +50 -0
  18. package/src/workspaces.mjs +74 -0
  19. package/templates/.claude/commands/add-auth.md +87 -0
  20. package/templates/.claude/commands/handoff-package.md +37 -0
  21. package/templates/.claude/commands/publish.md +77 -0
  22. package/templates/auth-clerk/env.example.additions +10 -0
  23. package/templates/auth-clerk/layout.tsx +25 -0
  24. package/templates/auth-clerk/middleware.ts +20 -0
  25. package/templates/auth-clerk/package.json.additions +5 -0
  26. package/templates/auth-clerk/sign-in-page.tsx +9 -0
  27. package/templates/auth-clerk/sign-up-page.tsx +9 -0
  28. package/templates/auth-google/auth.ts +12 -0
  29. package/templates/auth-google/env.example.additions +9 -0
  30. package/templates/auth-google/package.json.additions +5 -0
  31. package/templates/auth-google/route.ts +2 -0
  32. package/templates/auth-google/sign-in-page.tsx +32 -0
  33. package/templates/auth-google/sign-up-page.tsx +32 -0
  34. package/templates/private/middleware.ts +8 -1
  35. 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,5 @@
1
+ {
2
+ "dependencies": {
3
+ "@clerk/nextjs": "^6.0.0"
4
+ }
5
+ }
@@ -0,0 +1,9 @@
1
+ import { SignIn } from '@clerk/nextjs';
2
+
3
+ export default function SignInPage() {
4
+ return (
5
+ <main className="flex min-h-screen items-center justify-center p-6">
6
+ <SignIn />
7
+ </main>
8
+ );
9
+ }
@@ -0,0 +1,9 @@
1
+ import { SignUp } from '@clerk/nextjs';
2
+
3
+ export default function SignUpPage() {
4
+ return (
5
+ <main className="flex min-h-screen items-center justify-center p-6">
6
+ <SignUp />
7
+ </main>
8
+ );
9
+ }
@@ -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,5 @@
1
+ {
2
+ "dependencies": {
3
+ "next-auth": "^5.0.0-beta.20"
4
+ }
5
+ }
@@ -0,0 +1,2 @@
1
+ import { handlers } from '@/auth';
2
+ export const { GET, POST } = handlers;
@@ -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 = ['better-auth.session_token', 'authjs.session-token', 'session_token'];
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');
@@ -0,0 +1,5 @@
1
+ {
2
+ "dependencies": {
3
+ "@vercel/blob": "^0.27.0"
4
+ }
5
+ }