@nextsparkjs/core 0.1.0-beta.196 → 0.1.0-beta.197
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 +2 -0
- package/dist/components/auth/forms/SignupForm.d.ts.map +1 -1
- package/dist/components/auth/forms/SignupForm.js +55 -45
- package/dist/components/dashboard/block-editor/config-panel.d.ts.map +1 -1
- package/dist/components/dashboard/block-editor/config-panel.js +3 -2
- package/dist/components/dashboard/block-editor/entity-fields-sidebar.d.ts.map +1 -1
- package/dist/components/dashboard/block-editor/entity-fields-sidebar.js +3 -2
- package/dist/components/users/tables/UsersTable.d.ts.map +1 -1
- package/dist/components/users/tables/UsersTable.js +3 -4
- package/dist/hooks/useAuthReadiness.d.ts +1 -0
- package/dist/hooks/useAuthReadiness.d.ts.map +1 -1
- package/dist/hooks/useAuthReadiness.js +3 -1
- package/dist/lib/actions/entity.actions.js +2 -2
- package/dist/lib/actions/team.actions.js +2 -2
- package/dist/lib/actions/user.actions.js +4 -4
- package/dist/lib/api/auth/dual-auth.d.ts.map +1 -1
- package/dist/lib/api/auth/dual-auth.js +3 -2
- package/dist/lib/api/auth.d.ts.map +1 -1
- package/dist/lib/api/auth.js +7 -0
- package/dist/lib/api/entity/generic-handler.d.ts.map +1 -1
- package/dist/lib/api/entity/generic-handler.js +66 -12
- package/dist/lib/api/entity-handler.js +2 -2
- package/dist/lib/api/helpers.d.ts.map +1 -1
- package/dist/lib/api/helpers.js +4 -2
- package/dist/lib/api/post-categories.d.ts +29 -0
- package/dist/lib/api/post-categories.d.ts.map +1 -0
- package/dist/lib/api/post-categories.js +33 -0
- package/dist/lib/auth/auth-methods.d.ts +5 -2
- package/dist/lib/auth/auth-methods.d.ts.map +1 -1
- package/dist/lib/auth/auth-methods.js +4 -2
- package/dist/lib/auth/authorization-session.d.ts +14 -0
- package/dist/lib/auth/authorization-session.d.ts.map +1 -0
- package/dist/lib/auth/authorization-session.js +10 -0
- package/dist/lib/auth/request-session.d.ts +5 -0
- package/dist/lib/auth/request-session.d.ts.map +1 -1
- package/dist/lib/auth/request-session.js +3 -0
- package/dist/lib/auth/runtime-readiness.d.ts +7 -4
- package/dist/lib/auth/runtime-readiness.d.ts.map +1 -1
- package/dist/lib/auth/runtime-readiness.js +7 -6
- package/dist/lib/auth/sample-accounts.d.ts +2 -1
- package/dist/lib/auth/sample-accounts.d.ts.map +1 -1
- package/dist/lib/auth/sample-accounts.js +4 -1
- package/dist/lib/auth/suspension.d.ts +22 -0
- package/dist/lib/auth/suspension.d.ts.map +1 -0
- package/dist/lib/auth/suspension.js +43 -0
- package/dist/lib/auth.d.ts.map +1 -1
- package/dist/lib/auth.js +15 -15
- package/dist/lib/config/app.config.d.ts.map +1 -1
- package/dist/lib/config/app.config.js +6 -7
- package/dist/lib/config/types.d.ts +8 -6
- package/dist/lib/config/types.d.ts.map +1 -1
- package/dist/lib/db-pool.d.ts +31 -0
- package/dist/lib/db-pool.d.ts.map +1 -0
- package/dist/lib/db-pool.js +75 -0
- package/dist/lib/db.d.ts +1 -1
- package/dist/lib/db.d.ts.map +1 -1
- package/dist/lib/db.js +11 -14
- package/dist/lib/entities/api-generator.js +5 -5
- package/dist/lib/oauth/token-refresh.d.ts.map +1 -1
- package/dist/lib/oauth/token-refresh.js +3 -6
- package/dist/lib/permissions/check.d.ts +9 -0
- package/dist/lib/permissions/check.d.ts.map +1 -1
- package/dist/lib/permissions/check.js +11 -1
- package/dist/lib/services/generic-entity.service.d.ts.map +1 -1
- package/dist/lib/services/generic-entity.service.js +39 -7
- package/dist/lib/services/transactional-meta.service.d.ts.map +1 -1
- package/dist/lib/services/transactional-meta.service.js +7 -8
- package/dist/lib/validation.d.ts +5 -0
- package/dist/lib/validation.d.ts.map +1 -1
- package/dist/lib/validation.js +2 -0
- package/dist/migrations/030_taxonomies_team_writes.sql +29 -0
- package/dist/migrations/031_users_role_suspended.sql +41 -0
- package/dist/routes/(auth)/signup/page.cc.d.ts +3 -1
- package/dist/routes/(auth)/signup/page.cc.d.ts.map +1 -1
- package/dist/routes/(auth)/signup/page.cc.js +2 -2
- package/dist/routes/(auth)/signup/page.d.ts +3 -1
- package/dist/routes/(auth)/signup/page.d.ts.map +1 -1
- package/dist/routes/(auth)/signup/page.js +4 -3
- package/dist/routes/_internal/area-access.js +2 -2
- package/dist/routes/api/devtools/config/entities/route.js +2 -2
- package/dist/routes/api/devtools/config/theme/route.js +2 -2
- package/dist/routes/api/devtools/tests/[...path]/route.js +2 -2
- package/dist/routes/api/devtools/tests/route.js +2 -2
- package/dist/routes/api/superadmin/subscriptions/route.js +2 -2
- package/dist/routes/api/superadmin/teams/[teamId]/route.js +2 -2
- package/dist/routes/api/superadmin/teams/route.js +2 -2
- package/dist/routes/api/superadmin/users/[userId]/route.d.ts +5 -3
- package/dist/routes/api/superadmin/users/[userId]/route.d.ts.map +1 -1
- package/dist/routes/api/superadmin/users/[userId]/route.js +46 -23
- package/dist/routes/api/superadmin/users/route.js +2 -2
- package/dist/routes/api/user/delete-account/route.d.ts.map +1 -1
- package/dist/routes/api/user/delete-account/route.js +2 -3
- package/dist/routes/api/user/plan-flags/route.d.ts.map +1 -1
- package/dist/routes/api/user/plan-flags/route.js +5 -5
- package/dist/routes/api/user/profile/route.js +3 -3
- package/dist/routes/api/v1/auth/signup-with-invite/route.d.ts.map +1 -1
- package/dist/routes/api/v1/auth/signup-with-invite/route.js +80 -44
- package/dist/routes/api/v1/post-categories/[id]/route.d.ts.map +1 -1
- package/dist/routes/api/v1/post-categories/[id]/route.js +39 -34
- package/dist/routes/api/v1/post-categories/route.d.ts.map +1 -1
- package/dist/routes/api/v1/post-categories/route.js +18 -14
- package/dist/routes/api/v1/team-invitations/route.d.ts.map +1 -1
- package/dist/routes/api/v1/team-invitations/route.js +12 -5
- package/dist/routes/superadmin/users/page.js +2 -2
- package/dist/templates/next.config.mjs +11 -31
- package/dist/templates/projects/blog/package.json +1 -1
- package/dist/templates/projects/crm/package.json +1 -1
- package/dist/templates/projects/productivity/package.json +1 -1
- package/dist/templates/projects/starter/config/app.config.ts +8 -0
- package/dist/templates/proxy.ts +6 -3
- package/migrations/030_taxonomies_team_writes.sql +29 -0
- package/migrations/031_users_role_suspended.sql +41 -0
- package/package.json +3 -3
- package/scripts/build/registry/__tests__/build-output.test.mjs +1 -1
- package/scripts/build/registry/__tests__/project-mode-root-first.test.mjs +6 -6
- package/scripts/build/registry/__tests__/write-places.test.mjs +1 -1
- package/scripts/build/registry/contracts/__tests__/contracts-wire.test.mjs +1 -1
- package/scripts/build/registry/contracts/__tests__/contracts.test.mjs +1 -1
- package/scripts/build/registry/host/README.md +2 -2
- package/scripts/build/registry/host/__tests__/facade-emitter.test.mjs +14 -13
- package/scripts/build/registry/host/__tests__/host-mode.test.mjs +1 -1
- package/scripts/build/registry/host/__tests__/host-project-composition.test.mjs +1 -1
- package/scripts/build/registry/host/__tests__/host-project.test.mjs +2 -2
- package/scripts/build/registry/host/__tests__/host-proxy-areas.test.mjs +29 -2
- package/scripts/build/registry/host/__tests__/host-robustness.test.mjs +1 -1
- package/scripts/build/registry/host/facade-emitter.mjs +1 -1
- package/scripts/build/registry/host/next-route-exports.json +2 -2
- package/scripts/build/registry/host/proxy-areas.mjs +29 -2
- package/scripts/db/verify-theme-migrations.mjs +1 -1
- package/templates/next.config.mjs +11 -31
- package/templates/projects/blog/package.json +1 -1
- package/templates/projects/crm/package.json +1 -1
- package/templates/projects/productivity/package.json +1 -1
- package/templates/projects/starter/config/app.config.ts +8 -0
- package/templates/proxy.ts +6 -3
- package/tests/jest/__mocks__/@nextsparkjs/registries/permissions-registry.ts +5 -4
|
@@ -124,11 +124,12 @@ test("page and layout exports include Next.js's TS-plugin ALLOWED_EXPORTS (excep
|
|
|
124
124
|
}
|
|
125
125
|
})
|
|
126
126
|
|
|
127
|
-
test('metadata route loader still filters
|
|
128
|
-
// The table forbids dynamicParams and generateStaticParams on metadata routes
|
|
129
|
-
//
|
|
127
|
+
test('metadata route loader still filters generateSitemaps out of its re-exports', async () => {
|
|
128
|
+
// The table forbids dynamicParams and generateStaticParams on metadata routes: the loader
|
|
129
|
+
// declares its own generateStaticParams, and up to 16.3.7 it dropped dynamicParams (16.3.8
|
|
130
|
+
// re-exports it; the table keeps refusing it). Detect a Next.js change to that code.
|
|
130
131
|
const loader = await readFile(requireFromCore.resolve('next/dist/build/webpack/loaders/next-metadata-route-loader'), 'utf8')
|
|
131
|
-
assert.match(loader, /name !== 'default' && name !== 'generateSitemaps'
|
|
132
|
+
assert.match(loader, /exportNames\.filter\(\(name\)=>name !== 'default' && name !== 'generateSitemaps'\);/)
|
|
132
133
|
assert.match(loader, /export async function generateStaticParams/)
|
|
133
134
|
})
|
|
134
135
|
|
|
@@ -595,14 +596,14 @@ test('an aliased magic export is forwarded under its source local name', async (
|
|
|
595
596
|
assert.match(content, /^export \{ listSlugs as generateStaticParams \}$/m)
|
|
596
597
|
})
|
|
597
598
|
|
|
598
|
-
// --- fix round 3: ~baseline version range (baseline 16.3.
|
|
599
|
+
// --- fix round 3: ~baseline version range (baseline 16.3.8 since 0.1.0-beta.197) ---
|
|
599
600
|
|
|
600
601
|
test('isSupportedNextVersion accepts ~baseline: same minor, stable patch >= baseline', () => {
|
|
601
|
-
assert.equal(ROUTE_EXPORT_TABLE.next, '16.3.
|
|
602
|
-
for (const version of ['16.3.
|
|
602
|
+
assert.equal(ROUTE_EXPORT_TABLE.next, '16.3.8')
|
|
603
|
+
for (const version of ['16.3.8', '16.3.9', '16.3.10', '16.3.123', '16.3.8+build.1', '16.3.9+sha.abc-1']) assert.equal(isSupportedNextVersion(version), true, version)
|
|
603
604
|
for (const version of [
|
|
604
|
-
'16.3.5', '16.3.4', '16.3.0', '16.4.0', '16.2.9', '17.0.0', '15.3.6', '16.3.7-canary.0', '16.3.
|
|
605
|
-
'16.3.5+build.1', '16.3.
|
|
605
|
+
'16.3.7', '16.3.6', '16.3.5', '16.3.4', '16.3.0', '16.4.0', '16.2.9', '17.0.0', '15.3.6', '16.3.7-canary.0', '16.3.8-rc.1', '16.3.7-canary.0+build.1',
|
|
606
|
+
'16.3.5+build.1', '16.3.8+', '16.3.8+bad..meta', '16.3', 'latest', '', undefined,
|
|
606
607
|
]) {
|
|
607
608
|
assert.equal(isSupportedNextVersion(version), false, String(version))
|
|
608
609
|
}
|
|
@@ -625,7 +626,7 @@ test('a later 16.3 patch is accepted and its own schema internal is used', async
|
|
|
625
626
|
}
|
|
626
627
|
})
|
|
627
628
|
|
|
628
|
-
for (const version of ['16.3.5', '16.4.0', '17.0.0', '16.3.
|
|
629
|
+
for (const version of ['16.3.5', '16.4.0', '17.0.0', '16.3.8-canary.0']) {
|
|
629
630
|
test(`${DIAGNOSTICS.UNSUPPORTED_NEXT_VERSION}: next@${version} is refused`, async () => {
|
|
630
631
|
const dir = await fakeNextProject(version, "exports.AppSegmentConfigSchemaKeys = []\nexports.parseAppSegmentConfig = () => ({})\n")
|
|
631
632
|
try {
|
|
@@ -634,7 +635,7 @@ for (const version of ['16.3.5', '16.4.0', '17.0.0', '16.3.6-canary.0']) {
|
|
|
634
635
|
error => {
|
|
635
636
|
assert.deepEqual(error.diagnostics.map(d => d.code), [DIAGNOSTICS.UNSUPPORTED_NEXT_VERSION])
|
|
636
637
|
assert.match(error.message, new RegExp(`next@${version.replace(/\./g, '\\.')}`))
|
|
637
|
-
assert.match(error.message, /next@~16\.3\.
|
|
638
|
+
assert.match(error.message, /next@~16\.3\.8/)
|
|
638
639
|
return true
|
|
639
640
|
}
|
|
640
641
|
)
|
|
@@ -646,9 +647,9 @@ for (const version of ['16.3.5', '16.4.0', '17.0.0', '16.3.6-canary.0']) {
|
|
|
646
647
|
|
|
647
648
|
// --- fix round 4: build metadata ----------------------------------------------
|
|
648
649
|
|
|
649
|
-
test('a Next.js version with build metadata (16.3.
|
|
650
|
+
test('a Next.js version with build metadata (16.3.8+build.1) is accepted end to end', async () => {
|
|
650
651
|
const marker = "exports.AppSegmentConfigSchemaKeys = []\nexports.parseAppSegmentConfig = () => ({})\n"
|
|
651
|
-
const dir = await fakeNextProject('16.3.
|
|
652
|
+
const dir = await fakeNextProject('16.3.8+build.1', marker)
|
|
652
653
|
try {
|
|
653
654
|
assert.equal(loadNextSegmentConfig(dir).error, undefined)
|
|
654
655
|
const { content } = await emitFacade({ kind: 'page', target: 'a/page.tsx', specifier: '@/a', file: FILE, source: PAGE_WITH_CONFIG, projectRoot: dir })
|
|
@@ -17,7 +17,7 @@ const PREPARE_CLI = join(CORE_ROOT, 'scripts/build/registry/host/prepare-cli.mjs
|
|
|
17
17
|
function project({ legacyApp = false } = {}) {
|
|
18
18
|
const root = mkdtempSync(join(tmpdir(), 'nextspark-host-mode-'))
|
|
19
19
|
write(root, 'nextspark.config.ts', 'export default { plugins: [] }\n')
|
|
20
|
-
write(root, 'package.json', JSON.stringify({ name: 'host-mode', dependencies: { next: '16.3.
|
|
20
|
+
write(root, 'package.json', JSON.stringify({ name: 'host-mode', dependencies: { next: '16.3.8' } }))
|
|
21
21
|
write(root, 'templates/pricing/page.tsx', PAGE('Pricing'))
|
|
22
22
|
if (legacyApp) write(root, 'src/app/layout.tsx', 'export default function Layout({ children }) { return children }\n')
|
|
23
23
|
return { root, cleanup: () => rmSync(root, { recursive: true, force: true }) }
|
|
@@ -36,7 +36,7 @@ const LAYOUT = 'export default function Layout({ children }: { children: React.R
|
|
|
36
36
|
function project(extra = {}) {
|
|
37
37
|
const root = mkdtempSync(join(tmpdir(), 'nextspark-host-composition-'))
|
|
38
38
|
write(root, 'nextspark.config.ts', "export default { plugins: [], billing: { webhookExtensions: { stripe: './lib/billing/stripe-webhook-extensions' } } }\n")
|
|
39
|
-
write(root, 'package.json', JSON.stringify({ name: 'composition', dependencies: { next: '16.3.
|
|
39
|
+
write(root, 'package.json', JSON.stringify({ name: 'composition', dependencies: { next: '16.3.8' } }))
|
|
40
40
|
write(root, 'config/theme.config.ts', "export const compositionThemeConfig = { name: 'composition' }\n")
|
|
41
41
|
write(root, 'styles/globals.css', '@import "tailwindcss";\n')
|
|
42
42
|
write(root, 'lib/billing/stripe-webhook-extensions.ts', 'export const stripeWebhookExtensions = {}\n')
|
|
@@ -16,7 +16,7 @@ const PREPARE_CLI = join(CORE_ROOT, 'scripts/build/registry/host/prepare-cli.mjs
|
|
|
16
16
|
function project() {
|
|
17
17
|
const root = mkdtempSync(join(tmpdir(), 'nextspark-host-project-'))
|
|
18
18
|
write(root, 'nextspark.config.ts', 'export default { plugins: [] }\n')
|
|
19
|
-
write(root, 'package.json', JSON.stringify({ name: 'host-project', dependencies: { next: '16.3.
|
|
19
|
+
write(root, 'package.json', JSON.stringify({ name: 'host-project', dependencies: { next: '16.3.8' } }))
|
|
20
20
|
write(root, 'templates/pricing/page.tsx', PAGE('Pricing'))
|
|
21
21
|
write(root, 'api/ping/route.ts', ROUTE)
|
|
22
22
|
return { root, cleanup: () => rmSync(root, { recursive: true, force: true }) }
|
|
@@ -41,7 +41,7 @@ test('a real project: the registry build is staged and published with the host,
|
|
|
41
41
|
|
|
42
42
|
const record = readGeneration(root)
|
|
43
43
|
assert.equal(Object.keys(record.files).length, first.files.length)
|
|
44
|
-
assert.ok(record.versions.core && record.versions.next === '16.3.
|
|
44
|
+
assert.ok(record.versions.core && record.versions.next === '16.3.8')
|
|
45
45
|
assert.ok(record.inputs.files['nextspark.config.ts'] && record.inputs.files['templates/pricing/page.tsx'])
|
|
46
46
|
assert.deepEqual(await checkHost(config), { ok: true, problems: [] })
|
|
47
47
|
|
|
@@ -12,7 +12,7 @@ import { mkdtempSync, readFileSync, rmSync, symlinkSync } from 'node:fs'
|
|
|
12
12
|
import { tmpdir } from 'node:os'
|
|
13
13
|
import { join } from 'node:path'
|
|
14
14
|
|
|
15
|
-
import { PROXY_AREA_WARNING, PROXY_SESSION_WARNING, missingProxyAreas, withoutComments, proxyAreaNotice, proxyAreaNotices, proxySessionNotice } from '../proxy-areas.mjs'
|
|
15
|
+
import { PROXY_AREA_WARNING, PROXY_SESSION_WARNING, PROXY_SESSION_CACHED_WARNING, missingProxyAreas, withoutComments, proxyAreaNotice, proxyAreaNotices, proxySessionNotice, proxySessionCachedNotice } from '../proxy-areas.mjs'
|
|
16
16
|
import { predictHost } from '../prepare.mjs'
|
|
17
17
|
import { PAGE, tempHost, write } from './host-helpers.mjs'
|
|
18
18
|
|
|
@@ -83,7 +83,7 @@ test('a proxy that asks its own /api/auth/get-session over HTTP is warned about,
|
|
|
83
83
|
assert.equal(notice.code, PROXY_SESSION_WARNING)
|
|
84
84
|
assert.match(notice.message, /^src\/proxy\.ts checks the session by fetching \/api\/auth\/get-session/)
|
|
85
85
|
assert.match(notice.message, /X-Forwarded-Proto: https/)
|
|
86
|
-
assert.match(notice.message, /auth\.api\.getSession\(\{ headers: new Headers\(\{ cookie: request\.headers\.get\('cookie'\) \|\| '' \}\), query: \{ disableRefresh: true \} \}\)/)
|
|
86
|
+
assert.match(notice.message, /auth\.api\.getSession\(\{ headers: new Headers\(\{ cookie: request\.headers\.get\('cookie'\) \|\| '' \}\), query: \{ disableRefresh: true, disableCookieCache: true \} \}\)/)
|
|
87
87
|
assert.equal(proxySessionNotice('src/proxy.ts', readFileSync(join(CORE_ROOT, 'templates/proxy.ts'), 'utf8')), null)
|
|
88
88
|
assert.equal(proxySessionNotice('src/proxy.ts', "// was: betterFetch('/api/auth/get-session')\nexport function proxy() {}"), null)
|
|
89
89
|
assert.equal(proxySessionNotice('src/proxy.ts', "export { proxy } from '@nextsparkjs/core/templates/proxy'"), null)
|
|
@@ -138,3 +138,30 @@ test("prepare's prediction carries the warning among its notices", async () => {
|
|
|
138
138
|
rmSync(project, { recursive: true, force: true })
|
|
139
139
|
}
|
|
140
140
|
})
|
|
141
|
+
|
|
142
|
+
// The in-process session read of the 0.1.0-beta.196 template, without disableCookieCache.
|
|
143
|
+
const BETA_196_SESSION_READ = `async function getSession(request: NextRequest): Promise<Session | null> {
|
|
144
|
+
const session = await auth.api.getSession({
|
|
145
|
+
headers: new Headers({ cookie: request.headers.get('cookie') || '' }),
|
|
146
|
+
query: { disableRefresh: true },
|
|
147
|
+
})
|
|
148
|
+
return session as Session | null
|
|
149
|
+
}
|
|
150
|
+
`
|
|
151
|
+
|
|
152
|
+
test('a proxy that reads the session in process from the cookie cache is warned about, with the option to add', () => {
|
|
153
|
+
const notice = proxySessionCachedNotice('src/proxy.ts', BETA_196_SESSION_READ)
|
|
154
|
+
assert.equal(notice.code, PROXY_SESSION_CACHED_WARNING)
|
|
155
|
+
assert.match(notice.message, /^src\/proxy\.ts reads the session with auth\.api\.getSession from Better Auth's cookie cache/)
|
|
156
|
+
assert.match(notice.message, /query: \{ disableRefresh: true, disableCookieCache: true \}/)
|
|
157
|
+
assert.equal(proxySessionCachedNotice('src/proxy.ts', readFileSync(join(CORE_ROOT, 'templates/proxy.ts'), 'utf8')), null)
|
|
158
|
+
assert.match(notice.message, /role !== 'suspended'/)
|
|
159
|
+
// the cache option alone is not enough: the proxy must also refuse a suspended account's session
|
|
160
|
+
const cacheOnly = BETA_196_SESSION_READ.replace('disableRefresh: true', 'disableRefresh: true, disableCookieCache: true')
|
|
161
|
+
assert.notEqual(proxySessionCachedNotice('src/proxy.ts', cacheOnly), null)
|
|
162
|
+
assert.equal(proxySessionCachedNotice('src/proxy.ts', cacheOnly.replace('return session as Session | null', "return session && (session.user as { role?: unknown }).role !== 'suspended' ? (session as Session) : null")), null)
|
|
163
|
+
// a comment that names the option does not count
|
|
164
|
+
assert.notEqual(proxySessionCachedNotice('src/proxy.ts', `// disableCookieCache\n${BETA_196_SESSION_READ}`), null)
|
|
165
|
+
// no in-process read (the HTTP check has its own warning), no notice
|
|
166
|
+
assert.equal(proxySessionCachedNotice('src/proxy.ts', HTTP_SESSION_CHECK), null)
|
|
167
|
+
})
|
|
@@ -314,7 +314,7 @@ test('an npm-installed core ships only dist/: the manifest, variants and route m
|
|
|
314
314
|
test('the project config is read again for every generation: enabling a plugin in nextspark.config.ts adds its routes', async () => {
|
|
315
315
|
const root = mkdtempSync(join(tmpdir(), 'nextspark-config-reload-'))
|
|
316
316
|
try {
|
|
317
|
-
write(root, 'package.json', JSON.stringify({ name: 'reload', dependencies: { next: '16.3.
|
|
317
|
+
write(root, 'package.json', JSON.stringify({ name: 'reload', dependencies: { next: '16.3.8' } }))
|
|
318
318
|
write(root, 'nextspark.config.ts', 'export default { plugins: [] }\n')
|
|
319
319
|
write(root, 'plugins/search/plugin.config.ts', 'export const searchPluginConfig = {}\n')
|
|
320
320
|
write(root, 'plugins/search/templates/search/page.tsx', PAGE('Search'))
|
|
@@ -284,7 +284,7 @@ export function resolveNextPackage(projectRoot = process.cwd()) {
|
|
|
284
284
|
/**
|
|
285
285
|
* Whether `version` is a Next.js the table applies to: the table's `next` is the verified
|
|
286
286
|
* baseline, and core pins `next` to `~<baseline>`, so any stable patch of the same minor at or
|
|
287
|
-
* above the baseline is accepted (16.3.
|
|
287
|
+
* above the baseline is accepted (16.3.8, 16.3.9, 16.3.8+build.1), while other minors/majors, lower
|
|
288
288
|
* patches and prereleases (16.3.7-canary.0) are refused.
|
|
289
289
|
*/
|
|
290
290
|
export function isSupportedNextVersion(version, baseline = ROUTE_EXPORT_TABLE.next) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$comment": "Route file kinds x exports x emitted facade form, for the generated host (#203). Source of truth for host/facade-emitter.mjs; README.md renders it. Verified against the installed Next.js source listed in verifiedAgainst and by the probe builds recorded in README.md. Re-verify and bump `next` when upgrading Next.js: host/__tests__/facade-emitter.test.mjs fails when the installed version differs.",
|
|
3
|
-
"next": "16.3.
|
|
4
|
-
"supportedNext": "~16.3.
|
|
3
|
+
"next": "16.3.8",
|
|
4
|
+
"supportedNext": "~16.3.8 (same major.minor, stable patch >= the verified baseline in \"next\"; enforced by isSupportedNextVersion)",
|
|
5
5
|
"verifiedAgainst": [
|
|
6
6
|
"next/dist/build/segment-config/app/app-segment-config.js (AppSegmentConfigSchemaKeys, parseAppSegmentConfig)",
|
|
7
7
|
"next/dist/build/analysis/get-page-static-info.js (checkExports, getAppPageStaticInfo)",
|
|
@@ -92,7 +92,7 @@ export const PROXY_SESSION_WARNING = 'NS_PROXY_SESSION_OVER_HTTP'
|
|
|
92
92
|
/** What to write instead of the HTTP session check; migrate in the CLI says the same. */
|
|
93
93
|
export const PROXY_SESSION_FIX =
|
|
94
94
|
"import { auth } from '@nextsparkjs/core/lib/auth' and replace the betterFetch('/api/auth/get-session', ...) call with " +
|
|
95
|
-
"auth.api.getSession({ headers: new Headers({ cookie: request.headers.get('cookie') || '' }), query: { disableRefresh: true } }), " +
|
|
95
|
+
"auth.api.getSession({ headers: new Headers({ cookie: request.headers.get('cookie') || '' }), query: { disableRefresh: true, disableCookieCache: true } }), " +
|
|
96
96
|
'which returns the session itself (or null), not { data }'
|
|
97
97
|
|
|
98
98
|
/** The warning for a proxy whose code (not a comment) asks /api/auth/get-session over HTTP, or null. */
|
|
@@ -109,11 +109,38 @@ export function proxySessionNotice(file, source) {
|
|
|
109
109
|
}
|
|
110
110
|
}
|
|
111
111
|
|
|
112
|
+
export const PROXY_SESSION_CACHED_WARNING = 'NS_PROXY_SESSION_CACHED'
|
|
113
|
+
|
|
114
|
+
/** What to add to an in-process session read (the 0.1.0-beta.196 template); migrate in the CLI says the same. */
|
|
115
|
+
export const PROXY_SESSION_CACHED_FIX =
|
|
116
|
+
"add disableCookieCache: true to the query of its auth.api.getSession call: query: { disableRefresh: true, disableCookieCache: true }, " +
|
|
117
|
+
"and refuse a suspended account's session at the end of getSession: return session && (session.user as { role?: unknown }).role !== 'suspended' ? (session as Session) : null"
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* The warning for a proxy whose code (not a comment) reads the session in process (auth.api.getSession, as the
|
|
121
|
+
* 0.1.0-beta.196 template) without disableCookieCache, or without refusing a suspended account, or null: Better Auth
|
|
122
|
+
* then answers from the cached session_data cookie, so a signed-out cookie pair or an old role passes the proxy for up
|
|
123
|
+
* to 5 minutes, and a suspended account's session passes it.
|
|
124
|
+
*/
|
|
125
|
+
export function proxySessionCachedNotice(file, source) {
|
|
126
|
+
const code = withoutComments(source)
|
|
127
|
+
if (!code.includes('auth.api.getSession(') || (code.includes('disableCookieCache') && code.includes("'suspended'"))) return null
|
|
128
|
+
return {
|
|
129
|
+
code: PROXY_SESSION_CACHED_WARNING,
|
|
130
|
+
target: file,
|
|
131
|
+
message:
|
|
132
|
+
`${file} reads the session with auth.api.getSession from Better Auth's cookie cache: after a sign-out, the copied cookie ` +
|
|
133
|
+
`pair, or a role changed since sign-in, still passes the proxy until the cached cookie expires (5 minutes by default), ` +
|
|
134
|
+
`and a suspended account's session passes it. ` +
|
|
135
|
+
`As node_modules/@nextsparkjs/core/templates/proxy.ts does, ${PROXY_SESSION_CACHED_FIX}`,
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
112
139
|
/** The warnings for the proxy Next would load in `projectRoot` (the first of PROXY_FILES that exists). */
|
|
113
140
|
export function proxyAreaNotices(projectRoot) {
|
|
114
141
|
if (!projectRoot) return []
|
|
115
142
|
const file = PROXY_FILES.find(candidate => existsSync(join(projectRoot, candidate)))
|
|
116
143
|
if (!file) return []
|
|
117
144
|
const source = readFileSync(join(projectRoot, file), 'utf8')
|
|
118
|
-
return [proxyAreaNotice(file, source), proxySessionNotice(file, source)].filter(Boolean)
|
|
145
|
+
return [proxyAreaNotice(file, source), proxySessionNotice(file, source), proxySessionCachedNotice(file, source)].filter(Boolean)
|
|
119
146
|
}
|
|
@@ -157,7 +157,7 @@ async function main() {
|
|
|
157
157
|
extracted = fs.mkdtempSync(path.join(os.tmpdir(), `nextspark-template-${theme}-`));
|
|
158
158
|
fs.cpSync(located.sourceDir, extracted, { recursive: true });
|
|
159
159
|
fs.writeFileSync(path.join(extracted, 'nextspark.config.ts'), `export default { plugins: [], template: { name: ${JSON.stringify(theme)}, version: 'test' } }\n`);
|
|
160
|
-
fs.writeFileSync(path.join(extracted, 'package.json'), JSON.stringify({ name: `nextspark-template-${theme}`, private: true, dependencies: { next: '16.3.
|
|
160
|
+
fs.writeFileSync(path.join(extracted, 'package.json'), JSON.stringify({ name: `nextspark-template-${theme}`, private: true, dependencies: { next: '16.3.8' } }, null, 2));
|
|
161
161
|
runDir = extracted;
|
|
162
162
|
}
|
|
163
163
|
|
|
@@ -109,42 +109,21 @@ const nextConfig = {
|
|
|
109
109
|
formats: ['image/avif', 'image/webp'],
|
|
110
110
|
deviceSizes: [640, 750, 828, 1080, 1200, 1920],
|
|
111
111
|
imageSizes: [16, 32, 48, 64, 96, 128, 256, 384],
|
|
112
|
+
// Hosts the server may fetch and resize through /_next/image.
|
|
113
|
+
// Keep this list to the exact hosts you use.
|
|
112
114
|
remotePatterns: [
|
|
115
|
+
// Google profile pictures shown for accounts that sign in with Google.
|
|
113
116
|
{
|
|
114
117
|
protocol: 'https',
|
|
115
118
|
hostname: 'lh3.googleusercontent.com',
|
|
116
119
|
pathname: '/**',
|
|
117
120
|
},
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
},
|
|
123
|
-
{
|
|
124
|
-
protocol: 'https',
|
|
125
|
-
hostname: 'images.unsplash.com',
|
|
126
|
-
pathname: '/**',
|
|
127
|
-
},
|
|
128
|
-
{
|
|
129
|
-
protocol: 'https',
|
|
130
|
-
hostname: 'upload.wikimedia.org',
|
|
131
|
-
pathname: '/**',
|
|
132
|
-
},
|
|
133
|
-
{
|
|
134
|
-
protocol: 'https',
|
|
135
|
-
hostname: 'i.pravatar.cc',
|
|
136
|
-
pathname: '/**',
|
|
137
|
-
},
|
|
138
|
-
{
|
|
139
|
-
protocol: 'https',
|
|
140
|
-
hostname: '*.supabase.co',
|
|
141
|
-
pathname: '/**',
|
|
142
|
-
},
|
|
143
|
-
{
|
|
144
|
-
protocol: 'https',
|
|
145
|
-
hostname: '*.cloudinary.com',
|
|
146
|
-
pathname: '/**',
|
|
147
|
-
},
|
|
121
|
+
// Examples: add the exact host of your own store, e.g. your Vercel Blob store
|
|
122
|
+
// (`<store-id>.public.blob.vercel-storage.com`) once uploads use it.
|
|
123
|
+
// { protocol: 'https', hostname: '<store-id>.public.blob.vercel-storage.com', pathname: '/**' },
|
|
124
|
+
// { protocol: 'https', hostname: '<project-ref>.supabase.co', pathname: '/**' },
|
|
125
|
+
// { protocol: 'https', hostname: 'res.cloudinary.com', pathname: '/<cloud-name>/**' },
|
|
126
|
+
// { protocol: 'https', hostname: 'images.unsplash.com', pathname: '/**' },
|
|
148
127
|
],
|
|
149
128
|
},
|
|
150
129
|
// Next.js sets TURBOPACK before it loads this file whenever Turbopack builds
|
|
@@ -158,7 +137,8 @@ const nextConfig = {
|
|
|
158
137
|
async headers() {
|
|
159
138
|
const isProduction = process.env.NODE_ENV === 'production';
|
|
160
139
|
|
|
161
|
-
//
|
|
140
|
+
// Image hosts the browser may load directly (CSP img-src). The server only fetches the hosts in
|
|
141
|
+
// images.remotePatterns above; add a host there too when next/image should serve it.
|
|
162
142
|
// NOTE: Wildcard patterns (*.public.blob.vercel-storage.com, *.supabase.co, *.cloudinary.com)
|
|
163
143
|
// allow images from any account on these services for development flexibility.
|
|
164
144
|
// For production with stricter security, consider restricting to specific account subdomains.
|
|
@@ -126,6 +126,14 @@ export const APP_CONFIG_OVERRIDES = {
|
|
|
126
126
|
// methods: ['email-password', 'google'],
|
|
127
127
|
// methods: ['email-otp', 'email-password', 'google'],
|
|
128
128
|
|
|
129
|
+
/**
|
|
130
|
+
* Password endpoints (sign-in/email, sign-up/email, reset). Left undeclared
|
|
131
|
+
* they follow `methods` in production (off under the passwordless preset) and
|
|
132
|
+
* stay on in development (dev keyring, `pnpm db:seed` users, Cypress).
|
|
133
|
+
* Uncomment to keep password logins in production (API clients, test users):
|
|
134
|
+
*/
|
|
135
|
+
// emailAndPassword: { enabled: true },
|
|
136
|
+
|
|
129
137
|
/**
|
|
130
138
|
* Session duration / renewal in seconds (defaults: 7 days, renewed daily).
|
|
131
139
|
* Long-lived sessions for an installed PWA, for example:
|
package/templates/proxy.ts
CHANGED
|
@@ -189,14 +189,17 @@ function redirectAccessDenied(request: NextRequest): NextResponse {
|
|
|
189
189
|
* Nothing here leaves the process, so no forwarded scheme or host can point
|
|
190
190
|
* the check anywhere. `disableRefresh`: this response cannot carry the renewed
|
|
191
191
|
* session cookie, so the rolling renewal is left to the auth route
|
|
192
|
-
* (lib/auth/session-refresh).
|
|
192
|
+
* (lib/auth/session-refresh). `disableCookieCache`: the session comes from the
|
|
193
|
+
* database, not the signed session_data cookie, so a sign-out, a revoked
|
|
194
|
+
* session or a role change counts at once (lib/auth/authorization-session).
|
|
195
|
+
* A suspended account's session is no session (lib/auth/suspension).
|
|
193
196
|
*/
|
|
194
197
|
async function getSession(request: NextRequest): Promise<Session | null> {
|
|
195
198
|
const session = await auth.api.getSession({
|
|
196
199
|
headers: new Headers({ cookie: request.headers.get('cookie') || '' }),
|
|
197
|
-
query: { disableRefresh: true },
|
|
200
|
+
query: { disableRefresh: true, disableCookieCache: true },
|
|
198
201
|
})
|
|
199
|
-
return session as Session
|
|
202
|
+
return session && (session.user as { role?: unknown }).role !== 'suspended' ? (session as Session) : null
|
|
200
203
|
}
|
|
201
204
|
|
|
202
205
|
/**
|
|
@@ -37,12 +37,13 @@ export const TEAM_PERMISSIONS_BY_ROLE: Record<string, string[]> = {
|
|
|
37
37
|
viewer: ['team.view'],
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
// Entity permissions used by service tests (mirrors the default theme's customers entity
|
|
40
|
+
// Entity permissions used by service tests (mirrors the default theme's customers entity, and the starter's
|
|
41
|
+
// posts entity, whose `publish` action is owner/admin only)
|
|
41
42
|
const ENTITY_PERMISSIONS_BY_ROLE: Record<string, string[]> = {
|
|
42
|
-
owner: ['customers.create', 'customers.read', 'customers.list', 'customers.update', 'customers.delete'],
|
|
43
|
-
admin: ['customers.create', 'customers.read', 'customers.list', 'customers.update'],
|
|
43
|
+
owner: ['customers.create', 'customers.read', 'customers.list', 'customers.update', 'customers.delete', 'posts.create', 'posts.update', 'posts.publish'],
|
|
44
|
+
admin: ['customers.create', 'customers.read', 'customers.list', 'customers.update', 'posts.create', 'posts.update', 'posts.publish'],
|
|
44
45
|
editor: ['customers.read', 'customers.list'],
|
|
45
|
-
member: ['customers.read', 'customers.list'],
|
|
46
|
+
member: ['customers.read', 'customers.list', 'posts.create', 'posts.update'],
|
|
46
47
|
viewer: [],
|
|
47
48
|
}
|
|
48
49
|
|