@nextsparkjs/core 0.1.0-beta.192 → 0.1.0-beta.194
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 -2
- package/dist/components/app/layouts/PublicFooter.js +1 -1
- package/dist/components/app/layouts/PublicNavbar.js +1 -1
- package/dist/components/app/misc/ThemeToggle.d.ts.map +1 -1
- package/dist/components/app/misc/ThemeToggle.js +4 -3
- package/dist/lib/api/auth/permissions.d.ts +10 -0
- package/dist/lib/api/auth/permissions.d.ts.map +1 -1
- package/dist/lib/api/auth/permissions.js +7 -0
- package/dist/lib/api/rate-limit.d.ts.map +1 -1
- package/dist/lib/api/rate-limit.js +3 -0
- package/dist/lib/api/request-origin.d.ts +26 -0
- package/dist/lib/api/request-origin.d.ts.map +1 -0
- package/dist/lib/api/request-origin.js +46 -0
- package/dist/lib/auth.d.ts.map +1 -1
- package/dist/lib/auth.js +8 -12
- package/dist/lib/config/features-types.d.ts +5 -4
- package/dist/lib/config/features-types.d.ts.map +1 -1
- package/dist/lib/entities/external-api-generator.d.ts.map +1 -1
- package/dist/lib/entities/external-api-generator.js +3 -1
- package/dist/lib/selectors/auth-sel.d.ts +2 -7
- package/dist/lib/selectors/auth-sel.d.ts.map +1 -1
- package/dist/lib/selectors/auth-sel.js +1 -3
- package/dist/lib/selectors/by-domain/auth.d.ts +8 -0
- package/dist/lib/selectors/by-domain/auth.d.ts.map +1 -0
- package/dist/lib/selectors/by-domain/auth.js +10 -0
- package/dist/lib/selectors/by-domain/block-editor.d.ts +8 -0
- package/dist/lib/selectors/by-domain/block-editor.d.ts.map +1 -0
- package/dist/lib/selectors/by-domain/block-editor.js +10 -0
- package/dist/lib/selectors/by-domain/common.d.ts +8 -0
- package/dist/lib/selectors/by-domain/common.d.ts.map +1 -0
- package/dist/lib/selectors/by-domain/common.js +10 -0
- package/dist/lib/selectors/by-domain/dashboard.d.ts +8 -0
- package/dist/lib/selectors/by-domain/dashboard.d.ts.map +1 -0
- package/dist/lib/selectors/by-domain/dashboard.js +10 -0
- package/dist/lib/selectors/by-domain/devtools.d.ts +8 -0
- package/dist/lib/selectors/by-domain/devtools.d.ts.map +1 -0
- package/dist/lib/selectors/by-domain/devtools.js +10 -0
- package/dist/lib/selectors/by-domain/entities.d.ts +8 -0
- package/dist/lib/selectors/by-domain/entities.d.ts.map +1 -0
- package/dist/lib/selectors/by-domain/entities.js +10 -0
- package/dist/lib/selectors/by-domain/global-search.d.ts +8 -0
- package/dist/lib/selectors/by-domain/global-search.d.ts.map +1 -0
- package/dist/lib/selectors/by-domain/global-search.js +10 -0
- package/dist/lib/selectors/by-domain/media.d.ts +8 -0
- package/dist/lib/selectors/by-domain/media.d.ts.map +1 -0
- package/dist/lib/selectors/by-domain/media.js +10 -0
- package/dist/lib/selectors/by-domain/patterns.d.ts +8 -0
- package/dist/lib/selectors/by-domain/patterns.d.ts.map +1 -0
- package/dist/lib/selectors/by-domain/patterns.js +10 -0
- package/dist/lib/selectors/by-domain/public.d.ts +8 -0
- package/dist/lib/selectors/by-domain/public.d.ts.map +1 -0
- package/dist/lib/selectors/by-domain/public.js +10 -0
- package/dist/lib/selectors/by-domain/settings.d.ts +8 -0
- package/dist/lib/selectors/by-domain/settings.d.ts.map +1 -0
- package/dist/lib/selectors/by-domain/settings.js +10 -0
- package/dist/lib/selectors/by-domain/superadmin.d.ts +8 -0
- package/dist/lib/selectors/by-domain/superadmin.d.ts.map +1 -0
- package/dist/lib/selectors/by-domain/superadmin.js +10 -0
- package/dist/lib/selectors/by-domain/taxonomies.d.ts +8 -0
- package/dist/lib/selectors/by-domain/taxonomies.d.ts.map +1 -0
- package/dist/lib/selectors/by-domain/taxonomies.js +10 -0
- package/dist/lib/selectors/by-domain/teams.d.ts +8 -0
- package/dist/lib/selectors/by-domain/teams.d.ts.map +1 -0
- package/dist/lib/selectors/by-domain/teams.js +10 -0
- package/dist/lib/selectors/core-selectors.d.ts +1 -0
- package/dist/lib/selectors/core-selectors.d.ts.map +1 -1
- package/dist/lib/selectors/domains/public.selectors.d.ts +1 -0
- package/dist/lib/selectors/domains/public.selectors.d.ts.map +1 -1
- package/dist/lib/selectors/domains/public.selectors.js +3 -1
- package/dist/lib/selectors/selectors.d.ts +2 -0
- package/dist/lib/selectors/selectors.d.ts.map +1 -1
- package/dist/lib/services/subscription.service.d.ts.map +1 -1
- package/dist/lib/services/subscription.service.js +3 -0
- package/dist/lib/theme/ThemeProvider.d.ts.map +1 -1
- package/dist/lib/theme/ThemeProvider.js +7 -5
- package/dist/lib/utils/cors.d.ts +7 -0
- package/dist/lib/utils/cors.d.ts.map +1 -1
- package/dist/lib/utils/cors.js +8 -0
- package/dist/routes/(auth)/accept-invite/[token]/page.js +1 -1
- package/dist/routes/(auth)/forgot-password/page.js +1 -1
- package/dist/routes/(auth)/reset-password/page.js +1 -1
- package/dist/routes/(auth)/signup/page.cc.d.ts +2 -2
- package/dist/routes/(auth)/signup/page.d.ts +1 -1
- package/dist/routes/(auth)/signup/page.d.ts.map +1 -1
- package/dist/routes/(auth)/signup/page.js +1 -5
- package/dist/routes/(auth)/verify-email/page.js +1 -1
- package/dist/routes/(public)/docs/[section]/[page]/page.cc.js +1 -1
- package/dist/routes/(public)/docs/[section]/[page]/page.js +1 -1
- package/dist/routes/(public)/docs/layout.js +1 -1
- package/dist/routes/(public)/docs/page.js +1 -1
- package/dist/routes/_internal/default-public-layout.d.ts +5 -0
- package/dist/routes/_internal/default-public-layout.d.ts.map +1 -1
- package/dist/routes/_internal/default-public-layout.js +1 -2
- package/dist/routes/api/v1/users/[id]/meta/[key]/route.d.ts.map +1 -1
- package/dist/routes/api/v1/users/[id]/meta/[key]/route.js +13 -0
- package/dist/routes/api/v1/users/[id]/route.d.ts.map +1 -1
- package/dist/routes/api/v1/users/[id]/route.js +17 -0
- package/dist/routes/devtools/blocks/[slug]/page.d.ts +4 -1
- package/dist/routes/devtools/blocks/[slug]/page.d.ts.map +1 -1
- package/dist/routes/devtools/blocks/[slug]/page.js +2 -1
- package/dist/templates/features/blog/blocks/post-content/component.tsx +1 -1
- package/dist/templates/features/blog/entities/posts/posts.config.ts +0 -11
- package/dist/templates/features/pages/blocks/hero/component.tsx +1 -1
- package/dist/templates/features/pages/entities/pages/pages.config.ts +0 -11
- package/dist/templates/projects/blog/lib/block-selectors.ts +29 -0
- package/dist/templates/projects/blog/lib/selectors.ts +2 -9
- package/dist/templates/projects/blog/package.json +4 -7
- package/dist/templates/projects/crm/lib/block-selectors.ts +29 -0
- package/dist/templates/projects/crm/lib/selectors.ts +2 -9
- package/dist/templates/projects/crm/package.json +4 -4
- package/dist/templates/projects/crm/templates/dashboard/(main)/layout.tsx +2 -17
- package/dist/templates/projects/crm/templates/shared/CRMSidebarContext.tsx +23 -0
- package/dist/templates/projects/productivity/entities/boards/boards.service.ts +18 -18
- package/dist/templates/projects/productivity/entities/cards/cards.service.ts +18 -18
- package/dist/templates/projects/productivity/entities/lists/lists.service.ts +18 -18
- package/dist/templates/projects/productivity/lib/block-selectors.ts +29 -0
- package/dist/templates/projects/productivity/lib/selectors.ts +2 -9
- package/dist/templates/projects/productivity/package.json +4 -4
- package/dist/templates/projects/starter/lib/block-selectors.ts +28 -0
- package/dist/templates/projects/starter/lib/selectors.ts +2 -11
- package/package.json +12 -5
- package/scripts/build/auth-readiness.mjs +4 -0
- 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/contracts/render.mjs +3 -3
- package/scripts/build/registry/host/README.md +2 -2
- package/scripts/build/registry/host/__tests__/facade-emitter.test.mjs +9 -9
- 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-robustness.test.mjs +65 -2
- package/scripts/build/registry/host/facade-emitter.mjs +2 -2
- package/scripts/build/registry/host/generation.mjs +54 -0
- package/scripts/build/registry/host/next-route-exports.json +2 -2
- package/scripts/build/registry/host/prepare.mjs +3 -0
- package/scripts/db/migration-time-limit.mjs +216 -34
- package/scripts/db/run-migrations.mjs +11 -179
- package/scripts/db/verify-theme-migrations.mjs +1 -1
- package/templates/features/blog/blocks/post-content/component.tsx +1 -1
- package/templates/features/blog/entities/posts/posts.config.ts +0 -11
- package/templates/features/pages/blocks/hero/component.tsx +1 -1
- package/templates/features/pages/entities/pages/pages.config.ts +0 -11
- package/templates/projects/blog/lib/block-selectors.ts +29 -0
- package/templates/projects/blog/lib/selectors.ts +2 -9
- package/templates/projects/blog/package.json +4 -7
- package/templates/projects/crm/lib/block-selectors.ts +29 -0
- package/templates/projects/crm/lib/selectors.ts +2 -9
- package/templates/projects/crm/package.json +4 -4
- package/templates/projects/crm/templates/dashboard/(main)/layout.tsx +2 -17
- package/templates/projects/crm/templates/shared/CRMSidebarContext.tsx +23 -0
- package/templates/projects/productivity/entities/boards/boards.service.ts +18 -18
- package/templates/projects/productivity/entities/cards/cards.service.ts +18 -18
- package/templates/projects/productivity/entities/lists/lists.service.ts +18 -18
- package/templates/projects/productivity/lib/block-selectors.ts +29 -0
- package/templates/projects/productivity/lib/selectors.ts +2 -9
- package/templates/projects/productivity/package.json +4 -4
- package/templates/projects/starter/lib/block-selectors.ts +28 -0
- package/templates/projects/starter/lib/selectors.ts +2 -11
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nextsparkjs/core",
|
|
3
|
-
"version": "0.1.0-beta.
|
|
3
|
+
"version": "0.1.0-beta.194",
|
|
4
4
|
"description": "NextSpark - The complete SaaS framework for Next.js",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "NextSpark <hello@nextspark.dev>",
|
|
@@ -346,6 +346,10 @@
|
|
|
346
346
|
"types": "./dist/lib/selectors/index.d.ts",
|
|
347
347
|
"import": "./dist/lib/selectors/index.js"
|
|
348
348
|
},
|
|
349
|
+
"./selectors/*": {
|
|
350
|
+
"types": "./dist/lib/selectors/by-domain/*.d.ts",
|
|
351
|
+
"import": "./dist/lib/selectors/by-domain/*.js"
|
|
352
|
+
},
|
|
349
353
|
"./lib/selectors": {
|
|
350
354
|
"types": "./dist/lib/selectors/index.d.ts",
|
|
351
355
|
"import": "./dist/lib/selectors/index.js"
|
|
@@ -386,9 +390,9 @@
|
|
|
386
390
|
"scripts"
|
|
387
391
|
],
|
|
388
392
|
"peerDependencies": {
|
|
389
|
-
"next": "~16.3.
|
|
390
|
-
"react": "
|
|
391
|
-
"react-dom": "
|
|
393
|
+
"next": "~16.3.6",
|
|
394
|
+
"react": "^19.2.0",
|
|
395
|
+
"react-dom": "^19.2.0"
|
|
392
396
|
},
|
|
393
397
|
"devDependencies": {
|
|
394
398
|
"@better-fetch/fetch": "1.3.1",
|
|
@@ -492,7 +496,10 @@
|
|
|
492
496
|
"use-intl": "^4.9.1",
|
|
493
497
|
"uuid": "^13.0.0",
|
|
494
498
|
"zod": "^4.3.6",
|
|
495
|
-
"@nextsparkjs/ui": "0.1.0-beta.
|
|
499
|
+
"@nextsparkjs/ui": "0.1.0-beta.194"
|
|
500
|
+
},
|
|
501
|
+
"engines": {
|
|
502
|
+
"node": ">=22.14.0"
|
|
496
503
|
},
|
|
497
504
|
"scripts": {
|
|
498
505
|
"postinstall": "node scripts/postinstall.mjs || true",
|
|
@@ -192,6 +192,10 @@ async function main() {
|
|
|
192
192
|
console.log(' RESEND_API_KEY, RESEND_FROM_EMAIL; Google: GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET), or change auth.methods.')
|
|
193
193
|
console.log(' Credentials injected only at runtime: declare them with NEXTSPARK_AUTH_RUNTIME_ONLY=email,google;')
|
|
194
194
|
console.log(' they are validated again when the server starts and on every login request.')
|
|
195
|
+
if (result.diagnostics.some(({ code }) => code.endsWith('_PLACEHOLDER'))) {
|
|
196
|
+
console.log(' A placeholder value (re_..., your-google-client-id) is a value, not a missing one: NEXTSPARK_AUTH_RUNTIME_ONLY does not defer it.')
|
|
197
|
+
console.log(' Remove the placeholder lines from .env (and the build environment), or replace them with the real values.')
|
|
198
|
+
}
|
|
195
199
|
process.exit(1)
|
|
196
200
|
}
|
|
197
201
|
|
|
@@ -29,7 +29,7 @@ const RAW_CONTROL = /[\u0000-\u0009\u000b-\u001f\u007f-\u009f\u061c\u200e\u200f\
|
|
|
29
29
|
async function createProject(parent = tmpdir(), name = 'nextspark-build-output-test-') {
|
|
30
30
|
const root = await mkdtemp(join(parent, name))
|
|
31
31
|
await writeFile(join(root, 'nextspark.config.ts'), 'export default {}\n')
|
|
32
|
-
await writeFile(join(root, 'package.json'), '{"dependencies":{"next":"16.3.
|
|
32
|
+
await writeFile(join(root, 'package.json'), '{"dependencies":{"next":"16.3.6"}}')
|
|
33
33
|
await mkdir(join(root, 'src', 'app'), { recursive: true })
|
|
34
34
|
return root
|
|
35
35
|
}
|
|
@@ -17,7 +17,7 @@ async function project(files = {}) {
|
|
|
17
17
|
|
|
18
18
|
test('the nearest nextspark.config.ts owns every project source path', async () => {
|
|
19
19
|
const root = await project({
|
|
20
|
-
'package.json': JSON.stringify({ dependencies: { next: '16.3.
|
|
20
|
+
'package.json': JSON.stringify({ dependencies: { next: '16.3.6' } }),
|
|
21
21
|
'nextspark.config.ts': 'export default {}\n',
|
|
22
22
|
})
|
|
23
23
|
const nested = join(root, 'entities', 'tasks')
|
|
@@ -40,7 +40,7 @@ test('the nearest nextspark.config.ts owns every project source path', async ()
|
|
|
40
40
|
|
|
41
41
|
test('a next.config file cannot select a project without nextspark.config.ts', async () => {
|
|
42
42
|
const root = await project({
|
|
43
|
-
'package.json': JSON.stringify({ dependencies: { next: '16.3.
|
|
43
|
+
'package.json': JSON.stringify({ dependencies: { next: '16.3.6' } }),
|
|
44
44
|
'next.config.mjs': 'export default {}\n',
|
|
45
45
|
})
|
|
46
46
|
try {
|
|
@@ -66,7 +66,7 @@ test('an enabled packaged plugin resolves through the project dependency tree',
|
|
|
66
66
|
const root = await project({
|
|
67
67
|
'package.json': JSON.stringify({
|
|
68
68
|
dependencies: {
|
|
69
|
-
next: '16.3.
|
|
69
|
+
next: '16.3.6',
|
|
70
70
|
'@example/plugin-search': '1.0.0',
|
|
71
71
|
},
|
|
72
72
|
}),
|
|
@@ -98,7 +98,7 @@ test('an enabled packaged plugin that is not installed fails with an actionable
|
|
|
98
98
|
const root = await project({
|
|
99
99
|
'package.json': JSON.stringify({
|
|
100
100
|
dependencies: {
|
|
101
|
-
next: '16.3.
|
|
101
|
+
next: '16.3.6',
|
|
102
102
|
'@example/plugin-search': '1.0.0',
|
|
103
103
|
},
|
|
104
104
|
}),
|
|
@@ -119,7 +119,7 @@ test('a packaged plugin cannot use a path segment as its logical name', async ()
|
|
|
119
119
|
const root = await project({
|
|
120
120
|
'package.json': JSON.stringify({
|
|
121
121
|
dependencies: {
|
|
122
|
-
next: '16.3.
|
|
122
|
+
next: '16.3.6',
|
|
123
123
|
'@example/plugin-search': '1.0.0',
|
|
124
124
|
},
|
|
125
125
|
}),
|
|
@@ -146,7 +146,7 @@ test('a local plugin shadows an installed packaged plugin with the same NextSpar
|
|
|
146
146
|
const root = await project({
|
|
147
147
|
'package.json': JSON.stringify({
|
|
148
148
|
dependencies: {
|
|
149
|
-
next: '16.3.
|
|
149
|
+
next: '16.3.6',
|
|
150
150
|
'@example/plugin-search': '1.0.0',
|
|
151
151
|
},
|
|
152
152
|
}),
|
|
@@ -269,7 +269,7 @@ async function snapshot(root) {
|
|
|
269
269
|
async function buildableProject() {
|
|
270
270
|
const project = await directory()
|
|
271
271
|
await writeIn(project.root, 'nextspark.config.ts', 'export default {}\n')
|
|
272
|
-
await writeIn(project.root, 'package.json', '{"dependencies":{"next":"16.3.
|
|
272
|
+
await writeIn(project.root, 'package.json', '{"dependencies":{"next":"16.3.6"}}')
|
|
273
273
|
await writeIn(project.root, 'templates/pricing/page.tsx', 'export default function Pricing() { return null }\n')
|
|
274
274
|
return project
|
|
275
275
|
}
|
|
@@ -22,7 +22,7 @@ async function planFor(files) {
|
|
|
22
22
|
const root = temp()
|
|
23
23
|
try {
|
|
24
24
|
write(root, 'nextspark.config.ts', 'export default { plugins: [] }\n')
|
|
25
|
-
write(root, 'package.json', JSON.stringify({ name: 'wire', dependencies: { next: '16.3.
|
|
25
|
+
write(root, 'package.json', JSON.stringify({ name: 'wire', dependencies: { next: '16.3.6' } }))
|
|
26
26
|
write(root, 'config/theme.config.ts', "export const wireThemeConfig = { name: 'wire' }\n")
|
|
27
27
|
for (const [path, content] of Object.entries(files)) write(root, path, content)
|
|
28
28
|
const entities = await discoverAllEntities(getConfig(root), { includeCore: false })
|
|
@@ -73,7 +73,7 @@ const NOTES_CONFIG = `export const noteEntityConfig = {
|
|
|
73
73
|
function project(extra = {}) {
|
|
74
74
|
const root = temp()
|
|
75
75
|
write(root, 'nextspark.config.ts', 'export default { plugins: [] }\n')
|
|
76
|
-
write(root, 'package.json', JSON.stringify({ name: 'contracts-project', dependencies: { next: '16.3.
|
|
76
|
+
write(root, 'package.json', JSON.stringify({ name: 'contracts-project', dependencies: { next: '16.3.6' } }))
|
|
77
77
|
write(root, 'config/theme.config.ts', "export const contractsThemeConfig = { name: 'contracts' }\n")
|
|
78
78
|
write(root, 'entities/tasks/tasks.config.ts', TASKS_CONFIG)
|
|
79
79
|
write(root, 'entities/tasks/tasks.fields.ts', FIELDS)
|
|
@@ -201,7 +201,7 @@ export function renderEntity(contract, { source, shape } = {}) {
|
|
|
201
201
|
const lines = [CONTRACTS_HEADER]
|
|
202
202
|
if (source) lines.push(`// Source: ${source}`)
|
|
203
203
|
for (const warning of contract.warnings) lines.push(`// Warning: ${warning}`)
|
|
204
|
-
lines.push('', "import
|
|
204
|
+
lines.push('', "import * as z from 'zod'", "import type { EntityConfig } from '../schema/types'", "import { generateEntitySchemas } from '../schema/schema-generator'", `import { ${usedFromShape.join(', ')} } from '../schema/response-shape'`)
|
|
205
205
|
if (shared.length > 0) lines.push(`import { ${shared.join(', ')} } from '../fields'`)
|
|
206
206
|
lines.push('', `export const ${names.apiPath} = ${quote(`/api/v1/${contract.slug}`)}`, '')
|
|
207
207
|
|
|
@@ -246,7 +246,7 @@ export function renderEntity(contract, { source, shape } = {}) {
|
|
|
246
246
|
export function renderEnvelope() {
|
|
247
247
|
return `${CONTRACTS_HEADER}
|
|
248
248
|
|
|
249
|
-
import
|
|
249
|
+
import * as z from 'zod'
|
|
250
250
|
|
|
251
251
|
/** \`info\` of every response: the timestamp, plus whatever the endpoint adds (pagination, \`created\`). */
|
|
252
252
|
export const apiInfoSchema = z.looseObject({ timestamp: z.string() })
|
|
@@ -307,7 +307,7 @@ export const apiErrorResponseSchema = z.object({
|
|
|
307
307
|
export function renderFields() {
|
|
308
308
|
return `${CONTRACTS_HEADER}
|
|
309
309
|
|
|
310
|
-
import
|
|
310
|
+
import * as z from 'zod'
|
|
311
311
|
import { addressSchema, fileObjectSchema } from './schema/schema-generator'
|
|
312
312
|
import { mediaRefSchema } from './schema/media-ref'
|
|
313
313
|
|
|
@@ -28,7 +28,7 @@ const { target, content } = await emitFacade({
|
|
|
28
28
|
Every problem is a `FacadeEmitError` carrying all diagnostics (`code`, `file`, `line`, `exportName`,
|
|
29
29
|
`message`); nothing is dropped silently.
|
|
30
30
|
|
|
31
|
-
## Export table (Next.js 16.3.
|
|
31
|
+
## Export table (Next.js 16.3.6)
|
|
32
32
|
|
|
33
33
|
Source of truth: `next-route-exports.json`. The tests fail when the installed Next.js version differs,
|
|
34
34
|
when Next's `AppSegmentConfigSchemaKeys` or `HTTP_METHODS` change, and when this rendering is stale.
|
|
@@ -71,7 +71,7 @@ export { listSlugs as generateStaticParams }
|
|
|
71
71
|
Metadata image kinds accept Next's one-digit variants (`icon1`, `apple-icon9`, `opengraph-image2`,
|
|
72
72
|
`twitter-image3`), mapped to the base kind.
|
|
73
73
|
|
|
74
|
-
The emitter resolves `next/package.json` once (project first, then core) and accepts only `~16.3.
|
|
74
|
+
The emitter resolves `next/package.json` once (project first, then core) and accepts only `~16.3.6`: the table's
|
|
75
75
|
`next` is the verified baseline, and any stable 16.3.x patch at or above it is accepted, while other minors/majors,
|
|
76
76
|
lower patches and prereleases are refused (`NS_HOST_UNSUPPORTED_NEXT_VERSION`). It loads Next's segment config
|
|
77
77
|
schema from that same package root.
|
|
@@ -595,14 +595,14 @@ test('an aliased magic export is forwarded under its source local name', async (
|
|
|
595
595
|
assert.match(content, /^export \{ listSlugs as generateStaticParams \}$/m)
|
|
596
596
|
})
|
|
597
597
|
|
|
598
|
-
// --- fix round 3: ~16.3.
|
|
598
|
+
// --- fix round 3: ~baseline version range (baseline 16.3.6 since 0.1.0-beta.194) ---
|
|
599
599
|
|
|
600
600
|
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.
|
|
601
|
+
assert.equal(ROUTE_EXPORT_TABLE.next, '16.3.6')
|
|
602
|
+
for (const version of ['16.3.6', '16.3.7', '16.3.9', '16.3.10', '16.3.123', '16.3.6+build.1', '16.3.7+sha.abc-1']) assert.equal(isSupportedNextVersion(version), true, version)
|
|
603
603
|
for (const version of [
|
|
604
|
-
'16.3.4', '16.3.0', '16.4.0', '16.2.9', '17.0.0', '15.3.
|
|
605
|
-
'16.3.
|
|
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.6-rc.1', '16.3.7-canary.0+build.1',
|
|
605
|
+
'16.3.5+build.1', '16.3.6+', '16.3.6+bad..meta', '16.3', 'latest', '', undefined,
|
|
606
606
|
]) {
|
|
607
607
|
assert.equal(isSupportedNextVersion(version), false, String(version))
|
|
608
608
|
}
|
|
@@ -625,7 +625,7 @@ test('a later 16.3 patch is accepted and its own schema internal is used', async
|
|
|
625
625
|
}
|
|
626
626
|
})
|
|
627
627
|
|
|
628
|
-
for (const version of ['16.3.
|
|
628
|
+
for (const version of ['16.3.5', '16.4.0', '17.0.0', '16.3.6-canary.0']) {
|
|
629
629
|
test(`${DIAGNOSTICS.UNSUPPORTED_NEXT_VERSION}: next@${version} is refused`, async () => {
|
|
630
630
|
const dir = await fakeNextProject(version, "exports.AppSegmentConfigSchemaKeys = []\nexports.parseAppSegmentConfig = () => ({})\n")
|
|
631
631
|
try {
|
|
@@ -634,7 +634,7 @@ for (const version of ['16.3.4', '16.4.0', '17.0.0', '16.3.6-canary.0']) {
|
|
|
634
634
|
error => {
|
|
635
635
|
assert.deepEqual(error.diagnostics.map(d => d.code), [DIAGNOSTICS.UNSUPPORTED_NEXT_VERSION])
|
|
636
636
|
assert.match(error.message, new RegExp(`next@${version.replace(/\./g, '\\.')}`))
|
|
637
|
-
assert.match(error.message, /next@~16\.3\.
|
|
637
|
+
assert.match(error.message, /next@~16\.3\.6/)
|
|
638
638
|
return true
|
|
639
639
|
}
|
|
640
640
|
)
|
|
@@ -646,9 +646,9 @@ for (const version of ['16.3.4', '16.4.0', '17.0.0', '16.3.6-canary.0']) {
|
|
|
646
646
|
|
|
647
647
|
// --- fix round 4: build metadata ----------------------------------------------
|
|
648
648
|
|
|
649
|
-
test('a Next.js version with build metadata (16.3.
|
|
649
|
+
test('a Next.js version with build metadata (16.3.6+build.1) is accepted end to end', async () => {
|
|
650
650
|
const marker = "exports.AppSegmentConfigSchemaKeys = []\nexports.parseAppSegmentConfig = () => ({})\n"
|
|
651
|
-
const dir = await fakeNextProject('16.3.
|
|
651
|
+
const dir = await fakeNextProject('16.3.6+build.1', marker)
|
|
652
652
|
try {
|
|
653
653
|
assert.equal(loadNextSegmentConfig(dir).error, undefined)
|
|
654
654
|
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.6' } }))
|
|
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.6' } }))
|
|
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.6' } }))
|
|
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.6')
|
|
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
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { test } from 'node:test'
|
|
2
2
|
import assert from 'node:assert/strict'
|
|
3
3
|
import { spawn } from 'node:child_process'
|
|
4
|
-
import { existsSync, mkdirSync, mkdtempSync, readdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
|
|
4
|
+
import { chmodSync, existsSync, mkdirSync, mkdtempSync, readdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
|
|
5
5
|
import { hostname, tmpdir } from 'node:os'
|
|
6
6
|
import { join } from 'node:path'
|
|
7
7
|
|
|
@@ -139,6 +139,69 @@ test('crash mid-publish (SIGKILL): --check reports the host as not fresh, and th
|
|
|
139
139
|
}
|
|
140
140
|
})
|
|
141
141
|
|
|
142
|
+
test('what a killed writer leaves (temporary files, a staging directory) is swept by the next prepare instead of blocking it or piling up', async () => {
|
|
143
|
+
const host = tempHost({ 'templates/about/page.tsx': PAGE('About') })
|
|
144
|
+
try {
|
|
145
|
+
const config = hostConfig(host.root)
|
|
146
|
+
await prepareHost(config, { mode: 'production' })
|
|
147
|
+
const leftovers = [
|
|
148
|
+
'src/app/(public)/.page.tsx.4242.0a1b2c3d.nextspark-tmp',
|
|
149
|
+
'.nextspark/registries/.example.ts.4242.0a1b2c3d.nextspark-tmp',
|
|
150
|
+
'.nextspark/contracts/index.ts.nextspark-tmp',
|
|
151
|
+
'.nextspark/staging-AbC123/registries/example.ts',
|
|
152
|
+
]
|
|
153
|
+
const kept = ['.nextspark/my-notes.txt', '.nextspark/staging-notes/keep.txt']
|
|
154
|
+
for (const path of [...leftovers, ...kept]) write(host.hostRoot, path, 'x\n')
|
|
155
|
+
|
|
156
|
+
// A temporary file in src/app used to read as "a file NextSpark did not generate": prepare refused to run
|
|
157
|
+
const published = await prepareHost(config, { mode: 'production' })
|
|
158
|
+
assert.deepEqual(published.written, [])
|
|
159
|
+
for (const path of leftovers) assert.equal(existsSync(join(host.hostRoot, path)), false, `${path} is swept`)
|
|
160
|
+
assert.equal(existsSync(join(host.hostRoot, '.nextspark/staging-AbC123')), false)
|
|
161
|
+
// Only the writers' own names go: anything else is the user's
|
|
162
|
+
for (const path of kept) assert.equal(existsSync(join(host.hostRoot, path)), true, `${path} stays`)
|
|
163
|
+
write(host.hostRoot, 'src/app/(public)/page.tsx.nextspark-tmp', 'x\n')
|
|
164
|
+
await assert.rejects(() => prepareHost(config, { mode: 'production' }), error => error instanceof PrepareError && codes(error).includes(GENERATION_DIAGNOSTICS.FOREIGN))
|
|
165
|
+
assert.equal(existsSync(join(host.hostRoot, 'src/app/(public)/page.tsx.nextspark-tmp')), true)
|
|
166
|
+
} finally {
|
|
167
|
+
host.cleanup()
|
|
168
|
+
}
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
test('a scratch registry file a killed writer left is swept and the next prepare completes', async () => {
|
|
172
|
+
const host = tempHost({ 'templates/about/page.tsx': PAGE('About') })
|
|
173
|
+
try {
|
|
174
|
+
const config = hostConfig(host.root, { registries: manyRegistries(20, 'old') })
|
|
175
|
+
await prepareHost(config, { mode: 'production' })
|
|
176
|
+
const scratch = '.nextspark/registries/.r00003.ts.4242.0a1b2c3d.nextspark-tmp'
|
|
177
|
+
write(host.hostRoot, scratch, 'half written')
|
|
178
|
+
const fresh = hostConfig(host.root, { registries: manyRegistries(20, 'new') })
|
|
179
|
+
await prepareHost(fresh, { mode: 'production' })
|
|
180
|
+
assert.equal(existsSync(join(host.hostRoot, scratch)), false)
|
|
181
|
+
assert.deepEqual(await checkHost(fresh), { ok: true, problems: [] })
|
|
182
|
+
} finally {
|
|
183
|
+
host.cleanup()
|
|
184
|
+
}
|
|
185
|
+
})
|
|
186
|
+
|
|
187
|
+
test('the sweep never enters backups or migrate-rollback, and an unreadable directory under .nextspark does not crash prepare', async () => {
|
|
188
|
+
const host = tempHost({ 'templates/about/page.tsx': PAGE('About') })
|
|
189
|
+
const locked = join(host.hostRoot, '.nextspark/unreadable')
|
|
190
|
+
try {
|
|
191
|
+
const config = hostConfig(host.root)
|
|
192
|
+
await prepareHost(config, { mode: 'production' })
|
|
193
|
+
const kept = ['.nextspark/backups/20260101/src/app/.page.tsx.1.0a1b2c3d.nextspark-tmp', '.nextspark/backups/20260101/x.nextspark-tmp', '.nextspark/migrate-rollback/y.nextspark-tmp']
|
|
194
|
+
for (const path of kept) write(host.hostRoot, path, 'x\n')
|
|
195
|
+
write(host.hostRoot, '.nextspark/unreadable/z.nextspark-tmp', 'x\n')
|
|
196
|
+
if (process.getuid?.() !== 0) chmodSync(locked, 0)
|
|
197
|
+
await prepareHost(config, { mode: 'production' })
|
|
198
|
+
for (const path of kept) assert.equal(existsSync(join(host.hostRoot, path)), true, `${path} stays`)
|
|
199
|
+
} finally {
|
|
200
|
+
try { chmodSync(locked, 0o755) } catch {}
|
|
201
|
+
host.cleanup()
|
|
202
|
+
}
|
|
203
|
+
})
|
|
204
|
+
|
|
142
205
|
test('registries: a file in place of a generated registry is adopted only when a NextSpark registry build wrote it', async () => {
|
|
143
206
|
const cases = [
|
|
144
207
|
['byte-identical', REGISTRY, 'adopted'],
|
|
@@ -251,7 +314,7 @@ test('an npm-installed core ships only dist/: the manifest, variants and route m
|
|
|
251
314
|
test('the project config is read again for every generation: enabling a plugin in nextspark.config.ts adds its routes', async () => {
|
|
252
315
|
const root = mkdtempSync(join(tmpdir(), 'nextspark-config-reload-'))
|
|
253
316
|
try {
|
|
254
|
-
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.6' } }))
|
|
255
318
|
write(root, 'nextspark.config.ts', 'export default { plugins: [] }\n')
|
|
256
319
|
write(root, 'plugins/search/plugin.config.ts', 'export const searchPluginConfig = {}\n')
|
|
257
320
|
write(root, 'plugins/search/templates/search/page.tsx', PAGE('Search'))
|
|
@@ -284,8 +284,8 @@ 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.
|
|
288
|
-
* patches and prereleases (16.3.
|
|
287
|
+
* above the baseline is accepted (16.3.6, 16.3.9, 16.3.6+build.1), while other minors/majors, lower
|
|
288
|
+
* patches and prereleases (16.3.7-canary.0) are refused.
|
|
289
289
|
*/
|
|
290
290
|
export function isSupportedNextVersion(version, baseline = ROUTE_EXPORT_TABLE.next) {
|
|
291
291
|
// Stable versions only; build metadata (`+build.1`) is allowed, as npm's `~` range allows it.
|
|
@@ -438,6 +438,60 @@ export function acquireLock(hostRoot, { staleAfterMs = DEFAULT_STALE_LOCK_MS, at
|
|
|
438
438
|
throw lockedError(path)
|
|
439
439
|
}
|
|
440
440
|
|
|
441
|
+
// ---------------------------------------------------------------------------
|
|
442
|
+
// What a killed writer leaves behind
|
|
443
|
+
// ---------------------------------------------------------------------------
|
|
444
|
+
|
|
445
|
+
// The names of the scratch files and directories the writers create: `writeAtomically` (below),
|
|
446
|
+
// the contracts publisher (`<file>.nextspark-tmp`) and the registry build's staging directory
|
|
447
|
+
// (`fs.mkdtempSync('.nextspark/staging-')`).
|
|
448
|
+
const TEMPORARY_FILE = /^\..+\.\d+\.[0-9a-f]{8}\.nextspark-tmp$/
|
|
449
|
+
const CONTRACTS_TEMPORARY_FILE = /\.nextspark-tmp$/
|
|
450
|
+
const STAGING_DIR = /^staging-[A-Za-z0-9]{6}$/
|
|
451
|
+
|
|
452
|
+
/**
|
|
453
|
+
* Remove what a writer that was killed (SIGKILL, power loss) between creating a scratch file and
|
|
454
|
+
* renaming it leaves behind: its temporary files under `src/app` and `.nextspark`, and a registry
|
|
455
|
+
* build's staging directory. Call it with the lock held, when no live writer can own one of them.
|
|
456
|
+
* Without it a temporary file in `src/app` is a file "NextSpark did not generate", so the next
|
|
457
|
+
* prepare refuses to run until someone deletes it by hand, and the others stay forever.
|
|
458
|
+
* Only names the writers use are touched, never through a symlink; returns the paths removed.
|
|
459
|
+
*/
|
|
460
|
+
export function sweepInterruptedWrites(hostRoot, { app = true } = {}) {
|
|
461
|
+
const fs = projectFiles(hostRoot)
|
|
462
|
+
const removed = []
|
|
463
|
+
const entries = dir => {
|
|
464
|
+
try {
|
|
465
|
+
return lstatOrNull(join(hostRoot, dir))?.isDirectory() ? readdirSync(join(hostRoot, dir), { withFileTypes: true }) : []
|
|
466
|
+
} catch {
|
|
467
|
+
return [] // an unreadable directory is skipped: the build reports it
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
const visit = (dir, isTemporary, recursive) => {
|
|
471
|
+
for (const entry of entries(dir)) {
|
|
472
|
+
const path = `${dir}/${entry.name}`
|
|
473
|
+
if (entry.isDirectory()) {
|
|
474
|
+
if (recursive) visit(path, isTemporary, true)
|
|
475
|
+
} else if (entry.isFile() && isTemporary.test(entry.name)) {
|
|
476
|
+
fs.unlinkSync(join(hostRoot, path))
|
|
477
|
+
removed.push(path)
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
// Only the places the writers use; never `.nextspark/backups` or `.nextspark/migrate-rollback`.
|
|
482
|
+
// src/app belongs to the host only when it generates it (prepareContractsOnly leaves it alone).
|
|
483
|
+
if (app) visit(APP_DIR, TEMPORARY_FILE, true)
|
|
484
|
+
visit('.nextspark', TEMPORARY_FILE, false)
|
|
485
|
+
visit(REGISTRIES_DIR, TEMPORARY_FILE, true)
|
|
486
|
+
visit('.nextspark/contracts', CONTRACTS_TEMPORARY_FILE, true)
|
|
487
|
+
for (const entry of entries('.nextspark')) {
|
|
488
|
+
if (!entry.isDirectory() || !STAGING_DIR.test(entry.name)) continue
|
|
489
|
+
fs.rmSync(join(hostRoot, '.nextspark', entry.name), { recursive: true, force: true })
|
|
490
|
+
removed.push(`.nextspark/${entry.name}`)
|
|
491
|
+
}
|
|
492
|
+
return removed
|
|
493
|
+
}
|
|
494
|
+
|
|
441
495
|
// ---------------------------------------------------------------------------
|
|
442
496
|
// Publication
|
|
443
497
|
// ---------------------------------------------------------------------------
|
|
@@ -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.6",
|
|
4
|
+
"supportedNext": "~16.3.6 (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)",
|
|
@@ -58,6 +58,7 @@ import {
|
|
|
58
58
|
preflight,
|
|
59
59
|
publishGeneration,
|
|
60
60
|
readGeneration,
|
|
61
|
+
sweepInterruptedWrites,
|
|
61
62
|
validateFiles,
|
|
62
63
|
writeOwnedFile,
|
|
63
64
|
} from './generation.mjs'
|
|
@@ -209,6 +210,7 @@ export async function predictHost(config, { devStatus = false } = {}) {
|
|
|
209
210
|
export async function prepareHost(config, { mode = 'development', devStatus = false, cache, staleAfterMs, reportFailure = false, changed = [] } = {}) {
|
|
210
211
|
const release = acquireLock(config.hostRoot, { staleAfterMs })
|
|
211
212
|
try {
|
|
213
|
+
sweepInterruptedWrites(config.hostRoot)
|
|
212
214
|
return await generateAndPublish(config, { mode, devStatus, cache })
|
|
213
215
|
} catch (error) {
|
|
214
216
|
// `nextspark dev`: the failure goes to the browser under the same lock, so no other writer's
|
|
@@ -283,6 +285,7 @@ export async function prepareContractsOnly(config, { staleAfterMs } = {}) {
|
|
|
283
285
|
if (!config.contracts) throw new PrepareError([{ code: 'NS_CONTRACTS_UNAVAILABLE', message: 'this project has no contracts step' }])
|
|
284
286
|
const release = acquireLock(config.hostRoot, { staleAfterMs })
|
|
285
287
|
try {
|
|
288
|
+
sweepInterruptedWrites(config.hostRoot, { app: false })
|
|
286
289
|
const contracts = await planContractsStep(config)
|
|
287
290
|
return publishContractsStep(config, contracts)
|
|
288
291
|
} catch (error) {
|