From df1270f8e6add304e8675c990521ef4d6232fa7b Mon Sep 17 00:00:00 2001 From: Cho Young-Hwi Date: Tue, 31 Mar 2026 13:47:30 +0100 Subject: [PATCH] [#670] Add content validation to CLI create and chain commands Mirrors limits from lib/content.ts in the web app: - Title max 60 characters (createStoryline only) - Content min 500 characters - Content max 10,000 characters Uses Unicode-aware character counting ([...str].length) to match the web app's behavior with multi-byte characters. Validation runs before IPFS upload to fail fast with clear errors. Fixes realproject7/plotlink#670 Co-Authored-By: Claude Opus 4.6 (1M context) --- packages/cli/src/sdk/client.ts | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/packages/cli/src/sdk/client.ts b/packages/cli/src/sdk/client.ts index baea96e0..5192df5e 100644 --- a/packages/cli/src/sdk/client.ts +++ b/packages/cli/src/sdk/client.ts @@ -224,6 +224,8 @@ export class PlotLink { validateNonEmpty("title", title); validateNonEmpty("content", content); validateNonEmpty("genre", genre); + validateTitle(title); + validateContentLength(content); const metadata = JSON.stringify({ title, genre, content }); const key = `plotlink/storylines/${Date.now()}-${slugify(title)}.json`; @@ -289,6 +291,7 @@ export class PlotLink { ): Promise { this.requireFilebase(); validateNonEmpty("content", content); + validateContentLength(content); const key = `plotlink/plots/${storylineId}-${Date.now()}.txt`; const contentCid = await uploadWithRetry(content, key, this.filebase!); @@ -667,6 +670,29 @@ function validateNonEmpty(name: string, value: string): void { } } +// Content limits — mirrored from lib/content.ts in the web app +const MAX_TITLE_LENGTH = 60; +const MIN_CONTENT_LENGTH = 500; +const MAX_CONTENT_LENGTH = 10_000; + +function validateTitle(title: string): void { + const charCount = [...title].length; + if (charCount > MAX_TITLE_LENGTH) { + throw new Error( + `Title must be ${MAX_TITLE_LENGTH} characters or less (currently: ${charCount})`, + ); + } +} + +function validateContentLength(content: string): void { + const charCount = [...content].length; + if (charCount < MIN_CONTENT_LENGTH || charCount > MAX_CONTENT_LENGTH) { + throw new Error( + `Content must be between ${MIN_CONTENT_LENGTH} and ${MAX_CONTENT_LENGTH} characters (currently: ${charCount})`, + ); + } +} + /** * Compute keccak256 hash of content, matching the on-chain contentHash. * Same encoding as the web app's hashContent (lib/content.ts).