diff --git a/.changeset/node-engine-support.md b/.changeset/node-engine-support.md new file mode 100644 index 00000000..9bb8af57 --- /dev/null +++ b/.changeset/node-engine-support.md @@ -0,0 +1,8 @@ +--- +"@proofkit/cli": patch +"create-proofkit": patch +"@proofkit/fmdapi": patch +"@proofkit/fmodata": patch +--- + +Restrict Node engines to 22, 24, or 26. diff --git a/.changeset/quiet-command-parsing.md b/.changeset/quiet-command-parsing.md new file mode 100644 index 00000000..157ac389 --- /dev/null +++ b/.changeset/quiet-command-parsing.md @@ -0,0 +1,5 @@ +--- +"@proofkit/cli": patch +--- + +Parse generated package-manager commands with shell-style quoting. diff --git a/.github/workflows/check-skills.yml b/.github/workflows/check-skills.yml index 8faa36da..92574df4 100644 --- a/.github/workflows/check-skills.yml +++ b/.github/workflows/check-skills.yml @@ -35,7 +35,7 @@ jobs: - name: Setup Node uses: actions/setup-node@v4 with: - node-version: 20 + node-version: 22 - name: Install intent run: npm install -g @tanstack/intent diff --git a/.github/workflows/validate-skills.yml b/.github/workflows/validate-skills.yml index 8f39716a..61e6eaa5 100644 --- a/.github/workflows/validate-skills.yml +++ b/.github/workflows/validate-skills.yml @@ -23,7 +23,7 @@ jobs: - name: Setup Node uses: actions/setup-node@v4 with: - node-version: 20 + node-version: 22 - name: Install intent CLI run: npm install -g @tanstack/intent diff --git a/package.json b/package.json index 2d70a27a..15223cd2 100644 --- a/package.json +++ b/package.json @@ -46,7 +46,7 @@ }, "packageManager": "pnpm@11.1.1", "engines": { - "node": ">=18" + "node": "^22.0.0 || ^24.0.0 || ^26.0.0" }, "name": "with-changesets", "lint-staged": { diff --git a/packages/cli-old/package.json b/packages/cli-old/package.json index a9da8d36..468a8075 100644 --- a/packages/cli-old/package.json +++ b/packages/cli-old/package.json @@ -35,7 +35,7 @@ "package.json" ], "engines": { - "node": "^20.0.0 || ^22.0.0" + "node": "^22.0.0 || ^24.0.0 || ^26.0.0" }, "scripts": { "typecheck": "tsc", diff --git a/packages/cli/src/cli/init.ts b/packages/cli/src/cli/init.ts index 0d4ceee4..0e8de6de 100644 --- a/packages/cli/src/cli/init.ts +++ b/packages/cli/src/cli/init.ts @@ -4,7 +4,7 @@ import { execa } from "execa"; import fs from "fs-extra"; import type { PackageJson } from "type-fest"; -import { DEFAULT_APP_NAME } from "~/consts.js"; +import { DEFAULT_APP_NAME, NODE_RUNTIME_VERSION } from "~/consts.js"; import { createPnpmWorkspaceFileContent } from "~/core/planInit.js"; import { addAuth } from "~/generators/auth.js"; import { runCodegenCommand } from "~/generators/fmdapi.js"; @@ -20,9 +20,10 @@ import { buildPkgInstallerMap } from "~/installers/index.js"; import { initProgramState, isNonInteractiveMode, state } from "~/state.js"; import { getVersion } from "~/utils/getProofKitVersion.js"; import { getUserPkgManager } from "~/utils/getUserPkgManager.js"; +import { logger } from "~/utils/logger.js"; import { parseNameAndPath } from "~/utils/parseNameAndPath.js"; import { type Settings, setSettings } from "~/utils/parseSettings.js"; -import { formatPackageManagerCommand } from "~/utils/projectFiles.js"; +import { formatPackageManagerCommand, parseCommandString } from "~/utils/projectFiles.js"; import { validateAppName } from "~/utils/validateAppName.js"; import { promptForFileMakerDataSource } from "./add/data-source/filemaker.js"; import { select, text } from "./prompts.js"; @@ -147,8 +148,6 @@ type ProofKitPackageJSON = PackageJson & { }; }; -const NODE_RUNTIME_VERSION = "^24.11.0"; - const missingTypegenCommandPatterns = [ /ERR_PNPM_RECURSIVE_EXEC_FIRST_FAIL[\s\S]*Command\s+["'`]typegen["'`]\s+not found/i, /Command\s+["'`]typegen["'`]\s+not found/i, @@ -432,22 +431,34 @@ export const runInit = async (name?: string, opts?: CliFlags) => { } if (!noInstall) { - const [fixCommand, ...fixArgs] = formatPackageManagerCommand(pkgManager, "fix").split(" "); + const fixCommandString = formatPackageManagerCommand(pkgManager, "fix"); + const [fixCommand, ...fixArgs] = parseCommandString(fixCommandString); if (!fixCommand) { throw new Error(`Unable to resolve fix command for ${pkgManager}.`); } await execa(fixCommand, fixArgs, { cwd: projectDir, stdio: "pipe", - }).catch(() => undefined); + }).catch((error: unknown) => { + if (state.debug) { + logger.warn(`Fix command failed; continuing. packageManager=${pkgManager} command=${fixCommandString}`); + logger.error(error); + } + }); - const [lintCommand, ...lintArgs] = formatPackageManagerCommand(pkgManager, "lint").split(" "); + const lintCommandString = formatPackageManagerCommand(pkgManager, "lint"); + const [lintCommand, ...lintArgs] = parseCommandString(lintCommandString); if (!lintCommand) { throw new Error(`Unable to resolve lint command for ${pkgManager}.`); } await execa(lintCommand, lintArgs, { cwd: projectDir, stdio: "pipe", + }).catch((error: unknown) => { + logger.warn(`Lint did not succeed; continuing setup. packageManager=${pkgManager} command=${lintCommandString}`); + if (state.debug) { + logger.error(error); + } }); } diff --git a/packages/cli/src/consts.ts b/packages/cli/src/consts.ts index fe357c5a..251d02ba 100644 --- a/packages/cli/src/consts.ts +++ b/packages/cli/src/consts.ts @@ -7,6 +7,7 @@ const distPath = path.dirname(__filename); export const PKG_ROOT = process.env.PROOFKIT_PKG_ROOT ?? path.join(distPath, "../"); export const DEFAULT_APP_NAME = "my-proofkit-app"; +export const NODE_RUNTIME_VERSION = "^22.0.0 || ^24.0.0 || ^26.0.0"; export const cliName = "proofkit"; export const npmName = "@proofkit/cli"; export const DOCS_URL = "https://proofkit.proof.sh"; diff --git a/packages/cli/src/core/executeInitPlan.ts b/packages/cli/src/core/executeInitPlan.ts index e02b90a8..8a877119 100644 --- a/packages/cli/src/core/executeInitPlan.ts +++ b/packages/cli/src/core/executeInitPlan.ts @@ -24,6 +24,7 @@ import { getBrowserOxlintConfig, getUltraciteInitCommand } from "~/helpers/ultra import { formatPackageManagerCommand, normalizeImportAlias, + parseCommandString, replaceTextInFiles, updateTypegenConfig, } from "~/utils/projectFiles.js"; @@ -86,7 +87,7 @@ function renderNextSteps(plan: InitPlan, additionalSteps: string[] = []) { } function getPackageScriptCommand(plan: InitPlan, scriptName: string) { - const [command, ...args] = formatPackageManagerCommand(plan.request.packageManager, scriptName).split(" "); + const [command, ...args] = parseCommandString(formatPackageManagerCommand(plan.request.packageManager, scriptName)); if (!command) { throw new Error(`Unable to resolve ${scriptName} command for ${plan.request.packageManager}.`); } @@ -436,6 +437,8 @@ export const executeInitPlan = (plan: InitPlan) => yield* codegenService.runInitial(plan.targetDir, plan.request.packageManager); } + // plan.tasks.runFix is non-blocking: getPackageScriptCommand/processService.run can fail on fresh scaffolds. + // Effect.either also catches lint failures below and logs warnings; other errors still propagate. if (plan.tasks.runFix) { const fixCommand = getPackageScriptCommand(plan, "fix"); yield* Effect.either( @@ -448,16 +451,16 @@ export const executeInitPlan = (plan: InitPlan) => } if (plan.tasks.runLint) { - const fixCommand = getPackageScriptCommand(plan, "fix"); + const lintCommand = getPackageScriptCommand(plan, "lint"); const result = yield* Effect.either( - processService.run(fixCommand.command, fixCommand.args, { + processService.run(lintCommand.command, lintCommand.args, { cwd: plan.targetDir, stdout: "pipe", stderr: "pipe", }), ); if (result._tag === "Left") { - consoleService.warn("Lint fix did not succeed; continuing setup."); + consoleService.warn("Lint did not succeed; continuing setup."); } } diff --git a/packages/cli/src/core/planInit.ts b/packages/cli/src/core/planInit.ts index f69fb5a7..25b27553 100644 --- a/packages/cli/src/core/planInit.ts +++ b/packages/cli/src/core/planInit.ts @@ -1,6 +1,7 @@ import path from "node:path"; import type { PackageJson } from "type-fest"; +import { NODE_RUNTIME_VERSION } from "~/consts.js"; import type { InitPlan, InitRequest, ProofKitSettings } from "~/core/types.js"; import { getFmdapiVersion, @@ -25,7 +26,6 @@ const SHARED_PNPM_BUILD_POLICY = { } as const; const NPM_PACKAGE_MANAGER_WARNING = "Warning: We strongly suggest using PNPM 11 or greater as your package manager to better protect your computer and your app."; -const NODE_RUNTIME_VERSION = "^24.11.0"; const NPM_MIN_RELEASE_AGE_DAYS = 1; export function createPnpmWorkspaceFileContent(appType: InitRequest["appType"]) { diff --git a/packages/cli/src/helpers/intent.ts b/packages/cli/src/helpers/intent.ts index ce8a9588..83c6a139 100644 --- a/packages/cli/src/helpers/intent.ts +++ b/packages/cli/src/helpers/intent.ts @@ -1,8 +1,8 @@ import type { PackageManager } from "~/utils/packageManager.js"; -import { getTemplatePackageExecuteCommand } from "~/utils/projectFiles.js"; +import { getTemplatePackageExecuteCommand, parseCommandString } from "~/utils/projectFiles.js"; function splitExecuteCommand(packageManager: PackageManager) { - const [command, ...args] = getTemplatePackageExecuteCommand(packageManager).split(" "); + const [command, ...args] = parseCommandString(getTemplatePackageExecuteCommand(packageManager)); if (!command) { throw new Error(`Unable to resolve package execute command for ${packageManager}.`); } diff --git a/packages/cli/src/helpers/ultracite.ts b/packages/cli/src/helpers/ultracite.ts index 6ee96ea7..8a15406c 100644 --- a/packages/cli/src/helpers/ultracite.ts +++ b/packages/cli/src/helpers/ultracite.ts @@ -1,6 +1,6 @@ import type { AppType } from "~/core/types.js"; import type { PackageManager } from "~/utils/packageManager.js"; -import { getTemplatePackageExecuteCommand } from "~/utils/projectFiles.js"; +import { getTemplatePackageExecuteCommand, parseCommandString } from "~/utils/projectFiles.js"; const ULTRACITE_EDITORS = ["universal", "cursor"] as const; const ULTRACITE_AGENTS = ["universal", "claude", "codex"] as const; @@ -8,7 +8,7 @@ const ULTRACITE_HOOKS = ["cursor", "windsurf", "codebuddy", "claude"] as const; const ULTRACITE_INTEGRATIONS = ["husky", "lint-staged"] as const; function splitExecuteCommand(packageManager: PackageManager) { - const [command, ...args] = getTemplatePackageExecuteCommand(packageManager).split(" "); + const [command, ...args] = parseCommandString(getTemplatePackageExecuteCommand(packageManager)); if (!command) { throw new Error(`Unable to resolve package execute command for ${packageManager}.`); } diff --git a/packages/cli/src/utils/projectFiles.ts b/packages/cli/src/utils/projectFiles.ts index d5484d03..465c14d0 100644 --- a/packages/cli/src/utils/projectFiles.ts +++ b/packages/cli/src/utils/projectFiles.ts @@ -7,6 +7,7 @@ import type { PackageManager } from "~/utils/packageManager.js"; const commonFileMakerLayoutPrefixes = ["API_", "API ", "dapi_", "dapi"]; const TRAILING_SLASH_REGEX = /[^/]$/; +const WHITESPACE_REGEX = /\s/; const DEFAULT_FM_MCP_BASE_URL = "http://127.0.0.1:1365"; const textFileExtensions = new Set([ ".ts", @@ -54,6 +55,60 @@ export function formatPackageManagerCommand(packageManager: PackageManager, comm return ["npm", "bun"].includes(packageManager) ? `${packageManager} run ${command}` : `${packageManager} ${command}`; } +export function parseCommandString(command: string): string[] { + const tokens: string[] = []; + let current = ""; + let quote: "'" | '"' | undefined; + let escaping = false; + + for (const char of command) { + if (escaping) { + current += char; + escaping = false; + continue; + } + + if (char === "\\") { + escaping = true; + continue; + } + + if (quote) { + if (char === quote) { + quote = undefined; + } else { + current += char; + } + continue; + } + + if (char === "'" || char === '"') { + quote = char; + continue; + } + + if (WHITESPACE_REGEX.test(char)) { + if (current) { + tokens.push(current); + current = ""; + } + continue; + } + + current += char; + } + + if (escaping) { + current += "\\"; + } + + if (current) { + tokens.push(current); + } + + return tokens; +} + export function getTemplatePackageCommand(packageManager: PackageManager) { if (packageManager === "npm") { return "npm run"; diff --git a/packages/cli/template/vite-wv/package.json b/packages/cli/template/vite-wv/package.json index 859ba883..f0d83da9 100644 --- a/packages/cli/template/vite-wv/package.json +++ b/packages/cli/template/vite-wv/package.json @@ -3,6 +3,9 @@ "version": "0.0.0", "private": true, "type": "module", + "engines": { + "node": "^22.0.0 || ^24.0.0 || ^26.0.0" + }, "scripts": { "build": "vite build", "build:upload": "__PNPM_COMMAND__ build && __PNPM_COMMAND__ upload", diff --git a/packages/cli/template/vite-wv/src/routes/query-demo.tsx b/packages/cli/template/vite-wv/src/routes/query-demo.tsx index e9dcd08d..785c36e4 100644 --- a/packages/cli/template/vite-wv/src/routes/query-demo.tsx +++ b/packages/cli/template/vite-wv/src/routes/query-demo.tsx @@ -7,7 +7,7 @@ const getConnectionHint = (): string => export const QueryDemoPage = () => { const hintQuery = useQuery({ queryFn: getConnectionHint, - queryKey: ["starter-connection-hint"], + queryKey: ["starter-connection-hint"] as const, }); return ( diff --git a/packages/cli/tests/executor.test.ts b/packages/cli/tests/executor.test.ts index 43db6dde..b0ca09bc 100644 --- a/packages/cli/tests/executor.test.ts +++ b/packages/cli/tests/executor.test.ts @@ -69,7 +69,7 @@ describe("executeInitPlan command paths", () => { ].join(" "), "pnpx @tanstack/intent@latest install", "pnpm fix", - "pnpm fix", + "pnpm lint", ]); expect(tracker.filemakerBootstraps).toBe(1); expect(tracker.codegens).toBe(1); @@ -125,7 +125,7 @@ describe("executeInitPlan command paths", () => { expect(npmrcFile).toContain("min-release-age=1"); }); - it("warns and continues when final lint fix fails", async () => { + it("warns and continues when final lint fails", async () => { const cwd = await fs.mkdtemp(path.join(os.tmpdir(), "proofkit-lint-fix-warn-")); const console = { error: [] as string[], @@ -161,13 +161,13 @@ describe("executeInitPlan command paths", () => { makeTestLayer({ console, cwd, - failProcessCommand: "pnpm fix", + failProcessCommand: "pnpm lint", failures: { processRun: new ExternalCommandError({ - args: ["fix"], + args: ["lint"], command: "pnpm", cwd, - message: "fix failed", + message: "lint failed", }), }, packageManager: "pnpm", @@ -177,7 +177,8 @@ describe("executeInitPlan command paths", () => { ); expect(tracker.commands).toContain("pnpm fix"); - expect(console.warn).toContain("Lint fix did not succeed; continuing setup."); + expect(tracker.commands).toContain("pnpm lint"); + expect(console.warn).toContain("Lint did not succeed; continuing setup."); }); it("supports force overwrite for an existing directory", async () => { diff --git a/packages/cli/tests/init-run-init-regression.test.ts b/packages/cli/tests/init-run-init-regression.test.ts index 3342d2ff..09502384 100644 --- a/packages/cli/tests/init-run-init-regression.test.ts +++ b/packages/cli/tests/init-run-init-regression.test.ts @@ -200,6 +200,7 @@ describe("runInit browser post-init typegen regression", () => { it("writes pnpm build policy before install for pnpm 10", async () => { mockState.appType = "webviewer"; + execaMock.mockResolvedValue({ stdout: "10.0.0" }); await expect( runInit("demo-webviewer", { @@ -214,6 +215,19 @@ describe("runInit browser post-init typegen regression", () => { expect.stringContaining(' "sharp": false'), "utf8", ); + const workspaceWriteCallIndex = writeFileSyncMock.mock.calls.findIndex(([filePath]) => + String(filePath).endsWith("pnpm-workspace.yaml"), + ); + const firstPnpmScriptCallIndex = execaMock.mock.calls.findIndex( + ([command, args]) => command === "pnpm" && Array.isArray(args) && args[0] !== "-v", + ); + expect(workspaceWriteCallIndex).not.toBe(-1); + expect(firstPnpmScriptCallIndex).not.toBe(-1); + const workspaceWriteOrder = writeFileSyncMock.mock.invocationCallOrder[workspaceWriteCallIndex]; + const firstPnpmScriptOrder = execaMock.mock.invocationCallOrder[firstPnpmScriptCallIndex]; + expect(workspaceWriteOrder).toBeDefined(); + expect(firstPnpmScriptOrder).toBeDefined(); + expect(workspaceWriteOrder as number).toBeLessThan(firstPnpmScriptOrder as number); expect(execaMock).toHaveBeenCalledWith("pnpm", ["fix"], { cwd: "/tmp/proofkit-regression/demo-browser", stdio: "pipe", diff --git a/packages/cli/tests/init-scaffold-contract.test.ts b/packages/cli/tests/init-scaffold-contract.test.ts index 44a71aba..f1bfc070 100644 --- a/packages/cli/tests/init-scaffold-contract.test.ts +++ b/packages/cli/tests/init-scaffold-contract.test.ts @@ -3,6 +3,7 @@ import { existsSync, mkdirSync, readdirSync, readFileSync, rmSync } from "node:f import { join } from "node:path"; import { parse as parseJsonc } from "jsonc-parser/lib/esm/main.js"; import { beforeEach, describe, expect, it } from "vitest"; +import { NODE_RUNTIME_VERSION } from "~/consts.js"; interface PackageJsonShape { version?: string; @@ -182,10 +183,10 @@ describe("Init scaffold contract tests", () => { expect(packageJson.devEngines?.packageManager?.onFail).toBe("download"); expect(packageJson.devEngines?.runtime).toEqual({ name: "node", - version: "^24.11.0", + version: NODE_RUNTIME_VERSION, onFail: "download", }); - expect(packageJson.engines?.node).toBe("^24.11.0"); + expect(packageJson.engines?.node).toBe(NODE_RUNTIME_VERSION); expect(allProofkitDependenciesUseCurrentVersions(packageJson)).toBe(true); expect(readFileSync(join(browserProjectDir, "CLAUDE.md"), "utf-8")).toBe("@AGENTS.md\n"); expect(readFileSync(join(browserProjectDir, ".cursorignore"), "utf-8")).toBe("CLAUDE.md\n"); @@ -245,10 +246,10 @@ describe("Init scaffold contract tests", () => { expect(packageJson.devEngines?.packageManager?.onFail).toBe("download"); expect(packageJson.devEngines?.runtime).toEqual({ name: "node", - version: "^24.11.0", + version: NODE_RUNTIME_VERSION, onFail: "download", }); - expect(packageJson.engines?.node).toBe("^24.11.0"); + expect(packageJson.engines?.node).toBe(NODE_RUNTIME_VERSION); expect(allProofkitDependenciesUseCurrentVersions(packageJson)).toBe(true); expect(readFileSync(join(webviewerProjectDir, "CLAUDE.md"), "utf-8")).toBe("@AGENTS.md\n"); expect(readFileSync(join(webviewerProjectDir, ".cursorignore"), "utf-8")).toBe("CLAUDE.md\n"); diff --git a/packages/cli/tests/integration.test.ts b/packages/cli/tests/integration.test.ts index bf47bf85..3bdcc4e5 100644 --- a/packages/cli/tests/integration.test.ts +++ b/packages/cli/tests/integration.test.ts @@ -3,6 +3,7 @@ import path from "node:path"; import { Effect } from "effect"; import fs from "fs-extra"; import { describe, expect, it } from "vitest"; +import { NODE_RUNTIME_VERSION } from "~/consts.js"; import { executeInitPlan } from "~/core/executeInitPlan.js"; import { planInit } from "~/core/planInit.js"; import { getProofkitDependencyVersion, getTypegenVersion, getVersion } from "~/utils/getProofKitVersion.js"; @@ -74,11 +75,11 @@ describe("integration scaffold generation", () => { }); expect(packageJson.devEngines?.runtime).toEqual({ name: "node", - version: "^24.11.0", + version: NODE_RUNTIME_VERSION, onFail: "download", }); expect(packageJson.engines).toEqual({ - node: "^24.11.0", + node: NODE_RUNTIME_VERSION, }); expect(packageJson.proofkitMetadata).toMatchObject({ scaffoldPackage: "@proofkit/cli", diff --git a/packages/cli/tests/planner.test.ts b/packages/cli/tests/planner.test.ts index a0c5c47d..558b60fa 100644 --- a/packages/cli/tests/planner.test.ts +++ b/packages/cli/tests/planner.test.ts @@ -1,5 +1,6 @@ import path from "node:path"; import { describe, expect, it } from "vitest"; +import { NODE_RUNTIME_VERSION } from "~/consts.js"; import { planInit } from "~/core/planInit.js"; import { getFmdapiVersion, @@ -33,11 +34,11 @@ describe("planInit", () => { }); expect(plan.packageJson.devEngines?.runtime).toEqual({ name: "node", - version: "^24.11.0", + version: NODE_RUNTIME_VERSION, onFail: "download", }); expect(plan.packageJson.engines).toEqual({ - node: "^24.11.0", + node: NODE_RUNTIME_VERSION, }); expect(plan.settings.appType).toBe("browser"); expect(plan.packageJson.devDependencies["@proofkit/cli"]).toBe(proofkitCliVersion); diff --git a/packages/cli/tests/test-layer.ts b/packages/cli/tests/test-layer.ts index f848b0f2..e3d7d42b 100644 --- a/packages/cli/tests/test-layer.ts +++ b/packages/cli/tests/test-layer.ts @@ -348,11 +348,15 @@ export function makeTestLayer(options: { run: (command: string, args: string[]) => { const processCommand = [command, ...args].join(" "); tracker?.commands.push(processCommand); + const processRunFailure = options.failures?.processRun; if (options.failProcessCommand === processCommand) { - return Effect.fail(options.failures?.processRun as ExternalCommandError); + if (!processRunFailure) { + throw new Error("makeTestLayer requires failures.processRun when failProcessCommand is set."); + } + return Effect.fail(processRunFailure as ExternalCommandError); } - if (!options.failProcessCommand && options.failures?.processRun) { - return Effect.fail(options.failures.processRun as ExternalCommandError); + if (!options.failProcessCommand && processRunFailure) { + return Effect.fail(processRunFailure as ExternalCommandError); } return Effect.succeed({ stdout: "", stderr: "" }); }, diff --git a/packages/create-proofkit/package.json b/packages/create-proofkit/package.json index 688c2aef..4fec33e5 100644 --- a/packages/create-proofkit/package.json +++ b/packages/create-proofkit/package.json @@ -27,7 +27,7 @@ "vitest": "^4.0.17" }, "engines": { - "node": ">=20.12.0" + "node": "^22.0.0 || ^24.0.0 || ^26.0.0" }, "publishConfig": { "access": "public" diff --git a/packages/fmdapi/package.json b/packages/fmdapi/package.json index 03d1417f..f0720a05 100644 --- a/packages/fmdapi/package.json +++ b/packages/fmdapi/package.json @@ -80,7 +80,7 @@ "vitest": "^4.0.17" }, "engines": { - "node": ">=18.0.0" + "node": "^22.0.0 || ^24.0.0 || ^26.0.0" }, "files": [ "src", diff --git a/packages/fmodata/package.json b/packages/fmodata/package.json index 32a2493a..d3b24e11 100644 --- a/packages/fmodata/package.json +++ b/packages/fmodata/package.json @@ -93,7 +93,7 @@ "zod": "^4.3.5" }, "engines": { - "node": ">=18.0.0" + "node": "^22.0.0 || ^24.0.0 || ^26.0.0" }, "files": [ "src",