polen 0.10.0-next.19 → 0.10.0-next.21
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/build/api/api.d.ts +1 -0
- package/build/api/api.d.ts.map +1 -1
- package/build/api/api.js +1 -0
- package/build/api/api.js.map +1 -1
- package/build/api/builder/builder.d.ts +8 -2
- package/build/api/builder/builder.d.ts.map +1 -1
- package/build/api/builder/builder.js +6 -20
- package/build/api/builder/builder.js.map +1 -1
- package/build/api/config/configurator.d.ts +31 -8
- package/build/api/config/configurator.d.ts.map +1 -1
- package/build/api/config/configurator.js +9 -10
- package/build/api/config/configurator.js.map +1 -1
- package/build/api/config/merge.d.ts.map +1 -1
- package/build/api/config/merge.js +14 -21
- package/build/api/config/merge.js.map +1 -1
- package/build/api/content/scan.js +1 -1
- package/build/api/content/sidebar.js +4 -4
- package/build/api/project/index.d.ts +2 -0
- package/build/api/project/index.d.ts.map +1 -0
- package/build/api/project/index.js +2 -0
- package/build/api/project/index.js.map +1 -0
- package/build/api/project/validate.d.ts +11 -0
- package/build/api/project/validate.d.ts.map +1 -0
- package/build/api/project/validate.js +30 -0
- package/build/api/project/validate.js.map +1 -0
- package/build/api/vite/plugins/build.js +1 -1
- package/build/api/vite/plugins/core.d.ts.map +1 -1
- package/build/api/vite/plugins/core.js +1 -0
- package/build/api/vite/plugins/core.js.map +1 -1
- package/build/api/vite/plugins/serve.js +1 -1
- package/build/api/vite/plugins/serve.js.map +1 -1
- package/build/cli/_/parameters.d.ts +6 -0
- package/build/cli/_/parameters.d.ts.map +1 -0
- package/build/cli/_/parameters.js +9 -0
- package/build/cli/_/parameters.js.map +1 -0
- package/build/cli/commands/build.js +17 -10
- package/build/cli/commands/build.js.map +1 -1
- package/build/cli/commands/config/create.js +12 -1
- package/build/cli/commands/config/create.js.map +1 -1
- package/build/cli/commands/create.js +3 -5
- package/build/cli/commands/create.js.map +1 -1
- package/build/cli/commands/dev.js +14 -4
- package/build/cli/commands/dev.js.map +1 -1
- package/build/lib/graphql-document/components/GraphQLDocument.d.ts.map +1 -1
- package/build/lib/graphql-document/components/GraphQLDocument.js +3 -1
- package/build/lib/graphql-document/components/GraphQLDocument.js.map +1 -1
- package/build/lib/kit-temp.d.ts +33 -0
- package/build/lib/kit-temp.d.ts.map +1 -1
- package/build/lib/kit-temp.js +48 -0
- package/build/lib/kit-temp.js.map +1 -1
- package/build/lib/react-router-aid/get-paths-patterns.js +1 -1
- package/build/project-data.d.ts +1 -0
- package/build/project-data.d.ts.map +1 -1
- package/build/template/server/main.js +2 -1
- package/build/template/server/main.js.map +1 -1
- package/package.json +1 -1
- package/src/api/api.ts +1 -0
- package/src/api/builder/builder.ts +13 -23
- package/src/api/config/configurator.ts +41 -19
- package/src/api/config/merge.ts +18 -21
- package/src/api/content/scan.ts +1 -1
- package/src/api/content/sidebar.ts +4 -4
- package/src/api/project/index.ts +1 -0
- package/src/api/project/validate.ts +41 -0
- package/src/api/vite/plugins/build.ts +1 -1
- package/src/api/vite/plugins/core.ts +1 -0
- package/src/api/vite/plugins/serve.ts +1 -1
- package/src/cli/_/parameters.ts +10 -0
- package/src/cli/commands/build.ts +25 -10
- package/src/cli/commands/config/create.ts +18 -1
- package/src/cli/commands/create.ts +4 -4
- package/src/cli/commands/dev.ts +19 -5
- package/src/lib/graphql-document/components/GraphQLDocument.tsx +8 -6
- package/src/lib/kit-temp.test.ts +85 -1
- package/src/lib/kit-temp.ts +51 -0
- package/src/lib/react-router-aid/get-paths-patterns.ts +1 -1
- package/src/project-data.ts +1 -0
- package/src/template/server/main.ts +2 -1
package/src/api/config/merge.ts
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
import { Vite } from '#dep/vite/index'
|
2
|
+
import { spreadShallow } from '#lib/kit-temp'
|
2
3
|
import type { ConfigInput } from './configurator.js'
|
3
4
|
|
4
5
|
/**
|
@@ -13,40 +14,36 @@ export const mergeInputs = (
|
|
13
14
|
return base
|
14
15
|
}
|
15
16
|
|
16
|
-
const merged: ConfigInput =
|
17
|
-
...base,
|
18
|
-
...overrides,
|
19
|
-
}
|
17
|
+
const merged: ConfigInput = spreadShallow(base, overrides)
|
20
18
|
|
21
19
|
// Merge schema if both have it
|
22
|
-
if (base.schema
|
20
|
+
if (base.schema ?? overrides.schema) {
|
23
21
|
merged.schema = overrides.schema ?? base.schema
|
24
22
|
}
|
25
23
|
|
26
24
|
// Merge build config
|
27
|
-
if (base.build
|
28
|
-
merged.build =
|
29
|
-
|
30
|
-
|
31
|
-
|
25
|
+
if (base.build ?? overrides.build) {
|
26
|
+
merged.build = spreadShallow(base.build, overrides.build)
|
27
|
+
}
|
28
|
+
|
29
|
+
// Merge server config
|
30
|
+
if (base.server ?? overrides.server) {
|
31
|
+
merged.server = spreadShallow(base.server, overrides.server)
|
32
32
|
}
|
33
33
|
|
34
34
|
// Merge advanced config
|
35
|
-
if (base.advanced
|
36
|
-
merged.advanced =
|
37
|
-
...base.advanced,
|
38
|
-
...overrides.advanced,
|
39
|
-
}
|
35
|
+
if (base.advanced ?? overrides.advanced) {
|
36
|
+
merged.advanced = spreadShallow(base.advanced, overrides.advanced)
|
40
37
|
|
41
38
|
// Merge advanced.watch config
|
42
|
-
if (base.advanced?.watch
|
43
|
-
merged.advanced.watch =
|
44
|
-
|
45
|
-
|
46
|
-
|
39
|
+
if (base.advanced?.watch ?? overrides.advanced?.watch) {
|
40
|
+
merged.advanced.watch = spreadShallow(
|
41
|
+
base.advanced?.watch,
|
42
|
+
overrides.advanced?.watch,
|
43
|
+
)
|
47
44
|
|
48
45
|
// Merge watch.also arrays
|
49
|
-
if (base.advanced?.watch?.also
|
46
|
+
if (base.advanced?.watch?.also ?? overrides.advanced?.watch?.also) {
|
50
47
|
merged.advanced.watch.also = [
|
51
48
|
...(base.advanced?.watch?.also ?? []),
|
52
49
|
...(overrides.advanced?.watch?.also ?? []),
|
package/src/api/content/scan.ts
CHANGED
@@ -73,7 +73,7 @@ export const scan = async (options: {
|
|
73
73
|
// Update pages to have virtual root as parent if they don't have one
|
74
74
|
const pagesWithVirtualRoot = pagesWithIds.map(page => ({
|
75
75
|
...page,
|
76
|
-
parentId: page.parentId
|
76
|
+
parentId: page.parentId ?? `__virtual_root__`,
|
77
77
|
}))
|
78
78
|
|
79
79
|
tree = Tree.fromList([virtualRoot, ...pagesWithVirtualRoot])
|
@@ -173,13 +173,13 @@ const buildSidebarForDirectory = (topLevelDir: string, pages: Page[]): Sidebar =
|
|
173
173
|
}
|
174
174
|
|
175
175
|
// Process top-level pages (direct children of the directory)
|
176
|
-
const topLevelPages = pagesByParent.get(topLevelDir)
|
176
|
+
const topLevelPages = pagesByParent.get(topLevelDir) ?? []
|
177
177
|
|
178
178
|
// Sort pages by their directory order (extracted from file path)
|
179
179
|
const sortedTopLevelPages = [...topLevelPages].sort((a, b) => {
|
180
180
|
// For sections, we need to look at the directory name in the file path
|
181
|
-
const dirA = a.route.file.path.relative.dir.split(`/`).pop()
|
182
|
-
const dirB = b.route.file.path.relative.dir.split(`/`).pop()
|
181
|
+
const dirA = a.route.file.path.relative.dir.split(`/`).pop() ?? ``
|
182
|
+
const dirB = b.route.file.path.relative.dir.split(`/`).pop() ?? ``
|
183
183
|
|
184
184
|
// Extract order from directory names like "10_b", "20_c"
|
185
185
|
const orderMatchA = /^(\d+)[_-]/.exec(dirA)
|
@@ -197,7 +197,7 @@ const buildSidebarForDirectory = (topLevelDir: string, pages: Page[]): Sidebar =
|
|
197
197
|
for (const page of sortedTopLevelPages) {
|
198
198
|
const pageName = page.route.logical.path[page.route.logical.path.length - 1]!
|
199
199
|
const childPath = page.route.logical.path.join(`/`)
|
200
|
-
const childPages = pagesByParent.get(childPath)
|
200
|
+
const childPages = pagesByParent.get(childPath) ?? []
|
201
201
|
|
202
202
|
if (childPages.length > 0 || FileRouter.routeIsFromIndexFile(page.route)) {
|
203
203
|
// This is a section (has children or is an index page for a subdirectory)
|
@@ -0,0 +1 @@
|
|
1
|
+
export * from './validate.js'
|
@@ -0,0 +1,41 @@
|
|
1
|
+
import { Fs } from '@wollybeard/kit'
|
2
|
+
import consola from 'consola'
|
3
|
+
|
4
|
+
export interface ValidateProjectOptions {
|
5
|
+
mustExist?: boolean
|
6
|
+
mustBeEmpty?: boolean
|
7
|
+
silent?: boolean
|
8
|
+
}
|
9
|
+
|
10
|
+
/**
|
11
|
+
* Validate a project directory for Polen operations.
|
12
|
+
* Used by CLI commands to ensure valid project paths.
|
13
|
+
*/
|
14
|
+
export async function validateProjectDirectory(
|
15
|
+
dir: string,
|
16
|
+
options: ValidateProjectOptions = {},
|
17
|
+
): Promise<boolean> {
|
18
|
+
const { mustExist = true, mustBeEmpty = false, silent = false } = options
|
19
|
+
|
20
|
+
const stat = await Fs.stat(dir)
|
21
|
+
|
22
|
+
if (Fs.isNotFoundError(stat)) {
|
23
|
+
if (mustExist) {
|
24
|
+
if (!silent) consola.error(`Project directory does not exist: ${dir}`)
|
25
|
+
return false
|
26
|
+
}
|
27
|
+
return true
|
28
|
+
}
|
29
|
+
|
30
|
+
if (!stat.isDirectory()) {
|
31
|
+
if (!silent) consola.error(`Project path is not a directory: ${dir}`)
|
32
|
+
return false
|
33
|
+
}
|
34
|
+
|
35
|
+
if (mustBeEmpty && !(await Fs.isEmptyDir(dir))) {
|
36
|
+
if (!silent) consola.error(`Project directory is not empty: ${dir}`)
|
37
|
+
return false
|
38
|
+
}
|
39
|
+
|
40
|
+
return true
|
41
|
+
}
|
@@ -162,7 +162,7 @@ const BuildManifest = (config: Config.Config): Vite.Plugin => {
|
|
162
162
|
const manifest: PolenBuildManifest = {
|
163
163
|
type: config.build.architecture === `ssr` ? `ssr` : `ssg`,
|
164
164
|
version: packageJson.version,
|
165
|
-
basePath: config.build.base
|
165
|
+
basePath: config.build.base ?? `/`,
|
166
166
|
}
|
167
167
|
|
168
168
|
// Emit the manifest as an asset
|
@@ -209,6 +209,7 @@ export const Core = (config: Config.Config): Vite.PluginOption[] => {
|
|
209
209
|
paths: config.paths.project,
|
210
210
|
navbar, // Complete navbar with schema and pages
|
211
211
|
server: {
|
212
|
+
port: config.server.port,
|
212
213
|
static: {
|
213
214
|
// todo
|
214
215
|
// relative from CWD of process that boots n1ode server
|
@@ -1,11 +1,16 @@
|
|
1
1
|
// @ts-nocheck
|
2
2
|
import { Api } from '#api/index'
|
3
|
-
import {
|
3
|
+
import { projectParameter } from '#cli/_/parameters'
|
4
|
+
import { ensureOptionalAbsoluteWithCwd } from '#lib/kit-temp'
|
4
5
|
import { Command } from '@molt/command'
|
5
6
|
import { z } from 'zod'
|
6
7
|
|
7
8
|
const args = Command.create()
|
8
9
|
.parameter(`--debug -d`, z.boolean().default(false))
|
10
|
+
.parameter(
|
11
|
+
`--project -p`,
|
12
|
+
projectParameter,
|
13
|
+
)
|
9
14
|
.parameter(
|
10
15
|
`--architecture -a`,
|
11
16
|
Api.Config.BuildArchitecture.default('ssg').describe('Which kind of application architecture to output.'),
|
@@ -14,6 +19,10 @@ const args = Command.create()
|
|
14
19
|
`--base -b`,
|
15
20
|
z.string().optional().describe('Base public path for deployment (e.g., /my-project/)'),
|
16
21
|
)
|
22
|
+
.parameter(
|
23
|
+
`--port`,
|
24
|
+
z.number().optional().describe('Default port for the SSR application'),
|
25
|
+
)
|
17
26
|
.settings({
|
18
27
|
parameters: {
|
19
28
|
environment: {
|
@@ -27,14 +36,20 @@ const args = Command.create()
|
|
27
36
|
})
|
28
37
|
.parse()
|
29
38
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
39
|
+
const dir = ensureOptionalAbsoluteWithCwd(args.project)
|
40
|
+
|
41
|
+
if (!await Api.Project.validateProjectDirectory(dir)) {
|
42
|
+
process.exit(1)
|
43
|
+
}
|
44
|
+
|
36
45
|
await Api.Builder.build({
|
37
|
-
|
38
|
-
|
39
|
-
|
46
|
+
dir,
|
47
|
+
architecture: args.architecture,
|
48
|
+
base: args.base,
|
49
|
+
server: {
|
50
|
+
port: args.port,
|
51
|
+
},
|
52
|
+
advanced: {
|
53
|
+
debug: args.debug,
|
54
|
+
},
|
40
55
|
})
|
@@ -1,6 +1,23 @@
|
|
1
|
+
import { Api } from '#api/index'
|
2
|
+
import { projectParameter } from '#cli/_/parameters'
|
3
|
+
import { ensureOptionalAbsoluteWithCwd } from '#lib/kit-temp'
|
4
|
+
import { Command } from '@molt/command'
|
1
5
|
import { Fs, Path } from '@wollybeard/kit'
|
2
6
|
import consola from 'consola'
|
3
7
|
|
8
|
+
const args = Command.create()
|
9
|
+
.parameter(
|
10
|
+
`--project -p`,
|
11
|
+
projectParameter,
|
12
|
+
)
|
13
|
+
.parse()
|
14
|
+
|
15
|
+
const dir = ensureOptionalAbsoluteWithCwd(args.project)
|
16
|
+
|
17
|
+
if (!await Api.Project.validateProjectDirectory(dir)) {
|
18
|
+
process.exit(1)
|
19
|
+
}
|
20
|
+
|
4
21
|
const fileName = 'polen.config.ts'
|
5
22
|
const fileContent = `import { Polen } from 'polen'
|
6
23
|
|
@@ -9,7 +26,7 @@ export default Polen.defineConfig({
|
|
9
26
|
})
|
10
27
|
`
|
11
28
|
|
12
|
-
const filePath = Path.join(
|
29
|
+
const filePath = Path.join(dir, fileName)
|
13
30
|
|
14
31
|
const exists = await Fs.exists(filePath)
|
15
32
|
if (exists) {
|
@@ -1,4 +1,5 @@
|
|
1
1
|
// @ts-nocheck
|
2
|
+
import { Api } from '#api/index'
|
2
3
|
import { Command } from '@molt/command'
|
3
4
|
import { Err, Fs, Manifest, Name, Path, Str } from '@wollybeard/kit'
|
4
5
|
import * as Ansis from 'ansis'
|
@@ -48,10 +49,9 @@ const args = Command
|
|
48
49
|
|
49
50
|
const getProjectRoot = async (): Promise<string> => {
|
50
51
|
if (args.path) {
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
consola.error(`The given path ${args.path} already exists and is not an empty directory`)
|
52
|
+
if (await Api.Project.validateProjectDirectory(args.path, { mustExist: false, mustBeEmpty: true })) {
|
53
|
+
return args.path
|
54
|
+
}
|
55
55
|
process.exit(1)
|
56
56
|
}
|
57
57
|
|
package/src/cli/commands/dev.ts
CHANGED
@@ -1,9 +1,10 @@
|
|
1
1
|
// @ts-nocheck
|
2
2
|
import { Api } from '#api/index'
|
3
|
+
import { projectParameter } from '#cli/_/parameters'
|
3
4
|
import { Vite } from '#dep/vite/index'
|
4
|
-
import {
|
5
|
+
import { ensureOptionalAbsoluteWithCwd } from '#lib/kit-temp'
|
5
6
|
import { Command } from '@molt/command'
|
6
|
-
import { Err
|
7
|
+
import { Err } from '@wollybeard/kit'
|
7
8
|
import { z } from 'zod'
|
8
9
|
|
9
10
|
const args = Command.create()
|
@@ -11,12 +12,16 @@ const args = Command.create()
|
|
11
12
|
.parameter(
|
12
13
|
`--project -p`,
|
13
14
|
// @ts-expect-error
|
14
|
-
|
15
|
+
projectParameter,
|
15
16
|
)
|
16
17
|
.parameter(
|
17
18
|
`--base -b`,
|
18
19
|
z.string().optional().describe('Base public path for deployment (e.g., /my-project/)'),
|
19
20
|
)
|
21
|
+
.parameter(
|
22
|
+
`--port`,
|
23
|
+
z.number().optional().describe('Port to run the development server on'),
|
24
|
+
)
|
20
25
|
.settings({
|
21
26
|
parameters: {
|
22
27
|
environment: {
|
@@ -30,12 +35,21 @@ const args = Command.create()
|
|
30
35
|
})
|
31
36
|
.parse()
|
32
37
|
|
33
|
-
const dir = ensureOptionalAbsoluteWithCwd(args.project)
|
38
|
+
const dir = ensureOptionalAbsoluteWithCwd(args.project)
|
39
|
+
|
40
|
+
if (!await Api.Project.validateProjectDirectory(dir)) {
|
41
|
+
process.exit(1)
|
42
|
+
}
|
34
43
|
|
35
44
|
const viteUserConfig = await Api.ConfigResolver.fromFile({
|
36
45
|
dir,
|
37
46
|
overrides: {
|
38
|
-
|
47
|
+
build: {
|
48
|
+
base: args.base,
|
49
|
+
},
|
50
|
+
server: {
|
51
|
+
port: args.port,
|
52
|
+
},
|
39
53
|
advanced: {
|
40
54
|
debug: args.debug,
|
41
55
|
},
|
@@ -202,13 +202,15 @@ export const GraphQLDocument: React.FC<GraphQLDocumentProps> = ({
|
|
202
202
|
data-testid='graphql-document'
|
203
203
|
>
|
204
204
|
{/* Base code block */}
|
205
|
-
{renderCode
|
206
|
-
|
207
|
-
|
208
|
-
|
205
|
+
{renderCode
|
206
|
+
? (
|
207
|
+
renderCode()
|
208
|
+
)
|
209
|
+
: (
|
210
|
+
<pre className='code-block'>
|
209
211
|
<code>{children}</code>
|
210
|
-
|
211
|
-
|
212
|
+
</pre>
|
213
|
+
)}
|
212
214
|
|
213
215
|
{/* Interactive overlay layer */}
|
214
216
|
{!plain && isReady && (
|
package/src/lib/kit-temp.test.ts
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
import * as fc from 'fast-check'
|
2
2
|
import { describe, expect, test } from 'vitest'
|
3
|
-
import { objFilter, ObjOmit, ObjPick, objPolicyFilter } from './kit-temp.js'
|
3
|
+
import { objFilter, ObjOmit, ObjPick, objPolicyFilter, spreadShallow } from './kit-temp.js'
|
4
4
|
|
5
5
|
describe('objPolicyFilter', () => {
|
6
6
|
const testObj = { a: 1, b: 2, c: 3, d: 4 }
|
@@ -137,3 +137,87 @@ describe('property-based tests', () => {
|
|
137
137
|
)
|
138
138
|
})
|
139
139
|
})
|
140
|
+
|
141
|
+
describe('mergeShallow', () => {
|
142
|
+
test('merges objects while omitting undefined values', () => {
|
143
|
+
const base = { a: 1, b: 2, c: 3 }
|
144
|
+
const override = { b: undefined, c: 4, d: 5 }
|
145
|
+
|
146
|
+
expect(spreadShallow(base, override)).toEqual({ a: 1, b: 2, c: 4, d: 5 })
|
147
|
+
})
|
148
|
+
|
149
|
+
test('handles multiple objects', () => {
|
150
|
+
const obj1 = { a: 1, b: 2 }
|
151
|
+
const obj2 = { b: undefined, c: 3 }
|
152
|
+
const obj3 = { c: undefined, d: 4 }
|
153
|
+
|
154
|
+
expect(spreadShallow(obj1, obj2, obj3)).toEqual({ a: 1, b: 2, c: 3, d: 4 })
|
155
|
+
})
|
156
|
+
|
157
|
+
test('handles empty objects', () => {
|
158
|
+
expect(spreadShallow({}, {})).toEqual({})
|
159
|
+
expect(spreadShallow({ a: 1 }, {})).toEqual({ a: 1 })
|
160
|
+
expect(spreadShallow({}, { a: 1 })).toEqual({ a: 1 })
|
161
|
+
})
|
162
|
+
|
163
|
+
test('handles single object', () => {
|
164
|
+
const obj = { a: 1, b: undefined, c: 3 }
|
165
|
+
expect(spreadShallow(obj)).toEqual({ a: 1, c: 3 })
|
166
|
+
})
|
167
|
+
|
168
|
+
test('handles no objects', () => {
|
169
|
+
expect(spreadShallow()).toEqual({})
|
170
|
+
})
|
171
|
+
|
172
|
+
test('handles undefined objects', () => {
|
173
|
+
const obj = { a: 1, b: 2 }
|
174
|
+
expect(spreadShallow(undefined, obj, undefined)).toEqual({ a: 1, b: 2 })
|
175
|
+
expect(spreadShallow(obj, undefined)).toEqual({ a: 1, b: 2 })
|
176
|
+
expect(spreadShallow(undefined, undefined)).toEqual({})
|
177
|
+
})
|
178
|
+
|
179
|
+
test('preserves null values', () => {
|
180
|
+
const obj1 = { a: 1, b: null }
|
181
|
+
const obj2 = { b: 2, c: null }
|
182
|
+
expect(spreadShallow(obj1, obj2)).toEqual({ a: 1, b: 2, c: null })
|
183
|
+
})
|
184
|
+
|
185
|
+
test('preserves false and 0 values', () => {
|
186
|
+
const obj1 = { a: true, b: 1 }
|
187
|
+
const obj2 = { a: false, b: 0 }
|
188
|
+
expect(spreadShallow(obj1, obj2)).toEqual({ a: false, b: 0 })
|
189
|
+
})
|
190
|
+
|
191
|
+
test('property-based: never includes undefined values', () => {
|
192
|
+
fc.assert(
|
193
|
+
fc.property(
|
194
|
+
fc.array(fc.object({ withUndefined: true })),
|
195
|
+
(objects) => {
|
196
|
+
const result = spreadShallow(...objects)
|
197
|
+
Object.values(result).forEach(value => {
|
198
|
+
expect(value).not.toBe(undefined)
|
199
|
+
})
|
200
|
+
},
|
201
|
+
),
|
202
|
+
)
|
203
|
+
})
|
204
|
+
|
205
|
+
test('property-based: later objects override earlier ones', () => {
|
206
|
+
fc.assert(
|
207
|
+
fc.property(
|
208
|
+
fc.object(),
|
209
|
+
fc.object(),
|
210
|
+
fc.string(),
|
211
|
+
fc.anything({ withUndefined: false }),
|
212
|
+
(obj1, obj2, key, value) => {
|
213
|
+
// Set the same key in both objects
|
214
|
+
obj1[key] = 'first'
|
215
|
+
obj2[key] = value
|
216
|
+
|
217
|
+
const result = spreadShallow(obj1, obj2)
|
218
|
+
expect(result[key]).toBe(value)
|
219
|
+
},
|
220
|
+
),
|
221
|
+
)
|
222
|
+
})
|
223
|
+
})
|
package/src/lib/kit-temp.ts
CHANGED
@@ -592,3 +592,54 @@ export const brand = <$BaseType, $BrandName extends string>(
|
|
592
592
|
): Brand<$BaseType, $BrandName> => {
|
593
593
|
return value as Brand<$BaseType, $BrandName>
|
594
594
|
}
|
595
|
+
|
596
|
+
/**
|
597
|
+
* Shallow merge objects while omitting undefined values.
|
598
|
+
*
|
599
|
+
* This utility simplifies the common pattern of conditionally spreading objects
|
600
|
+
* to avoid including undefined values that would override existing values.
|
601
|
+
*
|
602
|
+
* @param objects - Objects to merge (later objects override earlier ones). Undefined objects are ignored.
|
603
|
+
* @returns Merged object with undefined values omitted
|
604
|
+
*
|
605
|
+
* @example
|
606
|
+
* ```ts
|
607
|
+
* // Instead of:
|
608
|
+
* const overrides = {
|
609
|
+
* ...(value1 ? { key1: value1 } : {}),
|
610
|
+
* ...(value2 ? { key2: value2 } : {}),
|
611
|
+
* }
|
612
|
+
*
|
613
|
+
* // Use:
|
614
|
+
* const overrides = mergeShallow({
|
615
|
+
* key1: value1,
|
616
|
+
* key2: value2,
|
617
|
+
* })
|
618
|
+
*
|
619
|
+
* // Example with config merging:
|
620
|
+
* const config = mergeShallow(
|
621
|
+
* defaultConfig,
|
622
|
+
* userConfig,
|
623
|
+
* { debug: args.debug, base: args.base }
|
624
|
+
* )
|
625
|
+
* // undefined values in the last object won't override earlier values
|
626
|
+
* ```
|
627
|
+
*/
|
628
|
+
export const spreadShallow = <T extends object>(...objects: (T | undefined)[]): T => {
|
629
|
+
const result = {} as T
|
630
|
+
|
631
|
+
for (const obj of objects) {
|
632
|
+
if (obj === undefined) continue
|
633
|
+
|
634
|
+
for (const key in obj) {
|
635
|
+
if (Object.prototype.hasOwnProperty.call(obj, key)) {
|
636
|
+
const value = obj[key]
|
637
|
+
if (value !== undefined) {
|
638
|
+
result[key] = value
|
639
|
+
}
|
640
|
+
}
|
641
|
+
}
|
642
|
+
}
|
643
|
+
|
644
|
+
return result
|
645
|
+
}
|
@@ -41,7 +41,7 @@ export const _getPathsRecurse = (
|
|
41
41
|
if (isIndexRoute(route)) {
|
42
42
|
// Index route uses the parent's accumulated path.
|
43
43
|
// If parentPath is empty (implying root), it should be ROOT_PATH
|
44
|
-
collectedPaths.add(parentPath
|
44
|
+
collectedPaths.add(parentPath ?? ROOT_PATH)
|
45
45
|
|
46
46
|
continue
|
47
47
|
}
|
package/src/project-data.ts
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
import { serve } from '@hono/node-server' // TODO: support non-node platforms.
|
2
2
|
import { neverCase } from '@wollybeard/kit/language'
|
3
|
+
import PROJECT_DATA from 'virtual:polen/project/data.jsonsuper'
|
3
4
|
import { createApp } from './app.js'
|
4
5
|
import { generate } from './ssg/generate.js'
|
5
6
|
import { view } from './view.js'
|
@@ -10,7 +11,7 @@ if (__BUILDING__) {
|
|
10
11
|
await generate(view)
|
11
12
|
break
|
12
13
|
case `ssr`:
|
13
|
-
const port = process.env[`PORT`] ? parseInt(process.env[`PORT`]) :
|
14
|
+
const port = process.env[`PORT`] ? parseInt(process.env[`PORT`]) : PROJECT_DATA.server.port
|
14
15
|
const app = createApp()
|
15
16
|
serve({ fetch: app.fetch, port })
|
16
17
|
break
|