-
-
Notifications
You must be signed in to change notification settings - Fork 41
refactor: migrate to zod 4 #368
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 8 commits
3fb3ba8
6da0174
882a4da
7f49e0d
11f2012
ad3f1d6
468f391
677bb4f
4d75ae0
aa7c8dd
0bf4f75
d405ef2
5104c7f
eddff30
349bdc1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,7 +1,7 @@ | ||
| import { exec } from 'node:child_process' | ||
| import { promisify } from 'node:util' | ||
| import rehypePrettyCode from 'rehype-pretty-code' | ||
| import { defineCollection, defineConfig, s } from 'velite' | ||
| import { context, defineCollection, defineConfig, s } from 'velite' | ||
|
|
||
| const slugify = (input: string) => | ||
| input | ||
|
|
@@ -26,11 +26,11 @@ const execAsync = promisify(exec) | |
| const timestamp = () => | ||
| s | ||
| .custom<string | undefined>(i => i === undefined || typeof i === 'string') | ||
| .transform<string>(async (value, { meta, addIssue }) => { | ||
| .transform<string>(async (value, { addIssue }) => { | ||
| if (value != null) { | ||
| addIssue({ fatal: false, code: 'custom', message: '`s.timestamp()` schema will resolve the value from `git log -1 --format=%cd`' }) | ||
| } | ||
| const { stdout } = await execAsync(`git log -1 --format=%cd ${meta.path}`) | ||
| const { stdout } = await execAsync(`git log -1 --format=%cd ${context().file.path}`) | ||
| return new Date(stdout || Date.now()).toISOString() | ||
| }) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Potential command injection via unescaped file path. The file path is interpolated directly into a shell command without escaping. If a file path contains shell metacharacters (spaces, quotes, Consider escaping the path or using +import { execFile } from 'node:child_process'
+
+const execFileAsync = promisify(execFile)
+
const timestamp = () =>
s
.custom<string | undefined>(i => i === undefined || typeof i === 'string')
.transform<string>(async (value, { addIssue }) => {
if (value != null) {
addIssue({ fatal: false, code: 'custom', message: '`s.timestamp()` schema will resolve the value from `git log -1 --format=%cd`' })
}
- const { stdout } = await execAsync(`git log -1 --format=%cd ${context().file.path}`)
+ const { stdout } = await execFileAsync('git', ['log', '-1', '--format=%cd', context().file.path])
return new Date(stdout || Date.now()).toISOString()
})
|
||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| import type { NextConfig } from 'next' | ||
| import type { Options as VeliteOptions } from 'velite' | ||
|
|
||
| export type Options = Omit<VeliteOptions, 'watch' | 'clean'> | ||
|
|
||
| type NextConfigFunction = (phase: string, options: { defaultConfig: NextConfig }) => Promise<NextConfig> | NextConfig | ||
| type NextConfigInput = NextConfig | NextConfigFunction | ||
|
|
||
| /** | ||
| * Create a Next.js plugin for integrating Velite | ||
| */ | ||
| declare const createNextPlugin: (options?: Options) => <T extends NextConfigInput>(nextConfig?: T) => Promise<T> | ||
|
|
||
| /** | ||
| * Next.js plugin for integrating Velite | ||
| */ | ||
| declare const withVelite: ReturnType<typeof createNextPlugin> | ||
|
Comment on lines
+12
to
+17
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: cat packages/next/index.js | head -80Repository: zce/velite Length of output: 1673 🏁 Script executed: cat packages/next/index.d.tsRepository: zce/velite Length of output: 722 Correct the type signature to reflect actual behavior. The generic 🤖 Prompt for AI Agents |
||
|
|
||
| export { createNextPlugin, withVelite, type Options } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,50 @@ | ||
| /** | ||
| * Thanks to `https://github.com/sdorra/content-collections` for Next.js v16+ Velite integration. | ||
| */ | ||
|
|
||
| /** | ||
| * Create a Next.js plugin for integrating Velite | ||
| * @param {Omit<import('velite').Options, 'watch' | 'clean'>} pluginOptions | ||
| * @returns {import('next').NextConfig} Next.js plugin | ||
| */ | ||
| export const createNextPlugin = (pluginOptions = {}) => { | ||
| const [command] = process.argv.slice(2).filter(i => !i.startsWith('-')) | ||
|
|
||
| // typegen loads next.config.js | ||
| const isTypegen = command === 'typegen' | ||
|
|
||
| // the build step loads next.config.js | ||
| const isBuild = command === 'build' | ||
|
|
||
| // starting with v16 next dev doesn't load next.config.js | ||
| // next dev - calls next-start in a forked process | ||
| // next-start loads next.config.js | ||
| // process.argv are not visible by next-start | ||
| const isDev = | ||
| // to make this compatible with previous versions | ||
| // check if command is NOT set (next-start) and we are in development mode | ||
| typeof command === 'undefined' && process.env.NODE_ENV === 'development' | ||
|
|
||
| /** | ||
| * @param {import('next').NextConfig} nextConfig | ||
| * @returns {Promise<import('next').NextConfig>} | ||
| */ | ||
| return async (nextConfig = {}) => { | ||
| // prevent multiple calls | ||
| if (process.env.__VELITE_STARTED) return nextConfig | ||
|
|
||
| // if not dev, build, or typegen, return the next config | ||
| if (!isDev && !isBuild && !isTypegen) return nextConfig | ||
|
|
||
| // start velite | ||
| process.env.__VELITE_STARTED = '1' | ||
|
|
||
| const { build } = await import('velite') | ||
|
|
||
| await build({ ...pluginOptions, watch: isDev, clean: !isDev }) | ||
|
|
||
| return nextConfig | ||
| } | ||
| } | ||
|
|
||
| export const withVelite = createNextPlugin() |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| { | ||
| "name": "@velite/plugin-next", | ||
| "version": "0.0.1", | ||
| "description": "Next.js plugin for integrating Velite", | ||
| "type": "module", | ||
| "exports": "./index.js", | ||
| "types": "./index.d.ts", | ||
| "files": [ | ||
| "index.js", | ||
| "index.d.ts" | ||
| ], | ||
| "keywords": [ | ||
| "next-plugin", | ||
| "velite", | ||
| "content" | ||
| ], | ||
| "peerDependencies": { | ||
| "next": "^16.0.0", | ||
| "velite": "workspace:*" | ||
| }, | ||
| "publishConfig": { | ||
| "access": "public" | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| # @velite/plugin-next | ||
|
|
||
| A Next.js plugin for integrating Velite content processing. | ||
|
|
||
| ## Installation | ||
|
|
||
| ```bash | ||
| npm install -D velite @velite/plugin-next | ||
| ``` | ||
|
|
||
| ## Usage | ||
|
|
||
| ```ts | ||
| // next.config.ts | ||
| import { withVelite } from '@velite/plugin-next' | ||
|
|
||
| export default withVelite({ | ||
| // other next config here... | ||
| }) | ||
| ``` | ||
|
|
||
| ## License | ||
|
|
||
| [MIT](../../license) © [zce](https://zce.me) | ||
|
Comment on lines
+1
to
+24
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Verify license link path/casing README content looks good. Please double-check that 🤖 Prompt for AI Agents |
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same command injection risk as in the Vite example.
This
timestamp()helper has the same vulnerability where the file path is passed directly to a shell command without escaping.Apply the same fix using
execFileinstead ofexec:🤖 Prompt for AI Agents